4

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

这是预期的结果: 预期结果

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

ImageMagick 中定义了一个 compose 方法,它似乎做同样的事情(Luminize),但结果远非预期的那样。

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

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

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

4

2 回答 2

3

基于 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>

于 2012-10-09T14:31:04.070 回答
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']).')');
于 2012-08-24T05:01:19.640 回答