PHP code example of povly / moonshine-intervention-image
1. Go to this page and download the library: Download povly/moonshine-intervention-image library . Choose the download type require .
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
povly / moonshine-intervention-image example snippets
// config/moonshine-intervention-image.php
return [
'default' => [
'quality' => env('MOONSHINE_INTERVENTION_IMAGE_QUALITY', 85),
'quality_webp' => env('MOONSHINE_INTERVENTION_IMAGE_QUALITY_WEBP', 80),
'quality_avif' => env('MOONSHINE_INTERVENTION_IMAGE_QUALITY_AVIF', 65),
'generate_webp' => env('MOONSHINE_INTERVENTION_IMAGE_WEBP', false),
'generate_avif' => env('MOONSHINE_INTERVENTION_IMAGE_AVIF', false),
'strip_metadata' => env('MOONSHINE_INTERVENTION_IMAGE_STRIP_METADATA', false),
'max_width' => env('MOONSHINE_INTERVENTION_IMAGE_MAX_WIDTH'),
'max_height' => env('MOONSHINE_INTERVENTION_IMAGE_MAX_HEIGHT'),
'logging' => env('MOONSHINE_INTERVENTION_IMAGE_LOGGING', false),
],
'png' => [
'indexed' => env('MOONSHINE_INTERVENTION_IMAGE_PNG_INDEXED', true),
'colors' => env('MOONSHINE_INTERVENTION_IMAGE_PNG_COLORS', 256),
],
'queue' => [
'enabled' => env('MOONSHINE_INTERVENTION_IMAGE_QUEUE_ENABLED', false),
'connection' => env('MOONSHINE_INTERVENTION_IMAGE_QUEUE_CONNECTION'),
'queue' => env('MOONSHINE_INTERVENTION_IMAGE_QUEUE_NAME', 'images'),
'delay' => env('MOONSHINE_INTERVENTION_IMAGE_QUEUE_DELAY'),
'tries' => env('MOONSHINE_INTERVENTION_IMAGE_QUEUE_TRIES', 3),
'timeout' => env('MOONSHINE_INTERVENTION_IMAGE_QUEUE_TIMEOUT', 120),
],
'presets' => [
'banner' => [
'quality' => 85,
'quality_webp' => 80,
'quality_avif' => 65,
'generate_webp' => true,
'generate_avif' => false,
'max_width' => 1920,
'max_height' => 1080,
'png_indexed' => true,
],
],
'watermark' => [
'enabled' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_ENABLED', false),
'image' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_IMAGE'),
'position' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_POSITION', 'bottom-right'),
'custom_position' => [
'x' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_CUSTOM_X'),
'y' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_CUSTOM_Y'),
],
'offset_x' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_OFFSET_X', 10),
'offset_y' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_OFFSET_Y', 10),
'opacity' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_OPACITY', 100),
'width' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_WIDTH'),
'height' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_HEIGHT'),
],
'watermark_text' => [
'enabled' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_ENABLED', false),
'text' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT'),
'font' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_FONT'),
'size' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_SIZE', 24),
'color' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_COLOR', 'ffffff'),
'position' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_POSITION', 'bottom-right'),
'custom_position' => [
'x' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_CUSTOM_X'),
'y' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_CUSTOM_Y'),
],
'offset_x' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_OFFSET_X', 10),
'offset_y' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_OFFSET_Y', 10),
'stroke_color' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_STROKE_COLOR'),
'stroke_width' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_STROKE_WIDTH', 2),
'angle' => env('MOONSHINE_INTERVENTION_IMAGE_WATERMARK_TEXT_ANGLE'),
],
'optimize' => [
'disk' => env('MOONSHINE_INTERVENTION_IMAGE_OPTIMIZE_DISK', 'public'),
'exclude_paths' => [],
],
];
use Povly\MoonshineInterventionImage\Fields\InterventionImage;
// Basic usage
InterventionImage::make('Image', 'image')
// Using preset from config
InterventionImage::make('Image', 'image')
->preset('banner')
// With WebP generation
InterventionImage::make('Image', 'image')
->generateWebp()
// With AVIF generation
InterventionImage::make('Image', 'image')
->generateAvif()
// With both formats
InterventionImage::make('Image', 'image')
->generateWebp()
->generateAvif()
// With quality control (1-100)
InterventionImage::make('Image', 'image')
->generateWebp()
->generateAvif()
->quality(85) // Original image
->qualityWebp(80) // WebP version
->qualityAvif(65) // AVIF version
// PNG optimization with indexed colors (reduces file size significantly)
InterventionImage::make('Image', 'image')
->pngIndexed() // Uses default 256 colors
->pngIndexed(true, 128) // Custom color count (2-256)
// Strip metadata (EXIF, IPTC, etc.)
InterventionImage::make('Image', 'image')
->stripMetadata()
// Resize images (keeps aspect ratio)
InterventionImage::make('Image', 'image')
->maxDimensions(1920, 1080)
// Multiple images
InterventionImage::make('Gallery', 'gallery')
->multiple()
->generateWebp()
->generateAvif()
// Enable logging (disabled by default)
InterventionImage::make('Image', 'image')
->generateWebp()
->logging()
// Queue processing (for large images)
InterventionImage::make('Image', 'image')
->generateWebp()
->queue()
// Queue with custom settings
InterventionImage::make('Image', 'image')
->generateWebp()
->queue(true, 'redis', 'images', 10) // enabled, connection, queue, delay(seconds)
use Povly\MoonshineInterventionImage\Enums\WatermarkPosition;
// Basic image watermark
InterventionImage::make('Image', 'image')
->watermarkImage('/path/to/watermark.png')
// With position
InterventionImage::make('Image', 'image')
->watermarkImage(
'/path/to/watermark.png',
WatermarkPosition::TopLeft
)
// Full control
InterventionImage::make('Image', 'image')
->watermarkImage(
path: '/path/to/watermark.png',
position: WatermarkPosition::BottomRight,
offsetX: 20, // Offset from position (pixels)
offsetY: 20,
opacity: 80, // 0-100 (default: 100)
width: 200, // Resize watermark width (optional)
height: null, // Resize watermark height (optional)
customX: null, // Custom X position (when position is 'custom')
customY: null // Custom Y position (when position is 'custom')
)
// Custom position
InterventionImage::make('Image', 'image')
->watermarkImage(
path: '/path/to/watermark.png',
position: WatermarkPosition::Custom,
customX: 150,
customY: 100
)
// Using string position
InterventionImage::make('Image', 'image')
->watermarkImage('/path/to/watermark.png', 'top-left')
use Povly\MoonshineInterventionImage\Enums\WatermarkPosition;
// Basic text watermark
InterventionImage::make('Image', 'image')
->watermarkText('© My Site 2025')
// With position
InterventionImage::make('Image', 'image')
->watermarkText(
'© My Site 2025',
WatermarkPosition::BottomRight
)
// Full control
InterventionImage::make('Image', 'image')
->watermarkText(
text: '© My Site 2025',
position: WatermarkPosition::BottomRight,
offsetX: 20, // Offset from position (pixels)
offsetY: 20,
size: 32, // Font size (default: 24)
color: 'ffffff', // Hex color without # (default: 'ffffff')
customX: null, // Custom X position (when position is 'custom')
customY: null // Custom Y position (when position is 'custom')
)
// With custom font
InterventionImage::make('Image', 'image')
->watermarkText('© My Site', WatermarkPosition::BottomRight)
->watermarkTextFont('/path/to/font.ttf')
// With stroke (text outline)
InterventionImage::make('Image', 'image')
->watermarkText('© My Site', WatermarkPosition::BottomRight)
->watermarkTextStroke('000000', 2) // color, width
// With rotation
InterventionImage::make('Image', 'image')
->watermarkText('© My Site', WatermarkPosition::Center)
->watermarkTextAngle(45) // degrees
// Using string position
InterventionImage::make('Image', 'image')
->watermarkText('© My Site', 'top-left')
// Disable watermark for specific field (even if enabled in config)
InterventionImage::make('Image', 'image')
->disableWatermark()
// config/moonshine-intervention-image.php
'watermark' => [
'enabled' => true,
'image' => public_path('images/watermark.png'),
'position' => 'bottom-right',
'offset_x' => 20,
'offset_y' => 20,
'opacity' => 80,
'width' => 200,
'height' => null,
],
'watermark_text' => [
'enabled' => true,
'text' => '© My Site 2025',
'font' => public_path('fonts/arial.ttf'),
'size' => 24,
'color' => 'ffffff',
'position' => 'bottom-right',
'offset_x' => 20,
'offset_y' => 20,
'stroke_color' => '000000',
'stroke_width' => 2,
'angle' => null,
],
// Move 50px left and 30px up from bottom-right corner
InterventionImage::make('Image', 'image')
->watermarkText(
'© My Site',
WatermarkPosition::BottomRight,
offsetX: 50,
offsetY: 30
)
// config/moonshine-intervention-image.php
'queue' => [
'enabled' => true,
'connection' => null, // null = default connection
'queue' => 'images',
'delay' => null, // seconds or Carbon instance
'tries' => 3,
'timeout' => 120,
],
// Enable queue with default config settings
InterventionImage::make('Image', 'image')
->generateWebp()
->queue()
// Override queue settings per field
InterventionImage::make('Image', 'image')
->generateWebp()
->queue(true, 'redis', 'high-priority', 5)
// Set only delay
InterventionImage::make('Image', 'image')
->generateWebp()
->queue()
->queueDelay(now()->addMinutes(5))
// config/moonshine-intervention-image.php
'optimize' => [
'disk' => env('MOONSHINE_INTERVENTION_IMAGE_OPTIMIZE_DISK', 'public'),
'exclude_paths' => [], // Paths to skip (relative to disk root)
],
'exclude_paths' => [
'assets/icons',
'avatars/raw',
],
InterventionImage::make('Image', 'image')
->pngIndexed() // 256 colors (default)
->pngIndexed(true, 128) // 128 colors - smaller file
->pngIndexed(true, 64) // 64 colors - even smaller
InterventionImage::make('Image', 'image')
->generateAvif()
->qualityAvif(65) // Lower values = smaller files, lower quality
// Via field method
InterventionImage::make('Image', 'image')
->logging()
// Via config
'default' => [
'logging' => true,
],
namespace App\MoonShine\Layouts;
use MoonShine\AssetManager\InlineJs;
use MoonShine\Laravel\Layouts\AppLayout;
final class MoonShineLayout extends AppLayout
{
protected function assets(): array
{
return [
...parent::assets(),
InlineJs::make(<<<'JS'
window.removeImage = function(event, name) {
let button = event.currentTarget;
let imageIndex = button.closest('.dropzone-item')?.dataset.id;
if (imageIndex === undefined) {
return;
}
fetch(`${button.dataset.asyncUrl}&imageIndex=${imageIndex}&name=${name}`)
.then(() => button.closest('.x-removeable')?.remove());
};
JS),
];
}
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Banner extends Model
{
protected $fillable = [
'title',
'image',
'images',
];
protected function casts(): array
{
return [
'images' => 'array',
];
}
}
namespace App\MoonShine\Resources\Banner;
use App\Models\Banner;
use App\MoonShine\Resources\Banner\Pages\BannerIndexPage;
use App\MoonShine\Resources\Banner\Pages\BannerFormPage;
use App\MoonShine\Resources\Banner\Pages\BannerDetailPage;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use MoonShine\Contracts\Core\DependencyInjection\CrudRequestContract;
use MoonShine\Laravel\Resources\ModelResource;
use MoonShine\Support\Attributes\AsyncMethod;
class BannerResource extends ModelResource
{
protected string $model = Banner::class;
protected string $title = 'Banners';
protected function pages(): array
{
return [
BannerIndexPage::class,
BannerFormPage::class,
BannerDetailPage::class,
];
}
#[AsyncMethod]
public function removeImageData(CrudRequestContract $request): void
{
$item = $request->getResource()?->getItem();
$imageIndex = $request->integer('imageIndex');
$name = $request->string('name');
if (is_null($item) || $imageIndex < 0) {
return;
}
$images = $item->{$name} ?? [];
if (isset($images[$imageIndex])) {
$this->deleteImageWithConversions($images[$imageIndex]);
Arr::forget($images, $imageIndex);
$item->{$name} = array_values($images);
$item->save();
}
}
protected function deleteImageWithConversions(string $filePath, string $disk = 'public'): void
{
if (! Storage::disk($disk)->exists($filePath)) {
return;
}
Storage::disk($disk)->delete($filePath);
$info = pathinfo($filePath);
$basePath = $info['dirname'].'/'.$info['filename'];
foreach (['webp', 'avif'] as $format) {
$conversionPath = $basePath.'.'.$format;
if (Storage::disk($disk)->exists($conversionPath)) {
Storage::disk($disk)->delete($conversionPath);
}
}
}
}
namespace App\MoonShine\Resources\Banner\Pages;
use MoonShine\Laravel\Pages\Crud\FormPage;
use MoonShine\UI\Fields\ID;
use MoonShine\UI\Fields\Text;
use MoonShine\UI\Components\Layout\Box;
use Povly\MoonshineInterventionImage\Fields\InterventionImage;
class BannerFormPage extends FormPage
{
protected function fields(): iterable
{
return [
Box::make([
ID::make(),
Text::make('Title', 'title')
->1080)
->removable(),
// Multiple images with AJAX deletion
InterventionImage::make('Gallery', 'images')
->dir('banners/gallery')
->multiple()
->removable(attributes: $this->getRemovableImageAttributes('images'))
->allowedExtensions(['jpg', 'jpeg', 'png', 'webp'])
->generateWebp()
->generateAvif()
->quality(85)
->qualityWebp(80)
->qualityAvif(65)
->pngIndexed()
->maxDimensions(1920, 1080),
]),
];
}
public function getRemovableImageAttributes(string $name): array
{
return [
'data-async-url' => $this->getResource()
? $this->getRouter()->getEndpoints()->method(
'removeImageData',
params: ['resourceItem' => $this->getResource()->getItemID()]
)
: null,
'@click.prevent' => "removeImage(\$event, '{$name}')",
];
}
}
namespace App\MoonShine\Layouts;
use MoonShine\AssetManager\InlineJs;
use MoonShine\Laravel\Layouts\AppLayout;
final class MoonShineLayout extends AppLayout
{
protected function assets(): array
{
return [
...parent::assets(),
InlineJs::make(<<<'JS'
window.removeImage = function(event, name) {
let button = event.currentTarget;
let imageIndex = button.closest('.dropzone-item')?.dataset.id;
if (imageIndex === undefined) {
return;
}
fetch(`${button.dataset.asyncUrl}&imageIndex=${imageIndex}&name=${name}`)
.then(() => button.closest('.x-removeable')?.remove());
};
window.removeLayoutImage = function(event, name) {
let button = event.currentTarget;
let accordion = button.closest('.accordion');
let layoutIndex = parseInt(accordion.querySelector('[data-r-index]')?.dataset.rIndex ?? 0);
let imageIndex = button.closest('.dropzone-item')?.dataset.id ?? 0;
fetch(`${button.dataset.asyncUrl}&imageIndex=${imageIndex}&layoutIndex=${layoutIndex}&name=${name}`)
.then(() => button.closest('.x-removeable')?.remove());
};
JS),
];
}
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use MoonShine\Layouts\Casts\LayoutsCast;
class Banner extends Model
{
protected $fillable = [
'title',
'image',
'images',
'content',
];
protected function casts(): array
{
return [
'images' => 'array',
'content' => LayoutsCast::class,
];
}
}
Schema::table('banners', function (Blueprint $table) {
$table->json('content')->nullable();
});
namespace App\MoonShine\Resources\Banner;
use App\Models\Banner;
use App\MoonShine\Resources\Banner\Pages\BannerIndexPage;
use App\MoonShine\Resources\Banner\Pages\BannerFormPage;
use App\MoonShine\Resources\Banner\Pages\BannerDetailPage;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use MoonShine\Contracts\Core\DependencyInjection\CrudRequestContract;
use MoonShine\Layouts\Casts\LayoutItem;
use MoonShine\Laravel\Resources\ModelResource;
use MoonShine\Support\Attributes\AsyncMethod;
class BannerResource extends ModelResource
{
protected string $model = Banner::class;
protected string $title = 'Banners';
protected function pages(): array
{
return [
BannerIndexPage::class,
BannerFormPage::class,
BannerDetailPage::class,
];
}
#[AsyncMethod]
public function removeImageData(CrudRequestContract $request): void
{
$item = $request->getResource()?->getItem();
$imageIndex = $request->integer('imageIndex');
$name = $request->string('name');
if (is_null($item) || $imageIndex < 0) {
return;
}
$images = $item->{$name} ?? [];
if (isset($images[$imageIndex])) {
$this->deleteImageWithConversions($images[$imageIndex]);
Arr::forget($images, $imageIndex);
$item->{$name} = array_values($images);
$item->save();
}
}
#[AsyncMethod]
public function removeLayoutImageData(CrudRequestContract $request): void
{
$item = $request->getResource()?->getItem();
$imageIndex = $request->integer('imageIndex');
$layoutIndex = $request->integer('layoutIndex');
$name = $request->string('name');
if (is_null($item) || $imageIndex < 0 || $layoutIndex < 0) {
return;
}
$content = $item->content;
if (! isset($content[$layoutIndex])) {
return;
}
$values = $content[$layoutIndex] instanceof LayoutItem
? $content[$layoutIndex]->getValues()
: $content[$layoutIndex]['values'];
$images = data_get($values, $name);
if (is_array($images) && isset($images[$imageIndex])) {
$this->deleteImageWithConversions($images[$imageIndex]);
Arr::forget($images, $imageIndex);
data_set($values, $name, array_values($images));
} elseif (is_string($images)) {
$this->deleteImageWithConversions($images);
data_set($values, $name, null);
}
if ($content[$layoutIndex] instanceof LayoutItem) {
$content[$layoutIndex] = new LayoutItem(
$content[$layoutIndex]->getName(),
$content[$layoutIndex]->getKey(),
$values
);
} else {
$content[$layoutIndex]['values'] = $values;
}
$item->content = $content;
$item->save();
}
protected function deleteImageWithConversions(string $filePath, string $disk = 'public'): void
{
if (! Storage::disk($disk)->exists($filePath)) {
return;
}
Storage::disk($disk)->delete($filePath);
$info = pathinfo($filePath);
$basePath = $info['dirname'].'/'.$info['filename'];
foreach (['webp', 'avif'] as $format) {
$conversionPath = $basePath.'.'.$format;
if (Storage::disk($disk)->exists($conversionPath)) {
Storage::disk($disk)->delete($conversionPath);
}
}
}
}
namespace App\MoonShine\Resources\Banner\Pages;
use MoonShine\Laravel\Pages\Crud\FormPage;
use MoonShine\UI\Fields\ID;
use MoonShine\UI\Fields\Text;
use MoonShine\UI\Components\Layout\Box;
use MoonShine\Layouts\Fields\Layouts;
use Povly\MoonshineInterventionImage\Fields\InterventionImage;
class BannerFormPage extends FormPage
{
protected function fields(): iterable
{
return [
Box::make([
ID::make(),
Text::make('Title', 'title')
-> ->removable(),
// Multiple images (outside layouts)
InterventionImage::make('Gallery', 'images')
->dir('banners/gallery')
->multiple()
->removable(attributes: $this->getRemovableImageAttributes('images'))
->allowedExtensions(['jpg', 'jpeg', 'png', 'webp'])
->generateWebp()
->generateAvif()
->quality(85)
->qualityWebp(80)
->qualityAvif(65)
->pngIndexed()
->maxDimensions(1920, 1080),
// Layouts field with images inside
Layouts::make('Content', 'content')
->addLayout('Image Block', 'image_block', [
Text::make('Title', 'title'),
InterventionImage::make('Image', 'image')
->dir('banners/blocks')
->allowedExtensions(['jpg', 'jpeg', 'png', 'webp'])
->generateWebp()
->generateAvif()
->quality(85)
->qualityWebp(80)
->qualityAvif(65)
->pngIndexed()
->maxDimensions(1920, 1080)
->removable(attributes: $this->getRemovableLayoutImageAttributes('image')),
])
->addLayout('Gallery Block', 'gallery', [
Text::make('Title', 'title'),
InterventionImage::make('Images', 'images')
->dir('banners/gallery-blocks')
->multiple()
->allowedExtensions(['jpg', 'jpeg', 'png', 'webp'])
->generateWebp()
->generateAvif()
->quality(85)
->qualityWebp(80)
->qualityAvif(65)
->pngIndexed()
->maxDimensions(1920, 1080)
->removable(attributes: $this->getRemovableLayoutImageAttributes('images')),
]),
]),
];
}
public function getRemovableImageAttributes(string $name): array
{
return [
'data-async-url' => $this->getResource()
? $this->getRouter()->getEndpoints()->method(
'removeImageData',
params: ['resourceItem' => $this->getResource()->getItemID()]
)
: null,
'@click.prevent' => "removeImage(\$event, '{$name}')",
];
}
public function getRemovableLayoutImageAttributes(string $name): array
{
return [
'data-async-url' => $this->getResource()
? $this->getRouter()->getEndpoints()->method(
'removeLayoutImageData',
params: ['resourceItem' => $this->getResource()->getItemID()]
)
: null,
'@click.prevent' => "removeLayoutImage(\$event, '{$name}')",
];
}
}
bash
php artisan vendor:publish --tag=moonshine-intervention-image-config
bash
# Force queue processing
php artisan moonshine:image:optimize --queue
# Force synchronous (direct) processing
php artisan moonshine:image:optimize --sync
bash
php artisan moonshine:image:optimize --preset=gallery
bash
php artisan moonshine:image:optimize banners/ uploads/ \
--disk=public \
--preset=gallery \
--queue \
--quality=80 \
--strip-metadata