diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..9140e22 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,49 @@ +Changes to adafruit-pi-cam +========================== + +Version 2.2 (Quality) +--------------------- + +- **new feature**: added settings page and logic for image quality. + The program now supports jpg and jpg+raw. Note that + for jpg+raw the raw bayer-data is append to the + generated jpg and the file size increases by about 6MB. + To process the file, you have to convert it first + to dng with [raspi_dng](https://github.com/bablokb/raspiraw). + Once in DNG-format, you can use the rawprocessor + of your choice (Rawtherapee works fine). + +Version 2.1 (Direct Quit) +------------------------- + +- **new feature**: after boot, the first setting screen is the + quit confirmation page. This allows faster shutdown. +- **internal**: code changes for future enhancements + + +Version 2 (major rework) +------------------------ + +This version needs a newer version of python-picam (tested with +version 1.8). It additionally needs the package python-pyexiv2. + +- **new feature**: added screen for setting AWB +- **new feature**: add thumbnail to captured image and save to /home/pi/.cache +- **performance**: display cached thumbnails instead of scaled down images +- **performance**: keep list of images-numbers instead of testing existence + of files with thousands of os-calls +- **change**: name images rpi_XXXX.jpg instead of IMG_XXXX.JPG + (unix-tools usually expect lower case in filenames) +- **fix**: change owner of captured images to pi:pi +- **fix**: capture raw RGB directly, no need to scale result with C-code +- **fix**: removed buggy cropping code (camera.crop is deprecated) +- **fix**: added shebang (no need to call python explicitly to execute code) +- **fix**: start cam.py from any directory + (in *nix, you should stay at HOME) +- **fix**: read and write cam.pkl in user's HOME-directory +- **new feature**: set EXIF-tag 'WhiteBalance' to auto or manual + + +Version 1 (original Adafruit release) +------------------------------------- + diff --git a/Makefile b/Makefile deleted file mode 100644 index a1122d8..0000000 --- a/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -# This is needed only for *compiling* the yuv2rgb.so file (Python module). -# Not needed for just running the Python code with precompiled .so file. - -all: yuv2rgb.so - -yuv2rgb.so: yuv2rgb.o - gcc -s -shared -Wl,-soname,libyuv2rgb.so -o yuv2rgb.so yuv2rgb.o - -yuv2rgb.o: yuv2rgb.c - gcc -fPIC -O3 -fomit-frame-pointer -funroll-loops -c yuv2rgb.c - -clean: - rm -f yuv2rgb.o yuv2rgb.so diff --git a/README.md b/README.md index 8e55f85..2d3bba0 100644 --- a/README.md +++ b/README.md @@ -5,5 +5,13 @@ Camera project for Raspberry Pi + camera + Adafruit PiTFT By PaintYourDragon (Phil B) for Adafruit Industries Read how to build this project over at -http://learn.adafruit.com/diy-wifi-raspberry-pi-touch-cam +[http://learn.adafruit.com/diy-wifi-raspberry-pi-touch-cam](http://learn.adafruit.com/diy-wifi-raspberry-pi-touch-cam) Enjoy! + +Modified by Bernhard Bablok. For list of changes, see ChangeLog.md + +Not that the prereqs have changed compared to the original documentation: + +- python-picam (current version, tested with 1.8) +- python-pyexiv2 (new prereq) + diff --git a/TODO b/TODO new file mode 100644 index 0000000..11d149d --- /dev/null +++ b/TODO @@ -0,0 +1,11 @@ +- rework UI + * add toggle button to unclutter screen + * add direct access-buttons for ISO, WB, (quality) + * add buttons for exposure-compensation + +- add features + * exposure compensation + * exposure (auto, shutter-speed) + +- fix 1920x1080 offset: no WYSIWYG + diff --git a/cam.py b/cam.py old mode 100644 new mode 100755 index b907622..c230ef6 --- a/cam.py +++ b/cam.py @@ -1,3 +1,10 @@ +#!/usr/bin/python + +# Version: 2.1 (see ChangeLog.md for a list of changes) + +# ------------------------------------------------------------------------------- +# Header from original version: + # Point-and-shoot camera for Raspberry Pi w/camera and Adafruit PiTFT. # This must run as root (sudo python cam.py) due to framebuffer, etc. # @@ -21,23 +28,47 @@ # # Written by Phil Burgess / Paint Your Dragon for Adafruit Industries. # BSD license, all text above must be included in any redistribution. +# ------------------------------------------------------------------------------- + +# ------------------------------------------------------------------------------- +# Changes from the original version by Bernhard Bablok (mail a_t bablokb dot de) +# Tested with picamera 1.8. +# ------------------------------------------------------------------------------- import atexit import cPickle as pickle import errno import fnmatch import io +import sys import os import os.path +import pwd import picamera +import pyexiv2 import pygame import stat import threading import time -import yuv2rgb from pygame.locals import * from subprocess import call +# Class encapsulating constants -------------------------------------------- + +class Mode: + UNDEFINED = -1 + PLAYBACK = 0 + DELETE = 1 + NO_IMAGES = 2 + VIEWFINDER = 3 + STORAGE = 4 + SIZE = 5 + EFFECT = 6 + ISO = 7 + AWB = 8 + QUALITY = 9 + QUIT = 10 + LAST = QUIT # LAST must be equal to the highest mode # UI classes --------------------------------------------------------------- @@ -52,12 +83,12 @@ class Icon: - def __init__(self, name): - self.name = name - try: - self.bitmap = pygame.image.load(iconPath + '/' + name + '.png') - except: - pass + def __init__(self, name): + self.name = name + try: + self.bitmap = pygame.image.load(iconPath + '/' + name + '.png') + except: + pass # Button is a simple tappable screen region. Each has: # - bounding rect ((X,Y,W,H) in pixels) @@ -77,55 +108,55 @@ def __init__(self, name): class Button: - def __init__(self, rect, **kwargs): - self.rect = rect # Bounds - self.color = None # Background fill color, if any - self.iconBg = None # Background Icon (atop color fill) - self.iconFg = None # Foreground Icon (atop background) - self.bg = None # Background Icon name - self.fg = None # Foreground Icon name - self.callback = None # Callback function - self.value = None # Value passed to callback - for key, value in kwargs.iteritems(): - if key == 'color': self.color = value - elif key == 'bg' : self.bg = value - elif key == 'fg' : self.fg = value - elif key == 'cb' : self.callback = value - elif key == 'value': self.value = value - - def selected(self, pos): - x1 = self.rect[0] - y1 = self.rect[1] - x2 = x1 + self.rect[2] - 1 - y2 = y1 + self.rect[3] - 1 - if ((pos[0] >= x1) and (pos[0] <= x2) and - (pos[1] >= y1) and (pos[1] <= y2)): - if self.callback: - if self.value is None: self.callback() - else: self.callback(self.value) - return True - return False - - def draw(self, screen): - if self.color: - screen.fill(self.color, self.rect) - if self.iconBg: - screen.blit(self.iconBg.bitmap, - (self.rect[0]+(self.rect[2]-self.iconBg.bitmap.get_width())/2, - self.rect[1]+(self.rect[3]-self.iconBg.bitmap.get_height())/2)) - if self.iconFg: - screen.blit(self.iconFg.bitmap, - (self.rect[0]+(self.rect[2]-self.iconFg.bitmap.get_width())/2, - self.rect[1]+(self.rect[3]-self.iconFg.bitmap.get_height())/2)) - - def setBg(self, name): - if name is None: - self.iconBg = None - else: - for i in icons: - if name == i.name: - self.iconBg = i - break + def __init__(self, rect, **kwargs): + self.rect = rect # Bounds + self.color = None # Background fill color, if any + self.iconBg = None # Background Icon (atop color fill) + self.iconFg = None # Foreground Icon (atop background) + self.bg = None # Background Icon name + self.fg = None # Foreground Icon name + self.callback = None # Callback function + self.value = None # Value passed to callback + for key, value in kwargs.iteritems(): + if key == 'color': self.color = value + elif key == 'bg' : self.bg = value + elif key == 'fg' : self.fg = value + elif key == 'cb' : self.callback = value + elif key == 'value': self.value = value + + def selected(self, pos): + x1 = self.rect[0] + y1 = self.rect[1] + x2 = x1 + self.rect[2] - 1 + y2 = y1 + self.rect[3] - 1 + if ((pos[0] >= x1) and (pos[0] <= x2) and + (pos[1] >= y1) and (pos[1] <= y2)): + if self.callback: + if self.value is None: self.callback() + else: self.callback(self.value) + return True + return False + + def draw(self, screen): + if self.color: + screen.fill(self.color, self.rect) + if self.iconBg: + screen.blit(self.iconBg.bitmap, + (self.rect[0]+(self.rect[2]-self.iconBg.bitmap.get_width())/2, + self.rect[1]+(self.rect[3]-self.iconBg.bitmap.get_height())/2)) + if self.iconFg: + screen.blit(self.iconFg.bitmap, + (self.rect[0]+(self.rect[2]-self.iconFg.bitmap.get_width())/2, + self.rect[1]+(self.rect[3]-self.iconFg.bitmap.get_height())/2)) + + def setBg(self, name): + if name is None: + self.iconBg = None + else: + for i in icons: + if name == i.name: + self.iconBg = i + break # UI callbacks ------------------------------------------------------------- @@ -133,98 +164,134 @@ def setBg(self, name): # the global buttons[] list. def isoCallback(n): # Pass 1 (next ISO) or -1 (prev ISO) - global isoMode - setIsoMode((isoMode + n) % len(isoData)) + global isoMode + setIsoMode((isoMode + n) % len(isoData)) + +def awbCallback(n): # Pass 1 (next AWB) or -1 (prev AWB) + global awbMode + setAwbMode((awbMode + n) % len(awbData)) def settingCallback(n): # Pass 1 (next setting) or -1 (prev setting) - global screenMode - screenMode += n - if screenMode < 4: screenMode = len(buttons) - 1 - elif screenMode >= len(buttons): screenMode = 4 + global screenMode + screenMode += n + if screenMode <= Mode.VIEWFINDER: screenMode = len(buttons) - 1 + elif screenMode >= len(buttons): screenMode = Mode.VIEWFINDER + 1 def fxCallback(n): # Pass 1 (next effect) or -1 (prev effect) - global fxMode - setFxMode((fxMode + n) % len(fxData)) + global fxMode + setFxMode((fxMode + n) % len(fxData)) def quitCallback(): # Quit confirmation button - saveSettings() - raise SystemExit + saveSettings() + raise SystemExit def viewCallback(n): # Viewfinder buttons - global loadIdx, scaled, screenMode, screenModePrior, settingMode, storeMode - - if n is 0: # Gear icon (settings) - screenMode = settingMode # Switch to last settings mode - elif n is 1: # Play icon (image playback) - if scaled: # Last photo is already memory-resident - loadIdx = saveIdx - screenMode = 0 # Image playback - screenModePrior = -1 # Force screen refresh - else: # Load image - r = imgRange(pathData[storeMode]) - if r: showImage(r[1]) # Show last image in directory - else: screenMode = 2 # No images - else: # Rest of screen = shutter - takePicture() + global imgNums, loadIdx, imgSurface, screenMode, screenModePrior, settingMode, storeMode + + if n is 0: # Gear icon (settings) + screenMode = settingMode # Switch to last settings mode + elif n is 1: # Play icon (image playback) + if imgSurface: # Last photo is already memory-resident + loadIdx = len(imgNums)-1 + screenMode = Mode.PLAYBACK + screenModePrior = Mode.UNDEFINED + else: # Load image + if len(imgNums): + loadIdx = len(imgNums)-1 + showImage(loadIdx) + else: screenMode = Mode.NO_IMAGES + else: # Rest of screen = shutter + takePicture() def doneCallback(): # Exit settings - global screenMode, settingMode - if screenMode > 3: - settingMode = screenMode - saveSettings() - screenMode = 3 # Switch back to viewfinder mode + global screenMode, settingMode + if screenMode > Mode.VIEWFINDER: + settingMode = screenMode + saveSettings() + screenMode = Mode.VIEWFINDER def imageCallback(n): # Pass 1 (next image), -1 (prev image) or 0 (delete) - global screenMode - if n is 0: - screenMode = 1 # Delete confirmation - else: - showNextImage(n) + global screenMode + if n is 0: + screenMode = Mode.DELETE + else: + showNextImage(n) def deleteCallback(n): # Delete confirmation - global loadIdx, scaled, screenMode, storeMode - screenMode = 0 - screenModePrior = -1 - if n is True: - os.remove(pathData[storeMode] + '/IMG_' + '%04d' % loadIdx + '.JPG') - if(imgRange(pathData[storeMode])): - screen.fill(0) - pygame.display.update() - showNextImage(-1) - else: # Last image deleteted; go to 'no images' mode - screenMode = 2 - scaled = None - loadIdx = -1 + global loadIdx, imgNums, imgSurface, screenMode, storeMode + screenMode = Mode.PLAYBACK + screenModePrior = Mode.UNDEFINED + if n is True: + os.remove(pathData[storeMode] + + '/rpi_' + '%04d' % imgNums[loadIdx] + '.jpg') + os.remove(cacheDir + '/rpi_' + '%04d' % imgNums[loadIdx] + '.jpg') + del imgNums[loadIdx] + if len(imgNums): + screen.fill(0) + pygame.display.update() + showNextImage(-1 if loadIdx==len(imgNums) else 0) + else: # Last image deleteted; go to 'no images' mode + screenMode = Mode.NO_IMAGES + imgSurface = None + loadIdx = -1 def storeModeCallback(n): # Radio buttons on storage settings screen - global storeMode - buttons[4][storeMode + 3].setBg('radio3-0') - storeMode = n - buttons[4][storeMode + 3].setBg('radio3-1') + global pathData, storeMode + buttons[Mode.STORAGE][storeMode + 3].setBg('radio3-0') + storeMode = n + buttons[Mode.STORAGE][storeMode + 3].setBg('radio3-1') + + #create directory if it does not exist + if not os.path.isdir(pathData[storeMode]): + try: + os.makedirs(pathData[storeMode]) + # Set new directory ownership to pi user, mode to 755 + os.chown(pathData[storeMode], uid, gid) + os.chmod(pathData[storeMode], + stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | + stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + except OSError as e: + # errno = 2 if can't create folder + print errno.errorcode[e.errno] + raise SystemExit + + # read all existing image numbers + readImgNumsList(storeMode) def sizeModeCallback(n): # Radio buttons on size settings screen - global sizeMode - buttons[5][sizeMode + 3].setBg('radio3-0') - sizeMode = n - buttons[5][sizeMode + 3].setBg('radio3-1') - camera.resolution = sizeData[sizeMode][1] -# camera.crop = sizeData[sizeMode][2] + global sizeMode + buttons[Mode.SIZE][sizeMode + 3].setBg('radio3-0') + sizeMode = n + buttons[Mode.SIZE][sizeMode + 3].setBg('radio3-1') + camera.resolution = sizeData[sizeMode][1] + +def qualityModeCallback(n): # Radio buttons on quality settings screen + global qualityMode + buttons[Mode.QUALITY][3+qualityMode].setBg('radio3-0') + qualityMode = n + buttons[Mode.QUALITY][3+qualityMode].setBg('radio3-1') # Global stuff ------------------------------------------------------------- -screenMode = 3 # Current screen mode; default = viewfinder -screenModePrior = -1 # Prior screen mode (for detecting changes) -settingMode = 4 # Last-used settings mode (default = storage) -storeMode = 0 # Storage mode; default = Photos folder -storeModePrior = -1 # Prior storage mode (for detecting changes) -sizeMode = 0 # Image size; default = Large -fxMode = 0 # Image effect; default = Normal -isoMode = 0 # ISO settingl default = Auto -iconPath = 'icons' # Subdirectory containing UI bitmaps (PNG format) -saveIdx = -1 # Image index for saving (-1 = none set yet) -loadIdx = -1 # Image index for loading -scaled = None # pygame Surface w/last-loaded image +screenMode = Mode.VIEWFINDER # Current screen mode; default = viewfinder +screenModePrior = Mode.UNDEFINED # Prior screen mode (for detecting changes) +settingMode = Mode.QUIT # Last-used settings mode (default = quit) +storeMode = 0 # Storage mode; default = Photos folder +storeModePrior = -1 # Prior storage mode (for detecting changes) +sizeMode = 0 # Image size; default = Large +fxMode = 0 # Image effect; default = Normal +isoMode = 0 # ISO setting; default = Auto +awbMode = 0 # AWB setting; default = auto +qualityMode = 0 # Quality setting: default = jpg +iconPath = 'icons' # Subdir containing UI bitmaps (PNG format) +loadIdx = -1 # Image index for loading +imgSurface = None # pygame Surface w/last-loaded image + +# list of existing image numbers. Read by storeModeCallback using +# readImgNumsList +imgNums = [] # To use Dropbox uploader, must have previously run the dropbox_uploader.sh # script to set up the app key and such. If this was done as the normal pi @@ -235,15 +302,21 @@ def sizeModeCallback(n): # Radio buttons on size settings screen upconfig = '/home/pi/.dropbox_uploader' sizeData = [ # Camera parameters for different size settings - # Full res Viewfinder Crop window - [(2592, 1944), (320, 240), (0.0 , 0.0 , 1.0 , 1.0 )], # Large - [(1920, 1080), (320, 180), (0.1296, 0.2222, 0.7408, 0.5556)], # Med - [(1440, 1080), (320, 240), (0.2222, 0.2222, 0.5556, 0.5556)]] # Small + # Full res Viewfinder + [(2592, 1944), (320, 240)], # Large + [(1920, 1080), (320, 180)], # Med + [(1440, 1080), (320, 240)]] # Small isoData = [ # Values for ISO settings [ISO value, indicator X position] [ 0, 27], [100, 64], [200, 97], [320, 137], [400, 164], [500, 197], [640, 244], [800, 297]] +# Setting for auto white balance. A fixed list is used because +# we have pre-generated icons. +awbData = [ 'auto', 'off', 'sunlight', + 'cloudy', 'shade', 'tungsten', 'fluorescent', 'incandescent', + 'flash', 'horizon' ] + # A fixed list of image effects is used (rather than polling # camera.IMAGE_EFFECTS) because the latter contains a few elements # that aren't valid (at least in video_port mode) -- e.g. blackboard, @@ -256,6 +329,9 @@ def sizeModeCallback(n): # Radio buttons on size settings screen 'negative', 'colorswap', 'posterise', 'denoise', 'blur', 'film', 'washedout', 'emboss', 'cartoon', 'solarize' ] +# cache directory for thumbnails +cacheDir = '/home/pi/.cache/picam' + pathData = [ '/home/pi/Photos', # Path for storeMode = 0 (Photos folder) '/boot/DCIM/CANON999', # Path for storeMode = 1 (Boot partition) @@ -271,297 +347,300 @@ def sizeModeCallback(n): # Radio buttons on size settings screen # set); trying to reuse those few elements just made for an ugly # tangle of code elsewhere. -buttons = [ - # Screen mode 0 is photo playback - [Button(( 0,188,320, 52), bg='done' , cb=doneCallback), - Button(( 0, 0, 80, 52), bg='prev' , cb=imageCallback, value=-1), - Button((240, 0, 80, 52), bg='next' , cb=imageCallback, value= 1), - Button(( 88, 70,157,102)), # 'Working' label (when enabled) - Button((148,129, 22, 22)), # Spinner (when enabled) - Button((121, 0, 78, 52), bg='trash', cb=imageCallback, value= 0)], - - # Screen mode 1 is delete confirmation - [Button(( 0,35,320, 33), bg='delete'), - Button(( 32,86,120,100), bg='yn', fg='yes', - cb=deleteCallback, value=True), - Button((168,86,120,100), bg='yn', fg='no', - cb=deleteCallback, value=False)], - - # Screen mode 2 is 'No Images' - [Button((0, 0,320,240), cb=doneCallback), # Full screen = button - Button((0,188,320, 52), bg='done'), # Fake 'Done' button - Button((0, 53,320, 80), bg='empty')], # 'Empty' message - - # Screen mode 3 is viewfinder / snapshot - [Button(( 0,188,156, 52), bg='gear', cb=viewCallback, value=0), - Button((164,188,156, 52), bg='play', cb=viewCallback, value=1), - Button(( 0, 0,320,240) , cb=viewCallback, value=2), - Button(( 88, 51,157,102)), # 'Working' label (when enabled) - Button((148, 110,22, 22))], # Spinner (when enabled) - - # Remaining screens are settings modes - - # Screen mode 4 is storage settings - [Button(( 0,188,320, 52), bg='done', cb=doneCallback), - Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), - Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), - Button(( 2, 60,100,120), bg='radio3-1', fg='store-folder', +buttons = [0] * (Mode.LAST+1) # create dummy elements for every screen + +buttons[Mode.PLAYBACK] = [ + Button(( 0,188,320, 52), bg='done' , cb=doneCallback), + Button(( 0, 0, 80, 52), bg='prev' , cb=imageCallback, value=-1), + Button((240, 0, 80, 52), bg='next' , cb=imageCallback, value= 1), + Button(( 88, 70,157,102)), # 'Working' label (when enabled) + Button((148,129, 22, 22)), # Spinner (when enabled) + Button((121, 0, 78, 52), bg='trash', cb=imageCallback, value= 0)] + +buttons[Mode.DELETE] = [ + Button(( 0,35,320, 33), bg='delete'), + Button(( 32,86,120,100), bg='yn', fg='yes',cb=deleteCallback, value=True), + Button((168,86,120,100), bg='yn', fg='no',cb=deleteCallback, value=False)] + +buttons[Mode.NO_IMAGES] = [ + Button((0, 0,320,240), cb=doneCallback), # Full screen = button + Button((0,188,320, 52), bg='done'), # Fake 'Done' button + Button((0, 53,320, 80), bg='empty')] # 'Empty' message + +buttons[Mode.VIEWFINDER] = [ + Button(( 0,188,156, 52), bg='gear', cb=viewCallback, value=0), + Button((164,188,156, 52), bg='play', cb=viewCallback, value=1), + Button(( 0, 0,320,240) , cb=viewCallback, value=2), + Button(( 88, 51,157,102)), # 'Working' label (when enabled) + Button((148, 110,22, 22))] # Spinner (when enabled) + +buttons[Mode.STORAGE] = [ + Button(( 0,188,320, 52), bg='done', cb=doneCallback), + Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), + Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), + Button(( 2, 60,100,120), bg='radio3-1', fg='store-folder', cb=storeModeCallback, value=0), - Button((110, 60,100,120), bg='radio3-0', fg='store-boot', + Button((110, 60,100,120), bg='radio3-0', fg='store-boot', cb=storeModeCallback, value=1), - Button((218, 60,100,120), bg='radio3-0', fg='store-dropbox', + Button((218, 60,100,120), bg='radio3-0', fg='store-dropbox', cb=storeModeCallback, value=2), - Button(( 0, 10,320, 35), bg='storage')], + Button(( 0, 10,320, 35), bg='storage')] - # Screen mode 5 is size settings - [Button(( 0,188,320, 52), bg='done', cb=doneCallback), - Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), - Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), - Button(( 2, 60,100,120), bg='radio3-1', fg='size-l', +buttons[Mode.SIZE] = [ + Button(( 0,188,320, 52), bg='done', cb=doneCallback), + Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), + Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), + Button(( 2, 60,100,120), bg='radio3-1', fg='size-l', cb=sizeModeCallback, value=0), - Button((110, 60,100,120), bg='radio3-0', fg='size-m', + Button((110, 60,100,120), bg='radio3-0', fg='size-m', cb=sizeModeCallback, value=1), - Button((218, 60,100,120), bg='radio3-0', fg='size-s', + Button((218, 60,100,120), bg='radio3-0', fg='size-s', cb=sizeModeCallback, value=2), - Button(( 0, 10,320, 29), bg='size')], - - # Screen mode 6 is graphic effect - [Button(( 0,188,320, 52), bg='done', cb=doneCallback), - Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), - Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), - Button(( 0, 70, 80, 52), bg='prev', cb=fxCallback , value=-1), - Button((240, 70, 80, 52), bg='next', cb=fxCallback , value= 1), - Button(( 0, 67,320, 91), bg='fx-none'), - Button(( 0, 11,320, 29), bg='fx')], - - # Screen mode 7 is ISO - [Button(( 0,188,320, 52), bg='done', cb=doneCallback), - Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), - Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), - Button(( 0, 70, 80, 52), bg='prev', cb=isoCallback , value=-1), - Button((240, 70, 80, 52), bg='next', cb=isoCallback , value= 1), - Button(( 0, 79,320, 33), bg='iso-0'), - Button(( 9,134,302, 26), bg='iso-bar'), - Button(( 17,157, 21, 19), bg='iso-arrow'), - Button(( 0, 10,320, 29), bg='iso')], - - # Screen mode 8 is quit confirmation - [Button(( 0,188,320, 52), bg='done' , cb=doneCallback), - Button(( 0, 0, 80, 52), bg='prev' , cb=settingCallback, value=-1), - Button((240, 0, 80, 52), bg='next' , cb=settingCallback, value= 1), - Button((110, 60,100,120), bg='quit-ok', cb=quitCallback), - Button(( 0, 10,320, 35), bg='quit')] -] - + Button(( 0, 10,320, 29), bg='size')] + +buttons[Mode.EFFECT] = [ + Button(( 0,188,320, 52), bg='done', cb=doneCallback), + Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), + Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), + Button(( 0, 70, 80, 52), bg='prev', cb=fxCallback , value=-1), + Button((240, 70, 80, 52), bg='next', cb=fxCallback , value= 1), + Button(( 0, 67,320, 91), bg='fx-none'), + Button(( 0, 11,320, 29), bg='fx')] + +buttons[Mode.ISO] = [ + Button(( 0,188,320, 52), bg='done', cb=doneCallback), + Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), + Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), + Button(( 0, 70, 80, 52), bg='prev', cb=isoCallback , value=-1), + Button((240, 70, 80, 52), bg='next', cb=isoCallback , value= 1), + Button(( 0, 79,320, 33), bg='iso-0'), + Button(( 9,134,302, 26), bg='iso-bar'), + Button(( 17,157, 21, 19), bg='iso-arrow'), + Button(( 0, 10,320, 29), bg='iso')] + +buttons[Mode.AWB] = [ + Button(( 0,188,320, 52), bg='done', cb=doneCallback), + Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), + Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), + Button(( 0, 70, 80, 52), bg='prev', cb=awbCallback , value=-1), + Button((240, 70, 80, 52), bg='next', cb=awbCallback , value= 1), + Button(( 0, 67,320, 91), bg='awb-auto'), + Button(( 0, 11,320, 29), bg='awb')] + +buttons[Mode.QUALITY] = [ + Button(( 0,188,320, 52), bg='done', cb=doneCallback), + Button(( 0, 0, 80, 52), bg='prev', cb=settingCallback, value=-1), + Button((240, 0, 80, 52), bg='next', cb=settingCallback, value= 1), + Button(( 32, 60,100,120), bg='radio3-1', fg='quality-jpg', + cb=qualityModeCallback, value=0), + Button((188, 60,100,120), bg='radio3-0', fg='quality-jpg+raw', + cb=qualityModeCallback, value=1), + Button(( 0, 10,320, 35), bg='quality')] + +buttons[Mode.QUIT] = [ + Button(( 0,188,320, 52), bg='done' , cb=doneCallback), + Button(( 0, 0, 80, 52), bg='prev' , cb=settingCallback, value=-1), + Button((240, 0, 80, 52), bg='next' , cb=settingCallback, value= 1), + Button((110, 60,100,120), bg='quit-ok', cb=quitCallback), + Button(( 0, 10,320, 35), bg='quit')] # Assorted utility functions ----------------------------------------------- def setFxMode(n): - global fxMode - fxMode = n - camera.image_effect = fxData[fxMode] - buttons[6][5].setBg('fx-' + fxData[fxMode]) + global fxMode + fxMode = n + camera.image_effect = fxData[fxMode] + buttons[Mode.EFFECT][5].setBg('fx-' + fxData[fxMode]) def setIsoMode(n): - global isoMode - isoMode = n - camera.ISO = isoData[isoMode][0] - buttons[7][5].setBg('iso-' + str(isoData[isoMode][0])) - buttons[7][7].rect = ((isoData[isoMode][1] - 10,) + - buttons[7][7].rect[1:]) + global isoMode + isoMode = n + camera.ISO = isoData[isoMode][0] + buttons[Mode.ISO][5].setBg('iso-' + str(isoData[isoMode][0])) + buttons[Mode.ISO][7].rect = ((isoData[isoMode][1] - 10,) + + buttons[Mode.ISO][7].rect[1:]) + +def setAwbMode(n): + global awbMode + awbMode = n + buttons[Mode.AWB][5].setBg('awb-' + awbData[awbMode]) + camera.awb_mode = awbData[awbMode] + if awbData[awbMode] == 'off': + camera.awb_gains = (1.0,1.0) # needed because of ignorant engineers + # record white-balance in exif. Too bad exif-tags only allow auto/manual. + if awbData[awbMode] == 'auto': + camera.exif_tags['EXIF.WhiteBalance'] = '0' + else: + camera.exif_tags['EXIF.WhiteBalance'] = '1' def saveSettings(): - try: - outfile = open('cam.pkl', 'wb') - # Use a dictionary (rather than pickling 'raw' values) so - # the number & order of things can change without breaking. - d = { 'fx' : fxMode, - 'iso' : isoMode, - 'size' : sizeMode, - 'store' : storeMode } - pickle.dump(d, outfile) - outfile.close() - except: - pass + try: + outfile = open(os.path.expanduser('~')+'/cam.pkl', 'wb') + # Use a dictionary (rather than pickling 'raw' values) so + # the number & order of things can change without breaking. + d = { 'fx' : fxMode, + 'iso' : isoMode, + 'awb' : awbMode, + 'quality' : qualityMode, + 'size' : sizeMode, + 'store' : storeMode } + pickle.dump(d, outfile) + outfile.close() + except: + pass def loadSettings(): - try: - infile = open('cam.pkl', 'rb') - d = pickle.load(infile) - infile.close() - if 'fx' in d: setFxMode( d['fx']) - if 'iso' in d: setIsoMode( d['iso']) - if 'size' in d: sizeModeCallback( d['size']) - if 'store' in d: storeModeCallback(d['store']) - except: - pass - -# Scan files in a directory, locating JPEGs with names matching the -# software's convention (IMG_XXXX.JPG), returning a tuple with the -# lowest and highest indices (or None if no matching files). -def imgRange(path): - min = 9999 - max = 0 - try: - for file in os.listdir(path): - if fnmatch.fnmatch(file, 'IMG_[0-9][0-9][0-9][0-9].JPG'): - i = int(file[4:8]) - if(i < min): min = i - if(i > max): max = i - finally: - return None if min > max else (min, max) + try: + infile = open(os.path.expanduser('~')+'/cam.pkl', 'rb') + d = pickle.load(infile) + infile.close() + if 'fx' in d: setFxMode( d['fx']) + if 'iso' in d: setIsoMode( d['iso']) + if 'awb' in d: setAwbMode( d['awb']) + if 'quality' in d: qualityModeCallback(d['quality']) + if 'size' in d: sizeModeCallback( d['size']) + if 'store' in d: storeModeCallback(d['store']) + except: + storeModeCallback(storeMode) + +# Read existing numbers into imgNums. Triggerd by a change of +# storeMode. +def readImgNumsList(n): + global pathData, imgNums + imgNums = [] + for file in os.listdir(pathData[n]): + if fnmatch.fnmatch(file,'rpi_[0-9][0-9][0-9][0-9].jpg'): + imgNums.append(int(file[4:8])) + imgNums.sort() # Busy indicator. To use, run in separate thread, set global 'busy' # to False when done. def spinner(): - global busy, screenMode, screenModePrior - - buttons[screenMode][3].setBg('working') - buttons[screenMode][3].draw(screen) - pygame.display.update() + global busy, screenMode, screenModePrior - busy = True - n = 0 - while busy is True: - buttons[screenMode][4].setBg('work-' + str(n)) - buttons[screenMode][4].draw(screen) - pygame.display.update() - n = (n + 1) % 5 - time.sleep(0.15) + buttons[screenMode][3].setBg('working') + buttons[screenMode][3].draw(screen) + pygame.display.update() - buttons[screenMode][3].setBg(None) - buttons[screenMode][4].setBg(None) - screenModePrior = -1 # Force refresh + busy = True + n = 0 + while busy is True: + buttons[screenMode][4].setBg('work-' + str(n)) + buttons[screenMode][4].draw(screen) + pygame.display.update() + n = (n + 1) % 5 + time.sleep(0.15) + + buttons[screenMode][3].setBg(None) + buttons[screenMode][4].setBg(None) + screenModePrior = Mode.UNDEFINED # Force refresh + +def saveThumbnail(fname,tname): # fname: filename with extension + metadata = pyexiv2.ImageMetadata(fname) + metadata.read() + thumb = metadata.exif_thumbnail + thumb.write_to_file(tname) # tname: thumbname without extension + os.chown(tname+".jpg", uid, gid) + os.chmod(tname+".jpg", + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) def takePicture(): - global busy, gid, loadIdx, saveIdx, scaled, sizeMode, storeMode, storeModePrior, uid - - if not os.path.isdir(pathData[storeMode]): - try: - os.makedirs(pathData[storeMode]) - # Set new directory ownership to pi user, mode to 755 - os.chown(pathData[storeMode], uid, gid) - os.chmod(pathData[storeMode], - stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | - stat.S_IRGRP | stat.S_IXGRP | - stat.S_IROTH | stat.S_IXOTH) - except OSError as e: - # errno = 2 if can't create folder - print errno.errorcode[e.errno] - return - - # If this is the first time accessing this directory, - # scan for the max image index, start at next pos. - if storeMode != storeModePrior: - r = imgRange(pathData[storeMode]) - if r is None: - saveIdx = 1 - else: - saveIdx = r[1] + 1 - if saveIdx > 9999: saveIdx = 0 - storeModePrior = storeMode - - # Scan for next available image slot - while True: - filename = pathData[storeMode] + '/IMG_' + '%04d' % saveIdx + '.JPG' - if not os.path.isfile(filename): break - saveIdx += 1 - if saveIdx > 9999: saveIdx = 0 - - t = threading.Thread(target=spinner) - t.start() - - scaled = None - camera.resolution = sizeData[sizeMode][0] - camera.crop = sizeData[sizeMode][2] - try: - camera.capture(filename, use_video_port=False, format='jpeg', - thumbnail=None) - # Set image file ownership to pi user, mode to 644 - # os.chown(filename, uid, gid) # Not working, why? - os.chmod(filename, - stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) - img = pygame.image.load(filename) - scaled = pygame.transform.scale(img, sizeData[sizeMode][1]) - if storeMode == 2: # Dropbox - if upconfig: - cmd = uploader + ' -f ' + upconfig + ' upload ' + filename + ' Photos/' + os.path.basename(filename) - else: - cmd = uploader + ' upload ' + filename + ' Photos/' + os.path.basename(filename) - call ([cmd], shell=True) - - finally: - # Add error handling/indicator (disk full, etc.) - camera.resolution = sizeData[sizeMode][1] - camera.crop = (0.0, 0.0, 1.0, 1.0) - - busy = False - t.join() - - if scaled: - if scaled.get_height() < 240: # Letterbox - screen.fill(0) - screen.blit(scaled, - ((320 - scaled.get_width() ) / 2, - (240 - scaled.get_height()) / 2)) - pygame.display.update() - time.sleep(2.5) - loadIdx = saveIdx + global busy, gid, loadIdx, imgSurface, sizeMode, storeMode, storeModePrior, uid, cacheDir, imgNums + + saveNum = imgNums[-1] + 1 % 10000 if len(imgNums) else 0 + filename = pathData[storeMode] + '/rpi_' + '%04d' % saveNum + '.jpg' + cachename = cacheDir+'/rpi_' + '%04d' % saveNum + + t = threading.Thread(target=spinner) + t.start() + + imgSurface = None + camera.resolution = sizeData[sizeMode][0] + try: + camera.capture(filename, use_video_port=False, format='jpeg', + thumbnail=(340,240,60),bayer=qualityMode==1) + imgNums.append(saveNum) + # Set image file ownership to pi user, mode to 644 + os.chown(filename, uid, gid) + os.chmod(filename, + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) + saveThumbnail(filename,cachename) + imgSurface = pygame.image.load(cachename+'.jpg') + if storeMode == 2: # Dropbox + if upconfig: + cmd = uploader + ' -f ' + upconfig + ' upload ' + filename + ' Photos/' + os.path.basename(filename) + else: + cmd = uploader + ' upload ' + filename + ' Photos/' + os.path.basename(filename) + call ([cmd], shell=True) + + finally: + # Add error handling/indicator (disk full, etc.) + camera.resolution = sizeData[sizeMode][1] + + busy = False + t.join() + + if imgSurface: + if imgSurface.get_height() < 240: # Letterbox + screen.fill(0) + screen.blit(imgSurface, + ((320 - imgSurface.get_width() ) / 2, + (240 - imgSurface.get_height()) / 2)) + pygame.display.update() + time.sleep(2.5) + loadIdx = len(imgNums)-1 def showNextImage(direction): - global busy, loadIdx + global busy, loadIdx, imgNums - t = threading.Thread(target=spinner) - t.start() + loadIdx += direction + if loadIdx == len(imgNums): # past end of list, continue at beginning + loadIdx = 0 + elif loadIdx == -1: # before start of list, continue with end of list + loadIdx = len(imgNums)-1 + showImage(loadIdx) - n = loadIdx - while True: - n += direction - if(n > 9999): n = 0 - elif(n < 0): n = 9999 - if os.path.exists(pathData[storeMode]+'/IMG_'+'%04d'%n+'.JPG'): - showImage(n) - break +def showImage(n): + global busy, imgNums, imgSurface, screenMode, screenModePrior, storeMode, pathData - busy = False - t.join() + t = threading.Thread(target=spinner) + t.start() -def showImage(n): - global busy, loadIdx, scaled, screenMode, screenModePrior, sizeMode, storeMode + cachefile = cacheDir + '/rpi_' + '%04d' % imgNums[n] + '.jpg' - t = threading.Thread(target=spinner) - t.start() + # if cachefile does not exist, recreate it + if not os.path.exists(cachefile): + filename = pathData[storeMode] + '/rpi_' + '%04d' % imgNums[n] + '.jpg' + saveThumbnail(filename,cacheDir + '/rpi_' + '%04d' % imgNums[n]) - img = pygame.image.load( - pathData[storeMode] + '/IMG_' + '%04d' % n + '.JPG') - scaled = pygame.transform.scale(img, sizeData[sizeMode][1]) - loadIdx = n + imgSurface = pygame.image.load(cachefile) - busy = False - t.join() + busy = False + t.join() - screenMode = 0 # Photo playback - screenModePrior = -1 # Force screen refresh + screenMode = Mode.PLAYBACK + screenModePrior = Mode.UNDEFINED # Force screen refresh # Initialization ----------------------------------------------------------- +# fix iconPath: it's relative to the executable +thisDir,thisFile = os.path.split(sys.argv[0]) +iconPath = thisDir + os.path.sep + iconPath + # Init framebuffer/touchscreen environment variables os.putenv('SDL_VIDEODRIVER', 'fbcon') os.putenv('SDL_FBDEV' , '/dev/fb1') os.putenv('SDL_MOUSEDRV' , 'TSLIB') os.putenv('SDL_MOUSEDEV' , '/dev/input/touchscreen') -# Get user & group IDs for file & folder creation -# (Want these to be 'pi' or other user, not root) -s = os.getenv("SUDO_UID") -uid = int(s) if s else os.getuid() -s = os.getenv("SUDO_GID") -gid = int(s) if s else os.getgid() +# running as root from /etc/rc.local and not under sudo control, so +# query ids directly +uid = pwd.getpwnam("pi").pw_uid +gid = pwd.getpwnam("pi").pw_gid # Buffers for viewfinder data rgb = bytearray(320 * 240 * 3) -yuv = bytearray(320 * 240 * 3 / 2) # Init pygame and screen pygame.init() @@ -572,9 +651,6 @@ def showImage(n): camera = picamera.PiCamera() atexit.register(camera.close) camera.resolution = sizeData[sizeMode][1] -#camera.crop = sizeData[sizeMode][2] -camera.crop = (0.0, 0.0, 1.0, 1.0) -# Leave raw format at default YUV, don't touch, don't set to RGB! # Load all icons at startup. for file in os.listdir(iconPath): @@ -592,8 +668,22 @@ def showImage(n): b.iconFg = i b.fg = None -loadSettings() # Must come last; fiddles with Button/Icon states +# one-time initialization of cache-directory +if not os.path.isdir(cacheDir): + try: + os.makedirs(cacheDir) + # Set new directory ownership to pi user, mode to 755 + os.chown(cacheDir, uid, gid) + os.chmod(cacheDir, + stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | + stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + except OSError as e: + # errno = 2 if can't create folder + print errno.errorcode[e.errno] + raise SystemExit +loadSettings() # Must come last; fiddles with Button/Icon states # Main loop ---------------------------------------------------------------- @@ -610,22 +700,21 @@ def showImage(n): # and refresh the display to show the live preview. In other modes # (image playback, etc.), stop and refresh the screen only when # screenMode changes. - if screenMode >= 3 or screenMode != screenModePrior: break + if screenMode >= Mode.VIEWFINDER or screenMode != screenModePrior: break # Refresh display - if screenMode >= 3: # Viewfinder or settings modes + if screenMode >= Mode.VIEWFINDER: # Viewfinder or settings modes stream = io.BytesIO() # Capture into in-memory stream - camera.capture(stream, use_video_port=True, format='raw') + camera.capture(stream, resize=sizeData[sizeMode][1], + use_video_port=True, format='rgb') stream.seek(0) - stream.readinto(yuv) # stream -> YUV buffer + stream.readinto(rgb) stream.close() - yuv2rgb.convert(yuv, rgb, sizeData[sizeMode][1][0], - sizeData[sizeMode][1][1]) img = pygame.image.frombuffer(rgb[0: (sizeData[sizeMode][1][0] * sizeData[sizeMode][1][1] * 3)], sizeData[sizeMode][1], 'RGB') - elif screenMode < 2: # Playback mode or delete confirmation - img = scaled # Show last-loaded image + elif screenMode in [Mode.PLAYBACK, Mode.DELETE]: + img = imgSurface # Show last-loaded image else: # 'No Photos' mode img = None # You get nothing, good day sir diff --git a/icons/awb-auto.png b/icons/awb-auto.png new file mode 100644 index 0000000..f24baa9 Binary files /dev/null and b/icons/awb-auto.png differ diff --git a/icons/awb-cloudy.png b/icons/awb-cloudy.png new file mode 100644 index 0000000..6a36d70 Binary files /dev/null and b/icons/awb-cloudy.png differ diff --git a/icons/awb-flash.png b/icons/awb-flash.png new file mode 100644 index 0000000..e973758 Binary files /dev/null and b/icons/awb-flash.png differ diff --git a/icons/awb-fluorescent.png b/icons/awb-fluorescent.png new file mode 100644 index 0000000..39b2af2 Binary files /dev/null and b/icons/awb-fluorescent.png differ diff --git a/icons/awb-horizon.png b/icons/awb-horizon.png new file mode 100644 index 0000000..7d3174e Binary files /dev/null and b/icons/awb-horizon.png differ diff --git a/icons/awb-incandescent.png b/icons/awb-incandescent.png new file mode 100644 index 0000000..b028bfb Binary files /dev/null and b/icons/awb-incandescent.png differ diff --git a/icons/awb-off.png b/icons/awb-off.png new file mode 100644 index 0000000..21b2a7a Binary files /dev/null and b/icons/awb-off.png differ diff --git a/icons/awb-shade.png b/icons/awb-shade.png new file mode 100644 index 0000000..7824a3d Binary files /dev/null and b/icons/awb-shade.png differ diff --git a/icons/awb-sunlight.png b/icons/awb-sunlight.png new file mode 100644 index 0000000..ae09ece Binary files /dev/null and b/icons/awb-sunlight.png differ diff --git a/icons/awb-tungsten.png b/icons/awb-tungsten.png new file mode 100644 index 0000000..8087a6f Binary files /dev/null and b/icons/awb-tungsten.png differ diff --git a/icons/awb.png b/icons/awb.png new file mode 100644 index 0000000..f181815 Binary files /dev/null and b/icons/awb.png differ diff --git a/icons/quality-jpg+raw.png b/icons/quality-jpg+raw.png new file mode 100644 index 0000000..2592614 Binary files /dev/null and b/icons/quality-jpg+raw.png differ diff --git a/icons/quality-jpg.png b/icons/quality-jpg.png new file mode 100644 index 0000000..2597e32 Binary files /dev/null and b/icons/quality-jpg.png differ diff --git a/icons/quality.png b/icons/quality.png new file mode 100644 index 0000000..5517806 Binary files /dev/null and b/icons/quality.png differ diff --git a/yuv2rgb.c b/yuv2rgb.c deleted file mode 100644 index 68557ec..0000000 --- a/yuv2rgb.c +++ /dev/null @@ -1,78 +0,0 @@ -/* YUV->RGB conversion for Raspberry Pi camera, because fast video - port doesn't currently support raw RGB capture, only YUV. - python-dev required to build. - - Adafruit invests time and resources providing this open source code, - please support Adafruit and open-source development by purchasing - products from Adafruit, thanks! - - Written by Phil Burgess / Paint Your Dragon for Adafruit Industries. - BSD license, all text above must be included in any redistribution. */ - -#include - -static PyObject *convert(PyObject *self, PyObject *args) { - Py_buffer inBuf, outBuf; - short row, col, r, g, b, w, h, rd, gd, bd; - unsigned char *rgbPtr, *yPtr, y; - signed char *uPtr, *vPtr, u, v; - - if(!PyArg_ParseTuple(args, "s*s*hh", &inBuf, &outBuf, &w, &h)) - return NULL; - - if(w & 31) w += 32 - (w & 31); // Round up width to multiple of 32 - if(h & 15) h += 16 - (h & 15); // Round up height to multiple of 16 - - rgbPtr = outBuf.buf; - yPtr = inBuf.buf; - uPtr = (signed char *)&yPtr[w * h]; - vPtr = &uPtr[(w * h) >> 2]; - w >>= 1; // 2 columns processed per iteration - - for(row=0; row> 8; - gd = ((183 * v) + (88 * u)) >> 8; - bd = (454 * u) >> 8; - // Even column - y = *yPtr++; - r = y + rd; - g = y - gd; - b = y + bd; - *rgbPtr++ = (r > 255) ? 255 : (r < 0) ? 0 : r; - *rgbPtr++ = (g > 255) ? 255 : (g < 0) ? 0 : g; - *rgbPtr++ = (b > 255) ? 255 : (b < 0) ? 0 : b; - // Odd column - y = *yPtr++; - r = y + rd; - g = y - gd; - b = y + bd; - *rgbPtr++ = (r > 255) ? 255 : (r < 0) ? 0 : r; - *rgbPtr++ = (g > 255) ? 255 : (g < 0) ? 0 : g; - *rgbPtr++ = (b > 255) ? 255 : (b < 0) ? 0 : b; - } - if(row & 1) { - uPtr += w; - vPtr += w; - } - } - - PyBuffer_Release(&inBuf); - PyBuffer_Release(&outBuf); - - Py_INCREF(Py_None); - return Py_None; -} - -static PyMethodDef yuv2rgb_methods[] = { - {"convert", convert, METH_VARARGS}, - {NULL,NULL} -}; - -PyMODINIT_FUNC inityuv2rgb(void) { - (void)Py_InitModule("yuv2rgb", yuv2rgb_methods); -} - diff --git a/yuv2rgb.so b/yuv2rgb.so deleted file mode 100755 index b39c3fa..0000000 Binary files a/yuv2rgb.so and /dev/null differ