diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..6e7859b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Nick de Kruijk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c4dda9 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +[![Latest Stable Version](https://poser.pugx.org/nickdekruijk/imageresize/v/stable)](https://packagist.org/packages/nickdekruijk/imageresize) +[![Latest Unstable Version](https://poser.pugx.org/nickdekruijk/imageresize/v/unstable)](https://packagist.org/packages/nickdekruijk/imageresize) +[![Monthly Downloads](https://poser.pugx.org/nickdekruijk/imageresize/d/monthly)](https://packagist.org/packages/nickdekruijk/imageresize) +[![Total Downloads](https://poser.pugx.org/nickdekruijk/imageresize/downloads)](https://packagist.org/packages/nickdekruijk/imageresize) +[![License](https://poser.pugx.org/nickdekruijk/imageresize/license)](https://packagist.org/packages/nickdekruijk/imageresize) + +# ImageResize for Laravel +A simple, yet efficient solution for image resizing and caching with Laravel. +Based on my previous imageresize package, now renamed to [nickdekruijk/imageresize-legacy](https://github.com/nickdekruijk/imageresize-legacy). + +## Installation +To install the package use + +`composer require nickdekruijk/imageresize` + +## Configuration +After installing for the first time publish the config file with + +`php artisan vendor:publish --tag=config --provider="NickDeKruijk\ImageResize\ServiceProvider"` + +A default config file called `imageresize.php` will be available in your Laravel `app/config` folder. See this file for more details. + +## How does it work +Let's assume you have an image in /public/media/images/test.jpg and a template called 'thumbnail'. And have set the imageresize.route config to 'media/resized'. +Referring to /media/resized/thumbnail/images/test.jpg will trigger the imageresize route in laravel since the file doesn't exist. Imageresize then creates the resized image and saves it as /public/media/resized/thumbnail/images/test.jpg +So the next time you refer to /media/resized/thumbnail/images/test.jpg the file does exist and the image is served without triggering any php/laravel code for optimal performance. + +## Drawbacks +There is however one disadvantage: if the original image is edited or removed the resized file will still remain the same since referring to it doesn't trigger the imageresize package. You will have to manually delete it or use the `php artisan imageresize:delete` command. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..46c2ea3 --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "nickdekruijk/imageresize", + "description": "A simple, yet efficient solution for image resizing and caching with Laravel", + "license": "MIT", + "keywords": [ "image", "laravel", "resize", "php", "cache" ], + "homepage": "http://www.nickdekruijk.nl/packages/imageresize", + "authors": [ + { + "name": "Nick de Kruijk", + "email": "git@nickdekruijk.nl" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=5.6.0", + }, + "extra": { + "laravel": { + "providers": [ + "NickDeKruijk\\ImageResize\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "NickDeKruijk\\ImageResize\\": "src/" + } + } +} diff --git a/src/ResizeController.php b/src/ResizeController.php new file mode 100644 index 0000000..130075f --- /dev/null +++ b/src/ResizeController.php @@ -0,0 +1,174 @@ + $array) { + self::deleteDir(self::path($template)); + } + } + } + + private static function deleteDir($dir) + { + if (!file_exists($dir) || !is_dir($dir)) return false; + $h = opendir($dir); + while($f = readdir($h)) { + if ($f[0] != '.') + if (is_dir($dir . '/' . $f)) + self::deleteDir($dir .'/'. $f); + else { + echo 'Deleting ' . $dir.'/' . $f . chr(10); + unlink($dir . '/' . $f); + } + } + echo 'Deleting dir ' . $dir . chr(10); + rmdir($dir); + closedir($h); + } + + private function makepath($target) + { + $dir = explode('/', $target); + array_pop($dir); + $p = ''; + foreach($dir as $d) { + $p .= $d . '/'; + if (!file_exists($p)) { + mkdir($p) or $this->error('Unable to create ' . $p); + } + if (!is_dir($p)) { + $this->error('Not a directory: ' . $p); + } + if (!is_writable($p)) { + $this->error('Not writable: ' . $p); + } + } + } + + private function resize($template, $original, $target) + { + $originalSize = getimagesize($original) or $this->error($original . ' is not a valid image'); + $type = $originalSize['mime']; + $originalWidth = $originalSize[0]; + $originalHeigth = $originalSize[1]; + + # Create new GD instance based on image type + if ($type == 'image/gif') { + $image_a = imagecreatefromgif($original) or $this->error(); + } elseif ($type == 'image/png') { + $image_a = imagecreatefrompng($original) or $this->error(); + } else { + $image_a = imagecreatefromjpeg($original) or $this->error(); + } + + if ($template['type'] == 'crop') { + $targetWidth = $template['width']; + $targetHeigth = $originalHeigth * ($targetWidth / $originalWidth); + if ($targetHeigth < $template['height']) { + $targetHeigth = $template['height']; + $targetWidth=$originalWidth * ($targetHeigth / $originalHeigth); + } + $dst_img = imagecreatetruecolor($targetWidth, $targetHeigth); + imagealphablending($dst_img, false); + imagesavealpha($dst_img, true); + imagecopyresampled($dst_img, $image_a, 0, 0, 0, 0, $targetWidth, $targetHeigth, imagesx($image_a), imagesy($image_a)); + imagedestroy($image_a); + $image_p = imagecreatetruecolor($template['width'], $template['height']); + imagealphablending($image_p, false); + imagesavealpha($image_p, true); + imagecopy($image_p, $dst_img, 0, 0, round((imagesx($dst_img) - $template['width']) / 2), round((imagesy($dst_img) - $template['height']) / 2), $template['width'], $template['height']); + imagedestroy($dst_img); + } elseif ($template['type'] == 'fit') { + $ratio_orig = $originalWidth / $originalHeigth; + if ($template['width'] / $template['height'] > $ratio_orig) { + $template['width'] = $template['height'] * $ratio_orig; + } else { + $template['height'] = $template['width'] / $ratio_orig; + } + $image_p = imagecreatetruecolor($template['width'], $template['height']); + imagealphablending($image_p, false); + imagesavealpha($image_p, true); + imagecopyresampled($image_p, $image_a, 0, 0, 0, 0, $template['width'], $template['height'], $originalWidth, $originalHeigth); + imagedestroy($image_a); + } else { + $this->error('Invalid template type ' . $template['type']); + } + + # Add blur filter if needed + if (isset($template['blur']) && $template['blur'] > 0) { + for ($x = 1; $x <= $template['blur']; $x++) { + imagefilter($image_p, IMG_FILTER_GAUSSIAN_BLUR); + } + } + + # Add grayscale filter if needed + if (isset($template['grayscale']) && $template['grayscale']) { + imagefilter($image_p, IMG_FILTER_GRAYSCALE); + } + + $this->makepath($target); + + # Save the resized image in a variable + if ($type == 'image/gif') { + imagegif($image_p, $target) or $this->error('Write error'); + } elseif ($type == 'image/png') { + imagepng($image_p, $target) or $this->error('Write error'); + } else { + imagejpeg($image_p, $target, isset($template['quality']) ? $template['quality'] : config('imageresize.quality_jpeg')) or $this->error('Write error'); + } + + imagedestroy($image_p); + } + + public function make($template, $image) + { + if (config('imageresize.templates.'.$template)) { + + $target = rtrim(config('imageresize.route'), '/') . '/' . $template . '/' . $image; + $template = config('imageresize.templates.' . $template); + + $original = rtrim(config('imageresize.originals'), '/') . '/' . $image; + if (!file_exists($original)) { + $this->error($original . ' does not exist'); + } + if (is_dir($original)) { + $this->error($original . ' is a directory'); + } + + $this->resize($template, $original, $target); + + return redirect($target); + } else { + $this->error('Template ' . $template . ' not found'); + } + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php new file mode 100644 index 0000000..3808c9b --- /dev/null +++ b/src/ServiceProvider.php @@ -0,0 +1,34 @@ +publishes([ + __DIR__.'/config.php' => config_path('imageresize.php'), + ], 'config'); + $this->loadRoutesFrom(__DIR__.'/routes.php'); + if ($this->app->runningInConsole()) { + $this->commands([ + UserCommand::class, + ]); + } + } + + /** + * Register the application services. + * + * @return void + */ + public function register() + { + $this->mergeConfigFrom(__DIR__.'/config.php', 'imageresize'); + } +} diff --git a/src/UserCommand.php b/src/UserCommand.php new file mode 100644 index 0000000..db09104 --- /dev/null +++ b/src/UserCommand.php @@ -0,0 +1,46 @@ +description = 'Delete the resized images from the "'.config('imageresize.route').'" folder.'; + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $arg = $this->arguments(); + if ($arg['command'] == 'imageresize:delete') { + ResizeController::delete($arg['template']); + } + } +} diff --git a/src/config.php b/src/config.php new file mode 100644 index 0000000..e3aea58 --- /dev/null +++ b/src/config.php @@ -0,0 +1,89 @@ + 'media/resized', + + /* + |-------------------------------------------------------------------------- + | Originals folder + |-------------------------------------------------------------------------- + | + | The path to the original images. + | + */ + + 'originals' => 'media', + + /* + |-------------------------------------------------------------------------- + | Template presets + |-------------------------------------------------------------------------- + | + | type: crop or fit + | quality: jpeg quality in percentage (0 - 100) + | width: image width (or maximum width when type is 'fit') + | height: image height (or maximum height when type is 'fit') + | grayscale: when true apply IMG_FILTER_GRAYSCALE filter + | blur: use IMG_FILTER_GAUSSIAN_BLUR filter (higher value is stronger blur) + | + */ + + 'templates' => [ + 'thumbnail' => [ + 'type' => 'crop', + 'quality' => 70, + 'width' => 180, + 'height' => 180, + ], + 'medium' => [ + 'type' => 'crop', + 'quality' => 60, + 'width' => 612, + 'height' => 408, + ], + 'large' => [ + 'type' => 'fit', + 'quality' => 70, + 'width' => 1600, + 'height' => 1600, + ], + 'blur' => [ + 'type' => 'fit', + 'blur' => 30, + 'width' => 300, + 'height' => 200, + ], + 'gray' => [ + 'type' => 'fit', + 'grayscale' => true, + 'width' => 300, + 'height' => 200, + ], + ], + + /* + |-------------------------------------------------------------------------- + | quality_jpeg + |-------------------------------------------------------------------------- + | + | The default JPEG quality when template doesn't specify it + | + */ + + 'quality_jpeg' => 80, + +]; diff --git a/src/routes.php b/src/routes.php new file mode 100644 index 0000000..1aea6d6 --- /dev/null +++ b/src/routes.php @@ -0,0 +1,5 @@ + ['web']], function () { + Route::get(rtrim(config('imageresize.route'), '/').'/{template}/{image}', 'NickDeKruijk\ImageResize\ResizeController@make')->where('image', '(.*)'); +});