clean up command-line parsing and add some error-diffusion experiments
This commit is contained in:
parent
94d1e465c9
commit
301ee00b69
@ -69,3 +69,52 @@ func colorBayer(level int, p color.Palette) quantizerFunction {
|
|||||||
return p[p.Index(rc)]
|
return p[p.Index(rc)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func colorError(errMap map[coord]float64, d diffusion, palette color.Palette) quantizerFunction {
|
||||||
|
r := float64(len(palette))
|
||||||
|
return func(x int, y int, c color.Color) color.Color {
|
||||||
|
p := coord{x: x, y: y}
|
||||||
|
rc := permuteColor(c, uint8(r*errMap[p]))
|
||||||
|
delete(errMap, p) // don't let the error map grow too big
|
||||||
|
nc := palette[palette.Index(rc)]
|
||||||
|
l := luminence(c) - luminence(nc)
|
||||||
|
applyError(d, l, p, errMap)
|
||||||
|
return nc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func simpleColorErrorDiffusion() quantizerFunction {
|
||||||
|
errMap := make(map[coord]float64)
|
||||||
|
d := diffusion{
|
||||||
|
divisor: 2.0,
|
||||||
|
matrix: map[coord]float64{
|
||||||
|
{x: 1, y: 0}: 1.0,
|
||||||
|
{x: 0, y: 1}: 1.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return colorError(errMap, d, sixteencolors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorJarvisJudiceNinke() quantizerFunction {
|
||||||
|
errMap := make(map[coord]float64)
|
||||||
|
d := diffusion{
|
||||||
|
divisor: 48.0,
|
||||||
|
matrix: map[coord]float64{
|
||||||
|
{x: 1, y: 0}: 7.0,
|
||||||
|
{x: 2, y: 0}: 5.0,
|
||||||
|
|
||||||
|
{x: -2, y: 1}: 3.0,
|
||||||
|
{x: -1, y: 1}: 5.0,
|
||||||
|
{x: 0, y: 1}: 7.0,
|
||||||
|
{x: 1, y: 1}: 5.0,
|
||||||
|
{x: 2, y: 1}: 3.0,
|
||||||
|
|
||||||
|
{x: -2, y: 2}: 1.0,
|
||||||
|
{x: -1, y: 2}: 3.0,
|
||||||
|
{x: 0, y: 2}: 5.0,
|
||||||
|
{x: 1, y: 2}: 3.0,
|
||||||
|
{x: 2, y: 2}: 1.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return colorError(errMap, d, sixteencolors)
|
||||||
|
}
|
||||||
|
125
main.go
125
main.go
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
@ -32,25 +33,73 @@ func apply(i image.Image, f quantizerFunction) image.Image {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
type ditherOpts struct {
|
||||||
if len(os.Args) == 1 || len(os.Args) > 4 || os.Args[1] == "help" {
|
raw string
|
||||||
fmt.Printf(`usage: %s <path/to/image.ext> <dither_option> <path/to/output.png>
|
level int
|
||||||
Supported dither options are: noop; naive; randomnoise; bayer{0,1}; bayer{0,1}n
|
invert bool
|
||||||
Supported input image formats are jpg, png
|
|
||||||
`, os.Args[0])
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := os.Args[1]
|
func (o *ditherOpts) Set(input string) error {
|
||||||
ditherer := os.Args[2]
|
for _, p := range input {
|
||||||
outfile := os.Args[3]
|
switch p {
|
||||||
|
case 'n':
|
||||||
|
o.invert = true
|
||||||
|
case '0':
|
||||||
|
o.level = 0
|
||||||
|
case '1':
|
||||||
|
o.level = 1
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown option %s", string(p))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ditherOpts) String() string {
|
||||||
|
return o.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ditherOpts := &ditherOpts{}
|
||||||
|
|
||||||
|
filenameFlag := flag.String("in", "", "Input filename. PNG or JPG formats accepted.")
|
||||||
|
verboseFlag := flag.Bool("v", false, "Include diagnostic log lines.")
|
||||||
|
paletteFlag := flag.String("p", "bw",
|
||||||
|
`Palette to use. Defaults to 'bw': black-and-white.
|
||||||
|
Pass 'sixteen' for the classic sixteen-color palette.
|
||||||
|
Not all dither options support palettes; unsupported ditherers will ignore this flag.`)
|
||||||
|
dithererFlag := flag.String("d", "naive",
|
||||||
|
`Ditherer to use. Defaults to 'naive', which just flattens to nearest color.
|
||||||
|
Other options: 'noise', 'bayer', 'error', 'floydsteinberg', 'jjn', 'atkinson'.`)
|
||||||
|
flag.Var(ditherOpts, "o",
|
||||||
|
`Supply options for the selected ditherer.
|
||||||
|
Currently only relevant to the bayer ditherer, which defaults to 'level 0, non-inverted'.
|
||||||
|
To change: '1' applies a level-one bayer diffuser; '1n' would invert it, '0' would be the default.`)
|
||||||
|
outputFlag := flag.String("out", "output.png", "Output filename. Outputs in PNG format.")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// extract all those pointers
|
||||||
|
filename := *filenameFlag
|
||||||
|
ditherer := *dithererFlag
|
||||||
|
palette := *paletteFlag
|
||||||
|
outfile := *outputFlag
|
||||||
|
verbose := *verboseFlag
|
||||||
|
|
||||||
|
if filename == "" || outfile == "" {
|
||||||
|
fmt.Printf("Usage of %s:\n", os.Args[0])
|
||||||
|
flag.PrintDefaults()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
i, codex, err := loadImage(filename)
|
i, codex, err := loadImage(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error loading %s; %v\n", filename, err)
|
fmt.Printf("error loading %s; %v\n", filename, err)
|
||||||
os.Exit(255)
|
os.Exit(255)
|
||||||
}
|
}
|
||||||
|
if verbose {
|
||||||
fmt.Printf("loaded %s using %s\n", filename, codex)
|
fmt.Printf("loaded %s using %s\n", filename, codex)
|
||||||
|
}
|
||||||
|
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
|
|
||||||
@ -59,33 +108,50 @@ func main() {
|
|||||||
case "noop":
|
case "noop":
|
||||||
new = apply(i, noOp)
|
new = apply(i, noOp)
|
||||||
case "naive":
|
case "naive":
|
||||||
|
if palette == "bw" {
|
||||||
new = apply(i, naiveBW)
|
new = apply(i, naiveBW)
|
||||||
case "palette":
|
} else if palette == "sixteen" {
|
||||||
new = apply(i, naivePalette(sixteencolors))
|
new = apply(i, naivePalette(sixteencolors))
|
||||||
case "randomnoise":
|
}
|
||||||
|
case "noise":
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
if palette == "bw" {
|
||||||
new = apply(i, randomNoise)
|
new = apply(i, randomNoise)
|
||||||
case "noisepalette":
|
} else if palette == "sixteen" {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
new = apply(i, randomNoisePalette(sixteencolors))
|
new = apply(i, randomNoisePalette(sixteencolors))
|
||||||
case "bayer0":
|
} else {
|
||||||
new = apply(i, bayerDithering(0, false))
|
fmt.Printf("unknown palette option for %s ditherer: %s\n", ditherer, palette)
|
||||||
case "bayer0p":
|
os.Exit(2)
|
||||||
new = apply(i, colorBayer(0, sixteencolors))
|
}
|
||||||
case "bayer0n":
|
case "bayer":
|
||||||
new = apply(i, bayerDithering(0, true))
|
if palette == "bw" {
|
||||||
case "bayer1":
|
new = apply(i, bayerDithering(ditherOpts.level, ditherOpts.invert))
|
||||||
new = apply(i, bayerDithering(1, false))
|
} else if palette == "sixteen" {
|
||||||
case "bayer1n":
|
new = apply(i, colorBayer(ditherOpts.level, sixteencolors))
|
||||||
new = apply(i, bayerDithering(1, true))
|
} else {
|
||||||
case "bayer1p":
|
fmt.Printf("unknown palette option for %s ditherer: %s\n", ditherer, palette)
|
||||||
new = apply(i, colorBayer(1, sixteencolors))
|
os.Exit(2)
|
||||||
case "simpleerror":
|
}
|
||||||
|
case "error":
|
||||||
|
if palette == "bw" {
|
||||||
new = apply(i, simpleErrorDiffusion())
|
new = apply(i, simpleErrorDiffusion())
|
||||||
|
} else if palette == "sixteen" {
|
||||||
|
new = apply(i, simpleColorErrorDiffusion())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("unknown palette option for %s ditherer: %s\n", ditherer, palette)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
case "floydsteinberg":
|
case "floydsteinberg":
|
||||||
new = apply(i, floydSteinberg())
|
new = apply(i, floydSteinberg())
|
||||||
case "jjn":
|
case "jjn":
|
||||||
|
if palette == "bw" {
|
||||||
new = apply(i, jarvisJudiceNinke())
|
new = apply(i, jarvisJudiceNinke())
|
||||||
|
} else if palette == "sixteen" {
|
||||||
|
new = apply(i, colorJarvisJudiceNinke())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("unknown palette option for %s ditherer: %s\n", ditherer, palette)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
case "atkinson":
|
case "atkinson":
|
||||||
new = apply(i, atkinson())
|
new = apply(i, atkinson())
|
||||||
default:
|
default:
|
||||||
@ -93,15 +159,20 @@ func main() {
|
|||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
fmt.Printf("dithering took %s\n", time.Since(t))
|
fmt.Printf("dithering took %s\n", time.Since(t))
|
||||||
|
}
|
||||||
|
|
||||||
err = saveImage(outfile, new)
|
err = saveImage(outfile, new)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error saving %s; %v\n", outfile, err)
|
fmt.Printf("error saving %s; %v\n", outfile, err)
|
||||||
os.Exit(255)
|
os.Exit(255)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
fmt.Printf("saved output to %s\n", outfile)
|
fmt.Printf("saved output to %s\n", outfile)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func loadImage(filename string) (image.Image, string, error) {
|
func loadImage(filename string) (image.Image, string, error) {
|
||||||
r, err := os.Open(filename)
|
r, err := os.Open(filename)
|
||||||
|
Loading…
Reference in New Issue
Block a user