outerweb / image-library
Store and link files to your models
Fund package maintenance!
outer-web
Installs: 11 304
Dependents: 1
Suggesters: 0
Security: 0
Stars: 11
Watchers: 1
Forks: 1
Open Issues: 1
pkg:composer/outerweb/image-library
Requires
- php: ^8.4
- illuminate/contracts: ^11.0||^12.0
- illuminate/database: ^11.0||^12.0
- illuminate/support: ^11.0||^12.0
- nesbot/carbon: ^3.10
- outerweb/enum-helpers: *
- outerweb/filament-translatable-fields: ^4.0
- spatie/eloquent-sortable: ^4.5
- spatie/image: ^3.8
- spatie/laravel-package-tools: ^1.16
- spatie/laravel-translatable: ^6.11
- spatie/temporary-directory: ^2.3
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
README
A powerful Laravel package for managing images with responsive breakpoints, automatic optimization, and contextual configurations. Store and link images to your models with advanced features like automatic WebP generation, responsive image versions, and flexible image contexts.
⚠️ Caution: V3 is a complete rewrite of the package and logic. Please take a look at the upgrade guide before upgrading from v2.x to v3.x.
Table of Contents
Requirements
This package uses spatie/image for image manipulations, so it requires the GD or Imagick PHP extension.
Installation
You can install the package via composer:
composer require outerweb/image-library
Run the install command to publish the migrations, config file, and service provider:
php artisan image-library:install
This will:
- Publish the configuration file to
config/image-library.php - Copy and register the
ImageLibraryServiceProviderin your application - Publish the database migrations
- Optionally run the migrations
Core concepts
SourceImages
SourceImages are the original images uploaded to the system. They are not directly linked to any model. They are meant for internal use in this package.
Images
Images are the link between a SourceImage and your Model(s). The Image has a BelongsTo relationship to the SourceImage and a MorphTo relationship to your Model.
You can see these as an instance of the uploaded images in a specific use case. You can define the use case using the context attribute on the Image model.
ImageContexts
ImageContexts allow you to define a configuration for images used in a specific way. Examples include "profile_picture", "thumbnail", "gallery_entry", "hero", etc.
They are fully customizable and should be defined in the ImageServiceProvider or in a custom service provider.
Images get generated based on the defined ImageContext when the Image model gets created or updated. This is based on the image_context_hash that is stored per Image. It is a hashed version of the whole configuration so that changes in the context will trigger regeneration of the images.
WebP versions
This package also generates WebP versions of images for better performance in modern browsers. You can configure whether to generate WebP versions globally in the config file or per ImageContext.
Responsive versions
The package supports generating multiple responsive versions of images based on defined breakpoints. You can configure the sizes and aspect ratios for each breakpoint in the ImageContext, ensuring optimal display across different devices.
Each breakpoint can have a minimum and maximum width defined in the context. This allows the package to generate only necessary image sizes based on your design requirements.
Breakpoints
Breakpoints define responsive screen sizes for image optimization. The package uses a Breakpoint enum that follows Tailwind CSS conventions, allowing you to specify different image configurations for various screen sizes.
Available breakpoints:
Breakpoint::Small('sm'): 640px and up - Mobile devices in landscape, small tabletsBreakpoint::Medium('md'): 768px and up - Tablets in portrait modeBreakpoint::Large('lg'): 1024px and up - Tablets in landscape, small desktopsBreakpoint::ExtraLarge('xl'): 1280px and up - Desktop screensBreakpoint::DoubleExtraLarge('2xl'): 1536px and up - Large desktop screens
You can use these breakpoints to define different aspect ratios, sizes, crop positions, and effects for different screen sizes, ensuring optimal image display across all devices.
Note: If the default breakpoints don't match your design system, you can create custom breakpoints. See Custom Breakpoints in the Configuration section.
If you don't need responsive images, you can disable breakpoints globally in the config file or per ImageContext.
Configuration
The config file
The config file allows you to customize various aspects of the image library. Some key configuration options include:
defaults.disk: The default filesystem disk for storing images if not specified during uploaduse_breakpoints: Enable or disable responsive breakpoints globallygenerate.webp: Automatically generate WebP versions of images if not specified in the image contextgenerate.responsive_versions: Generate multiple sizes for responsive images if not specified in the image contextdefaults.crop_position: Default crop position for image transformations if not specified in the image contextmodels: Customize the Eloquent models used by the package to easily extend functionalityenums: Customize the enums used by the package to easily extend functionalityspatie_image.driver: Choose between 'gd' or 'imagick' for image manipulations
Javascript
The package includes a JavaScript component that automatically sets the sizes attribute on picture elements rendered by the package. This ensures that the browser selects the most appropriate image size based on the actual display size of the image.
To include the script, add the following Blade component to the <head> section of your layout:
<x-image-library::scripts />
Defining ImageContexts
ImageContexts are defined in your application's ImageLibraryServiceProvider that gets published during installation. This provider extends the base service provider and allows you to define contexts in the imageContexts() method:
<?php namespace App\Providers; use Outerweb\ImageLibrary\Entities\AspectRatio; use Outerweb\ImageLibrary\Entities\ImageContext; use Outerweb\ImageLibrary\Enums\Breakpoint; use Outerweb\ImageLibrary\Providers\ImageLibraryServiceProvider as BaseServiceProvider; use Override; class ImageLibraryServiceProvider extends BaseServiceProvider { #[Override] public function imageContexts(): array { return [ // Profile picture - square aspect ratio, single image ImageContext::make('profile_picture') ->label(fn (): string => __('Profile Picture')) ->aspectRatio(AspectRatio::make(1, 1)) ->allowsMultiple(false), // Gallery images - 16:9 aspect ratio, multiple images allowed ImageContext::make('gallery') ->label(fn (): string => __('Gallery')) ->aspectRatio(AspectRatio::make(16, 9)) ->allowsMultiple(true), // Thumbnail - square with responsive sizing ImageContext::make('thumbnail') ->label(fn (): string => __('Thumbnail')) ->aspectRatio(AspectRatio::make(1, 1)) ->maxWidth([ Breakpoint::Small->value => 150, Breakpoint::Medium->value => 200, Breakpoint::Large->value => 250, ]) ->allowsMultiple(false), // Image that does not use breakpoints ImageContext::make('no_breakpoints') ->label(fn (): string => __('No Breakpoints')) ->useBreakpoints(false) ]; } }
Configuration Methods
ImageContexts provide extensive configuration options for different responsive breakpoints and image processing needs:
Label
You can define a human-readable label for each context to use in your UI.
ImageContext::make('thumbnail')
->label('Thumbnail')
If you need localization, you can define the label using a closure:
ImageContext::make('thumbnail') ->label(fn() => __('Thumbnail'))
If you need information about the ImageContext in the label, you can use the provided ImageContext instance:
ImageContext::make('thumbnail') ->label(fn(ImageContext $context) => __('Image Context: :context', ['context' => $context->key]))
Allowing multiple images
You can specify whether multiple images are allowed in this context:
ImageContext::make('gallery') ->allowsMultiple(true);
Generating WebP versions
By default, WebP versions are generated based on the global config. You can override this per context:
ImageContext::make('thumbnail') ->generateWebP(false);
Using breakpoints
By default, breakpoints are used based on the global config. You can override this per context:
ImageContext::make('thumbnail') ->useBreakpoints(false);
⚠️ Caution: Make sure to call
->useBreakpoints(false)before any other methods that depend on breakpoints, such asaspectRatio(),minWidth(),maxWidth(), etc. Otherwise, you may encounter errors since those methods check if breakpoints are enabled or not.
Generating responsive versions
By default, responsive versions are generated based on the global config. You can override this per context:
ImageContext::make('thumbnail') ->generateResponsiveVersions(false);
Note: If you disable breakpoints for an ImageContext, responsive versions will also be disabled as these are only generated when breakpoints are used.
Aspect Ratio
The aspect ratio can be configured per Breakpoint in one of the following ways:
// Single aspect ratio for all breakpoints ImageContext::make('thumbnail') ->aspectRatio(AspectRatio::make(1, 1)); // Different aspect ratios per breakpoint ImageContext::make('thumbnail') ->aspectRatio([ Breakpoint::Small->value => AspectRatio::make(1, 1), Breakpoint::Medium->value => AspectRatio::make(4, 3), Breakpoint::Large->value => AspectRatio::make(16, 9), Breakpoint::ExtraLarge->value => AspectRatio::make(16, 9), Breakpoint::DoubleExtraLarge->value => AspectRatio::make(2, 1), ]); // Per breakpoint ImageContext::make('thumbnail') ->aspectRatioForBreakpoint(Breakpoint::Small, AspectRatio::make(1, 1)) // From a Breakpoint and up ImageContext::make('thumbnail') ->aspectRatioFromBreakpoint(Breakpoint::Medium, AspectRatio::make(16, 9)) // Up till a Breakpoint ImageContext::make('thumbnail') ->aspectRatioUpToBreakpoint(Breakpoint::Large, AspectRatio::make(4, 3)) // Between two Breakpoints ImageContext::make('thumbnail') ->aspectRatioBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, AspectRatio::make(1, 1));
Minimum width
You can define the minimum width of the image used in your design per Breakpoint in one of the following ways:
// Single minimum width for all breakpoints ImageContext::make('thumbnail') ->minWidth(150) // Different minimum widths per breakpoint ImageContext::make('thumbnail') ->minWidth([ Breakpoint::Small->value => 100, Breakpoint::Medium->value => 150, Breakpoint::Large->value => 200, Breakpoint::ExtraLarge->value => 250, Breakpoint::DoubleExtraLarge->value => 300, ]); // Per breakpoint ImageContext::make('thumbnail') ->minWidthForBreakpoint(Breakpoint::Small, 100); // From a Breakpoint and up ImageContext::make('thumbnail') ->minWidthFromBreakpoint(Breakpoint::Medium, 150); // Up till a Breakpoint ImageContext::make('thumbnail') ->minWidthUpToBreakpoint(Breakpoint::Large, 200); // Between two Breakpoints ImageContext::make('thumbnail') ->minWidthBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, 150);
Maximum width
You can define the maximum width of the image used in your design per Breakpoint in one of the following ways:
// Single maximum width for all breakpoints ImageContext::make('thumbnail') ->maxWidth(250); // Different maximum widths per breakpoint ImageContext::make('thumbnail') ->maxWidth([ Breakpoint::Small->value => 150, Breakpoint::Medium->value => 200, Breakpoint::Large->value => 250, Breakpoint::ExtraLarge->value => 300, Breakpoint::DoubleExtraLarge->value => 350, ]); // Per breakpoint ImageContext::make('thumbnail') ->maxWidthForBreakpoint(Breakpoint::Small, 150); // From a Breakpoint and up ImageContext::make('thumbnail') ->maxWidthFromBreakpoint(Breakpoint::Medium, 200); // Up till a Breakpoint ImageContext::make('thumbnail') ->maxWidthUpToBreakpoint(Breakpoint::Large, 250); // Between two Breakpoints ImageContext::make('thumbnail') ->maxWidthBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, 200);
Crop Position
By default, the crop position from the config file is used. You can override this per context and per Breakpoint in one of the following ways:
// Single crop position for all breakpoints ImageContext::make('thumbnail') ->cropPosition(CropPosition::Center); // Different crop positions per breakpoint ImageContext::make('thumbnail') ->cropPosition([ Breakpoint::Small->value => CropPosition::Top, Breakpoint::Medium->value => CropPosition::Center, Breakpoint::Large->value => CropPosition::Bottom, Breakpoint::ExtraLarge->value => CropPosition::Center, Breakpoint::DoubleExtraLarge->value => CropPosition::Center, ]); // Per breakpoint ImageContext::make('thumbnail') ->cropPositionForBreakpoint(Breakpoint::Small, CropPosition::Top); // From a Breakpoint and up ImageContext::make('thumbnail') ->cropPositionFromBreakpoint(Breakpoint::Medium, CropPosition::Center); // Up till a Breakpoint ImageContext::make('thumbnail') ->cropPositionUpToBreakpoint(Breakpoint::Large, CropPosition::Bottom); // Between two Breakpoints ImageContext::make('thumbnail') ->cropPositionBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, CropPosition::Center);
Blur
You can apply a blur effect to images in this context per Breakpoint in one of the following ways:
// Single blur value for all breakpoints ImageContext::make('thumbnail') ->blur(10); // Different blur values per breakpoint ImageContext::make('thumbnail') ->blur([ Breakpoint::Small->value => 5, Breakpoint::Medium->value => 10, Breakpoint::Large->value => 15, Breakpoint::ExtraLarge->value => 20, Breakpoint::DoubleExtraLarge->value => 25, ]); // Per breakpoint ImageContext::make('thumbnail') ->blurForBreakpoint(Breakpoint::Small, 5); // From a Breakpoint and up ImageContext::make('thumbnail') ->blurFromBreakpoint(Breakpoint::Medium, 10); // Up till a Breakpoint ImageContext::make('thumbnail') ->blurUpToBreakpoint(Breakpoint::Large, 15); // Between two Breakpoints ImageContext::make('thumbnail') ->blurBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, 10);
Greyscale
You can apply a greyscale effect to images in this context per Breakpoint in one of the following ways:
// Single greyscale value for all breakpoints ImageContext::make('thumbnail') ->greyscale(true); // or even ->grayscale(true) // Different greyscale values per breakpoint ImageContext::make('thumbnail') ->greyscale([ Breakpoint::Small->value => false, Breakpoint::Medium->value => true, Breakpoint::Large->value => false, Breakpoint::ExtraLarge->value => true, Breakpoint::DoubleExtraLarge->value => false, ]); // Per breakpoint ImageContext::make('thumbnail') ->greyscaleForBreakpoint(Breakpoint::Small, false); // or even ->grayscaleForBreakpoint(Breakpoint::Small, false); // From a Breakpoint and up ImageContext::make('thumbnail') ->greyscaleFromBreakpoint(Breakpoint::Medium, true); // or even ->grayscaleFromBreakpoint(Breakpoint::Medium, true); // Up till a Breakpoint ImageContext::make('thumbnail') ->greyscaleUpToBreakpoint(Breakpoint::Large, false); // or even ->grayscaleUpToBreakpoint(Breakpoint::Large, false); // Between two Breakpoints ImageContext::make('thumbnail') ->greyscaleBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, true); // or even ->grayscaleBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, true);
Sepia
You can apply a sepia effect to images in this context per Breakpoint in one of the following ways:
// Single sepia value for all breakpoints ImageContext::make('thumbnail') ->sepia(true); // Different sepia values per breakpoint ImageContext::make('thumbnail') ->sepia([ Breakpoint::Small->value => false, Breakpoint::Medium->value => true, Breakpoint::Large->value => false, Breakpoint::ExtraLarge->value => true, Breakpoint::DoubleExtraLarge->value => false, ]); // Per breakpoint ImageContext::make('thumbnail') ->sepiaForBreakpoint(Breakpoint::Small, false); // From a Breakpoint and up ImageContext::make('thumbnail') ->sepiaFromBreakpoint(Breakpoint::Medium, true); // Up till a Breakpoint ImageContext::make('thumbnail') ->sepiaUpToBreakpoint(Breakpoint::Large, false); // Between two Breakpoints ImageContext::make('thumbnail') ->sepiaBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, true);
Preparing your model(s)
Using the HasImages Trait
Add the HasImages trait to any Eloquent model that should support image attachments:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Outerweb\ImageLibrary\Traits\HasImages; class Product extends Model { use HasImages; // Your model code... }
The trait provides:
images(): Default polymorphic relationship returning all imagesattachImage(): Method to attach images with context validation- Automatic context validation and image replacement for single-image contexts
Using Custom Relationships
For more control over image relationships, you can define custom morphic relationships alongside the HasImages trait. This allows you to create type-specific relationships for different image contexts.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\Relations\MorphMany; use Outerweb\ImageLibrary\Models\Image; use Outerweb\ImageLibrary\Traits\HasImages; class Article extends Model { use HasImages; /** * Single featured image relationship */ public function featuredImage(): MorphOne { return $this->morphOne(Image::class, 'model') ->where('context', 'featured'); } /** * Multiple gallery images relationship */ public function galleryImages(): MorphMany { return $this->morphMany(Image::class, 'model') ->where('context', 'gallery'); } /** * Images for a specific layout block (useful for page builders) */ public function getLayoutBlockImages(int $blockId): MorphMany { return $this->images() ->whereJsonContains('custom_properties->layout_block_id', $blockId); } }
Custom Breakpoints
If the default breakpoints don't match your design system, you can create a custom breakpoint enum. This is useful when you need different screen size thresholds or additional breakpoints.
Creating a Custom Breakpoint Enum
First, create a custom enum that implements the ConfiguresBreakpoints contract:
<?php namespace App\Enums; use Illuminate\Support\Str; use Outerweb\ImageLibrary\Contracts\ConfiguresBreakpoints; enum CustomBreakpoint: string implements ConfiguresBreakpoints { case Mobile = 'mobile'; case Tablet = 'tablet'; case Desktop = 'desktop'; case UltraWide = 'ultrawide'; public static function sortedCases(): array { return collect(self::cases()) ->sort(fn ($a, $b) => $a->getMinWidth() <=> $b->getMinWidth()) ->all(); } public function getLabel(): string { return match ($this) { self::Mobile => 'Mobile', self::Tablet => 'Tablet', self::Desktop => 'Desktop', self::UltraWide => 'Ultra Wide', }; } public function getMinWidth(): int { return match ($this) { self::Mobile => 320, self::Tablet => 768, self::Desktop => 1200, self::UltraWide => 1920, }; } public function getMaxWidth(): ?int { $index = array_search($this, self::sortedCases(), true); $next = self::sortedCases()[$index + 1] ?? null; return $next ? $next->getMinWidth() - 1 : null; } public function getSlug(): string { return Str::of($this->value) ->lower() ->slug() ->toString(); } }
Configuring the Custom Breakpoint Enum
Update your config/image-library.php file to use your custom enum:
'enums' => [ 'breakpoint' => App\Enums\CustomBreakpoint::class, ],
Disabling breakpoints
If you application or specific context does not require image versions per breakpoint, you can disable breakpoints:
Globally
'use_breakpoints' => false,
Per ImageContext
ImageContext::make('thumbnail') ->useBreakpoints(false);
⚠️ Caution: Make sure to call
->useBreakpoints(false)before any other methods that depend on breakpoints, such asaspectRatio(),minWidth(),maxWidth(), etc. Otherwise, you may encounter errors since those methods check if breakpoints are enabled or not.
Usage
Uploading an image
Upload images from UploadedFile instances (typically from form submissions) to create SourceImage records:
Basic Upload
use Outerweb\ImageLibrary\Facades\ImageLibrary; // Basic upload using default settings $sourceImage = ImageLibrary::upload($request->file('image')); // The SourceImage is now stored and optimized, ready to be attached to models
Upload with Custom Attributes
// Upload to specific disk $sourceImage = ImageLibrary::upload($request->file('image'), [ 'disk' => 's3', ]); // Upload with custom properties and metadata $sourceImage = ImageLibrary::upload($request->file('image'), [ 'disk' => 's3', 'custom_properties' => [ 'photographer' => 'John Doe', 'license' => 'Creative Commons', 'shoot_date' => '2024-01-15', 'camera_model' => 'Canon EOS R5' ], ]);
What Happens During Upload
- Automatic optimization: Images are processed using Spatie Image with your configured driver (GD/Imagick)
- Metadata extraction: Width, height, file size, and MIME type are automatically detected and stored
- UUID generation: A unique identifier is created for organized file storage
- File organization: Images are stored in a structured directory:
{base_path}/{uuid}/original.{extension} - Database record: A
SourceImagemodel is created with all metadata
Attaching an image to your model
After uploading a SourceImage, attach it to your models using the context system:
Basic Attachment
// Upload the source image $sourceImage = ImageLibrary::upload($request->file('image')); // Get your model $product = Product::find(1); // Attach with a context $image = $product->attachImage($sourceImage, [ 'context' => 'thumbnail' ]); // The image is now attached and will be processed according to the context configuration
Advanced Attachment Examples
// Attach with multilingual alt text $image = $product->attachImage($sourceImage, [ 'context' => 'featured_image', 'alt_text' => [ 'en' => 'Taylor Otwell driving his lamborghini', 'nl' => 'Taylor Otwell rijdt in zijn lamborghini', ] ]); // Attach with custom properties and metadata $image = $product->attachImage($sourceImage, [ 'context' => 'gallery', 'custom_properties' => [ 'photographer' => 'Jane Smith', 'copyright' => '© 2024 Company Name', 'location' => 'Swiss Alps', 'camera_settings' => [ 'aperture' => 'f/2.8', 'shutter_speed' => '1/500s', 'iso' => 200 ] ], 'alt_text' => [ 'en' => 'Mountain landscape photography' ] ]);
Attaching to a custom relationship
When using custom relationships, you can still use the attachImage method. You can specify the relationship to use:
$image = $product->attachImage($sourceImage, [ 'context' => 'featured' ], 'featuredImage');
Context-Specific Behavior
The attachImage method will replace the existing image if the context is not configured to allow multiple images. This ensures that single-image contexts always have only one associated image.
Using your model image(s)
You can access your model's images through the images relationship or any custom relationships you've defined.
// Get all images $images = $product->images; // Query images by context $thumbnails = $product->images() ->where('context', 'thumbnail') ->get(); // Query images with specific custom properties $landscapeImages = $product->images() ->where('context', 'gallery') ->whereJsonContains('custom_properties->layout_builder_block_id', $blockId) ->get();
Rendering images
You can render images in your views using the provided view component:
<x-image-library::image :image="$image" class="rounded-lg w-1/2" />
This will render a picture element with the following:
- a
sourceelement perBreakpointwith responsive image urls - a
sourceelement perBreakpointfor the WebP versions of the responsive image urls - an
imgelement with the default image url, alt text, and any additional attributes you provide
Make sure you added the script component to the <head> of your layout:
<x-image-library::scripts />
This script will set all sizes attributes of the picture elements automatically when:
- The page is loaded
- The viewport is resized
- The picture element is added to the viewport
- The picture element width changes
(Re)generating images
You can (re)generate images using the following artisan command:
php artisan image-library:generate
This will (re)generate all images files for all image records in the database based on their associated ImageContext configuration.
You can also (re)generate image files for a specific image:
php artisan image-library:generate {id}
Or for multiple images:
php artisan image-library:generate {id1} {id2} {id3}
Upgrading
From v2.x to v3.0
This is a major version with breaking changes. See the Upgrade guide for detailed instructions.
Changelog
Please see CHANGELOG for more information on what has changed recently.
License
The MIT License (MIT). Please see License File for more information.