Ken
Ken

Reputation: 550

ImageMagick: Decreasing RGB values of all pixels in an image

Specifically, with a given image I'm trying to decrease the RGB values for each pixel by 100.

For example, if a pixel has R: 232, G: 40, B: 120 then I want the new RGB values to be R: 132, G: 0, B: 20.

I have tried this solution that I found on the ImageMagick forums:

convert input.jpg -channel R -evaluate subtract 25700 \
-channel G -evaluate subtract 25700 \
-channel B -evaluate subtract 25700 output.jpg

Edit: the reason I use 25700 is because apparently you need to multiply the rgb value by 257. 100 * 257 = 25700.

While it appears to work at first (clearly darkening the image), it seems that certain pixels will not change and for what I'm doing it's vital that they do (I'm running a trim on the resulting image, trying to trim away the border with pixel values of 0).

An common problem is that I'll end up with a pixel that has a RGB values of 3, 0, 0, but I'll want that pixel to have values of 0 for RGB and increase the constant I subtract by - but it doesn't seem to work.

Any ideas? Thanks!

Upvotes: 3

Views: 2640

Answers (2)

Kurt Pfeifle
Kurt Pfeifle

Reputation: 90203

Ken, no, you don't need to write a big parser. It's quite easily done with a few shell commands. Test them first, then put them into a Shell or Batch script. Something like this (as Bash script):

#!/bin/bash

echo " ATTENTION: this script can take a loooong time to complete..."
echo " (This script is made to convert PNG files with an Alpha channel."
echo "  for other types of images, you need to slightly  modify it.)"
echo
echo " This script takes an 8-bit RGBA input image and creates a darker output image."
echo " Its method is: subtract the value of 100 from each color channel's numeric value."
echo

input="${1}"

_im_header=$(identify -format "%W,%H"  "${input}")

echo "# ImageMagick pixel enumeration: ${_im_header},255,rgba" > input-minus-120.txt

convert  "${input}"  input.txt

cat input.txt \
   | \
     sed 's#) .*$#)#;  s# ##g;  s#:#: #;  s#(# #;  s#)##; s#,# #g; s# #,#' \
   | \
     while read coord red green blue alpha; do
        echo -n "${coord}";
        echo -n " (";
        echo -n " $(($red   - 100)),";
        echo -n " $(($green - 100)),";
        echo -n " $(($blue  - 100)),";
        echo -n " $(($alpha))";
        echo -n " ) ";
        echo;
     done \
   | sed 's#-[0-9]*#0#g' \
>> input-minus-120.txt

convert  input-minus-120.txt  output-minus-120.jpg

This script required 153 seconds to run on a MacBook Pro, processing a 1080x889 Pixels PNG file of 750 kByte.

The generated input.txt had 960120 lines (the number of Pixels in the PNG).

So the performance of this brute force shell script is about 6275 Pixels/second.

Upvotes: 1

Kurt Pfeifle
Kurt Pfeifle

Reputation: 90203

Honestly, I don't really understand what the value 25700 in your commandline should achieve.

However, I suggest a different commandline to you, using the more powerful -fx operator. A bit more complicated looking, but hopefully more intuitively to understand...

But first, I'm looking at your description and see you want to subtract a fixed number of 120 from each of the current R, G, and B color values. So this is a gray pixel color... and as you can look up in ImageMagick's color built-in color list, its name is gray47:

convert -list color | grep '(120,120,120)'
  gray47                srgb(120,120,120)                             X11 XPM 
  grey47                srgb(120,120,120)                             SVG X11 

This leads me to the following command:

   convert \
       input.jpg \
      -channel red   -fx 'r - gray47' \
      -channel green -fx 'g - gray47' \
      -channel blue  -fx 'b - gray47' \
       output.jpg

This way or writing the command will probably open your eyes to some easily derived modifications should you need those in future...

To have an immediate preview window of the result popping up (without writing it to a file) you can also use -show: as output, like this:

   convert \
       input.jpg \
      -channel red   -fx 'r - gray47' \
      -channel green -fx 'g - gray47' \
      -channel blue  -fx 'b - gray47' \
      -show:

Update

If you want to check for the real differences of each pixel, you can make ImageMagick print out the color value for each pixel:

convert  input.jpg   input.txt
convert  output.jpg  output.txt

The format of the .txt file is pretty easy to understand, once you know that the first columns give the Pixel zero-based coordinates: 123,456: means: 124th (!) column, 457th () row.

Now you can compare the two .txt files to your heart's content even in an automated, scripted version, without a need to resort to Gimp. :-)

You could even use input.txt and apply a Perl-, Ruby-, Python- or Shellscript onto each of the pixel values to distract your 120 value from each channel, save it as output2.txt and then convert it back to JPEG:

convert  output2.txt  output2.jpg

Then look for pixel differences between the two output images:

compare  output.jpg  output2.jpg  delta.jpg
compare  output.jpg  output2.jpg  view:

An all-white plane will mean 'no differences' , any red pixels will hint to some sort of delta.

Now if that answer doesn't earn me an upvote, I don't know which would... :-)

Upvotes: 4

Related Questions