raw-buffer-api #1
@ -1,2 +1,52 @@
|
||||
package artist
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
|
||||
// Pattern is capable of generating a pattern pixel by pixel.
|
||||
type Pattern interface {
|
||||
// AtWhen returns the color of the pixel located at (x, y) relative to
|
||||
// the origin point of the pattern (0, 0), when the pattern has the
|
||||
// specified width and height. Patterns may ignore the width and height
|
||||
// parameters, but it may be useful for some patterns such as gradients.
|
||||
AtWhen (x, y, width, height int) (color.RGBA)
|
||||
}
|
||||
|
||||
// Texture is a struct that allows an image to be converted into a tiling
|
||||
// texture pattern.
|
||||
type Texture struct {
|
||||
data []color.RGBA
|
||||
width, height int
|
||||
}
|
||||
|
||||
// NewTexture converts an image into a texture.
|
||||
func NewTexture (source image.Image) (texture Texture) {
|
||||
bounds := source.Bounds()
|
||||
texture.width = bounds.Dx()
|
||||
texture.height = bounds.Dy()
|
||||
texture.data = make([]color.RGBA, texture.width * texture.height)
|
||||
|
||||
index := 0
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||
r, g, b, a := source.At(x, y).RGBA()
|
||||
texture.data[index] = color.RGBA {
|
||||
uint8(r >> 8),
|
||||
uint8(g >> 8),
|
||||
uint8(b >> 8),
|
||||
uint8(a >> 8),
|
||||
}
|
||||
index ++
|
||||
}}
|
||||
return
|
||||
}
|
||||
|
||||
// AtWhen returns the color at the specified x and y coordinates, wrapped to the
|
||||
// image's width. the width and height are ignored.
|
||||
func (texture Texture) AtWhen (x, y, width, height int) (pixel color.RGBA) {
|
||||
x %= texture.width
|
||||
y %= texture.height
|
||||
if x < 0 { x += texture.width }
|
||||
if y < 0 { y += texture.height }
|
||||
return texture.data[x + y * texture.width]
|
||||
}
|
||||
|
@ -7,10 +7,10 @@ import "git.tebibyte.media/sashakoshka/tomo"
|
||||
// ShadingProfile contains shading information that can be used to draw chiseled
|
||||
// objects.
|
||||
type ShadingProfile struct {
|
||||
Highlight tomo.Image
|
||||
Shadow tomo.Image
|
||||
Stroke tomo.Image
|
||||
Fill tomo.Image
|
||||
Highlight Pattern
|
||||
Shadow Pattern
|
||||
Stroke Pattern
|
||||
Fill Pattern
|
||||
StrokeWeight int
|
||||
ShadingWeight int
|
||||
}
|
||||
@ -43,6 +43,7 @@ func ChiseledRectangle (
|
||||
strokeWeight := profile.StrokeWeight
|
||||
shadingWeight := profile.ShadingWeight
|
||||
|
||||
data, stride := destination.Buffer()
|
||||
bounds = bounds.Canon()
|
||||
updatedRegion = bounds
|
||||
|
||||
@ -59,11 +60,6 @@ func ChiseledRectangle (
|
||||
fillBounds.Max = fillBounds.Max.Sub(shadingWeightVector)
|
||||
fillBounds = fillBounds.Canon()
|
||||
|
||||
strokeImageMin := stroke.Bounds().Min
|
||||
highlightImageMin := highlight.Bounds().Min
|
||||
shadowImageMin := shadow.Bounds().Min
|
||||
fillImageMin := fill.Bounds().Min
|
||||
|
||||
width := float64(bounds.Dx())
|
||||
height := float64(bounds.Dy())
|
||||
|
||||
@ -75,11 +71,10 @@ func ChiseledRectangle (
|
||||
point := image.Point { x, y }
|
||||
switch {
|
||||
case point.In(fillBounds):
|
||||
pixel = fill.RGBAAt (
|
||||
xx - strokeWeight - shadingWeight +
|
||||
fillImageMin.X,
|
||||
yy - strokeWeight - shadingWeight +
|
||||
fillImageMin.Y)
|
||||
pixel = fill.AtWhen (
|
||||
xx - strokeWeight - shadingWeight,
|
||||
yy - strokeWeight - shadingWeight,
|
||||
fillBounds.Dx(), fillBounds.Dy())
|
||||
|
||||
case point.In(shadingBounds):
|
||||
var highlighted bool
|
||||
@ -97,27 +92,21 @@ func ChiseledRectangle (
|
||||
width - float64(xx) >
|
||||
float64(yy)
|
||||
}
|
||||
|
||||
|
||||
shadingSource := shadow
|
||||
if highlighted {
|
||||
pixel = highlight.RGBAAt (
|
||||
xx - strokeWeight +
|
||||
highlightImageMin.X,
|
||||
yy - strokeWeight +
|
||||
highlightImageMin.Y)
|
||||
} else {
|
||||
pixel = shadow.RGBAAt (
|
||||
xx - strokeWeight +
|
||||
shadowImageMin.X,
|
||||
yy - strokeWeight +
|
||||
shadowImageMin.Y)
|
||||
shadingSource = highlight
|
||||
}
|
||||
|
||||
pixel = shadingSource.AtWhen (
|
||||
xx - strokeWeight,
|
||||
yy - strokeWeight,
|
||||
shadingBounds.Dx(),
|
||||
shadingBounds.Dy())
|
||||
default:
|
||||
pixel = stroke.RGBAAt (
|
||||
xx + strokeImageMin.X,
|
||||
yy + strokeImageMin.Y)
|
||||
pixel = stroke.AtWhen (
|
||||
xx, yy, bounds.Dx(), bounds.Dy())
|
||||
}
|
||||
destination.SetRGBA(x, y, pixel)
|
||||
data[x + y * stride] = pixel
|
||||
xx ++
|
||||
}
|
||||
yy ++
|
||||
|
@ -5,7 +5,7 @@ import "git.tebibyte.media/sashakoshka/tomo"
|
||||
|
||||
func Line (
|
||||
destination tomo.Canvas,
|
||||
source tomo.Image,
|
||||
source Pattern,
|
||||
weight int,
|
||||
min image.Point,
|
||||
max image.Point,
|
||||
@ -17,6 +17,8 @@ func Line (
|
||||
updatedRegion = image.Rectangle { Min: min, Max: max }.Canon()
|
||||
updatedRegion.Max.X ++
|
||||
updatedRegion.Max.Y ++
|
||||
width := updatedRegion.Dx()
|
||||
height := updatedRegion.Dy()
|
||||
|
||||
if abs(max.Y - min.Y) <
|
||||
abs(max.X - min.X) {
|
||||
@ -26,7 +28,7 @@ func Line (
|
||||
min = max
|
||||
max = temp
|
||||
}
|
||||
lineLow(destination, source, weight, min, max)
|
||||
lineLow(destination, source, weight, min, max, width, height)
|
||||
} else {
|
||||
|
||||
if max.Y < min.Y {
|
||||
@ -34,18 +36,21 @@ func Line (
|
||||
min = max
|
||||
max = temp
|
||||
}
|
||||
lineHigh(destination, source, weight, min, max)
|
||||
lineHigh(destination, source, weight, min, max, width, height)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lineLow (
|
||||
destination tomo.Canvas,
|
||||
source tomo.Image,
|
||||
source Pattern,
|
||||
weight int,
|
||||
min image.Point,
|
||||
max image.Point,
|
||||
width, height int,
|
||||
) {
|
||||
data, stride := destination.Buffer()
|
||||
|
||||
deltaX := max.X - min.X
|
||||
deltaY := max.Y - min.Y
|
||||
yi := 1
|
||||
@ -59,7 +64,7 @@ func lineLow (
|
||||
y := min.Y
|
||||
|
||||
for x := min.X; x < max.X; x ++ {
|
||||
destination.SetRGBA(x, y, source.RGBAAt(x, y))
|
||||
data[x + y * stride] = source.AtWhen(x, y, width, height)
|
||||
if D > 0 {
|
||||
y += yi
|
||||
D += 2 * (deltaY - deltaX)
|
||||
@ -71,11 +76,14 @@ func lineLow (
|
||||
|
||||
func lineHigh (
|
||||
destination tomo.Canvas,
|
||||
source tomo.Image,
|
||||
source Pattern,
|
||||
weight int,
|
||||
min image.Point,
|
||||
max image.Point,
|
||||
width, height int,
|
||||
) {
|
||||
data, stride := destination.Buffer()
|
||||
|
||||
deltaX := max.X - min.X
|
||||
deltaY := max.Y - min.Y
|
||||
xi := 1
|
||||
@ -89,7 +97,7 @@ func lineHigh (
|
||||
x := min.X
|
||||
|
||||
for y := min.Y; y < max.Y; y ++ {
|
||||
destination.SetRGBA(x, y, source.RGBAAt(x, y))
|
||||
data[x + y * stride] = source.AtWhen(x, y, width, height)
|
||||
if D > 0 {
|
||||
x += xi
|
||||
D += 2 * (deltaX - deltaY)
|
||||
|
@ -1,105 +1,51 @@
|
||||
package artist
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
|
||||
// Paste transfers one image onto another, offset by the specified point.
|
||||
// Paste transfers one canvas onto another, offset by the specified point.
|
||||
func Paste (
|
||||
destination tomo.Canvas,
|
||||
source tomo.Image,
|
||||
source tomo.Canvas,
|
||||
offset image.Point,
|
||||
) (
|
||||
updatedRegion image.Rectangle,
|
||||
) {
|
||||
sourceBounds := source.Bounds().Canon()
|
||||
dstData, dstStride := destination.Buffer()
|
||||
srcData, srcStride := source.Buffer()
|
||||
|
||||
sourceBounds :=
|
||||
source.Bounds().Canon().
|
||||
Intersect(destination.Bounds().Sub(offset))
|
||||
if sourceBounds.Empty() { return }
|
||||
|
||||
updatedRegion = sourceBounds.Add(offset)
|
||||
for y := sourceBounds.Min.Y; y < sourceBounds.Max.Y; y ++ {
|
||||
for x := sourceBounds.Min.X; x < sourceBounds.Max.X; x ++ {
|
||||
destination.SetRGBA (
|
||||
x + offset.X, y + offset.Y,
|
||||
source.RGBAAt(x, y))
|
||||
dstData[x + offset.X + (y + offset.Y) * dstStride] =
|
||||
srcData[x + y * srcStride]
|
||||
}}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Rectangle draws a rectangle with an inset border. If the border image is nil,
|
||||
// no border will be drawn. Likewise, if the fill image is nil, the rectangle
|
||||
// will have no fill.
|
||||
func Rectangle (
|
||||
func FillRectangle (
|
||||
destination tomo.Canvas,
|
||||
fill tomo.Image,
|
||||
stroke tomo.Image,
|
||||
weight int,
|
||||
source Pattern,
|
||||
bounds image.Rectangle,
|
||||
) (
|
||||
updatedRegion image.Rectangle,
|
||||
) {
|
||||
bounds = bounds.Canon()
|
||||
data, stride := destination.Buffer()
|
||||
bounds = bounds.Canon().Intersect(destination.Bounds()).Canon()
|
||||
if bounds.Empty() { return }
|
||||
updatedRegion = bounds
|
||||
|
||||
fillBounds := bounds
|
||||
fillBounds.Min = fillBounds.Min.Add(image.Point { weight, weight })
|
||||
fillBounds.Max = fillBounds.Max.Sub(image.Point { weight, weight })
|
||||
fillBounds = fillBounds.Canon()
|
||||
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||
var pixel color.RGBA
|
||||
if (image.Point { x, y }).In(fillBounds) {
|
||||
pixel = fill.RGBAAt(x, y)
|
||||
} else {
|
||||
pixel = stroke.RGBAAt(x, y)
|
||||
}
|
||||
destination.SetRGBA(x, y, pixel)
|
||||
width, height := bounds.Dx(), bounds.Dy()
|
||||
for y := 0; y < height; y ++ {
|
||||
for x := 0; x < width; x ++ {
|
||||
data[x + bounds.Min.X + (y + bounds.Min.Y) * stride] =
|
||||
source.AtWhen(x, y, width, height)
|
||||
}}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// OffsetRectangle is the same as Rectangle, but offsets the border image to the
|
||||
// top left corner of the border and the fill image to the top left corner of
|
||||
// the fill.
|
||||
func OffsetRectangle (
|
||||
destination tomo.Canvas,
|
||||
fill tomo.Image,
|
||||
stroke tomo.Image,
|
||||
weight int,
|
||||
bounds image.Rectangle,
|
||||
) (
|
||||
updatedRegion image.Rectangle,
|
||||
) {
|
||||
bounds = bounds.Canon()
|
||||
updatedRegion = bounds
|
||||
|
||||
fillBounds := bounds
|
||||
fillBounds.Min = fillBounds.Min.Add(image.Point { weight, weight })
|
||||
fillBounds.Max = fillBounds.Max.Sub(image.Point { weight, weight })
|
||||
fillBounds = fillBounds.Canon()
|
||||
|
||||
strokeImageMin := stroke.Bounds().Min
|
||||
fillImageMin := fill.Bounds().Min
|
||||
|
||||
yy := 0
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||
xx := 0
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||
var pixel color.RGBA
|
||||
if (image.Point { x, y }).In(fillBounds) {
|
||||
pixel = fill.RGBAAt (
|
||||
xx - weight + fillImageMin.X,
|
||||
yy - weight + fillImageMin.Y)
|
||||
} else {
|
||||
pixel = stroke.RGBAAt (
|
||||
xx + strokeImageMin.X,
|
||||
yy + strokeImageMin.Y)
|
||||
}
|
||||
destination.SetRGBA(x, y, pixel)
|
||||
xx ++
|
||||
}
|
||||
yy ++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package artist
|
||||
// import "fmt"
|
||||
import "image"
|
||||
import "unicode"
|
||||
import "image/draw"
|
||||
// import "image/draw"
|
||||
import "golang.org/x/image/font"
|
||||
import "golang.org/x/image/math/fixed"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
@ -95,34 +95,35 @@ func (drawer *TextDrawer) SetAlignment (align Align) {
|
||||
// Draw draws the drawer's text onto the specified canvas at the given offset.
|
||||
func (drawer *TextDrawer) Draw (
|
||||
destination tomo.Canvas,
|
||||
source tomo.Image,
|
||||
source Pattern,
|
||||
offset image.Point,
|
||||
) (
|
||||
updatedRegion image.Rectangle,
|
||||
) {
|
||||
if !drawer.layoutClean { drawer.recalculate() }
|
||||
for _, word := range drawer.layout {
|
||||
for _, character := range word.text {
|
||||
destinationRectangle,
|
||||
mask, maskPoint, _, ok := drawer.face.Glyph (
|
||||
fixed.P (
|
||||
offset.X + word.position.X + character.x,
|
||||
offset.Y + word.position.Y),
|
||||
character.character)
|
||||
if !ok { continue }
|
||||
// TODO: reimplement a version of draw mask that takes in a pattern
|
||||
// for _, word := range drawer.layout {
|
||||
// for _, character := range word.text {
|
||||
// destinationRectangle,
|
||||
// mask, maskPoint, _, ok := drawer.face.Glyph (
|
||||
// fixed.P (
|
||||
// offset.X + word.position.X + character.x,
|
||||
// offset.Y + word.position.Y),
|
||||
// character.character)
|
||||
// if !ok { continue }
|
||||
|
||||
// FIXME: clip destination rectangle if we are on the cusp of
|
||||
// the maximum height.
|
||||
|
||||
draw.DrawMask (
|
||||
destination,
|
||||
destinationRectangle,
|
||||
source, image.Point { },
|
||||
mask, maskPoint,
|
||||
draw.Over)
|
||||
// draw.DrawMask (
|
||||
// destination,
|
||||
// destinationRectangle,
|
||||
// source, image.Point { },
|
||||
// mask, maskPoint,
|
||||
// draw.Over)
|
||||
|
||||
updatedRegion = updatedRegion.Union(destinationRectangle)
|
||||
}}
|
||||
// updatedRegion = updatedRegion.Union(destinationRectangle)
|
||||
// }}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,8 @@ package artist
|
||||
import "image"
|
||||
import "image/color"
|
||||
|
||||
// Uniform is an infinite-sized Image of uniform color. It implements the
|
||||
// color.Color, color.Model, and tomo.Image interfaces.
|
||||
// Uniform is an infinite-sized pattern of uniform color. It implements the
|
||||
// color.Color, color.Model, and image.Image interfaces.
|
||||
type Uniform struct {
|
||||
C color.RGBA
|
||||
}
|
||||
@ -29,13 +29,11 @@ func (uniform *Uniform) RGBA () (r, g, b, a uint32) {
|
||||
}
|
||||
|
||||
func (uniform *Uniform) ColorModel () (model color.Model) {
|
||||
model = uniform
|
||||
return
|
||||
return uniform
|
||||
}
|
||||
|
||||
func (uniform *Uniform) Convert (in color.Color) (out color.Color) {
|
||||
out = uniform.C
|
||||
return
|
||||
func (uniform *Uniform) Convert (in color.Color) (c color.Color) {
|
||||
return uniform.C
|
||||
}
|
||||
|
||||
func (uniform *Uniform) Bounds () (rectangle image.Rectangle) {
|
||||
@ -45,13 +43,11 @@ func (uniform *Uniform) Bounds () (rectangle image.Rectangle) {
|
||||
}
|
||||
|
||||
func (uniform *Uniform) At (x, y int) (c color.Color) {
|
||||
c = uniform.C
|
||||
return
|
||||
return uniform.C
|
||||
}
|
||||
|
||||
func (uniform *Uniform) RGBAAt (x, y int) (c color.RGBA) {
|
||||
c = uniform.C
|
||||
return
|
||||
func (uniform *Uniform) AtWhen (x, y, width, height int) (c color.RGBA) {
|
||||
return uniform.C
|
||||
}
|
||||
|
||||
func (uniform *Uniform) RGBA64At (x, y int) (c color.RGBA64) {
|
||||
@ -59,13 +55,10 @@ func (uniform *Uniform) RGBA64At (x, y int) (c color.RGBA64) {
|
||||
g := uint16(uniform.C.G) << 8 | uint16(uniform.C.G)
|
||||
b := uint16(uniform.C.B) << 8 | uint16(uniform.C.B)
|
||||
a := uint16(uniform.C.A) << 8 | uint16(uniform.C.A)
|
||||
|
||||
c = color.RGBA64 { R: r, G: g, B: b, A: a }
|
||||
return
|
||||
return color.RGBA64 { R: r, G: g, B: b, A: a }
|
||||
}
|
||||
|
||||
// Opaque scans the entire image and reports whether it is fully opaque.
|
||||
func (uniform *Uniform) Opaque () (opaque bool) {
|
||||
opaque = uniform.C.A == 0xFF
|
||||
return
|
||||
return uniform.C.A == 0xFF
|
||||
}
|
||||
|
@ -1,99 +1 @@
|
||||
package artist
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
|
||||
import "image"
|
||||
import "image/draw"
|
||||
import "image/color"
|
||||
|
||||
// WrappedImage wraps an image.Image and allows it to satisfy tomo.Image.
|
||||
type WrappedImage struct { Underlying image.Image }
|
||||
|
||||
// WrapImage wraps a generic image.Image and allows it to satisfy tomo.Image.
|
||||
// Do not use this function to wrap images that already satisfy tomo.Image,
|
||||
// because the resulting wrapped image will be rather slow in comparison.
|
||||
func WrapImage (underlying image.Image) (wrapped tomo.Image) {
|
||||
wrapped = WrappedImage { Underlying: underlying }
|
||||
return
|
||||
}
|
||||
|
||||
func (wrapped WrappedImage) Bounds () (bounds image.Rectangle) {
|
||||
bounds = wrapped.Underlying.Bounds()
|
||||
return
|
||||
}
|
||||
|
||||
func (wrapped WrappedImage) ColorModel () (model color.Model) {
|
||||
model = wrapped.Underlying.ColorModel()
|
||||
return
|
||||
}
|
||||
|
||||
func (wrapped WrappedImage) At (x, y int) (pixel color.Color) {
|
||||
pixel = wrapped.Underlying.At(x, y)
|
||||
return
|
||||
}
|
||||
|
||||
func (wrapped WrappedImage) RGBAAt (x, y int) (pixel color.RGBA) {
|
||||
r, g, b, a := wrapped.Underlying.At(x, y).RGBA()
|
||||
pixel.R = uint8(r >> 8)
|
||||
pixel.G = uint8(g >> 8)
|
||||
pixel.B = uint8(b >> 8)
|
||||
pixel.A = uint8(a >> 8)
|
||||
return
|
||||
}
|
||||
|
||||
// WrappedCanvas wraps a draw.Image and allows it to satisfy tomo.Canvas.
|
||||
type WrappedCanvas struct { Underlying draw.Image }
|
||||
|
||||
// WrapCanvas wraps a generic draw.Image and allows it to satisfy tomo.Canvas.
|
||||
// Do not use this function to wrap images that already satisfy tomo.Canvas,
|
||||
// because the resulting wrapped image will be rather slow in comparison.
|
||||
func WrapCanvas (underlying draw.Image) (wrapped tomo.Canvas) {
|
||||
wrapped = WrappedCanvas { Underlying: underlying }
|
||||
return
|
||||
}
|
||||
|
||||
func (wrapped WrappedCanvas) Bounds () (bounds image.Rectangle) {
|
||||
bounds = wrapped.Underlying.Bounds()
|
||||
return
|
||||
}
|
||||
|
||||
func (wrapped WrappedCanvas) ColorModel () (model color.Model) {
|
||||
model = wrapped.Underlying.ColorModel()
|
||||
return
|
||||
}
|
||||
|
||||
func (wrapped WrappedCanvas) At (x, y int) (pixel color.Color) {
|
||||
pixel = wrapped.Underlying.At(x, y)
|
||||
return
|
||||
}
|
||||
|
||||
func (wrapped WrappedCanvas) RGBAAt (x, y int) (pixel color.RGBA) {
|
||||
r, g, b, a := wrapped.Underlying.At(x, y).RGBA()
|
||||
pixel.R = uint8(r >> 8)
|
||||
pixel.G = uint8(g >> 8)
|
||||
pixel.B = uint8(b >> 8)
|
||||
pixel.A = uint8(a >> 8)
|
||||
return
|
||||
}
|
||||
|
||||
func (wrapped WrappedCanvas) Set (x, y int, pixel color.Color) {
|
||||
wrapped.Underlying.Set(x, y, pixel)
|
||||
}
|
||||
|
||||
func (wrapped WrappedCanvas) SetRGBA (x, y int, pixel color.RGBA) {
|
||||
wrapped.Underlying.Set(x, y, pixel)
|
||||
}
|
||||
|
||||
// ToRGBA clones an existing image.Image into an image.RGBA struct, which
|
||||
// directly satisfies tomo.Image. This is useful for things like icons and
|
||||
// textures.
|
||||
func ToRGBA (input image.Image) (output *image.RGBA) {
|
||||
bounds := input.Bounds()
|
||||
output = image.NewRGBA(bounds)
|
||||
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||
output.Set(x, y, input.At(x, y))
|
||||
}}
|
||||
return
|
||||
}
|
||||
|
@ -187,8 +187,9 @@ func (window *Window) reallocateCanvas () {
|
||||
}
|
||||
|
||||
func (window *Window) redrawChildEntirely () {
|
||||
data, stride := window.child.Buffer()
|
||||
window.xCanvas.For (func (x, y int) (c xgraphics.BGRA) {
|
||||
rgba := window.child.RGBAAt(x, y)
|
||||
rgba := data[x + y * stride]
|
||||
c.R, c.G, c.B, c.A = rgba.R, rgba.G, rgba.B, rgba.A
|
||||
return
|
||||
})
|
||||
@ -206,13 +207,14 @@ func (window *Window) resizeChildToFit () {
|
||||
window.redrawChildEntirely()
|
||||
}
|
||||
|
||||
func (window *Window) childDrawCallback (region tomo.Image) {
|
||||
func (window *Window) childDrawCallback (region tomo.Canvas) {
|
||||
if window.skipChildDrawCallback { return }
|
||||
|
||||
data, stride := region.Buffer()
|
||||
bounds := region.Bounds()
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||
rgba := region.RGBAAt(x, y)
|
||||
rgba := data[x + y * stride]
|
||||
window.xCanvas.SetBGRA (x, y, xgraphics.BGRA {
|
||||
R: rgba.R,
|
||||
G: rgba.G,
|
||||
|
70
canvas.go
Normal file
70
canvas.go
Normal file
@ -0,0 +1,70 @@
|
||||
package tomo
|
||||
|
||||
import "image"
|
||||
import "image/draw"
|
||||
import "image/color"
|
||||
|
||||
// Canvas is like Image but also requires Set and SetRGBA methods. This
|
||||
// interface can be easily satisfied using an image.RGBA struct.
|
||||
type Canvas interface {
|
||||
draw.Image
|
||||
Buffer () (data []color.RGBA, stride int)
|
||||
}
|
||||
|
||||
// BasicCanvas is a general purpose implementation of tomo.Canvas.
|
||||
type BasicCanvas struct {
|
||||
pix []color.RGBA
|
||||
stride int
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
// NewBasicCanvas creates a new basic canvas with the specified width and
|
||||
// height, allocating a buffer for it.
|
||||
func NewBasicCanvas (width, height int) (canvas BasicCanvas) {
|
||||
canvas.pix = make([]color.RGBA, height * width)
|
||||
canvas.stride = width
|
||||
canvas.rect = image.Rect(0, 0, width, height)
|
||||
return
|
||||
}
|
||||
|
||||
// you know what it do
|
||||
func (canvas BasicCanvas) Bounds () (bounds image.Rectangle) {
|
||||
return canvas.rect
|
||||
}
|
||||
|
||||
// you know what it do
|
||||
func (canvas BasicCanvas) At (x, y int) (color.Color) {
|
||||
if !image.Pt(x, y).In(canvas.rect) { return nil }
|
||||
return canvas.pix[x + y * canvas.stride]
|
||||
}
|
||||
|
||||
// you know what it do
|
||||
func (canvas BasicCanvas) ColorModel () (model color.Model) {
|
||||
return color.RGBAModel
|
||||
}
|
||||
|
||||
// you know what it do
|
||||
func (canvas BasicCanvas) Set (x, y int, c color.Color) {
|
||||
if !image.Pt(x, y).In(canvas.rect) { return }
|
||||
r, g, b, a := c.RGBA()
|
||||
canvas.pix[x + y * canvas.stride] = color.RGBA {
|
||||
R: uint8(r >> 8),
|
||||
G: uint8(g >> 8),
|
||||
B: uint8(b >> 8),
|
||||
A: uint8(a >> 8),
|
||||
}
|
||||
}
|
||||
|
||||
// you know what it do
|
||||
func (canvas BasicCanvas) Buffer () (data []color.RGBA, stride int) {
|
||||
return canvas.pix, canvas.stride
|
||||
}
|
||||
|
||||
// Cut returns a sub-canvas of a given canvas.
|
||||
func Cut (canvas Canvas, bounds image.Rectangle) (reduced BasicCanvas) {
|
||||
bounds = bounds.Intersect(canvas.Bounds())
|
||||
if bounds.Empty() { return }
|
||||
reduced.rect = bounds
|
||||
reduced.pix, reduced.stride = canvas.Buffer()
|
||||
return
|
||||
}
|
@ -57,7 +57,7 @@ func (element *Container) Adopt (child tomo.Element, expand bool) {
|
||||
|
||||
return
|
||||
},
|
||||
Draw: func (region tomo.Image) {
|
||||
Draw: func (region tomo.Canvas) {
|
||||
element.drawChildRegion(child, region)
|
||||
},
|
||||
})
|
||||
@ -318,10 +318,9 @@ func (element *Container) recalculate () {
|
||||
func (element *Container) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
|
||||
artist.Rectangle (
|
||||
artist.FillRectangle (
|
||||
element.core,
|
||||
theme.BackgroundImage(),
|
||||
nil, 0,
|
||||
bounds)
|
||||
|
||||
for _, entry := range element.children {
|
||||
@ -329,7 +328,7 @@ func (element *Container) draw () {
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Container) drawChildRegion (child tomo.Element, region tomo.Image) {
|
||||
func (element *Container) drawChildRegion (child tomo.Element, region tomo.Canvas) {
|
||||
if element.warping { return }
|
||||
for _, entry := range element.children {
|
||||
if entry.Element == child {
|
||||
|
@ -93,10 +93,9 @@ func (element *Label) updateMinimumSize () {
|
||||
func (element *Label) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
|
||||
artist.Rectangle (
|
||||
artist.FillRectangle (
|
||||
element.core,
|
||||
theme.BackgroundImage(),
|
||||
nil, 0,
|
||||
bounds)
|
||||
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
|
@ -7,7 +7,7 @@ import "git.tebibyte.media/sashakoshka/tomo"
|
||||
// Core is a struct that implements some core functionality common to most
|
||||
// widgets. It is meant to be embedded directly into a struct.
|
||||
type Core struct {
|
||||
canvas *image.RGBA
|
||||
canvas tomo.BasicCanvas
|
||||
parent tomo.Element
|
||||
|
||||
metrics struct {
|
||||
@ -32,20 +32,19 @@ func (core Core) ColorModel () (model color.Model) {
|
||||
}
|
||||
|
||||
func (core Core) At (x, y int) (pixel color.Color) {
|
||||
if core.canvas == nil { return color.RGBA { } }
|
||||
pixel = core.canvas.At(x, y)
|
||||
return
|
||||
}
|
||||
|
||||
func (core Core) RGBAAt (x, y int) (pixel color.RGBA) {
|
||||
if core.canvas == nil { return color.RGBA { } }
|
||||
pixel = core.canvas.RGBAAt(x, y)
|
||||
return
|
||||
return core.canvas.At(x, y)
|
||||
}
|
||||
|
||||
func (core Core) Bounds () (bounds image.Rectangle) {
|
||||
if core.canvas != nil { bounds = core.canvas.Bounds() }
|
||||
return
|
||||
return core.canvas.Bounds()
|
||||
}
|
||||
|
||||
func (core Core) Set (x, y int, c color.Color) () {
|
||||
core.canvas.Set(x, y, c)
|
||||
}
|
||||
|
||||
func (core Core) Buffer () (data []color.RGBA, stride int) {
|
||||
return core.canvas.Buffer()
|
||||
}
|
||||
|
||||
func (core Core) Selectable () (selectable bool) {
|
||||
@ -72,13 +71,12 @@ func (core Core) MinimumSize () (width, height int) {
|
||||
// be used as a canvas. It must not be directly embedded into an element, but
|
||||
// instead kept as a private member.
|
||||
type CoreControl struct {
|
||||
*image.RGBA
|
||||
tomo.BasicCanvas
|
||||
core *Core
|
||||
}
|
||||
|
||||
func (control CoreControl) HasImage () (has bool) {
|
||||
has = control.RGBA != nil
|
||||
return
|
||||
func (control CoreControl) HasImage () (empty bool) {
|
||||
return !control.Bounds().Empty()
|
||||
}
|
||||
|
||||
func (control CoreControl) Select () (granted bool) {
|
||||
@ -98,7 +96,7 @@ func (control CoreControl) SetSelectable (selectable bool) {
|
||||
}
|
||||
|
||||
func (control CoreControl) PushRegion (bounds image.Rectangle) {
|
||||
control.core.hooks.RunDraw(control.SubImage(bounds).(*image.RGBA))
|
||||
control.core.hooks.RunDraw(tomo.Cut(control, bounds))
|
||||
}
|
||||
|
||||
func (control CoreControl) PushAll () {
|
||||
@ -108,8 +106,8 @@ func (control CoreControl) PushAll () {
|
||||
func (control *CoreControl) AllocateCanvas (width, height int) {
|
||||
core := control.core
|
||||
width, height, _ = control.ConstrainSize(width, height)
|
||||
core.canvas = image.NewRGBA(image.Rect (0, 0, width, height))
|
||||
control.RGBA = core.canvas
|
||||
core.canvas = tomo.NewBasicCanvas(width, height)
|
||||
control.BasicCanvas = core.canvas
|
||||
}
|
||||
|
||||
func (control CoreControl) SetMinimumSize (width, height int) {
|
||||
@ -125,19 +123,17 @@ func (control CoreControl) SetMinimumSize (width, height int) {
|
||||
|
||||
// if there is an image buffer, and the current size is less
|
||||
// than this new minimum size, send core.parent a resize event.
|
||||
if control.HasImage() {
|
||||
bounds := control.Bounds()
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
constrained := control.ConstrainSize (
|
||||
bounds.Dx(),
|
||||
bounds.Dy())
|
||||
if constrained {
|
||||
core.parent.Handle (tomo.EventResize {
|
||||
Width: imageWidth,
|
||||
Height: imageHeight,
|
||||
})
|
||||
}
|
||||
bounds := control.Bounds()
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
constrained := control.ConstrainSize (
|
||||
bounds.Dx(),
|
||||
bounds.Dy())
|
||||
if constrained {
|
||||
core.parent.Handle (tomo.EventResize {
|
||||
Width: imageWidth,
|
||||
Height: imageHeight,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ func (element *AnalogClock) draw () {
|
||||
}
|
||||
|
||||
func (element *AnalogClock) radialLine (
|
||||
source tomo.Image,
|
||||
source artist.Pattern,
|
||||
inner float64,
|
||||
outer float64,
|
||||
radian float64,
|
||||
|
@ -13,7 +13,7 @@ type Mouse struct {
|
||||
*core.Core
|
||||
core core.CoreControl
|
||||
drawing bool
|
||||
color tomo.Image
|
||||
color artist.Pattern
|
||||
lastMousePos image.Point
|
||||
}
|
||||
|
||||
@ -33,11 +33,11 @@ func (element *Mouse) Handle (event tomo.Event) {
|
||||
element.core.AllocateCanvas (
|
||||
resizeEvent.Width,
|
||||
resizeEvent.Height)
|
||||
artist.Rectangle (
|
||||
artist.FillRectangle (
|
||||
element.core,
|
||||
theme.AccentImage(),
|
||||
artist.NewUniform(color.Black),
|
||||
1, element.Bounds())
|
||||
element.Bounds())
|
||||
// TODO: draw a stroked rectangle around the edges
|
||||
artist.Line (
|
||||
element.core, artist.NewUniform(color.White), 1,
|
||||
image.Pt(1, 1),
|
||||
|
@ -3,7 +3,6 @@ package theme
|
||||
import "image"
|
||||
import "image/color"
|
||||
import "golang.org/x/image/font"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/defaultfont"
|
||||
|
||||
@ -151,34 +150,34 @@ func InputProfile (enabled bool, selected bool) artist.ShadingProfile {
|
||||
|
||||
// BackgroundImage returns the texture/color used for the fill of
|
||||
// BackgroundProfile.
|
||||
func BackgroundImage () tomo.Image {
|
||||
func BackgroundImage () artist.Pattern {
|
||||
return backgroundImage
|
||||
}
|
||||
|
||||
// RaisedImage returns the texture/color used for the fill of RaisedProfile.
|
||||
func RaisedImage () tomo.Image {
|
||||
func RaisedImage () artist.Pattern {
|
||||
return raisedImage
|
||||
}
|
||||
|
||||
// InputImage returns the texture/color used for the fill of InputProfile.
|
||||
func InputImage () tomo.Image {
|
||||
func InputImage () artist.Pattern {
|
||||
return inputImage
|
||||
}
|
||||
|
||||
// ForegroundImage returns the texture/color text and monochromatic icons should
|
||||
// be drawn with.
|
||||
func ForegroundImage () tomo.Image {
|
||||
func ForegroundImage () artist.Pattern {
|
||||
return foregroundImage
|
||||
}
|
||||
|
||||
// DisabledForegroundImage returns the texture/color text and monochromatic
|
||||
// icons should be drawn with if they are disabled.
|
||||
func DisabledForegroundImage () tomo.Image {
|
||||
func DisabledForegroundImage () artist.Pattern {
|
||||
return disabledForegroundImage
|
||||
}
|
||||
|
||||
// AccentImage returns the accent texture/color.
|
||||
func AccentImage () tomo.Image {
|
||||
func AccentImage () artist.Pattern {
|
||||
return accentImage
|
||||
}
|
||||
|
||||
|
29
tomo.go
29
tomo.go
@ -2,25 +2,6 @@ package tomo
|
||||
|
||||
import "image"
|
||||
import "errors"
|
||||
import "image/draw"
|
||||
import "image/color"
|
||||
|
||||
// Image represents a simple image buffer that fulfills the image.Image
|
||||
// interface while also having methods that do away with the use of the
|
||||
// color.Color interface to facilitate more efficient drawing. This interface
|
||||
// can be easily satisfied using an image.RGBA struct.
|
||||
type Image interface {
|
||||
image.Image
|
||||
RGBAAt (x, y int) (c color.RGBA)
|
||||
}
|
||||
|
||||
// Canvas is like Image but also requires Set and SetRGBA methods. This
|
||||
// interface can be easily satisfied using an image.RGBA struct.
|
||||
type Canvas interface {
|
||||
draw.Image
|
||||
RGBAAt (x, y int) (c color.RGBA)
|
||||
SetRGBA (x, y int, c color.RGBA)
|
||||
}
|
||||
|
||||
// ParentHooks is a struct that contains callbacks that let child elements send
|
||||
// information to their parent element without the child element knowing
|
||||
@ -29,7 +10,7 @@ type Canvas interface {
|
||||
type ParentHooks struct {
|
||||
// Draw is called when a part of the child element's surface is updated.
|
||||
// The updated region will be passed to the callback as a sub-image.
|
||||
Draw func (region Image)
|
||||
Draw func (region Canvas)
|
||||
|
||||
// MinimumSizeChange is called when the child element's minimum width
|
||||
// and/or height changes. When this function is called, the element will
|
||||
@ -49,7 +30,7 @@ type ParentHooks struct {
|
||||
}
|
||||
|
||||
// RunDraw runs the Draw hook if it is not nil. If it is nil, it does nothing.
|
||||
func (hooks ParentHooks) RunDraw (region Image) {
|
||||
func (hooks ParentHooks) RunDraw (region Canvas) {
|
||||
if hooks.Draw != nil {
|
||||
hooks.Draw(region)
|
||||
}
|
||||
@ -82,10 +63,10 @@ func (hooks ParentHooks) RunSelectabilityChange (selectable bool) {
|
||||
|
||||
// Element represents a basic on-screen object.
|
||||
type Element interface {
|
||||
// Element must implement the Image interface. Elements should start out
|
||||
// with a completely blank image buffer, and only set its size and draw
|
||||
// Element must implement the Canvas interface. Elements should start
|
||||
// out with a completely blank buffer, and only allocate memory and draw
|
||||
// on it for the first time when sent an EventResize event.
|
||||
Image
|
||||
Canvas
|
||||
|
||||
// Handle handles an event, propagating it to children if necessary.
|
||||
Handle (event Event)
|
||||
|
Reference in New Issue
Block a user