draw.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package software
  2. import (
  3. "fmt"
  4. "image"
  5. "math"
  6. "fyne.io/fyne/v2"
  7. "fyne.io/fyne/v2/canvas"
  8. "fyne.io/fyne/v2/internal/painter"
  9. "fyne.io/fyne/v2/internal/scale"
  10. "fyne.io/fyne/v2/theme"
  11. "golang.org/x/image/draw"
  12. )
  13. type gradient interface {
  14. Generate(int, int) image.Image
  15. Size() fyne.Size
  16. }
  17. func drawCircle(c fyne.Canvas, circle *canvas.Circle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
  18. pad := painter.VectorPad(circle)
  19. scaledWidth := scale.ToScreenCoordinate(c, circle.Size().Width+pad*2)
  20. scaledHeight := scale.ToScreenCoordinate(c, circle.Size().Height+pad*2)
  21. scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X-pad), scale.ToScreenCoordinate(c, pos.Y-pad)
  22. bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
  23. raw := painter.DrawCircle(circle, pad, func(in float32) float32 {
  24. return float32(math.Round(float64(in) * float64(c.Scale())))
  25. })
  26. // the clip intersect above cannot be negative, so we may need to compensate
  27. offX, offY := 0, 0
  28. if scaledX < 0 {
  29. offX = -scaledX
  30. }
  31. if scaledY < 0 {
  32. offY = -scaledY
  33. }
  34. draw.Draw(base, bounds, raw, image.Point{offX, offY}, draw.Over)
  35. }
  36. func drawGradient(c fyne.Canvas, g gradient, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
  37. bounds := g.Size()
  38. width := scale.ToScreenCoordinate(c, bounds.Width)
  39. height := scale.ToScreenCoordinate(c, bounds.Height)
  40. tex := g.Generate(width, height)
  41. drawTex(scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y), width, height, base, tex, clip)
  42. }
  43. func drawImage(c fyne.Canvas, img *canvas.Image, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
  44. bounds := img.Size()
  45. if bounds.IsZero() {
  46. return
  47. }
  48. width := scale.ToScreenCoordinate(c, bounds.Width)
  49. height := scale.ToScreenCoordinate(c, bounds.Height)
  50. scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y)
  51. origImg := painter.PaintImage(img, c, width, height)
  52. if img.FillMode == canvas.ImageFillContain {
  53. imgAspect := img.Aspect()
  54. objAspect := float32(width) / float32(height)
  55. if objAspect > imgAspect {
  56. newWidth := int(float32(height) * imgAspect)
  57. scaledX += (width - newWidth) / 2
  58. width = newWidth
  59. } else if objAspect < imgAspect {
  60. newHeight := int(float32(width) / imgAspect)
  61. scaledY += (height - newHeight) / 2
  62. height = newHeight
  63. }
  64. }
  65. drawPixels(scaledX, scaledY, width, height, img.ScaleMode, base, origImg, clip)
  66. }
  67. func drawPixels(x, y, width, height int, mode canvas.ImageScale, base *image.NRGBA, origImg image.Image, clip image.Rectangle) {
  68. if origImg.Bounds().Dx() == width && origImg.Bounds().Dy() == height {
  69. // do not scale or duplicate image since not needed, draw directly
  70. drawTex(x, y, width, height, base, origImg, clip)
  71. return
  72. }
  73. scaledBounds := image.Rect(0, 0, width, height)
  74. scaledImg := image.NewNRGBA(scaledBounds)
  75. switch mode {
  76. case canvas.ImageScalePixels:
  77. draw.NearestNeighbor.Scale(scaledImg, scaledBounds, origImg, origImg.Bounds(), draw.Over, nil)
  78. case canvas.ImageScaleFastest:
  79. draw.ApproxBiLinear.Scale(scaledImg, scaledBounds, origImg, origImg.Bounds(), draw.Over, nil)
  80. default:
  81. if mode != canvas.ImageScaleSmooth {
  82. fyne.LogError(fmt.Sprintf("Invalid canvas.ImageScale value (%d), using canvas.ImageScaleSmooth as default value", mode), nil)
  83. }
  84. draw.CatmullRom.Scale(scaledImg, scaledBounds, origImg, origImg.Bounds(), draw.Over, nil)
  85. }
  86. drawTex(x, y, width, height, base, scaledImg, clip)
  87. }
  88. func drawLine(c fyne.Canvas, line *canvas.Line, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
  89. pad := painter.VectorPad(line)
  90. scaledWidth := scale.ToScreenCoordinate(c, line.Size().Width+pad*2)
  91. scaledHeight := scale.ToScreenCoordinate(c, line.Size().Height+pad*2)
  92. scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X-pad), scale.ToScreenCoordinate(c, pos.Y-pad)
  93. bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
  94. raw := painter.DrawLine(line, pad, func(in float32) float32 {
  95. return float32(math.Round(float64(in) * float64(c.Scale())))
  96. })
  97. // the clip intersect above cannot be negative, so we may need to compensate
  98. offX, offY := 0, 0
  99. if scaledX < 0 {
  100. offX = -scaledX
  101. }
  102. if scaledY < 0 {
  103. offY = -scaledY
  104. }
  105. draw.Draw(base, bounds, raw, image.Point{offX, offY}, draw.Over)
  106. }
  107. func drawTex(x, y, width, height int, base *image.NRGBA, tex image.Image, clip image.Rectangle) {
  108. outBounds := image.Rect(x, y, x+width, y+height)
  109. clippedBounds := clip.Intersect(outBounds)
  110. srcPt := image.Point{X: clippedBounds.Min.X - outBounds.Min.X, Y: clippedBounds.Min.Y - outBounds.Min.Y}
  111. draw.Draw(base, clippedBounds, tex, srcPt, draw.Over)
  112. }
  113. func drawText(c fyne.Canvas, text *canvas.Text, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
  114. bounds := text.MinSize()
  115. width := scale.ToScreenCoordinate(c, bounds.Width+painter.VectorPad(text))
  116. height := scale.ToScreenCoordinate(c, bounds.Height)
  117. txtImg := image.NewRGBA(image.Rect(0, 0, width, height))
  118. color := text.Color
  119. if color == nil {
  120. color = theme.ForegroundColor()
  121. }
  122. face := painter.CachedFontFace(text.TextStyle, text.TextSize*c.Scale(), 1)
  123. painter.DrawString(txtImg, text.Text, color, face.Fonts, text.TextSize, c.Scale(), text.TextStyle.TabWidth)
  124. size := text.Size()
  125. offsetX := float32(0)
  126. offsetY := float32(0)
  127. switch text.Alignment {
  128. case fyne.TextAlignTrailing:
  129. offsetX = size.Width - bounds.Width
  130. case fyne.TextAlignCenter:
  131. offsetX = (size.Width - bounds.Width) / 2
  132. }
  133. if size.Height > bounds.Height {
  134. offsetY = (size.Height - bounds.Height) / 2
  135. }
  136. scaledX := scale.ToScreenCoordinate(c, pos.X+offsetX)
  137. scaledY := scale.ToScreenCoordinate(c, pos.Y+offsetY)
  138. imgBounds := image.Rect(scaledX, scaledY, scaledX+width, scaledY+height)
  139. clippedBounds := clip.Intersect(imgBounds)
  140. srcPt := image.Point{X: clippedBounds.Min.X - imgBounds.Min.X, Y: clippedBounds.Min.Y - imgBounds.Min.Y}
  141. draw.Draw(base, clippedBounds, txtImg, srcPt, draw.Over)
  142. }
  143. func drawRaster(c fyne.Canvas, rast *canvas.Raster, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
  144. bounds := rast.Size()
  145. if bounds.IsZero() {
  146. return
  147. }
  148. width := scale.ToScreenCoordinate(c, bounds.Width)
  149. height := scale.ToScreenCoordinate(c, bounds.Height)
  150. scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y)
  151. pix := rast.Generator(width, height)
  152. if pix.Bounds().Bounds().Dx() != width || pix.Bounds().Dy() != height {
  153. drawPixels(scaledX, scaledY, width, height, rast.ScaleMode, base, pix, clip)
  154. } else {
  155. drawTex(scaledX, scaledY, width, height, base, pix, clip)
  156. }
  157. }
  158. func drawRectangleStroke(c fyne.Canvas, rect *canvas.Rectangle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
  159. pad := painter.VectorPad(rect)
  160. scaledWidth := scale.ToScreenCoordinate(c, rect.Size().Width+pad*2)
  161. scaledHeight := scale.ToScreenCoordinate(c, rect.Size().Height+pad*2)
  162. scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X-pad), scale.ToScreenCoordinate(c, pos.Y-pad)
  163. bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
  164. raw := painter.DrawRectangle(rect, pad, func(in float32) float32 {
  165. return float32(math.Round(float64(in) * float64(c.Scale())))
  166. })
  167. // the clip intersect above cannot be negative, so we may need to compensate
  168. offX, offY := 0, 0
  169. if scaledX < 0 {
  170. offX = -scaledX
  171. }
  172. if scaledY < 0 {
  173. offY = -scaledY
  174. }
  175. draw.Draw(base, bounds, raw, image.Point{offX, offY}, draw.Over)
  176. }
  177. func drawRectangle(c fyne.Canvas, rect *canvas.Rectangle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
  178. if (rect.StrokeColor != nil && rect.StrokeWidth > 0) || rect.CornerRadius != 0 { // use a rasterizer if there is a stroke or radius
  179. drawRectangleStroke(c, rect, pos, base, clip)
  180. return
  181. }
  182. scaledWidth := scale.ToScreenCoordinate(c, rect.Size().Width)
  183. scaledHeight := scale.ToScreenCoordinate(c, rect.Size().Height)
  184. scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y)
  185. bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
  186. draw.Draw(base, bounds, image.NewUniform(rect.FillColor), image.Point{}, draw.Over)
  187. }