2021-01-10 03:35:31 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"image"
|
2021-01-13 01:19:48 +00:00
|
|
|
"image/color"
|
2021-01-10 03:35:31 +00:00
|
|
|
_ "image/jpeg"
|
|
|
|
"image/png"
|
|
|
|
"math/rand"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2021-01-13 01:19:48 +00:00
|
|
|
type coord struct {
|
|
|
|
x, y int
|
|
|
|
}
|
|
|
|
|
|
|
|
// provided x, y, and color at location, return a color
|
|
|
|
type quantizerFunction func(int, int, color.Color) color.Color
|
|
|
|
|
|
|
|
// apply sequentially applies a quantizing function to an image and returns the result
|
|
|
|
func apply(i image.Image, f quantizerFunction) image.Image {
|
|
|
|
out := image.NewRGBA(image.Rect(0, 0, i.Bounds().Max.X, i.Bounds().Max.Y))
|
|
|
|
|
|
|
|
b := out.Bounds()
|
|
|
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
|
|
for x := b.Min.X; x < b.Max.X; x++ {
|
|
|
|
out.Set(x, y, f(x, y, i.At(x, y)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2021-01-10 03:35:31 +00:00
|
|
|
func main() {
|
|
|
|
if len(os.Args) == 1 || len(os.Args) > 4 || os.Args[1] == "help" {
|
|
|
|
fmt.Printf(`usage: %s <path/to/image.ext> <dither_option> <path/to/output.png>
|
2021-01-11 23:01:42 +00:00
|
|
|
Supported dither options are: noop; naive; randomnoise; bayer{0,1}; bayer{0,1}n
|
2021-01-10 03:35:31 +00:00
|
|
|
Supported input image formats are jpg, png
|
|
|
|
`, os.Args[0])
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
filename := os.Args[1]
|
|
|
|
ditherer := os.Args[2]
|
|
|
|
outfile := os.Args[3]
|
|
|
|
|
|
|
|
i, codex, err := loadImage(filename)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("error loading %s; %v\n", filename, err)
|
|
|
|
os.Exit(255)
|
|
|
|
}
|
|
|
|
fmt.Printf("loaded %s using %s\n", filename, codex)
|
|
|
|
|
|
|
|
t := time.Now()
|
|
|
|
|
|
|
|
var new image.Image
|
|
|
|
switch ditherer {
|
|
|
|
case "noop":
|
|
|
|
new = apply(i, noOp)
|
|
|
|
case "naive":
|
|
|
|
new = apply(i, naiveBW)
|
2021-01-13 01:19:48 +00:00
|
|
|
case "palette":
|
|
|
|
new = apply(i, naivePalette(sixteencolors))
|
2021-01-10 03:35:31 +00:00
|
|
|
case "randomnoise":
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
new = apply(i, randomNoise)
|
2021-01-13 01:19:48 +00:00
|
|
|
case "noisepalette":
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
new = apply(i, randomNoisePalette(sixteencolors))
|
2021-01-10 03:35:31 +00:00
|
|
|
case "bayer0":
|
|
|
|
new = apply(i, bayerDithering(0, false))
|
2021-01-13 01:19:48 +00:00
|
|
|
case "bayer0p":
|
|
|
|
new = apply(i, colorBayer(0, sixteencolors))
|
2021-01-10 03:35:31 +00:00
|
|
|
case "bayer0n":
|
|
|
|
new = apply(i, bayerDithering(0, true))
|
|
|
|
case "bayer1":
|
|
|
|
new = apply(i, bayerDithering(1, false))
|
|
|
|
case "bayer1n":
|
|
|
|
new = apply(i, bayerDithering(1, true))
|
2021-01-13 01:19:48 +00:00
|
|
|
case "bayer1p":
|
|
|
|
new = apply(i, colorBayer(1, sixteencolors))
|
2021-01-11 23:01:42 +00:00
|
|
|
case "simpleerror":
|
|
|
|
new = apply(i, simpleErrorDiffusion())
|
2021-01-12 00:47:29 +00:00
|
|
|
case "floydsteinberg":
|
|
|
|
new = apply(i, floydSteinberg())
|
2021-01-12 00:52:05 +00:00
|
|
|
case "jjn":
|
|
|
|
new = apply(i, jarvisJudiceNinke())
|
|
|
|
case "atkinson":
|
|
|
|
new = apply(i, atkinson())
|
2021-01-10 03:35:31 +00:00
|
|
|
default:
|
|
|
|
fmt.Printf("unknown ditherer option: %s\n", ditherer)
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("dithering took %s\n", time.Since(t))
|
|
|
|
|
|
|
|
err = saveImage(outfile, new)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("error saving %s; %v\n", outfile, err)
|
|
|
|
os.Exit(255)
|
|
|
|
}
|
|
|
|
fmt.Printf("saved output to %s\n", outfile)
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadImage(filename string) (image.Image, string, error) {
|
|
|
|
r, err := os.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
return image.Decode(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
func saveImage(filename string, img image.Image) error {
|
|
|
|
f, err := os.Create(filename)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := png.Encode(f, img); err != nil {
|
|
|
|
f.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := f.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|