__init__.py 38 KB

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