Imagemagick maximum colors and scaling - resize

I am trying to convert 80x80 images to 56x56 images in greyscale in 2bpp.
The 80x80 images are colored and probably have up to 16 colors in them. They also have a transparent background.
I need them greyscaled with 4 colors in them, with white being the lightest and black being the darkest.
Each image has multiple colors in it, but each color has a palette of 3 colors, a dark one, medium one, and light one.
I need to convert all the dark ones to a dark grey, the medium ones to a light grey, and the light ones to white, while maintaining the black that is already in the image.
I can successfully convert the image to greyscale, trim the canvas, and fill the background with this command
convert input.png +dither -flatten -trim -set colorspace Gray -
separate -average output.png
Now I need to limit the colors, but it's not converting the right ones. The light colors are getting converted to light grey instead of white. When I change -level option, it only works with some of the images.
-auto-levels does not do what I want either.
Is there anyway to set the colors in the midrange to automatically level themselves to fit my requirements? I'm sorry if I'm not explaining this enough.
This is the code I've been tampering with but it only works on a few images. Messing with the gamma option can make it work for more images but breaks the original working images.
convert "$f" +dither -flatten -trim -set colorspace Gray -separate -
average -gamma 1.37 -level 25%,75% -colors 4 -adaptive-resize 56x56\>
-background white -gravity center -extent 56x56 -remap nido.png
"${f%.png}".png2
I can't provide an expected image, but an image something similar to what is expected. Here is original image https://img.pokemondb.net/sprites/black-white/normal/charizard.png and here is desired output format image https://img.pokemondb.net/sprites/red-blue/normal/charizard.png
Here is what I've got so far
https://www.pokecommunity.com/showthread.php?p=9692599#post9692599
convert "$f" +dither -background white -flatten -trim -adaptive-resize
56x56\> "${f%.png}".png2
convert "${f%.png}".png2 +dither -colorspace gray -separate -average
"${f%.png}".png2
convert "${f%.png}".png2 +dither -gamma 3.0 -black-threshold 70%
"${f%.png}".png2
convert "${f%.png}".png2 +dither -gamma 0.45 -white-threshold 90%
"${f%.png}".png2
convert "${f%.png}".png2 +dither -remap nidoking.png -background white
-gravity center -extent 56x56 "${f%.png}".png2
Btw, ^ is in a for loop, hence the variables. Changing the gamme and black/white theshold values are getting me closer, but it is extremely tedious and when I get one image to correctly convert another breaks. nidoking.png is my remap file. remap works perfect, it's just before the remap the colors are being separated or filtered properly.
Solved, thanks to Mark Setchell
This is what I ended up doing
#!/bin/bash
rm x*
rm colors*
cd images
rm *.png2
rm *.txt
for f in *.png
do
#Fitting canvas to image, setting background color, and removing transparency
convert "$f" +dither -background white -flatten -trim "${f%.png}".png2
#Converting image to greyscale
convert "${f%.png}".png2 +dither -colorspace gray -separate -average "${f%.png}".png2
#Resizing without blurring/adding pixels
convert "${f%.png}.png2" +dither -interpolate Nearest -interpolative-resize 56x56\> "${f%.png}".png2
#Grabbing every color used in image and putting it in a text file
convert "${f%.png}.png2" txt: | sed '1d' | cut -f 4 -d " " | sort -u > "${f%.png}".txt
done
#Putting all colors into one file
cat *.txt >> ../colors
cd ../
#One last clean up of file/sorting
cat colors | tr " " "\n" | sort -u > colors.txt
rm colors
#Splitting the hex codes into four files for each desired color
file=colors.txt
lines=$(wc -l <${file})
((lpp = (lines + 4 - 1) / 4))
split --lines=${lpp} ${file}
#Going back to images directory
cd images
for f in *.png
do
#Each while loop reads everyone line of the specified file and puts it in variable $i, then I use $i to convert to one of the desired 4 colors.
cat ../xaa | while read i
do
convert "${f%.png}".png2 +dither -fuzz 0% -fill "#000000" -opaque "${i}" "${f%.png}".png2
done
cat ../xab | while read i
do
convert "${f%.png}".png2 +dither -fuzz 0% -fill "#555555" -opaque "${i}" "${f%.png}".png2
done
cat ../xac | while read i
do
convert "${f%.png}".png2 +dither -fuzz 0% -fill "#AAAAAA" -opaque "${i}" "${f%.png}".png2
done
cat ../xad | while read i
do
convert "${f%.png}".png2 +dither -fuzz 0% -fill "#FFFFFF" -opaque "${i}" "${f%.png}".png2
done
mv "${f%.png}".png2 ../finished/"${f}"
done
This script turned this
Into this

Basically, the idea would be to do a reduction to 4 colours in the RGB colourspace (rather than in greyscale colourspace) to get the best four colours. Then get the lightness of each of those and map the darkest one to black, the next lighter to dark grey, the next lighter to light grey and the lightest to white.
Here it is mapped to the 4 best colours in RGB colourspace:
The code, without much error checking or corner-case handling, looks like this:
#!/bin/bash -x
# Do a colour reduction to get best 4 colours in RGB colourspace rather than in grey colourspace
magick pokething.png -alpha off +dither -colors 5 -unique-colors unique.png
# Get hex colours into array "hexcolours[]"
hexcolours=( $(convert unique.png txt: | awk 'NR>1{print $3}') )
echo DEBUG: hexcolours=${hexcolours[#]}
# Get lightness of each colour into array "lightness[]", i.e. H of HSL
# Note ggrep is just GNU grep
lightness=( $(convert unique.png -colorspace HSL -separate -delete 0,1 txt: | ggrep -Po "\d+(?=\)$)") )
echo DEBUG: lightness=${lightness[#]}
# Sort the colours by their lightness
fourshades=( $(for ((i=0;i<4;i++)) ;do
echo ${lightness[i]} ${hexcolours[i]}
done | sort -n | awk '{print $2}') )
echo DEBUG: fourshades=${fourshades[#]}
# Now change those colours in original image
magick pokething.png -alpha off +dither -colors 5 -fuzz 10% \
-fill black -opaque "${fourshades[0]}" \
-fill gray25 -opaque "${fourshades[1]}" \
-fill gray75 -opaque "${fourshades[2]}" \
-fill white -opaque "${fourshades[3]}" \
result.png
The output is as follows:
DEBUG: hexcolours=#000000 #094152 #A95244 #EF9E3C
DEBUG: lightness=0 46 119 150
DEBUG: fourshades=#000000 #094152 #A95244 #EF9E3C
That results in this being executed:
magick pokething.png -alpha off +dither -colors 5 -fuzz 10% \
-fill black -opaque '#000000' \
-fill gray25 -opaque '#094152' \
-fill gray75 -opaque '#A95244' \
-fill white -opaque '#EF9E3C' result.png
So, basically I am replacing #094152 with dark grey because 46 is the second darkest colour present. Then I am replacing #A95244 with light grey because 119 is the next lighter colour, then replacing #EF9E3C with white because that is the lightest colour.

You might be able to get your desired result by using pretty much only "-remap". This example will make a four color map from white, 65% gray, 35% gray, and black, and write that map to temporary memory. Next it reads your input image, sets the background to white, and flattens the image. Then it turns off dithering, remaps the input image to the map you created, and returns the result.
convert xc:white xc:gray65 xc:gray35 xc:black -append -write mpr:map +delete \
input.png -background white -flatten +dither -remap mpr:map output.png
You should do any resizing, cropping, etc., before the remap, of course. You probably don't need to turn it to grayscale first because the map has no color.

Related

ImageMagick convert to png8 (paletted PNG with binary alpha) using background color for partially transparent pixels

I have a 32-bit PNG image like this:
Displayed here with a checkerboard background for visibility, but instead of the checkerboard the image is actually transparent (for your reference: the original 32-bit image). As you can see, around the edges and towards the right, the red pixels are gradually fading out from opaque to transparent.
If I would display this 32-bit image on top of a blue background, the pixels around the edges and towards the right would gradually fade from red to blue.
Now I need to convert this image to an 8-bit PNG with binary alpha. I want the fully transparent area to remain fully transparent, but the partially transparent pixels to gradually blend with the intended background/matte color, in this example blue.
So something like this:
However, I can't seem to figure out how to do this with ImageMagick (using ImageMagick 7.0.8-26 on Mac). I tried as follows:
magick original.png -background 'rgba(0,0,255,0)' png8:output.png
But this results in:
It seems to ignore the blue background color altogether, and just convert the transparency to binary (probably taking fully opaque for ≥128 or fully transparent for <128).
Also if I specifiy rgb(0,0,255) as background color (i.e. no alpha) the result is the same.
Does ImageMagick have some smart 'background blending for partially transparent pixels' option that I don't know about?
Otherwise, I'm guessing I should somehow extract a binary bitmap where the original image is fully transparent (i.e. has alpha value or opacity 0), then flatten the image on a background of my choosing, and then re-apply the boolean alpha bitmap again. But not quite sure how to do this.
I can think of two techniques, but I'm sure folks have better answers.
First Option
Composite the blue background, and then copy the alpha channel from the original source.
magick original.png \( \
+clone \( +clone -fill BLUE -draw 'color 0,0 reset' \) \
-compose DstOver -composite \) \
-swap 0,1 -compose CopyAlpha -composite PNG8:output.png
Second Option
Replace transparent with blue color, but then call -transparent without any -fuzz operation.
magick original.png \( \
+clone -fill BLUE -draw 'color 0,0 reset' \) \
-compose DstOver -composite -transparent BLUE \
PNG8:output.png
Again, I'm sure the above examples can be improved on. YMMV
One way to do that with ImageMagick is to extract the alpha channel, colorize essentially everything except the totally transparent pixels, and composite the original image over the result.
convert input.png \( +clone -alpha extract -transparent black -fill blue \
-channel RGB -colorize 100 +channel \) +insert -composite output.png
That reads the input image, clones it inside the parentheses, and extracts the alpha channel to a black and white mask.
The pure black is made transparent with "-transparent black".
The white, everything except pure black, is changed to blue with "-colorize 100". Setting "-channel RGB" applies that fill color to only the white part of the mask and ignores the transparent.
Then after the parentheses the "+insert" moves that blue-on-transparent image to the back, and "-composite" places the input image over that blue and transparent one.
If you're using ImageMagick version 7, use "magick" instead of "convert". If you're running on Windows, change the end of line backslash "\" to a caret "^", and remove the backslashes that escape the parentheses "\(...\)" to simply "(...)".
Here is one more way in ImageMagick.
convert img.png \
\( -clone 0 -alpha extract -channel rgba \
\( xc:none xc:blue +append \) -clut +channel \
-channel alpha -threshold 0 +channel \) \
+swap -compose over -composite PNG8:result.png
OR FOR Imagemgick 7
magick img.png \
\( -clone 0 -alpha extract -alpha copy -channel rgba \
\( xc:none xc:blue +append \) -clut \
-channel alpha -threshold 0 +channel \) \
+swap -compose over -composite PNG8:result.png
Read the image.
Clone it, extract the alpha channel, create transparent blue color map and apply to the alpha channel, then threshold the alpha channel.
Swap the two images and composite the original over the processed alpha channel.
Save the output.
Thanks to the posted suggestions, however the problem is I don't want to mask any particular color (like black or blue) as transparent. That exact same color may also appear in the non-transparent parts.
After some tweaking, I got the exact result I need:
magick original.png -write MPR:foo -background 'rgb(0,0,255)' -flatten \( MPR:foo -alpha extract -threshold 0 \) -compose copy_opacity -composite png8:result.png
Explanation:
I start with original.png and use -write MPR:foo to save a temporary copy in memory called 'foo'
Then I flatten the image on a blue background, giving the correct color values everywhere (including blended with the background for partially transparent pixels) but losing the opacity data.
Then I side-process a 2nd layer, opening the same original input image (restoring from memory using MPR:foo) and extract its alpha channel which now becomes a grayscale image, containing the original alpha data.
I apply -threshold 0 on that layer, which changes all channel values that were >0 to the maximum (i.e. alpha 255 or fully opaque) and ≤0 remains 0 (fully transparent). So this is now my 'binary alpha' channel.
Then finally I merge these two layers by using a copy_opacity composite which just copies the 2nd layer (the binary alpha) and writes that as the resulting layer's alpha data.
Saving the end result gives me the exact image I need.

How do I use Imagemagick to replace a gray gradient background

I have several photos for which I need to remove the background (replace it with #ffffff)
I have tried several ways, but none that gives a good result.
eg. convert "$i" -fuzz 10% -fill '#ffffff' -opaque white $i-new.png
This makes the result whiter and creates a rather harsh line between the white color and the shadow.
More sample images
I think you want a flood fill using the colour from the top-left corner:
convert cauli.jpg -fuzz 10% -fill red -draw 'color 0,0 floodfill' result.png
Probably use a white fill though :-)

Configuration and optimization ImageMagic and Tesseract

We are using ImageMagic and tesseract to try to read information in documents, but we are not finding the right configuration and combination of both softwares to optimize the original scanned tif document, and apply tesseract to it to obtain the information.
First we use to scan the document in a scanner with a configuration of 300 dpi, and the tif document produces uses to have 170KB size.
Then we try to run a pre-process of the image with imagemagic before passiing it to tesseract 3.0.3, to produce a PDF with text document.
The first command we use is this one:
convert page.tiff -respect-parenthesis -compress LZW -density 300
-bordercolor black -border 1 -fuzz 1% -trim +repage -fill white -draw
"color 0,0 floodfill" -alpha off -shave 1x1 -bordercolor black -border 2
-fill white -draw "color 0,0 floodfill" -alpha off -shave 0x1 -fuzz 1%
-deskew 40 +repage temp.tiff
And then we apply it to tesseract this way:
tesseract -l spa temp.tiff temp pdf
This produces a quite heavy pdf https://drive.google.com/open?id=0B3CPIZ_TyzFXd2UtWldfajR4SVU but tesseract is not able to read data that are in cells, or in a table just under the header of the table if the background of the header is darker.
Then we have tried to use this command with convert:
convert page.tiff -compress LZW -fuzz 1% -trim -alpha off -shave 1x1 temp.tiff
And this produces a very light pdf document https://drive.google.com/open?id=0B3CPIZ_TyzFXWFEwT3JucDBTVVU, but we are still having the same problems.
Could someone point us what way shall we follow to optimize the image to try to obtain information like the ones in the example? or guidelines to optimize images to improve the tesseract accuracy?
The type of documents we are trying to process are very different with different kind of font types and sizes
If on a Unix-based system, you could try my script, textcleaner, at http://www.fmwconcepts.com/imagemagick/index.php

Imagemagick convert resize then crop

I have over 1000 images on different resolutions, (for example 1234x2122, 4400x5212 , etc) and I want to convert all of them to fixed 100x100 size, so.
first I need to resize the images keeping proportions, and get 100xA or Ax100, where A > 100 (it depends width and height of image, for some images width > height, and for some images height > width).
Crop this image to 100x100 from center
Is there a simple convert command, that I can use for all my images?
You would use the area-fill (^) geometry modifier on the -resize operation to unify the down-scale. For cropping the center, -extent with -gravity Center will work.
convert input.jpg -resize 100x100^ \
-gravity Center \
-extent 100x100 \
output.jpg
Update
As Mark Setchell pointed out in the comments, the mogrify utility can be leveraged to batch convert items.
mogrify -path ./path/to/write/results/ \
-resize 100x100^ \
-gravity Center \
-extent 100x100 \
./path/to/source/files/*
Reminder: Mogrify will overwrite original file with resulting image(s), unless you set the -path parameter.
To keep the aspect ratio and don't fill anything (extent), rather crop from the longer edge which is out of aspect you could do
convert input.jpg -resize 100x100^ \
-gravity Center \
-crop 100x100+0+0 +repage \
output.jpg
with option to crop more from one side if you like

Is it possible to distinguish grayscale from (scanned) monochrome within a shell script?

I have several thousand images that I want to run various IM commands on depending on which of three categories they fall into:
Color (often with bright colors)
Grayscale (scanned from paper, the "white" often has a yellowish tinge)
Monochrome (scanned, with the yellowish tinge, as above)
Can this be sorted out from a shell script?
Color Example #1
Grayscale Example #1
Monochrome Examples #1 and #2
I would say that the Hue and Saturation would be good discriminants for the colour image especially. A mono or grayscale image is very unsaturated, so its mean saturation will tend to be low whereas it will be higher for a colour image. Also, the hue (basically colour) of a colour image will tend to vary a lot between the different colours whereas the hue will tend to be a fairly constant value for a grey or mono image, so the amount of variation in the Hue should be a good measure - i.e. its standard deviation.
We can calculate the mean saturation using ImageMagick like this:
convert image.png -colorspace HSL -channel S -separate -format "%[mean]" info:
and the standard deviation of the Hue like this:
convert image.png -colorspace HSL -channel H -separate -format "%[standard-deviation]" info:
So, if we put all that together in a bash script and run it over your images we get this:
#!/bin/bash
for i in colour.png grey.png mono.png; do
SatMean=$(convert $i -colorspace HSL -channel S -separate -format "%[mean]" info:)
HueStdDev=$(convert $i -colorspace HSL -channel H -separate -format "%[standard-deviation]" info:)
echo $i: Mean saturation: $SatMean, Hue Std-Dev: $HueStdDev
done
Output
colour.png: Mean saturation: 17,807.9, Hue Std-Dev: 16,308.3
grey.png: Mean saturation: 7,019.67, Hue Std-Dev: 2,649.01
mono.png: Mean saturation: 14,606.1, Hue Std-Dev: 1,097.36
And it seems to differentiate quite well - I have added the thousands separator for clarity. The range of the values is based on your IM Quantisation level - mine is Q16 so the range is 0-65535.
Differentiating the mono from the grey is harder. Essentially, in the mono image you have a more starkly bi-modal histogram, and in the grey image, you have a more continuous histogram. We can plot the histograms like this:
convert colour.png histogram:colorhist.png
convert grey.png histogram:greyhist.png
convert mono.png histogram:monohist.png
Updated
To differentiate between the greyscale and mono, I want to look at the pixels in the middle of the histogram, basically ignoring blacks (and near blacks) and whites (and near whites). So I can do this to set all blacks and near blacks and whites and near whites to fully black:
convert image.png \
-colorspace gray \
-contrast-stretch 1% \
-black-threshold 20% \
-white-threshold 80% -fill black -opaque white \
out.png
If I now clone that image and set all the pixels in the clone to black, I can then calculate the difference between the histogram-chopped image and the black one
convert image.png \
-colorspace gray \
-contrast-stretch 1% \
-black-threshold 20% \
-white-threshold 80% -fill black -opaque white \
\( +clone -evaluate set 0 \) \
-metric ae -compare -format "%[distortion]" info:
Now, if I calculate the total number of pixels in the image, I can derive the percentage of pixels that are in the midtones and use this as a measure of whether the image is very grey or lacking in midtones.
#!/bin/bash
for i in colour.png grey.png mono.png; do
SatMean=$(convert $i -colorspace HSL -channel S -separate -format "%[mean]" info:)
HueStdDev=$(convert $i -colorspace HSL -channel H -separate -format "%[standard-deviation]" info:)
NumMidTones=$(convert $i -colorspace gray -contrast-stretch 1% -black-threshold 20% -white-threshold 80% -fill black -opaque white \( +clone -evaluate set 0 \) -metric ae -compare -format "%[distortion]" info:)
NumPixels=$(convert $i -ping -format "%[fx:w*h]" info:)
PctMidTones=$((NumMidTones*100/NumPixels))
echo $i: Mean saturation: $SatMean, Hue Std-Dev: $HueStdDev, PercentMidTones: $PctMidTones
done
Output
colour.png: Mean saturation: 17807.9, Hue Std-Dev: 16308.3, PercentMidTones: 70
grey.png: Mean saturation: 7019.67, Hue Std-Dev: 2649.01, PercentMidTones: 39
mono.png: Mean saturation: 14606.1, Hue Std-Dev: 1097.36, PercentMidTones: 27
First of all: Your question's headline is misleading.
"Is it possible to distinguish grayscale from (scanned) monochrome within a shell script?"
Straightforward identify tells color space and bit depth
It is misleading, because all the example images you provide are in fact in 8-bit sRGB colorspace:
identify http://i.stack.imgur.com/lygAE.png \
http://i.stack.imgur.com/H7vBP.png \
http://i.stack.imgur.com/ZOCTK.png
http://i.stack.imgur.com/lygAE.png=>lygAE.png PNG 236x216 236x216+0+0 8-bit sRGB 127KB 0.000u 0:00.000
http://i.stack.imgur.com/H7vBP.png=>H7vBP.png[1] PNG 259x192 259x192+0+0 8-bit sRGB 86.2KB 0.000u 0:00.000
http://i.stack.imgur.com/ZOCTK.png=>ZOCTK.png[2] PNG 264x179 264x179+0+0 8-bit sRGB 86.7KB 0.000u 0:00.000
As you can see, the identify command (part of the ImageMagick suite of commands) can tell you the depth and color space of an image easily.
identify with -format parameter tells specific image properties
You can include the -format parameter with 'percent escapes' in order to get to specific properties only of the image:
f : for image file name
d : for directory component of image
z : for image depth
r : for image class and color space
So try this:
identify -format "%f %d : %z %r\n" \
http://i.stack.imgur.com/lygAE.png \
http://i.stack.imgur.com/H7vBP.png \
http://i.stack.imgur.com/ZOCTK.png
Result:
lygAE.png //i.stack.imgur.com : 8 DirectClass sRGB
H7vBP.png //i.stack.imgur.com : 8 DirectClass sRGB
ZOCTK.png //i.stack.imgur.com : 8 DirectClass sRGB
Convert one image to real monochrome
Now to show you how a real "monochrome" image looks like, let's convert one of your samples accordingly:
convert \
-colorspace gray \
http://i.stack.imgur.com/lygAE.png \
+dither \
-colors 2 \
-depth 1 \
bmp3:monochrome.bmp
and
identify -format "%f : %z %r\n" monochrome.bmp http://i.stack.imgur.com/lygAE.png
monochrome.bmp : 1 PseudoClass Gray
lygAE.png : 8 DirectClass sRGB
Here are the respective images:
Telling the number of unique colors
If you have (as you do) all your images in sRGB color space with 8-bit depth, then in theory, each image can have as many as 16.777.216 (16 million) colors (also called "TrueColor"). However, most actual images do not use the full scope of this spectrum, and the "gray-ish" appearing images will actually use an even smaller number of them.
So ImageMagick has two other 'percent escape' to return information about images:
%k : returns the number of unique colors within an image. This is a calculated value. IM has to process the image and analyse every single pixel of it to arrive at this number.
So here is a command:
identify -format "%f - number of unique colors: %k\n" \
http://i.stack.imgur.com/lygAE.png \
http://i.stack.imgur.com/H7vBP.png \
http://i.stack.imgur.com/ZOCTK.png
Results:
lygAE.png - number of unique colors: 47583
H7vBP.png - number of unique colors: 7987
ZOCTK.png - number of unique colors: 5208
As you can see, your image with obvious coloring uses about 6 times as many uniq colors than the "gray-ish" scans do.
However, this is not necessarily so. See for instance this image:
It is color, isn't it?
I generated it with this command:
convert -size 100x100 \
xc:red \
xc:green \
xc:blue \
xc:white \
xc:black \
xc:cyan \
xc:magenta \
xc:yellow \
+append \
out.png
You can even count the number of unique colors by simply looking at it: 8.
Now what does identify tell us about it?
identify \
-format "%f:\n \
-- number of unique colors: %k\n \
-- depth: %z\n \
-- class/space: %r\n \
-- image type: %[type]\n" \
out.png
Result:
out.png:
-- number of unique colors: 8
-- depth: 8
-- class/space: PseudoClass sRGB
-- image type: Palette
So a low number of unique colors does not necessarily proof that the image is "gray-ish"!
You'lll have to play with this parameters a bit and see if you can come up with a combination that helps you to correctly classify your real-world "thousands of images".
Consider image statistics too
More values you could look at with the help of identify -format %... filename.suffix:
%[gamma] : value of image gamma
%[entropy] : CALCULATED: entropy of image
%[kurtosis] : CALCULATED: kurtosis value statistic of image
%[max] : CALCULATED: maximum value statistic of image
%[mean] : CALCULATED: mean value statistic of image
%[min] : CALCULATED: minimum value statistic of image
%[profile:icc] : ICC profile info
%[profile:icm] : ICM profile info
Last hint: look at the metadata!
Just in case your images were scanned by a device that leaves its own identifying meta data behind: check for them!
The command line tool exiftool is a good utility to do so.