├── LICENCE ├── README.md ├── composer.json ├── config └── multiSizeImage.php ├── schema.png └── src ├── MultiSizeImage.php └── MultiSizeImageServiceProvider.php /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gustavo Simões 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Multi Size Image 2 | 3 | Laravel package to optimize and store images in different sizes in order to load the appropriate one according to the screen size. 4 | 5 | ![schema](schema.png) 6 | 7 | ## Requirements 8 | * 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). 9 | * 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. 10 | 11 | ## Installation 12 | Require the package via Composer: 13 | 14 | ``` 15 | $ composer require guizoxxv/laravel-multi-size-image 16 | ``` 17 | 18 | ## Configuration 19 | Publish the package configuration file to your Laravel project to change the default behavior. 20 | 21 | ``` 22 | $ php artisan vendor:publish --provider="Guizoxxv\LaravelMultiSizeImage\MultiSizeImageServiceProvider" 23 | ``` 24 | 25 | A `config/multiSizeImage.php` file will be added in your project. 26 | 27 | ## Usage 28 | 29 | ### 1. Instantiate 30 | 31 | To apply Multi Size Image first you must create an instance of it. 32 | 33 | ```php 34 | use Guizoxxv\LaravelMultiSizeImage\MultiSizeImage; 35 | 36 | ... 37 | 38 | $multiSizeImage = new MultiSizeImage(); 39 | ``` 40 | 41 | ### 2. Process image 42 | 43 | Call the `processImage` method passing the file path as the first argument. 44 | 45 | ```php 46 | $filePath = Storage::path('folder/file.png'); 47 | 48 | $multiSizeImage->processImage($filePath); 49 | ``` 50 | 51 | > The file path must be absolute. 52 | 53 | The method returns an array of strings with the full path of the generated files. 54 | 55 | #### 2.1. Mime types 56 | 57 | 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 returns `null`. 58 | 59 | > 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. 60 | 61 | #### 2.2. Output path 62 | 63 | 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. 64 | 65 | ```php 66 | $multiSizeImage->processImage($filePath, $outputPath, $basePath); 67 | ``` 68 | 69 | The `basePath` optional parameter can be used to keep the original file path as of this path. 70 | 71 | #### 2.3. Resizing 72 | 73 | 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 to be resized to. 74 | 75 | ```php 76 | 'sizes' => [ 77 | 'tb' => 150, 78 | 'sm' => 300, 79 | 'lg' => 1024, 80 | ] 81 | ``` 82 | 83 | Above are the default values. The biggest dimension 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 distinguish which will be loaded. 84 | 85 | 86 | > **Example:** 87 | > 88 | > If a 2000x1000px (width x height) image is used, the following files will be generated: 89 | > * 5f4bc74348ccb@lg.png (1024x512px) 90 | > * 5f4bc7431e3ac@sm.png (300x150px) 91 | > * 5f4bc742eb1e3@tb.png (150x75px) 92 | > 93 | 94 | 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. 95 | 96 | > **Example:** 97 | > 98 | > If a 100x200px (width x height) image is used, the following files will be generated: 99 | > * 5f4bd0444e9dd.png (100x200px) 100 | > * 5f4bd0444e9dd@tb.png (75x150px) 101 | 102 | #### 2.4. File name 103 | 104 | If you want to keep the original's file name instead of using an auto-generated one, set `keep_original_name` to `true` in the `config/multiSizeImage.php` file. 105 | 106 | You can also provide an optional custom name as a forth parameter to the `processImage` method. 107 | 108 | ```php 109 | $multiSizeImage->processImage($filePath, $outputPath, $basePath, $fileName); 110 | ``` 111 | 112 | #### 2.5. Optimizing 113 | 114 | 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`. 115 | 116 | ```php 117 | $optimizerChain = (new OptimizerChain) 118 | ->addOptimizer(new Jpegoptim([ 119 | '-m85', 120 | '--strip-all', 121 | '--all-progressive', 122 | ])) 123 | ->addOptimizer(new Pngquant([ 124 | '--force', 125 | ])) 126 | ->addOptimizer(new Optipng([ 127 | '-i0', 128 | '-o2', 129 | '-quiet', 130 | ])); 131 | ``` 132 | 133 | To override the default optimization behavior you can provide a custom `OptimizerChain` as an argument when instantiating `MultiSizeImage`. 134 | 135 | ```php 136 | use Guizoxxv\LaravelMultiSizeImage\MultiSizeImage; 137 | use Spatie\ImageOptimizer\Optimizers\Svgo; 138 | use Spatie\ImageOptimizer\Optimizers\Optipng; 139 | use Spatie\ImageOptimizer\Optimizers\Gifsicle; 140 | use Spatie\ImageOptimizer\Optimizers\Pngquant; 141 | use Spatie\ImageOptimizer\Optimizers\Jpegoptim; 142 | use Spatie\ImageOptimizer\Optimizers\Cwebp; 143 | 144 | ... 145 | 146 | $optimizerChain = (new OptimizerChain) 147 | ->addOptimizer(new Jpegoptim([ 148 | '-m85', 149 | '--strip-all', 150 | '--all-progressive', 151 | ])) 152 | ->addOptimizer(new Pngquant([ 153 | '--force', 154 | ])) 155 | ->addOptimizer(new Optipng([ 156 | '-i0', 157 | '-o2', 158 | '-quiet', 159 | ])) 160 | ->addOptimizer(new Svgo([ 161 | '--disable=cleanupIDs', 162 | ])) 163 | ->addOptimizer(new Gifsicle([ 164 | '-b', 165 | '-O3' 166 | ])) 167 | ->addOptimizer(new Cwebp([ 168 | '-m 6', 169 | '-pass 10', 170 | '-mt', 171 | '-q 90', 172 | ])); 173 | 174 | $multiSizeImage = new MultiSizeImage($optimizerChain); 175 | ``` 176 | 177 | You can also disable optimization by setting `optimize` to `false` in the `config/multiSizeImage.php` file. 178 | 179 | #### 2.6. Delete original 180 | 181 | 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 `keep_original_file` to `true` in the `config/multiSizeImage.php` file. 182 | 183 | ### 3. Render 184 | 185 | Render the image file according to the screen size. 186 | 187 | > Remember to provide a fallback in case the image name does not have a suffix. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guizoxxv/laravel-multi-size-image", 3 | "description": "Laravel package to optimize and store images in different sizes in order to load the appropriate one according to the screen size.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Gustavo Oliveira", 8 | "email": "guizoxxv@gmail.com" 9 | } 10 | ], 11 | "minimum-stability": "dev", 12 | "require": { 13 | "intervention/image": "^2.5", 14 | "spatie/image-optimizer": "^1.2" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Guizoxxv\\LaravelMultiSizeImage\\": "src" 19 | } 20 | }, 21 | "extra": { 22 | "laravel": { 23 | "providers": [ 24 | "Guizoxxv\\LaravelMultiSizeImage\\MultiSizeImageServiceProvider" 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /config/multiSizeImage.php: -------------------------------------------------------------------------------- 1 | true, 14 | 15 | /* 16 | |-------------------------------------------------------------------------- 17 | | Keep original file 18 | |-------------------------------------------------------------------------- 19 | | 20 | | Defines if the original file should be kept. 21 | | 22 | */ 23 | 'keep_original_file' => false, 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Keep original name 28 | |-------------------------------------------------------------------------- 29 | | 30 | | Defines if the original file name should be kept. 31 | | If false one will be auto-generated. 32 | | The user can also pass a custom name to MultiSizeImage processImage method. 33 | | 34 | */ 35 | 'keep_original_name' => false, 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Sizes 40 | |-------------------------------------------------------------------------- 41 | | 42 | | List of sizes images should be resized to. 43 | | Key specifies the file name suffix. 44 | | Value specifies the size value. 45 | | 46 | */ 47 | 'sizes' => [ 48 | 'tb' => 150, 49 | 'sm' => 300, 50 | 'lg' => 1024, 51 | ], 52 | 53 | /* 54 | |-------------------------------------------------------------------------- 55 | | Mime types 56 | |-------------------------------------------------------------------------- 57 | | 58 | | List of mime types that should be processed. 59 | | 60 | */ 61 | 'mime_types' => [ 62 | 'image/jpeg', 63 | 'image/png' 64 | ], 65 | 66 | ]; -------------------------------------------------------------------------------- /schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guizoxxv/laravel-multi-size-image/8ab0bc7ab8433e08174385f42801b8364348877c/schema.png -------------------------------------------------------------------------------- /src/MultiSizeImage.php: -------------------------------------------------------------------------------- 1 | optimizerChain = $optimizerChain; 21 | } else { 22 | if (config('multiSizeImage.optimize')) { 23 | $this->optimizerChain = (new OptimizerChain) 24 | ->addOptimizer(new Jpegoptim([ 25 | '-m85', 26 | '--strip-all', 27 | '--all-progressive', 28 | ])) 29 | ->addOptimizer(new Pngquant([ 30 | '--force', 31 | ])) 32 | ->addOptimizer(new Optipng([ 33 | '-i0', 34 | '-o2', 35 | '-quiet', 36 | ])); 37 | } 38 | } 39 | } 40 | 41 | public function processImage( 42 | string $filePath, 43 | ?string $outputPath = null, 44 | ?string $basePath = '', 45 | ?string $fileName = null 46 | ): ?array 47 | { 48 | $resizedFilesPaths = []; 49 | 50 | try { 51 | // Ignore file if mime type does not match defined 52 | if ( 53 | !in_array( 54 | mime_content_type($filePath), 55 | config('multiSizeImage.mime_types') 56 | ) 57 | ) { 58 | return null; 59 | } 60 | 61 | // Set file name if not specified 62 | if (!$fileName) { 63 | // Keep original file name or generate a new one 64 | $fileName = config('multiSizeImage.keep_original_name') 65 | ? pathinfo($filePath, PATHINFO_FILENAME) 66 | : uniqid(); 67 | } 68 | 69 | // Make a new image version for each size defined 70 | foreach(config('multiSizeImage.sizes') as $size => $sizeValue) { 71 | // Create a Intervention image instance 72 | $img = ImageFacade::make($filePath); 73 | 74 | // Resize image 75 | $resizedFilePath = $this->resizeImage( 76 | $img, 77 | $size, 78 | $sizeValue, 79 | $fileName, 80 | rtrim($outputPath, '/'), 81 | rtrim($basePath, '/') 82 | ); 83 | 84 | // Get pathinfo 85 | $resizedFilePathInfo = pathinfo($resizedFilePath); 86 | 87 | // Add file full path to array to check if original can be deleted later 88 | array_push( 89 | $resizedFilesPaths, 90 | $resizedFilePathInfo['dirname'] . '/' . $resizedFilePathInfo['basename'] 91 | ); 92 | 93 | // Optimize if enabled 94 | if ($this->optimizerChain !== null) { 95 | $this->optimizerChain->optimize($resizedFilePath); 96 | } 97 | } 98 | 99 | // Check if original image can be deleted 100 | if ( 101 | !config('multiSizeImage.keep_original_file') 102 | && !in_array($filePath, $resizedFilesPaths) 103 | ) { 104 | // Delete original image 105 | unlink($filePath); 106 | 107 | if ($outputPath !== null) { 108 | // Delete remaining folder if empty 109 | $this->deleteFolderIfEmpty(pathinfo($filePath, PATHINFO_DIRNAME)); 110 | } 111 | } 112 | 113 | return $resizedFilesPaths; 114 | } catch (Exception $e) { 115 | throw $e; 116 | } 117 | } 118 | 119 | private function resizeImage( 120 | Image $img, 121 | string $size, 122 | int $sizeValue, 123 | string $fileName, 124 | ?string $outputPath, 125 | ?string $basePath 126 | ): string 127 | { 128 | // Resize if width or height is above size 129 | if (max([$img->width(), $img->height()]) > $sizeValue) { 130 | if ($img->width() >= $img->height()) { 131 | // Resize by width 132 | $img->resize($sizeValue , null, function ($constraint) { 133 | $constraint->aspectRatio(); // maintain aspect ration 134 | $constraint->upsize(); // prevent upsizing 135 | }); 136 | } else { 137 | // Resize by height 138 | $img->resize(null, $sizeValue, function ($constraint) { 139 | $constraint->aspectRatio(); // maintain aspect ration 140 | $constraint->upsize(); // prevent upsizing 141 | }); 142 | } 143 | } 144 | 145 | $fileName = $fileName . "@{$size}.{$img->extension}"; 146 | 147 | if ($outputPath) { 148 | if ($basePath) { 149 | // Get image dir relative to basePath 150 | $prefix = preg_quote($basePath, '/'); 151 | $fileDir = preg_replace("/^{$prefix}/", '', $img->dirname); 152 | 153 | // Set output file path based on specified path and base path 154 | $outputFilePath = "{$outputPath}/{$fileDir}/{$fileName}"; 155 | } else { 156 | // Set output file path based on specified path 157 | $outputFilePath = "{$outputPath}/{$fileName}"; 158 | } 159 | 160 | // Create folder if it does not already exists 161 | $this->createFolderIfNotExists(pathinfo($outputFilePath, PATHINFO_DIRNAME)); 162 | } else { 163 | // Set output file path based on original path 164 | $outputFilePath = "{$img->dirname}/{$fileName}"; 165 | } 166 | 167 | // Save resized image to output path 168 | $img->save($outputFilePath); 169 | 170 | return $outputFilePath; 171 | } 172 | 173 | private function createFolderIfNotExists(string $dirname): void 174 | { 175 | // Check if path exists 176 | if (!file_exists($dirname)) { 177 | // Create folder with 755 permission 178 | mkdir($dirname, 0755, true); 179 | } 180 | } 181 | 182 | private function deleteFolderIfEmpty($dirname): void 183 | { 184 | // Count remaining items 185 | $filesCount = count(array_slice(scandir($dirname), 2)); 186 | 187 | if ($filesCount === 0) { 188 | rmdir($dirname); 189 | } 190 | } 191 | 192 | } -------------------------------------------------------------------------------- /src/MultiSizeImageServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 17 | __DIR__ . '/../config/multiSizeImage.php' => config_path('multiSizeImage.php'), 18 | ]); 19 | } 20 | 21 | /** 22 | * Register any application services. 23 | * 24 | * @return void 25 | */ 26 | public function register() 27 | { 28 | $this->mergeConfigFrom( 29 | __DIR__ . '/../config/multiSizeImage.php', 'multiSizeImage' 30 | ); 31 | } 32 | 33 | } --------------------------------------------------------------------------------