Browse Source

Squash merge quantize into main

pull/30/head
makeworld 5 months ago
parent
commit
870ed78596
No known key found for this signature in database
GPG Key ID: 7DE9FA10A6B58CA5
  1. 4
      CHANGELOG.md
  2. 1
      README.md
  3. 322
      didder.1
  4. 2
      didder.1.md
  5. 1
      go.mod
  6. 2
      go.sum
  7. 38
      subcommand_helpers.go
  8. 84
      subcommands.go

4
CHANGELOG.md

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## Added
- Support for automatic palette choosing with `mmcq:N` (#2)
## [1.3.0] - 2022-12-20 ## [1.3.0] - 2022-12-20
## Changed ## Changed
- Updated dither library to v2.4.0 - Updated dither library to v2.4.0

1
README.md

@ -49,6 +49,7 @@ More methods of dithering are being worked on, such as Riemersma, Yuliluoma, and
- Combine multiple images into an animated GIF - Combine multiple images into an animated GIF
- Uses all CPU cores when possible - Uses all CPU cores when possible
- Support images with transparency (alpha channel is kept the same) - Support images with transparency (alpha channel is kept the same)
- Have the palette chosen for you based on colors in the image
## Installation ## Installation

322
didder.1

@ -1,8 +1,8 @@
.\" Automatically generated by Pandoc 3.1.9 .\" Automatically generated by Pandoc 3.8.2
.\" .\"
.TH "DIDDER" "1" "December 20, 2023" "didder v1.3.0" "User Manual" .TH "DIDDER" "1" "October 20, 2025" "didder v1.3.0-15-g566375b" "User Manual"
.SH NAME .SH NAME
didder \[em] dither images didder \(em dither images
.SH SYNOPSIS .SH SYNOPSIS
\f[B]didder\f[R] [global options] command [command options] \f[B]didder\f[R] [global options] command [command options]
[arguments\&...] [arguments\&...]
@ -12,8 +12,8 @@ Dither images with a variety of algorithms and processing options.
Images with transparency are supported, and their alpha channel is kept Images with transparency are supported, and their alpha channel is kept
the way it was to begin with. the way it was to begin with.
.PP .PP
Mandatory global flags are \f[B]--palette\f[R], \f[B]--in\f[R], and Mandatory global flags are \f[B]\-\-palette\f[R], \f[B]\-\-in\f[R], and
\f[B]--out\f[R], all others are optional. \f[B]\-\-out\f[R], all others are optional.
Each command applies a dithering algorithm or set of algorithms to the Each command applies a dithering algorithm or set of algorithms to the
input image(s). input image(s).
.PP .PP
@ -25,121 +25,113 @@ Homepage: \c
.UE \c .UE \c
.SH OPTIONS .SH OPTIONS
.TP .TP
\f[B]-i\f[R], \f[B]--in\f[R] \f[I]PATH\f[R] \f[B]\-i\f[R], \f[B]\-\-in\f[R] \f[I]PATH\f[R]
Set the input file. Set the input file.
This flag can be used multiple times to dither multiple images with the This flag can be used multiple times to dither multiple images with the
same palette and method. same palette and method.
A \f[I]PATH\f[R] of \[aq]\f[B]-\f[R]\[cq] stands for standard input. A \f[I]PATH\f[R] of \(aq\f[B]\-\f[R]\(cq stands for standard input.
.RS .RS
.PP
The input file path can also be parsed as a glob. The input file path can also be parsed as a glob.
This will only happen if the path contains an asterisk. This will only happen if the path contains an asterisk.
For example \f[B]-i \[aq]*.jpg\[cq]\f[R] will select all the .jpg files For example \f[B]\-i \(aq*.jpg\(cq\f[R] will select all the .jpg files
in the current directory as input. in the current directory as input.
See this page for more info on glob pattern matching: \c See this page for more info on glob pattern matching: \c
.UR https://golang.org/pkg/path/filepath/#Match .UR https://golang.org/pkg/path/filepath/#Match
.UE \c .UE \c
.RE .RE
.TP .TP
\f[B]-o\f[R], \f[B]--out\f[R] \f[I]PATH\f[R] \f[B]\-o\f[R], \f[B]\-\-out\f[R] \f[I]PATH\f[R]
Set the output file or directory. Set the output file or directory.
A \f[I]PATH\f[R] of \[aq]\f[B]-\f[R]\[cq] stands for standard output. A \f[I]PATH\f[R] of \(aq\f[B]\-\f[R]\(cq stands for standard output.
.RS .RS
.PP
If \f[I]PATH\f[R] is an existing directory, then for each image input, If \f[I]PATH\f[R] is an existing directory, then for each image input,
an output file with the same name (but possibly different extension) an output file with the same name (but possibly different extension)
will be created in that directory. will be created in that directory.
.PP If \f[I]PATH\f[R] is a file, that ends in .gif (or \f[B]\-\-format
If \f[I]PATH\f[R] is a file, that ends in .gif (or \f[B]--format
gif\f[R] is set) then multiple input files will be combined into an gif\f[R] is set) then multiple input files will be combined into an
animated GIF. animated GIF.
.RE .RE
.TP .TP
\f[B]-p\f[R], \f[B]--palette\f[R] \f[I]COLORS\f[R] \f[B]\-p\f[R], \f[B]\-\-palette\f[R] \f[I]COLORS\f[R]
Set the color palette used for dithering. Set the color palette used for dithering.
Colors are entered as a single quoted argument, with each color Colors are entered as a single quoted argument, with each color
separated by a space. separated by a space.
Colors can be formatted as RGB tuples (comma separated), hex codes Colors can be formatted as RGB tuples (comma separated), hex codes
(case-insensitive, with or without the `#'), a single number from 0-255 (case\-insensitive, with or without the `#'), a single number from
for grayscale, or a color name from the SVG 1.1 spec (aka the HTML or 0\-255 for grayscale, or a color name from the SVG 1.1 spec (aka the
W3C color names). HTML or W3C color names).
All colors are interpreted in the sRGB colorspace. All colors are interpreted in the sRGB colorspace.
.RS .RS
.PP
A list of all color names is available at \c A list of all color names is available at \c
.UR https://www.w3.org/TR/SVG11/types.html#ColorKeywords .UR https://www.w3.org/TR/SVG11/types.html#ColorKeywords
.UE \c .UE \c
.PP
Images are converted to grayscale automatically if the palette is Images are converted to grayscale automatically if the palette is
grayscale. grayscale.
This produces more correct results. This produces more correct results.
.PP Here\(cqs an example of all color formats being used: \f[B]\-\-palette
Here\[cq]s an example of all color formats being used: \f[B]--palette \(aq23,230,100 D24242 135 forestGreen\(cq\f[R]
\[aq]23,230,100 D24242 135 forestGreen\[cq]\f[R] Alternative, you can write mmcq:N in lieu of color names, where N is a
power of two.
This will use the median cut algorithm to pick the top N colors in the
image, and set those as the palette.
.RE .RE
.TP .TP
\f[B]-r\f[R], \f[B]--recolor\f[R] \f[I]COLORS\f[R] \f[B]\-r\f[R], \f[B]\-\-recolor\f[R] \f[I]COLORS\f[R]
Set the color palette used for replacing the dithered color palette Set the color palette used for replacing the dithered color palette
after dithering. after dithering.
The argument syntax is the same as \f[B]--palette\f[R], with one The argument syntax is the same as \f[B]\-\-palette\f[R], with one
exception. exception.
It also supports RGB\f[I]A\f[R] tuples, so 4 values. It also supports RGB\f[I]A\f[R] tuples, so 4 values.
This means you can also choose to change the opacity of a palette color This means you can also choose to change the opacity of a palette color
after dithering. after dithering.
The values are not premultiplied, so set the RGB to the color you want The values are not premultiplied, so set the RGB to the color you want
as you\[cq]d expect. as you\(cqd expect.
.RS .RS
.PP The \f[B]\-\-recolor\f[R] flag exists because when palettes that are
The \f[B]--recolor\f[R] flag exists because when palettes that are
severely limited in terms of RGB spread are used, accurately severely limited in terms of RGB spread are used, accurately
representing the image colors with the desired palette is impossible. representing the image colors with the desired palette is impossible.
Instead of accuracy of color, the new goal is accuracy of luminance, or Instead of accuracy of color, the new goal is accuracy of luminance, or
even just accuracy of contrast. even just accuracy of contrast.
For example, the original Nintendo Game Boy used a solely green palette: For example, the original Nintendo Game Boy used a solely green palette:
\c \c
.UR .UR https://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Game_Boy
https://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Game_Boy
.UE \c .UE \c
\&. \&.
By setting \f[B]--palette\f[R] to shades of gray and then By setting \f[B]\-\-palette\f[R] to shades of gray and then
\f[B]--recolor\f[R]-ing to the desired shades of green, input images \f[B]\-\-recolor\f[R]\-ing to the desired shades of green, input images
will be converted to grayscale automatically and then dithered in one will be converted to grayscale automatically and then dithered in one
dimension (gray), rather than trying to dither a color image (three dimension (gray), rather than trying to dither a color image (three
dimensions, RGB) into a one dimensional green palette. dimensions, RGB) into a one dimensional green palette.
This is similar to \[lq]hue shifting\[rq] or \[lq]colorizing\[rq] an This is similar to \(lqhue shifting\(rq or \(lqcolorizing\(rq an image
image in image editing software. in image editing software.
.PP For these situations, \f[B]\-\-recolor\f[R] should usually be a palette
For these situations, \f[B]--recolor\f[R] should usually be a palette made up of one hue, and \f[B]\-\-palette\f[R] should be the grayscale
made up of one hue, and \f[B]--palette\f[R] should be the grayscale
version of that palette. version of that palette.
The \f[B]--palette\f[R] could also be just equally spread grayscale The \f[B]\-\-palette\f[R] could also be just equally spread grayscale
values, which would increase the contrast but make the luminance values, which would increase the contrast but make the luminance
inaccurate. inaccurate.
.PP
Recoloring can also be useful for increasing contrast on a strange Recoloring can also be useful for increasing contrast on a strange
palette, like: \f[B]--palette \[aq]black white\[cq] --recolor palette, like: \f[B]\-\-palette \(aqblack white\(cq \-\-recolor
\[aq]indigo LimeGreen\[cq]\f[R]. \(aqindigo LimeGreen\(cq\f[R].
Setting just \f[B]--palette \[aq]indigo LimeGreen\[cq]\f[R] would give Setting just \f[B]\-\-palette \(aqindigo LimeGreen\(cq\f[R] would give
bad (low contrast) results because that palette is not that far apart in bad (low contrast) results because that palette is not that far apart in
RGB space. RGB space.
These \[lq]bad results\[rq] are much more pronounced when the input These \(lqbad results\(rq are much more pronounced when the input image
image is in color, because three dimensions are being reduced. is in color, because three dimensions are being reduced.
.RE .RE
.TP .TP
\f[B]-s\f[R], \f[B]--strength\f[R] \f[I]DECIMAL/PERCENT\f[R] \f[B]\-s\f[R], \f[B]\-\-strength\f[R] \f[I]DECIMAL/PERCENT\f[R]
Set the strength of dithering. Set the strength of dithering.
This will affect every command except \f[B]random\f[R]. This will affect every command except \f[B]random\f[R].
Decimal format is -1.0 to 1.0, and percentage format is -100% or 100%. Decimal format is \-1.0 to 1.0, and percentage format is \-100% or 100%.
The range is not limited. The range is not limited.
A zero value will be ignored. A zero value will be ignored.
Defaults to 100%, meaning that the dithering is applied at full Defaults to 100%, meaning that the dithering is applied at full
strength. strength.
.RS .RS
.PP
Reducing the strength is often visibly similar to reducing contrast. Reducing the strength is often visibly similar to reducing contrast.
With the \f[B]edm\f[R] command, \f[B]--strength\f[R] can be used to With the \f[B]edm\f[R] command, \f[B]\-\-strength\f[R] can be used to
reduce noise, when set to a value around 80%. reduce noise, when set to a value around 80%.
.PP
When using the \f[B]bayer\f[R] command with a grayscale palette, usually When using the \f[B]bayer\f[R] command with a grayscale palette, usually
100% is fine, but for 4x4 matrices or smaller, you may need to reduce 100% is fine, but for 4x4 matrices or smaller, you may need to reduce
the strength. the strength.
@ -150,60 +142,60 @@ color images, as 100% will distort colors too much.
Do not use the default of 100% for Bayer dithering color images. Do not use the default of 100% for Bayer dithering color images.
.RE .RE
.TP .TP
\f[B]-j\f[R], \f[B]--threads\f[R] \f[I]NUM\f[R] \f[B]\-j\f[R], \f[B]\-\-threads\f[R] \f[I]NUM\f[R]
Set the number of threads used. Set the number of threads used.
By default a thread will be created for each CPU. By default a thread will be created for each CPU.
As dithering is a CPU-bound operation, going above this will not improve As dithering is a CPU\-bound operation, going above this will not
performance. improve performance.
This flag does not affect \f[B]edm\f[R], as error diffusion dithering This flag does not affect \f[B]edm\f[R], as error diffusion dithering
cannot be parallelized. cannot be parallelized.
.TP .TP
\f[B]-g\f[R], \f[B]--grayscale\f[R] \f[B]\-g\f[R], \f[B]\-\-grayscale\f[R]
Make input image(s) grayscale before dithering. Make input image(s) grayscale before dithering.
.TP .TP
\f[B]--saturation\f[R] \f[I]DECIMAL/PERCENT\f[R] \f[B]\-\-saturation\f[R] \f[I]DECIMAL/PERCENT\f[R]
Change input image(s) saturation before dithering. Change input image(s) saturation before dithering.
Decimal range is -1.0 to 1.0, percentage range is -100% or 100%. Decimal range is \-1.0 to 1.0, percentage range is \-100% or 100%.
Values that exceed the range will be rounded down. Values that exceed the range will be rounded down.
-1.0 or -100% saturation is equivalent to \f[B]--grayscale\f[R]. \-1.0 or \-100% saturation is equivalent to \f[B]\-\-grayscale\f[R].
.TP .TP
\f[B]--brightness\f[R] \f[I]DECIMAL/PERCENT\f[R] \f[B]\-\-brightness\f[R] \f[I]DECIMAL/PERCENT\f[R]
Change input image(s) saturation before dithering. Change input image(s) saturation before dithering.
Decimal range is -1.0 to 1.0, percentage range is -100% or 100%. Decimal range is \-1.0 to 1.0, percentage range is \-100% or 100%.
Values that exceed the range will be rounded down. Values that exceed the range will be rounded down.
.TP .TP
\f[B]--contrast\f[R] \f[I]DECIMAL/PERCENT\f[R] \f[B]\-\-contrast\f[R] \f[I]DECIMAL/PERCENT\f[R]
Change input image(s) saturation before dithering. Change input image(s) saturation before dithering.
Decimal range is -1.0 to 1.0, percentage range is -100% or 100%. Decimal range is \-1.0 to 1.0, percentage range is \-100% or 100%.
Values that exceed the range will be rounded down. Values that exceed the range will be rounded down.
.TP .TP
\f[B]--no-exif-rotation\f[R] \f[B]\-\-no\-exif\-rotation\f[R]
Disable using the EXIF rotation flag in image metadata to rotate the Disable using the EXIF rotation flag in image metadata to rotate the
image before processing. image before processing.
.TP .TP
\f[B]-f\f[R], \f[B]--format\f[R] \f[I]FORMAT\f[R] \f[B]\-f\f[R], \f[B]\-\-format\f[R] \f[I]FORMAT\f[R]
Set the output file format. Set the output file format.
Valid options are \[aq]png\[cq] and \[aq]gif\[cq]. Valid options are \(aqpng\(cq and \(aqgif\(cq.
It will auto detect from filename when possible, so usually this does It will auto detect from filename when possible, so usually this does
not need to be set. not need to be set.
If \f[B]-o\f[R] is \[aq]\f[B]-\f[R]\[cq] or a directory, then PNG files If \f[B]\-o\f[R] is \(aq\f[B]\-\f[R]\(cq or a directory, then PNG files
will be outputted by default. will be outputted by default.
So this flag can be used to force GIF output instead. So this flag can be used to force GIF output instead.
If your output file has an extension that is not .png or .gif the format If your output file has an extension that is not .png or .gif the format
will need to be specified. will need to be specified.
.TP .TP
\f[B]--no-overwrite\f[R] \f[B]\-\-no\-overwrite\f[R]
Setting this flag means the program will stop before overwriting an Setting this flag means the program will stop before overwriting an
existing file. existing file.
Any files written before that one was encountered will stay in place. Any files written before that one was encountered will stay in place.
.TP .TP
\f[B]-c\f[R], \f[B]--compression\f[R] \f[I]TYPE\f[R] \f[B]\-c\f[R], \f[B]\-\-compression\f[R] \f[I]TYPE\f[R]
Set the type of PNG compression. Set the type of PNG compression.
Options are \[aq]default\[cq], \[aq]no\[cq], \[aq]speed\[cq], and Options are \(aqdefault\(cq, \(aqno\(cq, \(aqspeed\(cq, and
\[aq]size\[cq]. \(aqsize\(cq.
This flag is ignored for non-PNG output. This flag is ignored for non\-PNG output.
.TP .TP
\f[B]--fps\f[R] \f[I]DECIMAL\f[R] \f[B]\-\-fps\f[R] \f[I]DECIMAL\f[R]
Set frames per second for animated GIF output. Set frames per second for animated GIF output.
Note that not all FPS values can be represented by the GIF format, and Note that not all FPS values can be represented by the GIF format, and
so the closest possible one will be chosen. so the closest possible one will be chosen.
@ -211,49 +203,47 @@ This flag has no default, and is required when animated GIFs are being
outputted. outputted.
This flag is ignored for non animated GIF output. This flag is ignored for non animated GIF output.
.TP .TP
\f[B]-l\f[R], \f[B]--loop\f[R] \f[I]NUM\f[R] \f[B]\-l\f[R], \f[B]\-\-loop\f[R] \f[I]NUM\f[R]
Set the number of times animated GIF output should loop. Set the number of times animated GIF output should loop.
0 is the default, and will loop infinitely. 0 is the default, and will loop infinitely.
.TP .TP
\f[B]-x\f[R], \f[B]--width\f[R] \f[I]NUM\f[R] \f[B]\-x\f[R], \f[B]\-\-width\f[R] \f[I]NUM\f[R]
Set the width the input image(s) will be resized to, before dithering. Set the width the input image(s) will be resized to, before dithering.
Aspect ratio will be maintained if \f[B]--height\f[R] is not specified Aspect ratio will be maintained if \f[B]\-\-height\f[R] is not specified
as well. as well.
.TP .TP
\f[B]-y\f[R], \f[B]--height\f[R] \f[I]NUM\f[R] \f[B]\-y\f[R], \f[B]\-\-height\f[R] \f[I]NUM\f[R]
Set the height the input image(s) will be resized to, before dithering. Set the height the input image(s) will be resized to, before dithering.
Aspect ratio will be maintained if \f[B]--width\f[R] is not specified as Aspect ratio will be maintained if \f[B]\-\-width\f[R] is not specified
well. as well.
.TP .TP
\f[B]-u\f[R], \f[B]--upscale\f[R] \f[I]NUM\f[R] \f[B]\-u\f[R], \f[B]\-\-upscale\f[R] \f[I]NUM\f[R]
Scale image up after dithering. Scale image up after dithering.
So \[aq]2\[cq] will make the output two times as big as the input (after So \(aq2\(cq will make the output two times as big as the input (after
\f[B]-x\f[R] and/or \f[B]-y\f[R]). \f[B]\-x\f[R] and/or \f[B]\-y\f[R]).
Only integers are allowed, as scaling up by a non-integer amount would Only integers are allowed, as scaling up by a non\-integer amount would
distort the dithering pattern and introduce artifacts. distort the dithering pattern and introduce artifacts.
.TP .TP
\f[B]-v\f[R], \f[B]--version\f[R] \f[B]\-v\f[R], \f[B]\-\-version\f[R]
Get version information. Get version information.
.SH COMMANDS .SH COMMANDS
.TP .TP
\f[B]random\f[R] \f[I]MIN MAX\f[R] or \f[I]RED_MIN RED_MAX GREEN_MIN GREEN_MAX BLUE_MIN BLUE_MAX\f[R] \f[B]random\f[R] \f[I]MIN MAX\f[R] or \f[I]RED_MIN RED_MAX GREEN_MIN GREEN_MAX BLUE_MIN BLUE_MAX\f[R]
Grayscale and RGB random dithering Grayscale and RGB random dithering
.RS .RS
.PP
Accepts two arguments (min and max) for RGB or grayscale, or six Accepts two arguments (min and max) for RGB or grayscale, or six
(min/max for each channel) to control each RGB channel. (min/max for each channel) to control each RGB channel.
Arguments can be separated by commas or spaces. Arguments can be separated by commas or spaces.
.PP
Random dithering adds random noise to the image. Random dithering adds random noise to the image.
The min and max numbers limit the range of the random noise. The min and max numbers limit the range of the random noise.
A good default is -0.5,0.5, which means that a middle gray pixel is 50% A good default is \-0.5,0.5, which means that a middle gray pixel is 50%
likely to become black and 50% likely to become white, assuming a black likely to become black and 50% likely to become white, assuming a black
and white palette. and white palette.
So -0.2,2.0 will reduce the noise (20%), while -0.7,0.7 will increase it So \-0.2,0.2 will reduce the noise (20%), while \-0.7,0.7 will increase
(70%). it (70%).
Values like -0.5,0.7 will bias the noise to one end of the channel(s). Values like \-0.5,0.7 will bias the noise to one end of the channel(s).
.TP .TP
\f[B]-s\f[R], \f[B]--seed\f[R] \f[I]DECIMAL\f[R] \f[B]\-s\f[R], \f[B]\-\-seed\f[R] \f[I]DECIMAL\f[R]
Set the seed for randomization. Set the seed for randomization.
This will also only use one thread, to keep output deterministic. This will also only use one thread, to keep output deterministic.
By default a different seed is chosen each time and multiple threads are By default a different seed is chosen each time and multiple threads are
@ -263,9 +253,8 @@ used.
\f[B]bayer\f[R] \f[I]X\f[R] \f[I]Y\f[R] \f[B]bayer\f[R] \f[I]X\f[R] \f[I]Y\f[R]
Bayer matrix ordered dithering Bayer matrix ordered dithering
.RS .RS
.PP
Requires two arguments, for the X and Y dimension of the matrix. Requires two arguments, for the X and Y dimension of the matrix.
They can be separated by a space, comma, or \[aq]x\[cq]. They can be separated by a space, comma, or \(aqx\(cq.
Both arguments must be a power of two, with the exception of: 3x5, 5x3, Both arguments must be a power of two, with the exception of: 3x5, 5x3,
and 3x3. and 3x3.
.RE .RE
@ -273,233 +262,222 @@ and 3x3.
\f[B]odm\f[R] \f[I]NAME/JSON/FILE\f[R] \f[B]odm\f[R] \f[I]NAME/JSON/FILE\f[R]
Ordered Dithering Matrix Ordered Dithering Matrix
.RS .RS
.PP
Select or provide an ordered dithering matrix. Select or provide an ordered dithering matrix.
This only takes one argument, but there a few types available: This only takes one argument, but there a few types available:
.IP \[bu] 2 .IP \(bu 2
A preprogrammed matrix name A preprogrammed matrix name
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Inline JSON of a custom matrix Inline JSON of a custom matrix
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Or a path to JSON for your custom matrix. Or a path to JSON for your custom matrix.
\[aq]\f[B]-\f[R]\[cq] means standard input. \(aq\f[B]\-\f[R]\(cq means standard input.
.PP Here are all the built\-in ordered dithering matrices.
Here are all the built-in ordered dithering matrices.
You can find details on these matrices here: \c You can find details on these matrices here: \c
.UR .UR https://github.com/makeworld-the-better-one/dither/blob/v2.0.0/ordered_ditherers.go
https://github.com/makeworld-the-better-one/dither/blob/v2.0.0/ordered_ditherers.go
.UE \c .UE \c
.IP \[bu] 2 .IP \(bu 2
ClusteredDot4x4 ClusteredDot4x4
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDotDiagonal8x8 ClusteredDotDiagonal8x8
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Vertical5x3 Vertical5x3
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Horizontal3x5 Horizontal3x5
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDotDiagonal6x6 ClusteredDotDiagonal6x6
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDotDiagonal8x8_2 ClusteredDotDiagonal8x8_2
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDotDiagonal16x16 ClusteredDotDiagonal16x16
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDot6x6 ClusteredDot6x6
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDotSpiral5x5 ClusteredDotSpiral5x5
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDotHorizontalLine ClusteredDotHorizontalLine
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDotVerticalLine ClusteredDotVerticalLine
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDot8x8 ClusteredDot8x8
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDot6x6_2 ClusteredDot6x6_2
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDot6x6_3 ClusteredDot6x6_3
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
ClusteredDotDiagonal8x8_3 ClusteredDotDiagonal8x8_3
.PP Their names are case\-insensitive, and hyphens and underscores are
Their names are case-insensitive, and hyphens and underscores are
treated the same. treated the same.
.PP
The JSON format (whether inline or in a file) looks like the below. The JSON format (whether inline or in a file) looks like the below.
The matrix must be \[lq]rectangular\[rq], meaning each array must have The matrix must be \(lqrectangular\(rq, meaning each array must have the
the same length. same length.
More information how to use a custom matrix can be found here: \c More information how to use a custom matrix can be found here: \c
.UR .UR https://pkg.go.dev/github.com/makeworld-the-better-one/dither/v2#OrderedDitherMatrix
https://pkg.go.dev/github.com/makeworld-the-better-one/dither/v2#OrderedDitherMatrix
.UE \c .UE \c
.RE .RE
.IP .IP
.EX .EX
{ {
\[dq]matrix\[dq]: [ \(dqmatrix\(dq: [
[12, 5, 6, 13], [12, 5, 6, 13],
[4, 0, 1, 7], [4, 0, 1, 7],
[11, 3, 2, 8], [11, 3, 2, 8],
[15, 10, 9, 14] [15, 10, 9, 14]
], ],
\[dq]max\[dq]: 16 \(dqmax\(dq: 16
} }
.EE .EE
.TP .TP
\f[B]edm\f[R] \f[I]NAME/JSON/FILE\f[R] \f[B]edm\f[R] \f[I]NAME/JSON/FILE\f[R]
Error Diffusion Matrix Error Diffusion Matrix
.RS .RS
.PP
Select or provide an error diffusion matrix. Select or provide an error diffusion matrix.
This only takes one argument, but there a few types available: This only takes one argument, but there a few types available:
.IP \[bu] 2 .IP \(bu 2
A preprogrammed matrix name A preprogrammed matrix name
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Inline JSON of a custom matrix Inline JSON of a custom matrix
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Or a path to JSON for your custom matrix. Or a path to JSON for your custom matrix.
\[aq]\f[B]-\f[R]\[cq] means stdin. \(aq\f[B]\-\f[R]\(cq means stdin.
.PP Here are all the built\-in error diffusion matrices.
Here are all the built-in error diffusion matrices.
You can find details on these matrices here: \c You can find details on these matrices here: \c
.UR .UR https://github.com/makeworld-the-better-one/dither/blob/v2.0.0/error_diffusers.go
https://github.com/makeworld-the-better-one/dither/blob/v2.0.0/error_diffusers.go
.UE \c .UE \c
.IP \[bu] 2 .IP \(bu 2
Simple2D Simple2D
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
FloydSteinberg FloydSteinberg
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
FalseFloydSteinberg FalseFloydSteinberg
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
JarvisJudiceNinke JarvisJudiceNinke
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Atkinson Atkinson
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Stucki Stucki
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Burkes Burkes
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
Sierra (or Sierra3) Sierra (or Sierra3)
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
TwoRowSierra (or Sierra2) TwoRowSierra (or Sierra2)
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
SierraLite (or Sierra2_4A) SierraLite (or Sierra2_4A)
.PD 0 .PD 0
.P .P
.PD .PD
.IP \[bu] 2 .IP \(bu 2
StevenPigeon StevenPigeon
.PP Their names are case\-insensitive, and hyphens and underscores are
Their names are case-insensitive, and hyphens and underscores are
treated the same. treated the same.
.PP
The JSON format (whether inline or in a file) for a custom matrix is The JSON format (whether inline or in a file) for a custom matrix is
very simple, just a 2D array. very simple, just a 2D array.
The matrix must be \[lq]rectangular\[rq], meaning each array must have The matrix must be \(lqrectangular\(rq, meaning each array must have the
the same length. same length.
.TP .TP
\f[B]-s\f[R], \f[B]--serpentine\f[R] \f[B]\-s\f[R], \f[B]\-\-serpentine\f[R]
Enable serpentine dithering, which \[lq]snakes\[rq] back and forth when Enable serpentine dithering, which \(lqsnakes\(rq back and forth when
moving down the image, instead of going left-to-right each time. moving down the image, instead of going left\-to\-right each time.
This can reduce artifacts or patterns in the noise. This can reduce artifacts or patterns in the noise.
.RE .RE
.SH TIPS .SH TIPS
Read about \f[B]--strength\f[R] if you haven\[cq]t already. Read about \f[B]\-\-strength\f[R] if you haven\(cqt already.
.PP .PP
Read about \f[B]--recolor\f[R] if you haven\[cq]t already. Read about \f[B]\-\-recolor\f[R] if you haven\(cqt already.
.PP .PP
It\[cq]s easy to mess up a dithered image by scaling it manually. It\(cqs easy to mess up a dithered image by scaling it manually.
It\[cq]s best to scale the image to the size you want before dithering It\(cqs best to scale the image to the size you want before dithering
(externally, or with \f[B]--width\f[R] and/or \f[B]--height\f[R]), and (externally, or with \f[B]\-\-width\f[R] and/or \f[B]\-\-height\f[R]),
then leave it. and then leave it.
.PP .PP
If you need to scale it up afterward, use \f[B]--upscale\f[R], rather If you need to scale it up afterward, use \f[B]\-\-upscale\f[R], rather
than another tool. than another tool.
This will prevent image artifacts and blurring. This will prevent image artifacts and blurring.
.PP .PP
Be wary of environments where you can\[cq]t make sure an image will be Be wary of environments where you can\(cqt make sure an image will be
displayed at 100% size, pixel for pixel. displayed at 100% size, pixel for pixel.
Make sure to at least use nearest-neighbor scaling, do your best to Make sure to at least use nearest\-neighbor scaling, do your best to
preserve sharp pixel edges. preserve sharp pixel edges.
.PP .PP
Dithered images must only be encoded in a lossless image format. Dithered images must only be encoded in a lossless image format.
@ -508,31 +486,31 @@ This is why the tool only outputs PNG and GIF.
To increase the dithering artifacts for aesthetic effect, you can To increase the dithering artifacts for aesthetic effect, you can
downscale the image before dithering and upscale after. downscale the image before dithering and upscale after.
Like if the image is 1000 pixels tall, your command can look like Like if the image is 1000 pixels tall, your command can look like
\f[B]didder \[en]height 500 \[en]upscale 2 [\&...]\f[R]. \f[B]didder \(enheight 500 \(enupscale 2 [\&...]\f[R].
Depending on the input image size and what final size you want, you can Depending on the input image size and what final size you want, you can
of course just upscale as well. of course just upscale as well.
.PP .PP
If your palette (original or recolor) is low-spread \[em] meaning it If your palette (original or recolor) is low\-spread \(em meaning it
doesn\[cq]t span much of the available shades of a single hue or the doesn\(cqt span much of the available shades of a single hue or the
entire RGB space \[em] you can use flags like \f[B]--brightness\f[R], entire RGB space \(em you can use flags like \f[B]\-\-brightness\f[R],
\f[B]--contrast\f[R], and \f[B]--saturation\f[R] to improve the way \f[B]\-\-contrast\f[R], and \f[B]\-\-saturation\f[R] to improve the way
dithered images turn out. dithered images turn out.
For example, if your palette is dark, you can turn up the brightness. For example, if your palette is dark, you can turn up the brightness.
As mentioned above, these flags apply their transformations to the As mentioned above, these flags apply their transformations to the
original image and will not adjust your selected palette colors. original image and will not adjust your selected palette colors.
.SH EXAMPLES .SH EXAMPLES
.TP .TP
\f[B]didder --palette \[dq]black white\[rq] -i input.jpg -o test.png bayer 16x16\f[R] \f[B]didder \-\-palette \(dqblack white\(rq \-i input.jpg \-o test.png bayer 16x16\f[R]
This command dithers \f[CR]input.jpg\f[R] using only black and white This command dithers \f[CR]input.jpg\f[R] using only black and white
(implicitly converting the image to grayscale first), using a 16x16 (implicitly converting the image to grayscale first), using a 16x16
Bayer matrix. Bayer matrix.
The result is written to \f[CR]test.png\f[R]. The result is written to \f[CR]test.png\f[R].
.TP .TP
\f[B]didder --palette \[dq]black white\[rq] -i input.jpg -o test.png odm ClusteredDot4x4\f[R] \f[B]didder \-\-palette \(dqblack white\(rq \-i input.jpg \-o test.png odm ClusteredDot4x4\f[R]
Same command as above, but dithering with the preprogrammed ordered Same command as above, but dithering with the preprogrammed ordered
dithering matrix called ClusteredDot4x4. dithering matrix called ClusteredDot4x4.
.TP .TP
\f[B]didder -i david.png -o david_dithered.png --palette \[dq]black white\[rq] --recolor \[dq]black F273FF\[rq] --upscale 2 bayer 4x4\f[R] \f[B]didder \-i david.png \-o david_dithered.png \-\-palette \(dqblack white\(rq \-\-recolor \(dqblack F273FF\(rq \-\-upscale 2 bayer 4x4\f[R]
This is the command used for the README. This is the command used for the README.
It dithers using a 4x4 Bayer matrix, initially to black and white, which It dithers using a 4x4 Bayer matrix, initially to black and white, which
is then recolored to black and purple. is then recolored to black and purple.
@ -541,7 +519,7 @@ results.
The dithered image is upscaled to be two times larger, so that the Bayer The dithered image is upscaled to be two times larger, so that the Bayer
dithering artifacts can be seen more clearly. dithering artifacts can be seen more clearly.
.TP .TP
\f[B]didder -i input.png -o output.png -p \[dq]1E1E1E CDCDCD EDEDED FFFFFF\[rq] -r \[dq]11161e 116bcd 63b3ed e1efff\[rq] --strength 64% --brightness 20% bayer 32x32\f[R] \f[B]didder \-i input.png \-o output.png \-p \(dq1E1E1E CDCDCD EDEDED FFFFFF\(rq \-r \(dq11161e 116bcd 63b3ed e1efff\(rq \-\-strength 64% \-\-brightness 20% bayer 32x32\f[R]
This command uses a blue recolor palette, one that is biased to being This command uses a blue recolor palette, one that is biased to being
darker. darker.
The palette can be viewed at \c The palette can be viewed at \c
@ -552,14 +530,14 @@ The dithering palette is the grayscale version of those colors, to keep
luminance accurate. luminance accurate.
Strength is set to 64%, which although usually recommended for Bayer Strength is set to 64%, which although usually recommended for Bayer
dithering of color images, works well here. dithering of color images, works well here.
Alternatively, one could try and increase \f[B]--contrast\f[R]. Alternatively, one could try and increase \f[B]\-\-contrast\f[R].
Finally, the brightness is increased to compensate for the dark palette. Finally, the brightness is increased to compensate for the dark palette.
.TP .TP
\f[B]didder -p \[dq]black white\[rq] --recolor \[dq]darkgreen white\[rq] -i frame_01.png -i frame_02.png -o output.gif --fps 1 random -0.5,0.5\f[R] \f[B]didder \-p \(dqblack white\(rq \-\-recolor \(dqdarkgreen white\(rq \-i frame_01.png \-i frame_02.png \-o output.gif \-\-fps 1 random \-0.5,0.5\f[R]
This command takes two input images and creates an animated GIF, This command takes two input images and creates an animated GIF,
dithering and recoloring them along the way. dithering and recoloring them along the way.
The GIF moves at 1 frame per second, and by default loops infinitely. The GIF moves at 1 frame per second, and by default loops infinitely.
Random dithering is used, with recommended default of -0.5,0.5. Random dithering is used, with recommended default of \-0.5,0.5.
.SH REPORTING BUGS .SH REPORTING BUGS
Any bugs can be reported by creating an issue on GitHub: \c Any bugs can be reported by creating an issue on GitHub: \c
.UR https://github.com/makeworld-the-better-one/didder .UR https://github.com/makeworld-the-better-one/didder

2
didder.1.md

@ -45,6 +45,8 @@ Homepage: <https://github.com/makeworld-the-better-one/didder>
Here's an example of all color formats being used: **\--palette \'23,230,100 D24242 135 forestGreen'** Here's an example of all color formats being used: **\--palette \'23,230,100 D24242 135 forestGreen'**
Alternative, you can write mmcq:N in lieu of color names, where N is a power of two. This will use the median cut algorithm to pick the top N colors in the image, and set those as the palette.
**-r**, **\--recolor** *COLORS* **-r**, **\--recolor** *COLORS*
: Set the color palette used for replacing the dithered color palette after dithering. The argument syntax is the same as **\--palette**, with one exception. It also supports RGB*A* tuples, so 4 values. This means you can also choose to change the opacity of a palette color after dithering. The values are not premultiplied, so set the RGB to the color you want as you'd expect. : Set the color palette used for replacing the dithered color palette after dithering. The argument syntax is the same as **\--palette**, with one exception. It also supports RGB*A* tuples, so 4 values. This means you can also choose to change the opacity of a palette color after dithering. The values are not premultiplied, so set the RGB to the color you want as you'd expect.

1
go.mod

@ -4,6 +4,7 @@ go 1.16
require ( require (
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/joshdk/quantize v0.0.0-20171110221748-65999d3a4c76
github.com/makeworld-the-better-one/dither/v2 v2.4.0 github.com/makeworld-the-better-one/dither/v2 v2.4.0
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb

2
go.sum

@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/joshdk/quantize v0.0.0-20171110221748-65999d3a4c76 h1:e8dYMoMgTRUSrG/uOHRS0Bwj82/7DQemLuw7GS+PsKw=
github.com/joshdk/quantize v0.0.0-20171110221748-65999d3a4c76/go.mod h1:zrvWZKXVf8MDtGvHkoK00CJ62mZtdU2oUTr3xBlxa+s=
github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE= github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE=
github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc= github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

38
subcommand_helpers.go

@ -10,12 +10,14 @@ import (
"image/png" "image/png"
"io" "io"
"math" "math"
"math/bits"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/joshdk/quantize"
"github.com/makeworld-the-better-one/dither/v2" "github.com/makeworld-the-better-one/dither/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/image/colornames" "golang.org/x/image/colornames"
@ -49,7 +51,9 @@ func parsePercentArg(arg string, maxOne bool) (float64, error) {
// globalFlag returns the value of flag at the top level of the command. // globalFlag returns the value of flag at the top level of the command.
// For example, with the command: // For example, with the command:
// dither --threads 1 edm -s Simple2D //
// dither --threads 1 edm -s Simple2D
//
// "threads" is a global flag, and "s" is a flag local to the edm subcommand. // "threads" is a global flag, and "s" is a flag local to the edm subcommand.
func globalFlag(flag string, c *cli.Context) interface{} { func globalFlag(flag string, c *cli.Context) interface{} {
ancestor := c.Lineage()[len(c.Lineage())-1] ancestor := c.Lineage()[len(c.Lineage())-1]
@ -334,7 +338,7 @@ func postProcImage(img image.Image) image.Image {
// processImages dithers all the input images and writes them. // processImages dithers all the input images and writes them.
// It handles all image I/O. // It handles all image I/O.
func processImages(d *dither.Ditherer, c *cli.Context) error { func processImages(ditherConfig *dither.Ditherer, c *cli.Context) error {
outPath := globalFlag("out", c).(string) outPath := globalFlag("out", c).(string)
// Setup for if it's an animated GIF output // Setup for if it's an animated GIF output
@ -390,6 +394,7 @@ func processImages(d *dither.Ditherer, c *cli.Context) error {
if err != nil { if err != nil {
return fmt.Errorf("error loading '%s': %w", inputPath, err) return fmt.Errorf("error loading '%s': %w", inputPath, err)
} }
d := getDitherer(ditherConfig, img)
if isAnimGIF { if isAnimGIF {
if i == 0 { if i == 0 {
@ -547,3 +552,32 @@ func processImages(d *dither.Ditherer, c *cli.Context) error {
file.Close() file.Close()
return nil return nil
} }
// getDitherer returns the ditherer for an image, accounting for it possibly missing a palette
// and needing one generated via MMCQ.
func getDitherer(d *dither.Ditherer, img image.Image) *dither.Ditherer {
if mmcqNum == 0 {
// Assume ditherer is set up correctly
return d
}
// Get palette
// mmcq number is palette size, get log2 of that to get number of "levels" or cuts
paletteRGBA := quantize.Image(img, bits.Len(uint(mmcqNum))-1)
// Convert to NRGBA and store in global var
// This allows use by recolor if necessary
palette = make([]color.Color, len(paletteRGBA))
for i, c := range paletteRGBA {
palette[i] = color.NRGBAModel.Convert(c)
}
// Create proper ditherer
d2 := dither.NewDitherer(palette)
// Copy properties
d2.Matrix = d.Matrix
d2.Mapper = d.Mapper
d2.Special = d.Special
d2.SingleThreaded = d.SingleThreaded
d2.Serpentine = d.Serpentine
return d2
}

84
subcommands.go

@ -32,6 +32,9 @@ var (
// Guaranteed to only hold color.NRGBA. // Guaranteed to only hold color.NRGBA.
recolorPalette []color.Color recolorPalette []color.Color
// mmcqNum is set >0 if the user asked for color quantization.
mmcqNum int
grayscale bool grayscale bool
// Range -100,100 // Range -100,100
@ -55,7 +58,9 @@ var (
// upscale will always be 1 or above // upscale will always be 1 or above
upscale int upscale int
ditherer *dither.Ditherer // Options are set first, then copied into another ditherer with a palette set
// This allows the palette to change per-image, useful for MMCQ
ditherer = &dither.Ditherer{}
// range [-1, 1] // range [-1, 1]
strength float32 strength float32
@ -70,36 +75,64 @@ func preProcess(c *cli.Context) error {
runtime.GOMAXPROCS(int(c.Uint("threads"))) runtime.GOMAXPROCS(int(c.Uint("threads")))
var err error var err error
palette, err = parseColors("palette", c) paletteStr := globalFlag("palette", c).(string)
if err != nil { if strings.HasPrefix(paletteStr, "mmcq:") {
return err // Built-in quantization algorithm rather than list of colors
} mmcqNum, err = strconv.Atoi(paletteStr[5:])
if len(palette) < 2 { if err != nil || mmcqNum < 2 {
return errors.New("the palette must have at least two colors") return errors.New("power of two number must be after 'mmcq:'")
}
if (mmcqNum & (mmcqNum - 1)) != 0 {
return errors.New("mmcq number must be a power of two")
}
} }
if c.String("recolor") != "" { if mmcqNum == 0 {
recolorPalette, err = parseColors("recolor", c) palette, err = parseColors("palette", c)
if err != nil { if err != nil {
return err return err
} }
if len(recolorPalette) != len(palette) { if len(palette) < 2 {
return errors.New("recolor palette must have the same number of colors as the initial palette") return errors.New("the palette must have at least two colors")
} }
}
// Check if palette is grayscale and make image grayscale if c.String("recolor") != "" {
// Or if the user forces it recolorPalette, err = parseColors("recolor", c)
if err != nil {
return err
}
if len(recolorPalette) != len(palette) {
return errors.New("recolor palette must have the same number of colors as the initial palette")
}
}
grayscale = true // Check if palette is grayscale and make image grayscale
if !c.Bool("grayscale") { // Or if the user forces it
// Grayscale isn't specified by the user
// So check to see if palette is grayscale grayscale = true
for _, c := range palette { if !c.Bool("grayscale") {
r, g, b, _ := c.RGBA() // Grayscale isn't specified by the user
if r != g || g != b { // So check to see if palette is grayscale
grayscale = false for _, c := range palette {
break r, g, b, _ := c.RGBA()
if r != g || g != b {
grayscale = false
break
}
}
}
} else {
// For now just maintain the user's choice
// MMCQ is not going to be grayscale unless the image is grayscale anyway most likely
grayscale = c.Bool("grayscale")
if c.String("recolor") != "" {
recolorPalette, err = parseColors("recolor", c)
if err != nil {
return err
}
if len(recolorPalette) != mmcqNum {
return errors.New("recolor palette must have the same number of colors as the initial palette")
} }
} }
} }
@ -226,7 +259,10 @@ func preProcess(c *cli.Context) error {
upscale = 1 upscale = 1
} }
ditherer = dither.NewDitherer(palette) if mmcqNum == 0 {
// Use single palette for every image
ditherer = dither.NewDitherer(palette)
}
tmp, err := parsePercentArg(c.String("strength"), true) tmp, err := parsePercentArg(c.String("strength"), true)
if err != nil { if err != nil {

Loading…
Cancel
Save