在PHP中水印和存储和显示图像的正确方法


Right way of watermarking & storing & displaying images in PHP

我正在构建一个基于Web的系统,该系统将承载大量高分辨率图像,并且它们将可供出售。当然,我永远不会显示高分辨率图像,相反,浏览时人们只会看到低分辨率的水印图像。目前工作流程如下:

PHP 脚本处理高分辨率图像上传,当图像上传时,它会自动调整为低分辨率图像和缩略图,并且两个文件都保存在服务器上(不添加水印)。

当人们浏览时,页面会显示图像的缩略图,单击时,它会放大并显示带有水印的低分辨率图像。目前,每当打开低分辨率图像时,我都会动态应用水印。

我的问题是,正确的方法是什么:

1) 我是否应该仅在第一次访问时才使用缩略图保存带有缩略图的低分辨率图像的第二个副本?我的意思是,如果有人访问图像,我会即时添加水印,然后显示图像并将其存储在服务器上。下次访问同一图像时,如果存在带水印的副本,只需显示 wm 副本,否则动态应用水印。(如果更改了水印.png,只需删除带水印的图像,它们将在访问时重新创建)。

2)我应该像现在一样继续动态应用水印吗?

我最大的问题是 PHP file_exists() 和为图像添加水印之间的区别有多大,例如:

$image = new Imagick();
$image->readImage($workfolder.$event . DIRECTORY_SEPARATOR . $cat . DIRECTORY_SEPARATOR .$mit);
$watermark = new Imagick();
$watermark->readImage($workfolder.$event . DIRECTORY_SEPARATOR . "hires" . DIRECTORY_SEPARATOR ."WATERMARK.PNG");
$image->compositeImage($watermark, imagick::COMPOSITE_OVER, 0, 0);

所有低分辨率图像均为 1024x1024,JPG 质量设置为 45%,并删除了所有不必要的过滤器,因此低分辨率图像的文件大小约为 40Kb-80Kb。

它与这个问题有某种关系,只是规模和场景有点不同。

我在专用服务器(至强E3-1245v2)CPU上,32 GB 内存,2 TB存储空间),该网站总体上流量不大,但有时会出现巨大的峰值。当图像发布时,我们每小时获得几千次点击,人们浏览图像,下载,购买等。因此,虽然在正常使用中,我确信动态生成是正确的方法,但我有点担心峰值期。

需要提一下,我正在使用ImageMagick库进行图像处理,而不是GD。

感谢您的输入。

更新

没有一个答案是完整的解决方案,但这很好,因为我从来没有寻找过。这是一个艰难的决定,谁该接受,谁该给予赏金。

@Ambroise-Maupate解决方案很好,但它是PHP上的中继来完成这项工作。

@Hugo Delsing建议使用Web服务器来提供缓存文件,减少对PHP脚本的调用,这将意味着使用更少的资源,另一方面它并不是真正的存储友好。

我将使用 2 个答案的混合合并解决方案,中继 CRON 作业以清除垃圾。

谢谢你的指示。

就个人而言,我会以CDN的方式创建一个静态/无cookie的子域来处理这些类型的图像。主要原因是:

  1. 映像仅创建一次
  2. 仅创建访问的映像
  3. 创建后,将从缓存中提供图像,并且速度要快得多。

第一步是在指向空文件夹的子域上创建一个网站。使用 IIS/Apache 的设置或任何设置来禁用此新网站的会话。还要在网站上设置一些长缓存标题,因为内容不应该改变

第二步是创建一个包含以下内容的.htaccess文件。

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)   /create.php?path=$1  [L]

这将确保如果有人访问现有图像,它将直接显示图像而不会PHP干扰。每个不存在的请求都将由create.php脚本处理,这是您应该添加的下一件事。

<?php
function NotFound()
{
    if (!headers_sent()) {
        $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
        header($protocol . ' 404 Not Found');
        echo '<h1>Not Found</h1>';
        exit;
    }
}
$p = $_GET['path'];
//has path
if (strlen($p)<=1)
    NotFound();
$clean = explode('?', $p);
$clean = explode('#', $clean[0]);
$params = explode('/', substr($clean[0], 1)); //drop first /
//I use a check for two, because I dont allow images in the root folder
//I also use the path to determine how it should look
//EG: thumb/125/90/imagecode.jpg
if (count($params)<2)
    NotFound();
$type = $params[0];
//I use the type to handle different methods. For this example I only used the full sized image
//You could use the same to handle thumbnails or cropped/watermarked
switch ($type) {
    //case "crop":if (Crop($params)) return; else break;
    //case "thumb":if (Thumb($params)) return; else break;
    case "image":if (Image($params)) return; else break;
}
NotFound();
?>
<?php
/*
Just some example to show how you could create a responds
Since you already know how to create thumbs, I'm not going into details
Array
(
    [0] => image
    [1] => imagecode.JPG
)
*/
function Image($params) {
    $tmp = explode('.', $params[1]);
    if (count($tmp)!=2)
        return false;
    $code = $tmp[0];

    //WARNING!! SQL INJECTION
    //USE PROPER DB METHODS TO GET REALPATH, THIS IS JUST EXAMPLE
    $query = "SELECT realpath FROM images WHERE Code='".$code."'";
    //exec query here to $row
    $realpath = $row['realpath'];

    $f = file_get_contents($realpath);
    if (strlen($f)<=0)
        return false;
    //create folder structure
    @mkdir($params[0]);
    //if you had more folders, continue creating the structure
    //@mkdir($params[0].'/'.$params[1]);
    //store the image, so a second request won't access this script
    file_put_contents($params[0].'/'.$params[1], $f);
    //you could directly optimize the image for web to make it even better
    //optimizeImage($params[0].'/'.$params[1]);
    //now serve the file to the browser, because even the first request needs to show the image
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    header('Content-Type: '.finfo_file($finfo, $params[0].'/'.$params[1]));
    echo $f;
    return true;
}
?>

我建议您即时创建带水印的图像,并按照每个人的建议同时缓存它们。

然后你可以创建一个垃圾收集器PHP脚本,它将每天执行一次(使用cron)。此脚本将浏览缓存文件夹以读取每个图像访问时间。这可以使用fileatime() PHP方法完成。然后,当缓存的 wm 映像在 24 或 48 小时内未被访问时,只需将其删除即可。

使用此方法,您可以处理峰值周期,因为图像在第一个请求时被缓存。并且您将节省HDD空间,因为垃圾收集器脚本将为您删除未使用的图像。

仅当服务器分区启用了时间更新时,此方法才有效。

见 http://php.net/manual/en/function.fileatime.php

在大多数情况下,懒惰地应用水印可能是最有意义的(在请求时动态生成带水印的图像,然后缓存结果),但是,如果您有很大的需求高峰,您正在创建一种机制来自己DOS:在上传时创建带水印的版本。

考虑您的硬盘存储容量和派克。

我只会在查看时创建带水印的图像。(所以是的,在飞行中)这样,您就不会在一堆正在查看或可能不会被查看的文件中使用太多空间。

不会给缩略图加水印,我宁愿制作一个伪造水印并防止保存的过滤器。该过滤器将应用于所有缩略图,而无需创建第二个图像。

通过这种方式,您所有的拇指扣都带有水印(顶部带有附加元素的假货)。

然后,如果查看这些缩略图之一,它会生成带水印的图像(仅一次),因为在生成后加载新的带水印图像。

这将是处理HDD存储和Pike的最有效方法。

另一种选择是升级您的托管服务。Godaddy提供无限的存储空间和带宽,每年约50美元。