<?php
set_time_limit(90);
function isImageType($type) {
global $imageType;
return (strpos($imageType, $type) !== false);
}
// WIDTH HEIGHT TYPE
$imageTypes = array(
256 => array(
512 => 'tilesets_a5_vx',
),
288 => array(
256 => 'characters_dsplus',
),
384 => array(
192 => 'facesets_vx',
256 => 'characters_vx',
384 => 'icons_vx',
768 => 'tilesets_a5_mv_stylize',
936 => 'icons_ace',
),
512 => array(
256 => 'tilesets_a3_vx',
384 => 'tilesets_a1a2_vx',
480 => 'tilesets_a4_vx',
512 => 'tilesets_bcde_vx',
),
576 => array(
288 => 'facesets_mv_stylize',
384 => 'characters_mv_stylize',
),
768 => array(
384 => 'tilesets_a3_mv_stylize',
576 => 'tilesets_a1a2_mv_stylize',
720 => 'tilesets_a4_mv_stylize',
768 => 'tilesets_bcde_mv_stylize',
),
);
if(!isset($sessionId)) {
$sessionId = strtolower(base_convert(intval(round(microtime(true) * 1000) . rand(1, 100000)), 10, 36) . str_replace("php", "", pathinfo($_FILES['file']['tmp_name'], PATHINFO_FILENAME)));
}
$inputFileName = $sessionId . '.png';
if(!isset($inputFilePath)) {
$inputFileDir = '/home/kickoptr/public_html/public/mvfu/';
$inputFilePath = $inputFileDir . $inputFileName;
}
$outputFilePath = '/home/kickoptr/public_html/public/mvfu/output/' . $inputFileName;
$tempDir = '/home/kickoptr/public_html/public/tmp/' . pathinfo($inputFileName, PATHINFO_FILENAME) . '/';
if(!file_exists($tempDir)) {
mkdir($tempDir, 0775, true);
}
// Add a border between every sprite (if padding is on) before Waifu processing
$borderSize = 2;
$startTime = time();
$srcSize = getimagesize($inputFilePath);
$srcWidth = $srcSize[0];
$srcHeight = $srcSize[1];
if(isset($imageTypes[$srcWidth][$srcHeight])) {
$imageType = $imageTypes[$srcWidth][$srcHeight];
} else {
// We can force character sheets with a flag
if($character == true) {
$imageType = 'characters_forced';
}
// files with dollars or bangs are always treated as characters!
if($bang == true) {
if(isImageType('character')) {
$imageType .= '_bang';
} else $imageType = 'characters_bang';
}
if($dollar == true) {
if(isImageType('character')) {
$imageType .= '_dollar';
} else $imageType = 'characters_dollar';
}
// If it's not a character by this point, error
if(isImageType('character') == false) {
$error = 'Incompatible file (not supported yet?)';
removeDirectory($tempDir);
return $error;
}
}
$destWidth = $srcWidth;
$destHeight = $srcHeight;
if(isImageType('stylize')) {
$destWidth = intval($srcWidth * 2/3);
$destHeight = intval($srcHeight * 2/3);
}
// Load src image
$srcImage=imagecreatetruecolor($destWidth, $destHeight);
$alpha = imagecolorallocatealpha($srcImage, 0, 0, 0, 127);
imagefill($srcImage, 0, 0, $alpha);
imagesavealpha($srcImage, true);
$loadedImage=imagecreatefrompng($inputFilePath);
imagepalettetotruecolor($loadedImage);
imagesavealpha($loadedImage, true);
imagecopyresampled($srcImage, $loadedImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight);
if(isImageType('stylize')) {
$srcWidth = $destWidth;
$srcHeight = $destHeight;
}
$waifuScaleFactor = 0.75;
$finalScaleFactor = 1.5;
if(isImageType('character')) {
if($dollar == true) { // Dollar means it's just a single character
$tilesX = 4;
$tilesY = 1;
} else {
$tilesX = 12;
$tilesY = 8;
}
$padding = true;
$borderSize = 2;
}
if(isImageType('icons')) {
$tilesX = $srcWidth / 24;
$tilesY = $srcHeight / 24;
$padding = true;
$borderSize = 4;
$waifuScaleFactor = 1 / 1.5;
$finalScaleFactor = 32 / 24;
}
if(isImageType('faceset')) {
$tilesX = 4;
$tilesY = 2;
$padding = true;
$borderSize = 8;
}
if(isImageType('tileset')) {
$tilesX = $srcWidth / 32;
$tilesY = $srcHeight / 32;
$padding = false;
}
$tileWidth = $srcWidth / $tilesX;
$tileHeight = $srcHeight / $tilesY;
if($padding == true) {
$destImage = imagecreatetruecolor($srcWidth + (($tilesX + 1) * $borderSize), $srcHeight + (($tilesY + 1) * $borderSize));
// Transparent!
$alpha = imagecolorallocatealpha($destImage, 0, 0, 0, 127);
imagefill($destImage, 0, 0, $alpha);
imagesavealpha($destImage, true);
for($w = 0; $w < ($srcWidth / $tileWidth); $w++) {
for($h = 0; $h < ($srcHeight / $tileHeight); $h++) {
$x = $w * $tileWidth;
$y = $h * $tileHeight;
imagecopy($destImage, $srcImage,
$x + ($w * $borderSize) + $borderSize,
$y + ($h * $borderSize) + $borderSize, $x, $y, $tileWidth, $tileHeight);
}
}
} else {
$destImage = imagecreatetruecolor($srcWidth, $srcHeight);
$alpha = imagecolorallocatealpha($destImage, 0, 0, 0, 127);
imagefill($destImage, 0, 0, $alpha);
imagesavealpha($destImage, true);
imagecopy($destImage, $srcImage, 0, 0, 0, 0, $srcWidth, $srcHeight);
}
imagepng($destImage, $inputFilePath . '.padded');
$data = array(
'url' => 'http://www.kickbackgames.com/public/mvfu/' . $inputFileName . '.padded',
'noise' => 0,
'scale' => 2);
$opts = array('http' =>
array(
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query($data)
)
);
// Get Waifu'd PNG
$result = @file_get_contents('http://waifu2x.udp.jp/api', false, stream_context_create($opts));
if($result === false) {
error_log('waifu error');
$error = 'Error invoking waifu';
removeDirectory($tempDir);
return $error;
}
if(file_exists($inputFilePath . '.padded')) {
unlink($inputFilePath . '.padded');
}
$waifuImage = imagecreatefromstring($result);
imagealphablending($waifuImage, false);
imagesavealpha($waifuImage, true);
$waifuWidth = imagesx($waifuImage);
$waifuHeight = imagesy($waifuImage);
ob_start();
imagepng($waifuImage);
$waifuString = ob_get_clean();
// Reduce the image with filtering
$waifu = new Imagick();
$waifu->readImageBlob($waifuString);
$waifu->resizeImage($waifuWidth * $waifuScaleFactor, $waifuHeight * $waifuScaleFactor, Imagick::FILTER_LANCZOS, 1);
$waifu->setImageFormat('png32');
$destString = $waifu->getImageBlob();
$waifuImage = imagecreatefromstring($destString);
imagesavealpha($waifuImage, true);
$waifuWidth = $waifuWidth * $waifuScaleFactor;
$waifuHeight = $waifuHeight * $waifuScaleFactor;
$destWidth = $srcWidth * $finalScaleFactor;
$destHeight = $srcHeight * $finalScaleFactor;
$tileWidth = $destWidth / $tilesX;
$tileHeight = $destHeight / $tilesY;
$borderSize = $borderSize * $finalScaleFactor;
$destImage = imagecreatetruecolor($destWidth, $destHeight);
$alpha = imagecolorallocatealpha($destImage, 0, 0, 0, 127);
imagefill($destImage, 0, 0, $alpha);
imagealphablending($destImage, false);
imagesavealpha($destImage, true);
if($padding == true) {
for($w = 0; $w < ($destWidth / $tileWidth); $w++) {
for($h = 0; $h < ($destHeight / $tileHeight); $h++) {
$x = $w * $tileWidth;
$y = $h * $tileHeight;
imagecopy($destImage, $waifuImage,
$x, $y, $x + ($w * $borderSize) + $borderSize,
$y + ($h * $borderSize) + $borderSize, $tileWidth, $tileHeight);
}
}
} else {
imagecopy($destImage, $waifuImage, 0, 0, 0, 0, $waifuWidth, $waifuHeight);
}
// Generate the alpha channel
ob_start();
imagepng($srcImage);
$srcString = ob_get_clean();
if(strpos($imageType, 'tilesets') !== false) { // For tilesets, use a special method
$nn = new Imagick(); // Nearest-neighbor scaled image
$nn->readImageBlob($srcString);
$nn->resizeImage($srcWidth * $finalScaleFactor, $srcHeight * $finalScaleFactor, Imagick::FILTER_POINT, 1);
$nn->setImageFormat('png32');
$nnString = $nn->getImageBlob();
$nnImage = imagecreatefromstring($nnString);
imagesavealpha($nnImage, true);
imagealphablending($nnImage, false);
$rs = new Imagick(); // Bicubic (Lanczos) scaled image
$rs->readImageBlob($srcString);
$rs->resizeImage($srcWidth * $finalScaleFactor, $srcHeight * $finalScaleFactor, Imagick::FILTER_LANCZOS, 1);
$rs->setImageFormat('png32');
$rs->setImageMatte(true);
$resampledString = $rs->getImageBlob();
$resampledImage = imagecreatefromstring($resampledString);
imagesavealpha($resampledImage, true);
imagealphablending($resampledImage, false);
$maskFile = '';
switch($imageType) {
case 'tilesets_a1a2_vx': $maskFile = 'assets/512_384_auto_mask.png'; break;
case 'tilesets_a3_vx': $maskFile = 'assets/768_384_auto_mask.png'; break;
case 'tilesets_a4_vx': $maskFile = 'assets/768_720_auto_mask.png'; break;
case 'tilesets_a5_vx': $maskFile = 'assets/384_768_free_mask.png'; break;
case 'tilesets_bcde_vx': $maskFile = 'assets/512_512_free_mask.png'; break;
case 'tilesets_a1a2_mv_stylize': $maskFile = 'assets/512_384_auto_mask.png'; break;
case 'tilesets_a3_mv_stylize': $maskFile = 'assets/768_384_auto_mask.png'; break;
case 'tilesets_a4_mv_stylize': $maskFile = 'assets/768_720_auto_mask.png'; break;
case 'tilesets_a5_mv_stylize': $maskFile = 'assets/384_768_free_mask.png'; break;
case 'tilesets_bcde_mv_stylize': $maskFile = 'assets/512_512_free_mask.png'; break;
}
if(empty($maskFile)) {
$error = 'Tile mask not found';
removeDirectory($tempDir);
return $error;
}
$nn->setImageMatte(true);
$mask = new Imagick($maskFile); // Load mask
$mask->setImageFormat('png32');
$mask->setImageMatte(true);
$nn->compositeImage($mask, Imagick::COMPOSITE_DSTIN, 0, 0);
ob_start();
imagepng($destImage);
$destString = ob_get_clean();
$im = new Imagick();
$im->readImageBlob($destString);
$im->setImageFormat('png32');
$im->compositeImage($nn, Imagick::COMPOSITE_DEFAULT, 0, 0);
$destString = $im->getImageBlob();
$destImage = imagecreatefromstring($destString);
imagesavealpha($destImage, true);
imagealphablending($destImage, false);
$maxAlpha = 127;
$minAlpha = 5;
$edgeDistanceX = 0;
$edgeDistanceY = 0;
// Calculate alpha channel...
for($x = 0; $x < $destWidth; $x++) {
for($y = 0; $y < $destHeight; $y++) {
$a = $x % 48;
$b = $y % 48;
if($a > 24) {
$edgeDistanceX = 47 - $a;
} else $edgeDistanceX = $a;
if($b > 24) {
$edgeDistanceY = 47 - $b;
} else $edgeDistanceY = $b;
$rgb = imagecolorat($destImage, $x, $y);
$dAlpha = $maxAlpha - (($rgb >> 24) & 0xFF);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
// Get alpha value of the nearest neighbour image
$rAlpha = $maxAlpha - ((imagecolorat($nnImage, $x, $y) >> 24) & 0xFF);
// Fix edges with some fun opacity logic
if($edgeDistanceX < 2 || $edgeDistanceY < 2) {
if($dAlpha == $maxAlpha) { // If WAIFU = SOLID
$alpha = $dAlpha; // Always use WAIFU
} else if($dAlpha > $minAlpha) { // If WAIFU = NOT SOLID
// Semi-transparent border areas always use NN colours
$rgb = imagecolorat($resampledImage, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
if($rAlpha == $maxAlpha) { // And NN = SOLID
$alpha = $rAlpha; // Use NN
} else if($rAlpha < $minAlpha) { // If WAIFU = NOT SOLID and NN = TRANSPARENT, set transparent
$alpha = 0; // set TRANSPARENT
} else $alpha = $maxAlpha - (($rgb >> 24) & 0xFF); // Otherwise use original NN alpha
} else { // If WAIFU = TRANSPARENT
if($rAlpha == $maxAlpha) { // And NN = SOLID
$alpha = $rAlpha; // Use NN
} else $alpha = 0; // Otherwise set TRANSPARENT
}
} else $alpha = $maxAlpha - (imagecolorat($resampledImage, $x, $y) >> 24) & 0xFF;
$color = imagecolorallocatealpha($destImage, $r, $g, $b, $maxAlpha - $alpha);
imagesetpixel($destImage, $x, $y, $color);
}
}
} else {
$im = new Imagick();
$im->readImageBlob($srcString);
$im->resizeImage($srcWidth * $finalScaleFactor, $srcHeight * $finalScaleFactor, Imagick::FILTER_LANCZOS, 1);
$im->setImageFormat('png32');
$resampledString = $im->getImageBlob();
$resampledImage = imagecreatefromstring($resampledString);
imagesavealpha($resampledImage, true);
imagealphablending($resampledImage, false);
// Copy alpha channel from the standard resampled image
for($x = 0; $x < $destWidth; $x++) {
for($y = 0; $y < $destHeight; $y++) {
$alpha = (imagecolorat($resampledImage, $x, $y) >> 24) & 0xFF;
$rgb = imagecolorat($destImage, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$color = imagecolorallocatealpha($destImage, $r, $g, $b, $alpha);
imagesetpixel($destImage, $x, $y, $color);
}
}
}
$endTime = time();
// Save the image and send the session ID so we can get the image again
imagepng($destImage, $outputFilePath);
$output = json_encode(array('status' => 'success', 'message' => $sessionId));
echo $output;
// Clear temp files
removeDirectory($tempDir);
/** ------ OUTPUT ------ **/
//header("Content-Type: image/png");
//$output = file_get_contents($outputFilePath);