maximum width and height by size name */ public const array SIZES = [ 'list' => [150, 225], 'details' => [600, 900], ]; /** * @param string $data the image file's contents * @param callable(string): string $targetPath gives the file for a size name * * @throws CoverProcessingException */ public function createThumbnails(string $data, callable $targetPath): void { $image = $data === '' ? false : @imagecreatefromstring($data); if ($image === false) { throw new CoverProcessingException('The file could not be read as an image.'); } $image = self::applyExifOrientation($image, $data); foreach (self::SIZES as $name => [$maxWidth, $maxHeight]) { if (!imagejpeg(self::scale($image, $maxWidth, $maxHeight), $targetPath($name), self::JPEG_QUALITY)) { throw new CoverProcessingException('The thumbnail could not be written.'); } } } private static function scale(\GdImage $image, int $maxWidth, int $maxHeight): \GdImage { $width = imagesx($image); $height = imagesy($image); $ratio = min(1, $maxWidth / $width, $maxHeight / $height); $targetWidth = max(1, (int)round($width * $ratio)); $targetHeight = max(1, (int)round($height * $ratio)); // JPEG has no transparency, so transparent PNG and WebP covers go on a white background. $thumbnail = imagecreatetruecolor($targetWidth, $targetHeight); imagefill($thumbnail, 0, 0, (int)imagecolorallocate($thumbnail, 255, 255, 255)); imagecopyresampled($thumbnail, $image, 0, 0, 0, 0, $targetWidth, $targetHeight, $width, $height); return $thumbnail; } /** * Phones store the rotation of a photo as metadata, which thumbnails would otherwise lose. */ private static function applyExifOrientation(\GdImage $image, string $data): \GdImage { if (\IMAGETYPE_JPEG !== (@getimagesizefromstring($data)[2] ?? null)) { return $image; } $orientation = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data))['Orientation'] ?? 1; if (\in_array($orientation, [2, 4, 5, 7], true)) { imageflip($image, \IMG_FLIP_HORIZONTAL); } $angle = match ($orientation) { 3, 4 => 180, 5, 8 => 90, 6, 7 => 270, default => 0, }; return $angle === 0 ? $image : imagerotate($image, $angle, 0); } }