layerfx_stroke.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. # Layer effect > Stroke
  2. # (fr) Effet de calque > Trait
  3. from lazpaint import dialog
  4. try:
  5. from tkinter import Tk, Frame, Label, Scale, HORIZONTAL, Button, RIGHT, W, E
  6. except ImportError:
  7. dialog.show_message("Please install tkinter. On Debian distributions, use the command: apt install python3-tk")
  8. exit()
  9. from lazpaint import colors, image, layer, filters, tools, selection
  10. translation = dialog.translate_dict(["Layer is empty", "Radius", "Color",
  11. "Opacity", "Ok", "Cancel", "Run this script again to update effect"])
  12. if layer.is_empty():
  13. dialog.show_message(translation["Layer is empty"])
  14. exit()
  15. ############ image processing
  16. FRIENDLY_NAME = dialog.get_script_name()
  17. MAX_RADIUS = 100
  18. MAX_OPACITY = 255
  19. source_layer_id = layer.get_id()
  20. source_layer_name = layer.get_name()
  21. chosen_radius = layer.get_registry("stroke-radius")
  22. if chosen_radius == None:
  23. chosen_radius = image.get_registry("stroke-radius")
  24. if chosen_radius == None:
  25. chosen_radius = 10
  26. stroke_layer_id = layer.get_registry("stroke-layer-id")
  27. if image.get_layer_index(stroke_layer_id) == None:
  28. stroke_layer_id = None
  29. if stroke_layer_id is not None:
  30. layer.select_id(stroke_layer_id)
  31. chosen_opacity = layer.get_opacity()
  32. overlay_color = colors.str_to_RGBA(layer.get_registry("overlay-color"))
  33. layer.select_id(source_layer_id)
  34. else:
  35. chosen_opacity = layer.get_opacity()
  36. overlay_color = None
  37. if overlay_color is None:
  38. overlay_color = colors.BLACK
  39. def create_stroke_layer():
  40. global stroke_layer_id
  41. image.do_begin()
  42. if stroke_layer_id != None:
  43. layer.select_id(stroke_layer_id)
  44. stroke_index = image.get_layer_index()
  45. selection.select_all()
  46. selection.delete()
  47. layer.select_id(source_layer_id)
  48. layer.duplicate()
  49. image.move_layer_index(image.get_layer_index(), stroke_index+1)
  50. layer.merge_over()
  51. else:
  52. layer.select_id(source_layer_id)
  53. layer.duplicate()
  54. layer.set_name(FRIENDLY_NAME + " - " + source_layer_name)
  55. layer.set_registry("stroke-source-layer-id", source_layer_id)
  56. stroke_layer_id = layer.get_id()
  57. stroke_index = image.get_layer_index()
  58. image.move_layer_index(stroke_index, stroke_index-1)
  59. image.do_end()
  60. stroke_done = False
  61. opacity_done = False
  62. stroke_initial_color = None
  63. def apply_stroke():
  64. global stroke_done, opacity_done, stroke_initial_color
  65. if opacity_done:
  66. image.undo()
  67. opacity_done = False
  68. if stroke_done:
  69. image.undo()
  70. stroke_done = False
  71. image.do_begin()
  72. if chosen_radius > 0.5:
  73. layer.duplicate()
  74. disk_id = layer.get_id()
  75. filters.blur(name=filters.BLUR_DISK, radius=chosen_radius-0.5)
  76. filters.filter_function(red=overlay_color.red/255, green=overlay_color.green/255, blue=overlay_color.blue/255, alpha="min(alpha*"+str(chosen_radius*5)+",(1-alpha)*"+str(chosen_radius*5)+")", gamma_correction=False)
  77. layer.select_id(stroke_layer_id)
  78. else:
  79. disk_id = None
  80. filters.blur(name=filters.BLUR_CORONA, radius=chosen_radius+0.5)
  81. filters.filter_function(red=overlay_color.red/255, green=overlay_color.green/255, blue=overlay_color.blue/255, alpha="min(alpha*"+str(chosen_radius)+",(1-alpha)*"+str(chosen_radius)+")", gamma_correction=False)
  82. if disk_id is not None:
  83. layer.select_id(disk_id)
  84. layer.merge_over()
  85. layer.set_registry("overlay-color", overlay_color)
  86. stroke_initial_color = overlay_color
  87. stroke_done = image.do_end()
  88. apply_opacity()
  89. def apply_opacity():
  90. global opacity_done, chosen_opacity, overlay_color
  91. if opacity_done:
  92. image.undo()
  93. opacity_done = False
  94. image.do_begin()
  95. layer.set_opacity(chosen_opacity)
  96. if overlay_color != stroke_initial_color:
  97. filters.filter_function(red=overlay_color.red/255, green=overlay_color.green/255, blue=overlay_color.blue/255, gamma_correction=False)
  98. layer.set_registry("overlay-color", overlay_color)
  99. opacity_done = image.do_end()
  100. ######## interface
  101. def button_ok_click():
  102. global source_layer_id, chosen_radius, chosen_offset
  103. layer.select_id(source_layer_id)
  104. layer.set_registry("stroke-radius", chosen_radius)
  105. layer.set_registry("stroke-layer-id", stroke_layer_id)
  106. image.set_registry("stroke-radius", chosen_radius)
  107. image.do_end()
  108. exit()
  109. def button_cancel_click():
  110. if image.do_end():
  111. image.undo()
  112. layer.select_id(source_layer_id)
  113. exit()
  114. scale_radius_update_job = None
  115. def scale_radius_update_do():
  116. global scale_radius_update_job, chosen_radius, scale_radius
  117. new_radius = scale_radius.get()
  118. if new_radius != chosen_radius:
  119. chosen_radius = new_radius
  120. apply_stroke()
  121. scale_radius_update_job = None
  122. def scale_radius_update(event):
  123. global window, scale_radius_update_job
  124. if scale_radius_update_job:
  125. window.after_cancel(scale_radius_update_job)
  126. scale_radius_update_job = window.after(500, scale_radius_update_do)
  127. scale_opacity_update_job = None
  128. def scale_opacity_update_do():
  129. global chosen_opacity
  130. new_opacity = scale_opacity.get()
  131. if new_opacity != chosen_opacity:
  132. chosen_opacity = new_opacity
  133. apply_opacity()
  134. scale_opacity_update_job = None
  135. def scale_opacity_update(event):
  136. global window, scale_opacity_update_job
  137. if scale_opacity_update_job:
  138. window.after_cancel(scale_opacity_update_job)
  139. scale_opacity_update_job = window.after(100, scale_opacity_update_do)
  140. def button_color_click():
  141. global overlay_color, window
  142. new_color = colors.show_dialog(overlay_color)
  143. window.attributes('-topmost', True)
  144. window.attributes('-topmost', False)
  145. if new_color is not None and new_color != overlay_color:
  146. overlay_color = new_color
  147. apply_opacity()
  148. window = Tk()
  149. window.title(FRIENDLY_NAME)
  150. window.resizable(False, False)
  151. frame = Frame(window)
  152. frame.pack()
  153. label_radius = Label(frame, text=translation["Radius"])
  154. label_radius.grid(column=0, row=0)
  155. scale_radius = Scale(frame, from_=0, to=MAX_RADIUS, orient=HORIZONTAL, command=scale_radius_update)
  156. scale_radius.grid(column=1, row=0, sticky=W+E, padx=10)
  157. scale_radius.set(chosen_radius)
  158. label_opacity = Label(frame, text=translation["Opacity"])
  159. label_opacity.grid(column=0, row=1)
  160. scale_opacity = Scale(frame, from_=0, to=MAX_OPACITY, orient=HORIZONTAL, command=scale_opacity_update)
  161. scale_opacity.grid(column=1, row=1, sticky=W+E, padx=10)
  162. scale_opacity.set(chosen_opacity)
  163. label_color = Label(frame, text=translation["Color"])
  164. label_color.grid(column=0, row=2)
  165. button_color = Button(frame, text=translation["Color"] + "...", command=button_color_click)
  166. button_color.grid(column=1, row=2)
  167. label_info = Label(frame, text=translation["Run this script again to update effect"],
  168. background="lemon chiffon", foreground="black", borderwidth=1, relief="solid")
  169. label_info.grid(column=0, rows=3, columnspan=2)
  170. frame.columnconfigure(0, pad=20)
  171. frame.columnconfigure(1, minsize=250)
  172. frame.rowconfigure(0, pad=20)
  173. frame.rowconfigure(1, pad=20)
  174. frame.rowconfigure(2, pad=20)
  175. button_ok = Button(window, text=translation["Ok"], command=button_ok_click)
  176. button_ok.pack(side=RIGHT, padx=10, pady=10)
  177. button_cancel = Button(window, text=translation["Cancel"], command=button_cancel_click)
  178. button_cancel.pack(side=RIGHT, pady=10)
  179. image.do_begin()
  180. selection.deselect()
  181. create_stroke_layer()
  182. apply_stroke()
  183. window.update()
  184. window_width = window.winfo_width()
  185. screen_width = window.winfo_screenwidth()
  186. window.geometry('+%d+0' % (int((screen_width - window_width) / 2)))
  187. window.lift()
  188. window.attributes('-topmost',True)
  189. window.after_idle(window.attributes,'-topmost',False)
  190. window.mainloop()
  191. button_cancel_click()