| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- 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)
- }
|