219 lines
8.1 KiB
Go
219 lines
8.1 KiB
Go
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)
|
|
}
|