path($sourcePath); if (! file_exists($fullSrc)) { return null; } $data = @file_get_contents($fullSrc); if ($data === false) { return null; } // Prefer robust decode via GD from string (handles jpeg/png/webp if compiled) $src = @imagecreatefromstring($data); if (! $src) { return null; } $w = imagesx($src); $h = imagesy($src); if ($w === 0 || $h === 0) { imagedestroy($src); return null; } $scale = min(1.0, $maxEdge / max($w, $h)); $tw = (int) max(1, round($w * $scale)); $th = (int) max(1, round($h * $scale)); $dst = imagecreatetruecolor($tw, $th); imagecopyresampled($dst, $src, 0, 0, 0, 0, $tw, $th, $w, $h); // Ensure destination directory exists $destDir = dirname($destPath); Storage::disk($disk)->makeDirectory($destDir); $fullDest = Storage::disk($disk)->path($destPath); // Encode JPEG @imagejpeg($dst, $fullDest, $quality); imagedestroy($dst); imagedestroy($src); // Confirm file written if (! file_exists($fullDest)) { return null; } return $destPath; } catch (\Throwable $e) { // Silent failure; caller can fall back to original return null; } } /** * Apply a watermark in-place on the given disk/path. * Expects $config with keys: asset (path), position, opacity (0..1), scale (0..1), padding (px). */ public static function applyWatermarkOnDisk(string $disk, string $path, array $config): bool { $fullSrc = Storage::disk($disk)->path($path); if (! file_exists($fullSrc)) { return false; } $assetPath = $config['asset'] ?? null; if (! $assetPath) { return false; } $assetFull = null; if (Storage::disk('public')->exists($assetPath)) { $assetFull = Storage::disk('public')->path($assetPath); } elseif (file_exists($assetPath)) { $assetFull = $assetPath; } if (! $assetFull || ! file_exists($assetFull)) { return false; } try { $data = @file_get_contents($fullSrc); $src = $data !== false ? @imagecreatefromstring($data) : null; if (! $src) { return false; } $wmData = @file_get_contents($assetFull); $watermark = $wmData !== false ? @imagecreatefromstring($wmData) : null; if (! $watermark) { imagedestroy($src); return false; } imagesavealpha($src, true); imagesavealpha($watermark, true); $srcW = imagesx($src); $srcH = imagesy($src); $wmW = imagesx($watermark); $wmH = imagesy($watermark); if ($srcW <= 0 || $srcH <= 0 || $wmW <= 0 || $wmH <= 0) { imagedestroy($src); imagedestroy($watermark); return false; } $scale = max(0.05, min(1.0, (float) ($config['scale'] ?? 0.2))); $targetW = max(1, (int) round($srcW * $scale)); $targetH = max(1, (int) round($wmH * ($targetW / $wmW))); $resized = imagecreatetruecolor($targetW, $targetH); imagealphablending($resized, false); imagesavealpha($resized, true); imagecopyresampled($resized, $watermark, 0, 0, 0, 0, $targetW, $targetH, $wmW, $wmH); imagedestroy($watermark); $padding = max(0, (int) ($config['padding'] ?? 0)); $position = $config['position'] ?? 'bottom-right'; $x = $padding; $y = $padding; switch ($position) { case 'top-right': $x = max(0, $srcW - $targetW - $padding); break; case 'top-center': $x = (int) max(0, ($srcW - $targetW) / 2); break; case 'middle-left': $y = (int) max(0, ($srcH - $targetH) / 2); break; case 'center': $x = (int) max(0, ($srcW - $targetW) / 2); $y = (int) max(0, ($srcH - $targetH) / 2); break; case 'middle-right': $x = max(0, $srcW - $targetW - $padding); $y = (int) max(0, ($srcH - $targetH) / 2); break; case 'bottom-left': $y = max(0, $srcH - $targetH - $padding); break; case 'bottom-center': $x = (int) max(0, ($srcW - $targetW) / 2); $y = max(0, $srcH - $targetH - $padding); break; case 'bottom-right': $x = max(0, $srcW - $targetW - $padding); $y = max(0, $srcH - $targetH - $padding); break; default: break; } $offsetX = (int) ($config['offset_x'] ?? 0); $offsetY = (int) ($config['offset_y'] ?? 0); $x = max(0, min($srcW - $targetW, $x + $offsetX)); $y = max(0, min($srcH - $targetH, $y + $offsetY)); $opacity = max(0.0, min(1.0, (float) ($config['opacity'] ?? 0.25))); $mergeOpacity = (int) round($opacity * 100); // imagecopymerge uses 0-100 imagealphablending($src, true); imagecopymerge($src, $resized, $x, $y, 0, 0, $targetW, $targetH, $mergeOpacity); imagedestroy($resized); // Overwrite original (respect mime: always JPEG for compatibility) @imagejpeg($src, $fullSrc, 90); imagedestroy($src); return true; } catch (\Throwable $e) { return false; } } /** * Copy a source to destination and apply watermark there. */ public static function copyWithWatermark(string $disk, string $sourcePath, string $destPath, array $config): ?string { if (! Storage::disk($disk)->exists($sourcePath)) { return null; } Storage::disk($disk)->makeDirectory(dirname($destPath)); Storage::disk($disk)->copy($sourcePath, $destPath); $applied = self::applyWatermarkOnDisk($disk, $destPath, $config); return $applied ? $destPath : null; } }