diff --git a/circle.go b/circle.go new file mode 100644 index 0000000..0e8c8db --- /dev/null +++ b/circle.go @@ -0,0 +1,24 @@ +package imageutils + +import ( + "image" + "image/color" +) + +// go.dev/blog/image-draw#drawing-through-a-mask +type Circle struct { + image.Point + R int +} + +func (c Circle) ColorModel() color.Model { return color.AlphaModel } +func (c Circle) Bounds() image.Rectangle { + return image.Rect(c.Point.X-c.R, c.Point.Y-c.R, c.Point.X+c.R, c.Point.Y+c.R) +} +func (c Circle) At(x, y int) color.Color { + xx, yy, rr := float64(x-c.Point.X)+0.5, float64(y-c.Point.Y)+0.5, float64(c.R) + if xx*xx+yy*yy < rr*rr { + return color.Opaque + } + return color.Transparent +} diff --git a/cmd/jpg2png/jpg2png.go b/cmd/jpg2png/jpg2png.go new file mode 100644 index 0000000..6076422 --- /dev/null +++ b/cmd/jpg2png/jpg2png.go @@ -0,0 +1,18 @@ +package main + +import ( + "image/jpeg" + "image/png" + "os" +) + +func main() { + img, err := jpeg.Decode(os.Stdin) + if err != nil { + panic(err) + } + err = png.Encode(os.Stdout, img) + if err != nil { + panic(err) + } +} diff --git a/cmd/playground/circles/circles.go b/cmd/playground/circles/circles.go new file mode 100644 index 0000000..b0f0c99 --- /dev/null +++ b/cmd/playground/circles/circles.go @@ -0,0 +1,56 @@ +package main + +import ( + "math/rand" + "image" + "image/color" + "image/draw" + "image/png" + "os" + + iu "git.nkpl.cc/twocookedfaggots/imageutils" +) + +func square(side int) image.Rectangle { return image.Rect(0, 0, side, side) } + +func Circles(b image.Rectangle, radius int) chan iu.Circle { + ch := make(chan iu.Circle) + go func() { + defer close(ch) + for { + x, y := rand.Int()%b.Dx(), rand.Int()%b.Dy() + dxdy := b.Dx()*b.Dy() + r := rand.Int() % (dxdy/(dxdy/radius)) + ch <- iu.Circle{image.Point{x, y}, r} + } + }() + return ch +} + +// Converts color.Opaque and color.Transparent to color.Black and color.White respectively. +var BlackAndWhite = color.ModelFunc( + func(c color.Color) color.Color { + if color.AlphaModel.Convert(c) == color.Opaque { + return color.Black + } + return color.White + }, +) + +func main() { + canvas := image.NewRGBA(square(1024)) + circles := Circles(canvas.Bounds(), 1024/16) + + // draw n circles + for i := 0; i < 100; i++ { + c := <-circles + draw.DrawMask( + canvas, canvas.Bounds(), &image.Uniform{color.White}, + image.Point{}, c, image.Point{}, draw.Over, + ) + } + err := png.Encode(os.Stdout, canvas) + if err != nil { + panic(err) + } +} diff --git a/cmd/playground/main.go b/cmd/playground/main.go new file mode 100644 index 0000000..e1b7f4c --- /dev/null +++ b/cmd/playground/main.go @@ -0,0 +1,9 @@ +package main + +import ( + iu "git.nkpl.cc/twocookedfaggots/imageutils" +) + +func main() { + +} diff --git a/pkg/merge/IMG_0732.jpg b/pkg/merge/IMG_0732.jpg new file mode 100644 index 0000000..2ae773e Binary files /dev/null and b/pkg/merge/IMG_0732.jpg differ diff --git a/pkg/merge/converted.png b/pkg/merge/converted.png new file mode 100644 index 0000000..2cc9467 Binary files /dev/null and b/pkg/merge/converted.png differ diff --git a/pkg/merge/converted_gray.png b/pkg/merge/converted_gray.png new file mode 100644 index 0000000..52762a1 Binary files /dev/null and b/pkg/merge/converted_gray.png differ diff --git a/pkg/merge/merge.go b/pkg/merge/merge.go new file mode 100644 index 0000000..92d29c3 --- /dev/null +++ b/pkg/merge/merge.go @@ -0,0 +1,44 @@ +package main + +import ( + "image" + "image/color" + _ "image/jpeg" + "image/png" + _ "image/png" + "os" +) + +type Merge struct { + first, second image.Image +} + +func (m Merge) ColorModel() color.Model { return m.first.ColorModel() } +func (m Merge) Bounds() image.Rectangle { return m.first.Bounds() } + +func (m Merge) At(x, y int) color.Color { + if (x%2 != 0) && (y%2 != 0) { + return m.second.At(x, y) + } + return m.first.At(x, y) +} + +func main() { + f1, err := os.Open(os.Args[1]) + if err != nil { + panic(err) + } + f2, err := os.Open(os.Args[2]) + if err != nil { + panic(err) + } + img1, _, err := image.Decode(f1) + if err != nil { + panic(err) + } + img2, _, err := image.Decode(f2) + if err != nil { + panic(err) + } + png.Encode(os.Stdout, Merge{img1, img2}) +} diff --git a/pkg/merge/merged.png b/pkg/merge/merged.png new file mode 100644 index 0000000..f8e9283 Binary files /dev/null and b/pkg/merge/merged.png differ diff --git a/pkg/merge/palette.go b/pkg/merge/palette.go new file mode 100644 index 0000000..1968d93 --- /dev/null +++ b/pkg/merge/palette.go @@ -0,0 +1,152 @@ +package main + +import ( + "flag" + "fmt" + "image" + "image/color" + _ "image/jpeg" + "image/png" + _ "image/png" + "os" + + "slices" +) + +func paletteFromImage(img image.Image) (p []color.Color) { + for i := 0; i < img.Bounds().Dx(); i++ { + p = append(p, img.At(i, 0)) + } + return +} + +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 = fmt.Errorf("invalid length, must be 7 or 4") + + } + return +} + +func mean(c1, c2 color.Color) color.Color { + r1, g1, b1, _ := c1.RGBA() + r2, g2, b2, _ := c2.RGBA() + return color.RGBA{ + uint8((r1 + r2) / 2), + uint8((g1 + g2) / 2), + uint8((b1 + b2) / 2), + 0xFF, + } +} + +type Convert struct { + image.Image + color.Palette +} + +func (c Convert) ColorModel() color.Model { return c.Image.ColorModel() } +func (c Convert) Bounds() image.Rectangle { return c.Image.Bounds() } +func (c Convert) At(x, y int) color.Color { + if isMeanCloser(c.Palette, c.Image.At(x, y)) { + first, second := twoClosest(c.Palette, c.Image.At(x, y)) + // chessboard texture + if (x%2 != 0) && (y%2 != 0) || (x%2 == 0) && (y%2 == 0) { + return first + } + return second + } + return c.Palette.Convert(c.Image.At(x, y)) +} + +func twoClosest(p color.Palette, c color.Color) (first, second color.Color) { + first = p.Convert(c) + i := p.Index(c) + p2 := slices.Delete(slices.Clone(p), i, i+1) + second = p2.Convert(c) + return +} + +func isMeanCloser(p color.Palette, c color.Color) bool { + first, second := twoClosest(p, c) + mp := color.Palette{first, mean(first, second), second} + i := mp.Index(c) + return i == mp.Index(mean(first, second)) +} + +func main() { + paletteFile := flag.String("p", "", "provide palette file") + flag.Parse() + + var palette []color.Color + + var colors []string + _ = []string{ + "#ffffec", // soft-white + "#ff0000", // red + "#00ff00", // green + "#0000ff", // blue + "#000000", // black + } + funniPalette := []string{ + "#ffffec", + "#000000", + "#4a3544", + "#757575", + "#8e8e8e", + "#666666", + "#4c4c4c", + "#3b3b3b", + "#949494", + "#ffffff", + "#665361", + "#3f3f3f", + "#525252", + "#838383", + "#111111", + "#070707", + "#b9b9b9", + "#dfdfdf", + } + colors = funniPalette + if *paletteFile != "" { + f, err := os.Open(*paletteFile) + if err != nil { + panic(err) + } + img, _, err := image.Decode(f) + if err != nil { + panic(err) + } + palette = paletteFromImage(img) + } else { + palette = func() (p []color.Color) { + for _, v := range colors { + c, err := parseHexColor(v) + if err != nil { + panic(err) + } + p = append(p, c) + } + return + }() + } + img, _, err := image.Decode(os.Stdin) + if err != nil { + panic(err) + } + palette = append(palette, color.RGBA{88, 88, 88, 0xFF}) + err = png.Encode(os.Stdout, Convert{img, palette}) + if err != nil { + panic(err) + } +} diff --git a/pkg/merge/palette.png b/pkg/merge/palette.png new file mode 100644 index 0000000..d0ddb4d Binary files /dev/null and b/pkg/merge/palette.png differ diff --git a/pkg/merge/readme.md b/pkg/merge/readme.md new file mode 100644 index 0000000..dc035f4 --- /dev/null +++ b/pkg/merge/readme.md @@ -0,0 +1,5 @@ +experiments with palette and stuff + +--- + +![alt text](converted.png) \ No newline at end of file diff --git a/pkg/palette b/pkg/palette deleted file mode 160000 index e161720..0000000 --- a/pkg/palette +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e161720f1cf08d296c750662eaf5ae4d25811b74