mirror of
https://github.com/makew0rld/didder.git
synced 2025-09-02 17:32:34 +02:00
@@ -3,7 +3,7 @@
|
|||||||
title: DIDDER
|
title: DIDDER
|
||||||
section: 1
|
section: 1
|
||||||
header: User Manual
|
header: User Manual
|
||||||
footer: didder v1.0.0-3-g2404e20
|
footer: didder v1.0.0-4-g7593701
|
||||||
date: May 09, 2021
|
date: May 09, 2021
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,6 +16,8 @@ didder — dither images
|
|||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
Dither images with a variety of algorithms and processing options.
|
Dither images with a variety of algorithms and processing options.
|
||||||
|
|
||||||
|
Images with transparency are supported, and their alpha channel is kept the way it was to begin with.
|
||||||
|
|
||||||
Mandatory global flags are **\--palette**, **\--in**, and **\--out**, all others are optional. Each command applies a dithering algorithm or set of algorithms to the input image(s).
|
Mandatory global flags are **\--palette**, **\--in**, and **\--out**, all others are optional. Each command applies a dithering algorithm or set of algorithms to the input image(s).
|
||||||
|
|
||||||
The most important parts of this manual are highlighted in the **TIPS** section, make sure you check it out!
|
The most important parts of this manual are highlighted in the **TIPS** section, make sure you check it out!
|
||||||
@@ -49,7 +51,7 @@ Here's an example of all color formats being used: **\--palette \'23,230,100 D24
|
|||||||
|
|
||||||
**-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**.
|
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.
|
||||||
|
|
||||||
The **\--recolor** flag exists because when palettes that are severely limited in terms of RGB spread are used, accurately representing the image colors with the desired palette is impossible. Instead of accuracy of color, the new goal is accuracy of luminance, or even just accuracy of contrast. For example, the original Nintendo Game Boy used a solely green palette: <https://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Game_Boy>. By setting **\--palette** to shades of gray and then **\--recolor**-ing to the desired shades of green, input images will be converted to grayscale automatically and then dithered in one dimension (gray), rather than trying to dither a color image (three dimensions, RGB) into a one dimensional green palette. This is similar to "hue shifting" or "colorizing" an image in image editing software.
|
The **\--recolor** flag exists because when palettes that are severely limited in terms of RGB spread are used, accurately representing the image colors with the desired palette is impossible. Instead of accuracy of color, the new goal is accuracy of luminance, or even just accuracy of contrast. For example, the original Nintendo Game Boy used a solely green palette: <https://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Game_Boy>. By setting **\--palette** to shades of gray and then **\--recolor**-ing to the desired shades of green, input images will be converted to grayscale automatically and then dithered in one dimension (gray), rather than trying to dither a color image (three dimensions, RGB) into a one dimensional green palette. This is similar to "hue shifting" or "colorizing" an image in image editing software.
|
||||||
|
|
||||||
|
13
didder.1
13
didder.1
@@ -1,6 +1,6 @@
|
|||||||
.\" Automatically generated by Pandoc 2.13
|
.\" Automatically generated by Pandoc 2.13
|
||||||
.\"
|
.\"
|
||||||
.TH "DIDDER" "1" "May 09, 2021" "didder v1.0.0-3-g2404e20" "User Manual"
|
.TH "DIDDER" "1" "May 09, 2021" "didder v1.0.0-4-g7593701" "User Manual"
|
||||||
.hy
|
.hy
|
||||||
.SH NAME
|
.SH NAME
|
||||||
.PP
|
.PP
|
||||||
@@ -13,6 +13,9 @@ didder \[em] dither images
|
|||||||
.PP
|
.PP
|
||||||
Dither images with a variety of algorithms and processing options.
|
Dither images with a variety of algorithms and processing options.
|
||||||
.PP
|
.PP
|
||||||
|
Images with transparency are supported, and their alpha channel is kept
|
||||||
|
the way it was to begin with.
|
||||||
|
.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
|
||||||
@@ -78,7 +81,13 @@ Here\[cq]s an example of all color formats being used: \f[B]--palette
|
|||||||
\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].
|
The argument syntax is the same as \f[B]--palette\f[R], with one
|
||||||
|
exception.
|
||||||
|
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
|
||||||
|
after dithering.
|
||||||
|
The values are not premultiplied, so set the RGB to the color you want
|
||||||
|
as you\[cq]d expect.
|
||||||
.RS
|
.RS
|
||||||
.PP
|
.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
|
||||||
|
@@ -15,6 +15,8 @@ didder — dither images
|
|||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
Dither images with a variety of algorithms and processing options.
|
Dither images with a variety of algorithms and processing options.
|
||||||
|
|
||||||
|
Images with transparency are supported, and their alpha channel is kept the way it was to begin with.
|
||||||
|
|
||||||
Mandatory global flags are **\--palette**, **\--in**, and **\--out**, all others are optional. Each command applies a dithering algorithm or set of algorithms to the input image(s).
|
Mandatory global flags are **\--palette**, **\--in**, and **\--out**, all others are optional. Each command applies a dithering algorithm or set of algorithms to the input image(s).
|
||||||
|
|
||||||
The most important parts of this manual are highlighted in the **TIPS** section, make sure you check it out!
|
The most important parts of this manual are highlighted in the **TIPS** section, make sure you check it out!
|
||||||
@@ -44,7 +46,7 @@ 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'**
|
||||||
|
|
||||||
**-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**.
|
: 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.
|
||||||
|
|
||||||
The **\--recolor** flag exists because when palettes that are severely limited in terms of RGB spread are used, accurately representing the image colors with the desired palette is impossible. Instead of accuracy of color, the new goal is accuracy of luminance, or even just accuracy of contrast. For example, the original Nintendo Game Boy used a solely green palette: <https://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Game_Boy>. By setting **\--palette** to shades of gray and then **\--recolor**-ing to the desired shades of green, input images will be converted to grayscale automatically and then dithered in one dimension (gray), rather than trying to dither a color image (three dimensions, RGB) into a one dimensional green palette. This is similar to "hue shifting" or "colorizing" an image in image editing software.
|
The **\--recolor** flag exists because when palettes that are severely limited in terms of RGB spread are used, accurately representing the image colors with the desired palette is impossible. Instead of accuracy of color, the new goal is accuracy of luminance, or even just accuracy of contrast. For example, the original Nintendo Game Boy used a solely green palette: <https://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Game_Boy>. By setting **\--palette** to shades of gray and then **\--recolor**-ing to the desired shades of green, input images will be converted to grayscale automatically and then dithered in one dimension (gray), rather than trying to dither a color image (three dimensions, RGB) into a one dimensional green palette. This is similar to "hue shifting" or "colorizing" an image in image editing software.
|
||||||
|
|
||||||
|
@@ -88,7 +88,7 @@ func parseArgs(args []string, splitRunes string) []string {
|
|||||||
return finalArgs
|
return finalArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func hexToColor(hex string) (color.RGBA, error) {
|
func hexToColor(hex string) (color.NRGBA, error) {
|
||||||
// Modified from https://github.com/lucasb-eyer/go-colorful/blob/v1.2.0/colors.go#L333
|
// Modified from https://github.com/lucasb-eyer/go-colorful/blob/v1.2.0/colors.go#L333
|
||||||
|
|
||||||
hex = strings.TrimPrefix(hex, "#")
|
hex = strings.TrimPrefix(hex, "#")
|
||||||
@@ -97,35 +97,50 @@ func hexToColor(hex string) (color.RGBA, error) {
|
|||||||
var r, g, b uint8
|
var r, g, b uint8
|
||||||
n, err := fmt.Sscanf(strings.ToLower(hex), format, &r, &g, &b)
|
n, err := fmt.Sscanf(strings.ToLower(hex), format, &r, &g, &b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return color.RGBA{}, err
|
return color.NRGBA{}, err
|
||||||
}
|
}
|
||||||
if n != 3 {
|
if n != 3 {
|
||||||
return color.RGBA{}, fmt.Errorf("%s is not a hex color", hex)
|
return color.NRGBA{}, fmt.Errorf("%s is not a hex color", hex)
|
||||||
}
|
}
|
||||||
return color.RGBA{r, g, b, 255}, nil
|
return color.NRGBA{r, g, b, 255}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rgbToColor(s string) (color.RGBA, error) {
|
func rgbToColor(s string) (color.NRGBA, error) {
|
||||||
format := "%d,%d,%d"
|
format := "%d,%d,%d"
|
||||||
var r, g, b uint8
|
var r, g, b uint8
|
||||||
n, err := fmt.Sscanf(s, format, &r, &g, &b)
|
n, err := fmt.Sscanf(s, format, &r, &g, &b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return color.RGBA{}, err
|
return color.NRGBA{}, err
|
||||||
}
|
}
|
||||||
if n != 3 {
|
if n != 3 {
|
||||||
return color.RGBA{}, fmt.Errorf("%s is not an RGB tuple", s)
|
return color.NRGBA{}, fmt.Errorf("%s is not an RGB tuple", s)
|
||||||
}
|
}
|
||||||
return color.RGBA{r, g, b, 255}, nil
|
return color.NRGBA{r, g, b, 255}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rgbaToColor(s string) (color.NRGBA, error) {
|
||||||
|
format := "%d,%d,%d,%d"
|
||||||
|
var r, g, b, a uint8
|
||||||
|
n, err := fmt.Sscanf(s, format, &r, &g, &b, &a)
|
||||||
|
if err != nil {
|
||||||
|
return color.NRGBA{}, err
|
||||||
|
}
|
||||||
|
if n != 4 {
|
||||||
|
return color.NRGBA{}, fmt.Errorf("%s is not an RGBA tuple", s)
|
||||||
|
}
|
||||||
|
// Parse as non-premult, as that's more user-friendly
|
||||||
|
return color.NRGBA{r, g, b, a}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseColors takes args and turns them into a color slice. All returned
|
// parseColors takes args and turns them into a color slice. All returned
|
||||||
// colors are guaranteed to only be color.RGBA.
|
// colors are guaranteed to only be color.NRGBA.
|
||||||
func parseColors(flag string, c *cli.Context) ([]color.Color, error) {
|
func parseColors(flag string, c *cli.Context) ([]color.Color, error) {
|
||||||
args := parseArgs([]string{globalFlag(flag, c).(string)}, " ")
|
args := parseArgs([]string{globalFlag(flag, c).(string)}, " ")
|
||||||
colors := make([]color.Color, len(args))
|
colors := make([]color.Color, len(args))
|
||||||
|
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
// Try to parse as RGB numbers, then hex, then grayscale, then SVG colors, then fail
|
// Try to parse as RGB numbers, then hex, then grayscale, then SVG colors, then fail
|
||||||
|
// Optionally try for RGBA if it's recolor, see #1
|
||||||
|
|
||||||
if strings.Count(arg, ",") == 2 {
|
if strings.Count(arg, ",") == 2 {
|
||||||
rgbColor, err := rgbToColor(arg)
|
rgbColor, err := rgbToColor(arg)
|
||||||
@@ -136,6 +151,15 @@ func parseColors(flag string, c *cli.Context) ([]color.Color, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flag == "recolor" && strings.Count(arg, ",") == 3 {
|
||||||
|
rgbaColor, err := rgbaToColor(arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %s is not a valid RGBA tuple. Example: 25,200,150,100", flag, arg)
|
||||||
|
}
|
||||||
|
colors[i] = rgbaColor
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
hexColor, err := hexToColor(arg)
|
hexColor, err := hexToColor(arg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
colors[i] = hexColor
|
colors[i] = hexColor
|
||||||
@@ -147,13 +171,13 @@ func parseColors(flag string, c *cli.Context) ([]color.Color, error) {
|
|||||||
if n > 255 || n < 0 {
|
if n > 255 || n < 0 {
|
||||||
return nil, fmt.Errorf("%s: single numbers like %d must be in the range 0-255", flag, n)
|
return nil, fmt.Errorf("%s: single numbers like %d must be in the range 0-255", flag, n)
|
||||||
}
|
}
|
||||||
colors[i] = color.RGBA{uint8(n), uint8(n), uint8(n), 255}
|
colors[i] = color.NRGBA{uint8(n), uint8(n), uint8(n), 255}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlColor, ok := colornames.Map[strings.ToLower(arg)]
|
htmlColor, ok := colornames.Map[strings.ToLower(arg)]
|
||||||
if ok {
|
if ok {
|
||||||
colors[i] = htmlColor
|
colors[i] = color.NRGBAModel.Convert(htmlColor).(color.NRGBA)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,10 +253,22 @@ func recolor(src image.Image) image.Image {
|
|||||||
// Modified and returned value
|
// Modified and returned value
|
||||||
var img draw.Image
|
var img draw.Image
|
||||||
|
|
||||||
// Map of original palette colors to recolor colors
|
// getRecolor takes an image color and returns the recolor one
|
||||||
paletteToRecolor := make(map[color.Color]color.Color)
|
getRecolor := func(a color.Color) color.Color {
|
||||||
for i, c := range palette {
|
// palette and recolorPalette are both NRGBA, so use that here too
|
||||||
paletteToRecolor[c] = recolorPalette[i]
|
c := color.NRGBAModel.Convert(a).(color.NRGBA)
|
||||||
|
|
||||||
|
for i := range palette {
|
||||||
|
pc := palette[i].(color.NRGBA)
|
||||||
|
if pc.R == c.R && pc.G == c.G && pc.B == c.B {
|
||||||
|
// Colors match. Alpha is ignored because palette colors aren't
|
||||||
|
// allowed alpha, so theirs will always be 255. While the image
|
||||||
|
// might have a different alpha at that point
|
||||||
|
return recolorPalette[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This should never happen
|
||||||
|
return recolorPalette[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast path for paletted images
|
// Fast path for paletted images
|
||||||
@@ -240,7 +276,7 @@ func recolor(src image.Image) image.Image {
|
|||||||
// For each color in the image palette, replace it with the equivalent
|
// For each color in the image palette, replace it with the equivalent
|
||||||
// recolor palette color
|
// recolor palette color
|
||||||
for i, c := range p.Palette {
|
for i, c := range p.Palette {
|
||||||
p.Palette[i] = paletteToRecolor[color.RGBAModel.Convert(c)]
|
p.Palette[i] = getRecolor(c)
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
@@ -259,7 +295,7 @@ func recolor(src image.Image) image.Image {
|
|||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
// Image pixel -> convert to RGBA -> find recolor palette color using map
|
// Image pixel -> convert to RGBA -> find recolor palette color using map
|
||||||
// -> set color
|
// -> set color
|
||||||
img.Set(x, y, paletteToRecolor[color.RGBAModel.Convert(img.At(x, y))])
|
img.Set(x, y, getRecolor(img.At(x, y)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return img
|
return img
|
||||||
|
@@ -26,11 +26,11 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// palette stores the palette colors. It's set after pre-processing.
|
// palette stores the palette colors. It's set after pre-processing.
|
||||||
// Guaranteed to only hold color.RGBA.
|
// Guaranteed to only hold color.NRGBA.
|
||||||
palette []color.Color
|
palette []color.Color
|
||||||
|
|
||||||
// recolorPalette stores the recolor palette colors. It's set after pre-processing.
|
// recolorPalette stores the recolor palette colors. It's set after pre-processing.
|
||||||
// Guaranteed to only hold color.RGBA.
|
// Guaranteed to only hold color.NRGBA.
|
||||||
recolorPalette []color.Color
|
recolorPalette []color.Color
|
||||||
|
|
||||||
grayscale bool
|
grayscale bool
|
||||||
|
Reference in New Issue
Block a user