__init__.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import getpass
  2. import glob
  3. import os
  4. import subprocess
  5. import shutil
  6. import threading
  7. import cv2
  8. import Activity
  9. from gi.repository import GLib, Gtk, GdkPixbuf, Pango
  10. import Export
  11. class LightStack(Activity.Activity):
  12. def on_init(self):
  13. self.id = "LightStack"
  14. self.name = "Light Stack"
  15. self.subtitle = "Stack Many Images to Make Light Trails"
  16. UI_FILE = "ui/LightStack_Activity.glade"
  17. self.builder.add_from_file(UI_FILE)
  18. self.widget = self.builder.get_object("LightStack_main")
  19. self.stack.add_titled(self.widget, self.id, self.name)
  20. self.header_widget = self.builder.get_object("LightStack_header")
  21. self.header_stack.add_titled(self.header_widget, self.id, self.name)
  22. # Get all UI Components
  23. self.ui = {}
  24. components = [
  25. "open_window",
  26. "open_chooser",
  27. "preview_button",
  28. "open_header",
  29. "open_open_button",
  30. "open_cancel_button",
  31. "images",
  32. "preview",
  33. "export_image",
  34. "open_pf2",
  35. "add_button",
  36. "remove_button",
  37. "scroll_window",
  38. "popovermenu"
  39. ]
  40. self.paths = []
  41. self.exporting = False
  42. for component in components:
  43. self.ui[component] = self.builder.get_object("%s_%s" % (self.id, component))
  44. self.menu_popover = self.ui["popovermenu"]
  45. self.ui["open_window"].set_transient_for(self.root)
  46. self.ui["open_window"].set_titlebar(self.ui["open_header"])
  47. # Connect Siginals
  48. self.ui["add_button"].connect("clicked", self.on_add_clicked)
  49. self.ui["remove_button"].connect("clicked", self.on_remove_clicked)
  50. self.ui["open_open_button"].connect("clicked", self.on_file_opened)
  51. self.ui["open_cancel_button"].connect("clicked", self.on_file_canceled)
  52. self.ui["preview_button"].connect("clicked", self.on_preview_clicked)
  53. self.ui["export_image"].connect("clicked", self.export_clicked)
  54. self.ui["open_pf2"].connect("clicked", self.export_pf2_clicked)
  55. self.update_enabled()
  56. def on_add_clicked(self, sender):
  57. self.ui["open_window"].show_all()
  58. def on_file_canceled(self, sender):
  59. self.ui["open_window"].hide()
  60. def on_file_opened(self, sender):
  61. self.ui["open_window"].hide()
  62. paths = self.ui["open_chooser"].get_filenames()
  63. self.show_message("Loading Images", "Please wait while PhotoFiddle loads your images…", True, True)
  64. threading.Thread(target=self.add_images, args=(paths,)).start()
  65. def update_enabled(self):
  66. enabled = len(self.get_paths()) > 0
  67. self.ui["export_image"].set_sensitive(enabled)
  68. self.ui["open_pf2"].set_sensitive(enabled)
  69. self.ui["preview_button"].set_sensitive(enabled)
  70. def add_images(self, paths):
  71. c = 0
  72. for path in paths:
  73. # Create box
  74. box = Gtk.HBox()
  75. box.set_spacing(6)
  76. im = Gtk.Image()
  77. pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, 128, 128, True)
  78. im.set_from_pixbuf(pb)
  79. box.add(im)
  80. label = Gtk.Label()
  81. label.set_ellipsize(Pango.EllipsizeMode.START)
  82. label.set_text(path)
  83. box.add(label)
  84. box.show_all()
  85. c += 1
  86. GLib.idle_add(self.add_image_row, box, c, len(paths))
  87. GLib.idle_add(self.hide_message)
  88. def add_image_row(self, row, count, length):
  89. self.ui["images"].add(row)
  90. self.update_enabled()
  91. self.update_message_progress(count, length)
  92. def on_remove_clicked(self, sender):
  93. items = self.ui["images"].get_selected_rows()
  94. for item in items:
  95. item.destroy()
  96. self.update_enabled()
  97. def on_open(self, path):
  98. self.root.get_titlebar().set_subtitle("Light Stack")
  99. pass
  100. def on_exit(self):
  101. if(not self.exporting):
  102. self.root.get_titlebar().set_subtitle("")
  103. self.on_init()
  104. return True
  105. return False
  106. def on_preview_clicked(self, sender):
  107. self.show_message("Generating Preview…", "PhotoFiddle is preparing to generate a preview", True, True)
  108. paths = self.get_paths()
  109. w = self.ui["scroll_window"].get_allocated_width() - 12
  110. h = self.ui["scroll_window"].get_allocated_height() - 12
  111. threading.Thread(target=self.do_preview, args=(paths, w, h)).start()
  112. def do_stack(self, images):
  113. GLib.idle_add(self.show_message, "Light Stacking…", "PhotoFiddle is performing a light stack…", True, True)
  114. GLib.idle_add(self.update_message_progress, 0,1)
  115. im = cv2.imread(images[0], 2 | 1)
  116. bpp = int(str(im.dtype).replace("uint", "").replace("float", ""))
  117. np = float(2 ** bpp - 1)
  118. c = 1
  119. GLib.idle_add(self.update_message_progress, c, len(images))
  120. try:
  121. for f in images[1:]:
  122. layer= cv2.imread(f, 2 | 1)
  123. im[layer > im] = layer[layer > im]
  124. im[im < 0.0] = 0.0
  125. im[im > np] = np
  126. c += 1
  127. GLib.idle_add(self.update_message_progress, c, len(images))
  128. GLib.idle_add(self.hide_message)
  129. return im
  130. except:
  131. GLib.idle_add(self.show_message, "Stack Failed.", "All images in the stack must be the same size.", False, False)
  132. raise Exception()
  133. def get_paths(self):
  134. paths = []
  135. for item in self.ui["images"].get_children():
  136. paths += [item.get_children()[0].get_children()[1].get_text(),]
  137. return paths
  138. def do_preview(self, paths, w, h):
  139. if (not os.path.exists("/tmp/lightstack-%s" % getpass.getuser())):
  140. os.mkdir("/tmp/lightstack-%s" % getpass.getuser())
  141. length = len(paths)*3
  142. count = 0
  143. images = []
  144. for path in paths:
  145. GLib.idle_add(self.update_message_progress, count, length)
  146. im = cv2.imread(path, 2 | 1)
  147. height, width = im.shape[:2]
  148. # Get fitting size
  149. ratio = float(w) / width
  150. if (height * ratio > h):
  151. ratio = float(h) / height
  152. nw = width * ratio
  153. nh = height * ratio
  154. count += 1
  155. GLib.idle_add(self.update_message_progress, count, length)
  156. im = cv2.resize(im, (int(nw), int(nh)), interpolation=cv2.INTER_AREA)
  157. count += 1
  158. GLib.idle_add(self.update_message_progress, count, length)
  159. cv2.imwrite("/tmp/lightstack-%s/IN%i.tiff" % (getpass.getuser(),count), im)
  160. images += ["/tmp/lightstack-%s/IN%i.tiff" % (getpass.getuser(),count),]
  161. count += 1
  162. im = self.do_stack(images)
  163. cv2.imwrite("/tmp/lightstack-preview-%s.tiff" % getpass.getuser(), im)
  164. pb = GdkPixbuf.Pixbuf.new_from_file("/tmp/lightstack-preview-%s.tiff" % getpass.getuser())
  165. shutil.rmtree("/tmp/lightstack-%s" % getpass.getuser())
  166. GLib.idle_add(self.update_preview, pb)
  167. def update_preview(self, pb):
  168. self.ui["preview"].set_from_pixbuf(pb)
  169. def get_export_image(self, w, h):
  170. GLib.idle_add(self.export_started)
  171. im = self.do_stack(self.paths)
  172. GLib.idle_add(self.show_message, "Exporting…", "PhotoFiddle is exporting the light stack…", True, False)
  173. return im
  174. def export_started(self):
  175. self.exporting = True
  176. self.export_state_changed()
  177. def export_complete(self, path):
  178. self.exporting = False
  179. self.export_state_changed()
  180. self.show_message("Export Complete!", "Your photo has been exported to '%s'" % path)
  181. def export_state_changed(self):
  182. self.ui["export_image"].set_sensitive(not self.exporting)
  183. self.ui["open_pf2"].set_sensitive(not self.exporting)
  184. self.ui["preview_button"].set_sensitive(not self.exporting)
  185. def export_clicked(self, sender):
  186. self.paths = self.get_paths()
  187. height, width = cv2.imread(self.paths[0]).shape[:2]
  188. Export.ExportDialog(self.root, self.builder, width, height, self.get_export_image,
  189. self.export_complete, self.paths[0])
  190. def export_pf2_clicked(self, sender):
  191. GLib.idle_add(self.export_started)
  192. self.paths = self.get_paths()
  193. threading.Thread(target=self.do_export_pf2).start()
  194. def do_export_pf2(self):
  195. export_path = "".join(self.paths[0].split(".")[:-1])
  196. export_path += "_LightStack.tiff"
  197. im = self.do_stack(self.paths)
  198. GLib.idle_add(self.show_message, "Sending to Editor", "Exporting photo to the editor…", True)
  199. cv2.imwrite(export_path, im)
  200. GLib.idle_add(self.export_pf2_complete, export_path)
  201. def export_pf2_complete(self, path):
  202. self.exporting = False
  203. self.export_state_changed()
  204. self.switch_activity("PF2", path)