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