基于 Castles 有价值的答案,我试图找到在 PHP 中执行此操作的最佳解决方案。他引用的实现有两个主要缺陷:一个是它没有考虑不透明度(如果有的话),第二个是非常缓慢且耗费资源。在 PHP 中处理 500x500 像素的图像大约需要 15 秒,其中 Apache 将保持处理器高达 95%。
我发现最快和最少的资源消耗实际上是在 HTML5 中使用画布处理图像。结果令人惊叹,图像正在现场处理。
我将在最后的代码块下方发布,一个用于 PHP,一个用于 HTML。如果你需要使用这个服务器端,你可以在 Node.js 和 NodeCanvas 中复制粘贴 HTML 代码:https ://github.com/LearnBoost/node-canvas
PHP(不透明):
<?php
function Lum($colour) {
return ($colour['r'] * 0.3) + ($colour['g'] * 0.59) + ($colour['b'] * 0.11);
}
function ClipColour($colour) {
$result = $colour;
$luminance = Lum($colour);
$cMin = min($colour['r'], $colour['g'], $colour['b']);
$cMax = max($colour['r'], $colour['g'], $colour['b']);
if ($cMin < 0.0) {
$result['r'] = $luminance + ((($colour['r'] - $luminance) * $luminance) / ($luminance - $cMin));
$result['g'] = $luminance + ((($colour['g'] - $luminance) * $luminance) / ($luminance - $cMin));
$result['b'] = $luminance + ((($colour['b'] - $luminance) * $luminance) / ($luminance - $cMin));
}
if ($cMax > 255) {
$result['r'] = $luminance + ((($colour['r'] - $luminance) * (255 - $luminance)) / ($cMax - $luminance));
$result['g'] = $luminance + ((($colour['g'] - $luminance) * (255 - $luminance)) / ($cMax - $luminance));
$result['b'] = $luminance + ((($colour['b'] - $luminance) * (255 - $luminance)) / ($cMax - $luminance));
}
return $result;
}
function SetLum($colour, $luminance) {
$result = array();
$diff = $luminance - Lum($colour);
$result['r'] = $colour['r'] + $diff;
$result['g'] = $colour['g'] + $diff;
$result['b'] = $colour['b'] + $diff;
return ClipColour($result);
}
function normalizeColor( $color ) {
$color['r'] = $color['r'] / 255;
$color['g'] = $color['g'] / 255;
$color['b'] = $color['b'] / 255;
return $color;
}
function denormalizeColor( $color ) {
$color['r'] = round($color['r'] * 255);
$color['g'] = round($color['g'] * 255);
$color['b'] = round($color['b'] * 255);
return $color;
}
$overlay_color = array('r'=>180,'g'=>22,'b'=>1, 'a' => 0.35);
$img = new Imagick();
if( !isset($_GET['case']) ) {
$_GET['case'] = '';
}
//unmodified version
$original = new Imagick('girl.jpg');
//photoshop image to compare
$ps = new Imagick('original.jpg');
$img->addImage($original);
$it = $original->getPixelIterator();
foreach( $it as $row => $pixels ) {
foreach ( $pixels as $column => $pixel ) {
$rgbIni = $pixel->getColor();
$rgb = SetLum($overlay_color, Lum($rgbIni));
$overlay_color = normalizeColor($overlay_color);
$rgb = normalizeColor($rgb);
$rgbIni = normalizeColor($rgbIni);
$rgb['r'] = ((1 - $overlay_color['a']) * $rgbIni['r']) + ($overlay_color['a'] * $rgb['r']);
$rgb['g'] = ((1 - $overlay_color['a']) * $rgbIni['g']) + ($overlay_color['a'] * $rgb['g']);
$rgb['b'] = ((1 - $overlay_color['a']) * $rgbIni['b']) + ($overlay_color['a'] * $rgb['b']);
$test = denormalizeColor($test);
$rgb = denormalizeColor($rgb);
$overlay_color = denormalizeColor($overlay_color);
$pixel->setColor('rgb('.round($rgb['r']).','. round($rgb['g']).','.round($rgb['b']).')');
}
$it->syncIterator();
}
//add modified version
$img->addImage($original);
$img->addImage($ps);
$img->resetIterator();
$combined = $img->appendImages(true); //stack images
header('content-type: image/jpeg');
$combined->setImageFormat("jpeg");
echo $combined;
?>
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var RGBA = function(r, g, b, a) {
this.R = r || 0;
this.G = g || 0;
this.B = b || 0;
this.A = a || 0.5;
}
function SetLum(initialColor, pixelColor) {
var initalColorLuminance = initialColor.R * 0.3 + initialColor.G * 0.59 + initialColor.B * 0.11;
var pixelColorLuminance = pixelColor.R * 0.3 + pixelColor.G * 0.59 + pixelColor.B * 0.11;
var diff = pixelColorLuminance - initalColorLuminance;
var response = new Array;
response[0] = initialColor.R + diff;
response[1] = initialColor.G + diff;
response[2] = initialColor.B + diff;
//console.log(response[0]);
return ClipColour(response);
}
function alphaComposite(mv, ov, a) {
return (mv * a) + (ov * (1 - a));
}
function ClipColour(color) { //function to prevent underexposure or overexposure on some pixels
var result = color;
var luminance = color[0] * 0.3 + color[1] * 0.59 + color[1] * 0.11;
var cMin = Math.min(color[0], color[1], color[2]);
var cMax = Math.max(color[0], color[1], color[2]);
if (cMin < 0.0) {
color[0] = luminance + (((color[0] - luminance) * luminance) / (luminance - cMin));
color[1] = luminance + (((color[1] - luminance) * luminance) / (luminance - cMin));
color[2] = luminance + (((color[2] - luminance) * luminance) / (luminance - cMin));
}
if (cMax > 255) {
color[0] = luminance + (((color[0] - luminance) * (255 - luminance)) / (cMax - luminance));
color[1] = luminance + (((color[1] - luminance) * (255 - luminance)) / (cMax - luminance));
color[2] = luminance + (((color[2] - luminance) * (255 - luminance)) / (cMax - luminance));
}
return color;
}
function processImage(image, targetColour) {
var canvas = document.createElement('canvas');
c = canvas.getContext('2d');
canvas.width = image.width;
canvas.height = image.height;
// Draw the building on the original canvas
c.drawImage(image, 0, 0, canvas.width, canvas.height);
// There's a (much) faster way to cycle through all the pixels using typed arrays,
// but I'm playing it safe so that the example works in all browsers.
var imageData = c.getImageData(0, 0, canvas.width, canvas.height),
imageDataPixels = imageData.data;
for (var i = 0, len = imageDataPixels.length; i < len; i += 4) {
var pixelColor = new RGBA(imageDataPixels[i], imageDataPixels[i+1], imageDataPixels[i+2], 1);
var test = SetLum(targetColour, pixelColor);
var r = Math.round(test[0]);
var g = Math.round(test[1]);
var b = Math.round(test[2]);
imageDataPixels[i] = alphaComposite(r, imageDataPixels[i], targetColour.A);
imageDataPixels[i + 1] = alphaComposite(g, imageDataPixels[i + 1], targetColour.A);
imageDataPixels[i + 2] = alphaComposite(b, imageDataPixels[i + 2], targetColour.A);
}
c.putImageData(imageData, 0, 0);
return canvas;
}
document.addEventListener('DOMContentLoaded', function() {
var image = new Image(),
processImageFile = null;
image.src = "girl.jpg";
image.addEventListener('load', function() {
var canvas = document.getElementById('canvas'),
c = canvas.getContext('2d'),
imageRGBA = new RGBA(180, 22, 1, 0.35);
canvas.width = image.width;
canvas.height = image.height;
c.drawImage(image, 0, 0);
processImageFile = processImage(image, imageRGBA);
c.drawImage(processImageFile, 0, 0);
});
});
</script>
</head>
<body>
<img src="girl.jpg" />
<br />
<canvas id="canvas"></canvas>
<br />
<img src="original.jpg" />
</body>