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 }