Bring over X canvas package
This commit is contained in:
parent
3a5fde7d2e
commit
41ccb2dce4
133
x/canvas/canvas.go
Normal file
133
x/canvas/canvas.go
Normal file
@ -0,0 +1,133 @@
|
||||
package xcanvas
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
import "github.com/jezek/xgbutil"
|
||||
import "github.com/jezek/xgb/xproto"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
// Canvas satisfies the canvas.Canvas interface. It draws to an xgraphics.Image.
|
||||
// It must be closed after use.
|
||||
type Canvas struct {
|
||||
*xgraphics.Image
|
||||
}
|
||||
|
||||
// NewCanvas creates a new canvas from a bounding rectangle.
|
||||
func NewCanvas (x *xgbutil.XUtil, bounds image.Rectangle) *Canvas {
|
||||
return NewCanvasFrom(xgraphics.New(x, bounds))
|
||||
}
|
||||
|
||||
// NewCanvasFrom creates a new canvas from an existing xgraphics.Image. Note
|
||||
// that calling Close() on the resulting canvas will destroy this image.
|
||||
func NewCanvasFrom (image *xgraphics.Image) *Canvas {
|
||||
if image == nil { return nil }
|
||||
return &Canvas { image }
|
||||
}
|
||||
|
||||
// Pen returns a new drawing context.
|
||||
func (this *Canvas) Pen () canvas.Pen {
|
||||
return &pen {
|
||||
image: this.Image,
|
||||
}
|
||||
}
|
||||
|
||||
// SubCanvas returns a subset of this canvas that points to the same data.
|
||||
func (this *Canvas) SubCanvas (bounds image.Rectangle) canvas.Canvas {
|
||||
this.assert()
|
||||
subImage := this.Image.SubImage(bounds)
|
||||
if subImage == nil { return nil }
|
||||
xImage := subImage.(*xgraphics.Image)
|
||||
return &Canvas { xImage }
|
||||
}
|
||||
|
||||
// Push pushes this canvas to the screen.
|
||||
func (this *Canvas) Push (window xproto.Window) {
|
||||
this.assert()
|
||||
this.XDraw()
|
||||
this.XExpPaint(window, this.Bounds().Min.X, this.Bounds().Min.Y)
|
||||
}
|
||||
|
||||
// Close frees this canvas from the X server.
|
||||
func (this *Canvas) Close () {
|
||||
this.assert()
|
||||
this.Image.Destroy()
|
||||
}
|
||||
|
||||
func (this *Canvas) assert () {
|
||||
if this == nil { panic("nil canvas") }
|
||||
}
|
||||
|
||||
// TODO: we need to implement:
|
||||
// - cap
|
||||
// - joint
|
||||
// - align
|
||||
|
||||
type pen struct {
|
||||
image *xgraphics.Image
|
||||
|
||||
closed bool
|
||||
endCap canvas.Cap
|
||||
joint canvas.Joint
|
||||
weight int
|
||||
align canvas.StrokeAlign
|
||||
stroke xgraphics.BGRA
|
||||
fill xgraphics.BGRA
|
||||
texture *Texture
|
||||
}
|
||||
|
||||
func (this *pen) Rectangle (bounds image.Rectangle) {
|
||||
bounds = bounds.Canon()
|
||||
if this.weight == 0 {
|
||||
if this.fill.A > 0 && !this.textureObscures() {
|
||||
this.fillRectangle(this.fill, bounds)
|
||||
}
|
||||
if this.texture != nil {
|
||||
this.textureRectangle(bounds)
|
||||
}
|
||||
} else {
|
||||
if this.stroke.A > 0 {
|
||||
this.strokeRectangle(this.stroke, bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *pen) Path (points ...image.Point) {
|
||||
if this.weight == 0 {
|
||||
if this.fill.A > 0 {
|
||||
this.fillPolygon(this.fill, points...)
|
||||
}
|
||||
} else if this.closed && len(points) > 2 {
|
||||
if this.stroke.A > 0 {
|
||||
this.strokePolygon(this.stroke, points...)
|
||||
}
|
||||
} else {
|
||||
if this.stroke.A > 0 {
|
||||
this.polyLine(this.stroke, points...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *pen) Closed (closed bool) { this.closed = closed }
|
||||
func (this *pen) Cap (endCap canvas.Cap) { this.endCap = endCap }
|
||||
func (this *pen) Joint (joint canvas.Joint) { this.joint = joint }
|
||||
func (this *pen) StrokeWeight (weight int) { this.weight = weight }
|
||||
func (this *pen) StrokeAlign (align canvas.StrokeAlign) { this.align = align }
|
||||
|
||||
func (this *pen) Stroke (stroke color.Color) { this.stroke = convertColor(stroke) }
|
||||
func (this *pen) Fill (fill color.Color) { this.fill = convertColor(fill) }
|
||||
func (this *pen) Texture (texture canvas.Texture) { this.texture = AssertTexture(texture) }
|
||||
|
||||
func (this *pen) textureObscures () bool {
|
||||
return this.texture != nil && this.texture.Opaque()
|
||||
}
|
||||
|
||||
func convertColor (c color.Color) xgraphics.BGRA {
|
||||
r, g, b, a := c.RGBA()
|
||||
return xgraphics.BGRA {
|
||||
B: uint8(b >> 8),
|
||||
G: uint8(g >> 8),
|
||||
R: uint8(r >> 8),
|
||||
A: uint8(a >> 8),
|
||||
}
|
||||
}
|
296
x/canvas/draw.go
Normal file
296
x/canvas/draw.go
Normal file
@ -0,0 +1,296 @@
|
||||
package xcanvas
|
||||
|
||||
import "sort"
|
||||
import "image"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
|
||||
func (this *pen) textureRectangle (bounds image.Rectangle) {
|
||||
if this.texture.Opaque() {
|
||||
this.textureRectangleOpaque(bounds)
|
||||
} else {
|
||||
this.textureRectangleTransparent(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *pen) textureRectangleOpaque (bounds image.Rectangle) {
|
||||
dstBounds := bounds.Intersect(this.image.Bounds())
|
||||
var pos image.Point
|
||||
|
||||
dst := this.image.Pix
|
||||
src := this.texture.pix
|
||||
offset := this.texture.rect.Min.Sub(bounds.Min)
|
||||
|
||||
for pos.Y = dstBounds.Min.Y; pos.Y < dstBounds.Max.Y; pos.Y ++ {
|
||||
for pos.X = dstBounds.Min.X; pos.X < dstBounds.Max.X; pos.X ++ {
|
||||
srcPos := pos.Add(offset)
|
||||
dstIndex := this.image.PixOffset(pos.X, pos.Y)
|
||||
srcIndex := this.texture.PixOffset(srcPos.X, srcPos.Y)
|
||||
dst[dstIndex + 0] = src[srcIndex + 0]
|
||||
dst[dstIndex + 1] = src[srcIndex + 1]
|
||||
dst[dstIndex + 2] = src[srcIndex + 2]
|
||||
dst[dstIndex + 3] = src[srcIndex + 3]
|
||||
}}
|
||||
}
|
||||
|
||||
func (this *pen) textureRectangleTransparent (bounds image.Rectangle) {
|
||||
dstBounds := bounds.Intersect(this.image.Bounds())
|
||||
var pos image.Point
|
||||
|
||||
dst := this.image.Pix
|
||||
src := this.texture.pix
|
||||
offset := this.texture.rect.Min.Sub(bounds.Min)
|
||||
|
||||
for pos.Y = dstBounds.Min.Y; pos.Y < dstBounds.Max.Y; pos.Y ++ {
|
||||
for pos.X = dstBounds.Min.X; pos.X < dstBounds.Max.X; pos.X ++ {
|
||||
srcPos := pos.Add(offset)
|
||||
dstIndex := this.image.PixOffset(pos.X, pos.Y)
|
||||
srcIndex := this.texture.PixOffset(srcPos.X, srcPos.Y)
|
||||
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
|
||||
B: dst[dstIndex + 0],
|
||||
G: dst[dstIndex + 1],
|
||||
R: dst[dstIndex + 2],
|
||||
A: dst[dstIndex + 3],
|
||||
}, xgraphics.BGRA {
|
||||
B: src[srcIndex + 0],
|
||||
G: src[srcIndex + 1],
|
||||
R: src[srcIndex + 2],
|
||||
A: src[srcIndex + 3],
|
||||
})
|
||||
dst[dstIndex + 0] = pixel.B
|
||||
dst[dstIndex + 1] = pixel.G
|
||||
dst[dstIndex + 2] = pixel.R
|
||||
dst[dstIndex + 3] = pixel.A
|
||||
}}
|
||||
}
|
||||
|
||||
func (this *pen) fillRectangle (c xgraphics.BGRA, bounds image.Rectangle) {
|
||||
if c.A == 255 {
|
||||
this.fillRectangleOpaque(c, bounds)
|
||||
} else {
|
||||
this.fillRectangleTransparent(c, bounds)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *pen) fillRectangleOpaque (c xgraphics.BGRA, bounds image.Rectangle) {
|
||||
bounds = bounds.Intersect(this.image.Bounds())
|
||||
var pos image.Point
|
||||
|
||||
for pos.Y = bounds.Min.Y; pos.Y < bounds.Max.Y; pos.Y ++ {
|
||||
for pos.X = bounds.Min.X; pos.X < bounds.Max.X; pos.X ++ {
|
||||
index := this.image.PixOffset(pos.X, pos.Y)
|
||||
this.image.Pix[index + 0] = c.B
|
||||
this.image.Pix[index + 1] = c.G
|
||||
this.image.Pix[index + 2] = c.R
|
||||
this.image.Pix[index + 3] = c.A
|
||||
}}
|
||||
}
|
||||
|
||||
func (this *pen) fillRectangleTransparent (c xgraphics.BGRA, bounds image.Rectangle) {
|
||||
bounds = bounds.Intersect(this.image.Bounds())
|
||||
var pos image.Point
|
||||
|
||||
for pos.Y = bounds.Min.Y; pos.Y < bounds.Max.Y; pos.Y ++ {
|
||||
for pos.X = bounds.Min.X; pos.X < bounds.Max.X; pos.X ++ {
|
||||
index := this.image.PixOffset(pos.X, pos.Y)
|
||||
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
|
||||
B: this.image.Pix[index + 0],
|
||||
G: this.image.Pix[index + 1],
|
||||
R: this.image.Pix[index + 2],
|
||||
A: this.image.Pix[index + 3],
|
||||
}, c)
|
||||
this.image.Pix[index + 0] = pixel.B
|
||||
this.image.Pix[index + 1] = pixel.G
|
||||
this.image.Pix[index + 2] = pixel.R
|
||||
this.image.Pix[index + 3] = pixel.A
|
||||
}}
|
||||
}
|
||||
|
||||
func (this *pen) strokeRectangle (c xgraphics.BGRA, bounds image.Rectangle) {
|
||||
if this.weight > bounds.Dx() / 2 || this.weight > bounds.Dy() / 2 {
|
||||
this.fillRectangle(c, bounds)
|
||||
return
|
||||
}
|
||||
|
||||
top := image.Rect (
|
||||
bounds.Min.X,
|
||||
bounds.Min.Y,
|
||||
bounds.Max.X,
|
||||
bounds.Min.Y + this.weight)
|
||||
bottom := image.Rect (
|
||||
bounds.Min.X,
|
||||
bounds.Max.Y - this.weight,
|
||||
bounds.Max.X,
|
||||
bounds.Max.Y)
|
||||
left := image.Rect (
|
||||
bounds.Min.X,
|
||||
bounds.Min.Y + this.weight,
|
||||
bounds.Min.X + this.weight,
|
||||
bounds.Max.Y - this.weight)
|
||||
right := image.Rect (
|
||||
bounds.Max.X - this.weight,
|
||||
bounds.Min.Y + this.weight,
|
||||
bounds.Max.X,
|
||||
bounds.Max.Y - this.weight)
|
||||
|
||||
this.fillRectangle(c, top,)
|
||||
this.fillRectangle(c, bottom,)
|
||||
this.fillRectangle(c, left,)
|
||||
this.fillRectangle(c, right,)
|
||||
}
|
||||
|
||||
// the polygon filling algorithm is adapted from:
|
||||
// https://www.alienryderflex.com/polygon_fill/
|
||||
// (if you write C like that i will disassemble you)
|
||||
|
||||
func (this *pen) fillPolygon (c xgraphics.BGRA, points ...image.Point) {
|
||||
if len(points) < 3 { return }
|
||||
|
||||
// figure out the bounds of the polygon so we don't test empty space
|
||||
var area image.Rectangle
|
||||
area.Min = points[0]
|
||||
area.Max = points[0]
|
||||
for _, point := range points[1:] {
|
||||
if point.X < area.Min.X { area.Min.X = point.X }
|
||||
if point.Y < area.Min.Y { area.Min.Y = point.Y }
|
||||
if point.X > area.Max.X { area.Max.X = point.X }
|
||||
if point.Y > area.Max.Y { area.Max.Y = point.Y }
|
||||
}
|
||||
area = this.image.Bounds().Intersect(area)
|
||||
if area.Empty() { return }
|
||||
|
||||
context := fillingContext {
|
||||
image: this.image,
|
||||
color: this.fill,
|
||||
min: area.Min.X,
|
||||
max: area.Max.X,
|
||||
boundaries: make([]int, len(points)),
|
||||
points: points,
|
||||
}
|
||||
|
||||
for context.y = area.Min.Y; context.y < area.Max.Y; context.y ++ {
|
||||
// build boundary list
|
||||
boundaryCount := 0
|
||||
prevPoint := points[len(points) - 1]
|
||||
for _, point := range points {
|
||||
fy := float64(context.y)
|
||||
fPointX := float64(point.X)
|
||||
fPointY := float64(point.Y)
|
||||
fPrevX := float64(prevPoint.X)
|
||||
fPrevY := float64(prevPoint.Y)
|
||||
addboundary :=
|
||||
(fPointY < fy && fPrevY >= fy) ||
|
||||
(fPrevY < fy && fPointY >= fy)
|
||||
if addboundary {
|
||||
context.boundaries[boundaryCount] = int (
|
||||
fPointX +
|
||||
(fy - fPointY) /
|
||||
(fPrevY - fPointY) *
|
||||
(fPrevX - fPointX))
|
||||
boundaryCount ++
|
||||
}
|
||||
prevPoint = point
|
||||
}
|
||||
|
||||
// sort boundary list
|
||||
cutBoundaries := context.boundaries[:boundaryCount]
|
||||
sort.Ints(cutBoundaries)
|
||||
|
||||
// fill pixels between boundary pairs
|
||||
if c.A == 255 {
|
||||
context.fillPolygonHotOpaque()
|
||||
} else {
|
||||
context.fillPolygonHotTransparent()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type fillingContext struct {
|
||||
image *xgraphics.Image
|
||||
color xgraphics.BGRA
|
||||
min, max int
|
||||
y int
|
||||
boundaries []int
|
||||
points []image.Point
|
||||
}
|
||||
|
||||
func (context *fillingContext) fillPolygonHotOpaque () {
|
||||
for index := 0; index < len(context.boundaries); index += 2 {
|
||||
left := context.boundaries[index]
|
||||
right := context.boundaries[index + 1]
|
||||
|
||||
// stop if we have exited the polygon
|
||||
if left >= context.max { break }
|
||||
// begin filling if we are within the polygon
|
||||
if right > context.min {
|
||||
// constrain boundaries to image size
|
||||
if left < context.min { left = context.min }
|
||||
if right > context.max { right = context.max }
|
||||
|
||||
// fill pixels in between
|
||||
for x := left; x < right; x ++ {
|
||||
index := context.image.PixOffset(x, context.y)
|
||||
context.image.Pix[index + 0] = context.color.B
|
||||
context.image.Pix[index + 1] = context.color.G
|
||||
context.image.Pix[index + 2] = context.color.R
|
||||
context.image.Pix[index + 3] = context.color.A
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (context *fillingContext) fillPolygonHotTransparent () {
|
||||
for index := 0; index < len(context.boundaries); index += 2 {
|
||||
left := context.boundaries[index]
|
||||
right := context.boundaries[index + 1]
|
||||
|
||||
// stop if we have exited the polygon
|
||||
if left >= context.max { break }
|
||||
// begin filling if we are within the polygon
|
||||
if right > context.min {
|
||||
// constrain boundaries to image size
|
||||
if left < context.min { left = context.min }
|
||||
if right > context.max { right = context.max }
|
||||
|
||||
// fill pixels in between
|
||||
for x := left; x < right; x ++ {
|
||||
index := context.image.PixOffset(x, context.y)
|
||||
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
|
||||
B: context.image.Pix[index + 0],
|
||||
G: context.image.Pix[index + 1],
|
||||
R: context.image.Pix[index + 2],
|
||||
A: context.image.Pix[index + 3],
|
||||
}, context.color)
|
||||
context.image.Pix[index + 0] = pixel.B
|
||||
context.image.Pix[index + 1] = pixel.G
|
||||
context.image.Pix[index + 2] = pixel.R
|
||||
context.image.Pix[index + 3] = pixel.A
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *pen) strokePolygon (c xgraphics.BGRA, points ...image.Point) {
|
||||
prevPoint := points[len(points) - 1]
|
||||
for _, point := range points {
|
||||
this.line(c, prevPoint, point)
|
||||
prevPoint = point
|
||||
}
|
||||
}
|
||||
|
||||
func (this *pen) polyLine (c xgraphics.BGRA, points ...image.Point) {
|
||||
if len(points) < 2 { return }
|
||||
prevPoint := points[0]
|
||||
for _, point := range points[1:] {
|
||||
this.line(c, prevPoint, point)
|
||||
prevPoint = point
|
||||
}
|
||||
}
|
||||
|
||||
func wrap (n, min, max int) int {
|
||||
max -= min
|
||||
n -= min
|
||||
n %= max
|
||||
if n < 0 { n += max }
|
||||
return n + min
|
||||
}
|
95
x/canvas/line.go
Normal file
95
x/canvas/line.go
Normal file
@ -0,0 +1,95 @@
|
||||
package xcanvas
|
||||
|
||||
import "image"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
|
||||
// TODO: clip the line to the bounds
|
||||
func (this *pen) line (
|
||||
c xgraphics.BGRA,
|
||||
min image.Point,
|
||||
max image.Point,
|
||||
) {
|
||||
context := linePlottingContext {
|
||||
plottingContext: plottingContext {
|
||||
image: this.image,
|
||||
color: c,
|
||||
weight: this.weight,
|
||||
},
|
||||
min: min,
|
||||
max: max,
|
||||
}
|
||||
|
||||
if abs(max.Y - min.Y) < abs(max.X - min.X) {
|
||||
if max.X < min.X { context.swap() }
|
||||
context.lineLow()
|
||||
|
||||
} else {
|
||||
if max.Y < min.Y { context.swap() }
|
||||
context.lineHigh()
|
||||
}
|
||||
}
|
||||
|
||||
type linePlottingContext struct {
|
||||
plottingContext
|
||||
min image.Point
|
||||
max image.Point
|
||||
}
|
||||
|
||||
func (context *linePlottingContext) swap () {
|
||||
temp := context.max
|
||||
context.max = context.min
|
||||
context.min = temp
|
||||
}
|
||||
|
||||
func (context linePlottingContext) lineLow () {
|
||||
deltaX := context.max.X - context.min.X
|
||||
deltaY := context.max.Y - context.min.Y
|
||||
yi := 1
|
||||
|
||||
if deltaY < 0 {
|
||||
yi = -1
|
||||
deltaY *= -1
|
||||
}
|
||||
|
||||
D := (2 * deltaY) - deltaX
|
||||
point := context.min
|
||||
|
||||
for ; point.X < context.max.X; point.X ++ {
|
||||
context.plot(point)
|
||||
if D > 0 {
|
||||
D += 2 * (deltaY - deltaX)
|
||||
point.Y += yi
|
||||
} else {
|
||||
D += 2 * deltaY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (context linePlottingContext) lineHigh () {
|
||||
deltaX := context.max.X - context.min.X
|
||||
deltaY := context.max.Y - context.min.Y
|
||||
xi := 1
|
||||
|
||||
if deltaX < 0 {
|
||||
xi = -1
|
||||
deltaX *= -1
|
||||
}
|
||||
|
||||
D := (2 * deltaX) - deltaY
|
||||
point := context.min
|
||||
|
||||
for ; point.Y < context.max.Y; point.Y ++ {
|
||||
context.plot(point)
|
||||
if D > 0 {
|
||||
point.X += xi
|
||||
D += 2 * (deltaX - deltaY)
|
||||
} else {
|
||||
D += 2 * deltaX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func abs (n int) int {
|
||||
if n < 0 { n *= -1}
|
||||
return n
|
||||
}
|
47
x/canvas/plot.go
Normal file
47
x/canvas/plot.go
Normal file
@ -0,0 +1,47 @@
|
||||
package xcanvas
|
||||
|
||||
import "image"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
|
||||
type plottingContext struct {
|
||||
image *xgraphics.Image
|
||||
color xgraphics.BGRA
|
||||
weight int
|
||||
}
|
||||
|
||||
func (context plottingContext) square (center image.Point) (square image.Rectangle) {
|
||||
return image.Rect(0, 0, context.weight, context.weight).
|
||||
Sub(image.Pt(context.weight / 2, context.weight / 2)).
|
||||
Add(center).
|
||||
Intersect(context.image.Bounds())
|
||||
}
|
||||
|
||||
func (context plottingContext) plot (center image.Point) {
|
||||
square := context.square(center)
|
||||
|
||||
if context.color.A == 255 {
|
||||
for y := square.Min.Y; y < square.Max.Y; y ++ {
|
||||
for x := square.Min.X; x < square.Max.X; x ++ {
|
||||
index := context.image.PixOffset(x, y)
|
||||
context.image.Pix[index + 0] = context.color.B
|
||||
context.image.Pix[index + 1] = context.color.G
|
||||
context.image.Pix[index + 2] = context.color.R
|
||||
context.image.Pix[index + 3] = context.color.A
|
||||
}}
|
||||
} else {
|
||||
for y := square.Min.Y; y < square.Max.Y; y ++ {
|
||||
for x := square.Min.X; x < square.Max.X; x ++ {
|
||||
index := context.image.PixOffset(x, y)
|
||||
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
|
||||
B: context.image.Pix[index + 0],
|
||||
G: context.image.Pix[index + 1],
|
||||
R: context.image.Pix[index + 2],
|
||||
A: context.image.Pix[index + 3],
|
||||
}, context.color)
|
||||
context.image.Pix[index + 0] = pixel.B
|
||||
context.image.Pix[index + 1] = pixel.G
|
||||
context.image.Pix[index + 2] = pixel.R
|
||||
context.image.Pix[index + 3] = pixel.A
|
||||
}}
|
||||
}
|
||||
}
|
106
x/canvas/texture.go
Normal file
106
x/canvas/texture.go
Normal file
@ -0,0 +1,106 @@
|
||||
package xcanvas
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
import "github.com/jezek/xgbutil/xgraphics"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
// Texture is a read-only image texture that can be quickly written to a canvas.
|
||||
// It must be closed manually after use.
|
||||
type Texture struct {
|
||||
pix []uint8
|
||||
stride int
|
||||
rect image.Rectangle
|
||||
transparent bool
|
||||
}
|
||||
|
||||
// NewTextureFrom creates a new texture from a source image.
|
||||
func NewTextureFrom (source image.Image) *Texture {
|
||||
bounds := source.Bounds()
|
||||
texture := &Texture {
|
||||
pix: make([]uint8, bounds.Dx() * bounds.Dy() * 4),
|
||||
stride: bounds.Dx() * 4,
|
||||
rect: bounds.Sub(bounds.Min),
|
||||
}
|
||||
|
||||
index := 0
|
||||
var point image.Point
|
||||
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
|
||||
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
|
||||
r, g, b, a := source.At(point.X, point.Y).RGBA()
|
||||
texture.pix[index + 0] = uint8(b >> 8)
|
||||
texture.pix[index + 1] = uint8(g >> 8)
|
||||
texture.pix[index + 2] = uint8(r >> 8)
|
||||
texture.pix[index + 3] = uint8(a >> 8)
|
||||
index += 4
|
||||
|
||||
if a != 0xFFFF {
|
||||
texture.transparent = true
|
||||
}
|
||||
}}
|
||||
|
||||
return texture
|
||||
}
|
||||
|
||||
func (this *Texture) BGRAAt (x, y int) xgraphics.BGRA {
|
||||
if !(image.Point{ x, y }.In(this.rect)) {
|
||||
return xgraphics.BGRA { }
|
||||
}
|
||||
index := this.PixOffset(x, y)
|
||||
return xgraphics.BGRA {
|
||||
B: this.pix[index ],
|
||||
G: this.pix[index + 1],
|
||||
R: this.pix[index + 2],
|
||||
A: this.pix[index + 3],
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Texture) At (x, y int) color.Color {
|
||||
return this.BGRAAt(x, y)
|
||||
}
|
||||
|
||||
// Bounds returns the bounding rectangle of this texture.
|
||||
func (this *Texture) Bounds () image.Rectangle {
|
||||
return this.rect
|
||||
}
|
||||
|
||||
func (this *Texture) ColorModel () color.Model {
|
||||
return xgraphics.BGRAModel
|
||||
}
|
||||
|
||||
// Opaque reports whether or not the texture is fully opaque.
|
||||
func (this *Texture) Opaque () bool {
|
||||
return !this.transparent
|
||||
}
|
||||
|
||||
func (this *Texture) PixOffset (x, y int) int {
|
||||
x = wrap(x, this.rect.Min.X, this.rect.Max.X)
|
||||
y = wrap(y, this.rect.Min.Y, this.rect.Max.Y)
|
||||
return x * 4 + y * this.stride
|
||||
}
|
||||
|
||||
// Close frees the texture from memory.
|
||||
func (this *Texture) Close () error {
|
||||
// i lied we dont actually need to close this, but we will once this
|
||||
// texture resides on the x server or in video memory.
|
||||
return nil
|
||||
}
|
||||
|
||||
// SubTexture returns a subset of this texture that points to the same data.
|
||||
func (this *Texture) SubTexture (bounds image.Rectangle) canvas.Texture {
|
||||
clipped := *this
|
||||
clipped.rect = bounds
|
||||
return &clipped
|
||||
}
|
||||
|
||||
// AssertTexture checks if a given canvas.Texture is a texture from this package.
|
||||
func AssertTexture (unknown canvas.Texture) *Texture {
|
||||
if unknown == nil {
|
||||
return nil
|
||||
}
|
||||
if tx, ok := unknown.(*Texture); ok {
|
||||
return tx
|
||||
} else {
|
||||
panic("foregin texture implementation, i did not make this!")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user