Documentation ¶
Index ¶
- Variables
- func RoundClamp(i float32) uint16
- type Ditherer
- func (d *Ditherer) Dither(src image.Image) image.Image
- func (d *Ditherer) DitherConfig(src draw.Image) (image.Image, image.Config)
- func (d *Ditherer) DitherCopy(src image.Image) *image.RGBA
- func (d *Ditherer) DitherCopyConfig(src image.Image) (*image.RGBA, image.Config)
- func (d *Ditherer) DitherPaletted(src image.Image) *image.Paletted
- func (d *Ditherer) DitherPalettedConfig(src image.Image) (*image.Paletted, image.Config)
- func (d *Ditherer) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point)
- func (d *Ditherer) GetColorModel() color.Model
- func (d *Ditherer) GetPalette() []color.Color
- func (d *Ditherer) Quantize(p color.Palette, m image.Image) color.Palette
- type ErrorDiffusionMatrix
- type OrderedDitherMatrix
- type PixelMapper
- type SpecialDither
Constants ¶
This section is empty.
Variables ¶
var Atkinson = ErrorDiffusionMatrix{
{0, 0, 1.0 / 8, 1.0 / 8},
{1.0 / 8, 1.0 / 8, 1.0 / 8, 0},
{0, 1.0 / 8, 0, 0},
}
var Burkes = ErrorDiffusionMatrix{
{0, 0, 0, 8.0 / 32, 4.0 / 32},
{2.0 / 32, 4.0 / 32, 8.0 / 32, 4.0 / 32, 2.0 / 32},
}
var ClusteredDot4x4 = OrderedDitherMatrix{ Matrix: [][]uint{ {12, 5, 6, 13}, {4, 0, 1, 7}, {11, 3, 2, 8}, {15, 10, 9, 14}, }, Max: 16, }
ClusteredDot4x4 comes from http://caca.zoy.org/study/part2.html
It is not diagonal, so the dots form a grid.
var ClusteredDot6x6 = OrderedDitherMatrix{ Matrix: [][]uint{ {34, 29, 17, 21, 30, 35}, {28, 14, 9, 16, 20, 31}, {13, 8, 4, 5, 15, 19}, {12, 3, 0, 1, 10, 18}, {27, 7, 2, 6, 23, 24}, {33, 26, 11, 22, 25, 32}, }, Max: 36, }
ClusteredDot6x6 comes from Figure 5.9 of the book Digital Halftoning by Robert Ulichney. It can represent "37 levels of gray". It is not diagonal.
var ClusteredDot6x6_2 = OrderedDitherMatrix{ Matrix: [][]uint{ {34, 25, 21, 17, 29, 33}, {30, 13, 9, 5, 12, 24}, {18, 6, 1, 0, 8, 20}, {22, 10, 2, 3, 4, 16}, {26, 14, 7, 11, 15, 28}, {35, 31, 19, 23, 27, 32}, }, Max: 36, }
ClusteredDot6x6_2 comes from https://archive.is/71e9G. On the webpage it is called "central white point" while ClusteredDot6x6 is called "clustered dots".
It is nearly identical to ClusteredDot6x6.
var ClusteredDot6x6_3 = OrderedDitherMatrix{ Matrix: [][]uint{ {30, 22, 16, 21, 33, 35}, {24, 11, 7, 9, 26, 28}, {13, 5, 0, 2, 14, 19}, {15, 3, 1, 4, 12, 18}, {27, 8, 6, 10, 25, 29}, {32, 20, 17, 23, 31, 34}, }, Max: 36, }
ClusteredDot6x6_3 comes from https://archive.is/71e9G. On the webpage it is called "balanced centered point".
It is nearly identical to ClusteredDot6x6.
var ClusteredDot8x8 = OrderedDitherMatrix{ Matrix: [][]uint{ {3, 9, 17, 27, 25, 15, 7, 1}, {11, 29, 38, 46, 44, 36, 23, 5}, {19, 40, 52, 58, 56, 50, 34, 13}, {31, 48, 60, 63, 62, 54, 42, 21}, {30, 47, 59, 63, 61, 53, 41, 20}, {18, 39, 51, 57, 55, 49, 33, 12}, {10, 28, 37, 45, 43, 35, 22, 4}, {2, 8, 16, 26, 24, 14, 6, 0}, }, Max: 64, }
ClusteredDot8x8 comes from Figure 1.5 of the book Modern Digital Halftoning, Second Edition, by Daniel L. Lau and Gonzalo R. Arce. It is like ClusteredDotDiagonal8x8, but is not diagonal. It can represent "65 gray-levels".
var ClusteredDotDiagonal16x16 = OrderedDitherMatrix{ Matrix: [][]uint{ {63, 58, 50, 40, 41, 51, 59, 60, 64, 69, 77, 87, 86, 76, 68, 67}, {57, 33, 27, 18, 19, 28, 34, 52, 70, 94, 100, 109, 108, 99, 93, 75}, {49, 26, 13, 11, 12, 15, 29, 44, 78, 101, 114, 116, 115, 112, 98, 83}, {39, 17, 4, 3, 2, 9, 20, 42, 87, 110, 123, 124, 125, 118, 107, 85}, {38, 16, 5, 0, 1, 10, 21, 43, 89, 111, 122, 127, 126, 117, 106, 84}, {48, 25, 8, 6, 7, 14, 30, 45, 79, 102, 119, 121, 120, 113, 97, 82}, {56, 32, 24, 23, 22, 31, 35, 53, 71, 95, 103, 104, 105, 96, 92, 74}, {62, 55, 47, 37, 36, 46, 54, 61, 65, 72, 80, 90, 91, 81, 73, 66}, {64, 69, 77, 87, 86, 76, 68, 67, 63, 58, 50, 40, 41, 51, 59, 60}, {70, 94, 100, 109, 108, 99, 93, 75, 57, 33, 27, 18, 19, 28, 34, 52}, {78, 101, 114, 116, 115, 112, 98, 83, 49, 26, 13, 11, 12, 15, 29, 44}, {87, 110, 123, 124, 125, 118, 107, 85, 39, 17, 4, 3, 2, 9, 20, 42}, {89, 111, 122, 127, 126, 117, 106, 84, 38, 16, 5, 0, 1, 10, 21, 43}, {79, 102, 119, 121, 120, 113, 97, 82, 48, 25, 8, 6, 7, 14, 30, 45}, {71, 95, 103, 104, 105, 96, 92, 74, 56, 32, 24, 23, 22, 31, 35, 53}, {65, 72, 80, 90, 91, 81, 73, 66, 62, 55, 47, 37, 36, 46, 54, 61}, }, Max: 128, }
ClusteredDotDiagonal16x16 comes from Figure 5.4 of the book Digital Halftoning by Robert Ulichney. In the book it's called "M = 8". It can represent "129 levels of gray". Its dimensions are 16x16, but as a diagonal matrix it is 17x17. It is called "Diagonal" because the resulting dot pattern is at a 45 degree angle.
var ClusteredDotDiagonal6x6 = OrderedDitherMatrix{ Matrix: [][]uint{ {8, 6, 7, 9, 11, 10}, {5, 0, 1, 12, 17, 16}, {4, 3, 2, 13, 14, 15}, {9, 11, 10, 8, 6, 8}, {12, 17, 16, 5, 0, 1}, {13, 14, 15, 4, 3, 2}, }, Max: 18, }
ClusteredDotDiagonal6x6 comes from Figure 5.4 of the book Digital Halftoning by Robert Ulichney. In the book it's called "M = 3". It can represent "19 levels of gray". Its dimensions are 6x6, but as a diagonal matrix it is 7x7. It is called "Diagonal" because the resulting dot pattern is at a 45 degree angle.
var ClusteredDotDiagonal8x8 = OrderedDitherMatrix{ Matrix: [][]uint{ {24, 10, 12, 26, 35, 47, 49, 37}, {8, 0, 2, 14, 45, 59, 61, 51}, {22, 6, 4, 16, 43, 57, 63, 53}, {30, 20, 18, 28, 33, 41, 55, 39}, {34, 46, 48, 36, 25, 11, 13, 27}, {44, 58, 60, 50, 9, 1, 3, 15}, {42, 56, 62, 52, 23, 7, 5, 17}, {32, 40, 54, 38, 31, 21, 19, 29}, }, Max: 64, }
ClusteredDotDiagonal8x8 comes from http://caca.zoy.org/study/part2.html
They say it "mimics the halftoning techniques used by newspapers". It is called "Diagonal" because the resulting dot pattern is at a 45 degree angle.
var ClusteredDotDiagonal8x8_2 = OrderedDitherMatrix{ Matrix: [][]uint{ {13, 11, 12, 15, 18, 20, 19, 16}, {4, 3, 2, 9, 27, 28, 29, 22}, {5, 0, 1, 10, 26, 31, 30, 21}, {8, 6, 7, 14, 23, 25, 24, 17}, {18, 20, 19, 16, 13, 11, 12, 15}, {27, 28, 29, 22, 4, 3, 2, 9}, {26, 31, 30, 21, 5, 0, 1, 10}, {23, 25, 24, 17, 8, 6, 7, 14}, }, Max: 32, }
ClusteredDotDiagonal8x8_2 comes from Figure 5.4 of the book Digital Halftoning by Robert Ulichney. In the book it's called "M = 4". It can represent "33 levels of gray". Its dimensionsare 8x8, but as a diagonal matrix it is 9x9. It is called "Diagonal" because the resulting dot pattern is at a 45 degree angle.
It is almost identical to ClusteredDotDiagonal8x8, but worse because it can represent fewer gray levels. There is not much point in using it.
var ClusteredDotDiagonal8x8_3 = OrderedDitherMatrix{ Matrix: [][]uint{ {13, 9, 5, 12, 18, 22, 26, 19}, {6, 1, 0, 8, 25, 30, 31, 23}, {10, 2, 3, 4, 21, 29, 28, 27}, {14, 7, 11, 15, 17, 24, 20, 16}, {18, 22, 26, 19, 13, 9, 5, 12}, {25, 30, 31, 23, 6, 1, 0, 8}, {21, 29, 28, 27, 10, 2, 3, 4}, {17, 24, 20, 16, 14, 7, 11, 15}, }, Max: 32, }
ClusteredDotDiagonal8x8_3 comes from https://archive.is/71e9G. On the webpage it is called "diagonal ordered matrix with balanced centered points".
It is almost identical to ClusteredDotDiagonal8x8, but worse because it can represent fewer gray levels. There is not much point in using it.
It is called "Diagonal" because the resulting dot pattern is at a 45 degree angle.
var ClusteredDotHorizontalLine = OrderedDitherMatrix{ Matrix: [][]uint{ {35, 33, 31, 30, 32, 34}, {23, 21, 19, 18, 20, 22}, {11, 9, 7, 6, 8, 10}, {5, 3, 1, 0, 2, 4}, {17, 15, 13, 12, 14, 16}, {29, 27, 25, 24, 26, 28}, }, Max: 36, }
ClusteredDotHorizontalLine comes from Figure 5.13 of the book Digital Halftoning by Robert Ulichney. It can represent "37 levels of gray". Its dimensions are 6x6.
It "clusters pixels about horizontal lines".
var ClusteredDotSpiral5x5 = OrderedDitherMatrix{ Matrix: [][]uint{ {20, 21, 22, 23, 24}, {19, 6, 7, 8, 9}, {18, 5, 0, 1, 10}, {17, 4, 3, 2, 11}, {16, 15, 14, 13, 12}, }, Max: 25, }
ClusteredDotSpiral5x5 comes from Figure 5.13 of the book Digital Halftoning by Robert Ulichney. It can represent "26 levels of gray". Its dimensions are 5x5.
Instead of alternating dark and light dots like the other clustered-dot matrices, the dark parts grow to fill the area.
var ClusteredDotVerticalLine = OrderedDitherMatrix{ Matrix: [][]uint{ {35, 23, 11, 5, 17, 29}, {33, 21, 9, 3, 15, 27}, {31, 19, 7, 1, 13, 25}, {30, 18, 6, 0, 12, 24}, {32, 20, 8, 2, 14, 26}, {34, 22, 10, 4, 16, 28}, }, Max: 36, }
ClusteredDotVerticalLine is my rotated version of ClusteredDotHorizontalLine.
var FalseFloydSteinberg = ErrorDiffusionMatrix{
{0, 3.0 / 8},
{3.0 / 8, 2.0 / 8},
}
var FloydSteinberg = ErrorDiffusionMatrix{
{0, 0, 7.0 / 16},
{3.0 / 16, 5.0 / 16, 1.0 / 16},
}
var Horizontal3x5 = OrderedDitherMatrix{ Matrix: [][]uint{ {9, 10, 11}, {3, 4, 5}, {0, 1, 2}, {6, 7, 8}, {12, 13, 14}, }, Max: 15, }
Horizontal3x5 is my rotated version of Vertical5x3.
var JarvisJudiceNinke = ErrorDiffusionMatrix{
{0, 0, 0, 7.0 / 48, 5.0 / 48},
{3.0 / 48, 5.0 / 48, 7.0 / 48, 5.0 / 48, 3.0 / 48},
{1.0 / 48, 3.0 / 48, 5.0 / 48, 3.0 / 48, 1.0 / 48},
}
var Sierra = ErrorDiffusionMatrix{
{0, 0, 0, 5.0 / 32, 3.0 / 32},
{2.0 / 32, 4.0 / 32, 5.0 / 32, 4.0 / 32, 2.0 / 32},
{0, 2.0 / 32, 3.0 / 32, 2.0 / 32, 0},
}
var Sierra2 = TwoRowSierra
Sierra2 is another name for TwoRowSierra
var Sierra2_4A = SierraLite
Sierra2_4A (usually written as Sierra2-4A) is another name for SierraLite.
var Sierra3 = Sierra
Sierra3 is another name for the original Sierra matrix.
var SierraLite = ErrorDiffusionMatrix{
{0, 0, 2.0 / 4},
{1.0 / 4, 1.0 / 4, 0},
}
var Simple2D = ErrorDiffusionMatrix{
{0, 0.5},
{0.5, 0},
}
var StevenPigeon = ErrorDiffusionMatrix{
{0, 0, 0, 2.0 / 14, 1.0 / 14},
{0, 2.0 / 14, 2.0 / 14, 2.0 / 14, 0},
{1.0 / 14, 0, 1.0 / 14, 0, 1.0 / 14},
}
StevenPigeon is an error diffusion matrix developed by Steven Pigeon. Source: https://hbfs.wordpress.com/2013/12/31/dithering/
var Stucki = ErrorDiffusionMatrix{
{0, 0, 0, 8.0 / 42, 4.0 / 42},
{2.0 / 42, 4.0 / 42, 8.0 / 42, 4.0 / 42, 2.0 / 42},
{1.0 / 42, 2.0 / 42, 4.0 / 42, 2.0 / 42, 1.0 / 42},
}
var TwoRowSierra = ErrorDiffusionMatrix{
{0, 0, 0, 4.0 / 16, 3.0 / 16},
{1.0 / 16, 2.0 / 16, 3.0 / 16, 2.0 / 16, 1.0 / 16},
}
var Vertical5x3 = OrderedDitherMatrix{ Matrix: [][]uint{ {9, 3, 0, 6, 12}, {10, 4, 1, 7, 13}, {11, 5, 2, 8, 14}, }, Max: 15, }
Vertical5x3 comes from http://caca.zoy.org/study/part2.html
They say it "creates artistic vertical line artifacts".
Functions ¶
func RoundClamp ¶
RoundClamp clamps the number and rounds it, rounding ties to the nearest even number. This should be used if you're writing your own PixelMapper.
Types ¶
type Ditherer ¶
type Ditherer struct { // Matrix is the ErrorDiffusionMatrix for dithering. Matrix ErrorDiffusionMatrix // Mapper is the ColorMapper function for dithering. Mapper PixelMapper // Special is the special dithering algorithm that's being used. The default // value of 0 indicates that no special dithering algorithm is being used. Special SpecialDither // SingleThreaded controls whether the dithering happens sequentially or using // runtime.GOMAXPROCS(0) workers, which defaults to the number of CPUs. // // Note that error diffusion dithering (using Matrix) is sequential by nature // and so this field has no effect. // // Setting this to true is only useful in rare cases, like when numbers are // used sequentially in a PixelMapper, and the output must be deterministic. // Because otherwise the numbers will be retrieved in a different order each // time, as the goroutines call on the PixelMapper. SingleThreaded bool // Serpentine controls whether the error diffusion matrix is applied in a // serpentine manner, meaning that it goes right-to-left every other line. // This greatly reduces line-type artifacts. If a Mapper is being used this // field will have no effect. Serpentine bool // contains filtered or unexported fields }
Ditherer dithers images according to the settings in the struct. It can be safely reused for many images, and used concurrently.
Some members of the struct are public. Those members can be changed in-between dithering images, if you would like to dither again. If you change those public methods while an image is being dithered, the output image will have problems, so only change in-between dithering.
You can only set one of Matrix, Mapper, or Special. Trying to dither when none or more than one of those are set will cause the function to panic.
All methods can handle images with transparency, unless otherwise specified. Read the docs before using!
func NewDitherer ¶
NewDitherer creates a new Ditherer that uses a copy of the provided palette. If the palette is empty or nil then nil will be returned. All palette colors should be opaque.
func (*Ditherer) Dither ¶
Dither dithers the provided image.
It will always try to change the provided image and return it, but if that is not possible it will return the dithered image as a copy.
In comparison to DitherCopy, this can greatly reduce memory usage, and is quicker because it usually won't copy the image at the beginning. It should be preferred if you don't need to keep the original image.
Cases where a copy will be are limited to: If the input image is *image.Paletted and the image's palette is different than the Ditherer's, or if the image can't be casted to draw.Image.
The returned image type when copied is *image.RGBA. But it may be different if the image wasn't copied.
func (*Ditherer) DitherConfig ¶
DitherConfig is like Dither, but returns an image.Config as well.
func (*Ditherer) DitherCopy ¶
DitherCopy dithers a copy of the src image and returns it. The src image remains unchanged. If you don't need to keep the original image, use Dither.
func (*Ditherer) DitherCopyConfig ¶
DitherCopyConfig is like DitherCopy, but returns an image.Config as well.
func (*Ditherer) DitherPaletted ¶
DitherPaletted dithers a copy of the src image and returns it as an *image.Paletted. The src image remains unchanged. If you don't need an *image.Paletted, using Dither or DitherCopy should be preferred.
The palette of the returned image is the same palette the ditherer uses internally -- it will be equal to the output of GetPalette().
If the Ditherer's palette has over 256 colors then the function will panic, because *image.Paletted does not allow for that.
DitherPaletted can't handle images with transparency.
func (*Ditherer) DitherPalettedConfig ¶
DitherPalettedConfig is like DitherPaletted, but returns an image.Config as well.
DitherPalettedConfig can't handle images with transparency.
func (*Ditherer) Draw ¶
Draw implements draw.Drawer. This means you can use a Ditherer in many places, such as for encoding GIFs.
Draw ignores whether dst has a palette or not, and just uses the internal Ditherer palette. If the dst image passed has a palette (i.e. is of the type *image.Paletted), and the palette is the not the same as the Ditherer's palette, it will panic.
func (*Ditherer) GetColorModel ¶
GetColorModel returns a copy of the Ditherer's palette as a color.Model that finds the closest color using Euclidean distance in sRGB space.
func (*Ditherer) GetPalette ¶
GetPalette returns a copy of the current palette being used by the Ditherer.
func (*Ditherer) Quantize ¶
Quantize implements draw.Quantizer. It ignores the provided image and just returns the Ditherer's palette each time. This is useful for places that only allow you to set the palette through a draw.Quantizer, like the image/gif package.
This function will panic if the Ditherer's palette has more colors than the caller wants, which the caller indicates by cap(p).
It will also panic if there's already colors in the color.Palette provided to the func and not all of those colors are included in the Ditherer's palette. This is because the caller is indicating that certain colors must be in the palette, but the user who created the Ditherer does not want those colors.
type ErrorDiffusionMatrix ¶
type ErrorDiffusionMatrix [][]float32
ErrorDiffusionMatrix holds the matrix for the error-diffusion type of dithering. An example of this would be Floyd-Steinberg or Atkinson.
Zero values can be used to represent pixels that have already been processed. The current pixel is assumed to be the right-most zero value in the top row.
func ErrorDiffusionStrength ¶
func ErrorDiffusionStrength(edm ErrorDiffusionMatrix, strength float32) ErrorDiffusionMatrix
ErrorDiffusionStrength modifies an existing error diffusion matrix so that it will be applied with the specified strength.
strength is usually a value from 0 to 1.0, where 1.0 means 100% strength, and will not modify the matrix at all. It is inversely proportional to contrast - reducing the strength increases the contrast. It can be useful at values like 0.8 for reducing noise in the dithered image.
See the documentation for Bayer for more details.
func (ErrorDiffusionMatrix) CurrentPixel ¶
func (e ErrorDiffusionMatrix) CurrentPixel() int
CurrentPixel returns the index the current pixel. The current pixel is assumed to be the right-most zero value in the top row. In all matrixes that I have seen, the current pixel is always in the middle, but this function exists just in case.
Therefore with an ErrorDiffusionMatrix named edm, the current pixel is at:
edm[0][edm.CurrentPixel()]
Usually you'll want to cache this value.
func (ErrorDiffusionMatrix) Offset ¶
func (e ErrorDiffusionMatrix) Offset(x, y, curPx int) (int, int)
Offset will take the index of where you are in the matrix and return the offset from the current pixel. You have to pass the curPx value yourself to allow for caching, but it can be retrieved by calling CurrentPixel().
type OrderedDitherMatrix ¶
OrderedDitherMatrix is used to hold a matrix used for ordered dithering. This is useful if you find a matrix somewhere and would like to try it out. You can create this struct and then give it to PixelMapperFromMatrix.
The matrix must be rectangular - each slice inside the first one must be the same length.
Max is the value all the matrix values will be divided by. Usually this is the product of the dimensions of the matrix (x*y), or the greatest value in the matrix plus one. For diagonal matrices or other matrices with repeated values, it is the latter.
Leaving Max as 0 will cause a panic.
Matrix values should almost always range from 0 to Max-1. If the matrix you found ranges from 1 to Max, just subtract 1 from every value when programming it.
type PixelMapper ¶
PixelMapper is a function that takes the coordinate and color of a pixel, and returns a new color. That new color does not need to be part of any palette.
This is used for thresholding, random dithering, patterning, and ordered dithering - basically any dithering that can be applied to each pixel individually.
The provided RGB values are in the linear RGB space, and the returned values must be as well. All dithering operations should be happening in this space anyway, so this is done as a convenience. The RGB values are in the range [0, 65535], and must be returned in the same range.
It must be thread-safe, as it will be called concurrently.
func Bayer ¶
func Bayer(x, y uint, strength float32) PixelMapper
Bayer returns a PixelMapper that applies a Bayer matrix with the specified size. Please read this entire documentation, and see my recommendations at the end, especially if you're dithering color images.
First off, cache the result of this function. It's not trivial to generate, and it can be re-used or used concurrently with no issues.
The provided dimensions of the bayer matrix can only be powers of 2, but they do not need to be the same. If they are not powers of two this function will panic.
There are currently two exceptions to this, which come from hand-derived Bayer matrices by Joel Yliluoma: 5x3, 3x5, 3x3. As he notes, "they can have a visibly uneven look, and thus are rarely worth using".
Source:
https://bisqwit.iki.fi/story/howto/dither/jy/#Appendix%202ThresholdMatrix
strength should be in the range [-1, 1]. It is multiplied with 65535 (the max color value), which is then multiplied with the matrix.
You can use this to change the amount the matrix is applied to the image, the "strength" of the dithering matrix. Usually just keeping it at 1.0 is fine.
The closer to zero stength is, the smaller the range of colors that will be dithered. Colors outside that range will just be quantized, and not have a Bayer matrix applied. To dither the entire color range, set it to 1.0.
Why would you want to shrink the dither range? Well Bayer matrixes are fundamentally biased to making the image brighter, increasing the value in each channel. This means that there might be darker parts that would be better off just quantized to the darkest color in your palette, instead of made lighter and dithered. By shrinking the dither range, you dither the colors that are more in the "middle", and let the darker and lighter ones just get quantized.
You might also want to reduce the strength to reduce noise in the image, as dithering doesn't produce smooth colored areas. Usually a value around 0.8 is good for this.
You can also make strength negative. If you know already that your image is dark, and so you don't want it to be made bright, then this is a better approach then shrinking the dither range. A negative strength flips the bias of the Bayer matrix, making it biased towards making images darker. To dither the entire color range but inverted, set strength to -1.0.
The closer to zero you get, the more similar the effect of the negative and positive strength become. This is because they are shrinking the dither range towards the same spot.
At Bayer sizes above 4x4, the brightness bias mostly disappears, and the difference between strength being -1.0 vs 1.0 is not really noticeable. Decreasing it below 1.0 or or above -1.0 will still shrink the dithering range, but instead of fixing some bias, it will just increase the contrast of the image.
Greater than 1 or less than -1 doesn't really make sense, so stay away from that range. It expands the range of the dithering outside the possible color range, so there won't be enough dithering patterns in the output image. The further from zero, the larger the range.
Going away from zero is similar to reducing contrast. If you go too far from zero, the whole image becomes gray.
RECOMMENDATIONS ¶
For grayscale output, I would recommend 1.0 for lighter images, or -1.0 for darker images. If you cannot know beforehand, you may want to decrease that value, to reduce the risk of making dark images really bright. Try staying between 0.5 and 1.0.
If you're using a Bayer size larger than 4x4, just using 1.0 for strength should be fine for most kinds of grayscale images.
Color images are different. The Bayer matrix's bias to brightness applies to each RGB channel, and so the color of the image can become quite distorted at 1.0 strength. Several sites I have seen recommend 0.64 strength (written as 256/4), and from my own testing this is often a good value for color images. Do not default to 1.0 for Bayer dithering of color images.
Of course, experiment for yourself. And let me know if I'm wrong!
func PixelMapperFromMatrix ¶
func PixelMapperFromMatrix(odm OrderedDitherMatrix, strength float32) PixelMapper
PixelMapperFromMatrix takes an OrderedDitherMatrix, and will return a PixelMapper. This is a simple way to make use of the clustered-dot matrices in this library, or to try out some matrix you found online.
Because a PixelMapper is returned, this can make the matrix usable in more situations than originally designed, like with color images and multi-color palettes.
See Bayer for a detailed explanation of strength. You can use this to change the amount the matrix is applied to the image, and to reduce noise. Usually you'll just want to set it to 1.0.
func RandomNoiseGrayscale ¶
func RandomNoiseGrayscale(min, max float32) PixelMapper
RandomNoiseGrayscale returns a PixelMapper that adds random noise to the color before returning. This is the simplest form of dithering.
Non-grayscale colors will be converted to grayscale before the noise is added.
You must call rand.Seed before calling using the PixelMapper, otherwise the output will be the same each time. A simple way to initialize rand.Seed is:
rand.Seed(time.Now().UnixNano())
The noise added to each channel will be randomly chosen from within the range of min (inclusive) and max (exclusive). To simplify things, you can consider valid color values to range from 0 to 1. This means if you wanted the noise to shift the color through 50% of the color space at most, the min and max would be -0.5 and 0.5.
Statistically, -0.5 and 0.5 are the best values for random dithering, as they evenly dither colors. Using values closer to zero (like -0.2 and 0.2) will effectively reduce the contrast of the image, and values further from zero (like -0.7 and 0.7) will increase the contrast.
Making the min and max different values, like using -0.2 and 0.5 will make the image brighter or darker. In that example, the image will become brighter, as the randomness is more likely to land on the positive side and increase the color value.
If the noise puts the channel value too high or too low it will be clamped, not wrapped. Basically, don't worry about the values of your min and max distorting the image in an unexpected way.
func RandomNoiseRGB ¶
func RandomNoiseRGB(minR, maxR, minG, maxG, minB, maxB float32) PixelMapper
RandomNoiseRGB is like RandomNoiseGrayscale but it adds randomness in the R, G, and B channels. It should not be used when you want a grayscale output image, ie when your palette is grayscale.
Most of the time you will want all the mins to be the same, and all the maxes to be the same.
See RandomNoiseGrayscale for more details about values and how this function works.
type SpecialDither ¶
type SpecialDither int
SpecialDither is used to represent dithering algorithms that require custom code, because they cannot be represented by a PixelMapper or error diffusion matrix.
There are currently no SpecialDither options, but they will be added in the future.