__init__.py 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  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
  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. ]
  80. for component in components:
  81. self.ui[component] = self.builder.get_object("%s_%s" % (self.id, component))
  82. self.menu_popover = self.ui["popovermenu"]
  83. # Set up tools
  84. self.tools = [
  85. Contrast.Contrast,
  86. Tonemap.Tonemap,
  87. Details.Details,
  88. Colours.Colours,
  89. HueEqualiser.HueEqualiser,
  90. BlackWhite.BlackWhite,
  91. Blur.Blur
  92. ]
  93. # Setup layers
  94. self.layers = []
  95. self.base_layer = self.create_layer("base", True)
  96. # Set the first tool to active
  97. # self.tools[0].tool_button.set_active(True)
  98. # self.ui["tools"].show_all()
  99. # Disable ui components by default
  100. self.ui["original_toggle"].set_sensitive(False)
  101. self.ui["export_image"].set_sensitive(False)
  102. # Setup editor variables
  103. self.is_editing = False
  104. self.has_loaded = False
  105. self.image_path = ""
  106. self.bit_depth = 8
  107. self.image = None
  108. self.image_is_dirty = True
  109. self.original_image = None
  110. self.pwidth = 0
  111. self.pheight = 0
  112. self.awidth = 0
  113. self.aheight = 0
  114. self.pimage = None
  115. self.poriginal = None
  116. self.change_occurred = False
  117. self.additional_change_occurred = False
  118. self.running_stack = False
  119. self.upper_peak_on = False
  120. self.lower_peak_on = False
  121. self.is_exporting = False
  122. self.is_zooming = False
  123. self.undo_stack = []
  124. self.undo_position = 0
  125. self.undoing = False
  126. self.current_layer_path = None
  127. self.mousex = 0
  128. self.mousey = 0
  129. self.mousedown = False
  130. self.layer_order = []
  131. self.pre_undo_layer_name = "base"
  132. self.pre_undo_editing = False
  133. self.pre_undo_erasing = False
  134. self.last_selected_layer = None
  135. self.was_editing_mask = False
  136. self.source_image = None
  137. self.is_scaling = False
  138. self.current_processing_layer_name = "base"
  139. self.current_processing_layer_index = 0
  140. self.update_image_task_id = ""
  141. # Setup Open Dialog
  142. self.ui["open_window"].set_transient_for(self.root)
  143. self.ui["open_window"].set_titlebar(self.ui["open_header"])
  144. # Connect UI signals
  145. self.ui["open_button"].connect("clicked", self.on_open_clicked)
  146. self.ui["preview"].connect("draw", self.on_preview_draw)
  147. self.ui["original_toggle"].connect("toggled", self.on_preview_toggled)
  148. self.ui["upper_peak_toggle"].connect("toggled", self.on_upper_peak_toggled)
  149. self.ui["lower_peak_toggle"].connect("toggled", self.on_lower_peak_toggled)
  150. self.ui["show_hist"].connect("toggled", self.toggle_hist)
  151. self.ui["export_image"].connect("clicked", self.on_export_clicked)
  152. self.ui["open_open_button"].connect("clicked", self.on_file_opened)
  153. self.ui["open_cancel_button"].connect("clicked", self.on_file_canceled)
  154. self.ui["zoom_toggle"].connect("toggled", self.on_zoom_toggled)
  155. self.ui["zoom"].connect("value-changed", self.on_zoom_changed)
  156. self.ui["undo"].connect("clicked", self.on_undo)
  157. self.ui["redo"].connect("clicked", self.on_redo)
  158. self.ui["reset"].connect("clicked", self.on_reset)
  159. self.ui["preview_eventbox"].connect('motion-notify-event', self.preview_dragged)
  160. self.ui["preview_eventbox"].connect('button-press-event', self.new_path)
  161. self.ui["mask_draw_toggle"].connect("toggled", self.mask_draw_toggled)
  162. self.ui["mask_erase_toggle"].connect("toggled", self.mask_erase_toggled)
  163. self.ui["new_layer"].connect("clicked", self.new_layer_button_clicked)
  164. self.ui["layers_list"].connect("row-activated", self.layer_ui_activated)
  165. self.ui["add_layer_button"].connect("clicked", self.new_layer_button_clicked)
  166. self.ui["remove_layer_button"].connect("clicked", self.remove_layer_button_clicked)
  167. self.ui["edit_layer_mask_button"].connect("toggled", self.edit_mask_toggled)
  168. self.ui["layer_opacity"].connect("value-changed", self.update_layer_opacity)
  169. self.ui["scroll_window"].connect_after("draw", self.draw_ui_brush_circle)
  170. self.ui["scroll_window"].connect_after('motion-notify-event', self.mouse_coords_changed)
  171. self.ui["mask_brush_size"].connect("value-changed", self.brush_size_changed)
  172. self.ui["layer_blend_mode"].connect("changed", self.layer_blend_mode_changed)
  173. def on_open_clicked(self, sender):
  174. self.ui["open_window"].show_all()
  175. def on_file_opened(self, sender, path=None):
  176. self.has_loaded = False
  177. self.image = None
  178. self.undo_position = 0
  179. self.undo_stack = []
  180. self.update_undo_state()
  181. self.ui["open_window"].hide()
  182. self.ui["control_reveal"].set_reveal_child(True)
  183. self.ui["control_reveal"].set_sensitive(True)
  184. self.ui["original_toggle"].set_sensitive(True)
  185. self.ui["export_image"].set_sensitive(True)
  186. self.ui["reset"].set_sensitive(True)
  187. self.ui["new_layer"].set_sensitive(True)
  188. self.ui["layers_reveal"].set_reveal_child(False)
  189. self.is_editing = True
  190. if(path == None):
  191. self.image_path = self.ui["open_chooser"].get_filename()
  192. else:
  193. self.image_path = path
  194. self.show_message("Loading Photo…", "Please wait while PhotoFiddle loads your photo", True)
  195. self.root.get_titlebar().set_subtitle("%s…" % (self.image_path))
  196. # Delete all layers, except the base
  197. for layer in self.layers:
  198. if(layer.name != "base"):
  199. self.ui["layers_list"].remove(layer.selector_row)
  200. self.layers = [self.base_layer,]
  201. self.select_layer(self.base_layer)
  202. w = (self.ui["scroll_window"].get_allocated_width() - 12) * self.ui["zoom"].get_value()
  203. h = (self.ui["scroll_window"].get_allocated_height() - 12) * self.ui["zoom"].get_value()
  204. thread = threading.Thread(target=self.open_image,
  205. args=(w, h))
  206. thread.start()
  207. def on_zoom_toggled(self, sender):
  208. state = sender.get_active()
  209. self.ui["zoom_reveal"].set_reveal_child(state)
  210. if(not state):
  211. self.ui["zoom"].set_value(1)
  212. def on_zoom_changed(self, sender):
  213. threading.Thread(target=self.zoom_delay).start()
  214. def zoom_delay(self):
  215. if(not self.is_zooming):
  216. self.is_zooming = True
  217. time.sleep(0.5)
  218. GLib.idle_add(self.on_preview_draw, None, None)
  219. self.is_zooming = False
  220. def on_file_canceled(self, sender):
  221. self.ui["open_window"].hide()
  222. def on_preview_draw(self, sender, arg):
  223. if(self.is_editing):
  224. w = (self.ui["scroll_window"].get_allocated_width() - 12) * self.ui["zoom"].get_value()
  225. h = (self.ui["scroll_window"].get_allocated_height() - 12) * self.ui["zoom"].get_value()
  226. if(self.pheight != h) or (self.pwidth != w):
  227. thread = threading.Thread(target=self.resize_preview,
  228. args=(w, h))
  229. thread.start()
  230. def on_preview_toggled(self, sender):
  231. if(sender.get_active()):
  232. threading.Thread(target=self.draw_hist, args=(self.original_image,)).start()
  233. self.show_original()
  234. else:
  235. threading.Thread(target=self.draw_hist, args=(self.image,)).start()
  236. self.show_current()
  237. def toggle_hist(self, sender):
  238. show = sender.get_active()
  239. self.ui["histogram_reveal"].set_reveal_child(show)
  240. def on_open(self, path):
  241. self.root.get_titlebar().set_subtitle("Raster Editor")
  242. if(path != None):
  243. # We have been sent here from another
  244. # activity, clear any existing profiles
  245. self.image_path = path
  246. dataPath = self.get_data_path()
  247. if(os.path.exists(dataPath)):
  248. os.unlink(dataPath)
  249. self.on_file_opened(None, path)
  250. def on_exit(self):
  251. if(self.is_exporting):
  252. return False
  253. else:
  254. fname = "/dev/shm/phf2-preview-%s.png" % getpass.getuser()
  255. if (os.path.exists(fname)):
  256. os.unlink(fname)
  257. self.root.get_titlebar().set_subtitle("")
  258. self.on_init()
  259. return True
  260. def show_original(self):
  261. self.ui["preview"].set_from_pixbuf(self.poriginal)
  262. if (not self.ui["original_toggle"].get_active()):
  263. self.ui["original_toggle"].set_active(True)
  264. def show_current(self):
  265. self.ui["preview"].set_from_pixbuf(self.pimage)
  266. if (self.ui["original_toggle"].get_active()):
  267. self.ui["original_toggle"].set_active(False)
  268. def on_layer_change(self, layer):
  269. print(layer, "changed")
  270. if(self.has_loaded):
  271. if(not self.change_occurred):
  272. self.change_occurred = True
  273. thread = threading.Thread(target=self.update_image, args=(False, layer))
  274. thread.start()
  275. else:
  276. self.additional_change_occurred = True
  277. def on_layer_mask_change(self, layer):
  278. if(self.has_loaded):
  279. if(not self.change_occurred):
  280. self.change_occurred = True
  281. threading.Thread(target=self.__on_layer_mask_change).start()
  282. self.save_image_data()
  283. def __on_layer_mask_change(self):
  284. image = self.get_selected_layer().layer_copy.copy()
  285. image = self.get_selected_layer().get_layer_mask_preview(image)
  286. self.image_is_dirty = True
  287. self.image = image
  288. self.update_preview()
  289. self.change_occurred = False
  290. self.image_is_dirty = False
  291. def on_lower_peak_toggled(self, sender):
  292. self.lower_peak_on = sender.get_active()
  293. if (self.lower_peak_on):
  294. thread = threading.Thread(target=self.process_peaks, args=(True,))
  295. thread.start()
  296. else:
  297. thread = threading.Thread(target=self.update_image, args=(True,))
  298. thread.start()
  299. def on_upper_peak_toggled(self, sender):
  300. self.upper_peak_on = sender.get_active()
  301. if (self.lower_peak_on):
  302. thread = threading.Thread(target=self.process_peaks, args=(True,))
  303. thread.start()
  304. else:
  305. thread = threading.Thread(target=self.update_image, args=(True,))
  306. thread.start()
  307. def image_opened(self):
  308. self.root.get_titlebar().set_subtitle("%s (%s Bit)" % (self.image_path, self.bit_depth))
  309. self.hide_message()
  310. def on_export_clicked(self, sender):
  311. Export.ExportDialog(self.root, self.builder, self.awidth, self.aheight, self.get_export_image, self.on_export_complete, self.image_path)
  312. def on_export_complete(self, filename):
  313. self.is_exporting = False
  314. self.on_export_state_change()
  315. self.show_message("Export Complete!", "Your photo has been exported to '%s'" % filename)
  316. def on_export_state_change(self):
  317. self.ui["control_reveal"].set_sensitive(not self.is_exporting)
  318. self.ui["export_image"].set_sensitive(not self.is_exporting)
  319. self.ui["open_button"].set_sensitive(not self.is_exporting)
  320. def on_export_started(self):
  321. self.is_exporting = True
  322. self.on_export_state_change()
  323. def update_undo_state(self):
  324. self.ui["undo"].set_sensitive((self.undo_position > 0) and (not self.is_working))
  325. self.ui["redo"].set_sensitive((len(self.undo_stack)-1 > self.undo_position) and (not self.is_working))
  326. def on_undo(self, sender):
  327. self.pre_undo_layer_name = self.get_selected_layer().name
  328. self.pre_undo_erasing = self.ui["mask_erase_toggle"].get_active()
  329. self.pre_undo_editing = self.ui["edit_layer_mask_button"].get_active()
  330. self.undo_position -= 1
  331. self.update_undo_state()
  332. self.update_from_undo_stack(self.undo_stack[self.undo_position])
  333. def on_redo(self, sender):
  334. self.pre_undo_layer_name = self.get_selected_layer().name
  335. self.pre_undo_erasing = self.ui["mask_erase_toggle"].get_active()
  336. self.pre_undo_editing = self.ui["edit_layer_mask_button"].get_active()
  337. self.undo_position += 1
  338. self.update_undo_state()
  339. self.update_from_undo_stack(self.undo_stack[self.undo_position])
  340. def on_reset(self, sender):
  341. for layer in self.layers:
  342. layer.reset_tools()
  343. ## Background Tasks ##
  344. def open_image(self, w, h):
  345. self.load_image_data()
  346. try:
  347. # Get Bit Depth
  348. imdepth = cv2.imread(self.image_path, 2 | 1)
  349. self.bit_depth = str(imdepth.dtype).replace("uint", "").replace("float", "")
  350. # Read the 8Bit Preview
  351. self.source_image = cv2.imread(self.image_path).astype(numpy.uint8)
  352. # Load the image
  353. self.resize_preview(w, h)
  354. except:
  355. pass
  356. while(type(self.image) != numpy.ndarray):
  357. time.sleep(1)
  358. self.pwidth = 0
  359. self.pheight = 0
  360. GLib.idle_add(self.image_opened)
  361. time.sleep(1)
  362. self.has_loaded = True
  363. def resize_preview(self, w, h):
  364. print(w, h)
  365. if(type(self.source_image) == numpy.ndarray):
  366. # Inhibit undo stack to prevent
  367. # Adding an action on resize
  368. self.undoing = True
  369. self.original_image = self.source_image.copy()
  370. height, width = self.original_image.shape[:2]
  371. self.aheight = height
  372. self.awidth = width
  373. self.pheight = h
  374. self.pwidth = w
  375. # Get fitting size
  376. ratio = float(w)/width
  377. if(height*ratio > h):
  378. ratio = float(h)/height
  379. nw = width * ratio
  380. nh = height * ratio
  381. # Do quick ui resize
  382. # if(self.image != None):
  383. # # If we have an edited version, show that
  384. # image = cv2.resize(self.image, (int(nw), int(nh)), interpolation=cv2.INTER_NEAREST)
  385. # self.pimage = self.pimage = self.create_pixbuf(image)
  386. # GLib.idle_add(self.show_current)
  387. if(type(self.pimage) == numpy.ndarray and not self.is_scaling):
  388. self.is_scaling = True
  389. # If we have an edited version, show that
  390. image = self.pimage.scale_simple(int(nw), int(nh), GdkPixbuf.InterpType.NEAREST)
  391. self.pimage = image
  392. GLib.idle_add(self.show_current)
  393. self.is_scaling = False
  394. self.poriginal = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.image_path,
  395. int(nw), int(nh), True)
  396. if(type(self.pimage) != numpy.ndarray):
  397. # Otherwise show the original
  398. GLib.idle_add(self.show_original)
  399. # Resize OPENCV Copy
  400. self.original_image = cv2.resize(self.original_image, (int(nw), int(nh)), interpolation = cv2.INTER_AREA)
  401. self.image_is_dirty = True
  402. self.image = self.original_image.copy()
  403. # Update image
  404. if (not self.change_occurred):
  405. self.change_occurred = True
  406. self.update_image()
  407. else:
  408. self.additional_change_occurred = True
  409. def update_image(self, immediate=False, changed_layer=None):
  410. if(not immediate):
  411. time.sleep(0.5)
  412. self.additional_change_occurred = False
  413. self.is_working = True
  414. GLib.idle_add(self.update_undo_state)
  415. GLib.idle_add(self.start_work)
  416. image = numpy.copy(self.original_image)
  417. rst = time.time()
  418. task_id = uuid.uuid4()
  419. self.update_image_task_id = task_id
  420. image = self.run_stack(image, changed_layer=changed_layer)
  421. if(task_id == self.update_image_task_id):
  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 == None")
  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. if(self.additional_change_occurred):
  435. self.update_image()
  436. else:
  437. self.save_image_data()
  438. threading.Thread(target=self.draw_hist, args=(self.image,)).start()
  439. self.process_peaks()
  440. self.update_preview()
  441. GLib.idle_add(self.stop_work)
  442. self.is_working = False
  443. GLib.idle_add(self.update_undo_state)
  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. loadDefaults = False
  562. except:
  563. GLib.idle_add(self.show_message,"Unable to load previous edits…",
  564. "The edit file for this photo is corrupted and could not be loaded.")
  565. if(loadDefaults):
  566. for layer in self.layers:
  567. for tool in layer.tools:
  568. GLib.idle_add(tool.reset)
  569. layerDict = {}
  570. layerOrder = []
  571. for layer in self.layers:
  572. layerDict[layer.name] = layer.get_layer_dict()
  573. layerOrder.append(layer.name)
  574. self.undo_stack = [{"layers": layerDict, "layer-order": layerOrder}, ]
  575. self.undo_position = 0
  576. GLib.idle_add(self.update_undo_state)
  577. #time.sleep(2)
  578. self.undoing = False
  579. def create_layer_with_data(self, layer, data):
  580. if (layer == "base"):
  581. self.base_layer.set_from_layer_dict(data)
  582. else:
  583. GLib.idle_add(self.show_layers)
  584. ilayer = self.create_layer(layer, False)
  585. ilayer.set_from_layer_dict(data)
  586. def update_from_undo_stack(self, data):
  587. self.undoing = True
  588. self.delete_all_editable_layers()
  589. print(data["layer-order"])
  590. for layer_name in data["layer-order"]:
  591. if (layer_name == "base"):
  592. self.base_layer.set_from_layer_dict(data["layers"][layer_name])
  593. else:
  594. ilayer = self.create_layer(layer_name, False, layer_name == self.pre_undo_layer_name, True)
  595. ilayer.set_from_layer_dict(data["layers"][layer_name])
  596. self.show_layers()
  597. def get_export_image(self, w, h):
  598. GLib.idle_add(self.on_export_started)
  599. GLib.idle_add(self.show_message, "Exporting Photo", "Please wait…", True, True)
  600. img = cv2.imread(self.image_path, 2 | 1)
  601. img = cv2.resize(img, (int(w), int(h)), interpolation=cv2.INTER_AREA)
  602. img = self.run_stack(img, self.export_progress_callback)
  603. GLib.idle_add(self.show_message, "Exporting Photo", "Saving to filesystem…", True, True)
  604. GLib.idle_add(self.update_message_progress, 1, 1)
  605. return img
  606. def export_progress_callback(self, name, count, current):
  607. layer_name = self.current_processing_layer_name
  608. if(layer_name == "base"):
  609. layer_name = "Base Layer"
  610. GLib.idle_add(self.show_message, "Exporting Photo", "%s: %s" % (layer_name,
  611. name), True, True)
  612. GLib.idle_add(self.update_message_progress, current+(self.current_processing_layer_index*count),
  613. count*len(self.layers))
  614. ## Layers Stuff ##
  615. def preview_dragged(self, widget, event):
  616. x, y = widget.translate_coordinates(self.ui["preview"], event.x, event.y)
  617. draw = self.ui["mask_draw_toggle"].get_active()
  618. erase = self.ui["mask_erase_toggle"].get_active()
  619. layer = self.get_selected_layer()
  620. if((draw or erase) and layer.editable and self.current_layer_path):
  621. if (x < 0.0):
  622. x = 0
  623. if (y < 0.0):
  624. y = 0
  625. pwidth = self.pimage.get_width()
  626. pheight = self.pimage.get_height()
  627. if (x > pwidth):
  628. x = pwidth
  629. if (y > pheight):
  630. y = pheight
  631. print(x, y)
  632. fill = (0, 0, 255)
  633. if(erase):
  634. fill = (255, 0, 0)
  635. self.draw_path(x, y, pheight, pwidth, fill)
  636. self.on_layer_mask_change(layer)
  637. self.mouse_down_coords_changed(widget, event)
  638. return True
  639. def draw_path(self, x, y, pheight, pwidth, fill):
  640. preview = self.current_layer_path.add_point(int(x), int(y), (pheight, pwidth, 3), fill)
  641. # Bits per pixel
  642. bpp = float(str(self.image.dtype).replace("uint", "").replace("float", ""))
  643. # Pixel value range
  644. np = float(2 ** bpp - 1)
  645. self.image[preview == 255] = np
  646. cv2.imwrite("/dev/shm/phf2-preview-%s-drag.png" % getpass.getuser(), self.image)
  647. temppbuf = GdkPixbuf.Pixbuf.new_from_file("/dev/shm/phf2-preview-%s-drag.png" % getpass.getuser())
  648. self.ui["preview"].set_from_pixbuf(temppbuf)
  649. def new_path(self, widget, event):
  650. draw = self.ui["mask_draw_toggle"].get_active()
  651. erase = self.ui["mask_erase_toggle"].get_active()
  652. layer = self.get_selected_layer()
  653. if((draw or erase) and layer.editable):
  654. print(self.pimage.get_width(), self.pimage.get_height())
  655. width = self.pimage.get_width()
  656. size = self.ui["mask_brush_size"].get_value()
  657. feather = self.ui["mask_brush_feather"].get_value()
  658. self.current_layer_path = layer.mask.get_new_path(size, feather, float(self.awidth)/float(width), draw)
  659. def mask_draw_toggled(self, widget):
  660. self.ui["mask_erase_toggle"].set_active(not widget.get_active())
  661. def mask_erase_toggled(self, widget):
  662. self.ui["mask_draw_toggle"].set_active(not widget.get_active())
  663. self.ui["mask_brush_feather_scale"].set_sensitive(not widget.get_active())
  664. def edit_mask_toggled(self, widget):
  665. self.ui["layer_mask_reveal"].set_reveal_child(widget.get_active())
  666. self.ui["mask_draw_toggle"].set_active(widget.get_active())
  667. self.ui["mask_erase_toggle"].set_active(False)
  668. self.ui["scroll_window"].queue_draw()
  669. if (widget.get_active()):
  670. thread = threading.Thread(target=self.process_mask, args=(True,))
  671. thread.start()
  672. elif(self.was_editing_mask):
  673. self.on_layer_change(self.get_selected_layer())
  674. self.was_editing_mask = widget.get_active()
  675. def update_layer_opacity(self, sender):
  676. layer = self.get_selected_layer()
  677. if(layer.opacity != sender.get_value()):
  678. layer.set_opacity(sender.get_value())
  679. def create_layer(self, layer_name, is_base, select = False, set_pre_undo_draw_state = False):
  680. layer = Layer.Layer(is_base, layer_name, self.on_layer_change)
  681. for tool in self.tools:
  682. tool_instance = tool()
  683. layer.add_tool(tool_instance)
  684. self.layers += [layer,]
  685. GLib.idle_add(self.create_layer_ui, layer, select, set_pre_undo_draw_state)
  686. return layer
  687. def create_layer_ui(self, layer, select, set_pre_undo_draw_state = False):
  688. layer_box = Gtk.HBox()
  689. layer_box.set_hexpand(False)
  690. layer_box.set_halign(Gtk.Align.START)
  691. layer_toggle = Gtk.CheckButton()
  692. layer_toggle.set_sensitive(layer.editable)
  693. layer_toggle.set_active(layer.enabled)
  694. layer_toggle.set_hexpand(False)
  695. layer_toggle.set_halign(Gtk.Align.START)
  696. layer_toggle.set_margin_right(8)
  697. layer_toggle.set_margin_left(8)
  698. layer_toggle.set_margin_top(4)
  699. layer_toggle.set_margin_bottom(4)
  700. layer_toggle.connect("toggled", self.toggle_layer, layer)
  701. layer_label = Gtk.Label()
  702. layer_label.set_label(layer.name)
  703. if(layer.name == "base"):
  704. layer_label.set_label("Base Layer")
  705. layer_label.set_hexpand(True)
  706. layer_label.set_halign(Gtk.Align.FILL)
  707. layer_label.set_margin_top(4)
  708. layer_label.set_margin_bottom(4)
  709. layer_box.add(layer_toggle)
  710. layer_box.add(layer_label)
  711. layer.show_all()
  712. layer_box.show_all()
  713. self.ui["layers_list"].add(layer_box)
  714. layer.selector_row = layer_box.get_parent()
  715. self.ui["tool_box_stack"].add(layer.tool_box)
  716. self.ui["tool_stack"].add(layer.tool_stack)
  717. if(select):
  718. self.select_layer(layer)
  719. if(set_pre_undo_draw_state) and self.get_selected_layer().editable:
  720. print(self.pre_undo_editing, self.pre_undo_erasing)
  721. self.ui["mask_erase_toggle"].set_active(self.pre_undo_erasing)
  722. self.ui["edit_layer_mask_button"].set_active(self.pre_undo_editing)
  723. def layer_ui_activated(self, widget, row):
  724. layer_index = row.get_index()
  725. self.ui["tool_stack"].set_visible_child(self.layers[layer_index].tool_stack)
  726. self.ui["tool_box_stack"].set_visible_child(self.layers[layer_index].tool_box)
  727. self.ui["remove_layer_button"].set_sensitive(self.layers[layer_index].editable)
  728. self.ui["edit_layer_mask_button"].set_sensitive(self.layers[layer_index].editable)
  729. self.ui["layer_opacity_scale"].set_sensitive(self.layers[layer_index].editable)
  730. self.ui["layer_blend_mode"].set_sensitive(self.layers[layer_index].editable)
  731. self.ui["layer_blend_mode"].set_active(["additive", "overlay"].index(self.layers[layer_index].blend_mode))
  732. self.ui["layer_opacity"].set_value(self.layers[layer_index].opacity)
  733. if(self.ui["edit_layer_mask_button"].get_active()):
  734. self.ui["edit_layer_mask_button"].set_active(self.layers[layer_index].editable)
  735. self.on_layer_change(self.last_selected_layer)
  736. self.last_selected_layer = self.get_selected_layer()
  737. def toggle_layer(self, sender, layer):
  738. layer.set_enabled(sender.get_active())
  739. def new_layer_button_clicked(self, widget):
  740. self.show_layers()
  741. # Allocate an un-used layer name
  742. layer_number = len(self.layers)
  743. layer_name = "Layer %i" % layer_number
  744. while(self.layer_exists(layer_name)):
  745. layer_number += 1
  746. layer_name = "Layer %i" % layer_number
  747. # Create the layer
  748. layer = self.create_layer(layer_name, False)
  749. layer.set_size(self.awidth, self.aheight)
  750. # Save changes
  751. threading.Thread(target=self.save_image_data).start()
  752. self.on_layer_change(layer)
  753. def remove_layer_button_clicked(self, widget):
  754. layer_row = self.ui["layers_list"].get_selected_row()
  755. selected_index = 1
  756. new_layers = []
  757. for layer in self.layers:
  758. if(layer.selector_row != layer_row):
  759. new_layers += [layer]
  760. else:
  761. selected_index = self.layers.index(layer)
  762. self.ui["layers_list"].remove(layer_row)
  763. self.layers = new_layers
  764. # Select next layer
  765. self.select_layer(self.layers[selected_index -1])
  766. if (len(self.layers) == 1):
  767. self.ui["layers_reveal"].set_reveal_child(False)
  768. if(widget != None):
  769. # Only do this if the layer was actualy deleted by the user
  770. # and not by the undo-redo system for example
  771. # Save changes
  772. threading.Thread(target=self.save_image_data).start()
  773. self.on_layer_change(self.get_selected_layer())
  774. def get_selected_layer(self):
  775. layer_row = self.ui["layers_list"].get_selected_row()
  776. for layer in self.layers:
  777. if (layer.selector_row == layer_row):
  778. return layer
  779. def layer_exists(self, layer_name):
  780. for layer in self.layers:
  781. if(layer.name == layer_name):
  782. return True
  783. return False
  784. def show_layers(self):
  785. self.ui["layers_reveal"].set_reveal_child(True)
  786. def select_layer(self, layer):
  787. self.ui["layers_list"].select_row(layer.selector_row)
  788. self.layer_ui_activated(self.ui["layers_list"], layer.selector_row)
  789. def delete_all_editable_layers(self):
  790. count = len(self.layers) -1
  791. while(len(self.layers) != 1):
  792. self.select_layer(self.layers[1])
  793. self.remove_layer_button_clicked(None)
  794. def draw_ui_brush_circle(self, widget, context):
  795. drawing = self.ui["edit_layer_mask_button"].get_active()
  796. if(drawing):
  797. size = self.ui["mask_brush_size"].get_value()
  798. if(self.mousedown):
  799. context.set_source_rgb(255, 0, 0)
  800. else:
  801. context.set_source_rgb(255, 255, 255)
  802. context.arc(self.mousex, self.mousey, size/2.0, 0.0, 2 * numpy.pi)
  803. context.stroke()
  804. def mouse_coords_changed(self, widget, event):
  805. self.mousedown = False
  806. self.mousex, self.mousey = event.x, event.y
  807. widget.queue_draw()
  808. def mouse_down_coords_changed(self, widget, event):
  809. self.mousedown = True
  810. self.mousex, self.mousey = widget.translate_coordinates(self.ui["scroll_window"], event.x, event.y)
  811. widget.queue_draw()
  812. def brush_size_changed(self, sender):
  813. self.ui["scroll_window"].queue_draw()
  814. def layer_blend_mode_changed(self, sender):
  815. layer = self.get_selected_layer()
  816. if(layer.blend_mode != sender.get_active_text().lower()):
  817. layer.set_blending_mode(sender.get_active_text().lower())