texture.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package gl
  2. import (
  3. "fmt"
  4. "image"
  5. "image/color"
  6. "image/draw"
  7. "math"
  8. "fyne.io/fyne/v2"
  9. "fyne.io/fyne/v2/canvas"
  10. "fyne.io/fyne/v2/internal/cache"
  11. paint "fyne.io/fyne/v2/internal/painter"
  12. )
  13. var noTexture = Texture(cache.NoTexture)
  14. // Texture represents an uploaded GL texture
  15. type Texture cache.TextureType
  16. func (p *painter) freeTexture(obj fyne.CanvasObject) {
  17. texture, ok := cache.GetTexture(obj)
  18. if !ok {
  19. return
  20. }
  21. p.ctx.DeleteTexture(Texture(texture))
  22. p.logError()
  23. cache.DeleteTexture(obj)
  24. }
  25. func (p *painter) getTexture(object fyne.CanvasObject, creator func(canvasObject fyne.CanvasObject) Texture) (Texture, error) {
  26. texture, ok := cache.GetTexture(object)
  27. if !ok {
  28. texture = cache.TextureType(creator(object))
  29. cache.SetTexture(object, texture, p.canvas)
  30. }
  31. if !cache.IsValid(texture) {
  32. return noTexture, fmt.Errorf("no texture available")
  33. }
  34. return Texture(texture), nil
  35. }
  36. func (p *painter) imgToTexture(img image.Image, textureFilter canvas.ImageScale) Texture {
  37. switch i := img.(type) {
  38. case *image.Uniform:
  39. texture := p.newTexture(textureFilter)
  40. r, g, b, a := i.RGBA()
  41. r8, g8, b8, a8 := uint8(r>>8), uint8(g>>8), uint8(b>>8), uint8(a>>8)
  42. data := []uint8{r8, g8, b8, a8}
  43. p.ctx.TexImage2D(
  44. texture2D,
  45. 0,
  46. 1,
  47. 1,
  48. colorFormatRGBA,
  49. unsignedByte,
  50. data,
  51. )
  52. p.logError()
  53. return texture
  54. case *image.RGBA:
  55. if len(i.Pix) == 0 { // image is empty
  56. return noTexture
  57. }
  58. texture := p.newTexture(textureFilter)
  59. p.ctx.TexImage2D(
  60. texture2D,
  61. 0,
  62. i.Rect.Size().X,
  63. i.Rect.Size().Y,
  64. colorFormatRGBA,
  65. unsignedByte,
  66. i.Pix,
  67. )
  68. p.logError()
  69. return texture
  70. case *image.Gray:
  71. if len(i.Pix) == 0 { // image is empty
  72. return noTexture
  73. }
  74. p.ctx.PixelStorei(unpackAlignment, 1) // OpenGL expects 4 byte alignment for images which is not guaranteed for image.Gray
  75. texture := p.newTexture(textureFilter)
  76. p.ctx.TexImage2D(
  77. texture2D,
  78. 0,
  79. i.Bounds().Dx(),
  80. i.Bounds().Dy(),
  81. colorFormatR,
  82. unsignedByte,
  83. i.Pix,
  84. )
  85. p.ctx.PixelStorei(unpackAlignment, 4) // Reset to default for performance reasons
  86. p.logError()
  87. return texture
  88. default:
  89. rgba := image.NewRGBA(image.Rect(0, 0, img.Bounds().Dx(), img.Bounds().Dy()))
  90. draw.Draw(rgba, rgba.Rect, img, image.Point{}, draw.Over)
  91. return p.imgToTexture(rgba, textureFilter)
  92. }
  93. }
  94. func (p *painter) newGlCircleTexture(obj fyne.CanvasObject) Texture {
  95. circle := obj.(*canvas.Circle)
  96. raw := paint.DrawCircle(circle, paint.VectorPad(circle), p.textureScale)
  97. return p.imgToTexture(raw, canvas.ImageScaleSmooth)
  98. }
  99. func (p *painter) newGlImageTexture(obj fyne.CanvasObject) Texture {
  100. img := obj.(*canvas.Image)
  101. width := p.textureScale(img.Size().Width)
  102. height := p.textureScale(img.Size().Height)
  103. tex := paint.PaintImage(img, p.canvas, int(width), int(height))
  104. if tex == nil {
  105. return noTexture
  106. }
  107. return p.imgToTexture(tex, img.ScaleMode)
  108. }
  109. func (p *painter) newGlLinearGradientTexture(obj fyne.CanvasObject) Texture {
  110. gradient := obj.(*canvas.LinearGradient)
  111. w := gradient.Size().Width
  112. h := gradient.Size().Height
  113. switch gradient.Angle {
  114. case 90, 270:
  115. h = 1
  116. case 0, 180:
  117. w = 1
  118. }
  119. width := p.textureScale(w)
  120. height := p.textureScale(h)
  121. return p.imgToTexture(gradient.Generate(int(width), int(height)), canvas.ImageScaleSmooth)
  122. }
  123. func (p *painter) newGlRadialGradientTexture(obj fyne.CanvasObject) Texture {
  124. gradient := obj.(*canvas.RadialGradient)
  125. width := p.textureScale(gradient.Size().Width)
  126. height := p.textureScale(gradient.Size().Height)
  127. return p.imgToTexture(gradient.Generate(int(width), int(height)), canvas.ImageScaleSmooth)
  128. }
  129. func (p *painter) newGlRasterTexture(obj fyne.CanvasObject) Texture {
  130. rast := obj.(*canvas.Raster)
  131. width := p.textureScale(rast.Size().Width)
  132. height := p.textureScale(rast.Size().Height)
  133. return p.imgToTexture(rast.Generator(int(width), int(height)), rast.ScaleMode)
  134. }
  135. func (p *painter) newGlRectTexture(obj fyne.CanvasObject) Texture {
  136. rect := obj.(*canvas.Rectangle)
  137. if rect.StrokeColor != nil && rect.StrokeWidth > 0 {
  138. return p.newGlStrokedRectTexture(rect)
  139. }
  140. if rect.FillColor == nil {
  141. return noTexture
  142. }
  143. return p.imgToTexture(image.NewUniform(rect.FillColor), canvas.ImageScaleSmooth)
  144. }
  145. func (p *painter) newGlStrokedRectTexture(obj fyne.CanvasObject) Texture {
  146. rect := obj.(*canvas.Rectangle)
  147. raw := paint.DrawRectangle(rect, paint.VectorPad(rect), p.textureScale)
  148. return p.imgToTexture(raw, canvas.ImageScaleSmooth)
  149. }
  150. func (p *painter) newGlTextTexture(obj fyne.CanvasObject) Texture {
  151. text := obj.(*canvas.Text)
  152. bounds := text.MinSize()
  153. width := int(math.Ceil(float64(p.textureScale(bounds.Width) + paint.VectorPad(text)))) // potentially italic overspill
  154. height := int(math.Ceil(float64(p.textureScale(bounds.Height))))
  155. img := image.NewGray(image.Rect(0, 0, width, height))
  156. face, measureFace := paint.CachedFontFace(text.TextStyle, text.TextSize*p.canvas.Scale(), p.texScale)
  157. paint.DrawString(img, text.Text, color.White, face, measureFace, text.TextSize, p.pixScale, height, text.TextStyle.TabWidth)
  158. return p.imgToTexture(img, canvas.ImageScaleSmooth)
  159. }
  160. func (p *painter) newTexture(textureFilter canvas.ImageScale) Texture {
  161. if int(textureFilter) >= len(textureFilterToGL) {
  162. fyne.LogError(fmt.Sprintf("Invalid canvas.ImageScale value (%d), using canvas.ImageScaleSmooth as default value", textureFilter), nil)
  163. textureFilter = canvas.ImageScaleSmooth
  164. }
  165. texture := p.ctx.CreateTexture()
  166. p.logError()
  167. p.ctx.ActiveTexture(texture0)
  168. p.ctx.BindTexture(texture2D, texture)
  169. p.logError()
  170. p.ctx.TexParameteri(texture2D, textureMinFilter, textureFilterToGL[textureFilter])
  171. p.ctx.TexParameteri(texture2D, textureMagFilter, textureFilterToGL[textureFilter])
  172. p.ctx.TexParameteri(texture2D, textureWrapS, clampToEdge)
  173. p.ctx.TexParameteri(texture2D, textureWrapT, clampToEdge)
  174. p.logError()
  175. return texture
  176. }
  177. func (p *painter) textureScale(v float32) float32 {
  178. if p.pixScale == 1.0 {
  179. return float32(math.Round(float64(v)))
  180. }
  181. return float32(math.Round(float64(v * p.pixScale)))
  182. }