__init__.py 29 KB

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