[SOLVED] Reduce 16 to 8 bits/channel with dithering

Questions and postings pertaining to the usage of ImageMagick regardless of the interface. This includes the command-line utilities, as well as the C and C++ APIs. Usage questions are like "How do I use ImageMagick to create drop shadows?".
Lecram
Posts: 13
Joined: 2018-07-28T12:00:45-07:00
Authentication code: 1152
Location: Germany

[SOLVED] Reduce 16 to 8 bits/channel with dithering

Post by Lecram »

Hey folks,

I'd like to convert a given 16 bits per channel RGBA TIFF (= 64 bits total per pixel) into a dithered 8 bit PNG.

Code: Select all

convert in.tif -depth 8 -dither FloydSteinberg out.png
The output produced by this command lacks dithering. Any ideas?

Code: Select all

Version: ImageMagick 6.9.7-4 Q16 x86_64 20170114 http://www.imagemagick.org
Copyright: © 1999-2017 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib djvu fftw fontconfig freetype jbig jng jpeg lcms lqr ltdl lzma openexr pangocairo png tiff wmf x xml zlib
OS is Ubuntu 17.10 x64_64 4.13
Last edited by Lecram on 2018-07-31T02:41:43-07:00, edited 1 time in total.
User avatar
fmw42
Posts: 25562
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Reduce 16 to 8 bits/channel with dithering

Post by fmw42 »

Add -colors X where X<=256. To get the 8-bit color you must have no more than 256 colors. You should also add PNG8:out.png to force 8-bit color.

-depth 8 would produce 24-bit color if you do not reduce colors. -dither is only a setting and needs an operator such as -colors to perform the specified dithering type.
Lecram
Posts: 13
Joined: 2018-07-28T12:00:45-07:00
Authentication code: 1152
Location: Germany

Re: Reduce 16 to 8 bits/channel with dithering

Post by Lecram »

Thank you for your quick reply! :)

The PNG is supposed to have 8 bits per channel i.e. 32 bits/pixel or 16,777,216 colors whereas the input file contains up to 2.814749767×10¹⁴ colors.

EDIT:

Code: Select all

convert in.tif \
  -depth 8 \
  -colors 16777216 \
  -dither FloydSteinberg \
  out.png
yields the same output without dithering, that is, with banding.
Last edited by Lecram on 2018-07-28T13:25:30-07:00, edited 1 time in total.
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Reduce 16 to 8 bits/channel with dithering

Post by snibgo »

To reduce an opaque 16 bit/channel to 8 bit/channel with dithering, remap with hald:16, eg:

Code: Select all

magick in.png -remap hald:16 -depth 8 out.png
But this will ignore alpha. You could first "-alpha extract", give that the same treatment, and put it back with "-compose CopyOpacity -composite", but the result might be weird.
snibgo's IM pages: im.snibgo.com
Lecram
Posts: 13
Joined: 2018-07-28T12:00:45-07:00
Authentication code: 1152
Location: Germany

Re: Reduce 16 to 8 bits/channel with dithering

Post by Lecram »

Code: Select all

convert in.tiff -remap hald:16 -depth 8 out.png
actually yields a dithered 8 bits/channel image but apparently with a reduced amount of colors (the histogram in GIMP shows huge gaps and the dithering is quite obvious to the naked eye like in GIFs).

Browsing some older threads I've found viewtopic.php?f=1&t=9210&hilit=exr+reduction#p28594 which seemingly pretty much deals with the same problem. It concludes that the required functionality hasn't yet been implemented (as of 2007). :-|

Am I out of luck?
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Reduce 16 to 8 bits/channel with dithering

Post by snibgo »

Lecram wrote:... but apparently with a reduced amount of colors
Oh dear. You are correct; it doesn't use all the colors it can from the map, so the histogram has large gaps. Horrible.

A workaround is to separate the channels, apply "-colors 256" to each individually (thus dithering), and combine. Is this worse than a "correct" solution? If so, how much worse? I don't know.

Code: Select all

magick toes.png -channel RGB -separate -colors 256 -combine -depth 8 x.png
snibgo's IM pages: im.snibgo.com
Lecram
Posts: 13
Joined: 2018-07-28T12:00:45-07:00
Authentication code: 1152
Location: Germany

Re: Reduce 16 to 8 bits/channel with dithering

Post by Lecram »

Okay your code does half the trick. The histogram now looks fine. But sadly there's still banding going on, even with explicit "-dither FloydSteinberg":

Code: Select all

convert in.png -channel RGB -separate -dither FloydSteinberg -colors 256 -combine -depth 8 out.png
In the meantime I've found out that GIMP 2.10 now offers dithering when reducing bit depth. (I hope it's okay to "praise" 3rd party software in this forum.) From the changelog https://wiki.gimp.org/wiki/Release:2.10_changelog#Core:
Processing with 8-bit, 16-bit, and 32-bit per color channel precision […] Optional dithering during conversions between precision modes […]
I'd still much prefer a non-GUI solution using imagemagick's convert for automation/scripting.
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Reduce 16 to 8 bits/channel with dithering

Post by snibgo »

I agree, an IM facility to dither when reducing channel precision would be useful. (No problems "praising" other software; we all learn from each other.)
Lecram wrote:But sadly there's still banding going on, ...
Can you link to an example? Better yet, an artificial image created by IM that shows banding when processed? I've tried, but get no visible banding.
snibgo's IM pages: im.snibgo.com
Lecram
Posts: 13
Joined: 2018-07-28T12:00:45-07:00
Authentication code: 1152
Location: Germany

Re: Reduce 16 to 8 bits/channel with dithering

Post by Lecram »

I'm not yet sufficiently familiar with imagemagick as to try creating an image that certainly features the appropriate color depth. So here's some simple geometry/lighting made with Blender.

https://www.dropbox.com/sh/z2vw5ms7doqw ... ?dl=0&lst=
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Reduce 16 to 8 bits/channel with dithering

Post by snibgo »

Thanks for the samples. I can't see any banding on any of the images, on my HP Envy 17" laptop, with a display that I think uses 8 bits/channel. Perhaps the banding you see is an issue with the display.
snibgo's IM pages: im.snibgo.com
Lecram
Posts: 13
Joined: 2018-07-28T12:00:45-07:00
Authentication code: 1152
Location: Germany

Re: Reduce 16 to 8 bits/channel with dithering

Post by Lecram »

[…] Better yet, an artificial image created by IM […]
Creating such an image actually turned out to be quite straight forward, given IM's apparent intelligence.

Code: Select all

convert -size 256x256 gradient:"cyan-black" gradient-int_8.png
This creates an 8-bits per channel RGB image, as 8 bits are sufficient to represent 256 distinct colors, one for each row.

Code: Select all

convert -size 256x512 gradient:"cyan-black" gradient-int_16.png
In this one the doubled vertical size triggers IM to crank up the precision to 16 bits.

Code: Select all

convert gradient-int_16.png -depth 8 -dither FloydSteinberg gradient-int_8-reduced.png
Here, the bit reduction introduces banding with two adjacent rows now having the same exact values. I admit that it's hard to see but it's certainly there. I've also double-checked the renderings I've provided. The file int_8-rgba-imagemagick.png has banding present as well. It's easily verified using flood fill (with threshold set to zero) in GIMP or Paint.

Btw. I much appreciate your kind replies so far, especially given that this topic is rather a minor issue. :)
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Reduce 16 to 8 bits/channel with dithering

Post by snibgo »

Lecram wrote:convert gradient-int_16.png -depth 8 -dither FloydSteinberg gradient-int_8-reduced.png
Your command reads an image and saves it as a 8-bit/channel PNG. It does nothing else. "-dither" is a setting that affects the operation of some operations like "-colors". So if you don't do any of those operations, changing the setting has no effect.
snibgo's IM pages: im.snibgo.com
Lecram
Posts: 13
Joined: 2018-07-28T12:00:45-07:00
Authentication code: 1152
Location: Germany

Re: Reduce 16 to 8 bits/channel with dithering

Post by Lecram »

Earlier on you suggested

Code: Select all

convert in.tiff -remap hald:16 -depth 8 out.png
which worked except for the huge gaps in the histogram. It seems as though IM just decides to ignore/discard some of the colors provided by any colormap.

From http://www.imagemagick.org/Usage/quantize/#remap:
Also note that the final image did not use all 32 colors provided by this map, though more of the colors in the map will be used when some form of dithering was enabled […]
This seemingly arbitrary refusal to use certain colors might be the culprit. Why would IM do that and is there a way to enforce the use of all colors?
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Reduce 16 to 8 bits/channel with dithering

Post by snibgo »

Small maps (eg 256 colours) are not a problem. But large maps (eg more than 32636 colors) get "shrunk" somehow.

In quantize.c, RemapImages() "replaces the colors of a sequence of images with the closest color from a reference image." If it were that simple, then taking any image and remapping with itself as reference would make no change. But that's not true. For example:

Code: Select all

f:\web\im>%IM%convert toes.png -remap toes.png x.png

f:\web\im>%IM%compare -metric RMSE toes.png x.png NULL:
437.383 (0.00667403)

f:\web\im>%IM%convert toes.png -unique-colors info:
toes.png PNG 62206x1 62206x1+0+0 16-bit sRGB 0.016u 0:00.016

f:\web\im>%IM%convert x.png -unique-colors info:
x.png PNG 8055x1 8055x1+0+0 16-bit sRGB 0.000u 0:00.000
I suspect the problem is that the reference image is analysed into an oct-tree with a limited depth. IM has a "-treedepth" option, but that seems only to limit the depth, not to expand it.
snibgo's IM pages: im.snibgo.com
Lecram
Posts: 13
Joined: 2018-07-28T12:00:45-07:00
Authentication code: 1152
Location: Germany

Re: Reduce 16 to 8 bits/channel with dithering

Post by Lecram »

Okay, in order to circumvent the octree limitation I've tried this:

Code: Select all

#!/bin/bash

# Create high color depth (i.e. 16 bits per channel) rgb test image
convert -size 256x512 gradient:"cyan-black" gradient-int_16.png

# Extract one channel at a time from the original image
convert gradient-int_16.png -channel R -separate R16.png
convert gradient-int_16.png -channel G -separate G16.png
convert gradient-int_16.png -channel B -separate B16.png

# Create a palette for conversion/reduction to 256 shades of gray
convert -size 256x1 gradient:"black-white" palette.png

# Reduce the single channel images from 16 to 8 bits using the palette
convert R16.png -remap palette.png -dither FloydSteinberg -depth 8 R8.png
convert G16.png -remap palette.png -dither FloydSteinberg -depth 8 G8.png
convert B16.png -remap palette.png -dither FloydSteinberg -depth 8 B8.png

# Merge the reduced single channel images into a final RGB image
convert R8.png G8.png B8.png -channel RGB -combine out.png
The intermediate files R8.png, G8.png and B8.png still only use 64 distinct shades of gray, even though the palette.png provides 256 different shades. So the final image is still limited to 64^3 = 262,144 colors instead of the full 256^3 = 16,777,216. Dithering works, though.
Post Reply