1

I need a specific colour to be the first in the colour map when using -kmeans to reduce the amount of colours in an image. It's required by Unreal Engine 1 textures when defining a mask colour.

This is all I've got so far:

magick "$PNG_FILE" -kmeans 255 "$PCX_FILE" 

I can somewhat accomplish it using the -define kmeans:seed-colors="#ff00ff" directive, but it worsens the output too much, as it prevents the seed colours from being sampled automatically.

1 Answer 1

0

It cannot be done with ImageMagick, but you can do it with the NetPBM suite. The trick is to use ppmtopcx with a forced palette.

Here is a script that will do it for you - it is well commented and it creates all the intermediate files in easy-to-view formats:

#!/bin/bash ################################################################################ # Reorder palette of PNG file to put magenta first in palette and make into PCX ################################################################################ # Pick up filename from first parameter, or use "start.png" if none given input=${1:-'start.png'} >&2 echo "Processing file: $input" # Extract palette from original image >&2 echo "Extracting original palette into palette0.txt" magick identify -verbose "$input" | awk ' /Rendering intent:/ {exit} p==1 {print substr($3,2,6)} /Colormap:/ {p=1} ' > palette0.txt # Reorder palette with desired entry (FF00FF) first >&2 echo "Reordering palette into palette1.txt" { printf "FF00FF\n"; grep -v "FF00FF" palette0.txt; } > palette1.txt # Make PPM file with new palette # width will be number of palette entries, height will be 1 w=$(wc -l < palette1.txt) h=1 >&2 echo "Making ${w}x${h} PPM file with new palette in palette.ppm" printf "P6\n$w $h\n255\n" > palette.ppm # Convert textual palette (palette1.txt) to binary and append to PPM we just started above tr -d '#\n' < palette1.txt | xxd -r -p >> palette.ppm # Now convert original image to PPM and pass to "ppmtopcx" forcing the new palette as we go pngtopnm start.png | ppmtopcx -8bit -palette palette.ppm > result.pcx # Dump palette of created PCX file for fun - it's the final 768 bytes tail -c 768 result.pcx | xxd -c12 -g3 

I made a test image guaranteed to include magenta and 255 other colours like this:

magick -size 256x256 gradient:black-magenta start.png 

The dumped palette at the end looks like this:

00000000: ff00ff 000000 010001 020002 ............ 0000000c: 030003 040004 050005 060006 ............ 00000018: 070007 080008 090009 0a000a ............ ... ... 000002dc: f300f3 f400f4 f500f5 f600f6 ............ 000002e8: f700f7 f800f8 f900f9 fa00fa ............ 000002f4: fb00fb fc00fc fd00fd fe00fe ............ 

Hopefully you can see magenta (ff00ff) as the first entry and the that it is missing as the final entry (having been relocated to the start).


If you don't like NetPBM, you can do it very simply in Python with Pillow as follows:

#!/usr/bin/env python3 from PIL import Image import numpy as np # Load input image im = Image.open('start.png') # Get palette p = im.getpalette() #print(f'{p=}') # Make new palette, by swapping first and last RGB entries firstEntry = p[:3] print(f'{firstEntry=}') lastEntry = p[-3:] print(f'{lastEntry=}') newPalette = [ *lastEntry, *p[3:765], *firstEntry ] print(f'{len(newPalette)=}') #print(f'{newPalette=}') # Make image into Numpy array of palette indices na = np.array(im) # Find all locations with palette entry 0, and all locations with palette entry 255 mask0 = na==0 mask255 = na==255 # Swap 0 for 255 and 255 for 0 na[mask0] = 255 na[mask255] = 0 # Revert to PIL Image and push in new palette newIm = Image.fromarray(na) newIm.putpalette(newPalette) # Save result as PCX newIm.save('result.pcx') 

Note I have not coded the general case, just the demonstration of swapping palette indices 0 and 255. The other case is not much harder and you can certainly code it from this example. I didn't want to get bogged down in the general case because Python may be a non-starter for you.

4
  • Amazing, thank you! Works exactly as I need it to. The Python approach is my preferred one for sure. Commented Sep 16, 2024 at 6:06
  • Actually, I was a bit quick to reply. I'm not quite comprehending why you would swap 0 and 255 to make (255, 0, 255) the first colour in the index. This is my attempt in Python using your code: pastebin.com/tKLP1dux Commented Sep 16, 2024 at 6:28
  • Like I said, I did not code the general case where you look for one specific palette entry (such as magenta) and swap that entry with the first entry. I just took the last palette entry and swapped that with the first because the question was about setting the ordering of the palette, so if you can move the last entry to the first you can equally move any entry you choose to the first. I don't know which one you choose - your question doesn't say that, or provide a proper "minimum, complete, verifiable example". Commented Sep 16, 2024 at 6:50
  • Alright, so you mistook my question to mean "how do I rearrange existing colours in a palette" rather than "how do I define a colour as the first" (which is literally what it says), and you're sticking to your interpretation, despite my clarification. I can't say I fathom your thinking, but your answer was educational, and you clearly but effort into it. So thanks anyway. Commented Sep 17, 2024 at 7:24

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.