Skip to content

Instantly share code, notes, and snippets.

@Xkeeper0
Created June 2, 2016 23:21
Show Gist options
  • Save Xkeeper0/3e9125cd5a7bb368700407ac1fe2a7d3 to your computer and use it in GitHub Desktop.
Save Xkeeper0/3e9125cd5a7bb368700407ac1fe2a7d3 to your computer and use it in GitHub Desktop.
binary-to-PNG art farts
<?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);
}
print
"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