__init__.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. import ast
  2. import time
  3. from gi.repository import GLib, Gtk, Gdk, GdkPixbuf
  4. import Activity
  5. import Export
  6. import threading
  7. import cv2
  8. import numpy
  9. import getpass
  10. import os
  11. from PF2 import Histogram, Layer
  12. from PF2.Tools import BlackWhite
  13. from PF2.Tools import Colours
  14. from PF2.Tools import Contrast
  15. from PF2.Tools import Details
  16. from PF2.Tools import Tonemap
  17. from PF2.Tools import HueEqualiser
  18. class PF2(Activity.Activity):
  19. def on_init(self):
  20. self.id = "PF2"
  21. self.name = "Edit a Photo"
  22. self.subtitle = "Edit a Raster Image with PhotoFiddle"
  23. UI_FILE = "ui/PF2_Activity.glade"
  24. self.builder.add_from_file(UI_FILE)
  25. self.widget = self.builder.get_object("PF2_main")
  26. self.stack.add_titled(self.widget, self.id, self.name)
  27. self.header_widget = self.builder.get_object("PF2_header")
  28. self.header_stack.add_titled(self.header_widget, self.id, self.name)
  29. # Get all UI Components
  30. self.ui = {}
  31. components = [
  32. "control_reveal",
  33. "histogram_reveal",
  34. "layers_reveal",
  35. "upper_peak_toggle",
  36. "lower_peak_toggle",
  37. "histogram",
  38. "layer_stack",
  39. "preview",
  40. "preview_eventbox",
  41. "scroll_window",
  42. "open_button",
  43. "original_toggle",
  44. "tool_box_stack",
  45. "tool_stack",
  46. "open_window",
  47. "open_header",
  48. "open_cancel_button",
  49. "open_open_button",
  50. "open_chooser",
  51. "popovermenu",
  52. "show_hist",
  53. "export_image",
  54. "undo",
  55. "redo",
  56. "zoom_toggle",
  57. "zoom_reveal",
  58. "zoom",
  59. "reset",
  60. "mask_draw_toggle",
  61. "mask_erase_toggle",
  62. "mask_brush_size",
  63. "layers_list",
  64. "new_layer",
  65. "layer_mask_reveal",
  66. "add_layer_button",
  67. "remove_layer_button"
  68. ]
  69. for component in components:
  70. self.ui[component] = self.builder.get_object("%s_%s" % (self.id, component))
  71. self.menu_popover = self.ui["popovermenu"]
  72. # Set up tools
  73. self.tools = [
  74. Contrast.Contrast,
  75. Tonemap.Tonemap,
  76. Details.Details,
  77. Colours.Colours,
  78. HueEqualiser.HueEqualiser,
  79. BlackWhite.BlackWhite
  80. ]
  81. # Setup layers
  82. self.layers = []
  83. self.base_layer = self.create_layer("base", True)
  84. # Set the first tool to active
  85. # self.tools[0].tool_button.set_active(True)
  86. # self.ui["tools"].show_all()
  87. # Disable ui components by default
  88. self.ui["original_toggle"].set_sensitive(False)
  89. self.ui["export_image"].set_sensitive(False)
  90. # Setup editor variables
  91. self.is_editing = False
  92. self.has_loaded = False
  93. self.image_path = ""
  94. self.bit_depth = 8
  95. self.image = None
  96. self.original_image = None
  97. self.pwidth = 0
  98. self.pheight = 0
  99. self.awidth = 0
  100. self.aheight = 0
  101. self.pimage = None
  102. self.poriginal = None
  103. self.change_occurred = False
  104. self.additional_change_occurred = False
  105. self.running_stack = False
  106. self.upper_peak_on = False
  107. self.lower_peak_on = False
  108. self.is_exporting = False
  109. self.is_zooming = False
  110. self.undo_stack = []
  111. self.undo_position = 0
  112. self.undoing = False
  113. # Setup Open Dialog
  114. self.ui["open_window"].set_transient_for(self.root)
  115. self.ui["open_window"].set_titlebar(self.ui["open_header"])
  116. # Connect UI signals
  117. self.ui["open_button"].connect("clicked", self.on_open_clicked)
  118. self.ui["preview"].connect("draw", self.on_preview_draw)
  119. self.ui["original_toggle"].connect("toggled", self.on_preview_toggled)
  120. self.ui["upper_peak_toggle"].connect("toggled", self.on_upper_peak_toggled)
  121. self.ui["lower_peak_toggle"].connect("toggled", self.on_lower_peak_toggled)
  122. self.ui["show_hist"].connect("toggled", self.toggle_hist)
  123. self.ui["export_image"].connect("clicked", self.on_export_clicked)
  124. self.ui["open_open_button"].connect("clicked", self.on_file_opened)
  125. self.ui["open_cancel_button"].connect("clicked", self.on_file_canceled)
  126. self.ui["zoom_toggle"].connect("toggled", self.on_zoom_toggled)
  127. self.ui["zoom"].connect("value-changed", self.on_zoom_changed)
  128. self.ui["undo"].connect("clicked", self.on_undo)
  129. self.ui["redo"].connect("clicked", self.on_redo)
  130. self.ui["reset"].connect("clicked", self.on_reset)
  131. self.ui["preview_eventbox"].connect('motion-notify-event', self.preview_dragged)
  132. self.ui["mask_draw_toggle"].connect("toggled", self.mask_draw_toggled)
  133. self.ui["mask_erase_toggle"].connect("toggled", self.mask_erase_toggled)
  134. self.ui["new_layer"].connect("clicked", self.new_layer_button_clicked)
  135. self.ui["layers_list"].connect("row-activated", self.layer_ui_activated)
  136. self.ui["add_layer_button"].connect("clicked", self.new_layer_button_clicked)
  137. self.ui["remove_layer_button"].connect("clicked", self.remove_layer_button_clicked)
  138. def on_open_clicked(self, sender):
  139. self.ui["open_window"].show_all()
  140. def on_file_opened(self, sender, path=None):
  141. self.has_loaded = False
  142. self.image = None
  143. self.undo_position = 0
  144. self.undo_stack = []
  145. self.update_undo_state()
  146. self.ui["open_window"].hide()
  147. self.ui["control_reveal"].set_reveal_child(True)
  148. self.ui["control_reveal"].set_sensitive(True)
  149. self.ui["original_toggle"].set_sensitive(True)
  150. self.ui["export_image"].set_sensitive(True)
  151. self.ui["reset"].set_sensitive(True)
  152. self.ui["new_layer"].set_sensitive(True)
  153. self.ui["layers_reveal"].set_reveal_child(False)
  154. self.is_editing = True
  155. if(path == None):
  156. self.image_path = self.ui["open_chooser"].get_filename()
  157. else:
  158. self.image_path = path
  159. w = (self.ui["scroll_window"].get_allocated_width() - 12) * self.ui["zoom"].get_value()
  160. h = (self.ui["scroll_window"].get_allocated_height() - 12) * self.ui["zoom"].get_value()
  161. self.show_message("Loading Photo…", "Please wait while PhotoFiddle loads your photo", True)
  162. self.root.get_titlebar().set_subtitle("%s…" % (self.image_path))
  163. # Delete all layers, except the base
  164. for layer in self.layers:
  165. if(layer.name != "base"):
  166. self.ui["layers_list"].remove(layer.selector_row)
  167. self.layers = [self.base_layer,]
  168. self.select_layer(self.base_layer)
  169. thread = threading.Thread(target=self.open_image,
  170. args=(w, h))
  171. thread.start()
  172. def on_zoom_toggled(self, sender):
  173. state = sender.get_active()
  174. self.ui["zoom_reveal"].set_reveal_child(state)
  175. if(not state):
  176. self.ui["zoom"].set_value(1)
  177. def on_zoom_changed(self, sender):
  178. threading.Thread(target=self.zoom_delay).start()
  179. def zoom_delay(self):
  180. if(not self.is_zooming):
  181. self.is_zooming = True
  182. time.sleep(0.5)
  183. GLib.idle_add(self.on_preview_draw, None, None)
  184. self.is_zooming = False
  185. def on_file_canceled(self, sender):
  186. self.ui["open_window"].hide()
  187. def on_preview_draw(self, sender, arg):
  188. if(self.is_editing):
  189. w = (self.ui["scroll_window"].get_allocated_width() - 12) * self.ui["zoom"].get_value()
  190. h = (self.ui["scroll_window"].get_allocated_height() - 12) * self.ui["zoom"].get_value()
  191. if(self.pheight != h) or (self.pwidth != w):
  192. thread = threading.Thread(target=self.resize_preview,
  193. args=(w, h))
  194. thread.start()
  195. def on_preview_toggled(self, sender):
  196. if(sender.get_active()):
  197. threading.Thread(target=self.draw_hist, args=(self.original_image,)).start()
  198. self.show_original()
  199. else:
  200. threading.Thread(target=self.draw_hist, args=(self.image,)).start()
  201. self.show_current()
  202. def toggle_hist(self, sender):
  203. show = sender.get_active()
  204. self.ui["histogram_reveal"].set_reveal_child(show)
  205. def on_open(self, path):
  206. self.root.get_titlebar().set_subtitle("Raster Editor")
  207. if(path != None):
  208. # We have been sent here from another
  209. # activity, clear any existing profiles
  210. self.image_path = path
  211. dataPath = self.get_data_path()
  212. if(os.path.exists(dataPath)):
  213. os.unlink(dataPath)
  214. self.on_file_opened(None, path)
  215. def on_exit(self):
  216. if(self.is_exporting):
  217. return False
  218. else:
  219. fname = "/tmp/phf2-preview-%s.png" % getpass.getuser()
  220. if (os.path.exists(fname)):
  221. os.unlink(fname)
  222. self.root.get_titlebar().set_subtitle("")
  223. self.on_init()
  224. return True
  225. def show_original(self):
  226. self.ui["preview"].set_from_pixbuf(self.poriginal)
  227. if (not self.ui["original_toggle"].get_active()):
  228. self.ui["original_toggle"].set_active(True)
  229. def show_current(self):
  230. self.ui["preview"].set_from_pixbuf(self.pimage)
  231. if (self.ui["original_toggle"].get_active()):
  232. self.ui["original_toggle"].set_active(False)
  233. def on_layer_change(self, layer):
  234. if(self.has_loaded):
  235. if(not self.change_occurred):
  236. self.change_occurred = True
  237. thread = threading.Thread(target=self.update_image)
  238. thread.start()
  239. else:
  240. self.additional_change_occurred = True
  241. def on_lower_peak_toggled(self, sender):
  242. self.lower_peak_on = sender.get_active()
  243. if (self.lower_peak_on):
  244. thread = threading.Thread(target=self.process_peaks, args=(True,))
  245. thread.start()
  246. else:
  247. thread = threading.Thread(target=self.update_image, args=(True,))
  248. thread.start()
  249. def on_upper_peak_toggled(self, sender):
  250. self.upper_peak_on = sender.get_active()
  251. if (self.lower_peak_on):
  252. thread = threading.Thread(target=self.process_peaks, args=(True,))
  253. thread.start()
  254. else:
  255. thread = threading.Thread(target=self.update_image, args=(True,))
  256. thread.start()
  257. def image_opened(self, depth):
  258. self.root.get_titlebar().set_subtitle("%s (%s Bit)" % (self.image_path, depth))
  259. self.hide_message()
  260. def on_export_clicked(self, sender):
  261. Export.ExportDialog(self.root, self.builder, self.awidth, self.aheight, self.get_export_image, self.on_export_complete, self.image_path)
  262. def on_export_complete(self, filename):
  263. self.is_exporting = False
  264. self.on_export_state_change()
  265. self.show_message("Export Complete!", "Your photo has been exported to '%s'" % filename)
  266. def on_export_state_change(self):
  267. self.ui["control_reveal"].set_sensitive(not self.is_exporting)
  268. self.ui["export_image"].set_sensitive(not self.is_exporting)
  269. self.ui["open_button"].set_sensitive(not self.is_exporting)
  270. def on_export_started(self):
  271. self.is_exporting = True
  272. self.on_export_state_change()
  273. def update_undo_state(self):
  274. self.ui["undo"].set_sensitive(self.undo_position > 0)
  275. self.ui["redo"].set_sensitive(len(self.undo_stack)-1 > self.undo_position)
  276. def on_undo(self, sender):
  277. self.undo_position -= 1
  278. self.update_undo_state()
  279. self.update_from_undo_stack(self.undo_stack[self.undo_position])
  280. def on_redo(self, sender):
  281. self.undo_position += 1
  282. self.update_undo_state()
  283. self.update_from_undo_stack(self.undo_stack[self.undo_position])
  284. def on_reset(self, sender):
  285. for layer in self.layers:
  286. layer.reset_tools()
  287. ## Background Tasks ##
  288. def open_image(self, w, h):
  289. self.load_image_data()
  290. try:
  291. self.resize_preview(w, h)
  292. except:
  293. pass
  294. while(self.image == None):
  295. time.sleep(1)
  296. GLib.idle_add(self.image_opened, str(self.image.dtype).replace("uint", "").replace("float", ""))
  297. time.sleep(1)
  298. self.has_loaded = True
  299. def resize_preview(self, w, h):
  300. # Inhibit undo stack to prevent
  301. # Adding an action on resize
  302. self.undoing = True
  303. self.original_image = cv2.imread(self.image_path, 2 | 1)
  304. height, width = self.original_image.shape[:2]
  305. self.aheight = height
  306. self.awidth = width
  307. self.pheight = h
  308. self.pwidth = w
  309. # Get fitting size
  310. ratio = float(w)/width
  311. if(height*ratio > h):
  312. ratio = float(h)/height
  313. nw = width * ratio
  314. nh = height * ratio
  315. # Do quick ui resize
  316. if(self.image != None) and (os.path.exists("/tmp/phf2-preview-%s.png" % getpass.getuser())):
  317. # If we have an edited version, show that
  318. self.pimage = GdkPixbuf.Pixbuf.new_from_file_at_scale("/tmp/phf2-preview-%s.png" % getpass.getuser(),
  319. int(nw), int(nh), True)
  320. GLib.idle_add(self.show_current)
  321. self.poriginal = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.image_path,
  322. int(nw), int(nh), True)
  323. if(self.image == None):
  324. # Otherwise show the original
  325. GLib.idle_add(self.show_original)
  326. # Resize OPENCV Copy
  327. self.original_image = cv2.resize(self.original_image, (int(nw), int(nh)), interpolation = cv2.INTER_AREA)
  328. self.image = numpy.copy(self.original_image)
  329. # Update image
  330. if (not self.change_occurred):
  331. self.change_occurred = True
  332. self.update_image()
  333. else:
  334. self.additional_change_occurred = True
  335. def update_image(self, immediate=False):
  336. if(not immediate):
  337. time.sleep(0.5)
  338. self.additional_change_occurred = False
  339. GLib.idle_add(self.start_work)
  340. self.image = numpy.copy(self.original_image)
  341. self.image = self.run_stack(self.image)
  342. if(self.additional_change_occurred):
  343. self.update_image()
  344. else:
  345. self.save_image_data()
  346. self.draw_hist(self.image)
  347. self.process_peaks()
  348. self.update_preview()
  349. GLib.idle_add(self.stop_work)
  350. self.change_occurred = False
  351. self.undoing = False
  352. def run_stack(self, image, callback=None):
  353. if(not self.running_stack):
  354. self.running_stack = True
  355. baseImage = image.copy()
  356. for layer in self.layers:
  357. print(layer)
  358. image = layer.render_layer(baseImage, image, callback)
  359. self.running_stack = False
  360. return image
  361. else:
  362. while(self.running_stack):
  363. time.sleep(1)
  364. return self.run_stack(image, callback)
  365. def update_preview(self):
  366. fname = "/tmp/phf2-preview-%s.png" % getpass.getuser()
  367. if(os.path.exists(fname)):
  368. os.unlink(fname)
  369. cv2.imwrite(fname, self.image)
  370. self.pimage = GdkPixbuf.Pixbuf.new_from_file(fname)
  371. GLib.idle_add(self.show_current)
  372. def draw_hist(self, image):
  373. path = "/tmp/phf2-hist-%s.png" % getpass.getuser()
  374. Histogram.Histogram.draw_hist(image, path)
  375. GLib.idle_add(self.update_hist_ui, path)
  376. def update_hist_ui(self, path):
  377. try:
  378. self.ui["histogram"].set_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file(path))
  379. except:
  380. pass
  381. def process_peaks(self, do_update=False):
  382. bpp = float(str(self.image.dtype).replace("uint", "").replace("float", ""))
  383. if(self.upper_peak_on):
  384. self.image[(self.image == 2 ** bpp - 1).all(axis=2)] = 1
  385. if(self.lower_peak_on):
  386. self.image[(self.image == 0).all(axis=2)] = 2 ** bpp - 2
  387. if(do_update):
  388. self.update_preview()
  389. ## FILE STUFF ##
  390. def get_data_path(self):
  391. return "%s/.%s.pf2" % ("/".join(self.image_path.split("/")[:-1]), self.image_path.split("/")[-1:][0])
  392. def save_image_data(self):
  393. path = self.get_data_path()
  394. print(path)
  395. f = open(path, "w")
  396. layerDict = {}
  397. for layer in self.layers:
  398. layerDict[layer.name] = layer.get_layer_dict()
  399. if(not self.undoing) and (self.has_loaded):
  400. if(len(self.undo_stack)-1 != self.undo_position):
  401. self.undo_stack = self.undo_stack[:self.undo_position+1]
  402. if(self.undo_stack[self.undo_position] != layerDict):
  403. self.undo_stack += [layerDict,]
  404. self.undo_position = len(self.undo_stack)-1
  405. GLib.idle_add(self.update_undo_state)
  406. data = {
  407. "path":self.image_path,
  408. "format-revision":1,
  409. "layers": layerDict
  410. }
  411. f.write(str(data))
  412. f.close()
  413. def load_image_data(self):
  414. path = self.get_data_path()
  415. loadDefaults = True
  416. if(os.path.exists(path)):
  417. f = open(path, 'r')
  418. sdata = f.read()
  419. if(True):
  420. #try:
  421. data = ast.literal_eval(sdata)
  422. if(data["format-revision"] == 1):
  423. for layer in data["layers"]:
  424. GLib.idle_add(self.create_layer_with_data, layer, data["layers"][layer])
  425. self.undo_stack = [data["layers"],]
  426. self.undo_position = 0
  427. loadDefaults = False
  428. #except:
  429. # GLib.idle_add(self.show_message,"Unable to load previous edits…",
  430. # "The edit file for this photo is corrupted and could not be loaded.")
  431. if(loadDefaults):
  432. for layer in self.layers:
  433. for tool in layer.tools:
  434. GLib.idle_add(tool.reset)
  435. layerDict = {}
  436. for layer in self.layers:
  437. layerDict[layer.name] = layer.get_layer_dict()
  438. self.undo_stack = [layerDict, ]
  439. self.undo_position = 0
  440. GLib.idle_add(self.update_undo_state)
  441. time.sleep(2)
  442. self.undoing = False
  443. def create_layer_with_data(self, layer, data):
  444. if (layer == "base"):
  445. self.base_layer.set_from_layer_dict(data)
  446. else:
  447. GLib.idle_add(self.show_layers)
  448. ilayer = self.create_layer(layer, False)
  449. ilayer.set_from_layer_dict(data)
  450. def update_from_undo_stack(self, data):
  451. self.undoing = True
  452. for layer in data:
  453. if (layer == "base"):
  454. self.base_layer.set_from_layer_dict(data[layer])
  455. else:
  456. ilayer = self.create_layer(layer, False)
  457. ilayer.set_from_layer_dict(data[layer])
  458. def get_export_image(self, w, h):
  459. GLib.idle_add(self.on_export_started)
  460. GLib.idle_add(self.show_message, "Exporting Photo", "Please wait…", True, True)
  461. img = cv2.imread(self.image_path, 2 | 1)
  462. img = cv2.resize(img, (int(w), int(h)), interpolation=cv2.INTER_AREA)
  463. img = self.run_stack(img, self.export_progress_callback)
  464. GLib.idle_add(self.show_message, "Exporting Photo", "Saving to filesystem…", True, True)
  465. GLib.idle_add(self.update_message_progress, 1, 1)
  466. return img
  467. def export_progress_callback(self, name, count, current):
  468. GLib.idle_add(self.show_message, "Exporting Photo", "Processing: %s" % name, True, True)
  469. GLib.idle_add(self.update_message_progress, current, count)
  470. ## Layers Stuff ##
  471. def preview_dragged(self, widget, event):
  472. print(event.x, event.y)
  473. def mask_draw_toggled(self, widget):
  474. if(widget.get_active()):
  475. self.ui["mask_erase_toggle"].set_active(False)
  476. def mask_erase_toggled(self, widget):
  477. if(widget.get_active()):
  478. self.ui["mask_draw_toggle"].set_active(False)
  479. def create_layer(self, layer_name, is_base):
  480. layer = Layer.Layer(is_base, layer_name, self.on_layer_change)
  481. for tool in self.tools:
  482. tool_instance = tool()
  483. layer.add_tool(tool_instance)
  484. self.layers += [layer,]
  485. GLib.idle_add(self.create_layer_ui, layer)
  486. return layer
  487. def create_layer_ui(self, layer):
  488. layer_box = Gtk.HBox()
  489. layer_box.set_hexpand(False)
  490. layer_box.set_halign(Gtk.Align.START)
  491. layer_toggle = Gtk.CheckButton()
  492. layer_toggle.set_sensitive(layer.editable)
  493. layer_toggle.set_active(layer.enabled)
  494. layer_toggle.set_hexpand(False)
  495. layer_toggle.set_halign(Gtk.Align.START)
  496. layer_toggle.set_margin_right(8)
  497. layer_toggle.set_margin_left(8)
  498. layer_toggle.set_margin_top(4)
  499. layer_toggle.set_margin_bottom(4)
  500. layer_label = Gtk.Label()
  501. layer_label.set_label(layer.name)
  502. layer_label.set_hexpand(True)
  503. layer_label.set_halign(Gtk.Align.FILL)
  504. layer_label.set_margin_top(4)
  505. layer_label.set_margin_bottom(4)
  506. layer_box.add(layer_toggle)
  507. layer_box.add(layer_label)
  508. layer.show_all()
  509. layer_box.show_all()
  510. self.ui["layers_list"].add(layer_box)
  511. layer.selector_row = layer_box.get_parent()
  512. self.ui["tool_box_stack"].add(layer.tool_box)
  513. self.ui["tool_stack"].add(layer.tool_stack)
  514. def layer_ui_activated(self, widget, row):
  515. layer_index = row.get_index()
  516. self.ui["tool_stack"].set_visible_child(self.layers[layer_index].tool_stack)
  517. self.ui["tool_box_stack"].set_visible_child(self.layers[layer_index].tool_box)
  518. self.ui["layer_mask_reveal"].set_reveal_child(self.layers[layer_index].editable)
  519. self.ui["remove_layer_button"].set_sensitive(self.layers[layer_index].editable)
  520. def new_layer_button_clicked(self, widget):
  521. self.show_layers()
  522. # Allocate an un-used layer name
  523. layer_number = len(self.layers)
  524. layer_name = "Layer %i" % layer_number
  525. while(self.layer_exists(layer_name)):
  526. layer_number += 1
  527. layer_name = "Layer %i" % layer_number
  528. # Create the layer
  529. layer = self.create_layer(layer_name, False)
  530. # Save changes
  531. threading.Thread(target=self.save_image_data).start()
  532. def remove_layer_button_clicked(self, widget):
  533. layer_row = self.ui["layers_list"].get_selected_row()
  534. selected_index = 1
  535. new_layers = []
  536. for layer in self.layers:
  537. if(layer.selector_row != layer_row):
  538. new_layers += [layer]
  539. else:
  540. selected_index = self.layers.index(layer)
  541. self.ui["layers_list"].remove(layer_row)
  542. self.layers = new_layers
  543. # Select next layer
  544. self.select_layer(self.layers[selected_index -1])
  545. if(len(self.layers) == 1):
  546. self.ui["layers_reveal"].set_reveal_child(False)
  547. # Save changes
  548. threading.Thread(target=self.save_image_data).start()
  549. def layer_exists(self, layer_name):
  550. for layer in self.layers:
  551. if(layer.name == layer_name):
  552. return True
  553. return False
  554. def show_layers(self):
  555. self.ui["layers_reveal"].set_reveal_child(True)
  556. def select_layer(self, layer):
  557. self.ui["layers_list"].select_row(layer.selector_row)
  558. self.layer_ui_activated(self.ui["layers_list"], layer.selector_row)