diff --git a/.github/DESIGN_DOCS/resizer.htm b/.github/DESIGN_DOCS/resizer.htm index 69afa6c59..aec35c90f 100644 --- a/.github/DESIGN_DOCS/resizer.htm +++ b/.github/DESIGN_DOCS/resizer.htm @@ -1,11 +1,57 @@ == [ + * 'auto', // automatically choose between portrait and landscape based on the image's orientation + * 'exact', // resize to the exact dimensions given, without preserving aspect ratio + * 'portrait', // resize to the given height and adapt the width to preserve aspect ratio + * 'landscape', // resize to the given width and adapt the height to preserve aspect ratio + * 'crop', // crop to the given dimensions after fitting as much of the image as possible inside those + * 'fit', // fit the image inside the given maximal dimensions, keeping the aspect ratio + * ], + * 'quality' => numeric, 1 - 100 + * 'interlace' => boolean (default false), + * 'extension' => ['auto', 'png', 'gif', 'jpg', 'jpeg', 'webp', 'bmp', 'ico'], + * 'offset' => [x, y] Offset to crop the image from + * 'sharpen' => numeric, 1 - 100 + * + * // Options that could be processed by an addon + * + * 'blur' => numeric, 1 - 100 + * 'brightness'=> numeric, -100 - 100 + * 'contrast' => numeric, -100 - 100 + * 'pixelate' => numeric, 1 - 5000 + * 'greyscale' => boolean + * 'invert' => boolean + * 'opacity' => numeric, 0 - 100 + * 'rotate' => numeric, 1 - 360 + * 'flip' => [h, v] + * 'background' | 'fill' => string, hex value + * 'colourize' => string, RGB value + * ] + * + * Event::fire('system.resize.afterResize') + * Event::fire('system.resize.beforeResize') + * Event::fire('system.resize.processResize') + * + * + * + +use App; +use Storage; +use October\Rain\Database\Attach\File as FileModel; + /** * DRAFT RESIZER DESIGN * This is a rough draft, very WIP, of what the Image resizer UX / API will look like in October. * * Notes: * - Clearing the application cache should not invalidate any existing resized images + * - Invalid images should not result in a valid "image not found" image existing, it should result in a 404 or more specific error + * - Provide a new backend list column type "thumb" that will pass it through the resizer * * Configurations to support * @@ -14,6 +60,9 @@ * final generated image as requested or a link to the resizer route that will actually handle resizing the image. * - User should be able to extend the image resizing to provide pre or post processing of the images before / after being resized * also to include the ability to swap out the image resizer itself. The core workflow logic should remain the same though. + * Examples: + * - Post processing of resized images with TinyPNG to optimize filesize further + * - Replacement processing of resizing with Intervention Image (using GD or ImageMagick) */ class Helper { /** @@ -31,9 +80,122 @@ class Helper { */ public function getIdentifier($image, $width = null, $height = null, array $options = []) { + $image = static::normalizeImage($image); + if (is_null($image)) { + throw new \Exception("Unable to process the provided image: " . var_export($image)); + } + + $properties = [ + 'image' => $image, + 'width' => $width, + 'height' => $height, + 'options' => $options, + ]; } + /** + * Normalize the provided input into information that the resizer can work with + * + * @param mixed $image Supported values below: + * ['disk' => string, 'path' => string], + * instance of October\Rain\Database\Attach\File, + * string containing URL or path accessible to the application's filesystem manager + * @return array|null Array containing the disk and path ['disk' => string, 'path' => string], null if not found + */ + public static function normalizeImage($image) + { + $disk = null; + $path = null; + + // Process an array + if (is_array($image) && !empty($image['disk']) && !empty($image['path'])) { + $disk = $image['disk']; + $path = $image['path']; + + // Process a FileModel + } elseif ($image instanceof FileModel) { + $disk = $image->getDisk(); + $path = $image->getDiskPath(); + + // Process a string + } elseif (is_string($image)) { + // Parse the provided image path into a filesystem ready relative path + $relativePath = urldecode(parse_url($image, PHP_URL_PATH)); + + // Loop through the sources available to the application to pull from + // to identify the source most likely to be holding the image + $resizeSources = [ + 'themes' => [ + 'disk' => 'system', + 'folder' => config('cms.themesPathLocal', base_path('themes')), + 'path' => config('cms.themesPath', '/themes'), + ], + 'plugins' => [ + 'disk' => 'system', + 'folder' => config('cms.pluginsPathLocal', base_path('plugins')), + 'path' => config('cms.pluginsPath', '/plugins'), + ], + 'media' => [ + 'disk' => config('cms.storage.media.disk', 'local'), + 'folder' => config('cms.storage.media.folder', 'media'), + 'path' => config('cms.storage.media.path', '/storage/app/media'), + ], + 'modules' => [ + 'disk' => 'system', + 'folder' => base_path('modules'), + 'path' => '/modules', + ], + 'uploads' => [ + 'disk' => config('cms.storage.uploads.disk', 'local'), + 'folder' => config('cms.storage.uploads.folder', 'uploads'), + 'path' => config('cms.storage.uploads.path', '/storage/app/uploads'), + ], + ]; + foreach ($resizeSources as $source => $details) { + // Normalize the source path + $sourcePath = urldecode(parse_url($details['path'], PHP_URL_PATH)); + + // Identify if the current source is a match + if (starts_with($relativePath, $sourcePath)) { + // Generate a path relative to the selected disk + $path = $details['folder'] . '/' . str_after($relativePath, $sourcePath . '/'); + + // Handle disks of type "system" (the local file system the application is running on) + if ($details['disk'] === 'system') { + Config::set('filesystems.disks.system', [ + 'driver' => 'local', + 'root' => base_path(), + ]); + // Regenerate the path relative to the newly defined "system" disk + $path = str_after($path, base_path() . '/'); + } + + $disk = Storage::disk($details['disk']); + + // Verify that the file exists before exiting the identification process + if ($disk->exists($path)) { + break; + } else { + $disk = null; + $path = null; + continue; + } + } + } + } + + if (!$disk || !$path) { + return null; + } + + return [ + 'disk' => $disk, + 'path' => $path, + ]; + } + + /** * Get the reference to the resized image if the requested resize exists * diff --git a/config/cms.php b/config/cms.php index 790355414..17d58111a 100644 --- a/config/cms.php +++ b/config/cms.php @@ -325,6 +325,12 @@ return [ 'path' => '/storage/app/media', ], + 'resized' => [ + 'disk' => 'local', + 'folder' => 'resized', + 'path' => '/storage/app/resized', + ], + ], /*