mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
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:
+20
-47
@@ -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
@@ -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) {
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user