refinery
RAW file processor

util/licensed/gpl/convert.py

#!/usr/bin/env python

# Copyright (c) 2010 Adam Hooper
#
# This file is part of refinery (GPL portion).
#
# refinery (GPL portion) is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# refinery (GPL portion) is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with refinery (GPL portion).  If not, see
# <http://www.gnu.org/licenses/>.

import refinery
import pyexiv2 # A GPLed dependency, hence we're GPL too
using_pil = False
try:
  import PIL.Image
  print('Will use Python Imaging Library (PIL) to save the image')
  using_pil = True
except ImportError:
  print('Python Imaging Library (PIL) not found; will save as possibly-misoriented PPM')

# refinery doesn't use Exiv2::ExifData pointers directly (because of
# licensing), so we can't create a refinery::Exiv2ExifData pointer as we would
# in C++.
#
# The solution: create its parent, a refinery::ExifData. This Python subclass
# is functionally equivalent to a refinery::Exiv2ExifData pointer. We'll pass
# it to all our C++ methods as if it were a C++ pointer. (To them, it is one.)
class PyExifData(refinery.ExifData):
  def __init__(self, exivImage):
    refinery.ExifData.__init__(self)
    self.exivImage = exivImage

  def hasKey(self, key):
    return key in self.exivImage

  def getBytes(self, key, bytes):
    # bytes is an std::vector<unsigned char>
    tag = self.exivImage[key]
    value = None
    if tag.type == 'Undefined':
      value = [ord(c) for c in tag.value]
    else:
      value = [int(s) for s in tag.value.split()]
    bytes.clear()
    bytes.reserve(len(value))
    for b in value:
      bytes.append(b)

  def getInt(self, key):
    value = self.exivImage[key].value
    # Sometimes the value is erroneously a list and not an int
    # example: Exif.Image.Orientation
    if isinstance(value, list): value = value[0]
    return int(value)

  def getFloat(self, key):
    value = self.exivImage[key].value
    return float(value)

  def getString(self, key):
    value = self.exivImage[key].value
    return str(value)

  def setString(self, key, s):
    exivImage[key] = s

# Here's the image processing chain, from read to write
def convert(infile, outfile):
  print('Loading metadata from %s...' % infile)

  exivImage = pyexiv2.ImageMetadata(infile)
  exivImage.read()
  exifData = PyExifData(exivImage)

  print('Reading grayscale image...')
  reader = refinery.ImageReader()
  f = open(infile)
  grayImage = reader.readGrayImage(f, exifData)

  print('Scaling to full 16-bit color...')
  refinery.ScaleColorsFilter().filter(grayImage)

  print('Interpolating RGB values...')
  interpolator = refinery.Interpolator(refinery.Interpolator.INTERPOLATE_AHD)
  rgbImage = interpolator.interpolate(grayImage)

  print('Converting from camera RGB to sRGB...')
  refinery.ConvertToRgbFilter().filter(rgbImage)

  print('Gamma-correcting...')
  histogram = refinery.RGBHistogram(rgbImage)
  gammaCurve = refinery.GammaCurveU16(histogram)
  refinery.GammaFilter().filter(rgbImage, gammaCurve)

  if using_pil:
    print('Transforming into Python Imaging Library (PIL) Image...')
    width = rgbImage.width()
    height = rgbImage.height()
    ba = bytes('\0') * (width * height * 3)
    rgbImage.fillRgb8(ba)
    pilImage = PIL.Image.fromstring('RGB', (width, height), ba)

    print('Finding image orientation...')
    orientation = exifData.getInt('Exif.Image.Orientation')
    if orientation == 1:
      print('(original is fine, no reorientation needed)')
    elif orientation == 2:
      print('(original is flipped horizontally)')
      pilImage = pilImage.transpose(PIL.Image.FLIP_LEFT_RIGHT)
    elif orientation == 3:
      print('(original is rotated 180 degrees)')
      pilImage = pilImage.transpose(PIL.Image.ROTATE_180)
    elif orientation == 4:
      print('(original is flipped vertically)')
      pilImage = pilImage.transpose(PIL.Image.FLIP_TOP_BOTTOM)
    elif orientation == 5:
      print('(original is flipped by a top-left/bottom-right mirror')
      pilImage = pilImage.transpose(PIL.Image.ROTATE_270).transpose(PIL.Image.FLIP_TOP_BOTTOM)
    elif orientation == 6:
      print('(original is rotated 90 degrees counter-clockwise)')
      pilImage = pilImage.transpose(PIL.Image.ROTATE_90)
    elif orientation == 7:
      print('(original is flipped horizontally and rotated 90 degrees clockwise)')
      pilImage = pilImage.transpose(PIL.Image.ROTATE_90).transpose(PIL.Image.FLIP_TOP_BOTTOM)
    elif orientation == 8:
      print('(original is rotated 90 degrees clockwise)')
      pilImage = pilImage.transpose(PIL.Image.ROTATE_270)
    else:
      print('Error! Invalid orientation "%r", ignoring' % orientation)

    print('Writing image to %s...' % outfile)
    pilImage.save(outfile)
  else:
    print('Writing image to %s... (8-bit PPM)' % outfile)
    writer = refinery.ImageWriter()
    writer.writeImage(rgbImage, outfile, 8)

  print('Done!')

if __name__ == '__main__':
  import sys

  if len(sys.argv) != 3:
    print >>sys.stderr, 'Usage: %s <infile> <outfile>' % sys.argv[0]
    sys.exit(1)

  convert(sys.argv[1], sys.argv[2])
 All Classes Functions Variables Typedefs Enumerations Enumerator