add simpleerror ditherer and begin building out tests
This commit is contained in:
parent
6086d357a1
commit
f4d8d20ce5
4
main.go
4
main.go
@ -13,7 +13,7 @@ import (
|
||||
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>
|
||||
Supported dither options are: noop, naive, randomnoise
|
||||
Supported dither options are: noop; naive; randomnoise; bayer{0,1}; bayer{0,1}n
|
||||
Supported input image formats are jpg, png
|
||||
`, os.Args[0])
|
||||
os.Exit(0)
|
||||
@ -49,6 +49,8 @@ func main() {
|
||||
new = apply(i, bayerDithering(1, false))
|
||||
case "bayer1n":
|
||||
new = apply(i, bayerDithering(1, true))
|
||||
case "simpleerror":
|
||||
new = apply(i, simpleErrorDiffusion())
|
||||
default:
|
||||
fmt.Printf("unknown ditherer option: %s\n", ditherer)
|
||||
os.Exit(2)
|
||||
|
39
quantizer.go
39
quantizer.go
@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
@ -123,21 +122,31 @@ func bayerDithering(level int, invert bool) quantizerFunction {
|
||||
}
|
||||
}
|
||||
|
||||
// That is, "relative luminance": https://en.wikipedia.org/wiki/Relative_luminance.
|
||||
// go's color library doesn't give any information on what "color space" this RGBA is derived from.
|
||||
// Although the results look decent without using the sRGB->LinearRGB/CIE XYZ transformation,
|
||||
// it's probably subtly wrong. Will play around with it.
|
||||
// In particular, images seem "brighter" than they're supposed to be.
|
||||
func luminence(c color.Color) float64 {
|
||||
r, g, b, _ := c.RGBA()
|
||||
// we divide by 65535 because each RGB value from RGBA() returns in the range [0,65535)
|
||||
return (0.2126*float64(r) + 0.7152*float64(g) + 0.0722*float64(b)) / 65535
|
||||
func simpleErrorDiffusion() quantizerFunction {
|
||||
errMap := make(map[coord]float64)
|
||||
return func(x int, y int, c color.Color) color.Color {
|
||||
l := luminence(c) + errMap[coord{x: x, y: y}]
|
||||
if l > 0.5 {
|
||||
errMap[coord{x: x + 1, y: y}] = (l - 1.0) / 2.0
|
||||
errMap[coord{x: x, y: y + 1}] = (l - 1.0) / 2.0
|
||||
delete(errMap, coord{x: x, y: y})
|
||||
return color.White
|
||||
}
|
||||
errMap[coord{x: x + 1, y: y}] = l / 2.0
|
||||
errMap[coord{x: x, y: y + 1}] = l / 2.0
|
||||
delete(errMap, coord{x: x, y: y})
|
||||
return color.Black
|
||||
}
|
||||
}
|
||||
|
||||
// seems to result in super-dark images right now.
|
||||
func sRGBtoXYZ(u float64) float64 {
|
||||
if u <= 0.04045 {
|
||||
return (25 * u) / 323
|
||||
// That is, "relative luminance": https://en.wikipedia.org/wiki/Relative_luminance.
|
||||
// go's color library doesn't give any information on what "color space" the RGBA is derived from,
|
||||
// so we convert to Y'CbCr, which returns luminence directly as the Y component.
|
||||
func luminence(c color.Color) float64 {
|
||||
nr, ok := color.NRGBAModel.Convert(c).(color.NRGBA)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return math.Pow(((200*u)+11)/211, 2.4)
|
||||
y, _, _ := color.RGBToYCbCr(nr.R, nr.G, nr.B)
|
||||
return float64(y) / 255.0
|
||||
}
|
||||
|
38
quantizer_test.go
Normal file
38
quantizer_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNoop(t *testing.T) {
|
||||
c := noOp(0, 0, color.White)
|
||||
r, g, b, a := c.RGBA()
|
||||
if r != 0xffff || g != 0xffff || b != 0xffff || a != 0xffff {
|
||||
t.Log("noop did not return input")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuminence(t *testing.T) {
|
||||
l := luminence(color.White)
|
||||
if l != 1 {
|
||||
t.Logf("white input did not output 1, instead %f", l)
|
||||
t.Fail()
|
||||
}
|
||||
l = luminence(color.Black)
|
||||
if l != 0 {
|
||||
t.Logf("black input did not output 0, instead %f", l)
|
||||
t.Fail()
|
||||
}
|
||||
l = luminence(color.Gray16{0x8000})
|
||||
if l < 0.5 {
|
||||
t.Logf("white-leaning gray input was not above 0.5, instead %f", l)
|
||||
t.Fail()
|
||||
}
|
||||
l = luminence(color.Gray16{0x7fff})
|
||||
if l > 0.5 {
|
||||
t.Logf("black-leaning gray input not below 0.5, instead %f", l)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user