3

I've updated to IM 6.9.9-3 recently and found some very inconsistent behaviour. Here are two seemingly similar images:

image1.png

enter image description here

image2.png

enter image description here

Let's have a look at what IM thinks of their colourspaces:

[grochmal@phoenix]$ identify *
image1.png PNG 95x74 95x74+0+0 8-bit sRGB 1230B 0.000u 0:00.000
image2.png PNG 69x102 69x102+0+0 8-bit sRGB 2040B 0.000u 0:00.000
[grochmal@phoenix]$ identify -verbose image1.png | grep -A 2 Color
  Colorspace: sRGB
  Depth: 8-bit
  Channel depth:
--
  Colors: 96
  Histogram:
      1357: (217,217,217,255) #D9D9D9FF grey85
[grochmal@phoenix]$ identify -verbose image2.png | grep -A 2 Color
  Colorspace: sRGB
  Depth: 8-bit
  Channel depth:
--
  Colors: 188
  Histogram:
         1: ( 26, 26, 26,255) #1A1A1AFF graya(26,1)

Both have less than 255 colours (needed for a greyscale) so let's try to convert them to JPEGs:

[grochmal@phoenix]$ convert image1.png -strip -quality 80 image1.jpg
[grochmal@phoenix]$ convert image2.png -strip -quality 80 image2.jpg

Good, no errors. But one of the images now is in grayscale and another is in sRGB, moreover, IM added a colourmap to one fo the images:

[grochmal@phoenix]$ identify -verbose image1.jpg | grep -A 2 Color
  Colorspace: sRGB
  Depth: 8-bit
  Channel depth:
--
  Colors: 37
  Histogram:
      1520: (217,217,217) #D9D9D9 gray(217)
[grochmal@phoenix]$ identify -verbose image2.jpg | grep -A 2 Color
  Colorspace: Gray
  Depth: 8-bit
  Channel depth:
--
  Colors: 195
  Histogram:
         1: ( 14, 14, 14) #0E0E0E gray(14)
--
  Colormap entries: 256
  Colormap:
         0: (  0,  0,  0) #000000 gray(0)
         1: (  1,  1,  1) #010101 gray(1)
[grochmal@phoenix]$ identify *
image1.jpg JPEG 95x74 95x74+0+0 8-bit sRGB 397B 0.000u 0:00.000
image1.png PNG 95x74 95x74+0+0 8-bit sRGB 1230B 0.000u 0:00.000
image2.jpg JPEG 69x102 69x102+0+0 8-bit Gray 256c 687B 0.000u 0:00.000
image2.png PNG 69x102 69x102+0+0 8-bit sRGB 2040B 0.000u 0:00.000

The colourmap now breaks some of my thumbnail making scripts (the majority of images I use are actually much bigger but these two are the ones I experimented on the most on; it is a web game). Nevertheless, this raises three questions:

  1. How IM performs the decisions of (1) changing the colourspace of the image during the convert and (2) the decision of adding a colourmap?

  2. How can I force IM to perform a more consistent behaviour (preferably not adding colourmaps to anything)?

  3. Can I forcefully remove a colourmap? (I'm trying for 5 hours without success.)

grochmal
  • 8,657
  • 1
    The inconsistency can be explained by the fact that one of your images is all gray, and the other one isn't. In the first image, there are 18 pixels with rgb values (226, 227, 227), 16 pixels with rgb values (217, 217, 218), etc. These slightly-off-gray colors prevent the use of a grayscale output format. –  Jul 29 '17 at 22:50
  • @WumpusQ.Wumbley - Very good find, thanks! I think you just got me on the right path, the images also differ on Type:, and the combination of the RGB pixels with that is likely to produce the decision. I'll investigate more after a good nights sleep. – grochmal Jul 29 '17 at 23:48

1 Answers1

3

Thanks to @WumpusQ.Wumbley for pointing me in the right direction.

TL;DR: Use -type TrueColor to remove colourmaps, either from Palette types of PseudoClass images.

A colourmap is not a PseudoClass

Reading the IM forums I finally (more-or-less) understood what is a colourmap in an image and how to deal with it. First of all there is a difference between DirectClass and PseudoClass:

  • A DirectClass stores the colour values for every pixel.
  • A PseudoClass uses a colour table and then stores offsets into that table.

This was the source of my confusion: a PseudoClass is not a colourmap, they are different things. And since IM will almost always convert a PseudoClass image into a DirectClass image I was baffled.

Major operations involving image modifications would generally upgrade PsuedoColor images (images using a color table) to a DirectColor (images using separate color values for every pixel) before the operation is applied.

source (P.S. DirectClass and PseudoClass are synonyms to DirectColor and PseudoColor)

So, what is a Colourmap?

The colourmap is an index of colours against a pallette which is stored in the image. This is very similar to a PseudoClass since it also is a table of colours, the difference is that a PseudoClass will include only the colours present in the image and the colourmap will include all colours in a pallette. Note that a PseudoClass will have a colourmap section but this will be the colour table of the PseudoClass not a Palette type (yes, that is very confusing on IM's side).

The colourmap is defined by the -type option to IM:

-type type

the image type.

Choose from: Bilevel, Grayscale, GrayscaleMatte,
Palette, PaletteMatte, TrueColor, TrueColorMatte,
ColorSeparation, or ColorSeparationMatte.

Normally, when a format supports different subformats such
as grayscale and truecolor, the encoder will try to choose an
efficient subformat. The -type option can be used to override
this behavior. For example, to prevent a JPEG from being written
in grayscale format even though only gray pixels are present, use.

    convert bird.png -type TrueColor bird.jpg

Similarly, use -type TrueColorMatte to force the encoder to write
an alpha channel even though the image is opaque, if the output
format supports transparency.

Use -type optimize to ensure the image is written in the
smallest possible file size.

Note that the type is different from the colourspace. Most images will have a similar type an colourspace, e.g. Type: Grayscale and Colorspace: Gray, but that does not need to be the case. The image (in the question) that was uploaded to my server had a distinct type and colourspace.

The types from the man above can be understood as:

  • Bilevel: Black and white (I do not think anyone still uses this)
  • Grayscale: 0-255 are shades of Gray
  • Palette: Uses a colourmap present in the image
  • TrueColor: Uses one of the standard colour schemes (today most likely sRGB)
  • ColorSeparation: Not sure, but I think it stores channels separately
  • |one of the above|Matte: Forces the existence of an Alpha channel.

Note: By today's standards the difference between writing a grayscale image out in the sRGB colourspace or in the Gray colourspace should be almost negligible. (And using plain RGB instead of sRGB would also be negligible, and the image would often sdisplay badly. Don't use plain RGB, use sRGB.)

Back to the images

Now we can explain the decisions taken by IM, and we can argue that it is image1.png that is at fault, not IM. Let's again have a look at the images:

[grochmal@phoenix so]$ identify -verbose image1.png | grep -A 3 -e 'Color\|Type\|Class'
  Class: DirectClass
  Geometry: 95x74+0+0
  Resolution: 28.35x28.35
  Print size: 3.35097x2.61023
--
  Type: PaletteAlpha
  Endianess: Undefined
  Colorspace: sRGB
  Depth: 8-bit
  Channel depth:
    red: 8-bit
--
  Colors: 96
  Histogram:
      1357: (217,217,217,255) #D9D9D9FF grey85
        16: (217,217,218,255) #D9D9DAFF srgba(217,217,218,1)
[grochmal@phoenix so]$ identify -verbose image2.png | grep -A 3 -e 'Color\|Type\|Class'
  Class: DirectClass
  Geometry: 69x102+0+0
  Resolution: 28.35x28.35
  Print size: 2.43386x3.59788
--
  Type: GrayscaleAlpha
  Endianess: Undefined
  Colorspace: sRGB
  Depth: 8-bit
  Channel depth:
    gray: 8-bit
--
  Colors: 188
  Histogram:
         1: ( 26, 26, 26,255) #1A1A1AFF graya(26,1)
         1: ( 35, 35, 35,255) #232323FF graya(35,1)

We can see that image1.png has a PaletteAlpha type (Or PaletteMatte in IM terms) and image2 has a GrayscaleAlpha type.

The fact that image1.png does not have a colourmap is plain wrong. So buggy image writer must have written that image as having a colourmap but not writing the colourmap into the image.

When IM sees that the image should have a colourmap it assigns one, based on a guess probably. The guess appears to be a grayscale colourmap which is added during the conversion.

(See the question to see the output of the images being guess-converted)

How to fix it?

Fortunately, we can help IM by telling it that we do not want to keep or guess the type of the image. We simply want an image that will use colours from a standard colourmap (sRGB or Gray for our purposes) instead of a Palette which we do not have anyway (since the buggy image writer did not write it into the image).

We use -type TrueColor:

[grochmal@phoenix so]$ convert image1.png -strip -quality 80 -type TrueColor image1.jpg
[grochmal@phoenix so]$ convert image2.png -strip -quality 80 -type TrueColor image2.jpg

And the images come out the way we expect:

[grochmal@phoenix so]$ identify -verbose image1.jpg | grep -A 3 -e 'Color\|Type\|Class'
  Class: DirectClass
  Geometry: 95x74+0+0
  Resolution: 28x28
  Print size: 3.39286x2.64286
--
  Type: Grayscale
  Endianess: Undefined
  Colorspace: sRGB
  Depth: 8-bit
  Channel depth:
    gray: 8-bit
--
  Colors: 37
  Histogram:
      1520: (217,217,217) #D9D9D9 gray(217)
        95: (219,219,219) #DBDBDB gray(219)
[grochmal@phoenix so]$ identify -verbose image2.jpg | grep -A 3 -e 'Color\|Type\|Class'
  Class: DirectClass
  Geometry: 69x102+0+0
  Resolution: 28x28
  Print size: 2.46429x3.64286
--
  Type: Grayscale
  Endianess: Undefined
  Colorspace: sRGB
  Depth: 8-bit
  Channel depth:
    gray: 8-bit
--
  Colors: 195
  Histogram:
         1: ( 14, 14, 14) #0E0E0E gray(14)
         1: ( 37, 37, 37) #252525 gray(37)

Note that IM figured out that the images use a Grayscale of pixels and gave out a DirectClass,Grayscale,sRGB image, which is likely to be what we are after in almost all cases.

If you instead use -type Grayscale you will get a PseudoClass,Graysclae,Gray image, which may save you a handful of bytes (perhaps 10-20k on a typical web image of 100-500k) at the cost of a less compatible format. For example, my pygtk sometimes barfs on PseudoClass images, lack of backward compatibility I guess.

grochmal
  • 8,657