Duplicate blending mode for Color Photoshop in ImageMagick

I need to create a team that simulates the blending mode of Adobe Photoshop "Color" in ImageMagick to tint an image. To do this, I am trying to compose the original image and another image consisting of a full-color layer with 35% opacity. This should be combined with the original image and create a color tinted result.

This is the expected result: the expected result

The blending mode β€œColor” on the Adobe website is defined as follows: β€œCreates the color of the result with the brightness of the base color, hue and saturation of the blending color. This preserves the gray levels in the image and is useful for coloring monochrome images and for tinting color images.”

ImageMagick has a layout method that seems to do the same (Luminize), but results are far from expected.

What seems to be the closest result to Imagemagick is the default layout method used as follows:

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

I also tried to create an image that would contain the luminosity of the first image, as well as the hue and saturation of the second, using the -fx operator, but again the result was not next to what I needed.

+4
imagemagick
source share
2 answers

Based on Castles' valuable answer, I tried to find the best solution for this in PHP. The implementation that he cited has two main drawbacks: one that does not take into account opacity, if any, and the second is very slow and resource intensive. Processing an image with a size of 500 Γ— 500 pixels in PHP will take about 15 seconds, during which Apache held the processor up to 95%.

The fastest and least resource intensive resource I found actually did this in HTML5, using the canvas to process the image. The results are amazing and the image is processed in place.

I will post below the final code snippets, one for PHP and one for HTML. If you need to use this server, you can copy-paste the HTML code into Node.js and NodeCanvas: https://github.com/LearnBoost/node-canvas

PHP (with opacity):

 <?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 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> 

+3
source share

I also tried to do this, and the best I came up with was to use an overlap filter. Here is my code in 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; 

I can not find the formula for the "color" mode of Photoshop. If I could find it would be pretty straight forward.

Update: I found this site that very well explains the Photoshop formula: http://www.beneaththewaves.net/Photography/Secrets_of_Photoshops_Colour_Blend_Mode_Revealed_Sort_Of.html , and I managed to get it working in PHP. Here are the functions:

 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); } 

and here is the updated pixel conversion code:

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

All Articles