__init__.py 37 KB

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