php-imagine / Imagine

PHP Object Oriented image manipulation library
https://imagine.readthedocs.io
Other
4.42k stars 529 forks source link

Serve image with filename using $image->show(…); #739

Closed piaaaac closed 4 years ago

piaaaac commented 4 years ago

What I want to accomplish is very simple: add an image name to the served image. I made a simple script named thumbs.php that uses Imagine to create the thumbnail with a unique name into a thumbs folder and serves it using $image->show('jpg'). If the thumb has already been generated the file is used directly from the thumbs folder.

thumbs.php looks like this

require_once "../config.php";
require_once path("vendor") ."/autoload.php";
use Imagine\Image\Box;
use Imagine\Image\ImageInterface;
use Imagine\Gd\Imagine;
$imagine = new Imagine();

// --- Check inputs
if (!isset($_GET["path"])) {
    die("Error: no path provided (89754698)");
} elseif (!startsWith(urldecode($_GET["path"]), "content/")) {
    die("Error: path needs to start with 'content/' (98758964)");
}
if (!isset($_GET["w"]) && !isset($_GET["h"])) {
    die("Error: either 'w' or 'h' must be provided (98765342)");
}
if (isset($_GET["q"]) && !is_numeric($_GET["q"])) {
    die("Error: non numeric 'q' provided (83976598)");
} else {
    $q = (isset($_GET["q"])) ? (int)$_GET["q"] : 80;
}

// --- Check if thumb exists
$imagePath = path("site") ."/". urldecode($_GET["path"]);
$cleanName = substr($imagePath, strlen(path("content")) + 1);
$sizeSuffix = isset($_GET["w"]) ? ("w". $_GET["w"]) : ("h". $_GET["h"]);
$outputPath = path("thumbs") ."/". slugify($cleanName. "-q". $q ."-". $sizeSuffix) .".jpg";
if (file_exists($outputPath)) {
    $image = $imagine->open($outputPath);
    $image->show('jpg'); // <<<<<<<<<< add filename?
    exit;
} 

// --- Create and serve thumb
$image = $imagine->open($imagePath);
$w = $image->getSize()->getWidth();
$h = $image->getSize()->getHeight();
if (isset($_GET["w"])) {
    $newW = $_GET["w"];
    $newH = round($h/$w * $newW);
} else {
    $newH = $_GET["h"];
    $newW = round($w/$h * $newH);
}
$image->resize(new Box($newW, $newH));
$image->save($outputPath, ['jpeg_quality' => $q]);
$image->show('jpg'); // <<<<<<<<<< add filename?

Right now all the images are called thumbs.jpg and I think the browser can't cache them. Is this possible? I browsed the docs carefully and didn't find anything.

Additional info

mlocati commented 4 years ago

The browser may cache images not by "name" (whatever you mean with that), but by its URL. So, you should have an unique URL for every image, and play with HTTP headers to tell the browser to cache the returned image.

piaaaac commented 4 years ago

Thanks for your answer. By "name" I mean the filename I see when I download the generated image.

you should have an unique URL for every image, and play with HTTP headers to tell the browser to cache the returned image

Could you point me in the right direction to do that?

mlocati commented 4 years ago

By "name" I mean the filename I see when I download the generated image.

You could try to use the Content-Disposition header, something like this (before sending the image to the client):

header('Content-Disposition: inline; filename="wonderful-image-name.jpg"');

Could you point me in the right direction to do that?

Sure! See here :wink:

piaaaac commented 4 years ago

😅 really?

You could try to use the Content-Disposition header, something like this (before sending the image to the client)

Thanks

piaaaac commented 4 years ago

Is there a way to add a header to the one sent by $image->show() or do I have to send the image manually?

// from src/Gd/Image.php
public function show($format, array $options = array())
{
    header('Content-type: ' . $this->getMimeType($format));
    // additional 'Content-Disposition' header here <<<<

    $this->saveOrOutput($format, $options);

    return $this;
}

Or, looking at the code of saveOrOutput() I wonder if there's a way to add the filename (that would correspond to the 3rd parameter) when it's executed by $image->show(), which was my original intention.

private function saveOrOutput($format, array $options, $filename = null)
{
    $format = $this->normalizeFormat($format);
    // ...
mlocati commented 4 years ago

That would overcomplicated the code... which disposition, inline or attachment? What if the file name is not available, or the original file has a jpg extension but you are serving a png file... The easiest solution is to add headers on your own

piaaaac commented 4 years ago

I solved like follows:

1 - Save the file using Imagine:

$image->save($outputPath, ['jpeg_quality' => $q]);

2 - Serve the image using a separate function that reads the file again and sends it with the Content-Disposition header you suggested:

// Function
function serveJpegFromFile ($path) {
  $parts = explode("/", $path);
  $filename = array_pop($parts);
  $img = imagecreatefromjpeg($path);
  header("Content-type: image/jpeg");
  header("Content-Disposition: inline; filename=$filename");
  imagejpeg($img);
}

// Serve
serveJpegFromFile ($outputPath);

Still wondering, isn't this a pretty common case? It would be handy to specify the filename directly in $image->show(…). Anyway, thanks for your help!