From b0cc7ba12b9be19b527904102b3c4cb35bcff699 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Mon, 29 Jun 2026 15:50:24 -0300 Subject: [PATCH] feat(previews): show full vertical media without cropping Vertical previews (stories, reels, TikTok, Shorts) rendered media full-bleed, so a correctly-sized 9:16 image was clipped by the phone mockup, which is taller than 9:16. A shared VerticalMediaCanvas now renders every vertical format consistently: images show in full with a blurred-background extension filling the gaps (no crop), while videos stay full-bleed like the real feed. This mirrors how the platforms themselves display the content and matches the story blur applied at publish time. Also reduce fitToCanvas to two image decodes instead of three (reuse the probe as the foreground), lowering peak memory on the story publish path. --- app/Services/Media/MediaOptimizer.php | 9 ++--- .../posts/previews/FacebookPreview.vue | 37 +++++++++--------- .../posts/previews/InstagramPreview.vue | 38 +++++++++---------- .../posts/previews/PostMediaPreview.vue | 31 +++------------ .../posts/previews/TikTokPreview.vue | 28 ++++++-------- .../posts/previews/VerticalMediaCanvas.vue | 35 +++++++++++++++++ .../posts/previews/YouTubePreview.vue | 30 ++++++--------- 7 files changed, 102 insertions(+), 106 deletions(-) create mode 100644 resources/js/components/posts/previews/VerticalMediaCanvas.vue diff --git a/app/Services/Media/MediaOptimizer.php b/app/Services/Media/MediaOptimizer.php index 3b208ecd..6c13c0fc 100644 --- a/app/Services/Media/MediaOptimizer.php +++ b/app/Services/Media/MediaOptimizer.php @@ -156,14 +156,14 @@ public function cropToAspectRatio(string $filePath, float $ratio): string */ public function fitToCanvas(string $filePath, int $width, int $height): string { - $probe = $this->manager->decodePath($filePath); + $foreground = $this->manager->decodePath($filePath); $canvasRatio = $width / $height; - $imageRatio = $probe->width() / $probe->height(); + $imageRatio = $foreground->width() / $foreground->height(); $tempFile = tempnam(sys_get_temp_dir(), 'media_fit_'); if (abs($imageRatio - $canvasRatio) < 0.01) { - $sized = $this->manager->decodePath($filePath)->scaleDown($width, $height); + $sized = $foreground->scaleDown($width, $height); file_put_contents($tempFile, (string) $sized->encodeUsingMediaType('image/jpeg', quality: 100)); return $tempFile; @@ -174,8 +174,7 @@ public function fitToCanvas(string $filePath, int $width, int $height): string ->blur(40) ->brightness(-12); - $foreground = $this->manager->decodePath($filePath)->scaleDown($width, $height); - $canvas->insert($foreground, 0, 0, 'center'); + $canvas->insert($foreground->scaleDown($width, $height), 0, 0, 'center'); file_put_contents($tempFile, (string) $canvas->encodeUsingMediaType('image/jpeg', quality: 100)); diff --git a/resources/js/components/posts/previews/FacebookPreview.vue b/resources/js/components/posts/previews/FacebookPreview.vue index f0302ae2..9227cd52 100644 --- a/resources/js/components/posts/previews/FacebookPreview.vue +++ b/resources/js/components/posts/previews/FacebookPreview.vue @@ -3,6 +3,7 @@ import { IconDots, IconPhoto } from '@tabler/icons-vue'; import { computed } from 'vue'; import PostMediaPreview from '@/components/posts/previews/PostMediaPreview.vue'; +import VerticalMediaCanvas from '@/components/posts/previews/VerticalMediaCanvas.vue'; import type { MediaItem } from '@/types/media'; interface SocialAccount { @@ -214,17 +215,15 @@ const displayName = computed(() => props.socialAccount.display_name || props.soc