gpu: add linear gradient

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
This commit is contained in:
Egon Elbre
2020-11-01 21:21:19 +02:00
committed by Elias Naur
parent 0641a34b24
commit f00f3a3359
10 changed files with 452 additions and 71 deletions
+168 -41
View File
@@ -79,6 +79,12 @@ type drawState struct {
image imageOpData
// Current paint.ColorOp, if any.
color color.RGBA
// Current paint.LinearGradientOp.
stop1 f32.Point
stop2 f32.Point
color1 color.RGBA
color2 color.RGBA
}
type pathOp struct {
@@ -109,6 +115,9 @@ type material struct {
opaque bool
// For materialTypeColor.
color f32color.RGBA
// For materialTypeLinearGradient.
color1 f32color.RGBA
color2 f32color.RGBA
// For materialTypeTexture.
texture *texture
uvTrans f32.Affine2D
@@ -127,6 +136,13 @@ type imageOpData struct {
handle interface{}
}
type linearGradientOpData struct {
stop1 f32.Point
color1 color.RGBA
stop2 f32.Point
color2 color.RGBA
}
func (op *clipOp) decode(data []byte) {
if opconst.OpType(data[0]) != opconst.TypeClip {
panic("invalid op")
@@ -184,6 +200,35 @@ func decodeColorOp(data []byte) color.RGBA {
}
}
func decodeLinearGradientOp(data []byte) linearGradientOpData {
if opconst.OpType(data[0]) != opconst.TypeLinearGradient {
panic("invalid op")
}
bo := binary.LittleEndian
return linearGradientOpData{
stop1: f32.Point{
X: math.Float32frombits(bo.Uint32(data[1:])),
Y: math.Float32frombits(bo.Uint32(data[5:])),
},
stop2: f32.Point{
X: math.Float32frombits(bo.Uint32(data[9:])),
Y: math.Float32frombits(bo.Uint32(data[13:])),
},
color1: color.RGBA{
R: data[17+0],
G: data[17+1],
B: data[17+2],
A: data[17+3],
},
color2: color.RGBA{
R: data[21+0],
G: data[21+1],
B: data[21+2],
A: data[21+3],
},
}
}
func decodePaintOp(data []byte) paint.PaintOp {
bo := binary.LittleEndian
if opconst.OpType(data[0]) != opconst.TypePaint {
@@ -216,13 +261,14 @@ type texture struct {
}
type blitter struct {
ctx backend.Device
viewport image.Point
prog [2]*program
layout backend.InputLayout
colUniforms *blitColUniforms
texUniforms *blitTexUniforms
quadVerts backend.Buffer
ctx backend.Device
viewport image.Point
prog [3]*program
layout backend.InputLayout
colUniforms *blitColUniforms
texUniforms *blitTexUniforms
linearGradientUniforms *blitLinearGradientUniforms
quadVerts backend.Buffer
}
type blitColUniforms struct {
@@ -242,6 +288,16 @@ type blitTexUniforms struct {
}
}
type blitLinearGradientUniforms struct {
vert struct {
blitUniforms
_ [12]byte // Padding to a multiple of 16.
}
frag struct {
gradientUniforms
}
}
type uniformBuffer struct {
buf backend.Buffer
ptr []byte
@@ -264,6 +320,11 @@ type colorUniforms struct {
color f32color.RGBA
}
type gradientUniforms struct {
color1 f32color.RGBA
color2 f32color.RGBA
}
type materialType uint8
const (
@@ -274,6 +335,7 @@ const (
const (
materialColor materialType = iota
materialLinearGradient
materialTexture
)
@@ -447,8 +509,11 @@ func newBlitter(ctx backend.Device) *blitter {
}
b.colUniforms = new(blitColUniforms)
b.texUniforms = new(blitTexUniforms)
b.linearGradientUniforms = new(blitLinearGradientUniforms)
prog, layout, err := createColorPrograms(ctx, shader_blit_vert, shader_blit_frag,
[2]interface{}{&b.colUniforms.vert, &b.texUniforms.vert}, [2]interface{}{&b.colUniforms.frag, nil})
[3]interface{}{&b.colUniforms.vert, &b.linearGradientUniforms.vert, &b.texUniforms.vert},
[3]interface{}{&b.colUniforms.frag, &b.linearGradientUniforms.frag, nil},
)
if err != nil {
panic(err)
}
@@ -465,37 +530,59 @@ func (b *blitter) release() {
b.layout.Release()
}
func createColorPrograms(b backend.Device, vsSrc backend.ShaderSources, fsSrc [2]backend.ShaderSources, vertUniforms, fragUniforms [2]interface{}) ([2]*program, backend.InputLayout, error) {
var progs [2]*program
prog, err := b.NewProgram(vsSrc, fsSrc[materialTexture])
if err != nil {
return progs, nil, err
func createColorPrograms(b backend.Device, vsSrc backend.ShaderSources, fsSrc [3]backend.ShaderSources, vertUniforms, fragUniforms [3]interface{}) ([3]*program, backend.InputLayout, error) {
var progs [3]*program
{
prog, err := b.NewProgram(vsSrc, fsSrc[materialTexture])
if err != nil {
return progs, nil, err
}
var vertBuffer, fragBuffer *uniformBuffer
if u := vertUniforms[materialTexture]; u != nil {
vertBuffer = newUniformBuffer(b, u)
prog.SetVertexUniforms(vertBuffer.buf)
}
if u := fragUniforms[materialTexture]; u != nil {
fragBuffer = newUniformBuffer(b, u)
prog.SetFragmentUniforms(fragBuffer.buf)
}
progs[materialTexture] = newProgram(prog, vertBuffer, fragBuffer)
}
var vertBuffer *uniformBuffer
if u := vertUniforms[materialTexture]; u != nil {
vertBuffer = newUniformBuffer(b, u)
prog.SetVertexUniforms(vertBuffer.buf)
{
var vertBuffer, fragBuffer *uniformBuffer
prog, err := b.NewProgram(vsSrc, fsSrc[materialColor])
if err != nil {
progs[materialTexture].Release()
return progs, nil, err
}
if u := vertUniforms[materialColor]; u != nil {
vertBuffer = newUniformBuffer(b, u)
prog.SetVertexUniforms(vertBuffer.buf)
}
if u := fragUniforms[materialColor]; u != nil {
fragBuffer = newUniformBuffer(b, u)
prog.SetFragmentUniforms(fragBuffer.buf)
}
progs[materialColor] = newProgram(prog, vertBuffer, fragBuffer)
}
var fragBuffer *uniformBuffer
if u := fragUniforms[materialTexture]; u != nil {
fragBuffer = newUniformBuffer(b, u)
prog.SetFragmentUniforms(fragBuffer.buf)
{
var vertBuffer, fragBuffer *uniformBuffer
prog, err := b.NewProgram(vsSrc, fsSrc[materialLinearGradient])
if err != nil {
progs[materialTexture].Release()
progs[materialColor].Release()
return progs, nil, err
}
if u := vertUniforms[materialLinearGradient]; u != nil {
vertBuffer = newUniformBuffer(b, u)
prog.SetVertexUniforms(vertBuffer.buf)
}
if u := fragUniforms[materialLinearGradient]; u != nil {
fragBuffer = newUniformBuffer(b, u)
prog.SetFragmentUniforms(fragBuffer.buf)
}
progs[materialLinearGradient] = newProgram(prog, vertBuffer, fragBuffer)
}
progs[materialTexture] = newProgram(prog, vertBuffer, fragBuffer)
prog, err = b.NewProgram(vsSrc, fsSrc[materialColor])
if err != nil {
progs[materialTexture].Release()
return progs, nil, err
}
if u := vertUniforms[materialColor]; u != nil {
vertBuffer = newUniformBuffer(b, u)
prog.SetVertexUniforms(vertBuffer.buf)
}
if u := fragUniforms[materialColor]; u != nil {
fragBuffer = newUniformBuffer(b, u)
prog.SetFragmentUniforms(fragBuffer.buf)
}
progs[materialColor] = newProgram(prog, vertBuffer, fragBuffer)
layout, err := b.NewInputLayout(vsSrc, []backend.InputDesc{
{Type: backend.DataTypeFloat, Size: 2, Offset: 0},
{Type: backend.DataTypeFloat, Size: 2, Offset: 4 * 2},
@@ -503,6 +590,7 @@ func createColorPrograms(b backend.Device, vsSrc backend.ShaderSources, fsSrc [2
if err != nil {
progs[materialTexture].Release()
progs[materialColor].Release()
progs[materialLinearGradient].Release()
return progs, nil, err
}
return progs, layout, nil
@@ -768,6 +856,13 @@ loop:
case opconst.TypeColor:
state.matType = materialColor
state.color = decodeColorOp(encOp.Data)
case opconst.TypeLinearGradient:
state.matType = materialLinearGradient
op := decodeLinearGradientOp(encOp.Data)
state.stop1 = op.stop1
state.stop2 = op.stop2
state.color1 = op.color1
state.color2 = op.color2
case opconst.TypeImage:
state.matType = materialTexture
state.image = decodeImageOp(encOp.Data, encOp.Refs)
@@ -794,7 +889,7 @@ loop:
bounds := boundRectF(clip)
mat := state.materialFor(d.cache, bnd, off, partialTrans, bounds)
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && mat.material == materialColor {
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && (mat.material == materialColor) {
// The image is a uniform opaque color and takes up the whole screen.
// Scrap images up to and including this image and set clear color.
d.zimageOps = d.zimageOps[:0]
@@ -857,6 +952,14 @@ func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f3
m.material = materialColor
m.color = f32color.RGBAFromSRGB(d.color)
m.opaque = m.color.A == 1.0
case materialLinearGradient:
m.material = materialLinearGradient
m.color1 = f32color.RGBAFromSRGB(d.color1)
m.color2 = f32color.RGBAFromSRGB(d.color2)
m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0
m.uvTrans = trans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
case materialTexture:
m.material = materialTexture
dr := boundRectF(rect.Add(off))
@@ -905,7 +1008,7 @@ func (r *renderer) drawZOps(ops []imageOp) {
}
drc := img.clip
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvTrans)
r.blitter.blit(img.z, m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans)
}
r.ctx.SetDepthTest(false)
}
@@ -929,7 +1032,7 @@ func (r *renderer) drawOps(ops []imageOp) {
var fbo stencilFBO
switch img.clipType {
case clipTypeNone:
r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvTrans)
r.blitter.blit(img.z, m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans)
continue
case clipTypePath:
fbo = r.pather.stenciler.cover(img.place.Idx)
@@ -945,13 +1048,13 @@ func (r *renderer) drawOps(ops []imageOp) {
Max: img.place.Pos.Add(drc.Size()),
}
coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size)
r.pather.cover(img.z, m.material, m.color, scale, off, m.uvTrans, coverScale, coverOff)
r.pather.cover(img.z, m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
}
r.ctx.DepthMask(true)
r.ctx.SetDepthTest(false)
}
func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
p := b.prog[mat]
b.ctx.BindProgram(p.prog)
var uniforms *blitUniforms
@@ -964,6 +1067,14 @@ func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, scale, of
b.texUniforms.vert.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
b.texUniforms.vert.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &b.texUniforms.vert.blitUniforms
case materialLinearGradient:
b.linearGradientUniforms.frag.color1 = col1
b.linearGradientUniforms.frag.color2 = col2
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
b.linearGradientUniforms.vert.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
b.linearGradientUniforms.vert.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &b.linearGradientUniforms.vert.blitUniforms
}
uniforms.z = z
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
@@ -1036,6 +1147,22 @@ func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Poin
return scale, offset
}
// gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D {
d := stop2.Sub(stop1)
l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
// TODO: optimize
zp := f32.Point{}
return f32.Affine2D{}.
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
Offset(zp.Sub(stop1)). // offset to first stop point
Rotate(zp, a). // rotate to align gradient
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
}
// clipSpaceTransform returns the scale and offset that transforms the given
// rectangle from a viewport into OpenGL clip space.
func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) {
+30 -10
View File
@@ -27,11 +27,12 @@ type pather struct {
}
type coverer struct {
ctx backend.Device
prog [2]*program
texUniforms *coverTexUniforms
colUniforms *coverColUniforms
layout backend.InputLayout
ctx backend.Device
prog [3]*program
texUniforms *coverTexUniforms
colUniforms *coverColUniforms
linearGradientUniforms *coverLinearGradientUniforms
layout backend.InputLayout
}
type coverTexUniforms struct {
@@ -51,6 +52,16 @@ type coverColUniforms struct {
}
}
type coverLinearGradientUniforms struct {
vert struct {
coverUniforms
_ [12]byte // Padding to multiple of 16.
}
frag struct {
gradientUniforms
}
}
type coverUniforms struct {
transform [4]float32
uvCoverTransform [4]float32
@@ -149,9 +160,10 @@ func newCoverer(ctx backend.Device) *coverer {
}
c.colUniforms = new(coverColUniforms)
c.texUniforms = new(coverTexUniforms)
c.linearGradientUniforms = new(coverLinearGradientUniforms)
prog, layout, err := createColorPrograms(ctx, shader_cover_vert, shader_cover_frag,
[2]interface{}{&c.colUniforms.vert, &c.texUniforms.vert},
[2]interface{}{&c.colUniforms.frag, nil},
[3]interface{}{&c.colUniforms.vert, &c.linearGradientUniforms.vert, &c.texUniforms.vert},
[3]interface{}{&c.colUniforms.frag, &c.linearGradientUniforms.frag, nil},
)
if err != nil {
panic(err)
@@ -362,11 +374,11 @@ func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv ima
}
}
func (p *pather) cover(z float32, mat materialType, col f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
p.coverer.cover(z, mat, col, scale, off, uvTrans, coverScale, coverOff)
func (p *pather) cover(z float32, mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
p.coverer.cover(z, mat, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
}
func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
p := c.prog[mat]
c.ctx.BindProgram(p.prog)
var uniforms *coverUniforms
@@ -374,6 +386,14 @@ func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, scale, o
case materialColor:
c.colUniforms.frag.color = col
uniforms = &c.colUniforms.vert.coverUniforms
case materialLinearGradient:
c.linearGradientUniforms.frag.color1 = col1
c.linearGradientUniforms.frag.color2 = col2
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
c.linearGradientUniforms.vert.uvTransformR1 = [4]float32{t1, t2, t3, 0}
c.linearGradientUniforms.vert.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &c.linearGradientUniforms.vert.coverUniforms
case materialTexture:
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
c.texUniforms.vert.uvTransformR1 = [4]float32{t1, t2, t3, 0}
+104
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -48,6 +48,6 @@ void main() {
vCtrl = ctrl-pos;
vTo = to-pos;
pos = pos*transform.xy + transform.zw;
gl_Position = vec4(pos, 1, 1);
gl_Position = vec4(pos, 1, 1);
}
+5 -1
View File
@@ -70,7 +70,7 @@ func generate() error {
if ext := filepath.Ext(shader); ext != ".vert" && ext != ".frag" {
continue
}
const nvariants = 2
const nvariants = 3
var variants [nvariants]struct {
backend.ShaderSources
hlslSrc string
@@ -80,6 +80,10 @@ func generate() error {
FetchColorExpr: `_color`,
Header: `layout(binding=0) uniform Color { vec4 _color; };`,
},
{
FetchColorExpr: `mix(_color1, _color2, clamp(vUV.x, 0.0, 1.0))`,
Header: `layout(binding=0) uniform Gradient { vec4 _color1; vec4 _color2; };`,
},
{
FetchColorExpr: `texture(tex, vUV)`,
Header: `layout(binding=0) uniform sampler2D tex;`,
+21 -18
View File
@@ -16,6 +16,7 @@ const (
TypeImage
TypePaint
TypeColor
TypeLinearGradient
TypeArea
TypePointerInput
TypePass
@@ -29,24 +30,25 @@ const (
)
const (
TypeMacroLen = 1 + 4 + 4
TypeCallLen = 1 + 4 + 4
TypeTransformLen = 1 + 4*6
TypeLayerLen = 1
TypeRedrawLen = 1 + 8
TypeImageLen = 1 + 4*4
TypePaintLen = 1 + 4*4
TypeColorLen = 1 + 4
TypeAreaLen = 1 + 1 + 4*4
TypePointerInputLen = 1 + 1 + 1
TypePassLen = 1 + 1
TypeKeyInputLen = 1 + 1
TypeHideInputLen = 1
TypePushLen = 1
TypePopLen = 1
TypeAuxLen = 1
TypeClipLen = 1 + 4*4
TypeProfileLen = 1
TypeMacroLen = 1 + 4 + 4
TypeCallLen = 1 + 4 + 4
TypeTransformLen = 1 + 4*6
TypeLayerLen = 1
TypeRedrawLen = 1 + 8
TypeImageLen = 1 + 4*4
TypePaintLen = 1 + 4*4
TypeColorLen = 1 + 4
TypeLinearGradientLen = 1 + 8*2 + 4*2
TypeAreaLen = 1 + 1 + 4*4
TypePointerInputLen = 1 + 1 + 1
TypePassLen = 1 + 1
TypeKeyInputLen = 1 + 1
TypeHideInputLen = 1
TypePushLen = 1
TypePopLen = 1
TypeAuxLen = 1
TypeClipLen = 1 + 4*4
TypeProfileLen = 1
)
func (t OpType) Size() int {
@@ -59,6 +61,7 @@ func (t OpType) Size() int {
TypeImageLen,
TypePaintLen,
TypeColorLen,
TypeLinearGradientLen,
TypeAreaLen,
TypePointerInputLen,
TypePassLen,
Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

+94
View File
@@ -1,10 +1,12 @@
package rendertest
import (
"image/color"
"math"
"testing"
"gioui.org/f32"
"gioui.org/internal/f32color"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
@@ -194,3 +196,95 @@ func TestNegativeOverlaps(t *testing.T) {
r.expect(60, 122, colornames.White)
})
}
type Gradient struct {
From, To color.RGBA
}
var gradients = []Gradient{
{From: color.RGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xFF}, To: color.RGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}},
{From: color.RGBA{R: 0x19, G: 0xFF, B: 0x19, A: 0xFF}, To: color.RGBA{R: 0xFF, G: 0x19, B: 0x19, A: 0xFF}},
{From: color.RGBA{R: 0xFF, G: 0x19, B: 0x19, A: 0xFF}, To: color.RGBA{R: 0x19, G: 0x19, B: 0xFF, A: 0xFF}},
{From: color.RGBA{R: 0x19, G: 0x19, B: 0xFF, A: 0xFF}, To: color.RGBA{R: 0x19, G: 0xFF, B: 0x19, A: 0xFF}},
{From: color.RGBA{R: 0x19, G: 0xFF, B: 0xFF, A: 0xFF}, To: color.RGBA{R: 0xFF, G: 0x19, B: 0x19, A: 0xFF}},
{From: color.RGBA{R: 0xFF, G: 0xFF, B: 0x19, A: 0xFF}, To: color.RGBA{R: 0x19, G: 0x19, B: 0xFF, A: 0xFF}},
}
func TestLinearGradient(t *testing.T) {
const gradienth = 8
// 0.5 offset from ends to ensure that the center of the pixel
// aligns with gradient from and to colors.
pixelAligned := f32.Rect(0.5, 0, 127.5, gradienth)
samples := []int{0, 12, 32, 64, 96, 115, 127}
run(t, func(ops *op.Ops) {
gr := pixelAligned
for _, g := range gradients {
paint.LinearGradientOp{
Stop1: f32.Pt(gr.Min.X, gr.Min.Y),
Color1: g.From,
Stop2: f32.Pt(gr.Max.X, gr.Min.Y),
Color2: g.To,
}.Add(ops)
paint.PaintOp{Rect: gr}.Add(ops)
gr = gr.Add(f32.Pt(0, gradienth))
}
}, func(r result) {
gr := pixelAligned
for _, g := range gradients {
from := f32color.RGBAFromSRGB(g.From)
to := f32color.RGBAFromSRGB(g.To)
for _, p := range samples {
exp := lerp(from, to, float32(p)/float32(r.img.Bounds().Dx()-1))
r.expect(p, int(gr.Min.Y+gradienth/2), exp.SRGB())
}
gr = gr.Add(f32.Pt(0, gradienth))
}
})
}
func TestLinearGradientAngled(t *testing.T) {
run(t, func(ops *op.Ops) {
paint.LinearGradientOp{
Stop1: f32.Pt(64, 64),
Color1: colornames.Black,
Stop2: f32.Pt(0, 0),
Color2: colornames.Red,
}.Add(ops)
paint.PaintOp{Rect: f32.Rect(0, 0, 64, 64)}.Add(ops)
paint.LinearGradientOp{
Stop1: f32.Pt(64, 64),
Color1: colornames.White,
Stop2: f32.Pt(128, 0),
Color2: colornames.Green,
}.Add(ops)
paint.PaintOp{Rect: f32.Rect(64, 0, 128, 64)}.Add(ops)
paint.LinearGradientOp{
Stop1: f32.Pt(64, 64),
Color1: colornames.Black,
Stop2: f32.Pt(128, 128),
Color2: colornames.Blue,
}.Add(ops)
paint.PaintOp{Rect: f32.Rect(64, 64, 128, 128)}.Add(ops)
paint.LinearGradientOp{
Stop1: f32.Pt(64, 64),
Color1: colornames.White,
Stop2: f32.Pt(0, 128),
Color2: colornames.Magenta,
}.Add(ops)
paint.PaintOp{Rect: f32.Rect(0, 64, 64, 128)}.Add(ops)
}, func(r result) {})
}
// lerp calculates linear interpolation with color b and p.
func lerp(a, b f32color.RGBA, p float32) f32color.RGBA {
return f32color.RGBA{
R: a.R*(1-p) + b.R*p,
G: a.G*(1-p) + b.G*p,
B: a.B*(1-p) + b.B*p,
A: a.A*(1-p) + b.A*p,
}
}
+29
View File
@@ -37,6 +37,15 @@ type ColorOp struct {
Color color.RGBA
}
// LinearGradientOp sets the brush to a gradient starting at stop1 with color1 and
// ending at stop2 with color2.
type LinearGradientOp struct {
Stop1 f32.Point
Color1 color.RGBA
Stop2 f32.Point
Color2 color.RGBA
}
// PaintOp fills an area with the current brush, respecting the
// current clip path and transformation.
type PaintOp struct {
@@ -117,6 +126,26 @@ func (c ColorOp) Add(o *op.Ops) {
data[4] = c.Color.A
}
func (c LinearGradientOp) Add(o *op.Ops) {
data := o.Write(opconst.TypeLinearGradientLen)
data[0] = byte(opconst.TypeLinearGradient)
bo := binary.LittleEndian
bo.PutUint32(data[1:], math.Float32bits(c.Stop1.X))
bo.PutUint32(data[5:], math.Float32bits(c.Stop1.Y))
bo.PutUint32(data[9:], math.Float32bits(c.Stop2.X))
bo.PutUint32(data[13:], math.Float32bits(c.Stop2.Y))
data[17+0] = c.Color1.R
data[17+1] = c.Color1.G
data[17+2] = c.Color1.B
data[17+3] = c.Color1.A
data[21+0] = c.Color2.R
data[21+1] = c.Color2.G
data[21+2] = c.Color2.B
data[21+3] = c.Color2.A
}
func (d PaintOp) Add(o *op.Ops) {
data := o.Write(opconst.TypePaintLen)
data[0] = byte(opconst.TypePaint)