You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
571 lines
18 KiB
571 lines
18 KiB
.\" Automatically generated by Pandoc 2.18 |
|
.\" |
|
.\" Define V font for inline verbatim, using C font in formats |
|
.\" that render this, and otherwise B font. |
|
.ie "\f[CB]x\f[]"x" \{\ |
|
. ftr V B |
|
. ftr VI BI |
|
. ftr VB B |
|
. ftr VBI BI |
|
.\} |
|
.el \{\ |
|
. ftr V CR |
|
. ftr VI CI |
|
. ftr VB CB |
|
. ftr VBI CBI |
|
.\} |
|
.TH "DIDDER" "1" "May 24, 2022" "didder v1.1.0" "User Manual" |
|
.hy |
|
.SH NAME |
|
.PP |
|
didder \[em] dither images |
|
.SH SYNOPSIS |
|
.PP |
|
\f[B]didder\f[R] [global options] command [command options] |
|
[arguments\&...] |
|
.SH DESCRIPTION |
|
.PP |
|
Dither images with a variety of algorithms and processing options. |
|
.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 |
|
\f[B]--out\f[R], all others are optional. |
|
Each command applies a dithering algorithm or set of algorithms to the |
|
input image(s). |
|
.PP |
|
The most important parts of this manual are highlighted in the |
|
\f[B]TIPS\f[R] section, make sure you check it out! |
|
.PP |
|
Homepage: <https://github.com/makeworld-the-better-one/didder> |
|
.SH OPTIONS |
|
.TP |
|
\f[B]-i\f[R], \f[B]--in\f[R] \f[I]PATH\f[R] |
|
Set the input file. |
|
This flag can be used multiple times to dither multiple images with the |
|
same palette and method. |
|
A \f[I]PATH\f[R] of \[aq]\f[B]-\f[R]\[cq] stands for standard input. |
|
.RS |
|
.PP |
|
The input file path can also be parsed as a glob. |
|
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 |
|
in the current directory as input. |
|
See this page for more info on glob pattern matching: |
|
<https://golang.org/pkg/path/filepath/#Match> |
|
.RE |
|
.TP |
|
\f[B]-o\f[R], \f[B]--out\f[R] \f[I]PATH\f[R] |
|
Set the output file or directory. |
|
A \f[I]PATH\f[R] of \[aq]\f[B]-\f[R]\[cq] stands for standard output. |
|
.RS |
|
.PP |
|
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) |
|
will be created in that directory. |
|
.PP |
|
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 |
|
animated GIF. |
|
.RE |
|
.TP |
|
\f[B]-p\f[R], \f[B]--palette\f[R] \f[I]COLORS\f[R] |
|
Set the color palette used for dithering. |
|
Colors are entered as a single quoted argument, with each color |
|
separated by a space. |
|
Colors can be formatted as RGB tuples (comma separated), hex codes |
|
(case-insensitive, with or without the `#'), a single number from 0-255 |
|
for grayscale, or a color name from the SVG 1.1 spec (aka the HTML or |
|
W3C color names). |
|
All colors are interpreted in the sRGB colorspace. |
|
.RS |
|
.PP |
|
A list of all color names is available at |
|
<https://www.w3.org/TR/SVG11/types.html#ColorKeywords> |
|
.PP |
|
Images are converted to grayscale automatically if the palette is |
|
grayscale. |
|
This produces more correct results. |
|
.PP |
|
Here\[cq]s an example of all color formats being used: \f[B]--palette |
|
\[aq]23,230,100 D24242 135 forestGreen\[cq]\f[R] |
|
.RE |
|
.TP |
|
\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 |
|
after dithering. |
|
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 |
|
.PP |
|
The \f[B]--recolor\f[R] 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 \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 |
|
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 \[lq]hue shifting\[rq] or \[lq]colorizing\[rq] an |
|
image in image editing software. |
|
.PP |
|
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 |
|
version of that palette. |
|
The \f[B]--palette\f[R] could also be just equally spread grayscale |
|
values, which would increase the contrast but make the luminance |
|
inaccurate. |
|
.PP |
|
Recoloring can also be useful for increasing contrast on a strange |
|
palette, like: \f[B]--palette \[aq]black white\[cq] --recolor |
|
\[aq]indigo LimeGreen\[cq]\f[R]. |
|
Setting just \f[B]--palette \[aq]indigo LimeGreen\[cq]\f[R] would give |
|
bad (low contrast) results because that palette is not that far apart in |
|
RGB space. |
|
These \[lq]bad results\[rq] are much more pronounced when the input |
|
image is in color, because three dimensions are being reduced. |
|
.RE |
|
.TP |
|
\f[B]-s\f[R], \f[B]--strength\f[R] \f[I]DECIMAL/PERCENT\f[R] |
|
Set the strength of dithering. |
|
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%. |
|
The range is not limited. |
|
A zero value will be ignored. |
|
Defaults to 100%, meaning that the dithering is applied at full |
|
strength. |
|
.RS |
|
.PP |
|
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 |
|
reduce noise, when set to a value around 80%. |
|
.PP |
|
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 |
|
the strength. |
|
For \f[B]bayer\f[R] (and by extension \f[B]odm\f[R]) color palette |
|
images, several sites recommend 64% strength (written as 256/4). |
|
This is often a good default for \f[B]bayer\f[R]/\f[B]odm\f[R] dithering |
|
color images, as 100% will distort colors too much. |
|
Do not use the default of 100% for Bayer dithering color images. |
|
.RE |
|
.TP |
|
\f[B]-j\f[R], \f[B]--threads\f[R] \f[I]NUM\f[R] |
|
Set the number of threads used. |
|
By default a thread will be created for each CPU. |
|
As dithering is a CPU-bound operation, going above this will not improve |
|
performance. |
|
This flag does not affect \f[B]edm\f[R], as error diffusion dithering |
|
cannot be parallelized. |
|
.TP |
|
\f[B]-g\f[R], \f[B]--grayscale\f[R] |
|
Make input image(s) grayscale before dithering. |
|
.TP |
|
\f[B]--saturation\f[R] \f[I]DECIMAL/PERCENT\f[R] |
|
Change input image(s) saturation before dithering. |
|
Decimal range is -1.0 to 1.0, percentage range is -100% or 100%. |
|
Values that exceed the range will be rounded down. |
|
-1.0 or -100% saturation is equivalent to \f[B]--grayscale\f[R]. |
|
.TP |
|
\f[B]--brightness\f[R] \f[I]DECIMAL/PERCENT\f[R] |
|
Change input image(s) saturation before dithering. |
|
Decimal range is -1.0 to 1.0, percentage range is -100% or 100%. |
|
Values that exceed the range will be rounded down. |
|
.TP |
|
\f[B]--contrast\f[R] \f[I]DECIMAL/PERCENT\f[R] |
|
Change input image(s) saturation before dithering. |
|
Decimal range is -1.0 to 1.0, percentage range is -100% or 100%. |
|
Values that exceed the range will be rounded down. |
|
.TP |
|
\f[B]--no-exif-rotation\f[R] |
|
Disable using the EXIF rotation flag in image metadata to rotate the |
|
image before processing. |
|
.TP |
|
\f[B]-f\f[R], \f[B]--format\f[R] \f[I]FORMAT\f[R] |
|
Set the output file format. |
|
Valid options are \[aq]png\[cq] and \[aq]gif\[cq]. |
|
It will auto detect from filename when possible, so usually this does |
|
not need to be set. |
|
If \f[B]-o\f[R] is \[aq]\f[B]-\f[R]\[cq] or a directory, then PNG files |
|
will be outputted by default. |
|
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 |
|
will need to be specified. |
|
.TP |
|
\f[B]--no-overwrite\f[R] |
|
Setting this flag means the program will stop before overwriting an |
|
existing file. |
|
Any files written before that one was encountered will stay in place. |
|
.TP |
|
\f[B]-c\f[R], \f[B]--compression\f[R] \f[I]TYPE\f[R] |
|
Set the type of PNG compression. |
|
Options are \[aq]default\[cq], \[aq]no\[cq], \[aq]speed\[cq], and |
|
\[aq]size\[cq]. |
|
This flag is ignored for non-PNG output. |
|
.TP |
|
\f[B]--fps\f[R] \f[I]DECIMAL\f[R] |
|
Set frames per second for animated GIF output. |
|
Note that not all FPS values can be represented by the GIF format, and |
|
so the closest possible one will be chosen. |
|
This flag has no default, and is required when animated GIFs are being |
|
outputted. |
|
This flag is ignored for non animated GIF output. |
|
.TP |
|
\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. |
|
0 is the default, and will loop infinitely. |
|
.TP |
|
\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. |
|
Aspect ratio will be maintained if \f[B]--height\f[R] is not specified |
|
as well. |
|
.TP |
|
\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. |
|
Aspect ratio will be maintained if \f[B]--width\f[R] is not specified as |
|
well. |
|
.TP |
|
\f[B]-u\f[R], \f[B]--upscale\f[R] \f[I]NUM\f[R] |
|
Scale image up after dithering. |
|
So \[aq]2\[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]). |
|
Only integers are allowed, as scaling up by a non-integer amount would |
|
distort the dithering pattern and introduce artifacts. |
|
.TP |
|
\f[B]-v\f[R], \f[B]--version\f[R] |
|
Get version information. |
|
.SH COMMANDS |
|
.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] |
|
Grayscale and RGB random dithering |
|
.RS |
|
.PP |
|
Accepts two arguments (min and max) for RGB or grayscale, or six |
|
(min/max for each channel) to control each RGB channel. |
|
Arguments can be separated by commas or spaces. |
|
.PP |
|
Random dithering adds random noise to the image. |
|
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% |
|
likely to become black and 50% likely to become white, assuming a black |
|
and white palette. |
|
So -0.2,2.0 will reduce the noise (20%), while -0.7,0.7 will increase it |
|
(70%). |
|
Values like -0.5,0.7 will bias the noise to one end of the channel(s). |
|
.TP |
|
\f[B]-s\f[R], \f[B]--seed\f[R] \f[I]DECIMAL\f[R] |
|
Set the seed for randomization. |
|
This will also only use one thread, to keep output deterministic. |
|
By default a different seed is chosen each time and multiple threads are |
|
used. |
|
.RE |
|
.TP |
|
\f[B]bayer\f[R] \f[I]X\f[R] \f[I]Y\f[R] |
|
Bayer matrix ordered dithering |
|
.RS |
|
.PP |
|
Requires two arguments, for the X and Y dimension of the matrix. |
|
They can be separated by a space, comma, or \[aq]x\[cq]. |
|
Both arguments must be a power of two, with the exception of: 3x5, 5x3, |
|
and 3x3. |
|
.RE |
|
.TP |
|
\f[B]odm\f[R] \f[I]NAME/JSON/FILE\f[R] |
|
Ordered Dithering Matrix |
|
.RS |
|
.PP |
|
Select or provide an ordered dithering matrix. |
|
This only takes one argument, but there a few types available: |
|
.IP \[bu] 2 |
|
A preprogrammed matrix name |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Inline JSON of a custom matrix |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Or a path to JSON for your custom matrix. |
|
\[aq]\f[B]-\f[R]\[cq] means standard input. |
|
.PP |
|
Here are all the built-in ordered dithering matrices. |
|
You can find details on these matrices here: |
|
<https://github.com/makeworld-the-better-one/dither/blob/v2.0.0/ordered_ditherers.go> |
|
.IP \[bu] 2 |
|
ClusteredDot4x4 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDotDiagonal8x8 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Vertical5x3 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Horizontal3x5 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDotDiagonal6x6 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDotDiagonal8x8_2 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDotDiagonal16x16 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDot6x6 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDotSpiral5x5 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDotHorizontalLine |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDotVerticalLine |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDot8x8 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDot6x6_2 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDot6x6_3 |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
ClusteredDotDiagonal8x8_3 |
|
.PP |
|
Their names are case-insensitive, and hyphens and underscores are |
|
treated the same. |
|
.PP |
|
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 same length. |
|
More information how to use a custom matrix can be found here: |
|
<https://pkg.go.dev/github.com/makeworld-the-better-one/dither/v2#OrderedDitherMatrix> |
|
.RE |
|
.IP |
|
.nf |
|
\f[C] |
|
{ |
|
\[dq]matrix\[dq]: [ |
|
[12, 5, 6, 13], |
|
[4, 0, 1, 7], |
|
[11, 3, 2, 8], |
|
[15, 10, 9, 14] |
|
], |
|
\[dq]max\[dq]: 16 |
|
} |
|
\f[R] |
|
.fi |
|
.TP |
|
\f[B]edm\f[R] \f[I]NAME/JSON/FILE\f[R] |
|
Error Diffusion Matrix |
|
.RS |
|
.PP |
|
Select or provide an error diffusion matrix. |
|
This only takes one argument, but there a few types available: |
|
.IP \[bu] 2 |
|
A preprogrammed matrix name |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Inline JSON of a custom matrix |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Or a path to JSON for your custom matrix. |
|
\[aq]\f[B]-\f[R]\[cq] means stdin. |
|
.PP |
|
Here are all the built-in error diffusion matrices. |
|
You can find details on these matrices here: |
|
<https://github.com/makeworld-the-better-one/dither/blob/v2.0.0/error_diffusers.go> |
|
.IP \[bu] 2 |
|
Simple2D |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
FloydSteinberg |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
FalseFloydSteinberg |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
JarvisJudiceNinke |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Atkinson |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Stucki |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Burkes |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
Sierra (or Sierra3) |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
TwoRowSierra (or Sierra2) |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
SierraLite (or Sierra2_4A) |
|
.PD 0 |
|
.P |
|
.PD |
|
.IP \[bu] 2 |
|
StevenPigeon |
|
.PP |
|
Their names are case-insensitive, and hyphens and underscores are |
|
treated the same. |
|
.PP |
|
The JSON format (whether inline or in a file) for a custom matrix is |
|
very simple, just a 2D array. |
|
The matrix must be \[lq]rectangular\[rq], meaning each array must have |
|
the same length. |
|
.TP |
|
\f[B]-s\f[R], \f[B]--serpentine\f[R] |
|
Enable serpentine dithering, which \[lq]snakes\[rq] back and forth when |
|
moving down the image, instead of going left-to-right each time. |
|
This can reduce artifacts or patterns in the noise. |
|
.RE |
|
.SH TIPS |
|
.PP |
|
Read about \f[B]--strength\f[R] if you haven\[cq]t already. |
|
.PP |
|
Read about \f[B]--recolor\f[R] if you haven\[cq]t already. |
|
.PP |
|
It\[cq]s 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 |
|
(externally, or with \f[B]--width\f[R] and/or \f[B]--height\f[R]), and |
|
then leave it. |
|
.PP |
|
If you need to scale it up afterward, use \f[B]--upscale\f[R], rather |
|
than another tool. |
|
This will prevent image artifacts and blurring. |
|
.PP |
|
Be wary of environments where you can\[cq]t make sure an image will be |
|
displayed at 100% size, pixel for pixel. |
|
Make sure to at least use nearest-neighbor scaling, do your best to |
|
preserve sharp pixel edges. |
|
.PP |
|
Dithered images must only be encoded in a lossless image format. |
|
This is why the tool only outputs PNG and GIF. |
|
.PP |
|
To increase the dithering artifacts for aesthetic effect, you can |
|
downscale the image before dithering and upscale after. |
|
Like if the image is 1000 pixels tall, your command can look like |
|
\f[B]didder \[en]height 500 \[en]upscale 2 [\&...]\f[R]. |
|
Depending on the input image size and what final size you want, you can |
|
of course just upscale as well. |
|
.PP |
|
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 |
|
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 |
|
dithered images turn out. |
|
For example, if your palette is dark, you can turn up the brightness. |
|
As mentioned above, these flags apply their transformations to the |
|
original image and will not adjust your selected palette colors. |
|
.SH EXAMPLES |
|
.TP |
|
\f[B]didder --palette \[dq]black white\[rq] -i input.jpg -o test.png bayer 16x16\f[R] |
|
This command dithers \f[V]input.jpg\f[R] using only black and white |
|
(implicitly converting the image to grayscale first), using a 16x16 |
|
Bayer matrix. |
|
The result is written to \f[V]test.png\f[R]. |
|
.TP |
|
\f[B]didder --palette \[dq]black white\[rq] -i input.jpg -o test.png odm ClusteredDot4x4\f[R] |
|
Same command as above, but dithering with the preprogrammed ordered |
|
dithering matrix called ClusteredDot4x4. |
|
.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] |
|
This is the command used for the README. |
|
It dithers using a 4x4 Bayer matrix, initially to black and white, which |
|
is then recolored to black and purple. |
|
Dithering to black and purple directly would produce much lower contrast |
|
results. |
|
The dithered image is upscaled to be two times larger, so that the Bayer |
|
dithering artifacts can be seen more clearly. |
|
.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] |
|
This command uses a blue recolor palette, one that is biased to being |
|
darker. |
|
The palette can be viewed at |
|
<https://colorpeek.com/#11161e,116bcd,63b3ed,e1efff>. |
|
The dithering palette is the grayscale version of those colors, to keep |
|
luminance accurate. |
|
Strength is set to 64%, which although usually recommended for Bayer |
|
dithering of color images, works well here. |
|
Alternatively, one could try and increase \f[B]--contrast\f[R]. |
|
Finally, the brightness is increased to compensate for the dark palette. |
|
.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] |
|
This command takes two input images and creates an animated GIF, |
|
dithering and recoloring them along the way. |
|
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. |
|
.SH REPORTING BUGS |
|
.PP |
|
Any bugs can be reported by creating an issue on GitHub: |
|
<https://github.com/makeworld-the-better-one/didder>
|
|
|