Skip to content

Commit 2ae1967

Browse files
committed
gpu,op/paint: add paint.RadialGradientOp
Signed-off-by: Egon Elbre <[email protected]>
1 parent 82f63d2 commit 2ae1967

File tree

7 files changed

+157
-8
lines changed

7 files changed

+157
-8
lines changed

gpu/gpu.go

+70-8
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,13 @@ type drawState struct {
107107
// Current paint.ColorOp, if any.
108108
color color.NRGBA
109109

110-
// Current paint.LinearGradientOp.
110+
// Current paint.LinearGradientOp and paint.RadialGradientOp.
111111
stop1 f32.Point
112112
stop2 f32.Point
113113
color1 color.NRGBA
114114
color2 color.NRGBA
115+
// Current paint.RadialGradientOp.
116+
radiusy float32
115117
}
116118

117119
type pathOp struct {
@@ -187,9 +189,11 @@ type material struct {
187189
opaque bool
188190
// For materialTypeColor.
189191
color f32color.RGBA
190-
// For materialTypeLinearGradient.
192+
// For materialTypeLinearGradient and materialTypeRadialGradient.
191193
color1 f32color.RGBA
192194
color2 f32color.RGBA
195+
// For materialTypeRadialGradient.
196+
radiusy float32
193197
// For materialTypeTexture.
194198
data imageOpData
195199
uvTrans f32.Affine2D
@@ -212,9 +216,20 @@ type imageOpData struct {
212216
}
213217

214218
type linearGradientOpData struct {
215-
stop1 f32.Point
219+
stop1 f32.Point
220+
stop2 f32.Point
221+
222+
color1 color.NRGBA
223+
color2 color.NRGBA
224+
}
225+
226+
type radialGradientOpData struct {
227+
stop1 f32.Point
228+
stop2 f32.Point
229+
230+
radiusy float32
231+
216232
color1 color.NRGBA
217-
stop2 f32.Point
218233
color2 color.NRGBA
219234
}
220235

@@ -294,6 +309,36 @@ func decodeLinearGradientOp(data []byte) linearGradientOpData {
294309
}
295310
}
296311

312+
func decodeRadialGradientOp(data []byte) radialGradientOpData {
313+
if opconst.OpType(data[0]) != opconst.TypeRadialGradient {
314+
panic("invalid op")
315+
}
316+
bo := binary.LittleEndian
317+
return radialGradientOpData{
318+
stop1: f32.Point{
319+
X: math.Float32frombits(bo.Uint32(data[1:])),
320+
Y: math.Float32frombits(bo.Uint32(data[5:])),
321+
},
322+
stop2: f32.Point{
323+
X: math.Float32frombits(bo.Uint32(data[9:])),
324+
Y: math.Float32frombits(bo.Uint32(data[13:])),
325+
},
326+
radiusy: math.Float32frombits(bo.Uint32(data[17:])),
327+
color1: color.NRGBA{
328+
R: data[21+0],
329+
G: data[21+1],
330+
B: data[21+2],
331+
A: data[21+3],
332+
},
333+
color2: color.NRGBA{
334+
R: data[25+0],
335+
G: data[25+1],
336+
B: data[25+2],
337+
A: data[25+3],
338+
},
339+
}
340+
}
341+
297342
type clipType uint8
298343

299344
type resource interface {
@@ -975,6 +1020,14 @@ loop:
9751020
state.stop2 = op.stop2
9761021
state.color1 = op.color1
9771022
state.color2 = op.color2
1023+
case opconst.TypeRadialGradient:
1024+
state.matType = materialRadialGradient
1025+
op := decodeRadialGradientOp(encOp.Data)
1026+
state.stop1 = op.stop1
1027+
state.stop2 = op.stop2
1028+
state.radiusy = op.radiusy
1029+
state.color1 = op.color1
1030+
state.color2 = op.color2
9781031
case opconst.TypeImage:
9791032
state.matType = materialTexture
9801033
state.image = decodeImageOp(encOp.Data, encOp.Refs)
@@ -1086,15 +1139,15 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
10861139
m.color2 = f32color.LinearFromSRGB(d.color2)
10871140
m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0
10881141

1089-
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
1142+
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2, -1))
10901143
case materialRadialGradient:
10911144
m.material = materialRadialGradient
10921145

10931146
m.color1 = f32color.LinearFromSRGB(d.color1)
10941147
m.color2 = f32color.LinearFromSRGB(d.color2)
10951148
m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0
10961149

1097-
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
1150+
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2, d.radiusy))
10981151
case materialTexture:
10991152
m.material = materialTexture
11001153
dr := boundRectF(rect.Add(off))
@@ -1286,19 +1339,28 @@ func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Poin
12861339
}
12871340

12881341
// gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
1289-
func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D {
1342+
func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point, radiusy float32) f32.Affine2D {
12901343
d := stop2.Sub(stop1)
12911344
l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
12921345
a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
12931346

1347+
scalex := 1 / l
1348+
1349+
var scaley float32
1350+
if radiusy == -1 {
1351+
scaley = scalex
1352+
} else if radiusy != 0 {
1353+
scaley = 1 / radiusy
1354+
}
1355+
12941356
// TODO: optimize
12951357
zp := f32.Point{}
12961358
return f32.Affine2D{}.
12971359
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
12981360
Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
12991361
Offset(zp.Sub(stop1)). // offset to first stop point
13001362
Rotate(zp, a). // rotate to align gradient
1301-
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
1363+
Scale(zp, f32.Pt(scalex, scaley)) // scale gradient to right size
13021364
}
13031365

13041366
// clipSpaceTransform returns the scale and offset that transforms the given
Loading
Loading
Loading

gpu/internal/rendertest/render_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,44 @@ func TestLinearGradientAngled(t *testing.T) {
340340
}, func(r result) {})
341341
}
342342

343+
func TestRadialGradient(t *testing.T) {
344+
run(t, func(ops *op.Ops) {
345+
paint.RadialGradientOp{
346+
Stop1: f32.Pt(64, 64),
347+
Color1: red,
348+
Stop2: f32.Pt(64, 0),
349+
Color2: black,
350+
}.Add(ops)
351+
paint.PaintOp{}.Add(ops)
352+
}, func(r result) {})
353+
}
354+
355+
func TestEllipseGradient(t *testing.T) {
356+
run(t, func(ops *op.Ops) {
357+
paint.RadialGradientOp{
358+
Stop1: f32.Pt(64, 64),
359+
Color1: red,
360+
Stop2: f32.Pt(64, 0),
361+
Color2: black,
362+
RadiusY: 32,
363+
}.Add(ops)
364+
paint.PaintOp{}.Add(ops)
365+
}, func(r result) {})
366+
}
367+
368+
func TestEllipseGradientAngled(t *testing.T) {
369+
run(t, func(ops *op.Ops) {
370+
paint.RadialGradientOp{
371+
Stop1: f32.Pt(64, 64),
372+
Color1: red,
373+
Stop2: f32.Pt(32, 96),
374+
Color2: black,
375+
RadiusY: 32,
376+
}.Add(ops)
377+
paint.PaintOp{}.Add(ops)
378+
}, func(r result) {})
379+
}
380+
343381
// lerp calculates linear interpolation with color b and p.
344382
func lerp(a, b f32color.RGBA, p float32) f32color.RGBA {
345383
return f32color.RGBA{

internal/opconst/ops.go

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const (
1717
TypePaint
1818
TypeColor
1919
TypeLinearGradient
20+
TypeRadialGradient
2021
TypeArea
2122
TypePointerInput
2223
TypePass
@@ -46,6 +47,7 @@ const (
4647
TypePaintLen = 1
4748
TypeColorLen = 1 + 4
4849
TypeLinearGradientLen = 1 + 8*2 + 4*2
50+
TypeRadialGradientLen = 1 + 8*2 + 4 + 4*2
4951
TypeAreaLen = 1 + 1 + 4*4
5052
TypePointerInputLen = 1 + 1 + 1
5153
TypePassLen = 1 + 1
@@ -90,6 +92,7 @@ func (t OpType) Size() int {
9092
TypePaintLen,
9193
TypeColorLen,
9294
TypeLinearGradientLen,
95+
TypeRadialGradientLen,
9396
TypeAreaLen,
9497
TypePointerInputLen,
9598
TypePassLen,

op/paint/paint.go

+46
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,30 @@ type ColorOp struct {
3636

3737
// LinearGradientOp sets the brush to a gradient starting at stop1 with color1 and
3838
// ending at stop2 with color2.
39+
//
40+
// Note: this gradient does not work together with non-offset transforms.
3941
type LinearGradientOp struct {
4042
Stop1 f32.Point
4143
Color1 color.NRGBA
4244
Stop2 f32.Point
4345
Color2 color.NRGBA
4446
}
4547

48+
// RadialGradientOp sets the brush to a radial gradient center starting at stop1 with color1
49+
// and ellipse X axis ending at stop2 with color2.
50+
//
51+
// Note: this does not work together with non-offset transforms.
52+
type RadialGradientOp struct {
53+
Stop1 f32.Point
54+
Color1 color.NRGBA
55+
Stop2 f32.Point
56+
Color2 color.NRGBA
57+
58+
// RadiusY defines Y axis for the ellipse.
59+
// RadiusY = 0 draws a circle.
60+
RadiusY float32
61+
}
62+
4663
// PaintOp fills fills the current clip area with the current brush.
4764
type PaintOp struct {
4865
}
@@ -132,6 +149,35 @@ func (c LinearGradientOp) Add(o *op.Ops) {
132149
data[21+3] = c.Color2.A
133150
}
134151

152+
func (c RadialGradientOp) Add(o *op.Ops) {
153+
data := o.Write(opconst.TypeRadialGradientLen)
154+
data[0] = byte(opconst.TypeRadialGradient)
155+
156+
bo := binary.LittleEndian
157+
bo.PutUint32(data[1:], math.Float32bits(c.Stop1.X))
158+
bo.PutUint32(data[5:], math.Float32bits(c.Stop1.Y))
159+
bo.PutUint32(data[9:], math.Float32bits(c.Stop2.X))
160+
bo.PutUint32(data[13:], math.Float32bits(c.Stop2.Y))
161+
162+
radiusY := c.RadiusY
163+
if radiusY < 0 {
164+
radiusY = 0
165+
}
166+
if radiusY == 0 {
167+
radiusY = -1 // using -1 to avoid duplicate length calculation
168+
}
169+
bo.PutUint32(data[17:], math.Float32bits(radiusY))
170+
171+
data[21+0] = c.Color1.R
172+
data[21+1] = c.Color1.G
173+
data[21+2] = c.Color1.B
174+
data[21+3] = c.Color1.A
175+
data[25+0] = c.Color2.R
176+
data[25+1] = c.Color2.G
177+
data[25+2] = c.Color2.B
178+
data[25+3] = c.Color2.A
179+
}
180+
135181
func (d PaintOp) Add(o *op.Ops) {
136182
data := o.Write(opconst.TypePaintLen)
137183
data[0] = byte(opconst.TypePaint)

0 commit comments

Comments
 (0)