在ImageMagick中复制Photoshop的“Color”混合模式

5
我需要创建一个命令,模仿Adobe Photoshop中的“颜色”混合模式,在ImageMagick中对图像进行着色。为此,我正在尝试组合原始图像和另一张图像,该图像由一个完整的颜色层组成,透明度为35%。这应该与原始图像混合,并创建一个带有色彩色调的结果图像。
这是预期的结果: the expected result 在Adobe网站上,“颜色”混合模式被定义为:“创建具有基础颜色的亮度和混合颜色的色相和饱和度的结果颜色。这保留了图像中的灰阶,并且对于给单色图像着色和给彩色图像着色非常有用。”
ImageMagick中定义了一个compose方法似乎可以做同样的事情(Luminize),但结果远不如预期。
在Imagemagick中似乎提供最接近预期结果的是默认的blend compose方法,使用方式如下:
convert image.jpg color_layer.png -compose blend -composite result.jpg

我还尝试使用-fx运算符创建一个图像,该图像将包含第一张图像的亮度和第二张图像的色调和饱和度,但结果仍然远远不符合我的需求。


1
你在你的ImageMagick命令中如何考虑“35%不透明度”? - DarenW
为了解决这个问题,我创建了一个带有颜色的透明PNG文件,并将不透明度降低到35%。因此,我实际上是在尝试混合两个现有的图像文件。 - kioleanu
你能提供以下图片吗:(1)原始图像;(2)35%不透明度的彩色层图像;(3)预期结果的图像(即Photoshop创建的内容)。 - Kurt Pfeifle
我认为通过将原始图像与一个均匀的红色(半透明度为35%)图像混合不会起作用。我认为您需要从原始图像中提取红色通道,将其转换为约35%的半透明图像,并将其与原始图像混合... - Kurt Pfeifle
2个回答

3

根据Castles有价值的回答,我尝试在PHP中找到最佳解决方案。他引用的实现有两个主要缺陷:一是它没有考虑透明度(如果有);二是非常慢且耗费资源。在PHP中处理一个500x500像素的图像需要约15秒,在此期间Apache将把处理器占用高达95%。

我发现最快速且资源消耗最少的方法实际上是使用HTML5通过canvas处理图像。结果很惊人,并且图像正在当场处理。

我将在下面发布最终的代码块,一个是PHP的,另一个是HTML的。如果您需要在服务器端使用它,请将HTML代码复制粘贴到Node.js和NodeCanvas中: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>


2

我也一直在尝试这样做,最好的方法是使用叠加滤镜.. 这是我的php代码:

$overlay_color = array('r'=>180,'g'=>22,'b'=>1);

function overlay ($top, $bottom) {
    return $bottom < 128 ? ( 2 * $bottom * $top ) / 255 : 255 - ( 2 * ( 255 - $bottom ) * ( 255 - $top ) / 255 );
}

$original = new Imagick('img/girl.jpg');

$img = new Imagick();

//unmodified version
$img->addImage($original);


$it = $original->getPixelIterator();

foreach( $it as $row => $pixels )
{
    foreach ( $pixels as $column => $pixel )
    {
            $rgba = $pixel->getColor();
            $pixel->setColor('rgb('.overlay($overlay_color['r'], $rgba['r']).','.overlay($overlay_color['g'], $rgba['g']).','.overlay($overlay_color['b'], $rgba['b']).')');
    }

    $it->syncIterator();
}

//add modified version
$img->addImage($original);


$img->resetIterator();
$combined = $img->appendImages(true); //stack images


header('content-type: image/jpeg');

$combined->setImageFormat("jpeg");

echo $combined;

我似乎找不到Photoshop“颜色”模式的公式。如果我能找到,那就会相对简单。

更新:我找到了这个网站,它相当好地解释了实际的Photoshop公式:http://www.beneaththewaves.net/Photography/Secrets_of_Photoshops_Colour_Blend_Mode_Revealed_Sort_Of.html,我已经成功将其应用在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);

} 

以下是更新后的像素转换代码:

$rgb = $pixel->getColor();
$rgb = SetLum($overlay_color,Lum($rgb));
$pixel->setColor('rgb('.round($rgb['r']).','. round($rgb['g']).','.round($rgb['b']).')');

1
你好,Castles。这似乎对RGB图像(不是RGBA)有用。我还成功添加了透明度(我的颜色的alpha为0.35)。你可以在这里找到代码:http://pastebin.com/U1g5X16z。当我在混合之后而不是之前应用透明度时,效果更好。我的逻辑是,在实际混合之前需要添加透明度。然而,这段代码存在一个巨大的问题:它非常慢,并且在运行时使Apache使用99.5%的处理器。 - kioleanu
1
我也尝试用Imagemagick进行了翻译,但结果与预期相差甚远。也许你有什么想法?将original.jpg和transparent_layer.png转换为RGBA通道,并使用以下公式计算差异:"diff=(u.r0.3 + u.g0.59 + u.b0.11) - (v.r0.3 + v.g0.59 + v.b0.11); v + diff",然后保存为result.jpg。注意:虽然Imagemagick可以自行获取亮度值,但它不使用相同的公式,因此无法进行减法运算,否则会出错。 - kioleanu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接