iRadEntertainment

got_carried_out

Aug 23rd, 2021
368
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.97 KB | None | 0 0
  1. extends Node
  2.  
  3. var width := 200
  4. var height := 200
  5.  
  6. var selectedColor := 2
  7. var brushSize := 1
  8. enum {BRUSH_SQUARE, BRUSH_CIRCLE}
  9. var brushShape = BRUSH_SQUARE
  10. var pixelOffset : PoolVector2Array
  11.  
  12. var colors = [
  13. Color8( 0, 0, 0, 0), #Blank
  14. Color8(255,255,255,255),#white
  15. Color8( 0, 0, 0,255),#black
  16. Color8( 0, 60,200,255),#blue
  17. Color8(255, 23, 23,255),#red
  18. Color8(255,230, 0,255),#yellow
  19. Color8( 0,130, 50,255),#green
  20. Color8(128, 0,255,255),#purple
  21. Color8(255, 0,128,255) #pink
  22. ]
  23.  
  24. var canvas = Image.new()
  25.  
  26. var cursorPosition : Vector2 #Holds the cursor position
  27. var cursorRelative : Vector2
  28. var cursorRelativePreviousFrame : Vector2
  29. var stroke = [] #this will hold 2 coordinates
  30. var stroke_curve := Curve2D.new()
  31.  
  32.  
  33. #==================================== INIT =====================================
  34. func _ready():
  35. OS.vsync_enabled = false
  36. setup_ui()
  37. create_main_canvas()
  38. get_node("toolbar/palette/widthField").text = width as String
  39. get_node("toolbar/palette/heightField").text = height as String
  40.  
  41.  
  42. func create_main_canvas():
  43. if not has_node("canvas"):
  44. var canvasSprite = Sprite.new()
  45. canvasSprite.name = "canvas"
  46. add_child(canvasSprite)
  47. $canvas.position = get_viewport().size/2
  48. #Creating the canvas. NOTE, in the future this is gonna be replaced with code to load a png
  49. canvas.create(width,height,false,Image.FORMAT_RGBA4444) #RGBA4444 stores up to 16 colors. Which is perfect for us
  50. canvas.lock()
  51. clear_canvas()
  52.  
  53.  
  54. func setup_ui():
  55. #--- toolbar
  56. var barHeight = 28
  57. var bgBar = ColorRect.new()
  58. bgBar.name = "toolbar"
  59. bgBar.color = Color.darkslategray
  60. bgBar.anchor_right = 1
  61. bgBar.rect_min_size = Vector2(0,barHeight)
  62. add_child(bgBar)
  63.  
  64. var palette = HBoxContainer.new()
  65. palette.name = "palette"
  66. palette.anchor_bottom = 1
  67. palette.anchor_right = 1
  68. bgBar.add_child(palette)
  69.  
  70. var brushTypeButton = Button.new()
  71. brushTypeButton.name = "btn_brush_shape"
  72. brushTypeButton.connect("pressed",self,"change_brush_shape")
  73. brushTypeButton.rect_min_size = Vector2(barHeight,barHeight)
  74. brushTypeButton.text = "x"
  75. palette.add_child(brushTypeButton)
  76.  
  77. for i in range(colors.size()):
  78. var button = Button.new()
  79. button.connect("pressed",self,"set",["selectedColor", i])
  80. # button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
  81. button.rect_min_size = Vector2(barHeight,barHeight)
  82. palette.add_child(button)
  83. var colorDisplay = ColorRect.new()
  84. colorDisplay.color = colors[i]
  85. colorDisplay.mouse_filter = Control.MOUSE_FILTER_IGNORE
  86. colorDisplay.anchor_top = .2
  87. colorDisplay.anchor_left = .2
  88. colorDisplay.anchor_bottom = .8
  89. colorDisplay.anchor_right = .8
  90. button.add_child(colorDisplay)
  91.  
  92. var sizeSlider = HSlider.new()
  93. sizeSlider.min_value = 1
  94. sizeSlider.max_value = 21
  95. sizeSlider.step = 1
  96. sizeSlider.rect_min_size = Vector2(120,barHeight)
  97. sizeSlider.connect("value_changed",self,"change_brush_size")
  98. palette.add_child(sizeSlider)
  99.  
  100. var brushSizeLabel = Label.new()
  101. brushSizeLabel.name = "brushSizeLabel"
  102. brushSizeLabel.text = "%02d"%brushSize
  103. palette.add_child(brushSizeLabel)
  104.  
  105. var sep00 = VSeparator.new()
  106. palette.add_child(sep00)
  107.  
  108. var clearCanvasButton = Button.new()
  109. clearCanvasButton.connect("pressed",self,"clear_canvas")
  110. clearCanvasButton.rect_min_size = Vector2(80,barHeight)
  111. clearCanvasButton.text = "Clear"
  112. palette.add_child(clearCanvasButton)
  113.  
  114. var widthField = LineEdit.new()
  115. widthField.name = "widthField"
  116. widthField.placeholder_text = "width"
  117. widthField.rect_min_size = Vector2(80, barHeight)
  118. palette.add_child(widthField)
  119.  
  120. var heightField = LineEdit.new()
  121. heightField.name = "heightField"
  122. heightField.placeholder_text = "height"
  123. heightField.rect_min_size = Vector2(80, barHeight)
  124. palette.add_child(heightField)
  125.  
  126. var newCanvas = Button.new()
  127. newCanvas.connect("pressed", self, "make_new_canvas")
  128. newCanvas.name = "newCanvas"
  129. newCanvas.text = "New Canvas"
  130. palette.add_child(newCanvas)
  131.  
  132. #--- infobar
  133. var bgInfo = ColorRect.new()
  134. bgInfo.name = "infobar"
  135. bgInfo.color = Color.darkslategray
  136. bgInfo.rect_min_size = Vector2(0, barHeight)
  137. bgInfo.anchor_top = 1
  138. bgInfo.anchor_right = 1
  139. add_child(bgInfo)
  140. bgInfo.margin_top = -barHeight
  141.  
  142. var info = HBoxContainer.new()
  143. info.name = "info"
  144. info.anchor_bottom = 1
  145. info.anchor_right = 1
  146. bgInfo.add_child(info)
  147.  
  148. var zoomOutBtn = Button.new()
  149. zoomOutBtn.connect("pressed", self, "zoom_canvas", [false])
  150. zoomOutBtn.size_flags_vertical = Control.SIZE_SHRINK_CENTER
  151. zoomOutBtn.rect_min_size = Vector2(barHeight/2, barHeight/2)
  152. zoomOutBtn.text = "-"
  153. info.add_child(zoomOutBtn)
  154.  
  155. var zoomInfo = Label.new()
  156. zoomInfo.name = "zoomInfo"
  157. info.add_child(zoomInfo)
  158.  
  159. var zoomInBtn = Button.new()
  160. zoomInBtn.connect("pressed", self, "zoom_canvas", [true])
  161. zoomInBtn.size_flags_vertical = Control.SIZE_SHRINK_CENTER
  162. zoomInBtn.rect_min_size = Vector2(barHeight/2, barHeight/2)
  163. zoomInBtn.text = "+"
  164. info.add_child(zoomInBtn)
  165.  
  166. var sep01 = VSeparator.new()
  167. info.add_child(sep01)
  168.  
  169. var cursorPosInfo = Label.new()
  170. cursorPosInfo.name = "cursorPosInfo"
  171. info.add_child(cursorPosInfo)
  172.  
  173.  
  174.  
  175. #================================= PROCESSING ==================================
  176. func _unhandled_input(event):
  177. #--- calculate mouse position on canvas
  178. if event is InputEventMouse:
  179. $cursor.position = event.global_position
  180.  
  181. if event is InputEventMouseMotion or InputEventMouseButton:
  182. cursorPosition = (event.global_position - $canvas.position)
  183. cursorPosition += Vector2(width,height)/2*$canvas.scale.x
  184. cursorPosition /= $canvas.scale.x
  185. cursorPosition.x = round(cursorPosition.x)
  186. cursorPosition.y = round(cursorPosition.y)
  187. update_info()
  188.  
  189. if event is InputEventMouseMotion:
  190. cursorRelativePreviousFrame = cursorRelative
  191. cursorRelative = event.relative/($canvas.scale.x)
  192.  
  193. #--- draw single pixel on the first click
  194. if event.is_action_pressed("draw"):
  195. draw_brush([cursorPosition])
  196.  
  197. #--- draw while dragging
  198. if event is InputEventMouseMotion:
  199. if Input.is_action_pressed("draw"):
  200. #- clear arrays and curve
  201. stroke.clear()
  202. stroke_curve.clear_points()
  203. stroke_curve.bake_interval = 1
  204.  
  205. if false:#cursorRelative.x <= 1 and cursorRelative.y <= 1:
  206. draw_brush([cursorPosition])
  207. else:
  208.  
  209. #- add two points to the curve
  210. stroke_curve.add_point(cursorPosition - cursorRelative, cursorRelativePreviousFrame*.3, cursorRelativePreviousFrame*.3)
  211. stroke_curve.add_point(cursorPosition)
  212.  
  213. #- get rounded baked points from the curve
  214. for bakedPoint in stroke_curve.get_baked_points():
  215. var rounded_point = Vector2()
  216. rounded_point.x = round(bakedPoint.x)
  217. rounded_point.y = round(bakedPoint.y)
  218. if not rounded_point in stroke:
  219. stroke.append(rounded_point)
  220.  
  221. draw_brush(stroke)
  222.  
  223. #--- Zoom and pan
  224. if Input.is_action_pressed("screen_drag") and event is InputEventMouseMotion:
  225. $canvas.position += event.relative
  226. clamp_canvas_position_in_work_area()
  227. if event.is_action_pressed("zoom_in"):
  228. zoom_canvas(true, event.global_position)
  229. if event.is_action_pressed("zoom_out"):
  230. zoom_canvas(false, event.global_position)
  231. if event is InputEventMouseButton:
  232. if event.is_pressed() and event.button_index == BUTTON_RIGHT:
  233. flood_fill()
  234.  
  235.  
  236.  
  237. #=================================== PAINT =====================================
  238. func draw_pixels(positions : Array):
  239. for pos in positions:
  240. pos.x = clamp(pos.x, 0, width-1)
  241. pos.y = clamp(pos.y, 0, height-1)
  242. canvas.set_pixel(pos.x, pos.y, colors[selectedColor])
  243.  
  244.  
  245. func draw_brush(positions : Array):
  246. var pixelToDraw : PoolVector2Array = []
  247.  
  248. if brushSize == 1:
  249. draw_pixels(positions)
  250.  
  251. else:
  252. if brushShape == BRUSH_CIRCLE:
  253. pixelOffset = calculate_pixel_offsets_in_circle()
  254. elif brushShape == BRUSH_SQUARE:
  255. pixelOffset = calculate_pixel_offsets_in_square()
  256.  
  257. for pos in positions:
  258. for offset in pixelOffset:
  259. if not (pos + offset) in pixelToDraw:
  260. pixelToDraw.append(pos + offset)
  261.  
  262. draw_pixels(pixelToDraw)
  263.  
  264. update_canvas()
  265.  
  266. func flood_fill():
  267. if not pos_is_in_canvas(cursorPosition):
  268. return
  269. var sampled_color = canvas.get_pixel(cursorPosition.x, cursorPosition.y)
  270. if sampled_color == colors[selectedColor]:
  271. return
  272. var t_start = OS.get_ticks_msec()
  273. var current = Vector2()
  274. var queue = [cursorPosition]
  275. var looked = BitMap.new()
  276. looked.create(Vector2(width+2, height+2))
  277. var px_to_fill = []
  278. var iter = 0
  279. while not queue.empty():
  280. current = queue.pop_front()
  281. # px_to_fill.append(current)
  282. canvas.set_pixel(current.x, current.y, colors[selectedColor])
  283.  
  284. for dir in [Vector2.UP, Vector2.LEFT, Vector2.DOWN, Vector2.RIGHT]:
  285. iter += 1
  286. var next = (current + dir)
  287. # if pos_is_in_canvas(next):
  288. next.x = clamp(next.x, 0, width-1)
  289. next.y = clamp(next.y, 0, height-1)
  290. if not looked.get_bit(next):
  291. looked.set_bit(next, true)
  292. var next_px = canvas.get_pixel(next.x, next.y)
  293. if next_px == sampled_color:
  294. queue.push_front(next)
  295.  
  296. # for px in px_to_fill:
  297. # canvas.set_pixel(px.x, px.y, colors[selectedColor])
  298.  
  299. var t_end = OS.get_ticks_msec()
  300. print("===========")
  301. print("Flood Fill")
  302. print("===========")
  303. print("iterations: %s"%iter)
  304. print("tot px in canvas: %s"%(height*width))
  305. print("time in msec: %s"%(t_end-t_start))
  306. print("Check this link for improvements: https://godotengine.org/qa/56212/texture-floodfill-too-slow")
  307. print("===========")
  308.  
  309. update_canvas()
  310.  
  311.  
  312. #=================================== CANVAS ====================================
  313. func update_canvas():
  314. var texture = ImageTexture.new()
  315. texture.create_from_image(canvas)
  316. texture.flags = 16 #disables antialiasing
  317. $canvas.texture = texture
  318.  
  319.  
  320. func clear_canvas():
  321. for y in height:
  322. for x in width:
  323. canvas.set_pixel(x,y,colors[1])
  324. update_canvas()
  325.  
  326.  
  327. func make_new_canvas():
  328. width = get_node("toolbar/palette/widthField").text as int
  329. height = get_node("toolbar/palette/heightField").text as int
  330. width = clamp(width, 10, 1500)
  331. height = clamp(height, 10, 1500)
  332. canvas.unlock()
  333. create_main_canvas()
  334. get_node("toolbar/palette/widthField").text = width as String
  335. get_node("toolbar/palette/heightField").text = height as String
  336.  
  337.  
  338. #==================================== TOOLS ====================================
  339. func calculate_pixel_offsets_in_circle() -> PoolVector2Array:
  340. var circleArray : PoolVector2Array = calculate_pixel_offsets_in_square()
  341. for i in range(circleArray.size()-1, -1, -1):
  342. if circleArray[i].length() > (brushSize)/2:
  343. circleArray.remove(i)
  344.  
  345. return circleArray
  346.  
  347.  
  348. func calculate_pixel_offsets_in_square() -> PoolVector2Array:
  349. var squareArray : PoolVector2Array = []
  350. var r = (brushSize-1)/2
  351. for y in range(-r,r+1):
  352. for x in range(-r,r+1):
  353. squareArray.append(Vector2(x,y))
  354.  
  355. return squareArray
  356.  
  357.  
  358. func update_info():
  359. get_node("infobar/info/zoomInfo").text = "%%%0.02f"%($canvas.scale.x*100)
  360. get_node("infobar/info/cursorPosInfo").text = "pos - %s | %s"%[cursorPosition.x, cursorPosition.y]
  361.  
  362.  
  363. func zoom_canvas(zooming_in : bool, pos = null):
  364. $canvas.scale *= 2.0 if zooming_in else 0.5
  365. $canvas.scale.x = clamp($canvas.scale.x, 0.125, 64)
  366. $canvas.scale.y = $canvas.scale.x
  367. #- move the canvas to follow the mouse position after zoom
  368. if pos:
  369.  
  370. # adjust the canvas position only if the cursor is on the canvas sprite
  371. if pos_is_in_canvas(cursorPosition):
  372. if zooming_in:
  373. $canvas.position -= (cursorPosition - Vector2(width, height)/2)*($canvas.scale/2)
  374. else:
  375. $canvas.position += (cursorPosition - Vector2(width, height)/2)*$canvas.scale
  376. clamp_canvas_position_in_work_area()
  377.  
  378. update_info()
  379.  
  380.  
  381. func pos_is_in_canvas(pos) -> bool:
  382. var in_canvas_x = (pos.x >= 0) and (pos.x < width)
  383. var in_canvas_y = (pos.y >= 0) and (pos.y < height)
  384. return (in_canvas_x and in_canvas_y)
  385.  
  386.  
  387. func clamp_canvas_position_in_work_area():
  388. var workareaSize = get_viewport().size
  389. var min_pos = Vector2()
  390. var max_pos = Vector2()
  391. var margin = Vector2(32,32)
  392.  
  393. min_pos.x = -(width/2 * $canvas.scale.x) + margin.x
  394. min_pos.y = -(height/2 * $canvas.scale.x) + margin.y
  395. max_pos.x = workareaSize.x + (width/2 * $canvas.scale.x) - margin.x
  396. max_pos.y = workareaSize.y + (height/2 * $canvas.scale.x) - margin.y
  397.  
  398. $canvas.position.x = clamp($canvas.position.x, min_pos.x, max_pos.x)
  399. $canvas.position.y = clamp($canvas.position.y, min_pos.y, max_pos.y)
  400.  
  401.  
  402. #=================================== SIGNALS ===================================
  403. func _notification(what):
  404. if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
  405. # file.close()
  406. get_tree().quit()
  407.  
  408. func change_brush_shape():
  409. if brushShape == BRUSH_CIRCLE: brushShape = BRUSH_SQUARE
  410. else: brushShape = BRUSH_CIRCLE
  411. get_node("toolbar/palette/btn_brush_shape").text = "o" if brushShape == BRUSH_CIRCLE else "x"
  412.  
  413. func change_brush_size(val):
  414. brushSize = val
  415. get_node("toolbar/palette/brushSizeLabel").text = "%02d"%brushSize
  416. pixelOffset = calculate_pixel_offsets_in_square() if brushShape == BRUSH_SQUARE else calculate_pixel_offsets_in_circle()
  417.  
  418.  
  419.  
Add Comment
Please, Sign In to add comment