adam-gui/vendor/fyne.io/fyne/v2/internal/painter/software/draw.go

219 lines
8.1 KiB
Go
Raw Normal View History

2024-04-29 19:13:50 +02:00
package software
import (
"fmt"
"image"
"math"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/internal/painter"
"fyne.io/fyne/v2/internal/scale"
"fyne.io/fyne/v2/theme"
"golang.org/x/image/draw"
)
type gradient interface {
Generate(int, int) image.Image
Size() fyne.Size
}
func drawCircle(c fyne.Canvas, circle *canvas.Circle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
pad := painter.VectorPad(circle)
scaledWidth := scale.ToScreenCoordinate(c, circle.Size().Width+pad*2)
scaledHeight := scale.ToScreenCoordinate(c, circle.Size().Height+pad*2)
scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X-pad), scale.ToScreenCoordinate(c, pos.Y-pad)
bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
raw := painter.DrawCircle(circle, pad, func(in float32) float32 {
return float32(math.Round(float64(in) * float64(c.Scale())))
})
// the clip intersect above cannot be negative, so we may need to compensate
offX, offY := 0, 0
if scaledX < 0 {
offX = -scaledX
}
if scaledY < 0 {
offY = -scaledY
}
draw.Draw(base, bounds, raw, image.Point{offX, offY}, draw.Over)
}
func drawGradient(c fyne.Canvas, g gradient, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
bounds := g.Size()
width := scale.ToScreenCoordinate(c, bounds.Width)
height := scale.ToScreenCoordinate(c, bounds.Height)
tex := g.Generate(width, height)
drawTex(scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y), width, height, base, tex, clip)
}
func drawImage(c fyne.Canvas, img *canvas.Image, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
bounds := img.Size()
if bounds.IsZero() {
return
}
width := scale.ToScreenCoordinate(c, bounds.Width)
height := scale.ToScreenCoordinate(c, bounds.Height)
scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y)
origImg := painter.PaintImage(img, c, width, height)
if img.FillMode == canvas.ImageFillContain {
imgAspect := img.Aspect()
objAspect := float32(width) / float32(height)
if objAspect > imgAspect {
newWidth := int(float32(height) * imgAspect)
scaledX += (width - newWidth) / 2
width = newWidth
} else if objAspect < imgAspect {
newHeight := int(float32(width) / imgAspect)
scaledY += (height - newHeight) / 2
height = newHeight
}
}
drawPixels(scaledX, scaledY, width, height, img.ScaleMode, base, origImg, clip)
}
func drawPixels(x, y, width, height int, mode canvas.ImageScale, base *image.NRGBA, origImg image.Image, clip image.Rectangle) {
if origImg.Bounds().Dx() == width && origImg.Bounds().Dy() == height {
// do not scale or duplicate image since not needed, draw directly
drawTex(x, y, width, height, base, origImg, clip)
return
}
scaledBounds := image.Rect(0, 0, width, height)
scaledImg := image.NewNRGBA(scaledBounds)
switch mode {
case canvas.ImageScalePixels:
draw.NearestNeighbor.Scale(scaledImg, scaledBounds, origImg, origImg.Bounds(), draw.Over, nil)
case canvas.ImageScaleFastest:
draw.ApproxBiLinear.Scale(scaledImg, scaledBounds, origImg, origImg.Bounds(), draw.Over, nil)
default:
if mode != canvas.ImageScaleSmooth {
fyne.LogError(fmt.Sprintf("Invalid canvas.ImageScale value (%d), using canvas.ImageScaleSmooth as default value", mode), nil)
}
draw.CatmullRom.Scale(scaledImg, scaledBounds, origImg, origImg.Bounds(), draw.Over, nil)
}
drawTex(x, y, width, height, base, scaledImg, clip)
}
func drawLine(c fyne.Canvas, line *canvas.Line, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
pad := painter.VectorPad(line)
scaledWidth := scale.ToScreenCoordinate(c, line.Size().Width+pad*2)
scaledHeight := scale.ToScreenCoordinate(c, line.Size().Height+pad*2)
scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X-pad), scale.ToScreenCoordinate(c, pos.Y-pad)
bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
raw := painter.DrawLine(line, pad, func(in float32) float32 {
return float32(math.Round(float64(in) * float64(c.Scale())))
})
// the clip intersect above cannot be negative, so we may need to compensate
offX, offY := 0, 0
if scaledX < 0 {
offX = -scaledX
}
if scaledY < 0 {
offY = -scaledY
}
draw.Draw(base, bounds, raw, image.Point{offX, offY}, draw.Over)
}
func drawTex(x, y, width, height int, base *image.NRGBA, tex image.Image, clip image.Rectangle) {
outBounds := image.Rect(x, y, x+width, y+height)
clippedBounds := clip.Intersect(outBounds)
srcPt := image.Point{X: clippedBounds.Min.X - outBounds.Min.X, Y: clippedBounds.Min.Y - outBounds.Min.Y}
draw.Draw(base, clippedBounds, tex, srcPt, draw.Over)
}
func drawText(c fyne.Canvas, text *canvas.Text, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
bounds := text.MinSize()
width := scale.ToScreenCoordinate(c, bounds.Width+painter.VectorPad(text))
height := scale.ToScreenCoordinate(c, bounds.Height)
txtImg := image.NewRGBA(image.Rect(0, 0, width, height))
color := text.Color
if color == nil {
color = theme.ForegroundColor()
}
face := painter.CachedFontFace(text.TextStyle, text.TextSize*c.Scale(), 1)
painter.DrawString(txtImg, text.Text, color, face.Fonts, text.TextSize, c.Scale(), text.TextStyle.TabWidth)
size := text.Size()
offsetX := float32(0)
offsetY := float32(0)
switch text.Alignment {
case fyne.TextAlignTrailing:
offsetX = size.Width - bounds.Width
case fyne.TextAlignCenter:
offsetX = (size.Width - bounds.Width) / 2
}
if size.Height > bounds.Height {
offsetY = (size.Height - bounds.Height) / 2
}
scaledX := scale.ToScreenCoordinate(c, pos.X+offsetX)
scaledY := scale.ToScreenCoordinate(c, pos.Y+offsetY)
imgBounds := image.Rect(scaledX, scaledY, scaledX+width, scaledY+height)
clippedBounds := clip.Intersect(imgBounds)
srcPt := image.Point{X: clippedBounds.Min.X - imgBounds.Min.X, Y: clippedBounds.Min.Y - imgBounds.Min.Y}
draw.Draw(base, clippedBounds, txtImg, srcPt, draw.Over)
}
func drawRaster(c fyne.Canvas, rast *canvas.Raster, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
bounds := rast.Size()
if bounds.IsZero() {
return
}
width := scale.ToScreenCoordinate(c, bounds.Width)
height := scale.ToScreenCoordinate(c, bounds.Height)
scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y)
pix := rast.Generator(width, height)
if pix.Bounds().Bounds().Dx() != width || pix.Bounds().Dy() != height {
drawPixels(scaledX, scaledY, width, height, rast.ScaleMode, base, pix, clip)
} else {
drawTex(scaledX, scaledY, width, height, base, pix, clip)
}
}
func drawRectangleStroke(c fyne.Canvas, rect *canvas.Rectangle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
pad := painter.VectorPad(rect)
scaledWidth := scale.ToScreenCoordinate(c, rect.Size().Width+pad*2)
scaledHeight := scale.ToScreenCoordinate(c, rect.Size().Height+pad*2)
scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X-pad), scale.ToScreenCoordinate(c, pos.Y-pad)
bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
raw := painter.DrawRectangle(rect, pad, func(in float32) float32 {
return float32(math.Round(float64(in) * float64(c.Scale())))
})
// the clip intersect above cannot be negative, so we may need to compensate
offX, offY := 0, 0
if scaledX < 0 {
offX = -scaledX
}
if scaledY < 0 {
offY = -scaledY
}
draw.Draw(base, bounds, raw, image.Point{offX, offY}, draw.Over)
}
func drawRectangle(c fyne.Canvas, rect *canvas.Rectangle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
if (rect.StrokeColor != nil && rect.StrokeWidth > 0) || rect.CornerRadius != 0 { // use a rasterizer if there is a stroke or radius
drawRectangleStroke(c, rect, pos, base, clip)
return
}
scaledWidth := scale.ToScreenCoordinate(c, rect.Size().Width)
scaledHeight := scale.ToScreenCoordinate(c, rect.Size().Height)
scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y)
bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
draw.Draw(base, bounds, image.NewUniform(rect.FillColor), image.Point{}, draw.Over)
}