op/paint: add NewImageOp, unexport ImageOp fields

With public ImageOp fields there was no way to mark an image.Image as modified.
Replace them with NewImageOp that always make a copy, and use the opportunity
to ensure the copy is ready to upload to a GPU texture.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-10-14 17:37:18 +02:00
parent 2e22edbb99
commit 74407a50d5
4 changed files with 77 additions and 74 deletions
+20 -47
View File
@@ -18,7 +18,6 @@ import (
"gioui.org/internal/ops" "gioui.org/internal/ops"
"gioui.org/op" "gioui.org/op"
"gioui.org/op/paint" "gioui.org/op/paint"
"golang.org/x/image/draw"
) )
type GPU struct { type GPU struct {
@@ -79,9 +78,9 @@ type drawState struct {
rect bool rect bool
z int z int
// Current ImageOp image and rect, if any. // Current ImageOp image and size, if any.
img image.Image img *image.RGBA
imgRect image.Rectangle imgSize image.Point
// Current ColorOp, if any. // Current ColorOp, if any.
color color.RGBA color color.RGBA
} }
@@ -119,7 +118,7 @@ type material struct {
uvOffset f32.Point uvOffset f32.Point
} }
// clipOp is the shadow of draw.ClipOp. // clipOp is the shadow of paint.ClipOp.
type clipOp struct { type clipOp struct {
bounds f32.Rectangle bounds f32.Rectangle
} }
@@ -144,25 +143,16 @@ func (op *clipOp) decode(data []byte) {
} }
} }
func decodeImageOp(data []byte, refs []interface{}) paint.ImageOp { func decodeImageOp(data []byte, refs []interface{}) (*image.RGBA, image.Point) {
bo := binary.LittleEndian bo := binary.LittleEndian
if opconst.OpType(data[0]) != opconst.TypeImage { if opconst.OpType(data[0]) != opconst.TypeImage {
panic("invalid op") panic("invalid op")
} }
sr := image.Rectangle{ sz := image.Point{
Min: image.Point{ X: int(int32(bo.Uint32(data[1:]))),
X: int(int32(bo.Uint32(data[1:]))), Y: int(int32(bo.Uint32(data[5:]))),
Y: int(int32(bo.Uint32(data[5:]))),
},
Max: image.Point{
X: int(int32(bo.Uint32(data[9:]))),
Y: int(int32(bo.Uint32(data[13:]))),
},
}
return paint.ImageOp{
Src: refs[0].(image.Image),
Rect: sr,
} }
return refs[0].(*image.RGBA), sz
} }
func decodeColorOp(data []byte) paint.ColorOp { func decodeColorOp(data []byte) paint.ColorOp {
@@ -206,7 +196,7 @@ type resource interface {
} }
type texture struct { type texture struct {
src image.Image src *image.RGBA
id gl.Texture id gl.Texture
} }
@@ -737,9 +727,7 @@ loop:
state.img = nil state.img = nil
state.color = op.Color state.color = op.Color
case opconst.TypeImage: case opconst.TypeImage:
op := decodeImageOp(encOp.Data, encOp.Refs) state.img, state.imgSize = decodeImageOp(encOp.Data, encOp.Refs)
state.img = op.Src
state.imgRect = op.Rect
case opconst.TypePaint: case opconst.TypePaint:
op := decodePaintOp(encOp.Data) op := decodePaintOp(encOp.Data)
off := state.t.Transform(f32.Point{}) off := state.t.Transform(f32.Point{})
@@ -801,14 +789,12 @@ func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f3
m.material = materialColor m.material = materialColor
m.color = gamma(d.color.RGBA()) m.color = gamma(d.color.RGBA())
m.opaque = m.color[3] == 1.0 m.opaque = m.color[3] == 1.0
} else if uniform, ok := d.img.(*image.Uniform); ok {
m.material = materialColor
m.color = gamma(uniform.RGBA())
m.opaque = m.color[3] == 1.0
} else { } else {
m.material = materialTexture m.material = materialTexture
dr := boundRectF(rect.Add(off)) dr := boundRectF(rect.Add(off))
sr := d.imgRect sr := image.Rectangle{
Max: d.imgSize,
}
if dx := dr.Dx(); dx != 0 { if dx := dr.Dx(); dx != 0 {
// Don't clip 1 px width sources. // Don't clip 1 px width sources.
if sdx := sr.Dx(); sdx > 1 { if sdx := sr.Dx(); sdx > 1 {
@@ -908,25 +894,18 @@ func (r *renderer) drawOps(ops []imageOp) {
r.ctx.Disable(gl.DEPTH_TEST) r.ctx.Disable(gl.DEPTH_TEST)
} }
func (r *renderer) uploadTexture(img image.Image) { func (r *renderer) uploadTexture(img *image.RGBA) {
var pixels []byte var pixels []byte
b := img.Bounds() b := img.Bounds()
w, h := b.Dx(), b.Dy() w, h := b.Dx(), b.Dy()
switch img := img.(type) { if img.Stride != w*4 {
case *image.RGBA: panic("unsupported stride")
if img.Stride == w*4 {
start := (b.Min.X + b.Min.Y*w) * 4
end := (b.Max.X + (b.Max.Y-1)*w) * 4
pixels = img.Pix[start:end]
} else {
pixels = copyImage(img, b).Pix
}
default:
pixels = copyImage(img, b).Pix
} }
start := (b.Min.X + b.Min.Y*w) * 4
end := (b.Max.X + (b.Max.Y-1)*w) * 4
pixels = img.Pix[start:end]
tt := r.ctx.caps.srgbaTriple tt := r.ctx.caps.srgbaTriple
r.ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, w, h, tt.format, tt.typ, pixels) r.ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, w, h, tt.format, tt.typ, pixels)
} }
func gamma(r, g, b, a uint32) [4]float32 { func gamma(r, g, b, a uint32) [4]float32 {
@@ -1008,12 +987,6 @@ func createTexture(ctx *context) gl.Texture {
return tex return tex
} }
func copyImage(img image.Image, r image.Rectangle) *image.RGBA {
tmp := image.NewRGBA(r)
draw.Draw(tmp, r, img, r.Min, draw.Src)
return tmp
}
const blitVSrc = ` const blitVSrc = `
#version 100 #version 100
+41 -14
View File
@@ -6,6 +6,7 @@ import (
"encoding/binary" "encoding/binary"
"image" "image"
"image/color" "image/color"
"image/draw"
"math" "math"
"gioui.org/f32" "gioui.org/f32"
@@ -13,16 +14,12 @@ import (
"gioui.org/op" "gioui.org/op"
) )
// ImageOp sets the material to a section of an // ImageOp sets the material to an image.
// image.
type ImageOp struct { type ImageOp struct {
// Src is the image. Note that once a particular image.Image has uniform bool
// been used by an ImageOp, updating the image contents might not color color.RGBA
// be reflected in the rendered image. Use a new image.Image src *image.RGBA
// instead. size image.Point
Src image.Image
// Rect defines the section of Src to use.
Rect image.Rectangle
} }
// ColorOp sets the material to a constant color. // ColorOp sets the material to a constant color.
@@ -36,15 +33,45 @@ type PaintOp struct {
Rect f32.Rectangle Rect f32.Rectangle
} }
func NewImageOp(src image.Image) ImageOp {
switch src := src.(type) {
case *image.Uniform:
col := color.RGBAModel.Convert(src.C).(color.RGBA)
return ImageOp{
uniform: true,
color: col,
}
default:
sz := src.Bounds().Size()
// Copy the image into a GPU friendly format.
dst := image.NewRGBA(image.Rectangle{
Max: sz,
})
draw.Draw(dst, src.Bounds(), src, image.Point{}, draw.Src)
return ImageOp{
src: dst,
size: sz,
}
}
}
func (i ImageOp) Size() image.Point {
return i.size
}
func (i ImageOp) Add(o *op.Ops) { func (i ImageOp) Add(o *op.Ops) {
if i.uniform {
ColorOp{
Color: i.color,
}.Add(o)
return
}
data := make([]byte, opconst.TypeImageLen) data := make([]byte, opconst.TypeImageLen)
data[0] = byte(opconst.TypeImage) data[0] = byte(opconst.TypeImage)
bo := binary.LittleEndian bo := binary.LittleEndian
bo.PutUint32(data[1:], uint32(i.Rect.Min.X)) bo.PutUint32(data[1:], uint32(i.size.X))
bo.PutUint32(data[5:], uint32(i.Rect.Min.Y)) bo.PutUint32(data[5:], uint32(i.size.Y))
bo.PutUint32(data[9:], uint32(i.Rect.Max.X)) o.Write(data, i.src)
bo.PutUint32(data[13:], uint32(i.Rect.Max.Y))
o.Write(data, i.Src)
} }
func (c ColorOp) Add(o *op.Ops) { func (c ColorOp) Add(o *op.Ops) {
+15 -9
View File
@@ -41,7 +41,7 @@ type Icon struct {
size unit.Value size unit.Value
// Cached values. // Cached values.
img image.Image op paint.ImageOp
imgSize int imgSize int
} }
@@ -112,9 +112,11 @@ func (b IconButton) Layout(gtx *layout.Context, button *widget.Button) {
layout.UniformInset(b.Padding).Layout(gtx, func() { layout.UniformInset(b.Padding).Layout(gtx, func() {
size := gtx.Px(b.Size) - gtx.Px(b.Padding) size := gtx.Px(b.Size) - gtx.Px(b.Padding)
ico := b.Icon.image(size) ico := b.Icon.image(size)
paint.ImageOp{Src: ico, Rect: ico.Bounds()}.Add(gtx.Ops) ico.Add(gtx.Ops)
paint.PaintOp{ paint.PaintOp{
Rect: toRectF(ico.Bounds()), Rect: f32.Rectangle{
Max: toPointF(ico.Size()),
},
}.Add(gtx.Ops) }.Add(gtx.Ops)
gtx.Dimensions = layout.Dimensions{ gtx.Dimensions = layout.Dimensions{
Size: image.Point{X: size, Y: size}, Size: image.Point{X: size, Y: size},
@@ -143,9 +145,9 @@ func (b IconButton) Layout(gtx *layout.Context, button *widget.Button) {
st.Layout(gtx, bg, ico) st.Layout(gtx, bg, ico)
} }
func (ic *Icon) image(sz int) image.Image { func (ic *Icon) image(sz int) paint.ImageOp {
if sz == ic.imgSize { if sz == ic.imgSize {
return ic.img return ic.op
} }
m, _ := iconvg.DecodeMetadata(ic.src) m, _ := iconvg.DecodeMetadata(ic.src)
dx, dy := m.ViewBox.AspectRatio() dx, dy := m.ViewBox.AspectRatio()
@@ -157,15 +159,19 @@ func (ic *Icon) image(sz int) image.Image {
iconvg.Decode(&ico, ic.src, &iconvg.DecodeOptions{ iconvg.Decode(&ico, ic.src, &iconvg.DecodeOptions{
Palette: &m.Palette, Palette: &m.Palette,
}) })
ic.img = img ic.op = paint.NewImageOp(img)
ic.imgSize = sz ic.imgSize = sz
return img return ic.op
}
func toPointF(p image.Point) f32.Point {
return f32.Point{X: float32(p.X), Y: float32(p.Y)}
} }
func toRectF(r image.Rectangle) f32.Rectangle { func toRectF(r image.Rectangle) f32.Rectangle {
return f32.Rectangle{ return f32.Rectangle{
Min: f32.Point{X: float32(r.Min.X), Y: float32(r.Min.Y)}, Min: toPointF(r.Min),
Max: f32.Point{X: float32(r.Max.X), Y: float32(r.Max.Y)}, Max: toPointF(r.Max),
} }
} }
+1 -4
View File
@@ -15,8 +15,6 @@ import (
type Image struct { type Image struct {
// Src is the image to display. // Src is the image to display.
Src image.Image Src image.Image
// Rect is the source rectangle.
Rect image.Rectangle
// Scale is the ratio of image pixels to // Scale is the ratio of image pixels to
// dps. // dps.
Scale float32 Scale float32
@@ -25,7 +23,6 @@ type Image struct {
func (t *Theme) Image(img image.Image) Image { func (t *Theme) Image(img image.Image) Image {
return Image{ return Image{
Src: img, Src: img,
Rect: img.Bounds(),
Scale: 160 / 72, // About 72 DPI. Scale: 160 / 72, // About 72 DPI.
} }
} }
@@ -47,7 +44,7 @@ func (im Image) Layout(gtx *layout.Context) {
dr := f32.Rectangle{ dr := f32.Rectangle{
Max: f32.Point{X: float32(d.X), Y: float32(d.Y)}, Max: f32.Point{X: float32(d.X), Y: float32(d.Y)},
} }
paint.ImageOp{Src: im.Src, Rect: im.Rect}.Add(gtx.Ops) paint.NewImageOp(im.Src).Add(gtx.Ops)
paint.PaintOp{Rect: dr}.Add(gtx.Ops) paint.PaintOp{Rect: dr}.Add(gtx.Ops)
gtx.Dimensions = layout.Dimensions{Size: d, Baseline: d.Y} gtx.Dimensions = layout.Dimensions{Size: d, Baseline: d.Y}
} }