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

时间:2012-08-15 16:21:05

标签: imagemagick

我需要在ImageMagick中创建一个模仿Adobe Photoshop“Color”混合模式的命令,以便为图像着色。为了做到这一点,我试图组合原始图像和另一个由全色层组成的图像,不透明度为35%。这应该与原始图像混合并创建颜色着色结果图像。

这是预期的结果: the expected result

正在Adobe网站上定义“颜色”混合模式,如下所示:“使用基色的亮度和混合颜色的色调和饱和度创建结果颜色。这样可以保留灰度级别。图像,可用于着色单色图像和着色彩色图像。“

在ImageMagick中定义了一个组合方法似乎做了同样的事情(Luminize),但结果并不是预期的结果。

似乎在Imagemagick中提供最接近的结果是默认的混合撰写方法,使用如下:

convert image.jpg color_layer.png -compose blend -composite result.jpg

我还尝试使用-fx运算符创建一个包含第一个图像的亮度和第二个图像的色调和饱和度的图像,但结果再次无法满足我的需要。

2 个答案:

答案 0 :(得分:3)

基于Castles有价值的答案,我试图找到在PHP中执行此操作的最佳解决方案。他引用的实施有两个主要缺陷:一个是不考虑不透明度,如果有的话,第二个是非常慢和资源消耗。在PHP中处理500x500像素的图像大约需要15秒,而Apache将把处理器保持在95%以上。

我发现最快和最少的资源消耗实际上是通过使用canvas来处理图像在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>

答案 1 :(得分: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']).')');