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