純規の暇人趣味ブログ

手を突っ込んで足を洗う

プログラムでMinecraftのスキンから頭だけを抜き出す方法

      2015/12/27    HimaJyun

こん、
マイクラのマルチ界ではどうやら、自分がログインする→「こんにちは」、その挨拶に返事する→「こん」

と、使い分けされている様ですね。
なんでも、挨拶の返事に返事をしない様にだとか?

さて、そのマイクラ、主にマルチでその存在意義が発揮されるスキン(いわば、見た目)
ここから頭だけを抜き出して拡大するプログラムの作り方を書き留めておこうと思います。

スキンゲット!!

さて、宣伝兼ねてですが、僕のマイクラ鯖ではオンラインユーザの数がWebページに表示されます。
そして、オンラインユーザのIDも取得できるので、どうせの事ならIDからスキンを取得して表示しようと言う魂胆です。
(鯖に誰かが居る時にトップページを見て頂ければ、ユーザの顔が表示されていると思います。)

スキンの取得方法

さて、ユーザIDからUUIDを取得→UUIDから云々→そこからスキンを……
とか考えちゃってるあなた、ハッキリって無駄ですよ!!

実はですね、マイクラのスキンは「http://skins.minecraft.net/MinecraftSkins/<ユーザ名>.png」にアクセスするだけで取得出来ちゃうのです。

アクセスすると、リダイレクトされて正しいURLに飛ばされるって仕組みですね。
例えば、僕なら「http://skins.minecraft.net/MinecraftSkins/HimaJyun.png」ですね。

Mojang鯖は海外なので、通信の往復を極力減らすべきです。
つまり、下手にUUIDを云々するより、極力こちらの手法を使った方が表示が早くなると思いますよ。

顔の部分を抜き出す

新旧どちらのスキンタイプを問わず、ユーザの頭はx=8px,y=8pxの座標から始まり、
そこから縦横8pxの大きさとなっています。
minecraft-get-head-skin_001

髪飾りを抜き出す

こちらはお好みでどうぞ、開始地点がx=40になった以外は頭と全く同じです。
minecraft-get-head-skin_002

切り出した髪飾りを、先程作成した頭とフュージョンさせると、完全体セルスキンの出来上がりです。

拡大する

このままでは8px*8pxの超ミニマムロリロリサイズなスキンが出来上がるだけです。
適正なサイズに拡大してやる必要がありますね。

48px*48px辺りが使いやすく、取り回しも良い感じでは無いでしょうか?
minecraft-get-head-skin_003

PHPで実際にやってみる

言うだけならばエベレストの登頂だって出来てしまうのです。
と、言う訳で実際にやってみました、スパゲッティなコードですがお許しを
あと、cURLとGDを使います、もし使うってのならインストールして下さい。

<?php
/* -- 設定→ -- */
//キャッシュ期間、strtotime関数が認識する書式で指定してね。
$cacheTime = '7 day';
//画像サイズの大きさ、単位はピクセル
$size = 48;
//キャッシュディレクトリのフォルダ名、Web鯖から書き込めるパーミッションが必要です(例:775)
$cachedir = __DIR__.'/SkinCache/';
/* -- ←設定 -- */

//エラー時などに出力する画像(加工済みのスティーブ)です。
$Steve['base64'] = 'iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAgVBMVEUrHg13QjWBUzmqfWYoHAtqQDBSPYl6TjODVTucaUy0hG27iXL///8kGAgmGgoqHQ0sHg4vHw8vIA0zJBE0JRI/KhVCKhJtQypvRSyAUzSPXj6QXkOWX0CcY0aiakecclysdlqze2K1e2etgG23gnK2iWy+iGy9i3K9jnK9jnTGloAbjjvxAAAAb0lEQVR42uXLRw6CABAAQBQEBey99/r/B5rMicT4gp37JBMSxmT0GZAwJGb4NWXBnBEZkcOdFx+evDkxI2ZIKUm5cqSi1xAzlNTkXKjIqUmJGfacudFpeHBgTcywZUfrjw1tYoYVS9oNBd2GgpDhCyA+ivFHylWNAAAAAElFTkSuQmCC';
//エラー時の画像のMD5(ETagに使うだけなので任意の文字でも良いでしょう。)
$Steve['md5'] = '72d28dae3706a922d01e68f0acde7c9b';

//ユーザ名が指定されているか、リファラがあるかを確認、無ければスティーブを出力して終わり。
if(empty($_GET['name']) || empty($_SERVER['HTTP_REFERER']))outImage($Steve);
//リファラチェック、画像の加工は決して軽くはない処理なので外部からの不正利用を阻止
if(strpos(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST), $_SERVER['SERVER_NAME']) === false)outImage($Steve);
//(必要ないと思いますが。)URLエンコードを行います。変な値がMojang鯖に飛ばない様に
$name = urlencode($_GET['name']);

//キャッシュファイルがあるか確認
if(file_exists($cachedir.$name))
{
 //あったら読み取る
 $data = unserialize(file_get_contents($cachedir.$name));

 //新しければ出力
 if($data['LastModify'] >= strtotime('-'.$cacheTime))outImage($data);
}

// cURL
$ch = curl_init();

//おぷしょんず
curl_setopt($ch, CURLOPT_URL, 'http://skins.minecraft.net/MinecraftSkins/'.$name.'.png');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS,10);
curl_setopt($ch, CURLOPT_AUTOREFERER,true);

//実行
$rs = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// cURL

//404 = スキン未設定
if($statusCode == 404) {

 $Steve['LastModify'] = strtotime('now');
 file_put_contents($cachedir.$name, serialize($Steve));
 outImage($Steve);

//200以外 = サーバエラー?
} else if($statusCode != 200) {
 outImage($Steve);
}

//画像加工
$skin = imagecreatefromstring($rs);
$head = ImageCreateTrueColor($size, $size);
$tmp = imagecrop($skin, array('x' => 8, 'y' => 8, 'width' => 8, 'height' => 8,));
imagecopy($tmp, $skin, 0, 0, 40, 8, 8, 8);
ImageCopyResized($head,$tmp,0,0,0,0,$size,$size,8,8);

//出力をバッファリング(GDはバイナリ出力出来ない)
ob_start();
imagepng($head,NULL,9);
$image = ob_get_clean();

//md5にしたり、base64にしたりして煮込む
$data['md5'] = md5($image);
$data['base64'] = base64_encode($image);
$data['LastModify'] = strtotime('now');

//メモリから画像を破棄
imagedestroy($skin);
imagedestroy($tmp);
imagedestroy($head);

//出来上がったデータを書き込む
file_put_contents($cachedir.$name, serialize($data));

//出力
outImage($data);

//出力
function outImage($data)
{
 if(isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
   if($_SERVER['HTTP_IF_NONE_MATCH'] === $data['md5']){
   header('HTTP/1.1 304 Not Modified');
   header('ETag: '.$data['md5']);
   exit;
   }
 }
 header("Content-type: image/png");
 header('ETag: '.$data['md5']);
 echo base64_decode($data['base64']);
 exit;
}
?>

これをサーバにアップロードして、キャッシュ用のディレクトリを作成
<img src="このファイルのURL?name=ユーザ名" alt="画像" width="48" height="48" >などとすると画像が出ますよ!!

カッコイイですね!!

 - Web制作 ,