[#154 state:fixed] Added exif orientation support.

This commit is contained in:
Virgil Dupras 2011-05-31 10:05:12 -04:00
parent cc7ccff48e
commit 1f26fbeacc
4 changed files with 134 additions and 35 deletions

View File

@ -30,14 +30,12 @@ class Photo(PhotoBase):
HANDLED_EXTS = PhotoBase.HANDLED_EXTS.copy()
HANDLED_EXTS.update({'psd', 'nef', 'cr2'})
def _read_info(self, field):
PhotoBase._read_info(self, field)
if field == 'dimensions':
self.dimensions = _block_osx.get_image_size(str(self.path))
def _plat_get_dimensions(self):
return _block_osx.get_image_size(str(self.path))
def get_blocks(self, block_count_per_side):
def _plat_get_blocks(self, block_count_per_side, orientation):
try:
blocks = _block_osx.getblocks(str(self.path), block_count_per_side)
blocks = _block_osx.getblocks(str(self.path), block_count_per_side, orientation)
except Exception as e:
raise IOError('The reading of "%s" failed with "%s"' % (str(self.path), str(e)))
if not blocks:

View File

@ -11,6 +11,8 @@
#import <Foundation/Foundation.h>
#define RADIANS( degrees ) ( degrees * M_PI / 180 )
static CFStringRef
pystring2cfstring(PyObject *pystring)
{
@ -149,12 +151,10 @@ static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
CFURLRef image_url;
CGImageSourceRef source;
CGImageRef image;
size_t width, height;
int block_count, block_width, block_height, i;
size_t width, height, image_width, image_height;
int block_count, block_width, block_height, orientation, i;
width = 0;
height = 0;
if (!PyArg_ParseTuple(args, "Oi", &path, &block_count)) {
if (!PyArg_ParseTuple(args, "Oii", &path, &block_count, &orientation)) {
return NULL;
}
@ -163,6 +163,10 @@ static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
return NULL;
}
if ((orientation > 8) || (orientation < 0)) {
orientation = 0; // simplifies checks later since we can only have values in 0-8
}
image_path = pystring2cfstring(path);
if (image_path == NULL) {
return PyErr_NoMemory();
@ -182,13 +186,61 @@ static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
return PyErr_NoMemory();
}
width = CGImageGetWidth(image);
height = CGImageGetHeight(image);
CGContextRef myContext = MyCreateBitmapContext(width, height);
CGRect myBoundingBox = CGRectMake(0, 0, width, height);
CGContextDrawImage(myContext, myBoundingBox, image);
unsigned char *bitmapData = CGBitmapContextGetData(myContext);
CGContextRelease(myContext);
width = image_width = CGImageGetWidth(image);
height = image_height = CGImageGetHeight(image);
if (orientation >= 5) {
// orientations 5-8 rotate the photo sideways, so we have to swap width and height
width = image_height;
height = image_width;
}
CGContextRef context = MyCreateBitmapContext(width, height);
if (orientation == 2) {
// Flip X
CGContextTranslateCTM(context, width, 0);
CGContextScaleCTM(context, -1, 1);
}
else if (orientation == 3) {
// Rot 180
CGContextTranslateCTM(context, width, height);
CGContextRotateCTM(context, RADIANS(180));
}
else if (orientation == 4) {
// Flip Y
CGContextTranslateCTM(context, 0, height);
CGContextScaleCTM(context, 1, -1);
}
else if (orientation == 5) {
// Flip X + Rot CW 90
CGContextTranslateCTM(context, width, 0);
CGContextScaleCTM(context, -1, 1);
CGContextTranslateCTM(context, 0, height);
CGContextRotateCTM(context, RADIANS(-90));
}
else if (orientation == 6) {
// Rot CW 90
CGContextTranslateCTM(context, 0, height);
CGContextRotateCTM(context, RADIANS(-90));
}
else if (orientation == 7) {
// Rot CCW 90 + Flip X
CGContextTranslateCTM(context, width, 0);
CGContextScaleCTM(context, -1, 1);
CGContextTranslateCTM(context, width, 0);
CGContextRotateCTM(context, RADIANS(90));
}
else if (orientation == 8) {
// Rot CCW 90
CGContextTranslateCTM(context, width, 0);
CGContextRotateCTM(context, RADIANS(90));
}
CGRect myBoundingBox = CGRectMake(0, 0, image_width, image_height);
CGContextDrawImage(context, myBoundingBox, image);
unsigned char *bitmapData = CGBitmapContextGetData(context);
CGContextRelease(context);
CGImageRelease(image);
CFRelease(source);
if (bitmapData == NULL) {

View File

@ -6,8 +6,10 @@
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license
from hscommon import io
from hscommon.util import get_file_ext
from core import fs
from . import exif
class Photo(fs.File):
INITIAL_INFO = fs.File.INITIAL_INFO.copy()
@ -17,10 +19,35 @@ class Photo(fs.File):
# These extensions are supported on all platforms
HANDLED_EXTS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'tif'}
def _plat_get_dimensions(self):
raise NotImplementedError()
def _plat_get_blocks(self, block_count_per_side, orientation):
raise NotImplementedError()
def _get_orientation(self):
if not hasattr(self, '_cached_orientation'):
try:
with io.open(self.path, 'rb') as fp:
exifdata = exif.get_fields(fp)
# the value is a list (probably one-sized) of ints
orientations = exifdata['Orientation']
self._cached_orientation = orientations[0]
except Exception: # Couldn't read EXIF data, no transforms
self._cached_orientation = 0
return self._cached_orientation
@classmethod
def can_handle(cls, path):
return fs.File.can_handle(path) and get_file_ext(path[-1]) in cls.HANDLED_EXTS
def get_blocks(self, block_count_per_side):
raise NotImplementedError()
def _read_info(self, field):
fs.File._read_info(self, field)
if field == 'dimensions':
self.dimensions = self._plat_get_dimensions()
if self._get_orientation() in {5, 6, 7, 8}:
self.dimensions = (self.dimensions[1], self.dimensions[0])
def get_blocks(self, block_count_per_side):
return self._plat_get_blocks(block_count_per_side, self._get_orientation())

View File

@ -9,7 +9,7 @@
import os.path as op
import logging
from PyQt4.QtGui import QImage, QImageReader
from PyQt4.QtGui import QImage, QImageReader, QTransform
from core_pe import data as data_pe, __appname__
from core_pe.photo import Photo as PhotoBase
@ -23,23 +23,45 @@ from .preferences import Preferences
from .preferences_dialog import PreferencesDialog
class File(PhotoBase):
def _read_info(self, field):
PhotoBase._read_info(self, field)
if field == 'dimensions':
try:
ir = QImageReader(str(self.path))
size = ir.size()
if size.isValid():
self.dimensions = (size.width(), size.height())
else:
self.dimensions = (0, 0)
except EnvironmentError:
self.dimensions = (0, 0)
logging.warning("Could not read image '%s'", str(self.path))
def _plat_get_dimensions(self):
try:
ir = QImageReader(str(self.path))
size = ir.size()
if size.isValid():
return (size.width(), size.height())
else:
return (0, 0)
except EnvironmentError:
logging.warning("Could not read image '%s'", str(self.path))
return (0, 0)
def get_blocks(self, block_count_per_side):
def _plat_get_blocks(self, block_count_per_side, orientation):
image = QImage(str(self.path))
image = image.convertToFormat(QImage.Format_RGB888)
# MYSTERY TO SOLVE: For reasons I cannot explain, orientations 5 and 7 don't work for
# duplicate scanning. The transforms seems to work fine (if I try to save the image after
# the transform, we see that the image has been correctly flipped and rotated), but the
# analysis part yields wrong blocks. I spent enought time with this feature, so I'll leave
# like that for now. (by the way, orientations 5 and 7 work fine under Cocoa)
if 2 <= orientation <= 8:
t = QTransform()
if orientation == 2:
t.scale(-1, 1)
elif orientation == 3:
t.rotate(180)
elif orientation == 4:
t.scale(1, -1)
elif orientation == 5:
t.scale(-1, 1)
t.rotate(90)
elif orientation == 6:
t.rotate(90)
elif orientation == 7:
t.scale(-1, 1)
t.rotate(270)
elif orientation == 8:
t.rotate(270)
image = image.transformed(t)
return getblocks(image, block_count_per_side)