/
githubmirror
/
framework
Обзор
Документация
Войти
/
githubmirror
/
framework
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
13.x
src/Illuminate/Image/Image.php
636 строк
16 KB
Caleb White
[13.x] feat: implement Responsable on Image class (#61111)
09 авг 2026, 17:30
Не верифицирован
09 авг 2026, 17:30
935b827
Код
Авторство
О чём код?
<?php namespace Illuminate\Image; use Closure; use finfo; use Illuminate\Container\Container; use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; use Illuminate\Contracts\Image\Driver; use Illuminate\Contracts\Image\Transformation; use Illuminate\Contracts\Support\Responsable; use Illuminate\Http\Response; use Illuminate\Http\UploadedFile; use Illuminate\Image\Transformations\Blur; use Illuminate\Image\Transformations\Contain; use Illuminate\Image\Transformations\Cover; use Illuminate\Image\Transformations\Crop; use Illuminate\Image\Transformations\FlipHorizontally; use Illuminate\Image\Transformations\FlipVertically; use Illuminate\Image\Transformations\Grayscale; use Illuminate\Image\Transformations\Orient; use Illuminate\Image\Transformations\Resize; use Illuminate\Image\Transformations\Rotate; use Illuminate\Image\Transformations\Scale; use Illuminate\Image\Transformations\Sharpen; use Illuminate\Support\Str; use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\Macroable; use Stringable; use Throwable; class Image implements Responsable, Stringable { use Conditionable, Macroable; /** * The image processing pipeline. */ protected ImagePipeline $pipeline; /** * The driver override. */ protected ?string $driver = null; /** * Whether the image has been processed. */ protected bool $processed = false; /** * The cached hash name. */ protected ?string $hashName = null; /** * Create a new image instance. */ public function __construct( protected Closure|string $contents, protected ?UploadedFile $file = null, ) { $this->pipeline = new ImagePipeline; } /** * Set the cover dimensions. * * @param int<1, max> $width * @param int<1, max> $height */ public function cover(int $width, int $height): static { return $this->transform(new Cover(max(1, $width), max(1, $height))); } /** * Set the contain dimensions. * * @param int<1, max> $width * @param int<1, max> $height */ public function contain(int $width, int $height, ?string $background = null): static { return $this->transform(new Contain(max(1, $width), max(1, $height), $background)); } /** * Crop the image to the given dimensions and position. * * @param int<1, max> $width * @param int<1, max> $height */ public function crop(int $width, int $height, int $x = 0, int $y = 0): static { return $this->transform(new Crop(max(1, $width), max(1, $height), $x, $y)); } /** * Resize the image to the given dimensions. * * @param int<1, max>|null $width * @param int<1, max>|null $height */ public function resize(?int $width = null, ?int $height = null): static { if ($width === null && $height === null) { throw new ImageException('At least one resize dimension must be specified.'); } return $this->transform(new Resize( $width === null ? null : max(1, $width), $height === null ? null : max(1, $height), )); } /** * Rotate the image clockwise by the given angle. */ public function rotate(float $angle, ?string $background = null): static { return $this->transform(new Rotate($angle, $background)); } /** * Set the scale dimensions. * * @param int<1, max>|null $width * @param int<1, max>|null $height */ public function scale(?int $width = null, ?int $height = null): static { if ($width === null && $height === null) { throw new ImageException('At least one scale dimension must be specified.'); } return $this->transform(new Scale( $width === null ? null : max(1, $width), $height === null ? null : max(1, $height), )); } /** * Auto-orient the image based on EXIF data. */ public function orient(): static { return $this->transform(new Orient); } /** * Apply a blur effect. * * @param int<0, 100> $amount */ public function blur(int $amount = 5): static { return $this->transform(new Blur(max(0, min(100, $amount)))); } /** * Convert the image to grayscale. */ public function grayscale(): static { return $this->transform(new Grayscale); } /** * Sharpen the image. * * @param int<0, 100> $amount */ public function sharpen(int $amount = 10): static { return $this->transform(new Sharpen(max(0, min(100, $amount)))); } /** * Flip the image vertically. */ public function flipVertically(): static { return $this->transform(new FlipVertically); } /** * Flip the image horizontally. */ public function flipHorizontally(): static { return $this->transform(new FlipHorizontally); } /** * Flip the image vertically. */ public function flip(): static { return $this->flipVertically(); } /** * Flip the image horizontally. */ public function flop(): static { return $this->flipHorizontally(); } /** * Add a transformation to the image pipeline. */ public function transform(Transformation $transformation): static { return $this->withClone(fn (Image $image) => $image->pipeline->add($transformation)); } /** * Set the optimization options. * * @throws ImageException */ public function optimize(string $format = 'webp', int $quality = ImageOutputOptions::DEFAULT_QUALITY): static { return $this->toFormat($format)->quality($quality); } /** * Set the output quality. * * @param int<1, 100> $quality */ public function quality(int $quality): static { return $this->withOutput(fn (ImageOutputOptions $output) => $output->quality = max(1, min(100, $quality))); } /** * Convert the image to WebP format. */ public function toWebp(): static { return $this->toFormat('webp'); } /** * Convert the image to JPEG format. */ public function toJpg(): static { return $this->toFormat('jpg'); } /** * Convert the image to JPEG format. */ public function toJpeg(): static { return $this->toJpg(); } /** * Convert the image to PNG format. */ public function toPng(): static { return $this->toFormat('png'); } /** * Convert the image to GIF format. */ public function toGif(): static { return $this->toFormat('gif'); } /** * Convert the image to AVIF format. */ public function toAvif(): static { return $this->toFormat('avif'); } /** * Convert the image to HEIC format. */ public function toHeic(): static { return $this->toFormat('heic'); } /** * Convert the image to BMP format. */ public function toBmp(): static { return $this->toFormat('bmp'); } /** * Set the output format. * * @throws ImageException */ public function toFormat(string $format): static { if (! in_array($format, ['webp', 'jpg', 'jpeg', 'png', 'gif', 'avif', 'heic', 'heif', 'bmp'])) { throw new ImageException("The [{$format}] format is not supported."); } $format = $format === 'heif' ? 'heic' : $format; return $this->withOutput(fn (ImageOutputOptions $output) => $output->format = $format); } /** * Store the processed image on a filesystem disk. * * @param array<string, mixed> $options */ public function store(string $path = '', ?string $disk = null, array $options = []): string|false { return $this->storeAs($path, $this->hashName(), $disk, $options); } /** * Store the processed image on a filesystem disk with public visibility. * * @param array<string, mixed> $options */ public function storePublicly(string $path = '', ?string $disk = null, array $options = []): string|false { $options['visibility'] = 'public'; return $this->storeAs($path, $this->hashName(), $disk, $options); } /** * Store the processed image on a filesystem disk with a given name. * * @param array<string, mixed> $options */ public function storeAs(string $path, ?string $name = null, ?string $disk = null, array $options = []): string|false { if (is_null($name)) { [$path, $name] = ['', $path]; } $path = trim($path.'/'.$name, '/'); $result = Container::getInstance()->make(FilesystemFactory::class) ->disk($disk) ->put($path, $this->toBytes(), $options); return $result ? $path : false; } /** * Store the processed image on a filesystem disk with public visibility and a given name. * * @param array<string, mixed> $options */ public function storePubliclyAs(string $path, ?string $name = null, ?string $disk = null, array $options = []): string|false { if (is_null($name)) { [$path, $name] = ['', $path]; } $options['visibility'] = 'public'; return $this->storeAs($path, $name, $disk, $options); } /** * Get a hashed filename with the correct extension. */ public function hashName(string $path = ''): string { $this->hashName ??= Str::random(40); $hash = $this->hashName.'.'.$this->extension(); return $path ? $path.'/'.$hash : $hash; } /** * Process the image and return the raw bytes. */ public function toBytes(): string { if ($this->pipeline->hasChanges() && ! $this->processed) { try { $this->contents = $this->resolveDriver()->process( value($this->contents), $this->pipeline ); } catch (ImageException $e) { throw $e; } catch (Throwable $e) { throw new ImageException("Failed to process image: {$e->getMessage()}", 0, $e); } $this->pipeline = new ImagePipeline; $this->processed = true; } return value($this->contents); } /** * Process the image and return as a base64 encoded string. */ public function toBase64(): string { return base64_encode($this->toBytes()); } /** * Process the image and return as a data URI. */ public function toDataUri(): string { return 'data:'.$this->mimeType().';base64,'.$this->toBase64(); } /** * Get the file extension based on the MIME type. */ public function extension(): string { return match ($this->mimeType()) { 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp', 'image/avif', 'image/x-avif' => 'avif', 'image/heic', 'image/x-heic', 'image/heif' => 'heic', 'image/bmp' => 'bmp', 'image/svg+xml' => 'svg', 'image/tiff' => 'tiff', default => 'bin', }; } /** * Get the MIME type of the processed image. */ public function mimeType(): string { return once(function () { return (new finfo(FILEINFO_MIME_TYPE))->buffer($this->toBytes()); }); } /** * Get the dimensions of the processed image. * * @return array{0: int, 1: int} */ public function dimensions(): array { return once(function () { $contents = $this->toBytes(); // getimagesize() misreports HEIC's coded / padded frame size, so read HEIC via the driver... if (in_array($this->mimeType(), ['image/heic', 'image/heif', 'image/x-heic'], true)) { try { return $this->resolveDriver()->dimensions($contents); } catch (Throwable) { // The driver can't decode this image; fall back to the native reader below... } } $size = @getimagesizefromstring($contents); if ($size === false) { throw new ImageException('Unable to determine the dimensions of the image.'); } return [$size[0], $size[1]]; }); } /** * Get the width of the processed image. */ public function width(): int { return $this->dimensions()[0]; } /** * Get the height of the processed image. */ public function height(): int { return $this->dimensions()[1]; } /** * Get the dominant (average) color of the image as a hex string. */ public function dominantColor(): string { return once(function () { $contents = value($this->contents); if ($this->pipeline->hasChanges() && ! $this->processed) { $contents = $this->resolveDriver()->process($contents, clone $this->pipeline); } return $this->resolveDriver()->dominantColor($contents); }); } /** * Set the driver to use for processing. */ public function using(string $driver): static { $clone = $this->newClone(); $clone->driver = $driver; return $clone; } /** * Use the GD driver for processing. */ public function usingGd(): static { return $this->using('gd'); } /** * Use the Imagick driver for processing. */ public function usingImagick(): static { return $this->using('imagick'); } /** * Resolve the image processing driver. */ protected function resolveDriver(): Driver { $manager = Container::getInstance()->make('image'); return $this->driver ? $manager->driver($this->driver) : $manager->driver(); } /** * Get the underlying uploaded file instance. */ public function file(): ?UploadedFile { return $this->file; } /** * Create an immutable clone with copied options. */ protected function newClone(): static { $clone = clone $this; $clone->pipeline = clone $this->pipeline; $clone->processed = false; return $clone; } /** * Create an immutable clone with updated output options. */ protected function withOutput(Closure $callback): static { return $this->withClone(fn (Image $image) => $callback($image->pipeline->output)); } /** * Create an immutable clone with the given callback applied. */ protected function withClone(Closure $callback): static { $clone = $this->newClone(); $callback($clone); return $clone; } /** * Create an HTTP response that represents the image. * * @param \Illuminate\Http\Request $request */ public function toResponse($request): Response { return new Response($this->toBytes(), 200, [ 'Content-Type' => $this->mimeType(), ]); } /** * Prevent serialization of the image. * * @throws ImageException */ public function __serialize(): never { throw new ImageException('Images cannot be serialized. Store the image first and serialize the path instead.'); } /** * Get the string representation of the image. */ public function toString(): string { return $this->toDataUri(); } /** * Get the string representation of the image. */ public function __toString(): string { return $this->toString(); } }