draw.go 13 KB

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