draw.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. package gl
  2. import (
  3. "image/color"
  4. "math"
  5. "fyne.io/fyne/v2"
  6. "fyne.io/fyne/v2/canvas"
  7. paint "fyne.io/fyne/v2/internal/painter"
  8. "fyne.io/fyne/v2/theme"
  9. )
  10. func (p *painter) createBuffer(points []float32) Buffer {
  11. vbo := p.ctx.CreateBuffer()
  12. p.logError()
  13. p.ctx.BindBuffer(arrayBuffer, vbo)
  14. p.logError()
  15. p.ctx.BufferData(arrayBuffer, points, staticDraw)
  16. p.logError()
  17. return vbo
  18. }
  19. func (p *painter) defineVertexArray(prog Program, name string, size, stride, offset int) {
  20. vertAttrib := p.ctx.GetAttribLocation(prog, name)
  21. p.ctx.EnableVertexAttribArray(vertAttrib)
  22. p.ctx.VertexAttribPointerWithOffset(vertAttrib, size, float, false, stride*floatSize, offset*floatSize)
  23. p.logError()
  24. }
  25. func (p *painter) drawCircle(circle *canvas.Circle, pos fyne.Position, frame fyne.Size) {
  26. p.drawTextureWithDetails(circle, p.newGlCircleTexture, pos, circle.Size(), frame, canvas.ImageFillStretch,
  27. 1.0, paint.VectorPad(circle))
  28. }
  29. func (p *painter) drawGradient(o fyne.CanvasObject, texCreator func(fyne.CanvasObject) Texture, pos fyne.Position, frame fyne.Size) {
  30. p.drawTextureWithDetails(o, texCreator, pos, o.Size(), frame, canvas.ImageFillStretch, 1.0, 0)
  31. }
  32. func (p *painter) drawImage(img *canvas.Image, pos fyne.Position, frame fyne.Size) {
  33. p.drawTextureWithDetails(img, p.newGlImageTexture, pos, img.Size(), frame, img.FillMode, float32(img.Alpha()), 0)
  34. }
  35. func (p *painter) drawLine(line *canvas.Line, pos fyne.Position, frame fyne.Size) {
  36. if line.StrokeColor == color.Transparent || line.StrokeColor == nil || line.StrokeWidth == 0 {
  37. return
  38. }
  39. points, halfWidth, feather := p.lineCoords(pos, line.Position1, line.Position2, line.StrokeWidth, 0.5, frame)
  40. p.ctx.UseProgram(p.lineProgram)
  41. vbo := p.createBuffer(points)
  42. p.defineVertexArray(p.lineProgram, "vert", 2, 4, 0)
  43. p.defineVertexArray(p.lineProgram, "normal", 2, 4, 2)
  44. p.ctx.BlendFunc(srcAlpha, oneMinusSrcAlpha)
  45. p.logError()
  46. colorUniform := p.ctx.GetUniformLocation(p.lineProgram, "color")
  47. r, g, b, a := line.StrokeColor.RGBA()
  48. if a == 0 {
  49. p.ctx.Uniform4f(colorUniform, 0, 0, 0, 0)
  50. } else {
  51. alpha := float32(a)
  52. p.ctx.Uniform4f(colorUniform, float32(r)/alpha, float32(g)/alpha, float32(b)/alpha, alpha/0xffff)
  53. }
  54. lineWidthUniform := p.ctx.GetUniformLocation(p.lineProgram, "lineWidth")
  55. p.ctx.Uniform1f(lineWidthUniform, halfWidth)
  56. featherUniform := p.ctx.GetUniformLocation(p.lineProgram, "feather")
  57. p.ctx.Uniform1f(featherUniform, feather)
  58. p.ctx.DrawArrays(triangles, 0, 6)
  59. p.logError()
  60. p.freeBuffer(vbo)
  61. }
  62. func (p *painter) drawObject(o fyne.CanvasObject, pos fyne.Position, frame fyne.Size) {
  63. switch obj := o.(type) {
  64. case *canvas.Circle:
  65. p.drawCircle(obj, pos, frame)
  66. case *canvas.Line:
  67. p.drawLine(obj, pos, frame)
  68. case *canvas.Image:
  69. p.drawImage(obj, pos, frame)
  70. case *canvas.Raster:
  71. p.drawRaster(obj, pos, frame)
  72. case *canvas.Rectangle:
  73. p.drawRectangle(obj, pos, frame)
  74. case *canvas.Text:
  75. p.drawText(obj, pos, frame)
  76. case *canvas.LinearGradient:
  77. p.drawGradient(obj, p.newGlLinearGradientTexture, pos, frame)
  78. case *canvas.RadialGradient:
  79. p.drawGradient(obj, p.newGlRadialGradientTexture, pos, frame)
  80. }
  81. }
  82. func (p *painter) drawRaster(img *canvas.Raster, pos fyne.Position, frame fyne.Size) {
  83. p.drawTextureWithDetails(img, p.newGlRasterTexture, pos, img.Size(), frame, canvas.ImageFillStretch, float32(img.Alpha()), 0)
  84. }
  85. func (p *painter) drawRectangle(rect *canvas.Rectangle, pos fyne.Position, frame fyne.Size) {
  86. if (rect.FillColor == color.Transparent || rect.FillColor == nil) && (rect.StrokeColor == color.Transparent || rect.StrokeColor == nil || rect.StrokeWidth == 0) {
  87. return
  88. }
  89. p.drawTextureWithDetails(rect, p.newGlRectTexture, pos, rect.Size(), frame, canvas.ImageFillStretch,
  90. 1.0, paint.VectorPad(rect))
  91. }
  92. func (p *painter) drawText(text *canvas.Text, pos fyne.Position, frame fyne.Size) {
  93. if text.Text == "" || text.Text == " " {
  94. return
  95. }
  96. size := text.MinSize()
  97. containerSize := text.Size()
  98. switch text.Alignment {
  99. case fyne.TextAlignTrailing:
  100. pos = fyne.NewPos(pos.X+containerSize.Width-size.Width, pos.Y)
  101. case fyne.TextAlignCenter:
  102. pos = fyne.NewPos(pos.X+(containerSize.Width-size.Width)/2, pos.Y)
  103. }
  104. if containerSize.Height > size.Height {
  105. pos = fyne.NewPos(pos.X, pos.Y+(containerSize.Height-size.Height)/2)
  106. }
  107. color := text.Color
  108. if color == nil {
  109. color = theme.ForegroundColor()
  110. }
  111. // text size is sensitive to position on screen
  112. size, _ = roundToPixelCoords(size, text.Position(), p.pixScale)
  113. size.Width += roundToPixel(paint.VectorPad(text), p.pixScale)
  114. p.drawSingleChannelTexture(text, p.newGlTextTexture, pos, size, frame, color, 0)
  115. }
  116. func (p *painter) drawSingleChannelTexture(o fyne.CanvasObject, creator func(canvasObject fyne.CanvasObject) Texture,
  117. pos fyne.Position, size, frame fyne.Size, c color.Color, pad float32) {
  118. texture, err := p.getTexture(o, creator)
  119. if err != nil {
  120. return
  121. }
  122. points := p.rectCoords(size, pos, frame, canvas.ImageFillStretch, 0, pad)
  123. p.ctx.UseProgram(p.singleChannelProgram)
  124. vbo := p.createBuffer(points)
  125. p.defineVertexArray(p.singleChannelProgram, "vert", 3, 5, 0)
  126. p.defineVertexArray(p.singleChannelProgram, "vertTexCoord", 2, 5, 3)
  127. p.ctx.BlendFunc(srcAlpha, oneMinusSrcAlpha)
  128. p.logError()
  129. shaderColor := p.ctx.GetUniformLocation(p.singleChannelProgram, "color")
  130. r, g, b, a := getFragmentColor(c)
  131. p.ctx.Uniform4f(shaderColor, r, g, b, a)
  132. p.ctx.ActiveTexture(texture0)
  133. p.ctx.BindTexture(texture2D, texture)
  134. p.logError()
  135. p.ctx.DrawArrays(triangleStrip, 0, 4)
  136. p.logError()
  137. p.freeBuffer(vbo)
  138. }
  139. func (p *painter) drawTextureWithDetails(o fyne.CanvasObject, creator func(canvasObject fyne.CanvasObject) Texture,
  140. pos fyne.Position, size, frame fyne.Size, fill canvas.ImageFill, alpha float32, pad float32) {
  141. texture, err := p.getTexture(o, creator)
  142. if err != nil {
  143. return
  144. }
  145. aspect := float32(0)
  146. if img, ok := o.(*canvas.Image); ok {
  147. aspect = paint.GetAspect(img)
  148. if aspect == 0 {
  149. aspect = 1 // fallback, should not occur - normally an image load error
  150. }
  151. }
  152. points := p.rectCoords(size, pos, frame, fill, aspect, pad)
  153. p.ctx.UseProgram(p.program)
  154. vbo := p.createBuffer(points)
  155. p.defineVertexArray(p.program, "vert", 3, 5, 0)
  156. p.defineVertexArray(p.program, "vertTexCoord", 2, 5, 3)
  157. // here we have to choose between blending the image alpha or fading it...
  158. // TODO find a way to support both
  159. if alpha != 1.0 {
  160. p.ctx.BlendColor(0, 0, 0, alpha)
  161. p.ctx.BlendFunc(constantAlpha, oneMinusConstantAlpha)
  162. } else {
  163. p.ctx.BlendFunc(one, oneMinusSrcAlpha)
  164. }
  165. p.logError()
  166. p.ctx.ActiveTexture(texture0)
  167. p.ctx.BindTexture(texture2D, texture)
  168. p.logError()
  169. p.ctx.DrawArrays(triangleStrip, 0, 4)
  170. p.logError()
  171. p.freeBuffer(vbo)
  172. }
  173. func (p *painter) freeBuffer(vbo Buffer) {
  174. p.ctx.BindBuffer(arrayBuffer, noBuffer)
  175. p.logError()
  176. p.ctx.DeleteBuffer(vbo)
  177. p.logError()
  178. }
  179. func (p *painter) lineCoords(pos, pos1, pos2 fyne.Position, lineWidth, feather float32, frame fyne.Size) ([]float32, float32, float32) {
  180. // Shift line coordinates so that they match the target position.
  181. xPosDiff := pos.X - fyne.Min(pos1.X, pos2.X)
  182. yPosDiff := pos.Y - fyne.Min(pos1.Y, pos2.Y)
  183. pos1.X = roundToPixel(pos1.X+xPosDiff, p.pixScale)
  184. pos1.Y = roundToPixel(pos1.Y+yPosDiff, p.pixScale)
  185. pos2.X = roundToPixel(pos2.X+xPosDiff, p.pixScale)
  186. pos2.Y = roundToPixel(pos2.Y+yPosDiff, p.pixScale)
  187. if lineWidth <= 1 {
  188. offset := float32(0.5) // adjust location for lines < 1pt on regular display
  189. if lineWidth <= 0.5 && p.pixScale > 1 { // and for 1px drawing on HiDPI (width 0.5)
  190. offset = 0.25
  191. }
  192. if pos1.X == pos2.X {
  193. pos1.X -= offset
  194. pos2.X -= offset
  195. }
  196. if pos1.Y == pos2.Y {
  197. pos1.Y -= offset
  198. pos2.Y -= offset
  199. }
  200. }
  201. x1Pos := pos1.X / frame.Width
  202. x1 := -1 + x1Pos*2
  203. y1Pos := pos1.Y / frame.Height
  204. y1 := 1 - y1Pos*2
  205. x2Pos := pos2.X / frame.Width
  206. x2 := -1 + x2Pos*2
  207. y2Pos := pos2.Y / frame.Height
  208. y2 := 1 - y2Pos*2
  209. normalX := (pos2.Y - pos1.Y) / frame.Width
  210. normalY := (pos2.X - pos1.X) / frame.Height
  211. dirLength := float32(math.Sqrt(float64(normalX*normalX + normalY*normalY)))
  212. normalX /= dirLength
  213. normalY /= dirLength
  214. normalObjX := normalX * 0.5 * frame.Width
  215. normalObjY := normalY * 0.5 * frame.Height
  216. widthMultiplier := float32(math.Sqrt(float64(normalObjX*normalObjX + normalObjY*normalObjY)))
  217. halfWidth := (roundToPixel(lineWidth+feather, p.pixScale) * 0.5) / widthMultiplier
  218. featherWidth := feather / widthMultiplier
  219. return []float32{
  220. // coord x, y normal x, y
  221. x1, y1, normalX, normalY,
  222. x2, y2, normalX, normalY,
  223. x2, y2, -normalX, -normalY,
  224. x2, y2, -normalX, -normalY,
  225. x1, y1, normalX, normalY,
  226. x1, y1, -normalX, -normalY,
  227. }, halfWidth, featherWidth
  228. }
  229. // rectCoords calculates the openGL coordinate space of a rectangle
  230. func (p *painter) rectCoords(size fyne.Size, pos fyne.Position, frame fyne.Size,
  231. fill canvas.ImageFill, aspect float32, pad float32) []float32 {
  232. size, pos = rectInnerCoords(size, pos, fill, aspect)
  233. size, pos = roundToPixelCoords(size, pos, p.pixScale)
  234. xPos := (pos.X - pad) / frame.Width
  235. x1 := -1 + xPos*2
  236. x2Pos := (pos.X + size.Width + pad) / frame.Width
  237. x2 := -1 + x2Pos*2
  238. yPos := (pos.Y - pad) / frame.Height
  239. y1 := 1 - yPos*2
  240. y2Pos := (pos.Y + size.Height + pad) / frame.Height
  241. y2 := 1 - y2Pos*2
  242. return []float32{
  243. // coord x, y, z texture x, y
  244. x1, y2, 0, 0.0, 1.0, // top left
  245. x1, y1, 0, 0.0, 0.0, // bottom left
  246. x2, y2, 0, 1.0, 1.0, // top right
  247. x2, y1, 0, 1.0, 0.0, // bottom right
  248. }
  249. }
  250. func rectInnerCoords(size fyne.Size, pos fyne.Position, fill canvas.ImageFill, aspect float32) (fyne.Size, fyne.Position) {
  251. if fill == canvas.ImageFillContain || fill == canvas.ImageFillOriginal {
  252. // change pos and size accordingly
  253. viewAspect := size.Width / size.Height
  254. newWidth, newHeight := size.Width, size.Height
  255. widthPad, heightPad := float32(0), float32(0)
  256. if viewAspect > aspect {
  257. newWidth = size.Height * aspect
  258. widthPad = (size.Width - newWidth) / 2
  259. } else if viewAspect < aspect {
  260. newHeight = size.Width / aspect
  261. heightPad = (size.Height - newHeight) / 2
  262. }
  263. return fyne.NewSize(newWidth, newHeight), fyne.NewPos(pos.X+widthPad, pos.Y+heightPad)
  264. }
  265. return size, pos
  266. }
  267. func roundToPixel(v float32, pixScale float32) float32 {
  268. if pixScale == 1.0 {
  269. return float32(math.Round(float64(v)))
  270. }
  271. return float32(math.Round(float64(v*pixScale))) / pixScale
  272. }
  273. func roundToPixelCoords(size fyne.Size, pos fyne.Position, pixScale float32) (fyne.Size, fyne.Position) {
  274. end := pos.Add(size)
  275. end.X = roundToPixel(end.X, pixScale)
  276. end.Y = roundToPixel(end.Y, pixScale)
  277. pos.X = roundToPixel(pos.X, pixScale)
  278. pos.Y = roundToPixel(pos.Y, pixScale)
  279. size.Width = end.X - pos.X
  280. size.Height = end.Y - pos.Y
  281. return size, pos
  282. }
  283. // Returns FragmentColor(red,green,blue,alpha) from fyne.Color
  284. func getFragmentColor(col color.Color) (float32, float32, float32, float32) {
  285. r, g, b, a := col.RGBA()
  286. if a == 0 {
  287. return 0, 0, 0, 0
  288. }
  289. alpha := float32(a)
  290. return float32(r) / alpha, float32(g) / alpha, float32(b) / alpha, alpha / 0xffff
  291. }