ImageMagick: Decreasing RGB values of all pixels in an image - imagemagick

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!

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... :-)

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.

Related

How to preserve red color only using ImageMagick command line

I have the following image:
What I want is to preserve only red color and desaturate
every other color into grayscale. Resulting in this:
How can I do that with Imagemagick command line?
I tried this but failed:
convert original.png \( -clone 0 -transparent red -alpha extract -transparent black \) redonly.png
Here is one way, though not perfect, using ImageMagick. I specify hue=0 deg for red and tolerance=25 deg both in range 0 to 360.
Input:
hue=0
tolerance=25
toler=`convert xc: -format "%[fx:(100*$tolerance/360)]" info:`
hueval=`convert xc: -format "%[fx:50-$hue]" info:`
thresh=`convert xc: -format "%[fx:100-$tolerance]" info:`
convert tomato.jpg \
\( -clone 0 -colorspace gray -colorspace sRGB \) \
\( -clone 0 -colorspace HSL -channel 0 -separate +channel \
-evaluate AddModulus ${hueval}% \
-solarize 50% -level 0x50% \
-threshold $thresh% \) \
-swap 0,1 -alpha off -compose over -composite \
result.jpg
Also not perfect, but fairly easy to understand. Basically, you could use the fx operator to inspect the Hue of each pixel and, depending on its Hue/colour, return either the original pixel or its greyscale equivalent.
So, as a first stab, you might do this to replace all pixels exhibiting a high Hue value with their greyscale (lightness) equivalent:
magick ripe.jpg -fx "(u.hue<0.1)? u : u.lightness" result.jpg
Then you might realise that red Hues wrap around at 0/360 degrees on the Hue circle, so you could do:
magick ripe.jpg -fx "(u.hue<0.1)||(u.hue>0.9)? u : u.lightness" result.jpg
Explanation
There are a couple of things going on here. Firstly, the -fx operator is a very low-level, extremely powerful (and sadly rather slow because it is interpreted) way of running a piece of "code" on every pixel in the image. Secondly, I am running a ternary operator with the format:
condition? valueA : valueB
so I am testing a condition for every pixel, and if true I return valueA, and if false I return valueB. When I refer to u and u.hue and u.lightness, the u means the first image in my command - I could load two images and use features of the first to select features of the second, then I would use u and v to differentiate. Finally, the values are scaled on the range [0,1] so I don't test for "Hue>350 in the range [0,360]", instead I test for Hue>0.9 as a sloppy equivalent - I guess I could have used Hue>(350/360). Note that you can make the expression arbitrarily complicated and also put it in a separate file to re-use it like this:
magick ripe.jpg -fx #DeSatNonRed.fx result.jpg
DeSatNonRed.fx might look something like this:
hueMin=350/360;
hueMax=20/360;
(hue>hueMin) || (hue<hueMax) ? u : u.lightness
Note that, in the general case, you should also really consider Saturation when looking at Hues, which you can add in above, but which I omitted for clarity, and because your image is almost fully saturated anyway.
Keywords: Image processing, ImageMagick, low-level fx operator, ternary, pixel-by-pixel, evaluate, expression.

Imagemagick - how to get largest layer and compress?

Please help. Is there an easy way to take the largest layer of a tiff and zip compress it back as a single layer tiff again with imagemagick or similar?
Just a slightly easier version of Fred's answer. You can generate a list of the area (in pixels) of each layer in a TIF followed by the layer/scene number like this:
magick identify -format "%[fx:w*h] %s\n" image.tif
Sample Output
240000 0
560000 1
200000 2
So, if we do that again, sort it reverse numerically and take the second field of the first result, we will get the number of the layer with the largest area:
layer=$(magick identify -format "%[fx:w*h] %s\n" image.tif | sort -rn | awk 'NR==1{print $2}')
So, the complete solution would look like:
#!/bin/bash
# Get layer number of layer with largest area
layer=$(magick identify -format "%[fx:w*h] %s\n" image.tif | sort -rn | awk 'NR==1{print $2}')
# Extract that layer and recompress as single layer
magick image.tif[$layer] -compress lzw result.tif
If you are using ImageMagick v6 or older:
magick identify ... becomes identify ...
magick image.tif ... becomes convert image.tif ...
In concept, using ImageMagick this can be done in a single command. Here's an example...
magick input.tif -background none -virtual-pixel none ^
( -clone 0--1 +repage -layers merge ) ^
-distort affine "0,0 0,%[fx:s.w==u[-1].w&&s.h==u[-1].h?0:h]" ^
-delete -1 -layers merge output.tif
That starts by reading in the original TIF and setting the background and virtual-pixel settings to "none".
Then inside the parentheses it clones all the layers of the TIF, repages them, and merges them into a single image with the dimensions of the largest layer. That will become a gauge to measure with.
Next it uses "-distort affine" to slide each image out of the viewport and leave it transparent unless the image matches the width and height of that gauge. So after that distort, the largest image will remain unchanged, and all the others will be transparent.
Finish by deleting that gauge image and merging the rest. All the layers are transparent except the largest one, so merging them leaves just that visible one as a single layer.
The command is in Windows syntax using IM7. If you're using ImageMagick v6, use "convert" instead of "magick". To make it work in *nix, change the continued line carets "^" to backslashes "\" and escape the parentheses with backslashes "\(...\)". There may be other issues I've overlooked.
Obviously if there are two or more layers matching the largest dimensions, the output result will only be the first one from the original TIF.
Edited to add: This method will only work if both the greatest width and greatest height are on the same image.
How do you define largest? Width, Height, File size? If the largest dimension from width and height is used, then in Unix, you can do the following on a 3 layer tif file. Get the max dimension of each layer. Then find which layer is the largest. Then use just that layer when reading and writing the file.
Arr=(`identify -format "%[fx:max(w,h)]\n" img.tif`)
echo "${Arr[*]}"
500 1024 770
num=${#Arr[*]}
dim=0
for ((i=0; i<num; i++)); do
if [ ${Arr[$i]} > $dim ]; then
dim=${Arr[$i]}
index=$i
fi
done
echo "$index"
2
convert img.tif[$index] -compress zip newimg.tif
identify newimg.tif
newimg.tif[2] TIFF 770x768 770x768+0+0 8-bit sRGB 3662B 0.000u 0:00.000
I cannot think of any direct and simple method to find the largest layer and extract it in the same command line.

Extracting data digitally

I have 200 copies of this page(15 * 10 matrix) and i have to write all the numbers from 0 - 9 in each corresponding cell and then extract those digits digitally in a seperate image of (32*32 pixels) for each digit, after scanning each page once. How can i achieve this? This is required for my research purpose. I am a CS student so i can code too.
Update:
For mark : Here is one of the scanned image
This is for some local language ( 0 - 9) ..
Update 2:
The commands for the previous image are working fine but on new images,something is getting wrong(some kind of offsets)..
I am attaching the image below
What changes do u suggest ?
Updated Answer
I have taken your feedback and improved the algorithm to the following bash script now...
#!/bin/bash
################################################################################
# dice
#
# Trim borders off an image (twice) and then dice into 10x15 cells.
#
# Usage: ./dice image
################################################################################
# Pick up image name from first parameter
image="$1"
echo DEBUG: Processing image $image...
# Apply median filter to remove noisy black dots around image and then get the
# dimensions of the "trim box" - note we don't use the (degraded) median-filtered image in
# later steps.
trimbox=$(convert "$image" -median 9x9 -fuzz 50% -format %# info:)
echo DEBUG: trimbox $trimbox
# Now trim original unfiltered image into stage1-$$.png (for debug)
convert "$1" -crop $trimbox +repage stage1-$$.png
echo DEBUG: Trimmed outer: stage1-$$.png
# Now trim column headings
convert stage1-$$.png -crop 2000x2590+120+190 +repage stage2-$$.png
echo DEBUG: Trimmed inner: stage2-$$.png
# Now slice into 10x15 rectangles
echo DEBUG: Slicing and dicing
convert stage2-$$.png -crop 10x15# +repage rectangles-%03d.png
# Now trim the edges off the rectangles and resize all to a constant size
for f in rectangles*png; do
echo DEBUG: Trimming and resizing $f
trimbox=$(convert "$f" -median 9x9 -shave 15x15 -bordercolor black -border 15 -threshold 50% -floodfill +0+0 white -fuzz 50% -format %# info:)
echo DEBUG: Cell trimbox $trimbox
convert "$f" -crop $trimbox +repage -resize 32x32! "$f"
done
Here are the resulting cells - i.e. 150 separate image files. I have put a red border around the individual cells/files so you can see their extent:
Original Answer
I would do that with ImageMagick which is free and installed on most Linux distros and is available for OSX and Windows too. There are Perl, PHP, Java, node, .NET, Ruby, C/C++ bindings too if you prefer those languages. Here I am using the command line in Terminal.
First job is to get rid of noise and trim the outer edges:
convert scan.jpg -median 3x3 -fuzz 50% -trim +repage trimmed1.png
Now, trim again to get rid of outer frame and column titles across the top:
convert trimmed1.png -crop 2000x2590+120+190 +repage trimmed2.png
Now divide into 10 cells by 15 cells and save as rectangles-nnn.png
convert trimmed2.png -crop 10x15# rectangles-%03d.png
Check what we got - yes, 150 images:
ls -l rect*
rectangles-000.png rectangles-022.png rectangles-044.png rectangles-066.png rectangles-088.png rectangles-110.png rectangles-132.png
rectangles-001.png rectangles-023.png rectangles-045.png rectangles-067.png rectangles-089.png rectangles-111.png rectangles-133.png
rectangles-002.png rectangles-024.png rectangles-046.png rectangles-068.png rectangles-090.png rectangles-112.png rectangles-134.png
rectangles-003.png rectangles-025.png rectangles-047.png rectangles-069.png rectangles-091.png rectangles-113.png rectangles-135.png
rectangles-004.png rectangles-026.png rectangles-048.png rectangles-070.png rectangles-092.png rectangles-114.png rectangles-136.png
rectangles-005.png rectangles-027.png rectangles-049.png rectangles-071.png rectangles-093.png rectangles-115.png rectangles-137.png
rectangles-006.png rectangles-028.png rectangles-050.png rectangles-072.png rectangles-094.png rectangles-116.png rectangles-138.png
rectangles-007.png rectangles-029.png rectangles-051.png rectangles-073.png rectangles-095.png rectangles-117.png rectangles-139.png
rectangles-008.png rectangles-030.png rectangles-052.png rectangles-074.png rectangles-096.png rectangles-118.png rectangles-140.png
rectangles-009.png rectangles-031.png rectangles-053.png rectangles-075.png rectangles-097.png rectangles-119.png rectangles-141.png
rectangles-010.png rectangles-032.png rectangles-054.png rectangles-076.png rectangles-098.png rectangles-120.png rectangles-142.png
rectangles-011.png rectangles-033.png rectangles-055.png rectangles-077.png rectangles-099.png rectangles-121.png rectangles-143.png
rectangles-012.png rectangles-034.png rectangles-056.png rectangles-078.png rectangles-100.png rectangles-122.png rectangles-144.png
rectangles-013.png rectangles-035.png rectangles-057.png rectangles-079.png rectangles-101.png rectangles-123.png rectangles-145.png
rectangles-014.png rectangles-036.png rectangles-058.png rectangles-080.png rectangles-102.png rectangles-124.png rectangles-146.png
rectangles-015.png rectangles-037.png rectangles-059.png rectangles-081.png rectangles-103.png rectangles-125.png rectangles-147.png
rectangles-016.png rectangles-038.png rectangles-060.png rectangles-082.png rectangles-104.png rectangles-126.png rectangles-148.png
rectangles-017.png rectangles-039.png rectangles-061.png rectangles-083.png rectangles-105.png rectangles-127.png rectangles-149.png
rectangles-018.png rectangles-040.png rectangles-062.png rectangles-084.png rectangles-106.png rectangles-128.png
rectangles-019.png rectangles-041.png rectangles-063.png rectangles-085.png rectangles-107.png rectangles-129.png
rectangles-020.png rectangles-042.png rectangles-064.png rectangles-086.png rectangles-108.png rectangles-130.png
rectangles-021.png rectangles-043.png rectangles-065.png rectangles-087.png rectangles-109.png rectangles-131.png
Look at first couple:
You can do those 3 steps in 1 like this:
convert scan.jpg -median 3x3 -fuzz 50% -trim +repage \
-crop 2000x2590+120+190 +repage \
-crop 10x15# rectangles-%03d.png
You may want to shave a few pixels off each side of each image and resize to 32x32 with something like (untested):
mogrify -shave 3x3 -resize 32x32! rectangles*png

Imagemagick: remove alpha component (replace all intermediate alpha pixel with solid pixel)

To solve Android build issue I need to replace all intermediate alpha pixel with solid pixel (leaving transparent background as is).
How to that with ImageMagick or other-command line tool to all images in a tree?
Image bg_all_block.9.png
Image btn_bg_common_press.9.png
UPDATE: I have found that I can detect if alpha is used, as in Detect Alpha Channel with ImageMagick
Other found links
https://graphicdesign.stackexchange.com/questions/16120/batch-replacing-color-with-transparency
http://www.imagemagick.org/Usage/color_basics/#replace
To remove the alpha channel from single image use this command:
convert input.png -alpha off output.png
To remove the alpha channel from all images inside a folder, make use find to first find all PNG files, and then run 'm through convert:
find . -name "*.png" -exec convert "{}" -alpha off "{}" \;
Please test on a COPY of your files to be sure.
...
see dialog below, and the answer is based on that "we need to remove alpha that is not 255"
convert input.png -channel A -threshold 254 output.png
and for batch
mkdir batch
FOR %G IN (*.png) DO convert %G -channel A -threshold 254 batch\%G
What worked for me on macOS for batch processing was:
for f in *.png; do convert "$f" -channel A -threshold 254 "${f%%.png}.png"; done
To remove alpha channel from all pictures in the folder (f.ex. all .png files) I use following command (in terminal on macOS):
for file in *.png; do convert $file -alpha deactivate; done
Unfortunately, none of any other solution given in this thread worked for me.

ImageMagick: How can i work with histogram result?

im getting the most existing colors of an image and display it with the "histogram" funktion like
convert image.jpg -scale 100x100 -gravity \
center -crop 50% -dither None -remap color_map.gif \
-format %c histogram:info:
22: ( 0, 0, 0) #000000 black
881: (119,133,142) #77858E rgb(119,133,142)
268: (186, 84, 29) #BA541D rgb(186,84,29)
662: (212,212,212) #D4D4D4 grey83
20: (244,203, 98) #F4CB62 rgb(244,203,98)
647: (255,255,255) #FFFFFF white
Ho can i work now with this output? i want to save the most existing color in my database, but i dont know how to get now only the color with the number 881.
Can any one help me?
If you want to do this purely from the shell (assuming Bash-like Unix environment), something like this would work:
convert image.jpg -scale 100x100 -gravity center \
-crop 50% -dither None -remap color_map.gif \
-format %c histogram:info: | sort | tail -n1 | \
sed -e 's/.*\(#[0-9A-F]\+\).*/\1/'
That takes the output from ImageMagick, sorts it so the largest color count is on the bottom, takes just that line, then extracts the color hex from the line. You can tweak the sed regex if your goal is to get the decimal rgb values instead of the hex.
So for your example image, the output should be just:
#77858E

Resources