2016-07-21 22:01:57 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
class Minecraft
|
|
|
|
{
|
|
|
|
/**
|
2018-06-28 21:55:33 +08:00
|
|
|
* Cut and resize to get the head part from a skin image.
|
|
|
|
* HD skin support added by xfl03 <xfl03@hotmail.com>.
|
2016-07-21 22:01:57 +08:00
|
|
|
*
|
2018-06-28 21:55:33 +08:00
|
|
|
* @see https://github.com/jamiebicknell/Minecraft-Avatar/blob/master/face.php
|
|
|
|
* @param string $binary Binary image data or decoded base64 formatted image.
|
|
|
|
* @param int $height The height of generated image in pixel.
|
|
|
|
* @param string $view Which side of head to be captured, defaults to 'f' for front view.
|
2016-07-21 22:01:57 +08:00
|
|
|
* @return resource
|
|
|
|
*/
|
2018-06-28 21:55:33 +08:00
|
|
|
public static function generateAvatarFromSkin($binary, $height, $view = 'f')
|
2016-07-21 22:01:57 +08:00
|
|
|
{
|
2018-06-28 21:55:33 +08:00
|
|
|
$src = imagecreatefromstring($binary);
|
|
|
|
$dest = imagecreatetruecolor($height, $height);
|
|
|
|
$ratio = imagesx($src) / 64;
|
2016-07-21 22:01:57 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
$x = [
|
|
|
|
'f' => 8, // Front
|
|
|
|
'l' => 16, // Left
|
|
|
|
'r' => 0, // Right
|
2019-03-02 22:58:37 +08:00
|
|
|
'b' => 24, // Back
|
2018-06-28 21:55:33 +08:00
|
|
|
];
|
2016-07-21 22:01:57 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
imagecopyresized($dest, $src, 0, 0, $x[$view] * $ratio, 8 * $ratio, $height, $height, 8 * $ratio, 8 * $ratio); // Face
|
|
|
|
imagecolortransparent($src, imagecolorat($src, 63 * $ratio, 0)); // Black hat issue
|
|
|
|
imagecopyresized($dest, $src, 0, 0, ($x[$view] + 32) * $ratio, 8 * $ratio, $height, $height, 8 * $ratio, 8 * $ratio); // Accessories
|
2016-07-21 22:01:57 +08:00
|
|
|
|
|
|
|
imagedestroy($src);
|
2019-03-02 22:58:37 +08:00
|
|
|
|
2016-07-21 22:01:57 +08:00
|
|
|
return $dest;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-06-28 21:55:33 +08:00
|
|
|
* Generate a image preview for a skin texture.
|
2016-07-21 22:01:57 +08:00
|
|
|
*
|
2018-06-28 21:55:33 +08:00
|
|
|
* @see https://github.com/NC22/Minecraft-HD-skin-viewer-2D/blob/master/SkinViewer2D.class.php
|
|
|
|
* @param string $binary Binary image data or decoded base64 formatted image.
|
|
|
|
* @param int $height The height of generated image in pixel.
|
|
|
|
* @param bool $alex Whether the given skin is in Alex model.
|
|
|
|
* @param string $side Which side of model to be captured, 'front', 'back' or 'both'.
|
|
|
|
* @param int $gap Gap size between front & back preview in relative pixel.
|
2016-07-21 22:01:57 +08:00
|
|
|
* @return resource
|
|
|
|
*/
|
2018-06-28 21:55:33 +08:00
|
|
|
public static function generatePreviewFromSkin($binary, $height, $alex = false, $side = 'both', $gap = 4)
|
2016-07-21 22:01:57 +08:00
|
|
|
{
|
2018-06-28 21:55:33 +08:00
|
|
|
$src = imagecreatefromstring($binary);
|
2016-07-21 22:01:57 +08:00
|
|
|
|
|
|
|
$ratio = imagesx($src) / 64;
|
2016-08-29 12:19:21 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
// Check if given skin contains double layers
|
2016-08-29 12:19:21 +08:00
|
|
|
$double = imagesy($src) == 64 * $ratio;
|
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
$dest = imagecreatetruecolor((32 + $gap) * $ratio, 32 * $ratio);
|
2016-08-29 12:19:21 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
if ($side == 'both') {
|
|
|
|
// The width of front view and gap, the back side view will be drawn on its right.
|
|
|
|
$half_width = (16 + $gap) * $ratio;
|
|
|
|
$dest = imagecreatetruecolor((32 + $gap) * $ratio, 32 * $ratio);
|
|
|
|
} else {
|
|
|
|
// No need to calculate this if only single side view is required
|
|
|
|
$half_width = 0;
|
|
|
|
$dest = imagecreatetruecolor((16 + $gap) * $ratio, 32 * $ratio);
|
|
|
|
}
|
2016-07-21 22:01:57 +08:00
|
|
|
|
|
|
|
$transparent = imagecolorallocatealpha($dest, 255, 255, 255, 127);
|
|
|
|
imagefill($dest, 0, 0, $transparent);
|
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
if ($side == 'both' || $side == 'front') {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, 4 * $ratio, 0 * $ratio, 8 * $ratio, 8 * $ratio, 8 * $ratio, 8 * $ratio); // Head - 1
|
|
|
|
imagecopy($dest, $src, 4 * $ratio, 0 * $ratio, 40 * $ratio, 8 * $ratio, 8 * $ratio, 8 * $ratio); // Head - 2
|
|
|
|
imagecopy($dest, $src, 4 * $ratio, 8 * $ratio, 20 * $ratio, 20 * $ratio, 8 * $ratio, 12 * $ratio); // Body - 1
|
|
|
|
imagecopy($dest, $src, 4 * $ratio, 20 * $ratio, 4 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Right Leg - 1
|
2016-08-29 12:19:21 +08:00
|
|
|
|
2018-01-04 10:00:15 +08:00
|
|
|
if ($alex) {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, 1 * $ratio, 8 * $ratio, 44 * $ratio, 20 * $ratio, 3 * $ratio, 12 * $ratio); // Right Arm - 1
|
2018-01-04 10:00:15 +08:00
|
|
|
} else {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, 0 * $ratio, 8 * $ratio, 44 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Right Arm - 1
|
2018-01-04 10:00:15 +08:00
|
|
|
}
|
|
|
|
|
2016-08-29 12:19:21 +08:00
|
|
|
// Check if given skin is double layer skin.
|
|
|
|
// If not, flip right arm/leg to generate left arm/leg.
|
|
|
|
if ($double) {
|
|
|
|
imagecopy($dest, $src, 8 * $ratio, 20 * $ratio, 20 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio); // Left Leg - 1
|
|
|
|
|
|
|
|
// copy second layer
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, 4 * $ratio, 8 * $ratio, 20 * $ratio, 36 * $ratio, 8 * $ratio, 12 * $ratio); // Body - 2
|
|
|
|
imagecopy($dest, $src, 4 * $ratio, 20 * $ratio, 4 * $ratio, 36 * $ratio, 4 * $ratio, 12 * $ratio); // Right Leg - 2
|
|
|
|
imagecopy($dest, $src, 8 * $ratio, 20 * $ratio, 4 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio); // Left Leg - 2
|
2016-08-29 12:19:21 +08:00
|
|
|
|
2018-01-04 10:00:15 +08:00
|
|
|
if ($alex) {
|
|
|
|
imagecopy($dest, $src, 12 * $ratio, 8 * $ratio, 36 * $ratio, 52 * $ratio, 3 * $ratio, 12 * $ratio); // Left Arm - 1
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, 1 * $ratio, 8 * $ratio, 44 * $ratio, 36 * $ratio, 3 * $ratio, 12 * $ratio); // Right Arm - 2
|
|
|
|
imagecopy($dest, $src, 11 * $ratio, 8 * $ratio, 50 * $ratio, 52 * $ratio, 3 * $ratio, 12 * $ratio); // Left Arm - 2
|
2018-01-04 10:00:15 +08:00
|
|
|
} else {
|
|
|
|
imagecopy($dest, $src, 12 * $ratio, 8 * $ratio, 36 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio); // Left Arm - 1
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, 0 * $ratio, 8 * $ratio, 44 * $ratio, 36 * $ratio, 4 * $ratio, 12 * $ratio); // Right Arm - 2
|
|
|
|
imagecopy($dest, $src, 12 * $ratio, 8 * $ratio, 52 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio); // Left Arm - 2
|
2018-01-04 10:00:15 +08:00
|
|
|
}
|
2016-08-29 12:19:21 +08:00
|
|
|
} else {
|
2018-01-04 10:00:15 +08:00
|
|
|
// I am not sure whether there are single layer Alex-model skin.
|
|
|
|
if ($alex) {
|
2019-03-02 22:58:37 +08:00
|
|
|
static::imageflip($dest, $src, 12 * $ratio, 8 * $ratio, 44 * $ratio, 20 * $ratio, 3 * $ratio, 12 * $ratio); // Left Arm
|
2018-01-04 10:00:15 +08:00
|
|
|
} else {
|
2019-03-02 22:58:37 +08:00
|
|
|
static::imageflip($dest, $src, 12 * $ratio, 8 * $ratio, 44 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Left Arm
|
2018-01-04 10:00:15 +08:00
|
|
|
}
|
2019-03-02 22:58:37 +08:00
|
|
|
static::imageflip($dest, $src, 8 * $ratio, 20 * $ratio, 4 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Left Leg
|
2016-08-29 10:34:54 +08:00
|
|
|
}
|
2016-07-21 22:01:57 +08:00
|
|
|
}
|
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
if ($side == 'both' || $side == 'back') {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 4 * $ratio, 8 * $ratio, 32 * $ratio, 20 * $ratio, 8 * $ratio, 12 * $ratio); // Body
|
|
|
|
imagecopy($dest, $src, $half_width + 4 * $ratio, 0 * $ratio, 24 * $ratio, 8 * $ratio, 8 * $ratio, 8 * $ratio); // Head
|
2018-06-28 21:55:33 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 8 * $ratio, 20 * $ratio, 12 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Right Leg
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 4 * $ratio, 0 * $ratio, 56 * $ratio, 8 * $ratio, 8 * $ratio, 8 * $ratio); // Headwear
|
2018-01-04 10:00:15 +08:00
|
|
|
|
|
|
|
if ($alex) {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 12 * $ratio, 8 * $ratio, 51 * $ratio, 20 * $ratio, 3 * $ratio, 12 * $ratio); // Right Arm
|
2018-01-04 10:00:15 +08:00
|
|
|
} else {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 12 * $ratio, 8 * $ratio, 52 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Right Arm
|
2018-01-04 10:00:15 +08:00
|
|
|
}
|
2016-08-29 12:19:21 +08:00
|
|
|
|
|
|
|
if ($double) {
|
2018-01-04 10:00:15 +08:00
|
|
|
if ($alex) {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 1 * $ratio, 8 * $ratio, 43 * $ratio, 52 * $ratio, 3 * $ratio, 12 * $ratio);
|
2018-01-04 10:00:15 +08:00
|
|
|
} else {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 0 * $ratio, 8 * $ratio, 44 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio);
|
2018-01-04 10:00:15 +08:00
|
|
|
}
|
|
|
|
imagecopy($dest, $src, $half_width + 4 * $ratio, 20 * $ratio, 28 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio); // Left Leg
|
2016-08-29 12:19:21 +08:00
|
|
|
|
|
|
|
// copy second layer
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 4 * $ratio, 8 * $ratio, 32 * $ratio, 36 * $ratio, 8 * $ratio, 12 * $ratio);
|
|
|
|
imagecopy($dest, $src, $half_width + 12 * $ratio, 8 * $ratio, 52 * $ratio, 36 * $ratio, 4 * $ratio, 12 * $ratio);
|
2018-01-04 10:00:15 +08:00
|
|
|
if ($alex) {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 1 * $ratio, 8 * $ratio, 59 * $ratio, 52 * $ratio, 3 * $ratio, 12 * $ratio);
|
2018-01-04 10:00:15 +08:00
|
|
|
} else {
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 0 * $ratio, 8 * $ratio, 60 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio);
|
2018-01-04 10:00:15 +08:00
|
|
|
}
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopy($dest, $src, $half_width + 8 * $ratio, 20 * $ratio, 12 * $ratio, 36 * $ratio, 4 * $ratio, 12 * $ratio);
|
|
|
|
imagecopy($dest, $src, $half_width + 4 * $ratio, 20 * $ratio, 12 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio);
|
2016-08-29 12:19:21 +08:00
|
|
|
} else {
|
2019-03-02 22:58:37 +08:00
|
|
|
static::imageflip($dest, $src, $half_width + 0 * $ratio, 8 * $ratio, 52 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio);
|
2018-06-28 21:55:33 +08:00
|
|
|
static::imageflip($dest, $src, $half_width + 4 * $ratio, 20 * $ratio, 12 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio);
|
2016-08-29 10:34:54 +08:00
|
|
|
}
|
2016-07-21 22:01:57 +08:00
|
|
|
}
|
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
$width = ($side == 'both') ? $height / 32 * (32 + $gap) : $height / 2;
|
2016-07-21 22:01:57 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
$fullsize = imagecreatetruecolor($width, $height);
|
2016-07-21 22:01:57 +08:00
|
|
|
imagesavealpha($fullsize, true);
|
|
|
|
$transparent = imagecolorallocatealpha($fullsize, 255, 255, 255, 127);
|
|
|
|
imagefill($fullsize, 0, 0, $transparent);
|
|
|
|
imagecopyresized($fullsize, $dest, 0, 0, 0, 0, imagesx($fullsize), imagesy($fullsize), imagesx($dest), imagesy($dest));
|
|
|
|
|
|
|
|
imagedestroy($dest);
|
|
|
|
imagedestroy($src);
|
|
|
|
|
|
|
|
return $fullsize;
|
|
|
|
}
|
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
/**
|
|
|
|
* Generate a image preview for a cape texture.
|
|
|
|
*
|
|
|
|
* @param string $binary Binary image data or decoded base64 formatted image.
|
|
|
|
* @param int $height The size of generated image in pixel.
|
|
|
|
* @param int $fillWidth Create a image with given size, And draw the preview on the center of it.
|
|
|
|
* @param int $fillHeight Set the value to 0 to disable.
|
|
|
|
* @return resource
|
|
|
|
*/
|
|
|
|
public static function generatePreviewFromCape($binary, $height, $fillWidth = 0, $fillHeight = 0)
|
2016-07-21 22:01:57 +08:00
|
|
|
{
|
2018-06-28 21:55:33 +08:00
|
|
|
$src = imagecreatefromstring($binary);
|
|
|
|
$ratio = imagesx($src) / 64;
|
|
|
|
$width = $height / 16 * 10;
|
2016-07-21 22:01:57 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
$dest = imagecreatetruecolor($width, $height);
|
2016-07-21 22:01:57 +08:00
|
|
|
imagesavealpha($dest, true);
|
2018-06-28 21:55:33 +08:00
|
|
|
$transparent = imagecolorallocatealpha($dest, 255, 255, 255, 127);
|
|
|
|
imagefill($dest, 0, 0, $transparent);
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopyresized($dest, $src, 0, 0, $ratio, $ratio, $width, $height, imagesx($src) * 10 / 64, imagesy($src) * 16 / 32);
|
2016-07-21 22:01:57 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
imagedestroy($src);
|
|
|
|
if ($fillWidth == 0 || $fillHeight == 0) {
|
|
|
|
return $dest;
|
|
|
|
}
|
2016-07-21 22:01:57 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
$filled = imagecreatetruecolor($fillWidth, $fillHeight);
|
|
|
|
imagesavealpha($filled, true);
|
|
|
|
$transparent = imagecolorallocatealpha($filled, 255, 255, 255, 127);
|
|
|
|
imagefill($filled, 0, 0, $transparent);
|
2019-03-02 22:58:37 +08:00
|
|
|
imagecopyresized($filled, $dest, ($fillWidth - $width) / 2, ($fillHeight - $height) / 2, 0, 0, $width, $height, $width, $height);
|
2016-07-21 22:01:57 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
imagedestroy($dest);
|
2019-03-02 22:58:37 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
return $filled;
|
|
|
|
}
|
2016-07-21 22:01:57 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
/**
|
|
|
|
* Flip the given image.
|
|
|
|
*/
|
|
|
|
protected static function imageflip(&$result, &$img, $rx = 0, $ry = 0, $x = 0, $y = 0, $size_x = null, $size_y = null)
|
|
|
|
{
|
|
|
|
$size_x = ($size_x < 1) ? $imagesx($img) : $size_x;
|
|
|
|
$size_y = ($size_y < 1) ? $imagesy($img) : $size_y;
|
2016-07-21 22:01:57 +08:00
|
|
|
|
2018-06-28 21:55:33 +08:00
|
|
|
imagecopyresampled($result, $img, $rx, $ry, ($x + $size_x - 1), $y, $size_x, $size_y, 0 - $size_x, $size_y);
|
2016-07-21 22:01:57 +08:00
|
|
|
}
|
|
|
|
}
|