Kaynağa Gözat

canvas refactor

Ethosa 4 yıl önce
ebeveyn
işleme
c2bd99cabb
5 değiştirilmiş dosya ile 95 ekleme ve 73 silme
  1. 1 1
      README.md
  2. 1 1
      nodesnim.nimble
  3. 89 70
      src/nodesnim/nodes/canvas.nim
  4. BIN
      tests/assets/canvas.png
  5. 4 1
      tests/test2.nim

+ 1 - 1
README.md

@@ -9,7 +9,7 @@ The Nim GUI/2D framework based on OpenGL and SDL2.
 [![time tracker](https://wakatime.com/badge/github/Ethosa/nodesnim.svg)](https://wakatime.com/badge/github/Ethosa/nodesnim)
 [![test](https://github.com/Ethosa/nodesnim/workflows/test/badge.svg)](https://github.com/Ethosa/nodesnim/actions)
 
-<h4>Stable version - 0.3.0</h4>
+<h4>Stable version - 0.3.1</h4>
 </div>
 
 ## Install

+ 1 - 1
nodesnim.nimble

@@ -1,7 +1,7 @@
 [Package]
 name = "nodesnim"
 author = "Ethosa"
-version = "0.3.0"
+version = "0.3.1"
 description = "The Nim GUI/2D framework based on OpenGL and SDL2."
 license = "MIT"
 srcDir = "src"

+ 89 - 70
src/nodesnim/nodes/canvas.nim

@@ -5,11 +5,14 @@
 import
   math,
   ../thirdparty/opengl,
+  ../thirdparty/sdl2,
+  ../thirdparty/sdl2/image,
 
   ../core/vector2,
   ../core/color,
   ../core/anchor,
   ../core/enums,
+  ../core/tools,
 
   node
 
@@ -18,21 +21,10 @@ const TAU = PI + PI
 
 
 type
-  DrawCommandType* {.size: sizeof(int8).} = enum
-    POINT, LINE, RECT, FILL, CIRCLE
-  DrawCommand* = object
-    x1*, y1*: GLfloat
-    color*: ColorRef
-    case kind*: DrawCommandType
-    of LINE, RECT:
-      x2*, y2*: GLfloat
-    of CIRCLE:
-      points*: seq[GLfloat]
-    else:
-      discard
-
   CanvasObj* = object of NodeObj
-    commands*: seq[DrawCommand]
+    surface: SurfacePtr
+    renderer: RendererPtr
+    canvas_texture: Gluint
 
     position*: Vector2Obj            ## Node position, by default is Vector2(0, 0).
     global_position*: Vector2Obj     ## Node global position.
@@ -57,10 +49,35 @@ proc Canvas*(name: string = "Canvas"): CanvasRef =
   result.global_position = Vector2()
   result.anchor = Anchor(0, 0, 0, 0)
   result.size_anchor = Vector2()
+  result.canvas_texture = 0
+  result.surface = createRGBSurface(
+      0, 40, 40, 32,
+      0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000'u32)
+  result.renderer = result.surface.createSoftwareRenderer()
   result.kind = CANVAS_NODE
   result.type_of_node = NODE_TYPE_CONTROL
+  glGenTextures(1, result.canvas_texture.addr)
+
+
+# --- Private --- #
+template loadColor(color_argument_name: untyped): untyped =
+  let clr = toUint32Tuple(`color_argument_name`)
+  echo clr
+  canvas.renderer.setDrawColor(clr.r.uint8, clr.g.uint8, clr.b.uint8, clr.a.uint8)
 
+template loadGL(canvas: untyped): untyped =
+  `canvas`.renderer.present()
+  discard `canvas`.renderer.readPixels(nil, 0, `canvas`.surface.pixels, 0)
+  glBindTexture(GL_TEXTURE_2D, `canvas`.canvas_texture)
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA.GLint, `canvas`.surface.w,  `canvas`.surface.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, `canvas`.surface.pixels)
+  glBindTexture(GL_TEXTURE_2D, 0)
 
+
+# --- Public --- #
 method calcGlobalPosition*(self: CanvasRef) {.base.} =
   ## Returns global node position.
   self.global_position = self.position
@@ -74,57 +91,26 @@ method calcPositionAnchor*(self: CanvasRef) {.base.} =
   ## This used in the Window object.
   discard
 
-proc calculateX(canvas: CanvasRef, x, gx: GLfloat): GLfloat =
-  if x > canvas.rect_size.x + gx:
-    canvas.rect_size.x + gx
-  elif x < gx:
-    gx
-  else:
-    x
-proc calculateY(canvas: CanvasRef, y, gy: GLfloat): GLfloat =
-  if y < gy - canvas.rect_size.y:
-    gy - canvas.rect_size.y
-  elif y > gy:
-    gy
-  else:
-    y
-
-
 method draw*(canvas: CanvasRef, w, h: GLfloat) =
   ## This uses in the `window.nim`.
   let
     x = -w/2 + canvas.global_position.x
     y = h/2 - canvas.global_position.y
-  for cmd in canvas.commands:
-    case cmd.kind:
-    of POINT:
-      glBegin(GL_POINTS)
-      glColor4f(cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a)
-      glVertex2f(canvas.calculateX(x + cmd.x1, x), canvas.calculateY(y - cmd.y1, y))
-      glEnd()
-    of LINE:
-      glBegin(GL_LINES)
-      glColor4f(cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a)
-      glVertex2f(canvas.calculateX(x + cmd.x1, x), canvas.calculateY(y - cmd.y1, y))
-      glVertex2f(canvas.calculateX(x + cmd.x2, x), canvas.calculateY(y - cmd.y2, y))
-      glEnd()
-    of RECT:
-      glColor4f(cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a)
-      glRectf(
-        canvas.calculateX(x + cmd.x1, x), canvas.calculateY(y - cmd.y1, y),
-        canvas.calculateX(x + cmd.x2, x), canvas.calculateY(y - cmd.y2, y))
-    of FILL:
-      glColor4f(cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a)
-      glRectf(x, y, x + canvas.rect_size.x, y - canvas.rect_size.y)
-    of CIRCLE:
-      glColor4f(cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a)
-      glBegin(GL_TRIANGLE_FAN)
-      glVertex2f(canvas.calculateX(x + cmd.x1, x), canvas.calculateY(y - cmd.y1, y))
-      for i in countup(0, cmd.points.len()-1, 2):
-        glVertex2f(
-          canvas.calculateX(x + cmd.points[i] + cmd.x1, x),
-          canvas.calculateY(y - cmd.y1 - cmd.points[i+1], y))
-      glEnd()
+  glColor4f(1, 1, 1, 1)
+  glBindTexture(GL_TEXTURE_2D, canvas.canvas_texture)
+  glEnable(GL_TEXTURE_2D)
+  glBegin(GL_QUADS)
+  glTexCoord2f(0, 0)
+  glVertex2f(x, y)
+  glTexCoord2f(1, 0)
+  glVertex2f(x + canvas.rect_size.x, y)
+  glTexCoord2f(1, 1)
+  glVertex2f(x + canvas.rect_size.x, y - canvas.rect_size.y)
+  glTexCoord2f(0, 1)
+  glVertex2f(x, y - canvas.rect_size.y)
+  glEnd()
+  glDisable(GL_TEXTURE_2D)
+  glBindTexture(GL_TEXTURE_2D, 0)
 
 method duplicate*(self: CanvasRef): CanvasRef {.base.} =
   ## Duplicates Canvas object and create a new Canvas.
@@ -147,6 +133,20 @@ method move*(self: CanvasRef, x, y: float) {.base, inline.} =
   self.position += Vector2(x, y)
   self.anchor.clear()
 
+method bezier*(canvas: CanvasRef, x1, y1, x2, y2, x3, y3: GLfloat, color: ColorRef) {.base.} =
+  ## Draws a quadric bezier curve at 3 points.
+  loadColor(color)
+  for pnt in bezier_iter(0.001, Vector2(x1, y1), Vector2(x2, y2), Vector2(x3, y3)):
+    canvas.renderer.drawPoint(pnt.x.cint, pnt.y.cint)
+  loadGL(canvas)
+
+method cubic_bezier*(canvas: CanvasRef, x1, y1, x2, y2, x3, y3, x4, y4: GLfloat, color: ColorRef) {.base.} =
+  ## Draws a quadric bezier curve at 3 points.
+  loadColor(color)
+  for pnt in cubic_bezier_iter(0.001, Vector2(x1, y1), Vector2(x2, y2), Vector2(x3, y3), Vector2(x4, y4)):
+    canvas.renderer.drawPoint(pnt.x.cint, pnt.y.cint)
+  loadGL(canvas)
+
 method circle*(canvas: CanvasRef, x, y, radius: GLfloat, color: ColorRef, quality: int = 100) {.base.} =
   ## Draws a circle in the canvas.
   ##
@@ -156,12 +156,11 @@ method circle*(canvas: CanvasRef, x, y, radius: GLfloat, color: ColorRef, qualit
   ## - `radius` - circle radius.
   ## - `color` - Color object.
   ## - `quality` - circle quality.
-  var pnts: seq[GLfloat]
+  loadColor(color)
   for i in 0..quality:
     let angle = TAU*i.float/quality.float
-    pnts.add(radius*cos(angle))
-    pnts.add(radius*sin(angle))
-  canvas.commands.add(DrawCommand(kind: CIRCLE, x1: x, y1: y, color: color, points: pnts))
+    canvas.renderer.drawPoint((x + radius*cos(angle)).cint, (y + radius*sin(angle)).cint)
+  loadGL(canvas)
 
 method point*(canvas: CanvasRef, x, y: GLfloat, color: ColorRef) {.base.} =
   ## Draws a point in the canvas.
@@ -170,7 +169,9 @@ method point*(canvas: CanvasRef, x, y: GLfloat, color: ColorRef) {.base.} =
   ## - `x` - point position at X axis.
   ## - `y` - point position at Y axis.
   ## - `color` - point color.
-  canvas.commands.add(DrawCommand(kind: POINT, x1: x, y1: y, color: color))
+  loadColor(color)
+  canvas.renderer.drawPoint(x.cint, y.cint)
+  loadGL(canvas)
 
 method line*(canvas: CanvasRef, x1, y1, x2, y2: GLfloat, color: ColorRef) {.base.} =
   ## Draws a line in the canvas.
@@ -181,7 +182,9 @@ method line*(canvas: CanvasRef, x1, y1, x2, y2: GLfloat, color: ColorRef) {.base
   ## - `x2` - second position at X axis.
   ## - `y2` - second position at Y axis.
   ## - `color` - line color.
-  canvas.commands.add(DrawCommand(kind: LINE, x1: x1, y1: y1, x2: x2, y2: y2, color: color))
+  loadColor(color)
+  canvas.renderer.drawLine(x1.cint, y1.cint, x2.cint, y2.cint)
+  loadGL(canvas)
 
 method rect*(canvas: CanvasRef, x1, y1, x2, y2: GLfloat, color: ColorRef) {.base.} =
   ## Draws a line in the canvas.
@@ -192,13 +195,16 @@ method rect*(canvas: CanvasRef, x1, y1, x2, y2: GLfloat, color: ColorRef) {.base
   ## - `x2` - second position at X axis.
   ## - `y2` - second position at Y axis.
   ## - `color` - rectangle color.
-  canvas.commands.add(
-    DrawCommand(kind: RECT, x1: x1, y1: y1, x2: x2, y2: y2, color: color)
-  )
+  loadColor(color)
+  var rectangle = rect(x1.cint, y1.cint, x2.cint, y2.cint)
+  canvas.renderer.drawRect(rectangle)
+  loadGL(canvas)
 
 method fill*(canvas: CanvasRef, color: ColorRef) {.base.} =
   ## Fills canvas.
-  canvas.commands = @[DrawCommand(kind: FILL, x1: 0, y1: 0, color: color)]
+  loadColor(color)
+  canvas.renderer.clear()
+  loadGL(canvas)
 
 method resize*(self: CanvasRef, w, h: GLfloat, save_anchor: bool = false) {.base.} =
   ## Resizes canvas.
@@ -218,7 +224,20 @@ method resize*(self: CanvasRef, w, h: GLfloat, save_anchor: bool = false) {.base
       self.size_anchor.y = 0.0
   else:
     self.rect_size.y = self.rect_min_size.y
+  if self.kind == CANVAS_NODE:
+    var new_surface = createRGBSurface(
+      0, w.cint, h.cint, 32,
+      0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000'u32)
+    self.surface.blitSurface(nil, new_surface, nil)
+    self.renderer.destroy()
+    self.surface.freeSurface()
+    self.surface = new_surface
+    self.renderer = self.surface.createSoftwareRenderer()
+    loadGL(self)
 
+proc saveAs*(self: CanvasRef, filename: cstring) =
+  ## Saves canvas as image file.
+  discard self.surface.savePNG(filename)
 
 method setAnchor*(self: CanvasRef, anchor: AnchorObj) {.base.} =
   ## Changes node anchor.

BIN
tests/assets/canvas.png


+ 4 - 1
tests/test2.nim

@@ -11,13 +11,16 @@ var
 
 main.addChild(canvas)
 
+canvas.resize(256, 256)
 canvas.fill(Color(0xffaaccff'u32))
 canvas.point(5, 5, Color("#64ffffff"))
 canvas.line(8, 16, 128, 64, Color("#ffff64ff"))
 canvas.circle(0, 240, 32, Color("#aaff6456"))
 canvas.line(200, -150, 0, 256, Color("#0e1317ff"))
-canvas.resize(256, 256)
+canvas.bezier(0, 0, 256, 0, 256, 256, Color("#227"))
+canvas.cubic_bezier(0, 0, 256, 0, 0, 256, 256, 256, Color("#272"))
 canvas.move(74.4, 89.4)
+canvas.saveAs("assets/canvas.png")  # save result in file.
 
 
 addScene(main)