芝麻web文件管理V1.00
编辑当前文件:/home/pulsehostuk9/www/invoicer.pulsehost.co.uk/vendor/spatie/image/src/Drivers/Gd/GdDriver.php
*/ protected array $exif = []; protected int $quality = -1; protected string $originalPath; public function new(int $width, int $height, ?string $backgroundColor = null): static { $image = imagecreatetruecolor($width, $height); if (! $image) { throw new Exception('Could not create image'); } $backgroundColor = new GdColor($backgroundColor); imagefill($image, 0, 0, $backgroundColor->getInt()); return (new static)->setImage($image); } protected function setImage(GdImage $image): static { $this->image = $image; return $this; } public function loadFile(string $path, bool $autoRotate = true): static { $this->optimize = false; $this->quality = -1; $this->originalPath = $path; $handle = fopen($path, 'r'); $contents = ''; if (filesize($path)) { $contents = fread($handle, filesize($path)); } fclose($handle); $this->setExif($path); $image = @imagecreatefromstring($contents); if (! $image) { throw CouldNotLoadImage::make($path); } imagealphablending($image, false); imagesavealpha($image, true); $this->image = $image; if ($autoRotate) { $this->autoRotate(); } return $this; } public function image(): GdImage { return $this->image; } public function getWidth(): int { return imagesx($this->image); } public function getHeight(): int { return imagesy($this->image); } public function brightness(int $brightness): static { // TODO: Convert value between -100 and 100 to -255 and 255 $brightness = round($brightness * 2.55); imagefilter($this->image, IMG_FILTER_BRIGHTNESS, $brightness); return $this; } public function blur(int $blur): static { for ($i = 0; $i < $blur; $i++) { imagefilter($this->image, IMG_FILTER_GAUSSIAN_BLUR); } return $this; } public function save(?string $path = null): static { if (! $path) { $path = $this->originalPath; } if (is_null($this->format)) { $extension = pathinfo($path, PATHINFO_EXTENSION); } else { $extension = $this->format; } switch (strtolower($extension)) { case 'jpg': case 'jpeg': case 'jfif': imagejpeg($this->image, $path, $this->quality); break; case 'png': imagepng($this->image, $path, $this->pngCompression()); break; case 'gif': imagegif($this->image, $path); break; case 'webp': $quality = $this->quality === 100 ? IMG_WEBP_LOSSLESS : $this->quality; imagewebp($this->image, $path, $quality); break; case 'avif': imageavif($this->image, $path, $this->quality); break; default: throw UnsupportedImageFormat::make($extension); } if ($this->optimize) { $this->optimizerChain->optimize($path); } $this->format = null; return $this; } public function base64(string $imageFormat = 'jpeg', bool $prefixWithFormat = true): string { ob_start(); switch (strtolower($imageFormat)) { case 'jpg': case 'jpeg': case 'jfif': imagejpeg($this->image, null, $this->quality); break; case 'png': imagepng($this->image, null, $this->pngCompression()); break; case 'gif': imagegif($this->image, null); break; case 'webp': imagewebp($this->image, null); break; case 'avif': imageavif($this->image, null); break; default: throw UnsupportedImageFormat::make($imageFormat); } $imageData = ob_get_contents(); ob_end_clean(); if ($prefixWithFormat) { return 'data:image/'.$imageFormat.';base64,'.base64_encode($imageData); } return base64_encode($imageData); } public function driverName(): string { return 'gd'; } public function getSize(): Size { return new Size($this->getWidth(), $this->getHeight()); } public function fit( Fit $fit, ?int $desiredWidth = null, ?int $desiredHeight = null, bool $relative = false, string $backgroundColor = '#ffffff' ): static { if ($fit === Fit::Crop) { return $this->fitCrop($fit, $this->getWidth(), $this->getHeight(), $desiredWidth, $desiredHeight); } $calculatedSize = $fit->calculateSize( $this->getWidth(), $this->getHeight(), $desiredWidth, $desiredHeight ); $this->modify( $calculatedSize->width, $calculatedSize->height, 0, 0, $this->getWidth(), $this->getHeight(), ); if ($fit->shouldResizeCanvas()) { $this->resizeCanvas($desiredWidth, $desiredHeight, AlignPosition::Center, $relative, $backgroundColor); } return $this; } protected function modify( int $desiredWidth, int $desiredHeight, int $sourceX = 0, int $sourceY = 0, int $sourceWidth = 0, int $sourceHeight = 0, ): static { $newImage = imagecreatetruecolor($desiredWidth, $desiredHeight); $transparentColorValue = imagecolortransparent($this->image); if ($transparentColorValue !== -1) { $rgba = imagecolorsforindex($newImage, $transparentColorValue); $transparentColor = imagecolorallocatealpha( $newImage, $rgba['red'], $rgba['green'], $rgba['blue'], 127 ); imagefill($newImage, 0, 0, $transparentColor); imagecolortransparent($newImage, $transparentColor); } else { imagealphablending($newImage, false); imagesavealpha($newImage, true); } imagecopyresampled( $newImage, $this->image, 0, 0, $sourceX, $sourceY, $desiredWidth, $desiredHeight, $sourceWidth, $sourceHeight, ); $this->image = $newImage; return $this; } public function pickColor(int $x, int $y, ColorFormat $colorFormat): mixed { $color = imagecolorat($this->image, $x, $y); if (! imageistruecolor($this->image)) { $color = imagecolorsforindex($this->image, $color); $color['alpha'] = round(1 - $color['alpha'] / 127, 2); } $color = new GdColor($color); return $color->format($colorFormat); } public function resizeCanvas( ?int $width = null, ?int $height = null, ?AlignPosition $position = null, bool $relative = false, string $backgroundColor = '#ffffff' ): static { $position ??= AlignPosition::Center; $originalWidth = $this->getWidth(); $originalHeight = $this->getHeight(); $width ??= $originalWidth; $height ??= $originalHeight; if ($relative) { $width = $originalWidth + $width; $height = $originalHeight + $height; } // check for negative width/height $width = ($width <= 0) ? $width + $originalWidth : $width; $height = ($height <= 0) ? $height + $originalHeight : $height; // create new canvas $canvas = $this->new($width, $height, $backgroundColor); // set copy position $canvasSize = $canvas->getSize()->align($position); $imageSize = $this->getSize()->align($position); $canvasPosition = $imageSize->relativePosition($canvasSize); $imagePosition = $canvasSize->relativePosition($imageSize); if ($width <= $originalWidth) { $destinationX = 0; $sourceX = $canvasPosition->x; $sourceWidth = $canvasSize->width; } else { $destinationX = $imagePosition->x; $sourceX = 0; $sourceWidth = $originalWidth; } if ($height <= $originalHeight) { $destinationY = 0; $sourceY = $canvasPosition->y; $sourceHeight = $canvasSize->height; } else { $destinationY = $imagePosition->y; $sourceY = 0; $sourceHeight = $originalHeight; } // make image area transparent to keep transparency // even if background-color is set $transparent = imagecolorallocatealpha($canvas->image, 255, 255, 255, 127); imagealphablending($canvas->image, false); // do not blend / just overwrite imagefilledrectangle($canvas->image, $destinationX, $destinationY, $destinationX + $sourceWidth - 1, $destinationY + $sourceHeight - 1, $transparent); // copy image into new canvas imagecopy($canvas->image, $this->image, $destinationX, $destinationY, $sourceX, $sourceY, $sourceWidth, $sourceHeight); // set new core to canvas $this->image = $canvas->image; return $this; } public function gamma(float $gamma): static { imagegammacorrect($this->image, 1, $gamma); return $this; } public function contrast(float $level): static { imagefilter($this->image, IMG_FILTER_CONTRAST, ($level * -1)); return $this; } public function colorize(int $red, int $green, int $blue): static { $red = round($red * 2.55); $green = round($green * 2.55); $blue = round($blue * 2.55); imagefilter($this->image, IMG_FILTER_COLORIZE, $red, $green, $blue); return $this; } public function greyscale(): static { imagefilter($this->image, IMG_FILTER_GRAYSCALE); return $this; } public function manualCrop(int $width, int $height, ?int $x = null, ?int $y = null): static { $cropped = new Size($width, $height); $position = new Point($x ?? 0, $y ?? 0); if (is_null($x) && is_null($y)) { $position = $this ->getSize() ->align(AlignPosition::Center) ->relativePosition($cropped->align(AlignPosition::Center)); } $maxCroppedWidth = $this->getWidth() - $x; $maxCroppedHeight = $this->getHeight() - $y; $width = min($cropped->width, $maxCroppedWidth); $height = min($cropped->height, $maxCroppedHeight); $this->modify( $width, $height, $position->x, $position->y, $width, $height, ); return $this; } public function crop(int $width, int $height, CropPosition $position = CropPosition::Center): static { $width = min($width, $this->getWidth()); $height = min($height, $this->getHeight()); [$offsetX, $offsetY] = $this->calculateCropOffsets($width, $height, $position); $maxWidth = $this->getWidth() - $offsetX; $maxHeight = $this->getHeight() - $offsetY; $width = min($width, $maxWidth); $height = min($height, $maxHeight); return $this->manualCrop($width, $height, $offsetX, $offsetY); } public function focalCrop(int $width, int $height, ?int $cropCenterX = null, ?int $cropCenterY = null): static { [$width, $height, $cropCenterX, $cropCenterY] = $this->calculateFocalCropCoordinates( $width, $height, $cropCenterX, $cropCenterY ); $this->manualCrop($width, $height, $cropCenterX, $cropCenterY); return $this; } public function sepia(): static { return $this ->greyscale() ->brightness(0) ->contrast(5) ->colorize(38, 25, 10) ->contrast(5); } public function sharpen(float $amount): static { $min = $amount >= 10 ? $amount * -0.01 : 0; $max = $amount * -0.025; $abs = ((4 * $min + 4 * $max) * -1) + 1; $matrix = [ [$min, $max, $min], [$max, $abs, $max], [$min, $max, $min], ]; imageconvolution($this->image, $matrix, 1, 0); return $this; } public function background(string $color): static { $width = $this->getWidth(); $height = $this->getHeight(); $newImage = $this->new($width, $height, $color); $backgroundSize = $newImage->getSize()->align(AlignPosition::TopLeft); $overlaySize = $this->getSize()->align(AlignPosition::TopLeft); $target = $backgroundSize->relativePosition($overlaySize); $this->overlay($newImage, $this, $target->x, $target->y); return $this; } public function overlay(ImageDriver $bottomImage, ImageDriver $topImage, int $x = 0, int $y = 0): static { $bottomImage->insert($topImage, AlignPosition::TopLeft, $x, $y); $this->image = $bottomImage->image(); return $this; } public function orientation(?Orientation $orientation = null): static { if (is_null($orientation)) { $orientation = $this->getOrientationFromExif($this->exif); } $this->image = imagerotate($this->image, $orientation->degrees() * -1, 0); return $this; } public function setExif(string $path): void { if (! extension_loaded('exif')) { return; } if (! extension_loaded('fileinfo')) { return; } $fInfo = finfo_open(FILEINFO_RAW); if ($fInfo) { $info = finfo_file($fInfo, $path); finfo_close($fInfo); } if (! isset($info) || ! is_string($info) || ! str_contains($info, 'Exif')) { return; } $result = @exif_read_data($path); if (! is_array($result)) { $this->exif = []; return; } $this->exif = $result; } /** * @return array
*/ public function exif(): array { return $this->exif; } public function flip(FlipDirection $flip): static { $direction = match ($flip) { FlipDirection::Horizontal => IMG_FLIP_HORIZONTAL, FlipDirection::Vertical => IMG_FLIP_VERTICAL, FlipDirection::Both => IMG_FLIP_BOTH, }; imageflip($this->image, $direction); return $this; } public function pixelate(int $pixelate = 50): static { imagefilter($this->image, IMG_FILTER_PIXELATE, $pixelate, true); return $this; } public function insert( ImageDriver|string $otherImage, AlignPosition $position = AlignPosition::Center, int $x = 0, int $y = 0, int $alpha = 100 ): static { $this->ensureNumberBetween($alpha, 0, 100, 'alpha'); if (is_string($otherImage)) { $otherImage = (new self())->loadFile($otherImage); } $imageSize = $this->getSize()->align($position, $x, $y); $otherImageSize = $otherImage->getSize()->align($position); $target = $imageSize->relativePosition($otherImageSize); imagealphablending($this->image, true); // check here for the next 3 line https://www.php.net/manual/en/function.imagecopymerge.php#92787 $cut = imagecreatetruecolor($otherImageSize->width, $otherImageSize->height); if (! $cut) { throw new Exception('Could not create image'); } imagecopy($cut, $this->image, 0, 0, $target->x, $target->y, $otherImageSize->width, $otherImageSize->height); imagecopy($cut, $otherImage->image, 0, 0, 0, 0, $otherImageSize->width, $otherImageSize->height); imagecopymerge( $this->image, $cut, $target->x, $target->y, 0, 0, $otherImageSize->width, $otherImageSize->height, $alpha ); return $this; } public function resize(int $width, int $height, array $constraints = []): static { $resized = $this->getSize()->resize($width, $height, $constraints); $this->modify($resized->width, $resized->height, 0, 0, $this->getWidth(), $this->getHeight()); return $this; } public function width(int $width, array $constraints = [Constraint::PreserveAspectRatio]): static { $newHeight = (int) round($width / $this->getSize()->aspectRatio()); $this->resize($width, $newHeight, $constraints); return $this; } public function height(int $height, array $constraints = [Constraint::PreserveAspectRatio]): static { $newWidth = (int) round($height * $this->getSize()->aspectRatio()); $this->resize($newWidth, $height, $constraints); return $this; } public function border(int $width, BorderType $type, string $color = '000000'): static { imagealphablending($this->image, true); imagesavealpha($this->image, true); if ($type === BorderType::Shrink) { $originalWidth = $this->getWidth(); $originalHeight = $this->getHeight(); $this ->resize( (int) round($this->getWidth() - ($width * 2)), (int) round($this->getHeight() - ($width * 2)), [Constraint::PreserveAspectRatio], ) ->resizeCanvas( $originalWidth, $originalHeight, AlignPosition::Center, false, $color, ); return $this; } if ($type === BorderType::Expand) { $this->resizeCanvas( (int) round($width * 2), (int) round($width * 2), AlignPosition::Center, true, $color, ); return $this; } if ($type === BorderType::Overlay) { $backgroundColor = new GdColor(null); imagefilledrectangle( $this->image, (int) round($width / 2), (int) round($width / 2), (int) round($this->getWidth() - ($width / 2)), (int) round($this->getHeight() - ($width / 2)), $backgroundColor->getInt() ); $borderColor = new GdColor($color); imagesetthickness($this->image, $width); imagerectangle( $this->image, (int) round($width / 2), (int) round($width / 2), (int) round($this->getWidth() - ($width / 2)), (int) round($this->getHeight() - ($width / 2)), $borderColor->getInt() ); return $this; } return $this; } /** @param int<-1, 100> $quality */ public function quality(int $quality): static { $this->quality = $quality; return $this; } /** @return int<-1, 9> */ protected function pngCompression(): int { if ($this->quality === -1) { return -1; } return (int) round((100 - $this->quality) / 10); } public function format(string $format): static { if (! in_array($format, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif'])) { throw UnsupportedImageFormat::make($format); } $this->format = $format; return $this; } public function autoRotate(): void { if (! $this->exif || empty($this->exif['Orientation'])) { return; } switch ($this->exif['Orientation']) { case 8: $this->image = imagerotate($this->image, 90, 0); break; case 3: $this->image = imagerotate($this->image, 180, 0); break; case 5: case 7: case 6: $this->image = imagerotate($this->image, -90, 0); break; } } public function text( string $text, int $fontSize, string $color = '000000', int $x = 0, int $y = 0, int $angle = 0, string $fontPath = '', int $width = 0, ): static { $textColor = new GdColor($color); if (! $fontPath || ! file_exists($fontPath)) { throw InvalidFont::make($fontPath); } imagettftext( $this->image, $fontSize, $angle, $x, $y, $textColor->getInt(), $fontPath, $width > 0 ? $this->wrapText($text, $fontSize, $fontPath, $angle, $width) : $text, ); return $this; } public function wrapText(string $text, int $fontSize, string $fontPath = '', int $angle = 0, int $width = 0): string { if (! $fontPath || ! file_exists($fontPath)) { throw InvalidFont::make($fontPath); } $wrapped = ''; $words = explode(' ', $text); foreach ($words as $word) { $teststring = "{$wrapped} {$word}"; $testbox = imagettfbbox($fontSize, $angle, $fontPath, $teststring); if (! $testbox) { $wrapped .= ' '.$word; continue; } if ($testbox[2] > $width) { $wrapped .= "\n".$word; } else { $wrapped .= ' '.$word; } } return $wrapped; } }