Created
June 2, 2016 23:21
-
-
Save Xkeeper0/3e9125cd5a7bb368700407ac1fe2a7d3 to your computer and use it in GitHub Desktop.
binary-to-PNG art farts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// See https://twitter.com/xkeepah/status/738508123489468416 | |
// Encode file. First arg = file, second arg = PNG to use as a mask for artfart | |
$image = encodeFile("Gimmick! (Japan).nes", "gimmick-title.png"); | |
imagepng($image, "/tmp/test.png"); | |
// Filename to decode from PNG. art fart alpha not used | |
$data = decodeFile("/tmp/test.png"); | |
file_put_contents("/tmp/test.bin", $data); | |
function decodeFile($file) { | |
$image = imagecreatefrompng($file); | |
$imageSX = imagesx($image); | |
$imageSY = imagesy($image); | |
$data = ""; | |
for ($y = 0; $y < $imageSY; $y++) { | |
for ($x = 0; $x < $imageSX; $x++) { | |
$pixelV = imagecolorat($image, $x, $y); | |
// GD PNG support seems to store alpha as 7-bits instead of 8 so, welp | |
//$pixelVA = ($pixelV >> 24) & 0xFF; | |
$pixelVR = ($pixelV >> 16) & 0xFF; | |
$pixelVG = ($pixelV >> 8) & 0xFF; | |
$pixelVB = ($pixelV >> 0) & 0xFF; | |
//$data .= pack("CCCC", $pixelVB, $pixelVG, $pixelVR, $pixelVA); | |
$data .= pack("CCC", $pixelVB, $pixelVG, $pixelVR); | |
} | |
} | |
$dataLenA = unpack("Vsize", substr($data, 0, 4)); | |
$dataLen = $dataLenA['size']; | |
print "Extracted data length: $dataLen\n"; | |
$realData = substr($data, 4, $dataLen); | |
return $realData; | |
} | |
function encodeFile($file, $alphaMask = null) { | |
$data = file_get_contents($file); | |
// Size of data to encode | |
$size = strlen($data); | |
print "Encoding data length: $size\n"; | |
// Add the data size to the file | |
$sizeE = pack("V", $size); | |
$wdata = $sizeE . $data; | |
// Now the actual data size to encode | |
$size = strlen($data); | |
// Determine size of (square) image | |
// Divide by three (24BPP), square root (square image), ceil (round up) | |
$imageSize = ceil(sqrt($size / 3)); | |
// Pad our data to that length | |
$fullSize = pow($imageSize * 3, 2); | |
$imageData = str_pad($wdata, $fullSize, "\0", STR_PAD_RIGHT); | |
$len = strlen($imageData); | |
print "Encoding full length: $size - $fullSize - $len\n"; | |
$imageXC = $imageSize / 2; | |
$imageYC = $imageSize / 2; | |
$imageMD = sqrt( pow(0 - $imageXC, 2) + pow(0 - $imageYC, 2) ); | |
print "V: $imageMD\n"; | |
// Create PNG | |
$image = imagecreatetruecolor($imageSize, $imageSize); | |
imagesavealpha($image, true); | |
imagealphablending($image, false); | |
if ($alphaMask) { | |
$imageMask = new AlphaMaskImage($alphaMask, $imageSize, $imageSize); | |
} | |
for ($pos = 0; $pos < $len; $pos += 3) { | |
$pixelX = ($pos / 3) % $imageSize; | |
$pixelY = floor(($pos / 3) / $imageSize); | |
$pixelP = sqrt( pow($pixelX - $imageXC, 2) + pow($pixelY - $imageYC, 2) ); | |
#$pixelA = floor(($pixelP / $imageMD * 0x7F) % 0x7F); | |
$pixelA = floor(sqrt( pow($pixelX - $imageXC, 2) + pow($pixelY - $imageYC, 2) ) % 0x7F); | |
if ($alphaMask) { | |
$pixelA = $imageMask->getMaskValue($pixelX, $pixelY); | |
} | |
$pixelV = ($pixelA << 24) + | |
(ord($imageData{$pos + 2}) << 16) + | |
(ord($imageData{$pos + 1}) << 8) + | |
(ord($imageData{$pos + 0})); | |
imagesetpixel($image, $pixelX, $pixelY, $pixelV); | |
} | |
return $image; | |
} | |
class AlphaMaskImage { | |
protected $image = null; | |
protected $size = array('x' => 0, 'y' => 0); | |
protected $source = array('x' => 0, 'y' => 0); | |
protected $padding = array('x' => 0, 'y' => 0); | |
protected $scale = 0; | |
public function __construct($filename, $sx, $sy) { | |
$this->image = imagecreatefrompng($filename); | |
imagefilter($this->image, IMG_FILTER_GRAYSCALE); | |
$this->size = array('x' => imagesx($this->image), 'y' => imagesy($this->image)); | |
$this->source = array('x' => $sx, 'y' => $sy); | |
$ratioX = $this->size['x'] / $sx; | |
$ratioY = $this->size['y'] / $sy; | |
if ($ratioX < $ratioY) { | |
// X is larger, so X needs padding | |
$this->scale = $ratioY; | |
// Padding = ((source X) - (scaled mask X)) / 2 | |
$this->padding['x'] = floor(($sx - ($this->size['x'] / $this->scale)) / 2); | |
} else { | |
// Y is larger, so Y needs padding | |
$this->scale = $ratioX; | |
// Padding = ((source X) - (scaled mask X)) / 2 | |
$this->padding['y'] = floor(($sy - ($this->size['y'] / $this->scale)) / 2); | |
} | |
"Image X/Y: ". $sx .", ". $sy ."\n". | |
"Mask X/Y: ". $this->size['x'] .", ". $this->size['y'] ."\n". | |
"Scaled X/Y: ". ($sx * $this->scale) .", ". ($sy * $this->scale) ."\n". | |
"Ratio X/Y: ". $ratioX .", ". $ratioY ."\n". | |
"Padding X/Y: ". $this->padding['x'] .", ". $this->padding['y'] ."\n". | |
"Scale factor: ". $this->scale ."\n". | |
""; | |
#die(); | |
} | |
public function getMaskValue($x, $y) { | |
#print "Asked to mask pixel at $x, $y ... "; | |
if ( | |
(min($x, $this->source['x'] - $x) <= $this->padding['x']) || | |
(min($y, $this->source['y'] - $y) <= $this->padding['y']) | |
) { | |
// Padding range, return max value | |
#print "padded.\n"; | |
return 0x00; | |
} | |
$maskX = floor(($x - $this->padding['x']) * $this->scale); | |
$maskY = floor(($y - $this->padding['y']) * $this->scale); | |
$color = imagecolorat($this->image, $maskX, $maskY) & 0xFF; | |
#print "Mask source $maskX, $maskY ... result is $color\n"; | |
$color = ceil(($color / 0xFF) * 0x7F); | |
#die(); | |
return $color; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment