dithering done :)

This commit is contained in:
2026-03-04 21:44:20 +03:00
parent e5c0537f2b
commit b9808c8150
7 changed files with 102 additions and 97 deletions

View File

@@ -18,16 +18,19 @@ func (_ grid) Bounds() image.Rectangle {
} }
func (g grid) At(x, y int) color.Color { func (g grid) At(x, y int) color.Color {
step := int(64 / (g.Gray.Y / 4)) n := g.Gray.Y / 4
if (y*8+x)%step == 0 { if n == 0 {
return color.Black return color.Black
} }
step := int(64 / n)
if (y*8+x)%step == 0 {
return color.White return color.White
} }
return color.Black
}
type uniformGrid struct { type uniformGrid struct {
image.Image image.Image
image.Rectangle
} }
func (g uniformGrid) ColorModel() color.Model { func (g uniformGrid) ColorModel() color.Model {
@@ -35,9 +38,23 @@ func (g uniformGrid) ColorModel() color.Model {
} }
func (g uniformGrid) Bounds() image.Rectangle { func (g uniformGrid) Bounds() image.Rectangle {
return g.Rectangle // just need to return something
return g.Image.Bounds()
} }
func (g uniformGrid) At(x, y int) color.Color { func (g uniformGrid) At(x, y int) color.Color {
return color.Color(nil) dx, dy := g.Image.Bounds().Dx(), g.Image.Bounds().Dy()
return g.Image.At(x%dx, y%dy)
}
type Dithering struct {
image.Image
}
func (d Dithering) ColorModel() color.Model { return color.GrayModel }
func (d Dithering) Bounds() image.Rectangle { return d.Image.Bounds() }
func (d Dithering) At(x, y int) color.Color {
c := color.GrayModel.Convert(d.Image.At(x, y))
return uniformGrid{grid{c.(color.Gray)}}.
At(x, y)
} }

View File

@@ -1,9 +1,11 @@
package dithering package dithering
import ( import (
"image"
"image/color" "image/color"
"image/png" "image/png"
"math/rand" "math/rand"
"os"
"os/exec" "os/exec"
"testing" "testing"
"time" "time"
@@ -11,25 +13,61 @@ import (
"git.nkpl.cc/twocookedfaggots/imageutils" "git.nkpl.cc/twocookedfaggots/imageutils"
) )
func TestGrid(t *testing.T) { func init() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
}
func callImage(img image.Image) {
for y := 0; y < img.Bounds().Dy(); y++ {
for x := 0; x < img.Bounds().Dx(); x++ {
_ = img.At(x, y)
}
}
}
func TestGrid(t *testing.T) {
command := exec.Command("imv", "-") command := exec.Command("imv", "-")
w, err := command.StdinPipe() w, err := command.StdinPipe()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
} }
img := imageutils.Scale(1<<6, grid{color.Gray{uint8(rand.Int() % 64)}})
go func() {
err = png.Encode(w, img)
if err != nil {
t.Error(err)
}
}()
err = command.Start() err = command.Start()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
}
n := uint8(rand.Int() % 256)
t.Log(n)
img := imageutils.Scale(1<<6, grid{color.Gray{n}})
err = png.Encode(w, img)
if err != nil {
t.Fatal(err)
}
callImage(grid{color.Gray{4}})
callImage(grid{color.Gray{0}})
}
func TestDithering(t *testing.T) {
r, err := os.Open("/home/potassium/documents/wallpaper/8.png")
if err != nil {
t.Fatal(err)
}
defer r.Close()
w, err := os.Create("sample.png")
if err != nil {
t.Fatal(err)
}
defer w.Close()
img, err := png.Decode(r)
if err != nil {
t.Fatal(err)
}
err = png.Encode(w, Dithering{img})
if err != nil {
t.Fatal(err)
} }
} }

15
pkg/dithering/cmd/main.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"git.nkpl.cc/twocookedfaggots/imageutils/pkg/dithering"
"git.nkpl.cc/twocookedfaggots/imageutils/util"
)
func main() {
err := util.ProcessStdio(func(img image.Image) image.Image {
return dithering.Dithering{img}
})
if err != nil {
panic(err)
}
}

BIN
pkg/dithering/sample.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

View File

@@ -1,32 +0,0 @@
package imageutils
import (
"fmt"
"image/color"
"errors"
)
var ParseHexColorErr = errors.New("invalid length, must be 7 or 4")
func ParseHexColor(s string) (c color.RGBA, err error) {
c.A = 0xff
switch len(s) {
case 7:
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
case 4:
_, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B)
// Double the hex digits:
c.R *= 17
c.G *= 17
c.B *= 17
default:
err = ParseHexColorErr
}
return
}
func ColorToHex(c color.Color) string {
r, g, b, _ := color.NRGBAModel.Convert(c).RGBA()
return fmt.Sprintf("#%02x%02x%02x", byte(r), byte(g), byte(b))
}

View File

@@ -1,47 +0,0 @@
package imageutils
import (
"fmt"
"image/png"
"os"
"path/filepath"
"testing"
)
// Returns path of first occured file
// with png extension in user's home directory.
func firstPNG(root string) (filename string,
err error) {
var fn filepath.WalkFunc = func(path string,
info os.FileInfo,
fileErr error) error {
if fileErr != nil {
return fileErr
}
if !info.IsDir() &&
filepath.Ext(path) == ".png" {
filename = path
return nil
}
return nil
}
err = filepath.Walk(root, fn)
return
}
func TestColorToHex(t *testing.T) {
root := os.Getenv("HOME")
path, err := firstPNG(root)
if err != nil {
t.Fatal(err)
}
f, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
img, err := png.Decode(f)
if err != nil {
t.Fatal(err)
}
fmt.Println(ColorToHex(img.At(0, 0)))
}

14
util/stdio.go Normal file
View File

@@ -0,0 +1,14 @@
package imageutils
// ProcessImage takes input png image from stdin, processes it with f and outputs it to stdout.
func ProcessStdio(f func(image.Image) image.Image) {
img, err := png.Decode(os.Stdin)
if err != nil {
return err
}
err = png.Encode(os.Stdout, f(img))
if err != nil {
return err
}
return err
}