resizeimagemagickpixelgrayscalebpp

Imagemagick maximum colors and scaling


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 Original Image

Into thisFiltered Image


Solution

  • 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:

    enter image description here

    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
    

    enter image description here

    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.