Skip to content

Commit 9d456fe

Browse files
committed
vector: bug fix: a callback for an image and its sub-image should be treated correctly
Closes #3355
1 parent 9f8f506 commit 9d456fe

File tree

4 files changed

+55
-3
lines changed

4 files changed

+55
-3
lines changed

image.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,14 @@ func (*Image) private() {
16051605

16061606
var currentCallbackToken atomic.Int64
16071607

1608+
//go:linkname originalImage
1609+
func originalImage(img *Image) *Image {
1610+
if img.isSubImage() {
1611+
return img.original
1612+
}
1613+
return img
1614+
}
1615+
16081616
//go:linkname addUsageCallback
16091617
func addUsageCallback(img *Image, callback func(image *Image)) int64 {
16101618
return img.addUsageCallback(img, callback)

vector/fill.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package vector
1616

1717
import (
1818
"fmt"
19+
"image"
1920
"slices"
2021

2122
"github.com/hajimehoshi/ebiten/v2"
@@ -129,6 +130,7 @@ var theAtlas atlas
129130
type fillPathsState struct {
130131
paths []*Path
131132
colors []ebiten.ColorScale
133+
bounds []image.Rectangle
132134

133135
vertices []ebiten.Vertex
134136
indices []uint32
@@ -143,10 +145,11 @@ func (f *fillPathsState) reset() {
143145
p.Reset()
144146
}
145147
f.paths = f.paths[:0]
148+
f.bounds = f.bounds[:0]
146149
f.colors = slices.Delete(f.colors, 0, len(f.colors))
147150
}
148151

149-
func (f *fillPathsState) addPath(path *Path, clr ebiten.ColorScale) {
152+
func (f *fillPathsState) addPath(path *Path, bounds image.Rectangle, clr ebiten.ColorScale) {
150153
if path == nil {
151154
return
152155
}
@@ -163,6 +166,7 @@ func (f *fillPathsState) addPath(path *Path, clr ebiten.ColorScale) {
163166
dst.subPaths[i].ops = slices.Grow(dst.subPaths[i].ops, len(subPath.ops))[:len(subPath.ops)]
164167
copy(dst.subPaths[i].ops, subPath.ops)
165168
}
169+
f.bounds = append(f.bounds, bounds)
166170
f.colors = append(f.colors, clr)
167171
}
168172

@@ -481,6 +485,10 @@ func (f *fillPathsState) fillPaths(dst *ebiten.Image) {
481485
panic(fmt.Sprintf("vector: failed to create stencil buffer even-odd shader: %v", err))
482486
}
483487
}
484-
dst.DrawTrianglesShader32(vs, is, shader, op)
488+
dst2 := dst
489+
if dst.Bounds() != f.bounds[i] {
490+
dst2 = dst.SubImage(f.bounds[i]).(*ebiten.Image)
491+
}
492+
dst2.DrawTrianglesShader32(vs, is, shader, op)
485493
}
486494
}

vector/util.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@ func FillPath(dst *ebiten.Image, path *Path, fillOptions *FillOptions, drawPathO
353353
defer theFillPathM.Unlock()
354354

355355
// Remove the previous registered callbacks.
356+
bounds := dst.Bounds()
357+
// Get the original image if dst is a sub-image to integrate the callbacks.
358+
dst = originalImage(dst)
356359
if token, ok := theCallbackTokens[dst]; ok {
357360
removeUsageCallback(dst, token)
358361
}
@@ -369,13 +372,17 @@ func FillPath(dst *ebiten.Image, path *Path, fillOptions *FillOptions, drawPathO
369372
s.antialias = drawPathOptions.AntiAlias
370373
s.blend = drawPathOptions.Blend
371374
s.fillRule = fillOptions.FillRule
372-
s.addPath(path, drawPathOptions.ColorScale)
375+
s.addPath(path, bounds, drawPathOptions.ColorScale)
373376

374377
// Use an independent callback function to avoid unexpected captures.
375378
theCallbackTokens[dst] = addUsageCallback(dst, fillPathCallback)
376379
}
377380

378381
func fillPathCallback(dst *ebiten.Image) {
382+
if originalImage(dst) != dst {
383+
panic("vector: dst must be the original image")
384+
}
385+
379386
theFillPathM.Lock()
380387
defer theFillPathM.Unlock()
381388

@@ -404,6 +411,9 @@ func StrokePath(dst *ebiten.Image, path *Path, strokeOptions *StrokeOptions, dra
404411
FillPath(dst, &stroke, nil, drawPathOptions)
405412
}
406413

414+
//go:linkname originalImage github.com/hajimehoshi/ebiten/v2.originalImage
415+
func originalImage(img *ebiten.Image) *ebiten.Image
416+
407417
//go:linkname addUsageCallback github.com/hajimehoshi/ebiten/v2.addUsageCallback
408418
func addUsageCallback(img *ebiten.Image, fn func(img *ebiten.Image)) int64
409419

vector/util_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,29 @@ func TestRaceConditionWithSubImage(t *testing.T) {
170170
}
171171
wg.Wait()
172172
}
173+
174+
// Issue #3355
175+
func TestFillPathSubImageAndImage(t *testing.T) {
176+
dst := ebiten.NewImage(200, 200)
177+
defer dst.Deallocate()
178+
for i := range 100 {
179+
var path vector.Path
180+
path.LineTo(0, 0)
181+
path.LineTo(0, 100)
182+
path.LineTo(100, 100)
183+
path.LineTo(100, 0)
184+
path.LineTo(0, 0)
185+
path.Close()
186+
drawOp := &vector.DrawPathOptions{}
187+
drawOp.ColorScale.ScaleWithColor(color.RGBA{255, 0, 0, 255})
188+
subDst := dst.SubImage(image.Rect(0, 0, 100, 100)).(*ebiten.Image)
189+
vector.FillPath(subDst, &path, nil, drawOp)
190+
drawOp.ColorScale.Reset()
191+
drawOp.ColorScale.ScaleWithColor(color.RGBA{0, 255, 0, 255})
192+
vector.FillPath(dst, &path, nil, drawOp)
193+
194+
if got, want := dst.At(50, 50), (color.RGBA{0, 255, 0, 255}); got != want {
195+
t.Errorf("%d: got: %v, want: %v", i, got, want)
196+
}
197+
}
198+
}

0 commit comments

Comments
 (0)