__init__.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029
  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. from PF2.Tools import Blur
  19. class PF2(Activity.Activity):
  20. def on_init(self):
  21. self.id = "PF2"
  22. self.name = "Edit a Photo"
  23. self.subtitle = "Edit a Raster Image with PhotoFiddle"
  24. UI_FILE = "ui/PF2_Activity.glade"
  25. self.builder.add_from_file(UI_FILE)
  26. self.widget = self.builder.get_object("PF2_main")
  27. self.stack.add_titled(self.widget, self.id, self.name)
  28. self.header_widget = self.builder.get_object("PF2_header")
  29. self.header_stack.add_titled(self.header_widget, self.id, self.name)
  30. # Get all UI Components
  31. self.ui = {}
  32. components = [
  33. "main",
  34. "control_reveal",
  35. "histogram_reveal",
  36. "layers_reveal",
  37. "upper_peak_toggle",
  38. "lower_peak_toggle",
  39. "histogram",
  40. "layer_stack",
  41. "preview",
  42. "preview_eventbox",
  43. "scroll_window",
  44. "open_button",
  45. "original_toggle",
  46. "tool_box_stack",
  47. "tool_stack",
  48. "open_window",
  49. "open_header",
  50. "open_cancel_button",
  51. "open_open_button",
  52. "open_chooser",
  53. "popovermenu",
  54. "show_hist",
  55. "export_image",
  56. "undo",
  57. "redo",
  58. "zoom_toggle",
  59. "zoom_reveal",
  60. "zoom",
  61. "reset",
  62. "mask_draw_toggle",
  63. "mask_erase_toggle",
  64. "mask_brush_size",
  65. "layers_list",
  66. "new_layer",
  67. "layer_mask_reveal",
  68. "add_layer_button",
  69. "remove_layer_button",
  70. "mask_brush_feather",
  71. "mask_brush_feather_scale",
  72. "edit_layer_mask_button",
  73. "layer_opacity",
  74. "layer_opacity_scale",
  75. "layer_blend_mode",
  76. "viewport"
  77. ]
  78. for component in components:
  79. self.ui[component] = self.builder.get_object("%s_%s" % (self.id, component))
  80. self.menu_popover = self.ui["popovermenu"]
  81. # Set up tools
  82. self.tools = [
  83. Contrast.Contrast,
  84. Tonemap.Tonemap,
  85. Details.Details,
  86. Colours.Colours,
  87. HueEqualiser.HueEqualiser,
  88. BlackWhite.BlackWhite,
  89. Blur.Blur
  90. ]
  91. # Setup layers
  92. self.layers = []
  93. self.base_layer = self.create_layer("base", True)
  94. # Set the first tool to active
  95. # self.tools[0].tool_button.set_active(True)
  96. # self.ui["tools"].show_all()
  97. # Disable ui components by default
  98. self.ui["original_toggle"].set_sensitive(False)
  99. self.ui["export_image"].set_sensitive(False)
  100. # Setup editor variables
  101. self.is_editing = False
  102. self.has_loaded = False
  103. self.image_path = ""
  104. self.bit_depth = 8
  105. self.image = None
  106. self.image_is_dirty = True
  107. self.original_image = None
  108. self.pwidth = 0
  109. self.pheight = 0
  110. self.awidth = 0
  111. self.aheight = 0
  112. self.pimage = None
  113. self.poriginal = None
  114. self.change_occurred = False
  115. self.additional_change_occurred = False
  116. self.running_stack = False
  117. self.upper_peak_on = False
  118. self.lower_peak_on = False
  119. self.is_exporting = False
  120. self.is_zooming = False
  121. self.undo_stack = []
  122. self.undo_position = 0
  123. self.undoing = False
  124. self.current_layer_path = None
  125. self.mousex = 0
  126. self.mousey = 0
  127. self.mousedown = False
  128. self.layer_order = []
  129. self.pre_undo_layer_name = "base"
  130. self.pre_undo_editing = False
  131. self.pre_undo_erasing = False
  132. self.last_selected_layer = None
  133. self.was_editing_mask = False
  134. self.source_image = None
  135. self.is_scaling = False
  136. self.current_processing_layer_name = "base"
  137. self.current_processing_layer_index = 0
  138. # Setup Open Dialog
  139. self.ui["open_window"].set_transient_for(self.root)
  140. self.ui["open_window"].set_titlebar(self.ui["open_header"])
  141. # Connect UI signals
  142. self.ui["open_button"].connect("clicked", self.on_open_clicked)
  143. self.ui["preview"].connect("draw", self.on_preview_draw)
  144. self.ui["original_toggle"].connect("toggled", self.on_preview_toggled)
  145. self.ui["upper_peak_toggle"].connect("toggled", self.on_upper_peak_toggled)
  146. self.ui["lower_peak_toggle"].connect("toggled", self.on_lower_peak_toggled)
  147. self.ui["show_hist"].connect("toggled", self.toggle_hist)
  148. self.ui["export_image"].connect("clicked", self.on_export_clicked)
  149. self.ui["open_open_button"].connect("clicked", self.on_file_opened)
  150. self.ui["open_cancel_button"].connect("clicked", self.on_file_canceled)
  151. self.ui["zoom_toggle"].connect("toggled", self.on_zoom_toggled)
  152. self.ui["zoom"].connect("value-changed", self.on_zoom_changed)
  153. self.ui["undo"].connect("clicked", self.on_undo)
  154. self.ui["redo"].connect("clicked", self.on_redo)
  155. self.ui["reset"].connect("clicked", self.on_reset)
  156. self.ui["preview_eventbox"].connect('motion-notify-event', self.preview_dragged)
  157. self.ui["preview_eventbox"].connect('button-press-event', self.new_path)
  158. self.ui["mask_draw_toggle"].connect("toggled", self.mask_draw_toggled)
  159. self.ui["mask_erase_toggle"].connect("toggled", self.mask_erase_toggled)
  160. self.ui["new_layer"].connect("clicked", self.new_layer_button_clicked)
  161. self.ui["layers_list"].connect("row-activated", self.layer_ui_activated)
  162. self.ui["add_layer_button"].connect("clicked", self.new_layer_button_clicked)
  163. self.ui["remove_layer_button"].connect("clicked", self.remove_layer_button_clicked)
  164. self.ui["edit_layer_mask_button"].connect("toggled", self.edit_mask_toggled)
  165. self.ui["layer_opacity"].connect("value-changed", self.update_layer_opacity)
  166. self.ui["scroll_window"].connect_after("draw", self.draw_ui_brush_circle)
  167. self.ui["scroll_window"].connect_after('motion-notify-event', self.mouse_coords_changed)
  168. self.ui["mask_brush_size"].connect("value-changed", self.brush_size_changed)
  169. self.ui["layer_blend_mode"].connect("changed", self.layer_blend_mode_changed)
  170. def on_open_clicked(self, sender):
  171. self.ui["open_window"].show_all()
  172. def on_file_opened(self, sender, path=None):
  173. self.has_loaded = False
  174. self.image = None
  175. self.undo_position = 0
  176. self.undo_stack = []
  177. self.update_undo_state()
  178. self.ui["open_window"].hide()
  179. self.ui["control_reveal"].set_reveal_child(True)
  180. self.ui["control_reveal"].set_sensitive(True)
  181. self.ui["original_toggle"].set_sensitive(True)
  182. self.ui["export_image"].set_sensitive(True)
  183. self.ui["reset"].set_sensitive(True)
  184. self.ui["new_layer"].set_sensitive(True)
  185. self.ui["layers_reveal"].set_reveal_child(False)
  186. self.is_editing = True
  187. if(path == None):
  188. self.image_path = self.ui["open_chooser"].get_filename()
  189. else:
  190. self.image_path = path
  191. w = (self.ui["scroll_window"].get_allocated_width() - 12) * self.ui["zoom"].get_value()
  192. h = (self.ui["scroll_window"].get_allocated_height() - 12) * self.ui["zoom"].get_value()
  193. self.show_message("Loading Photo…", "Please wait while PhotoFiddle loads your photo", True)
  194. self.root.get_titlebar().set_subtitle("%s…" % (self.image_path))
  195. # Delete all layers, except the base
  196. for layer in self.layers:
  197. if(layer.name != "base"):
  198. self.ui["layers_list"].remove(layer.selector_row)
  199. self.layers = [self.base_layer,]
  200. self.select_layer(self.base_layer)
  201. thread = threading.Thread(target=self.open_image,
  202. args=(w, h))
  203. thread.start()
  204. def on_zoom_toggled(self, sender):
  205. state = sender.get_active()
  206. self.ui["zoom_reveal"].set_reveal_child(state)
  207. if(not state):
  208. self.ui["zoom"].set_value(1)
  209. def on_zoom_changed(self, sender):
  210. threading.Thread(target=self.zoom_delay).start()
  211. def zoom_delay(self):
  212. if(not self.is_zooming):
  213. self.is_zooming = True
  214. time.sleep(0.5)
  215. GLib.idle_add(self.on_preview_draw, None, None)
  216. self.is_zooming = False
  217. def on_file_canceled(self, sender):
  218. self.ui["open_window"].hide()
  219. def on_preview_draw(self, sender, arg):
  220. if(self.is_editing):
  221. w = (self.ui["scroll_window"].get_allocated_width() - 12) * self.ui["zoom"].get_value()
  222. h = (self.ui["scroll_window"].get_allocated_height() - 12) * self.ui["zoom"].get_value()
  223. if(self.pheight != h) or (self.pwidth != w):
  224. thread = threading.Thread(target=self.resize_preview,
  225. args=(w, h))
  226. thread.start()
  227. def on_preview_toggled(self, sender):
  228. if(sender.get_active()):
  229. threading.Thread(target=self.draw_hist, args=(self.original_image,)).start()
  230. self.show_original()
  231. else:
  232. threading.Thread(target=self.draw_hist, args=(self.image,)).start()
  233. self.show_current()
  234. def toggle_hist(self, sender):
  235. show = sender.get_active()
  236. self.ui["histogram_reveal"].set_reveal_child(show)
  237. def on_open(self, path):
  238. self.root.get_titlebar().set_subtitle("Raster Editor")
  239. if(path != None):
  240. # We have been sent here from another
  241. # activity, clear any existing profiles
  242. self.image_path = path
  243. dataPath = self.get_data_path()
  244. if(os.path.exists(dataPath)):
  245. os.unlink(dataPath)
  246. self.on_file_opened(None, path)
  247. def on_exit(self):
  248. if(self.is_exporting):
  249. return False
  250. else:
  251. fname = "/tmp/phf2-preview-%s.png" % getpass.getuser()
  252. if (os.path.exists(fname)):
  253. os.unlink(fname)
  254. self.root.get_titlebar().set_subtitle("")
  255. self.on_init()
  256. return True
  257. def show_original(self):
  258. self.ui["preview"].set_from_pixbuf(self.poriginal)
  259. if (not self.ui["original_toggle"].get_active()):
  260. self.ui["original_toggle"].set_active(True)
  261. def show_current(self):
  262. self.ui["preview"].set_from_pixbuf(self.pimage)
  263. if (self.ui["original_toggle"].get_active()):
  264. self.ui["original_toggle"].set_active(False)
  265. def on_layer_change(self, layer):
  266. print(layer, "changed")
  267. if(self.has_loaded):
  268. if(not self.change_occurred):
  269. self.change_occurred = True
  270. thread = threading.Thread(target=self.update_image, args=(False, layer))
  271. thread.start()
  272. else:
  273. self.additional_change_occurred = True
  274. def on_layer_mask_change(self, layer):
  275. if(self.has_loaded):
  276. if(not self.change_occurred):
  277. self.change_occurred = True
  278. threading.Thread(target=self.__on_layer_mask_change).start()
  279. self.save_image_data()
  280. def __on_layer_mask_change(self):
  281. image = self.get_selected_layer().layer_copy.copy()
  282. image = self.get_selected_layer().get_layer_mask_preview(image)
  283. self.image_is_dirty = True
  284. self.image = image
  285. self.update_preview()
  286. self.change_occurred = False
  287. self.image_is_dirty = False
  288. def on_lower_peak_toggled(self, sender):
  289. self.lower_peak_on = sender.get_active()
  290. if (self.lower_peak_on):
  291. thread = threading.Thread(target=self.process_peaks, args=(True,))
  292. thread.start()
  293. else:
  294. thread = threading.Thread(target=self.update_image, args=(True,))
  295. thread.start()
  296. def on_upper_peak_toggled(self, sender):
  297. self.upper_peak_on = sender.get_active()
  298. if (self.lower_peak_on):
  299. thread = threading.Thread(target=self.process_peaks, args=(True,))
  300. thread.start()
  301. else:
  302. thread = threading.Thread(target=self.update_image, args=(True,))
  303. thread.start()
  304. def image_opened(self):
  305. self.root.get_titlebar().set_subtitle("%s (%s Bit)" % (self.image_path, self.bit_depth))
  306. self.hide_message()
  307. def on_export_clicked(self, sender):
  308. Export.ExportDialog(self.root, self.builder, self.awidth, self.aheight, self.get_export_image, self.on_export_complete, self.image_path)
  309. def on_export_complete(self, filename):
  310. self.is_exporting = False
  311. self.on_export_state_change()
  312. self.show_message("Export Complete!", "Your photo has been exported to '%s'" % filename)
  313. def on_export_state_change(self):
  314. self.ui["control_reveal"].set_sensitive(not self.is_exporting)
  315. self.ui["export_image"].set_sensitive(not self.is_exporting)
  316. self.ui["open_button"].set_sensitive(not self.is_exporting)
  317. def on_export_started(self):
  318. self.is_exporting = True
  319. self.on_export_state_change()
  320. def update_undo_state(self):
  321. self.ui["undo"].set_sensitive((self.undo_position > 0) and (not self.is_working))
  322. self.ui["redo"].set_sensitive((len(self.undo_stack)-1 > self.undo_position) and (not self.is_working))
  323. def on_undo(self, sender):
  324. self.pre_undo_layer_name = self.get_selected_layer().name
  325. self.pre_undo_erasing = self.ui["mask_erase_toggle"].get_active()
  326. self.pre_undo_editing = self.ui["edit_layer_mask_button"].get_active()
  327. self.undo_position -= 1
  328. self.update_undo_state()
  329. self.update_from_undo_stack(self.undo_stack[self.undo_position])
  330. def on_redo(self, sender):
  331. self.pre_undo_layer_name = self.get_selected_layer().name
  332. self.pre_undo_erasing = self.ui["mask_erase_toggle"].get_active()
  333. self.pre_undo_editing = self.ui["edit_layer_mask_button"].get_active()
  334. self.undo_position += 1
  335. self.update_undo_state()
  336. self.update_from_undo_stack(self.undo_stack[self.undo_position])
  337. def on_reset(self, sender):
  338. for layer in self.layers:
  339. layer.reset_tools()
  340. ## Background Tasks ##
  341. def open_image(self, w, h):
  342. self.load_image_data()
  343. try:
  344. # Get Bit Depth
  345. imdepth = cv2.imread(self.image_path, 2 | 1)
  346. self.bit_depth = str(imdepth.dtype).replace("uint", "").replace("float", "")
  347. # Read the 8Bit Preview
  348. self.source_image = cv2.imread(self.image_path).astype(numpy.uint8)
  349. # Load the image
  350. self.resize_preview(w, h)
  351. except:
  352. pass
  353. while(self.image == None):
  354. time.sleep(1)
  355. GLib.idle_add(self.image_opened)
  356. time.sleep(1)
  357. self.has_loaded = True
  358. def resize_preview(self, w, h):
  359. if(self.source_image != None):
  360. # Inhibit undo stack to prevent
  361. # Adding an action on resize
  362. self.undoing = True
  363. self.original_image = self.source_image.copy()
  364. height, width = self.original_image.shape[:2]
  365. self.aheight = height
  366. self.awidth = width
  367. self.pheight = h
  368. self.pwidth = w
  369. # Get fitting size
  370. ratio = float(w)/width
  371. if(height*ratio > h):
  372. ratio = float(h)/height
  373. nw = width * ratio
  374. nh = height * ratio
  375. # Do quick ui resize
  376. # if(self.image != None):
  377. # # If we have an edited version, show that
  378. # image = cv2.resize(self.image, (int(nw), int(nh)), interpolation=cv2.INTER_NEAREST)
  379. # self.pimage = self.pimage = self.create_pixbuf(image)
  380. # GLib.idle_add(self.show_current)
  381. if(self.pimage != None and not self.is_scaling):
  382. self.is_scaling = True
  383. # If we have an edited version, show that
  384. image = self.pimage.scale_simple(int(nw), int(nh), GdkPixbuf.InterpType.NEAREST)
  385. self.pimage = image
  386. GLib.idle_add(self.show_current)
  387. self.is_scaling = False
  388. self.poriginal = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.image_path,
  389. int(nw), int(nh), True)
  390. if(self.pimage == None):
  391. # Otherwise show the original
  392. GLib.idle_add(self.show_original)
  393. # Resize OPENCV Copy
  394. self.original_image = cv2.resize(self.original_image, (int(nw), int(nh)), interpolation = cv2.INTER_AREA)
  395. self.image_is_dirty = True
  396. self.image = self.original_image.copy()
  397. # Update image
  398. if (not self.change_occurred):
  399. self.change_occurred = True
  400. self.update_image()
  401. else:
  402. self.additional_change_occurred = True
  403. def update_image(self, immediate=False, changed_layer=None):
  404. if(not immediate):
  405. time.sleep(0.5)
  406. self.additional_change_occurred = False
  407. self.is_working = True
  408. GLib.idle_add(self.update_undo_state)
  409. GLib.idle_add(self.start_work)
  410. image = numpy.copy(self.original_image)
  411. rst = time.time()
  412. self.image = self.run_stack(image, changed_layer=changed_layer)
  413. if(self.image == None):
  414. GLib.idle_add(self.stop_work)
  415. self.is_working = False
  416. GLib.idle_add(self.update_undo_state)
  417. raise Exception()
  418. self.process_mask()
  419. self.image_is_dirty = False
  420. if(self.additional_change_occurred):
  421. self.update_image()
  422. else:
  423. self.save_image_data()
  424. threading.Thread(target=self.draw_hist, args=(self.image,)).start()
  425. self.process_peaks()
  426. self.update_preview()
  427. GLib.idle_add(self.stop_work)
  428. self.is_working = False
  429. GLib.idle_add(self.update_undo_state)
  430. self.change_occurred = False
  431. self.undoing = False
  432. def run_stack(self, image, callback=None, changed_layer=None):
  433. if(not self.running_stack):
  434. self.running_stack = True
  435. baseImage = image.copy()
  436. try:
  437. image = None
  438. carry = True
  439. if(self.image != None) and (baseImage.shape == self.image.shape) and (changed_layer != None) and (changed_layer in self.layers):
  440. changed_layer_index = self.layers.index(changed_layer)
  441. if(changed_layer_index > 0):
  442. carry = False
  443. layer_under = self.layers[changed_layer_index -1]
  444. image = layer_under.layer_copy.copy()
  445. for layer in self.layers[changed_layer_index: ]:
  446. self.current_processing_layer_name = layer.name
  447. self.current_processing_layer_index = self.layers.index(layer)
  448. if(self.additional_change_occurred):
  449. break
  450. print(layer)
  451. image = layer.render_layer(baseImage, image, callback)
  452. if(carry):
  453. for layer in self.layers:
  454. self.current_processing_layer_name = layer.name
  455. self.current_processing_layer_index = self.layers.index(layer)
  456. if (self.additional_change_occurred):
  457. break
  458. print(layer)
  459. image = layer.render_layer(baseImage, image, callback)
  460. self.running_stack = False
  461. return image
  462. except(Exception):
  463. GLib.idle_add(self.show_message, "Image Render Failed…",
  464. "PhotoFiddle encountered an internal error and was unable to render the image.")
  465. else:
  466. while(self.running_stack):
  467. time.sleep(1)
  468. return self.run_stack(image, callback)
  469. def update_preview(self):
  470. if(not self.is_scaling):
  471. image = self.create_pixbuf(self.image)
  472. self.pimage = image
  473. GLib.idle_add(self.show_current)
  474. def create_pixbuf(self, im):
  475. image = cv2.cvtColor(im.copy(), cv2.COLOR_BGR2RGB).astype(numpy.uint8)
  476. pb = GdkPixbuf.Pixbuf.new_from_data(image.tostring(),
  477. GdkPixbuf.Colorspace.RGB,
  478. False,
  479. 8,
  480. self.image.shape[1],
  481. self.image.shape[0],
  482. self.image.shape[2] * self.image.shape[1])
  483. return pb
  484. def draw_hist(self, image):
  485. path = "/tmp/phf2-hist-%s.png" % getpass.getuser()
  486. Histogram.Histogram.draw_hist(image, path)
  487. GLib.idle_add(self.update_hist_ui, path)
  488. def update_hist_ui(self, path):
  489. try:
  490. self.ui["histogram"].set_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file(path))
  491. except:
  492. pass
  493. def process_peaks(self, do_update=False):
  494. bpp = float(str(self.image.dtype).replace("uint", "").replace("float", ""))
  495. if(self.upper_peak_on):
  496. self.image[(self.image == 2 ** bpp - 1).all(axis=2)] = 1
  497. if(self.lower_peak_on):
  498. self.image[(self.image == 0).all(axis=2)] = 2 ** bpp - 2
  499. if(do_update):
  500. self.update_preview()
  501. def process_mask(self, do_update=False):
  502. if(self.ui["edit_layer_mask_button"].get_active()):
  503. self.image = self.get_selected_layer().get_layer_mask_preview(self.image)
  504. if (do_update):
  505. self.update_preview()
  506. ## FILE STUFF ##
  507. def get_data_path(self):
  508. return "%s/.%s.pf2" % ("/".join(self.image_path.split("/")[:-1]), self.image_path.split("/")[-1:][0])
  509. def save_image_data(self):
  510. path = self.get_data_path()
  511. print(path)
  512. f = open(path, "w")
  513. layerDict = {}
  514. layerOrder = []
  515. for layer in self.layers:
  516. layerDict[layer.name] = layer.get_layer_dict()
  517. layerOrder.append(layer.name)
  518. if(not self.undoing) and (self.has_loaded):
  519. if(len(self.undo_stack)-1 != self.undo_position):
  520. self.undo_stack = self.undo_stack[:self.undo_position+1]
  521. if(self.undo_stack[self.undo_position] != {"layers": layerDict, "layer-order": layerOrder}):
  522. self.undo_stack += [{"layers": layerDict, "layer-order": layerOrder},]
  523. self.undo_position = len(self.undo_stack)-1
  524. GLib.idle_add(self.update_undo_state)
  525. data = {
  526. "path":self.image_path,
  527. "format-revision":1,
  528. "layers": layerDict,
  529. "layer-order": layerOrder
  530. }
  531. f.write(str(data))
  532. f.close()
  533. def load_image_data(self):
  534. path = self.get_data_path()
  535. loadDefaults = True
  536. if(os.path.exists(path)):
  537. f = open(path, 'r')
  538. sdata = f.read()
  539. try:
  540. data = ast.literal_eval(sdata)
  541. if(data["format-revision"] == 1):
  542. if("layer-order" not in data):
  543. # Backwards compatability
  544. data["layer-order"] = ["base",]
  545. for layer_name in data["layer-order"]:
  546. GLib.idle_add(self.create_layer_with_data, layer_name, data["layers"][layer_name])
  547. self.undo_stack = [data,]
  548. self.undo_position = 0
  549. loadDefaults = False
  550. except:
  551. GLib.idle_add(self.show_message,"Unable to load previous edits…",
  552. "The edit file for this photo is corrupted and could not be loaded.")
  553. if(loadDefaults):
  554. for layer in self.layers:
  555. for tool in layer.tools:
  556. GLib.idle_add(tool.reset)
  557. layerDict = {}
  558. layerOrder = []
  559. for layer in self.layers:
  560. layerDict[layer.name] = layer.get_layer_dict()
  561. layerOrder.append(layer.name)
  562. self.undo_stack = [{"layers": layerDict, "layer-order": layerOrder}, ]
  563. self.undo_position = 0
  564. GLib.idle_add(self.update_undo_state)
  565. #time.sleep(2)
  566. self.undoing = False
  567. def create_layer_with_data(self, layer, data):
  568. if (layer == "base"):
  569. self.base_layer.set_from_layer_dict(data)
  570. else:
  571. GLib.idle_add(self.show_layers)
  572. ilayer = self.create_layer(layer, False)
  573. ilayer.set_from_layer_dict(data)
  574. def update_from_undo_stack(self, data):
  575. self.undoing = True
  576. self.delete_all_editable_layers()
  577. print(data["layer-order"])
  578. for layer_name in data["layer-order"]:
  579. if (layer_name == "base"):
  580. self.base_layer.set_from_layer_dict(data["layers"][layer_name])
  581. else:
  582. ilayer = self.create_layer(layer_name, False, layer_name == self.pre_undo_layer_name, True)
  583. ilayer.set_from_layer_dict(data["layers"][layer_name])
  584. self.show_layers()
  585. def get_export_image(self, w, h):
  586. GLib.idle_add(self.on_export_started)
  587. GLib.idle_add(self.show_message, "Exporting Photo", "Please wait…", True, True)
  588. img = cv2.imread(self.image_path, 2 | 1)
  589. img = cv2.resize(img, (int(w), int(h)), interpolation=cv2.INTER_AREA)
  590. img = self.run_stack(img, self.export_progress_callback)
  591. GLib.idle_add(self.show_message, "Exporting Photo", "Saving to filesystem…", True, True)
  592. GLib.idle_add(self.update_message_progress, 1, 1)
  593. return img
  594. def export_progress_callback(self, name, count, current):
  595. layer_name = self.current_processing_layer_name
  596. if(layer_name == "base"):
  597. layer_name = "Base Layer"
  598. GLib.idle_add(self.show_message, "Exporting Photo", "%s: %s" % (layer_name,
  599. name), True, True)
  600. GLib.idle_add(self.update_message_progress, current+(self.current_processing_layer_index*count),
  601. count*len(self.layers))
  602. ## Layers Stuff ##
  603. def preview_dragged(self, widget, event):
  604. x, y = widget.translate_coordinates(self.ui["preview"], event.x, event.y)
  605. draw = self.ui["mask_draw_toggle"].get_active()
  606. erase = self.ui["mask_erase_toggle"].get_active()
  607. layer = self.get_selected_layer()
  608. if((draw or erase) and layer.editable and self.current_layer_path):
  609. if (x < 0.0):
  610. x = 0
  611. if (y < 0.0):
  612. y = 0
  613. pwidth = self.pimage.get_width()
  614. pheight = self.pimage.get_height()
  615. if (x > pwidth):
  616. x = pwidth
  617. if (y > pheight):
  618. y = pheight
  619. print(x, y)
  620. fill = (0, 0, 255)
  621. if(erase):
  622. fill = (255, 0, 0)
  623. self.draw_path(x, y, pheight, pwidth, fill)
  624. self.on_layer_mask_change(layer)
  625. self.mouse_down_coords_changed(widget, event)
  626. return True
  627. def draw_path(self, x, y, pheight, pwidth, fill):
  628. preview = self.current_layer_path.add_point(int(x), int(y), (pheight, pwidth, 3), fill)
  629. # Bits per pixel
  630. bpp = float(str(self.image.dtype).replace("uint", "").replace("float", ""))
  631. # Pixel value range
  632. np = float(2 ** bpp - 1)
  633. self.image[preview == 255] = np
  634. cv2.imwrite("/tmp/phf2-preview-%s-drag.png" % getpass.getuser(), self.image)
  635. temppbuf = GdkPixbuf.Pixbuf.new_from_file("/tmp/phf2-preview-%s-drag.png" % getpass.getuser())
  636. self.ui["preview"].set_from_pixbuf(temppbuf)
  637. def new_path(self, widget, event):
  638. draw = self.ui["mask_draw_toggle"].get_active()
  639. erase = self.ui["mask_erase_toggle"].get_active()
  640. layer = self.get_selected_layer()
  641. if((draw or erase) and layer.editable):
  642. print(self.pimage.get_width(), self.pimage.get_height())
  643. width = self.pimage.get_width()
  644. size = self.ui["mask_brush_size"].get_value()
  645. feather = self.ui["mask_brush_feather"].get_value()
  646. self.current_layer_path = layer.mask.get_new_path(size, feather, float(self.awidth)/float(width), draw)
  647. def mask_draw_toggled(self, widget):
  648. self.ui["mask_erase_toggle"].set_active(not widget.get_active())
  649. def mask_erase_toggled(self, widget):
  650. self.ui["mask_draw_toggle"].set_active(not widget.get_active())
  651. self.ui["mask_brush_feather_scale"].set_sensitive(not widget.get_active())
  652. def edit_mask_toggled(self, widget):
  653. self.ui["layer_mask_reveal"].set_reveal_child(widget.get_active())
  654. self.ui["mask_draw_toggle"].set_active(widget.get_active())
  655. self.ui["mask_erase_toggle"].set_active(False)
  656. self.ui["scroll_window"].queue_draw()
  657. if (widget.get_active()):
  658. thread = threading.Thread(target=self.process_mask, args=(True,))
  659. thread.start()
  660. elif(self.was_editing_mask):
  661. self.on_layer_change(self.get_selected_layer())
  662. self.was_editing_mask = widget.get_active()
  663. def update_layer_opacity(self, sender):
  664. layer = self.get_selected_layer()
  665. if(layer.opacity != sender.get_value()):
  666. layer.set_opacity(sender.get_value())
  667. def create_layer(self, layer_name, is_base, select = False, set_pre_undo_draw_state = False):
  668. layer = Layer.Layer(is_base, layer_name, self.on_layer_change)
  669. for tool in self.tools:
  670. tool_instance = tool()
  671. layer.add_tool(tool_instance)
  672. self.layers += [layer,]
  673. GLib.idle_add(self.create_layer_ui, layer, select, set_pre_undo_draw_state)
  674. return layer
  675. def create_layer_ui(self, layer, select, set_pre_undo_draw_state = False):
  676. layer_box = Gtk.HBox()
  677. layer_box.set_hexpand(False)
  678. layer_box.set_halign(Gtk.Align.START)
  679. layer_toggle = Gtk.CheckButton()
  680. layer_toggle.set_sensitive(layer.editable)
  681. layer_toggle.set_active(layer.enabled)
  682. layer_toggle.set_hexpand(False)
  683. layer_toggle.set_halign(Gtk.Align.START)
  684. layer_toggle.set_margin_right(8)
  685. layer_toggle.set_margin_left(8)
  686. layer_toggle.set_margin_top(4)
  687. layer_toggle.set_margin_bottom(4)
  688. layer_toggle.connect("toggled", self.toggle_layer, layer)
  689. layer_label = Gtk.Label()
  690. layer_label.set_label(layer.name)
  691. if(layer.name == "base"):
  692. layer_label.set_label("Base Layer")
  693. layer_label.set_hexpand(True)
  694. layer_label.set_halign(Gtk.Align.FILL)
  695. layer_label.set_margin_top(4)
  696. layer_label.set_margin_bottom(4)
  697. layer_box.add(layer_toggle)
  698. layer_box.add(layer_label)
  699. layer.show_all()
  700. layer_box.show_all()
  701. self.ui["layers_list"].add(layer_box)
  702. layer.selector_row = layer_box.get_parent()
  703. self.ui["tool_box_stack"].add(layer.tool_box)
  704. self.ui["tool_stack"].add(layer.tool_stack)
  705. if(select):
  706. self.select_layer(layer)
  707. if(set_pre_undo_draw_state) and self.get_selected_layer().editable:
  708. print(self.pre_undo_editing, self.pre_undo_erasing)
  709. self.ui["mask_erase_toggle"].set_active(self.pre_undo_erasing)
  710. self.ui["edit_layer_mask_button"].set_active(self.pre_undo_editing)
  711. def layer_ui_activated(self, widget, row):
  712. layer_index = row.get_index()
  713. self.ui["tool_stack"].set_visible_child(self.layers[layer_index].tool_stack)
  714. self.ui["tool_box_stack"].set_visible_child(self.layers[layer_index].tool_box)
  715. self.ui["remove_layer_button"].set_sensitive(self.layers[layer_index].editable)
  716. self.ui["edit_layer_mask_button"].set_sensitive(self.layers[layer_index].editable)
  717. self.ui["layer_opacity_scale"].set_sensitive(self.layers[layer_index].editable)
  718. self.ui["layer_blend_mode"].set_sensitive(self.layers[layer_index].editable)
  719. self.ui["layer_blend_mode"].set_active(["additive", "overlay"].index(self.layers[layer_index].blend_mode))
  720. self.ui["layer_opacity"].set_value(self.layers[layer_index].opacity)
  721. if(self.ui["edit_layer_mask_button"].get_active()):
  722. self.ui["edit_layer_mask_button"].set_active(self.layers[layer_index].editable)
  723. self.on_layer_change(self.last_selected_layer)
  724. self.last_selected_layer = self.get_selected_layer()
  725. def toggle_layer(self, sender, layer):
  726. layer.set_enabled(sender.get_active())
  727. def new_layer_button_clicked(self, widget):
  728. self.show_layers()
  729. # Allocate an un-used layer name
  730. layer_number = len(self.layers)
  731. layer_name = "Layer %i" % layer_number
  732. while(self.layer_exists(layer_name)):
  733. layer_number += 1
  734. layer_name = "Layer %i" % layer_number
  735. # Create the layer
  736. layer = self.create_layer(layer_name, False)
  737. layer.set_size(self.awidth, self.aheight)
  738. # Save changes
  739. threading.Thread(target=self.save_image_data).start()
  740. self.on_layer_change(layer)
  741. def remove_layer_button_clicked(self, widget):
  742. layer_row = self.ui["layers_list"].get_selected_row()
  743. selected_index = 1
  744. new_layers = []
  745. for layer in self.layers:
  746. if(layer.selector_row != layer_row):
  747. new_layers += [layer]
  748. else:
  749. selected_index = self.layers.index(layer)
  750. self.ui["layers_list"].remove(layer_row)
  751. self.layers = new_layers
  752. # Select next layer
  753. self.select_layer(self.layers[selected_index -1])
  754. if (len(self.layers) == 1):
  755. self.ui["layers_reveal"].set_reveal_child(False)
  756. if(widget != None):
  757. # Only do this if the layer was actualy deleted by the user
  758. # and not by the undo-redo system for example
  759. # Save changes
  760. threading.Thread(target=self.save_image_data).start()
  761. self.on_layer_change(self.get_selected_layer())
  762. def get_selected_layer(self):
  763. layer_row = self.ui["layers_list"].get_selected_row()
  764. for layer in self.layers:
  765. if (layer.selector_row == layer_row):
  766. return layer
  767. def layer_exists(self, layer_name):
  768. for layer in self.layers:
  769. if(layer.name == layer_name):
  770. return True
  771. return False
  772. def show_layers(self):
  773. self.ui["layers_reveal"].set_reveal_child(True)
  774. def select_layer(self, layer):
  775. self.ui["layers_list"].select_row(layer.selector_row)
  776. self.layer_ui_activated(self.ui["layers_list"], layer.selector_row)
  777. def delete_all_editable_layers(self):
  778. count = len(self.layers) -1
  779. while(len(self.layers) != 1):
  780. self.select_layer(self.layers[1])
  781. self.remove_layer_button_clicked(None)
  782. def draw_ui_brush_circle(self, widget, context):
  783. drawing = self.ui["edit_layer_mask_button"].get_active()
  784. if(drawing):
  785. size = self.ui["mask_brush_size"].get_value()
  786. if(self.mousedown):
  787. context.set_source_rgb(255, 0, 0)
  788. else:
  789. context.set_source_rgb(255, 255, 255)
  790. context.arc(self.mousex, self.mousey, size/2.0, 0.0, 2 * numpy.pi)
  791. context.stroke()
  792. def mouse_coords_changed(self, widget, event):
  793. self.mousedown = False
  794. self.mousex, self.mousey = event.x, event.y
  795. widget.queue_draw()
  796. def mouse_down_coords_changed(self, widget, event):
  797. self.mousedown = True
  798. self.mousex, self.mousey = widget.translate_coordinates(self.ui["scroll_window"], event.x, event.y)
  799. widget.queue_draw()
  800. def brush_size_changed(self, sender):
  801. self.ui["scroll_window"].queue_draw()
  802. def layer_blend_mode_changed(self, sender):
  803. layer = self.get_selected_layer()
  804. if(layer.blend_mode != sender.get_active_text().lower()):
  805. layer.set_blending_mode(sender.get_active_text().lower())