Compare commits

21 Commits

Author SHA1 Message Date
e5c0537f2b some redesign 2026-03-04 02:14:21 +03:00
c25b65a5f6 grayscale converter example 2026-03-02 22:22:02 +03:00
1122be9007 update of test.go 2026-02-22 04:03:25 +03:00
dc5f2466d1 Delete pkg/.hex_test.go.swp 2026-02-22 02:05:56 +04:00
0bbccec989 Delete util/.hex_test.go.swp 2026-02-22 02:05:25 +04:00
c5eefdec1f draft work as intended :) 2026-02-22 01:11:57 +03:00
352631e071 merging all related stuff here 2026-02-22 01:06:02 +03:00
cc5c9c6d1a cleaning up, early http server draft 2026-01-24 09:39:27 +03:00
d52658ab5c cleaningup 2025-12-14 06:16:10 +03:00
1db672d481 renamed 2025-12-14 06:07:13 +03:00
02ece86303 cmd 2025-12-14 06:04:57 +03:00
2709034fc3 Update go.mod 2024-12-24 17:40:15 +04:00
aadc89bca0 README update 2024-09-05 14:13:00 +03:00
11aa6e044b now all tests work correctly 2024-09-05 13:50:59 +03:00
c577804946 concat test 2024-09-03 23:07:15 +03:00
7c1911bbda genprof 2024-09-03 19:49:27 +03:00
a49ce499e2 singel pixel test 2024-09-03 10:01:55 +03:00
a78768aeb8 description change 2024-08-20 19:05:56 +03:00
e6889deab6 unexpected stupidity 2024-08-20 10:11:29 +03:00
73d114ef5b readme 2024-08-20 10:07:01 +03:00
d4aec6bcaf tests 2024-08-20 09:04:11 +03:00
37 changed files with 1042 additions and 8 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
child_porn
imageutils.test
cpu.out
profile.out

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# imageutils
## that is exactly image utilities, what i made for myself
||(with a dummy as fuck image rescaling)||
utilities work in theory, but doing everything very slow.
i really love Go's image.Image interface type, but its really hard to do something with it well.
TODO:
- create main binary that give access to all implemented things in cmd/

View File

@@ -21,9 +21,9 @@ type pair struct {
}
// Takes color.Model of the first Image.
func (p pair) ColorModel() color.Model { return p.first.ColorModel() }
func (p *pair) ColorModel() color.Model { return p.first.ColorModel() }
func (p pair) Bounds() image.Rectangle {
func (p *pair) Bounds() image.Rectangle {
var (
b1 = p.first.Bounds()
b2 = p.second.Bounds()
@@ -52,7 +52,7 @@ func (p pair) Bounds() image.Rectangle {
}
}
func (p pair) At(x, y int) color.Color {
func (p *pair) At(x, y int) color.Color {
img := image.NewRGBA(p.Bounds())
point := image.Point{}
@@ -86,7 +86,7 @@ func (p pair) At(x, y int) color.Color {
return img.At(x, y)
}
func render(p pair) image.Image {
func render(p *pair) image.Image {
img := image.NewRGBA(p.Bounds())
point := image.Point{}
@@ -122,5 +122,5 @@ func render(p pair) image.Image {
// Concat concatenates second image on a given side.
func Concat(i1, i2 image.Image, s Side) image.Image {
return render(pair{i1, i2, s})
return render(&pair{i1, i2, s})
}

24
concat_test.go Normal file
View File

@@ -0,0 +1,24 @@
package imageutils
import (
"image"
"image/png"
"io"
"testing"
)
func BenchmarkConcat(b *testing.B) {
var instance image.Image = image.NewRGBA(image.Rect(0, 0, 0, 0))
pixel := SinglePixel{}
for i := 0; i < b.N; i++ {
instance = Concat(instance, pixel, Right)
}
err := png.Encode(
io.Discard,
instance,
)
if err != nil {
panic(err)
}
}

2
genprof Executable file
View File

@@ -0,0 +1,2 @@
go test -cpuprofile=profile.out -bench=$1 &&
go tool pprof -text profile.out

9
go.mod
View File

@@ -1,3 +1,8 @@
module github.com/potassium5703/imageutils
module git.nkpl.cc/twocookedfaggots/imageutils
go 1.21.4
go 1.25.5
require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
golang.org/x/image v0.36.0
)

4
go.sum Normal file
View File

@@ -0,0 +1,4 @@
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=

47
http/blank/blank.go Normal file
View File

@@ -0,0 +1,47 @@
package blank
import (
"image"
"image/color"
"git.nkpl.cc/twocookedfaggots/imageutils"
)
type blank struct {
image.Image
color.Color
imageutils.Side
}
func (b blank) ColorModel() color.Model { return b.Image.ColorModel() }
func (b blank) Bounds() image.Rectangle {
rect := image.Rectangle{}
switch b.Side {
case imageutils.Left:
fallthrough
case imageutils.Right:
rect = image.Rectangle{
image.ZP,
image.Point{
1, b.Image.Bounds().Dy(),
},
}
case imageutils.Up:
fallthrough
case imageutils.Down:
rect = image.Rectangle{
image.ZP,
image.Point{
b.Image.Bounds().Dx(), 1,
},
}
}
return rect
}
func (b blank) At(x, y int) color.Color { return b.Color }
func Blank(img image.Image, clr color.Color, side imageutils.Side) image.Image {
return imageutils.Concat(img, blank{img, clr, side}, side)
}

43
http/http.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import (
"net/http"
"log"
)
type imageTransform struct {
image.Image
TransformFunc func(image.Image) image.Image
}
type BlankFill struct {
}
func (t imageTransform) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
func indexHTML(w http.ResponseWriter, r *http.Request) {
f, err := os.Open("")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Fatalln("error opening file:", err)
return
}
_, err := io.Copy(w, f)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Println("copy error:", err)
return
}
}
func main() {
http.HandleFunc("/", indexHTML)
http.Handle("POST /upload", imageTransform)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Panicln(err)
}
}

4
http/index.html Normal file
View File

@@ -0,0 +1,4 @@
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="image" accept="image/*" required>
<button type="submit">Upload Image</button>
</form>

49
http/test.go Normal file
View File

@@ -0,0 +1,49 @@
//go:build ignore
// works as i wanted
package main
import (
"image"
"image/color"
"image/png"
"image/jpeg"
"os"
"git.nkpl.cc/twocookedfaggots/imageutils"
"git.nkpl.cc/twocookedfaggots/imageutils/http/blank"
)
func BlankFillWhite(img image.Image) image.Image {
dy := img.Bounds().Dy()
up, down := dy*2/3, dy*1/3
for i := 0; i < up; i++ {
img = blank.Blank(img, color.White, imageutils.Up)
}
for i := 0; i < down; i++ {
img = blank.Blank(img, color.White, imageutils.Down)
}
return img
}
func ProcessImageStdio(f func(image.Image) image.Image) error {
img, err := jpeg.Decode(os.Stdin)
if err != nil {
return err
}
img = f(img)
err = png.Encode(os.Stdout, img)
if err != nil {
return err
}
return nil
}
func main() {
err := ProcessImageStdio(
BlankFillWhite,
)
if err != nil {
panic(err)
}
}

43
pkg/dithering/8x8grid.go Normal file
View File

@@ -0,0 +1,43 @@
package dithering
import (
"image"
"image/color"
)
type grid struct {
color.Gray
}
func (_ grid) ColorModel() color.Model {
return color.RGBAModel
}
func (_ grid) Bounds() image.Rectangle {
return image.Rect(0, 0, 8, 8)
}
func (g grid) At(x, y int) color.Color {
step := int(64 / (g.Gray.Y / 4))
if (y*8+x)%step == 0 {
return color.Black
}
return color.White
}
type uniformGrid struct {
image.Image
image.Rectangle
}
func (g uniformGrid) ColorModel() color.Model {
return g.Image.ColorModel()
}
func (g uniformGrid) Bounds() image.Rectangle {
return g.Rectangle
}
func (g uniformGrid) At(x, y int) color.Color {
return color.Color(nil)
}

View File

@@ -0,0 +1,35 @@
package dithering
import (
"image/color"
"image/png"
"math/rand"
"os/exec"
"testing"
"time"
"git.nkpl.cc/twocookedfaggots/imageutils"
)
func TestGrid(t *testing.T) {
rand.Seed(time.Now().UnixNano())
command := exec.Command("imv", "-")
w, err := command.StdinPipe()
if err != nil {
t.Error(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()
if err != nil {
t.Error(err)
}
}

View File

@@ -0,0 +1,58 @@
package main
import (
"errors"
"image"
"image/color"
)
var errOutOfBounds error = errors.New("downscale: out of bounds")
type Downscaled struct {
image.Image
Factor int
}
func isOutOfBounds(pt image.Point, img image.Image) bool {
x, y := img.Bounds().Dx(), img.Bounds().Dy()
if pt.X > x || pt.Y > y {
return true
}
return false
}
func meanColor(palette color.Palette) color.Color {
var r, g, b int
for _, v := range palette {
c := color.RGBAModel.Convert(v).(color.RGBA)
r += int(c.R)
g += int(c.G)
b += int(c.B)
}
r, g, b = r/len(palette), g/len(palette), b/len(palette)
return color.RGBA{uint8(r), uint8(g), uint8(b), 0xff}
}
func (d Downscaled) ColorModel() color.Model { return d.Image.ColorModel() }
func (d Downscaled) Bounds() image.Rectangle {
return image.Rect(0, 0, d.Image.Bounds().Dx()/d.Factor, d.Image.Bounds().Dy()/d.Factor)
}
func (d Downscaled) At(x, y int) color.Color {
palette := color.Palette{}
pt := image.Point{x * d.Factor, y * d.Factor}
if isOutOfBounds(pt, d.Image) {
panic(errOutOfBounds)
}
for i := 0; i < d.Factor; i++ {
for j := 0; j < d.Factor; j++ {
currentPt := image.Point{pt.X + i, pt.Y + j}
if i > 0 || j > 0 {
if isOutOfBounds(currentPt, d.Image) {
continue
}
}
palette = append(palette, d.Image.At(currentPt.X, currentPt.Y))
}
}
return palette.Convert(meanColor(palette))
}

21
pkg/downscale/main.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import (
"image/jpeg"
"image/png"
"os"
)
func main() {
// open image
img, err := jpeg.Decode(os.Stdin)
if err != nil {
panic(err)
}
// no pre-processing needed so we just
// write image
err = png.Encode(os.Stdout, Downscaled{img, 3})
if err != nil {
panic(err)
}
}

41
pkg/grayscale/main.go Normal file
View File

@@ -0,0 +1,41 @@
package main
import (
"image"
"image/color"
"image/png"
"os"
)
// processImage takes input png image from stdin, processes it with f and outputs it to stdout.
func processImage(f func(image.Image) image.Image) error {
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
}
type convert struct {
image.Image
color.Model
}
func (c convert) ColorModel() color.Model { return c.Model }
func (c convert) Bounds() image.Rectangle { return c.Image.Bounds() }
func (c convert) At(x, y int) color.Color { return c.Model.Convert(c.Image.At(x, y)) }
func grayscale(img image.Image) image.Image {
return convert{img, color.GrayModel}
}
func main() {
err := processImage(grayscale)
if err != nil {
panic(err)
}
}

32
pkg/hex.go Normal file
View File

@@ -0,0 +1,32 @@
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))
}

47
pkg/hex_test.go Normal file
View File

@@ -0,0 +1,47 @@
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)))
}

BIN
pkg/mesh/8.clock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

41
pkg/mesh/9p.go Normal file
View File

@@ -0,0 +1,41 @@
//go:build ignore
package main
func ListenAndServe(addr string, filesystem fs.FS) error {
styx.HandlerFunc(func(s *styx.Session) {
for s.Next() {
switch msg := s.Request.(type) {
case styx.Twalk:
msg.Rwalk(fs.Stat(filesystem, msg.Path()))
case styx.Topen:
msg.Ropen(filesystem.Open(msg.Path()))
case styx.Tstat:
msg.Rstat(fs.Stat(filesystem, msg.Path()))
}
}
},
)
}
type FS struct {
meshFile
}
type meshFile struct {
}
type meshFileInfo struct {
bufio.Reader
time.Time
}
func (f meshFileInfo) Name() string { return "mesh" }
func (f meshFileInfo) Size() int64 { return f.Reader.Size() }
func (f meshFileInfo) Mode() fs.FileMode { return fs.ModePerm }
func (f meshFileInfo) ModTime() time.Time { return f.Time }
func (f meshFileInfo) IsDir() bool { return false }
func (f meshFileInfo) Sys() any { return nil }
func (f meshFile) Stat() (fs.FileInfo, error) {
}

91
pkg/mesh/mesh.go Normal file
View File

@@ -0,0 +1,91 @@
package main
import (
"flag"
"image"
"image/color"
"image/draw"
"image/png"
"os"
"time"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/font/gofont/gomono"
"golang.org/x/image/math/fixed"
// "github.com/droyo/styx"
// "io/fs"
)
type Mesh struct {
image.Image
X, Y int // divide
}
// using source's color.Model
func (mesh Mesh) ColorModel() color.Model { return mesh.Image.ColorModel() }
// using source's image.Rectangle
func (mesh Mesh) Bounds() image.Rectangle { return mesh.Image.Bounds() }
func (mesh Mesh) At(x, y int) color.Color {
dx, dy := mesh.Image.Bounds().Dx(), mesh.Image.Bounds().Dy()
if (x%(dx/mesh.X)) == 0 || (y%(dy/mesh.Y)) == 0 {
return color.Black
}
return mesh.Image.At(x, y)
}
func render(img image.Image) draw.Image {
dst := image.NewRGBA(img.Bounds())
draw.Draw(dst, dst.Bounds(), img, image.Point{}, draw.Src)
return dst
}
func main() {
// x := flag.Int("x", 2, "use to grid by x axis")
// y := flag.Int("y", 2, "use to grid by y axis")
flag.Parse() // now flags is ready to use
img, err := png.Decode(os.Stdin)
if err != nil {
panic(err)
}
// Mesh{ // our image modifyer
// Image: img,
// X: *x,
// Y: *y,
// },
dst := render(
img,
)
f, err := truetype.Parse(gomono.TTF)
if err != nil {
panic(err)
}
face := truetype.NewFace(f, &truetype.Options{
Size: float64(img.Bounds().Dx() / 19),
DPI: 72,
Hinting: font.HintingNone,
})
d := &font.Drawer{
Dst: dst,
Src: image.NewUniform(color.Black),
Face: face,
Dot: fixed.Point26_6{fixed.Int26_6(img.Bounds().Dx() /
9*64,
),
fixed.Int26_6(img.Bounds().Dy() /
5*64,
),
},
}
d.DrawString(time.Now().Format(time.Kitchen))
err = png.Encode(os.Stdout, dst)
if err != nil {
panic(err)
}
}

67
pkg/mkpalette/palette.go Normal file
View File

@@ -0,0 +1,67 @@
package main
import (
"encoding/csv"
"fmt"
"image/color"
"image/png"
"maps"
"os"
"slices"
imgutil "git.nkpl.cc/twocookedfaggots/imageutils/pkg"
)
func check(err error) {
if err != nil {
panic(err)
}
}
type Set[S comparable] map[S]struct{}
func (s Set[S]) Add(v S) {
s[v] = struct{}{}
}
func (s Set[S]) Contains(v S) bool {
_, ok := s[v]
return ok
}
func Map[S1 ~[]T1, S2 ~[]T2, T1, T2 any](s S1, f func(T1) T2) S2 {
result := make(S2, len(s))
for i := range s {
result[i] = f(s[i])
}
return result
}
type hex string
func (h hex) String() string {
return string(fmt.Sprintf("hex value: %s\n", string(h)))
}
func main() {
img, err := png.Decode(os.Stdin)
check(err)
p := Set[color.Color]{}
for y := 0; y < img.Bounds().Dy(); y++ {
for x := 0; x < img.Bounds().Dx(); x++ {
p.Add(img.At(x, y))
}
}
w := csv.NewWriter(os.Stdout)
s := Map[color.Palette, []string](
slices.Collect(maps.Keys(p)), imgutil.ColorToHex,
)
/*
h := make([]hex, 0, len(s))
for i := range s {
h = append(h, hex(s[i]))
}
*/
err = w.Write(s)
check(err)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
pkg/negate/minus19blue Executable file

Binary file not shown.

39
pkg/negate/minus19blue.go Normal file
View File

@@ -0,0 +1,39 @@
//go:build ignore
package main
import (
"image"
"image/color"
"image/png"
"os"
)
type Reshade struct{ image.Image }
func (rs Reshade) ColorModel() color.Model { return rs.Image.ColorModel() }
func (rs Reshade) Bounds() image.Rectangle { return rs.Image.Bounds() }
func (rs Reshade) At(x, y int) color.Color {
c := rs.Image.At(x, y)
r, g, b, a := c.RGBA()
// minus 19 blue
return color.RGBA{
uint8(r >> 8),
uint8(g >> 8),
uint8(b>>8) - 8*8,
uint8(a >> 8),
}
}
func check(err error) {
if err != nil {
panic(err)
}
}
func main() {
img, err := png.Decode(os.Stdin)
check(err)
err = png.Encode(os.Stdout, Reshade{img})
check(err)
}

70
pkg/negate/mixed.go Normal file
View File

@@ -0,0 +1,70 @@
package main
import (
"image"
"image/color"
"image/png"
"math/rand"
"os"
)
type MixedNegation struct {
src image.Image
counter int
}
func toRGBA(c color.Color) color.RGBA {
r, g, b, a := c.RGBA()
return color.RGBA{
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: uint8(a >> 8),
}
}
func negateRGBA(c color.RGBA) color.RGBA {
c.R = 255 - c.R
c.G = 255 - c.G
c.B = 255 - c.B
return c
}
func Negate(c color.Color) color.Color {
return negateRGBA(toRGBA(c))
}
func randomBool() bool {
return rand.Intn(3) == 0
}
func (mn MixedNegation) ColorModel() color.Model { return mn.src.ColorModel() }
func (mn MixedNegation) Bounds() image.Rectangle { return mn.src.Bounds() }
func (mn *MixedNegation) At(x, y int) color.Color {
if (x%1 != 0) && (y%8 != 0) {
if randomBool() {
return Negate(mn.src.At(x, y))
}
}
return mn.src.At(x, y)
}
func pixelCount(img image.Image) int {
return img.Bounds().Dx() * img.Bounds().Dy()
}
func main() {
img, err := png.Decode(os.Stdin)
if err != nil {
panic(err)
}
n := pixelCount(img)
println(n)
err = png.Encode(os.Stdout, &MixedNegation{src: img, counter: n})
if err != nil {
panic(err)
}
}

BIN
pkg/negate/negate Executable file

Binary file not shown.

44
pkg/negate/negate.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"image"
"image/color"
"image/png"
"os"
)
func NegateRGBA(c color.RGBA) color.RGBA {
c.R = 255 - c.R
c.G = 255 - c.G
c.B = 255 - c.B
return c
}
type Negate struct{ image.Image }
func (n Negate) ColorModel() color.Model { return n.Image.ColorModel() }
func (n Negate) Bounds() image.Rectangle { return n.Image.Bounds() }
func (n Negate) At(x, y int) color.Color {
c := n.Image.At(x, y)
r, g, b, a := c.RGBA()
RGBAColor := color.RGBA{
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: uint8(a >> 8),
}
return NegateRGBA(RGBAColor)
}
func check(err error) {
if err != nil {
panic(err)
}
}
func main() {
img, err := png.Decode(os.Stdin)
check(err)
err = png.Encode(os.Stdout, Negate{img})
check(err)
}

BIN
pkg/negate/negated5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 KiB

BIN
pkg/negate/negated8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

74
pkg/showpalette/show.go Normal file
View File

@@ -0,0 +1,74 @@
package main
import (
"encoding/csv"
"image"
"image/color"
"image/png"
"math"
"os"
imgutil "git.nkpl.cc/twocookedfaggots/imageutils/pkg"
)
func nearestHighSqrt(n int) int {
if math.Mod(math.Sqrt(float64(n)), 1) == 0 {
return n
}
return nearestHighSqrt(n + 1)
}
type showColors struct {
color.Palette
}
func (s showColors) ColorModel() color.Model {
return color.RGBAModel
}
func (s showColors) Bounds() image.Rectangle {
n := nearestHighSqrt(len(s.Palette))
return image.Rect(0, 0, n, n)
}
func (s showColors) At(x, y int) color.Color {
n := nearestHighSqrt(len(s.Palette))
return s.Palette[y*n+x]
}
func check(err error) {
if err != nil {
panic(err)
}
}
func Must[T any](v T, err error) T {
check(err)
return v
}
func Map[S1 ~[]T1, S2 ~[]T2, T1, T2 any](s S1, f func(T1) T2) S2 {
result := make(S2, len(s))
for i := range s {
result[i] = f(s[i])
}
return result
}
func main() {
r := csv.NewReader(os.Stdin)
r.TrailingComma = true
colors, err := r.Read()
check(err)
palette := color.Palette(
Map[[]string, color.Palette](colors,
func(v string) color.Color {
println(v + "cat-v")
return Must(imgutil.ParseHexColor(v))
},
),
)
err = png.Encode(os.Stdout, showColors{palette})
check(err)
}

1
pkg/texture Submodule

Submodule pkg/texture added at fce43fc4b0

View File

@@ -38,7 +38,7 @@ func (r rescaled) At(x, y int) color.Color {
// Scale scales image.Image to a given scale
// and then returns image.Image.
// For now it will work only with positive integers.
func Scale(img image.Image, scale int) image.Image {
func Scale(scale int, img image.Image) image.Image {
if scale < 1 {
scale = 1
}

60
scale_test.go Normal file
View File

@@ -0,0 +1,60 @@
package imageutils
import (
"image"
"image/color"
"image/draw"
"image/png"
"io"
"testing"
"git.nkpl.cc/twocookedfaggots/imageutils/pkg/texture"
)
func Render(img image.Image, rect image.Rectangle) image.Image {
newimg := image.NewRGBA(rect)
draw.Draw(newimg, rect, image.White, image.ZP, draw.Src)
draw.Draw(newimg, rect, img, image.ZP, draw.Over)
return newimg
}
func BenchmarkScale(b *testing.B) {
for i := 0; i < b.N; i++ {
err := png.Encode(io.Discard,
Scale(64, Render(
texture.New(color.White,
color.Black, 2),
image.Rect(0, 0, 64, 64),
)),
)
if err != nil {
panic(err)
}
}
}
type SinglePixel struct{}
func (s SinglePixel) At(x, y int) color.Color {
return color.White
}
func (s SinglePixel) ColorModel() color.Model {
return color.RGBAModel
}
func (s SinglePixel) Bounds() image.Rectangle {
return image.Rect(0, 0, 1, 1)
}
func BenchmarkSinglePixel(b *testing.B) {
instance := SinglePixel{}
err := png.Encode(io.Discard,
Scale(b.N, instance),
)
if err != nil {
panic(err)
}
}

32
util/hex.go Normal file
View File

@@ -0,0 +1,32 @@
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))
}

47
util/hex_test.go Normal file
View File

@@ -0,0 +1,47 @@
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)))
}