Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6651de5
go: Bump versions to current supported versions 1.25.x and 1.26.x
peterhellberg May 28, 2026
4fb35cd
rand: Replaced the deprecated rand.Seed call with a package-level *ra…
peterhellberg May 28, 2026
ef23411
palette: Palette.Random now calls gfx.RandIntn instead of rand.Intn
peterhellberg May 28, 2026
f91ee3a
draw: Replaced the m.X != w / m.Y != h termination with r.Min.X < r.M…
peterhellberg May 28, 2026
95d8982
draw: Renamed DrawCicleFast → DrawCircleFast
peterhellberg May 28, 2026
ac9042e
gfx: Replace interface{} with any
peterhellberg May 28, 2026
97f4be0
draw_int: Removed bare return statements in DrawIntRectangle and Draw…
peterhellberg May 28, 2026
9ee3ca7
image: Replaced the per-pixel DrawColorOver path with an inline premu…
peterhellberg May 28, 2026
edd3642
gfx: Moved HTTP related code into new gfxhttp package
peterhellberg May 28, 2026
1cb4189
gfxhttp: Thread context.Context through all requests
peterhellberg May 28, 2026
f215358
math: Make Sign, Clamp, and Lerp generic via new Numeric, Signed, and…
peterhellberg May 28, 2026
82af7dd
sort: Replace SortSlice reflection wrapper with generic slices.SortFu…
peterhellberg May 28, 2026
f3d2792
license: Update copyright year
peterhellberg May 28, 2026
1991273
doc: Update copyright year
peterhellberg May 28, 2026
b30e03a
examples: Update examples
peterhellberg May 28, 2026
b165b95
block: Color every vertex in Block.Triangles so faces render uniforml…
peterhellberg May 28, 2026
d7c2d82
triangle: Round Triangle.Bounds Max up so adjacent triangles meet wit…
peterhellberg May 28, 2026
140828d
paletted: Honor Rect.Min in Paletted.PixOffset so non-origin Paletted…
peterhellberg May 28, 2026
4c18041
gfx: Drop legacy // +build directives now that go.mod is on Go 1.25
peterhellberg May 28, 2026
4218a5b
examples: Use gfx.V and keyed SignedDistance literals to satisfy go v…
peterhellberg May 28, 2026
befd7c9
gfx: Modernize counted for loops to range-over-int (go fix)
peterhellberg May 28, 2026
a1e4ddf
examples/gfx-example-animation: Slice 5 palette colors so the flower …
peterhellberg May 28, 2026
123a884
draw: Use the triangle's first vertex color in DrawTrianglesWireframe…
peterhellberg May 28, 2026
9c0c846
paletted: Bounds-check ColorIndexAt and SetColorIndex so out-of-range…
peterhellberg May 28, 2026
28c9ac9
block: Round Block.DrawBounds Max up so fractional Rect edges don't l…
peterhellberg May 28, 2026
77f6bc3
gfxhttp: Add tests for raw Get response, GetImage decode path, WithHT…
peterhellberg May 28, 2026
51ccd75
doc: List WithHeader alongside the other gfxhttp options
peterhellberg May 28, 2026
e04f05b
gfx: Drop !tinygo build constraints from files whose imports TinyGo n…
peterhellberg May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
test:
strategy:
matrix:
go-version: ['1.24.x', '1.23.x']
go-version: ['1.26.x', '1.25.x']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2019-2025 Peter Hellberg - https://c7.se
Copyright (c) 2019-2026 Peter Hellberg - https://c7.se

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Expand Down
53 changes: 35 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ var edg32 = gfx.PaletteEDG32
func main() {
m := gfx.NewNRGBA(gfx.IR(0, 0, 1024, 256))
p := gfx.Polygon{
{80, 40},
{440, 60},
{700, 200},
{250, 230},
{310, 140},
gfx.V(80, 40),
gfx.V(440, 60),
gfx.V(700, 200),
gfx.V(250, 230),
gfx.V(310, 140),
}

p.EachPixel(m, func(x, y int) {
Expand Down Expand Up @@ -141,7 +141,7 @@ func main() {
m := gfx.NewImage(1024, 256, c(5))

gfx.EachPixel(m.Bounds(), func(x, y int) {
sd := gfx.SignedDistance{gfx.IV(x, y)}
sd := gfx.SignedDistance{Vec: gfx.IV(x, y)}

if d := sd.OpRepeat(gfx.V(128, 128), func(sd gfx.SignedDistance) float64 {
return sd.OpSubtraction(sd.Circle(50), sd.Line(gfx.V(0, 0), gfx.V(64, 64)))
Expand Down Expand Up @@ -193,10 +193,10 @@ func main() {
m = gfx.NewImage(w, h)
)

for py := 0; py < h; py++ {
for py := range h {
x := p0.X

for px := 0; px < w; px++ {
for px := range w {
cc := p.CmplxPhaseAt(gfx.CmplxCos(gfx.CmplxSin(0.42 / complex(y*x, x*x))))

m.Set(px, py, cc)
Expand Down Expand Up @@ -242,7 +242,7 @@ func main() {
}

for i := 0; i < len(p)-4; i++ {
t := gfx.NewTile(p[i:i+4], 8, fireflower)
t := gfx.NewTile(p[i:i+5], 8, fireflower)

a.AddPalettedImage(gfx.NewScaledPalettedImage(t, 20))
}
Expand Down Expand Up @@ -369,11 +369,11 @@ func main() {
c := gfx.V(128, 128)

p := gfx.Polygon{
{50, 50},
{50, 206},
{128, 96},
{206, 206},
{206, 50},
gfx.V(50, 50),
gfx.V(50, 206),
gfx.V(128, 96),
gfx.V(206, 206),
gfx.V(206, 50),
}

for d := 0.0; d < 360; d += 2 {
Expand Down Expand Up @@ -411,7 +411,20 @@ The `gfx.Error` type is a string that implements the `error` interface.

## HTTP

You can use `gfx.GetPNG` to download and decode a PNG given an URL.
HTTP helpers live in the `gfxhttp` subpackage so that importers of `gfx`
who do not need them are not forced to pull in `net/http` (and its
transitive `crypto/tls`, `crypto/x509`, `net`, etc.).

```go
import "github.com/peterhellberg/gfx/gfxhttp"

img, err := gfxhttp.GetPNG(ctx, "https://example.com/sprite.png")
```

`gfxhttp.NewClient` accepts options like `WithUserAgent`, `WithTimeout`,
`WithHTTPClient` and `WithHeader`. Non-2xx responses are returned as
`*gfxhttp.StatusError`. Map tile servers are available through
`gfxhttp.TileServer`.

## Log

Expand All @@ -421,9 +434,13 @@ when experimenting with graphical effects, so I've included `gfx.Log`,

## Math

I have included a few functions that call functions in the `math` package.
I have included a few functions that call functions in the `math` package
(`gfx.MathMin`, `gfx.MathMax`, `gfx.MathAbs`, `gfx.MathSqrt`, …) so that you
don't have to import `math` yourself.

There is also `gfx.Sign`, `gfx.Clamp` and `gfx.Lerp` functions for `float64`.
`gfx.Sign`, `gfx.Clamp` and `gfx.Lerp` are generic: `gfx.Sign` and `gfx.Lerp`
work on any numeric type (the `gfx.Numeric`/`gfx.Signed` constraints) and
`gfx.Clamp` works on any `cmp.Ordered` type.

## Cmplx

Expand Down Expand Up @@ -581,7 +598,7 @@ func main() {

## License (MIT)

Copyright (c) 2019-2025 [Peter Hellberg](https://c7.se)
Copyright (c) 2019-2026 [Peter Hellberg](https://c7.se)

> Permission is hereby granted, free of charge, to any person obtaining
> a copy of this software and associated documentation files (the
Expand Down
3 changes: 0 additions & 3 deletions animation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

import (
Expand Down
3 changes: 0 additions & 3 deletions base64.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

import (
Expand Down
3 changes: 0 additions & 3 deletions batch.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

import "image/color"
Expand Down
28 changes: 17 additions & 11 deletions block.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

import (
Expand Down Expand Up @@ -80,12 +77,12 @@ func (b Block) Triangles(origin Vec3) []Triangle {
c, l, m, d := b.Corners(origin), b.Color.Light, b.Color.Medium, b.Color.Dark

return []Triangle{
T(Vx(c.FrontUp, d), Vx(c.LeftDown), Vx(c.LeftUp)),
T(Vx(c.FrontUp, d), Vx(c.FrontDown), Vx(c.LeftDown)),
T(Vx(c.FrontUp, m), Vx(c.FrontDown), Vx(c.RightDown)),
T(Vx(c.FrontUp, m), Vx(c.RightUp), Vx(c.RightDown)),
T(Vx(c.FrontUp, l), Vx(c.BackUp), Vx(c.LeftUp)),
T(Vx(c.FrontUp, l), Vx(c.BackUp), Vx(c.RightUp)),
T(Vx(c.FrontUp, d), Vx(c.LeftDown, d), Vx(c.LeftUp, d)),
T(Vx(c.FrontUp, d), Vx(c.FrontDown, d), Vx(c.LeftDown, d)),
T(Vx(c.FrontUp, m), Vx(c.FrontDown, m), Vx(c.RightDown, m)),
T(Vx(c.FrontUp, m), Vx(c.RightUp, m), Vx(c.RightDown, m)),
T(Vx(c.FrontUp, l), Vx(c.BackUp, l), Vx(c.LeftUp, l)),
T(Vx(c.FrontUp, l), Vx(c.BackUp, l), Vx(c.RightUp, l)),
}
}

Expand Down Expand Up @@ -128,14 +125,23 @@ func (b Block) DrawRectangles(dst draw.Image, origin Vec3) {
func (b Block) DrawBounds(dst draw.Image, origin Vec3) {
r := b.Rect(origin)

// Round Min down and Max up so the rectangle covers every pixel
// the float Rect touches — otherwise a fractional right/bottom edge
// loses a column or row through int() truncation.
minX := int(math.Floor(r.Min.X))
minY := int(math.Floor(r.Min.Y))
maxX := int(math.Floor(r.Max.X)) + 1
maxY := int(math.Floor(r.Max.Y)) + 1

if r.Area() < 20 {
DrawColor(dst, r.Bounds(), b.Color.Medium)
DrawColor(dst, IR(minX, minY, maxX, maxY), b.Color.Medium)
return
}

rw6 := r.W() / 6
topMaxY := int(math.Floor(r.Max.Y-rw6)) + 1

DrawColor(dst, IR(int(r.Min.X), int(r.Min.Y), int(r.Max.X), int(r.Max.Y-rw6)), b.Color.Light)
DrawColor(dst, IR(minX, minY, maxX, topMaxY), b.Color.Light)

DrawLineBresenham(dst, r.Min, V(r.Max.X, r.Min.Y), b.Color.Medium)
DrawLineBresenham(dst, r.Min, V(r.Min.X, r.Max.Y-rw6), b.Color.Dark)
Expand Down
3 changes: 0 additions & 3 deletions blocks.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

import (
Expand Down
3 changes: 0 additions & 3 deletions box.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

// Box is a 3D cuboid with a min and max Vec3
Expand Down
3 changes: 0 additions & 3 deletions cie_lab.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

import "math"
Expand Down
3 changes: 0 additions & 3 deletions decode.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

import (
Expand Down
22 changes: 10 additions & 12 deletions draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@ func DrawOver(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point

// DrawPalettedImage draws a PalettedImage over a PalettedDrawImage.
func DrawPalettedImage(dst PalettedDrawImage, r image.Rectangle, src PalettedImage) {
w, h, m := r.Dx(), r.Dy(), r.Min

for x := m.X; x != w; x++ {
for y := m.Y; y != h; y++ {
for x := r.Min.X; x < r.Max.X; x++ {
for y := r.Min.Y; y < r.Max.Y; y++ {
if src.AlphaAt(x, y) > 0 {
dst.SetColorIndex(x, y, src.ColorIndexAt(x, y))
}
Expand All @@ -47,10 +45,8 @@ func DrawPalettedImage(dst PalettedDrawImage, r image.Rectangle, src PalettedIma
// DrawPalettedLayer draws a *Layer over a *Paletted.
// (slightly faster than using the generic DrawPalettedImage)
func DrawPalettedLayer(dst *Paletted, r image.Rectangle, src *Layer) {
w, h, m := r.Dx(), r.Dy(), r.Min

for x := m.X; x != w; x++ {
for y := m.Y; y != h; y++ {
for x := r.Min.X; x < r.Max.X; x++ {
for y := r.Min.Y; y < r.Max.Y; y++ {
if src.AlphaAt(x, y) > 0 {
dst.SetColorIndex(x, y, src.ColorIndexAt(x, y))
}
Expand Down Expand Up @@ -83,10 +79,12 @@ func DrawTrianglesOver(dst draw.Image, triangles []Triangle) {
}
}

// DrawTrianglesWireframe draws triangles on dst.
// DrawTrianglesWireframe draws triangles on dst using each triangle's
// first vertex color.
func DrawTrianglesWireframe(dst draw.Image, triangles []Triangle) {
for _, t := range triangles {
t.DrawWireframe(dst, t.Color(V(0, 0)))
a, _, _ := t.Colors()
t.DrawWireframe(dst, a)
}
}

Expand Down Expand Up @@ -123,8 +121,8 @@ func DrawCircleFilled(dst draw.Image, u Vec, radius float64, c color.Color) {
})
}

// DrawCicleFast draws a (crude) filled circle.
func DrawCicleFast(dst draw.Image, u Vec, radius float64, c color.Color) {
// DrawCircleFast draws a (crude) filled circle.
func DrawCircleFast(dst draw.Image, u Vec, radius float64, c color.Color) {
ir := int(radius)
r2 := ir * ir
pt := u.Pt()
Expand Down
4 changes: 0 additions & 4 deletions draw_int.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ func DrawIntRectangle(dst draw.Image, x, y, w, h int, c color.Color) {
DrawIntLine(dst, x, y, x, y+h-1, c)
DrawIntLine(dst, x+w-1, y, x+w-1, y+h-1, c)
DrawIntLine(dst, x, y+h-1, x+w-1, y+h-1, c)

return
}

// DrawIntFilledRectangle draws a filled rectangle given a point, width and height
Expand All @@ -97,8 +95,6 @@ func DrawIntFilledRectangle(dst draw.Image, x, y, w, h int, c color.Color) {
for i := x; i < x+w; i++ {
DrawIntLine(dst, i, y, i, y+h-1, c)
}

return
}

// DrawIntCircle draws a circle given a point and radius
Expand Down
3 changes: 0 additions & 3 deletions draw_target.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

import (
Expand Down
53 changes: 50 additions & 3 deletions draw_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build !tinygo
// +build !tinygo

package gfx

Expand Down Expand Up @@ -36,6 +35,54 @@ func TestDrawLayerOverPaletted(t *testing.T) {
DrawPalettedLayer(dst, dst.Bounds(), src)
}

func TestDrawPalettedImageNonOriginRect(t *testing.T) {
src := NewPaletted(8, 16, PaletteEN4)
for i := range src.Pix {
src.Pix[i] = 1
}

dst := NewPaletted(8, 16, PaletteEN4)
r := IR(2, 4, 6, 10)

DrawPalettedImage(dst, r, src)

for y := range 16 {
for x := range 8 {
inside := x >= r.Min.X && x < r.Max.X && y >= r.Min.Y && y < r.Max.Y
got := dst.ColorIndexAt(x, y)
want := uint8(0)
if inside {
want = 1
}
if got != want {
t.Fatalf("dst.ColorIndexAt(%d, %d) = %d, want %d (inside=%v)", x, y, got, want, inside)
}
}
}
}

func TestDrawPalettedLayerNonOriginRect(t *testing.T) {
src := newTestLayer()
dst := NewPaletted(16, 12, PaletteEN4)
r := IR(4, 4, 12, 8)

DrawPalettedLayer(dst, r, src)

for y := range 12 {
for x := range 16 {
inside := x >= r.Min.X && x < r.Max.X && y >= r.Min.Y && y < r.Max.Y
got := dst.ColorIndexAt(x, y)
if inside {
if want := src.ColorIndexAt(x, y); got != want {
t.Fatalf("dst.ColorIndexAt(%d, %d) = %d, want %d (from src)", x, y, got, want)
}
} else if got != 0 {
t.Fatalf("dst.ColorIndexAt(%d, %d) = %d, want 0 (outside r)", x, y, got)
}
}
}
}

func TestDrawLine(t *testing.T) {
dst := NewImage(32, 32)

Expand Down Expand Up @@ -85,10 +132,10 @@ func TestDrawFilledCircle(t *testing.T) {
DrawCircleFilled(dst, V(16, 16), 8, ColorMagenta)
}

func TestDrawFastFilledCircle(t *testing.T) {
func TestDrawCircleFast(t *testing.T) {
dst := NewImage(32, 32)

DrawCicleFast(dst, V(16, 16), 8, ColorMagenta)
DrawCircleFast(dst, V(16, 16), 8, ColorMagenta)
}

func TestDrawPointCircle(t *testing.T) {
Expand Down
3 changes: 0 additions & 3 deletions drawer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !tinygo
// +build !tinygo

package gfx

// Drawer glues all the fundamental interfaces (Target, Triangles, Picture) into a coherent and the
Expand Down
2 changes: 1 addition & 1 deletion errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ func (e Error) Error() string {
}

// Errorf constructs a formatted error.
func Errorf(format string, a ...interface{}) error {
func Errorf(format string, a ...any) error {
return Error(Sprintf(format, a...))
}
Binary file modified examples/gfx-example-animation/gfx-example-animation.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading