normalizeVariant(trim($sourceUrl)); } if ($variants === []) { throw new RuntimeException('Tidak ada image source yang bisa dinormalisasi ke format story.'); } return $variants; } public function normalizeVariant(string $sourceUrl): array { if (! extension_loaded('gd')) { throw new RuntimeException('GD extension belum aktif, jadi normalisasi image story belum bisa dijalankan.'); } $binary = $this->loadImageBinary($sourceUrl); $resource = @imagecreatefromstring($binary); if ($resource === false) { throw new RuntimeException('Gagal membaca image source untuk dinormalisasi ke story post.'); } $storyCanvas = $this->renderCanvas($resource, 1080, 1920); $thumbCanvas = $this->renderCanvas($resource, 270, 480); imagedestroy($resource); $storyRelative = 'images/story_'.now()->format('Ymd_His').'_'.Str::lower(Str::random(8)).'.jpg'; $thumbRelative = 'images/story_thumb_'.now()->format('Ymd_His').'_'.Str::lower(Str::random(8)).'.jpg'; Storage::disk('public')->put($storyRelative, $this->jpegBinary($storyCanvas, 92)); Storage::disk('public')->put($thumbRelative, $this->jpegBinary($thumbCanvas, 82)); imagedestroy($storyCanvas); imagedestroy($thumbCanvas); return [ 'url' => $this->offloadPublicPath($storyRelative), 'thumbnail_url' => $this->offloadPublicPath($thumbRelative), 'width' => 1080, 'height' => 1920, ]; } private function offloadPublicPath(string $relativePath): string { try { return $this->hfMedia()->offloadPublicRelativePath($relativePath); } catch (\Throwable) { return Storage::url($relativePath); } } private function hfMedia(): HuggingFaceMediaStorageService { if ($this->hfMediaStorage instanceof HuggingFaceMediaStorageService) { return $this->hfMediaStorage; } $service = app(HuggingFaceMediaStorageService::class); if (! $service instanceof HuggingFaceMediaStorageService) { throw new RuntimeException('Service HuggingFaceMediaStorageService tidak tersedia.'); } $this->hfMediaStorage = $service; return $this->hfMediaStorage; } private function renderCanvas($source, int $targetWidth, int $targetHeight) { $sourceWidth = imagesx($source); $sourceHeight = imagesy($source); $scale = max($targetWidth / max(1, $sourceWidth), $targetHeight / max(1, $sourceHeight)); $resizedWidth = (int) ceil($sourceWidth * $scale); $resizedHeight = (int) ceil($sourceHeight * $scale); $offsetX = (int) floor(($resizedWidth - $targetWidth) / 2); $offsetY = (int) floor(($resizedHeight - $targetHeight) / 2); $resized = imagecreatetruecolor($resizedWidth, $resizedHeight); $canvas = imagecreatetruecolor($targetWidth, $targetHeight); if ($resized === false || $canvas === false) { throw new RuntimeException('Gagal menyiapkan canvas image story.'); } imagealphablending($resized, true); imagesavealpha($resized, true); imagealphablending($canvas, true); imagesavealpha($canvas, true); $background = imagecolorallocate($resized, 11, 19, 31); imagefilledrectangle($resized, 0, 0, $resizedWidth, $resizedHeight, $background); imagecopyresampled($resized, $source, 0, 0, 0, 0, $resizedWidth, $resizedHeight, $sourceWidth, $sourceHeight); $canvasBg = imagecolorallocate($canvas, 11, 19, 31); imagefilledrectangle($canvas, 0, 0, $targetWidth, $targetHeight, $canvasBg); imagecopy($canvas, $resized, 0, 0, $offsetX, $offsetY, $targetWidth, $targetHeight); imagedestroy($resized); return $canvas; } private function jpegBinary($canvas, int $quality): string { ob_start(); imagejpeg($canvas, null, $quality); $binary = ob_get_clean(); if (! is_string($binary) || $binary === '') { throw new RuntimeException('Gagal mengekspor image story ke JPEG.'); } return $binary; } private function loadImageBinary(string $imageUrl): string { $path = parse_url($imageUrl, PHP_URL_PATH) ?: $imageUrl; if (is_string($path) && str_starts_with($path, '/storage/')) { $relative = ltrim(substr($path, strlen('/storage/')), '/'); if (Storage::disk('public')->exists($relative)) { $binary = Storage::disk('public')->get($relative); if ($binary !== '') { return $binary; } } } $response = Http::timeout(90)->get($imageUrl); if (! $response->successful()) { throw new RuntimeException('Gagal mengambil image source untuk story post.'); } return (string) $response->body(); } }