Compare commits
11 Commits
1.0.1
...
e5c0537f2b
| Author | SHA1 | Date | |
|---|---|---|---|
| e5c0537f2b | |||
| c25b65a5f6 | |||
| 1122be9007 | |||
| dc5f2466d1 | |||
| 0bbccec989 | |||
| c5eefdec1f | |||
| 352631e071 | |||
| cc5c9c6d1a | |||
| d52658ab5c | |||
| 1db672d481 | |||
| 02ece86303 |
3
README
3
README
@@ -1,3 +0,0 @@
|
||||
(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.
|
||||
9
README.md
Normal file
9
README.md
Normal 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/
|
||||
9
go.mod
9
go.mod
@@ -1,5 +1,8 @@
|
||||
module git.niplace.ru/XoxJlopeZi4BB/imageutils
|
||||
module git.nkpl.cc/twocookedfaggots/imageutils
|
||||
|
||||
go 1.22.6
|
||||
go 1.25.5
|
||||
|
||||
require github.com/potassium5703/texture v0.0.0-20240820054037-fce43fc4b0f0 // indirect
|
||||
require (
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
golang.org/x/image v0.36.0
|
||||
)
|
||||
|
||||
6
go.sum
6
go.sum
@@ -1,2 +1,4 @@
|
||||
github.com/potassium5703/texture v0.0.0-20240820054037-fce43fc4b0f0 h1:sowjIIVme5ovcdB0kjP3w+4xbVNrlOPdx7Up4LIGJz8=
|
||||
github.com/potassium5703/texture v0.0.0-20240820054037-fce43fc4b0f0/go.mod h1:KwM7hMpZhr3XySWuK/SZ7s1BXVQe8p1IyZftYg9KtWY=
|
||||
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
47
http/blank/blank.go
Normal 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
43
http/http.go
Normal 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
4
http/index.html
Normal 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
49
http/test.go
Normal 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
43
pkg/dithering/8x8grid.go
Normal 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)
|
||||
}
|
||||
35
pkg/dithering/8x8grid_test.go
Normal file
35
pkg/dithering/8x8grid_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
58
pkg/downscale/downscale.go
Normal file
58
pkg/downscale/downscale.go
Normal 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
21
pkg/downscale/main.go
Normal 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
41
pkg/grayscale/main.go
Normal 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
32
pkg/hex.go
Normal 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
47
pkg/hex_test.go
Normal 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
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
41
pkg/mesh/9p.go
Normal 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
91
pkg/mesh/mesh.go
Normal 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
67
pkg/mkpalette/palette.go
Normal 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)
|
||||
}
|
||||
BIN
pkg/negate/8.absolutecinema.png
Normal file
BIN
pkg/negate/8.absolutecinema.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
pkg/negate/minus19blue
Executable file
BIN
pkg/negate/minus19blue
Executable file
Binary file not shown.
39
pkg/negate/minus19blue.go
Normal file
39
pkg/negate/minus19blue.go
Normal 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
70
pkg/negate/mixed.go
Normal 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
BIN
pkg/negate/negate
Executable file
Binary file not shown.
44
pkg/negate/negate.go
Normal file
44
pkg/negate/negate.go
Normal 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
BIN
pkg/negate/negated5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 977 KiB |
BIN
pkg/negate/negated8.png
Normal file
BIN
pkg/negate/negated8.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
pkg/negate/reshaded,negated8.png
Normal file
BIN
pkg/negate/reshaded,negated8.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
74
pkg/showpalette/show.go
Normal file
74
pkg/showpalette/show.go
Normal 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
1
pkg/texture
Submodule
Submodule pkg/texture added at fce43fc4b0
2
scale.go
2
scale.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/potassium5703/texture"
|
||||
"git.nkpl.cc/twocookedfaggots/imageutils/pkg/texture"
|
||||
)
|
||||
|
||||
func Render(img image.Image, rect image.Rectangle) image.Image {
|
||||
@@ -21,11 +21,11 @@ func Render(img image.Image, rect image.Rectangle) image.Image {
|
||||
func BenchmarkScale(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := png.Encode(io.Discard,
|
||||
Scale(Render(
|
||||
Scale(64, Render(
|
||||
texture.New(color.White,
|
||||
color.Black, 2),
|
||||
image.Rect(0, 0, 64, 64),
|
||||
), 64),
|
||||
)),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -33,10 +33,25 @@ func BenchmarkScale(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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(instance, b.N),
|
||||
Scale(b.N, instance),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
20
util.go
20
util.go
@@ -1,20 +0,0 @@
|
||||
package imageutils
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
32
util/hex.go
Normal file
32
util/hex.go
Normal 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
47
util/hex_test.go
Normal 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)))
|
||||
}
|
||||
Reference in New Issue
Block a user