From da8088bcf2f78968e1387fe2fbe4e0915a98287b Mon Sep 17 00:00:00 2001 From: "gustavo.simoes" Date: Sun, 30 Aug 2020 18:49:25 -0300 Subject: [PATCH] v0.1.0 --- LICENCE | 21 +++ README.md | 183 ++++++++++++++++++++++++++ composer.json | 28 ++++ config/multiSizeImage.php | 66 ++++++++++ src/MultiSizeImage.php | 176 +++++++++++++++++++++++++ src/MultiSizeImageServiceProvider.php | 31 +++++ 6 files changed, 505 insertions(+) create mode 100644 LICENCE create mode 100644 README.md create mode 100644 composer.json create mode 100644 config/multiSizeImage.php create mode 100644 src/MultiSizeImage.php create mode 100644 src/MultiSizeImageServiceProvider.php diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..fa3e74e --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Gustavo Simões + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..863c5ed --- /dev/null +++ b/README.md @@ -0,0 +1,183 @@ +# Laravel Multi Size Image + +Laravel package to optimize and stored images in different sizes in order to load the appropriate one according to the screen size. + +## Pre-requisites +* To resize the images this package uses the [Intervention library](http://image.intervention.io/) which requires a image library like [GD](https://www.php.net/manual/en/book.image.php) or [ImageMagick](https://www.php.net/manual/en/book.imagick.php). +* To optimize the images this package uses [image-optimizer](https://github.com/spatie/image-optimizer) package which requires [optimizers](https://github.com/spatie/image-optimizer#optimization-tools) to be present in your system. This package uses [JpegOptim](http://freshmeat.sourceforge.net/projects/jpegoptim), [Optipng](http://optipng.sourceforge.net/) and [Pngquant 2](https://pngquant.org/) optimizers. + +## Installation +Require the package via Composer: + +``` +$ composer require guizoxxv/laravel-multi-size-image +``` + +## Configuration +Publish the package configuration file to your Laravel project to change the default behavior. + +``` +$ php artisan vendor:publish --provider="Guizoxxv\LaravelMultiSizeImage\MultiSizeImageServiceProvider" +``` + +A `config/multiSizeImage.php` file will be added in your project. + +## Usage + +**1. Instantiate** + +To apply Multi Size Image first you must create a instance of it. + +```php +use Guizoxxv\LaravelMultiSizeImage\MultiSizeImage; + +... + +$multiSizeImage = new MultiSizeImage(); +``` + +**2. Process image** + +Call the `processImage` method passing the file path as the first argument. + +```php +$filePath = Storage::path('folder/file.png'); + +$multiSizeImage->processImage($filePath); +``` + +> The file path must be absolute. + +The method returns an array of strings with the full path of the generated files. + +**2.1. Mime types** + +Only mime types defined in the `mime_types` array in the `config/multiSizeImage.php` file are considered. If a file with mime type not present is used, it is ignored and the method retuns `null`. + +> This package is configured to optimize `jpeg` and `png` images. Check the [Optimizing](#optimizing) section to learn how to optimize images with other mime types. + +**2.2. Output path** + +The default behavior is to create the resized image versions in the same path as the original's. To send the images to a different location you can provide the output path as a second optional parameter. + +```php +$multiSizeImage->processImage($filePath, $outputPath); +``` + +**2.3. Resizing** + +The resizable values are defined by the `sizes` array in the `config/multiSizeImage.php` file. This array has the keys as the size identification and the value as the size for the image be resized to. + +```php +'sizes' => [ + 'tb' => 150, + 'sm' => 300, + 'lg' => 1024, +] +``` + +Above are the default values. The biggest dimmension is considered when resizing and the aspect ratio is kept. An auto-generated name will be used as the new file name. The size identification is used as a suffix in the file name to distinct which will be loaded. + + +> **Example:** +> +> If a 2000x1000px (width x height) image is used, the following files will be generated: +> * 5f4bc74348ccb@lg.png (1024x512px) +> * 5f4bc7431e3ac@sm.png (300x150px) +> * 5f4bc742eb1e3@tb.png (150x75px) +> + +If the image width and height are lower than the specified resize value, the image is not resized and the new file is generated without a suffix. + +> **Example:** +> +> If a 100x200px (width x height) image is used, the following files will be generated: +> * 5f4bd0444e9dd.png (100x200px) +> * 5f4bd0444e9dd@tb.png (75x150px) + +**2.4. File name** + +If you want to keep the original's file name instead of using a auto-generated one, set `keep_original_name` to `true` in the `config/multiSizeImage.php` file. + +You can also provide a optional custom name as a third parameter to the `processImage` method. + +```php +$multiSizeImage->processImage($filePath, $outputPath, $fileName); +``` + +**2.5. Optimizing** + +By default the newly generate image is also optimized using [image-optimizer](https://github.com/spatie/image-optimizer) package with [JpegOptim](http://freshmeat.sourceforge.net/projects/jpegoptim), [Optipng](http://optipng.sourceforge.net/) and [Pngquant 2](https://pngquant.org/) optimizers with the following `OptimizerChain`. + +```php +$optimizerChain = (new OptimizerChain) + ->addOptimizer(new Jpegoptim([ + '-m85', + '--strip-all', + '--all-progressive', + ])) + ->addOptimizer(new Pngquant([ + '--force', + ])) + ->addOptimizer(new Optipng([ + '-i0', + '-o2', + '-quiet', + ])); +``` + +To override the default optimization behavior you can provide a custom `OptimizerChain` as an argument when instantiating `MultiSizeImage`. + +```php +use Guizoxxv\LaravelMultiSizeImage\MultiSizeImage; +use Spatie\ImageOptimizer\Optimizers\Svgo; +use Spatie\ImageOptimizer\Optimizers\Optipng; +use Spatie\ImageOptimizer\Optimizers\Gifsicle; +use Spatie\ImageOptimizer\Optimizers\Pngquant; +use Spatie\ImageOptimizer\Optimizers\Jpegoptim; +use Spatie\ImageOptimizer\Optimizers\Cwebp; + +... + +$optimizerChain = (new OptimizerChain) + ->addOptimizer(new Jpegoptim([ + '-m85', + '--strip-all', + '--all-progressive', + ])) + ->addOptimizer(new Pngquant([ + '--force', + ])) + ->addOptimizer(new Optipng([ + '-i0', + '-o2', + '-quiet', + ])) + ->addOptimizer(new Svgo([ + '--disable=cleanupIDs', + ])) + ->addOptimizer(new Gifsicle([ + '-b', + '-O3' + ])) + ->addOptimizer(new Cwebp([ + '-m 6', + '-pass 10', + '-mt', + '-q 90', + ])); + +$multiSizeImage = new MultiSizeImage($optimizerChain); +``` + +You can also disable optimization by setting `optimize` to `false` in the `config/multiSizeImage.php` file. + +**2.6. Delete original** + +The default behavior is to delete the original image after processing if the resized files names don't match the original's (changed name or path). If you choose to keep it set set `keep_original_file` to `true` in the `config/multiSizeImage.php` file. + +**3. Render** + +Render the image file according to the screen size. + +> Remenber to provide a fallback in case the image name does not have a suffix. \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..3c71d5e --- /dev/null +++ b/composer.json @@ -0,0 +1,28 @@ +{ + "name": "guizoxxv/laravel-multi-size-image", + "description": "Package to store multi-sized optimized images versions.", + "license": "MIT", + "authors": [ + { + "name": "Gustavo Simões", + "email": "guizoxxv@gmail.com" + } + ], + "minimum-stability": "dev", + "require": { + "intervention/image": "^2.5", + "spatie/image-optimizer": "^1.2" + }, + "autoload": { + "psr-4": { + "Guizoxxv\\LaravelMultiSizeImage\\": "src" + } + }, + "extra": { + "laravel": { + "providers": [ + "Guizoxxv\\LaravelMultiSizeImage\\MultiSizeImageServiceProvider" + ] + } + } +} diff --git a/config/multiSizeImage.php b/config/multiSizeImage.php new file mode 100644 index 0000000..93c7d2e --- /dev/null +++ b/config/multiSizeImage.php @@ -0,0 +1,66 @@ + true, + + /* + |-------------------------------------------------------------------------- + | Keep original file + |-------------------------------------------------------------------------- + | + | Defines if the original file should be kept. + | + */ + 'keep_original_file' => false, + + /* + |-------------------------------------------------------------------------- + | Keep original name + |-------------------------------------------------------------------------- + | + | Defines if the original file name should be kept. + | If false one will be auto-generated. + | The user can also pass a custom name to MultiSizeImage processImage method. + | + */ + 'keep_original_name' => false, + + /* + |-------------------------------------------------------------------------- + | Sizes + |-------------------------------------------------------------------------- + | + | List of sizes images should be resized to. + | Key specifies the file name suffix. + | Value specifies the size value. + | + */ + 'sizes' => [ + 'tb' => 150, + 'sm' => 300, + 'lg' => 1024, + ], + + /* + |-------------------------------------------------------------------------- + | Mime types + |-------------------------------------------------------------------------- + | + | List of mime types that should be processed. + | + */ + 'mime_types' => [ + 'image/jpeg', + 'image/png' + ], + +]; \ No newline at end of file diff --git a/src/MultiSizeImage.php b/src/MultiSizeImage.php new file mode 100644 index 0000000..11ab9ce --- /dev/null +++ b/src/MultiSizeImage.php @@ -0,0 +1,176 @@ +optimizerChain = $optimizerChain; + } else { + if (config('multiSizeImage.optimize')) { + $this->optimizerChain = (new OptimizerChain) + ->addOptimizer(new Jpegoptim([ + '-m85', + '--strip-all', + '--all-progressive', + ])) + ->addOptimizer(new Pngquant([ + '--force', + ])) + ->addOptimizer(new Optipng([ + '-i0', + '-o2', + '-quiet', + ])); + } + } + } + + public function processImage( + string $path, + ?string $outputPath = null, + ?string $fileName = null + ): array + { + $resizedFilesPaths = []; + + try { + // Ignore file if mime type does not match defined + if ( + !in_array( + mime_content_type($path), + config('multiSizeImage.mime_types') + ) + ) { + return null; + } + + // Set file name if not specified + if (!$fileName) { + // Keep original file name or generate a new one + $fileName = config('multiSizeImage.keep_original_name') + ? pathinfo($path, PATHINFO_FILENAME) + : uniqid(); + } + + // Make a new image version for each size defined + foreach(config('multiSizeImage.sizes') as $size => $sizeValue) { + // Create a Intervention image instance + $img = ImageFacade::make($path); + + // Resize image + $resizedFilePath = $this->resizeImage($img, $size, $sizeValue, $fileName, $outputPath); + + // Get pathinfo + $resizedFilePathInfo = pathinfo($resizedFilePath); + + // Add file full path to array to check if original can be deleted later + array_push( + $resizedFilesPaths, + $resizedFilePathInfo['dirname'] . '/' . $resizedFilePathInfo['basename'] + ); + + // Optimize if enabled + if ($this->optimizerChain !== null) { + $this->optimizerChain->optimize($resizedFilePath); + } + } + + // Check if original image can be deleted + if ( + !config('multiSizeImage.keep_original_file') + && !in_array($path, $resizedFilesPaths) + ) { + // Delete original image + unlink($path); + + if ($outputPath !== null) { + // Delete remaining folder if empty + $this->deleteFolderIfEmpty(pathinfo($path, PATHINFO_DIRNAME)); + } + } + + return $resizedFilesPaths; + } catch (Exception $e) { + throw $e; + } + } + + private function resizeImage( + Image $img, + string $size, + int $sizeValue, + string $fileName, + ?string $outputPath + ): string + { + // Resize if width or height is above size + if (max([$img->width(), $img->height()]) > $sizeValue) { + if ($img->width() >= $img->height()) { + // Resize by width + $img->resize($sizeValue , null, function ($constraint) { + $constraint->aspectRatio(); // maintain aspect ration + $constraint->upsize(); // prevent upsizing + }); + } else { + // Resize by height + $img->resize(null, $sizeValue, function ($constraint) { + $constraint->aspectRatio(); // maintain aspect ration + $constraint->upsize(); // prevent upsizing + }); + } + + $fileName = $fileName . "@{$size}.{$img->extension}"; + } else { + $fileName = $fileName . ".{$img->extension}"; + } + + if ($outputPath) { + // Set output file path based on specified path + $outputFilePath = "{$outputPath}/{$fileName}"; + + // Create folder if it does not already exists + $this->createFolderIfNotExists($outputPath); + } else { + // Set output file path based on original path + $outputFilePath = "{$img->dirname}/{$fileName}"; + } + + // Save resized image to output path + $img->save($outputFilePath); + + return $outputFilePath; + } + + private function createFolderIfNotExists(string $dirname): void + { + // Check if path exists + if (!file_exists($dirname)) { + // Create folder with 755 permission + mkdir($dirname, 0755, true); + } + } + + private function deleteFolderIfEmpty($dirname): void + { + // Count remaining items + $filesCount = count(array_slice(scandir($dirname), 2)); + + if ($filesCount === 0) { + rmdir($dirname); + } + } + +} \ No newline at end of file diff --git a/src/MultiSizeImageServiceProvider.php b/src/MultiSizeImageServiceProvider.php new file mode 100644 index 0000000..15b380d --- /dev/null +++ b/src/MultiSizeImageServiceProvider.php @@ -0,0 +1,31 @@ +publishes([ + __DIR__ . '/../config/multiSizeImage.php' => config_path('multiSizeImage.php'), + ]); + } + + /** + * Register any application services. + * + * @return void + */ + public function register() + { + // + } + +} \ No newline at end of file