Last active
March 14, 2023 17:18
-
-
Save Gkiokan/26ef3ed0a6a7e34f3b00e1c5d2adf208 to your computer and use it in GitHub Desktop.
Image Proxy Controller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Controllers; | |
use Illuminate\Http\Request; | |
use Intervention\Image\Facades\Image; | |
use Storage; | |
use Http; | |
use Cache; | |
use Log; | |
class ProxyController extends Controller | |
{ | |
public $forceWidth = null; | |
public $forceCompression = null; | |
public $folder = 'images/'; | |
/** | |
* Entry Points | |
*/ | |
public function image(Request $r, $url=null){ | |
return $this->proxy($r, $url); | |
} | |
public function cover(Request $r, $url=null){ | |
$this->forceWidth = 300; | |
$this->forceCompression = 50; | |
$this->folder = 'covers/'; | |
return $this->proxy($r, $url); | |
} | |
public function actor(Request $r, $url=null){ | |
$this->forceWidth = 200; | |
$this->forceCompression = 40; | |
$this->folder = 'actors/'; | |
return $this->proxy($r, $url); | |
} | |
/** | |
* General Image Proxy | |
*/ | |
public function proxy(Request $request, $url=null){ | |
$imageUrl = $this->validateUrl($url); | |
// Check if the image already exists in the storage | |
$filename = basename($imageUrl); | |
$filename = hash('fnv1a32', $filename); | |
$filePath = $this->folder . $filename; | |
// return file if we have it already | |
if (Storage::disk('cdn')->exists($filePath)){ | |
$image = Storage::disk('cdn')->get($filePath); | |
$mime = Storage::disk('cdn')->mimeType($filePath); | |
return response($image, 200)->header('Content-Type', $mime); | |
} | |
// Fetch the remote image | |
try { | |
$response = Http::get($imageUrl); | |
$contentType = $response->header('Content-Type'); | |
} | |
catch( \Exception $e){ | |
return response($e->getMessage(), 400); | |
} | |
// url check | |
if( strstr($response->handlerStats()['url'], 'nopicture') ) | |
return response('Probably no-image', 404); | |
// check mime type | |
if (!str_starts_with($contentType, 'image/')) | |
return response('Invalid content type', 400); | |
// put this in a job | |
$this->createJob($imageUrl, $filePath); | |
// Return the image as a response directly for now | |
return response($response->body())->header('Content-Type', $contentType); | |
} | |
function validateUrl($url=null){ | |
if( !$url ) | |
return response('Invalid URL', 400); | |
if( strstr($url, 'nopicture') ) | |
return response(null, 404); | |
if (!filter_var($url, FILTER_VALIDATE_URL)) { | |
return response('Invalid URL', 400); | |
} | |
$urlComponents = parse_url($url); | |
if (!isset($urlComponents['scheme']) || !in_array($urlComponents['scheme'], ['http', 'https'])) { | |
return response('Invalid URL scheme', 400); | |
} | |
$host = $urlComponents['host']; | |
$isValidDomain = Cache::remember('dns:' . $host, 60 * 60 * 30, function () use ($host) { | |
return checkdnsrr($host, 'A'); | |
}); | |
if (!$isValidDomain) { | |
return response('Invalid domain', 400); | |
} | |
return $url; | |
} | |
public function resizeImage($body=null, $forceWidth=null, $forceCompression=null){ | |
$image = Image::make($body); | |
$size = strlen($body); // $image->filesize() // not working because we have the stream and not the file | |
$softLimit = 1000000; // 1 * 1024 * 1024; | |
$limit = 4000000; // 4 * 1024 * 1024; | |
$hardLimit = 8000000; // 8 * 1024 * 1024; | |
$compression = 60; | |
$maxWidthPercent = 60; | |
$factor = 1; | |
// debugging | |
// dd($image, $image->getWidth(), $size, $softLimit, $size > $softLimit); | |
// if limit exceeds | |
// if ($size > $softLimit): | |
if ( true ): | |
/** | |
* Prepare the Compression logic | |
*/ | |
if ($size > $softLimit): | |
$compression = 40; | |
$maxWidthPercent = 50; | |
$factor = 0.9; | |
endif; | |
if( $size > $hardLimit ): | |
$factor = 0.6; | |
endif; | |
// resize the image based on the filesize | |
$width = $image->getWidth(); | |
$newWidth = round($width * ($maxWidthPercent / 100)); | |
// set the compression higher for 4MB+ | |
if( $size > $limit ) | |
$compression = 25; | |
$mimeToExtension = [ | |
'image/jpeg' => 'jpg', | |
'image/png' => 'png', | |
'image/gif' => 'gif', | |
'image/webp' => 'webp', | |
'image/svg+xml' => 'svg', | |
'image/bmp' => 'bmp', | |
'image/tiff' => 'tiff', | |
// add more MIME types as keys and their corresponding extensions as values | |
]; | |
$extension = $mimeToExtension[$image->mime()] ?? false; | |
// simple check | |
if( $extension == false ) | |
return response('Not supported file format', 400); | |
// Override values if given | |
if( $forceWidth ) | |
$newWidth = $forceWidth; | |
if( $forceCompression ) | |
$compression = $forceCompression * $factor; | |
/** | |
* Do the Magic here | |
*/ | |
Log::info("Image Resize and patch to $newWidth Compression $compression"); | |
// resize | |
$image->resize($newWidth, null, function ($constraint) { | |
$constraint->aspectRatio(); | |
$constraint->upsize(); | |
}); | |
// encode as WebP if original image is JPEG or PNG | |
if ($image->mime() === 'image/jpeg' || $image->mime() === 'image/png') | |
$image->encode('webp', $compression); | |
// otherwise, encode using the original extension | |
else | |
$image->encode($extension, $compression); | |
endif; | |
return $image->stream()->__toString(); | |
} | |
public function createJob($imageUrl, $filePath){ | |
dispatch( function() use ($imageUrl, $filePath){ | |
Log::info("Patching CDN Image \n URL: $imageUrl \n Width: $this->forceWidth \n Compression: $this->forceCompression"); | |
// catch the image again | |
$response = Http::get($imageUrl); | |
// url check | |
if( strstr($response->handlerStats()['url'], 'nopicture') ) | |
return response("Probably a No-Picture file", 400); | |
// No Image size 14282 | |
if( strlen($response->body()) == 14282) | |
return response("Probably a No-Picture file", 400); | |
// Resize the image if it's larger than 8MB while maintaining its proportions | |
$image = $this->resizeImage($response->body(), $this->forceWidth, $this->forceCompression); | |
// Save the image to the application | |
Storage::disk('cdn')->put($filePath, $image); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment