class Photobooth: """The main class. It contains all the logic for the photobooth. """ def __init__(self, display_size, picture_basename, picture_size, pose_time, display_time, trigger_channel, shutdown_channel, lamp_channel, idle_slideshow, slideshow_display_time): self.display = GuiModule('Photobooth', display_size) self.pictures = PictureList(picture_basename) self.camera = CameraModule(picture_size) self.pic_size = picture_size self.pose_time = pose_time self.display_time = display_time self.trigger_channel = trigger_channel self.shutdown_channel = shutdown_channel self.lamp_channel = lamp_channel self.idle_slideshow = idle_slideshow if self.idle_slideshow: self.slideshow_display_time = slideshow_display_time self.slideshow = Slideshow( display_size, display_time, os.path.dirname(os.path.realpath(picture_basename))) input_channels = [trigger_channel, shutdown_channel] output_channels = [lamp_channel] self.gpio = GPIO(self.handle_gpio, input_channels, output_channels) def teardown(self): self.display.clear() self.display.show_message("Shutting down...") self.display.apply() self.gpio.set_output(self.lamp_channel, 0) sleep(0.5) self.display.teardown() self.gpio.teardown() exit(0) def _run_plain(self): while True: self.camera.set_idle() # Display default message self.display.clear() self.display.show_message("Hit the button!") self.display.apply() # Wait for an event and handle it event = self.display.wait_for_event() self.handle_event(event) def _run_slideshow(self): while True: self.camera.set_idle() self.slideshow.display_next("Hit the button!") tic = clock() while clock() - tic < self.slideshow_display_time: self.check_and_handle_events() def run(self): while True: try: # Enable lamp self.gpio.set_output(self.lamp_channel, 1) # Select idle screen type if self.idle_slideshow: self._run_slideshow() else: self._run_plain() # Catch exceptions and display message except CameraException as e: self.handle_exception(e.message) # Do not catch KeyboardInterrupt and SystemExit except (KeyboardInterrupt, SystemExit): raise except Exception as e: print('SERIOUS ERROR: ' + repr(e)) self.handle_exception("SERIOUS ERROR!") def check_and_handle_events(self): r, e = self.display.check_for_event() while r: self.handle_event(e) r, e = self.display.check_for_event() def handle_gpio(self, channel): if channel in [self.trigger_channel, self.shutdown_channel]: self.display.trigger_event(channel) def handle_event(self, event): if event.type == 0: self.teardown() elif event.type == 1: self.handle_keypress(event.value) elif event.type == 2: self.handle_mousebutton(event.value[0], event.value[1]) elif event.type == 3: self.handle_gpio_event(event.value) def handle_keypress(self, key): """Implements the actions for the different keypress events""" # Exit the application if key == ord('q'): self.teardown() # Take pictures elif key == ord('c'): self.take_picture() def handle_mousebutton(self, key, pos): """Implements the actions for the different mousebutton events""" # Take a picture if key == 1: self.take_picture() def handle_gpio_event(self, channel): """Implements the actions taken for a GPIO event""" if channel == self.trigger_channel: self.take_picture() elif channel == self.shutdown_channel: self.teardown() def handle_exception(self, msg): """Displays an error message and returns""" self.display.clear() print("Error: " + msg) self.display.show_message("ERROR:\n\n" + msg) self.display.apply() sleep(3) def assemble_pictures(self, input_filenames): """Assembles four pictures into a 2x2 grid It assumes, all original pictures have the same aspect ratio as the resulting image. For the thumbnail sizes we have: h = (H - 2 * a - 2 * b) / 2 w = (W - 2 * a - 2 * b) / 2 W |---------------------------------------| --- +---+-------------+---+-------------+---+ --- | | | | a | | +-------------+ +-------------+ | --- | | | | | | | | | | | 0 | | 1 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- H | | | | 2*b | | +-------------+ +-------------+ | --- | | | | | | | | | | | 2 | | 3 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- | | | | a --- +---+-------------+---+-------------+---+ --- |---|-------------|---|-------------|---| a w 2*b w a """ # Thumbnail size of pictures outer_border = 50 inner_border = 20 thumb_box = (int(self.pic_size[0] / 2), int(self.pic_size[1] / 2)) thumb_size = (thumb_box[0] - outer_border - inner_border, thumb_box[1] - outer_border - inner_border) # Create output image with white background output_image = Image.new('RGB', self.pic_size, (255, 255, 255)) # Image 0 img = Image.open(input_filenames[0]) img.thumbnail(thumb_size) offset = (thumb_box[0] - inner_border - img.size[0], thumb_box[1] - inner_border - img.size[1]) output_image.paste(img, offset) # Image 1 img = Image.open(input_filenames[1]) img.thumbnail(thumb_size) offset = (thumb_box[0] + inner_border, thumb_box[1] - inner_border - img.size[1]) output_image.paste(img, offset) # Image 2 img = Image.open(input_filenames[2]) img.thumbnail(thumb_size) offset = (thumb_box[0] - inner_border - img.size[0], thumb_box[1] + inner_border) output_image.paste(img, offset) # Image 3 img = Image.open(input_filenames[3]) img.thumbnail(thumb_size) offset = (thumb_box[0] + inner_border, thumb_box[1] + inner_border) output_image.paste(img, offset) # Save assembled image output_filename = self.pictures.get_next() output_image.save(output_filename, "JPEG") return output_filename def show_counter(self, seconds): if self.camera.has_preview(): tic = clock() toc = clock() - tic while toc < seconds: self.display.clear() self.camera.take_preview("/tmp/photobooth_preview.jpg") self.display.show_picture("/tmp/photobooth_preview.jpg", flip=True) self.display.show_message(str(seconds - int(toc))) self.display.apply() # Limit progress to 1 "second" per preview (e.g., too slow on Raspi 1) toc = min(toc + 1, clock() - tic) else: for i in range(seconds): self.display.clear() self.display.show_message(str(seconds - i)) self.display.apply() sleep(1) def take_picture(self): """Implements the picture taking routine""" # Disable lamp self.gpio.set_output(self.lamp_channel, 0) # Show pose message self.display.clear() self.display.show_message("POSE!\n\nTaking four pictures...") self.display.apply() sleep(2) # Extract display and image sizes size = self.display.get_size() outsize = (int(size[0] / 2), int(size[1] / 2)) # Take pictures filenames = [i for i in range(4)] for x in range(4): # Countdown self.show_counter(self.pose_time) # Try each picture up to 3 times remaining_attempts = 3 while remaining_attempts > 0: remaining_attempts = remaining_attempts - 1 self.display.clear() self.display.show_message("S M I L E !!!\n\n" + str(x + 1) + " of 4") self.display.apply() tic = clock() try: filenames[x] = self.camera.take_picture( "/tmp/photobooth_%02d.jpg" % x) remaining_attempts = 0 except CameraException as e: # On recoverable errors: display message and retry if e.recoverable: if remaining_attempts > 0: self.display.clear() self.display.show_message(e.message) self.display.apply() sleep(5) else: raise CameraException( "Giving up! Please start over!", False) else: raise e # Measure used time and sleep a second if too fast toc = clock() - tic if toc < 1.0: sleep(1.0 - toc) # Show 'Wait' self.display.clear() self.display.show_message("Please wait!\n\nProcessing...") self.display.apply() # Assemble them outfile = self.assemble_pictures(filenames) # Show pictures for 10 seconds conn = cups.Connection() printers = 'Canon' #conn.getPrinters() printer_name = 'Canon' #printers.keys()[0] cups.setUser('pi') conn.printFile(printer_name, outfile, "Photo Booth", {}) self.display.clear() self.display.show_picture(outfile, size, (0, 0)) self.display.apply() sleep(self.display_time) # Reenable lamp self.gpio.set_output(self.lamp_channel, 1)
class Photobooth: """The main class. It contains all the logic for the photobooth. """ def __init__(self, display_size, display_rotate, picture_basename, picture_size, pose_time, display_time, trigger_channel, shutdown_channel, lamp_channel, idle_slideshow, slideshow_display_time): self.display = GuiModule('Photobooth', display_size) if (display_size == (0, 0)): # Detect actual resolution display_size = self.display.get_size() self.display_rotate = display_rotate if (display_rotate): self.display.set_rotate(True) self.pictures = PictureList(picture_basename) self.camera = CameraModule((picture_size[0] / 2, picture_size[1] / 2), camera_rotate=camera_rotate) self.camera_rotate = camera_rotate self.pic_size = picture_size self.pose_time = pose_time self.display_time = display_time self.trigger_channel = trigger_channel self.shutdown_channel = shutdown_channel self.lamp_channel = lamp_channel self.idle_slideshow = idle_slideshow if self.idle_slideshow: self.slideshow_display_time = slideshow_display_time self.slideshow = Slideshow( display_size, display_time, os.path.dirname(os.path.realpath(picture_basename))) if (display_rotate): self.slideshow.display.set_rotate(True) input_channels = [trigger_channel, shutdown_channel] output_channels = [lamp_channel] self.gpio = GPIO(self.handle_gpio, input_channels, output_channels) self.printer_module = PrinterModule() try: pygame.mixer.init(buffer=1024) self.shutter = pygame.mixer.Sound(shutter_sound) self.bip1 = pygame.mixer.Sound(bip1_sound) self.bip2 = pygame.mixer.Sound(bip2_sound) self.bip2.play() except pygame.error: self.shutter = None pass def teardown(self): self.display.msg("Shutting down...") self.gpio.set_output(self.lamp_channel, 0) sleep(0.5) self.display.teardown() self.gpio.teardown() self.remove_tempfiles() exit(0) def remove_tempfiles(self): for filename in glob(tmp_dir + "photobooth_*.jpg"): try: os.remove(filename) except OSError: pass def _run_plain(self): while True: self.camera.set_idle() # Display default message self.display.msg("Hit the button!") # Wait for an event and handle it event = self.display.wait_for_event() self.handle_event(event) def _run_slideshow(self): while True: self.camera.set_idle() self.slideshow.display_next("Hit the button!") tic = time() while time() - tic < self.slideshow_display_time: self.check_and_handle_events() def run(self): while True: try: # Enable lamp self.gpio.set_output(self.lamp_channel, 1) # Select idle screen type if self.idle_slideshow: self._run_slideshow() else: self._run_plain() # Catch exceptions and display message except CameraException as e: self.handle_exception(e.message) # Do not catch KeyboardInterrupt and SystemExit except (KeyboardInterrupt, SystemExit): raise except Exception as e: import sys print('SERIOUS ERROR' + repr(e)) sys.excepthook(*sys.exc_info()) self.handle_exception("SERIOUS ERROR!\n(see log file)") def check_and_handle_events(self): r, e = self.display.check_for_event() while r: self.handle_event(e) r, e = self.display.check_for_event() def clear_event_queue(self): r, e = self.display.check_for_event() while r: r, e = self.display.check_for_event() def handle_gpio(self, channel): if channel in [self.trigger_channel, self.shutdown_channel]: self.display.trigger_event(channel) def handle_event(self, event): if event.type == 0: self.teardown() elif event.type == 1: self.handle_keypress(event.value) elif event.type == 2: self.handle_mousebutton(event.value[0], event.value[1]) elif event.type == 3: self.handle_gpio_event(event.value) def handle_keypress(self, key): """Implements the actions for the different keypress events""" # Exit the application if key == ord('q'): self.teardown() # Take pictures elif key == ord('c'): self.take_picture() elif key == ord('f'): self.display.toggle_fullscreen() elif key == ord('i'): # Re-initialize the camera for debugging self.camera.reinit() elif key == ord('p'): self.toggle_auto_print() elif key == ord('r'): self.toggle_rotate() elif key == ord('1'): # Just for debugging self.show_preview_fps_1(5) elif key == ord('2'): # Just for debugging self.show_preview_fps_2(5) elif key == ord('3'): # Just for debugging self.show_preview_fps_3(5) def toggle_auto_print(self): "Toggle auto print and show an error message if printing isn't possible." if self.printer_module.can_print(): global auto_print auto_print = not auto_print self.display.msg("Autoprinting %s" % ("enabled" if auto_print else "disabled")) else: self.display.msg("Printing not configured\n(see log file)") def toggle_rotate(self): "Toggle rotating the display and camera." self.toggle_display_rotate() self.toggle_camera_rotate() self.display.msg("Display and camera rotated") def toggle_display_rotate(self): "Toggle rotating the display 90 degrees counter clockwise." self.display_rotate = (not self.display_rotate) self.display.set_rotate(self.display_rotate) self.slideshow.display.set_rotate(self.display_rotate) def toggle_camera_rotate(self): "Toggle rotating the camera 90 degrees counter clockwise." self.camera_rotate = (not self.camera_rotate) self.camera.set_rotate(self.camera_rotate) def handle_mousebutton(self, key, pos): """Implements the actions for the different mousebutton events""" # Take a picture if key == 1: self.take_picture() def handle_gpio_event(self, channel): """Implements the actions taken for a GPIO event""" if channel == self.trigger_channel: self.take_picture() elif channel == self.shutdown_channel: self.teardown() def handle_exception(self, msg): """Displays an error message and returns""" print("Error: " + msg) try: self.display.msg("ERROR:\n\n" + msg) except GuiException: self.display.msg("ERROR") sleep(3) def assemble_pictures(self, input_filenames): """Assembles four pictures into a 2x2 grid of thumbnails. The total size (WxH) is assigned in the global variable assembled_size at the top of this file. (E.g., 2352x1568) The outer border (a) is 2% of W The inner border (b) is 1% of W Note that if the camera is on its side, H and W will be swapped to create a portrait, rather than landscape, montage. Thumbnail sizes are calculated like so: h = (H - 2 * a - 2 * b) / 2 w = (W - 2 * a - 2 * b) / 2 W |---------------------------------------| --- +---+-------------+---+-------------+---+ --- | | | | a | | +-------------+ +-------------+ | --- | | | | | | | | | | | 0 | | 1 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- H | | | | 2*b | | +-------------+ +-------------+ | --- | | | | | | | | | | | 2 | | 3 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- | | | | a --- +---+-------------+---+-------------+---+ --- |---|-------------|---|-------------|---| a w 2*b w a [Note that extra padding will be added on the sides if the aspect ratio of the camera images do not match the aspect ratio of the final assembled image.] """ # If the display is in portrait orientation, # we should create an assembled image that fits it. if self.display.get_rotate(): (H, W) = self.pic_size else: (W, H) = self.pic_size # Thumbnail size of pictures outer_border = int(2 * max(W, H) / 100) # 2% of long edge inner_border = int(1 * max(W, H) / 100) # 1% of long edge thumb_box = (int(W / 2), int(H / 2)) thumb_size = (thumb_box[0] - outer_border - inner_border, thumb_box[1] - outer_border - inner_border) # Create output image with white background output_image = Image.new('RGB', (W, H), (255, 255, 255)) # Image 0 img = Image.open(input_filenames[0]) img = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) offset = (thumb_box[0] - inner_border - img.size[0], thumb_box[1] - inner_border - img.size[1]) output_image.paste(img, offset) # Image 1 img = Image.open(input_filenames[1]) img = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) offset = (thumb_box[0] + inner_border, thumb_box[1] - inner_border - img.size[1]) output_image.paste(img, offset) # Image 2 img = Image.open(input_filenames[2]) img = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) offset = (thumb_box[0] - inner_border - img.size[0], thumb_box[1] + inner_border) output_image.paste(img, offset) # Image 3 img = Image.open(input_filenames[3]) img = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) offset = (thumb_box[0] + inner_border, thumb_box[1] + inner_border) output_image.paste(img, offset) # Save assembled image output_filename = self.pictures.get_next() output_image.save(output_filename, "JPEG") return output_filename def show_preview(self, message=""): """If camera allows previews, take a photo and show it so people can pose before the shot. For speed, previews are decimated to fit within the screen instead of being scaled. For even more speed, the previews are blitted directly to a subsurface of the display. (Converting to a pygame Surface is slower). """ self.display.clear() if self.camera.has_preview(): f = self.camera.get_preview_array(self.display.get_size()) self.display.blit_array(f) self.display.show_message(message) self.display.apply() def show_counter(self, seconds): """Loop over showing the preview (if possible), with a count down""" tic = time() toc = time() - tic old_t = None while toc < seconds: t = seconds - int(toc) if t != old_t and self.bip1: self.bip1.play() old_t = t self.show_preview(str(t)) # Limit progress to 1 "second" per preview (e.g., too slow on Raspi 1) toc = min(toc + 1, time() - tic) def show_preview_fps_1(self, seconds): """XXX Debugging code for benchmarking XXX This is the original show_countdown preview code. (~5fps) """ import cv2, pygame, numpy tic = time() toc = 0 frames = 0 while toc < seconds: frames = frames + 1 self.display.clear() if self.camera.has_preview(): self.camera.take_preview(tmp_dir + "photobooth_preview.jpg") self.display.show_picture(tmp_dir + "photobooth_preview.jpg", flip=True) self.display.show_message(str(seconds - int(toc))) self.display.apply() toc = time() - tic self.display.msg("FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) print("Method 1 FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) sleep(3) def show_preview_fps_2(self, seconds): """XXX Debugging code for benchmarking XXX As a test, I'm trying a direct conversion from OpenCV to a PyGame Surface in memory and it's much faster than the original code, but still slower than subsurface blitting. (~10fps) """ import cv2, pygame, numpy tic = time() toc = 0 frames = 0 while toc < seconds: frames = frames + 1 self.display.clear() # Capture a preview image from the camera as a pygame surface. s = self.camera.get_preview_pygame_surface() (w, h) = s.get_size() (dw, dh) = self.display.get_size() # Figure out maximum proportional scaling size = (dw, dh) image_size = (w, h) offset = (0, 0) # New image size new_size = maxpect(image_size, size) # Update offset offset = tuple(a + int((b - c) / 2) for a, b, c in zip(offset, size, new_size)) # Apply scaling s = pygame.transform.scale(s, new_size).convert() # Display it using undocumented interface to GUI_Pygame self.display.surface_list.append((s, offset)) self.display.show_message(str(seconds - int(toc))) self.display.apply() toc = time() - tic self.display.msg("FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) print("Method 2 FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) sleep(3) def show_preview_fps_3(self, seconds): """XXX Debugging code for benchmarking XXX This is the fastest method, which decimates the array and blits it directly to a subsurface of the display. (~14fps) """ import cv2, pygame, numpy tic = time() toc = 0 frames = 0 while toc < seconds: frames = frames + 1 self.display.clear() # Grab a preview, decimated to fit within the screen size f = self.camera.get_preview_array(self.display.get_size()) # Blit it to the center of the screen self.display.blit_array(f) self.display.show_message(str(seconds - int(toc))) self.display.apply() toc = time() - tic self.display.msg("FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) print "Method 3 FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc) sleep(3) def show_pose(self, seconds, message=""): """Loop over showing the preview (if possible), with a static message. Note that this is *necessary* for OpenCV webcams as V4L will ramp the brightness level only after a certain number of frames have been taken. """ tic = time() toc = time() - tic while toc < seconds: self.show_preview(message) # Limit progress to 1 "second" per preview (e.g., too slow on Raspi 1) toc = min(toc + 1, time() - tic) def take_picture(self): """Implements the picture taking routine""" # Disable lamp self.gpio.set_output(self.lamp_channel, 0) # Show pose message self.show_pose(2, "POSE!\n\nTaking 4 pictures ...") # Extract display and image sizes size = self.display.get_size() outsize = (int(size[0] / 2), int(size[1] / 2)) # Take pictures filenames = [i for i in range(4)] for x in range(4): # Countdown self.show_counter(self.pose_time) # Try each picture up to 3 times remaining_attempts = 3 while remaining_attempts > 0: remaining_attempts = remaining_attempts - 1 self.display.clear((255, 230, 200)) self.display.show_message("S M I L E !!!\n\n" + str(x + 1) + " of 4") self.display.apply() tic = time() try: filenames[x] = self.camera.take_picture( tmp_dir + "photobooth_%02d.jpg" % x) remaining_attempts = 0 if self.shutter: self.shutter.play() except CameraException as e: # On recoverable errors: display message and retry if e.recoverable: if remaining_attempts > 0: self.display.msg(e.message) sleep(5) else: raise CameraException( "Giving up! Please start over!", False) else: raise e # Measure used time and sleep a second if too fast toc = time() - tic if toc < 1.0: sleep(1.0 - toc) # Show 'Wait' self.display.msg("Please wait!\n\nWorking\n...") # Assemble them outfile = self.assemble_pictures(filenames) if self.printer_module.can_print(): # Show picture for 10 seconds and then send it to the printer. # If auto_print is True, hitting the button cancels the print. # If auto_print is False, hitting the button sends the print tic = time() t = int(self.display_time - (time() - tic)) old_t = self.display_time + 1 button_pressed = False # Clear event queue (in case they hit the button twice accidentally) self.clear_event_queue() while t > 0: if t != old_t: self.display.clear() self.display.show_picture(outfile, size, (0, 0)) self.display.show_message( "%s%d" % ("Printing in " if auto_print else "Print photo?\n", t)) self.display.apply() old_t = t # Flash the lamp so they'll know they can hit the button self.gpio.set_output(self.lamp_channel, int(time() * 2) % 2) # Watch for button, gpio, mouse press to cancel/enable printing r, e = self.display.check_for_event() if r: # Caught a button press. self.display.clear() self.display.show_picture(outfile, size, (0, 0)) self.display.show_message( "Printing%s" % (" cancelled" if auto_print else "")) self.display.apply() self.gpio.set_output(self.lamp_channel, 0) sleep(1) # Discard extra events (e.g., they hit the button a bunch) self.clear_event_queue() button_pressed = True break t = int(self.display_time - (time() - tic)) # Either button pressed or countdown timed out self.gpio.set_output(self.lamp_channel, 0) if auto_print ^ button_pressed: self.display.msg("Printing") self.printer_module.enqueue(outfile) else: # No printer available, so just show montage for 10 seconds self.display.clear() self.display.show_picture(outfile, size, (0, 0)) self.display.apply() sleep(self.display_time) # Reenable lamp self.gpio.set_output(self.lamp_channel, 1)
class Photobooth: """The main class. It contains all the logic for the photobooth. """ def __init__(self, display_size, picture_basename, picture_size, pose_time_first, pose_time, display_time, trigger_channel, shutdown_channel, lamp_channel, idle_slideshow, slideshow_display_time): self.display = GuiModule('Photobooth', display_size) self.pictures = PictureList(picture_basename) self.prints = PictureList(print_basename) self.camera = CameraModule(picture_size) self.pic_size = picture_size self.pose_time_first = pose_time_first self.pose_time = pose_time self.display_time = display_time self.trigger_channel = trigger_channel self.shutdown_channel = shutdown_channel self.lamp_channel = lamp_channel self.idle_slideshow = idle_slideshow if self.idle_slideshow: self.slideshow_display_time = slideshow_display_time self.slideshow = Slideshow(display_size, display_time, os.path.dirname(os.path.realpath(picture_basename))) input_channels = [ trigger_channel, shutdown_channel ] output_channels = [ lamp_channel ] self.gpio = GPIO(self.handle_gpio, input_channels, output_channels) self.bt1 = BTMon(btaddr1, 1, self.handle_bt) self.bt2 = BTMon(btaddr2, 2, self.handle_bt) def teardown(self): self.display.clear() self.display.show_message(u"Stänger av...") self.display.apply() self.gpio.set_output(self.lamp_channel, 0) self.display.cancel_events() self.display.teardown() self.gpio.teardown() exit(0) def _run_plain(self): while True: self.camera.set_idle() # Display default message self.display.clear() #self.display.show_message(u"Tryck på knappen!") self.display.show_message(u" Ta bild\n\n\n\n\n\n\n\n\n", color=(255,0,0)) self.display.show_message(u"\n\n\n\n\n\n\n\nPreview ") #self.display.show_message(u"Ta bild Preview \n | | \n | | \n v v \n R S \n") self.display.apply() # Wait for an event and handle it event = self.display.wait_for_event() self.handle_event(event) def _run_slideshow(self): while True: self.camera.set_idle() #self.slideshow.display_next(u"Tryck på knappen!") #self.slideshow.display_next(u"Ta bild Preview \n | | \n | | \n v v \n R S \n") self.slideshow.display_next(u"\n\n\n\n\n\n\n\nPreview " ,u" Ta bild\n\n\n\n\n\n\n\n\n") tic = time() while time() - tic < self.slideshow_display_time: self.check_and_handle_events() def run(self): while True: try: # Enable lamp self.gpio.set_output(self.lamp_channel, 1) # Select idle screen type if self.idle_slideshow: self._run_slideshow() else: self._run_plain() # Catch exceptions and display message except CameraException as e: self.handle_exception(e.message) # Do not catch KeyboardInterrupt and SystemExit except (KeyboardInterrupt): #, SystemExit): raise except Exception as e: print('SERIOUS ERROR: ' + repr(e)) traceback.print_exc(); self.handle_exception("SERIOUS ERROR!") def check_and_handle_events(self): r, e = self.display.check_for_event() while r: self.handle_event(e) r, e = self.display.check_for_event() def handle_gpio(self, channel): if channel in [ self.trigger_channel, self.shutdown_channel ]: self.display.trigger_event(channel) def handle_bt(self, channel): self.display.trigger_event(channel) def convert_event(self, event): if event.type == 0: return 0 elif event.type == 1: key = event.value if key == ord('q'): return 0 elif key == ord('c'): return 1 elif key == ord('u'): return 2 elif key == ord('r'): return 1 elif key == ord('s'): return 2 elif event.type == 2: if event.value[0] == 1: return 1 elif event.type == 3: if event.value == 1: return 1 if event.value == 2: return 2 return -1 def handle_event(self, event): code = self.convert_event(event) if code == 0: self.teardown() elif code == 1: # Take pictures self.take_picture() self.display.cancel_events() elif code == 2: self.show_preview(20, False) self.display.cancel_events() def handle_exception(self, msg): """Displays an error message and returns""" self.display.clear() print("Error: " + msg) self.display.show_message(u"FEL:\n\n" + msg) self.display.apply() self.display.cancel_events() exit(1) def assemble_pictures(self, input_filenames, size): """Assembles four pictures into a 2x2 grid It assumes, all original pictures have the same aspect ratio as the resulting image. For the thumbnail sizes we have: h = (H - 2 * a - 2 * b) / 2 w = (W - 2 * a - 2 * b) / 2 W |---------------------------------------| --- +---+-------------+---+-------------+---+ --- | | | | a | | +-------------+ +-------------+ | --- | | | | | | | | | | | 0 | | 1 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- H | | | | 2*b | | +-------------+ +-------------+ | --- | | | | | | | | | | | 2 | | 3 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- | | | | a --- +---+-------------+---+-------------+---+ --- |---|-------------|---|-------------|---| a w 2*b w a """ # Thumbnail size of pictures outer_border = 40 inner_border = 20 thumb_box = ( int( size[0] / 2 ) , int( size[1] / 2 ) ) thumb_size = ( thumb_box[0] - outer_border - inner_border , thumb_box[1] - outer_border - inner_border ) # Create output image with white background output_image = Image.new('RGB', size, (0, 0, 0)) # Image 0 img = Image.open(input_filenames[0]) img.thumbnail(thumb_size) offset = ( thumb_box[0] - inner_border - img.size[0] , thumb_box[1] - inner_border - img.size[1] ) output_image.paste(img, offset) # Image 1 img = Image.open(input_filenames[1]) img.thumbnail(thumb_size) offset = ( thumb_box[0] + inner_border, thumb_box[1] - inner_border - img.size[1] ) output_image.paste(img, offset) # Image 2 img = Image.open(input_filenames[2]) img.thumbnail(thumb_size) offset = ( thumb_box[0] - inner_border - img.size[0] , thumb_box[1] + inner_border ) output_image.paste(img, offset) # Image 3 img = Image.open(input_filenames[3]) img.thumbnail(thumb_size) offset = ( thumb_box[0] + inner_border , thumb_box[1] + inner_border ) output_image.paste(img, offset) output_image = output_image.convert(mode) # Save assembled image output_filename = self.pictures.get_next() sleep(0.01) output_image.save(output_filename, "JPEG") sleep(0.01) return output_filename def assemble_print(self, input_filenames, size): """Assembles four pictures into a 2x2 grid It assumes, all original pictures have the same aspect ratio as the resulting image. For the thumbnail sizes we have: h = (H - 2*a - 6*b) / 4 w = (W - 2*a - 2*b) / 2 W |---------------------------------------| w0 w1 --- +---+-------------+---+-------------+---+ --- | | | | a | | +-------------+ +-------------+ | --- h0 | | | | | | | | | | | 0 | | 0 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- | | | | b | | +-------------+ +-------------+ | --- h1 | | | | | | | | | | | 1 | | 1 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- H | | | | b | | +-------------+ +-------------+ | --- h2 | | | | | | | | | | | 2 | | 2 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- | | | | b | | +-------------+ +-------------+ | --- h3 | | | | | | | | | | | 3 | | 3 | | | h | | | | | | | | | | +-------------+ +-------------+ | --- | | | | a --- +---+-------------+---+-------------+---+ --- |---|-------------|---|-------------|---| a w b w a """ # Thumbnail size of pictures outer_borderx = 0 outer_bordery = 110 inner_borderx = 20 inner_bordery = 10 thumb_size = ( int((size[0] - 2*outer_borderx - inner_borderx)/2) , int((size[1] - 2*outer_bordery - 3*inner_bordery)/4) ) w = [outer_borderx ,outer_borderx+thumb_size[0]+inner_borderx ] h = [outer_bordery ,outer_bordery+1*(thumb_size[1]+inner_bordery) ,outer_bordery+2*(thumb_size[1]+inner_bordery) ,outer_bordery+3*(thumb_size[1]+inner_bordery) ] # Create output image with white background output_image = Image.new('RGB', size, (255, 255, 255)) # Image 0 img = Image.open(input_filenames[0]) img.thumbnail(thumb_size, Image.ANTIALIAS) output_image.paste(img, (w[0],h[0])) output_image.paste(img, (w[1],h[0])) # Image 1 img = Image.open(input_filenames[1]) img.thumbnail(thumb_size, Image.ANTIALIAS) output_image.paste(img, (w[0],h[1])) output_image.paste(img, (w[1],h[1])) # Image 2 img = Image.open(input_filenames[2]) img.thumbnail(thumb_size, Image.ANTIALIAS) output_image.paste(img, (w[0],h[2])) output_image.paste(img, (w[1],h[2])) # Image 3 img = Image.open(input_filenames[3]) img.thumbnail(thumb_size, Image.ANTIALIAS) output_image.paste(img, (w[0],h[3])) output_image.paste(img, (w[1],h[3])) sleep(0.01) output_image = output_image.convert(mode) # Text draw = ImageDraw.Draw(output_image) font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeSans.ttf", 80, encoding="unic") def drawTextCentered(x, y, W, text): w, h = font.getsize(text) draw.text((x+(W-w)/2,y), text, "black", font) #drawTextCentered(0, 0, thumb_size[0], "Ylva & Simon") #drawTextCentered(w[1], 0, thumb_size[0], "Ylva & Simon") #bottom = outer_bordery+4*(thumb_size[1]+inner_bordery) #drawTextCentered(0, bottom, thumb_size[0], "2018-06-09") #drawTextCentered(w[1], bottom, thumb_size[0], "2018-06-09") # Save assembled image sleep(0.01) output_filename = self.prints.get_next() output_image.save(output_filename, "JPEG") sleep(0.01) return output_filename def show_preview(self, seconds, should_count=True): secs = abs(seconds) if secs == 1: sleep(1) self.display.cancel_events() elif self.camera.has_preview() and not seconds < 0: tic = time() toc = time() - tic while toc < secs: self.display.clear() buff = self.camera.take_preview_buff() img = Image.open(StringIO.StringIO(buff)) sleep(0.01) img = img.convert(mode) img = img.convert("RGB") pygameimg = pygame.image.frombuffer(img.tobytes(), img.size, img.mode) self.display.show_picture(image=pygameimg, flip=True) self.display.show_message(str(secs - int(toc)) + " ") if toc < 10 and not should_count: self.display.show_message(u"\n\n\n\n\n\n\n\nAvbryt ") self.display.apply() sleep(0.01) # Limit progress to 1 "second" per preview (e.g., too slow on Raspi 1) toc = min(toc + 1, time() - tic) r, e = self.display.check_for_event() if not should_count and r and self.convert_event(e) == 2: self.display.cancel_events() sleep(0.01) return else: for i in range(secs): self.display.clear() sleep(0.01) self.display.show_message(str(secs - i)) self.display.apply() sleep(1) def take_picture(self): """Implements the picture taking routine""" # Disable lamp self.gpio.set_output(self.lamp_channel, 0) # Show pose message self.display.clear() self.display.show_message(u"POSERA!\n\nTar fyra bilder..."); self.display.apply() sleep(2) # Extract display and image sizes display_size = self.display.get_size() outsize = (int(display_size[0]/2), int(display_size[1]/2)) # Take pictures filenames = [i for i in range(4)] for x in range(4): # Countdown if x==0: self.show_preview(self.pose_time_first) else: self.show_preview(self.pose_time) # Try each picture up to 3 times remaining_attempts = 3 while remaining_attempts > 0: remaining_attempts = remaining_attempts - 1 self.display.clear() self.display.show_message(u"OMELETT!!!\n\n" + str(x+1) + " av 4") self.display.apply() tic = time() try: filenames[x] = self.camera.take_picture("/tmp/photobooth_%02d.jpg" % x) remaining_attempts = 0 except CameraException as e: # On recoverable errors: display message and retry if e.recoverable: if remaining_attempts > 0: self.display.clear() self.display.show_message(e.message) self.display.apply() sleep(5) else: raise CameraException("Giving up! Please start over!", False) else: raise e # Measure used time and sleep a second if too fast toc = time() - tic if toc < 1.0: sleep(1.0 - toc) # Show 'Wait' self.display.clear() self.display.show_message(u"Vänta!\n\nLaddar...") self.display.apply() sleep(0.01) self.camera.set_idle() sleep(0.01) # Assemble them outfile = self.assemble_pictures(filenames, display_size) sleep(0.01) # Show pictures for 10 seconds self.display.clear() self.display.show_picture(outfile, display_size, (0,0)) self.display.apply() sleep(0.01) self.display.clear() self.display.show_picture(outfile, display_size, (0,0)) self.display.show_message(u" Skriv ut\n\n\n\n\n\n\n\n\n", color=(255,0,0)) self.display.show_message(u"\n\n\n\n\n\n\n\nAvbryt ") self.display.apply() self.run_after(filenames) #self.display.clear() #self.display.show_picture(outfile, display_size, (0,0)) #self.display.show_message(u"Laddar upp") #self.display.apply() #self.upload(filenames) # Reenable lamp self.gpio.set_output(self.lamp_channel, 1) def run_after(self,filenames): while True: event = self.display.wait_for_event() if not self.handle_event_after(event,filenames): return def handle_event_after(self, event, filesnames): code = self.convert_event(event) if code == 0: self.teardown() elif code == 1: self.print_out(filesnames) self.display.cancel_events() return False elif code == 2: self.display.cancel_events() return False return True def print_out(self, filenames): display_size = self.display.get_size() # Show 'Wait' self.display.clear() self.display.show_message(u"Vänta!\n\nLaddar...") self.display.apply() sleep(0.01) # Assemble them outfile = self.assemble_print(filenames, (self.pic_size[1],self.pic_size[0])) sleep(0.01) # Show pictures for 10 seconds self.display.clear() self.display.show_picture(outfile, display_size, (0,0)) self.display.show_message(u"Vänta!\n\nSkriver ut...") self.display.apply() sleep(0.01) os.system("lp -o fit-to-page %s" % outfile) sleep(10) def upload(self, filenames): sleep(10)