Fotobox1/photobox.py

414 lines
9.1 KiB
Python
Executable File

#!/usr/bin/python
import datetime
import imp
import inspect
import os
import sys
from math import floor
from shutil import copyfile
from time import sleep
import pygame
from PIL import Image
try:
imp.find_module('RPi')
import RPi.GPIO as GPIO
except ImportError:
import FakeGPIO as GPIO
try:
imp.find_module('picamera')
import picamera
except ImportError:
import FakePicamera as picamera
__all__ = ["monotonic_time"]
import ctypes
CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h>
class timespec(ctypes.Structure):
_fields_ = [
('tv_sec', ctypes.c_long),
('tv_nsec', ctypes.c_long)
]
librt = ctypes.CDLL('librt.so.1', use_errno=True)
clock_gettime = librt.clock_gettime
clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
def monotonic_time():
t = timespec()
if clock_gettime(CLOCK_MONOTONIC_RAW, ctypes.pointer(t)) != 0:
errno_ = ctypes.get_errno()
raise OSError(errno_, os.strerror(errno_))
return t.tv_sec + t.tv_nsec * 1e-9
os.chdir(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
# CONFIGURATION
WIDTH = 800
HEIGHT = 480
FONTSIZE = 500
PIC_INTERVAL = 0.5
TEXT_INTERVAL = 0.5
TEXT_POS = (250, -40)
OVERLAY_ALPHA = 64
COLOR_BACK = pygame.Color(0, 0, 0)
COLOR_FLASH = pygame.Color(255, 255, 255)
GRAPHICS_DIR = "graphics"
IMAGE_DIR = "images/0-CURRENT"
COUNTDOWN = 3
SHOT_COUNT = 3
BORDER = 10
GPIO_LED_BORDER = 7
GPIO_SPOT_RIGHT = 35
GPIO_SPOT_LEFT = 37
GPIO_BUTTON_LED = 36
GPIO_BUTTON = 38
THUMB_WIDTH = int((800 - BORDER) / SHOT_COUNT - BORDER)
THUMB_HEIGHT = int(THUMB_WIDTH / 1.333)
IMAGES_ORIGINAL = os.path.join(IMAGE_DIR, "original")
IMAGES_TODO = os.path.join(IMAGE_DIR, "todo")
# global variables
spot_mode = 0
session_start = monotonic_time()
# GPIO helpers
def led_border(state):
global GPIO_LED_BORDER
GPIO.output(GPIO_LED_BORDER, state)
def set_spot_mode(new_mode):
global spot_mode
spot_mode = new_mode % 4
GPIO.output(GPIO_SPOT_LEFT, not (spot_mode & 1))
GPIO.output(GPIO_SPOT_RIGHT, not (spot_mode & 2))
def next_spot_mode():
global spot_mode
set_spot_mode(spot_mode + 1)
# user interface
def button_press(gpio_id):
next_spot_mode()
# INIT GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
GPIO.setup(GPIO_SPOT_RIGHT, GPIO.OUT, initial=GPIO.HIGH)
GPIO.setup(GPIO_SPOT_LEFT, GPIO.OUT, initial=GPIO.HIGH)
GPIO.setup(GPIO_LED_BORDER, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(GPIO_BUTTON_LED, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(GPIO_BUTTON, GPIO.IN)
GPIO.add_event_detect(GPIO_BUTTON, GPIO.FALLING, button_press, bouncetime=300)
led_border(False)
sleep(0.02)
led_border(True)
sleep(0.02)
led_border(False)
sleep(0.02)
led_border(True)
sleep(0.02)
led_border(False)
sleep(0.02)
led_border(True)
sleep(0.02)
led_border(False)
# INIT PYGAME
pygame.init()
pygame.mouse.set_visible(False)
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# LOAD GRAPHICS
gfx = {}
def _load(name):
filename = "%s.png" % name
path = os.path.join(GRAPHICS_DIR, filename)
img = pygame.image.load(path)
rect = img.get_rect()
gfx[name] = (img, rect)
_load("begin")
_load("loading")
_load("yes")
_load("no")
_load("end_yes")
_load("end_no")
_load("saved")
_load("canceled")
# LOAD COUNTDOWN OVERLAYS
img_cdn = []
pad_cdn = []
for i in range(0, COUNTDOWN):
filename = "%s.png" % (COUNTDOWN - i)
path = os.path.join(GRAPHICS_DIR, filename)
img = Image.open(path)
pad = Image.new('RGB', (
((img.size[0] + 31) // 32) * 32,
((img.size[1] + 15) // 16) * 16,
))
pad.paste(img, (0, 0))
img_cdn.append(img)
pad_cdn.append(pad)
# FUNCTIONS ========================================================================================
def waitForEvent():
pygame.event.clear()
while 1:
sleep(0.25)
events = pygame.event.get()
if len(events) > 0:
for event in events:
if event.type == pygame.QUIT:
sys.exit(0)
return events
def waitForTouch():
pos = (-1, -1)
while True:
events = waitForEvent()
for event in events:
if event.type == pygame.MOUSEMOTION:
pos = event.pos
elif event.type == pygame.MOUSEBUTTONUP:
return pos
def maintainance():
screen.fill(COLOR_BACK)
screen.blit(maintain, maintain_rect)
pygame.display.flip()
# wait
pos = waitForTouch()
def waitForBegin():
screen.fill(COLOR_BACK)
screen.blit(*gfx["begin"])
pygame.display.flip()
# wait
pos = waitForTouch()
if pos[0] > 720 and pos[1] > 400:
maintainance()
def countdown(camera, pad, img):
sleep(PIC_INTERVAL)
o = camera.add_overlay(pad.tobytes(), size=img.size)
o.alpha = OVERLAY_ALPHA
o.layer = 3
sleep(TEXT_INTERVAL)
camera.remove_overlay(o)
def makePhotos():
# CLEAR SCREEN
screen.fill(COLOR_BACK)
pygame.display.update()
# INIT CAMERA
camera = picamera.PiCamera()
camera.vflip = False
camera.hflip = True
camera.resolution = (2592, 1944)
camera.rotation = 90
camera.start_preview()
# COUNTDOWN
led_border(True)
for i in range(0, COUNTDOWN):
countdown(camera, pad_cdn[i], img_cdn[i])
led_border(False)
# SHOTS
for i in range(0, SHOT_COUNT):
# SHOT!
sleep(PIC_INTERVAL)
led_border(True)
camera.capture("./tmp%s.jpg" % i, format='jpeg', quality=100, thumbnail=None)
led_border(False)
# PAUSE
if i < SHOT_COUNT - 1:
screen.fill(COLOR_BACK)
pygame.display.update()
camera.start_preview()
sleep(TEXT_INTERVAL)
# CLOSE CAMERA
camera.close()
def chooseImages():
# clear screen
screen.fill(COLOR_BACK)
screen.blit(*gfx["loading"])
pygame.display.update()
# load images from disk
img_tmp = []
rect_tmp = []
choices = []
x = 12
for i in range(0, SHOT_COUNT):
img = pygame.image.load("tmp%s.jpg" % i)
img = pygame.transform.scale(img, (THUMB_WIDTH, THUMB_HEIGHT))
rect = img.get_rect()
rect.x = x
rect.y = 146
img_tmp.append(img)
rect_tmp.append(rect)
choices.append(0)
x = x + THUMB_WIDTH + BORDER
save = False
cancel = False
any_choice = False
while not cancel and (not save or not any_choice):
# reset save (if nothing selected)
save = False
# draw background
screen.fill(COLOR_BACK)
if any_choice:
screen.blit(*gfx["end_yes"])
else:
screen.blit(*gfx["end_no"])
# draw images
for i in range(0, SHOT_COUNT):
screen.blit(img_tmp[i], rect_tmp[i])
# draw choices
x = 12
for choice in choices:
if choice > 0:
(img, rect) = gfx["yes"]
else:
(img, rect) = gfx["no"]
rect.x = x + (THUMB_WIDTH - rect.width) / 2
rect.y = 340
screen.blit(img, rect)
x = x + THUMB_WIDTH + BORDER
# update display
pygame.display.flip()
# wait for new event (touch screen / mouse / keyboard)
pos = waitForTouch()
if pos[1] > 140:
# toggle image
i = int(floor((pos[0] - BORDER / 2) / (THUMB_WIDTH + BORDER)))
if i >= 0 and i < SHOT_COUNT:
choices[i] = (choices[i] + 1) % 2
elif pos[1] < 100:
# top menu buttons
if pos[0] > 0 and pos[0] < 266:
cancel = True
elif pos[0] > 533 and pos[0] < 800 and (choices[0] > 0 or choices[1] > 0 or choices[2] > 0):
save = True
any_choice = False
for choice in choices:
if choice > 0:
any_choice = True
break
return (choices, save)
def saveImages(choices):
local = datetime.datetime.now()
session_age = (monotonic_time() - session_start)
try:
os.makedirs(IMAGES_ORIGINAL)
except OSError:
pass
try:
os.makedirs(IMAGES_TODO)
except OSError:
pass
print("saving images...")
for i in range(0, SHOT_COUNT):
if choices[i] > 0:
print(" saving image #%s" % i)
filename = "%s-%s-session_age=%05d.jpg" % (local.strftime("%Y%m%dT%H:%M:%S.%f%z"), i, session_age)
path_tmp = "tmp%s.jpg" % i
path_original = os.path.join(IMAGES_ORIGINAL, filename)
path_todo = os.path.join(IMAGES_TODO, filename)
os.rename(path_tmp, path_original)
copyfile(path_original, path_todo)
try:
while 1:
waitForBegin()
makePhotos()
(choices, save) = chooseImages()
if save:
saveImages(choices);
screen.fill(COLOR_BACK)
screen.blit(*gfx["saved"])
pygame.display.flip()
else:
screen.fill(COLOR_BACK)
screen.blit(*gfx["canceled"])
pygame.display.flip()
sleep(2)
except KeyboardInterrupt:
print("")
finally:
led_border(False)
pygame.quit()