diff --git a/app/Events/GetAvatarPreview.php b/app/Events/GetAvatarPreview.php
deleted file mode 100644
index d2a64716..00000000
--- a/app/Events/GetAvatarPreview.php
+++ /dev/null
@@ -1,25 +0,0 @@
-texture = $texture;
- $this->size = $size;
- }
-}
diff --git a/app/Events/GetSkinPreview.php b/app/Events/GetSkinPreview.php
deleted file mode 100644
index bae7c23c..00000000
--- a/app/Events/GetSkinPreview.php
+++ /dev/null
@@ -1,25 +0,0 @@
-texture = $texture;
- $this->size = $size;
- }
-}
diff --git a/app/Http/Controllers/TextureController.php b/app/Http/Controllers/TextureController.php
index 2996a8df..c0074e52 100644
--- a/app/Http/Controllers/TextureController.php
+++ b/app/Http/Controllers/TextureController.php
@@ -2,25 +2,31 @@
namespace App\Http\Controllers;
-use App\Events\GetAvatarPreview;
-use App\Events\GetSkinPreview;
use App\Models\Player;
use App\Models\Texture;
use App\Models\User;
-use App\Services\Minecraft;
+use Blessing\Minecraft;
use Cache;
use Carbon\Carbon;
-use Event;
-use Exception;
-use Illuminate\Support\Arr;
+use Illuminate\Http\Request;
use Image;
-use Option;
-use Response;
use Storage;
-use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
class TextureController extends Controller
{
+ public function __construct()
+ {
+ $this->middleware('cache.headers:etag;public;max_age='.option('cache_expire_time'))
+ ->only([
+ 'preview',
+ 'raw',
+ 'texture',
+ 'avatarByPlayer',
+ 'avatarByUser',
+ 'avatarByTexture',
+ ]);
+ }
+
public function json($player)
{
$player = $this->getPlayerInstance($player);
@@ -37,144 +43,112 @@ class TextureController extends Controller
return response()->json($player)->setLastModified($player->last_modified);
}
- public function texture($hash, $headers = [], $message = '')
+ public function preview(Minecraft $minecraft, Request $request, $tid)
{
- try {
- if (Storage::disk('textures')->has($hash)) {
- return $this->outputImage(Storage::disk('textures')->get($hash), array_merge([
- 'Last-Modified' => Storage::disk('textures')->lastModified($hash),
- 'Accept-Ranges' => 'bytes',
- 'Content-Length' => Storage::disk('textures')->size($hash),
- ], $headers));
- }
- } catch (Exception $e) {
- report($e);
- }
+ $texture = Texture::findOrFail($tid);
+ $hash = $texture->hash;
- return abort(404, $message);
- }
+ $disk = Storage::disk('textures');
+ abort_if($disk->missing($hash), 404);
- public function avatarByTid($tid, $size = 128)
- {
- if ($t = Texture::find($tid)) {
- try {
- if (Storage::disk('textures')->has($t->hash)) {
- $responses = event(new GetAvatarPreview($t, $size));
-
- if (isset($responses[0]) && $responses[0] instanceof SymfonyResponse) {
- return $responses[0]; // @codeCoverageIgnore
- } else {
- $png = Minecraft::generateAvatarFromSkin(
- Storage::disk('textures')->read($t->hash), $size
- );
-
- return $this->outputImage(png($png));
- }
+ $height = (int) $request->query('height', 200);
+ $now = Carbon::now();
+ $response = Cache::remember(
+ 'preview-t'.$tid,
+ option('enable_preview_cache') ? $now->addYear() : $now->addMinute(),
+ function () use ($minecraft, $disk, $texture, $hash, $height) {
+ $file = $disk->get($hash);
+ if ($texture->type === 'cape') {
+ $image = $minecraft->renderCape($file, 12);
+ } else {
+ $image = $minecraft->renderSkin($file, 12, $texture->type === 'alex');
}
- } catch (Exception $e) {
- report($e);
+
+ $lastModified = $disk->lastModified($hash);
+
+ return Image::make($image)
+ ->resize(null, $height, function ($constraint) {
+ $constraint->aspectRatio();
+ })
+ ->response('png', 100)
+ ->setLastModified(Carbon::createFromTimestamp($lastModified));
}
- }
+ );
- $default = Image::make(storage_path('static_textures/avatar.png'));
-
- return $default->resize($size, $size)->response();
- }
-
- public function avatar($uid, $size = 128)
- {
- $user = User::find($uid);
-
- if ($user) {
- return $this->avatarByTid($user->avatar, $size);
- }
-
- $default = Image::make(storage_path('static_textures/avatar.png'));
-
- return $default->resize($size, $size)->response();
- }
-
- public function preview($tid, $size = 250)
- {
- if ($t = Texture::find($tid)) {
- try {
- if (Storage::disk('textures')->has($t->hash)) {
- $responses = event(new GetSkinPreview($t, $size));
-
- if (isset($responses[0]) && $responses[0] instanceof \Symfony\Component\HttpFoundation\Response) {
- return $responses[0]; // @codeCoverageIgnore
- } else {
- $binary = Storage::disk('textures')->read($t->hash);
-
- if ($t->type == 'cape') {
- $png = Minecraft::generatePreviewFromCape(
- $binary, $size * 0.8, $size * 1.125, $size
- );
- } else {
- $png = Minecraft::generatePreviewFromSkin(
- $binary, $size, ($t->type == 'alex'), 'both', 4
- );
- }
-
- return $this->outputImage(png($png));
- }
- }
- } catch (Exception $e) {
- report($e);
- }
- }
-
- // Show this if given texture is invalid.
- return response()->file(storage_path('static_textures/broken.png'));
+ return $response;
}
public function raw($tid)
{
- abort_unless(option('allow_downloading_texture'), 404);
+ abort_unless(option('allow_downloading_texture'), 403);
- return ($t = Texture::find($tid))
- ? $this->texture($t->hash)
- : abort(404, trans('skinlib.non-existent'));
+ $texture = Texture::findOrFail($tid);
+
+ return $this->texture($texture->hash);
}
- public function avatarByPlayer($size, $name)
+ public function texture(string $hash)
{
- $player = Player::where('name', $name)->first();
- abort_unless($player, 404);
+ $disk = Storage::disk('textures');
+ abort_if($disk->missing($hash), 404);
- $hash = optional($player->skin)->hash;
- if (Storage::disk('textures')->has($hash)) {
- $png = Minecraft::generateAvatarFromSkin(
- Storage::disk('textures')->read($hash),
- $size
- );
+ $lastModified = Carbon::createFromTimestamp($disk->lastModified($hash));
- return $this->outputImage(png($png));
- }
-
- return abort(404);
+ return response($disk->get($hash))
+ ->withHeaders([
+ 'Content-Type' => 'image/png',
+ 'Content-Length' => $disk->size($hash),
+ ])
+ ->setLastModified($lastModified);
}
- protected function outputImage($content, $headers = [])
+ public function avatarByPlayer(Minecraft $minecraft, Request $request, $name)
{
- $request = request();
+ $player = Player::where('name', $name)->firstOrFail();
- $ifNoneMatch = $request->header('If-None-Match');
- $eTag = md5($content);
+ return $this->avatar($minecraft, $player->skin, (int) $request->query('size', 100));
+ }
- $ifModifiedSince = Carbon::parse($request->header('If-Modified-Since', 0));
- $lastModified = Carbon::parse(Arr::pull($headers, 'Last-Modified', time()));
+ public function avatarByUser(Minecraft $minecraft, Request $request, $uid)
+ {
+ $texture = Texture::find(optional(User::find($uid))->avatar);
- if ($eTag === $ifNoneMatch || $lastModified <= $ifModifiedSince) {
- return response(null)->withHeaders($headers)->setNotModified();
+ return $this->avatar($minecraft, $texture, (int) $request->query('size', 100));
+ }
+
+ public function avatarByTexture(Minecraft $minecraft, Request $request, $tid)
+ {
+ $texture = Texture::find($tid);
+
+ return $this->avatar($minecraft, $texture, (int) $request->query('size', 100));
+ }
+
+ protected function avatar(Minecraft $minecraft, Texture $texture = null, int $size = 100)
+ {
+ $disk = Storage::disk('textures');
+ if (is_null($texture) || $disk->missing($texture->hash)) {
+ return Image::make(storage_path('static_textures/avatar.png'))
+ ->resize($size, $size)
+ ->response('png', 100);
}
- return response($content, 200, $headers)->withHeaders([
- 'Content-Type' => 'image/png',
- 'ETag' => $eTag,
- 'Last-Modified' => $lastModified->toRfc7231String(),
- 'Cache-Control' => 'max-age='.option('cache_expire_time').', public',
- ]);
+ $hash = $texture->hash;
+ $now = Carbon::now();
+ $response = Cache::remember(
+ 'avatar-2d-t'.$texture->tid.'-s'.$size,
+ option('enable_avatar_cache') ? $now->addYear() : $now->addMinute(),
+ function () use ($minecraft, $disk, $hash, $size) {
+ $image = $minecraft->render2dAvatar($disk->get($hash), 25);
+ $lastModified = Carbon::createFromTimestamp($disk->lastModified($hash));
+
+ return Image::make($image)
+ ->resize($size, $size)
+ ->response('png', 100)
+ ->setLastModified($lastModified);
+ }
+ );
+
+ return $response;
}
protected function getPlayerInstance($player_name)
@@ -184,16 +158,4 @@ class TextureController extends Controller
return $player;
}
-
- /**
- * Default steve skin, base64 encoded.
- *
- * @see https://minecraft.gamepedia.com/File:Steve_skin.png
- *
- * @return string
- */
- public static function getDefaultSteveSkin()
- {
- return 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAFDUlEQVR42u2a20sUURzH97G0LKMotPuWbVpslj1olJXdjCgyisowsSjzgrB0gSKyC5UF1ZNQWEEQSBQ9dHsIe+zJ/+nXfM/sb/rN4ZwZ96LOrnPgyxzP/M7Z+X7OZc96JpEISfWrFhK0YcU8knlozeJKunE4HahEqSc2nF6zSEkCgGCyb+82enyqybtCZQWAzdfVVFgBJJNJn1BWFgC49/VpwGVlD0CaxQiA5HSYEwBM5sMAdKTqygcAG9+8coHKY/XXAZhUNgDYuBSPjJL/GkzVVhAEU5tqK5XZ7cnFtHWtq/TahdSw2l0HUisr1UKIWJQBAMehDuqiDdzndsP2EZECAG1ZXaWMwOCODdXqysLf++uXUGv9MhUHIByDOijjdiSAoH3ErANQD73C7TXXuGOsFj1d4YH4OTJAEy8y9Hd0mCaeZ5z8dfp88zw1bVyiYhCLOg1ZeAqC0ybaDttHRGME1DhDeVWV26u17lRAPr2+mj7dvULfHw2q65fhQRrLXKDfIxkau3ZMCTGIRR3URR5toU38HbaPiMwUcKfBAkoun09PzrbQ2KWD1JJaqswjdeweoR93rirzyCMBCmIQizqoizZkm2H7iOgAcHrMHbbV9KijkUYv7qOn55sdc4fo250e+vUg4329/Xk6QB/6DtOws+dHDGJRB3XRBve+XARt+4hIrAF4UAzbnrY0ve07QW8uHfB+0LzqanMM7qVb+3f69LJrD90/1axiEIs6qIs21BTIToewfcSsA+Bfb2x67OoR1aPPzu2i60fSNHRwCw221Suz0O3jO+jh6V1KyCMGse9721XdN5ePutdsewxS30cwuMjtC860T5JUKpXyKbSByUn7psi5l+juDlZYGh9324GcPKbkycaN3jUSAGxb46IAYPNZzW0AzgiQ5tVnzLUpUDCAbakMQXXrOtX1UMtHn+Q9/X5L4wgl7t37r85OSrx+TYl379SCia9KXjxRpiTjIZTBFOvrV1f8ty2eY/T7XJ81FQAwmA8ASH1ob68r5PnBsxA88/xAMh6SpqW4HRnLBrkOA9Xv5wPAZjAUgOkB+SHxgBgR0qSMh0zmZRsmwDJm1gFg2PMDIC8/nAHIMls8x8GgzOsG5WiaqREgYzDvpTwjLDy8NM15LpexDEA3LepjU8Z64my+8PtDCmUyRr+fFwA2J0eAFYA0AxgSgMmYBMZTwFQnO9RNAEaHOj2DXF5UADmvAToA2ftyxZYA5BqgmZZApDkdAK4mAKo8GzPlr8G8AehzMAyA/i1girUA0HtYB2CaIkUBEHQ/cBHSvwF0AKZFS5M0ZwMQtEaEAmhtbSUoDADH9ff3++QZ4o0I957e+zYAMt6wHkhzpjkuAcgpwNcpA7AZDLsvpwiuOkBvxygA6Bsvb0HlaeKIF2EbADZpGiGzBsA0gnwQHGOhW2snRpbpPexbAB2Z1oicAMQpTnGKU5ziFKc4xSlOcYpTnOIUpzgVmgo+XC324WfJAdDO/+ceADkCpuMFiFKbApEHkOv7BfzfXt+5gpT8V7rpfYJcDz+jAsB233r6yyBsJ0mlBCDofuBJkel4vOwBFPv8fyYAFPJ+wbSf/88UANNRVy4Awo6+Ig2gkCmgA5DHWjoA+X7AlM//owLANkX0w0359od++pvX8fdMAcj3/QJ9iJsAFPQCxHSnQt8vMJ3v2wCYpkhkAOR7vG7q4aCXoMoSgG8hFAuc/grMdAD4B/kHl9da7Ne9AAAAAElFTkSuQmCC';
- }
}
diff --git a/app/Http/Controllers/UpdateController.php b/app/Http/Controllers/UpdateController.php
index e6d1ac50..955700c1 100644
--- a/app/Http/Controllers/UpdateController.php
+++ b/app/Http/Controllers/UpdateController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Services\PackageManager;
+use Cache;
use Composer\CaBundle\CaBundle;
use Composer\Semver\Comparator;
use Exception;
@@ -75,6 +76,7 @@ class UpdateController extends Controller
$artisan->call('migrate', ['--force' => true]);
$artisan->call('view:clear');
$filesystem->put(storage_path('install.lock'), '');
+ Cache::flush();
return view('setup.updates.success');
}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index f7e8cfa3..03063887 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -58,6 +58,7 @@ class Kernel extends HttpKernel
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'verified' => \App\Http\Middleware\CheckUserVerified::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'admin' => \App\Http\Middleware\CheckAdministrator::class,
diff --git a/app/Http/View/Composers/UserMenuComposer.php b/app/Http/View/Composers/UserMenuComposer.php
index 68a51bd2..dbfd1f2c 100644
--- a/app/Http/View/Composers/UserMenuComposer.php
+++ b/app/Http/View/Composers/UserMenuComposer.php
@@ -24,7 +24,7 @@ class UserMenuComposer
{
$user = auth()->user();
$email = base64_encode($user->email);
- $avatarUrl = url('/avatar/user/'.$user->uid.'/25');
+ $avatarUrl = url('/avatar/user/'.$user->uid.'?size=25');
$avatar = $this->filter->apply('user_avatar', $avatarUrl, [$user]);
$view->with(compact('user', 'avatar'));
diff --git a/app/Http/View/Composers/UserPanelComposer.php b/app/Http/View/Composers/UserPanelComposer.php
index 7b8c422a..2d201b0b 100644
--- a/app/Http/View/Composers/UserPanelComposer.php
+++ b/app/Http/View/Composers/UserPanelComposer.php
@@ -23,7 +23,7 @@ class UserPanelComposer
public function compose(View $view)
{
$user = auth()->user();
- $avatarUrl = url('/avatar/user/'.$user->uid.'/45');
+ $avatarUrl = url('/avatar/user/'.$user->uid.'?size=45');
$avatar = $this->filter->apply('user_avatar', $avatarUrl, [$user]);
$badges = [];
diff --git a/app/Listeners/CacheAvatarPreview.php b/app/Listeners/CacheAvatarPreview.php
deleted file mode 100644
index aeec92c3..00000000
--- a/app/Listeners/CacheAvatarPreview.php
+++ /dev/null
@@ -1,25 +0,0 @@
-texture;
- $size = $event->size;
- $key = "avatar-{$texture->tid}-$size";
-
- $content = Cache::rememberForever($key, function () use ($texture, $size) {
- $res = Storage::disk('textures')->read($texture->hash);
-
- return png(Minecraft::generateAvatarFromSkin($res, $size));
- });
-
- return response($content, 200, ['Content-Type' => 'image/png']);
- }
-}
diff --git a/app/Listeners/CacheSkinPreview.php b/app/Listeners/CacheSkinPreview.php
deleted file mode 100644
index 5c34943b..00000000
--- a/app/Listeners/CacheSkinPreview.php
+++ /dev/null
@@ -1,34 +0,0 @@
-texture;
- $size = $event->size;
- $key = "preview-{$texture->tid}-{$size}";
-
- $content = Cache::rememberForever($key, function () use ($texture, $size) {
- $res = Storage::disk('textures')->read($texture->hash);
-
- if ($texture->type == 'cape') {
- $png = Minecraft::generatePreviewFromCape($res, $size * 0.8, $size * 1.125, $size);
- } else {
- $png = Minecraft::generatePreviewFromSkin($res, $size, $texture->type == 'alex', 'both', 4);
- }
-
- return png($png);
- });
-
- return response($content, 200, [
- 'Content-Type' => 'image/png',
- 'Last-Modified' => Storage::disk('textures')->lastModified($texture->hash),
- ]);
- }
-}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index 4a489212..83a12a0a 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -46,12 +46,6 @@ class EventServiceProvider extends ServiceProvider
{
parent::boot();
- if (option('enable_avatar_cache')) {
- Event::listen(Events\GetAvatarPreview::class, Listeners\CacheAvatarPreview::class);
- }
- if (option('enable_preview_cache')) {
- Event::listen(Events\GetSkinPreview::class, Listeners\CacheSkinPreview::class);
- }
if (option('enable_notfound_cache')) {
Event::subscribe(Listeners\CachePlayerExists::class);
}
diff --git a/app/Services/Minecraft.php b/app/Services/Minecraft.php
deleted file mode 100644
index 51b91fed..00000000
--- a/app/Services/Minecraft.php
+++ /dev/null
@@ -1,218 +0,0 @@
-.
- *
- * @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
- *
- * @return resource
- */
- public static function generateAvatarFromSkin(string $binary, int $height, string $view = 'f')
- {
- $src = imagecreatefromstring($binary);
- $dest = imagecreatetruecolor($height, $height);
- $ratio = imagesx($src) / 64;
-
- $x = [
- 'f' => 8, // Front
- 'l' => 16, // Left
- 'r' => 0, // Right
- 'b' => 24, // Back
- ];
-
- 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
-
- imagedestroy($src);
-
- return $dest;
- }
-
- /**
- * Generate a image preview for a skin texture.
- *
- * @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
- *
- * @return resource
- */
- public static function generatePreviewFromSkin(string $binary, int $height, $alex = false, $side = 'both', $gap = 4)
- {
- $src = imagecreatefromstring($binary);
-
- $ratio = imagesx($src) / 64;
-
- // Check if given skin contains double layers
- $double = imagesy($src) == 64 * $ratio;
-
- $dest = imagecreatetruecolor((32 + $gap) * $ratio, 32 * $ratio);
-
- 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);
- }
-
- $transparent = imagecolorallocatealpha($dest, 255, 255, 255, 127);
- imagefill($dest, 0, 0, $transparent);
-
- if ($side == 'both' || $side == 'front') {
- 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
-
- if ($alex) {
- imagecopy($dest, $src, 1 * $ratio, 8 * $ratio, 44 * $ratio, 20 * $ratio, 3 * $ratio, 12 * $ratio); // Right Arm - 1
- } else {
- imagecopy($dest, $src, 0 * $ratio, 8 * $ratio, 44 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Right Arm - 1
- }
-
- // 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
- 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
-
- if ($alex) {
- imagecopy($dest, $src, 12 * $ratio, 8 * $ratio, 36 * $ratio, 52 * $ratio, 3 * $ratio, 12 * $ratio); // Left Arm - 1
- 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
- } else {
- imagecopy($dest, $src, 12 * $ratio, 8 * $ratio, 36 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio); // Left Arm - 1
- 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
- }
- } else {
- // I am not sure whether there are single layer Alex-model skin.
- if ($alex) {
- static::imageflip($dest, $src, 12 * $ratio, 8 * $ratio, 44 * $ratio, 20 * $ratio, 3 * $ratio, 12 * $ratio); // Left Arm
- } else {
- static::imageflip($dest, $src, 12 * $ratio, 8 * $ratio, 44 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Left Arm
- }
- static::imageflip($dest, $src, 8 * $ratio, 20 * $ratio, 4 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Left Leg
- }
- }
-
- if ($side == 'both' || $side == 'back') {
- 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
- imagecopy($dest, $src, $half_width + 8 * $ratio, 20 * $ratio, 12 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Right Leg
- imagecopy($dest, $src, $half_width + 4 * $ratio, 0 * $ratio, 56 * $ratio, 8 * $ratio, 8 * $ratio, 8 * $ratio); // Headwear
-
- if ($alex) {
- imagecopy($dest, $src, $half_width + 12 * $ratio, 8 * $ratio, 51 * $ratio, 20 * $ratio, 3 * $ratio, 12 * $ratio); // Right Arm
- } else {
- imagecopy($dest, $src, $half_width + 12 * $ratio, 8 * $ratio, 52 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio); // Right Arm
- }
-
- if ($double) {
- if ($alex) {
- imagecopy($dest, $src, $half_width + 1 * $ratio, 8 * $ratio, 43 * $ratio, 52 * $ratio, 3 * $ratio, 12 * $ratio);
- } else {
- imagecopy($dest, $src, $half_width + 0 * $ratio, 8 * $ratio, 44 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio);
- }
- imagecopy($dest, $src, $half_width + 4 * $ratio, 20 * $ratio, 28 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio); // Left Leg
-
- // copy second layer
- 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);
- if ($alex) {
- imagecopy($dest, $src, $half_width + 1 * $ratio, 8 * $ratio, 59 * $ratio, 52 * $ratio, 3 * $ratio, 12 * $ratio);
- } else {
- imagecopy($dest, $src, $half_width + 0 * $ratio, 8 * $ratio, 60 * $ratio, 52 * $ratio, 4 * $ratio, 12 * $ratio);
- }
- 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);
- } else {
- static::imageflip($dest, $src, $half_width + 0 * $ratio, 8 * $ratio, 52 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio);
- static::imageflip($dest, $src, $half_width + 4 * $ratio, 20 * $ratio, 12 * $ratio, 20 * $ratio, 4 * $ratio, 12 * $ratio);
- }
- }
-
- $width = ($side == 'both') ? $height / 32 * (32 + $gap) : $height / 2;
-
- $fullsize = imagecreatetruecolor($width, $height);
- 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;
- }
-
- /**
- * 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(string $binary, int $height, $fillWidth = 0, $fillHeight = 0)
- {
- $src = imagecreatefromstring($binary);
- $ratio = imagesx($src) / 64;
- $width = $height / 16 * 10;
-
- $dest = imagecreatetruecolor($width, $height);
- imagesavealpha($dest, true);
- $transparent = imagecolorallocatealpha($dest, 255, 255, 255, 127);
- imagefill($dest, 0, 0, $transparent);
- imagecopyresized($dest, $src, 0, 0, $ratio, $ratio, $width, $height, imagesx($src) * 10 / 64, imagesy($src) * 16 / 32);
-
- imagedestroy($src);
- if ($fillWidth == 0 || $fillHeight == 0) {
- return $dest;
- }
-
- $filled = imagecreatetruecolor($fillWidth, $fillHeight);
- imagesavealpha($filled, true);
- $transparent = imagecolorallocatealpha($filled, 255, 255, 255, 127);
- imagefill($filled, 0, 0, $transparent);
- imagecopyresized($filled, $dest, ($fillWidth - $width) / 2, ($fillHeight - $height) / 2, 0, 0, $width, $height, $width, $height);
-
- imagedestroy($dest);
-
- return $filled;
- }
-
- /**
- * 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;
-
- imagecopyresampled($result, $img, $rx, $ry, ($x + $size_x - 1), $y, $size_x, $size_y, 0 - $size_x, $size_y);
- }
-}
diff --git a/app/helpers.php b/app/helpers.php
index 0068690e..7b9f70bc 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -82,16 +82,3 @@ if (!function_exists('option_localized')) {
return option($key.'_'.config('app.locale'), option($key));
}
}
-
-if (!function_exists('png')) {
- function png($resource)
- {
- ob_start();
- imagepng($resource);
- $image = ob_get_contents();
- ob_end_clean();
- imagedestroy($resource);
-
- return $image;
- }
-}
diff --git a/composer.json b/composer.json
index 0b6480da..f48d6d04 100644
--- a/composer.json
+++ b/composer.json
@@ -15,6 +15,7 @@
"ext-zip": "*",
"blessing/filter": "^1.0",
"blessing/rejection": "^1.0",
+ "blessing/texture-renderer": "^0.1.1",
"composer/ca-bundle": "^1.2",
"composer/semver": "^1.4",
"doctrine/dbal": "^2.9",
diff --git a/composer.lock b/composer.lock
index 56c8760b..a70ccc5a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "7fffc88319cd5ed6dbadbf6b52564465",
+ "content-hash": "938107f4ff375749aa599b1e4069f2d2",
"packages": [
{
"name": "blessing/filter",
@@ -98,6 +98,42 @@
"description": "Rejection is an object that indicates you are rejecting.",
"time": "2019-12-31T09:26:32+00:00"
},
+ {
+ "name": "blessing/texture-renderer",
+ "version": "0.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bs-community/texture-renderer.git",
+ "reference": "26dc3d8c516f8bd106a41ff3d534cbe2652a1753"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bs-community/texture-renderer/zipball/26dc3d8c516f8bd106a41ff3d534cbe2652a1753",
+ "reference": "26dc3d8c516f8bd106a41ff3d534cbe2652a1753",
+ "shasum": ""
+ },
+ "require": {
+ "intervention/image": "^2.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Blessing\\": "src/Blessing"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Pig Fang",
+ "email": "g-plane@hotmail.com"
+ }
+ ],
+ "description": "Minecraft texture renderer.",
+ "time": "2020-01-10T08:35:52+00:00"
+ },
{
"name": "composer/ca-bundle",
"version": "1.2.5",
@@ -5190,6 +5226,7 @@
"psr",
"psr-7"
],
+ "abandoned": "laminas/laminas-diactoros",
"time": "2019-11-13T19:16:13+00:00"
}
],
diff --git a/resources/assets/src/components/ApplyToPlayerDialog.vue b/resources/assets/src/components/ApplyToPlayerDialog.vue
index 20c23cc8..7e926608 100644
--- a/resources/assets/src/components/ApplyToPlayerDialog.vue
+++ b/resources/assets/src/components/ApplyToPlayerDialog.vue
@@ -92,7 +92,7 @@ export default {
}
},
avatarUrl(player) {
- return `${blessing.base_url}/avatar/${player.tid_skin}/35`
+ return `${blessing.base_url}/avatar/${player.tid_skin}?size=35`
},
},
}
diff --git a/resources/assets/src/components/ClosetItem.vue b/resources/assets/src/components/ClosetItem.vue
index d0e52c80..0bb1cb8c 100644
--- a/resources/assets/src/components/ClosetItem.vue
+++ b/resources/assets/src/components/ClosetItem.vue
@@ -76,7 +76,7 @@ export default {
},
computed: {
previewLink() {
- return `${blessing.base_url}/preview/${this.tid}.png`
+ return `${blessing.base_url}/preview/${this.tid}?height=150`
},
linkToSkinlib() {
return `${blessing.base_url}/skinlib/show/${this.tid}`
diff --git a/resources/assets/src/components/SkinLibItem.vue b/resources/assets/src/components/SkinLibItem.vue
index 601ea1c5..7c428717 100644
--- a/resources/assets/src/components/SkinLibItem.vue
+++ b/resources/assets/src/components/SkinLibItem.vue
@@ -60,7 +60,7 @@ export default {
return `${blessing.base_url}/skinlib/show/${this.tid}`
},
urlToPreview() {
- return `${blessing.base_url}/preview/${this.tid}.png`
+ return `${blessing.base_url}/preview/${this.tid}?height=150`
},
likeActionText() {
if (this.anonymous) {
@@ -102,6 +102,8 @@ export default {
.texture-img
background #eff1f0
+ img
+ height 210px
.btn-like
color #6c757d
diff --git a/resources/assets/src/views/admin/Players.vue b/resources/assets/src/views/admin/Players.vue
index 82e56f61..48f4b8c1 100644
--- a/resources/assets/src/views/admin/Players.vue
+++ b/resources/assets/src/views/admin/Players.vue
@@ -36,13 +36,13 @@
v-if="props.row.tid_skin"
:href="`${baseUrl}/skinlib/show/${props.row.tid_skin}`"
>
-
+
-
+
diff --git a/resources/assets/src/views/skinlib/Show.vue b/resources/assets/src/views/skinlib/Show.vue
index d6bb0a8c..89d8daca 100644
--- a/resources/assets/src/views/skinlib/Show.vue
+++ b/resources/assets/src/views/skinlib/Show.vue
@@ -312,7 +312,7 @@ export default {
},
download() {
const a = document.createElement('a')
- a.href = `${this.baseUrl}/raw/${this.tid}.png`
+ a.href = `${this.baseUrl}/raw/${this.tid}`
a.download = `${this.name}.png`
const event = new MouseEvent('click')
a.dispatchEvent(event)
diff --git a/resources/assets/src/views/user/Players.vue b/resources/assets/src/views/user/Players.vue
index 54551957..3967b112 100644
--- a/resources/assets/src/views/user/Players.vue
+++ b/resources/assets/src/views/user/Players.vue
@@ -75,7 +75,7 @@
@@ -86,7 +86,7 @@
diff --git a/resources/assets/tests/components/ApplyToPlayerDialog.test.ts b/resources/assets/tests/components/ApplyToPlayerDialog.test.ts
index 0ba2b8a5..55612143 100644
--- a/resources/assets/tests/components/ApplyToPlayerDialog.test.ts
+++ b/resources/assets/tests/components/ApplyToPlayerDialog.test.ts
@@ -48,5 +48,5 @@ test('compute avatar URL', () => {
// eslint-disable-next-line camelcase
const wrapper = mount(ApplyToPlayerDialog)
const { avatarUrl } = wrapper.vm
- expect(avatarUrl({ tid_skin: 1 })).toBe('/avatar/1/35')
+ expect(avatarUrl({ tid_skin: 1 })).toBe('/avatar/1?size=35')
})
diff --git a/resources/assets/tests/components/ClosetItem.test.ts b/resources/assets/tests/components/ClosetItem.test.ts
index d2181573..ccd50dfd 100644
--- a/resources/assets/tests/components/ClosetItem.test.ts
+++ b/resources/assets/tests/components/ClosetItem.test.ts
@@ -17,7 +17,7 @@ function factory(opt = {}) {
test('computed values', () => {
const wrapper = mount(ClosetItem, { propsData: factory() })
- expect(wrapper.find('img').attributes('src')).toBe('/preview/1.png')
+ expect(wrapper.find('img').attributes('src')).toBe('/preview/1?height=150')
expect(
wrapper
.findAll('.dropdown-item')
diff --git a/resources/assets/tests/components/SkinLibItem.test.ts b/resources/assets/tests/components/SkinLibItem.test.ts
index fedc2357..fc78fd6c 100644
--- a/resources/assets/tests/components/SkinLibItem.test.ts
+++ b/resources/assets/tests/components/SkinLibItem.test.ts
@@ -11,7 +11,7 @@ test('urls', () => {
propsData: { tid: 1 },
})
expect(wrapper.find('a').attributes('href')).toBe('/skinlib/show/1')
- expect(wrapper.find('img').attributes('src')).toBe('/preview/1.png')
+ expect(wrapper.find('img').attributes('src')).toBe('/preview/1?height=150')
})
test('render basic information', () => {
diff --git a/resources/assets/tests/views/admin/Players.test.ts b/resources/assets/tests/views/admin/Players.test.ts
index 13f5580f..8d923641 100644
--- a/resources/assets/tests/views/admin/Players.test.ts
+++ b/resources/assets/tests/views/admin/Players.test.ts
@@ -45,7 +45,7 @@ test('change texture', async () => {
)
modal.vm.$emit('confirm')
await flushPromises()
- expect(wrapper.html()).toContain('/preview/5/64')
+ expect(wrapper.html()).toContain('/preview/5?height=64')
})
test('change player name', async () => {
diff --git a/resources/assets/tests/views/user/Closet.test.ts b/resources/assets/tests/views/user/Closet.test.ts
index 3630a1e3..8d854923 100644
--- a/resources/assets/tests/views/user/Closet.test.ts
+++ b/resources/assets/tests/views/user/Closet.test.ts
@@ -210,7 +210,7 @@ test('apply texture', async () => {
button.trigger('click')
await flushPromises()
expect(wrapper.find('input[type="radio"]').attributes('value')).toBe('1')
- expect(wrapper.find('.model-label > img').attributes('src')).toBe('/avatar/10/35')
+ expect(wrapper.find('.model-label > img').attributes('src')).toBe('/avatar/10?size=35')
expect(wrapper.find('.modal-body').text()).toContain('name')
jest.runAllTimers()
})
diff --git a/resources/assets/tests/views/user/Players.test.ts b/resources/assets/tests/views/user/Players.test.ts
index 4d76180b..377afdc1 100644
--- a/resources/assets/tests/views/user/Players.test.ts
+++ b/resources/assets/tests/views/user/Players.test.ts
@@ -76,10 +76,10 @@ test('click to preview player', async () => {
expect(Vue.prototype.$http.get).toBeCalledWith('/skinlib/info/2')
wrapper.find('[data-test="to2d"]').trigger('click')
- expect(wrapper.find('img').attributes('src')).toBe('/preview/2/64')
+ expect(wrapper.find('img').attributes('src')).toBe('/preview/2?height=128')
wrapper.find('tbody > tr:nth-child(4)').trigger('click')
await flushPromises()
- expect(wrapper.find('img').attributes('src')).toBe('/preview/5/64')
+ expect(wrapper.find('img').attributes('src')).toBe('/preview/5?height=128')
})
test('change player name', async () => {
diff --git a/routes/static.php b/routes/static.php
index 5e7c1851..861cd323 100644
--- a/routes/static.php
+++ b/routes/static.php
@@ -7,10 +7,10 @@ Route::group(['middleware' => 'player'], function () {
Route::get('/textures/{hash}', 'TextureController@texture');
-Route::get('/avatar/player/{size}/{name}.png', 'TextureController@avatarByPlayer');
-Route::get('/avatar/user/{uid}/{size?}', 'TextureController@avatar');
-Route::get('/avatar/{tid}/{size?}', 'TextureController@avatarByTid');
+Route::get('/avatar/player/{name}', 'TextureController@avatarByPlayer');
+Route::get('/avatar/user/{uid}', 'TextureController@avatarByUser');
+Route::get('/avatar/{tid}', 'TextureController@avatarByTexture');
-Route::get('/raw/{tid}.png', 'TextureController@raw');
+Route::get('/raw/{tid}', 'TextureController@raw');
-Route::get('/preview/{tid}/{size?}', 'TextureController@preview');
+Route::get('/preview/{tid}', 'TextureController@preview');
diff --git a/tests/Fakes/Minecraft.php b/tests/Fakes/Minecraft.php
new file mode 100644
index 00000000..3543f4ce
--- /dev/null
+++ b/tests/Fakes/Minecraft.php
@@ -0,0 +1,26 @@
+swap(\Blessing\Minecraft::class, new Fakes\Minecraft());
+ }
+
public function testJson()
{
$steve = factory(Texture::class)->create();
@@ -67,161 +70,160 @@ class TextureControllerTest extends TestCase
])->assertHeader('Last-Modified');
}
- public function testTexture()
- {
- Storage::fake('textures');
- $steve = factory(Texture::class)->create();
- Storage::disk('textures')->put($steve->hash, '');
- $this->get('/textures/nope')
- ->assertSee('404');
-
- $this->get('/textures/'.$steve->hash)
- ->assertHeader('Content-Type', 'image/png')
- ->assertHeader('Last-Modified')
- ->assertHeader('ETag')
- ->assertHeader('Cache-Control', 'max-age='.option('cache_expire_time').', public')
- ->assertHeader('Accept-Ranges', 'bytes')
- ->assertHeader('Content-Length', Storage::disk('textures')->size($steve->hash))
- ->assertSuccessful();
-
- // Cache test
- $this->get('/textures/'.$steve->hash, [
- 'If-None-Match' => md5(''),
- ])->assertStatus(304);
- $this->get('/textures/'.$steve->hash, [
- 'If-Modified-Since' => Carbon::now()->addHours(1)->toRfc7231String(),
- ])->assertStatus(304);
-
- Storage::shouldReceive('disk')->with('textures')->andThrow(new Exception());
- $this->get('/textures/'.$steve->hash)->assertNotFound();
- }
-
- public function testAvatarByTid()
- {
- $this->get('/avatar/1')->assertHeader('Content-Type', 'image/png');
- }
-
- public function testAvatar()
- {
- Event::fake();
-
- Storage::fake('textures');
- $image = $this->get('/avatar/user/5/45')
- ->assertHeader('Content-Type', 'image/png')
- ->getContent();
- $image = Image::make($image);
- $this->assertEquals(45, $image->width());
- $this->assertEquals(45, $image->height());
-
- $steve = factory(Texture::class)->create();
- $png = base64_decode(\App\Http\Controllers\TextureController::getDefaultSteveSkin());
- Storage::disk('textures')->put($steve->hash, $png);
-
- $user = factory(User::class)->create(['avatar' => $steve->tid]);
-
- $mock = Mockery::mock('overload:Minecraft');
- $mock->shouldReceive('generateAvatarFromSkin')
- ->once()
- ->andReturn(imagecreatefromstring($png));
-
- $this->get('/avatar/user/'.$user->uid)
- ->assertHeader('Content-Type', 'image/png');
- Event::assertDispatched(\App\Events\GetAvatarPreview::class);
-
- Storage::shouldReceive('disk')->with('textures')->andThrow(new Exception());
- $image = $this->get('/avatar/user/'.$user->uid.'/45')
- ->assertHeader('Content-Type', 'image/png')
- ->getContent();
- $image = Image::make($image);
- $this->assertEquals(45, $image->width());
- $this->assertEquals(45, $image->height());
- }
-
public function testPreview()
{
- Event::fake();
- Storage::fake('textures');
+ $disk = Storage::fake('textures');
+
+ $this->get('/preview/0')->assertNotFound();
+
+ $skin = factory(Texture::class)->create();
+ $this->get('/preview/'.$skin->tid)->assertNotFound();
+
+ $disk->put($skin->hash, '');
+ $content = $this->get('/preview/'.$skin->tid)
+ ->assertHeader('Content-Type', 'image/png')
+ ->getContent();
+ $image = Image::make($content);
+ $this->assertEquals(80, $image->width());
+ $this->assertEquals(200, $image->height());
+ $this->assertTrue(Cache::has('preview-t'.$skin->tid));
- $steve = factory(Texture::class)->create();
$cape = factory(Texture::class, 'cape')->create();
-
- $this->get('/preview/0')
- ->assertHeader('Content-Type', 'image/png');
-
- $this->get("/preview/{$steve->tid}")
- ->assertHeader('Content-Type', 'image/png');
-
- $png = base64_decode(\App\Http\Controllers\TextureController::getDefaultSteveSkin());
- Storage::disk('textures')->put($steve->hash, $png);
- Storage::disk('textures')->put($cape->hash, $png);
-
- $mock = Mockery::mock('overload:Minecraft');
- $mock->shouldReceive('generatePreviewFromSkin')
- ->once()
- ->andReturn(imagecreatefromstring($png));
- $this->get("/preview/{$steve->tid}/56")
- ->assertHeader('Content-Type', 'image/png');
- Event::fake(\App\Events\GetSkinPreview::class);
-
- $mock->shouldReceive('generatePreviewFromCape')
- ->once()
- ->andReturn(imagecreatefromstring($png));
- $this->get("/preview/{$cape->tid}")
- ->assertHeader('Content-Type', 'image/png');
-
- Storage::shouldReceive('disk')->with('textures')->andThrow(new Exception());
- $this->get("/preview/{$steve->tid}")
- ->assertHeader('Content-Type', 'image/png');
+ $disk->put($cape->hash, '');
+ $content = $this->get('/preview/'.$cape->tid.'?height=100')->getContent();
+ $image = Image::make($content);
+ $this->assertEquals(50, $image->width());
+ $this->assertEquals(100, $image->height());
+ $this->assertTrue(Cache::has('preview-t'.$cape->tid));
}
public function testRaw()
{
- Storage::fake('textures');
- $steve = factory(Texture::class)->create();
- Storage::disk('textures')->put($steve->hash, '');
+ $disk = Storage::fake('textures');
+ $skin = factory(Texture::class)->create();
// Not found
- $this->get('/raw/0.png')
- ->assertNotFound()
- ->assertSee(trans('skinlib.non-existent'));
+ $this->get('/raw/0')->assertNotFound();
+
+ // Missing texture file
+ $this->get('/raw/'.$skin->tid)->assertNotFound();
// Success
- $this->get("/raw/{$steve->tid}.png")
- ->assertHeader('Content-Type', 'image/png');
-
- // Texture is deleted
- Storage::disk('textures')->delete($steve->hash);
- $this->get("/raw/{$steve->tid}.png")->assertNotFound();
+ $disk->put($skin->hash, '');
+ $this->get('/raw/'.$skin->tid)->assertHeader('Content-Type', 'image/png');
// Disallow downloading texture directly
option(['allow_downloading_texture' => false]);
- $this->get("/raw/{$steve->tid}.png")->assertNotFound();
+ $this->get('/raw/'.$skin->tid)->assertForbidden();
+ }
+
+ public function testTexture()
+ {
+ $disk = Storage::fake('textures');
+ $skin = factory(Texture::class)->create();
+
+ $this->get('/textures/'.$skin->hash)->assertNotFound();
+
+ $disk->put($skin->hash, '');
+ $this->get('/textures/'.$skin->hash)
+ ->assertHeader('Content-Type', 'image/png');
}
public function testAvatarByPlayer()
{
- Storage::fake('textures');
+ $disk = Storage::fake('textures');
- // No such player.
- $this->get('/avatar/player/1/abc.png')->assertNotFound();
+ $this->get('/avatar/player/abc')->assertNotFound();
- // No such texture.
$player = factory(Player::class)->create();
- $this->get("/avatar/player/1/{$player->name}.png")->assertNotFound();
+ $this->get('/avatar/player/'.$player->name)
+ ->assertSuccessful()
+ ->assertHeader('Content-Type', 'image/png');
$texture = factory(Texture::class)->create();
+ $disk->put($texture->hash, '');
$player->tid_skin = $texture->tid;
$player->save();
- $this->get("/avatar/player/1/{$player->name}.png")->assertNotFound();
+ $image = $this->get('/avatar/player/'.$player->name)
+ ->assertSuccessful()
+ ->assertHeader('Content-Type', 'image/png')
+ ->getContent();
+ $image = Image::make($image);
+ $this->assertEquals(100, $image->width());
+ $this->assertEquals(100, $image->height());
- // Success
- $png = base64_decode(\App\Http\Controllers\TextureController::getDefaultSteveSkin());
- Storage::disk('textures')->put($texture->hash, $png);
- Mockery::mock('overload:Minecraft')
- ->shouldReceive('generateAvatarFromSkin')
- ->once()
- ->andReturn(imagecreatefromstring($png));
- $this->get("/avatar/player/20/{$player->name}.png")->assertSuccessful();
- Storage::disk('textures')->delete($texture->hash);
+ $image = $this->get('/avatar/player/'.$player->name.'?size=50')->getContent();
+ $image = Image::make($image);
+ $this->assertEquals(50, $image->width());
+ $this->assertEquals(50, $image->height());
+ }
+
+ public function testAvatarByUser()
+ {
+ $disk = Storage::fake('textures');
+
+ $this->get('/avatar/user/0')
+ ->assertSuccessful()
+ ->assertHeader('Content-Type', 'image/png');
+
+ $user = factory(User::class)->create();
+ $this->get('/avatar/user/'.$user->uid)
+ ->assertSuccessful()
+ ->assertHeader('Content-Type', 'image/png');
+
+ $texture = factory(Texture::class)->create();
+ $disk->put($texture->hash, '');
+ $user->avatar = $texture->tid;
+ $user->save();
+ $image = $this->get('/avatar/user/'.$user->uid)
+ ->assertSuccessful()
+ ->assertHeader('Content-Type', 'image/png')
+ ->getContent();
+ $image = Image::make($image);
+ $this->assertEquals(100, $image->width());
+ $this->assertEquals(100, $image->height());
+
+ $image = $this->get('/avatar/user/'.$user->uid.'?size=50')->getContent();
+ $image = Image::make($image);
+ $this->assertEquals(50, $image->width());
+ $this->assertEquals(50, $image->height());
+ }
+
+ public function testAvatarByTexture()
+ {
+ $disk = Storage::fake('textures');
+
+ $image = $this->get('/avatar/0')
+ ->assertSuccessful()
+ ->assertHeader('Content-Type', 'image/png')
+ ->getContent();
+ $image = Image::make($image);
+ $this->assertEquals(100, $image->width());
+ $this->assertEquals(100, $image->height());
+
+ $texture = factory(Texture::class)->create();
+ $image = $this->get('/avatar/'.$texture->tid)
+ ->assertSuccessful()
+ ->assertHeader('Content-Type', 'image/png')
+ ->getContent();
+ $image = Image::make($image);
+ $this->assertEquals(100, $image->width());
+ $this->assertEquals(100, $image->height());
+
+ $disk->put($texture->hash, '');
+ $image = $this->get('/avatar/'.$texture->tid)
+ ->assertSuccessful()
+ ->assertHeader('Content-Type', 'image/png')
+ ->getContent();
+ $image = Image::make($image);
+ $this->assertEquals(100, $image->width());
+ $this->assertEquals(100, $image->height());
+ $this->assertTrue(Cache::has('avatar-2d-t'.$texture->tid.'-s100'));
+
+ $image = $this->get('/avatar/'.$texture->tid.'?size=50')->getContent();
+ $image = Image::make($image);
+ $this->assertEquals(50, $image->width());
+ $this->assertEquals(50, $image->height());
+ $this->assertTrue(Cache::has('avatar-2d-t'.$texture->tid.'-s50'));
}
}
diff --git a/tests/HttpTest/ViewTest/ComposersTest/UserMenuComposerTest.php b/tests/HttpTest/ViewTest/ComposersTest/UserMenuComposerTest.php
index d2c29d8b..d819ad06 100644
--- a/tests/HttpTest/ViewTest/ComposersTest/UserMenuComposerTest.php
+++ b/tests/HttpTest/ViewTest/ComposersTest/UserMenuComposerTest.php
@@ -11,12 +11,12 @@ class UserMenuComposerTest extends TestCase
$user = factory(User::class)->make();
$this->actingAs($user)
->get('/')
- ->assertSee(url('/avatar/user/'.$user->uid.'/25'));
+ ->assertSee(url('/avatar/user/'.$user->uid.'?size=25'));
$this->actingAs($user)
->get('/skinlib')
- ->assertSee(url('/avatar/user/'.$user->uid.'/25'));
+ ->assertSee(url('/avatar/user/'.$user->uid.'?size=25'));
$this->actingAs($user)
->get('/user')
- ->assertSee(url('/avatar/user/'.$user->uid.'/25'));
+ ->assertSee(url('/avatar/user/'.$user->uid.'?size=25'));
}
}
diff --git a/tests/HttpTest/ViewTest/ComposersTest/UserPanelComposerTest.php b/tests/HttpTest/ViewTest/ComposersTest/UserPanelComposerTest.php
index c9e04daa..38ad06d6 100644
--- a/tests/HttpTest/ViewTest/ComposersTest/UserPanelComposerTest.php
+++ b/tests/HttpTest/ViewTest/ComposersTest/UserPanelComposerTest.php
@@ -12,7 +12,7 @@ class UserPanelComposerTest extends TestCase
$user = factory(User::class)->make();
$this->actingAs($user);
- $this->get('/user')->assertSee(url('/avatar/user/'.$user->uid.'/45'));
+ $this->get('/user')->assertSee(url('/avatar/user/'.$user->uid.'?size=45'));
}
public function testBadges()
diff --git a/tests/ListenersTest/CacheAvatarPreviewTest.php b/tests/ListenersTest/CacheAvatarPreviewTest.php
deleted file mode 100644
index f7293e86..00000000
--- a/tests/ListenersTest/CacheAvatarPreviewTest.php
+++ /dev/null
@@ -1,34 +0,0 @@
- true]);
- $provider = new \App\Providers\EventServiceProvider(app());
- $provider->boot();
- Storage::fake('textures');
-
- $texture = factory(Texture::class)->create();
- Storage::disk('textures')
- ->putFileAs('.', UploadedFile::fake()->image($texture->hash), $texture->hash);
- $mock = Mockery::mock('overload:Minecraft');
- $mock->shouldReceive('generateAvatarFromSkin')->andReturn(imagecreatetruecolor(1, 1));
-
- event(new GetAvatarPreview($texture, 45));
- $this->assertTrue(Cache::has("avatar-{$texture->tid}-45"));
- }
-}
diff --git a/tests/ListenersTest/CacheSkinPreviewTest.php b/tests/ListenersTest/CacheSkinPreviewTest.php
deleted file mode 100644
index 8c499a21..00000000
--- a/tests/ListenersTest/CacheSkinPreviewTest.php
+++ /dev/null
@@ -1,42 +0,0 @@
- true]);
- $provider = new \App\Providers\EventServiceProvider(app());
- $provider->boot();
- Storage::fake('textures');
-
- $skin = factory(Texture::class)->create();
- Storage::disk('textures')
- ->putFileAs('.', UploadedFile::fake()->image($skin->hash), $skin->hash);
- $mock = Mockery::mock('overload:Minecraft');
- $mock->shouldReceive('generatePreviewFromSkin')->andReturn(imagecreatetruecolor(1, 1));
-
- event(new GetSkinPreview($skin, 45));
- $this->assertTrue(Cache::has("preview-{$skin->tid}-45"));
-
- $cape = factory(Texture::class, 'cape')->create();
- Storage::disk('textures')
- ->putFileAs('.', UploadedFile::fake()->image($cape->hash), $cape->hash);
- $mock->shouldReceive('generatePreviewFromCape')->andReturn(imagecreatetruecolor(1, 1));
-
- event(new GetSkinPreview($cape, 45));
- $this->assertTrue(Cache::has("preview-{$cape->tid}-45"));
- }
-}
diff --git a/tests/ServicesTest/MinecraftTest.php b/tests/ServicesTest/MinecraftTest.php
deleted file mode 100644
index 92ffdc92..00000000
--- a/tests/ServicesTest/MinecraftTest.php
+++ /dev/null
@@ -1,112 +0,0 @@
-fileFactory = new FileFactory();
- }
-
- public function testGenerateAvatarFromSkin()
- {
- $file = $this->fileFactory->image('skin.png');
-
- imagepng(imagecreatetruecolor(64, 32), $file->path());
- $avatar = Minecraft::generateAvatarFromSkin(file_get_contents($file->path()), 50);
- $this->assertEquals(50, imagesx($avatar));
- $this->assertEquals(50, imagesy($avatar));
-
- imagepng(imagecreatetruecolor(128, 64), $file->path());
- $avatar = Minecraft::generateAvatarFromSkin(file_get_contents($file->path()), 50);
- $this->assertEquals(50, imagesx($avatar));
- $this->assertEquals(50, imagesy($avatar));
-
- $avatar = Minecraft::generateAvatarFromSkin(
- base64_decode(TextureController::getDefaultSteveSkin()), 50, 'l'
- );
- $this->assertEquals(50, imagesx($avatar));
- $this->assertEquals(50, imagesy($avatar));
- }
-
- public function testGeneratePreviewFromSkin()
- {
- $file = $this->fileFactory->image('skin.png');
-
- imagepng(imagecreatetruecolor(64, 32), $file->path());
- $preview = Minecraft::generatePreviewFromSkin(
- file_get_contents($file->path()), 50, false, 'front'
- );
- $this->assertEquals(25, imagesx($preview));
- $this->assertEquals(50, imagesy($preview));
-
- imagepng(imagecreatetruecolor(64, 32), $file->path());
- $preview = Minecraft::generatePreviewFromSkin(
- file_get_contents($file->path()),
- 50,
- true, // Alex model
- 'both',
- 4
- );
- $this->assertEquals(56, imagesx($preview));
- $this->assertEquals(50, imagesy($preview));
-
- imagepng(imagecreatetruecolor(64, 64), $file->path());
- $preview = Minecraft::generatePreviewFromSkin(
- file_get_contents($file->path()),
- 100,
- true, // Alex model
- 'both',
- 8
- );
- $this->assertEquals(125, imagesx($preview));
- $this->assertEquals(100, imagesy($preview));
-
- imagepng(imagecreatetruecolor(128, 64), $file->path());
- $preview = Minecraft::generatePreviewFromSkin(file_get_contents($file->path()), 50);
- $this->assertEquals(56, imagesx($preview));
- $this->assertEquals(50, imagesy($preview));
-
- imagepng(imagecreatetruecolor(128, 128), $file->path());
- $preview = Minecraft::generatePreviewFromSkin(file_get_contents($file->path()), 50);
- $this->assertEquals(56, imagesx($preview));
- $this->assertEquals(50, imagesy($preview));
-
- $preview = Minecraft::generatePreviewFromSkin(
- base64_decode(TextureController::getDefaultSteveSkin()),
- 50,
- false,
- 'back'
- );
- $this->assertEquals(25, imagesx($preview));
- $this->assertEquals(50, imagesy($preview));
- }
-
- public function testGeneratePreviewFromCape()
- {
- $file = $this->fileFactory->image('cape.png');
-
- imagepng(imagecreatetruecolor(128, 64), $file->path());
- $preview = Minecraft::generatePreviewFromCape(file_get_contents($file->path()), 64);
- $this->assertEquals(40, imagesx($preview));
- $this->assertEquals(64, imagesy($preview));
-
- imagepng(imagecreatetruecolor(128, 64), $file->path());
- $preview = Minecraft::generatePreviewFromCape(
- file_get_contents($file->path()),
- 64,
- 281,
- 250
- );
- $this->assertEquals(281, imagesx($preview));
- $this->assertEquals(250, imagesy($preview));
- }
-}