Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKeshav Sharma <keshav7890@gmail.com>2011-07-03 11:22:14 (GMT)
committer Keshav Sharma <keshav7890@gmail.com>2011-07-03 11:22:14 (GMT)
commitca10d560496dc9f6edde8b422fe591c96c175a6d (patch)
tree5efdf45be57028217d8f742651301ec234824401
added activity folder
-rw-r--r--ImageProcess.py523
-rw-r--r--ImageProcessorActivity.py564
-rw-r--r--MANIFEST116
-rw-r--r--PIL/ArgImagePlugin.py498
-rw-r--r--PIL/BdfFontFile.py133
-rw-r--r--PIL/BmpImagePlugin.py245
-rw-r--r--PIL/BufrStubImagePlugin.py68
-rw-r--r--PIL/ContainerIO.py116
-rw-r--r--PIL/CurImagePlugin.py90
-rw-r--r--PIL/DcxImagePlugin.py78
-rw-r--r--PIL/EpsImagePlugin.py349
-rw-r--r--PIL/ExifTags.py157
-rw-r--r--PIL/FitsStubImagePlugin.py73
-rw-r--r--PIL/FliImagePlugin.py142
-rw-r--r--PIL/FontFile.py146
-rw-r--r--PIL/FpxImagePlugin.py226
-rw-r--r--PIL/GbrImagePlugin.py70
-rw-r--r--PIL/GdImageFile.py86
-rw-r--r--PIL/GifImagePlugin.py405
-rw-r--r--PIL/GimpGradientFile.py124
-rw-r--r--PIL/GimpPaletteFile.py61
-rw-r--r--PIL/GribStubImagePlugin.py68
-rw-r--r--PIL/Hdf5StubImagePlugin.py70
-rw-r--r--PIL/IcnsImagePlugin.py211
-rw-r--r--PIL/IcoImagePlugin.py88
-rw-r--r--PIL/ImImagePlugin.py335
-rw-r--r--PIL/Image.py2093
-rw-r--r--PIL/ImageChops.py302
-rw-r--r--PIL/ImageColor.py262
-rw-r--r--PIL/ImageDraw.py378
-rw-r--r--PIL/ImageDraw2.py108
-rw-r--r--PIL/ImageEnhance.py89
-rw-r--r--PIL/ImageFile.py520
-rw-r--r--PIL/ImageFileIO.py47
-rw-r--r--PIL/ImageFilter.py263
-rw-r--r--PIL/ImageFont.py377
-rw-r--r--PIL/ImageGL.py28
-rw-r--r--PIL/ImageGrab.py71
-rw-r--r--PIL/ImageMath.py207
-rw-r--r--PIL/ImageMode.py46
-rw-r--r--PIL/ImageOps.py408
-rw-r--r--PIL/ImagePalette.py161
-rw-r--r--PIL/ImagePath.py71
-rw-r--r--PIL/ImageQt.py84
-rw-r--r--PIL/ImageSequence.py38
-rw-r--r--PIL/ImageStat.py179
-rw-r--r--PIL/ImageTk.py296
-rw-r--r--PIL/ImageTransform.py91
-rw-r--r--PIL/ImageWin.py215
-rw-r--r--PIL/ImtImagePlugin.py93
-rw-r--r--PIL/IptcImagePlugin.py280
-rw-r--r--PIL/JpegImagePlugin.py430
-rw-r--r--PIL/McIdasImagePlugin.py74
-rw-r--r--PIL/MicImagePlugin.py96
-rw-r--r--PIL/MpegImagePlugin.py83
-rw-r--r--PIL/MspImagePlugin.py103
-rw-r--r--PIL/OleFileIO.py528
-rw-r--r--PIL/PSDraw.py199
-rw-r--r--PIL/PaletteFile.py53
-rw-r--r--PIL/PalmImagePlugin.py226
-rw-r--r--PIL/PcdImagePlugin.py76
-rw-r--r--PIL/PcfFontFile.py256
-rw-r--r--PIL/PcxImagePlugin.py167
-rw-r--r--PIL/PdfImagePlugin.py210
-rw-r--r--PIL/PixarImagePlugin.py71
-rw-r--r--PIL/PngImagePlugin.py558
-rw-r--r--PIL/PpmImagePlugin.py131
-rw-r--r--PIL/PsdImagePlugin.py271
-rw-r--r--PIL/SgiImagePlugin.py91
-rw-r--r--PIL/SpiderImagePlugin.py294
-rw-r--r--PIL/SunImagePlugin.py87
-rw-r--r--PIL/TarIO.py57
-rw-r--r--PIL/TgaImagePlugin.py133
-rw-r--r--PIL/TiffImagePlugin.py763
-rw-r--r--PIL/TiffTags.py200
-rw-r--r--PIL/WalImageFile.py125
-rw-r--r--PIL/WmfImagePlugin.py148
-rw-r--r--PIL/XVThumbImagePlugin.py68
-rw-r--r--PIL/XbmImagePlugin.py94
-rw-r--r--PIL/XpmImagePlugin.py129
-rw-r--r--PIL/__init__.py12
-rw-r--r--ProgressDialog.py19
-rw-r--r--activity/activity-imageprocessor.svg296
-rw-r--r--activity/activity.info19
-rw-r--r--icons/blur.svg91
-rw-r--r--icons/cam.svg105
-rw-r--r--icons/contour.svg81
-rw-r--r--icons/copy.svg90
-rw-r--r--icons/embross.svg109
-rw-r--r--icons/finedges.svg100
-rw-r--r--icons/grey.svg94
-rw-r--r--icons/invert.svg104
-rw-r--r--icons/left_bottom.svg274
-rw-r--r--icons/left_top.svg297
-rw-r--r--icons/mirror.svg116
-rw-r--r--icons/offset.svg84
-rw-r--r--icons/original.svg67
-rw-r--r--icons/paste.svg141
-rw-r--r--icons/right_bottom.svg280
-rw-r--r--icons/right_top.svg314
-rw-r--r--icons/rotate_anticlockwise.svg71
-rw-r--r--icons/rotate_clockwise.svg71
-rw-r--r--icons/sharpen.svg269
-rw-r--r--icons/solarize.svg109
-rw-r--r--icons/watermark_scale.svg110
-rw-r--r--icons/watermark_tile.svg110
-rw-r--r--icons/watermark_tl.svg110
-rw-r--r--setup.py6
-rw-r--r--toolbar.py355
109 files changed, 21374 insertions, 0 deletions
diff --git a/ImageProcess.py b/ImageProcess.py
new file mode 100644
index 0000000..2e5cb54
--- /dev/null
+++ b/ImageProcess.py
@@ -0,0 +1,523 @@
+# Copyright (C) 2008, One Laptop per Child
+# Author: Keshav Sharma <keshav7890@gmail.com> & Vaibhav Sharma
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from __future__ import division
+import pygtk
+import gtk
+from gtk import gdk
+import gobject
+import sys
+import Image,ImageEnhance,ImageFont,ImageFilter,ImageOps
+import ImageDraw
+import StringIO
+import logging
+import random
+from sugar import mime
+import gst, pygame, sys, time
+from random import *
+
+class ImageProcessor(gtk.DrawingArea):
+ __gsignals__ = {
+ 'expose-event' : ('override'),
+ 'zoom-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, []),
+ 'angle-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, []),
+ }
+
+ __gproperties__ = {
+ 'zoom': (gobject.TYPE_FLOAT, 'Zoom Factor', 'Factor of zoom',0, 4, 1, gobject.PARAM_READWRITE),
+ 'angle': (gobject.TYPE_INT, 'Angle', 'Angle of rotation',0, 360, 0, gobject.PARAM_READWRITE),
+ 'file_location':( gobject.TYPE_STRING, 'File Location', 'Location of the image file',
+ '', gobject.PARAM_READWRITE),
+ }
+
+ def __init__(self):
+ gtk.DrawingArea.__init__(self)
+ self.set_app_paintable(True)
+ self.pixbuf = None
+ self.zoom = None
+ self.input_text = "text to edit"
+ self._tempfile = None
+ self.file_location = None
+ self._temp_pixbuf = None
+ self.edit_text = None
+ self.im = None
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ self.angle = 0
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'zoom' : return self.zoom
+ elif pspec.name == 'angle' : return self.angle
+ elif pspec.name == 'file_location': return self.file_location
+ else: raise AttributeError('unknown property %s' % pspec.name)
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == 'zoom': self.set_zoom(value)
+ elif pspec.name == 'angle': self.set_angle(value)
+ elif pspec.name == 'file_location' : self.set_file_location(value)
+ else: raise AttributeError('unknown property %s' % pspec.name)
+
+ def calculate_optimal_zoom(self, width=None, height=None, pixbuf=None):
+ # This tries to figure out a best fit model
+ # If the image can fit in, we show it in 1:1,
+ # in any other case we show it in a fit to screen way
+
+ if pixbuf == None:
+ pixbuf = self.pixbuf
+
+ if width == None or height == None:
+ rect = self.parent.get_allocation()
+ width = rect.width
+ height = rect.height
+
+ if width < pixbuf.get_width() or height < pixbuf.get_height():
+ # Image is larger than allocated size
+ zoom = min(width / pixbuf.get_width(),
+ height / pixbuf.get_height())
+ else: zoom = 1
+
+ self._optimal_zoom_flag = True
+
+ return zoom - 0.018 #XXX: Hack
+
+ def set_pixbuf( self , pixbuf ):
+ self.pixbuf = pixbuf
+ self.zoom = None
+ self._image_changed_flag = True
+ if self.window:
+ alloc = self.get_allocation()
+ rect = gdk.Rectangle( alloc.x , alloc.y , alloc.width ,alloc.height )
+ self.window.invalidate_rect( rect , True )
+ self.window.process_updates( True )
+
+ #def do_size_request(self, requisition):
+ # requisition.width = self.pixbuf.get_width()
+ # requisition.height = self.pixbuf.get_height()
+
+ def do_expose_event(self, event):
+ ctx = self.window.cairo_create()
+ ctx.rectangle(event.area.x, event.area.y,
+ event.area.width, event.area.height)
+ ctx.clip()
+ self.draw(ctx)
+
+ def draw(self, ctx):
+ if not self.pixbuf:
+ return
+ if self.zoom == None:
+ self.zoom = self.calculate_optimal_zoom()
+
+ if self._temp_pixbuf == None or self._image_changed_flag == True:
+ width, height = self.rotate()
+ self._temp_pixbuf = self._temp_pixbuf.scale_simple(width, height, gtk.gdk.INTERP_TILES)
+ self._image_changed_flag = False
+
+ rect = self.get_allocation()
+ x = rect.x
+ y = rect.y
+
+ width = self._temp_pixbuf.get_width()
+ height = self._temp_pixbuf.get_height()
+
+ if self.parent:
+ rect = self.parent.get_allocation()
+ if rect.width > width:
+ x = int(((rect.width - x) - width) / 2)
+
+ if rect.height > height:
+ y = int(((rect.height - y) - height) / 2)
+
+ self.set_size_request(self._temp_pixbuf.get_width(),
+ self._temp_pixbuf.get_height())
+
+ ctx.set_source_pixbuf(self._temp_pixbuf, x, y)
+
+ ctx.paint()
+
+ def set_zoom(self, zoom):
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = False
+ self.zoom = zoom
+
+ if self.window:
+ alloc = self.get_allocation()
+ rect = gdk.Rectangle(alloc.x, alloc.y,
+ alloc.width, alloc.height)
+ self.window.invalidate_rect(rect, True)
+ self.window.process_updates(True)
+
+ self.emit('zoom-changed')
+
+ def set_angle(self, angle):
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+
+ self.angle = angle
+
+ if self.window:
+ alloc = self.get_allocation()
+ rect = gdk.Rectangle(alloc.x, alloc.y,
+ alloc.width, alloc.height)
+ self.window.invalidate_rect(rect, True)
+ self.window.process_updates(True)
+
+ self.emit('angle-changed')
+
+ def rotate(self):
+ if self.angle == 0:
+ rotate = gtk.gdk.PIXBUF_ROTATE_NONE
+ elif self.angle == 90:
+ rotate = gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE
+ elif self.angle == 180:
+ rotate = gtk.gdk.PIXBUF_ROTATE_UPSIDEDOWN
+ elif self.angle == 270:
+ rotate = gtk.gdk.PIXBUF_ROTATE_CLOCKWISE
+ elif self.angle == 360:
+ self.angle = 0
+ rotate = gtk.gdk.PIXBUF_ROTATE_NONE
+ else:
+ logging.warning('Got unsupported rotate angle')
+
+ self._temp_pixbuf = self.pixbuf.rotate_simple(rotate)
+ width = int(self._temp_pixbuf.get_width() * self.zoom)
+ height = int(self._temp_pixbuf.get_height() * self.zoom)
+
+ return (width, height)
+
+ def zoom_in(self):
+ self.set_zoom(self.zoom + 0.2)
+ if self.zoom > (4): return False
+ else: return True
+
+ def zoom_out(self):
+ self.set_zoom(self.zoom - 0.2)
+ if self.zoom <= 0.2: return False
+ else: return True
+
+ def grey(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ im.convert("RGB")
+ r, g, b = im.split()
+ im = Image.merge("RGB", (g,g,g))
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+ def image_copy(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ self.im = im.copy()
+
+ def image_paste(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.im
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def image_Blur(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ im = im.filter(ImageFilter.BLUR)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def image_Transpose(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ im = im.transpose(Image.FLIP_LEFT_RIGHT)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def image_Offset(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ w,h=im.size
+ im = im.offset(w/2,h/2)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def image_Contour(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ im = im.filter(ImageFilter.CONTOUR)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def image_Finedges(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ im = im.filter(ImageFilter.FIND_EDGES)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def image_Solarize(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ im = ImageOps.solarize(im, threshold=128)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def image_left_top(self,edit_text):
+ self.edit_text="left_top"
+ self.image_edit_text(self.edit_text)
+
+ def image_right_top(self,edit_text):
+ self.edit_text='right_top'
+ self.image_edit_text(self.edit_text)
+
+ def image_left_bottom(self,edit_text):
+ self.edit_text='left_bottom'
+ self.image_edit_text(self.edit_text)
+
+ def image_right_bottom(self,edit_text):
+ self.edit_text='right_bottom'
+ self.image_edit_text(self.edit_text)
+
+ def image_edit_text(self,edit_text):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ self.edit_text = edit_text
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ out=self.Imprint(im, self.input_text,self.edit_text)
+ pix=self.imagetopixbuf(out)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+ def input_text_cb(self,text):
+ self.input_text=str(text)
+
+ def Imprint(self,im, inputtext,edit_text, font=None, color=None, opacity=.6, margin=(30,30)):
+ """
+ imprints a PIL image with the indicated text in lower-right corner
+ """
+ if im.mode != "RGBA":
+ im = im.convert("RGBA")
+ w,h=im.size
+ textlayer = Image.new("RGBA", im.size, (0,0,0,0))
+ textdraw = ImageDraw.Draw(textlayer)
+ textsize = textdraw.textsize(inputtext, font=font)
+ if edit_text=="left_top":
+ textpos = [margin[i] for i in [0,1]]
+ elif edit_text=="right_top":
+ textpos = im.size[0]-textsize[0]-margin[0],margin[1]
+ elif edit_text=="left_bottom":
+ textpos = margin[0],im.size[1]-textsize[1]-margin[1]
+ else:
+ textpos = [im.size[i]-textsize[i]-margin[i] for i in [0,1]]
+ textdraw.text(textpos, inputtext, font=font, fill="red")
+ if opacity != 1:
+ textlayer = self.reduce_opacity(textlayer,opacity)
+ return Image.composite(textlayer, im, textlayer)
+
+ def image_Sharpen(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ im = im.filter(ImageFilter.SHARPEN)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def image_Ambross(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ im = im.filter(ImageFilter.EMBOSS)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def image_Invert(self,value):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+ if self.window:
+ im = self.pixbuftoImage(pixbuf)
+ im = ImageOps.invert(im)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+
+ def image_Watermark(self,mark,pos):
+ pixbuf = self.pixbuf
+ self._image_changed_flag = True
+ self._optimal_zoom_flag = True
+
+ if self.window:
+ im1=Image.open(mark)
+ im = self.pixbuftoImage(pixbuf)
+ if pos=="tile":
+ im = self.watermark(im, im1,"tile",0.5)
+ elif pos=="scale":
+ im = self.watermark(im, im1,"scale",0.5)
+ else :#if pos is top_left
+ im = self.watermark(im, im1,(0,0),0.5)
+ #(raw_input('type of watermark(tile/scale,(xsize,ysize))')), 0.5)
+ pix=self.imagetopixbuf(im)
+ self.set_pixbuf( pix )
+ self.window.process_updates( True )
+
+
+ def pixbuftoImage(self,pb):
+ width,height = pb.get_width(),pb.get_height()
+ return Image.fromstring("RGB",(width,height),pb.get_pixels() )
+
+ def imagetopixbuf(self,im):
+ file1 = StringIO.StringIO()
+ im.save(file1, "ppm")
+ contents = file1.getvalue()
+ file1.close()
+ loader = gtk.gdk.PixbufLoader("pnm")
+ loader.write(contents, len(contents))
+ pixbuf = loader.get_pixbuf()
+ loader.close()
+ return pixbuf
+
+ def reduce_opacity(self,im, opacity):
+ """Returns an image with reduced opacity."""
+ assert opacity >= 0 and opacity <= 1
+ if im.mode != 'RGBA': im = im.convert('RGBA')
+ else : im = im.copy()
+ alpha = im.split()[3]
+ alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
+ im.putalpha(alpha)
+ return im
+ #watermark to an image
+ def watermark(self,im, mark, position, opacity=1):
+ """Adds a watermark to an image."""
+ if opacity < 1 : mark = self.reduce_opacity(mark, opacity)
+ if im.mode != 'RGBA': im = im.convert('RGBA')
+ # create a transparent layer the size of the image and draw the
+ # watermark in that layer.
+ layer = Image.new('RGBA', im.size, (0,0,0,0))
+ if position == 'tile':
+ w,h=im.size
+ mark = mark.resize((w/2, h/2))
+ for y in range(0, im.size[1], mark.size[1]):
+ for x in range(0, im.size[0], mark.size[0]): layer.paste(mark,(x, y))
+ elif position == 'scale':
+ # scale, but preserve the aspect ratio
+ ratio = min(float(im.size[0]) / mark.size[0], float(im.size[1]) / mark.size[1])
+ w = int(mark.size[0] * ratio)
+ h = int(mark.size[1] * ratio)
+ mark = mark.resize((w, h))
+ layer.paste(mark, ((im.size[0] - w) / 2, (im.size[1] - h) / 2))
+ else:
+ w,h=im.size
+ mark = mark.resize((w/2, h/2))
+ layer.paste(mark, position)
+ # composite the watermark with the layer
+ return Image.composite(layer, im, layer)
+
+ def original_cb(self,value):
+ self.set_file_location(self.original)
+
+ def set_file_location(self, file_location):
+ self.original=file_location
+ self.pixbuf = gtk.gdk.pixbuf_new_from_file(file_location)
+ self.file_location = file_location
+ self.zoom = None
+ self._image_changed_flag = True
+ if self.window:
+ alloc = self.get_allocation()
+ rect = gdk.Rectangle(alloc.x, alloc.y, alloc.width, alloc.height)
+ self.window.invalidate_rect(rect, True)
+ self.window.process_updates(True)
+
+#check working of functions
+def update(view_object):
+ view.grey("g")
+ return True
+
+
+if __name__ == '__main__':
+ window = gtk.Window()
+
+ vadj = gtk.Adjustment()
+ hadj = gtk.Adjustment()
+ sw = gtk.ScrolledWindow(hadj, vadj)
+
+ view = ImageProcessor()
+
+ view.set_file_location(sys.argv[1])
+
+
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+
+
+ sw.add_with_viewport(view)
+ window.add(sw)
+
+ window.set_size_request(800, 600)
+
+ window.show_all()
+
+ gobject.timeout_add(1000, update, view)
+
+ gtk.main()
diff --git a/ImageProcessorActivity.py b/ImageProcessorActivity.py
new file mode 100644
index 0000000..24fb3e3
--- /dev/null
+++ b/ImageProcessorActivity.py
@@ -0,0 +1,564 @@
+# Copyright (C) 2008, One Laptop per Child
+# Author: Keshav Sharma <keshav7890@gmail.com> & Vaibhav Sharma
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+# The sharing bits have been taken from ReadEtexts
+
+from __future__ import division
+
+from sugar.activity import activity
+import logging
+
+from gettext import gettext as _
+
+import time
+import os
+import gtk
+import gobject
+
+from sugar.graphics.alert import NotifyAlert
+from sugar.graphics.objectchooser import ObjectChooser
+from sugar import mime
+from sugar.graphics.toolbarbox import ToolbarButton
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toolbarbox import ToolbarBox
+from sugar.activity.widgets import ActivityToolbarButton
+from sugar.activity.widgets import StopButton
+from toolbar import ViewToolbar, EditToolbar
+
+from sugar import network
+from sugar.datastore import datastore
+import telepathy
+import dbus
+import pic
+import ImageProcess
+import ProgressDialog
+
+_logger = logging.getLogger('imageprocessor-activity')
+
+
+class ImageProcessorHTTPRequestHandler(network.ChunkedGlibHTTPRequestHandler):
+ """HTTP Request Handler for transferring document while collaborating.
+
+ RequestHandler class that integrates with Glib mainloop. It writes
+ the specified file to the client in chunks, returning control to the
+ mainloop between chunks.
+
+ """
+
+ def translate_path(self, path):
+ """Return the filepath to the shared document."""
+ return self.server.filepath
+
+
+class ImageProcessorHTTPServer(network.GlibTCPServer):
+ """HTTP Server for transferring document while collaborating."""
+
+ def __init__(self, server_address, filepath):
+ """Set up the GlibTCPServer with the ImageProcessorHTTPRequestHandler.
+
+ filepath -- path to shared document to be served.
+ """
+ self.filepath = filepath
+ network.GlibTCPServer.__init__(self, server_address,
+ ImageProcessorHTTPRequestHandler)
+
+
+class ImageProcessorURLDownloader(network.GlibURLDownloader):
+ """URLDownloader that provides content-length and content-type."""
+
+ def get_content_length(self):
+ """Return the content-length of the download."""
+ if self._info is not None:
+ return int(self._info.headers.get('Content-Length'))
+
+ def get_content_type(self):
+ """Return the content-type of the download."""
+ if self._info is not None:
+ return self._info.headers.get('Content-type')
+ return None
+
+IMAGEVIEWER_STREAM_SERVICE = 'imageprocessor-activity-http'
+
+
+class ImageProcessorActivity(activity.Activity):
+
+ def __init__(self, handle):
+ activity.Activity.__init__(self, handle)
+
+ self.zoom = None
+ self._object_id = handle.object_id
+ self._old_zoom = None
+ self._fileserver = None
+ self._fileserver_tube_id = None
+ self.view = ImageProcess.ImageProcessor()
+ self.progressdialog = None
+ self.im=None
+ toolbar_box = ToolbarBox()
+ self._add_toolbar_buttons(toolbar_box)
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show()
+
+ vadj = gtk.Adjustment()
+ hadj = gtk.Adjustment()
+ self.sw = gtk.ScrolledWindow(hadj, vadj)
+
+ self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.sw.add_with_viewport(self.view)
+ self.set_canvas(self.sw)
+ self.sw.show_all()
+
+ self.unused_download_tubes = set()
+ self._want_document = True
+ self._download_content_length = 0
+ self._download_content_type = None
+ # Status of temp file used for write_file:
+ self._tempfile = None
+ self._close_requested = False
+ self.connect("shared", self._shared_cb)
+ h = hash(self._activity_id)
+ self.port = 1024 + (h % 64511)
+
+ self.is_received_document = False
+
+ if self._shared_activity and handle.object_id == None:
+ # We're joining, and we don't already have the document.
+ if self.get_shared():
+ # Already joined for some reason, just get the document
+ self._joined_cb(self)
+ else:
+ # Wait for a successful join before trying to get the document
+ self.connect("joined", self._joined_cb)
+ elif self._object_id is None:
+ self._show_object_picker = gobject.timeout_add(1000, self._show_picker_cb)
+
+ def handle_view_source(self):
+ pass
+ raise NotImplementedError
+
+ def enter_callback(self, widget, entry):
+ self.view.input_text_cb(entry.get_text())
+
+ def fullscreen(self):
+ self._old_zoom = self.view.get_property('zoom') #XXX: Hack
+ # Zoom to fit screen if possible
+ screen = self.get_screen()
+ zoom = self.view.calculate_optimal_zoom(
+ screen.get_width(), screen.get_height())
+ self.view.set_zoom(zoom)
+ activity.Activity.fullscreen(self)
+
+ def unfullscreen(self):
+ self.view.set_zoom(self._old_zoom)
+ activity.Activity.unfullscreen(self)
+
+ def _add_toolbar_buttons(self, toolbar_box):
+ activity_button = ActivityToolbarButton(self)
+ toolbar_box.toolbar.insert(activity_button, 0)
+ activity_button.show()
+
+
+ self._view_toolbar = ViewToolbar()
+
+ self._view_toolbar.connect('zoom_in', self.__zoom_in_cb)
+ self._view_toolbar.connect('zoom_out', self.__zoom_out_cb)
+ self._view_toolbar.connect('zoom_to_fit', self.__zoom_tofit_cb)
+ self._view_toolbar.connect('zoom_original', self.__zoom_original_cb)
+ self._view_toolbar.connect('rotate_clockwise', self.__rotate_anticlockwise_cb)
+ self._view_toolbar.connect('rotate_anticlockwise', self.__rotate_clockwise_cb)
+ self._view_toolbar.connect('copy', self.view.image_copy)
+ self._view_toolbar.connect('paste', self.view.image_paste)
+ view_toolbar_button = ToolbarButton(page=self._view_toolbar, icon_name='toolbar-view')
+ self._view_toolbar.show()
+ toolbar_box.toolbar.insert(view_toolbar_button, -1)
+ view_toolbar_button.show()
+
+ self.entry = gtk.Entry()
+ self.entry.set_max_length(15)
+ self.entry.connect("activate", self.enter_callback,self.entry)
+ self.entry.set_text("text")
+ self.entry.insert_text(" to edit", len(self.entry.get_text()))
+ self.entry.select_region(0, len(self.entry.get_text()))
+ self.entry.show()
+ tool_item = gtk.ToolItem()
+ tool_item.set_expand(True)
+ tool_item.add(self.entry)
+ toolbar_box.toolbar.insert(tool_item, -1)
+ tool_item.show()
+
+
+ self._edit_toolbar = EditToolbar()
+ self._edit_toolbar.connect('grey', self.view.grey)
+ self._edit_toolbar.connect('blur', self.view.image_Blur)
+ self._edit_toolbar.connect('transpose', self.view.image_Transpose)
+ self._edit_toolbar.connect('offset', self.view.image_Offset)
+ self._edit_toolbar.connect('contour', self.view.image_Contour)
+ self._edit_toolbar.connect('finedges', self.view.image_Finedges)
+ self._edit_toolbar.connect('solarize', self.view.image_Solarize)
+ self._edit_toolbar.connect('invert', self.view.image_Invert)
+ self._edit_toolbar.connect('watermark_tl', self.watermrk_cb,"tl")
+ self._edit_toolbar.connect('watermark_tile', self.watermrk_cb,"tile")
+ self._edit_toolbar.connect('watermark_scale', self.watermrk_cb,"scale")
+ self._edit_toolbar.connect('ambross', self.view.image_Ambross)
+ self._edit_toolbar.connect('left_top', self.view.image_left_top)
+ self._edit_toolbar.connect('right_top', self.view.image_right_top)
+ self._edit_toolbar.connect('left_bottom', self.view.image_left_bottom)
+ self._edit_toolbar.connect('right_bottom', self.view.image_right_bottom)
+ self._edit_toolbar.connect('sharpen', self.view.image_Sharpen)
+
+ edit_toolbar_button = ToolbarButton(page=self._edit_toolbar, icon_name='toolbar-edit')
+ self._edit_toolbar.show()
+ toolbar_box.toolbar.insert(edit_toolbar_button, -1)
+ edit_toolbar_button.set_expanded(True)
+ edit_toolbar_button.show()
+
+ spacer = gtk.SeparatorToolItem()
+ spacer.props.draw = False
+ toolbar_box.toolbar.insert(spacer, -1)
+ spacer.show()
+
+ original_button = ToolButton('original')
+ original_button.set_tooltip(_('Undo original pic'))
+ original_button.connect('clicked', self.view.original_cb)
+ toolbar_box.toolbar.insert(original_button, -1)
+ original_button.show()
+
+ fullscreen_button = ToolButton('view-fullscreen')
+ fullscreen_button.set_tooltip(_('Fullscreen'))
+ fullscreen_button.connect('clicked', self.__fullscreen_cb)
+ toolbar_box.toolbar.insert(fullscreen_button, -1)
+ fullscreen_button.show()
+
+
+
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ toolbar_box.toolbar.insert(separator, -1)
+ separator.show()
+
+ stop_button = StopButton(self)
+ toolbar_box.toolbar.insert(stop_button, -1)
+ stop_button.show()
+
+
+ def watermrk_cb(self, button,pos):
+ self.w="kuch"
+ self.do_load_an_image_cb(button)
+ self.view.image_Watermark(self.im,pos)
+
+ def __zoom_in_cb(self, button):
+ self._view_toolbar._zoom_in_button.set_sensitive(self.view.zoom_in())
+ self._view_toolbar._zoom_out_button.set_sensitive(True)
+
+ def __zoom_out_cb(self, button):
+ self._view_toolbar._zoom_out_button.set_sensitive(self.view.zoom_out())
+ self._view_toolbar._zoom_in_button.set_sensitive(True)
+
+ def __zoom_tofit_cb(self, button):
+ zoom = self.view.calculate_optimal_zoom()
+ self.view.set_zoom(zoom)
+
+ def __zoom_original_cb(self, button):
+ self.view.set_zoom(1)
+
+ def __rotate_anticlockwise_cb(self, button):
+ angle = self.view.get_property('angle')
+ self.view.set_angle(angle + 90)
+
+ def __rotate_clockwise_cb(self, button):
+ angle = self.view.get_property('angle')
+ if angle == 0:
+ angle = 360
+
+ self.view.set_angle(angle - 90)
+
+ def __fullscreen_cb(self, button):
+ self._old_zoom = self.view.get_property('zoom') #XXX: Hack
+ # Zoom to fit screen if possible
+ screen = self.get_screen()
+ zoom = self.view.calculate_optimal_zoom(screen.get_width(), screen.get_height())
+ self.view.set_zoom(zoom)
+
+ self.fullscreen()
+
+ def _show_picker_cb(self):
+ if not self._want_document:
+ return
+
+ chooser = ObjectChooser(_('Choose document'), self,
+ gtk.DIALOG_MODAL |
+ gtk.DIALOG_DESTROY_WITH_PARENT, \
+ what_filter=mime.GENERIC_TYPE_IMAGE)
+
+ try:
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ jobject = chooser.get_selected_object()
+ if jobject and jobject.file_path:
+ self.read_file(jobject.file_path)
+ finally:
+ chooser.destroy()
+ del chooser
+
+ def do_load_an_image_cb(self, button):
+ """ Load an image from the Journal """
+ chooser = ObjectChooser(_('Choose document'), self,
+ gtk.DIALOG_MODAL |
+ gtk.DIALOG_DESTROY_WITH_PARENT, \
+ what_filter=mime.GENERIC_TYPE_IMAGE)
+ try:
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ dsobject = chooser.get_selected_object()
+ try:
+ _logger.debug("opening %s " % dsobject.file_path)
+ tempfile = os.path.join(self.get_activity_root(), 'instance','tmp%i' % time.time())
+ os.link(dsobject.file_path, tempfile)
+ self.im=tempfile
+ except:
+ _logger.debug("couldn't open %s" % dsobject.file_path)
+ dsobject.destroy()
+ finally:
+ chooser.destroy()
+ del chooser
+ return
+
+ def read_file(self, file_path):
+ self._want_document = False
+
+ tempfile = os.path.join(self.get_activity_root(), 'instance', \
+ 'tmp%i' % time.time())
+
+ os.link(file_path, tempfile)
+ self._tempfile = tempfile
+ gobject.idle_add(self.__set_file_idle_cb, tempfile)
+
+ def __set_file_idle_cb(self, file_path):
+ self.view.set_file_location(file_path)
+
+ try:
+ self.zoom = int(self.metadata.get('zoom', '0'))
+ if self.zoom > 0:
+ self.view.set_zoom(self.zoom)
+ except Exception:
+ pass
+
+ return False
+
+ def write_file(self, file_path):
+ if self._tempfile:
+ self.metadata['activity'] = self.get_bundle_id()
+ self.metadata['zoom'] = str(self.zoom)
+ if self._close_requested:
+ os.link(self._tempfile, file_path)
+ os.unlink(self._tempfile)
+ self._tempfile = None
+ else:
+ raise NotImplementedError
+
+ def can_close(self):
+ self._close_requested = True
+ return True
+
+ def _download_result_cb(self, getter, tempfile, suggested_name, tube_id):
+ if self._download_content_type == 'text/html':
+ # got an error page instead
+ self._download_error_cb(getter, 'HTTP Error', tube_id)
+ return
+
+ del self.unused_download_tubes
+
+ self._tempfile = tempfile
+ file_path = os.path.join(self.get_activity_root(), 'instance',
+ '%i' % time.time())
+ _logger.debug("Saving file %s to datastore...", file_path)
+ os.link(tempfile, file_path)
+ self._jobject.file_path = file_path
+ datastore.write(self._jobject, transfer_ownership=True)
+
+
+ _logger.debug("Got document %s (%s) from tube %u",
+ tempfile, suggested_name, tube_id)
+
+ self.progressdialog.destroy()
+
+ gobject.idle_add(self.__set_file_idle_cb, tempfile)
+ self.save()
+
+ def _download_progress_cb(self, getter, bytes_downloaded, tube_id):
+ if self._download_content_length > 0:
+ _logger.debug("Downloaded %u of %u bytes from tube %u...",
+ bytes_downloaded, self._download_content_length,
+ tube_id)
+ else:
+ _logger.debug("Downloaded %u bytes from tube %u...",
+ bytes_downloaded, tube_id)
+ total = self._download_content_length
+
+ fraction = bytes_downloaded / total
+ self.progressdialog.set_fraction(fraction)
+
+ #gtk.main_iteration()
+
+ def _download_error_cb(self, getter, err, tube_id):
+ _logger.debug("Error getting document from tube %u: %s",
+ tube_id, err)
+ self._alert('Failure', 'Error getting document from tube')
+ self._want_document = True
+ self._download_content_length = 0
+ self._download_content_type = None
+ gobject.idle_add(self._get_document)
+
+ def _download_document(self, tube_id, path):
+ # FIXME: should ideally have the CM listen on a Unix socket
+ # instead of IPv4 (might be more compatible with Rainbow)
+ chan = self._shared_activity.telepathy_tubes_chan
+ iface = chan[telepathy.CHANNEL_TYPE_TUBES]
+ addr = iface.AcceptStreamTube(tube_id,
+ telepathy.SOCKET_ADDRESS_TYPE_IPV4,
+ telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0,
+ utf8_strings=True)
+ _logger.debug('Accepted stream tube: listening address is %r', addr)
+ # SOCKET_ADDRESS_TYPE_IPV4 is defined to have addresses of type '(sq)'
+ assert isinstance(addr, dbus.Struct)
+ assert len(addr) == 2
+ assert isinstance(addr[0], str)
+ assert isinstance(addr[1], (int, long))
+ assert addr[1] > 0 and addr[1] < 65536
+ port = int(addr[1])
+
+ getter = ImageProcessorURLDownloader("http://%s:%d/document"
+ % (addr[0], port))
+ getter.connect("finished", self._download_result_cb, tube_id)
+ getter.connect("progress", self._download_progress_cb, tube_id)
+ getter.connect("error", self._download_error_cb, tube_id)
+ _logger.debug("Starting download to %s...", path)
+ getter.start(path)
+ self._download_content_length = getter.get_content_length()
+ self._download_content_type = getter.get_content_type()
+
+ return False
+
+ def _get_document(self):
+ if not self._want_document:
+ return False
+
+ # Assign a file path to download if one doesn't exist yet
+ if not self._jobject.file_path:
+ path = os.path.join(self.get_activity_root(), 'instance',
+ 'tmp%i' % time.time())
+ else:
+ path = self._jobject.file_path
+
+ # Pick an arbitrary tube we can try to download the document from
+ try:
+ tube_id = self.unused_download_tubes.pop()
+ except (ValueError, KeyError), e:
+ _logger.debug('No tubes to get the document from right now: %s',
+ e)
+ return False
+
+ # Avoid trying to download the document multiple times at once
+ self._want_document = False
+ gobject.idle_add(self._download_document, tube_id, path)
+ return False
+
+ def _joined_cb(self, also_self):
+ """Callback for when a shared activity is joined.
+
+ Get the shared document from another participant.
+ """
+ self.watch_for_tubes()
+
+ self.progressdialog = ProgressDialog.ProgressDialog(self)
+ self.progressdialog.show_all()
+
+ gobject.idle_add(self._get_document)
+
+ def _share_document(self):
+ """Share the document."""
+ # FIXME: should ideally have the fileserver listen on a Unix socket
+ # instead of IPv4 (might be more compatible with Rainbow)
+
+ _logger.debug('Starting HTTP server on port %d', self.port)
+ self._fileserver = ImageProcessorHTTPServer(("", self.port),
+ self._tempfile)
+
+ # Make a tube for it
+ chan = self._shared_activity.telepathy_tubes_chan
+ iface = chan[telepathy.CHANNEL_TYPE_TUBES]
+ self._fileserver_tube_id = \
+ iface.OfferStreamTube(IMAGEVIEWER_STREAM_SERVICE,
+ {},
+ telepathy.SOCKET_ADDRESS_TYPE_IPV4,
+ ('127.0.0.1', dbus.UInt16(self.port)),
+ telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0)
+
+ def watch_for_tubes(self):
+ """Watch for new tubes."""
+ tubes_chan = self._shared_activity.telepathy_tubes_chan
+
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
+ self._new_tube_cb)
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=self._list_tubes_reply_cb,
+ error_handler=self._list_tubes_error_cb)
+
+ def _new_tube_cb(self, tube_id, initiator, tube_type, service, params,
+ state):
+ """Callback when a new tube becomes available."""
+ _logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
+ 'params=%r state=%d', tube_id, initiator, tube_type,
+ service, params, state)
+ if service == IMAGEVIEWER_STREAM_SERVICE:
+ _logger.debug('I could download from that tube')
+ self.unused_download_tubes.add(tube_id)
+ # if no download is in progress, let's fetch the document
+ if self._want_document:
+ gobject.idle_add(self._get_document)
+
+ def _list_tubes_reply_cb(self, tubes):
+ """Callback when new tubes are available."""
+ for tube_info in tubes:
+ self._new_tube_cb(*tube_info)
+
+ def _list_tubes_error_cb(self, e):
+ """Handle ListTubes error by logging."""
+ _logger.error('ListTubes() failed: %s', e)
+
+ def _shared_cb(self, activityid):
+ """Callback when activity shared.
+
+ Set up to share the document.
+
+ """
+ # We initiated this activity and have now shared it, so by
+ # definition we have the file.
+ _logger.debug('Activity became shared')
+ self.watch_for_tubes()
+ self._share_document()
+
+ def _alert(self, title, text=None):
+ alert = NotifyAlert(timeout=5)
+ alert.props.title = title
+ alert.props.msg = text
+ self.add_alert(alert)
+ alert.connect('response', self._alert_cancel_cb)
+ alert.show()
+
+ def _alert_cancel_cb(self, alert, response_id):
+ self.remove_alert(alert)
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..5d698c0
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,116 @@
+ImageProcessor.activity/
+ImageProcessor.activity/activity
+ImageProcessor.activity/activity/activity-imageprocessor.svg
+ImageProcessor.activity/activity/activity.info
+ImageProcessor.activity/icons
+ImageProcessor.activity/icons/blur.svg
+ImageProcessor.activity/icons/cam.svg
+ImageProcessor.activity/icons/contour.svg
+ImageProcessor.activity/icons/copy.svg
+ImageProcessor.activity/icons/embross.svg
+ImageProcessor.activity/icons/finedges.svg
+ImageProcessor.activity/icons/grey.svg
+ImageProcessor.activity/icons/invert.svg
+ImageProcessor.activity/icons/left_bottom.svg
+ImageProcessor.activity/icons/left_top.svg
+ImageProcessor.activity/icons/mirror.svg
+ImageProcessor.activity/icons/offset.svg
+ImageProcessor.activity/icons/original.svg
+ImageProcessor.activity/icons/paste.svg
+ImageProcessor.activity/icons/right_bottom.svg
+ImageProcessor.activity/icons/right_top.svg
+ImageProcessor.activity/icons/rotate_anticlockwise.svg
+ImageProcessor.activity/icons/rotate_clockwise.svg
+ImageProcessor.activity/icons/sharpen.svg
+ImageProcessor.activity/icons/solarize.svg
+ImageProcessor.activity/icons/watermark_scale.svg
+ImageProcessor.activity/icons/watermark_tile.svg
+ImageProcessor.activity/icons/watermark_tl.svg
+ImageProcessor.activity/ImageProcess.py
+ImageProcessor.activity/ImageProcess.py~
+ImageProcessor.activity/ImageProcessor.xo
+ImageProcessor.activity/ImageProcessorActivity.py
+ImageProcessor.activity/MANIFEST
+ImageProcessor.activity/pic.py
+ImageProcessor.activity/PIL
+ImageProcessor.activity/PIL/ImageGL.py
+ImageProcessor.activity/PIL/PixarImagePlugin.py
+ImageProcessor.activity/PIL/ArgImagePlugin.py
+ImageProcessor.activity/PIL/BdfFontFile.py
+ImageProcessor.activity/PIL/BmpImagePlugin.py
+ImageProcessor.activity/PIL/BufrStubImagePlugin.py
+ImageProcessor.activity/PIL/ContainerIO.py
+ImageProcessor.activity/PIL/CurImagePlugin.py
+ImageProcessor.activity/PIL/DcxImagePlugin.py
+ImageProcessor.activity/PIL/EpsImagePlugin.py
+ImageProcessor.activity/PIL/ExifTags.py
+ImageProcessor.activity/PIL/FitsStubImagePlugin.py
+ImageProcessor.activity/PIL/FliImagePlugin.py
+ImageProcessor.activity/PIL/FontFile.py
+ImageProcessor.activity/PIL/FpxImagePlugin.py
+ImageProcessor.activity/PIL/GbrImagePlugin.py
+ImageProcessor.activity/PIL/GdImageFile.py
+ImageProcessor.activity/PIL/GifImagePlugin.py
+ImageProcessor.activity/PIL/GimpGradientFile.py
+ImageProcessor.activity/PIL/GimpPaletteFile.py
+ImageProcessor.activity/PIL/GribStubImagePlugin.py
+ImageProcessor.activity/PIL/Hdf5StubImagePlugin.py
+ImageProcessor.activity/PIL/IcnsImagePlugin.py
+ImageProcessor.activity/PIL/IcoImagePlugin.py
+ImageProcessor.activity/PIL/Image.py
+ImageProcessor.activity/PIL/ImageChops.py
+ImageProcessor.activity/PIL/ImageColor.py
+ImageProcessor.activity/PIL/ImageDraw.py
+ImageProcessor.activity/PIL/ImageDraw2.py
+ImageProcessor.activity/PIL/ImageEnhance.py
+ImageProcessor.activity/PIL/ImageFile.py
+ImageProcessor.activity/PIL/ImageFileIO.py
+ImageProcessor.activity/PIL/ImageFilter.py
+ImageProcessor.activity/PIL/ImageFont.py
+ImageProcessor.activity/PIL/PngImagePlugin.py
+ImageProcessor.activity/PIL/PpmImagePlugin.py
+ImageProcessor.activity/PIL/PsdImagePlugin.py
+ImageProcessor.activity/PIL/PSDraw.py
+ImageProcessor.activity/PIL/SgiImagePlugin.py
+ImageProcessor.activity/PIL/SpiderImagePlugin.py
+ImageProcessor.activity/PIL/SunImagePlugin.py
+ImageProcessor.activity/PIL/TarIO.py
+ImageProcessor.activity/PIL/TgaImagePlugin.py
+ImageProcessor.activity/PIL/TiffImagePlugin.py
+ImageProcessor.activity/PIL/TiffTags.py
+ImageProcessor.activity/PIL/WalImageFile.py
+ImageProcessor.activity/PIL/WmfImagePlugin.py
+ImageProcessor.activity/PIL/XbmImagePlugin.py
+ImageProcessor.activity/PIL/XpmImagePlugin.py
+ImageProcessor.activity/PIL/XVThumbImagePlugin.py
+ImageProcessor.activity/PIL/__init__.py
+ImageProcessor.activity/PIL/ImageGrab.py
+ImageProcessor.activity/PIL/ImageMath.py
+ImageProcessor.activity/PIL/ImageMode.py
+ImageProcessor.activity/PIL/ImageOps.py
+ImageProcessor.activity/PIL/ImagePalette.py
+ImageProcessor.activity/PIL/ImagePath.py
+ImageProcessor.activity/PIL/ImageQt.py
+ImageProcessor.activity/PIL/ImageSequence.py
+ImageProcessor.activity/PIL/ImageStat.py
+ImageProcessor.activity/PIL/ImageTk.py
+ImageProcessor.activity/PIL/ImageTransform.py
+ImageProcessor.activity/PIL/ImageWin.py
+ImageProcessor.activity/PIL/ImImagePlugin.py
+ImageProcessor.activity/PIL/ImtImagePlugin.py
+ImageProcessor.activity/PIL/IptcImagePlugin.py
+ImageProcessor.activity/PIL/JpegImagePlugin.py
+ImageProcessor.activity/PIL/McIdasImagePlugin.py
+ImageProcessor.activity/PIL/MicImagePlugin.py
+ImageProcessor.activity/PIL/MpegImagePlugin.py
+ImageProcessor.activity/PIL/MspImagePlugin.py
+ImageProcessor.activity/PIL/OleFileIO.py
+ImageProcessor.activity/PIL/PaletteFile.py
+ImageProcessor.activity/PIL/PalmImagePlugin.py
+ImageProcessor.activity/PIL/PcdImagePlugin.py
+ImageProcessor.activity/PIL/PcfFontFile.py
+ImageProcessor.activity/PIL/PcxImagePlugin.py
+ImageProcessor.activity/PIL/PdfImagePlugin.py
+ImageProcessor.activity/ProgressDialog.py
+ImageProcessor.activity/setup.py
+ImageProcessor.activity/toolbar.py
diff --git a/PIL/ArgImagePlugin.py b/PIL/ArgImagePlugin.py
new file mode 100644
index 0000000..48582d9
--- /dev/null
+++ b/PIL/ArgImagePlugin.py
@@ -0,0 +1,498 @@
+#
+# THIS IS WORK IN PROGRESS
+#
+# The Python Imaging Library.
+# $Id: ArgImagePlugin.py 2309 2005-03-02 15:06:34Z fredrik $
+#
+# ARG animation support code
+#
+# history:
+# 1996-12-30 fl Created
+# 1996-01-06 fl Added safe scripting environment
+# 1996-01-10 fl Added JHDR, UHDR and sYNC support
+# 2005-03-02 fl Removed AAPP and ARUN support
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.4"
+
+import marshal, string
+
+import Image, ImageFile, ImagePalette
+
+from PngImagePlugin import i16, i32, ChunkStream, _MODES
+
+MAGIC = "\212ARG\r\n\032\n"
+
+# --------------------------------------------------------------------
+# ARG parser
+
+class ArgStream(ChunkStream):
+ "Parser callbacks for ARG data"
+
+ def __init__(self, fp):
+
+ ChunkStream.__init__(self, fp)
+
+ self.eof = 0
+
+ self.im = None
+ self.palette = None
+
+ self.__reset()
+
+ def __reset(self):
+
+ # reset decoder state (called on init and sync)
+
+ self.count = 0
+ self.id = None
+ self.action = ("NONE",)
+
+ self.images = {}
+ self.names = {}
+
+
+ def chunk_AHDR(self, offset, bytes):
+ "AHDR -- animation header"
+
+ # assertions
+ if self.count != 0:
+ raise SyntaxError, "misplaced AHDR chunk"
+
+ s = self.fp.read(bytes)
+ self.size = i32(s), i32(s[4:])
+ try:
+ self.mode, self.rawmode = _MODES[(ord(s[8]), ord(s[9]))]
+ except:
+ raise SyntaxError, "unknown ARG mode"
+
+ if Image.DEBUG:
+ print "AHDR size", self.size
+ print "AHDR mode", self.mode, self.rawmode
+
+ return s
+
+ def chunk_AFRM(self, offset, bytes):
+ "AFRM -- next frame follows"
+
+ # assertions
+ if self.count != 0:
+ raise SyntaxError, "misplaced AFRM chunk"
+
+ self.show = 1
+ self.id = 0
+ self.count = 1
+ self.repair = None
+
+ s = self.fp.read(bytes)
+ if len(s) >= 2:
+ self.id = i16(s)
+ if len(s) >= 4:
+ self.count = i16(s[2:4])
+ if len(s) >= 6:
+ self.repair = i16(s[4:6])
+ else:
+ self.repair = None
+
+ if Image.DEBUG:
+ print "AFRM", self.id, self.count
+
+ return s
+
+ def chunk_ADEF(self, offset, bytes):
+ "ADEF -- store image"
+
+ # assertions
+ if self.count != 0:
+ raise SyntaxError, "misplaced ADEF chunk"
+
+ self.show = 0
+ self.id = 0
+ self.count = 1
+ self.repair = None
+
+ s = self.fp.read(bytes)
+ if len(s) >= 2:
+ self.id = i16(s)
+ if len(s) >= 4:
+ self.count = i16(s[2:4])
+
+ if Image.DEBUG:
+ print "ADEF", self.id, self.count
+
+ return s
+
+ def chunk_NAME(self, offset, bytes):
+ "NAME -- name the current image"
+
+ # assertions
+ if self.count == 0:
+ raise SyntaxError, "misplaced NAME chunk"
+
+ name = self.fp.read(bytes)
+ self.names[self.id] = name
+
+ return name
+
+ def chunk_AEND(self, offset, bytes):
+ "AEND -- end of animation"
+
+ if Image.DEBUG:
+ print "AEND"
+
+ self.eof = 1
+
+ raise EOFError, "end of ARG file"
+
+ def __getmodesize(self, s, full=1):
+
+ size = i32(s), i32(s[4:])
+
+ try:
+ mode, rawmode = _MODES[(ord(s[8]), ord(s[9]))]
+ except:
+ raise SyntaxError, "unknown image mode"
+
+ if full:
+ if ord(s[12]):
+ pass # interlace not yet supported
+ if ord(s[11]):
+ raise SyntaxError, "unknown filter category"
+
+ return size, mode, rawmode
+
+ def chunk_PAST(self, offset, bytes):
+ "PAST -- paste one image into another"
+
+ # assertions
+ if self.count == 0:
+ raise SyntaxError, "misplaced PAST chunk"
+
+ if self.repair is not None:
+ # we must repair the target image before we
+ # start pasting
+
+ # brute force; a better solution would be to
+ # update only the dirty rectangles in images[id].
+ # note that if images[id] doesn't exist, it must
+ # be created
+
+ self.images[self.id] = self.images[self.repair].copy()
+ self.repair = None
+
+ s = self.fp.read(bytes)
+ im = self.images[i16(s)]
+ x, y = i32(s[2:6]), i32(s[6:10])
+ bbox = x, y, im.size[0]+x, im.size[1]+y
+
+ if im.mode in ["RGBA"]:
+ # paste with transparency
+ # FIXME: should handle P+transparency as well
+ self.images[self.id].paste(im, bbox, im)
+ else:
+ # paste without transparency
+ self.images[self.id].paste(im, bbox)
+
+ self.action = ("PAST",)
+ self.__store()
+
+ return s
+
+ def chunk_BLNK(self, offset, bytes):
+ "BLNK -- create blank image"
+
+ # assertions
+ if self.count == 0:
+ raise SyntaxError, "misplaced BLNK chunk"
+
+ s = self.fp.read(bytes)
+ size, mode, rawmode = self.__getmodesize(s, 0)
+
+ # store image (FIXME: handle colour)
+ self.action = ("BLNK",)
+ self.im = Image.core.fill(mode, size, 0)
+ self.__store()
+
+ return s
+
+ def chunk_IHDR(self, offset, bytes):
+ "IHDR -- full image follows"
+
+ # assertions
+ if self.count == 0:
+ raise SyntaxError, "misplaced IHDR chunk"
+
+ # image header
+ s = self.fp.read(bytes)
+ size, mode, rawmode = self.__getmodesize(s)
+
+ # decode and store image
+ self.action = ("IHDR",)
+ self.im = Image.core.new(mode, size)
+ self.decoder = Image.core.zip_decoder(rawmode)
+ self.decoder.setimage(self.im, (0,0) + size)
+ self.data = ""
+
+ return s
+
+ def chunk_DHDR(self, offset, bytes):
+ "DHDR -- delta image follows"
+
+ # assertions
+ if self.count == 0:
+ raise SyntaxError, "misplaced DHDR chunk"
+
+ s = self.fp.read(bytes)
+
+ size, mode, rawmode = self.__getmodesize(s)
+
+ # delta header
+ diff = ord(s[13])
+ offs = i32(s[14:18]), i32(s[18:22])
+
+ bbox = offs + (offs[0]+size[0], offs[1]+size[1])
+
+ if Image.DEBUG:
+ print "DHDR", diff, bbox
+
+ # FIXME: decode and apply image
+ self.action = ("DHDR", diff, bbox)
+
+ # setup decoder
+ self.im = Image.core.new(mode, size)
+
+ self.decoder = Image.core.zip_decoder(rawmode)
+ self.decoder.setimage(self.im, (0,0) + size)
+
+ self.data = ""
+
+ return s
+
+ def chunk_JHDR(self, offset, bytes):
+ "JHDR -- JPEG image follows"
+
+ # assertions
+ if self.count == 0:
+ raise SyntaxError, "misplaced JHDR chunk"
+
+ # image header
+ s = self.fp.read(bytes)
+ size, mode, rawmode = self.__getmodesize(s, 0)
+
+ # decode and store image
+ self.action = ("JHDR",)
+ self.im = Image.core.new(mode, size)
+ self.decoder = Image.core.jpeg_decoder(rawmode)
+ self.decoder.setimage(self.im, (0,0) + size)
+ self.data = ""
+
+ return s
+
+ def chunk_UHDR(self, offset, bytes):
+ "UHDR -- uncompressed image data follows (EXPERIMENTAL)"
+
+ # assertions
+ if self.count == 0:
+ raise SyntaxError, "misplaced UHDR chunk"
+
+ # image header
+ s = self.fp.read(bytes)
+ size, mode, rawmode = self.__getmodesize(s, 0)
+
+ # decode and store image
+ self.action = ("UHDR",)
+ self.im = Image.core.new(mode, size)
+ self.decoder = Image.core.raw_decoder(rawmode)
+ self.decoder.setimage(self.im, (0,0) + size)
+ self.data = ""
+
+ return s
+
+ def chunk_IDAT(self, offset, bytes):
+ "IDAT -- image data block"
+
+ # pass compressed chunks through the decoder
+ s = self.fp.read(bytes)
+ self.data = self.data + s
+ n, e = self.decoder.decode(self.data)
+ if n < 0:
+ # end of image
+ if e < 0:
+ raise IOError, "decoder error %d" % e
+ else:
+ self.data = self.data[n:]
+
+ return s
+
+ def chunk_DEND(self, offset, bytes):
+ return self.chunk_IEND(offset, bytes)
+
+ def chunk_JEND(self, offset, bytes):
+ return self.chunk_IEND(offset, bytes)
+
+ def chunk_UEND(self, offset, bytes):
+ return self.chunk_IEND(offset, bytes)
+
+ def chunk_IEND(self, offset, bytes):
+ "IEND -- end of image"
+
+ # we now have a new image. carry out the operation
+ # defined by the image header.
+
+ # won't need these anymore
+ del self.decoder
+ del self.data
+
+ self.__store()
+
+ return self.fp.read(bytes)
+
+ def __store(self):
+
+ # apply operation
+ cid = self.action[0]
+
+ if cid in ["BLNK", "IHDR", "JHDR", "UHDR"]:
+ # store
+ self.images[self.id] = self.im
+
+ elif cid == "DHDR":
+ # paste
+ cid, mode, bbox = self.action
+ im0 = self.images[self.id]
+ im1 = self.im
+ if mode == 0:
+ im1 = im1.chop_add_modulo(im0.crop(bbox))
+ im0.paste(im1, bbox)
+
+ self.count = self.count - 1
+
+ if self.count == 0 and self.show:
+ self.im = self.images[self.id]
+ raise EOFError # end of this frame
+
+ def chunk_PLTE(self, offset, bytes):
+ "PLTE -- palette data"
+
+ s = self.fp.read(bytes)
+ if self.mode == "P":
+ self.palette = ImagePalette.raw("RGB", s)
+ return s
+
+ def chunk_sYNC(self, offset, bytes):
+ "SYNC -- reset decoder"
+
+ if self.count != 0:
+ raise SyntaxError, "misplaced sYNC chunk"
+
+ s = self.fp.read(bytes)
+ self.__reset()
+ return s
+
+
+# --------------------------------------------------------------------
+# ARG reader
+
+def _accept(prefix):
+ return prefix[:8] == MAGIC
+
+##
+# Image plugin for the experimental Animated Raster Graphics format.
+
+class ArgImageFile(ImageFile.ImageFile):
+
+ format = "ARG"
+ format_description = "Animated raster graphics"
+
+ def _open(self):
+
+ if self.fp.read(8) != MAGIC:
+ raise SyntaxError, "not an ARG file"
+
+ self.arg = ArgStream(self.fp)
+
+ # read and process the first chunk (AHDR)
+
+ cid, offset, bytes = self.arg.read()
+
+ if cid != "AHDR":
+ raise SyntaxError, "expected an AHDR chunk"
+
+ s = self.arg.call(cid, offset, bytes)
+
+ self.arg.crc(cid, s)
+
+ # image characteristics
+ self.mode = self.arg.mode
+ self.size = self.arg.size
+
+ def load(self):
+
+ if self.arg.im is None:
+ self.seek(0)
+
+ # image data
+ self.im = self.arg.im
+ self.palette = self.arg.palette
+
+ # set things up for further processing
+ Image.Image.load(self)
+
+ def seek(self, frame):
+
+ if self.arg.eof:
+ raise EOFError, "end of animation"
+
+ self.fp = self.arg.fp
+
+ while 1:
+
+ #
+ # process chunks
+
+ cid, offset, bytes = self.arg.read()
+
+ if self.arg.eof:
+ raise EOFError, "end of animation"
+
+ try:
+ s = self.arg.call(cid, offset, bytes)
+ except EOFError:
+ break
+
+ except "glurk": # AttributeError
+ if Image.DEBUG:
+ print cid, bytes, "(unknown)"
+ s = self.fp.read(bytes)
+
+ self.arg.crc(cid, s)
+
+ self.fp.read(4) # ship extra CRC
+
+ def tell(self):
+ return 0
+
+ def verify(self):
+ "Verify ARG file"
+
+ # back up to first chunk
+ self.fp.seek(8)
+
+ self.arg.verify(self)
+ self.arg.close()
+
+ self.fp = None
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("ARG", ArgImageFile, _accept)
+
+Image.register_extension("ARG", ".arg")
+
+Image.register_mime("ARG", "video/x-arg")
diff --git a/PIL/BdfFontFile.py b/PIL/BdfFontFile.py
new file mode 100644
index 0000000..9297a18
--- /dev/null
+++ b/PIL/BdfFontFile.py
@@ -0,0 +1,133 @@
+#
+# The Python Imaging Library
+# $Id: BdfFontFile.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# bitmap distribution font (bdf) file parser
+#
+# history:
+# 1996-05-16 fl created (as bdf2pil)
+# 1997-08-25 fl converted to FontFile driver
+# 2001-05-25 fl removed bogus __init__ call
+# 2002-11-20 fl robustification (from Kevin Cazabon, Dmitry Vasiliev)
+# 2003-04-22 fl more robustification (from Graham Dumpleton)
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1997-2003 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+import FontFile
+
+import string
+
+# --------------------------------------------------------------------
+# parse X Bitmap Distribution Format (BDF)
+# --------------------------------------------------------------------
+
+bdf_slant = {
+ "R": "Roman",
+ "I": "Italic",
+ "O": "Oblique",
+ "RI": "Reverse Italic",
+ "RO": "Reverse Oblique",
+ "OT": "Other"
+}
+
+bdf_spacing = {
+ "P": "Proportional",
+ "M": "Monospaced",
+ "C": "Cell"
+}
+
+def bdf_char(f):
+
+ # skip to STARTCHAR
+ while 1:
+ s = f.readline()
+ if not s:
+ return None
+ if s[:9] == "STARTCHAR":
+ break
+ id = string.strip(s[9:])
+
+ # load symbol properties
+ props = {}
+ while 1:
+ s = f.readline()
+ if not s or s[:6] == "BITMAP":
+ break
+ i = string.find(s, " ")
+ props[s[:i]] = s[i+1:-1]
+
+ # load bitmap
+ bitmap = []
+ while 1:
+ s = f.readline()
+ if not s or s[:7] == "ENDCHAR":
+ break
+ bitmap.append(s[:-1])
+ bitmap = string.join(bitmap, "")
+
+ [x, y, l, d] = map(int, string.split(props["BBX"]))
+ [dx, dy] = map(int, string.split(props["DWIDTH"]))
+
+ bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
+
+ try:
+ im = Image.fromstring("1", (x, y), bitmap, "hex", "1")
+ except ValueError:
+ # deal with zero-width characters
+ im = Image.new("1", (x, y))
+
+ return id, int(props["ENCODING"]), bbox, im
+
+##
+# Font file plugin for the X11 BDF format.
+
+class BdfFontFile(FontFile.FontFile):
+
+ def __init__(self, fp):
+
+ FontFile.FontFile.__init__(self)
+
+ s = fp.readline()
+ if s[:13] != "STARTFONT 2.1":
+ raise SyntaxError, "not a valid BDF file"
+
+ props = {}
+ comments = []
+
+ while 1:
+ s = fp.readline()
+ if not s or s[:13] == "ENDPROPERTIES":
+ break
+ i = string.find(s, " ")
+ props[s[:i]] = s[i+1:-1]
+ if s[:i] in ["COMMENT", "COPYRIGHT"]:
+ if string.find(s, "LogicalFontDescription") < 0:
+ comments.append(s[i+1:-1])
+
+ font = string.split(props["FONT"], "-")
+
+ font[4] = bdf_slant[string.upper(font[4])]
+ font[11] = bdf_spacing[string.upper(font[11])]
+
+ ascent = int(props["FONT_ASCENT"])
+ descent = int(props["FONT_DESCENT"])
+
+ fontname = string.join(font[1:], ";")
+
+ # print "#", fontname
+ # for i in comments:
+ # print "#", i
+
+ font = []
+ while 1:
+ c = bdf_char(fp)
+ if not c:
+ break
+ id, ch, (xy, dst, src), im = c
+ if ch >= 0 and ch < len(self.glyph):
+ self.glyph[ch] = xy, dst, src, im
diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py
new file mode 100644
index 0000000..a939d9b
--- /dev/null
+++ b/PIL/BmpImagePlugin.py
@@ -0,0 +1,245 @@
+#
+# The Python Imaging Library.
+# $Id: BmpImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# BMP file handler
+#
+# Windows (and OS/2) native bitmap storage format.
+#
+# history:
+# 1995-09-01 fl Created
+# 1996-04-30 fl Added save
+# 1997-08-27 fl Fixed save of 1-bit images
+# 1998-03-06 fl Load P images as L where possible
+# 1998-07-03 fl Load P images as 1 where possible
+# 1998-12-29 fl Handle small palettes
+# 2002-12-30 fl Fixed load of 1-bit palette images
+# 2003-04-21 fl Fixed load of 1-bit monochrome images
+# 2003-04-23 fl Added limited support for BI_BITFIELDS compression
+#
+# Copyright (c) 1997-2003 by Secret Labs AB
+# Copyright (c) 1995-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.7"
+
+
+import string
+import Image, ImageFile, ImagePalette
+
+
+#
+# --------------------------------------------------------------------
+# Read BMP file
+
+def i16(c):
+ return ord(c[0]) + (ord(c[1])<<8)
+
+def i32(c):
+ return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24)
+
+
+BIT2MODE = {
+ # bits => mode, rawmode
+ 1: ("P", "P;1"),
+ 4: ("P", "P;4"),
+ 8: ("P", "P"),
+ 16: ("RGB", "BGR;16"),
+ 24: ("RGB", "BGR"),
+ 32: ("RGB", "BGRX")
+}
+
+def _accept(prefix):
+ return prefix[:2] == "BM"
+
+##
+# Image plugin for the Windows BMP format.
+
+class BmpImageFile(ImageFile.ImageFile):
+
+ format = "BMP"
+ format_description = "Windows Bitmap"
+
+ def _bitmap(self, header = 0, offset = 0):
+
+ if header:
+ self.fp.seek(header)
+
+ read = self.fp.read
+
+ # CORE/INFO
+ s = read(4)
+ s = s + ImageFile._safe_read(self.fp, i32(s)-4)
+
+ if len(s) == 12:
+
+ # OS/2 1.0 CORE
+ bits = i16(s[10:])
+ self.size = i16(s[4:]), i16(s[6:])
+ compression = 0
+ lutsize = 3
+ colors = 0
+
+ elif len(s) in [40, 64]:
+
+ # WIN 3.1 or OS/2 2.0 INFO
+ bits = i16(s[14:])
+ self.size = i32(s[4:]), i32(s[8:])
+ compression = i32(s[16:])
+ lutsize = 4
+ colors = i32(s[32:])
+
+ else:
+ raise IOError("Unsupported BMP header type (%d)" % len(s))
+
+ if not colors:
+ colors = 1 << bits
+
+ # MODE
+ try:
+ self.mode, rawmode = BIT2MODE[bits]
+ except KeyError:
+ raise IOError("Unsupported BMP pixel depth (%d)" % bits)
+
+ if compression == 3:
+ # BI_BITFIELDS compression
+ mask = i32(read(4)), i32(read(4)), i32(read(4))
+ if bits == 32 and mask == (0xff0000, 0x00ff00, 0x0000ff):
+ rawmode = "BGRX"
+ elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f):
+ rawmode = "BGR;16"
+ elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f):
+ rawmode = "BGR;15"
+ else:
+ # print bits, map(hex, mask)
+ raise IOError("Unsupported BMP bitfields layout")
+ elif compression != 0:
+ raise IOError("Unsupported BMP compression (%d)" % compression)
+
+ # LUT
+ if self.mode == "P":
+ palette = []
+ greyscale = 1
+ if colors == 2:
+ indices = (0, 255)
+ else:
+ indices = range(colors)
+ for i in indices:
+ rgb = read(lutsize)[:3]
+ if rgb != chr(i)*3:
+ greyscale = 0
+ palette.append(rgb)
+ if greyscale:
+ if colors == 2:
+ self.mode = rawmode = "1"
+ else:
+ self.mode = rawmode = "L"
+ else:
+ self.mode = "P"
+ self.palette = ImagePalette.raw(
+ "BGR", string.join(palette, "")
+ )
+
+ if not offset:
+ offset = self.fp.tell()
+
+ self.tile = [("raw",
+ (0, 0) + self.size,
+ offset,
+ (rawmode, ((self.size[0]*bits+31)>>3)&(~3), -1))]
+
+ self.info["compression"] = compression
+
+ def _open(self):
+
+ # HEAD
+ s = self.fp.read(14)
+ if s[:2] != "BM":
+ raise SyntaxError("Not a BMP file")
+ offset = i32(s[10:])
+
+ self._bitmap(offset=offset)
+
+
+class DibImageFile(BmpImageFile):
+
+ format = "DIB"
+ format_description = "Windows Bitmap"
+
+ def _open(self):
+ self._bitmap()
+
+#
+# --------------------------------------------------------------------
+# Write BMP file
+
+def o16(i):
+ return chr(i&255) + chr(i>>8&255)
+
+def o32(i):
+ return chr(i&255) + chr(i>>8&255) + chr(i>>16&255) + chr(i>>24&255)
+
+SAVE = {
+ "1": ("1", 1, 2),
+ "L": ("L", 8, 256),
+ "P": ("P", 8, 256),
+ "RGB": ("BGR", 24, 0),
+}
+
+def _save(im, fp, filename, check=0):
+
+ try:
+ rawmode, bits, colors = SAVE[im.mode]
+ except KeyError:
+ raise IOError("cannot write mode %s as BMP" % im.mode)
+
+ if check:
+ return check
+
+ stride = ((im.size[0]*bits+7)/8+3)&(~3)
+ header = 40 # or 64 for OS/2 version 2
+ offset = 14 + header + colors * 4
+ image = stride * im.size[1]
+
+ # bitmap header
+ fp.write("BM" + # file type (magic)
+ o32(offset+image) + # file size
+ o32(0) + # reserved
+ o32(offset)) # image data offset
+
+ # bitmap info header
+ fp.write(o32(header) + # info header size
+ o32(im.size[0]) + # width
+ o32(im.size[1]) + # height
+ o16(1) + # planes
+ o16(bits) + # depth
+ o32(0) + # compression (0=uncompressed)
+ o32(image) + # size of bitmap
+ o32(1) + o32(1) + # resolution
+ o32(colors) + # colors used
+ o32(colors)) # colors important
+
+ fp.write("\000" * (header - 40)) # padding (for OS/2 format)
+
+ if im.mode == "1":
+ for i in (0, 255):
+ fp.write(chr(i) * 4)
+ elif im.mode == "L":
+ for i in range(256):
+ fp.write(chr(i) * 4)
+ elif im.mode == "P":
+ fp.write(im.im.getpalette("RGB", "BGRX"))
+
+ ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, stride, -1))])
+
+#
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
+Image.register_save(BmpImageFile.format, _save)
+
+Image.register_extension(BmpImageFile.format, ".bmp")
diff --git a/PIL/BufrStubImagePlugin.py b/PIL/BufrStubImagePlugin.py
new file mode 100644
index 0000000..ee02659
--- /dev/null
+++ b/PIL/BufrStubImagePlugin.py
@@ -0,0 +1,68 @@
+#
+# The Python Imaging Library
+# $Id: BufrStubImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# BUFR stub adapter
+#
+# Copyright (c) 1996-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image, ImageFile
+
+_handler = None
+
+##
+# Install application-specific BUFR image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+# --------------------------------------------------------------------
+# Image adapter
+
+def _accept(prefix):
+ return prefix[:4] == "BUFR" or prefix[:4] == "ZCZC"
+
+class BufrStubImageFile(ImageFile.StubImageFile):
+
+ format = "BUFR"
+ format_description = "BUFR"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(8)):
+ raise SyntaxError("Not a BUFR file")
+
+ self.fp.seek(offset)
+
+ # make something up
+ self.mode = "F"
+ self.size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("BUFR save handler not installed")
+ _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept)
+Image.register_save(BufrStubImageFile.format, _save)
+
+Image.register_extension(BufrStubImageFile.format, ".bufr")
diff --git a/PIL/ContainerIO.py b/PIL/ContainerIO.py
new file mode 100644
index 0000000..7b7283f
--- /dev/null
+++ b/PIL/ContainerIO.py
@@ -0,0 +1,116 @@
+#
+# The Python Imaging Library.
+# $Id: ContainerIO.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# a class to read from a container file
+#
+# History:
+# 1995-06-18 fl Created
+# 1995-09-07 fl Added readline(), readlines()
+#
+# Copyright (c) 1997-2001 by Secret Labs AB
+# Copyright (c) 1995 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# A file object that provides read access to a part of an existing
+# file (for example a TAR file).
+
+class ContainerIO:
+
+ ##
+ # Create file object.
+ #
+ # @param file Existing file.
+ # @param offset Start of region, in bytes.
+ # @param length Size of region, in bytes.
+
+ def __init__(self, file, offset, length):
+ self.fh = file
+ self.pos = 0
+ self.offset = offset
+ self.length = length
+ self.fh.seek(offset)
+
+ ##
+ # Always false.
+
+ def isatty(self):
+ return 0
+
+ ##
+ # Move file pointer.
+ #
+ # @param offset Offset in bytes.
+ # @param mode Starting position. Use 0 for beginning of region, 1
+ # for current offset, and 2 for end of region. You cannot move
+ # the pointer outside the defined region.
+
+ def seek(self, offset, mode = 0):
+ if mode == 1:
+ self.pos = self.pos + offset
+ elif mode == 2:
+ self.pos = self.length + offset
+ else:
+ self.pos = offset
+ # clamp
+ self.pos = max(0, min(self.pos, self.length))
+ self.fh.seek(self.offset + self.pos)
+
+ ##
+ # Get current file pointer.
+ #
+ # @return Offset from start of region, in bytes.
+
+ def tell(self):
+ return self.pos
+
+ ##
+ # Read data.
+ #
+ # @def read(bytes=0)
+ # @param bytes Number of bytes to read. If omitted or zero,
+ # read until end of region.
+ # @return An 8-bit string.
+
+ def read(self, n = 0):
+ if n:
+ n = min(n, self.length - self.pos)
+ else:
+ n = self.length - self.pos
+ if not n: # EOF
+ return ""
+ self.pos = self.pos + n
+ return self.fh.read(n)
+
+ ##
+ # Read a line of text.
+ #
+ # @return An 8-bit string.
+
+ def readline(self):
+ s = ""
+ while 1:
+ c = self.read(1)
+ if not c:
+ break
+ s = s + c
+ if c == "\n":
+ break
+ return s
+
+ ##
+ # Read multiple lines of text.
+ #
+ # @return A list of 8-bit strings.
+
+ def readlines(self):
+ l = []
+ while 1:
+ s = self.readline()
+ if not s:
+ break
+ l.append(s)
+ return l
diff --git a/PIL/CurImagePlugin.py b/PIL/CurImagePlugin.py
new file mode 100644
index 0000000..b879da3
--- /dev/null
+++ b/PIL/CurImagePlugin.py
@@ -0,0 +1,90 @@
+#
+# The Python Imaging Library.
+# $Id: CurImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# Windows Cursor support for PIL
+#
+# notes:
+# uses BmpImagePlugin.py to read the bitmap data.
+#
+# history:
+# 96-05-27 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+import string
+
+import Image, BmpImagePlugin
+
+
+#
+# --------------------------------------------------------------------
+
+def i16(c):
+ return ord(c[0]) + (ord(c[1])<<8)
+
+def i32(c):
+ return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24)
+
+
+def _accept(prefix):
+ return prefix[:4] == "\0\0\2\0"
+
+##
+# Image plugin for Windows Cursor files.
+
+class CurImageFile(BmpImagePlugin.BmpImageFile):
+
+ format = "CUR"
+ format_description = "Windows Cursor"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ # check magic
+ s = self.fp.read(6)
+ if not _accept(s):
+ raise SyntaxError, "not an CUR file"
+
+ # pick the largest cursor in the file
+ m = ""
+ for i in range(i16(s[4:])):
+ s = self.fp.read(16)
+ if not m:
+ m = s
+ elif ord(s[0]) > ord(m[0]) and ord(s[1]) > ord(m[1]):
+ m = s
+ #print "width", ord(s[0])
+ #print "height", ord(s[1])
+ #print "colors", ord(s[2])
+ #print "reserved", ord(s[3])
+ #print "hotspot x", i16(s[4:])
+ #print "hotspot y", i16(s[6:])
+ #print "bytes", i32(s[8:])
+ #print "offset", i32(s[12:])
+
+ # load as bitmap
+ self._bitmap(i32(m[12:]) + offset)
+
+ # patch up the bitmap height
+ self.size = self.size[0], self.size[1]/2
+ d, e, o, a = self.tile[0]
+ self.tile[0] = d, (0,0)+self.size, o, a
+
+ return
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("CUR", CurImageFile, _accept)
+
+Image.register_extension("CUR", ".cur")
diff --git a/PIL/DcxImagePlugin.py b/PIL/DcxImagePlugin.py
new file mode 100644
index 0000000..3a0df01
--- /dev/null
+++ b/PIL/DcxImagePlugin.py
@@ -0,0 +1,78 @@
+#
+# The Python Imaging Library.
+# $Id: DcxImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# DCX file handling
+#
+# DCX is a container file format defined by Intel, commonly used
+# for fax applications. Each DCX file consists of a directory
+# (a list of file offsets) followed by a set of (usually 1-bit)
+# PCX files.
+#
+# History:
+# 1995-09-09 fl Created
+# 1996-03-20 fl Properly derived from PcxImageFile.
+# 1998-07-15 fl Renamed offset attribute to avoid name clash
+# 2002-07-30 fl Fixed file handling
+#
+# Copyright (c) 1997-98 by Secret Labs AB.
+# Copyright (c) 1995-96 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.2"
+
+import Image, ImageFile
+
+from PcxImagePlugin import PcxImageFile
+
+MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
+
+def i32(c):
+ return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24)
+
+def _accept(prefix):
+ return i32(prefix) == MAGIC
+
+##
+# Image plugin for the Intel DCX format.
+
+class DcxImageFile(PcxImageFile):
+
+ format = "DCX"
+ format_description = "Intel DCX"
+
+ def _open(self):
+
+ # Header
+ s = self.fp.read(4)
+ if i32(s) != MAGIC:
+ raise SyntaxError, "not a DCX file"
+
+ # Component directory
+ self._offset = []
+ for i in range(1024):
+ offset = i32(self.fp.read(4))
+ if not offset:
+ break
+ self._offset.append(offset)
+
+ self.__fp = self.fp
+ self.seek(0)
+
+ def seek(self, frame):
+ if frame >= len(self._offset):
+ raise EOFError("attempt to seek outside DCX directory")
+ self.frame = frame
+ self.fp = self.__fp
+ self.fp.seek(self._offset[frame])
+ PcxImageFile._open(self)
+
+ def tell(self):
+ return self.frame
+
+
+Image.register_open("DCX", DcxImageFile, _accept)
+
+Image.register_extension("DCX", ".dcx")
diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py
new file mode 100644
index 0000000..e0a608e
--- /dev/null
+++ b/PIL/EpsImagePlugin.py
@@ -0,0 +1,349 @@
+#
+# The Python Imaging Library.
+# $Id: EpsImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# EPS file handling
+#
+# History:
+# 1995-09-01 fl Created (0.1)
+# 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2)
+# 1996-08-22 fl Don't choke on floating point BoundingBox values
+# 1996-08-23 fl Handle files from Macintosh (0.3)
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
+# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.5"
+
+import re, string
+import Image, ImageFile
+
+#
+# --------------------------------------------------------------------
+
+def i32(c):
+ return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24)
+
+def o32(i):
+ return chr(i&255) + chr(i>>8&255) + chr(i>>16&255) + chr(i>>24&255)
+
+split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
+field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
+
+def Ghostscript(tile, size, fp):
+ """Render an image using Ghostscript (Unix only)"""
+
+ # Unpack decoder tile
+ decoder, tile, offset, data = tile[0]
+ length, bbox = data
+
+ import tempfile, os
+
+ file = tempfile.mktemp()
+
+ # Build ghostscript command
+ command = ["gs",
+ "-q", # quite mode
+ "-g%dx%d" % size, # set output geometry (pixels)
+ "-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
+ "-sDEVICE=ppmraw", # ppm driver
+ "-sOutputFile=%s" % file,# output file
+ "- >/dev/null 2>/dev/null"]
+
+ command = string.join(command)
+
+ # push data through ghostscript
+ try:
+ gs = os.popen(command, "w")
+ # adjust for image origin
+ if bbox[0] != 0 or bbox[1] != 0:
+ gs.write("%d %d translate\n" % (-bbox[0], -bbox[1]))
+ fp.seek(offset)
+ while length > 0:
+ s = fp.read(8192)
+ if not s:
+ break
+ length = length - len(s)
+ gs.write(s)
+ status = gs.close()
+ if status:
+ raise IOError("gs failed (status %d)" % status)
+ im = Image.core.open_ppm(file)
+ finally:
+ try: os.unlink(file)
+ except: pass
+
+ return im
+
+
+class PSFile:
+ """Wrapper that treats either CR or LF as end of line."""
+ def __init__(self, fp):
+ self.fp = fp
+ self.char = None
+ def __getattr__(self, id):
+ v = getattr(self.fp, id)
+ setattr(self, id, v)
+ return v
+ def seek(self, offset, whence=0):
+ self.char = None
+ self.fp.seek(offset, whence)
+ def tell(self):
+ pos = self.fp.tell()
+ if self.char:
+ pos = pos - 1
+ return pos
+ def readline(self):
+ s = ""
+ if self.char:
+ c = self.char
+ self.char = None
+ else:
+ c = self.fp.read(1)
+ while c not in "\r\n":
+ s = s + c
+ c = self.fp.read(1)
+ if c == "\r":
+ self.char = self.fp.read(1)
+ if self.char == "\n":
+ self.char = None
+ return s + "\n"
+
+
+def _accept(prefix):
+ return prefix[:4] == "%!PS" or i32(prefix) == 0xC6D3D0C5L
+
+##
+# Image plugin for Encapsulated Postscript. This plugin supports only
+# a few variants of this format.
+
+class EpsImageFile(ImageFile.ImageFile):
+ """EPS File Parser for the Python Imaging Library"""
+
+ format = "EPS"
+ format_description = "Encapsulated Postscript"
+
+ def _open(self):
+
+ # FIXME: should check the first 512 bytes to see if this
+ # really is necessary (platform-dependent, though...)
+
+ fp = PSFile(self.fp)
+
+ # HEAD
+ s = fp.read(512)
+ if s[:4] == "%!PS":
+ offset = 0
+ fp.seek(0, 2)
+ length = fp.tell()
+ elif i32(s) == 0xC6D3D0C5L:
+ offset = i32(s[4:])
+ length = i32(s[8:])
+ fp.seek(offset)
+ else:
+ raise SyntaxError, "not an EPS file"
+
+ fp.seek(offset)
+
+ box = None
+
+ self.mode = "RGB"
+ self.size = 1, 1 # FIXME: huh?
+
+ #
+ # Load EPS header
+
+ s = fp.readline()
+
+ while s:
+
+ if len(s) > 255:
+ raise SyntaxError, "not an EPS file"
+
+ if s[-2:] == '\r\n':
+ s = s[:-2]
+ elif s[-1:] == '\n':
+ s = s[:-1]
+
+ try:
+ m = split.match(s)
+ except re.error, v:
+ raise SyntaxError, "not an EPS file"
+
+ if m:
+ k, v = m.group(1, 2)
+ self.info[k] = v
+ if k == "BoundingBox":
+ try:
+ # Note: The DSC spec says that BoundingBox
+ # fields should be integers, but some drivers
+ # put floating point values there anyway.
+ box = map(int, map(float, string.split(v)))
+ self.size = box[2] - box[0], box[3] - box[1]
+ self.tile = [("eps", (0,0) + self.size, offset,
+ (length, box))]
+ except:
+ pass
+
+ else:
+
+ m = field.match(s)
+
+ if m:
+ k = m.group(1)
+ if k == "EndComments":
+ break
+ if k[:8] == "PS-Adobe":
+ self.info[k[:8]] = k[9:]
+ else:
+ self.info[k] = ""
+ else:
+ raise IOError, "bad EPS header"
+
+ s = fp.readline()
+
+ if s[:1] != "%":
+ break
+
+
+ #
+ # Scan for an "ImageData" descriptor
+
+ while s[0] == "%":
+
+ if len(s) > 255:
+ raise SyntaxError, "not an EPS file"
+
+ if s[-2:] == '\r\n':
+ s = s[:-2]
+ elif s[-1:] == '\n':
+ s = s[:-1]
+
+ if s[:11] == "%ImageData:":
+
+ [x, y, bi, mo, z3, z4, en, id] =\
+ string.split(s[11:], maxsplit=7)
+
+ x = int(x); y = int(y)
+
+ bi = int(bi)
+ mo = int(mo)
+
+ en = int(en)
+
+ if en == 1:
+ decoder = "eps_binary"
+ elif en == 2:
+ decoder = "eps_hex"
+ else:
+ break
+ if bi != 8:
+ break
+ if mo == 1:
+ self.mode = "L"
+ elif mo == 2:
+ self.mode = "LAB"
+ elif mo == 3:
+ self.mode = "RGB"
+ else:
+ break
+
+ if id[:1] == id[-1:] == '"':
+ id = id[1:-1]
+
+ # Scan forward to the actual image data
+ while 1:
+ s = fp.readline()
+ if not s:
+ break
+ if s[:len(id)] == id:
+ self.size = x, y
+ self.tile2 = [(decoder,
+ (0, 0, x, y),
+ fp.tell(),
+ 0)]
+ return
+
+ s = fp.readline()
+ if not s:
+ break
+
+ if not box:
+ raise IOError, "cannot determine EPS bounding box"
+
+ def load(self):
+ # Load EPS via Ghostscript
+ if not self.tile:
+ return
+ self.im = Ghostscript(self.tile, self.size, self.fp)
+ self.mode = self.im.mode
+ self.size = self.im.size
+ self.tile = []
+
+#
+# --------------------------------------------------------------------
+
+def _save(im, fp, filename, eps=1):
+ """EPS Writer for the Python Imaging Library."""
+
+ #
+ # make sure image data is available
+ im.load()
+
+ #
+ # determine postscript image mode
+ if im.mode == "L":
+ operator = (8, 1, "image")
+ elif im.mode == "RGB":
+ operator = (8, 3, "false 3 colorimage")
+ elif im.mode == "CMYK":
+ operator = (8, 4, "false 4 colorimage")
+ else:
+ raise ValueError, "image mode is not supported"
+
+ if eps:
+ #
+ # write EPS header
+ fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
+ fp.write("%%Creator: PIL 0.1 EpsEncode\n")
+ #fp.write("%%CreationDate: %s"...)
+ fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
+ fp.write("%%Pages: 1\n")
+ fp.write("%%EndComments\n")
+ fp.write("%%Page: 1 1\n")
+ fp.write("%%ImageData: %d %d " % im.size)
+ fp.write("%d %d 0 1 1 \"%s\"\n" % operator)
+
+ #
+ # image header
+ fp.write("gsave\n")
+ fp.write("10 dict begin\n")
+ fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
+ fp.write("%d %d scale\n" % im.size)
+ fp.write("%d %d 8\n" % im.size) # <= bits
+ fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
+ fp.write("{ currentfile buf readhexstring pop } bind\n")
+ fp.write("%s\n" % operator[2])
+
+ ImageFile._save(im, fp, [("eps", (0,0)+im.size, 0, None)])
+
+ fp.write("\n%%%%EndBinary\n")
+ fp.write("grestore end\n")
+ fp.flush()
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
+
+Image.register_save(EpsImageFile.format, _save)
+
+Image.register_extension(EpsImageFile.format, ".ps")
+Image.register_extension(EpsImageFile.format, ".eps")
+
+Image.register_mime(EpsImageFile.format, "application/postscript")
diff --git a/PIL/ExifTags.py b/PIL/ExifTags.py
new file mode 100644
index 0000000..01a6b74
--- /dev/null
+++ b/PIL/ExifTags.py
@@ -0,0 +1,157 @@
+#
+# The Python Imaging Library.
+# $Id: ExifTags.py 2631 2006-02-12 23:41:52Z fredrik $
+#
+# EXIF tags
+#
+# Copyright (c) 2003 by Secret Labs AB
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# This module provides constants and clear-text names for various
+# well-known EXIF tags.
+##
+
+##
+# Maps EXIF tags to tag names.
+
+TAGS = {
+
+ # possibly incomplete
+ 0x0100: "ImageWidth",
+ 0x0101: "ImageLength",
+ 0x0102: "BitsPerSample",
+ 0x0103: "Compression",
+ 0x0106: "PhotometricInterpretation",
+ 0x010e: "ImageDescription",
+ 0x010f: "Make",
+ 0x0110: "Model",
+ 0x0111: "StripOffsets",
+ 0x0112: "Orientation",
+ 0x0115: "SamplesPerPixel",
+ 0x0116: "RowsPerStrip",
+ 0x0117: "StripByteConunts",
+ 0x011a: "XResolution",
+ 0x011a: "XResolution",
+ 0x011b: "YResolution",
+ 0x011b: "YResolution",
+ 0x011c: "PlanarConfiguration",
+ 0x0128: "ResolutionUnit",
+ 0x0128: "ResolutionUnit",
+ 0x012d: "TransferFunction",
+ 0x0131: "Software",
+ 0x0132: "DateTime",
+ 0x013b: "Artist",
+ 0x013e: "WhitePoint",
+ 0x013f: "PrimaryChromaticities",
+ 0x0201: "JpegIFOffset",
+ 0x0202: "JpegIFByteCount",
+ 0x0211: "YCbCrCoefficients",
+ 0x0211: "YCbCrCoefficients",
+ 0x0212: "YCbCrSubSampling",
+ 0x0213: "YCbCrPositioning",
+ 0x0213: "YCbCrPositioning",
+ 0x0214: "ReferenceBlackWhite",
+ 0x0214: "ReferenceBlackWhite",
+ 0x1000: "RelatedImageFileFormat",
+ 0x1001: "RelatedImageLength",
+ 0x1001: "RelatedImageWidth",
+ 0x828d: "CFARepeatPatternDim",
+ 0x828e: "CFAPattern",
+ 0x828f: "BatteryLevel",
+ 0x8298: "Copyright",
+ 0x829a: "ExposureTime",
+ 0x829d: "FNumber",
+ 0x8769: "ExifOffset",
+ 0x8773: "InterColorProfile",
+ 0x8822: "ExposureProgram",
+ 0x8824: "SpectralSensitivity",
+ 0x8825: "GPSInfo",
+ 0x8827: "ISOSpeedRatings",
+ 0x8828: "OECF",
+ 0x8829: "Interlace",
+ 0x882a: "TimeZoneOffset",
+ 0x882b: "SelfTimerMode",
+ 0x9000: "ExifVersion",
+ 0x9003: "DateTimeOriginal",
+ 0x9004: "DateTimeDigitized",
+ 0x9101: "ComponentsConfiguration",
+ 0x9102: "CompressedBitsPerPixel",
+ 0x9201: "ShutterSpeedValue",
+ 0x9202: "ApertureValue",
+ 0x9203: "BrightnessValue",
+ 0x9204: "ExposureBiasValue",
+ 0x9205: "MaxApertureValue",
+ 0x9206: "SubjectDistance",
+ 0x9207: "MeteringMode",
+ 0x9208: "LightSource",
+ 0x9209: "Flash",
+ 0x920a: "FocalLength",
+ 0x920b: "FlashEnergy",
+ 0x920c: "SpatialFrequencyResponse",
+ 0x920d: "Noise",
+ 0x9211: "ImageNumber",
+ 0x9212: "SecurityClassification",
+ 0x9213: "ImageHistory",
+ 0x9214: "SubjectLocation",
+ 0x9215: "ExposureIndex",
+ 0x9216: "TIFF/EPStandardID",
+ 0x927c: "MakerNote",
+ 0x9286: "UserComment",
+ 0x9290: "SubsecTime",
+ 0x9291: "SubsecTimeOriginal",
+ 0x9292: "SubsecTimeDigitized",
+ 0xa000: "FlashPixVersion",
+ 0xa001: "ColorSpace",
+ 0xa002: "ExifImageWidth",
+ 0xa003: "ExifImageHeight",
+ 0xa004: "RelatedSoundFile",
+ 0xa005: "ExifInteroperabilityOffset",
+ 0xa20b: "FlashEnergy",
+ 0xa20c: "SpatialFrequencyResponse",
+ 0xa20e: "FocalPlaneXResolution",
+ 0xa20f: "FocalPlaneYResolution",
+ 0xa210: "FocalPlaneResolutionUnit",
+ 0xa214: "SubjectLocation",
+ 0xa215: "ExposureIndex",
+ 0xa217: "SensingMethod",
+ 0xa300: "FileSource",
+ 0xa301: "SceneType",
+ 0xa302: "CFAPattern",
+
+}
+
+##
+# Maps EXIF GSP tags to tag names.
+
+GPSTAGS = {
+ 0: "GPSVersionID",
+ 1: "GPSLatitudeRef",
+ 2: "GPSLatitude",
+ 3: "GPSLongitudeRef",
+ 4: "GPSLongitude",
+ 5: "GPSAltitudeRef",
+ 6: "GPSAltitude",
+ 7: "GPSTimeStamp",
+ 8: "GPSSatellites",
+ 9: "GPSStatus",
+ 10: "GPSMeasureMode",
+ 11: "GPSDOP",
+ 12: "GPSSpeedRef",
+ 13: "GPSSpeed",
+ 14: "GPSTrackRef",
+ 15: "GPSTrack",
+ 16: "GPSImgDirectionRef",
+ 17: "GPSImgDirection",
+ 18: "GPSMapDatum",
+ 19: "GPSDestLatitudeRef",
+ 20: "GPSDestLatitude",
+ 21: "GPSDestLongitudeRef",
+ 22: "GPSDestLongitude",
+ 23: "GPSDestBearingRef",
+ 24: "GPSDestBearing",
+ 25: "GPSDestDistanceRef",
+ 26: "GPSDestDistance"
+}
diff --git a/PIL/FitsStubImagePlugin.py b/PIL/FitsStubImagePlugin.py
new file mode 100644
index 0000000..05f5d46
--- /dev/null
+++ b/PIL/FitsStubImagePlugin.py
@@ -0,0 +1,73 @@
+#
+# The Python Imaging Library
+# $Id: FitsStubImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# FITS stub adapter
+#
+# Copyright (c) 1998-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image, ImageFile
+
+_handler = None
+
+##
+# Install application-specific FITS image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+# --------------------------------------------------------------------
+# Image adapter
+
+def _accept(prefix):
+ return prefix[:6] == "SIMPLE"
+
+class FITSStubImageFile(ImageFile.StubImageFile):
+
+ format = "FITS"
+ format_description = "FITS"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(6)):
+ raise SyntaxError("Not a FITS file")
+
+ # FIXME: add more sanity checks here; mandatory header items
+ # include SIMPLE, BITPIX, NAXIS, etc.
+
+ self.fp.seek(offset)
+
+ # make something up
+ self.mode = "F"
+ self.size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("FITS save handler not installed")
+ _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(FITSStubImageFile.format, FITSStubImageFile, _accept)
+Image.register_save(FITSStubImageFile.format, _save)
+
+Image.register_extension(FITSStubImageFile.format, ".fit")
+Image.register_extension(FITSStubImageFile.format, ".fits")
diff --git a/PIL/FliImagePlugin.py b/PIL/FliImagePlugin.py
new file mode 100644
index 0000000..5b1bf8e
--- /dev/null
+++ b/PIL/FliImagePlugin.py
@@ -0,0 +1,142 @@
+#
+# The Python Imaging Library.
+# $Id: FliImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# FLI/FLC file handling.
+#
+# History:
+# 95-09-01 fl Created
+# 97-01-03 fl Fixed parser, setup decoder tile
+# 98-07-15 fl Renamed offset attribute to avoid name clash
+#
+# Copyright (c) Secret Labs AB 1997-98.
+# Copyright (c) Fredrik Lundh 1995-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.2"
+
+import Image, ImageFile, ImagePalette
+import string
+
+
+def i16(c):
+ return ord(c[0]) + (ord(c[1])<<8)
+
+def i32(c):
+ return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24)
+
+#
+# decoder
+
+def _accept(prefix):
+ return i16(prefix[4:6]) in [0xAF11, 0xAF12]
+
+##
+# Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
+# method to load individual frames.
+
+class FliImageFile(ImageFile.ImageFile):
+
+ format = "FLI"
+ format_description = "Autodesk FLI/FLC Animation"
+
+ def _open(self):
+
+ # HEAD
+ s = self.fp.read(128)
+ magic = i16(s[4:6])
+ if magic not in [0xAF11, 0xAF12]:
+ raise SyntaxError, "not an FLI/FLC file"
+
+ # image characteristics
+ self.mode = "P"
+ self.size = i16(s[8:10]), i16(s[10:12])
+
+ # animation speed
+ duration = i32(s[16:20])
+ if magic == 0xAF11:
+ duration = (duration * 1000) / 70
+ self.info["duration"] = duration
+
+ # look for palette
+ palette = map(lambda a: (a,a,a), range(256))
+
+ s = self.fp.read(16)
+
+ self.__offset = 128
+
+ if i16(s[4:6]) == 0xF100:
+ # prefix chunk; ignore it
+ self.__offset = self.__offset + i32(s)
+ s = self.fp.read(16)
+
+ if i16(s[4:6]) == 0xF1FA:
+ # look for palette chunk
+ s = self.fp.read(6)
+ if i16(s[4:6]) == 11:
+ self._palette(palette, 2)
+ elif i16(s[4:6]) == 4:
+ self._palette(palette, 0)
+
+ palette = map(lambda (r,g,b): chr(r)+chr(g)+chr(b), palette)
+ self.palette = ImagePalette.raw("RGB", string.join(palette, ""))
+
+ # set things up to decode first frame
+ self.frame = -1
+ self.__fp = self.fp
+
+ self.seek(0)
+
+ def _palette(self, palette, shift):
+ # load palette
+
+ i = 0
+ for e in range(i16(self.fp.read(2))):
+ s = self.fp.read(2)
+ i = i + ord(s[0])
+ n = ord(s[1])
+ if n == 0:
+ n = 256
+ s = self.fp.read(n * 3)
+ for n in range(0, len(s), 3):
+ r = ord(s[n]) << shift
+ g = ord(s[n+1]) << shift
+ b = ord(s[n+2]) << shift
+ palette[i] = (r, g, b)
+ i = i + 1
+
+ def seek(self, frame):
+
+ if frame != self.frame + 1:
+ raise ValueError, "cannot seek to frame %d" % frame
+ self.frame = frame
+
+ # move to next frame
+ self.fp = self.__fp
+ self.fp.seek(self.__offset)
+
+ s = self.fp.read(4)
+ if not s:
+ raise EOFError
+
+ framesize = i32(s)
+
+ self.decodermaxblock = framesize
+ self.tile = [("fli", (0,0)+self.size, self.__offset, None)]
+
+ self.__offset = self.__offset + framesize
+
+ def tell(self):
+
+ return self.frame
+
+#
+# registry
+
+Image.register_open("FLI", FliImageFile, _accept)
+
+Image.register_extension("FLI", ".fli")
+Image.register_extension("FLI", ".flc")
diff --git a/PIL/FontFile.py b/PIL/FontFile.py
new file mode 100644
index 0000000..bf4b905
--- /dev/null
+++ b/PIL/FontFile.py
@@ -0,0 +1,146 @@
+#
+# The Python Imaging Library
+# $Id: FontFile.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# base class for raster font file parsers
+#
+# history:
+# 1997-06-05 fl created
+# 1997-08-19 fl restrict image width
+#
+# Copyright (c) 1997-1998 by Secret Labs AB
+# Copyright (c) 1997-1998 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import os
+import Image
+
+import marshal
+
+try:
+ import zlib
+except ImportError:
+ zlib = None
+
+WIDTH = 800
+
+def puti16(fp, values):
+ # write network order (big-endian) 16-bit sequence
+ for v in values:
+ if v < 0:
+ v = v + 65536
+ fp.write(chr(v>>8&255) + chr(v&255))
+
+##
+# Base class for raster font file handlers.
+
+class FontFile:
+
+ bitmap = None
+
+ def __init__(self):
+
+ self.info = {}
+ self.glyph = [None] * 256
+
+ def __getitem__(self, ix):
+ return self.glyph[ix]
+
+ def compile(self):
+ "Create metrics and bitmap"
+
+ if self.bitmap:
+ return
+
+ # create bitmap large enough to hold all data
+ h = w = maxwidth = 0
+ lines = 1
+ for glyph in self:
+ if glyph:
+ d, dst, src, im = glyph
+ h = max(h, src[3] - src[1])
+ w = w + (src[2] - src[0])
+ if w > WIDTH:
+ lines = lines + 1
+ w = (src[2] - src[0])
+ maxwidth = max(maxwidth, w)
+
+ xsize = maxwidth
+ ysize = lines * h
+
+ if xsize == 0 and ysize == 0:
+ return ""
+
+ self.ysize = h
+
+ # paste glyphs into bitmap
+ self.bitmap = Image.new("1", (xsize, ysize))
+ self.metrics = [None] * 256
+ x = y = 0
+ for i in range(256):
+ glyph = self[i]
+ if glyph:
+ d, dst, src, im = glyph
+ xx, yy = src[2] - src[0], src[3] - src[1]
+ x0, y0 = x, y
+ x = x + xx
+ if x > WIDTH:
+ x, y = 0, y + h
+ x0, y0 = x, y
+ x = xx
+ s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
+ self.bitmap.paste(im.crop(src), s)
+ # print chr(i), dst, s
+ self.metrics[i] = d, dst, s
+
+
+ def save1(self, filename):
+ "Save font in version 1 format"
+
+ self.compile()
+
+ # font data
+ self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
+
+ # font metrics
+ fp = open(os.path.splitext(filename)[0] + ".pil", "wb")
+ fp.write("PILfont\n")
+ fp.write(";;;;;;%d;\n" % self.ysize) # HACK!!!
+ fp.write("DATA\n")
+ for id in range(256):
+ m = self.metrics[id]
+ if not m:
+ puti16(fp, [0] * 10)
+ else:
+ puti16(fp, m[0] + m[1] + m[2])
+ fp.close()
+
+
+ def save2(self, filename):
+ "Save font in version 2 format"
+
+ # THIS IS WORK IN PROGRESS
+
+ self.compile()
+
+ data = marshal.dumps((self.metrics, self.info))
+
+ if zlib:
+ data = "z" + zlib.compress(data, 9)
+ else:
+ data = "u" + data
+
+ fp = open(os.path.splitext(filename)[0] + ".pil", "wb")
+
+ fp.write("PILfont2\n" + self.name + "\n" + "DATA\n")
+
+ fp.write(data)
+
+ self.bitmap.save(fp, "PNG")
+
+ fp.close()
+
+
+ save = save1 # for now
diff --git a/PIL/FpxImagePlugin.py b/PIL/FpxImagePlugin.py
new file mode 100644
index 0000000..d4a1aef
--- /dev/null
+++ b/PIL/FpxImagePlugin.py
@@ -0,0 +1,226 @@
+#
+# THIS IS WORK IN PROGRESS
+#
+# The Python Imaging Library.
+# $Id: FpxImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# FlashPix support for PIL
+#
+# History:
+# 97-01-25 fl Created (reads uncompressed RGB images only)
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+
+import string
+
+import Image, ImageFile
+from OleFileIO import *
+
+
+# we map from colour field tuples to (mode, rawmode) descriptors
+MODES = {
+ # opacity
+ (0x00007ffe): ("A", "L"),
+ # monochrome
+ (0x00010000,): ("L", "L"),
+ (0x00018000, 0x00017ffe): ("RGBA", "LA"),
+ # photo YCC
+ (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
+ (0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"),
+ # standard RGB (NIFRGB)
+ (0x00030000, 0x00030001, 0x00030002): ("RGB","RGB"),
+ (0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA","RGBA"),
+}
+
+#
+# --------------------------------------------------------------------
+
+def _accept(prefix):
+ return prefix[:8] == MAGIC
+
+##
+# Image plugin for the FlashPix images.
+
+class FpxImageFile(ImageFile.ImageFile):
+
+ format = "FPX"
+ format_description = "FlashPix"
+
+ def _open(self):
+ #
+ # read the OLE directory and see if this is a likely
+ # to be a FlashPix file
+
+ try:
+ self.ole = OleFileIO(self.fp)
+ except IOError:
+ raise SyntaxError, "not an FPX file; invalid OLE file"
+
+ if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
+ raise SyntaxError, "not an FPX file; bad root CLSID"
+
+ self._open_index(1)
+
+ def _open_index(self, index = 1):
+ #
+ # get the Image Contents Property Set
+
+ prop = self.ole.getproperties([
+ "Data Object Store %06d" % index,
+ "\005Image Contents"
+ ])
+
+ # size (highest resolution)
+
+ self.size = prop[0x1000002], prop[0x1000003]
+
+ size = max(self.size)
+ i = 1
+ while size > 64:
+ size = size / 2
+ i = i + 1
+ self.maxid = i - 1
+
+ # mode. instead of using a single field for this, flashpix
+ # requires you to specify the mode for each channel in each
+ # resolution subimage, and leaves it to the decoder to make
+ # sure that they all match. for now, we'll cheat and assume
+ # that this is always the case.
+
+ id = self.maxid << 16
+
+ s = prop[0x2000002|id]
+
+ colors = []
+ for i in range(i32(s, 4)):
+ # note: for now, we ignore the "uncalibrated" flag
+ colors.append(i32(s, 8+i*4) & 0x7fffffff)
+
+ self.mode, self.rawmode = MODES[tuple(colors)]
+
+ # load JPEG tables, if any
+ self.jpeg = {}
+ for i in range(256):
+ id = 0x3000001|(i << 16)
+ if prop.has_key(id):
+ self.jpeg[i] = prop[id]
+
+ # print len(self.jpeg), "tables loaded"
+
+ self._open_subimage(1, self.maxid)
+
+ def _open_subimage(self, index = 1, subimage = 0):
+ #
+ # setup tile descriptors for a given subimage
+
+ stream = [
+ "Data Object Store %06d" % index,
+ "Resolution %04d" % subimage,
+ "Subimage 0000 Header"
+ ]
+
+ fp = self.ole.openstream(stream)
+
+ # skip prefix
+ p = fp.read(28)
+
+ # header stream
+ s = fp.read(36)
+
+ size = i32(s, 4), i32(s, 8)
+ tilecount = i32(s, 12)
+ tilesize = i32(s, 16), i32(s, 20)
+ channels = i32(s, 24)
+ offset = i32(s, 28)
+ length = i32(s, 32)
+
+ # print size, self.mode, self.rawmode
+
+ if size != self.size:
+ raise IOError, "subimage mismatch"
+
+ # get tile descriptors
+ fp.seek(28 + offset)
+ s = fp.read(i32(s, 12) * length)
+
+ x = y = 0
+ xsize, ysize = size
+ xtile, ytile = tilesize
+ self.tile = []
+
+ for i in range(0, len(s), length):
+
+ compression = i32(s, i+8)
+
+ if compression == 0:
+ self.tile.append(("raw", (x,y,x+xtile,y+ytile),
+ i32(s, i) + 28, (self.rawmode)))
+
+ elif compression == 1:
+
+ # FIXME: the fill decoder is not implemented
+ self.tile.append(("fill", (x,y,x+xtile,y+ytile),
+ i32(s, i) + 28, (self.rawmode, s[12:16])))
+
+ elif compression == 2:
+
+ internal_color_conversion = ord(s[14])
+ jpeg_tables = ord(s[15])
+ rawmode = self.rawmode
+
+ if internal_color_conversion:
+ # The image is stored as usual (usually YCbCr).
+ if rawmode == "RGBA":
+ # For "RGBA", data is stored as YCbCrA based on
+ # negative RGB. The following trick works around
+ # this problem :
+ jpegmode, rawmode = "YCbCrK", "CMYK"
+ else:
+ jpegmode = None # let the decoder decide
+
+ else:
+ # The image is stored as defined by rawmode
+ jpegmode = rawmode
+
+ self.tile.append(("jpeg", (x,y,x+xtile,y+ytile),
+ i32(s, i) + 28, (rawmode, jpegmode)))
+
+ # FIXME: jpeg tables are tile dependent; the prefix
+ # data must be placed in the tile descriptor itself!
+
+ if jpeg_tables:
+ self.tile_prefix = self.jpeg[jpeg_tables]
+
+ else:
+ raise IOError, "unknown/invalid compression"
+
+ x = x + xtile
+ if x >= xsize:
+ x, y = 0, y + ytile
+ if y >= ysize:
+ break # isn't really required
+
+ self.stream = stream
+ self.fp = None
+
+ def load(self):
+
+ if not self.fp:
+ self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
+
+ ImageFile.ImageFile.load(self)
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("FPX", FpxImageFile, _accept)
+
+Image.register_extension("FPX", ".fpx")
diff --git a/PIL/GbrImagePlugin.py b/PIL/GbrImagePlugin.py
new file mode 100644
index 0000000..273e1f8
--- /dev/null
+++ b/PIL/GbrImagePlugin.py
@@ -0,0 +1,70 @@
+#
+# The Python Imaging Library
+# $Id: GbrImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# load a GIMP brush file
+#
+# History:
+# 96-03-14 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image, ImageFile
+
+def i32(c):
+ return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24L)
+
+def _accept(prefix):
+ return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
+
+##
+# Image plugin for the GIMP brush format.
+
+class GbrImageFile(ImageFile.ImageFile):
+
+ format = "GBR"
+ format_description = "GIMP brush file"
+
+ def _open(self):
+
+ header_size = i32(self.fp.read(4))
+ version = i32(self.fp.read(4))
+ if header_size < 20 or version != 1:
+ raise SyntaxError, "not a GIMP brush"
+
+ width = i32(self.fp.read(4))
+ height = i32(self.fp.read(4))
+ bytes = i32(self.fp.read(4))
+ if width <= 0 or height <= 0 or bytes != 1:
+ raise SyntaxError, "not a GIMP brush"
+
+ comment = self.fp.read(header_size - 20)[:-1]
+
+ self.mode = "L"
+ self.size = width, height
+
+ self.info["comment"] = comment
+
+ # Since the brush is so small, we read the data immediately
+ self.data = self.fp.read(width * height)
+
+ def load(self):
+
+ if not self.data:
+ return
+
+ # create an image out of the brush data block
+ self.im = Image.core.new(self.mode, self.size)
+ self.im.fromstring(self.data)
+ self.data = ""
+
+#
+# registry
+
+Image.register_open("GBR", GbrImageFile, _accept)
+
+Image.register_extension("GBR", ".gbr")
diff --git a/PIL/GdImageFile.py b/PIL/GdImageFile.py
new file mode 100644
index 0000000..e6ddfa7
--- /dev/null
+++ b/PIL/GdImageFile.py
@@ -0,0 +1,86 @@
+#
+# The Python Imaging Library.
+# $Id: GdImageFile.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# GD file handling
+#
+# History:
+# 1996-04-12 fl Created
+#
+# Copyright (c) 1997 by Secret Labs AB.
+# Copyright (c) 1996 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+# NOTE: This format cannot be automatically recognized, so the
+# class is not registered for use with Image.open(). To open a
+# gd file, use the GdImageFile.open() function instead.
+
+# THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This
+# implementation is provided for convenience and demonstrational
+# purposes only.
+
+
+__version__ = "0.1"
+
+import string
+import Image, ImageFile, ImagePalette
+
+def i16(c):
+ return ord(c[1]) + (ord(c[0])<<8)
+
+##
+# Image plugin for the GD uncompressed format. Note that this format
+# is not supported by the standard <b>Image.open</b> function. To use
+# this plugin, you have to import the <b>GdImageFile</b> module and
+# use the <b>GdImageFile.open</b> function.
+
+class GdImageFile(ImageFile.ImageFile):
+
+ format = "GD"
+ format_description = "GD uncompressed images"
+
+ def _open(self):
+
+ # Header
+ s = self.fp.read(775)
+
+ self.mode = "L" # FIXME: "P"
+ self.size = i16(s[0:2]), i16(s[2:4])
+
+ # transparency index
+ tindex = i16(s[5:7])
+ if tindex < 256:
+ self.info["transparent"] = tindex
+
+ self.palette = ImagePalette.raw("RGB", s[7:])
+
+ self.tile = [("raw", (0,0)+self.size, 775, ("L", 0, -1))]
+
+##
+# Load texture from a GD image file.
+#
+# @param filename GD file name, or an opened file handle.
+# @param mode Optional mode. In this version, if the mode argument
+# is given, it must be "r".
+# @return An image instance.
+# @exception IOError If the image could not be read.
+
+def open(fp, mode = "r"):
+
+ if mode != "r":
+ raise ValueError("bad mode")
+
+ if type(fp) == type(""):
+ import __builtin__
+ filename = fp
+ fp = __builtin__.open(fp, "rb")
+ else:
+ filename = ""
+
+ try:
+ return GdImageFile(fp, filename)
+ except SyntaxError:
+ raise IOError("cannot identify this image file")
diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py
new file mode 100644
index 0000000..cce0fe2
--- /dev/null
+++ b/PIL/GifImagePlugin.py
@@ -0,0 +1,405 @@
+#
+# The Python Imaging Library.
+# $Id: GifImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# GIF file handling
+#
+# History:
+# 1995-09-01 fl Created
+# 1996-12-14 fl Added interlace support
+# 1996-12-30 fl Added animation support
+# 1997-01-05 fl Added write support, fixed local colour map bug
+# 1997-02-23 fl Make sure to load raster data in getdata()
+# 1997-07-05 fl Support external decoder (0.4)
+# 1998-07-09 fl Handle all modes when saving (0.5)
+# 1998-07-15 fl Renamed offset attribute to avoid name clash
+# 2001-04-16 fl Added rewind support (seek to frame 0) (0.6)
+# 2001-04-17 fl Added palette optimization (0.7)
+# 2002-06-06 fl Added transparency support for save (0.8)
+# 2004-02-24 fl Disable interlacing for small images
+#
+# Copyright (c) 1997-2004 by Secret Labs AB
+# Copyright (c) 1995-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.9"
+
+
+import Image, ImageFile, ImagePalette
+
+
+# --------------------------------------------------------------------
+# Helpers
+
+def i16(c):
+ return ord(c[0]) + (ord(c[1])<<8)
+
+def o16(i):
+ return chr(i&255) + chr(i>>8&255)
+
+
+# --------------------------------------------------------------------
+# Identify/read GIF files
+
+def _accept(prefix):
+ return prefix[:6] in ["GIF87a", "GIF89a"]
+
+##
+# Image plugin for GIF images. This plugin supports both GIF87 and
+# GIF89 images.
+
+class GifImageFile(ImageFile.ImageFile):
+
+ format = "GIF"
+ format_description = "Compuserve GIF"
+
+ global_palette = None
+
+ def data(self):
+ s = self.fp.read(1)
+ if s and ord(s):
+ return self.fp.read(ord(s))
+ return None
+
+ def _open(self):
+
+ # Screen
+ s = self.fp.read(13)
+ if s[:6] not in ["GIF87a", "GIF89a"]:
+ raise SyntaxError, "not a GIF file"
+
+ self.info["version"] = s[:6]
+
+ self.size = i16(s[6:]), i16(s[8:])
+
+ self.tile = []
+
+ flags = ord(s[10])
+
+ bits = (flags & 7) + 1
+
+ if flags & 128:
+ # get global palette
+ self.info["background"] = ord(s[11])
+ # check if palette contains colour indices
+ p = self.fp.read(3<<bits)
+ for i in range(0, len(p), 3):
+ if not (chr(i/3) == p[i] == p[i+1] == p[i+2]):
+ p = ImagePalette.raw("RGB", p)
+ self.global_palette = self.palette = p
+ break
+
+ self.__fp = self.fp # FIXME: hack
+ self.__rewind = self.fp.tell()
+ self.seek(0) # get ready to read first frame
+
+ def seek(self, frame):
+
+ if frame == 0:
+ # rewind
+ self.__offset = 0
+ self.dispose = None
+ self.__frame = -1
+ self.__fp.seek(self.__rewind)
+
+ if frame != self.__frame + 1:
+ raise ValueError, "cannot seek to frame %d" % frame
+ self.__frame = frame
+
+ self.tile = []
+
+ self.fp = self.__fp
+ if self.__offset:
+ # backup to last frame
+ self.fp.seek(self.__offset)
+ while self.data():
+ pass
+ self.__offset = 0
+
+ if self.dispose:
+ self.im = self.dispose
+ self.dispose = None
+
+ self.palette = self.global_palette
+
+ while 1:
+
+ s = self.fp.read(1)
+ if not s or s == ";":
+ break
+
+ elif s == "!":
+ #
+ # extensions
+ #
+ s = self.fp.read(1)
+ block = self.data()
+ if ord(s) == 249:
+ #
+ # graphic control extension
+ #
+ flags = ord(block[0])
+ if flags & 1:
+ self.info["transparency"] = ord(block[3])
+ self.info["duration"] = i16(block[1:3]) * 10
+ try:
+ # disposal methods
+ if flags & 8:
+ # replace with background colour
+ self.dispose = Image.core.fill("P", self.size,
+ self.info["background"])
+ elif flags & 16:
+ # replace with previous contents
+ self.dispose = self.im.copy()
+ except (AttributeError, KeyError):
+ pass
+ elif ord(s) == 255:
+ #
+ # application extension
+ #
+ self.info["extension"] = block, self.fp.tell()
+ if block[:11] == "NETSCAPE2.0":
+ self.info["loop"] = 1 # FIXME
+ while self.data():
+ pass
+
+ elif s == ",":
+ #
+ # local image
+ #
+ s = self.fp.read(9)
+
+ # extent
+ x0, y0 = i16(s[0:]), i16(s[2:])
+ x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:])
+ flags = ord(s[8])
+
+ interlace = (flags & 64) != 0
+
+ if flags & 128:
+ bits = (flags & 7) + 1
+ self.palette =\
+ ImagePalette.raw("RGB", self.fp.read(3<<bits))
+
+ # image data
+ bits = ord(self.fp.read(1))
+ self.__offset = self.fp.tell()
+ self.tile = [("gif",
+ (x0, y0, x1, y1),
+ self.__offset,
+ (bits, interlace))]
+ break
+
+ else:
+ pass
+ # raise IOError, "illegal GIF tag `%x`" % ord(s)
+
+ if not self.tile:
+ # self.__fp = None
+ raise EOFError, "no more images in GIF file"
+
+ self.mode = "L"
+ if self.palette:
+ self.mode = "P"
+
+ def tell(self):
+ return self.__frame
+
+
+# --------------------------------------------------------------------
+# Write GIF files
+
+try:
+ import _imaging_gif
+except ImportError:
+ _imaging_gif = None
+
+RAWMODE = {
+ "1": "L",
+ "L": "L",
+ "P": "P",
+}
+
+def _save(im, fp, filename):
+
+ if _imaging_gif:
+ # call external driver
+ try:
+ _imaging_gif.save(im, fp, filename)
+ return
+ except IOError:
+ pass # write uncompressed file
+
+ try:
+ rawmode = RAWMODE[im.mode]
+ imOut = im
+ except KeyError:
+ # convert on the fly (EXPERIMENTAL -- I'm not sure PIL
+ # should automatically convert images on save...)
+ if Image.getmodebase(im.mode) == "RGB":
+ imOut = im.convert("P")
+ rawmode = "P"
+ else:
+ imOut = im.convert("L")
+ rawmode = "L"
+
+ # header
+ for s in getheader(imOut, im.encoderinfo):
+ fp.write(s)
+
+ flags = 0
+
+ try:
+ interlace = im.encoderinfo["interlace"]
+ except KeyError:
+ interlace = 1
+
+ # workaround for @PIL153
+ if min(im.size) < 16:
+ interlace = 0
+
+ if interlace:
+ flags = flags | 64
+
+ try:
+ transparency = im.encoderinfo["transparency"]
+ except KeyError:
+ pass
+ else:
+ # transparency extension block
+ fp.write("!" +
+ chr(249) + # extension intro
+ chr(4) + # length
+ chr(1) + # transparency info present
+ o16(0) + # duration
+ chr(int(transparency)) # transparency index
+ + chr(0))
+
+ # local image header
+ fp.write("," +
+ o16(0) + o16(0) + # bounding box
+ o16(im.size[0]) + # size
+ o16(im.size[1]) +
+ chr(flags) + # flags
+ chr(8)) # bits
+
+ imOut.encoderconfig = (8, interlace)
+
+ ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, rawmode)])
+
+ fp.write("\0") # end of image data
+
+ fp.write(";") # end of file
+
+ try:
+ fp.flush()
+ except: pass
+
+def _save_netpbm(im, fp, filename):
+
+ #
+ # If you need real GIF compression and/or RGB quantization, you
+ # can use the external NETPBM/PBMPLUS utilities. See comments
+ # below for information on how to enable this.
+
+ import os
+ file = im._dump()
+ if im.mode != "RGB":
+ os.system("ppmtogif %s >%s" % (file, filename))
+ else:
+ os.system("ppmquant 256 %s | ppmtogif >%s" % (file, filename))
+ try: os.unlink(file)
+ except: pass
+
+
+# --------------------------------------------------------------------
+# GIF utilities
+
+def getheader(im, info=None):
+ """Return a list of strings representing a GIF header"""
+
+ optimize = info and info.get("optimize", 0)
+
+ s = [
+ "GIF87a" + # magic
+ o16(im.size[0]) + # size
+ o16(im.size[1]) +
+ chr(7 + 128) + # flags: bits + palette
+ chr(0) + # background
+ chr(0) # reserved/aspect
+ ]
+
+ if optimize:
+ # minimize color palette
+ i = 0
+ maxcolor = 0
+ for count in im.histogram():
+ if count:
+ maxcolor = i
+ i = i + 1
+ else:
+ maxcolor = 256
+
+ # global palette
+ if im.mode == "P":
+ # colour palette
+ s.append(im.im.getpalette("RGB")[:maxcolor*3])
+ else:
+ # greyscale
+ for i in range(maxcolor):
+ s.append(chr(i) * 3)
+
+ return s
+
+def getdata(im, offset = (0, 0), **params):
+ """Return a list of strings representing this image.
+ The first string is a local image header, the rest contains
+ encoded image data."""
+
+ class collector:
+ data = []
+ def write(self, data):
+ self.data.append(data)
+
+ im.load() # make sure raster data is available
+
+ fp = collector()
+
+ try:
+ im.encoderinfo = params
+
+ # local image header
+ fp.write("," +
+ o16(offset[0]) + # offset
+ o16(offset[1]) +
+ o16(im.size[0]) + # size
+ o16(im.size[1]) +
+ chr(0) + # flags
+ chr(8)) # bits
+
+ ImageFile._save(im, fp, [("gif", (0,0)+im.size, 0, RAWMODE[im.mode])])
+
+ fp.write("\0") # end of image data
+
+ finally:
+ del im.encoderinfo
+
+ return fp.data
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(GifImageFile.format, GifImageFile, _accept)
+Image.register_save(GifImageFile.format, _save)
+Image.register_extension(GifImageFile.format, ".gif")
+Image.register_mime(GifImageFile.format, "image/gif")
+
+#
+# Uncomment the following line if you wish to use NETPBM/PBMPLUS
+# instead of the built-in "uncompressed" GIF encoder
+
+# Image.register_save(GifImageFile.format, _save_netpbm)
diff --git a/PIL/GimpGradientFile.py b/PIL/GimpGradientFile.py
new file mode 100644
index 0000000..e246d6d
--- /dev/null
+++ b/PIL/GimpGradientFile.py
@@ -0,0 +1,124 @@
+#
+# Python Imaging Library
+# $Id: GimpGradientFile.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# stuff to read (and render) GIMP gradient files
+#
+# History:
+# 97-08-23 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from math import pi, log, sin, sqrt
+import string
+
+# --------------------------------------------------------------------
+# Stuff to translate curve segments to palette values (derived from
+# the corresponding code in GIMP, written by Federico Mena Quintero.
+# See the GIMP distribution for more information.)
+#
+
+EPSILON = 1e-10
+
+def linear(middle, pos):
+ if pos <= middle:
+ if middle < EPSILON:
+ return 0.0
+ else:
+ return 0.5 * pos / middle
+ else:
+ pos = pos - middle
+ middle = 1.0 - middle
+ if middle < EPSILON:
+ return 1.0
+ else:
+ return 0.5 + 0.5 * pos / middle
+
+def curved(middle, pos):
+ return pos ** (log(0.5) / log(max(middle, EPSILON)))
+
+def sine(middle, pos):
+ return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
+
+def sphere_increasing(middle, pos):
+ return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
+
+def sphere_decreasing(middle, pos):
+ return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
+
+SEGMENTS = [ linear, curved, sine, sphere_increasing, sphere_decreasing ]
+
+class GradientFile:
+
+ gradient = None
+
+ def getpalette(self, entries = 256):
+
+ palette = []
+
+ ix = 0
+ x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
+
+ for i in range(entries):
+
+ x = i / float(entries-1)
+
+ while x1 < x:
+ ix = ix + 1
+ x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
+
+ w = x1 - x0
+
+ if w < EPSILON:
+ scale = segment(0.5, 0.5)
+ else:
+ scale = segment((xm - x0) / w, (x - x0) / w)
+
+ # expand to RGBA
+ r = chr(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5))
+ g = chr(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5))
+ b = chr(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5))
+ a = chr(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5))
+
+ # add to palette
+ palette.append(r + g + b + a)
+
+ return string.join(palette, ""), "RGBA"
+
+##
+# File handler for GIMP's gradient format.
+
+class GimpGradientFile(GradientFile):
+
+ def __init__(self, fp):
+
+ if fp.readline()[:13] != "GIMP Gradient":
+ raise SyntaxError, "not a GIMP gradient file"
+
+ count = int(fp.readline())
+
+ gradient = []
+
+ for i in range(count):
+
+ s = string.split(fp.readline())
+ w = map(float, s[:11])
+
+ x0, x1 = w[0], w[2]
+ xm = w[1]
+ rgb0 = w[3:7]
+ rgb1 = w[7:11]
+
+ segment = SEGMENTS[int(s[11])]
+ cspace = int(s[12])
+
+ if cspace != 0:
+ raise IOError, "cannot handle HSV colour space"
+
+ gradient.append((x0, x1, xm, rgb0, rgb1, segment))
+
+ self.gradient = gradient
diff --git a/PIL/GimpPaletteFile.py b/PIL/GimpPaletteFile.py
new file mode 100644
index 0000000..3939147
--- /dev/null
+++ b/PIL/GimpPaletteFile.py
@@ -0,0 +1,61 @@
+#
+# Python Imaging Library
+# $Id: GimpPaletteFile.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# stuff to read GIMP palette files
+#
+# History:
+# 1997-08-23 fl Created
+# 2004-09-07 fl Support GIMP 2.0 palette files.
+#
+# Copyright (c) Secret Labs AB 1997-2004. All rights reserved.
+# Copyright (c) Fredrik Lundh 1997-2004.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import re, string
+
+##
+# File handler for GIMP's palette format.
+
+class GimpPaletteFile:
+
+ rawmode = "RGB"
+
+ def __init__(self, fp):
+
+ self.palette = map(lambda i: chr(i)*3, range(256))
+
+ if fp.readline()[:12] != "GIMP Palette":
+ raise SyntaxError, "not a GIMP palette file"
+
+ i = 0
+
+ while i <= 255:
+
+ s = fp.readline()
+
+ if not s:
+ break
+ # skip fields and comment lines
+ if re.match("\w+:|#", s):
+ continue
+ if len(s) > 100:
+ raise SyntaxError, "bad palette file"
+
+ v = tuple(map(int, string.split(s)[:3]))
+ if len(v) != 3:
+ raise ValueError, "bad palette entry"
+
+ if 0 <= i <= 255:
+ self.palette[i] = chr(v[0]) + chr(v[1]) + chr(v[2])
+
+ i = i + 1
+
+ self.palette = string.join(self.palette, "")
+
+
+ def getpalette(self):
+
+ return self.palette, self.rawmode
diff --git a/PIL/GribStubImagePlugin.py b/PIL/GribStubImagePlugin.py
new file mode 100644
index 0000000..73d5404
--- /dev/null
+++ b/PIL/GribStubImagePlugin.py
@@ -0,0 +1,68 @@
+#
+# The Python Imaging Library
+# $Id: GribStubImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# GRIB stub adapter
+#
+# Copyright (c) 1996-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image, ImageFile
+
+_handler = None
+
+##
+# Install application-specific GRIB image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+# --------------------------------------------------------------------
+# Image adapter
+
+def _accept(prefix):
+ return prefix[0:4] == "GRIB" and prefix[7] == chr(1)
+
+class GribStubImageFile(ImageFile.StubImageFile):
+
+ format = "GRIB"
+ format_description = "GRIB"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(8)):
+ raise SyntaxError("Not a GRIB file")
+
+ self.fp.seek(offset)
+
+ # make something up
+ self.mode = "F"
+ self.size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("GRIB save handler not installed")
+ _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(GribStubImageFile.format, GribStubImageFile, _accept)
+Image.register_save(GribStubImageFile.format, _save)
+
+Image.register_extension(GribStubImageFile.format, ".grib")
diff --git a/PIL/Hdf5StubImagePlugin.py b/PIL/Hdf5StubImagePlugin.py
new file mode 100644
index 0000000..31d4f6e
--- /dev/null
+++ b/PIL/Hdf5StubImagePlugin.py
@@ -0,0 +1,70 @@
+#
+# The Python Imaging Library
+# $Id: Hdf5StubImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# HDF5 stub adapter
+#
+# Copyright (c) 2000-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image, ImageFile
+
+_handler = None
+
+##
+# Install application-specific HDF5 image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+# --------------------------------------------------------------------
+# Image adapter
+
+def _accept(prefix):
+ return prefix[:8] == "\x89HDF\r\n\x1a\n"
+
+class HDF5StubImageFile(ImageFile.StubImageFile):
+
+ format = "HDF5"
+ format_description = "HDF5"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(8)):
+ raise SyntaxError("Not an HDF file")
+
+ self.fp.seek(offset)
+
+ # make something up
+ self.mode = "F"
+ self.size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("HDF5 save handler not installed")
+ _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept)
+Image.register_save(HDF5StubImageFile.format, _save)
+
+Image.register_extension(HDF5StubImageFile.format, ".h5")
+Image.register_extension(HDF5StubImageFile.format, ".hdf")
diff --git a/PIL/IcnsImagePlugin.py b/PIL/IcnsImagePlugin.py
new file mode 100644
index 0000000..a85695d
--- /dev/null
+++ b/PIL/IcnsImagePlugin.py
@@ -0,0 +1,211 @@
+#
+# The Python Imaging Library.
+# $Id: ImImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# Mac OS X icns file decoder, based on icns.py by Bob Ippolito.
+#
+# history:
+# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
+#
+# Copyright (c) 2004 by Bob Ippolito.
+# Copyright (c) 2004 by Secret Labs.
+# Copyright (c) 2004 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image, ImageFile
+import string, struct
+
+HEADERSIZE = 8
+
+def nextheader(fobj):
+ return struct.unpack('>4sI', fobj.read(HEADERSIZE))
+
+def read_32t(fobj, (start, length), (width, height)):
+ # The 128x128 icon seems to have an extra header for some reason.
+ fobj.seek(start)
+ sig = fobj.read(4)
+ if sig != '\x00\x00\x00\x00':
+ raise SyntaxError, 'Unknown signature, expecting 0x00000000'
+ return read_32(fobj, (start + 4, length - 4), (width, height))
+
+def read_32(fobj, (start, length), size):
+ """
+ Read a 32bit RGB icon resource. Seems to be either uncompressed or
+ an RLE packbits-like scheme.
+ """
+ fobj.seek(start)
+ sizesq = size[0] * size[1]
+ if length == sizesq * 3:
+ # uncompressed ("RGBRGBGB")
+ indata = fobj.read(length)
+ im = Image.frombuffer("RGB", size, indata, "raw", "RGB", 0, 1)
+ else:
+ # decode image
+ im = Image.new("RGB", size, None)
+ for band_ix in range(3):
+ data = []
+ bytesleft = sizesq
+ while bytesleft > 0:
+ byte = fobj.read(1)
+ if not byte:
+ break
+ byte = ord(byte)
+ if byte & 0x80:
+ blocksize = byte - 125
+ byte = fobj.read(1)
+ for i in range(blocksize):
+ data.append(byte)
+ else:
+ blocksize = byte + 1
+ data.append(fobj.read(blocksize))
+ bytesleft = bytesleft - blocksize
+ if bytesleft <= 0:
+ break
+ if bytesleft != 0:
+ raise SyntaxError(
+ "Error reading %r channel [%r]" % (channel, bytesleft)
+ )
+ band = Image.frombuffer(
+ "L", size, string.join(data, ""), "raw", "L", 0, 1
+ )
+ im.im.putband(band.im, band_ix)
+ return {"RGB": im}
+
+def read_mk(fobj, (start, length), size):
+ # Alpha masks seem to be uncompressed
+ fobj.seek(start)
+ band = Image.frombuffer(
+ "L", size, fobj.read(size[0]*size[1]), "raw", "L", 0, 1
+ )
+ return {"A": band}
+
+class IcnsFile:
+
+ SIZES = {
+ (128, 128): [
+ ('it32', read_32t),
+ ('t8mk', read_mk),
+ ],
+ (48, 48): [
+ ('ih32', read_32),
+ ('h8mk', read_mk),
+ ],
+ (32, 32): [
+ ('il32', read_32),
+ ('l8mk', read_mk),
+ ],
+ (16, 16): [
+ ('is32', read_32),
+ ('s8mk', read_mk),
+ ],
+ }
+
+ def __init__(self, fobj):
+ """
+ fobj is a file-like object as an icns resource
+ """
+ # signature : (start, length)
+ self.dct = dct = {}
+ self.fobj = fobj
+ sig, filesize = nextheader(fobj)
+ if sig != 'icns':
+ raise SyntaxError, 'not an icns file'
+ i = HEADERSIZE
+ while i < filesize:
+ sig, blocksize = nextheader(fobj)
+ i = i + HEADERSIZE
+ blocksize = blocksize - HEADERSIZE
+ dct[sig] = (i, blocksize)
+ fobj.seek(blocksize, 1)
+ i = i + blocksize
+
+ def itersizes(self):
+ sizes = []
+ for size, fmts in self.SIZES.items():
+ for (fmt, reader) in fmts:
+ if self.dct.has_key(fmt):
+ sizes.append(size)
+ break
+ return sizes
+
+ def bestsize(self):
+ sizes = self.itersizes()
+ if not sizes:
+ raise SyntaxError, "No 32bit icon resources found"
+ return max(sizes)
+
+ def dataforsize(self, size):
+ """
+ Get an icon resource as {channel: array}. Note that
+ the arrays are bottom-up like windows bitmaps and will likely
+ need to be flipped or transposed in some way.
+ """
+ dct = {}
+ for code, reader in self.SIZES[size]:
+ desc = self.dct.get(code)
+ if desc is not None:
+ dct.update(reader(self.fobj, desc, size))
+ return dct
+
+ def getimage(self, size=None):
+ if size is None:
+ size = self.bestsize()
+ channels = self.dataforsize(size)
+ im = channels.get("RGB").copy()
+ try:
+ im.putalpha(channels["A"])
+ except KeyError:
+ pass
+ return im
+
+##
+# Image plugin for Mac OS icons.
+
+class IcnsImageFile(ImageFile.ImageFile):
+ """
+ PIL read-only image support for Mac OS .icns files.
+ Chooses the best resolution, but will possibly load
+ a different size image if you mutate the size attribute
+ before calling 'load'.
+
+ The info dictionary has a key 'sizes' that is a list
+ of sizes that the icns file has.
+ """
+
+ format = "ICNS"
+ format_description = "Mac OS icns resource"
+
+ def _open(self):
+ self.icns = IcnsFile(self.fp)
+ self.mode = 'RGBA'
+ self.size = self.icns.bestsize()
+ self.info['sizes'] = self.icns.itersizes()
+ # Just use this to see if it's loaded or not yet.
+ self.tile = ('',)
+
+ def load(self):
+ Image.Image.load(self)
+ if not self.tile:
+ return
+ self.load_prepare()
+ # This is likely NOT the best way to do it, but whatever.
+ im = self.icns.getimage(self.size)
+ self.im = im.im
+ self.mode = im.mode
+ self.size = im.size
+ self.fp = None
+ self.icns = None
+ self.tile = ()
+ self.load_end()
+
+
+Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == 'icns')
+Image.register_extension("ICNS", '.icns')
+
+if __name__ == '__main__':
+ import os, sys
+ im = Image.open(open(sys.argv[1], "rb"))
+ im.save("out.png")
+ os.startfile("out.png")
diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py
new file mode 100644
index 0000000..5cdd284
--- /dev/null
+++ b/PIL/IcoImagePlugin.py
@@ -0,0 +1,88 @@
+#
+# The Python Imaging Library.
+# $Id: IcoImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# Windows Icon support for PIL
+#
+# Notes:
+# uses BmpImagePlugin.py to read the bitmap data.
+#
+# History:
+# 96-05-27 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+import string
+
+import Image, BmpImagePlugin
+
+
+#
+# --------------------------------------------------------------------
+
+def i16(c):
+ return ord(c[0]) + (ord(c[1])<<8)
+
+def i32(c):
+ return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24)
+
+
+def _accept(prefix):
+ return prefix[:4] == "\0\0\1\0"
+
+##
+# Image plugin for Windows Icon files.
+
+class IcoImageFile(BmpImagePlugin.BmpImageFile):
+
+ format = "ICO"
+ format_description = "Windows Icon"
+
+ def _open(self):
+
+ # check magic
+ s = self.fp.read(6)
+ if not _accept(s):
+ raise SyntaxError, "not an ICO file"
+
+ # pick the largest icon in the file
+ m = ""
+ for i in range(i16(s[4:])):
+ s = self.fp.read(16)
+ if not m:
+ m = s
+ elif ord(s[0]) > ord(m[0]) and ord(s[1]) > ord(m[1]):
+ m = s
+ #print "width", ord(s[0])
+ #print "height", ord(s[1])
+ #print "colors", ord(s[2])
+ #print "reserved", ord(s[3])
+ #print "planes", i16(s[4:])
+ #print "bitcount", i16(s[6:])
+ #print "bytes", i32(s[8:])
+ #print "offset", i32(s[12:])
+
+ # load as bitmap
+ self._bitmap(i32(m[12:]))
+
+ # patch up the bitmap height
+ self.size = self.size[0], self.size[1]/2
+ d, e, o, a = self.tile[0]
+ self.tile[0] = d, (0,0)+self.size, o, a
+
+ return
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("ICO", IcoImageFile, _accept)
+
+Image.register_extension("ICO", ".ico")
diff --git a/PIL/ImImagePlugin.py b/PIL/ImImagePlugin.py
new file mode 100644
index 0000000..17b8c0e
--- /dev/null
+++ b/PIL/ImImagePlugin.py
@@ -0,0 +1,335 @@
+#
+# The Python Imaging Library.
+# $Id: ImImagePlugin.py 2285 2005-02-07 23:52:14Z fredrik $
+#
+# IFUNC IM file handling for PIL
+#
+# history:
+# 1995-09-01 fl Created.
+# 1997-01-03 fl Save palette images
+# 1997-01-08 fl Added sequence support
+# 1997-01-23 fl Added P and RGB save support
+# 1997-05-31 fl Read floating point images
+# 1997-06-22 fl Save floating point images
+# 1997-08-27 fl Read and save 1-bit images
+# 1998-06-25 fl Added support for RGB+LUT images
+# 1998-07-02 fl Added support for YCC images
+# 1998-07-15 fl Renamed offset attribute to avoid name clash
+# 1998-12-29 fl Added I;16 support
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
+# 2003-09-26 fl Added LA/PA support
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2001 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.7"
+
+import re, string
+import Image, ImageFile, ImagePalette
+
+
+# --------------------------------------------------------------------
+# Standard tags
+
+COMMENT = "Comment"
+DATE = "Date"
+EQUIPMENT = "Digitalization equipment"
+FRAMES = "File size (no of images)"
+LUT = "Lut"
+NAME = "Name"
+SCALE = "Scale (x,y)"
+SIZE = "Image size (x*y)"
+MODE = "Image type"
+
+TAGS = { COMMENT:0, DATE:0, EQUIPMENT:0, FRAMES:0, LUT:0, NAME:0,
+ SCALE:0, SIZE:0, MODE:0 }
+
+OPEN = {
+ # ifunc93/p3cfunc formats
+ "0 1 image": ("1", "1"),
+ "L 1 image": ("1", "1"),
+ "Greyscale image": ("L", "L"),
+ "Grayscale image": ("L", "L"),
+ "RGB image": ("RGB", "RGB;L"),
+ "RLB image": ("RGB", "RLB"),
+ "RYB image": ("RGB", "RLB"),
+ "B1 image": ("1", "1"),
+ "B2 image": ("P", "P;2"),
+ "B4 image": ("P", "P;4"),
+ "X 24 image": ("RGB", "RGB"),
+ "L 32 S image": ("I", "I;32"),
+ "L 32 F image": ("F", "F;32"),
+ # old p3cfunc formats
+ "RGB3 image": ("RGB", "RGB;T"),
+ "RYB3 image": ("RGB", "RYB;T"),
+ # extensions
+ "LA image": ("LA", "LA;L"),
+ "RGBA image": ("RGBA", "RGBA;L"),
+ "RGBX image": ("RGBX", "RGBX;L"),
+ "CMYK image": ("CMYK", "CMYK;L"),
+ "YCC image": ("YCbCr", "YCbCr;L"),
+}
+
+# ifunc95 extensions
+for i in ["8", "8S", "16", "16S", "32", "32F"]:
+ OPEN["L %s image" % i] = ("F", "F;%s" % i)
+ OPEN["L*%s image" % i] = ("F", "F;%s" % i)
+for i in ["16", "16B"]:
+ OPEN["L %s image" % i] = ("I;%s" % i, "I;%s" % i)
+ OPEN["L*%s image" % i] = ("I;%s" % i, "I;%s" % i)
+for i in ["32S"]:
+ OPEN["L %s image" % i] = ("I", "I;%s" % i)
+ OPEN["L*%s image" % i] = ("I", "I;%s" % i)
+for i in range(2, 33):
+ OPEN["L*%s image" % i] = ("F", "F;%s" % i)
+
+
+# --------------------------------------------------------------------
+# Read IM directory
+
+split = re.compile(r"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
+
+def number(s):
+ try:
+ return int(s)
+ except ValueError:
+ return float(s)
+
+##
+# Image plugin for the IFUNC IM file format.
+
+class ImImageFile(ImageFile.ImageFile):
+
+ format = "IM"
+ format_description = "IFUNC Image Memory"
+
+ def _open(self):
+
+ # Quick rejection: if there's not an LF among the first
+ # 100 bytes, this is (probably) not a text header.
+
+ if not "\n" in self.fp.read(100):
+ raise SyntaxError, "not an IM file"
+ self.fp.seek(0)
+
+ n = 0
+
+ # Default values
+ self.info[MODE] = "L"
+ self.info[SIZE] = (512, 512)
+ self.info[FRAMES] = 1
+
+ self.rawmode = "L"
+
+ while 1:
+
+ s = self.fp.read(1)
+
+ # Some versions of IFUNC uses \n\r instead of \r\n...
+ if s == "\r":
+ continue
+
+ if not s or s[0] == chr(0) or s[0] == chr(26):
+ break
+
+ # FIXME: this may read whole file if not a text file
+ s = s + self.fp.readline()
+
+ if len(s) > 100:
+ raise SyntaxError, "not an IM file"
+
+ if s[-2:] == '\r\n':
+ s = s[:-2]
+ elif s[-1:] == '\n':
+ s = s[:-1]
+
+ try:
+ m = split.match(s)
+ except re.error, v:
+ raise SyntaxError, "not an IM file"
+
+ if m:
+
+ k, v = m.group(1,2)
+
+ # Convert value as appropriate
+ if k in [FRAMES, SCALE, SIZE]:
+ v = string.replace(v, "*", ",")
+ v = tuple(map(number, string.split(v, ",")))
+ if len(v) == 1:
+ v = v[0]
+ elif k == MODE and OPEN.has_key(v):
+ v, self.rawmode = OPEN[v]
+
+ # Add to dictionary. Note that COMMENT tags are
+ # combined into a list of strings.
+ if k == COMMENT:
+ if self.info.has_key(k):
+ self.info[k].append(v)
+ else:
+ self.info[k] = [v]
+ else:
+ self.info[k] = v
+
+ if TAGS.has_key(k):
+ n = n + 1
+
+ else:
+
+ raise SyntaxError, "Syntax error in IM header: " + s
+
+ if not n:
+ raise SyntaxError, "Not an IM file"
+
+ # Basic attributes
+ self.size = self.info[SIZE]
+ self.mode = self.info[MODE]
+
+ # Skip forward to start of image data
+ while s and s[0] != chr(26):
+ s = self.fp.read(1)
+ if not s:
+ raise SyntaxError, "File truncated"
+
+ if self.info.has_key(LUT):
+ # convert lookup table to palette or lut attribute
+ palette = self.fp.read(768)
+ greyscale = 1 # greyscale palette
+ linear = 1 # linear greyscale palette
+ for i in range(256):
+ if palette[i] == palette[i+256] == palette[i+512]:
+ if palette[i] != chr(i):
+ linear = 0
+ else:
+ greyscale = 0
+ if self.mode == "L" or self.mode == "LA":
+ if greyscale:
+ if not linear:
+ self.lut = map(ord, palette[:256])
+ else:
+ if self.mode == "L":
+ self.mode = self.rawmode = "P"
+ elif self.mode == "LA":
+ self.mode = self.rawmode = "PA"
+ self.palette = ImagePalette.raw("RGB;L", palette)
+ elif self.mode == "RGB":
+ if not greyscale or not linear:
+ self.lut = map(ord, palette)
+
+ self.frame = 0
+
+ self.__offset = offs = self.fp.tell()
+
+ self.__fp = self.fp # FIXME: hack
+
+ if self.rawmode[:2] == "F;":
+
+ # ifunc95 formats
+ try:
+ # use bit decoder (if necessary)
+ bits = int(self.rawmode[2:])
+ if bits not in [8, 16, 32]:
+ self.tile = [("bit", (0,0)+self.size, offs,
+ (bits, 8, 3, 0, -1))]
+ return
+ except ValueError:
+ pass
+
+ if self.rawmode in ["RGB;T", "RYB;T"]:
+ # Old LabEye/3PC files. Would be very surprised if anyone
+ # ever stumbled upon such a file ;-)
+ size = self.size[0] * self.size[1]
+ self.tile = [("raw", (0,0)+self.size, offs, ("G", 0, -1)),
+ ("raw", (0,0)+self.size, offs+size, ("R", 0, -1)),
+ ("raw", (0,0)+self.size, offs+2*size, ("B", 0, -1))]
+ else:
+ # LabEye/IFUNC files
+ self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))]
+
+ def seek(self, frame):
+
+ if frame < 0 or frame >= self.info[FRAMES]:
+ raise EOFError, "seek outside sequence"
+
+ if self.frame == frame:
+ return
+
+ self.frame = frame
+
+ if self.mode == "1":
+ bits = 1
+ else:
+ bits = 8 * len(self.mode)
+
+ size = ((self.size[0] * bits + 7) / 8) * self.size[1]
+ offs = self.__offset + frame * size
+
+ self.fp = self.__fp
+
+ self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))]
+
+ def tell(self):
+
+ return self.frame
+
+#
+# --------------------------------------------------------------------
+# Save IM files
+
+SAVE = {
+ # mode: (im type, raw mode)
+ "1": ("0 1", "1"),
+ "L": ("Greyscale", "L"),
+ "LA": ("LA", "LA;L"),
+ "P": ("Greyscale", "P"),
+ "PA": ("LA", "PA;L"),
+ "I": ("L 32S", "I;32S"),
+ "I;16": ("L 16", "I;16"),
+ "I;16B": ("L 16B", "I;16B"),
+ "F": ("L 32F", "F;32F"),
+ "RGB": ("RGB", "RGB;L"),
+ "RGBA": ("RGBA", "RGBA;L"),
+ "RGBX": ("RGBX", "RGBX;L"),
+ "CMYK": ("CMYK", "CMYK;L"),
+ "YCbCr": ("YCC", "YCbCr;L")
+}
+
+def _save(im, fp, filename, check=0):
+
+ try:
+ type, rawmode = SAVE[im.mode]
+ except KeyError:
+ raise ValueError, "Cannot save %s images as IM" % im.mode
+
+ try:
+ frames = im.encoderinfo["frames"]
+ except KeyError:
+ frames = 1
+
+ if check:
+ return check
+
+ fp.write("Image type: %s image\r\n" % type)
+ if filename:
+ fp.write("Name: %s\r\n" % filename)
+ fp.write("Image size (x*y): %d*%d\r\n" % im.size)
+ fp.write("File size (no of images): %d\r\n" % frames)
+ if im.mode == "P":
+ fp.write("Lut: 1\r\n")
+ fp.write("\000" * (511-fp.tell()) + "\032")
+ if im.mode == "P":
+ fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
+ ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, -1))])
+
+#
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open("IM", ImImageFile)
+Image.register_save("IM", _save)
+
+Image.register_extension("IM", ".im")
diff --git a/PIL/Image.py b/PIL/Image.py
new file mode 100644
index 0000000..04ed316
--- /dev/null
+++ b/PIL/Image.py
@@ -0,0 +1,2093 @@
+#
+# The Python Imaging Library.
+# $Id: Image.py 2933 2006-12-03 12:08:22Z fredrik $
+#
+# the Image class wrapper
+#
+# partial release history:
+# 1995-09-09 fl Created
+# 1996-03-11 fl PIL release 0.0 (proof of concept)
+# 1996-04-30 fl PIL release 0.1b1
+# 1999-07-28 fl PIL release 1.0 final
+# 2000-06-07 fl PIL release 1.1
+# 2000-10-20 fl PIL release 1.1.1
+# 2001-05-07 fl PIL release 1.1.2
+# 2002-03-15 fl PIL release 1.1.3
+# 2003-05-10 fl PIL release 1.1.4
+# 2005-03-28 fl PIL release 1.1.5
+# 2006-12-02 fl PIL release 1.1.6
+#
+# Copyright (c) 1997-2006 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1995-2006 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+VERSION = "1.1.6"
+
+try:
+ import warnings
+except ImportError:
+ warnings = None
+
+class _imaging_not_installed:
+ # module placeholder
+ def __getattr__(self, id):
+ raise ImportError("The _imaging C module is not installed")
+
+try:
+ # give Tk a chance to set up the environment, in case we're
+ # using an _imaging module linked against libtcl/libtk (use
+ # __import__ to hide this from naive packagers; we don't really
+ # depend on Tk unless ImageTk is used, and that module already
+ # imports Tkinter)
+ __import__("FixTk")
+except ImportError:
+ pass
+
+try:
+ # If the _imaging C module is not present, you can still use
+ # the "open" function to identify files, but you cannot load
+ # them. Note that other modules should not refer to _imaging
+ # directly; import Image and use the Image.core variable instead.
+ import _imaging
+ core = _imaging
+ del _imaging
+except ImportError, v:
+ core = _imaging_not_installed()
+ if str(v)[:20] == "Module use of python" and warnings:
+ # The _imaging C module is present, but not compiled for
+ # the right version (windows only). Print a warning, if
+ # possible.
+ warnings.warn(
+ "The _imaging extension was built for another version "
+ "of Python; most PIL functions will be disabled",
+ RuntimeWarning
+ )
+
+import ImageMode
+import ImagePalette
+
+import os, string, sys
+
+# type stuff
+from types import IntType, StringType, TupleType
+
+try:
+ UnicodeStringType = type(unicode(""))
+ ##
+ # (Internal) Checks if an object is a string. If the current
+ # Python version supports Unicode, this checks for both 8-bit
+ # and Unicode strings.
+ def isStringType(t):
+ return isinstance(t, StringType) or isinstance(t, UnicodeStringType)
+except NameError:
+ def isStringType(t):
+ return isinstance(t, StringType)
+
+##
+# (Internal) Checks if an object is a tuple.
+
+def isTupleType(t):
+ return isinstance(t, TupleType)
+
+##
+# (Internal) Checks if an object is an image object.
+
+def isImageType(t):
+ return hasattr(t, "im")
+
+##
+# (Internal) Checks if an object is a string, and that it points to a
+# directory.
+
+def isDirectory(f):
+ return isStringType(f) and os.path.isdir(f)
+
+from operator import isNumberType, isSequenceType
+
+#
+# Debug level
+
+DEBUG = 0
+
+#
+# Constants (also defined in _imagingmodule.c!)
+
+NONE = 0
+
+# transpose
+FLIP_LEFT_RIGHT = 0
+FLIP_TOP_BOTTOM = 1
+ROTATE_90 = 2
+ROTATE_180 = 3
+ROTATE_270 = 4
+
+# transforms
+AFFINE = 0
+EXTENT = 1
+PERSPECTIVE = 2
+QUAD = 3
+MESH = 4
+
+# resampling filters
+NONE = 0
+NEAREST = 0
+ANTIALIAS = 1 # 3-lobed lanczos
+LINEAR = BILINEAR = 2
+CUBIC = BICUBIC = 3
+
+# dithers
+NONE = 0
+NEAREST = 0
+ORDERED = 1 # Not yet implemented
+RASTERIZE = 2 # Not yet implemented
+FLOYDSTEINBERG = 3 # default
+
+# palettes/quantizers
+WEB = 0
+ADAPTIVE = 1
+
+# categories
+NORMAL = 0
+SEQUENCE = 1
+CONTAINER = 2
+
+# --------------------------------------------------------------------
+# Registries
+
+ID = []
+OPEN = {}
+MIME = {}
+SAVE = {}
+EXTENSION = {}
+
+# --------------------------------------------------------------------
+# Modes supported by this version
+
+_MODEINFO = {
+ # NOTE: this table will be removed in future versions. use
+ # getmode* functions or ImageMode descriptors instead.
+
+ # official modes
+ "1": ("L", "L", ("1",)),
+ "L": ("L", "L", ("L",)),
+ "I": ("L", "I", ("I",)),
+ "F": ("L", "F", ("F",)),
+ "P": ("RGB", "L", ("P",)),
+ "RGB": ("RGB", "L", ("R", "G", "B")),
+ "RGBX": ("RGB", "L", ("R", "G", "B", "X")),
+ "RGBA": ("RGB", "L", ("R", "G", "B", "A")),
+ "CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
+ "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
+
+ # Experimental modes include I;16, I;16B, RGBa, BGR;15,
+ # and BGR;24. Use these modes only if you know exactly
+ # what you're doing...
+
+}
+
+if sys.byteorder == 'little':
+ _ENDIAN = '<'
+else:
+ _ENDIAN = '>'
+
+_MODE_CONV = {
+ # official modes
+ "1": ('|b1', None),
+ "L": ('|u1', None),
+ "I": ('%si4' % _ENDIAN, None), # FIXME: is this correct?
+ "F": ('%sf4' % _ENDIAN, None), # FIXME: is this correct?
+ "P": ('|u1', None),
+ "RGB": ('|u1', 3),
+ "RGBX": ('|u1', 4),
+ "RGBA": ('|u1', 4),
+ "CMYK": ('|u1', 4),
+ "YCbCr": ('|u1', 4),
+}
+
+def _conv_type_shape(im):
+ shape = im.size[::-1]
+ typ, extra = _MODE_CONV[im.mode]
+ if extra is None:
+ return shape, typ
+ else:
+ return shape+(extra,), typ
+
+
+MODES = _MODEINFO.keys()
+MODES.sort()
+
+# raw modes that may be memory mapped. NOTE: if you change this, you
+# may have to modify the stride calculation in map.c too!
+_MAPMODES = ("L", "P", "RGBX", "RGBA", "CMYK", "I;16", "I;16B")
+
+##
+# Gets the "base" mode for given mode. This function returns "L" for
+# images that contain grayscale data, and "RGB" for images that
+# contain color data.
+#
+# @param mode Input mode.
+# @return "L" or "RGB".
+# @exception KeyError If the input mode was not a standard mode.
+
+def getmodebase(mode):
+ return ImageMode.getmode(mode).basemode
+
+##
+# Gets the storage type mode. Given a mode, this function returns a
+# single-layer mode suitable for storing individual bands.
+#
+# @param mode Input mode.
+# @return "L", "I", or "F".
+# @exception KeyError If the input mode was not a standard mode.
+
+def getmodetype(mode):
+ return ImageMode.getmode(mode).basetype
+
+##
+# Gets a list of individual band names. Given a mode, this function
+# returns a tuple containing the names of individual bands (use
+# {@link #getmodetype} to get the mode used to store each individual
+# band.
+#
+# @param mode Input mode.
+# @return A tuple containing band names. The length of the tuple
+# gives the number of bands in an image of the given mode.
+# @exception KeyError If the input mode was not a standard mode.
+
+def getmodebandnames(mode):
+ return ImageMode.getmode(mode).bands
+
+##
+# Gets the number of individual bands for this mode.
+#
+# @param mode Input mode.
+# @return The number of bands in this mode.
+# @exception KeyError If the input mode was not a standard mode.
+
+def getmodebands(mode):
+ return len(ImageMode.getmode(mode).bands)
+
+# --------------------------------------------------------------------
+# Helpers
+
+_initialized = 0
+
+##
+# Explicitly loads standard file format drivers.
+
+def preinit():
+ "Load standard file format drivers."
+
+ global _initialized
+ if _initialized >= 1:
+ return
+
+ try:
+ import BmpImagePlugin
+ except ImportError:
+ pass
+ try:
+ import GifImagePlugin
+ except ImportError:
+ pass
+ try:
+ import JpegImagePlugin
+ except ImportError:
+ pass
+ try:
+ import PpmImagePlugin
+ except ImportError:
+ pass
+ try:
+ import PngImagePlugin
+ except ImportError:
+ pass
+# try:
+# import TiffImagePlugin
+# except ImportError:
+# pass
+
+ _initialized = 1
+
+##
+# Explicitly initializes the Python Imaging Library. This function
+# loads all available file format drivers.
+
+def init():
+ "Load all file format drivers."
+
+ global _initialized
+ if _initialized >= 2:
+ return
+
+ visited = {}
+
+ directories = sys.path
+
+ try:
+ directories = directories + [os.path.dirname(__file__)]
+ except NameError:
+ pass
+
+ # only check directories (including current, if present in the path)
+ for directory in filter(isDirectory, directories):
+ fullpath = os.path.abspath(directory)
+ if visited.has_key(fullpath):
+ continue
+ for file in os.listdir(directory):
+ if file[-14:] == "ImagePlugin.py":
+ f, e = os.path.splitext(file)
+ try:
+ sys.path.insert(0, directory)
+ try:
+ __import__(f, globals(), locals(), [])
+ finally:
+ del sys.path[0]
+ except ImportError:
+ if DEBUG:
+ print "Image: failed to import",
+ print f, ":", sys.exc_value
+ visited[fullpath] = None
+
+ if OPEN or SAVE:
+ _initialized = 2
+
+
+# --------------------------------------------------------------------
+# Codec factories (used by tostring/fromstring and ImageFile.load)
+
+def _getdecoder(mode, decoder_name, args, extra=()):
+
+ # tweak arguments
+ if args is None:
+ args = ()
+ elif not isTupleType(args):
+ args = (args,)
+
+ try:
+ # get decoder
+ decoder = getattr(core, decoder_name + "_decoder")
+ # print decoder, (mode,) + args + extra
+ return apply(decoder, (mode,) + args + extra)
+ except AttributeError:
+ raise IOError("decoder %s not available" % decoder_name)
+
+def _getencoder(mode, encoder_name, args, extra=()):
+
+ # tweak arguments
+ if args is None:
+ args = ()
+ elif not isTupleType(args):
+ args = (args,)
+
+ try:
+ # get encoder
+ encoder = getattr(core, encoder_name + "_encoder")
+ # print encoder, (mode,) + args + extra
+ return apply(encoder, (mode,) + args + extra)
+ except AttributeError:
+ raise IOError("encoder %s not available" % encoder_name)
+
+
+# --------------------------------------------------------------------
+# Simple expression analyzer
+
+class _E:
+ def __init__(self, data): self.data = data
+ def __coerce__(self, other): return self, _E(other)
+ def __add__(self, other): return _E((self.data, "__add__", other.data))
+ def __mul__(self, other): return _E((self.data, "__mul__", other.data))
+
+def _getscaleoffset(expr):
+ stub = ["stub"]
+ data = expr(_E(stub)).data
+ try:
+ (a, b, c) = data # simplified syntax
+ if (a is stub and b == "__mul__" and isNumberType(c)):
+ return c, 0.0
+ if (a is stub and b == "__add__" and isNumberType(c)):
+ return 1.0, c
+ except TypeError: pass
+ try:
+ ((a, b, c), d, e) = data # full syntax
+ if (a is stub and b == "__mul__" and isNumberType(c) and
+ d == "__add__" and isNumberType(e)):
+ return c, e
+ except TypeError: pass
+ raise ValueError("illegal expression")
+
+
+# --------------------------------------------------------------------
+# Implementation wrapper
+
+##
+# This class represents an image object. To create Image objects, use
+# the appropriate factory functions. There's hardly ever any reason
+# to call the Image constructor directly.
+#
+# @see #open
+# @see #new
+# @see #fromstring
+
+class Image:
+
+ format = None
+ format_description = None
+
+ def __init__(self):
+ self.im = None
+ self.mode = ""
+ self.size = (0, 0)
+ self.palette = None
+ self.info = {}
+ self.category = NORMAL
+ self.readonly = 0
+
+ def _new(self, im):
+ new = Image()
+ new.im = im
+ new.mode = im.mode
+ new.size = im.size
+ new.palette = self.palette
+ if im.mode == "P":
+ new.palette = ImagePalette.ImagePalette()
+ try:
+ new.info = self.info.copy()
+ except AttributeError:
+ # fallback (pre-1.5.2)
+ new.info = {}
+ for k, v in self.info:
+ new.info[k] = v
+ return new
+
+ _makeself = _new # compatibility
+
+ def _copy(self):
+ self.load()
+ self.im = self.im.copy()
+ self.readonly = 0
+
+ def _dump(self, file=None, format=None):
+ import tempfile
+ if not file:
+ file = tempfile.mktemp()
+ self.load()
+ if not format or format == "PPM":
+ self.im.save_ppm(file)
+ else:
+ file = file + "." + format
+ self.save(file, format)
+ return file
+
+ def __getattr__(self, name):
+ if name == "__array_interface__":
+ # numpy array interface support
+ new = {}
+ shape, typestr = _conv_type_shape(self)
+ new['shape'] = shape
+ new['typestr'] = typestr
+ new['data'] = self.tostring()
+ return new
+ raise AttributeError(name)
+
+ ##
+ # Returns a string containing pixel data.
+ #
+ # @param encoder_name What encoder to use. The default is to
+ # use the standard "raw" encoder.
+ # @param *args Extra arguments to the encoder.
+ # @return An 8-bit string.
+
+ def tostring(self, encoder_name="raw", *args):
+ "Return image as a binary string"
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isTupleType(args[0]):
+ args = args[0]
+
+ if encoder_name == "raw" and args == ():
+ args = self.mode
+
+ self.load()
+
+ # unpack data
+ e = _getencoder(self.mode, encoder_name, args)
+ e.setimage(self.im)
+
+ bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
+
+ data = []
+ while 1:
+ l, s, d = e.encode(bufsize)
+ data.append(d)
+ if s:
+ break
+ if s < 0:
+ raise RuntimeError("encoder error %d in tostring" % s)
+
+ return string.join(data, "")
+
+ ##
+ # Returns the image converted to an X11 bitmap. This method
+ # only works for mode "1" images.
+ #
+ # @param name The name prefix to use for the bitmap variables.
+ # @return A string containing an X11 bitmap.
+ # @exception ValueError If the mode is not "1"
+
+ def tobitmap(self, name="image"):
+ "Return image as an XBM bitmap"
+
+ self.load()
+ if self.mode != "1":
+ raise ValueError("not a bitmap")
+ data = self.tostring("xbm")
+ return string.join(["#define %s_width %d\n" % (name, self.size[0]),
+ "#define %s_height %d\n"% (name, self.size[1]),
+ "static char %s_bits[] = {\n" % name, data, "};"], "")
+
+ ##
+ # Loads this image with pixel data from a string.
+ # <p>
+ # This method is similar to the {@link #fromstring} function, but
+ # loads data into this image instead of creating a new image
+ # object.
+
+ def fromstring(self, data, decoder_name="raw", *args):
+ "Load data to image from binary string"
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isTupleType(args[0]):
+ args = args[0]
+
+ # default format
+ if decoder_name == "raw" and args == ():
+ args = self.mode
+
+ # unpack data
+ d = _getdecoder(self.mode, decoder_name, args)
+ d.setimage(self.im)
+ s = d.decode(data)
+
+ if s[0] >= 0:
+ raise ValueError("not enough image data")
+ if s[1] != 0:
+ raise ValueError("cannot decode image data")
+
+ ##
+ # Allocates storage for the image and loads the pixel data. In
+ # normal cases, you don't need to call this method, since the
+ # Image class automatically loads an opened image when it is
+ # accessed for the first time.
+ #
+ # @return An image access object.
+
+ def load(self):
+ "Explicitly load pixel data."
+ if self.im and self.palette and self.palette.dirty:
+ # realize palette
+ apply(self.im.putpalette, self.palette.getdata())
+ self.palette.dirty = 0
+ self.palette.mode = "RGB"
+ self.palette.rawmode = None
+ if self.info.has_key("transparency"):
+ self.im.putpalettealpha(self.info["transparency"], 0)
+ self.palette.mode = "RGBA"
+ if self.im:
+ return self.im.pixel_access(self.readonly)
+
+ ##
+ # Verifies the contents of a file. For data read from a file, this
+ # method attempts to determine if the file is broken, without
+ # actually decoding the image data. If this method finds any
+ # problems, it raises suitable exceptions. If you need to load
+ # the image after using this method, you must reopen the image
+ # file.
+
+ def verify(self):
+ "Verify file contents."
+ pass
+
+
+ ##
+ # Returns a converted copy of this image. For the "P" mode, this
+ # method translates pixels through the palette. If mode is
+ # omitted, a mode is chosen so that all information in the image
+ # and the palette can be represented without a palette.
+ # <p>
+ # The current version supports all possible conversions between
+ # "L", "RGB" and "CMYK."
+ # <p>
+ # When translating a colour image to black and white (mode "L"),
+ # the library uses the ITU-R 601-2 luma transform:
+ # <p>
+ # <b>L = R * 299/1000 + G * 587/1000 + B * 114/1000</b>
+ # <p>
+ # When translating a greyscale image into a bilevel image (mode
+ # "1"), all non-zero values are set to 255 (white). To use other
+ # thresholds, use the {@link #Image.point} method.
+ #
+ # @def convert(mode, matrix=None)
+ # @param mode The requested mode.
+ # @param matrix An optional conversion matrix. If given, this
+ # should be 4- or 16-tuple containing floating point values.
+ # @return An Image object.
+
+ def convert(self, mode=None, data=None, dither=None,
+ palette=WEB, colors=256):
+ "Convert to other pixel format"
+
+ if not mode:
+ # determine default mode
+ if self.mode == "P":
+ self.load()
+ if self.palette:
+ mode = self.palette.mode
+ else:
+ mode = "RGB"
+ else:
+ return self.copy()
+
+ self.load()
+
+ if data:
+ # matrix conversion
+ if mode not in ("L", "RGB"):
+ raise ValueError("illegal conversion")
+ im = self.im.convert_matrix(mode, data)
+ return self._new(im)
+
+ if mode == "P" and palette == ADAPTIVE:
+ im = self.im.quantize(colors)
+ return self._new(im)
+
+ # colourspace conversion
+ if dither is None:
+ dither = FLOYDSTEINBERG
+
+ try:
+ im = self.im.convert(mode, dither)
+ except ValueError:
+ try:
+ # normalize source image and try again
+ im = self.im.convert(getmodebase(self.mode))
+ im = im.convert(mode, dither)
+ except KeyError:
+ raise ValueError("illegal conversion")
+
+ return self._new(im)
+
+ def quantize(self, colors=256, method=0, kmeans=0, palette=None):
+
+ # methods:
+ # 0 = median cut
+ # 1 = maximum coverage
+
+ # NOTE: this functionality will be moved to the extended
+ # quantizer interface in a later version of PIL.
+
+ self.load()
+
+ if palette:
+ # use palette from reference image
+ palette.load()
+ if palette.mode != "P":
+ raise ValueError("bad mode for palette image")
+ if self.mode != "RGB" and self.mode != "L":
+ raise ValueError(
+ "only RGB or L mode images can be quantized to a palette"
+ )
+ im = self.im.convert("P", 1, palette.im)
+ return self._makeself(im)
+
+ im = self.im.quantize(colors, method, kmeans)
+ return self._new(im)
+
+ ##
+ # Copies this image. Use this method if you wish to paste things
+ # into an image, but still retain the original.
+ #
+ # @return An Image object.
+
+ def copy(self):
+ "Copy raster data"
+
+ self.load()
+ im = self.im.copy()
+ return self._new(im)
+
+ ##
+ # Returns a rectangular region from this image. The box is a
+ # 4-tuple defining the left, upper, right, and lower pixel
+ # coordinate.
+ # <p>
+ # This is a lazy operation. Changes to the source image may or
+ # may not be reflected in the cropped image. To break the
+ # connection, call the {@link #Image.load} method on the cropped
+ # copy.
+ #
+ # @param The crop rectangle, as a (left, upper, right, lower)-tuple.
+ # @return An Image object.
+
+ def crop(self, box=None):
+ "Crop region from image"
+
+ self.load()
+ if box is None:
+ return self.copy()
+
+ # lazy operation
+ return _ImageCrop(self, box)
+
+ ##
+ # Configures the image file loader so it returns a version of the
+ # image that as closely as possible matches the given mode and
+ # size. For example, you can use this method to convert a colour
+ # JPEG to greyscale while loading it, or to extract a 128x192
+ # version from a PCD file.
+ # <p>
+ # Note that this method modifies the Image object in place. If
+ # the image has already been loaded, this method has no effect.
+ #
+ # @param mode The requested mode.
+ # @param size The requested size.
+
+ def draft(self, mode, size):
+ "Configure image decoder"
+
+ pass
+
+ def _expand(self, xmargin, ymargin=None):
+ if ymargin is None:
+ ymargin = xmargin
+ self.load()
+ return self._new(self.im.expand(xmargin, ymargin, 0))
+
+ ##
+ # Filters this image using the given filter. For a list of
+ # available filters, see the <b>ImageFilter</b> module.
+ #
+ # @param filter Filter kernel.
+ # @return An Image object.
+ # @see ImageFilter
+
+ def filter(self, filter):
+ "Apply environment filter to image"
+
+ self.load()
+
+ from ImageFilter import Filter
+ if not isinstance(filter, Filter):
+ filter = filter()
+
+ if self.im.bands == 1:
+ return self._new(filter.filter(self.im))
+ # fix to handle multiband images since _imaging doesn't
+ ims = []
+ for c in range(self.im.bands):
+ ims.append(self._new(filter.filter(self.im.getband(c))))
+ return merge(self.mode, ims)
+
+ ##
+ # Returns a tuple containing the name of each band in this image.
+ # For example, <b>getbands</b> on an RGB image returns ("R", "G", "B").
+ #
+ # @return A tuple containing band names.
+
+ def getbands(self):
+ "Get band names"
+
+ return ImageMode.getmode(self.mode).bands
+
+ ##
+ # Calculates the bounding box of the non-zero regions in the
+ # image.
+ #
+ # @return The bounding box is returned as a 4-tuple defining the
+ # left, upper, right, and lower pixel coordinate. If the image
+ # is completely empty, this method returns None.
+
+ def getbbox(self):
+ "Get bounding box of actual data (non-zero pixels) in image"
+
+ self.load()
+ return self.im.getbbox()
+
+ ##
+ # Returns a list of colors used in this image.
+ #
+ # @param maxcolors Maximum number of colors. If this number is
+ # exceeded, this method returns None. The default limit is
+ # 256 colors.
+ # @return An unsorted list of (count, pixel) values.
+
+ def getcolors(self, maxcolors=256):
+ "Get colors from image, up to given limit"
+
+ self.load()
+ if self.mode in ("1", "L", "P"):
+ h = self.im.histogram()
+ out = []
+ for i in range(256):
+ if h[i]:
+ out.append((h[i], i))
+ if len(out) > maxcolors:
+ return None
+ return out
+ return self.im.getcolors(maxcolors)
+
+ ##
+ # Returns the contents of this image as a sequence object
+ # containing pixel values. The sequence object is flattened, so
+ # that values for line one follow directly after the values of
+ # line zero, and so on.
+ # <p>
+ # Note that the sequence object returned by this method is an
+ # internal PIL data type, which only supports certain sequence
+ # operations. To convert it to an ordinary sequence (e.g. for
+ # printing), use <b>list(im.getdata())</b>.
+ #
+ # @param band What band to return. The default is to return
+ # all bands. To return a single band, pass in the index
+ # value (e.g. 0 to get the "R" band from an "RGB" image).
+ # @return A sequence-like object.
+
+ def getdata(self, band = None):
+ "Get image data as sequence object."
+
+ self.load()
+ if band is not None:
+ return self.im.getband(band)
+ return self.im # could be abused
+
+ ##
+ # Gets the the minimum and maximum pixel values for each band in
+ # the image.
+ #
+ # @return For a single-band image, a 2-tuple containing the
+ # minimum and maximum pixel value. For a multi-band image,
+ # a tuple containing one 2-tuple for each band.
+
+ def getextrema(self):
+ "Get min/max value"
+
+ self.load()
+ if self.im.bands > 1:
+ extrema = []
+ for i in range(self.im.bands):
+ extrema.append(self.im.getband(i).getextrema())
+ return tuple(extrema)
+ return self.im.getextrema()
+
+ ##
+ # Returns a PyCObject that points to the internal image memory.
+ #
+ # @return A PyCObject object.
+
+ def getim(self):
+ "Get PyCObject pointer to internal image memory"
+
+ self.load()
+ return self.im.ptr
+
+
+ ##
+ # Returns the image palette as a list.
+ #
+ # @return A list of color values [r, g, b, ...], or None if the
+ # image has no palette.
+
+ def getpalette(self):
+ "Get palette contents."
+
+ self.load()
+ try:
+ return map(ord, self.im.getpalette())
+ except ValueError:
+ return None # no palette
+
+
+ ##
+ # Returns the pixel value at a given position.
+ #
+ # @param xy The coordinate, given as (x, y).
+ # @return The pixel value. If the image is a multi-layer image,
+ # this method returns a tuple.
+
+ def getpixel(self, xy):
+ "Get pixel value"
+
+ self.load()
+ return self.im.getpixel(xy)
+
+ ##
+ # Returns the horizontal and vertical projection.
+ #
+ # @return Two sequences, indicating where there are non-zero
+ # pixels along the X-axis and the Y-axis, respectively.
+
+ def getprojection(self):
+ "Get projection to x and y axes"
+
+ self.load()
+ x, y = self.im.getprojection()
+ return map(ord, x), map(ord, y)
+
+ ##
+ # Returns a histogram for the image. The histogram is returned as
+ # a list of pixel counts, one for each pixel value in the source
+ # image. If the image has more than one band, the histograms for
+ # all bands are concatenated (for example, the histogram for an
+ # "RGB" image contains 768 values).
+ # <p>
+ # A bilevel image (mode "1") is treated as a greyscale ("L") image
+ # by this method.
+ # <p>
+ # If a mask is provided, the method returns a histogram for those
+ # parts of the image where the mask image is non-zero. The mask
+ # image must have the same size as the image, and be either a
+ # bi-level image (mode "1") or a greyscale image ("L").
+ #
+ # @def histogram(mask=None)
+ # @param mask An optional mask.
+ # @return A list containing pixel counts.
+
+ def histogram(self, mask=None, extrema=None):
+ "Take histogram of image"
+
+ self.load()
+ if mask:
+ mask.load()
+ return self.im.histogram((0, 0), mask.im)
+ if self.mode in ("I", "F"):
+ if extrema is None:
+ extrema = self.getextrema()
+ return self.im.histogram(extrema)
+ return self.im.histogram()
+
+ ##
+ # (Deprecated) Returns a copy of the image where the data has been
+ # offset by the given distances. Data wraps around the edges. If
+ # yoffset is omitted, it is assumed to be equal to xoffset.
+ # <p>
+ # This method is deprecated. New code should use the <b>offset</b>
+ # function in the <b>ImageChops</b> module.
+ #
+ # @param xoffset The horizontal distance.
+ # @param yoffset The vertical distance. If omitted, both
+ # distances are set to the same value.
+ # @return An Image object.
+
+ def offset(self, xoffset, yoffset=None):
+ "(deprecated) Offset image in horizontal and/or vertical direction"
+ if warnings:
+ warnings.warn(
+ "'offset' is deprecated; use 'ImageChops.offset' instead",
+ DeprecationWarning, stacklevel=2
+ )
+ import ImageChops
+ return ImageChops.offset(self, xoffset, yoffset)
+
+ ##
+ # Pastes another image into this image. The box argument is either
+ # a 2-tuple giving the upper left corner, a 4-tuple defining the
+ # left, upper, right, and lower pixel coordinate, or None (same as
+ # (0, 0)). If a 4-tuple is given, the size of the pasted image
+ # must match the size of the region.
+ # <p>
+ # If the modes don't match, the pasted image is converted to the
+ # mode of this image (see the {@link #Image.convert} method for
+ # details).
+ # <p>
+ # Instead of an image, the source can be a integer or tuple
+ # containing pixel values. The method then fills the region
+ # with the given colour. When creating RGB images, you can
+ # also use colour strings as supported by the ImageColor module.
+ # <p>
+ # If a mask is given, this method updates only the regions
+ # indicated by the mask. You can use either "1", "L" or "RGBA"
+ # images (in the latter case, the alpha band is used as mask).
+ # Where the mask is 255, the given image is copied as is. Where
+ # the mask is 0, the current value is preserved. Intermediate
+ # values can be used for transparency effects.
+ # <p>
+ # Note that if you paste an "RGBA" image, the alpha band is
+ # ignored. You can work around this by using the same image as
+ # both source image and mask.
+ #
+ # @param im Source image or pixel value (integer or tuple).
+ # @param box An optional 4-tuple giving the region to paste into.
+ # If a 2-tuple is used instead, it's treated as the upper left
+ # corner. If omitted or None, the source is pasted into the
+ # upper left corner.
+ # <p>
+ # If an image is given as the second argument and there is no
+ # third, the box defaults to (0, 0), and the second argument
+ # is interpreted as a mask image.
+ # @param mask An optional mask image.
+ # @return An Image object.
+
+ def paste(self, im, box=None, mask=None):
+ "Paste other image into region"
+
+ if isImageType(box) and mask is None:
+ # abbreviated paste(im, mask) syntax
+ mask = box; box = None
+
+ if box is None:
+ # cover all of self
+ box = (0, 0) + self.size
+
+ if len(box) == 2:
+ # lower left corner given; get size from image or mask
+ if isImageType(im):
+ size = im.size
+ elif isImageType(mask):
+ size = mask.size
+ else:
+ # FIXME: use self.size here?
+ raise ValueError(
+ "cannot determine region size; use 4-item box"
+ )
+ box = box + (box[0]+size[0], box[1]+size[1])
+
+ if isStringType(im):
+ import ImageColor
+ im = ImageColor.getcolor(im, self.mode)
+
+ elif isImageType(im):
+ im.load()
+ if self.mode != im.mode:
+ if self.mode != "RGB" or im.mode not in ("RGBA", "RGBa"):
+ # should use an adapter for this!
+ im = im.convert(self.mode)
+ im = im.im
+
+ self.load()
+ if self.readonly:
+ self._copy()
+
+ if mask:
+ mask.load()
+ self.im.paste(im, box, mask.im)
+ else:
+ self.im.paste(im, box)
+
+ ##
+ # Maps this image through a lookup table or function.
+ #
+ # @param lut A lookup table, containing 256 values per band in the
+ # image. A function can be used instead, it should take a single
+ # argument. The function is called once for each possible pixel
+ # value, and the resulting table is applied to all bands of the
+ # image.
+ # @param mode Output mode (default is same as input). In the
+ # current version, this can only be used if the source image
+ # has mode "L" or "P", and the output has mode "1".
+ # @return An Image object.
+
+ def point(self, lut, mode=None):
+ "Map image through lookup table"
+
+ self.load()
+
+ if not isSequenceType(lut):
+ # if it isn't a list, it should be a function
+ if self.mode in ("I", "I;16", "F"):
+ # check if the function can be used with point_transform
+ scale, offset = _getscaleoffset(lut)
+ return self._new(self.im.point_transform(scale, offset))
+ # for other modes, convert the function to a table
+ lut = map(lut, range(256)) * self.im.bands
+
+ if self.mode == "F":
+ # FIXME: _imaging returns a confusing error message for this case
+ raise ValueError("point operation not supported for this mode")
+
+ return self._new(self.im.point(lut, mode))
+
+ ##
+ # Adds or replaces the alpha layer in this image. If the image
+ # does not have an alpha layer, it's converted to "LA" or "RGBA".
+ # The new layer must be either "L" or "1".
+ #
+ # @param im The new alpha layer. This can either be an "L" or "1"
+ # image having the same size as this image, or an integer or
+ # other color value.
+
+ def putalpha(self, alpha):
+ "Set alpha layer"
+
+ self.load()
+ if self.readonly:
+ self._copy()
+
+ if self.mode not in ("LA", "RGBA"):
+ # attempt to promote self to a matching alpha mode
+ try:
+ mode = getmodebase(self.mode) + "A"
+ try:
+ self.im.setmode(mode)
+ except (AttributeError, ValueError):
+ # do things the hard way
+ im = self.im.convert(mode)
+ if im.mode not in ("LA", "RGBA"):
+ raise ValueError # sanity check
+ self.im = im
+ self.mode = self.im.mode
+ except (KeyError, ValueError):
+ raise ValueError("illegal image mode")
+
+ if self.mode == "LA":
+ band = 1
+ else:
+ band = 3
+
+ if isImageType(alpha):
+ # alpha layer
+ if alpha.mode not in ("1", "L"):
+ raise ValueError("illegal image mode")
+ alpha.load()
+ if alpha.mode == "1":
+ alpha = alpha.convert("L")
+ else:
+ # constant alpha
+ try:
+ self.im.fillband(band, alpha)
+ except (AttributeError, ValueError):
+ # do things the hard way
+ alpha = new("L", self.size, alpha)
+ else:
+ return
+
+ self.im.putband(alpha.im, band)
+
+ ##
+ # Copies pixel data to this image. This method copies data from a
+ # sequence object into the image, starting at the upper left
+ # corner (0, 0), and continuing until either the image or the
+ # sequence ends. The scale and offset values are used to adjust
+ # the sequence values: <b>pixel = value*scale + offset</b>.
+ #
+ # @param data A sequence object.
+ # @param scale An optional scale value. The default is 1.0.
+ # @param offset An optional offset value. The default is 0.0.
+
+ def putdata(self, data, scale=1.0, offset=0.0):
+ "Put data from a sequence object into an image."
+
+ self.load()
+ if self.readonly:
+ self._copy()
+
+ self.im.putdata(data, scale, offset)
+
+ ##
+ # Attaches a palette to this image. The image must be a "P" or
+ # "L" image, and the palette sequence must contain 768 integer
+ # values, where each group of three values represent the red,
+ # green, and blue values for the corresponding pixel
+ # index. Instead of an integer sequence, you can use an 8-bit
+ # string.
+ #
+ # @def putpalette(data)
+ # @param data A palette sequence (either a list or a string).
+
+ def putpalette(self, data, rawmode="RGB"):
+ "Put palette data into an image."
+
+ self.load()
+ if self.mode not in ("L", "P"):
+ raise ValueError("illegal image mode")
+ if not isStringType(data):
+ data = string.join(map(chr, data), "")
+ self.mode = "P"
+ self.palette = ImagePalette.raw(rawmode, data)
+ self.palette.mode = "RGB"
+ self.load() # install new palette
+
+ ##
+ # Modifies the pixel at the given position. The colour is given as
+ # a single numerical value for single-band images, and a tuple for
+ # multi-band images.
+ # <p>
+ # Note that this method is relatively slow. For more extensive
+ # changes, use {@link #Image.paste} or the <b>ImageDraw</b> module
+ # instead.
+ #
+ # @param xy The pixel coordinate, given as (x, y).
+ # @param value The pixel value.
+ # @see #Image.paste
+ # @see #Image.putdata
+ # @see ImageDraw
+
+ def putpixel(self, xy, value):
+ "Set pixel value"
+
+ self.load()
+ if self.readonly:
+ self._copy()
+
+ return self.im.putpixel(xy, value)
+
+ ##
+ # Returns a resized copy of this image.
+ #
+ # @def resize(size, filter=NEAREST)
+ # @param size The requested size in pixels, as a 2-tuple:
+ # (width, height).
+ # @param filter An optional resampling filter. This can be
+ # one of <b>NEAREST</b> (use nearest neighbour), <b>BILINEAR</b>
+ # (linear interpolation in a 2x2 environment), <b>BICUBIC</b>
+ # (cubic spline interpolation in a 4x4 environment), or
+ # <b>ANTIALIAS</b> (a high-quality downsampling filter).
+ # If omitted, or if the image has mode "1" or "P", it is
+ # set <b>NEAREST</b>.
+ # @return An Image object.
+
+ def resize(self, size, resample=NEAREST):
+ "Resize image"
+
+ if resample not in (NEAREST, BILINEAR, BICUBIC, ANTIALIAS):
+ raise ValueError("unknown resampling filter")
+
+ self.load()
+
+ if self.mode in ("1", "P"):
+ resample = NEAREST
+
+ if resample == ANTIALIAS:
+ # requires stretch support (imToolkit & PIL 1.1.3)
+ try:
+ im = self.im.stretch(size, resample)
+ except AttributeError:
+ raise ValueError("unsupported resampling filter")
+ else:
+ im = self.im.resize(size, resample)
+
+ return self._new(im)
+
+ ##
+ # Returns a rotated copy of this image. This method returns a
+ # copy of this image, rotated the given number of degrees counter
+ # clockwise around its centre.
+ #
+ # @def rotate(angle, filter=NEAREST)
+ # @param angle In degrees counter clockwise.
+ # @param filter An optional resampling filter. This can be
+ # one of <b>NEAREST</b> (use nearest neighbour), <b>BILINEAR</b>
+ # (linear interpolation in a 2x2 environment), or <b>BICUBIC</b>
+ # (cubic spline interpolation in a 4x4 environment).
+ # If omitted, or if the image has mode "1" or "P", it is
+ # set <b>NEAREST</b>.
+ # @param expand Optional expansion flag. If true, expands the output
+ # image to make it large enough to hold the entire rotated image.
+ # If false or omitted, make the output image the same size as the
+ # input image.
+ # @return An Image object.
+
+ def rotate(self, angle, resample=NEAREST, expand=0):
+ "Rotate image. Angle given as degrees counter-clockwise."
+
+ if expand:
+ import math
+ angle = -angle * math.pi / 180
+ matrix = [
+ math.cos(angle), math.sin(angle), 0.0,
+ -math.sin(angle), math.cos(angle), 0.0
+ ]
+ def transform(x, y, (a, b, c, d, e, f)=matrix):
+ return a*x + b*y + c, d*x + e*y + f
+
+ # calculate output size
+ w, h = self.size
+ xx = []
+ yy = []
+ for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
+ x, y = transform(x, y)
+ xx.append(x)
+ yy.append(y)
+ w = int(math.ceil(max(xx)) - math.floor(min(xx)))
+ h = int(math.ceil(max(yy)) - math.floor(min(yy)))
+
+ # adjust center
+ x, y = transform(w / 2.0, h / 2.0)
+ matrix[2] = self.size[0] / 2.0 - x
+ matrix[5] = self.size[1] / 2.0 - y
+
+ return self.transform((w, h), AFFINE, matrix)
+
+ if resample not in (NEAREST, BILINEAR, BICUBIC):
+ raise ValueError("unknown resampling filter")
+
+ self.load()
+
+ if self.mode in ("1", "P"):
+ resample = NEAREST
+
+ return self._new(self.im.rotate(angle, resample))
+
+ ##
+ # Saves this image under the given filename. If no format is
+ # specified, the format to use is determined from the filename
+ # extension, if possible.
+ # <p>
+ # Keyword options can be used to provide additional instructions
+ # to the writer. If a writer doesn't recognise an option, it is
+ # silently ignored. The available options are described later in
+ # this handbook.
+ # <p>
+ # You can use a file object instead of a filename. In this case,
+ # you must always specify the format. The file object must
+ # implement the <b>seek</b>, <b>tell</b>, and <b>write</b>
+ # methods, and be opened in binary mode.
+ #
+ # @def save(file, format=None, **options)
+ # @param file File name or file object.
+ # @param format Optional format override. If omitted, the
+ # format to use is determined from the filename extension.
+ # If a file object was used instead of a filename, this
+ # parameter should always be used.
+ # @param **options Extra parameters to the image writer.
+ # @return None
+ # @exception KeyError If the output format could not be determined
+ # from the file name. Use the format option to solve this.
+ # @exception IOError If the file could not be written. The file
+ # may have been created, and may contain partial data.
+
+ def save(self, fp, format=None, **params):
+ "Save image to file or stream"
+
+ if isStringType(fp):
+ filename = fp
+ else:
+ if hasattr(fp, "name") and isStringType(fp.name):
+ filename = fp.name
+ else:
+ filename = ""
+
+ # may mutate self!
+ self.load()
+
+ self.encoderinfo = params
+ self.encoderconfig = ()
+
+ preinit()
+
+ ext = string.lower(os.path.splitext(filename)[1])
+
+ if not format:
+ try:
+ format = EXTENSION[ext]
+ except KeyError:
+ init()
+ try:
+ format = EXTENSION[ext]
+ except KeyError:
+ raise KeyError(ext) # unknown extension
+
+ try:
+ save_handler = SAVE[string.upper(format)]
+ except KeyError:
+ init()
+ save_handler = SAVE[string.upper(format)] # unknown format
+
+ if isStringType(fp):
+ import __builtin__
+ fp = __builtin__.open(fp, "wb")
+ close = 1
+ else:
+ close = 0
+
+ try:
+ save_handler(self, fp, filename)
+ finally:
+ # do what we can to clean up
+ if close:
+ fp.close()
+
+ ##
+ # Seeks to the given frame in this sequence file. If you seek
+ # beyond the end of the sequence, the method raises an
+ # <b>EOFError</b> exception. When a sequence file is opened, the
+ # library automatically seeks to frame 0.
+ # <p>
+ # Note that in the current version of the library, most sequence
+ # formats only allows you to seek to the next frame.
+ #
+ # @param frame Frame number, starting at 0.
+ # @exception EOFError If the call attempts to seek beyond the end
+ # of the sequence.
+ # @see #Image.tell
+
+ def seek(self, frame):
+ "Seek to given frame in sequence file"
+
+ # overridden by file handlers
+ if frame != 0:
+ raise EOFError
+
+ ##
+ # Displays this image. This method is mainly intended for
+ # debugging purposes.
+ # <p>
+ # On Unix platforms, this method saves the image to a temporary
+ # PPM file, and calls the <b>xv</b> utility.
+ # <p>
+ # On Windows, it saves the image to a temporary BMP file, and uses
+ # the standard BMP display utility to show it (usually Paint).
+ #
+ # @def show(title=None)
+ # @param title Optional title to use for the image window,
+ # where possible.
+
+ def show(self, title=None, command=None):
+ "Display image (for debug purposes only)"
+
+ _showxv(self, title, command)
+
+ ##
+ # Split this image into individual bands. This method returns a
+ # tuple of individual image bands from an image. For example,
+ # splitting an "RGB" image creates three new images each
+ # containing a copy of one of the original bands (red, green,
+ # blue).
+ #
+ # @return A tuple containing bands.
+
+ def split(self):
+ "Split image into bands"
+
+ ims = []
+ self.load()
+ for i in range(self.im.bands):
+ ims.append(self._new(self.im.getband(i)))
+ return tuple(ims)
+
+ ##
+ # Returns the current frame number.
+ #
+ # @return Frame number, starting with 0.
+ # @see #Image.seek
+
+ def tell(self):
+ "Return current frame number"
+
+ return 0
+
+ ##
+ # Make this image into a thumbnail. This method modifies the
+ # image to contain a thumbnail version of itself, no larger than
+ # the given size. This method calculates an appropriate thumbnail
+ # size to preserve the aspect of the image, calls the {@link
+ # #Image.draft} method to configure the file reader (where
+ # applicable), and finally resizes the image.
+ # <p>
+ # Note that the bilinear and bicubic filters in the current
+ # version of PIL are not well-suited for thumbnail generation.
+ # You should use <b>ANTIALIAS</b> unless speed is much more
+ # important than quality.
+ # <p>
+ # Also note that this function modifies the Image object in place.
+ # If you need to use the full resolution image as well, apply this
+ # method to a {@link #Image.copy} of the original image.
+ #
+ # @param size Requested size.
+ # @param resample Optional resampling filter. This can be one
+ # of <b>NEAREST</b>, <b>BILINEAR</b>, <b>BICUBIC</b>, or
+ # <b>ANTIALIAS</b> (best quality). If omitted, it defaults
+ # to <b>NEAREST</b> (this will be changed to ANTIALIAS in a
+ # future version).
+ # @return None
+
+ def thumbnail(self, size, resample=NEAREST):
+ "Create thumbnail representation (modifies image in place)"
+
+ # FIXME: the default resampling filter will be changed
+ # to ANTIALIAS in future versions
+
+ # preserve aspect ratio
+ x, y = self.size
+ if x > size[0]: y = max(y * size[0] / x, 1); x = size[0]
+ if y > size[1]: x = max(x * size[1] / y, 1); y = size[1]
+ size = x, y
+
+ if size == self.size:
+ return
+
+ self.draft(None, size)
+
+ self.load()
+
+ try:
+ im = self.resize(size, resample)
+ except ValueError:
+ if resample != ANTIALIAS:
+ raise
+ im = self.resize(size, NEAREST) # fallback
+
+ self.im = im.im
+ self.mode = im.mode
+ self.size = size
+
+ self.readonly = 0
+
+ # FIXME: the different tranform methods need further explanation
+ # instead of bloating the method docs, add a separate chapter.
+
+ ##
+ # Transforms this image. This method creates a new image with the
+ # given size, and the same mode as the original, and copies data
+ # to the new image using the given transform.
+ # <p>
+ # @def transform(size, method, data, resample=NEAREST)
+ # @param size The output size.
+ # @param method The transformation method. This is one of
+ # <b>EXTENT</b> (cut out a rectangular subregion), <b>AFFINE</b>
+ # (affine transform), <b>PERSPECTIVE</b> (perspective
+ # transform), <b>QUAD</b> (map a quadrilateral to a
+ # rectangle), or <b>MESH</b> (map a number of source quadrilaterals
+ # in one operation).
+ # @param data Extra data to the transformation method.
+ # @param resample Optional resampling filter. It can be one of
+ # <b>NEAREST</b> (use nearest neighbour), <b>BILINEAR</b>
+ # (linear interpolation in a 2x2 environment), or
+ # <b>BICUBIC</b> (cubic spline interpolation in a 4x4
+ # environment). If omitted, or if the image has mode
+ # "1" or "P", it is set to <b>NEAREST</b>.
+ # @return An Image object.
+
+ def transform(self, size, method, data=None, resample=NEAREST, fill=1):
+ "Transform image"
+
+ import ImageTransform
+ if isinstance(method, ImageTransform.Transform):
+ method, data = method.getdata()
+ if data is None:
+ raise ValueError("missing method data")
+ im = new(self.mode, size, None)
+ if method == MESH:
+ # list of quads
+ for box, quad in data:
+ im.__transformer(box, self, QUAD, quad, resample, fill)
+ else:
+ im.__transformer((0, 0)+size, self, method, data, resample, fill)
+
+ return im
+
+ def __transformer(self, box, image, method, data,
+ resample=NEAREST, fill=1):
+
+ # FIXME: this should be turned into a lazy operation (?)
+
+ w = box[2]-box[0]
+ h = box[3]-box[1]
+
+ if method == AFFINE:
+ # change argument order to match implementation
+ data = (data[2], data[0], data[1],
+ data[5], data[3], data[4])
+ elif method == EXTENT:
+ # convert extent to an affine transform
+ x0, y0, x1, y1 = data
+ xs = float(x1 - x0) / w
+ ys = float(y1 - y0) / h
+ method = AFFINE
+ data = (x0 + xs/2, xs, 0, y0 + ys/2, 0, ys)
+ elif method == PERSPECTIVE:
+ # change argument order to match implementation
+ data = (data[2], data[0], data[1],
+ data[5], data[3], data[4],
+ data[6], data[7])
+ elif method == QUAD:
+ # quadrilateral warp. data specifies the four corners
+ # given as NW, SW, SE, and NE.
+ nw = data[0:2]; sw = data[2:4]; se = data[4:6]; ne = data[6:8]
+ x0, y0 = nw; As = 1.0 / w; At = 1.0 / h
+ data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At,
+ (se[0]-sw[0]-ne[0]+x0)*As*At,
+ y0, (ne[1]-y0)*As, (sw[1]-y0)*At,
+ (se[1]-sw[1]-ne[1]+y0)*As*At)
+ else:
+ raise ValueError("unknown transformation method")
+
+ if resample not in (NEAREST, BILINEAR, BICUBIC):
+ raise ValueError("unknown resampling filter")
+
+ image.load()
+
+ self.load()
+
+ if image.mode in ("1", "P"):
+ resample = NEAREST
+
+ self.im.transform2(box, image.im, method, data, resample, fill)
+
+ ##
+ # Returns a flipped or rotated copy of this image.
+ #
+ # @param method One of <b>FLIP_LEFT_RIGHT</b>, <b>FLIP_TOP_BOTTOM</b>,
+ # <b>ROTATE_90</b>, <b>ROTATE_180</b>, or <b>ROTATE_270</b>.
+
+ def transpose(self, method):
+ "Transpose image (flip or rotate in 90 degree steps)"
+
+ self.load()
+ im = self.im.transpose(method)
+ return self._new(im)
+
+# --------------------------------------------------------------------
+# Lazy operations
+
+class _ImageCrop(Image):
+
+ def __init__(self, im, box):
+
+ Image.__init__(self)
+
+ x0, y0, x1, y1 = box
+ if x1 < x0:
+ x1 = x0
+ if y1 < y0:
+ y1 = y0
+
+ self.mode = im.mode
+ self.size = x1-x0, y1-y0
+
+ self.__crop = x0, y0, x1, y1
+
+ self.im = im.im
+
+ def load(self):
+
+ # lazy evaluation!
+ if self.__crop:
+ self.im = self.im.crop(self.__crop)
+ self.__crop = None
+
+ # FIXME: future versions should optimize crop/paste
+ # sequences!
+
+# --------------------------------------------------------------------
+# Factories
+
+#
+# Debugging
+
+def _wedge():
+ "Create greyscale wedge (for debugging only)"
+
+ return Image()._new(core.wedge("L"))
+
+##
+# Creates a new image with the given mode and size.
+#
+# @param mode The mode to use for the new image.
+# @param size A 2-tuple, containing (width, height) in pixels.
+# @param color What colour to use for the image. Default is black.
+# If given, this should be a single integer or floating point value
+# for single-band modes, and a tuple for multi-band modes (one value
+# per band). When creating RGB images, you can also use colour
+# strings as supported by the ImageColor module. If the colour is
+# None, the image is not initialised.
+# @return An Image object.
+
+def new(mode, size, color=0):
+ "Create a new image"
+
+ if color is None:
+ # don't initialize
+ return Image()._new(core.new(mode, size))
+
+ if isStringType(color):
+ # css3-style specifier
+
+ import ImageColor
+ color = ImageColor.getcolor(color, mode)
+
+ return Image()._new(core.fill(mode, size, color))
+
+##
+# Creates an image memory from pixel data in a string.
+# <p>
+# In its simplest form, this function takes three arguments
+# (mode, size, and unpacked pixel data).
+# <p>
+# You can also use any pixel decoder supported by PIL. For more
+# information on available decoders, see the section <a
+# href="pil-decoder.htm"><i>Writing Your Own File Decoder</i></a>.
+# <p>
+# Note that this function decodes pixel data only, not entire images.
+# If you have an entire image in a string, wrap it in a
+# <b>StringIO</b> object, and use {@link #open} to load it.
+#
+# @param mode The image mode.
+# @param size The image size.
+# @param data An 8-bit string containing raw data for the given mode.
+# @param decoder_name What decoder to use.
+# @param *args Additional parameters for the given decoder.
+# @return An Image object.
+
+def fromstring(mode, size, data, decoder_name="raw", *args):
+ "Load image from string"
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isTupleType(args[0]):
+ args = args[0]
+
+ if decoder_name == "raw" and args == ():
+ args = mode
+
+ im = new(mode, size)
+ im.fromstring(data, decoder_name, args)
+ return im
+
+##
+# (New in 1.1.4) Creates an image memory from pixel data in a string
+# or byte buffer.
+# <p>
+# This function is similar to {@link #fromstring}, but uses data in
+# the byte buffer, where possible. This means that changes to the
+# original buffer object are reflected in this image). Not all modes
+# can share memory; supported modes include "L", "RGBX", "RGBA", and
+# "CMYK".
+# <p>
+# Note that this function decodes pixel data only, not entire images.
+# If you have an entire image file in a string, wrap it in a
+# <b>StringIO</b> object, and use {@link #open} to load it.
+# <p>
+# In the current version, the default parameters used for the "raw"
+# decoder differs from that used for {@link fromstring}. This is a
+# bug, and will probably be fixed in a future release. The current
+# release issues a warning if you do this; to disable the warning,
+# you should provide the full set of parameters. See below for
+# details.
+#
+# @param mode The image mode.
+# @param size The image size.
+# @param data An 8-bit string or other buffer object containing raw
+# data for the given mode.
+# @param decoder_name What decoder to use.
+# @param *args Additional parameters for the given decoder. For the
+# default encoder ("raw"), it's recommended that you provide the
+# full set of parameters:
+# <b>frombuffer(mode, size, data, "raw", mode, 0, 1)</b>.
+# @return An Image object.
+# @since 1.1.4
+
+def frombuffer(mode, size, data, decoder_name="raw", *args):
+ "Load image from string or buffer"
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isTupleType(args[0]):
+ args = args[0]
+
+ if decoder_name == "raw":
+ if args == ():
+ if warnings:
+ warnings.warn(
+ "the frombuffer defaults may change in a future release; "
+ "for portability, change the call to read:\n"
+ " frombuffer(mode, size, data, 'raw', mode, 0, 1)",
+ RuntimeWarning, stacklevel=2
+ )
+ args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6
+ if args[0] in _MAPMODES:
+ im = new(mode, (1,1))
+ im = im._new(
+ core.map_buffer(data, size, decoder_name, None, 0, args)
+ )
+ im.readonly = 1
+ return im
+
+ return apply(fromstring, (mode, size, data, decoder_name, args))
+
+
+##
+# (New in 1.1.6) Create an image memory from an object exporting
+# the array interface (using the buffer protocol).
+#
+# If obj is not contiguous, then the tostring method is called
+# and {@link frombuffer} is used.
+#
+# @param obj Object with array interface
+# @param mode Mode to use (will be determined from type if None)
+# @return An image memory.
+
+def fromarray(obj, mode=None):
+ arr = obj.__array_interface__
+ shape = arr['shape']
+ ndim = len(shape)
+ try:
+ strides = arr['strides']
+ except KeyError:
+ strides = None
+ if mode is None:
+ typestr = arr['typestr']
+ if not (typestr[0] == '|' or typestr[0] == _ENDIAN or
+ typestr[1:] not in ['u1', 'b1', 'i4', 'f4']):
+ raise TypeError("cannot handle data-type")
+ typestr = typestr[:2]
+ if typestr == 'i4':
+ mode = 'I'
+ elif typestr == 'f4':
+ mode = 'F'
+ elif typestr == 'b1':
+ mode = '1'
+ elif ndim == 2:
+ mode = 'L'
+ elif ndim == 3:
+ mode = 'RGB'
+ elif ndim == 4:
+ mode = 'RGBA'
+ else:
+ raise TypeError("Do not understand data.")
+ ndmax = 4
+ bad_dims=0
+ if mode in ['1','L','I','P','F']:
+ ndmax = 2
+ elif mode == 'RGB':
+ ndmax = 3
+ if ndim > ndmax:
+ raise ValueError("Too many dimensions.")
+
+ size = shape[:2][::-1]
+ if strides is not None:
+ obj = obj.tostring()
+
+ return frombuffer(mode, size, obj, "raw", mode, 0, 1)
+
+##
+# Opens and identifies the given image file.
+# <p>
+# This is a lazy operation; this function identifies the file, but the
+# actual image data is not read from the file until you try to process
+# the data (or call the {@link #Image.load} method).
+#
+# @def open(file, mode="r")
+# @param file A filename (string) or a file object. The file object
+# must implement <b>read</b>, <b>seek</b>, and <b>tell</b> methods,
+# and be opened in binary mode.
+# @param mode The mode. If given, this argument must be "r".
+# @return An Image object.
+# @exception IOError If the file cannot be found, or the image cannot be
+# opened and identified.
+# @see #new
+
+def open(fp, mode="r"):
+ "Open an image file, without loading the raster data"
+
+ if mode != "r":
+ raise ValueError("bad mode")
+
+ if isStringType(fp):
+ import __builtin__
+ filename = fp
+ fp = __builtin__.open(fp, "rb")
+ else:
+ filename = ""
+
+ prefix = fp.read(16)
+
+ preinit()
+
+ for i in ID:
+ try:
+ factory, accept = OPEN[i]
+ if not accept or accept(prefix):
+ fp.seek(0)
+ return factory(fp, filename)
+ except (SyntaxError, IndexError, TypeError):
+ pass
+
+ init()
+
+ for i in ID:
+ try:
+ factory, accept = OPEN[i]
+ if not accept or accept(prefix):
+ fp.seek(0)
+ return factory(fp, filename)
+ except (SyntaxError, IndexError, TypeError):
+ pass
+
+ raise IOError("cannot identify image file")
+
+#
+# Image processing.
+
+##
+# Creates a new image by interpolating between two input images, using
+# a constant alpha.
+#
+# <pre>
+# out = image1 * (1.0 - alpha) + image2 * alpha
+# </pre>
+#
+# @param im1 The first image.
+# @param im2 The second image. Must have the same mode and size as
+# the first image.
+# @param alpha The interpolation alpha factor. If alpha is 0.0, a
+# copy of the first image is returned. If alpha is 1.0, a copy of
+# the second image is returned. There are no restrictions on the
+# alpha value. If necessary, the result is clipped to fit into
+# the allowed output range.
+# @return An Image object.
+
+def blend(im1, im2, alpha):
+ "Interpolate between images."
+
+ im1.load()
+ im2.load()
+ return im1._new(core.blend(im1.im, im2.im, alpha))
+
+##
+# Creates a new image by interpolating between two input images,
+# using the mask as alpha.
+#
+# @param image1 The first image.
+# @param image2 The second image. Must have the same mode and
+# size as the first image.
+# @param mask A mask image. This image can can have mode
+# "1", "L", or "RGBA", and must have the same size as the
+# other two images.
+
+def composite(image1, image2, mask):
+ "Create composite image by blending images using a transparency mask"
+
+ image = image2.copy()
+ image.paste(image1, None, mask)
+ return image
+
+##
+# Applies the function (which should take one argument) to each pixel
+# in the given image. If the image has more than one band, the same
+# function is applied to each band. Note that the function is
+# evaluated once for each possible pixel value, so you cannot use
+# random components or other generators.
+#
+# @def eval(image, function)
+# @param image The input image.
+# @param function A function object, taking one integer argument.
+# @return An Image object.
+
+def eval(image, *args):
+ "Evaluate image expression"
+
+ return image.point(args[0])
+
+##
+# Creates a new image from a number of single-band images.
+#
+# @param mode The mode to use for the output image.
+# @param bands A sequence containing one single-band image for
+# each band in the output image. All bands must have the
+# same size.
+# @return An Image object.
+
+def merge(mode, bands):
+ "Merge a set of single band images into a new multiband image."
+
+ if getmodebands(mode) != len(bands) or "*" in mode:
+ raise ValueError("wrong number of bands")
+ for im in bands[1:]:
+ if im.mode != getmodetype(mode):
+ raise ValueError("mode mismatch")
+ if im.size != bands[0].size:
+ raise ValueError("size mismatch")
+ im = core.new(mode, bands[0].size)
+ for i in range(getmodebands(mode)):
+ bands[i].load()
+ im.putband(bands[i].im, i)
+ return bands[0]._new(im)
+
+# --------------------------------------------------------------------
+# Plugin registry
+
+##
+# Register an image file plugin. This function should not be used
+# in application code.
+#
+# @param id An image format identifier.
+# @param factory An image file factory method.
+# @param accept An optional function that can be used to quickly
+# reject images having another format.
+
+def register_open(id, factory, accept=None):
+ id = string.upper(id)
+ ID.append(id)
+ OPEN[id] = factory, accept
+
+##
+# Registers an image MIME type. This function should not be used
+# in application code.
+#
+# @param id An image format identifier.
+# @param mimetype The image MIME type for this format.
+
+def register_mime(id, mimetype):
+ MIME[string.upper(id)] = mimetype
+
+##
+# Registers an image save function. This function should not be
+# used in application code.
+#
+# @param id An image format identifier.
+# @param driver A function to save images in this format.
+
+def register_save(id, driver):
+ SAVE[string.upper(id)] = driver
+
+##
+# Registers an image extension. This function should not be
+# used in application code.
+#
+# @param id An image format identifier.
+# @param extension An extension used for this format.
+
+def register_extension(id, extension):
+ EXTENSION[string.lower(extension)] = string.upper(id)
+
+
+# --------------------------------------------------------------------
+# Simple display support
+
+def _showxv(image, title=None, command=None):
+
+ if os.name == "nt":
+ format = "BMP"
+ elif sys.platform == "darwin":
+ format = "JPEG"
+ if not command:
+ command = "open -a /Applications/Preview.app"
+ else:
+ format = None
+ if not command:
+ command = "xv"
+ if title:
+ command = command + " -name \"%s\"" % title
+
+ if image.mode == "I;16":
+ # @PIL88 @PIL101
+ # "I;16" isn't an 'official' mode, but we still want to
+ # provide a simple way to show 16-bit images.
+ base = "L"
+ else:
+ base = getmodebase(image.mode)
+ if base != image.mode and image.mode != "1":
+ file = image.convert(base)._dump(format=format)
+ else:
+ file = image._dump(format=format)
+
+ if os.name == "nt":
+ command = "start /wait %s && del /f %s" % (file, file)
+ elif sys.platform == "darwin":
+ # on darwin open returns immediately resulting in the temp
+ # file removal while app is opening
+ command = "(%s %s; sleep 20; rm -f %s)&" % (command, file, file)
+ else:
+ command = "(%s %s; rm -f %s)&" % (command, file, file)
+
+ os.system(command)
diff --git a/PIL/ImageChops.py b/PIL/ImageChops.py
new file mode 100644
index 0000000..6db0710
--- /dev/null
+++ b/PIL/ImageChops.py
@@ -0,0 +1,302 @@
+#
+# The Python Imaging Library.
+# $Id: ImageChops.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# standard channel operations
+#
+# History:
+# 1996-03-24 fl Created
+# 1996-08-13 fl Added logical operations (for "1" images)
+# 2000-10-12 fl Added offset method (from Image.py)
+#
+# Copyright (c) 1997-2000 by Secret Labs AB
+# Copyright (c) 1996-2000 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+
+##
+# The <b>ImageChops</b> module contains a number of arithmetical image
+# operations, called <i>channel operations</i> ("chops"). These can be
+# used for various purposes, including special effects, image
+# compositions, algorithmic painting, and more.
+# <p>
+# At this time, channel operations are only implemented for 8-bit
+# images (e.g. &quot;L&quot; and &quot;RGB&quot;).
+# <p>
+# Most channel operations take one or two image arguments and returns
+# a new image. Unless otherwise noted, the result of a channel
+# operation is always clipped to the range 0 to MAX (which is 255 for
+# all modes supported by the operations in this module).
+##
+
+##
+# Return an image with the same size as the given image, but filled
+# with the given pixel value.
+#
+# @param image Reference image.
+# @param value Pixel value.
+# @return An image object.
+
+def constant(image, value):
+ "Fill a channel with a given grey level"
+
+ return Image.new("L", image.size, value)
+
+##
+# Copy image.
+#
+# @param image Source image.
+# @return A copy of the source image.
+
+def duplicate(image):
+ "Create a copy of a channel"
+
+ return image.copy()
+
+##
+# Inverts an image
+# (MAX - image).
+#
+# @param image Source image.
+# @return An image object.
+
+def invert(image):
+ "Invert a channel"
+
+ image.load()
+ return image._new(image.im.chop_invert())
+
+##
+# Compare images, and return lighter pixel value
+# (max(image1, image2)).
+# <p>
+# Compares the two images, pixel by pixel, and returns a new image
+# containing the lighter values.
+#
+# @param image1 First image.
+# @param image1 Second image.
+# @return An image object.
+
+def lighter(image1, image2):
+ "Select the lighter pixels from each image"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_lighter(image2.im))
+
+##
+# Compare images, and return darker pixel value
+# (min(image1, image2)).
+# <p>
+# Compares the two images, pixel by pixel, and returns a new image
+# containing the darker values.
+#
+# @param image1 First image.
+# @param image1 Second image.
+# @return An image object.
+
+def darker(image1, image2):
+ "Select the darker pixels from each image"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_darker(image2.im))
+
+##
+# Calculate absolute difference
+# (abs(image1 - image2)).
+# <p>
+# Returns the absolute value of the difference between the two images.
+#
+# @param image1 First image.
+# @param image1 Second image.
+# @return An image object.
+
+def difference(image1, image2):
+ "Subtract one image from another"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_difference(image2.im))
+
+##
+# Superimpose positive images
+# (image1 * image2 / MAX).
+# <p>
+# Superimposes two images on top of each other. If you multiply an
+# image with a solid black image, the result is black. If you multiply
+# with a solid white image, the image is unaffected.
+#
+# @param image1 First image.
+# @param image1 Second image.
+# @return An image object.
+
+def multiply(image1, image2):
+ "Superimpose two positive images"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_multiply(image2.im))
+
+##
+# Superimpose negative images
+# (MAX - ((MAX - image1) * (MAX - image2) / MAX)).
+# <p>
+# Superimposes two inverted images on top of each other.
+#
+# @param image1 First image.
+# @param image1 Second image.
+# @return An image object.
+
+def screen(image1, image2):
+ "Superimpose two negative images"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_screen(image2.im))
+
+##
+# Add images
+# ((image1 + image2) / scale + offset).
+# <p>
+# Adds two images, dividing the result by scale and adding the
+# offset. If omitted, scale defaults to 1.0, and offset to 0.0.
+#
+# @param image1 First image.
+# @param image1 Second image.
+# @return An image object.
+
+def add(image1, image2, scale=1.0, offset=0):
+ "Add two images"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_add(image2.im, scale, offset))
+
+##
+# Subtract images
+# ((image1 - image2) / scale + offset).
+# <p>
+# Subtracts two images, dividing the result by scale and adding the
+# offset. If omitted, scale defaults to 1.0, and offset to 0.0.
+#
+# @param image1 First image.
+# @param image1 Second image.
+# @return An image object.
+
+def subtract(image1, image2, scale=1.0, offset=0):
+ "Subtract two images"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_subtract(image2.im, scale, offset))
+
+##
+# Add images without clipping
+# ((image1 + image2) % MAX).
+# <p>
+# Adds two images, without clipping the result.
+#
+# @param image1 First image.
+# @param image1 Second image.
+# @return An image object.
+
+def add_modulo(image1, image2):
+ "Add two images without clipping"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_add_modulo(image2.im))
+
+##
+# Subtract images without clipping
+# ((image1 - image2) % MAX).
+# <p>
+# Subtracts two images, without clipping the result.
+#
+# @param image1 First image.
+# @param image1 Second image.
+# @return An image object.
+
+def subtract_modulo(image1, image2):
+ "Subtract two images without clipping"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_subtract_modulo(image2.im))
+
+##
+# Logical AND
+# (image1 and image2).
+
+def logical_and(image1, image2):
+ "Logical and between two images"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_and(image2.im))
+
+##
+# Logical OR
+# (image1 or image2).
+
+def logical_or(image1, image2):
+ "Logical or between two images"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_or(image2.im))
+
+##
+# Logical XOR
+# (image1 xor image2).
+
+def logical_xor(image1, image2):
+ "Logical xor between two images"
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_xor(image2.im))
+
+##
+# Blend images using constant transparency weight.
+# <p>
+# Same as the <b>blend</b> function in the <b>Image</b> module.
+
+def blend(image1, image2, alpha):
+ "Blend two images using a constant transparency weight"
+
+ return Image.blend(image1, image2, alpha)
+
+##
+# Create composite using transparency mask.
+# <p>
+# Same as the <b>composite</b> function in the <b>Image</b> module.
+
+def composite(image1, image2, mask):
+ "Create composite image by blending images using a transparency mask"
+
+ return Image.composite(image1, image2, mask)
+
+##
+# Offset image data.
+# <p>
+# Returns a copy of the image where data has been offset by the given
+# distances. Data wraps around the edges. If yoffset is omitted, it
+# is assumed to be equal to xoffset.
+#
+# @param image Source image.
+# @param xoffset The horizontal distance.
+# @param yoffset The vertical distance. If omitted, both
+# distances are set to the same value.
+# @return An Image object.
+
+def offset(image, xoffset, yoffset=None):
+ "Offset image in horizontal and/or vertical direction"
+ if yoffset is None:
+ yoffset = xoffset
+ image.load()
+ return image._new(image.im.offset(xoffset, yoffset))
diff --git a/PIL/ImageColor.py b/PIL/ImageColor.py
new file mode 100644
index 0000000..d97c0c4
--- /dev/null
+++ b/PIL/ImageColor.py
@@ -0,0 +1,262 @@
+#
+# The Python Imaging Library
+# $Id: ImageColor.py 2813 2006-10-07 10:11:35Z fredrik $
+#
+# map CSS3-style colour description strings to RGB
+#
+# History:
+# 2002-10-24 fl Added support for CSS-style color strings
+# 2002-12-15 fl Added RGBA support
+# 2004-03-27 fl Fixed remaining int() problems for Python 1.5.2
+# 2004-07-19 fl Fixed gray/grey spelling issues
+#
+# Copyright (c) 2002-2004 by Secret Labs AB
+# Copyright (c) 2002-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+import re, string
+
+try:
+ x = int("a", 16)
+except TypeError:
+ # python 1.5.2 doesn't support int(x,b)
+ str2int = string.atoi
+else:
+ str2int = int
+
+##
+# Convert color string to RGB tuple.
+#
+# @param color A CSS3-style colour string.
+# @return An RGB-tuple.
+# @exception ValueError If the color string could not be interpreted
+# as an RGB value.
+
+def getrgb(color):
+ # FIXME: add RGBA support
+ try:
+ rgb = colormap[color]
+ except KeyError:
+ try:
+ # fall back on case-insensitive lookup
+ rgb = colormap[string.lower(color)]
+ except KeyError:
+ rgb = None
+ # found color in cache
+ if rgb:
+ if isinstance(rgb, type(())):
+ return rgb
+ colormap[color] = rgb = getrgb(rgb)
+ return rgb
+ # check for known string formats
+ m = re.match("#\w\w\w$", color)
+ if m:
+ return (
+ str2int(color[1]*2, 16),
+ str2int(color[2]*2, 16),
+ str2int(color[3]*2, 16)
+ )
+ m = re.match("#\w\w\w\w\w\w$", color)
+ if m:
+ return (
+ str2int(color[1:3], 16),
+ str2int(color[3:5], 16),
+ str2int(color[5:7], 16)
+ )
+ m = re.match("rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
+ if m:
+ return (
+ str2int(m.group(1)),
+ str2int(m.group(2)),
+ str2int(m.group(3))
+ )
+ m = re.match("rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
+ if m:
+ return (
+ int((str2int(m.group(1)) * 255) / 100.0 + 0.5),
+ int((str2int(m.group(2)) * 255) / 100.0 + 0.5),
+ int((str2int(m.group(3)) * 255) / 100.0 + 0.5)
+ )
+ m = re.match("hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
+ if m:
+ from colorsys import hls_to_rgb
+ rgb = hls_to_rgb(
+ float(m.group(1)) / 360.0,
+ float(m.group(3)) / 100.0,
+ float(m.group(2)) / 100.0,
+ )
+ return (
+ int(rgb[0] * 255 + 0.5),
+ int(rgb[1] * 255 + 0.5),
+ int(rgb[2] * 255 + 0.5)
+ )
+ raise ValueError("unknown color specifier: %r" % color)
+
+def getcolor(color, mode):
+ # same as getrgb, but converts the result to the given mode
+ color = getrgb(color)
+ if mode == "RGB":
+ return color
+ if mode == "RGBA":
+ r, g, b = color
+ return r, g, b, 255
+ if Image.getmodebase(mode) == "L":
+ r, g, b = color
+ return r*299/1000 + g*587/1000 + b*114/1000
+ return color
+
+colormap = {
+ # X11 colour table (from "CSS3 module: Color working draft"), with
+ # gray/grey spelling issues fixed. This is a superset of HTML 4.0
+ # colour names used in CSS 1.
+ "aliceblue": "#f0f8ff",
+ "antiquewhite": "#faebd7",
+ "aqua": "#00ffff",
+ "aquamarine": "#7fffd4",
+ "azure": "#f0ffff",
+ "beige": "#f5f5dc",
+ "bisque": "#ffe4c4",
+ "black": "#000000",
+ "blanchedalmond": "#ffebcd",
+ "blue": "#0000ff",
+ "blueviolet": "#8a2be2",
+ "brown": "#a52a2a",
+ "burlywood": "#deb887",
+ "cadetblue": "#5f9ea0",
+ "chartreuse": "#7fff00",
+ "chocolate": "#d2691e",
+ "coral": "#ff7f50",
+ "cornflowerblue": "#6495ed",
+ "cornsilk": "#fff8dc",
+ "crimson": "#dc143c",
+ "cyan": "#00ffff",
+ "darkblue": "#00008b",
+ "darkcyan": "#008b8b",
+ "darkgoldenrod": "#b8860b",
+ "darkgray": "#a9a9a9",
+ "darkgrey": "#a9a9a9",
+ "darkgreen": "#006400",
+ "darkkhaki": "#bdb76b",
+ "darkmagenta": "#8b008b",
+ "darkolivegreen": "#556b2f",
+ "darkorange": "#ff8c00",
+ "darkorchid": "#9932cc",
+ "darkred": "#8b0000",
+ "darksalmon": "#e9967a",
+ "darkseagreen": "#8fbc8f",
+ "darkslateblue": "#483d8b",
+ "darkslategray": "#2f4f4f",
+ "darkslategrey": "#2f4f4f",
+ "darkturquoise": "#00ced1",
+ "darkviolet": "#9400d3",
+ "deeppink": "#ff1493",
+ "deepskyblue": "#00bfff",
+ "dimgray": "#696969",
+ "dimgrey": "#696969",
+ "dodgerblue": "#1e90ff",
+ "firebrick": "#b22222",
+ "floralwhite": "#fffaf0",
+ "forestgreen": "#228b22",
+ "fuchsia": "#ff00ff",
+ "gainsboro": "#dcdcdc",
+ "ghostwhite": "#f8f8ff",
+ "gold": "#ffd700",
+ "goldenrod": "#daa520",
+ "gray": "#808080",
+ "grey": "#808080",
+ "green": "#008000",
+ "greenyellow": "#adff2f",
+ "honeydew": "#f0fff0",
+ "hotpink": "#ff69b4",
+ "indianred": "#cd5c5c",
+ "indigo": "#4b0082",
+ "ivory": "#fffff0",
+ "khaki": "#f0e68c",
+ "lavender": "#e6e6fa",
+ "lavenderblush": "#fff0f5",
+ "lawngreen": "#7cfc00",
+ "lemonchiffon": "#fffacd",
+ "lightblue": "#add8e6",
+ "lightcoral": "#f08080",
+ "lightcyan": "#e0ffff",
+ "lightgoldenrodyellow": "#fafad2",
+ "lightgreen": "#90ee90",
+ "lightgray": "#d3d3d3",
+ "lightgrey": "#d3d3d3",
+ "lightpink": "#ffb6c1",
+ "lightsalmon": "#ffa07a",
+ "lightseagreen": "#20b2aa",
+ "lightskyblue": "#87cefa",
+ "lightslategray": "#778899",
+ "lightslategrey": "#778899",
+ "lightsteelblue": "#b0c4de",
+ "lightyellow": "#ffffe0",
+ "lime": "#00ff00",
+ "limegreen": "#32cd32",
+ "linen": "#faf0e6",
+ "magenta": "#ff00ff",
+ "maroon": "#800000",
+ "mediumaquamarine": "#66cdaa",
+ "mediumblue": "#0000cd",
+ "mediumorchid": "#ba55d3",
+ "mediumpurple": "#9370db",
+ "mediumseagreen": "#3cb371",
+ "mediumslateblue": "#7b68ee",
+ "mediumspringgreen": "#00fa9a",
+ "mediumturquoise": "#48d1cc",
+ "mediumvioletred": "#c71585",
+ "midnightblue": "#191970",
+ "mintcream": "#f5fffa",
+ "mistyrose": "#ffe4e1",
+ "moccasin": "#ffe4b5",
+ "navajowhite": "#ffdead",
+ "navy": "#000080",
+ "oldlace": "#fdf5e6",
+ "olive": "#808000",
+ "olivedrab": "#6b8e23",
+ "orange": "#ffa500",
+ "orangered": "#ff4500",
+ "orchid": "#da70d6",
+ "palegoldenrod": "#eee8aa",
+ "palegreen": "#98fb98",
+ "paleturquoise": "#afeeee",
+ "palevioletred": "#db7093",
+ "papayawhip": "#ffefd5",
+ "peachpuff": "#ffdab9",
+ "peru": "#cd853f",
+ "pink": "#ffc0cb",
+ "plum": "#dda0dd",
+ "powderblue": "#b0e0e6",
+ "purple": "#800080",
+ "red": "#ff0000",
+ "rosybrown": "#bc8f8f",
+ "royalblue": "#4169e1",
+ "saddlebrown": "#8b4513",
+ "salmon": "#fa8072",
+ "sandybrown": "#f4a460",
+ "seagreen": "#2e8b57",
+ "seashell": "#fff5ee",
+ "sienna": "#a0522d",
+ "silver": "#c0c0c0",
+ "skyblue": "#87ceeb",
+ "slateblue": "#6a5acd",
+ "slategray": "#708090",
+ "slategrey": "#708090",
+ "snow": "#fffafa",
+ "springgreen": "#00ff7f",
+ "steelblue": "#4682b4",
+ "tan": "#d2b48c",
+ "teal": "#008080",
+ "thistle": "#d8bfd8",
+ "tomato": "#ff6347",
+ "turquoise": "#40e0d0",
+ "violet": "#ee82ee",
+ "wheat": "#f5deb3",
+ "white": "#ffffff",
+ "whitesmoke": "#f5f5f5",
+ "yellow": "#ffff00",
+ "yellowgreen": "#9acd32",
+}
diff --git a/PIL/ImageDraw.py b/PIL/ImageDraw.py
new file mode 100644
index 0000000..7f9107b
--- /dev/null
+++ b/PIL/ImageDraw.py
@@ -0,0 +1,378 @@
+#
+# The Python Imaging Library
+# $Id: ImageDraw.py 2817 2006-10-07 15:34:03Z fredrik $
+#
+# drawing interface operations
+#
+# History:
+# 1996-04-13 fl Created (experimental)
+# 1996-08-07 fl Filled polygons, ellipses.
+# 1996-08-13 fl Added text support
+# 1998-06-28 fl Handle I and F images
+# 1998-12-29 fl Added arc; use arc primitive to draw ellipses
+# 1999-01-10 fl Added shape stuff (experimental)
+# 1999-02-06 fl Added bitmap support
+# 1999-02-11 fl Changed all primitives to take options
+# 1999-02-20 fl Fixed backwards compatibility
+# 2000-10-12 fl Copy on write, when necessary
+# 2001-02-18 fl Use default ink for bitmap/text also in fill mode
+# 2002-10-24 fl Added support for CSS-style color strings
+# 2002-12-10 fl Added experimental support for RGBA-on-RGB drawing
+# 2002-12-11 fl Refactored low-level drawing API (work in progress)
+# 2004-08-26 fl Made Draw() a factory function, added getdraw() support
+# 2004-09-04 fl Added width support to line primitive
+# 2004-09-10 fl Added font mode handling
+# 2006-06-19 fl Added font bearing support (getmask2)
+#
+# Copyright (c) 1997-2006 by Secret Labs AB
+# Copyright (c) 1996-2006 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image, ImageColor
+
+try:
+ import warnings
+except ImportError:
+ warnings = None
+
+##
+# A simple 2D drawing interface for PIL images.
+# <p>
+# Application code should use the <b>Draw</b> factory, instead of
+# directly.
+
+class ImageDraw:
+
+ ##
+ # Create a drawing instance.
+ #
+ # @param im The image to draw in.
+ # @param mode Optional mode to use for color values. For RGB
+ # images, this argument can be RGB or RGBA (to blend the
+ # drawing into the image). For all other modes, this argument
+ # must be the same as the image mode. If omitted, the mode
+ # defaults to the mode of the image.
+
+ def __init__(self, im, mode=None):
+ im.load()
+ if im.readonly:
+ im._copy() # make it writable
+ blend = 0
+ if mode is None:
+ mode = im.mode
+ if mode != im.mode:
+ if mode == "RGBA" and im.mode == "RGB":
+ blend = 1
+ else:
+ raise ValueError("mode mismatch")
+ if mode == "P":
+ self.palette = im.palette
+ else:
+ self.palette = None
+ self.im = im.im
+ self.draw = Image.core.draw(self.im, blend)
+ self.mode = mode
+ if mode in ("I", "F"):
+ self.ink = self.draw.draw_ink(1, mode)
+ else:
+ self.ink = self.draw.draw_ink(-1, mode)
+ if mode in ("1", "P", "I", "F"):
+ # FIXME: fix Fill2 to properly support matte for I+F images
+ self.fontmode = "1"
+ else:
+ self.fontmode = "L" # aliasing is okay for other modes
+ self.fill = 0
+ self.font = None
+
+ ##
+ # Set the default pen color.
+
+ def setink(self, ink):
+ # compatibility
+ if warnings:
+ warnings.warn(
+ "'setink' is deprecated; use keyword arguments instead",
+ DeprecationWarning, stacklevel=2
+ )
+ if Image.isStringType(ink):
+ ink = ImageColor.getcolor(ink, self.mode)
+ if self.palette and not Image.isNumberType(ink):
+ ink = self.palette.getcolor(ink)
+ self.ink = self.draw.draw_ink(ink, self.mode)
+
+ ##
+ # Set the default background color.
+
+ def setfill(self, onoff):
+ # compatibility
+ if warnings:
+ warnings.warn(
+ "'setfill' is deprecated; use keyword arguments instead",
+ DeprecationWarning, stacklevel=2
+ )
+ self.fill = onoff
+
+ ##
+ # Set the default font.
+
+ def setfont(self, font):
+ # compatibility
+ self.font = font
+
+ ##
+ # Get the current default font.
+
+ def getfont(self):
+ if not self.font:
+ # FIXME: should add a font repository
+ import ImageFont
+ self.font = ImageFont.load_default()
+ return self.font
+
+ def _getink(self, ink, fill=None):
+ if ink is None and fill is None:
+ if self.fill:
+ fill = self.ink
+ else:
+ ink = self.ink
+ else:
+ if ink is not None:
+ if Image.isStringType(ink):
+ ink = ImageColor.getcolor(ink, self.mode)
+ if self.palette and not Image.isNumberType(ink):
+ ink = self.palette.getcolor(ink)
+ ink = self.draw.draw_ink(ink, self.mode)
+ if fill is not None:
+ if Image.isStringType(fill):
+ fill = ImageColor.getcolor(fill, self.mode)
+ if self.palette and not Image.isNumberType(fill):
+ fill = self.palette.getcolor(fill)
+ fill = self.draw.draw_ink(fill, self.mode)
+ return ink, fill
+
+ ##
+ # Draw an arc.
+
+ def arc(self, xy, start, end, fill=None):
+ ink, fill = self._getink(fill)
+ if ink is not None:
+ self.draw.draw_arc(xy, start, end, ink)
+
+ ##
+ # Draw a bitmap.
+
+ def bitmap(self, xy, bitmap, fill=None):
+ bitmap.load()
+ ink, fill = self._getink(fill)
+ if ink is None:
+ ink = fill
+ if ink is not None:
+ self.draw.draw_bitmap(xy, bitmap.im, ink)
+
+ ##
+ # Draw a chord.
+
+ def chord(self, xy, start, end, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_chord(xy, start, end, fill, 1)
+ if ink is not None:
+ self.draw.draw_chord(xy, start, end, ink, 0)
+
+ ##
+ # Draw an ellipse.
+
+ def ellipse(self, xy, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_ellipse(xy, fill, 1)
+ if ink is not None:
+ self.draw.draw_ellipse(xy, ink, 0)
+
+ ##
+ # Draw a line, or a connected sequence of line segments.
+
+ def line(self, xy, fill=None, width=0):
+ ink, fill = self._getink(fill)
+ if ink is not None:
+ self.draw.draw_lines(xy, ink, width)
+
+ ##
+ # (Experimental) Draw a shape.
+
+ def shape(self, shape, fill=None, outline=None):
+ # experimental
+ shape.close()
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_outline(shape, fill, 1)
+ if ink is not None:
+ self.draw.draw_outline(shape, ink, 0)
+
+ ##
+ # Draw a pieslice.
+
+ def pieslice(self, xy, start, end, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_pieslice(xy, start, end, fill, 1)
+ if ink is not None:
+ self.draw.draw_pieslice(xy, start, end, ink, 0)
+
+ ##
+ # Draw one or more individual pixels.
+
+ def point(self, xy, fill=None):
+ ink, fill = self._getink(fill)
+ if ink is not None:
+ self.draw.draw_points(xy, ink)
+
+ ##
+ # Draw a polygon.
+
+ def polygon(self, xy, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_polygon(xy, fill, 1)
+ if ink is not None:
+ self.draw.draw_polygon(xy, ink, 0)
+
+ ##
+ # Draw a rectangle.
+
+ def rectangle(self, xy, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_rectangle(xy, fill, 1)
+ if ink is not None:
+ self.draw.draw_rectangle(xy, ink, 0)
+
+ ##
+ # Draw text.
+
+ def text(self, xy, text, fill=None, font=None, anchor=None):
+ ink, fill = self._getink(fill)
+ if font is None:
+ font = self.getfont()
+ if ink is None:
+ ink = fill
+ if ink is not None:
+ try:
+ mask, offset = font.getmask2(text, self.fontmode)
+ xy = xy[0] + offset[0], xy[1] + offset[1]
+ except AttributeError:
+ try:
+ mask = font.getmask(text, self.fontmode)
+ except TypeError:
+ mask = font.getmask(text)
+ self.draw.draw_bitmap(xy, mask, ink)
+
+ ##
+ # Get the size of a given string, in pixels.
+
+ def textsize(self, text, font=None):
+ if font is None:
+ font = self.getfont()
+ return font.getsize(text)
+
+##
+# A simple 2D drawing interface for PIL images.
+#
+# @param im The image to draw in.
+# @param mode Optional mode to use for color values. For RGB
+# images, this argument can be RGB or RGBA (to blend the
+# drawing into the image). For all other modes, this argument
+# must be the same as the image mode. If omitted, the mode
+# defaults to the mode of the image.
+
+def Draw(im, mode=None):
+ try:
+ return im.getdraw(mode)
+ except AttributeError:
+ return ImageDraw(im, mode)
+
+# experimental access to the outline API
+try:
+ Outline = Image.core.outline
+except:
+ Outline = None
+
+##
+# (Experimental) A more advanced 2D drawing interface for PIL images,
+# based on the WCK interface.
+#
+# @param im The image to draw in.
+# @param hints An optional list of hints.
+# @return A (drawing context, drawing resource factory) tuple.
+
+def getdraw(im=None, hints=None):
+ # FIXME: this needs more work!
+ # FIXME: come up with a better 'hints' scheme.
+ handler = None
+ if not hints or "nicest" in hints:
+ try:
+ import _imagingagg
+ handler = _imagingagg
+ except ImportError:
+ pass
+ if handler is None:
+ import ImageDraw2
+ handler = ImageDraw2
+ if im:
+ im = handler.Draw(im)
+ return im, handler
+
+##
+# (experimental) Fills a bounded region with a given color.
+#
+# @param image Target image.
+# @param xy Seed position (a 2-item coordinate tuple).
+# @param value Fill color.
+# @param border Optional border value. If given, the region consists of
+# pixels with a color different from the border color. If not given,
+# the region consists of pixels having the same color as the seed
+# pixel.
+
+def floodfill(image, xy, value, border=None):
+ "Fill bounded region."
+ # based on an implementation by Eric S. Raymond
+ pixel = image.load()
+ x, y = xy
+ try:
+ background = pixel[x, y]
+ if background == value:
+ return # seed point already has fill color
+ pixel[x, y] = value
+ except IndexError:
+ return # seed point outside image
+ edge = [(x, y)]
+ if border is None:
+ while edge:
+ newedge = []
+ for (x, y) in edge:
+ for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
+ try:
+ p = pixel[s, t]
+ except IndexError:
+ pass
+ else:
+ if p == background:
+ pixel[s, t] = value
+ newedge.append((s, t))
+ edge = newedge
+ else:
+ while edge:
+ newedge = []
+ for (x, y) in edge:
+ for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
+ try:
+ p = pixel[s, t]
+ except IndexError:
+ pass
+ else:
+ if p != value and p != border:
+ pixel[s, t] = value
+ newedge.append((s, t))
+ edge = newedge
diff --git a/PIL/ImageDraw2.py b/PIL/ImageDraw2.py
new file mode 100644
index 0000000..65c8cbb
--- /dev/null
+++ b/PIL/ImageDraw2.py
@@ -0,0 +1,108 @@
+#
+# The Python Imaging Library
+# $Id: ImageDraw.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# WCK-style drawing interface operations
+#
+# History:
+# 2003-12-07 fl created
+# 2005-05-15 fl updated; added to PIL as ImageDraw2
+# 2005-05-15 fl added text support
+# 2005-05-20 fl added arc/chord/pieslice support
+#
+# Copyright (c) 2003-2005 by Secret Labs AB
+# Copyright (c) 2003-2005 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image, ImageColor, ImageDraw, ImageFont, ImagePath
+
+class Pen:
+ def __init__(self, color, width=1, opacity=255):
+ self.color = ImageColor.getrgb(color)
+ self.width = width
+
+class Brush:
+ def __init__(self, color, opacity=255):
+ self.color = ImageColor.getrgb(color)
+
+class Font:
+ def __init__(self, color, file, size=12):
+ # FIXME: add support for bitmap fonts
+ self.color = ImageColor.getrgb(color)
+ self.font = ImageFont.truetype(file, size)
+
+class Draw:
+
+ def __init__(self, image, size=None, color=None):
+ if not hasattr(image, "im"):
+ image = Image.new(image, size, color)
+ self.draw = ImageDraw.Draw(image)
+ self.image = image
+ self.transform = None
+
+ def flush(self):
+ return self.image
+
+ def render(self, op, xy, pen, brush=None):
+ # handle color arguments
+ outline = fill = None; width = 1
+ if isinstance(pen, Pen):
+ outline = pen.color
+ width = pen.width
+ elif isinstance(brush, Pen):
+ outline = brush.color
+ width = brush.width
+ if isinstance(brush, Brush):
+ fill = brush.color
+ elif isinstance(pen, Brush):
+ fill = pen.color
+ # handle transformation
+ if self.transform:
+ xy = ImagePath.Path(xy)
+ xy.transform(self.transform)
+ # render the item
+ if op == "line":
+ self.draw.line(xy, fill=outline, width=width)
+ else:
+ getattr(self.draw, op)(xy, fill=fill, outline=outline)
+
+ def settransform(self, (xoffset, yoffset)):
+ self.transform = (1, 0, xoffset, 0, 1, yoffset)
+
+ def arc(self, xy, start, end, *options):
+ self.render("arc", xy, start, end, *options)
+
+ def chord(self, xy, start, end, *options):
+ self.render("chord", xy, start, end, *options)
+
+ def ellipse(self, xy, *options):
+ self.render("ellipse", xy, *options)
+
+ def pieslice(self, xy, start, end, *options):
+ self.render("pieslice", xy, start, end, *options)
+
+ def line(self, xy, *options):
+ self.render("line", xy, *options)
+
+ def rectangle(self, xy, *options):
+ self.render("rectangle", xy, *options)
+
+ def ellipse(self, xy, *options):
+ self.render("ellipse", xy, *options)
+
+ def polygon(self, xy, *options):
+ self.render("polygon", xy, *options)
+
+ def symbol(self, xy, symbol, *options):
+ raise NotImplementedError("not in this version")
+
+ def text(self, xy, text, font):
+ if self.transform:
+ xy = ImagePath.Path(xy)
+ xy.transform(self.transform)
+ self.draw.text(xy, text, font=font.font, fill=font.color)
+
+ def textsize(self, text, font):
+ return self.draw.textsize(text, font=font.font)
diff --git a/PIL/ImageEnhance.py b/PIL/ImageEnhance.py
new file mode 100644
index 0000000..cddd084
--- /dev/null
+++ b/PIL/ImageEnhance.py
@@ -0,0 +1,89 @@
+#
+# The Python Imaging Library.
+# $Id: ImageEnhance.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# image enhancement classes
+#
+# For a background, see "Image Processing By Interpolation and
+# Extrapolation", Paul Haeberli and Douglas Voorhies. Available
+# at http://www.sgi.com/grafica/interp/index.html
+#
+# History:
+# 96-03-23 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image, ImageFilter
+
+class _Enhance:
+
+ ##
+ # Returns an enhanced image. The enhancement factor is a floating
+ # point value controlling the enhancement. Factor 1.0 always
+ # returns a copy of the original image, lower factors mean less
+ # colour (brightness, contrast, etc), and higher values more.
+ # There are no restrictions on this value.
+ #
+ # @param factor Enhancement factor.
+ # @return An enhanced image.
+
+ def enhance(self, factor):
+ return Image.blend(self.degenerate, self.image, factor)
+
+##
+# Color enhancement object.
+# <p>
+# This class can be used to adjust the colour balance of an image, in
+# a manner similar to the controls on a colour TV set. An enhancement
+# factor of 0.0 gives a black and white image, a factor of 1.0 gives
+# the original image.
+
+class Color(_Enhance):
+ "Adjust image colour balance"
+ def __init__(self, image):
+ self.image = image
+ self.degenerate = image.convert("L").convert(image.mode)
+
+##
+# Contrast enhancement object.
+# <p>
+# This class can be used to control the contrast of an image, similar
+# to the contrast control on a TV set. An enhancement factor of 0.0
+# gives an solid grey image, factor 1.0 gives the original image.
+
+class Contrast(_Enhance):
+ "Adjust image contrast"
+ def __init__(self, image):
+ self.image = image
+ mean = reduce(lambda a,b: a+b, image.convert("L").histogram())/256.0
+ self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
+
+##
+# Brightness enhancement object.
+# <p>
+# This class can be used to control the brighntess of an image. An
+# enhancement factor of 0.0 gives a black image, factor 1.0 gives the
+# original image.
+
+class Brightness(_Enhance):
+ "Adjust image brightness"
+ def __init__(self, image):
+ self.image = image
+ self.degenerate = Image.new(image.mode, image.size, 0)
+
+##
+# Sharpness enhancement object.
+# <p>
+# This class can be used to adjust the sharpness of an image. The
+# enhancement factor 0.0 gives a blurred image, 1.0 gives the original
+# image, and a factor of 2.0 gives a sharpened image.
+
+class Sharpness(_Enhance):
+ "Adjust image sharpness"
+ def __init__(self, image):
+ self.image = image
+ self.degenerate = image.filter(ImageFilter.SMOOTH)
diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py
new file mode 100644
index 0000000..d51aff3
--- /dev/null
+++ b/PIL/ImageFile.py
@@ -0,0 +1,520 @@
+#
+# The Python Imaging Library.
+# $Id: ImageFile.py 2930 2006-12-02 13:50:40Z fredrik $
+#
+# base class for image file handlers
+#
+# history:
+# 1995-09-09 fl Created
+# 1996-03-11 fl Fixed load mechanism.
+# 1996-04-15 fl Added pcx/xbm decoders.
+# 1996-04-30 fl Added encoders.
+# 1996-12-14 fl Added load helpers
+# 1997-01-11 fl Use encode_to_file where possible
+# 1997-08-27 fl Flush output in _save
+# 1998-03-05 fl Use memory mapping for some modes
+# 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B"
+# 1999-05-31 fl Added image parser
+# 2000-10-12 fl Set readonly flag on memory-mapped images
+# 2002-03-20 fl Use better messages for common decoder errors
+# 2003-04-21 fl Fall back on mmap/map_buffer if map is not available
+# 2003-10-30 fl Added StubImageFile class
+# 2004-02-25 fl Made incremental parser more robust
+#
+# Copyright (c) 1997-2004 by Secret Labs AB
+# Copyright (c) 1995-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+import traceback, sys, string, os
+
+MAXBLOCK = 65536
+
+SAFEBLOCK = 1024*1024
+
+ERRORS = {
+ -1: "image buffer overrun error",
+ -2: "decoding error",
+ -3: "unknown error",
+ -8: "bad configuration",
+ -9: "out of memory error"
+}
+
+#
+# --------------------------------------------------------------------
+# Helpers
+
+def _tilesort(t1, t2):
+ # sort on offset
+ return cmp(t1[2], t2[2])
+
+#
+# --------------------------------------------------------------------
+# ImageFile base class
+
+##
+# Base class for image file handlers.
+
+class ImageFile(Image.Image):
+ "Base class for image file format handlers."
+
+ def __init__(self, fp=None, filename=None):
+ Image.Image.__init__(self)
+
+ self.tile = None
+ self.readonly = 1 # until we know better
+
+ self.decoderconfig = ()
+ self.decodermaxblock = MAXBLOCK
+
+ if Image.isStringType(fp):
+ # filename
+ self.fp = open(fp, "rb")
+ self.filename = fp
+ else:
+ # stream
+ self.fp = fp
+ self.filename = filename
+
+ try:
+ self._open()
+ except IndexError, v: # end of data
+ if Image.DEBUG > 1:
+ traceback.print_exc()
+ raise SyntaxError, v
+ except TypeError, v: # end of data (ord)
+ if Image.DEBUG > 1:
+ traceback.print_exc()
+ raise SyntaxError, v
+ except KeyError, v: # unsupported mode
+ if Image.DEBUG > 1:
+ traceback.print_exc()
+ raise SyntaxError, v
+ except EOFError, v: # got header but not the first frame
+ if Image.DEBUG > 1:
+ traceback.print_exc()
+ raise SyntaxError, v
+
+ if not self.mode or self.size[0] <= 0:
+ raise SyntaxError, "not identified by this driver"
+
+ def draft(self, mode, size):
+ "Set draft mode"
+
+ pass
+
+ def verify(self):
+ "Check file integrity"
+
+ # raise exception if something's wrong. must be called
+ # directly after open, and closes file when finished.
+ self.fp = None
+
+ def load(self):
+ "Load image data based on tile list"
+
+ pixel = Image.Image.load(self)
+
+ if self.tile is None:
+ raise IOError("cannot load this image")
+ if not self.tile:
+ return pixel
+
+ self.map = None
+
+ readonly = 0
+
+ if self.filename and len(self.tile) == 1:
+ # try memory mapping
+ d, e, o, a = self.tile[0]
+ if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
+ try:
+ if hasattr(Image.core, "map"):
+ # use built-in mapper
+ self.map = Image.core.map(self.filename)
+ self.map.seek(o)
+ self.im = self.map.readimage(
+ self.mode, self.size, a[1], a[2]
+ )
+ else:
+ # use mmap, if possible
+ import mmap
+ file = open(self.filename, "r+")
+ size = os.path.getsize(self.filename)
+ # FIXME: on Unix, use PROT_READ etc
+ self.map = mmap.mmap(file.fileno(), size)
+ self.im = Image.core.map_buffer(
+ self.map, self.size, d, e, o, a
+ )
+ readonly = 1
+ except (AttributeError, EnvironmentError, ImportError):
+ self.map = None
+
+ self.load_prepare()
+
+ # look for read/seek overrides
+ try:
+ read = self.load_read
+ except AttributeError:
+ read = self.fp.read
+
+ try:
+ seek = self.load_seek
+ except AttributeError:
+ seek = self.fp.seek
+
+ if not self.map:
+
+ # sort tiles in file order
+ self.tile.sort(_tilesort)
+
+ try:
+ # FIXME: This is a hack to handle TIFF's JpegTables tag.
+ prefix = self.tile_prefix
+ except AttributeError:
+ prefix = ""
+
+ for d, e, o, a in self.tile:
+ d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
+ seek(o)
+ try:
+ d.setimage(self.im, e)
+ except ValueError:
+ continue
+ b = prefix
+ t = len(b)
+ while 1:
+ s = read(self.decodermaxblock)
+ if not s:
+ self.tile = []
+ raise IOError("image file is truncated (%d bytes not processed)" % len(b))
+ b = b + s
+ n, e = d.decode(b)
+ if n < 0:
+ break
+ b = b[n:]
+ t = t + n
+
+ self.tile = []
+ self.readonly = readonly
+
+ self.fp = None # might be shared
+
+ if not self.map and e < 0:
+ error = ERRORS.get(e, "decoder error %d" % e)
+ raise IOError(error + " when reading image file")
+
+ # post processing
+ if hasattr(self, "tile_post_rotate"):
+ # FIXME: This is a hack to handle rotated PCD's
+ self.im = self.im.rotate(self.tile_post_rotate)
+ self.size = self.im.size
+
+ self.load_end()
+
+ return Image.Image.load(self)
+
+ def load_prepare(self):
+ # create image memory if necessary
+ if not self.im or\
+ self.im.mode != self.mode or self.im.size != self.size:
+ self.im = Image.core.new(self.mode, self.size)
+ # create palette (optional)
+ if self.mode == "P":
+ Image.Image.load(self)
+
+ def load_end(self):
+ # may be overridden
+ pass
+
+ # may be defined for contained formats
+ # def load_seek(self, pos):
+ # pass
+
+ # may be defined for blocked formats (e.g. PNG)
+ # def load_read(self, bytes):
+ # pass
+
+##
+# Base class for stub image loaders.
+# <p>
+# A stub loader is an image loader that can identify files of a
+# certain format, but relies on external code to load the file.
+
+class StubImageFile(ImageFile):
+ "Base class for stub image loaders."
+
+ def _open(self):
+ raise NotImplementedError(
+ "StubImageFile subclass must implement _open"
+ )
+
+ def load(self):
+ loader = self._load()
+ if loader is None:
+ raise IOError("cannot find loader for this %s file" % self.format)
+ image = loader.load(self)
+ assert image is not None
+ # become the other object (!)
+ self.__class__ = image.__class__
+ self.__dict__ = image.__dict__
+
+ ##
+ # (Hook) Find actual image loader.
+
+ def _load(self):
+ raise NotImplementedError(
+ "StubImageFile subclass must implement _load"
+ )
+
+##
+# (Internal) Support class for the <b>Parser</b> file.
+
+class _ParserFile:
+ # parser support class.
+
+ def __init__(self, data):
+ self.data = data
+ self.offset = 0
+
+ def close(self):
+ self.data = self.offset = None
+
+ def tell(self):
+ return self.offset
+
+ def seek(self, offset, whence=0):
+ if whence == 0:
+ self.offset = offset
+ elif whence == 1:
+ self.offset = self.offset + offset
+ else:
+ # force error in Image.open
+ raise IOError("illegal argument to seek")
+
+ def read(self, bytes=0):
+ pos = self.offset
+ if bytes:
+ data = self.data[pos:pos+bytes]
+ else:
+ data = self.data[pos:]
+ self.offset = pos + len(data)
+ return data
+
+ def readline(self):
+ # FIXME: this is slow!
+ s = ""
+ while 1:
+ c = self.read(1)
+ if not c:
+ break
+ s = s + c
+ if c == "\n":
+ break
+ return s
+
+##
+# Incremental image parser. This class implements the standard
+# feed/close consumer interface.
+
+class Parser:
+
+ incremental = None
+ image = None
+ data = None
+ decoder = None
+ finished = 0
+
+ ##
+ # (Consumer) Reset the parser. Note that you can only call this
+ # method immediately after you've created a parser; parser
+ # instances cannot be reused.
+
+ def reset(self):
+ assert self.data is None, "cannot reuse parsers"
+
+ ##
+ # (Consumer) Feed data to the parser.
+ #
+ # @param data A string buffer.
+ # @exception IOError If the parser failed to parse the image file.
+
+ def feed(self, data):
+ # collect data
+
+ if self.finished:
+ return
+
+ if self.data is None:
+ self.data = data
+ else:
+ self.data = self.data + data
+
+ # parse what we have
+ if self.decoder:
+
+ if self.offset > 0:
+ # skip header
+ skip = min(len(self.data), self.offset)
+ self.data = self.data[skip:]
+ self.offset = self.offset - skip
+ if self.offset > 0 or not self.data:
+ return
+
+ n, e = self.decoder.decode(self.data)
+
+ if n < 0:
+ # end of stream
+ self.data = None
+ self.finished = 1
+ if e < 0:
+ # decoding error
+ self.image = None
+ error = ERRORS.get(e, "decoder error %d" % e)
+ raise IOError(error + " when reading image file")
+ else:
+ # end of image
+ return
+ self.data = self.data[n:]
+
+ elif self.image:
+
+ # if we end up here with no decoder, this file cannot
+ # be incrementally parsed. wait until we've gotten all
+ # available data
+ pass
+
+ else:
+
+ # attempt to open this file
+ try:
+ try:
+ fp = _ParserFile(self.data)
+ im = Image.open(fp)
+ finally:
+ fp.close() # explicitly close the virtual file
+ except IOError:
+ pass # not enough data
+ else:
+ flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
+ if flag or len(im.tile) != 1:
+ # custom load code, or multiple tiles
+ self.decode = None
+ else:
+ # initialize decoder
+ im.load_prepare()
+ d, e, o, a = im.tile[0]
+ im.tile = []
+ self.decoder = Image._getdecoder(
+ im.mode, d, a, im.decoderconfig
+ )
+ self.decoder.setimage(im.im, e)
+
+ # calculate decoder offset
+ self.offset = o
+ if self.offset <= len(self.data):
+ self.data = self.data[self.offset:]
+ self.offset = 0
+
+ self.image = im
+
+ ##
+ # (Consumer) Close the stream.
+ #
+ # @return An image object.
+ # @exception IOError If the parser failed to parse the image file.
+
+ def close(self):
+ # finish decoding
+ if self.decoder:
+ # get rid of what's left in the buffers
+ self.feed("")
+ self.data = self.decoder = None
+ if not self.finished:
+ raise IOError("image was incomplete")
+ if not self.image:
+ raise IOError("cannot parse this image")
+ if self.data:
+ # incremental parsing not possible; reopen the file
+ # not that we have all data
+ try:
+ fp = _ParserFile(self.data)
+ self.image = Image.open(fp)
+ finally:
+ fp.close() # explicitly close the virtual file
+ return self.image
+
+# --------------------------------------------------------------------
+
+##
+# (Helper) Save image body to file.
+#
+# @param im Image object.
+# @param fp File object.
+# @param tile Tile list.
+
+def _save(im, fp, tile):
+ "Helper to save image based on tile list"
+
+ im.load()
+ if not hasattr(im, "encoderconfig"):
+ im.encoderconfig = ()
+ tile.sort(_tilesort)
+ # FIXME: make MAXBLOCK a configuration parameter
+ bufsize = max(MAXBLOCK, im.size[0] * 4) # see RawEncode.c
+ try:
+ fh = fp.fileno()
+ fp.flush()
+ except AttributeError:
+ # compress to Python file-compatible object
+ for e, b, o, a in tile:
+ e = Image._getencoder(im.mode, e, a, im.encoderconfig)
+ if o > 0:
+ fp.seek(o, 0)
+ e.setimage(im.im, b)
+ while 1:
+ l, s, d = e.encode(bufsize)
+ fp.write(d)
+ if s:
+ break
+ if s < 0:
+ raise IOError("encoder error %d when writing image file" % s)
+ else:
+ # slight speedup: compress to real file object
+ for e, b, o, a in tile:
+ e = Image._getencoder(im.mode, e, a, im.encoderconfig)
+ if o > 0:
+ fp.seek(o, 0)
+ e.setimage(im.im, b)
+ s = e.encode_to_file(fh, bufsize)
+ if s < 0:
+ raise IOError("encoder error %d when writing image file" % s)
+ try:
+ fp.flush()
+ except: pass
+
+
+##
+# Reads large blocks in a safe way. Unlike fp.read(n), this function
+# doesn't trust the user. If the requested size is larger than
+# SAFEBLOCK, the file is read block by block.
+#
+# @param fp File handle. Must implement a <b>read</b> method.
+# @param size Number of bytes to read.
+# @return A string containing up to <i>size</i> bytes of data.
+
+def _safe_read(fp, size):
+ if size <= 0:
+ return ""
+ if size <= SAFEBLOCK:
+ return fp.read(size)
+ data = []
+ while size > 0:
+ block = fp.read(min(size, SAFEBLOCK))
+ if not block:
+ break
+ data.append(block)
+ size = size - len(block)
+ return string.join(data, "")
diff --git a/PIL/ImageFileIO.py b/PIL/ImageFileIO.py
new file mode 100644
index 0000000..7c6a5c1
--- /dev/null
+++ b/PIL/ImageFileIO.py
@@ -0,0 +1,47 @@
+#
+# The Python Imaging Library.
+# $Id: ImageFileIO.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# kludge to get basic ImageFileIO functionality
+#
+# History:
+# 1998-08-06 fl Recreated
+#
+# Copyright (c) Secret Labs AB 1998-2002.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from StringIO import StringIO
+
+##
+# The <b>ImageFileIO</b> module can be used to read an image from a
+# socket, or any other stream device.
+# <p>
+# This module is deprecated. New code should use the <b>Parser</b>
+# class in the <a href="imagefile">ImageFile</a> module instead.
+#
+# @see ImageFile#Parser
+
+class ImageFileIO(StringIO):
+
+ ##
+ # Adds buffering to a stream file object, in order to
+ # provide <b>seek</b> and <b>tell</b> methods required
+ # by the <b>Image.open</b> method. The stream object must
+ # implement <b>read</b> and <b>close</b> methods.
+ #
+ # @param fp Stream file handle.
+ # @see Image#open
+
+ def __init__(self, fp):
+ data = fp.read()
+ StringIO.__init__(self, data)
+
+if __name__ == "__main__":
+
+ import Image
+ fp = open("/images/clenna.im", "rb")
+ im = Image.open(ImageFileIO(fp))
+ im.load() # make sure we can read the raster data
+ print im.mode, im.size
diff --git a/PIL/ImageFilter.py b/PIL/ImageFilter.py
new file mode 100644
index 0000000..5857686
--- /dev/null
+++ b/PIL/ImageFilter.py
@@ -0,0 +1,263 @@
+#
+# The Python Imaging Library.
+# $Id: ImageFilter.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# standard filters
+#
+# History:
+# 1995-11-27 fl Created
+# 2002-06-08 fl Added rank and mode filters
+# 2003-09-15 fl Fixed rank calculation in rank filter; added expand call
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2002 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+class Filter:
+ pass
+
+##
+# Convolution filter kernel.
+
+class Kernel(Filter):
+
+ ##
+ # Create a convolution kernel. The current version only
+ # supports 3x3 and 5x5 integer and floating point kernels.
+ # <p>
+ # In the current version, kernels can only be applied to
+ # "L" and "RGB" images.
+ #
+ # @def __init__(size, kernel, **options)
+ # @param size Kernel size, given as (width, height). In
+ # the current version, this must be (3,3) or (5,5).
+ # @param kernel A sequence containing kernel weights.
+ # @param **options Optional keyword arguments.
+ # @keyparam scale Scale factor. If given, the result for each
+ # pixel is divided by this value. The default is the sum
+ # of the kernel weights.
+ # @keyparam offset Offset. If given, this value is added to the
+ # result, after it has been divided by the scale factor.
+
+ def __init__(self, size, kernel, scale=None, offset=0):
+ if scale is None:
+ # default scale is sum of kernel
+ scale = reduce(lambda a,b: a+b, kernel)
+ if size[0] * size[1] != len(kernel):
+ raise ValueError("not enough coefficients in kernel")
+ self.filterargs = size, scale, offset, kernel
+
+ def filter(self, image):
+ if image.mode == "P":
+ raise ValueError("cannot filter palette images")
+ return apply(image.filter, self.filterargs)
+
+class BuiltinFilter(Kernel):
+ def __init__(self):
+ pass
+
+##
+# Rank filter.
+
+class RankFilter(Filter):
+ name = "Rank"
+
+ ##
+ # Create a rank filter. The rank filter sorts all pixels in
+ # a window of the given size, and returns the rank'th value.
+ #
+ # @param size The kernel size, in pixels.
+ # @param rank What pixel value to pick. Use 0 for a min filter,
+ # size*size/2 for a median filter, size*size-1 for a max filter,
+ # etc.
+
+ def __init__(self, size, rank):
+ self.size = size
+ self.rank = rank
+
+ def filter(self, image):
+ image = image.expand(self.size/2, self.size/2)
+ return image.rankfilter(self.size, self.rank)
+
+##
+# Median filter. Picks the median pixel value in a window with the
+# given size.
+
+class MedianFilter(RankFilter):
+ name = "Median"
+
+ ##
+ # Create a median filter.
+ #
+ # @param size The kernel size, in pixels.
+
+ def __init__(self, size=3):
+ self.size = size
+ self.rank = size*size/2
+
+##
+# Min filter. Picks the lowest pixel value in a window with the given
+# size.
+
+class MinFilter(RankFilter):
+ name = "Min"
+
+ ##
+ # Create a min filter.
+ #
+ # @param size The kernel size, in pixels.
+
+ def __init__(self, size=3):
+ self.size = size
+ self.rank = 0
+
+##
+# Max filter. Picks the largest pixel value in a window with the
+# given size.
+
+class MaxFilter(RankFilter):
+ name = "Max"
+
+ ##
+ # Create a max filter.
+ #
+ # @param size The kernel size, in pixels.
+
+ def __init__(self, size=3):
+ self.size = size
+ self.rank = size*size-1
+
+##
+# Mode filter. Picks the most frequent pixel value in a box with the
+# given size. Pixel values that occur only once or twice are ignored;
+# if no pixel value occurs more than twice, the original pixel value
+# is preserved.
+
+class ModeFilter(Filter):
+ name = "Mode"
+
+ ##
+ # Create a mode filter.
+ #
+ # @param size The kernel size, in pixels.
+
+ def __init__(self, size=3):
+ self.size = size
+ def filter(self, image):
+ return image.modefilter(self.size)
+
+##
+# Blur filter.
+
+class BLUR(BuiltinFilter):
+ name = "Blur"
+ filterargs = (5, 5), 16, 0, (
+ 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 1,
+ 1, 0, 0, 0, 1,
+ 1, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1
+ )
+
+##
+# Contour filter.
+
+class CONTOUR(BuiltinFilter):
+ name = "Contour"
+ filterargs = (3, 3), 1, 255, (
+ -1, -1, -1,
+ -1, 8, -1,
+ -1, -1, -1
+ )
+
+##
+# Detail filter.
+
+class DETAIL(BuiltinFilter):
+ name = "Detail"
+ filterargs = (3, 3), 6, 0, (
+ 0, -1, 0,
+ -1, 10, -1,
+ 0, -1, 0
+ )
+
+##
+# Edge enhancement filter.
+
+class EDGE_ENHANCE(BuiltinFilter):
+ name = "Edge-enhance"
+ filterargs = (3, 3), 2, 0, (
+ -1, -1, -1,
+ -1, 10, -1,
+ -1, -1, -1
+ )
+
+##
+# Stronger edge enhancement filter.
+
+class EDGE_ENHANCE_MORE(BuiltinFilter):
+ name = "Edge-enhance More"
+ filterargs = (3, 3), 1, 0, (
+ -1, -1, -1,
+ -1, 9, -1,
+ -1, -1, -1
+ )
+
+##
+# Embossing filter.
+
+class EMBOSS(BuiltinFilter):
+ name = "Emboss"
+ filterargs = (3, 3), 1, 128, (
+ -1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 0
+ )
+
+##
+# Edge-finding filter.
+
+class FIND_EDGES(BuiltinFilter):
+ name = "Find Edges"
+ filterargs = (3, 3), 1, 0, (
+ -1, -1, -1,
+ -1, 8, -1,
+ -1, -1, -1
+ )
+
+##
+# Smoothing filter.
+
+class SMOOTH(BuiltinFilter):
+ name = "Smooth"
+ filterargs = (3, 3), 13, 0, (
+ 1, 1, 1,
+ 1, 5, 1,
+ 1, 1, 1
+ )
+
+##
+# Stronger smoothing filter.
+
+class SMOOTH_MORE(BuiltinFilter):
+ name = "Smooth More"
+ filterargs = (5, 5), 100, 0, (
+ 1, 1, 1, 1, 1,
+ 1, 5, 5, 5, 1,
+ 1, 5, 44, 5, 1,
+ 1, 5, 5, 5, 1,
+ 1, 1, 1, 1, 1
+ )
+
+##
+# Sharpening filter.
+
+class SHARPEN(BuiltinFilter):
+ name = "Sharpen"
+ filterargs = (3, 3), 16, 0, (
+ -2, -2, -2,
+ -2, 32, -2,
+ -2, -2, -2
+ )
diff --git a/PIL/ImageFont.py b/PIL/ImageFont.py
new file mode 100644
index 0000000..b987a33
--- /dev/null
+++ b/PIL/ImageFont.py
@@ -0,0 +1,377 @@
+#
+# The Python Imaging Library.
+# $Id: ImageFont.py 2813 2006-10-07 10:11:35Z fredrik $
+#
+# PIL raster font management
+#
+# History:
+# 1996-08-07 fl created (experimental)
+# 1997-08-25 fl minor adjustments to handle fonts from pilfont 0.3
+# 1999-02-06 fl rewrote most font management stuff in C
+# 1999-03-17 fl take pth files into account in load_path (from Richard Jones)
+# 2001-02-17 fl added freetype support
+# 2001-05-09 fl added TransposedFont wrapper class
+# 2002-03-04 fl make sure we have a "L" or "1" font
+# 2002-12-04 fl skip non-directory entries in the system path
+# 2003-04-29 fl add embedded default font
+# 2003-09-27 fl added support for truetype charmap encodings
+#
+# Todo:
+# Adapt to PILFONT2 format (16-bit fonts, compressed, single file)
+#
+# Copyright (c) 1997-2003 by Secret Labs AB
+# Copyright (c) 1996-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+import os, string, sys
+
+# FIXME: add support for pilfont2 format (see FontFile.py)
+
+# --------------------------------------------------------------------
+# Font metrics format:
+# "PILfont" LF
+# fontdescriptor LF
+# (optional) key=value... LF
+# "DATA" LF
+# binary data: 256*10*2 bytes (dx, dy, dstbox, srcbox)
+#
+# To place a character, cut out srcbox and paste at dstbox,
+# relative to the character position. Then move the character
+# position according to dx, dy.
+# --------------------------------------------------------------------
+
+##
+# The <b>ImageFont</b> module defines a class with the same name.
+# Instances of this class store bitmap fonts, and are used with the
+# <b>text</b> method of the <b>ImageDraw</b> class.
+# <p>
+# PIL uses it's own font file format to store bitmap fonts. You can
+# use the <b>pilfont</b> utility to convert BDF and PCF font
+# descriptors (X window font formats) to this format.
+# <p>
+# Starting with version 1.1.4, PIL can be configured to support
+# TrueType and OpenType fonts. For earlier version, TrueType
+# support is only available as part of the imToolkit package
+#
+# @see ImageDraw#ImageDraw.text
+# @see pilfont
+
+class ImageFont:
+ "PIL font wrapper"
+
+ def _load_pilfont(self, filename):
+
+ file = open(filename, "rb")
+
+ for ext in (".png", ".gif", ".pbm"):
+ try:
+ fullname = os.path.splitext(filename)[0] + ext
+ image = Image.open(fullname)
+ except:
+ pass
+ else:
+ if image and image.mode in ("1", "L"):
+ break
+ else:
+ raise IOError("cannot find glyph data file")
+
+ self.file = fullname
+
+ return self._load_pilfont_data(file, image)
+
+ def _load_pilfont_data(self, file, image):
+
+ # read PILfont header
+ if file.readline() != "PILfont\n":
+ raise SyntaxError("Not a PILfont file")
+ d = string.split(file.readline(), ";")
+ self.info = [] # FIXME: should be a dictionary
+ s = file.readline()
+ while s and s != "DATA\n":
+ self.info.append(s)
+
+ # read PILfont metrics
+ data = file.read(256*20)
+
+ # check image
+ if image.mode not in ("1", "L"):
+ raise TypeError("invalid font image mode")
+
+ image.load()
+
+ self.font = Image.core.font(image.im, data)
+
+ # delegate critical operations to internal type
+ self.getsize = self.font.getsize
+ self.getmask = self.font.getmask
+
+##
+# Wrapper for FreeType fonts. Application code should use the
+# <b>truetype</b> factory function to create font objects.
+
+class FreeTypeFont:
+ "FreeType font wrapper (requires _imagingft service)"
+
+ def __init__(self, file, size, index=0, encoding=""):
+ # FIXME: use service provider instead
+ import _imagingft
+ self.font = _imagingft.getfont(file, size, index, encoding)
+
+ def getname(self):
+ return self.font.family, self.font.style
+
+ def getmetrics(self):
+ return self.font.ascent, self.font.descent
+
+ def getsize(self, text):
+ return self.font.getsize(text)[0]
+
+ def getmask(self, text, mode=""):
+ return self.getmask2(text, mode)[0]
+
+ def getmask2(self, text, mode="", fill=Image.core.fill):
+ size, offset = self.font.getsize(text)
+ im = fill("L", size, 0)
+ self.font.render(text, im.id, mode=="1")
+ return im, offset
+
+##
+# Wrapper that creates a transposed font from any existing font
+# object.
+#
+# @param font A font object.
+# @param orientation An optional orientation. If given, this should
+# be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM,
+# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
+
+class TransposedFont:
+ "Wrapper for writing rotated or mirrored text"
+
+ def __init__(self, font, orientation=None):
+ self.font = font
+ self.orientation = orientation # any 'transpose' argument, or None
+
+ def getsize(self, text):
+ w, h = self.font.getsize(text)
+ if self.orientation in (Image.ROTATE_90, Image.ROTATE_270):
+ return h, w
+ return w, h
+
+ def getmask(self, text, mode=""):
+ im = self.font.getmask(text, mode)
+ if self.orientation is not None:
+ return im.transpose(self.orientation)
+ return im
+
+##
+# Load font file. This function loads a font object from the given
+# bitmap font file, and returns the corresponding font object.
+#
+# @param filename Name of font file.
+# @return A font object.
+# @exception IOError If the file could not be read.
+
+def load(filename):
+ "Load a font file."
+ f = ImageFont()
+ f._load_pilfont(filename)
+ return f
+
+##
+# Load a TrueType or OpenType font file, and create a font object.
+# This function loads a font object from the given file, and creates
+# a font object for a font of the given size.
+# <p>
+# This function requires the _imagingft service.
+#
+# @param filename A truetype font file. Under Windows, if the file
+# is not found in this filename, the loader also looks in Windows
+# <b>fonts</b> directory
+# @param size The requested size, in points.
+# @param index Which font face to load (default is first available face).
+# @param encoding Which font encoding to use (default is Unicode). Common
+# encodings are "unic" (Unicode), "symb" (Microsoft Symbol), "ADOB"
+# (Adobe Standard), "ADBE" (Adobe Expert), and "armn" (Apple Roman).
+# See the FreeType documentation for more information.
+# @return A font object.
+# @exception IOError If the file could not be read.
+
+def truetype(filename, size, index=0, encoding=""):
+ "Load a truetype font file."
+ try:
+ return FreeTypeFont(filename, size, index, encoding)
+ except IOError:
+ if sys.platform == "win32":
+ # check the windows font repository
+ # NOTE: must use uppercase WINDIR, to work around bugs in
+ # 1.5.2's os.environ.get()
+ windir = os.environ.get("WINDIR")
+ if windir:
+ filename = os.path.join(windir, "fonts", filename)
+ return FreeTypeFont(filename, size, index, encoding)
+ raise
+
+##
+# Load font file. Same as load, but searches for a bitmap font along
+# the Python path.
+#
+# @param filename Name of font file.
+# @return A font object.
+# @exception IOError If the file could not be read.
+# @see #load
+
+def load_path(filename):
+ "Load a font file, searching along the Python path."
+ for dir in sys.path:
+ if Image.isDirectory(dir):
+ try:
+ return load(os.path.join(dir, filename))
+ except IOError:
+ pass
+ raise IOError("cannot find font file")
+
+##
+# Load a (probably rather ugly) default font.
+#
+# @return A font object.
+
+def load_default():
+ "Load a default font."
+ from StringIO import StringIO
+ import base64
+ f = ImageFont()
+ f._load_pilfont_data(
+ # courB08
+ StringIO(base64.decodestring('''
+UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
+BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
+AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
+AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
+ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
+BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
+//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
+AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
+AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
+ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
+AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
+/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
+AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
+AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
+AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
+BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
+AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
+2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
+AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
++gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
+////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
+BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
+AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
+AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
+AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
+BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
+//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
+AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
+AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
+mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
+AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
+AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
+AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
+Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
+//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
+AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
+AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
+DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
+AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
++wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
+AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
+///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
+AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
+BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
+Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
+eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
+AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
++gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
+////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
+BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
+AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
+AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
+Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
+Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
+//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
+AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
+AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
+LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
+AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
+AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
+AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
+AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
+AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
+EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
+AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
+pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
+AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
++QAGAAIAzgAKANUAEw==
+''')), Image.open(StringIO(base64.decodestring('''
+iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
+Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
+M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
+LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
+IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
+Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
+NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
+in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
+SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
+AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
+y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
+ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
+lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
+/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
+AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
+c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
+/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
+pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
+oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
+evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
+AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
+Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
+w7IkEbzhVQAAAABJRU5ErkJggg==
+'''))))
+ return f
+
+if __name__ == "__main__":
+ # create font data chunk for embedding
+ import base64, os, sys
+ font = "../Images/courB08"
+ print " f._load_pilfont_data("
+ print " # %s" % os.path.basename(font)
+ print " StringIO(base64.decodestring('''"
+ base64.encode(open(font + ".pil", "rb"), sys.stdout)
+ print "''')), Image.open(StringIO(base64.decodestring('''"
+ base64.encode(open(font + ".pbm", "rb"), sys.stdout)
+ print "'''))))"
diff --git a/PIL/ImageGL.py b/PIL/ImageGL.py
new file mode 100644
index 0000000..482f42d
--- /dev/null
+++ b/PIL/ImageGL.py
@@ -0,0 +1,28 @@
+#
+# The Python Imaging Library.
+# $Id: ImageGL.py 2438 2005-05-25 21:09:48Z Fredrik $
+#
+# OpenGL pixmap/texture interface (requires imToolkit OpenGL extensions)
+#
+# History:
+# 2003-09-13 fl Added
+#
+# Copyright (c) Secret Labs AB 2003.
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# OpenGL pixmap/texture interface (requires imToolkit OpenGL
+# extensions.)
+##
+
+import _imaginggl
+
+##
+# Texture factory.
+
+class TextureFactory:
+ pass # overwritten by the _imaginggl module
+
+from _imaginggl import *
diff --git a/PIL/ImageGrab.py b/PIL/ImageGrab.py
new file mode 100644
index 0000000..ecbfbac
--- /dev/null
+++ b/PIL/ImageGrab.py
@@ -0,0 +1,71 @@
+#
+# The Python Imaging Library
+# $Id: ImageGrab.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# screen grabber (windows only)
+#
+# History:
+# 2001-04-26 fl created
+# 2001-09-17 fl use builtin driver, if present
+# 2002-11-19 fl added grabclipboard support
+#
+# Copyright (c) 2001-2002 by Secret Labs AB
+# Copyright (c) 2001-2002 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+
+##
+# (New in 1.1.3) The <b>ImageGrab</b> module can be used to copy
+# the contents of the screen to a PIL image memory.
+# <p>
+# The current version works on Windows only.</p>
+#
+# @since 1.1.3
+##
+
+try:
+ # built-in driver (1.1.3 and later)
+ grabber = Image.core.grabscreen
+except AttributeError:
+ # stand-alone driver (pil plus)
+ import _grabscreen
+ grabber = _grabscreen.grab
+
+##
+# (New in 1.1.3) Take a snapshot of the screen. The pixels inside the
+# bounding box are returned as an "RGB" image. If the bounding box is
+# omitted, the entire screen is copied.
+#
+# @param bbox What region to copy. Default is the entire screen.
+# @return An image
+# @since 1.1.3
+
+def grab(bbox=None):
+ size, data = grabber()
+ im = Image.fromstring(
+ "RGB", size, data,
+ # RGB, 32-bit line padding, origo in lower left corner
+ "raw", "BGR", (size[0]*3 + 3) & -4, -1
+ )
+ if bbox:
+ im = im.crop(bbox)
+ return im
+
+##
+# (New in 1.1.4) Take a snapshot of the clipboard image, if any.
+#
+# @return An image, a list of filenames, or None if the clipboard does
+# not contain image data or filenames. Note that if a list is
+# returned, the filenames may not represent image files.
+# @since 1.1.4
+
+def grabclipboard():
+ debug = 0 # temporary interface
+ data = Image.core.grabclipboard(debug)
+ if Image.isStringType(data):
+ import BmpImagePlugin, StringIO
+ return BmpImagePlugin.DibImageFile(StringIO.StringIO(data))
+ return data
diff --git a/PIL/ImageMath.py b/PIL/ImageMath.py
new file mode 100644
index 0000000..117a5ae
--- /dev/null
+++ b/PIL/ImageMath.py
@@ -0,0 +1,207 @@
+#
+# The Python Imaging Library
+# $Id: ImageMath.py 2508 2005-09-12 19:01:03Z fredrik $
+#
+# a simple math add-on for the Python Imaging Library
+#
+# History:
+# 1999-02-15 fl Original PIL Plus release
+# 2005-05-05 fl Simplified and cleaned up for PIL 1.1.6
+# 2005-09-12 fl Fixed int() and float() for Python 2.4.1
+#
+# Copyright (c) 1999-2005 by Secret Labs AB
+# Copyright (c) 2005 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+import _imagingmath
+
+VERBOSE = 0
+
+def _isconstant(v):
+ return isinstance(v, type(0)) or isinstance(v, type(0.0))
+
+class _Operand:
+ # wraps an image operand, providing standard operators
+
+ def __init__(self, im):
+ self.im = im
+
+ def __fixup(self, im1):
+ # convert image to suitable mode
+ if isinstance(im1, _Operand):
+ # argument was an image.
+ if im1.im.mode in ("1", "L"):
+ return im1.im.convert("I")
+ elif im1.im.mode in ("I", "F"):
+ return im1.im
+ else:
+ raise ValueError, "unsupported mode: %s" % im1.im.mode
+ else:
+ # argument was a constant
+ if _isconstant(im1) and self.im.mode in ("1", "L", "I"):
+ return Image.new("I", self.im.size, im1)
+ else:
+ return Image.new("F", self.im.size, im1)
+
+ def apply(self, op, im1, im2=None, mode=None):
+ im1 = self.__fixup(im1)
+ if im2 is None:
+ # unary operation
+ out = Image.new(mode or im1.mode, im1.size, None)
+ im1.load()
+ try:
+ op = getattr(_imagingmath, op+"_"+im1.mode)
+ except AttributeError:
+ raise TypeError, "bad operand type for '%s'" % op
+ _imagingmath.unop(op, out.im.id, im1.im.id)
+ else:
+ # binary operation
+ im2 = self.__fixup(im2)
+ if im1.mode != im2.mode:
+ # convert both arguments to floating point
+ if im1.mode != "F": im1 = im1.convert("F")
+ if im2.mode != "F": im2 = im2.convert("F")
+ if im1.mode != im2.mode:
+ raise ValueError, "mode mismatch"
+ if im1.size != im2.size:
+ # crop both arguments to a common size
+ size = (min(im1.size[0], im2.size[0]),
+ min(im1.size[1], im2.size[1]))
+ if im1.size != size: im1 = im1.crop((0, 0) + size)
+ if im2.size != size: im2 = im2.crop((0, 0) + size)
+ out = Image.new(mode or im1.mode, size, None)
+ else:
+ out = Image.new(mode or im1.mode, im1.size, None)
+ im1.load(); im2.load()
+ try:
+ op = getattr(_imagingmath, op+"_"+im1.mode)
+ except AttributeError:
+ raise TypeError, "bad operand type for '%s'" % op
+ _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)
+ return _Operand(out)
+
+ # unary operators
+ def __nonzero__(self):
+ # an image is "true" if it contains at least one non-zero pixel
+ return self.im.getbbox() is not None
+ def __abs__(self):
+ return self.apply("abs", self)
+ def __pos__(self):
+ return self
+ def __neg__(self):
+ return self.apply("neg", self)
+
+ # binary operators
+ def __add__(self, other):
+ return self.apply("add", self, other)
+ def __radd__(self, other):
+ return self.apply("add", other, self)
+ def __sub__(self, other):
+ return self.apply("sub", self, other)
+ def __rsub__(self, other):
+ return self.apply("sub", other, self)
+ def __mul__(self, other):
+ return self.apply("mul", self, other)
+ def __rmul__(self, other):
+ return self.apply("mul", other, self)
+ def __div__(self, other):
+ return self.apply("div", self, other)
+ def __rdiv__(self, other):
+ return self.apply("div", other, self)
+ def __mod__(self, other):
+ return self.apply("mod", self, other)
+ def __rmod__(self, other):
+ return self.apply("mod", other, self)
+ def __pow__(self, other):
+ return self.apply("pow", self, other)
+ def __rpow__(self, other):
+ return self.apply("pow", other, self)
+
+ # bitwise
+ def __invert__(self):
+ return self.apply("invert", self)
+ def __and__(self, other):
+ return self.apply("and", self, other)
+ def __rand__(self, other):
+ return self.apply("and", other, self)
+ def __or__(self, other):
+ return self.apply("or", self, other)
+ def __ror__(self, other):
+ return self.apply("or", other, self)
+ def __xor__(self, other):
+ return self.apply("xor", self, other)
+ def __rxor__(self, other):
+ return self.apply("xor", other, self)
+ def __lshift__(self, other):
+ return self.apply("lshift", self, other)
+ def __rshift__(self, other):
+ return self.apply("rshift", self, other)
+
+ # logical
+ def __eq__(self, other):
+ return self.apply("eq", self, other)
+ def __ne__(self, other):
+ return self.apply("ne", self, other)
+ def __lt__(self, other):
+ return self.apply("lt", self, other)
+ def __le__(self, other):
+ return self.apply("le", self, other)
+ def __gt__(self, other):
+ return self.apply("gt", self, other)
+ def __ge__(self, other):
+ return self.apply("ge", self, other)
+
+# conversions
+def imagemath_int(self):
+ return _Operand(self.im.convert("I"))
+def imagemath_float(self):
+ return _Operand(self.im.convert("F"))
+
+# logical
+def imagemath_equal(self, other):
+ return self.apply("eq", self, other, mode="I")
+def imagemath_notequal(self, other):
+ return self.apply("ne", self, other, mode="I")
+
+def imagemath_min(self, other):
+ return self.apply("min", self, other)
+def imagemath_max(self, other):
+ return self.apply("max", self, other)
+
+def imagemath_convert(self, mode):
+ return _Operand(self.im.convert(mode))
+
+ops = {}
+for k, v in globals().items():
+ if k[:10] == "imagemath_":
+ ops[k[10:]] = v
+
+##
+# Evaluates an image expression.
+#
+# @param expression A string containing a Python-style expression.
+# @keyparam options Values to add to the evaluation context. You
+# can either use a dictionary, or one or more keyword arguments.
+# @return The evaluated expression. This is usually an image object,
+# but can also be an integer, a floating point value, or a pixel
+# tuple, depending on the expression.
+
+def eval(expression, _dict={}, **kw):
+
+ # build execution namespace
+ args = ops.copy()
+ args.update(_dict)
+ args.update(kw)
+ for k, v in args.items():
+ if hasattr(v, "im"):
+ args[k] = _Operand(v)
+
+ import __builtin__
+ out =__builtin__.eval(expression, args)
+ try:
+ return out.im
+ except AttributeError:
+ return out
diff --git a/PIL/ImageMode.py b/PIL/ImageMode.py
new file mode 100644
index 0000000..87a0e5a
--- /dev/null
+++ b/PIL/ImageMode.py
@@ -0,0 +1,46 @@
+#
+# The Python Imaging Library.
+# $Id: /work/modules/pil/PIL/ImageFilter.py 486 2004-10-06T08:55:20.930352Z fredrik $
+#
+# standard mode descriptors
+#
+# History:
+# 2006-03-20 fl Added
+#
+# Copyright (c) 2006 by Secret Labs AB.
+# Copyright (c) 2006 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+# mode descriptor cache
+_modes = {}
+
+##
+# Wrapper for mode strings.
+
+class ModeDescriptor:
+
+ def __init__(self, mode, bands, basemode, basetype):
+ self.mode = mode
+ self.bands = bands
+ self.basemode = basemode
+ self.basetype = basetype
+
+ def __str__(self):
+ return self.mode
+
+##
+# Gets a mode descriptor for the given mode.
+
+def getmode(mode):
+ if not _modes:
+ # initialize mode cache
+ import Image
+ # core modes
+ for m, (basemode, basetype, bands) in Image._MODEINFO.items():
+ _modes[m] = ModeDescriptor(m, bands, basemode, basetype)
+ # extra experimental modes
+ _modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
+ _modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
+ return _modes[mode]
diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py
new file mode 100644
index 0000000..89b5e72
--- /dev/null
+++ b/PIL/ImageOps.py
@@ -0,0 +1,408 @@
+#
+# The Python Imaging Library.
+# $Id: ImageOps.py 2760 2006-06-19 13:31:40Z fredrik $
+#
+# standard image operations
+#
+# History:
+# 2001-10-20 fl Created
+# 2001-10-23 fl Added autocontrast operator
+# 2001-12-18 fl Added Kevin's fit operator
+# 2004-03-14 fl Fixed potential division by zero in equalize
+# 2005-05-05 fl Fixed equalize for low number of values
+#
+# Copyright (c) 2001-2004 by Secret Labs AB
+# Copyright (c) 2001-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+import operator
+
+##
+# (New in 1.1.3) The <b>ImageOps</b> module contains a number of
+# 'ready-made' image processing operations. This module is somewhat
+# experimental, and most operators only work on L and RGB images.
+#
+# @since 1.1.3
+##
+
+#
+# helpers
+
+def _border(border):
+ if type(border) is type(()):
+ if len(border) == 2:
+ left, top = right, bottom = border
+ elif len(border) == 4:
+ left, top, right, bottom = border
+ else:
+ left = top = right = bottom = border
+ return left, top, right, bottom
+
+def _color(color, mode):
+ if Image.isStringType(color):
+ import ImageColor
+ color = ImageColor.getcolor(color, mode)
+ return color
+
+def _lut(image, lut):
+ if image.mode == "P":
+ # FIXME: apply to lookup table, not image data
+ raise NotImplementedError("mode P support coming soon")
+ elif image.mode in ("L", "RGB"):
+ if image.mode == "RGB" and len(lut) == 256:
+ lut = lut + lut + lut
+ return image.point(lut)
+ else:
+ raise IOError, "not supported for this image mode"
+
+#
+# actions
+
+##
+# Maximize (normalize) image contrast. This function calculates a
+# histogram of the input image, removes <i>cutoff</i> percent of the
+# lightest and darkest pixels from the histogram, and remaps the image
+# so that the darkest pixel becomes black (0), and the lightest
+# becomes white (255).
+#
+# @param image The image to process.
+# @param cutoff How many percent to cut off from the histogram.
+# @param ignore The background pixel value (use None for no background).
+# @return An image.
+
+def autocontrast(image, cutoff=0, ignore=None):
+ "Maximize image contrast, based on histogram"
+ histogram = image.histogram()
+ lut = []
+ for layer in range(0, len(histogram), 256):
+ h = histogram[layer:layer+256]
+ if ignore is not None:
+ # get rid of outliers
+ try:
+ h[ignore] = 0
+ except TypeError:
+ # assume sequence
+ for ix in ignore:
+ h[ix] = 0
+ if cutoff:
+ # cut off pixels from both ends of the histogram
+ # get number of pixels
+ n = 0
+ for ix in range(256):
+ n = n + h[ix]
+ # remove cutoff% pixels from the low end
+ cut = n * cutoff / 100
+ for lo in range(256):
+ if cut > h[lo]:
+ cut = cut - h[lo]
+ h[lo] = 0
+ else:
+ h[lo] = h[lo] - cut
+ cut = 0
+ if cut <= 0:
+ break
+ # remove cutoff% samples from the hi end
+ cut = n * cutoff / 100
+ for hi in range(255, -1, -1):
+ if cut > h[hi]:
+ cut = cut - h[hi]
+ h[hi] = 0
+ else:
+ h[hi] = h[hi] - cut
+ cut = 0
+ if cut <= 0:
+ break
+ # find lowest/highest samples after preprocessing
+ for lo in range(256):
+ if h[lo]:
+ break
+ for hi in range(255, -1, -1):
+ if h[hi]:
+ break
+ if hi <= lo:
+ # don't bother
+ lut.extend(range(256))
+ else:
+ scale = 255.0 / (hi - lo)
+ offset = -lo * scale
+ for ix in range(256):
+ ix = int(ix * scale + offset)
+ if ix < 0:
+ ix = 0
+ elif ix > 255:
+ ix = 255
+ lut.append(ix)
+ return _lut(image, lut)
+
+##
+# Colorize grayscale image. The <i>black</i> and <i>white</i>
+# arguments should be RGB tuples; this function calculates a colour
+# wedge mapping all black pixels in the source image to the first
+# colour, and all white pixels to the second colour.
+#
+# @param image The image to colourize.
+# @param black The colour to use for black input pixels.
+# @param white The colour to use for white input pixels.
+# @return An image.
+
+def colorize(image, black, white):
+ "Colorize a grayscale image"
+ assert image.mode == "L"
+ black = _color(black, "RGB")
+ white = _color(white, "RGB")
+ red = []; green = []; blue = []
+ for i in range(256):
+ red.append(black[0]+i*(white[0]-black[0])/255)
+ green.append(black[1]+i*(white[1]-black[1])/255)
+ blue.append(black[2]+i*(white[2]-black[2])/255)
+ image = image.convert("RGB")
+ return _lut(image, red + green + blue)
+
+##
+# Remove border from image. The same amount of pixels are removed
+# from all four sides. This function works on all image modes.
+#
+# @param image The image to crop.
+# @param border The number of pixels to remove.
+# @return An image.
+# @see Image#Image.crop
+
+def crop(image, border=0):
+ "Crop border off image"
+ left, top, right, bottom = _border(border)
+ return image.crop(
+ (left, top, image.size[0]-right, image.size[1]-bottom)
+ )
+
+##
+# Deform the image.
+#
+# @param image The image to deform.
+# @param deformer A deformer object. Any object that implements a
+# <b>getmesh</b> method can be used.
+# @param resample What resampling filter to use.
+# @return An image.
+
+def deform(image, deformer, resample=Image.BILINEAR):
+ "Deform image using the given deformer"
+ return image.transform(
+ image.size, Image.MESH, deformer.getmesh(image), resample
+ )
+
+##
+# Equalize the image histogram. This function applies a non-linear
+# mapping to the input image, in order to create a uniform
+# distribution of grayscale values in the output image.
+#
+# @param image The image to equalize.
+# @param mask An optional mask. If given, only the pixels selected by
+# the mask are included in the analysis.
+# @return An image.
+
+def equalize(image, mask=None):
+ "Equalize image histogram"
+ if image.mode == "P":
+ image = image.convert("RGB")
+ h = image.histogram(mask)
+ lut = []
+ for b in range(0, len(h), 256):
+ histo = filter(None, h[b:b+256])
+ if len(histo) <= 1:
+ lut.extend(range(256))
+ else:
+ step = (reduce(operator.add, histo) - histo[-1]) / 255
+ if not step:
+ lut.extend(range(256))
+ else:
+ n = step / 2
+ for i in range(256):
+ lut.append(n / step)
+ n = n + h[i+b]
+ return _lut(image, lut)
+
+##
+# Add border to the image
+#
+# @param image The image to expand.
+# @param border Border width, in pixels.
+# @param fill Pixel fill value (a colour value). Default is 0 (black).
+# @return An image.
+
+def expand(image, border=0, fill=0):
+ "Add border to image"
+ left, top, right, bottom = _border(border)
+ width = left + image.size[0] + right
+ height = top + image.size[1] + bottom
+ out = Image.new(image.mode, (width, height), _color(fill, image.mode))
+ out.paste(image, (left, top))
+ return out
+
+##
+# Returns a sized and cropped version of the image, cropped to the
+# requested aspect ratio and size.
+# <p>
+# The <b>fit</b> function was contributed by Kevin Cazabon.
+#
+# @param size The requested output size in pixels, given as a
+# (width, height) tuple.
+# @param method What resampling method to use. Default is Image.NEAREST.
+# @param bleed Remove a border around the outside of the image (from all
+# four edges. The value is a decimal percentage (use 0.01 for one
+# percent). The default value is 0 (no border).
+# @param centering Control the cropping position. Use (0.5, 0.5) for
+# center cropping (e.g. if cropping the width, take 50% off of the
+# left side, and therefore 50% off the right side). (0.0, 0.0)
+# will crop from the top left corner (i.e. if cropping the width,
+# take all of the crop off of the right side, and if cropping the
+# height, take all of it off the bottom). (1.0, 0.0) will crop
+# from the bottom left corner, etc. (i.e. if cropping the width,
+# take all of the crop off the left side, and if cropping the height
+# take none from the top, and therefore all off the bottom).
+# @return An image.
+
+def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
+ """
+ This method returns a sized and cropped version of the image,
+ cropped to the aspect ratio and size that you request.
+ """
+
+ # by Kevin Cazabon, Feb 17/2000
+ # kevin@cazabon.com
+ # http://www.cazabon.com
+
+ # ensure inputs are valid
+ if type(centering) != type([]):
+ centering = [centering[0], centering[1]]
+
+ if centering[0] > 1.0 or centering[0] < 0.0:
+ centering [0] = 0.50
+ if centering[1] > 1.0 or centering[1] < 0.0:
+ centering[1] = 0.50
+
+ if bleed > 0.49999 or bleed < 0.0:
+ bleed = 0.0
+
+ # calculate the area to use for resizing and cropping, subtracting
+ # the 'bleed' around the edges
+
+ # number of pixels to trim off on Top and Bottom, Left and Right
+ bleedPixels = (
+ int((float(bleed) * float(image.size[0])) + 0.5),
+ int((float(bleed) * float(image.size[1])) + 0.5)
+ )
+
+ liveArea = (
+ bleedPixels[0], bleedPixels[1], image.size[0] - bleedPixels[0] - 1,
+ image.size[1] - bleedPixels[1] - 1
+ )
+
+ liveSize = (liveArea[2] - liveArea[0], liveArea[3] - liveArea[1])
+
+ # calculate the aspect ratio of the liveArea
+ liveAreaAspectRatio = float(liveSize[0])/float(liveSize[1])
+
+ # calculate the aspect ratio of the output image
+ aspectRatio = float(size[0]) / float(size[1])
+
+ # figure out if the sides or top/bottom will be cropped off
+ if liveAreaAspectRatio >= aspectRatio:
+ # liveArea is wider than what's needed, crop the sides
+ cropWidth = int((aspectRatio * float(liveSize[1])) + 0.5)
+ cropHeight = liveSize[1]
+ else:
+ # liveArea is taller than what's needed, crop the top and bottom
+ cropWidth = liveSize[0]
+ cropHeight = int((float(liveSize[0])/aspectRatio) + 0.5)
+
+ # make the crop
+ leftSide = int(liveArea[0] + (float(liveSize[0]-cropWidth) * centering[0]))
+ if leftSide < 0:
+ leftSide = 0
+ topSide = int(liveArea[1] + (float(liveSize[1]-cropHeight) * centering[1]))
+ if topSide < 0:
+ topSide = 0
+
+ out = image.crop(
+ (leftSide, topSide, leftSide + cropWidth, topSide + cropHeight)
+ )
+
+ # resize the image and return it
+ return out.resize(size, method)
+
+##
+# Flip the image vertically (top to bottom).
+#
+# @param image The image to flip.
+# @return An image.
+
+def flip(image):
+ "Flip image vertically"
+ return image.transpose(Image.FLIP_TOP_BOTTOM)
+
+##
+# Convert the image to grayscale.
+#
+# @param image The image to convert.
+# @return An image.
+
+def grayscale(image):
+ "Convert to grayscale"
+ return image.convert("L")
+
+##
+# Invert (negate) the image.
+#
+# @param image The image to invert.
+# @return An image.
+
+def invert(image):
+ "Invert image (negate)"
+ lut = []
+ for i in range(256):
+ lut.append(255-i)
+ return _lut(image, lut)
+
+##
+# Flip image horizontally (left to right).
+#
+# @param image The image to mirror.
+# @return An image.
+
+def mirror(image):
+ "Flip image horizontally"
+ return image.transpose(Image.FLIP_LEFT_RIGHT)
+
+##
+# Reduce the number of bits for each colour channel.
+#
+# @param image The image to posterize.
+# @param bits The number of bits to keep for each channel (1-8).
+# @return An image.
+
+def posterize(image, bits):
+ "Reduce the number of bits per color channel"
+ lut = []
+ mask = ~(2**(8-bits)-1)
+ for i in range(256):
+ lut.append(i & mask)
+ return _lut(image, lut)
+
+##
+# Invert all pixel values above a threshold.
+#
+# @param image The image to posterize.
+# @param threshold All pixels above this greyscale level are inverted.
+# @return An image.
+
+def solarize(image, threshold=128):
+ "Invert all values above threshold"
+ lut = []
+ for i in range(256):
+ if i < threshold:
+ lut.append(i)
+ else:
+ lut.append(255-i)
+ return _lut(image, lut)
diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py
new file mode 100644
index 0000000..0e9a93a
--- /dev/null
+++ b/PIL/ImagePalette.py
@@ -0,0 +1,161 @@
+#
+# The Python Imaging Library.
+# $Id: ImagePalette.py 2339 2005-03-25 08:02:17Z fredrik $
+#
+# image palette object
+#
+# History:
+# 1996-03-11 fl Rewritten.
+# 1997-01-03 fl Up and running.
+# 1997-08-23 fl Added load hack
+# 2001-04-16 fl Fixed randint shadow bug in random()
+#
+# Copyright (c) 1997-2001 by Secret Labs AB
+# Copyright (c) 1996-1997 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import array
+import Image
+
+##
+# Colour palette wrapper for palette mapped images.
+
+class ImagePalette:
+ "Colour palette for palette mapped images"
+
+ def __init__(self, mode = "RGB", palette = None):
+ self.mode = mode
+ self.rawmode = None # if set, palette contains raw data
+ self.palette = palette or range(256)*len(self.mode)
+ self.colors = {}
+ self.dirty = None
+ if len(self.mode)*256 != len(self.palette):
+ raise ValueError, "wrong palette size"
+
+ def getdata(self):
+ # experimental: get palette contains in format suitable
+ # for the low-level im.putpalette primitive
+ if self.rawmode:
+ return self.rawmode, self.palette
+ return self.mode + ";L", self.tostring()
+
+ def tostring(self):
+ # experimental: convert palette to string
+ if self.rawmode:
+ raise ValueError("palette contains raw palette data")
+ if Image.isStringType(self.palette):
+ return self.palette
+ return array.array("B", self.palette).tostring()
+
+ def getcolor(self, color):
+ # experimental: given an rgb tuple, allocate palette entry
+ if self.rawmode:
+ raise ValueError("palette contains raw palette data")
+ if Image.isTupleType(color):
+ try:
+ return self.colors[color]
+ except KeyError:
+ # allocate new color slot
+ if Image.isStringType(self.palette):
+ self.palette = map(int, self.palette)
+ index = len(self.colors)
+ if index >= 256:
+ raise ValueError("cannot allocate more than 256 colors")
+ self.colors[color] = index
+ self.palette[index] = color[0]
+ self.palette[index+256] = color[1]
+ self.palette[index+512] = color[2]
+ self.dirty = 1
+ return index
+ else:
+ raise ValueError("unknown color specifier: %r" % color)
+
+ def save(self, fp):
+ # (experimental) save palette to text file
+ if self.rawmode:
+ raise ValueError("palette contains raw palette data")
+ if type(fp) == type(""):
+ fp = open(fp, "w")
+ fp.write("# Palette\n")
+ fp.write("# Mode: %s\n" % self.mode)
+ for i in range(256):
+ fp.write("%d" % i)
+ for j in range(i, len(self.palette), 256):
+ fp.write(" %d" % self.palette[j])
+ fp.write("\n")
+ fp.close()
+
+# --------------------------------------------------------------------
+# Internal
+
+def raw(rawmode, data):
+ palette = ImagePalette()
+ palette.rawmode = rawmode
+ palette.palette = data
+ palette.dirty = 1
+ return palette
+
+# --------------------------------------------------------------------
+# Factories
+
+def new(mode, data):
+ return Image.core.new_palette(mode, data)
+
+def negative(mode = "RGB"):
+ palette = range(256)
+ palette.reverse()
+ return ImagePalette(mode, palette * len(mode))
+
+def random(mode = "RGB"):
+ from random import randint
+ palette = map(lambda a, randint=randint:
+ randint(0, 255), [0]*256*len(mode))
+ return ImagePalette(mode, palette)
+
+def wedge(mode = "RGB"):
+ return ImagePalette(mode, range(256) * len(mode))
+
+def load(filename):
+
+ # FIXME: supports GIMP gradients only
+
+ fp = open(filename, "rb")
+
+ lut = None
+
+ if not lut:
+ try:
+ import GimpPaletteFile
+ fp.seek(0)
+ p = GimpPaletteFile.GimpPaletteFile(fp)
+ lut = p.getpalette()
+ except (SyntaxError, ValueError):
+ pass
+
+ if not lut:
+ try:
+ import GimpGradientFile
+ fp.seek(0)
+ p = GimpGradientFile.GimpGradientFile(fp)
+ lut = p.getpalette()
+ except (SyntaxError, ValueError):
+ pass
+
+ if not lut:
+ try:
+ import PaletteFile
+ fp.seek(0)
+ p = PaletteFile.PaletteFile(fp)
+ lut = p.getpalette()
+ except (SyntaxError, ValueError):
+ pass
+
+ if not lut:
+ raise IOError, "cannot load palette"
+
+ return lut # data, rawmode
+
+
+# add some psuedocolour palettes as well
diff --git a/PIL/ImagePath.py b/PIL/ImagePath.py
new file mode 100644
index 0000000..120a607
--- /dev/null
+++ b/PIL/ImagePath.py
@@ -0,0 +1,71 @@
+#
+# The Python Imaging Library
+# $Id: ImagePath.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# path interface
+#
+# History:
+# 1996-11-04 fl Created
+# 2002-04-14 fl Added documentation stub class
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+
+##
+# Path wrapper.
+
+class Path:
+
+ ##
+ # Creates a path object.
+ #
+ # @param xy Sequence. The sequence can contain 2-tuples [(x, y), ...]
+ # or a flat list of numbers [x, y, ...].
+
+ def __init__(self, xy):
+ pass
+
+ ##
+ # Compacts the path, by removing points that are close to each
+ # other. This method modifies the path in place.
+
+ def compact(self, distance=2):
+ pass
+
+ ##
+ # Gets the bounding box.
+
+ def getbbox(self):
+ pass
+
+ ##
+ # Maps the path through a function.
+
+ def map(self, function):
+ pass
+
+ ##
+ # Converts the path to Python list.
+ #
+ # @param flat By default, this function returns a list of 2-tuples
+ # [(x, y), ...]. If this argument is true, it returns a flat
+ # list [x, y, ...] instead.
+ # @return A list of coordinates.
+
+ def tolist(self, flat=0):
+ pass
+
+ ##
+ # Transforms the path.
+
+ def transform(self, matrix):
+ pass
+
+
+# override with C implementation
+Path = Image.core.path
diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py
new file mode 100644
index 0000000..2e9f476
--- /dev/null
+++ b/PIL/ImageQt.py
@@ -0,0 +1,84 @@
+#
+# The Python Imaging Library.
+# $Id: ImageQt.py 2741 2006-06-18 16:17:20Z fredrik $
+#
+# a simple Qt image interface.
+#
+# history:
+# 2006-06-03 fl: created
+# 2006-06-04 fl: inherit from QImage instead of wrapping it
+# 2006-06-05 fl: removed toimage helper; move string support to ImageQt
+#
+# Copyright (c) 2006 by Secret Labs AB
+# Copyright (c) 2006 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+
+from PyQt4.QtGui import QImage, qRgb
+
+##
+# (Internal) Turns an RGB color into a Qt compatible color integer.
+
+def rgb(r, g, b):
+ # use qRgb to pack the colors, and then turn the resulting long
+ # into a negative integer with the same bitpattern.
+ return (qRgb(r, g, b) & 0xffffff) - 0x1000000
+
+##
+# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage
+# class.
+#
+# @param im A PIL Image object, or a file name (given either as Python
+# string or a PyQt string object).
+
+class ImageQt(QImage):
+
+ def __init__(self, im):
+
+ data = None
+ colortable = None
+
+ # handle filename, if given instead of image name
+ if hasattr(im, "toUtf8"):
+ # FIXME - is this really the best way to do this?
+ im = unicode(im.toUtf8(), "utf-8")
+ if Image.isStringType(im):
+ im = Image.open(im)
+
+ if im.mode == "1":
+ format = QImage.Format_Mono
+ elif im.mode == "L":
+ format = QImage.Format_Indexed8
+ colortable = []
+ for i in range(256):
+ colortable.append(rgb(i, i, i))
+ elif im.mode == "P":
+ format = QImage.Format_Indexed8
+ colortable = []
+ palette = im.getpalette()
+ for i in range(0, len(palette), 3):
+ colortable.append(rgb(*palette[i:i+3]))
+ elif im.mode == "RGB":
+ data = im.tostring("raw", "BGRX")
+ format = QImage.Format_RGB32
+ elif im.mode == "RGBA":
+ try:
+ data = im.tostring("raw", "BGRA")
+ except SystemError:
+ # workaround for earlier versions
+ r, g, b, a = im.split()
+ im = Image.merge("RGBA", (b, g, r, a))
+ format = QImage.Format_ARGB32
+ else:
+ raise ValueError("unsupported image mode %r" % im.mode)
+
+ # must keep a reference, or Qt will crash!
+ self.__data = data or im.tostring()
+
+ QImage.__init__(self, self.__data, im.size[0], im.size[1], format)
+
+ if colortable:
+ self.setColorTable(colortable)
diff --git a/PIL/ImageSequence.py b/PIL/ImageSequence.py
new file mode 100644
index 0000000..452b096
--- /dev/null
+++ b/PIL/ImageSequence.py
@@ -0,0 +1,38 @@
+#
+# The Python Imaging Library.
+# $Id: ImageSequence.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# sequence support classes
+#
+# history:
+# 1997-02-20 fl Created
+#
+# Copyright (c) 1997 by Secret Labs AB.
+# Copyright (c) 1997 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# This class implements an iterator object that can be used to loop
+# over an image sequence.
+
+class Iterator:
+
+ ##
+ # Create an iterator.
+ #
+ # @param im An image object.
+
+ def __init__(self, im):
+ if not hasattr(im, "seek"):
+ raise AttributeError("im must have seek method")
+ self.im = im
+
+ def __getitem__(self, ix):
+ try:
+ if ix:
+ self.im.seek(ix)
+ return self.im
+ except EOFError:
+ raise IndexError # end of sequence
diff --git a/PIL/ImageStat.py b/PIL/ImageStat.py
new file mode 100644
index 0000000..d53e645
--- /dev/null
+++ b/PIL/ImageStat.py
@@ -0,0 +1,179 @@
+#
+# The Python Imaging Library.
+# $Id: ImageStat.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# global image statistics
+#
+# History:
+# 1996-04-05 fl Created
+# 1997-05-21 fl Added mask; added rms, var, stddev attributes
+# 1997-08-05 fl Added median
+# 1998-07-05 hk Fixed integer overflow error
+#
+# Notes:
+# This class shows how to implement delayed evaluation of attributes.
+# To get a certain value, simply access the corresponding attribute.
+# The __getattr__ dispatcher takes care of the rest.
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+import operator, math
+
+##
+# The <b>ImageStat</b> module calculates global statistics for an
+# image, or a region of an image.
+##
+
+##
+# Calculate statistics for the given image. If a mask is included,
+# only the regions covered by that mask are included in the
+# statistics.
+
+class Stat:
+ "Get image or feature statistics"
+
+ ##
+ # Create a statistics object.
+ #
+ # @def __init__(image, mask=None)
+ # @param image A PIL image, or a precalculate histogram.
+ # @param mask An optional mask.
+
+ def __init__(self, image_or_list, mask = None):
+ try:
+ if mask:
+ self.h = image_or_list.histogram(mask)
+ else:
+ self.h = image_or_list.histogram()
+ except AttributeError:
+ self.h = image_or_list # assume it to be a histogram list
+ if type(self.h) != type([]):
+ raise TypeError, "first argument must be image or list"
+ self.bands = range(len(self.h) / 256)
+
+ def __getattr__(self, id):
+ "Calculate missing attribute"
+ if id[:4] == "_get":
+ raise AttributeError, id
+ # calculate missing attribute
+ v = getattr(self, "_get" + id)()
+ setattr(self, id, v)
+ return v
+
+ def _getextrema(self):
+ "Get min/max values for each band in the image"
+
+ def minmax(histogram):
+ n = 255
+ x = 0
+ for i in range(256):
+ if histogram[i]:
+ n = min(n, i)
+ x = max(x, i)
+ return n, x # returns (255, 0) if there's no data in the histogram
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ v.append(minmax(self.h[i:]))
+ return v
+
+ def _getcount(self):
+ "Get total number of pixels in each layer"
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ v.append(reduce(operator.add, self.h[i:i+256]))
+ return v
+
+ def _getsum(self):
+ "Get sum of all pixels in each layer"
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ sum = 0.0
+ for j in range(256):
+ sum = sum + j * self.h[i+j]
+ v.append(sum)
+ return v
+
+ def _getsum2(self):
+ "Get squared sum of all pixels in each layer"
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ sum2 = 0.0
+ for j in range(256):
+ sum2 = sum2 + (j ** 2) * float(self.h[i+j])
+ v.append(sum2)
+ return v
+
+ def _getmean(self):
+ "Get average pixel level for each layer"
+
+ v = []
+ for i in self.bands:
+ v.append(self.sum[i] / self.count[i])
+ return v
+
+ def _getmedian(self):
+ "Get median pixel level for each layer"
+
+ v = []
+ for i in self.bands:
+ s = 0
+ l = self.count[i]/2
+ b = i * 256
+ for j in range(256):
+ s = s + self.h[b+j]
+ if s > l:
+ break
+ v.append(j)
+ return v
+
+ def _getrms(self):
+ "Get RMS for each layer"
+
+ v = []
+ for i in self.bands:
+ v.append(math.sqrt(self.sum2[i] / self.count[i]))
+ return v
+
+
+ def _getvar(self):
+ "Get variance for each layer"
+
+ v = []
+ for i in self.bands:
+ n = self.count[i]
+ v.append((self.sum2[i]-(self.sum[i]**2.0)/n)/n)
+ return v
+
+ def _getstddev(self):
+ "Get standard deviation for each layer"
+
+ v = []
+ for i in self.bands:
+ v.append(math.sqrt(self.var[i]))
+ return v
+
+Global = Stat # compatibility
+
+if __name__ == "__main__":
+
+ im = Image.open("Images/lena.ppm")
+
+ st = Stat(im)
+
+ print "extrema", st.extrema
+ print "sum ", st.sum
+ print "mean ", st.mean
+ print "median ", st.median
+ print "rms ", st.rms
+ print "sum2 ", st.sum2
+ print "var ", st.var
+ print "stddev ", st.stddev
diff --git a/PIL/ImageTk.py b/PIL/ImageTk.py
new file mode 100644
index 0000000..30f7c0e
--- /dev/null
+++ b/PIL/ImageTk.py
@@ -0,0 +1,296 @@
+#
+# The Python Imaging Library.
+# $Id: ImageTk.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# a Tk display interface
+#
+# History:
+# 96-04-08 fl Created
+# 96-09-06 fl Added getimage method
+# 96-11-01 fl Rewritten, removed image attribute and crop method
+# 97-05-09 fl Use PyImagingPaste method instead of image type
+# 97-05-12 fl Minor tweaks to match the IFUNC95 interface
+# 97-05-17 fl Support the "pilbitmap" booster patch
+# 97-06-05 fl Added file= and data= argument to image constructors
+# 98-03-09 fl Added width and height methods to Image classes
+# 98-07-02 fl Use default mode for "P" images without palette attribute
+# 98-07-02 fl Explicitly destroy Tkinter image objects
+# 99-07-24 fl Support multiple Tk interpreters (from Greg Couch)
+# 99-07-26 fl Automatically hook into Tkinter (if possible)
+# 99-08-15 fl Hook uses _imagingtk instead of _imaging
+#
+# Copyright (c) 1997-1999 by Secret Labs AB
+# Copyright (c) 1996-1997 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Tkinter, Image
+
+##
+# The <b>ImageTk</b> module contains support to create and modify
+# Tkinter <b>BitmapImage</b> and <b>PhotoImage</b> objects.
+# <p>
+# For examples, see the demo programs in the <i>Scripts</i>
+# directory.
+##
+
+# --------------------------------------------------------------------
+# Check for Tkinter interface hooks
+
+_pilbitmap_ok = None
+
+def _pilbitmap_check():
+ global _pilbitmap_ok
+ if _pilbitmap_ok is None:
+ try:
+ im = Image.new("1", (1,1))
+ Tkinter.BitmapImage(data="PIL:%d" % im.im.id)
+ _pilbitmap_ok = 1
+ except Tkinter.TclError:
+ _pilbitmap_ok = 0
+ return _pilbitmap_ok
+
+# --------------------------------------------------------------------
+# PhotoImage
+
+##
+# Creates a Tkinter-compatible photo image. This can be used
+# everywhere Tkinter expects an image object. If the image is an RGBA
+# image, pixels having alpha 0 are treated as transparent.
+
+class PhotoImage:
+
+ ##
+ # Create a photo image object. The constructor takes either
+ # a PIL image, or a mode and a size. Alternatively, you can
+ # use the <b>file</b> or <b>data</b> options to initialize
+ # the photo image object.
+ # <p>
+ # @def __init__(image=None, size=None, **options)
+ # @param image Either a PIL image, or a mode string. If a
+ # mode string is used, a size must also be given.
+ # @param size If the first argument is a mode string, this
+ # defines the size of the image.
+ # @keyparam file A filename to load the image from (using
+ # Image.open(file)).
+ # @keyparam data An 8-bit string containing image data (as
+ # loaded from an image file).
+
+ def __init__(self, image=None, size=None, **kw):
+
+ # Tk compatibility: file or data
+ if image is None:
+ if kw.has_key("file"):
+ image = Image.open(kw["file"])
+ del kw["file"]
+ elif kw.has_key("data"):
+ from StringIO import StringIO
+ image = Image.open(StringIO(kw["data"]))
+ del kw["data"]
+
+ if hasattr(image, "mode") and hasattr(image, "size"):
+ # got an image instead of a mode
+ mode = image.mode
+ if mode == "P":
+ # palette mapped data
+ image.load()
+ try:
+ mode = image.palette.mode
+ except AttributeError:
+ mode = "RGB" # default
+ size = image.size
+ kw["width"], kw["height"] = size
+ else:
+ mode = image
+ image = None
+
+ if mode not in ["1", "L", "RGB", "RGBA"]:
+ mode = Image.getmodebase(mode)
+
+ self.__mode = mode
+ self.__size = size
+ self.__photo = apply(Tkinter.PhotoImage, (), kw)
+ self.tk = self.__photo.tk
+ if image:
+ self.paste(image)
+
+ def __del__(self):
+ name = self.__photo.name
+ self.__photo.name = None
+ try:
+ self.__photo.tk.call("image", "delete", name)
+ except:
+ pass # ignore internal errors
+
+ ##
+ # Get the Tkinter photo image identifier. This method is
+ # automatically called by Tkinter whenever a PhotoImage object is
+ # passed to a Tkinter method.
+ #
+ # @return A Tkinter photo image identifier (a string).
+
+ def __str__(self):
+ return str(self.__photo)
+
+ ##
+ # Get the width of the image.
+ #
+ # @return The width, in pixels.
+
+ def width(self):
+ return self.__size[0]
+
+ ##
+ # Get the height of the image.
+ #
+ # @return The height, in pixels.
+
+ def height(self):
+ return self.__size[1]
+
+ ##
+ # Paste a PIL image into the photo image. Note that this can
+ # be very slow if the photo image is displayed.
+ #
+ # @param im A PIL image. The size must match the target region.
+ # If the mode does not match, the image is converted to the
+ # mode of the bitmap image.
+ # @param box A 4-tuple defining the left, upper, right, and
+ # lower pixel coordinate. If None is given instead of a
+ # tuple, all of the image is assumed.
+
+ def paste(self, im, box=None):
+
+ # convert to blittable
+ im.load()
+ image = im.im
+ if image.isblock() and im.mode == self.__mode:
+ block = image
+ else:
+ block = image.new_block(self.__mode, im.size)
+ image.convert2(block, image) # convert directly between buffers
+
+ tk = self.__photo.tk
+
+ try:
+ tk.call("PyImagingPhoto", self.__photo, block.id)
+ except Tkinter.TclError, v:
+ # activate Tkinter hook
+ try:
+ import _imagingtk
+ try:
+ _imagingtk.tkinit(tk.interpaddr(), 1)
+ except AttributeError:
+ _imagingtk.tkinit(id(tk), 0)
+ tk.call("PyImagingPhoto", self.__photo, block.id)
+ except (ImportError, AttributeError, Tkinter.TclError):
+ raise # configuration problem; cannot attach to Tkinter
+
+# --------------------------------------------------------------------
+# BitmapImage
+
+##
+# Create a Tkinter-compatible bitmap image. This can be used
+# everywhere Tkinter expects an image object.
+
+class BitmapImage:
+
+ ##
+ # Create a Tkinter-compatible bitmap image.
+ # <p>
+ # The given image must have mode "1". Pixels having value 0 are
+ # treated as transparent. Options, if any, are passed on to
+ # Tkinter. The most commonly used option is <b>foreground</b>,
+ # which is used to specify the colour for the non-transparent
+ # parts. See the Tkinter documentation for information on how to
+ # specify colours.
+ #
+ # @def __init__(image=None, **options)
+ # @param image A PIL image.
+
+ def __init__(self, image=None, **kw):
+
+ # Tk compatibility: file or data
+ if image is None:
+ if kw.has_key("file"):
+ image = Image.open(kw["file"])
+ del kw["file"]
+ elif kw.has_key("data"):
+ from StringIO import StringIO
+ image = Image.open(StringIO(kw["data"]))
+ del kw["data"]
+
+ self.__mode = image.mode
+ self.__size = image.size
+
+ if _pilbitmap_check():
+ # fast way (requires the pilbitmap booster patch)
+ image.load()
+ kw["data"] = "PIL:%d" % image.im.id
+ self.__im = image # must keep a reference
+ else:
+ # slow but safe way
+ kw["data"] = image.tobitmap()
+ self.__photo = apply(Tkinter.BitmapImage, (), kw)
+
+ def __del__(self):
+ name = self.__photo.name
+ self.__photo.name = None
+ try:
+ self.__photo.tk.call("image", "delete", name)
+ except:
+ pass # ignore internal errors
+
+ ##
+ # Get the width of the image.
+ #
+ # @return The width, in pixels.
+
+ def width(self):
+ return self.__size[0]
+
+ ##
+ # Get the height of the image.
+ #
+ # @return The height, in pixels.
+
+ def height(self):
+ return self.__size[1]
+
+ ##
+ # Get the Tkinter bitmap image identifier. This method is
+ # automatically called by Tkinter whenever a BitmapImage object
+ # is passed to a Tkinter method.
+ #
+ # @return A Tkinter bitmap image identifier (a string).
+
+ def __str__(self):
+ return str(self.__photo)
+
+##
+# Copies the contents of a PhotoImage to a PIL image memory.
+
+def getimage(photo):
+ photo.tk.call("PyImagingPhotoGet", photo)
+
+# --------------------------------------------------------------------
+# Helper for the Image.show method.
+
+def _show(image, title):
+
+ class UI(Tkinter.Label):
+ def __init__(self, master, im):
+ if im.mode == "1":
+ self.image = BitmapImage(im, foreground="white", master=master)
+ else:
+ self.image = PhotoImage(im, master=master)
+ Tkinter.Label.__init__(self, master, image=self.image,
+ bg="black", bd=0)
+
+ if not Tkinter._default_root:
+ raise IOError, "tkinter not initialized"
+ top = Tkinter.Toplevel()
+ if title:
+ top.title(title)
+ UI(top, image).pack()
diff --git a/PIL/ImageTransform.py b/PIL/ImageTransform.py
new file mode 100644
index 0000000..2944cd3
--- /dev/null
+++ b/PIL/ImageTransform.py
@@ -0,0 +1,91 @@
+#
+# The Python Imaging Library.
+# $Id: ImageTransform.py 2813 2006-10-07 10:11:35Z fredrik $
+#
+# transform wrappers
+#
+# History:
+# 2002-04-08 fl Created
+#
+# Copyright (c) 2002 by Secret Labs AB
+# Copyright (c) 2002 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+
+class Transform:
+ def __init__(self, data):
+ self.data = data
+ def getdata(self):
+ return self.method, self.data
+
+##
+# Define an affine image transform.
+# <p>
+# This function takes a 6-tuple (<i>a, b, c, d, e, f</i>) which
+# contain the first two rows from an affine transform matrix. For
+# each pixel (<i>x, y</i>) in the output image, the new value is
+# taken from a position (a <i>x</i> + b <i>y</i> + c,
+# d <i>x</i> + e <i>y</i> + f) in the input image, rounded to
+# nearest pixel.
+# <p>
+# This function can be used to scale, translate, rotate, and shear the
+# original image.
+#
+# @def AffineTransform(matrix)
+# @param matrix A 6-tuple (<i>a, b, c, d, e, f</i>) containing
+# the first two rows from an affine transform matrix.
+# @see Image#Image.transform
+
+class AffineTransform(Transform):
+ method = Image.AFFINE
+
+##
+# Define a transform to extract a subregion from an image.
+# <p>
+# Maps a rectangle (defined by two corners) from the image to a
+# rectangle of the given size. The resulting image will contain
+# data sampled from between the corners, such that (<i>x0, y0</i>)
+# in the input image will end up at (0,0) in the output image,
+# and (<i>x1, y1</i>) at <i>size</i>.
+# <p>
+# This method can be used to crop, stretch, shrink, or mirror an
+# arbitrary rectangle in the current image. It is slightly slower than
+# <b>crop</b>, but about as fast as a corresponding <b>resize</b>
+# operation.
+#
+# @def ExtentTransform(bbox)
+# @param bbox A 4-tuple (<i>x0, y0, x1, y1</i>) which specifies
+# two points in the input image's coordinate system.
+# @see Image#Image.transform
+
+class ExtentTransform(Transform):
+ method = Image.EXTENT
+
+##
+# Define an quad image transform.
+# <p>
+# Maps a quadrilateral (a region defined by four corners) from the
+# image to a rectangle of the given size.
+#
+# @def QuadTransform(xy)
+# @param xy An 8-tuple (<i>x0, y0, x1, y1, x2, y2, y3, y3</i>) which
+# contain the upper left, lower left, lower right, and upper right
+# corner of the source quadrilateral.
+# @see Image#Image.transform
+
+class QuadTransform(Transform):
+ method = Image.QUAD
+
+##
+# Define an mesh image transform. A mesh transform consists of one
+# or more individual quad transforms.
+#
+# @def MeshTransform(data)
+# @param data A list of (bbox, quad) tuples.
+# @see Image#Image.transform
+
+class MeshTransform(Transform):
+ method = Image.MESH
diff --git a/PIL/ImageWin.py b/PIL/ImageWin.py
new file mode 100644
index 0000000..13c56b9
--- /dev/null
+++ b/PIL/ImageWin.py
@@ -0,0 +1,215 @@
+#
+# The Python Imaging Library.
+# $Id: ImageWin.py 2662 2006-03-21 22:41:02Z fredrik $
+#
+# a Windows DIB display interface
+#
+# History:
+# 1996-05-20 fl Created
+# 1996-09-20 fl Fixed subregion exposure
+# 1997-09-21 fl Added draw primitive (for tzPrint)
+# 2003-05-21 fl Added experimental Window/ImageWindow classes
+# 2003-09-05 fl Added fromstring/tostring methods
+#
+# Copyright (c) Secret Labs AB 1997-2003.
+# Copyright (c) Fredrik Lundh 1996-2003.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+
+##
+# The <b>ImageWin</b> module contains support to create and display
+# images under Windows 95/98, NT, 2000 and later.
+
+class HDC:
+ def __init__(self, dc):
+ self.dc = dc
+ def __int__(self):
+ return self.dc
+
+class HWND:
+ def __init__(self, wnd):
+ self.wnd = wnd
+ def __int__(self):
+ return self.wnd
+
+##
+# Create a Windows bitmap with the given mode and size. The mode can
+# be one of "1", "L", "P", or "RGB".
+#
+# If the display requires a palette, this constructor creates a
+# suitable palette and associates it with the image. For an "L" image,
+# 128 greylevels are allocated. For an "RGB" image, a 6x6x6 colour
+# cube is used, together with 20 greylevels.
+#
+# To make sure that palettes work properly under Windows, you must
+# call the <b>palette</b> method upon certain events from Windows.
+
+class Dib:
+
+ ##
+ # Create Windows bitmap.
+ #
+ # @param image Either a PIL image, or a mode string. If a
+ # mode string is used, a size must also be given. The
+ # mode can be one of "1", "L", "P", or "RGB".
+ # @param size If the first argument is a mode string, this
+ # defines the size of the image.
+
+ def __init__(self, image, size=None):
+ if hasattr(image, "mode") and hasattr(image, "size"):
+ mode = image.mode
+ size = image.size
+ else:
+ mode = image
+ image = None
+ if mode not in ["1", "L", "P", "RGB"]:
+ mode = Image.getmodebase(mode)
+ self.image = Image.core.display(mode, size)
+ self.mode = mode
+ self.size = size
+ if image:
+ self.paste(image)
+
+ ##
+ # Copy the bitmap contents to a device context.
+ #
+ # @param handle Device context (HDC), cast to a Python integer,
+ # or a HDC or HWND instance. In PythonWin, you can use the
+ # <b>GetHandleAttrib</b> method of the <b>CDC</b> class to get
+ # a suitable handle.
+
+ def expose(self, handle):
+ if isinstance(handle, HWND):
+ dc = self.image.getdc(handle)
+ try:
+ result = self.image.expose(dc)
+ finally:
+ self.image.releasedc(handle, dc)
+ else:
+ result = self.image.expose(handle)
+ return result
+
+ def draw(self, handle, dst, src=None):
+ if not src:
+ src = (0,0) + self.size
+ if isinstance(handle, HWND):
+ dc = self.image.getdc(handle)
+ try:
+ result = self.image.draw(dc, dst, src)
+ finally:
+ self.image.releasedc(handle, dc)
+ else:
+ result = self.image.draw(handle, dst, src)
+ return result
+
+ ##
+ # Installs the palette associated with the image in the
+ # given device context.
+ # <p>
+ # This method should be called upon <b>QUERYNEWPALETTE</b>
+ # and <b>PALETTECHANGED</b> events from Windows. If this
+ # method returns a non-zero value, one or more display
+ # palette entries were changed, and the image should be
+ # redrawn.
+ #
+ # @param handle Device context (HDC), cast to a Python integer,
+ # or an HDC or HWND instance.
+ # @return A true value if one or more entries were changed
+ # (this indicates that the image should be redrawn).
+
+ def query_palette(self, handle):
+ if isinstance(handle, HWND):
+ handle = self.image.getdc(handle)
+ try:
+ result = self.image.query_palette(handle)
+ finally:
+ self.image.releasedc(handle, handle)
+ else:
+ result = self.image.query_palette(handle)
+ return result
+
+ ##
+ # Paste a PIL image into the bitmap image.
+ #
+ # @param im A PIL image. The size must match the target region.
+ # If the mode does not match, the image is converted to the
+ # mode of the bitmap image.
+ # @param box A 4-tuple defining the left, upper, right, and
+ # lower pixel coordinate. If None is given instead of a
+ # tuple, all of the image is assumed.
+
+ def paste(self, im, box=None):
+ im.load()
+ if self.mode != im.mode:
+ im = im.convert(self.mode)
+ if box:
+ self.image.paste(im.im, box)
+ else:
+ self.image.paste(im.im)
+
+ ##
+ # Load display memory contents from string buffer.
+ #
+ # @param buffer A string buffer containing display data (usually
+ # data returned from <b>tostring</b>)
+
+ def fromstring(self, buffer):
+ return self.image.fromstring(buffer)
+
+ ##
+ # Copy display memory contents to string buffer.
+ #
+ # @return A string buffer containing display data.
+
+ def tostring(self):
+ return self.image.tostring()
+
+
+##
+# Create a Window with the given title size.
+
+class Window:
+
+ def __init__(self, title="PIL", width=None, height=None):
+ self.hwnd = Image.core.createwindow(
+ title, self.__dispatcher, width or 0, height or 0
+ )
+
+ def __dispatcher(self, action, *args):
+ return apply(getattr(self, "ui_handle_" + action), args)
+
+ def ui_handle_clear(self, dc, x0, y0, x1, y1):
+ pass
+
+ def ui_handle_damage(self, x0, y0, x1, y1):
+ pass
+
+ def ui_handle_destroy(self):
+ pass
+
+ def ui_handle_repair(self, dc, x0, y0, x1, y1):
+ pass
+
+ def ui_handle_resize(self, width, height):
+ pass
+
+ def mainloop(self):
+ Image.core.eventloop()
+
+##
+# Create an image window which displays the given image.
+
+class ImageWindow(Window):
+
+ def __init__(self, image, title="PIL"):
+ if not isinstance(image, Dib):
+ image = Dib(image)
+ self.image = image
+ width, height = image.size
+ Window.__init__(self, title, width=width, height=height)
+
+ def ui_handle_repair(self, dc, x0, y0, x1, y1):
+ self.image.draw(dc, (x0, y0, x1, y1))
diff --git a/PIL/ImtImagePlugin.py b/PIL/ImtImagePlugin.py
new file mode 100644
index 0000000..f321ae3
--- /dev/null
+++ b/PIL/ImtImagePlugin.py
@@ -0,0 +1,93 @@
+#
+# The Python Imaging Library.
+# $Id: ImtImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# IM Tools support for PIL
+#
+# history:
+# 1996-05-27 fl Created (read 8-bit images only)
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.2)
+#
+# Copyright (c) Secret Labs AB 1997-2001.
+# Copyright (c) Fredrik Lundh 1996-2001.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.2"
+
+import string, re
+
+import Image, ImageFile
+
+#
+# --------------------------------------------------------------------
+
+field = re.compile(r"([a-z]*) ([^ \r\n]*)")
+
+##
+# Image plugin for IM Tools images.
+
+class ImtImageFile(ImageFile.ImageFile):
+
+ format = "IMT"
+ format_description = "IM Tools"
+
+ def _open(self):
+
+ # Quick rejection: if there's not a LF among the first
+ # 100 bytes, this is (probably) not a text header.
+
+ if not "\n" in self.fp.read(100):
+ raise SyntaxError, "not an IM file"
+ self.fp.seek(0)
+
+ xsize = ysize = 0
+
+ while 1:
+
+ s = self.fp.read(1)
+ if not s:
+ break
+
+ if s == chr(12):
+
+ # image data begins
+ self.tile = [("raw", (0,0)+self.size,
+ self.fp.tell(),
+ (self.mode, 0, 1))]
+
+ break
+
+ else:
+
+ # read key/value pair
+ # FIXME: dangerous, may read whole file
+ s = s + self.fp.readline()
+ if len(s) == 1 or len(s) > 100:
+ break
+ if s[0] == "*":
+ continue # comment
+
+ m = field.match(s)
+ if not m:
+ break
+ k, v = m.group(1,2)
+ if k == "width":
+ xsize = int(v)
+ self.size = xsize, ysize
+ elif k == "height":
+ ysize = int(v)
+ self.size = xsize, ysize
+ elif k == "pixel" and v == "n8":
+ self.mode = "L"
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("IMT", ImtImageFile)
+
+#
+# no extension registered (".im" is simply too common)
diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py
new file mode 100644
index 0000000..3535759
--- /dev/null
+++ b/PIL/IptcImagePlugin.py
@@ -0,0 +1,280 @@
+#
+# The Python Imaging Library.
+# $Id: IptcImagePlugin.py 2813 2006-10-07 10:11:35Z fredrik $
+#
+# IPTC/NAA file handling
+#
+# history:
+# 1995-10-01 fl Created
+# 1998-03-09 fl Cleaned up and added to PIL
+# 2002-06-18 fl Added getiptcinfo helper
+#
+# Copyright (c) Secret Labs AB 1997-2002.
+# Copyright (c) Fredrik Lundh 1995.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.3"
+
+
+import Image, ImageFile
+import os, tempfile
+
+
+COMPRESSION = {
+ 1: "raw",
+ 5: "jpeg"
+}
+
+PAD = chr(0) * 4
+
+#
+# Helpers
+
+def i16(c):
+ return ord(c[1]) + (ord(c[0])<<8)
+
+def i32(c):
+ return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
+
+def i(c):
+ return i32((PAD + c)[-4:])
+
+def dump(c):
+ for i in c:
+ print "%02x" % ord(i),
+ print
+
+##
+# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
+# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
+
+class IptcImageFile(ImageFile.ImageFile):
+
+ format = "IPTC"
+ format_description = "IPTC/NAA"
+
+ def getint(self, key):
+ return i(self.info[key])
+
+ def field(self):
+ #
+ # get a IPTC field header
+ s = self.fp.read(5)
+ if not len(s):
+ return None, 0
+
+ tag = ord(s[1]), ord(s[2])
+
+ # syntax
+ if ord(s[0]) != 0x1C or tag[0] < 1 or tag[0] > 9:
+ raise SyntaxError, "invalid IPTC/NAA file"
+
+ # field size
+ size = ord(s[3])
+ if size > 132:
+ raise IOError, "illegal field length in IPTC/NAA file"
+ elif size == 128:
+ size = 0
+ elif size > 128:
+ size = i(self.fp.read(size-128))
+ else:
+ size = i16(s[3:])
+
+ return tag, size
+
+ def _is_raw(self, offset, size):
+ #
+ # check if the file can be mapped
+
+ # DISABLED: the following only slows things down...
+ return 0
+
+ self.fp.seek(offset)
+ t, sz = self.field()
+ if sz != size[0]:
+ return 0
+ y = 1
+ while 1:
+ self.fp.seek(sz, 1)
+ t, s = self.field()
+ if t != (8, 10):
+ break
+ if s != sz:
+ return 0
+ y = y + 1
+ return y == size[1]
+
+ def _open(self):
+
+ # load descriptive fields
+ while 1:
+ offset = self.fp.tell()
+ tag, size = self.field()
+ if not tag or tag == (8,10):
+ break
+ if size:
+ self.info[tag] = self.fp.read(size)
+ else:
+ self.info[tag] = None
+ # print tag, self.info[tag]
+
+ # mode
+ layers = ord(self.info[(3,60)][0])
+ component = ord(self.info[(3,60)][1])
+ if self.info.has_key((3,65)):
+ id = ord(self.info[(3,65)][0])-1
+ else:
+ id = 0
+ if layers == 1 and not component:
+ self.mode = "L"
+ elif layers == 3 and component:
+ self.mode = "RGB"[id]
+ elif layers == 4 and component:
+ self.mode = "CMYK"[id]
+
+ # size
+ self.size = self.getint((3,20)), self.getint((3,30))
+
+ # compression
+ try:
+ compression = COMPRESSION[self.getint((3,120))]
+ except KeyError:
+ raise IOError, "Unknown IPTC image compression"
+
+ # tile
+ if tag == (8,10):
+ if compression == "raw" and self._is_raw(offset, self.size):
+ self.tile = [(compression, (offset, size + 5, -1),
+ (0, 0, self.size[0], self.size[1]))]
+ else:
+ self.tile = [("iptc", (compression, offset),
+ (0, 0, self.size[0], self.size[1]))]
+
+ def load(self):
+
+ if len(self.tile) != 1 or self.tile[0][0] != "iptc":
+ return ImageFile.ImageFile.load(self)
+
+ type, tile, box = self.tile[0]
+
+ encoding, offset = tile
+
+ self.fp.seek(offset)
+
+ # Copy image data to temporary file
+ outfile = tempfile.mktemp()
+ o = open(outfile, "wb")
+ if encoding == "raw":
+ # To simplify access to the extracted file,
+ # prepend a PPM header
+ o.write("P5\n%d %d\n255\n" % self.size)
+ while 1:
+ type, size = self.field()
+ if type != (8, 10):
+ break
+ while size > 0:
+ s = self.fp.read(min(size, 8192))
+ if not s:
+ break
+ o.write(s)
+ size = size - len(s)
+ o.close()
+
+ try:
+ try:
+ # fast
+ self.im = Image.core.open_ppm(outfile)
+ except:
+ # slightly slower
+ im = Image.open(outfile)
+ im.load()
+ self.im = im.im
+ finally:
+ try: os.unlink(outfile)
+ except: pass
+
+
+Image.register_open("IPTC", IptcImageFile)
+
+Image.register_extension("IPTC", ".iim")
+
+##
+# Get IPTC information from TIFF, JPEG, or IPTC file.
+#
+# @param im An image containing IPTC data.
+# @return A dictionary containing IPTC information, or None if
+# no IPTC information block was found.
+
+def getiptcinfo(im):
+
+ import TiffImagePlugin, JpegImagePlugin
+ import StringIO
+
+ data = None
+
+ if isinstance(im, IptcImageFile):
+ # return info dictionary right away
+ return im.info
+
+ elif isinstance(im, JpegImagePlugin.JpegImageFile):
+ # extract the IPTC/NAA resource
+ try:
+ app = im.app["APP13"]
+ if app[:14] == "Photoshop 3.0\x00":
+ app = app[14:]
+ # parse the image resource block
+ offset = 0
+ while app[offset:offset+4] == "8BIM":
+ offset = offset + 4
+ # resource code
+ code = JpegImagePlugin.i16(app, offset)
+ offset = offset + 2
+ # resource name (usually empty)
+ name_len = ord(app[offset])
+ name = app[offset+1:offset+1+name_len]
+ offset = 1 + offset + name_len
+ if offset & 1:
+ offset = offset + 1
+ # resource data block
+ size = JpegImagePlugin.i32(app, offset)
+ offset = offset + 4
+ if code == 0x0404:
+ # 0x0404 contains IPTC/NAA data
+ data = app[offset:offset+size]
+ break
+ offset = offset + size
+ if offset & 1:
+ offset = offset + 1
+ except (AttributeError, KeyError):
+ pass
+
+ elif isinstance(im, TiffImagePlugin.TiffImageFile):
+ # get raw data from the IPTC/NAA tag (PhotoShop tags the data
+ # as 4-byte integers, so we cannot use the get method...)
+ try:
+ type, data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
+ except (AttributeError, KeyError):
+ pass
+
+ if data is None:
+ return None # no properties
+
+ # create an IptcImagePlugin object without initializing it
+ class FakeImage:
+ pass
+ im = FakeImage()
+ im.__class__ = IptcImageFile
+
+ # parse the IPTC information chunk
+ im.info = {}
+ im.fp = StringIO.StringIO(data)
+
+ try:
+ im._open()
+ except (IndexError, KeyError):
+ pass # expected failure
+
+ return im.info
diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py
new file mode 100644
index 0000000..b8ba8e4
--- /dev/null
+++ b/PIL/JpegImagePlugin.py
@@ -0,0 +1,430 @@
+#
+# The Python Imaging Library.
+# $Id: JpegImagePlugin.py 2763 2006-06-22 21:43:28Z fredrik $
+#
+# JPEG (JFIF) file handling
+#
+# See "Digital Compression and Coding of Continous-Tone Still Images,
+# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
+#
+# History:
+# 1995-09-09 fl Created
+# 1995-09-13 fl Added full parser
+# 1996-03-25 fl Added hack to use the IJG command line utilities
+# 1996-05-05 fl Workaround Photoshop 2.5 CMYK polarity bug
+# 1996-05-28 fl Added draft support, JFIF version (0.1)
+# 1996-12-30 fl Added encoder options, added progression property (0.2)
+# 1997-08-27 fl Save mode 1 images as BW (0.3)
+# 1998-07-12 fl Added YCbCr to draft and save methods (0.4)
+# 1998-10-19 fl Don't hang on files using 16-bit DQT's (0.4.1)
+# 2001-04-16 fl Extract DPI settings from JFIF files (0.4.2)
+# 2002-07-01 fl Skip pad bytes before markers; identify Exif files (0.4.3)
+# 2003-04-25 fl Added experimental EXIF decoder (0.5)
+# 2003-06-06 fl Added experimental EXIF GPSinfo decoder
+# 2003-09-13 fl Extract COM markers
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-1996 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.5"
+
+import array, string
+import Image, ImageFile
+
+def i16(c,o=0):
+ return ord(c[o+1]) + (ord(c[o])<<8)
+
+def i32(c,o=0):
+ return ord(c[o+3]) + (ord(c[o+2])<<8) + (ord(c[o+1])<<16) + (ord(c[o])<<24)
+
+#
+# Parser
+
+def Skip(self, marker):
+ n = i16(self.fp.read(2))-2
+ ImageFile._safe_read(self.fp, n)
+
+def APP(self, marker):
+ #
+ # Application marker. Store these in the APP dictionary.
+ # Also look for well-known application markers.
+
+ n = i16(self.fp.read(2))-2
+ s = ImageFile._safe_read(self.fp, n)
+
+ app = "APP%d" % (marker&15)
+
+ self.app[app] = s # compatibility
+ self.applist.append((app, s))
+
+ if marker == 0xFFE0 and s[:4] == "JFIF":
+ # extract JFIF information
+ self.info["jfif"] = version = i16(s, 5) # version
+ self.info["jfif_version"] = divmod(version, 256)
+ # extract JFIF properties
+ try:
+ jfif_unit = ord(s[7])
+ jfif_density = i16(s, 8), i16(s, 10)
+ except:
+ pass
+ else:
+ if jfif_unit == 1:
+ self.info["dpi"] = jfif_density
+ self.info["jfif_unit"] = jfif_unit
+ self.info["jfif_density"] = jfif_density
+ elif marker == 0xFFE1 and s[:5] == "Exif\0":
+ # extract Exif information (incomplete)
+ self.info["exif"] = s # FIXME: value will change
+ elif marker == 0xFFE2 and s[:5] == "FPXR\0":
+ # extract FlashPix information (incomplete)
+ self.info["flashpix"] = s # FIXME: value will change
+ elif marker == 0xFFEE and s[:5] == "Adobe":
+ self.info["adobe"] = i16(s, 5)
+ # extract Adobe custom properties
+ try:
+ adobe_transform = ord(s[1])
+ except:
+ pass
+ else:
+ self.info["adobe_transform"] = adobe_transform
+
+def COM(self, marker):
+ #
+ # Comment marker. Store these in the APP dictionary.
+
+ n = i16(self.fp.read(2))-2
+ s = ImageFile._safe_read(self.fp, n)
+
+ self.app["COM"] = s # compatibility
+ self.applist.append(("COM", s))
+
+def SOF(self, marker):
+ #
+ # Start of frame marker. Defines the size and mode of the
+ # image. JPEG is colour blind, so we use some simple
+ # heuristics to map the number of layers to an appropriate
+ # mode. Note that this could be made a bit brighter, by
+ # looking for JFIF and Adobe APP markers.
+
+ n = i16(self.fp.read(2))-2
+ s = ImageFile._safe_read(self.fp, n)
+ self.size = i16(s[3:]), i16(s[1:])
+
+ self.bits = ord(s[0])
+ if self.bits != 8:
+ raise SyntaxError("cannot handle %d-bit layers" % self.bits)
+
+ self.layers = ord(s[5])
+ if self.layers == 1:
+ self.mode = "L"
+ elif self.layers == 3:
+ self.mode = "RGB"
+ elif self.layers == 4:
+ self.mode = "CMYK"
+ else:
+ raise SyntaxError("cannot handle %d-layer images" % self.layers)
+
+ if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:
+ self.info["progression"] = 1
+
+ for i in range(6, len(s), 3):
+ t = s[i:i+3]
+ # 4-tuples: id, vsamp, hsamp, qtable
+ self.layer.append((t[0], ord(t[1])/16, ord(t[1])&15, ord(t[2])))
+
+def DQT(self, marker):
+ #
+ # Define quantization table. Support baseline 8-bit tables
+ # only. Note that there might be more than one table in
+ # each marker.
+
+ # FIXME: The quantization tables can be used to estimate the
+ # compression quality.
+
+ n = i16(self.fp.read(2))-2
+ s = ImageFile._safe_read(self.fp, n)
+ while len(s):
+ if len(s) < 65:
+ raise SyntaxError("bad quantization table marker")
+ v = ord(s[0])
+ if v/16 == 0:
+ self.quantization[v&15] = array.array("b", s[1:65])
+ s = s[65:]
+ else:
+ return # FIXME: add code to read 16-bit tables!
+ # raise SyntaxError, "bad quantization table element size"
+
+
+#
+# JPEG marker table
+
+MARKER = {
+ 0xFFC0: ("SOF0", "Baseline DCT", SOF),
+ 0xFFC1: ("SOF1", "Extended Sequential DCT", SOF),
+ 0xFFC2: ("SOF2", "Progressive DCT", SOF),
+ 0xFFC3: ("SOF3", "Spatial lossless", SOF),
+ 0xFFC4: ("DHT", "Define Huffman table", Skip),
+ 0xFFC5: ("SOF5", "Differential sequential DCT", SOF),
+ 0xFFC6: ("SOF6", "Differential progressive DCT", SOF),
+ 0xFFC7: ("SOF7", "Differential spatial", SOF),
+ 0xFFC8: ("JPG", "Extension", None),
+ 0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF),
+ 0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF),
+ 0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF),
+ 0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip),
+ 0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF),
+ 0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF),
+ 0xFFCF: ("SOF15", "Differential spatial (AC)", SOF),
+ 0xFFD0: ("RST0", "Restart 0", None),
+ 0xFFD1: ("RST1", "Restart 1", None),
+ 0xFFD2: ("RST2", "Restart 2", None),
+ 0xFFD3: ("RST3", "Restart 3", None),
+ 0xFFD4: ("RST4", "Restart 4", None),
+ 0xFFD5: ("RST5", "Restart 5", None),
+ 0xFFD6: ("RST6", "Restart 6", None),
+ 0xFFD7: ("RST7", "Restart 7", None),
+ 0xFFD8: ("SOI", "Start of image", None),
+ 0xFFD9: ("EOI", "End of image", None),
+ 0xFFDA: ("SOS", "Start of scan", Skip),
+ 0xFFDB: ("DQT", "Define quantization table", DQT),
+ 0xFFDC: ("DNL", "Define number of lines", Skip),
+ 0xFFDD: ("DRI", "Define restart interval", Skip),
+ 0xFFDE: ("DHP", "Define hierarchical progression", SOF),
+ 0xFFDF: ("EXP", "Expand reference component", Skip),
+ 0xFFE0: ("APP0", "Application segment 0", APP),
+ 0xFFE1: ("APP1", "Application segment 1", APP),
+ 0xFFE2: ("APP2", "Application segment 2", APP),
+ 0xFFE3: ("APP3", "Application segment 3", APP),
+ 0xFFE4: ("APP4", "Application segment 4", APP),
+ 0xFFE5: ("APP5", "Application segment 5", APP),
+ 0xFFE6: ("APP6", "Application segment 6", APP),
+ 0xFFE7: ("APP7", "Application segment 7", APP),
+ 0xFFE8: ("APP8", "Application segment 8", APP),
+ 0xFFE9: ("APP9", "Application segment 9", APP),
+ 0xFFEA: ("APP10", "Application segment 10", APP),
+ 0xFFEB: ("APP11", "Application segment 11", APP),
+ 0xFFEC: ("APP12", "Application segment 12", APP),
+ 0xFFED: ("APP13", "Application segment 13", APP),
+ 0xFFEE: ("APP14", "Application segment 14", APP),
+ 0xFFEF: ("APP15", "Application segment 15", APP),
+ 0xFFF0: ("JPG0", "Extension 0", None),
+ 0xFFF1: ("JPG1", "Extension 1", None),
+ 0xFFF2: ("JPG2", "Extension 2", None),
+ 0xFFF3: ("JPG3", "Extension 3", None),
+ 0xFFF4: ("JPG4", "Extension 4", None),
+ 0xFFF5: ("JPG5", "Extension 5", None),
+ 0xFFF6: ("JPG6", "Extension 6", None),
+ 0xFFF7: ("JPG7", "Extension 7", None),
+ 0xFFF8: ("JPG8", "Extension 8", None),
+ 0xFFF9: ("JPG9", "Extension 9", None),
+ 0xFFFA: ("JPG10", "Extension 10", None),
+ 0xFFFB: ("JPG11", "Extension 11", None),
+ 0xFFFC: ("JPG12", "Extension 12", None),
+ 0xFFFD: ("JPG13", "Extension 13", None),
+ 0xFFFE: ("COM", "Comment", COM)
+}
+
+
+def _accept(prefix):
+ return prefix[0] == "\377"
+
+##
+# Image plugin for JPEG and JFIF images.
+
+class JpegImageFile(ImageFile.ImageFile):
+
+ format = "JPEG"
+ format_description = "JPEG (ISO 10918)"
+
+ def _open(self):
+
+ s = self.fp.read(1)
+
+ if ord(s[0]) != 255:
+ raise SyntaxError("not a JPEG file")
+
+ # Create attributes
+ self.bits = self.layers = 0
+
+ # JPEG specifics (internal)
+ self.layer = []
+ self.huffman_dc = {}
+ self.huffman_ac = {}
+ self.quantization = {}
+ self.app = {} # compatibility
+ self.applist = []
+
+ while 1:
+
+ s = s + self.fp.read(1)
+
+ i = i16(s)
+
+ if MARKER.has_key(i):
+ name, description, handler = MARKER[i]
+ # print hex(i), name, description
+ if handler is not None:
+ handler(self, i)
+ if i == 0xFFDA: # start of scan
+ rawmode = self.mode
+ if self.mode == "CMYK":
+ rawmode = "CMYK;I"
+ self.tile = [("jpeg", (0,0) + self.size, 0, (rawmode, ""))]
+ # self.__offset = self.fp.tell()
+ break
+ s = self.fp.read(1)
+ elif i == 0 or i == 65535:
+ # padded marker or junk; move on
+ s = "\xff"
+ else:
+ raise SyntaxError("no marker found")
+
+ def draft(self, mode, size):
+
+ if len(self.tile) != 1:
+ return
+
+ d, e, o, a = self.tile[0]
+ scale = 0
+
+ if a[0] == "RGB" and mode in ["L", "YCbCr"]:
+ self.mode = mode
+ a = mode, ""
+
+ if size:
+ scale = max(self.size[0] / size[0], self.size[1] / size[1])
+ for s in [8, 4, 2, 1]:
+ if scale >= s:
+ break
+ e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1]
+ self.size = ((self.size[0]+s-1)/s, (self.size[1]+s-1)/s)
+ scale = s
+
+ self.tile = [(d, e, o, a)]
+ self.decoderconfig = (scale, 1)
+
+ return self
+
+ def load_djpeg(self):
+
+ # ALTERNATIVE: handle JPEGs via the IJG command line utilities
+
+ import tempfile, os
+ file = tempfile.mktemp()
+ os.system("djpeg %s >%s" % (self.filename, file))
+
+ try:
+ self.im = Image.core.open_ppm(file)
+ finally:
+ try: os.unlink(file)
+ except: pass
+
+ self.mode = self.im.mode
+ self.size = self.im.size
+
+ self.tile = []
+
+ def _getexif(self):
+ # Extract EXIF information. This method is highly experimental,
+ # and is likely to be replaced with something better in a future
+ # version.
+ import TiffImagePlugin, StringIO
+ def fixup(value):
+ if len(value) == 1:
+ return value[0]
+ return value
+ # The EXIF record consists of a TIFF file embedded in a JPEG
+ # application marker (!).
+ try:
+ data = self.info["exif"]
+ except KeyError:
+ return None
+ file = StringIO.StringIO(data[6:])
+ head = file.read(8)
+ exif = {}
+ # process dictionary
+ info = TiffImagePlugin.ImageFileDirectory(head)
+ info.load(file)
+ for key, value in info.items():
+ exif[key] = fixup(value)
+ # get exif extension
+ file.seek(exif[0x8769])
+ info = TiffImagePlugin.ImageFileDirectory(head)
+ info.load(file)
+ for key, value in info.items():
+ exif[key] = fixup(value)
+ # get gpsinfo extension
+ try:
+ file.seek(exif[0x8825])
+ except KeyError:
+ pass
+ else:
+ info = TiffImagePlugin.ImageFileDirectory(head)
+ info.load(file)
+ exif[0x8825] = gps = {}
+ for key, value in info.items():
+ gps[key] = fixup(value)
+ return exif
+
+# --------------------------------------------------------------------
+# stuff to save JPEG files
+
+RAWMODE = {
+ "1": "L",
+ "L": "L",
+ "RGB": "RGB",
+ "RGBA": "RGB",
+ "RGBX": "RGB",
+ "CMYK": "CMYK;I",
+ "YCbCr": "YCbCr",
+}
+
+def _save(im, fp, filename):
+
+ try:
+ rawmode = RAWMODE[im.mode]
+ except KeyError:
+ raise IOError("cannot write mode %s as JPEG" % im.mode)
+
+ info = im.encoderinfo
+
+ dpi = info.get("dpi", (0, 0))
+
+ # get keyword arguments
+ im.encoderconfig = (
+ info.get("quality", 0),
+ # "progressive" is the official name, but older documentation
+ # says "progression"
+ # FIXME: issue a warning if the wrong form is used (post-1.1.5)
+ info.has_key("progressive") or info.has_key("progression"),
+ info.get("smooth", 0),
+ info.has_key("optimize"),
+ info.get("streamtype", 0),
+ dpi[0], dpi[1]
+ )
+
+ ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)])
+
+def _save_cjpeg(im, fp, filename):
+ # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
+ import os
+ file = im._dump()
+ os.system("cjpeg %s >%s" % (file, filename))
+ try: os.unlink(file)
+ except: pass
+
+# -------------------------------------------------------------------q-
+# Registry stuff
+
+Image.register_open("JPEG", JpegImageFile, _accept)
+Image.register_save("JPEG", _save)
+
+Image.register_extension("JPEG", ".jfif")
+Image.register_extension("JPEG", ".jpe")
+Image.register_extension("JPEG", ".jpg")
+Image.register_extension("JPEG", ".jpeg")
+
+Image.register_mime("JPEG", "image/jpeg")
diff --git a/PIL/McIdasImagePlugin.py b/PIL/McIdasImagePlugin.py
new file mode 100644
index 0000000..b3b48ec
--- /dev/null
+++ b/PIL/McIdasImagePlugin.py
@@ -0,0 +1,74 @@
+#
+# The Python Imaging Library.
+# $Id: McIdasImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# Basic McIdas support for PIL
+#
+# History:
+# 97-05-05 fl Created (8-bit images only)
+#
+# Thanks to Richard Jones <richard.jones@bom.gov.au> for specs
+# and samples.
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.1"
+
+import string
+
+import Image, ImageFile
+
+def i16(c,i=0):
+ return ord(c[1+i])+(ord(c[i])<<8)
+
+def i32(c,i=0):
+ return ord(c[3+i])+(ord(c[2+i])<<8)+(ord(c[1+i])<<16)+(ord(c[i])<<24)
+
+def _accept(s):
+ return i32(s) == 0 and i32(s, 4) == 4
+
+##
+# Image plugin for McIdas area images.
+
+class McIdasImageFile(ImageFile.ImageFile):
+
+ format = "MCIDAS"
+ format_description = "McIdas area file"
+
+ def _open(self):
+
+ # parse area file directory
+ s = self.fp.read(256)
+ if not _accept(s):
+ raise SyntaxError, "not an McIdas area file"
+
+ # get mode
+ if i32(s, 40) != 1 or i32(s, 52) != 1:
+ raise SyntaxError, "unsupported McIdas format"
+
+ self.mode = "L"
+
+ # get size
+ self.size = i32(s, 36), i32(s, 32)
+
+ # setup image descriptor
+ prefix = i32(s, 56)
+ offset = i32(s, 132)
+
+ self.tile = [("raw", (0, 0) + self.size, offset,
+ ("L", prefix + self.size[0], 1))]
+
+ # FIXME: should store the navigation and calibration blocks
+ # somewhere (or perhaps extract some basic information from
+ # them...)
+
+# --------------------------------------------------------------------
+# registry
+
+Image.register_open("MCIDAS", McIdasImageFile, _accept)
+
+# no default extension
diff --git a/PIL/MicImagePlugin.py b/PIL/MicImagePlugin.py
new file mode 100644
index 0000000..b7b108e
--- /dev/null
+++ b/PIL/MicImagePlugin.py
@@ -0,0 +1,96 @@
+#
+# The Python Imaging Library.
+# $Id: MicImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# Microsoft Image Composer support for PIL
+#
+# Notes:
+# uses TiffImagePlugin.py to read the actual image streams
+#
+# History:
+# 97-01-20 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+import string
+
+import Image, TiffImagePlugin
+from OleFileIO import *
+
+
+#
+# --------------------------------------------------------------------
+
+
+def _accept(prefix):
+ return prefix[:8] == MAGIC
+
+##
+# Image plugin for Microsoft's Image Composer file format.
+
+class MicImageFile(TiffImagePlugin.TiffImageFile):
+
+ format = "MIC"
+ format_description = "Microsoft Image Composer"
+
+ def _open(self):
+
+ # read the OLE directory and see if this is a likely
+ # to be a Microsoft Image Composer file
+
+ try:
+ self.ole = OleFileIO(self.fp)
+ except IOError:
+ raise SyntaxError, "not an MIC file; invalid OLE file"
+
+ # find ACI subfiles with Image members (maybe not the
+ # best way to identify MIC files, but what the... ;-)
+
+ self.images = []
+ for file in self.ole.listdir():
+ if file[1:] and file[0][-4:] == ".ACI" and file[1] == "Image":
+ self.images.append(file)
+
+ # if we didn't find any images, this is probably not
+ # an MIC file.
+ if not self.images:
+ raise SyntaxError, "not an MIC file; no image entries"
+
+ self.__fp = self.fp
+ self.frame = 0
+
+ if len(self.images) > 1:
+ self.category = Image.CONTAINER
+
+ self.seek(0)
+
+ def seek(self, frame):
+
+ try:
+ filename = self.images[frame]
+ except IndexError:
+ raise EOFError, "no such frame"
+
+ self.fp = self.ole.openstream(filename)
+
+ TiffImagePlugin.TiffImageFile._open(self)
+
+ self.frame = frame
+
+ def tell(self):
+
+ return self.frame
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("MIC", MicImageFile, _accept)
+
+Image.register_extension("MIC", ".mic")
diff --git a/PIL/MpegImagePlugin.py b/PIL/MpegImagePlugin.py
new file mode 100644
index 0000000..9623c7a
--- /dev/null
+++ b/PIL/MpegImagePlugin.py
@@ -0,0 +1,83 @@
+#
+# The Python Imaging Library.
+# $Id: MpegImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# MPEG file handling
+#
+# History:
+# 95-09-09 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1995.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.1"
+
+import array, string
+import Image, ImageFile
+
+#
+# Bitstream parser
+
+class BitStream:
+
+ def __init__(self, fp):
+ self.fp = fp
+ self.bits = 0
+ self.bitbuffer = 0
+
+ def next(self):
+ return ord(self.fp.read(1))
+
+ def peek(self, bits):
+ while self.bits < bits:
+ c = self.next()
+ if c < 0:
+ self.bits = 0
+ continue
+ self.bitbuffer = (self.bitbuffer << 8) + c
+ self.bits = self.bits + 8
+ return self.bitbuffer >> (self.bits - bits) & (1L << bits) - 1
+
+ def skip(self, bits):
+ while self.bits < bits:
+ self.bitbuffer = (self.bitbuffer << 8) + ord(self.fp.read(1))
+ self.bits = self.bits + 8
+ self.bits = self.bits - bits
+
+ def read(self, bits):
+ v = self.peek(bits)
+ self.bits = self.bits - bits
+ return v
+
+##
+# Image plugin for MPEG streams. This plugin can identify a stream,
+# but it cannot read it.
+
+class MpegImageFile(ImageFile.ImageFile):
+
+ format = "MPEG"
+ format_description = "MPEG"
+
+ def _open(self):
+
+ s = BitStream(self.fp)
+
+ if s.read(32) != 0x1B3:
+ raise SyntaxError, "not an MPEG file"
+
+ self.mode = "RGB"
+ self.size = s.read(12), s.read(12)
+
+
+# --------------------------------------------------------------------
+# Registry stuff
+
+Image.register_open("MPEG", MpegImageFile)
+
+Image.register_extension("MPEG", ".mpg")
+Image.register_extension("MPEG", ".mpeg")
+
+Image.register_mime("MPEG", "video/mpeg")
diff --git a/PIL/MspImagePlugin.py b/PIL/MspImagePlugin.py
new file mode 100644
index 0000000..40aac19
--- /dev/null
+++ b/PIL/MspImagePlugin.py
@@ -0,0 +1,103 @@
+#
+# The Python Imaging Library.
+# $Id: MspImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# MSP file handling
+#
+# This is the format used by the Paint program in Windows 1 and 2.
+#
+# History:
+# 95-09-05 fl Created
+# 97-01-03 fl Read/write MSP images
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1995-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+import Image, ImageFile
+
+
+#
+# read MSP files
+
+def i16(c):
+ return ord(c[0]) + (ord(c[1])<<8)
+
+def _accept(prefix):
+ return prefix[:4] in ["DanM", "LinS"]
+
+##
+# Image plugin for Windows MSP images. This plugin supports both
+# uncompressed (Windows 1.0).
+
+class MspImageFile(ImageFile.ImageFile):
+
+ format = "MSP"
+ format_description = "Windows Paint"
+
+ def _open(self):
+
+ # Header
+ s = self.fp.read(32)
+ if s[:4] not in ["DanM", "LinS"]:
+ raise SyntaxError, "not an MSP file"
+
+ # Header checksum
+ sum = 0
+ for i in range(0, 32, 2):
+ sum = sum ^ i16(s[i:i+2])
+ if sum != 0:
+ raise SyntaxError, "bad MSP checksum"
+
+ self.mode = "1"
+ self.size = i16(s[4:]), i16(s[6:])
+
+ if s[:4] == "DanM":
+ self.tile = [("raw", (0,0)+self.size, 32, ("1", 0, 1))]
+ else:
+ self.tile = [("msp", (0,0)+self.size, 32+2*self.size[1], None)]
+
+#
+# write MSP files (uncompressed only)
+
+def o16(i):
+ return chr(i&255) + chr(i>>8&255)
+
+def _save(im, fp, filename):
+
+ if im.mode != "1":
+ raise IOError, "cannot write mode %s as MSP" % im.mode
+
+ # create MSP header
+ header = [0] * 16
+
+ header[0], header[1] = i16("Da"), i16("nM") # version 1
+ header[2], header[3] = im.size
+ header[4], header[5] = 1, 1
+ header[6], header[7] = 1, 1
+ header[8], header[9] = im.size
+
+ sum = 0
+ for h in header:
+ sum = sum ^ h
+ header[12] = sum # FIXME: is this the right field?
+
+ # header
+ for h in header:
+ fp.write(o16(h))
+
+ # image body
+ ImageFile._save(im, fp, [("raw", (0,0)+im.size, 32, ("1", 0, 1))])
+
+#
+# registry
+
+Image.register_open("MSP", MspImageFile, _accept)
+Image.register_save("MSP", _save)
+
+Image.register_extension("MSP", ".msp")
diff --git a/PIL/OleFileIO.py b/PIL/OleFileIO.py
new file mode 100644
index 0000000..9383eca
--- /dev/null
+++ b/PIL/OleFileIO.py
@@ -0,0 +1,528 @@
+#
+# THIS IS WORK IN PROGRESS
+#
+# The Python Imaging Library
+# $Id: OleFileIO.py 2339 2005-03-25 08:02:17Z fredrik $
+#
+# stuff to deal with OLE2 Structured Storage files. this module is
+# used by PIL to read Image Composer and FlashPix files, but can also
+# be used to read other files of this type.
+#
+# History:
+# 1997-01-20 fl Created
+# 1997-01-22 fl Fixed 64-bit portability quirk
+# 2003-09-09 fl Fixed typo in OleFileIO.loadfat (noted by Daniel Haertle)
+# 2004-02-29 fl Changed long hex constants to signed integers
+#
+# Notes:
+# FIXME: sort out sign problem (eliminate long hex constants)
+# FIXME: change filename to use "a/b/c" instead of ["a", "b", "c"]
+# FIXME: provide a glob mechanism function (using fnmatchcase)
+#
+# Literature:
+#
+# "FlashPix Format Specification, Appendix A", Kodak and Microsoft,
+# September 1996.
+#
+# Quotes:
+#
+# "If this document and functionality of the Software conflict,
+# the actual functionality of the Software represents the correct
+# functionality" -- Microsoft, in the OLE format specification
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import string, StringIO
+
+
+def i16(c, o = 0):
+ return ord(c[o])+(ord(c[o+1])<<8)
+
+def i32(c, o = 0):
+ return ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24)
+
+
+MAGIC = '\320\317\021\340\241\261\032\341'
+
+#
+# --------------------------------------------------------------------
+# property types
+
+VT_EMPTY=0; VT_NULL=1; VT_I2=2; VT_I4=3; VT_R4=4; VT_R8=5; VT_CY=6;
+VT_DATE=7; VT_BSTR=8; VT_DISPATCH=9; VT_ERROR=10; VT_BOOL=11;
+VT_VARIANT=12; VT_UNKNOWN=13; VT_DECIMAL=14; VT_I1=16; VT_UI1=17;
+VT_UI2=18; VT_UI4=19; VT_I8=20; VT_UI8=21; VT_INT=22; VT_UINT=23;
+VT_VOID=24; VT_HRESULT=25; VT_PTR=26; VT_SAFEARRAY=27; VT_CARRAY=28;
+VT_USERDEFINED=29; VT_LPSTR=30; VT_LPWSTR=31; VT_FILETIME=64;
+VT_BLOB=65; VT_STREAM=66; VT_STORAGE=67; VT_STREAMED_OBJECT=68;
+VT_STORED_OBJECT=69; VT_BLOB_OBJECT=70; VT_CF=71; VT_CLSID=72;
+VT_VECTOR=0x1000;
+
+# map property id to name (for debugging purposes)
+
+VT = {}
+for k, v in vars().items():
+ if k[:3] == "VT_":
+ VT[v] = k
+
+#
+# --------------------------------------------------------------------
+# Some common document types (root.clsid fields)
+
+WORD_CLSID = "00020900-0000-0000-C000-000000000046"
+
+
+#
+# --------------------------------------------------------------------
+
+class _OleStream(StringIO.StringIO):
+
+ """OLE2 Stream
+
+ Returns a read-only file object which can be used to read
+ the contents of a OLE stream. To open a stream, use the
+ openstream method in the OleFile class.
+
+ This function can be used with either ordinary streams,
+ or ministreams, depending on the offset, sectorsize, and
+ fat table arguments.
+ """
+
+ # FIXME: should store the list of sects obtained by following
+ # the fat chain, and load new sectors on demand instead of
+ # loading it all in one go.
+
+ def __init__(self, fp, sect, size, offset, sectorsize, fat):
+
+ data = []
+
+ while sect != -2: # 0xFFFFFFFEL:
+ fp.seek(offset + sectorsize * sect)
+ data.append(fp.read(sectorsize))
+ sect = fat[sect]
+
+ data = string.join(data, "")
+
+ # print len(data), size
+
+ StringIO.StringIO.__init__(self, data[:size])
+
+#
+# --------------------------------------------------------------------
+
+# FIXME: should add a counter in here to avoid looping forever
+# if the tree is broken.
+
+class _OleDirectoryEntry:
+
+ """OLE2 Directory Entry
+
+ Encapsulates a stream directory entry. Note that the
+ constructor builds a tree of all subentries, so we only
+ have to call it with the root object.
+ """
+
+ def __init__(self, sidlist, sid):
+
+ # store directory parameters. the caller provides
+ # a complete list of directory entries, as read from
+ # the directory stream.
+
+ name, type, sect, size, sids, clsid = sidlist[sid]
+
+ self.sid = sid
+ self.name = name
+ self.type = type # 1=storage 2=stream
+ self.sect = sect
+ self.size = size
+ self.clsid = clsid
+
+ # process child nodes, if any
+
+ self.kids = []
+
+ sid = sidlist[sid][4][2]
+
+ if sid != -1:
+
+ # the directory entries are organized as a red-black tree.
+ # the following piece of code does an ordered traversal of
+ # such a tree (at least that's what I hope ;-)
+
+ stack = [self.sid]
+
+ # start at leftmost position
+
+ left, right, child = sidlist[sid][4]
+
+ while left != -1: # 0xFFFFFFFFL:
+ stack.append(sid)
+ sid = left
+ left, right, child = sidlist[sid][4]
+
+ while sid != self.sid:
+
+ self.kids.append(_OleDirectoryEntry(sidlist, sid))
+
+ # try to move right
+ left, right, child = sidlist[sid][4]
+ if right != -1: # 0xFFFFFFFFL:
+ # and then back to the left
+ sid = right
+ while 1:
+ left, right, child = sidlist[sid][4]
+ if left == -1: # 0xFFFFFFFFL:
+ break
+ stack.append(sid)
+ sid = left
+ else:
+ # couldn't move right; move up instead
+ while 1:
+ ptr = stack[-1]
+ del stack[-1]
+ left, right, child = sidlist[ptr][4]
+ if right != sid:
+ break
+ sid = right
+ left, right, child = sidlist[sid][4]
+ if right != ptr:
+ sid = ptr
+
+ # in the OLE file, entries are sorted on (length, name).
+ # for convenience, we sort them on name instead.
+
+ self.kids.sort()
+
+ def __cmp__(self, other):
+ "Compare entries by name"
+
+ return cmp(self.name, other.name)
+
+ def dump(self, tab = 0):
+ "Dump this entry, and all its subentries (for debug purposes only)"
+
+ TYPES = ["(invalid)", "(storage)", "(stream)", "(lockbytes)",
+ "(property)", "(root)"]
+
+ print " "*tab + repr(self.name), TYPES[self.type],
+ if self.type in (2, 5):
+ print self.size, "bytes",
+ print
+ if self.type in (1, 5) and self.clsid:
+ print " "*tab + "{%s}" % self.clsid
+
+ for kid in self.kids:
+ kid.dump(tab + 2)
+
+#
+# --------------------------------------------------------------------
+
+##
+# This class encapsulates the interface to an OLE 2 structured
+# storage file. Use the {@link listdir} and {@link openstream}
+# methods to access the contents of this file.
+
+class OleFileIO:
+ """OLE container object
+
+ This class encapsulates the interface to an OLE 2 structured
+ storage file. Use the listdir and openstream methods to access
+ the contents of this file.
+
+ Object names are given as a list of strings, one for each subentry
+ level. The root entry should be omitted. For example, the following
+ code extracts all image streams from a Microsoft Image Composer file:
+
+ ole = OleFileIO("fan.mic")
+
+ for entry in ole.listdir():
+ if entry[1:2] == "Image":
+ fin = ole.openstream(entry)
+ fout = open(entry[0:1], "wb")
+ while 1:
+ s = fin.read(8192)
+ if not s:
+ break
+ fout.write(s)
+
+ You can use the viewer application provided with the Python Imaging
+ Library to view the resulting files (which happens to be standard
+ TIFF files).
+ """
+
+ def __init__(self, filename = None):
+
+ if filename:
+ self.open(filename)
+
+ ##
+ # Open an OLE2 file.
+
+ def open(self, filename):
+ """Open an OLE2 file"""
+
+ if type(filename) == type(""):
+ self.fp = open(filename, "rb")
+ else:
+ self.fp = filename
+
+ header = self.fp.read(512)
+
+ if len(header) != 512 or header[:8] != MAGIC:
+ raise IOError, "not an OLE2 structured storage file"
+
+ # file clsid (probably never used, so we don't store it)
+ clsid = self._clsid(header[8:24])
+
+ # FIXME: could check version and byte order fields
+
+ self.sectorsize = 1 << i16(header, 30)
+ self.minisectorsize = 1 << i16(header, 32)
+
+ self.minisectorcutoff = i32(header, 56)
+
+ # Load file allocation tables
+ self.loadfat(header)
+
+ # Load direcory. This sets both the sidlist (ordered by id)
+ # and the root (ordered by hierarchy) members.
+ self.loaddirectory(i32(header, 48))
+
+ self.ministream = None
+ self.minifatsect = i32(header, 60)
+
+ def loadfat(self, header):
+ # Load the FAT table. The header contains a sector numbers
+ # for the first 109 FAT sectors. Additional sectors are
+ # described by DIF blocks (FIXME: not yet implemented)
+
+ sect = header[76:512]
+ fat = []
+ for i in range(0, len(sect), 4):
+ ix = i32(sect, i)
+ if ix == -2 or ix == -1: # ix == 0xFFFFFFFEL or ix == 0xFFFFFFFFL:
+ break
+ s = self.getsect(ix)
+ fat = fat + map(lambda i, s=s: i32(s, i), range(0, len(s), 4))
+ self.fat = fat
+
+ def loadminifat(self):
+ # Load the MINIFAT table. This is stored in a standard sub-
+ # stream, pointed to by a header field.
+
+ s = self._open(self.minifatsect).read()
+
+ self.minifat = map(lambda i, s=s: i32(s, i), range(0, len(s), 4))
+
+ def getsect(self, sect):
+ # Read given sector
+
+ self.fp.seek(512 + self.sectorsize * sect)
+ return self.fp.read(self.sectorsize)
+
+ def _unicode(self, s):
+ # Map unicode string to Latin 1
+
+ # FIXME: some day, Python will provide an official way to handle
+ # Unicode strings, but until then, this will have to do...
+ return filter(ord, s)
+
+ def loaddirectory(self, sect):
+ # Load the directory. The directory is stored in a standard
+ # substream, independent of its size.
+
+ # read directory stream
+ fp = self._open(sect)
+
+ # create list of sid entries
+ self.sidlist = []
+ while 1:
+ entry = fp.read(128)
+ if not entry:
+ break
+ type = ord(entry[66])
+ name = self._unicode(entry[0:0+i16(entry, 64)])
+ ptrs = i32(entry, 68), i32(entry, 72), i32(entry, 76)
+ sect, size = i32(entry, 116), i32(entry, 120)
+ clsid = self._clsid(entry[80:96])
+ self.sidlist.append((name, type, sect, size, ptrs, clsid))
+
+ # create hierarchical list of directory entries
+ self.root = _OleDirectoryEntry(self.sidlist, 0)
+
+ def dumpdirectory(self):
+ # Dump directory (for debugging only)
+
+ self.root.dump()
+
+ def _clsid(self, clsid):
+ if clsid == "\0" * len(clsid):
+ return ""
+ return (("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) %
+ ((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) +
+ tuple(map(ord, clsid[8:16]))))
+
+ def _list(self, files, prefix, node):
+ # listdir helper
+
+ prefix = prefix + [node.name]
+ for entry in node.kids:
+ if entry.kids:
+ self._list(files, prefix, entry)
+ else:
+ files.append(prefix[1:] + [entry.name])
+
+ def _find(self, filename):
+ # openstream helper
+
+ node = self.root
+ for name in filename:
+ for kid in node.kids:
+ if kid.name == name:
+ break
+ else:
+ raise IOError, "file not found"
+ node = kid
+ return node.sid
+
+ def _open(self, start, size = 0x7FFFFFFF):
+ # openstream helper.
+
+ if size < self.minisectorcutoff:
+ # ministream object
+ if not self.ministream:
+ self.loadminifat()
+ self.ministream = self._open(self.sidlist[0][2])
+ return _OleStream(self.ministream, start, size, 0,
+ self.minisectorsize, self.minifat)
+
+ # standard stream
+ return _OleStream(self.fp, start, size, 512,
+ self.sectorsize, self.fat)
+
+ ##
+ # Returns a list of streams stored in this file.
+
+ def listdir(self):
+ """Return a list of streams stored in this file"""
+
+ files = []
+ self._list(files, [], self.root)
+ return files
+
+ ##
+ # Opens a stream as a read-only file object.
+
+ def openstream(self, filename):
+ """Open a stream as a read-only file object"""
+
+ slot = self._find(filename)
+ name, type, sect, size, sids, clsid = self.sidlist[slot]
+ if type != 2:
+ raise IOError, "this file is not a stream"
+ return self._open(sect, size)
+
+ ##
+ # Gets a list of properties described in substream.
+
+ def getproperties(self, filename):
+ """Return properties described in substream"""
+
+ fp = self.openstream(filename)
+
+ data = {}
+
+ # header
+ s = fp.read(28)
+ clsid = self._clsid(s[8:24])
+
+ # format id
+ s = fp.read(20)
+ fmtid = self._clsid(s[:16])
+ fp.seek(i32(s, 16))
+
+ # get section
+ s = "****" + fp.read(i32(fp.read(4))-4)
+
+ for i in range(i32(s, 4)):
+
+ id = i32(s, 8+i*8)
+ offset = i32(s, 12+i*8)
+ type = i32(s, offset)
+
+ # test for common types first (should perhaps use
+ # a dictionary instead?)
+
+ if type == VT_I2:
+ value = i16(s, offset+4)
+ if value >= 32768:
+ value = value - 65536
+ elif type == VT_UI2:
+ value = i16(s, offset+4)
+ elif type in (VT_I4, VT_ERROR):
+ value = i32(s, offset+4)
+ elif type == VT_UI4:
+ value = i32(s, offset+4) # FIXME
+ elif type in (VT_BSTR, VT_LPSTR):
+ count = i32(s, offset+4)
+ value = s[offset+8:offset+8+count-1]
+ elif type == VT_BLOB:
+ count = i32(s, offset+4)
+ value = s[offset+8:offset+8+count]
+ elif type == VT_LPWSTR:
+ count = i32(s, offset+4)
+ value = self._unicode(s[offset+8:offset+8+count*2])
+ elif type == VT_FILETIME:
+ value = long(i32(s, offset+4)) + (long(i32(s, offset+8))<<32)
+ # FIXME: this is a 64-bit int: "number of 100ns periods
+ # since Jan 1,1601". Should map this to Python time
+ value = value / 10000000L # seconds
+ elif type == VT_UI1:
+ value = ord(s[offset+4])
+ elif type == VT_CLSID:
+ value = self._clsid(s[offset+4:offset+20])
+ elif type == VT_CF:
+ count = i32(s, offset+4)
+ value = s[offset+8:offset+8+count]
+ else:
+ value = None # everything else yields "None"
+
+ # FIXME: add support for VT_VECTOR
+
+ #print "%08x" % id, repr(value),
+ #print "(%s)" % VT[i32(s, offset) & 0xFFF]
+
+ data[id] = value
+
+ return data
+
+#
+# --------------------------------------------------------------------
+# This script can be used to dump the directory of any OLE2 structured
+# storage file.
+
+if __name__ == "__main__":
+
+ import sys
+
+ for file in sys.argv[1:]:
+ try:
+ ole = OleFileIO(file)
+ print "-" * 68
+ print file
+ print "-" * 68
+ ole.dumpdirectory()
+ for file in ole.listdir():
+ if file[-1][0] == "\005":
+ print file
+ props = ole.getproperties(file)
+ props = props.items()
+ props.sort()
+ for k, v in props:
+ print " ", k, v
+ except IOError, v:
+ print "***", "cannot read", file, "-", v
diff --git a/PIL/PSDraw.py b/PIL/PSDraw.py
new file mode 100644
index 0000000..7e88f55
--- /dev/null
+++ b/PIL/PSDraw.py
@@ -0,0 +1,199 @@
+#
+# The Python Imaging Library
+# $Id: PSDraw.py 2813 2006-10-07 10:11:35Z fredrik $
+#
+# simple postscript graphics interface
+#
+# History:
+# 1996-04-20 fl Created
+# 1999-01-10 fl Added gsave/grestore to image method
+# 2005-05-04 fl Fixed floating point issue in image (from Eric Etheridge)
+#
+# Copyright (c) 1997-2005 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1996 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import EpsImagePlugin
+import string
+
+##
+# Simple Postscript graphics interface.
+
+class PSDraw:
+
+ def __init__(self, fp=None):
+ if not fp:
+ import sys
+ fp = sys.stdout
+ self.fp = fp
+
+ def begin_document(self, id = None):
+ "Write Postscript DSC header"
+ # FIXME: incomplete
+ self.fp.write("%!PS-Adobe-3.0\n"
+ "save\n"
+ "/showpage { } def\n"
+ "%%EndComments\n"
+ "%%BeginDocument\n")
+ #self.fp.write(ERROR_PS) # debugging!
+ self.fp.write(EDROFF_PS)
+ self.fp.write(VDI_PS)
+ self.fp.write("%%EndProlog\n")
+ self.isofont = {}
+
+ def end_document(self):
+ "Write Postscript DSC footer"
+ self.fp.write("%%EndDocument\n"
+ "restore showpage\n"
+ "%%End\n")
+ if hasattr(self.fp, "flush"):
+ self.fp.flush()
+
+ def setfont(self, font, size):
+ if not self.isofont.has_key(font):
+ # reencode font
+ self.fp.write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %\
+ (font, font))
+ self.isofont[font] = 1
+ # rough
+ self.fp.write("/F0 %d /PSDraw-%s F\n" % (size, font))
+
+ def setink(self, ink):
+ print "*** NOT YET IMPLEMENTED ***"
+
+ def line(self, xy0, xy1):
+ xy = xy0 + xy1
+ self.fp.write("%d %d %d %d Vl\n" % xy)
+
+ def rectangle(self, box):
+ self.fp.write("%d %d M %d %d 0 Vr\n" % box)
+
+ def text(self, xy, text):
+ text = string.joinfields(string.splitfields(text, "("), "\\(")
+ text = string.joinfields(string.splitfields(text, ")"), "\\)")
+ xy = xy + (text,)
+ self.fp.write("%d %d M (%s) S\n" % xy)
+
+ def image(self, box, im, dpi = None):
+ "Write an PIL image"
+ # default resolution depends on mode
+ if not dpi:
+ if im.mode == "1":
+ dpi = 200 # fax
+ else:
+ dpi = 100 # greyscale
+ # image size (on paper)
+ x = float(im.size[0] * 72) / dpi
+ y = float(im.size[1] * 72) / dpi
+ # max allowed size
+ xmax = float(box[2] - box[0])
+ ymax = float(box[3] - box[1])
+ if x > xmax:
+ y = y * xmax / x; x = xmax
+ if y > ymax:
+ x = x * ymax / y; y = ymax
+ dx = (xmax - x) / 2 + box[0]
+ dy = (ymax - y) / 2 + box[1]
+ self.fp.write("gsave\n%f %f translate\n" % (dx, dy))
+ if (x, y) != im.size:
+ # EpsImagePlugin._save prints the image at (0,0,xsize,ysize)
+ sx = x / im.size[0]
+ sy = y / im.size[1]
+ self.fp.write("%f %f scale\n" % (sx, sy))
+ EpsImagePlugin._save(im, self.fp, None, 0)
+ self.fp.write("\ngrestore\n")
+
+# --------------------------------------------------------------------
+# Postscript driver
+
+#
+# EDROFF.PS -- Postscript driver for Edroff 2
+#
+# History:
+# 94-01-25 fl: created (edroff 2.04)
+#
+# Copyright (c) Fredrik Lundh 1994.
+#
+
+EDROFF_PS = """\
+/S { show } bind def
+/P { moveto show } bind def
+/M { moveto } bind def
+/X { 0 rmoveto } bind def
+/Y { 0 exch rmoveto } bind def
+/E { findfont
+ dup maxlength dict begin
+ {
+ 1 index /FID ne { def } { pop pop } ifelse
+ } forall
+ /Encoding exch def
+ dup /FontName exch def
+ currentdict end definefont pop
+} bind def
+/F { findfont exch scalefont dup setfont
+ [ exch /setfont cvx ] cvx bind def
+} bind def
+"""
+
+#
+# VDI.PS -- Postscript driver for VDI meta commands
+#
+# History:
+# 94-01-25 fl: created (edroff 2.04)
+#
+# Copyright (c) Fredrik Lundh 1994.
+#
+
+VDI_PS = """\
+/Vm { moveto } bind def
+/Va { newpath arcn stroke } bind def
+/Vl { moveto lineto stroke } bind def
+/Vc { newpath 0 360 arc closepath } bind def
+/Vr { exch dup 0 rlineto
+ exch dup neg 0 exch rlineto
+ exch neg 0 rlineto
+ 0 exch rlineto
+ 100 div setgray fill 0 setgray } bind def
+/Tm matrix def
+/Ve { Tm currentmatrix pop
+ translate scale newpath 0 0 .5 0 360 arc closepath
+ Tm setmatrix
+} bind def
+/Vf { currentgray exch setgray fill setgray } bind def
+"""
+
+#
+# ERROR.PS -- Error handler
+#
+# History:
+# 89-11-21 fl: created (pslist 1.10)
+#
+
+ERROR_PS = """\
+/landscape false def
+/errorBUF 200 string def
+/errorNL { currentpoint 10 sub exch pop 72 exch moveto } def
+errordict begin /handleerror {
+ initmatrix /Courier findfont 10 scalefont setfont
+ newpath 72 720 moveto $error begin /newerror false def
+ (PostScript Error) show errorNL errorNL
+ (Error: ) show
+ /errorname load errorBUF cvs show errorNL errorNL
+ (Command: ) show
+ /command load dup type /stringtype ne { errorBUF cvs } if show
+ errorNL errorNL
+ (VMstatus: ) show
+ vmstatus errorBUF cvs show ( bytes available, ) show
+ errorBUF cvs show ( bytes used at level ) show
+ errorBUF cvs show errorNL errorNL
+ (Operand stargck: ) show errorNL /ostargck load {
+ dup type /stringtype ne { errorBUF cvs } if 72 0 rmoveto show errorNL
+ } forall errorNL
+ (Execution stargck: ) show errorNL /estargck load {
+ dup type /stringtype ne { errorBUF cvs } if 72 0 rmoveto show errorNL
+ } forall
+ end showpage
+} def end
+"""
diff --git a/PIL/PaletteFile.py b/PIL/PaletteFile.py
new file mode 100644
index 0000000..bbbebfa
--- /dev/null
+++ b/PIL/PaletteFile.py
@@ -0,0 +1,53 @@
+#
+# Python Imaging Library
+# $Id: PaletteFile.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# stuff to read simple, teragon-style palette files
+#
+# History:
+# 97-08-23 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import string
+
+##
+# File handler for Teragon-style palette files.
+
+class PaletteFile:
+
+ rawmode = "RGB"
+
+ def __init__(self, fp):
+
+ self.palette = map(lambda i: (i, i, i), range(256))
+
+ while 1:
+
+ s = fp.readline()
+
+ if not s:
+ break
+ if len(s) > 100:
+ raise SyntaxError, "bad palette file"
+
+ v = map(int, string.split(s))
+ try:
+ [i, r, g, b] = v
+ except ValueError:
+ [i, r] = v
+ g = b = r
+
+ if 0 <= i <= 255:
+ self.palette[i] = chr(r) + chr(g) + chr(b)
+
+ self.palette = string.join(self.palette, "")
+
+
+ def getpalette(self):
+
+ return self.palette, self.rawmode
diff --git a/PIL/PalmImagePlugin.py b/PIL/PalmImagePlugin.py
new file mode 100644
index 0000000..19f0c2f
--- /dev/null
+++ b/PIL/PalmImagePlugin.py
@@ -0,0 +1,226 @@
+#
+# The Python Imaging Library.
+# $Id: PalmImagePlugin.py 2438 2005-05-25 21:09:48Z Fredrik $
+#
+
+##
+# Image plugin for Palm pixmap images (output only).
+##
+
+__version__ = "1.0"
+
+import Image, ImageFile
+import StringIO
+
+_Palm8BitColormapValues = (
+ ( 255, 255, 255 ), ( 255, 204, 255 ), ( 255, 153, 255 ), ( 255, 102, 255 ),
+ ( 255, 51, 255 ), ( 255, 0, 255 ), ( 255, 255, 204 ), ( 255, 204, 204 ),
+ ( 255, 153, 204 ), ( 255, 102, 204 ), ( 255, 51, 204 ), ( 255, 0, 204 ),
+ ( 255, 255, 153 ), ( 255, 204, 153 ), ( 255, 153, 153 ), ( 255, 102, 153 ),
+ ( 255, 51, 153 ), ( 255, 0, 153 ), ( 204, 255, 255 ), ( 204, 204, 255 ),
+ ( 204, 153, 255 ), ( 204, 102, 255 ), ( 204, 51, 255 ), ( 204, 0, 255 ),
+ ( 204, 255, 204 ), ( 204, 204, 204 ), ( 204, 153, 204 ), ( 204, 102, 204 ),
+ ( 204, 51, 204 ), ( 204, 0, 204 ), ( 204, 255, 153 ), ( 204, 204, 153 ),
+ ( 204, 153, 153 ), ( 204, 102, 153 ), ( 204, 51, 153 ), ( 204, 0, 153 ),
+ ( 153, 255, 255 ), ( 153, 204, 255 ), ( 153, 153, 255 ), ( 153, 102, 255 ),
+ ( 153, 51, 255 ), ( 153, 0, 255 ), ( 153, 255, 204 ), ( 153, 204, 204 ),
+ ( 153, 153, 204 ), ( 153, 102, 204 ), ( 153, 51, 204 ), ( 153, 0, 204 ),
+ ( 153, 255, 153 ), ( 153, 204, 153 ), ( 153, 153, 153 ), ( 153, 102, 153 ),
+ ( 153, 51, 153 ), ( 153, 0, 153 ), ( 102, 255, 255 ), ( 102, 204, 255 ),
+ ( 102, 153, 255 ), ( 102, 102, 255 ), ( 102, 51, 255 ), ( 102, 0, 255 ),
+ ( 102, 255, 204 ), ( 102, 204, 204 ), ( 102, 153, 204 ), ( 102, 102, 204 ),
+ ( 102, 51, 204 ), ( 102, 0, 204 ), ( 102, 255, 153 ), ( 102, 204, 153 ),
+ ( 102, 153, 153 ), ( 102, 102, 153 ), ( 102, 51, 153 ), ( 102, 0, 153 ),
+ ( 51, 255, 255 ), ( 51, 204, 255 ), ( 51, 153, 255 ), ( 51, 102, 255 ),
+ ( 51, 51, 255 ), ( 51, 0, 255 ), ( 51, 255, 204 ), ( 51, 204, 204 ),
+ ( 51, 153, 204 ), ( 51, 102, 204 ), ( 51, 51, 204 ), ( 51, 0, 204 ),
+ ( 51, 255, 153 ), ( 51, 204, 153 ), ( 51, 153, 153 ), ( 51, 102, 153 ),
+ ( 51, 51, 153 ), ( 51, 0, 153 ), ( 0, 255, 255 ), ( 0, 204, 255 ),
+ ( 0, 153, 255 ), ( 0, 102, 255 ), ( 0, 51, 255 ), ( 0, 0, 255 ),
+ ( 0, 255, 204 ), ( 0, 204, 204 ), ( 0, 153, 204 ), ( 0, 102, 204 ),
+ ( 0, 51, 204 ), ( 0, 0, 204 ), ( 0, 255, 153 ), ( 0, 204, 153 ),
+ ( 0, 153, 153 ), ( 0, 102, 153 ), ( 0, 51, 153 ), ( 0, 0, 153 ),
+ ( 255, 255, 102 ), ( 255, 204, 102 ), ( 255, 153, 102 ), ( 255, 102, 102 ),
+ ( 255, 51, 102 ), ( 255, 0, 102 ), ( 255, 255, 51 ), ( 255, 204, 51 ),
+ ( 255, 153, 51 ), ( 255, 102, 51 ), ( 255, 51, 51 ), ( 255, 0, 51 ),
+ ( 255, 255, 0 ), ( 255, 204, 0 ), ( 255, 153, 0 ), ( 255, 102, 0 ),
+ ( 255, 51, 0 ), ( 255, 0, 0 ), ( 204, 255, 102 ), ( 204, 204, 102 ),
+ ( 204, 153, 102 ), ( 204, 102, 102 ), ( 204, 51, 102 ), ( 204, 0, 102 ),
+ ( 204, 255, 51 ), ( 204, 204, 51 ), ( 204, 153, 51 ), ( 204, 102, 51 ),
+ ( 204, 51, 51 ), ( 204, 0, 51 ), ( 204, 255, 0 ), ( 204, 204, 0 ),
+ ( 204, 153, 0 ), ( 204, 102, 0 ), ( 204, 51, 0 ), ( 204, 0, 0 ),
+ ( 153, 255, 102 ), ( 153, 204, 102 ), ( 153, 153, 102 ), ( 153, 102, 102 ),
+ ( 153, 51, 102 ), ( 153, 0, 102 ), ( 153, 255, 51 ), ( 153, 204, 51 ),
+ ( 153, 153, 51 ), ( 153, 102, 51 ), ( 153, 51, 51 ), ( 153, 0, 51 ),
+ ( 153, 255, 0 ), ( 153, 204, 0 ), ( 153, 153, 0 ), ( 153, 102, 0 ),
+ ( 153, 51, 0 ), ( 153, 0, 0 ), ( 102, 255, 102 ), ( 102, 204, 102 ),
+ ( 102, 153, 102 ), ( 102, 102, 102 ), ( 102, 51, 102 ), ( 102, 0, 102 ),
+ ( 102, 255, 51 ), ( 102, 204, 51 ), ( 102, 153, 51 ), ( 102, 102, 51 ),
+ ( 102, 51, 51 ), ( 102, 0, 51 ), ( 102, 255, 0 ), ( 102, 204, 0 ),
+ ( 102, 153, 0 ), ( 102, 102, 0 ), ( 102, 51, 0 ), ( 102, 0, 0 ),
+ ( 51, 255, 102 ), ( 51, 204, 102 ), ( 51, 153, 102 ), ( 51, 102, 102 ),
+ ( 51, 51, 102 ), ( 51, 0, 102 ), ( 51, 255, 51 ), ( 51, 204, 51 ),
+ ( 51, 153, 51 ), ( 51, 102, 51 ), ( 51, 51, 51 ), ( 51, 0, 51 ),
+ ( 51, 255, 0 ), ( 51, 204, 0 ), ( 51, 153, 0 ), ( 51, 102, 0 ),
+ ( 51, 51, 0 ), ( 51, 0, 0 ), ( 0, 255, 102 ), ( 0, 204, 102 ),
+ ( 0, 153, 102 ), ( 0, 102, 102 ), ( 0, 51, 102 ), ( 0, 0, 102 ),
+ ( 0, 255, 51 ), ( 0, 204, 51 ), ( 0, 153, 51 ), ( 0, 102, 51 ),
+ ( 0, 51, 51 ), ( 0, 0, 51 ), ( 0, 255, 0 ), ( 0, 204, 0 ),
+ ( 0, 153, 0 ), ( 0, 102, 0 ), ( 0, 51, 0 ), ( 17, 17, 17 ),
+ ( 34, 34, 34 ), ( 68, 68, 68 ), ( 85, 85, 85 ), ( 119, 119, 119 ),
+ ( 136, 136, 136 ), ( 170, 170, 170 ), ( 187, 187, 187 ), ( 221, 221, 221 ),
+ ( 238, 238, 238 ), ( 192, 192, 192 ), ( 128, 0, 0 ), ( 128, 0, 128 ),
+ ( 0, 128, 0 ), ( 0, 128, 128 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
+ ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
+ ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
+ ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
+ ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
+ ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ),
+ ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ))
+
+# so build a prototype image to be used for palette resampling
+def build_prototype_image():
+ image = Image.new("L", (1,len(_Palm8BitColormapValues),))
+ image.putdata(range(len(_Palm8BitColormapValues)))
+ palettedata = ()
+ for i in range(len(_Palm8BitColormapValues)):
+ palettedata = palettedata + _Palm8BitColormapValues[i]
+ for i in range(256 - len(_Palm8BitColormapValues)):
+ palettedata = palettedata + (0, 0, 0)
+ image.putpalette(palettedata)
+ return image
+
+Palm8BitColormapImage = build_prototype_image()
+
+# OK, we now have in Palm8BitColormapImage, a "P"-mode image with the right palette
+#
+# --------------------------------------------------------------------
+
+_FLAGS = {
+ "custom-colormap": 0x4000,
+ "is-compressed": 0x8000,
+ "has-transparent": 0x2000,
+ }
+
+_COMPRESSION_TYPES = {
+ "none": 0xFF,
+ "rle": 0x01,
+ "scanline": 0x00,
+ }
+
+def o16b(i):
+ return chr(i>>8&255) + chr(i&255)
+
+#
+# --------------------------------------------------------------------
+
+##
+# (Internal) Image save plugin for the Palm format.
+
+def _save(im, fp, filename, check=0):
+
+ if im.mode == "P":
+
+ # we assume this is a color Palm image with the standard colormap,
+ # unless the "info" dict has a "custom-colormap" field
+
+ rawmode = "P"
+ bpp = 8
+ version = 1
+
+ elif im.mode == "L" and im.encoderinfo.has_key("bpp") and im.encoderinfo["bpp"] in (1, 2, 4):
+
+ # this is 8-bit grayscale, so we shift it to get the high-order bits, and invert it because
+ # Palm does greyscale from white (0) to black (1)
+ bpp = im.encoderinfo["bpp"]
+ im = im.point(lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift))
+ # we ignore the palette here
+ im.mode = "P"
+ rawmode = "P;" + str(bpp)
+ version = 1
+
+ elif im.mode == "L" and im.info.has_key("bpp") and im.info["bpp"] in (1, 2, 4):
+
+ # here we assume that even though the inherent mode is 8-bit grayscale, only
+ # the lower bpp bits are significant. We invert them to match the Palm.
+ bpp = im.info["bpp"]
+ im = im.point(lambda x, maxval=(1 << bpp)-1: maxval - (x & maxval))
+ # we ignore the palette here
+ im.mode = "P"
+ rawmode = "P;" + str(bpp)
+ version = 1
+
+ elif im.mode == "1":
+
+ # monochrome -- write it inverted, as is the Palm standard
+ rawmode = "1;I"
+ bpp = 1
+ version = 0
+
+ else:
+
+ raise IOError, "cannot write mode %s as Palm" % im.mode
+
+ if check:
+ return check
+
+ #
+ # make sure image data is available
+ im.load()
+
+ # write header
+
+ cols = im.size[0]
+ rows = im.size[1]
+
+ rowbytes = ((cols + (16/bpp - 1)) / (16 / bpp)) * 2;
+ transparent_index = 0
+ compression_type = _COMPRESSION_TYPES["none"]
+
+ flags = 0;
+ if im.mode == "P" and im.info.has_key("custom-colormap"):
+ flags = flags & _FLAGS["custom-colormap"]
+ colormapsize = 4 * 256 + 2;
+ colormapmode = im.palette.mode
+ colormap = im.getdata().getpalette()
+ else:
+ colormapsize = 0
+
+ if im.info.has_key("offset"):
+ offset = (rowbytes * rows + 16 + 3 + colormapsize) / 4;
+ else:
+ offset = 0
+
+ fp.write(o16b(cols) + o16b(rows) + o16b(rowbytes) + o16b(flags))
+ fp.write(chr(bpp))
+ fp.write(chr(version))
+ fp.write(o16b(offset))
+ fp.write(chr(transparent_index))
+ fp.write(chr(compression_type))
+ fp.write(o16b(0)) # reserved by Palm
+
+ # now write colormap if necessary
+
+ if colormapsize > 0:
+ fp.write(o16b(256))
+ for i in range(256):
+ fp.write(chr(i))
+ if colormapmode == 'RGB':
+ fp.write(chr(colormap[3 * i]) + chr(colormap[3 * i + 1]) + chr(colormap[3 * i + 2]))
+ elif colormapmode == 'RGBA':
+ fp.write(chr(colormap[4 * i]) + chr(colormap[4 * i + 1]) + chr(colormap[4 * i + 2]))
+
+ # now convert data to raw form
+ ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, rowbytes, 1))])
+
+ fp.flush()
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_save("Palm", _save)
+
+Image.register_extension("Palm", ".palm")
+
+Image.register_mime("Palm", "image/palm")
diff --git a/PIL/PcdImagePlugin.py b/PIL/PcdImagePlugin.py
new file mode 100644
index 0000000..957da24
--- /dev/null
+++ b/PIL/PcdImagePlugin.py
@@ -0,0 +1,76 @@
+#
+# The Python Imaging Library.
+# $Id: PcdImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# PCD file handling
+#
+# History:
+# 96-05-10 fl Created
+# 96-05-27 fl Added draft mode (128x192, 256x384)
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+
+import Image, ImageFile
+
+##
+# Image plugin for PhotoCD images. This plugin only reads the 768x512
+# image from the file; higher resolutions are encoded in a proprietary
+# encoding.
+
+class PcdImageFile(ImageFile.ImageFile):
+
+ format = "PCD"
+ format_description = "Kodak PhotoCD"
+
+ def _open(self):
+
+ # rough
+ self.fp.seek(2048)
+ s = self.fp.read(2048)
+
+ if s[:4] != "PCD_":
+ raise SyntaxError, "not a PCD file"
+
+ orientation = ord(s[1538]) & 3
+ if orientation == 1:
+ self.tile_post_rotate = 90 # hack
+ elif orientation == 3:
+ self.tile_post_rotate = -90
+
+ self.mode = "RGB"
+ self.size = 768, 512 # FIXME: not correct for rotated images!
+ self.tile = [("pcd", (0,0)+self.size, 96*2048, None)]
+
+ def draft(self, mode, size):
+
+ if len(self.tile) != 1:
+ return
+
+ d, e, o, a = self.tile[0]
+
+ if size:
+ scale = max(self.size[0] / size[0], self.size[1] / size[1])
+ for s, o in [(4,0*2048), (2,0*2048), (1,96*2048)]:
+ if scale >= s:
+ break
+ # e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1]
+ # self.size = ((self.size[0]+s-1)/s, (self.size[1]+s-1)/s)
+
+ self.tile = [(d, e, o, a)]
+
+ return self
+
+#
+# registry
+
+Image.register_open("PCD", PcdImageFile)
+
+Image.register_extension("PCD", ".pcd")
diff --git a/PIL/PcfFontFile.py b/PIL/PcfFontFile.py
new file mode 100644
index 0000000..9a689b2
--- /dev/null
+++ b/PIL/PcfFontFile.py
@@ -0,0 +1,256 @@
+#
+# THIS IS WORK IN PROGRESS
+#
+# The Python Imaging Library
+# $Id: PcfFontFile.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# portable compiled font file parser
+#
+# history:
+# 1997-08-19 fl created
+# 2003-09-13 fl fixed loading of unicode fonts
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1997-2003 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+import FontFile
+
+import string
+
+# --------------------------------------------------------------------
+# declarations
+
+PCF_MAGIC = 0x70636601 # "\x01fcp"
+
+PCF_PROPERTIES = (1<<0)
+PCF_ACCELERATORS = (1<<1)
+PCF_METRICS = (1<<2)
+PCF_BITMAPS = (1<<3)
+PCF_INK_METRICS = (1<<4)
+PCF_BDF_ENCODINGS = (1<<5)
+PCF_SWIDTHS = (1<<6)
+PCF_GLYPH_NAMES = (1<<7)
+PCF_BDF_ACCELERATORS = (1<<8)
+
+BYTES_PER_ROW = [
+ lambda bits: ((bits+7) >> 3),
+ lambda bits: ((bits+15) >> 3) & ~1,
+ lambda bits: ((bits+31) >> 3) & ~3,
+ lambda bits: ((bits+63) >> 3) & ~7,
+]
+
+
+def l16(c):
+ return ord(c[0]) + (ord(c[1])<<8)
+def l32(c):
+ return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24)
+
+def b16(c):
+ return ord(c[1]) + (ord(c[0])<<8)
+def b32(c):
+ return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
+
+def sz(s, o):
+ return s[o:string.index(s, "\0", o)]
+
+##
+# Font file plugin for the X11 PCF format.
+
+class PcfFontFile(FontFile.FontFile):
+
+ name = "name"
+
+ def __init__(self, fp):
+
+ magic = l32(fp.read(4))
+ if magic != PCF_MAGIC:
+ raise SyntaxError, "not a PCF file"
+
+ FontFile.FontFile.__init__(self)
+
+ count = l32(fp.read(4))
+ self.toc = {}
+ for i in range(count):
+ type = l32(fp.read(4))
+ self.toc[type] = l32(fp.read(4)), l32(fp.read(4)), l32(fp.read(4))
+
+ self.fp = fp
+
+ self.info = self._load_properties()
+
+ metrics = self._load_metrics()
+ bitmaps = self._load_bitmaps(metrics)
+ encoding = self._load_encoding()
+
+ #
+ # create glyph structure
+
+ for ch in range(256):
+ ix = encoding[ch]
+ if ix is not None:
+ x, y, l, r, w, a, d, f = metrics[ix]
+ glyph = (w, 0), (l, d-y, x+l, d), (0, 0, x, y), bitmaps[ix]
+ self.glyph[ch] = glyph
+
+ def _getformat(self, tag):
+
+ format, size, offset = self.toc[tag]
+
+ fp = self.fp
+ fp.seek(offset)
+
+ format = l32(fp.read(4))
+
+ if format & 4:
+ i16, i32 = b16, b32
+ else:
+ i16, i32 = l16, l32
+
+ return fp, format, i16, i32
+
+ def _load_properties(self):
+
+ #
+ # font properties
+
+ properties = {}
+
+ fp, format, i16, i32 = self._getformat(PCF_PROPERTIES)
+
+ nprops = i32(fp.read(4))
+
+ # read property description
+ p = []
+ for i in range(nprops):
+ p.append((i32(fp.read(4)), ord(fp.read(1)), i32(fp.read(4))))
+ if nprops & 3:
+ fp.seek(4 - (nprops & 3), 1) # pad
+
+ data = fp.read(i32(fp.read(4)))
+
+ for k, s, v in p:
+ k = sz(data, k)
+ if s:
+ v = sz(data, v)
+ properties[k] = v
+
+ return properties
+
+ def _load_metrics(self):
+
+ #
+ # font metrics
+
+ metrics = []
+
+ fp, format, i16, i32 = self._getformat(PCF_METRICS)
+
+ append = metrics.append
+
+ if (format & 0xff00) == 0x100:
+
+ # "compressed" metrics
+ for i in range(i16(fp.read(2))):
+ left = ord(fp.read(1)) - 128
+ right = ord(fp.read(1)) - 128
+ width = ord(fp.read(1)) - 128
+ ascent = ord(fp.read(1)) - 128
+ descent = ord(fp.read(1)) - 128
+ xsize = right - left
+ ysize = ascent + descent
+ append(
+ (xsize, ysize, left, right, width,
+ ascent, descent, 0)
+ )
+
+ else:
+
+ # "jumbo" metrics
+ for i in range(i32(fp.read(4))):
+ left = i16(fp.read(2))
+ right = i16(fp.read(2))
+ width = i16(fp.read(2))
+ ascent = i16(fp.read(2))
+ descent = i16(fp.read(2))
+ attributes = i16(fp.read(2))
+ xsize = right - left
+ ysize = ascent + descent
+ append(
+ (xsize, ysize, left, right, width,
+ ascent, descent, attributes)
+ )
+
+ return metrics
+
+ def _load_bitmaps(self, metrics):
+
+ #
+ # bitmap data
+
+ bitmaps = []
+
+ fp, format, i16, i32 = self._getformat(PCF_BITMAPS)
+
+ nbitmaps = i32(fp.read(4))
+
+ if nbitmaps != len(metrics):
+ raise IOError, "Wrong number of bitmaps"
+
+ offsets = []
+ for i in range(nbitmaps):
+ offsets.append(i32(fp.read(4)))
+
+ bitmapSizes = []
+ for i in range(4):
+ bitmapSizes.append(i32(fp.read(4)))
+
+ byteorder = format & 4 # non-zero => MSB
+ bitorder = format & 8 # non-zero => MSB
+ padindex = format & 3
+
+ bitmapsize = bitmapSizes[padindex]
+ offsets.append(bitmapsize)
+
+ data = fp.read(bitmapsize)
+
+ pad = BYTES_PER_ROW[padindex]
+ mode = "1;R"
+ if bitorder:
+ mode = "1"
+
+ for i in range(nbitmaps):
+ x, y, l, r, w, a, d, f = metrics[i]
+ b, e = offsets[i], offsets[i+1]
+ bitmaps.append(
+ Image.fromstring("1", (x, y), data[b:e], "raw", mode, pad(x))
+ )
+
+ return bitmaps
+
+ def _load_encoding(self):
+
+ # map character code to bitmap index
+ encoding = [None] * 256
+
+ fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
+
+ firstCol, lastCol = i16(fp.read(2)), i16(fp.read(2))
+ firstRow, lastRow = i16(fp.read(2)), i16(fp.read(2))
+
+ default = i16(fp.read(2))
+
+ nencoding = (lastCol - firstCol + 1) * (lastRow - firstRow + 1)
+
+ for i in range(nencoding):
+ encodingOffset = i16(fp.read(2))
+ if encodingOffset != 0xFFFF:
+ try:
+ encoding[i+firstCol] = encodingOffset
+ except IndexError:
+ break # only load ISO-8859-1 glyphs
+
+ return encoding
diff --git a/PIL/PcxImagePlugin.py b/PIL/PcxImagePlugin.py
new file mode 100644
index 0000000..8c0cd11
--- /dev/null
+++ b/PIL/PcxImagePlugin.py
@@ -0,0 +1,167 @@
+#
+# The Python Imaging Library.
+# $Id: PcxImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# PCX file handling
+#
+# This format was originally used by ZSoft's popular PaintBrush
+# program for the IBM PC. It is also supported by many MS-DOS and
+# Windows applications, including the Windows PaintBrush program in
+# Windows 3.
+#
+# history:
+# 1995-09-01 fl Created
+# 1996-05-20 fl Fixed RGB support
+# 1997-01-03 fl Fixed 2-bit and 4-bit support
+# 1999-02-03 fl Fixed 8-bit support (broken in 1.0b1)
+# 1999-02-07 fl Added write support
+# 2002-06-09 fl Made 2-bit and 4-bit support a bit more robust
+# 2002-07-30 fl Seek from to current position, not beginning of file
+# 2003-06-03 fl Extract DPI settings (info["dpi"])
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2003 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.6"
+
+import Image, ImageFile, ImagePalette
+
+def i16(c,o):
+ return ord(c[o]) + (ord(c[o+1])<<8)
+
+def _accept(prefix):
+ return ord(prefix[0]) == 10 and ord(prefix[1]) in [0, 2, 3, 5]
+
+##
+# Image plugin for Paintbrush images.
+
+class PcxImageFile(ImageFile.ImageFile):
+
+ format = "PCX"
+ format_description = "Paintbrush"
+
+ def _open(self):
+
+ # header
+ s = self.fp.read(128)
+ if not _accept(s):
+ raise SyntaxError, "not a PCX file"
+
+ # image
+ bbox = i16(s,4), i16(s,6), i16(s,8)+1, i16(s,10)+1
+ if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
+ raise SyntaxError, "bad PCX image size"
+
+ # format
+ version = ord(s[1])
+ bits = ord(s[3])
+ planes = ord(s[65])
+ stride = i16(s,66)
+
+ self.info["dpi"] = i16(s,12), i16(s,14)
+
+ if bits == 1 and planes == 1:
+ mode = rawmode = "1"
+
+ elif bits == 1 and planes in (2, 4):
+ mode = "P"
+ rawmode = "P;%dL" % planes
+ self.palette = ImagePalette.raw("RGB", s[16:64])
+
+ elif version == 5 and bits == 8 and planes == 1:
+ mode = rawmode = "L"
+ # FIXME: hey, this doesn't work with the incremental loader !!!
+ self.fp.seek(-769, 2)
+ s = self.fp.read(769)
+ if len(s) == 769 and ord(s[0]) == 12:
+ # check if the palette is linear greyscale
+ for i in range(256):
+ if s[i*3+1:i*3+4] != chr(i)*3:
+ mode = rawmode = "P"
+ break
+ if mode == "P":
+ self.palette = ImagePalette.raw("RGB", s[1:])
+ self.fp.seek(128)
+
+ elif version == 5 and bits == 8 and planes == 3:
+ mode = "RGB"
+ rawmode = "RGB;L"
+
+ else:
+ raise IOError, "unknown PCX mode"
+
+ self.mode = mode
+ self.size = bbox[2]-bbox[0], bbox[3]-bbox[1]
+
+ bbox = (0, 0) + self.size
+
+ self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
+
+# --------------------------------------------------------------------
+# save PCX files
+
+SAVE = {
+ # mode: (version, bits, planes, raw mode)
+ "1": (2, 1, 1, "1"),
+ "L": (5, 8, 1, "L"),
+ "P": (5, 8, 1, "P"),
+ "RGB": (5, 8, 3, "RGB;L"),
+}
+
+def o16(i):
+ return chr(i&255) + chr(i>>8&255)
+
+def _save(im, fp, filename, check=0):
+
+ try:
+ version, bits, planes, rawmode = SAVE[im.mode]
+ except KeyError:
+ raise ValueError, "Cannot save %s images as PCX" % im.mode
+
+ if check:
+ return check
+
+ # bytes per plane
+ stride = (im.size[0] * bits + 7) / 8
+
+ # under windows, we could determine the current screen size with
+ # "Image.core.display_mode()[1]", but I think that's overkill...
+
+ screen = im.size
+
+ dpi = 100, 100
+
+ # PCX header
+ fp.write(
+ chr(10) + chr(version) + chr(1) + chr(bits) + o16(0) +
+ o16(0) + o16(im.size[0]-1) + o16(im.size[1]-1) + o16(dpi[0]) +
+ o16(dpi[1]) + chr(0)*24 + chr(255)*24 + chr(0) + chr(planes) +
+ o16(stride) + o16(1) + o16(screen[0]) + o16(screen[1]) +
+ chr(0)*54
+ )
+
+ assert fp.tell() == 128
+
+ ImageFile._save(im, fp, [("pcx", (0,0)+im.size, 0,
+ (rawmode, bits*planes))])
+
+ if im.mode == "P":
+ # colour palette
+ fp.write(chr(12))
+ fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
+ elif im.mode == "L":
+ # greyscale palette
+ fp.write(chr(12))
+ for i in range(256):
+ fp.write(chr(i)*3)
+
+# --------------------------------------------------------------------
+# registry
+
+Image.register_open("PCX", PcxImageFile, _accept)
+Image.register_save("PCX", _save)
+
+Image.register_extension("PCX", ".pcx")
diff --git a/PIL/PdfImagePlugin.py b/PIL/PdfImagePlugin.py
new file mode 100644
index 0000000..86567d6
--- /dev/null
+++ b/PIL/PdfImagePlugin.py
@@ -0,0 +1,210 @@
+#
+# The Python Imaging Library.
+# $Id: PdfImagePlugin.py 2438 2005-05-25 21:09:48Z Fredrik $
+#
+# PDF (Acrobat) file handling
+#
+# History:
+# 1996-07-16 fl Created
+# 1997-01-18 fl Fixed header
+# 2004-02-21 fl Fixes for 1/L/CMYK images, etc.
+# 2004-02-24 fl Fixes for 1 and P images.
+#
+# Copyright (c) 1997-2004 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1996-1997 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# Image plugin for PDF images (output only).
+##
+
+__version__ = "0.4"
+
+import Image, ImageFile
+import StringIO
+
+
+#
+# --------------------------------------------------------------------
+
+# object ids:
+# 1. catalogue
+# 2. pages
+# 3. image
+# 4. page
+# 5. page contents
+
+def _obj(fp, obj, **dict):
+ fp.write("%d 0 obj\n" % obj)
+ if dict:
+ fp.write("<<\n")
+ for k, v in dict.items():
+ if v is not None:
+ fp.write("/%s %s\n" % (k, v))
+ fp.write(">>\n")
+
+def _endobj(fp):
+ fp.write("endobj\n")
+
+##
+# (Internal) Image save plugin for the PDF format.
+
+def _save(im, fp, filename):
+
+ #
+ # make sure image data is available
+ im.load()
+
+ xref = [0]*(5+1) # placeholders
+
+ fp.write("%PDF-1.2\n")
+ fp.write("% created by PIL PDF driver " + __version__ + "\n")
+
+ #
+ # Get image characteristics
+
+ width, height = im.size
+
+ # FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)
+ # or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports
+ # Flatedecode (zip compression).
+
+ bits = 8
+ params = None
+
+ if im.mode == "1":
+ filter = "/ASCIIHexDecode"
+ colorspace = "/DeviceGray"
+ procset = "/ImageB" # grayscale
+ bits = 1
+ elif im.mode == "L":
+ filter = "/DCTDecode"
+ # params = "<< /Predictor 15 /Columns %d >>" % (width-2)
+ colorspace = "/DeviceGray"
+ procset = "/ImageB" # grayscale
+ elif im.mode == "P":
+ filter = "/ASCIIHexDecode"
+ colorspace = "[ /Indexed /DeviceRGB 255 <"
+ palette = im.im.getpalette("RGB")
+ for i in range(256):
+ r = ord(palette[i*3])
+ g = ord(palette[i*3+1])
+ b = ord(palette[i*3+2])
+ colorspace = colorspace + "%02x%02x%02x " % (r, g, b)
+ colorspace = colorspace + "> ]"
+ procset = "/ImageI" # indexed color
+ elif im.mode == "RGB":
+ filter = "/DCTDecode"
+ colorspace = "/DeviceRGB"
+ procset = "/ImageC" # color images
+ elif im.mode == "CMYK":
+ filter = "/DCTDecode"
+ colorspace = "/DeviceCMYK"
+ procset = "/ImageC" # color images
+ else:
+ raise ValueError("cannot save mode %s" % im.mode)
+
+ #
+ # catalogue
+
+ xref[1] = fp.tell()
+ _obj(fp, 1, Type = "/Catalog",
+ Pages = "2 0 R")
+ _endobj(fp)
+
+ #
+ # pages
+
+ xref[2] = fp.tell()
+ _obj(fp, 2, Type = "/Pages",
+ Count = 1,
+ Kids = "[4 0 R]")
+ _endobj(fp)
+
+ #
+ # image
+
+ op = StringIO.StringIO()
+
+ if filter == "/ASCIIHexDecode":
+ if bits == 1:
+ # FIXME: the hex encoder doesn't support packed 1-bit
+ # images; do things the hard way...
+ data = im.tostring("raw", "1")
+ im = Image.new("L", (len(data), 1), None)
+ im.putdata(data)
+ ImageFile._save(im, op, [("hex", (0,0)+im.size, 0, im.mode)])
+ elif filter == "/DCTDecode":
+ ImageFile._save(im, op, [("jpeg", (0,0)+im.size, 0, im.mode)])
+ elif filter == "/FlateDecode":
+ ImageFile._save(im, op, [("zip", (0,0)+im.size, 0, im.mode)])
+ elif filter == "/RunLengthDecode":
+ ImageFile._save(im, op, [("packbits", (0,0)+im.size, 0, im.mode)])
+ else:
+ raise ValueError("unsupported PDF filter (%s)" % filter)
+
+ xref[3] = fp.tell()
+ _obj(fp, 3, Type = "/XObject",
+ Subtype = "/Image",
+ Width = width,
+ Height = height,
+ Length = len(op.getvalue()),
+ Filter = filter,
+ BitsPerComponent = bits,
+ DecodeParams = params,
+ ColorSpace = colorspace)
+
+ fp.write("stream\n")
+ fp.write(op.getvalue())
+ fp.write("\nendstream\n")
+
+ _endobj(fp)
+
+ #
+ # page
+
+ xref[4] = fp.tell()
+ _obj(fp, 4)
+ fp.write("<<\n/Type /Page\n/Parent 2 0 R\n"\
+ "/Resources <<\n/ProcSet [ /PDF %s ]\n"\
+ "/XObject << /image 3 0 R >>\n>>\n"\
+ "/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" %\
+ (procset, width, height))
+ _endobj(fp)
+
+ #
+ # page contents
+
+ op = StringIO.StringIO()
+
+ op.write("q %d 0 0 %d 0 0 cm /image Do Q\n" % (width, height))
+
+ xref[5] = fp.tell()
+ _obj(fp, 5, Length = len(op.getvalue()))
+
+ fp.write("stream\n")
+ fp.write(op.getvalue())
+ fp.write("\nendstream\n")
+
+ _endobj(fp)
+
+ #
+ # trailer
+ startxref = fp.tell()
+ fp.write("xref\n0 %d\n0000000000 65535 f \n" % len(xref))
+ for x in xref[1:]:
+ fp.write("%010d 00000 n \n" % x)
+ fp.write("trailer\n<<\n/Size %d\n/Root 1 0 R\n>>\n" % len(xref))
+ fp.write("startxref\n%d\n%%%%EOF\n" % startxref)
+ fp.flush()
+
+#
+# --------------------------------------------------------------------
+
+Image.register_save("PDF", _save)
+
+Image.register_extension("PDF", ".pdf")
+
+Image.register_mime("PDF", "application/pdf")
diff --git a/PIL/PixarImagePlugin.py b/PIL/PixarImagePlugin.py
new file mode 100644
index 0000000..d4b136f
--- /dev/null
+++ b/PIL/PixarImagePlugin.py
@@ -0,0 +1,71 @@
+#
+# The Python Imaging Library.
+# $Id: PixarImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# PIXAR raster support for PIL
+#
+# history:
+# 97-01-29 fl Created
+#
+# notes:
+# This is incomplete; it is based on a few samples created with
+# Photoshop 2.5 and 3.0, and a summary description provided by
+# Greg Coats <gcoats@labiris.er.usgs.gov>. Hopefully, "L" and
+# "RGBA" support will be added in future versions.
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.1"
+
+import Image, ImageFile
+
+#
+# helpers
+
+def i16(c):
+ return ord(c[0]) + (ord(c[1])<<8)
+
+def i32(c):
+ return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24)
+
+##
+# Image plugin for PIXAR raster images.
+
+class PixarImageFile(ImageFile.ImageFile):
+
+ format = "PIXAR"
+ format_description = "PIXAR raster image"
+
+ def _open(self):
+
+ # assuming a 4-byte magic label (FIXME: add "_accept" hook)
+ s = self.fp.read(4)
+ if s != "\200\350\000\000":
+ raise SyntaxError, "not a PIXAR file"
+
+ # read rest of header
+ s = s + self.fp.read(508)
+
+ self.size = i16(s[418:420]), i16(s[416:418])
+
+ # get channel/depth descriptions
+ mode = i16(s[424:426]), i16(s[426:428])
+
+ if mode == (14, 2):
+ self.mode = "RGB"
+ # FIXME: to be continued...
+
+ # create tile descriptor (assuming "dumped")
+ self.tile = [("raw", (0,0)+self.size, 1024, (self.mode, 0, 1))]
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("PIXAR", PixarImageFile)
+
+#
+# FIXME: what's the standard extension?
diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py
new file mode 100644
index 0000000..0e8ce9f
--- /dev/null
+++ b/PIL/PngImagePlugin.py
@@ -0,0 +1,558 @@
+#
+# The Python Imaging Library.
+# $Id: PngImagePlugin.py 2203 2004-12-19 14:32:32Z fredrik $
+#
+# PNG support code
+#
+# See "PNG (Portable Network Graphics) Specification, version 1.0;
+# W3C Recommendation", 1996-10-01, Thomas Boutell (ed.).
+#
+# history:
+# 1996-05-06 fl Created (couldn't resist it)
+# 1996-12-14 fl Upgraded, added read and verify support (0.2)
+# 1996-12-15 fl Separate PNG stream parser
+# 1996-12-29 fl Added write support, added getchunks
+# 1996-12-30 fl Eliminated circular references in decoder (0.3)
+# 1998-07-12 fl Read/write 16-bit images as mode I (0.4)
+# 2001-02-08 fl Added transparency support (from Zircon) (0.5)
+# 2001-04-16 fl Don't close data source in "open" method (0.6)
+# 2004-02-24 fl Don't even pretend to support interlaced files (0.7)
+# 2004-08-31 fl Do basic sanity check on chunk identifiers (0.8)
+# 2004-09-20 fl Added PngInfo chunk container
+# 2004-12-18 fl Added DPI read support (based on code by Niki Spahiev)
+#
+# Copyright (c) 1997-2004 by Secret Labs AB
+# Copyright (c) 1996 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.8.2"
+
+import re, string
+
+import Image, ImageFile, ImagePalette
+
+
+def i16(c):
+ return ord(c[1]) + (ord(c[0])<<8)
+def i32(c):
+ return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
+
+is_cid = re.compile("\w\w\w\w").match
+
+
+_MAGIC = "\211PNG\r\n\032\n"
+
+
+_MODES = {
+ # supported bits/color combinations, and corresponding modes/rawmodes
+ (1, 0): ("1", "1"),
+ (2, 0): ("L", "L;2"),
+ (4, 0): ("L", "L;4"),
+ (8, 0): ("L", "L"),
+ (16,0): ("I", "I;16B"),
+ (8, 2): ("RGB", "RGB"),
+ (16,2): ("RGB", "RGB;16B"),
+ (1, 3): ("P", "P;1"),
+ (2, 3): ("P", "P;2"),
+ (4, 3): ("P", "P;4"),
+ (8, 3): ("P", "P"),
+ (8, 4): ("LA", "LA"),
+ (16,4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
+ (8, 6): ("RGBA", "RGBA"),
+ (16,6): ("RGBA", "RGBA;16B"),
+}
+
+
+# --------------------------------------------------------------------
+# Support classes. Suitable for PNG and related formats like MNG etc.
+
+class ChunkStream:
+
+ def __init__(self, fp):
+
+ self.fp = fp
+ self.queue = []
+
+ if not hasattr(Image.core, "crc32"):
+ self.crc = self.crc_skip
+
+ def read(self):
+ "Fetch a new chunk. Returns header information."
+
+ if self.queue:
+ cid, pos, len = self.queue[-1]
+ del self.queue[-1]
+ self.fp.seek(pos)
+ else:
+ s = self.fp.read(8)
+ cid = s[4:]
+ pos = self.fp.tell()
+ len = i32(s)
+
+ if not is_cid(cid):
+ raise SyntaxError, "broken PNG file (chunk %s)" % repr(cid)
+
+ return cid, pos, len
+
+ def close(self):
+ self.queue = self.crc = self.fp = None
+
+ def push(self, cid, pos, len):
+
+ self.queue.append((cid, pos, len))
+
+ def call(self, cid, pos, len):
+ "Call the appropriate chunk handler"
+
+ if Image.DEBUG:
+ print "STREAM", cid, pos, len
+ return getattr(self, "chunk_" + cid)(pos, len)
+
+ def crc(self, cid, data):
+ "Read and verify checksum"
+
+ crc1 = Image.core.crc32(data, Image.core.crc32(cid))
+ crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
+ if crc1 != crc2:
+ raise SyntaxError, "broken PNG file"\
+ "(bad header checksum in %s)" % cid
+
+ def crc_skip(self, cid, data):
+ "Read checksum. Used if the C module is not present"
+
+ self.fp.read(4)
+
+ def verify(self, endchunk = "IEND"):
+
+ # Simple approach; just calculate checksum for all remaining
+ # blocks. Must be called directly after open.
+
+ cids = []
+
+ while 1:
+ cid, pos, len = self.read()
+ if cid == endchunk:
+ break
+ self.crc(cid, ImageFile._safe_read(self.fp, len))
+ cids.append(cid)
+
+ return cids
+
+
+# --------------------------------------------------------------------
+# PNG chunk container (for use with save(pnginfo=))
+
+class PngInfo:
+
+ def __init__(self):
+ self.chunks = []
+
+ def add(self, cid, data):
+ self.chunks.append((cid, data))
+
+ def add_text(self, key, value, zip=0):
+ if zip:
+ import zlib
+ self.add("zTXt", key + "\0\0" + zlib.compress(value))
+ else:
+ self.add("tEXt", key + "\0" + value)
+
+# --------------------------------------------------------------------
+# PNG image stream (IHDR/IEND)
+
+class PngStream(ChunkStream):
+
+ def __init__(self, fp):
+
+ ChunkStream.__init__(self, fp)
+
+ # local copies of Image attributes
+ self.im_info = {}
+ self.im_size = (0,0)
+ self.im_mode = None
+ self.im_tile = None
+ self.im_palette = None
+
+ def chunk_IHDR(self, pos, len):
+
+ # image header
+ s = ImageFile._safe_read(self.fp, len)
+ self.im_size = i32(s), i32(s[4:])
+ try:
+ self.im_mode, self.im_rawmode = _MODES[(ord(s[8]), ord(s[9]))]
+ except:
+ pass
+ if ord(s[12]):
+ self.im_info["interlace"] = 1
+ if ord(s[11]):
+ raise SyntaxError, "unknown filter category"
+ return s
+
+ def chunk_IDAT(self, pos, len):
+
+ # image data
+ self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
+ self.im_idat = len
+ raise EOFError
+
+ def chunk_IEND(self, pos, len):
+
+ # end of PNG image
+ raise EOFError
+
+ def chunk_PLTE(self, pos, len):
+
+ # palette
+ s = ImageFile._safe_read(self.fp, len)
+ if self.im_mode == "P":
+ self.im_palette = "RGB", s
+ return s
+
+ def chunk_tRNS(self, pos, len):
+
+ # transparency
+ s = ImageFile._safe_read(self.fp, len)
+ if self.im_mode == "P":
+ i = string.find(s, chr(0))
+ if i >= 0:
+ self.im_info["transparency"] = i
+ elif self.im_mode == "L":
+ self.im_info["transparency"] = i16(s)
+ return s
+
+ def chunk_gAMA(self, pos, len):
+
+ # gamma setting
+ s = ImageFile._safe_read(self.fp, len)
+ self.im_info["gamma"] = i32(s) / 100000.0
+ return s
+
+ def chunk_pHYs(self, pos, len):
+
+ # pixels per unit
+ s = ImageFile._safe_read(self.fp, len)
+ px, py = i32(s), i32(s[4:])
+ unit = ord(s[8])
+ if unit == 1: # meter
+ dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5)
+ self.im_info["dpi"] = dpi
+ elif unit == 0:
+ self.im_info["aspect"] = px, py
+ return s
+
+ def chunk_tEXt(self, pos, len):
+
+ # text
+ s = ImageFile._safe_read(self.fp, len)
+ try:
+ k, v = string.split(s, "\0", 1)
+ except ValueError:
+ k = s; v = "" # fallback for broken tEXt tags
+ if k:
+ self.im_info[k] = v
+ return s
+
+
+# --------------------------------------------------------------------
+# PNG reader
+
+def _accept(prefix):
+ return prefix[:8] == _MAGIC
+
+##
+# Image plugin for PNG images.
+
+class PngImageFile(ImageFile.ImageFile):
+
+ format = "PNG"
+ format_description = "Portable network graphics"
+
+ def _open(self):
+
+ if self.fp.read(8) != _MAGIC:
+ raise SyntaxError, "not a PNG file"
+
+ #
+ # Parse headers up to the first IDAT chunk
+
+ self.png = PngStream(self.fp)
+
+ while 1:
+
+ #
+ # get next chunk
+
+ cid, pos, len = self.png.read()
+
+ try:
+ s = self.png.call(cid, pos, len)
+ except EOFError:
+ break
+ except AttributeError:
+ if Image.DEBUG:
+ print cid, pos, len, "(unknown)"
+ s = ImageFile._safe_read(self.fp, len)
+
+ self.png.crc(cid, s)
+
+ #
+ # Copy relevant attributes from the PngStream. An alternative
+ # would be to let the PngStream class modify these attributes
+ # directly, but that introduces circular references which are
+ # difficult to break if things go wrong in the decoder...
+ # (believe me, I've tried ;-)
+
+ self.mode = self.png.im_mode
+ self.size = self.png.im_size
+ self.info = self.png.im_info
+ self.tile = self.png.im_tile
+
+ if self.png.im_palette:
+ rawmode, data = self.png.im_palette
+ self.palette = ImagePalette.raw(rawmode, data)
+
+ self.__idat = len # used by load_read()
+
+
+ def verify(self):
+ "Verify PNG file"
+
+ if self.fp is None:
+ raise RuntimeError("verify must be called directly after open")
+
+ # back up to beginning of IDAT block
+ self.fp.seek(self.tile[0][2] - 8)
+
+ self.png.verify()
+ self.png.close()
+
+ self.fp = None
+
+ def load_prepare(self):
+ "internal: prepare to read PNG file"
+
+ if self.info.get("interlace"):
+ raise IOError("cannot read interlaced PNG files")
+
+ ImageFile.ImageFile.load_prepare(self)
+
+ def load_read(self, bytes):
+ "internal: read more image data"
+
+ while self.__idat == 0:
+ # end of chunk, skip forward to next one
+
+ self.fp.read(4) # CRC
+
+ cid, pos, len = self.png.read()
+
+ if cid not in ["IDAT", "DDAT"]:
+ self.png.push(cid, pos, len)
+ return ""
+
+ self.__idat = len # empty chunks are allowed
+
+ # read more data from this chunk
+ if bytes <= 0:
+ bytes = self.__idat
+ else:
+ bytes = min(bytes, self.__idat)
+
+ self.__idat = self.__idat - bytes
+
+ return self.fp.read(bytes)
+
+
+ def load_end(self):
+ "internal: finished reading image data"
+
+ self.png.close()
+ self.png = None
+
+
+# --------------------------------------------------------------------
+# PNG writer
+
+def o16(i):
+ return chr(i>>8&255) + chr(i&255)
+
+def o32(i):
+ return chr(i>>24&255) + chr(i>>16&255) + chr(i>>8&255) + chr(i&255)
+
+_OUTMODES = {
+ # supported PIL modes, and corresponding rawmodes/bits/color combinations
+ "1": ("1", chr(1)+chr(0)),
+ "L;1": ("L;1", chr(1)+chr(0)),
+ "L;2": ("L;2", chr(2)+chr(0)),
+ "L;4": ("L;4", chr(4)+chr(0)),
+ "L": ("L", chr(8)+chr(0)),
+ "LA": ("LA", chr(8)+chr(4)),
+ "I": ("I;16B", chr(16)+chr(0)),
+ "P;1": ("P;1", chr(1)+chr(3)),
+ "P;2": ("P;2", chr(2)+chr(3)),
+ "P;4": ("P;4", chr(4)+chr(3)),
+ "P": ("P", chr(8)+chr(3)),
+ "RGB": ("RGB", chr(8)+chr(2)),
+ "RGBA":("RGBA", chr(8)+chr(6)),
+}
+
+def putchunk(fp, cid, *data):
+ "Write a PNG chunk (including CRC field)"
+
+ data = string.join(data, "")
+
+ fp.write(o32(len(data)) + cid)
+ fp.write(data)
+ hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
+ fp.write(o16(hi) + o16(lo))
+
+class _idat:
+ # wrap output from the encoder in IDAT chunks
+
+ def __init__(self, fp, chunk):
+ self.fp = fp
+ self.chunk = chunk
+ def write(self, data):
+ self.chunk(self.fp, "IDAT", data)
+
+def _save(im, fp, filename, chunk=putchunk, check=0):
+ # save an image to disk (called by the save method)
+
+ mode = im.mode
+
+ if mode == "P":
+
+ #
+ # attempt to minimize storage requirements for palette images
+
+ if im.encoderinfo.has_key("bits"):
+
+ # number of bits specified by user
+ n = 1 << im.encoderinfo["bits"]
+
+ else:
+
+ # check palette contents
+ n = 256 # FIXME
+
+ if n <= 2:
+ bits = 1
+ elif n <= 4:
+ bits = 2
+ elif n <= 16:
+ bits = 4
+ else:
+ bits = 8
+
+ if bits != 8:
+ mode = "%s;%d" % (mode, bits)
+
+ # encoder options
+ if im.encoderinfo.has_key("dictionary"):
+ dictionary = im.encoderinfo["dictionary"]
+ else:
+ dictionary = ""
+
+ im.encoderconfig = (im.encoderinfo.has_key("optimize"), dictionary)
+
+ # get the corresponding PNG mode
+ try:
+ rawmode, mode = _OUTMODES[mode]
+ except KeyError:
+ raise IOError, "cannot write mode %s as PNG" % mode
+
+ if check:
+ return check
+
+ #
+ # write minimal PNG file
+
+ fp.write(_MAGIC)
+
+ chunk(fp, "IHDR",
+ o32(im.size[0]), o32(im.size[1]), # 0: size
+ mode, # 8: depth/type
+ chr(0), # 10: compression
+ chr(0), # 11: filter category
+ chr(0)) # 12: interlace flag
+
+ if im.mode == "P":
+ chunk(fp, "PLTE", im.im.getpalette("RGB"))
+
+ if im.encoderinfo.has_key("transparency"):
+ if im.mode == "P":
+ transparency = max(0, min(255, im.encoderinfo["transparency"]))
+ chunk(fp, "tRNS", chr(255) * transparency + chr(0))
+ elif im.mode == "L":
+ transparency = max(0, min(65535, im.encoderinfo["transparency"]))
+ chunk(fp, "tRNS", o16(transparency))
+ else:
+ raise IOError, "cannot use transparency for this mode"
+
+ if 0:
+ # FIXME: to be supported some day
+ chunk(fp, "gAMA", o32(int(gamma * 100000.0)))
+
+ dpi = im.encoderinfo.get("dpi")
+ if dpi:
+ chunk(fp, "pHYs",
+ o32(int(dpi[0] / 0.0254 + 0.5)),
+ o32(int(dpi[1] / 0.0254 + 0.5)),
+ chr(1))
+
+ info = im.encoderinfo.get("pnginfo")
+ if info:
+ for cid, data in info.chunks:
+ chunk(fp, cid, data)
+
+ ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)])
+
+ chunk(fp, "IEND", "")
+
+ try:
+ fp.flush()
+ except:
+ pass
+
+
+# --------------------------------------------------------------------
+# PNG chunk converter
+
+def getchunks(im, **params):
+ """Return a list of PNG chunks representing this image."""
+
+ class collector:
+ data = []
+ def write(self, data):
+ pass
+ def append(self, chunk):
+ self.data.append(chunk)
+
+ def append(fp, cid, *data):
+ data = string.join(data, "")
+ hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
+ crc = o16(hi) + o16(lo)
+ fp.append((cid, data, crc))
+
+ fp = collector()
+
+ try:
+ im.encoderinfo = params
+ _save(im, fp, None, append)
+ finally:
+ del im.encoderinfo
+
+ return fp.data
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open("PNG", PngImageFile, _accept)
+Image.register_save("PNG", _save)
+
+Image.register_extension("PNG", ".png")
+
+Image.register_mime("PNG", "image/png")
diff --git a/PIL/PpmImagePlugin.py b/PIL/PpmImagePlugin.py
new file mode 100644
index 0000000..b9b9f81
--- /dev/null
+++ b/PIL/PpmImagePlugin.py
@@ -0,0 +1,131 @@
+#
+# The Python Imaging Library.
+# $Id: PpmImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# PPM support for PIL
+#
+# History:
+# 96-03-24 fl Created
+# 98-03-06 fl Write RGBA images (as RGB, that is)
+#
+# Copyright (c) Secret Labs AB 1997-98.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.2"
+
+import string
+
+import Image, ImageFile
+
+#
+# --------------------------------------------------------------------
+
+MODES = {
+ # standard
+ "P4": "1",
+ "P5": "L",
+ "P6": "RGB",
+ # extensions
+ "P0CMYK": "CMYK",
+ # PIL extensions (for test purposes only)
+ "PyP": "P",
+ "PyRGBA": "RGBA",
+ "PyCMYK": "CMYK"
+}
+
+def _accept(prefix):
+ return prefix[0] == "P" and prefix[1] in "0456y"
+
+##
+# Image plugin for PBM, PGM, and PPM images.
+
+class PpmImageFile(ImageFile.ImageFile):
+
+ format = "PPM"
+ format_description = "Pbmplus image"
+
+ def _token(self, s = ""):
+ while 1: # read until next whitespace
+ c = self.fp.read(1)
+ if not c or c in string.whitespace:
+ break
+ s = s + c
+ return s
+
+ def _open(self):
+
+ # check magic
+ s = self.fp.read(1)
+ if s != "P":
+ raise SyntaxError, "not a PPM file"
+ mode = MODES[self._token(s)]
+
+ if mode == "1":
+ self.mode = "1"
+ rawmode = "1;I"
+ else:
+ self.mode = rawmode = mode
+
+ for ix in range(3):
+ while 1:
+ while 1:
+ s = self.fp.read(1)
+ if s not in string.whitespace:
+ break
+ if s != "#":
+ break
+ s = self.fp.readline()
+ s = int(self._token(s))
+ if ix == 0:
+ xsize = s
+ elif ix == 1:
+ ysize = s
+ if mode == "1":
+ break
+
+ self.size = xsize, ysize
+ self.tile = [("raw",
+ (0, 0, xsize, ysize),
+ self.fp.tell(),
+ (rawmode, 0, 1))]
+
+ # ALTERNATIVE: load via builtin debug function
+ # self.im = Image.core.open_ppm(self.filename)
+ # self.mode = self.im.mode
+ # self.size = self.im.size
+
+#
+# --------------------------------------------------------------------
+
+def _save(im, fp, filename):
+ if im.mode == "1":
+ rawmode, head = "1;I", "P4"
+ elif im.mode == "L":
+ rawmode, head = "L", "P5"
+ elif im.mode == "RGB":
+ rawmode, head = "RGB", "P6"
+ elif im.mode == "RGBA":
+ rawmode, head = "RGB", "P6"
+ else:
+ raise IOError, "cannot write mode %s as PPM" % im.mode
+ fp.write(head + "\n%d %d\n" % im.size)
+ if head != "P4":
+ fp.write("255\n")
+ ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))])
+
+ # ALTERNATIVE: save via builtin debug function
+ # im._dump(filename)
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("PPM", PpmImageFile, _accept)
+Image.register_save("PPM", _save)
+
+Image.register_extension("PPM", ".pbm")
+Image.register_extension("PPM", ".pgm")
+Image.register_extension("PPM", ".ppm")
diff --git a/PIL/PsdImagePlugin.py b/PIL/PsdImagePlugin.py
new file mode 100644
index 0000000..1e68c0d
--- /dev/null
+++ b/PIL/PsdImagePlugin.py
@@ -0,0 +1,271 @@
+#
+# The Python Imaging Library
+# $Id: PsdImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# Adobe PSD 2.5/3.0 file handling
+#
+# History:
+# 1995-09-01 fl Created
+# 1997-01-03 fl Read most PSD images
+# 1997-01-18 fl Fixed P and CMYK support
+# 2001-10-21 fl Added seek/tell support (for layers)
+#
+# Copyright (c) 1997-2001 by Secret Labs AB.
+# Copyright (c) 1995-2001 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.4"
+
+import string
+import Image, ImageFile, ImagePalette
+
+MODES = {
+ # (photoshop mode, bits) -> (pil mode, required channels)
+ (0, 1): ("1", 1),
+ (0, 8): ("L", 1),
+ (1, 8): ("L", 1),
+ (2, 8): ("P", 1),
+ (3, 8): ("RGB", 3),
+ (4, 8): ("CMYK", 4),
+ (7, 8): ("L", 1), # FIXME: multilayer
+ (8, 8): ("L", 1), # duotone
+ (9, 8): ("LAB", 3)
+}
+
+#
+# helpers
+
+def i16(c):
+ return ord(c[1]) + (ord(c[0])<<8)
+
+def i32(c):
+ return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
+
+# --------------------------------------------------------------------.
+# read PSD images
+
+def _accept(prefix):
+ return prefix[:4] == "8BPS"
+
+##
+# Image plugin for Photoshop images.
+
+class PsdImageFile(ImageFile.ImageFile):
+
+ format = "PSD"
+ format_description = "Adobe Photoshop"
+
+ def _open(self):
+
+ read = self.fp.read
+
+ #
+ # header
+
+ s = read(26)
+ if s[:4] != "8BPS" or i16(s[4:]) != 1:
+ raise SyntaxError, "not a PSD file"
+
+ psd_bits = i16(s[22:])
+ psd_channels = i16(s[12:])
+ psd_mode = i16(s[24:])
+
+ mode, channels = MODES[(psd_mode, psd_bits)]
+
+ if channels > psd_channels:
+ raise IOError, "not enough channels"
+
+ self.mode = mode
+ self.size = i32(s[18:]), i32(s[14:])
+
+ #
+ # color mode data
+
+ size = i32(read(4))
+ if size:
+ data = read(size)
+ if mode == "P" and size == 768:
+ self.palette = ImagePalette.raw("RGB;L", data)
+
+ #
+ # image resources
+
+ self.resources = []
+
+ size = i32(read(4))
+ if size:
+ # load resources
+ end = self.fp.tell() + size
+ while self.fp.tell() < end:
+ signature = read(4)
+ id = i16(read(2))
+ name = read(ord(read(1)))
+ if not (len(name) & 1):
+ read(1) # padding
+ data = read(i32(read(4)))
+ if (len(data) & 1):
+ read(1) # padding
+ self.resources.append((id, name, data))
+
+ #
+ # layer and mask information
+
+ self.layers = []
+
+ size = i32(read(4))
+ if size:
+ end = self.fp.tell() + size
+ size = i32(read(4))
+ if size:
+ self.layers = _layerinfo(self.fp)
+ self.fp.seek(end)
+
+ #
+ # image descriptor
+
+ self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels)
+
+ # keep the file open
+ self._fp = self.fp
+ self.frame = 0
+
+ def seek(self, layer):
+ # seek to given layer (1..max)
+ if layer == self.frame:
+ return
+ try:
+ if layer <= 0:
+ raise IndexError
+ name, mode, bbox, tile = self.layers[layer-1]
+ self.mode = mode
+ self.tile = tile
+ self.frame = layer
+ self.fp = self._fp
+ return name, bbox
+ except IndexError:
+ raise EOFError, "no such layer"
+
+ def tell(self):
+ # return layer number (0=image, 1..max=layers)
+ return self.frame
+
+ def load_prepare(self):
+ # create image memory if necessary
+ if not self.im or\
+ self.im.mode != self.mode or self.im.size != self.size:
+ self.im = Image.core.fill(self.mode, self.size, 0)
+ # create palette (optional)
+ if self.mode == "P":
+ Image.Image.load(self)
+
+def _layerinfo(file):
+ # read layerinfo block
+ layers = []
+ read = file.read
+
+ for i in range(abs(i16(read(2)))):
+
+ # bounding box
+ y0 = i32(read(4)); x0 = i32(read(4))
+ y1 = i32(read(4)); x1 = i32(read(4))
+
+ # image info
+ info = []
+ mode = []
+ for i in range(i16(read(2))):
+ type = i16(read(2))
+ if type == 65535:
+ m = "A"
+ else:
+ m = "RGB"[type]
+ mode.append(m)
+ size = i32(read(4))
+ info.append((m, size))
+
+ # figure out the image mode
+ mode.sort()
+ if mode == ["R"]:
+ mode = "L"
+ elif mode == ["B", "G", "R"]:
+ mode = "RGB"
+ elif mode == ["A", "B", "G", "R"]:
+ mode = "RGBA"
+ else:
+ mode = None # unknown
+
+ # skip over blend flags and extra information
+ filler = read(12)
+ name = None # FIXME
+ file.seek(i32(read(4)), 1)
+
+ layers.append((name, mode, (x0, y0, x1, y1)))
+
+ # get tiles
+ i = 0
+ for name, mode, bbox in layers:
+ tile = []
+ for m in mode:
+ t = _maketile(file, m, bbox, 1)
+ if t:
+ tile.extend(t)
+ layers[i] = name, mode, bbox, tile
+ i = i + 1
+
+ return layers
+
+def _maketile(file, mode, bbox, channels):
+
+ tile = None
+ read = file.read
+
+ compression = i16(read(2))
+
+ xsize = bbox[2] - bbox[0]
+ ysize = bbox[3] - bbox[1]
+
+ offset = file.tell()
+
+ if compression == 0:
+ #
+ # raw compression
+ tile = []
+ for channel in range(channels):
+ layer = mode[channel]
+ if mode == "CMYK":
+ layer = layer + ";I"
+ tile.append(("raw", bbox, offset, layer))
+ offset = offset + xsize*ysize
+
+ elif compression == 1:
+ #
+ # packbits compression
+ i = 0
+ tile = []
+ bytecount = read(channels * ysize * 2)
+ offset = file.tell()
+ for channel in range(channels):
+ layer = mode[channel]
+ if mode == "CMYK":
+ layer = layer + ";I"
+ tile.append(
+ ("packbits", bbox, offset, layer)
+ )
+ for y in range(ysize):
+ offset = offset + i16(bytecount[i:i+2])
+ i = i + 2
+
+ file.seek(offset)
+
+ if offset & 1:
+ read(1) # padding
+
+ return tile
+
+# --------------------------------------------------------------------
+# registry
+
+Image.register_open("PSD", PsdImageFile, _accept)
+
+Image.register_extension("PSD", ".psd")
diff --git a/PIL/SgiImagePlugin.py b/PIL/SgiImagePlugin.py
new file mode 100644
index 0000000..0ab741e
--- /dev/null
+++ b/PIL/SgiImagePlugin.py
@@ -0,0 +1,91 @@
+#
+# The Python Imaging Library.
+# $Id: SgiImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# SGI image file handling
+#
+# See "The SGI Image File Format (Draft version 0.97)", Paul Haeberli.
+# <ftp://ftp.sgi.com/graphics/SGIIMAGESPEC>
+#
+# History:
+# 1995-09-10 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1995.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+
+import string
+import Image, ImageFile
+
+
+def i16(c):
+ return ord(c[1]) + (ord(c[0])<<8)
+
+def i32(c):
+ return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
+
+
+def _accept(prefix):
+ return i16(prefix) == 474
+
+##
+# Image plugin for SGI images.
+
+class SgiImageFile(ImageFile.ImageFile):
+
+ format = "SGI"
+ format_description = "SGI Image File Format"
+
+ def _open(self):
+
+ # HEAD
+ s = self.fp.read(512)
+ if i16(s) != 474:
+ raise SyntaxError, "not an SGI image file"
+
+ # relevant header entries
+ compression = ord(s[2])
+
+ # bytes, dimension, zsize
+ layout = ord(s[3]), i16(s[4:]), i16(s[10:])
+
+ # determine mode from bytes/zsize
+ if layout == (1, 2, 1):
+ self.mode = "L"
+ elif layout == (1, 3, 3):
+ self.mode = "RGB"
+ else:
+ raise SyntaxError, "unsupported SGI image mode"
+
+ # size
+ self.size = i16(s[6:]), i16(s[8:])
+
+ # decoder info
+ if compression == 0:
+ if self.mode == "RGB":
+ # RGB images are band interleaved
+ size = self.size[0]*self.size[1]
+ self.tile = [("raw", (0,0)+self.size, 512, ("R",0,1)),
+ ("raw", (0,0)+self.size, 512+size, ("G",0,1)),
+ ("raw", (0,0)+self.size, 512+2*size, ("B",0,1))]
+ else:
+ self.tile = [("raw", (0,0)+self.size, 512, (self.mode, 0, 1))]
+ if compression == 1:
+ self.tile = [("sgi_rle", (0,0)+self.size, 512, (self.mode, 0, 1))]
+
+#
+# registry
+
+Image.register_open("SGI", SgiImageFile, _accept)
+
+Image.register_extension("SGI", ".bw")
+Image.register_extension("SGI", ".rgb")
+Image.register_extension("SGI", ".rgba")
+
+Image.register_extension("SGI", ".sgi") # really?
diff --git a/PIL/SpiderImagePlugin.py b/PIL/SpiderImagePlugin.py
new file mode 100644
index 0000000..e0fd045
--- /dev/null
+++ b/PIL/SpiderImagePlugin.py
@@ -0,0 +1,294 @@
+#
+# The Python Imaging Library.
+#
+# SPIDER image file handling
+#
+# History:
+# 2004-08-02 Created BB
+# 2006-03-02 added save method
+# 2006-03-13 added support for stack images
+#
+# Copyright (c) 2004 by Health Research Inc. (HRI) RENSSELAER, NY 12144.
+# Copyright (c) 2004 by William Baxter.
+# Copyright (c) 2004 by Secret Labs AB.
+# Copyright (c) 2004 by Fredrik Lundh.
+#
+
+##
+# Image plugin for the Spider image format. This format is is used
+# by the SPIDER software, in processing image data from electron
+# microscopy and tomography.
+##
+
+#
+# SpiderImagePlugin.py
+#
+# The Spider image format is used by SPIDER software, in processing
+# image data from electron microscopy and tomography.
+#
+# Spider home page:
+# http://www.wadsworth.org/spider_doc/spider/docs/spider.html
+#
+# Details about the Spider image format:
+# http://www.wadsworth.org/spider_doc/spider/docs/image_doc.html
+#
+
+import Image, ImageFile
+import os, string, struct, sys
+
+def isInt(f):
+ try:
+ i = int(f)
+ if f-i == 0: return 1
+ else: return 0
+ except:
+ return 0
+
+iforms = [1,3,-11,-12,-21,-22]
+
+# There is no magic number to identify Spider files, so just check a
+# series of header locations to see if they have reasonable values.
+# Returns no.of bytes in the header, if it is a valid Spider header,
+# otherwise returns 0
+
+def isSpiderHeader(t):
+ h = (99,) + t # add 1 value so can use spider header index start=1
+ # header values 1,2,5,12,13,22,23 should be integers
+ for i in [1,2,5,12,13,22,23]:
+ if not isInt(h[i]): return 0
+ # check iform
+ iform = int(h[5])
+ if not iform in iforms: return 0
+ # check other header values
+ labrec = int(h[13]) # no. records in file header
+ labbyt = int(h[22]) # total no. of bytes in header
+ lenbyt = int(h[23]) # record length in bytes
+ #print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt)
+ if labbyt != (labrec * lenbyt): return 0
+ # looks like a valid header
+ return labbyt
+
+def isSpiderImage(filename):
+ fp = open(filename,'rb')
+ f = fp.read(92) # read 23 * 4 bytes
+ fp.close()
+ bigendian = 1
+ t = struct.unpack('>23f',f) # try big-endian first
+ hdrlen = isSpiderHeader(t)
+ if hdrlen == 0:
+ bigendian = 0
+ t = struct.unpack('<23f',f) # little-endian
+ hdrlen = isSpiderHeader(t)
+ return hdrlen
+
+
+class SpiderImageFile(ImageFile.ImageFile):
+
+ format = "SPIDER"
+ format_description = "Spider 2D image"
+
+ def _open(self):
+ # check header
+ n = 27 * 4 # read 27 float values
+ f = self.fp.read(n)
+
+ try:
+ self.bigendian = 1
+ t = struct.unpack('>27f',f) # try big-endian first
+ hdrlen = isSpiderHeader(t)
+ if hdrlen == 0:
+ self.bigendian = 0
+ t = struct.unpack('<27f',f) # little-endian
+ hdrlen = isSpiderHeader(t)
+ if hdrlen == 0:
+ raise SyntaxError, "not a valid Spider file"
+ except struct.error:
+ raise SyntaxError, "not a valid Spider file"
+
+ h = (99,) + t # add 1 value : spider header index starts at 1
+ iform = int(h[5])
+ if iform != 1:
+ raise SyntaxError, "not a Spider 2D image"
+
+ self.size = int(h[12]), int(h[2]) # size in pixels (width, height)
+ self.istack = int(h[24])
+ self.imgnumber = int(h[27])
+
+ if self.istack == 0 and self.imgnumber == 0:
+ # stk=0, img=0: a regular 2D image
+ offset = hdrlen
+ self.nimages = 1
+ elif self.istack > 0 and self.imgnumber == 0:
+ # stk>0, img=0: Opening the stack for the first time
+ self.imgbytes = int(h[12]) * int(h[2]) * 4
+ self.hdrlen = hdrlen
+ self.nimages = int(h[26])
+ # Point to the first image in the stack
+ offset = hdrlen * 2
+ self.imgnumber = 1
+ elif self.istack == 0 and self.imgnumber > 0:
+ # stk=0, img>0: an image within the stack
+ offset = hdrlen + self.stkoffset
+ self.istack = 2 # So Image knows it's still a stack
+ else:
+ raise SyntaxError, "inconsistent stack header values"
+
+ if self.bigendian:
+ self.rawmode = "F;32BF"
+ else:
+ self.rawmode = "F;32F"
+ self.mode = "F"
+
+ self.tile = [("raw", (0, 0) + self.size, offset,
+ (self.rawmode, 0, 1))]
+ self.__fp = self.fp # FIXME: hack
+
+ # 1st image index is zero (although SPIDER imgnumber starts at 1)
+ def tell(self):
+ if self.imgnumber < 1:
+ return 0
+ else:
+ return self.imgnumber - 1
+
+ def seek(self, frame):
+ if self.istack == 0:
+ return
+ if frame >= self.nimages:
+ raise EOFError, "attempt to seek past end of file"
+ self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
+ self.fp = self.__fp
+ self.fp.seek(self.stkoffset)
+ self._open()
+
+ # returns a byte image after rescaling to 0..255
+ def convert2byte(self, depth=255):
+ (min, max) = self.getextrema()
+ m = 1
+ if max != min:
+ m = depth / (max-min)
+ b = -m * min
+ return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
+
+ # returns a ImageTk.PhotoImage object, after rescaling to 0..255
+ def tkPhotoImage(self):
+ import ImageTk
+ return ImageTk.PhotoImage(self.convert2byte(), palette=256)
+
+# --------------------------------------------------------------------
+# Image series
+
+# given a list of filenames, return a list of images
+def loadImageSeries(filelist=None):
+ " create a list of Image.images for use in montage "
+ if filelist == None or len(filelist) < 1:
+ return
+
+ imglist = []
+ for img in filelist:
+ if not os.path.exists(img):
+ print "unable to find %s" % img
+ continue
+ try:
+ im = Image.open(img).convert2byte()
+ except:
+ if not isSpiderImage(img):
+ print img + " is not a Spider image file"
+ continue
+ im.info['filename'] = img
+ imglist.append(im)
+ return imglist
+
+# --------------------------------------------------------------------
+# For saving images in Spider format
+
+def makeSpiderHeader(im):
+ nsam,nrow = im.size
+ lenbyt = nsam * 4 # There are labrec records in the header
+ labrec = 1024 / lenbyt
+ if 1024%lenbyt != 0: labrec += 1
+ labbyt = labrec * lenbyt
+ hdr = []
+ nvalues = labbyt / 4
+ for i in range(nvalues):
+ hdr.append(0.0)
+
+ if len(hdr) < 23:
+ return []
+
+ # NB these are Fortran indices
+ hdr[1] = 1.0 # nslice (=1 for an image)
+ hdr[2] = float(nrow) # number of rows per slice
+ hdr[5] = 1.0 # iform for 2D image
+ hdr[12] = float(nsam) # number of pixels per line
+ hdr[13] = float(labrec) # number of records in file header
+ hdr[22] = float(labbyt) # total number of bytes in header
+ hdr[23] = float(lenbyt) # record length in bytes
+
+ # adjust for Fortran indexing
+ hdr = hdr[1:]
+ hdr.append(0.0)
+ # pack binary data into a string
+ hdrstr = []
+ for v in hdr:
+ hdrstr.append(struct.pack('f',v))
+ return hdrstr
+
+def _save(im, fp, filename):
+ if im.mode[0] != "F":
+ im = im.convert('F')
+
+ hdr = makeSpiderHeader(im)
+ if len(hdr) < 256:
+ raise IOError, "Error creating Spider header"
+
+ # write the SPIDER header
+ try:
+ fp = open(filename, 'wb')
+ except:
+ raise IOError, "Unable to open %s for writing" % filename
+ fp.writelines(hdr)
+
+ rawmode = "F;32NF" #32-bit native floating point
+ ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode,0,1))])
+
+ fp.close()
+
+def _save_spider(im, fp, filename):
+ # get the filename extension and register it with Image
+ fn, ext = os.path.splitext(filename)
+ Image.register_extension("SPIDER", ext)
+ _save(im, fp, filename)
+
+# --------------------------------------------------------------------
+
+Image.register_open("SPIDER", SpiderImageFile)
+Image.register_save("SPIDER", _save_spider)
+
+if __name__ == "__main__":
+
+ if not sys.argv[1:]:
+ print "Syntax: python SpiderImagePlugin.py Spiderimage [outfile]"
+ sys.exit()
+
+ filename = sys.argv[1]
+ if not isSpiderImage(filename):
+ print "input image must be in Spider format"
+ sys.exit()
+
+ outfile = ""
+ if len(sys.argv[1:]) > 1:
+ outfile = sys.argv[2]
+
+ im = Image.open(filename)
+ print "image: " + str(im)
+ print "format: " + str(im.format)
+ print "size: " + str(im.size)
+ print "mode: " + str(im.mode)
+ print "max, min: ",
+ print im.getextrema()
+
+ if outfile != "":
+ # perform some image operation
+ im = im.transpose(Image.FLIP_LEFT_RIGHT)
+ print "saving a flipped version of %s as %s " % (os.path.basename(filename), outfile)
+ im.save(outfile, "SPIDER")
diff --git a/PIL/SunImagePlugin.py b/PIL/SunImagePlugin.py
new file mode 100644
index 0000000..2e7fe52
--- /dev/null
+++ b/PIL/SunImagePlugin.py
@@ -0,0 +1,87 @@
+#
+# The Python Imaging Library.
+# $Id: SunImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# Sun image file handling
+#
+# History:
+# 1995-09-10 fl Created
+# 1996-05-28 fl Fixed 32-bit alignment
+# 1998-12-29 fl Import ImagePalette module
+# 2001-12-18 fl Fixed palette loading (from Jean-Claude Rimbault)
+#
+# Copyright (c) 1997-2001 by Secret Labs AB
+# Copyright (c) 1995-1996 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.3"
+
+
+import string
+import Image, ImageFile, ImagePalette
+
+
+def i16(c):
+ return ord(c[1]) + (ord(c[0])<<8)
+
+def i32(c):
+ return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
+
+
+def _accept(prefix):
+ return i32(prefix) == 0x59a66a95
+
+##
+# Image plugin for Sun raster files.
+
+class SunImageFile(ImageFile.ImageFile):
+
+ format = "SUN"
+ format_description = "Sun Raster File"
+
+ def _open(self):
+
+ # HEAD
+ s = self.fp.read(32)
+ if i32(s) != 0x59a66a95:
+ raise SyntaxError, "not an SUN raster file"
+
+ offset = 32
+
+ self.size = i32(s[4:8]), i32(s[8:12])
+
+ depth = i32(s[12:16])
+ if depth == 1:
+ self.mode, rawmode = "1", "1;I"
+ elif depth == 8:
+ self.mode = rawmode = "L"
+ elif depth == 24:
+ self.mode, rawmode = "RGB", "BGR"
+ else:
+ raise SyntaxError, "unsupported mode"
+
+ compression = i32(s[20:24])
+
+ if i32(s[24:28]) != 0:
+ length = i32(s[28:32])
+ offset = offset + length
+ self.palette = ImagePalette.raw("RGB;L", self.fp.read(length))
+ if self.mode == "L":
+ self.mode = rawmode = "P"
+
+ stride = (((self.size[0] * depth + 7) / 8) + 3) & (~3)
+
+ if compression == 1:
+ self.tile = [("raw", (0,0)+self.size, offset, (rawmode, stride))]
+ elif compression == 2:
+ self.tile = [("sun_rle", (0,0)+self.size, offset, rawmode)]
+
+#
+# registry
+
+Image.register_open("SUN", SunImageFile, _accept)
+
+Image.register_extension("SUN", ".ras")
diff --git a/PIL/TarIO.py b/PIL/TarIO.py
new file mode 100644
index 0000000..fbab8ca
--- /dev/null
+++ b/PIL/TarIO.py
@@ -0,0 +1,57 @@
+#
+# The Python Imaging Library.
+# $Id: TarIO.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# read files from within a tar file
+#
+# History:
+# 95-06-18 fl Created
+# 96-05-28 fl Open files in binary mode
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1995-96.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import ContainerIO
+import string
+
+##
+# A file object that provides read access to a given member of a TAR
+# file.
+
+class TarIO(ContainerIO.ContainerIO):
+
+ ##
+ # Create file object.
+ #
+ # @param tarfile Name of TAR file.
+ # @param file Name of member file.
+
+ def __init__(self, tarfile, file):
+
+ fh = open(tarfile, "rb")
+
+ while 1:
+
+ s = fh.read(512)
+ if len(s) != 512:
+ raise IOError, "unexpected end of tar file"
+
+ name = s[:100]
+ i = string.find(name, chr(0))
+ if i == 0:
+ raise IOError, "cannot find subfile"
+ if i > 0:
+ name = name[:i]
+
+ size = string.atoi(s[124:136], 8)
+
+ if file == name:
+ break
+
+ fh.seek((size + 511) & (~511), 1)
+
+ # Open region
+ ContainerIO.ContainerIO.__init__(self, fh, fh.tell(), size)
diff --git a/PIL/TgaImagePlugin.py b/PIL/TgaImagePlugin.py
new file mode 100644
index 0000000..e709de8
--- /dev/null
+++ b/PIL/TgaImagePlugin.py
@@ -0,0 +1,133 @@
+#
+# The Python Imaging Library.
+# $Id: TgaImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# TGA file handling
+#
+# History:
+# 95-09-01 fl created (reads 24-bit files only)
+# 97-01-04 fl support more TGA versions, including compressed images
+# 98-07-04 fl fixed orientation and alpha layer bugs
+# 98-09-11 fl fixed orientation for runlength decoder
+#
+# Copyright (c) Secret Labs AB 1997-98.
+# Copyright (c) Fredrik Lundh 1995-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.3"
+
+import Image, ImageFile, ImagePalette
+
+
+def i16(c):
+ return ord(c[0]) + (ord(c[1])<<8)
+
+def i32(c):
+ return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24)
+
+
+MODES = {
+ # map imagetype/depth to rawmode
+ (1, 8): "P",
+ (3, 1): "1",
+ (3, 8): "L",
+ (2, 16): "BGR;5",
+ (2, 24): "BGR",
+ (2, 32): "BGRA",
+}
+
+
+def _accept(prefix):
+ return prefix[0] == "\0"
+
+##
+# Image plugin for Targa files.
+
+class TgaImageFile(ImageFile.ImageFile):
+
+ format = "TGA"
+ format_description = "Targa"
+
+ def _open(self):
+
+ # process header
+ s = self.fp.read(18)
+
+ id = ord(s[0])
+
+ colormaptype = ord(s[1])
+ imagetype = ord(s[2])
+
+ depth = ord(s[16])
+
+ flags = ord(s[17])
+
+ self.size = i16(s[12:]), i16(s[14:])
+
+ # validate header fields
+ if id != 0 or colormaptype not in (0, 1) or\
+ self.size[0] <= 0 or self.size[1] <= 0 or\
+ depth not in (8, 16, 24, 32):
+ raise SyntaxError, "not a TGA file"
+
+ # image mode
+ if imagetype in (3, 11):
+ self.mode = "L"
+ if depth == 1:
+ self.mode = "1" # ???
+ elif imagetype in (1, 9):
+ self.mode = "P"
+ elif imagetype in (2, 10):
+ self.mode = "RGB"
+ if depth == 32:
+ self.mode = "RGBA"
+ else:
+ raise SyntaxError, "unknown TGA mode"
+
+ # orientation
+ orientation = flags & 0x30
+ if orientation == 0x20:
+ orientation = 1
+ elif not orientation:
+ orientation = -1
+ else:
+ raise SyntaxError, "unknown TGA orientation"
+
+ if imagetype & 8:
+ self.info["compression"] = "tga_rle"
+
+ if colormaptype:
+ # read palette
+ start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:])
+ if mapdepth == 16:
+ self.palette = ImagePalette.raw("BGR;16",
+ "\0"*2*start + self.fp.read(2*size))
+ elif mapdepth == 24:
+ self.palette = ImagePalette.raw("BGR",
+ "\0"*3*start + self.fp.read(3*size))
+ elif mapdepth == 32:
+ self.palette = ImagePalette.raw("BGRA",
+ "\0"*4*start + self.fp.read(4*size))
+
+ # setup tile descriptor
+ try:
+ rawmode = MODES[(imagetype&7, depth)]
+ if imagetype & 8:
+ # compressed
+ self.tile = [("tga_rle", (0, 0)+self.size,
+ self.fp.tell(), (rawmode, orientation, depth))]
+ else:
+ self.tile = [("raw", (0, 0)+self.size,
+ self.fp.tell(), (rawmode, 0, orientation))]
+ except KeyError:
+ pass # cannot decode
+
+#
+# registry
+
+Image.register_open("TGA", TgaImageFile, _accept)
+
+Image.register_extension("TGA", ".tga")
diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py
new file mode 100644
index 0000000..178066c
--- /dev/null
+++ b/PIL/TiffImagePlugin.py
@@ -0,0 +1,763 @@
+#
+# The Python Imaging Library.
+# $Id: TiffImagePlugin.py 2803 2006-07-31 19:18:57Z fredrik $
+#
+# TIFF file handling
+#
+# TIFF is a flexible, if somewhat aged, image file format originally
+# defined by Aldus. Although TIFF supports a wide variety of pixel
+# layouts and compression methods, the name doesn't really stand for
+# "thousands of incompatible file formats," it just feels that way.
+#
+# To read TIFF data from a stream, the stream must be seekable. For
+# progressive decoding, make sure to use TIFF files where the tag
+# directory is placed first in the file.
+#
+# History:
+# 1995-09-01 fl Created
+# 1996-05-04 fl Handle JPEGTABLES tag
+# 1996-05-18 fl Fixed COLORMAP support
+# 1997-01-05 fl Fixed PREDICTOR support
+# 1997-08-27 fl Added support for rational tags (from Perry Stoll)
+# 1998-01-10 fl Fixed seek/tell (from Jan Blom)
+# 1998-07-15 fl Use private names for internal variables
+# 1999-06-13 fl Rewritten for PIL 1.0 (1.0)
+# 2000-10-11 fl Additional fixes for Python 2.0 (1.1)
+# 2001-04-17 fl Fixed rewind support (seek to frame 0) (1.2)
+# 2001-05-12 fl Added write support for more tags (from Greg Couch) (1.3)
+# 2001-12-18 fl Added workaround for broken Matrox library
+# 2002-01-18 fl Don't mess up if photometric tag is missing (D. Alan Stewart)
+# 2003-05-19 fl Check FILLORDER tag
+# 2003-09-26 fl Added RGBa support
+# 2004-02-24 fl Added DPI support; fixed rational write support
+# 2005-02-07 fl Added workaround for broken Corel Draw 10 files
+# 2006-01-09 fl Added support for float/double tags (from Russell Nelson)
+#
+# Copyright (c) 1997-2006 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1995-1997 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "1.3.5"
+
+import Image, ImageFile
+import ImagePalette
+
+import array, string, sys
+
+try:
+ if sys.byteorder == "little":
+ byteorder = "II"
+ else:
+ byteorder = "MM"
+except AttributeError:
+ if ord(array.array("i",[1]).tostring()[0]):
+ byteorder = "II"
+ else:
+ byteorder = "MM"
+
+#
+# --------------------------------------------------------------------
+# Read TIFF files
+
+def il16(c,o=0):
+ return ord(c[o]) + (ord(c[o+1])<<8)
+def il32(c,o=0):
+ return ord(c[o]) + (ord(c[o+1])<<8) + (ord(c[o+2])<<16) + (ord(c[o+3])<<24)
+def ol16(i):
+ return chr(i&255) + chr(i>>8&255)
+def ol32(i):
+ return chr(i&255) + chr(i>>8&255) + chr(i>>16&255) + chr(i>>24&255)
+
+def ib16(c,o=0):
+ return ord(c[o+1]) + (ord(c[o])<<8)
+def ib32(c,o=0):
+ return ord(c[o+3]) + (ord(c[o+2])<<8) + (ord(c[o+1])<<16) + (ord(c[o])<<24)
+
+# a few tag names, just to make the code below a bit more readable
+IMAGEWIDTH = 256
+IMAGELENGTH = 257
+BITSPERSAMPLE = 258
+COMPRESSION = 259
+PHOTOMETRIC_INTERPRETATION = 262
+FILLORDER = 266
+IMAGEDESCRIPTION = 270
+STRIPOFFSETS = 273
+SAMPLESPERPIXEL = 277
+ROWSPERSTRIP = 278
+STRIPBYTECOUNTS = 279
+X_RESOLUTION = 282
+Y_RESOLUTION = 283
+PLANAR_CONFIGURATION = 284
+RESOLUTION_UNIT = 296
+SOFTWARE = 305
+DATE_TIME = 306
+ARTIST = 315
+PREDICTOR = 317
+COLORMAP = 320
+EXTRASAMPLES = 338
+SAMPLEFORMAT = 339
+JPEGTABLES = 347
+COPYRIGHT = 33432
+IPTC_NAA_CHUNK = 33723 # newsphoto properties
+PHOTOSHOP_CHUNK = 34377 # photoshop properties
+
+COMPRESSION_INFO = {
+ # Compression => pil compression name
+ 1: "raw",
+ 2: "tiff_ccitt",
+ 3: "group3",
+ 4: "group4",
+ 5: "tiff_lzw",
+ 6: "tiff_jpeg", # obsolete
+ 7: "jpeg",
+ 32771: "tiff_raw_16", # 16-bit padding
+ 32773: "packbits"
+}
+
+OPEN_INFO = {
+ # (PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,
+ # ExtraSamples) => mode, rawmode
+ (0, 1, 1, (1,), ()): ("1", "1;I"),
+ (0, 1, 2, (1,), ()): ("1", "1;IR"),
+ (0, 1, 1, (8,), ()): ("L", "L;I"),
+ (0, 1, 2, (8,), ()): ("L", "L;IR"),
+ (1, 1, 1, (1,), ()): ("1", "1"),
+ (1, 1, 2, (1,), ()): ("1", "1;R"),
+ (1, 1, 1, (8,), ()): ("L", "L"),
+ (1, 1, 1, (8,8), (2,)): ("LA", "LA"),
+ (1, 1, 2, (8,), ()): ("L", "L;R"),
+ (1, 1, 1, (16,), ()): ("I;16", "I;16"),
+ (1, 2, 1, (16,), ()): ("I;16S", "I;16S"),
+ (1, 2, 1, (32,), ()): ("I", "I;32S"),
+ (1, 3, 1, (32,), ()): ("F", "F;32F"),
+ (2, 1, 1, (8,8,8), ()): ("RGB", "RGB"),
+ (2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"),
+ (2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"),
+ (2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"),
+ (2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"),
+ (2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10
+ (3, 1, 1, (1,), ()): ("P", "P;1"),
+ (3, 1, 2, (1,), ()): ("P", "P;1R"),
+ (3, 1, 1, (2,), ()): ("P", "P;2"),
+ (3, 1, 2, (2,), ()): ("P", "P;2R"),
+ (3, 1, 1, (4,), ()): ("P", "P;4"),
+ (3, 1, 2, (4,), ()): ("P", "P;4R"),
+ (3, 1, 1, (8,), ()): ("P", "P"),
+ (3, 1, 1, (8,8), (2,)): ("PA", "PA"),
+ (3, 1, 2, (8,), ()): ("P", "P;R"),
+ (5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"),
+ (6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"),
+ (8, 1, 1, (8,8,8), ()): ("LAB", "LAB"),
+}
+
+PREFIXES = ["MM\000\052", "II\052\000", "II\xBC\000"]
+
+def _accept(prefix):
+ return prefix[:4] in PREFIXES
+
+##
+# Wrapper for TIFF IFDs.
+
+class ImageFileDirectory:
+
+ # represents a TIFF tag directory. to speed things up,
+ # we don't decode tags unless they're asked for.
+
+ def __init__(self, prefix="II"):
+ self.prefix = prefix[:2]
+ if self.prefix == "MM":
+ self.i16, self.i32 = ib16, ib32
+ # FIXME: save doesn't yet support big-endian mode...
+ elif self.prefix == "II":
+ self.i16, self.i32 = il16, il32
+ self.o16, self.o32 = ol16, ol32
+ else:
+ raise SyntaxError("not a TIFF IFD")
+ self.reset()
+
+ def reset(self):
+ self.tags = {}
+ self.tagdata = {}
+ self.next = None
+
+ # dictionary API (sort of)
+
+ def keys(self):
+ return self.tagdata.keys() + self.tags.keys()
+
+ def items(self):
+ items = self.tags.items()
+ for tag in self.tagdata.keys():
+ items.append((tag, self[tag]))
+ return items
+
+ def __len__(self):
+ return len(self.tagdata) + len(self.tags)
+
+ def __getitem__(self, tag):
+ try:
+ return self.tags[tag]
+ except KeyError:
+ type, data = self.tagdata[tag] # unpack on the fly
+ size, handler = self.load_dispatch[type]
+ self.tags[tag] = data = handler(self, data)
+ del self.tagdata[tag]
+ return data
+
+ def get(self, tag, default=None):
+ try:
+ return self[tag]
+ except KeyError:
+ return default
+
+ def getscalar(self, tag, default=None):
+ try:
+ value = self[tag]
+ if len(value) != 1:
+ if tag == SAMPLEFORMAT:
+ # work around broken (?) matrox library
+ # (from Ted Wright, via Bob Klimek)
+ raise KeyError # use default
+ raise ValueError, "not a scalar"
+ return value[0]
+ except KeyError:
+ if default is None:
+ raise
+ return default
+
+ def has_key(self, tag):
+ return self.tags.has_key(tag) or self.tagdata.has_key(tag)
+
+ def __setitem__(self, tag, value):
+ if type(value) is not type(()):
+ value = (value,)
+ self.tags[tag] = value
+
+ # load primitives
+
+ load_dispatch = {}
+
+ def load_byte(self, data):
+ l = []
+ for i in range(len(data)):
+ l.append(ord(data[i]))
+ return tuple(l)
+ load_dispatch[1] = (1, load_byte)
+
+ def load_string(self, data):
+ if data[-1:] == '\0':
+ data = data[:-1]
+ return data
+ load_dispatch[2] = (1, load_string)
+
+ def load_short(self, data):
+ l = []
+ for i in range(0, len(data), 2):
+ l.append(self.i16(data, i))
+ return tuple(l)
+ load_dispatch[3] = (2, load_short)
+
+ def load_long(self, data):
+ l = []
+ for i in range(0, len(data), 4):
+ l.append(self.i32(data, i))
+ return tuple(l)
+ load_dispatch[4] = (4, load_long)
+
+ def load_rational(self, data):
+ l = []
+ for i in range(0, len(data), 8):
+ l.append((self.i32(data, i), self.i32(data, i+4)))
+ return tuple(l)
+ load_dispatch[5] = (8, load_rational)
+
+ def load_float(self, data):
+ a = array.array("f", data)
+ if self.prefix != byteorder:
+ a.byteswap()
+ return tuple(a)
+ load_dispatch[11] = (4, load_float)
+
+ def load_double(self, data):
+ a = array.array("d", data)
+ if self.prefix != byteorder:
+ a.byteswap()
+ return tuple(a)
+ load_dispatch[12] = (8, load_double)
+
+ def load_undefined(self, data):
+ # Untyped data
+ return data
+ load_dispatch[7] = (1, load_undefined)
+
+ def load(self, fp):
+ # load tag dictionary
+
+ self.reset()
+
+ i16 = self.i16
+ i32 = self.i32
+
+ for i in range(i16(fp.read(2))):
+
+ ifd = fp.read(12)
+
+ tag, typ = i16(ifd), i16(ifd, 2)
+
+ if Image.DEBUG:
+ import TiffTags
+ tagname = TiffTags.TAGS.get(tag, "unknown")
+ typname = TiffTags.TYPES.get(typ, "unknown")
+ print "tag: %s (%d)" % (tagname, tag),
+ print "- type: %s (%d)" % (typname, typ),
+
+ try:
+ dispatch = self.load_dispatch[typ]
+ except KeyError:
+ if Image.DEBUG:
+ print "- unsupported type", typ
+ continue # ignore unsupported type
+
+ size, handler = dispatch
+
+ size = size * i32(ifd, 4)
+
+ # Get and expand tag value
+ if size > 4:
+ here = fp.tell()
+ fp.seek(i32(ifd, 8))
+ data = ImageFile._safe_read(fp, size)
+ fp.seek(here)
+ else:
+ data = ifd[8:8+size]
+
+ if len(data) != size:
+ raise IOError, "not enough data"
+
+ self.tagdata[tag] = typ, data
+
+ if Image.DEBUG:
+ if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK):
+ print "- value: <table: %d bytes>" % size
+ else:
+ print "- value:", self[tag]
+
+ self.next = i32(fp.read(4))
+
+ # save primitives
+
+ def save(self, fp):
+
+ o16 = self.o16
+ o32 = self.o32
+
+ fp.write(o16(len(self.tags)))
+
+ # always write in ascending tag order
+ tags = self.tags.items()
+ tags.sort()
+
+ directory = []
+ append = directory.append
+
+ offset = fp.tell() + len(self.tags) * 12 + 4
+
+ stripoffsets = None
+
+ # pass 1: convert tags to binary format
+ for tag, value in tags:
+
+ if Image.DEBUG:
+ import TiffTags
+ tagname = TiffTags.TAGS.get(tag, "unknown")
+ print "save: %s (%d)" % (tagname, tag),
+ print "- value:", value
+
+ if type(value[0]) is type(""):
+ # string data
+ typ = 2
+ data = value = string.join(value, "\0") + "\0"
+
+ else:
+ # integer data
+ if tag == STRIPOFFSETS:
+ stripoffsets = len(directory)
+ typ = 4 # to avoid catch-22
+ elif tag in (X_RESOLUTION, Y_RESOLUTION):
+ # identify rational data fields
+ typ = 5
+ else:
+ typ = 3
+ for v in value:
+ if v >= 65536:
+ typ = 4
+ if typ == 3:
+ data = string.join(map(o16, value), "")
+ else:
+ data = string.join(map(o32, value), "")
+
+ # figure out if data fits into the directory
+ if len(data) == 4:
+ append((tag, typ, len(value), data, ""))
+ elif len(data) < 4:
+ append((tag, typ, len(value), data + (4-len(data))*"\0", ""))
+ else:
+ count = len(value)
+ if typ == 5:
+ count = count / 2 # adjust for rational data field
+ append((tag, typ, count, o32(offset), data))
+ offset = offset + len(data)
+ if offset & 1:
+ offset = offset + 1 # word padding
+
+ # update strip offset data to point beyond auxiliary data
+ if stripoffsets is not None:
+ tag, typ, count, value, data = directory[stripoffsets]
+ assert not data, "multistrip support not yet implemented"
+ value = o32(self.i32(value) + offset)
+ directory[stripoffsets] = tag, typ, count, value, data
+
+ # pass 2: write directory to file
+ for tag, typ, count, value, data in directory:
+ if Image.DEBUG > 1:
+ print tag, typ, count, repr(value), repr(data)
+ fp.write(o16(tag) + o16(typ) + o32(count) + value)
+ fp.write("\0\0\0\0") # end of directory
+
+ # pass 3: write auxiliary data to file
+ for tag, typ, count, value, data in directory:
+ fp.write(data)
+ if len(data) & 1:
+ fp.write("\0")
+
+ return offset
+
+##
+# Image plugin for TIFF files.
+
+class TiffImageFile(ImageFile.ImageFile):
+
+ format = "TIFF"
+ format_description = "Adobe TIFF"
+
+ def _open(self):
+ "Open the first image in a TIFF file"
+
+ # Header
+ ifh = self.fp.read(8)
+
+ if ifh[:4] not in PREFIXES:
+ raise SyntaxError, "not a TIFF file"
+
+ # image file directory (tag dictionary)
+ self.tag = self.ifd = ImageFileDirectory(ifh[:2])
+
+ # setup frame pointers
+ self.__first = self.__next = self.ifd.i32(ifh, 4)
+ self.__frame = -1
+ self.__fp = self.fp
+
+ # and load the first frame
+ self._seek(0)
+
+ def seek(self, frame):
+ "Select a given frame as current image"
+
+ if frame < 0:
+ frame = 0
+ self._seek(frame)
+
+ def tell(self):
+ "Return the current frame number"
+
+ return self._tell()
+
+ def _seek(self, frame):
+
+ self.fp = self.__fp
+ if frame < self.__frame:
+ # rewind file
+ self.__frame = -1
+ self.__next = self.__first
+ while self.__frame < frame:
+ if not self.__next:
+ raise EOFError, "no more images in TIFF file"
+ self.fp.seek(self.__next)
+ self.tag.load(self.fp)
+ self.__next = self.tag.next
+ self.__frame = self.__frame + 1
+ self._setup()
+
+ def _tell(self):
+
+ return self.__frame
+
+ def _decoder(self, rawmode, layer):
+ "Setup decoder contexts"
+
+ args = None
+ if rawmode == "RGB" and self._planar_configuration == 2:
+ rawmode = rawmode[layer]
+ compression = self._compression
+ if compression == "raw":
+ args = (rawmode, 0, 1)
+ elif compression == "jpeg":
+ args = rawmode, ""
+ if self.tag.has_key(JPEGTABLES):
+ # Hack to handle abbreviated JPEG headers
+ self.tile_prefix = self.tag[JPEGTABLES]
+ elif compression == "packbits":
+ args = rawmode
+ elif compression == "tiff_lzw":
+ args = rawmode
+ if self.tag.has_key(317):
+ # Section 14: Differencing Predictor
+ self.decoderconfig = (self.tag[PREDICTOR][0],)
+
+ return args
+
+ def _setup(self):
+ "Setup this image object based on current tags"
+
+ if self.tag.has_key(0xBC01):
+ raise IOError, "Windows Media Photo files not yet supported"
+
+ getscalar = self.tag.getscalar
+
+ # extract relevant tags
+ self._compression = COMPRESSION_INFO[getscalar(COMPRESSION, 1)]
+ self._planar_configuration = getscalar(PLANAR_CONFIGURATION, 1)
+
+ # photometric is a required tag, but not everyone is reading
+ # the specification
+ photo = getscalar(PHOTOMETRIC_INTERPRETATION, 0)
+
+ fillorder = getscalar(FILLORDER, 1)
+
+ if Image.DEBUG:
+ print "*** Summary ***"
+ print "- compression:", self._compression
+ print "- photometric_interpretation:", photo
+ print "- planar_configuration:", self._planar_configuration
+ print "- fill_order:", fillorder
+
+ # size
+ xsize = getscalar(IMAGEWIDTH)
+ ysize = getscalar(IMAGELENGTH)
+ self.size = xsize, ysize
+
+ if Image.DEBUG:
+ print "- size:", self.size
+
+ format = getscalar(SAMPLEFORMAT, 1)
+
+ # mode: check photometric interpretation and bits per pixel
+ key = (
+ photo, format, fillorder,
+ self.tag.get(BITSPERSAMPLE, (1,)),
+ self.tag.get(EXTRASAMPLES, ())
+ )
+ if Image.DEBUG:
+ print "format key:", key
+ try:
+ self.mode, rawmode = OPEN_INFO[key]
+ except KeyError:
+ if Image.DEBUG:
+ print "- unsupported format"
+ raise SyntaxError, "unknown pixel mode"
+
+ if Image.DEBUG:
+ print "- raw mode:", rawmode
+ print "- pil mode:", self.mode
+
+ self.info["compression"] = self._compression
+
+ xdpi = getscalar(X_RESOLUTION, (1, 1))
+ ydpi = getscalar(Y_RESOLUTION, (1, 1))
+
+ if xdpi and ydpi and getscalar(RESOLUTION_UNIT, 1) == 1:
+ xdpi = xdpi[0] / (xdpi[1] or 1)
+ ydpi = ydpi[0] / (ydpi[1] or 1)
+ self.info["dpi"] = xdpi, ydpi
+
+ # build tile descriptors
+ x = y = l = 0
+ self.tile = []
+ if self.tag.has_key(STRIPOFFSETS):
+ # striped image
+ h = getscalar(ROWSPERSTRIP, ysize)
+ w = self.size[0]
+ a = None
+ for o in self.tag[STRIPOFFSETS]:
+ if not a:
+ a = self._decoder(rawmode, l)
+ self.tile.append(
+ (self._compression,
+ (0, min(y, ysize), w, min(y+h, ysize)),
+ o, a))
+ y = y + h
+ if y >= self.size[1]:
+ x = y = 0
+ l = l + 1
+ a = None
+ elif self.tag.has_key(324):
+ # tiled image
+ w = getscalar(322)
+ h = getscalar(323)
+ a = None
+ for o in self.tag[324]:
+ if not a:
+ a = self._decoder(rawmode, l)
+ # FIXME: this doesn't work if the image size
+ # is not a multiple of the tile size...
+ self.tile.append(
+ (self._compression,
+ (x, y, x+w, y+h),
+ o, a))
+ x = x + w
+ if x >= self.size[0]:
+ x, y = 0, y + h
+ if y >= self.size[1]:
+ x = y = 0
+ l = l + 1
+ a = None
+ else:
+ if Image.DEBUG:
+ print "- unsupported data organization"
+ raise SyntaxError("unknown data organization")
+
+ # fixup palette descriptor
+ if self.mode == "P":
+ palette = map(lambda a: chr(a / 256), self.tag[COLORMAP])
+ self.palette = ImagePalette.raw("RGB;L", string.join(palette, ""))
+
+#
+# --------------------------------------------------------------------
+# Write TIFF files
+
+# little endian is default
+
+SAVE_INFO = {
+ # mode => rawmode, photometrics, sampleformat, bitspersample, extra
+ "1": ("1", 1, 1, (1,), None),
+ "L": ("L", 1, 1, (8,), None),
+ "LA": ("LA", 1, 1, (8,8), 2),
+ "P": ("P", 3, 1, (8,), None),
+ "PA": ("PA", 3, 1, (8,8), 2),
+ "I": ("I;32S", 1, 2, (32,), None),
+ "I;16": ("I;16", 1, 1, (16,), None),
+ "I;16S": ("I;16S", 1, 2, (16,), None),
+ "F": ("F;32F", 1, 3, (32,), None),
+ "RGB": ("RGB", 2, 1, (8,8,8), None),
+ "RGBX": ("RGBX", 2, 1, (8,8,8,8), 0),
+ "RGBA": ("RGBA", 2, 1, (8,8,8,8), 2),
+ "CMYK": ("CMYK", 5, 1, (8,8,8,8), None),
+ "YCbCr": ("YCbCr", 6, 1, (8,8,8), None),
+ "LAB": ("LAB", 8, 1, (8,8,8), None),
+}
+
+def _cvt_res(value):
+ # convert value to TIFF rational number -- (numerator, denominator)
+ if type(value) in (type([]), type(())):
+ assert(len(value) % 2 == 0)
+ return value
+ if type(value) == type(1):
+ return (value, 1)
+ value = float(value)
+ return (int(value * 65536), 65536)
+
+def _save(im, fp, filename):
+
+ try:
+ rawmode, photo, format, bits, extra = SAVE_INFO[im.mode]
+ except KeyError:
+ raise IOError, "cannot write mode %s as TIFF" % im.mode
+
+ ifd = ImageFileDirectory()
+
+ # tiff header (write via IFD to get everything right)
+ fp.write(ifd.prefix + ifd.o16(42) + ifd.o32(8))
+
+ ifd[IMAGEWIDTH] = im.size[0]
+ ifd[IMAGELENGTH] = im.size[1]
+
+ # additions written by Greg Couch, gregc@cgl.ucsf.edu
+ # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
+ if hasattr(im, 'tag'):
+ # preserve tags from original TIFF image file
+ for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION):
+ if im.tag.tagdata.has_key(key):
+ ifd[key] = im.tag.tagdata.get(key)
+ if im.encoderinfo.has_key("description"):
+ ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"]
+ if im.encoderinfo.has_key("resolution"):
+ ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \
+ = _cvt_res(im.encoderinfo["resolution"])
+ if im.encoderinfo.has_key("x resolution"):
+ ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"])
+ if im.encoderinfo.has_key("y resolution"):
+ ifd[Y_RESOLUTION] = _cvt_res(im.encoderinfo["y resolution"])
+ if im.encoderinfo.has_key("resolution unit"):
+ unit = im.encoderinfo["resolution unit"]
+ if unit == "inch":
+ ifd[RESOLUTION_UNIT] = 2
+ elif unit == "cm" or unit == "centimeter":
+ ifd[RESOLUTION_UNIT] = 3
+ else:
+ ifd[RESOLUTION_UNIT] = 1
+ if im.encoderinfo.has_key("software"):
+ ifd[SOFTWARE] = im.encoderinfo["software"]
+ if im.encoderinfo.has_key("date time"):
+ ifd[DATE_TIME] = im.encoderinfo["date time"]
+ if im.encoderinfo.has_key("artist"):
+ ifd[ARTIST] = im.encoderinfo["artist"]
+ if im.encoderinfo.has_key("copyright"):
+ ifd[COPYRIGHT] = im.encoderinfo["copyright"]
+
+ dpi = im.encoderinfo.get("dpi")
+ if dpi:
+ ifd[RESOLUTION_UNIT] = 1
+ ifd[X_RESOLUTION] = _cvt_res(dpi[0])
+ ifd[Y_RESOLUTION] = _cvt_res(dpi[1])
+
+ if bits != (1,):
+ ifd[BITSPERSAMPLE] = bits
+ if len(bits) != 1:
+ ifd[SAMPLESPERPIXEL] = len(bits)
+ if extra is not None:
+ ifd[EXTRASAMPLES] = extra
+ if format != 1:
+ ifd[SAMPLEFORMAT] = format
+
+ ifd[PHOTOMETRIC_INTERPRETATION] = photo
+
+ if im.mode == "P":
+ lut = im.im.getpalette("RGB", "RGB;L")
+ ifd[COLORMAP] = tuple(map(lambda v: ord(v) * 256, lut))
+
+ # data orientation
+ stride = len(bits) * ((im.size[0]*bits[0]+7)/8)
+ ifd[ROWSPERSTRIP] = im.size[1]
+ ifd[STRIPBYTECOUNTS] = stride * im.size[1]
+ ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
+ ifd[COMPRESSION] = 1 # no compression
+
+ offset = ifd.save(fp)
+
+ ImageFile._save(im, fp, [
+ ("raw", (0,0)+im.size, offset, (rawmode, stride, 1))
+ ])
+
+#
+# --------------------------------------------------------------------
+# Register
+
+Image.register_open("TIFF", TiffImageFile, _accept)
+Image.register_save("TIFF", _save)
+
+Image.register_extension("TIFF", ".tif")
+Image.register_extension("TIFF", ".tiff")
+
+Image.register_mime("TIFF", "image/tiff")
diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py
new file mode 100644
index 0000000..2795fc3
--- /dev/null
+++ b/PIL/TiffTags.py
@@ -0,0 +1,200 @@
+#
+# The Python Imaging Library.
+# $Id: TiffTags.py 2340 2005-03-25 08:05:37Z fredrik $
+#
+# TIFF tags
+#
+# This module provides clear-text names for various well-known
+# TIFF tags. the TIFF codec works just fine without it.
+#
+# Copyright (c) Secret Labs AB 1999.
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# This module provides constants and clear-text names for various
+# well-known TIFF tags.
+##
+
+##
+# Map tag numbers (or tag number, tag value tuples) to tag names.
+
+TAGS = {
+
+ 254: "NewSubfileType",
+ 255: "SubfileType",
+ 256: "ImageWidth",
+ 257: "ImageLength",
+ 258: "BitsPerSample",
+
+ 259: "Compression",
+ (259, 1): "Uncompressed",
+ (259, 2): "CCITT 1d",
+ (259, 3): "Group 3 Fax",
+ (259, 4): "Group 4 Fax",
+ (259, 5): "LZW",
+ (259, 6): "JPEG",
+ (259, 32773): "PackBits",
+
+ 262: "PhotometricInterpretation",
+ (262, 0): "WhiteIsZero",
+ (262, 1): "BlackIsZero",
+ (262, 2): "RGB",
+ (262, 3): "RGB Palette",
+ (262, 4): "Transparency Mask",
+ (262, 5): "CMYK",
+ (262, 6): "YCbCr",
+ (262, 8): "CieLAB",
+ (262, 32803): "CFA", # TIFF/EP, Adobe DNG
+ (262, 32892): "LinearRaw", # Adobe DNG
+
+ 263: "Thresholding",
+ 264: "CellWidth",
+ 265: "CellHeight",
+ 266: "FillOrder",
+ 269: "DocumentName",
+
+ 270: "ImageDescription",
+ 271: "Make",
+ 272: "Model",
+ 273: "StripOffsets",
+ 274: "Orientation",
+ 277: "SamplesPerPixel",
+ 278: "RowsPerStrip",
+ 279: "StripByteCounts",
+
+ 280: "MinSampleValue",
+ 281: "MaxSampleValue",
+ 282: "XResolution",
+ 283: "YResolution",
+ 284: "PlanarConfiguration",
+ (284, 1): "Contigous",
+ (284, 2): "Separate",
+
+ 285: "PageName",
+ 286: "XPosition",
+ 287: "YPosition",
+ 288: "FreeOffsets",
+ 289: "FreeByteCounts",
+
+ 290: "GrayResponseUnit",
+ 291: "GrayResponseCurve",
+ 292: "T4Options",
+ 293: "T6Options",
+ 296: "ResolutionUnit",
+ 297: "PageNumber",
+
+ 301: "TransferFunction",
+ 305: "Software",
+ 306: "DateTime",
+
+ 315: "Artist",
+ 316: "HostComputer",
+ 317: "Predictor",
+ 318: "WhitePoint",
+ 319: "PrimaryChromaticies",
+
+ 320: "ColorMap",
+ 321: "HalftoneHints",
+ 322: "TileWidth",
+ 323: "TileLength",
+ 324: "TileOffsets",
+ 325: "TileByteCounts",
+
+ 332: "InkSet",
+ 333: "InkNames",
+ 334: "NumberOfInks",
+ 336: "DotRange",
+ 337: "TargetPrinter",
+ 338: "ExtraSamples",
+ 339: "SampleFormat",
+
+ 340: "SMinSampleValue",
+ 341: "SMaxSampleValue",
+ 342: "TransferRange",
+
+ 347: "JPEGTables",
+
+ # obsolete JPEG tags
+ 512: "JPEGProc",
+ 513: "JPEGInterchangeFormat",
+ 514: "JPEGInterchangeFormatLength",
+ 515: "JPEGRestartInterval",
+ 517: "JPEGLosslessPredictors",
+ 518: "JPEGPointTransforms",
+ 519: "JPEGQTables",
+ 520: "JPEGDCTables",
+ 521: "JPEGACTables",
+
+ 529: "YCbCrCoefficients",
+ 530: "YCbCrSubSampling",
+ 531: "YCbCrPositioning",
+ 532: "ReferenceBlackWhite",
+
+ 33432: "Copyright",
+
+ # various extensions (should check specs for "official" names)
+ 33723: "IptcNaaInfo",
+ 34377: "PhotoshopInfo",
+
+ # Adobe DNG
+ 50706: "DNGVersion",
+ 50707: "DNGBackwardVersion",
+ 50708: "UniqueCameraModel",
+ 50709: "LocalizedCameraModel",
+ 50710: "CFAPlaneColor",
+ 50711: "CFALayout",
+ 50712: "LinearizationTable",
+ 50713: "BlackLevelRepeatDim",
+ 50714: "BlackLevel",
+ 50715: "BlackLevelDeltaH",
+ 50716: "BlackLevelDeltaV",
+ 50717: "WhiteLevel",
+ 50718: "DefaultScale",
+ 50741: "BestQualityScale",
+ 50719: "DefaultCropOrigin",
+ 50720: "DefaultCropSize",
+ 50778: "CalibrationIlluminant1",
+ 50779: "CalibrationIlluminant2",
+ 50721: "ColorMatrix1",
+ 50722: "ColorMatrix2",
+ 50723: "CameraCalibration1",
+ 50724: "CameraCalibration2",
+ 50725: "ReductionMatrix1",
+ 50726: "ReductionMatrix2",
+ 50727: "AnalogBalance",
+ 50728: "AsShotNeutral",
+ 50729: "AsShotWhiteXY",
+ 50730: "BaselineExposure",
+ 50731: "BaselineNoise",
+ 50732: "BaselineSharpness",
+ 50733: "BayerGreenSplit",
+ 50734: "LinearResponseLimit",
+ 50735: "CameraSerialNumber",
+ 50736: "LensInfo",
+ 50737: "ChromaBlurRadius",
+ 50738: "AntiAliasStrength",
+ 50740: "DNGPrivateData",
+ 50741: "MakerNoteSafety",
+}
+
+##
+# Map type numbers to type names.
+
+TYPES = {
+
+ 1: "byte",
+ 2: "ascii",
+ 3: "short",
+ 4: "long",
+ 5: "rational",
+ 6: "signed byte",
+ 7: "undefined",
+ 8: "signed short",
+ 9: "signed long",
+ 10: "signed rational",
+ 11: "float",
+ 12: "double",
+
+}
diff --git a/PIL/WalImageFile.py b/PIL/WalImageFile.py
new file mode 100644
index 0000000..34e7fc2
--- /dev/null
+++ b/PIL/WalImageFile.py
@@ -0,0 +1,125 @@
+#
+# The Python Imaging Library.
+# $Id: WalImageFile.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# WAL file handling
+#
+# History:
+# 2003-04-23 fl created
+#
+# Copyright (c) 2003 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+# NOTE: This format cannot be automatically recognized, so the reader
+# is not registered for use with Image.open(). To open a WEL file, use
+# the WalImageFile.open() function instead.
+
+# This reader is based on the specification available from:
+# http://www.flipcode.com/tutorials/tut_q2levels.shtml
+# and has been tested with a few sample files found using google.
+
+import Image
+
+def i32(c, o=0):
+ return ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24)
+
+##
+# Load texture from a Quake2 WAL texture file.
+# <p>
+# By default, a Quake2 standard palette is attached to the texture.
+# To override the palette, use the <b>putpalette</b> method.
+#
+# @param filename WAL file name, or an opened file handle.
+# @return An image instance.
+
+def open(filename):
+ # FIXME: modify to return a WalImageFile instance instead of
+ # plain Image object ?
+
+ if hasattr(filename, "read"):
+ fp = filename
+ else:
+ import __builtin__
+ fp = __builtin__.open(filename, "rb")
+
+ # read header fields
+ header = fp.read(32+24+32+12)
+ size = i32(header, 32), i32(header, 36)
+ offset = i32(header, 40)
+
+ # load pixel data
+ fp.seek(offset)
+
+ im = Image.fromstring("P", size, fp.read(size[0] * size[1]))
+ im.putpalette(quake2palette)
+
+ im.format = "WAL"
+ im.format_description = "Quake2 Texture"
+
+ # strings are null-terminated
+ im.info["name"] = header[:32].split("\0", 1)[0]
+ next_name = header[56:56+32].split("\0", 1)[0]
+ if next_name:
+ im.info["next_name"] = next_name
+
+ return im
+
+
+quake2palette = (
+ # default palette taken from piffo 0.93 by Hans Häggström
+ "\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e"
+ "\x1e\x1e\x22\x22\x22\x26\x26\x26\x29\x29\x29\x2c\x2c\x2c\x2f\x2f"
+ "\x2f\x32\x32\x32\x35\x35\x35\x37\x37\x37\x3a\x3a\x3a\x3c\x3c\x3c"
+ "\x24\x1e\x13\x22\x1c\x12\x20\x1b\x12\x1f\x1a\x10\x1d\x19\x10\x1b"
+ "\x17\x0f\x1a\x16\x0f\x18\x14\x0d\x17\x13\x0d\x16\x12\x0d\x14\x10"
+ "\x0b\x13\x0f\x0b\x10\x0d\x0a\x0f\x0b\x0a\x0d\x0b\x07\x0b\x0a\x07"
+ "\x23\x23\x26\x22\x22\x25\x22\x20\x23\x21\x1f\x22\x20\x1e\x20\x1f"
+ "\x1d\x1e\x1d\x1b\x1c\x1b\x1a\x1a\x1a\x19\x19\x18\x17\x17\x17\x16"
+ "\x16\x14\x14\x14\x13\x13\x13\x10\x10\x10\x0f\x0f\x0f\x0d\x0d\x0d"
+ "\x2d\x28\x20\x29\x24\x1c\x27\x22\x1a\x25\x1f\x17\x38\x2e\x1e\x31"
+ "\x29\x1a\x2c\x25\x17\x26\x20\x14\x3c\x30\x14\x37\x2c\x13\x33\x28"
+ "\x12\x2d\x24\x10\x28\x1f\x0f\x22\x1a\x0b\x1b\x14\x0a\x13\x0f\x07"
+ "\x31\x1a\x16\x30\x17\x13\x2e\x16\x10\x2c\x14\x0d\x2a\x12\x0b\x27"
+ "\x0f\x0a\x25\x0f\x07\x21\x0d\x01\x1e\x0b\x01\x1c\x0b\x01\x1a\x0b"
+ "\x01\x18\x0a\x01\x16\x0a\x01\x13\x0a\x01\x10\x07\x01\x0d\x07\x01"
+ "\x29\x23\x1e\x27\x21\x1c\x26\x20\x1b\x25\x1f\x1a\x23\x1d\x19\x21"
+ "\x1c\x18\x20\x1b\x17\x1e\x19\x16\x1c\x18\x14\x1b\x17\x13\x19\x14"
+ "\x10\x17\x13\x0f\x14\x10\x0d\x12\x0f\x0b\x0f\x0b\x0a\x0b\x0a\x07"
+ "\x26\x1a\x0f\x23\x19\x0f\x20\x17\x0f\x1c\x16\x0f\x19\x13\x0d\x14"
+ "\x10\x0b\x10\x0d\x0a\x0b\x0a\x07\x33\x22\x1f\x35\x29\x26\x37\x2f"
+ "\x2d\x39\x35\x34\x37\x39\x3a\x33\x37\x39\x30\x34\x36\x2b\x31\x34"
+ "\x27\x2e\x31\x22\x2b\x2f\x1d\x28\x2c\x17\x25\x2a\x0f\x20\x26\x0d"
+ "\x1e\x25\x0b\x1c\x22\x0a\x1b\x20\x07\x19\x1e\x07\x17\x1b\x07\x14"
+ "\x18\x01\x12\x16\x01\x0f\x12\x01\x0b\x0d\x01\x07\x0a\x01\x01\x01"
+ "\x2c\x21\x21\x2a\x1f\x1f\x29\x1d\x1d\x27\x1c\x1c\x26\x1a\x1a\x24"
+ "\x18\x18\x22\x17\x17\x21\x16\x16\x1e\x13\x13\x1b\x12\x12\x18\x10"
+ "\x10\x16\x0d\x0d\x12\x0b\x0b\x0d\x0a\x0a\x0a\x07\x07\x01\x01\x01"
+ "\x2e\x30\x29\x2d\x2e\x27\x2b\x2c\x26\x2a\x2a\x24\x28\x29\x23\x27"
+ "\x27\x21\x26\x26\x1f\x24\x24\x1d\x22\x22\x1c\x1f\x1f\x1a\x1c\x1c"
+ "\x18\x19\x19\x16\x17\x17\x13\x13\x13\x10\x0f\x0f\x0d\x0b\x0b\x0a"
+ "\x30\x1e\x1b\x2d\x1c\x19\x2c\x1a\x17\x2a\x19\x14\x28\x17\x13\x26"
+ "\x16\x10\x24\x13\x0f\x21\x12\x0d\x1f\x10\x0b\x1c\x0f\x0a\x19\x0d"
+ "\x0a\x16\x0b\x07\x12\x0a\x07\x0f\x07\x01\x0a\x01\x01\x01\x01\x01"
+ "\x28\x29\x38\x26\x27\x36\x25\x26\x34\x24\x24\x31\x22\x22\x2f\x20"
+ "\x21\x2d\x1e\x1f\x2a\x1d\x1d\x27\x1b\x1b\x25\x19\x19\x21\x17\x17"
+ "\x1e\x14\x14\x1b\x13\x12\x17\x10\x0f\x13\x0d\x0b\x0f\x0a\x07\x07"
+ "\x2f\x32\x29\x2d\x30\x26\x2b\x2e\x24\x29\x2c\x21\x27\x2a\x1e\x25"
+ "\x28\x1c\x23\x26\x1a\x21\x25\x18\x1e\x22\x14\x1b\x1f\x10\x19\x1c"
+ "\x0d\x17\x1a\x0a\x13\x17\x07\x10\x13\x01\x0d\x0f\x01\x0a\x0b\x01"
+ "\x01\x3f\x01\x13\x3c\x0b\x1b\x39\x10\x20\x35\x14\x23\x31\x17\x23"
+ "\x2d\x18\x23\x29\x18\x3f\x3f\x3f\x3f\x3f\x39\x3f\x3f\x31\x3f\x3f"
+ "\x2a\x3f\x3f\x20\x3f\x3f\x14\x3f\x3c\x12\x3f\x39\x0f\x3f\x35\x0b"
+ "\x3f\x32\x07\x3f\x2d\x01\x3d\x2a\x01\x3b\x26\x01\x39\x21\x01\x37"
+ "\x1d\x01\x34\x1a\x01\x32\x16\x01\x2f\x12\x01\x2d\x0f\x01\x2a\x0b"
+ "\x01\x27\x07\x01\x23\x01\x01\x1d\x01\x01\x17\x01\x01\x10\x01\x01"
+ "\x3d\x01\x01\x19\x19\x3f\x3f\x01\x01\x01\x01\x3f\x16\x16\x13\x10"
+ "\x10\x0f\x0d\x0d\x0b\x3c\x2e\x2a\x36\x27\x20\x30\x21\x18\x29\x1b"
+ "\x10\x3c\x39\x37\x37\x32\x2f\x31\x2c\x28\x2b\x26\x21\x30\x22\x20"
+)
+
+if __name__ == "__main__":
+ im = open("../hacks/sample.wal")
+ print im.info, im.mode, im.size
+ im.save("../out.png")
diff --git a/PIL/WmfImagePlugin.py b/PIL/WmfImagePlugin.py
new file mode 100644
index 0000000..2191160
--- /dev/null
+++ b/PIL/WmfImagePlugin.py
@@ -0,0 +1,148 @@
+#
+# The Python Imaging Library
+# $Id: WmfImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# WMF stub codec
+#
+# history:
+# 1996-12-14 fl Created
+# 2004-02-22 fl Turned into a stub driver
+# 2004-02-23 fl Added EMF support
+#
+# Copyright (c) Secret Labs AB 1997-2004. All rights reserved.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.2"
+
+import Image, ImageFile
+
+_handler = None
+
+##
+# Install application-specific WMF image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+# --------------------------------------------------------------------
+
+def word(c, o=0):
+ return ord(c[o]) + (ord(c[o+1])<<8)
+
+def short(c, o=0):
+ v = ord(c[o]) + (ord(c[o+1])<<8)
+ if v >= 32768:
+ v = v - 65536
+ return v
+
+def dword(c, o=0):
+ return ord(c[o]) + (ord(c[o+1])<<8) + (ord(c[o+2])<<16) + (ord(c[o+3])<<24)
+
+def long(c, o=0):
+ return dword(c, o)
+
+#
+# --------------------------------------------------------------------
+# Read WMF file
+
+def _accept(prefix):
+ return (
+ prefix[:6] == "\xd7\xcd\xc6\x9a\x00\x00" or
+ prefix[:4] == "\x01\x00\x00\x00"
+ )
+
+##
+# Image plugin for Windows metafiles.
+
+class WmfStubImageFile(ImageFile.StubImageFile):
+
+ format = "WMF"
+ format_description = "Windows Metafile"
+
+ def _open(self):
+
+ # check placable header
+ s = self.fp.read(80)
+
+ if s[:6] == "\xd7\xcd\xc6\x9a\x00\x00":
+
+ # placeable windows metafile
+
+ # get units per inch
+ inch = word(s, 14)
+
+ # get bounding box
+ x0 = short(s, 6); y0 = short(s, 8)
+ x1 = short(s, 10); y1 = short(s, 12)
+
+ # normalize size to 72 dots per inch
+ size = (x1 - x0) * 72 / inch, (y1 - y0) * 72 / inch
+
+ self.info["wmf_bbox"] = x0, y0, x1, y1
+
+ self.info["dpi"] = 72
+
+ # print self.mode, self.size, self.info
+
+ # sanity check (standard metafile header)
+ if s[22:26] != "\x01\x00\t\x00":
+ raise SyntaxError("Unsupported WMF file format")
+
+ elif long(s) == 1 and s[40:44] == " EMF":
+ # enhanced metafile
+
+ # get bounding box
+ x0 = long(s, 8); y0 = long(s, 12)
+ x1 = long(s, 16); y1 = long(s, 20)
+
+ # get frame (in 0.01 millimeter units)
+ frame = long(s, 24), long(s, 28), long(s, 32), long(s, 36)
+
+ # normalize size to 72 dots per inch
+ size = x1 - x0, y1 - y0
+
+ # calculate dots per inch from bbox and frame
+ xdpi = 2540 * (x1 - y0) / (frame[2] - frame[0])
+ ydpi = 2540 * (y1 - y0) / (frame[3] - frame[1])
+
+ self.info["wmf_bbox"] = x0, y0, x1, y1
+
+ if xdpi == ydpi:
+ self.info["dpi"] = xdpi
+ else:
+ self.info["dpi"] = xdpi, ydpi
+
+ else:
+ raise SyntaxError("Unsupported file format")
+
+ self.mode = "RGB"
+ self.size = size
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("WMF save handler not installed")
+ _handler.save(im, fp, filename)
+
+#
+# --------------------------------------------------------------------
+# Registry stuff
+
+Image.register_open(WmfStubImageFile.format, WmfStubImageFile, _accept)
+Image.register_save(WmfStubImageFile.format, _save)
+
+Image.register_extension(WmfStubImageFile.format, ".wmf")
+Image.register_extension(WmfStubImageFile.format, ".emf")
diff --git a/PIL/XVThumbImagePlugin.py b/PIL/XVThumbImagePlugin.py
new file mode 100644
index 0000000..4a9c0d6
--- /dev/null
+++ b/PIL/XVThumbImagePlugin.py
@@ -0,0 +1,68 @@
+#
+# The Python Imaging Library.
+# $Id: XVThumbImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# XV Thumbnail file handler by Charles E. "Gene" Cash
+# (gcash@magicnet.net)
+#
+# see xvcolor.c and xvbrowse.c in the sources to John Bradley's XV,
+# available from ftp://ftp.cis.upenn.edu/pub/xv/
+#
+# history:
+# 98-08-15 cec created (b/w only)
+# 98-12-09 cec added color palette
+# 98-12-28 fl added to PIL (with only a few very minor modifications)
+#
+# To do:
+# FIXME: make save work (this requires quantization support)
+#
+
+__version__ = "0.1"
+
+import string
+import Image, ImageFile, ImagePalette
+
+# standard color palette for thumbnails (RGB332)
+PALETTE = ""
+for r in range(8):
+ for g in range(8):
+ for b in range(4):
+ PALETTE = PALETTE + (chr((r*255)/7)+chr((g*255)/7)+chr((b*255)/3))
+
+##
+# Image plugin for XV thumbnail images.
+
+class XVThumbImageFile(ImageFile.ImageFile):
+
+ format = "XVThumb"
+ format_description = "XV thumbnail image"
+
+ def _open(self):
+
+ # check magic
+ s = self.fp.read(6)
+ if s != "P7 332":
+ raise SyntaxError, "not an XV thumbnail file"
+
+ # skip info comments
+ while 1:
+ s = string.strip(self.fp.readline())
+ if s == "#END_OF_COMMENTS":
+ break
+
+ # read header line
+ s = string.split(self.fp.readline())
+
+ self.mode = "P"
+ self.size = int(s[0]), int(s[1])
+
+ self.palette = ImagePalette.raw("RGB", PALETTE)
+
+ self.tile = [
+ ("raw", (0, 0)+self.size,
+ self.fp.tell(), (self.mode, 0, 1)
+ )]
+
+# --------------------------------------------------------------------
+
+Image.register_open("XVThumb", XVThumbImageFile)
diff --git a/PIL/XbmImagePlugin.py b/PIL/XbmImagePlugin.py
new file mode 100644
index 0000000..a1dccea
--- /dev/null
+++ b/PIL/XbmImagePlugin.py
@@ -0,0 +1,94 @@
+#
+# The Python Imaging Library.
+# $Id: XbmImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# XBM File handling
+#
+# History:
+# 1995-09-08 fl Created
+# 1996-11-01 fl Added save support
+# 1997-07-07 fl Made header parser more tolerant
+# 1997-07-22 fl Fixed yet another parser bug
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
+# 2001-05-13 fl Added hotspot handling (based on code from Bernhard Herzog)
+# 2004-02-24 fl Allow some whitespace before first #define
+#
+# Copyright (c) 1997-2004 by Secret Labs AB
+# Copyright (c) 1996-1997 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.6"
+
+import re, string
+import Image, ImageFile
+
+# XBM header
+xbm_head = re.compile(
+ "\s*#define[ \t]+[^_]*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
+ "#define[ \t]+[^_]*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
+ "(?P<hotspot>"
+ "#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
+ "#define[ \t]+[^_]*_y_hot[ \t]+(?P<yhot>[0-9]+)[\r\n]+"
+ ")?"
+ "[\\000-\\377]*_bits\\[\\]"
+)
+
+def _accept(prefix):
+ return string.lstrip(prefix)[:7] == "#define"
+
+##
+# Image plugin for X11 bitmaps.
+
+class XbmImageFile(ImageFile.ImageFile):
+
+ format = "XBM"
+ format_description = "X11 Bitmap"
+
+ def _open(self):
+
+ m = xbm_head.match(self.fp.read(512))
+
+ if m:
+
+ xsize = int(m.group("width"))
+ ysize = int(m.group("height"))
+
+ if m.group("hotspot"):
+ self.info["hotspot"] = (
+ int(m.group("xhot")), int(m.group("yhot"))
+ )
+
+ self.mode = "1"
+ self.size = xsize, ysize
+
+ self.tile = [("xbm", (0, 0)+self.size, m.end(), None)]
+
+
+def _save(im, fp, filename):
+
+ if im.mode != "1":
+ raise IOError, "cannot write mode %s as XBM" % im.mode
+
+ fp.write("#define im_width %d\n" % im.size[0])
+ fp.write("#define im_height %d\n" % im.size[1])
+
+ hotspot = im.encoderinfo.get("hotspot")
+ if hotspot:
+ fp.write("#define im_x_hot %d\n" % hotspot[0])
+ fp.write("#define im_y_hot %d\n" % hotspot[1])
+
+ fp.write("static char im_bits[] = {\n")
+
+ ImageFile._save(im, fp, [("xbm", (0,0)+im.size, 0, None)])
+
+ fp.write("};\n")
+
+
+Image.register_open("XBM", XbmImageFile, _accept)
+Image.register_save("XBM", _save)
+
+Image.register_extension("XBM", ".xbm")
+
+Image.register_mime("XBM", "image/xbm")
diff --git a/PIL/XpmImagePlugin.py b/PIL/XpmImagePlugin.py
new file mode 100644
index 0000000..b8ac01f
--- /dev/null
+++ b/PIL/XpmImagePlugin.py
@@ -0,0 +1,129 @@
+#
+# The Python Imaging Library.
+# $Id: XpmImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# XPM File handling
+#
+# History:
+# 1996-12-29 fl Created
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
+#
+# Copyright (c) Secret Labs AB 1997-2001.
+# Copyright (c) Fredrik Lundh 1996-2001.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.2"
+
+
+import re, string
+import Image, ImageFile, ImagePalette
+
+# XPM header
+xpm_head = re.compile("\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)")
+
+
+def _accept(prefix):
+ return prefix[:9] == "/* XPM */"
+
+##
+# Image plugin for X11 pixel maps.
+
+class XpmImageFile(ImageFile.ImageFile):
+
+ format = "XPM"
+ format_description = "X11 Pixel Map"
+
+ def _open(self):
+
+ if not _accept(self.fp.read(9)):
+ raise SyntaxError, "not an XPM file"
+
+ # skip forward to next string
+ while 1:
+ s = self.fp.readline()
+ if not s:
+ raise SyntaxError, "broken XPM file"
+ m = xpm_head.match(s)
+ if m:
+ break
+
+ self.size = int(m.group(1)), int(m.group(2))
+
+ pal = int(m.group(3))
+ bpp = int(m.group(4))
+
+ if pal > 256 or bpp != 1:
+ raise ValueError, "cannot read this XPM file"
+
+ #
+ # load palette description
+
+ palette = ["\0\0\0"] * 256
+
+ for i in range(pal):
+
+ s = self.fp.readline()
+ if s[-2:] == '\r\n':
+ s = s[:-2]
+ elif s[-1:] in '\r\n':
+ s = s[:-1]
+
+ c = ord(s[1])
+ s = string.split(s[2:-2])
+
+ for i in range(0, len(s), 2):
+
+ if s[i] == "c":
+
+ # process colour key
+ rgb = s[i+1]
+ if rgb == "None":
+ self.info["transparency"] = c
+ elif rgb[0] == "#":
+ # FIXME: handle colour names (see ImagePalette.py)
+ rgb = string.atoi(rgb[1:], 16)
+ palette[c] = chr((rgb >> 16) & 255) +\
+ chr((rgb >> 8) & 255) +\
+ chr(rgb & 255)
+ else:
+ # unknown colour
+ raise ValueError, "cannot read this XPM file"
+ break
+
+ else:
+
+ # missing colour key
+ raise ValueError, "cannot read this XPM file"
+
+ self.mode = "P"
+ self.palette = ImagePalette.raw("RGB", string.join(palette, ""))
+
+ self.tile = [("raw", (0, 0)+self.size, self.fp.tell(), ("P", 0, 1))]
+
+ def load_read(self, bytes):
+
+ #
+ # load all image data in one chunk
+
+ xsize, ysize = self.size
+
+ s = [None] * ysize
+
+ for i in range(ysize):
+ s[i] = string.ljust(self.fp.readline()[1:xsize+1], xsize)
+
+ self.fp = None
+
+ return string.join(s, "")
+
+#
+# Registry
+
+Image.register_open("XPM", XpmImageFile, _accept)
+
+Image.register_extension("XPM", ".xpm")
+
+Image.register_mime("XPM", "image/xpm")
diff --git a/PIL/__init__.py b/PIL/__init__.py
new file mode 100644
index 0000000..3290883
--- /dev/null
+++ b/PIL/__init__.py
@@ -0,0 +1,12 @@
+#
+# The Python Imaging Library.
+# $Id: __init__.py 2134 2004-10-06 08:55:20Z fredrik $
+#
+# package placeholder
+#
+# Copyright (c) 1999 by Secret Labs AB.
+#
+# See the README file for information on usage and redistribution.
+#
+
+# ;-)
diff --git a/ProgressDialog.py b/ProgressDialog.py
new file mode 100644
index 0000000..6d58ef5
--- /dev/null
+++ b/ProgressDialog.py
@@ -0,0 +1,19 @@
+import gtk
+from gettext import gettext as _
+
+
+class ProgressDialog(gtk.Dialog):
+
+ def __init__(self, parent):
+ gtk.Dialog.__init__(self, _('Downloading...'), parent, \
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, \
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
+ self._activity = parent
+ self.connect('response', self._response_cb)
+ self._pb = gtk.ProgressBar()
+ self._pb.set_text(_('Retrieving shared image, please wait...'))
+ self.vbox.add(self._pb)
+ def _response_cb(self, dialog, response_id):
+ if response_id == gtk.RESPONSE_REJECT : self._activity.close()
+ else: pass
+ def set_fraction(self, fraction) : self._pb.set_fraction(fraction)
diff --git a/activity/activity-imageprocessor.svg b/activity/activity-imageprocessor.svg
new file mode 100644
index 0000000..7a011e3
--- /dev/null
+++ b/activity/activity-imageprocessor.svg
@@ -0,0 +1,296 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50"
+ height="50"
+ id="svg3049"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="activity-imageprocessor.svg">
+ <metadata
+ id="metadata3073">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs3071" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ id="namedview3069"
+ showgrid="false"
+ inkscape:zoom="6"
+ inkscape:cx="4.7372372"
+ inkscape:cy="14.40738"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg3049" />
+ <rect
+ x="3"
+ y="7"
+ width="44"
+ height="36"
+ style="fill:#FFFFFF;stroke:#010101;stroke-width:3"
+ id="rect3051" />
+ <polyline
+ points="15,7 25,1 35,7"
+ style="fill:none;;stroke:#010101;stroke-width:1.25"
+ id="polyline3053" />
+ <circle
+ cx="14"
+ cy="19"
+ r="4.5"
+ style="fill:#010101;stroke:#010101;stroke-width:1.5"
+ id="circle3055"
+ sodipodi:cx="14"
+ sodipodi:cy="19"
+ sodipodi:rx="4.5"
+ sodipodi:ry="4.5"
+ transform="translate(13.166667,13.166667)" />
+ <polyline
+ points="3,36 16,32 26,35"
+ style="fill:none;;stroke:#010101;stroke-width:2.5"
+ id="polyline3057" />
+ <polyline
+ points="15,43 37,28 47,34 47,43"
+ style="fill:#010101;stroke:#010101;stroke-width:3"
+ id="polyline3059" />
+ <polyline
+ points="22,41.5 35,30 27,41.5"
+ style="fill:#FFFFFF;stroke:none;;stroke-width:0"
+ id="polyline3061" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3111"
+ sodipodi:sides="5"
+ sodipodi:cx="21.5"
+ sodipodi:cy="11.166667"
+ sodipodi:r1="1.771691"
+ sodipodi:r2="0.88584542"
+ sodipodi:arg1="0.71883"
+ sodipodi:arg2="1.3471485"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 22.833333,12.333334 -1.136863,-0.302884 -0.894013,0.764812 -0.06325,-1.174817 -1.003645,-0.613918 1.097772,-0.423193 0.273727,-1.1442338 0.741712,0.9132688 1.172817,-0.09326 -0.63937,0.987626 z"
+ inkscape:transform-center-x="0.1214251"
+ inkscape:transform-center-y="-0.049485824"
+ transform="translate(-0.66666667,0.66666667)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3113"
+ sodipodi:sides="5"
+ sodipodi:cx="26.833332"
+ sodipodi:cy="12.666667"
+ sodipodi:r1="1.6996732"
+ sodipodi:r2="0.84983653"
+ sodipodi:arg1="1.3734008"
+ sodipodi:arg2="2.0017193"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 27.166665,14.333334 -0.688318,-0.894522 -1.127103,0.0599 0.638038,-0.931052 -0.405264,-1.053428 1.082647,0.3191 0.876637,-0.710957 0.03107,1.128266 0.947056,0.614034 -1.063443,0.378207 z"
+ inkscape:transform-center-x="-0.10300566"
+ inkscape:transform-center-y="0.061188296"
+ transform="translate(1.6666667,-0.66666667)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3115"
+ sodipodi:sides="5"
+ sodipodi:cx="22.333332"
+ sodipodi:cy="16"
+ sodipodi:r1="1.9002924"
+ sodipodi:r2="0.95014614"
+ sodipodi:arg1="0.90975316"
+ sodipodi:arg2="1.5380717"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 23.499999,17.5 22.36442,16.949637 21.267267,17.573091 21.43978,16.32302 20.507801,15.472224 21.749999,15.25 l 0.521158,-1.149275 0.595208,1.112729 1.254072,0.140506 -0.874339,0.909928 z"
+ inkscape:transform-center-x="0.019213219"
+ inkscape:transform-center-y="-0.16309176"
+ transform="translate(0,0.66666667)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3117"
+ sodipodi:sides="5"
+ sodipodi:cx="36"
+ sodipodi:cy="14.166667"
+ sodipodi:r1="1.771691"
+ sodipodi:r2="0.88584542"
+ sodipodi:arg1="0.71883"
+ sodipodi:arg2="1.3471485"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 37.333333,15.333334 -1.136863,-0.302884 -0.894013,0.764812 -0.06325,-1.174817 -1.003645,-0.613918 1.097772,-0.423193 0.273727,-1.144234 0.741712,0.913269 1.172817,-0.09326 -0.63937,0.987626 z"
+ inkscape:transform-center-x="0.1214251"
+ inkscape:transform-center-y="-0.049485824"
+ transform="translate(0.66666667,-1.3333333)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3119"
+ sodipodi:sides="5"
+ sodipodi:cx="31.833332"
+ sodipodi:cy="18.166668"
+ sodipodi:r1="1.5"
+ sodipodi:r2="0.74999994"
+ sodipodi:arg1="1.5707963"
+ sodipodi:arg2="2.1991149"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 31.833332,19.666668 -0.440839,-0.893237 -0.985746,-0.143238 0.713293,-0.695288 -0.168386,-0.981763 0.881678,0.463526 0.881678,-0.463526 -0.168386,0.981763 0.713293,0.695288 -0.985746,0.143238 z"
+ inkscape:transform-center-y="0.14323725"
+ transform="translate(-0.16666667,-2.1666667)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3121"
+ sodipodi:sides="5"
+ sodipodi:cx="25.833332"
+ sodipodi:cy="20.166668"
+ sodipodi:r1="1.6666667"
+ sodipodi:r2="0.83333325"
+ sodipodi:arg1="1.5707963"
+ sodipodi:arg2="2.1991149"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 25.833332,21.833335 -0.489821,-0.992486 -1.095273,-0.159153 0.792547,-0.772542 -0.187095,-1.090848 0.979642,0.515029 0.979642,-0.515029 -0.187095,1.090848 0.792547,0.772542 -1.095273,0.159153 z"
+ inkscape:transform-center-y="0.1591525"
+ transform="translate(2,0.83333333)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3123"
+ sodipodi:sides="5"
+ sodipodi:cx="38.833332"
+ sodipodi:cy="18"
+ sodipodi:r1="2"
+ sodipodi:r2="0.99999988"
+ sodipodi:arg1="1.5707963"
+ sodipodi:arg2="2.1991149"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 38.833332,20 38.245547,18.809017 36.931219,18.618034 37.882276,17.690983 37.657762,16.381966 38.833332,17 l 1.175571,-0.618034 -0.224515,1.309017 0.951057,0.927051 -1.314328,0.190983 z"
+ inkscape:transform-center-y="0.19098301"
+ transform="translate(-18.5,5.6666666)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3127"
+ sodipodi:sides="5"
+ sodipodi:cx="40.5"
+ sodipodi:cy="12.166667"
+ sodipodi:r1="1.6996732"
+ sodipodi:r2="0.84983653"
+ sodipodi:arg1="0.19739556"
+ sodipodi:arg2="0.82571409"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 42.166667,12.5 -1.09045,0.291324 -0.378208,1.063443 L 40.083976,12.907711 38.95571,12.876637 39.666667,12 l -0.3191,-1.082647 1.053428,0.405264 0.931052,-0.638039 -0.0599,1.127104 z"
+ inkscape:transform-center-x="-0.061188296"
+ inkscape:transform-center-y="0.10300566"
+ transform="translate(1.6666666,11.5)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3111-6"
+ sodipodi:sides="5"
+ sodipodi:cx="21.5"
+ sodipodi:cy="11.166667"
+ sodipodi:r1="1.771691"
+ sodipodi:r2="0.88584542"
+ sodipodi:arg1="0.71883"
+ sodipodi:arg2="1.3471485"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 22.833333,12.333334 -1.136863,-0.302884 -0.894013,0.764812 -0.06325,-1.174817 -1.003645,-0.613918 1.097772,-0.423193 0.273727,-1.1442338 0.741712,0.9132688 1.172817,-0.09326 -0.63937,0.987626 z"
+ inkscape:transform-center-x="0.1214251"
+ inkscape:transform-center-y="-0.049485824"
+ transform="translate(-9.3785749,5.3828188)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3111-4"
+ sodipodi:sides="5"
+ sodipodi:cx="21.5"
+ sodipodi:cy="11.166667"
+ sodipodi:r1="1.771691"
+ sodipodi:r2="0.88584542"
+ sodipodi:arg1="0.71883"
+ sodipodi:arg2="1.3471485"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 22.833333,12.333334 -1.136863,-0.302884 -0.894013,0.764812 -0.06325,-1.174817 -1.003645,-0.613918 1.097772,-0.423193 0.273727,-1.1442338 0.741712,0.9132688 1.172817,-0.09326 -0.63937,0.987626 z"
+ inkscape:transform-center-x="0.1214251"
+ inkscape:transform-center-y="-0.049485824"
+ transform="translate(-12.878575,0.04948551)" />
+ <path
+ sodipodi:type="star"
+ style="fill:#000000"
+ id="path3111-1"
+ sodipodi:sides="5"
+ sodipodi:cx="21.5"
+ sodipodi:cy="11.166667"
+ sodipodi:r1="1.771691"
+ sodipodi:r2="0.88584542"
+ sodipodi:arg1="0.71883"
+ sodipodi:arg2="1.3471485"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 22.833333,12.333334 -1.136863,-0.302884 -0.894013,0.764812 -0.06325,-1.174817 -1.003645,-0.613918 1.097772,-0.423193 0.273727,-1.1442338 0.741712,0.9132688 1.172817,-0.09326 -0.63937,0.987626 z"
+ inkscape:transform-center-x="0.1214251"
+ inkscape:transform-center-y="-0.049485824"
+ transform="translate(14.954758,9.7161522)" />
+ <g
+ transform="matrix(0.16889331,0,0,0.13072195,5.0807054,25.30874)"
+ id="g3534">
+ <g
+ transform="scale(0.92,0.92)"
+ id="g3536">
+ <path
+ style="fill:#ffffff;stroke:#010101;stroke-width:3"
+ inkscape:connector-curvature="0"
+ id="Left"
+ d="M 12.99,13.58 C 6.25,14.6 1.54,5.1 11.04,1.5 21.21,1.49 20.24,12.49 12.99,13.58 z M 17,50.37 c -0.21,10.84 -9.79,10.84 -10,0 0,0 0,-24.36 0,-24.36 0.01,-3.75 -0.58,-10.27 5.04,-10.27 4.52,0 14.97,10.11 16.12,14.17 1.08,3.83 -2.27,6.92 -6.16,6.18 -1.59,-0.3 -3.58,-1.51 -5,-2.28 0,0 0,16.56 0,16.56 z" />
+ <path
+ style="fill:#010101;stroke:#ffffff;stroke-width:3"
+ inkscape:connector-curvature="0"
+ id="Right"
+ d="M 47.98,13.8 C 41.36,14.71 35.84,4.88 46.01,1.5 c 10.41,0.26 8.5,11.4 1.97,12.3 z M 30.84,29.99 c 1.14,-4.06 11.61,-14.18 16.12,-14.18 5.28,0 5.02,5.68 5.04,9.31 0,0 0,24.36 0,24.36 -0.08,10.9 -6.33,10.31 -9.15,6.59 C 40.77,53.32 41,48.85 41,45.58 c 0,0 0,-11.69 0,-11.69 -8.15,5.58 -11.2,-0.18 -10.16,-3.9 z" />
+ </g>
+ </g>
+</svg>
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..274972e
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,19 @@
+[Activity]
+sweet = imageprocessor
+name = Image Processor
+summary = The Image Processor activity is a simple and fast image processor tool
+description = It has features one would expect of a standard image processor,
+ like filtering, merging, watermarkin text and images over images, etc and also image viewer functions.
+homepage = http://wiki.sugarlabs.org/go/Activities/Image_Processor
+license = GPLv2+
+
+icon = activity-imageprocessor
+exec = sugar-activity ImageProcessorActivity.ImageProcessorActivity
+mime_types = image/bmp;image/gif;image/jpeg;image/png;image/tiff;image/svg+xml
+
+version = 1
+stability = testing
+
+# support original activity.info fields
+activity_version = 1
+bundle_id = org.laptop.ImageProcessorActivity
diff --git a/icons/blur.svg b/icons/blur.svg
new file mode 100644
index 0000000..de2a505
--- /dev/null
+++ b/icons/blur.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3043"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="blur.svg">
+ <defs
+ id="defs3045" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="7.7888027"
+ inkscape:cy="9.3382944"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:object-nodes="false"
+ inkscape:snap-bbox="true" />
+ <metadata
+ id="metadata3048">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.4415842"
+ y="14.693564"
+ id="text3821"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3823"
+ x="1.4415842"
+ y="14.693564" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:14.29584885px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="3.0767288"
+ y="13.203192"
+ id="text4470"
+ sodipodi:linespacing="125%"
+ transform="scale(1.057428,0.94569086)"><tspan
+ sodipodi:role="line"
+ id="tspan4472"
+ x="3.0767288"
+ y="13.203192">B</tspan></text>
+ <path
+ style="fill:#ffffff"
+ d="m 4.7434561,7.6877716 0,-4.8012145 1.8808169,1.269e-4 c 1.0949068,7.38e-5 2.0502346,0.020888 2.2862624,0.049812 1.1783266,0.1443982 1.9696656,0.5551486 2.3068066,1.1973649 0.222872,0.4245466 0.312815,0.9678866 0.237876,1.4369908 -0.139015,0.8701982 -0.795937,1.5048331 -1.7783617,1.7180272 -0.2137815,0.046392 -0.3383506,0.095386 -0.3383506,0.133076 0,0.032808 0.045613,0.059703 0.1013614,0.059765 0.1853999,2.253e-4 0.9342899,0.2223851 1.2089869,0.3586765 0.764587,0.3793511 1.122325,0.9740406 1.122325,1.8657095 0,1.2082979 -0.648365,2.0955939 -1.8346276,2.5107139 -0.6271878,0.219477 -0.7717147,0.231467 -3.0419814,0.252365 l -2.1511139,0.0198 0,-4.8012144 z m 4.1451781,3.7186674 c 0.6667722,-0.143879 1.0845361,-0.427723 1.3132978,-0.892302 0.143881,-0.292199 0.150188,-0.326806 0.150188,-0.8240906 0,-0.4654889 -0.01214,-0.5449751 -0.117611,-0.769855 C 10.089928,8.6119149 9.8506163,8.373239 9.5376457,8.2251802 9.0650315,8.0015982 8.8231211,7.9696743 7.4126393,7.9447543 l -1.317698,-0.023281 0,1.7908102 0,1.7908097 1.2951733,-0.02639 c 0.7123453,-0.01451 1.3866791,-0.04613 1.4985196,-0.07027 z M 8.7432491,6.8943733 C 9.4441932,6.7806254 9.8776118,6.4741177 10.031607,5.9832637 10.152952,5.5964818 10.121942,4.9662949 9.9667391,4.6650044 9.7626382,4.2687904 9.3146263,4.0268615 8.5951888,3.9243598 8.4217483,3.8996489 7.7882396,3.8790293 7.1873918,3.8785386 l -1.0924505,-8.924e-4 0,1.5316832 0,1.5316831 1.1825495,-3.423e-4 C 7.927893,6.9404809 8.5874843,6.9196485 8.7432491,6.8943712 l 0,2.1e-6 z"
+ id="path3024"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#f5f5f5"
+ d="m 4.7434561,7.6792014 0,-4.7926443 0.7545793,0.00315 c 1.6571787,0.00692 3.5903556,0.081128 3.887415,0.1492244 0.9154366,0.2098511 1.5123616,0.5760065 1.7908566,1.0985168 0.260254,0.488285 0.34335,1.0956903 0.215565,1.5756973 -0.205124,0.7705148 -0.694404,1.2354013 -1.5881284,1.5089504 -0.2558807,0.07832 -0.4652379,0.1665799 -0.4652379,0.1961346 0,0.06173 0.1009875,0.1084247 0.234489,0.1084247 0.1473938,0 1.0133723,0.3036224 1.2328793,0.4322626 0.320868,0.1880407 0.598078,0.4890642 0.750679,0.815165 0.120657,0.2578377 0.142829,0.3633395 0.160672,0.7645225 0.02443,0.5491928 -0.03774,0.9211418 -0.217132,1.2991888 -0.320832,0.676109 -0.843795,1.101548 -1.7206657,1.399791 -0.4304949,0.14642 -1.1075693,0.190637 -3.2903028,0.214875 l -1.7456684,0.01938 0,-4.7926436 z m 2.7845642,3.8116636 c 1.1247284,-0.03661 1.364956,-0.05673 1.6206761,-0.135714 0.3822094,-0.118052 0.6277508,-0.262478 0.8335816,-0.490309 0.299426,-0.331428 0.363733,-0.5098 0.383619,-1.0640648 0.0099,-0.2752989 -0.0038,-0.5766066 -0.03066,-0.676435 C 10.261953,8.8521842 10.039849,8.5339955 9.8093307,8.3709203 9.3226789,8.0266493 8.8951945,7.9540957 7.1198175,7.9144495 l -1.0248762,-0.022887 0,1.8222498 c 0,1.4283567 0.012172,1.8220107 0.056312,1.8211437 0.030971,-6.08e-4 0.6505167,-0.02045 1.3767671,-0.04409 l 0,0 z M 9.1708913,6.8165429 C 9.6320825,6.6450119 9.9018403,6.3938434 10.039602,6.0076951 10.115382,5.7952847 10.122081,5.1142082 10.051161,4.8325599 9.9730154,4.5222142 9.6340629,4.2008402 9.2484067,4.0714364 8.7386863,3.900404 8.5082929,3.8776462 7.2865358,3.8776462 l -1.1915945,0 0,1.5350772 0,1.5350772 1.407797,-0.017212 c 1.3055295,-0.015961 1.4267103,-0.024246 1.668153,-0.114046 l 0,0 z"
+ id="path3097"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/cam.svg b/icons/cam.svg
new file mode 100644
index 0000000..857f037
--- /dev/null
+++ b/icons/cam.svg
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg2985"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="offset.svg">
+ <defs
+ id="defs2987" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="5.4681565"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata2990">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="4.1896038"
+ y="14.243069"
+ id="text3011"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3013"
+ x="4.1896038"
+ y="14.243069" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.8920792"
+ y="14.603465"
+ id="text3015"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3017"></tspan></text>
+ <rect
+ style="fill:#000000"
+ id="rect3059"
+ width="9.6405945"
+ height="7.1178217"
+ x="3.8742576"
+ y="4.2420793" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 6.8024753,5.1881187 8.8747526,2.7554454"
+ id="path3065"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 10.541584,4.8727722 8.6044555,3.2059405"
+ id="path3067"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#f5f5f5"
+ d="m 3.9185148,7.8153583 0,-3.5363861 1.5204208,-1.44e-5 1.5204208,-1.44e-5 L 7.635099,3.4852888 C 8.0067574,3.0487787 8.3521385,2.6412743 8.4026119,2.5797233 l 0.091771,-0.1119109 0.3549059,0.3016135 c 0.1951981,0.1658874 0.3870329,0.3376097 0.426299,0.3816049 0.039266,0.043995 0.3478876,0.3157802 0.6858255,0.6039664 l 0.6144329,0.523975 1.469107,0 1.469106,0 0,3.5363861 0,3.5363867 -4.7977719,0 -4.7977723,0 0,-3.5363867 z M 9.0992079,4.2650395 c 0,-0.00766 -0.1135103,-0.1119302 -0.2522448,-0.2317049 L 8.5947183,3.8155623 8.4209137,4.0134801 c -0.095593,0.1088548 -0.1746691,0.213122 -0.1757256,0.2317049 -0.00106,0.018583 0.1906659,0.033787 0.4260495,0.033787 0.2353837,0 0.4279703,-0.00627 0.4279703,-0.013933 z"
+ id="path3069"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:#f5f5f5"
+ id="rect3071"
+ width="7.4331684"
+ height="4.9554439"
+ x="5.2257428"
+ y="5.5935645" />
+ </g>
+</svg>
diff --git a/icons/contour.svg b/icons/contour.svg
new file mode 100644
index 0000000..3ca6972
--- /dev/null
+++ b/icons/contour.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3043"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="contour.svg">
+ <defs
+ id="defs3045" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="7.7888027"
+ inkscape:cy="8.2141213"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3048">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="fill:#ff0000;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path3276"
+ sodipodi:cx="14.55099"
+ sodipodi:cy="8.6118813"
+ sodipodi:rx="9.6405945"
+ sodipodi:ry="6.1267328"
+ d="m 24.191585,8.6118813 a 9.6405945,6.1267328 0 1 1 -19.2811894,0 9.6405945,6.1267328 0 1 1 19.2811894,0 z"
+ transform="matrix(0.77310759,0,0,0.78636047,1.1841824,1.3668185)" />
+ <path
+ style="fill:#f5f5f5"
+ d="m 11.388258,12.498201 c -0.07433,-0.01031 -0.297327,-0.04024 -0.495544,-0.0665 C 9.6328073,12.264768 8.2444115,11.761952 7.3009299,11.130913 6.8583989,10.83493 6.2815595,10.293311 6.0230798,9.9310839 4.3197023,7.5440142 6.2925332,4.7867668 10.30707,3.943738 c 1.781847,-0.3741772 4.007612,-0.215719 5.676238,0.4041065 1.006015,0.3736932 1.838701,0.8971804 2.471883,1.5540058 2.394173,2.4835756 0.317584,5.6870727 -4.206289,6.4889267 -0.455085,0.08066 -0.770544,0.102341 -1.644307,0.112994 -0.594654,0.0073 -1.142005,0.0047 -1.216337,-0.0056 z"
+ id="path3071"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 11.861278,13.307426 C 10.69765,13.227336 9.8507744,13.070752 8.9330601,12.766011 6.0951211,11.823626 4.3840023,9.8124104 4.6507228,7.7326351 4.7741216,6.7704223 5.202267,5.9811486 6.0368877,5.1772797 6.9251019,4.3217919 8.3147893,3.6107467 9.766476,3.2690046 c 1.007398,-0.237152 1.35864,-0.2720881 2.70297,-0.2688501 1.15742,0.00279 1.315226,0.012777 1.914604,0.121198 2.792051,0.505051 4.88149,1.8803782 5.594488,3.682447 0.593409,1.4998138 0.170063,3.0766059 -1.159358,4.3181385 -1.19748,1.118314 -3.075837,1.907785 -5.043298,2.11969 -0.485881,0.05233 -1.569293,0.08957 -1.914604,0.0658 l 0,0 z m 1.747444,-0.784795 c 2.588495,-0.281096 4.766036,-1.471209 5.566796,-3.0424742 0.224604,-0.440722 0.314668,-0.8166903 0.317375,-1.3248722 0.0028,-0.5174048 -0.06291,-0.808818 -0.28931,-1.2839109 C 18.271733,4.9159178 15.358475,3.613431 12.131575,3.7095534 10.187822,3.7674535 8.5938267,4.2298981 7.2539083,5.1246452 6.5042996,5.6252057 5.9602319,6.2286829 5.6436656,6.9107184 c -0.209435,0.4512232 -0.2740249,0.747217 -0.2735408,1.253546 0.00118,1.2318562 0.7929537,2.3577556 2.2855816,3.2500806 0.8128676,0.485949 2.1886462,0.931277 3.2820566,1.062373 0.222995,0.02674 0.456126,0.05593 0.518069,0.06488 0.262189,0.03787 1.750191,0.02476 2.15289,-0.01897 z"
+ id="path3073"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#f5f5f5"
+ d="M 11.343209,13.245117 C 9.5937341,13.047645 8.1056839,12.547838 6.9058324,11.754689 6.371566,11.401518 5.5996242,10.644014 5.2940686,10.173071 4.333312,8.692286 4.4609788,7.0416268 5.644523,5.6418912 6.5242567,4.6014616 7.9776713,3.7613648 9.6390341,3.3330005 10.656527,3.0706509 10.898273,3.0460473 12.469446,3.0449358 l 1.441584,-0.00102 0.675743,0.1437076 c 3.06324,0.6514473 5.091652,2.1976978 5.542729,4.2251995 0.121327,0.5453416 0.07646,1.3228244 -0.106086,1.838493 -0.175145,0.4947481 -0.348625,0.8049689 -0.714633,1.2779159 -1.015785,1.312573 -3.041351,2.333074 -5.262604,2.651352 -0.557056,0.07982 -2.216538,0.119439 -2.70297,0.06453 l 0,0 z m 2.65792,-0.761604 C 16.765411,12.076606 18.883573,10.69757 19.40378,8.966101 19.516955,8.5894051 19.542459,7.9263467 19.458841,7.5345759 19.213513,6.3851352 18.209412,5.3106395 16.714948,4.5983127 15.491619,4.0152208 14.335659,3.7506808 12.823067,3.7076607 c -1.40431,-0.03994 -2.56562,0.113516 -3.6854803,0.4870019 -1.6873215,0.562741 -2.9205738,1.5045897 -3.4796228,2.6574276 -0.2369382,0.4885999 -0.291032,0.7272059 -0.2911923,1.284439 -2.658e-4,0.9183702 0.2931535,1.5643927 1.0650169,2.3448648 0.4480104,0.453008 1.021282,0.873535 1.555232,1.140851 0.6916321,0.346258 1.9286198,0.723796 2.8072965,0.85681 0.792543,0.119975 2.406819,0.122219 3.206812,0.0045 l 0,-2e-6 z"
+ id="path3203"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/copy.svg b/icons/copy.svg
new file mode 100644
index 0000000..87eed71
--- /dev/null
+++ b/icons/copy.svg
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3575"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="copy.svg">
+ <defs
+ id="defs3577" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="8"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3580">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#000000"
+ id="rect3608"
+ width="5.3158417"
+ height="6.1267328"
+ x="3.919307"
+ y="3.521287" />
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.5316832"
+ y="15.279208"
+ id="text3612"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3614"
+ x="1.5316832"
+ y="15.279208" /></text>
+ <path
+ style="fill:#ffffff"
+ d="m 3.9635643,6.5899505 0,-3.0408416 2.6353961,0 2.635396,0 0,3.0408416 0,3.0408416 -2.635396,0 -2.6353961,0 0,-3.0408416 z"
+ id="path3620"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:#000000"
+ id="rect3622"
+ width="5.2257428"
+ height="6.2168317"
+ x="5.3608913"
+ y="5.0079207" />
+ <path
+ style="fill:#ffffff"
+ d="m 5.4051485,8.1216337 0,-3.0858912 2.5903465,0 2.590347,0 0,3.0858912 0,3.0858913 -2.590347,0 -2.5903465,0 0,-3.0858913 z"
+ id="path3624"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/embross.svg b/icons/embross.svg
new file mode 100644
index 0000000..2e15a5b
--- /dev/null
+++ b/icons/embross.svg
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3017"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="embross.svg">
+ <defs
+ id="defs3019" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.328398"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3022">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.4415842"
+ y="15.279208"
+ id="text3025"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3027"
+ x="1.4415842"
+ y="15.279208"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.3336635"
+ y="8.7019796"
+ id="text3029"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3031" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.1985149"
+ y="11.314851"
+ id="text3033"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3035" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:29.19195557px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="4.2054524"
+ y="12.5575"
+ id="text3056"
+ sodipodi:linespacing="122%"
+ transform="scale(1.0093569,0.99072982)"><tspan
+ sodipodi:role="line"
+ id="tspan3058"
+ x="4.2054524"
+ y="12.5575"
+ style="font-size:13.1363802px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans">E</tspan></text>
+ <path
+ style="fill:#ffffff"
+ d="m 5.540297,7.8204634 0,-4.6175743 2.4326733,0 2.4326737,0 0,0.4955446 0,0.4955445 -1.8470301,0 -1.8470298,0 0,1.4415842 0,1.4415842 1.734406,0 1.7344059,0 0,0.4955445 0,0.4955446 -1.7344059,0 -1.734406,0 0,1.6893564 0,1.6893569 1.8470298,0 1.8470301,0 0,0.495544 0,0.495545 -2.4326737,0 -2.4326733,0 0,-4.6175746 z"
+ id="path3132"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#f5f5f5"
+ d="m 5.540297,7.8204634 0,-4.6175743 2.4326733,0 2.4326737,0 0,0.4955446 0,0.4955445 -1.8470301,0 -1.8470298,0 0,1.4415842 0,1.4415842 1.734406,0 1.7344059,0 0,0.4955445 0,0.4955446 -1.7344059,0 -1.734406,0 0,1.6893564 0,1.6893569 1.8470298,0 1.8470301,0 0,0.495544 0,0.495545 -2.4326737,0 -2.4326733,0 0,-4.6175746 z"
+ id="path3263"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/finedges.svg b/icons/finedges.svg
new file mode 100644
index 0000000..3b16d4e
--- /dev/null
+++ b/icons/finedges.svg
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3043"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="finedges.svg">
+ <defs
+ id="defs3045" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="7.7888027"
+ inkscape:cy="6.9403464"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3048">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.9821782"
+ y="5.5485148"
+ id="text3297"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3299" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="13.740099"
+ y="14.423267"
+ id="text3305"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3307" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.67574257"
+ y="14.918812"
+ id="text3309"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3311" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:13.50702095px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="5.1331983"
+ y="13.114629"
+ id="text3313"
+ sodipodi:linespacing="125%"
+ transform="scale(1.020869,0.9795576)"><tspan
+ sodipodi:role="line"
+ id="tspan3315"
+ x="5.1331983"
+ y="13.114629">f</tspan></text>
+ <path
+ style="fill:#f5f5f5"
+ d="m 6.7706839,9.7444061 0,-3.1084159 -0.6555721,0 -0.6555721,0 L 5.4731563,6.4030397 5.486773,6.1700893 6.1174661,5.9601549 6.7481591,5.7502206 6.7771927,5.0330807 C 6.8193983,3.9905895 7.0046848,3.4968714 7.4878301,3.1395026 7.8393087,2.8795238 8.2268772,2.7842575 8.9330601,2.7842575 c 0.5975615,0 1.2211019,0.1010956 1.2876469,0.2087682 0.02506,0.040551 -0.190196,0.7516978 -0.2465672,0.8145826 C 9.9582342,3.8253522 9.8239135,3.8069587 9.67565,3.7667341 9.3244588,3.6714539 8.6934965,3.6694407 8.4772412,3.7629104 8.2831961,3.8467805 8.1059794,4.067469 8.0331486,4.3159407 c -0.02905,0.099109 -0.064245,0.469078 -0.07821,0.8221535 l -0.025391,0.6419554 0.8396274,0 0.8396273,0 0,0.4279703 0,0.4279703 -0.8334158,0 -0.8334159,0 0,3.1084159 0,3.1084159 -0.5856436,0 -0.5856435,0 0,-3.1084159 z"
+ id="path3323"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/grey.svg b/icons/grey.svg
new file mode 100644
index 0000000..4eb8472
--- /dev/null
+++ b/icons/grey.svg
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3017"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="grey.svg">
+ <defs
+ id="defs3019" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="7.3052196"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3022">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.4415842"
+ y="15.279208"
+ id="text3025"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3027"
+ x="1.4415842"
+ y="15.279208"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.3336635"
+ y="8.7019796"
+ id="text3029"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3031" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:12.65840054px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="3.8965118"
+ y="12.109899"
+ id="text3041"
+ sodipodi:linespacing="125%"
+ transform="scale(0.94927888,1.0534312)"><tspan
+ sodipodi:role="line"
+ id="tspan3043"
+ x="3.8965118"
+ y="12.109899">G</tspan></text>
+ <path
+ style="fill:#f5f5f5"
+ d="M 7.4098515,12.781578 C 6.1820894,12.510841 5.2609612,11.653394 4.8229633,10.373532 4.5453404,9.5622969 4.435353,8.717675 4.4735346,7.6901764 4.5320841,6.1145624 4.9267076,5.068623 5.7829135,4.2197023 6.1119391,3.8934762 6.2766621,3.7698051 6.6120263,3.5972193 7.5754672,3.1014111 8.8077893,2.9942705 9.9888834,3.3036283 c 0.2783056,0.072895 0.8845426,0.3118392 0.9452786,0.3725745 0.01519,0.01519 -0.338535,0.9325931 -0.373093,0.9676356 -0.0092,0.00937 -0.177992,-0.047951 -0.375012,-0.1273704 C 9.6386942,4.295826 9.2135229,4.1968248 8.6893829,4.1679674 7.8180297,4.1199937 7.1210605,4.3647728 6.5786146,4.90928 5.8890943,5.6014204 5.5849223,6.5575251 5.58642,8.0280477 c 0.00111,1.0872752 0.1066919,1.6528565 0.4372757,2.3423423 0.5995403,1.25044 1.8553742,1.724536 3.632985,1.371507 l 0.3885673,-0.07717 0,-1.43542 0,-1.4354187 -0.8784658,0 -0.8784654,0 0,-0.5180693 0,-0.5180693 1.4190595,0 1.4190597,0 0,2.3129493 0,2.312948 -0.394184,0.117907 c -0.562028,0.168112 -0.696364,0.198258 -1.2333802,0.27678 -0.5813391,0.085 -1.7103017,0.08676 -2.0890203,0.0032 l 0,-1e-6 z"
+ id="path3045"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/invert.svg b/icons/invert.svg
new file mode 100644
index 0000000..f4bb71a
--- /dev/null
+++ b/icons/invert.svg
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3017"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="invert.svg">
+ <defs
+ id="defs3019" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.328398"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3022">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.4415842"
+ y="15.279208"
+ id="text3025"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3027"
+ x="1.4415842"
+ y="15.279208"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.3336635"
+ y="8.7019796"
+ id="text3029"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3031" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.1985149"
+ y="11.314851"
+ id="text3033"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3035" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:30.70153999px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.6724377"
+ y="14.187632"
+ id="text3056"
+ sodipodi:linespacing="122%"
+ transform="scale(1.1043937,0.90547417)"><tspan
+ sodipodi:role="line"
+ id="tspan3058"
+ x="1.6724377"
+ y="14.187632"
+ style="font-size:13.8156929px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans"> I</tspan></text>
+ <path
+ style="fill:#f5f5f5"
+ d="m 6.4412871,12.555481 c 0,-0.189042 0.019349,-0.295299 0.056312,-0.309238 0.030971,-0.01168 0.3147676,-0.0696 0.630658,-0.128706 l 0.5743462,-0.10747 3.5e-5,-3.6143641 3.51e-5,-3.6143645 -0.1013614,-0.024151 C 7.5455631,4.7439042 7.2668193,4.6869003 6.9818812,4.6305119 L 6.4638119,4.5279874 6.4503998,4.2483591 6.4369878,3.9687307 l 1.9639528,0 1.9639524,0 -0.01341,0.2796578 -0.01341,0.2796578 -0.4054452,0.080633 C 9.7096287,4.6530275 9.4206285,4.7110942 9.290401,4.7377165 l -0.2367772,0.048404 0.01153,3.6112055 0.01153,3.6112059 0.540594,0.100942 c 0.8265408,0.154336 0.7433168,0.104843 0.7433168,0.442046 l 0,0.291963 -1.9596534,0 -1.9596535,0 0,-0.288002 z"
+ id="path3439"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/left_bottom.svg b/icons/left_bottom.svg
new file mode 100644
index 0000000..8018d15
--- /dev/null
+++ b/icons/left_bottom.svg
@@ -0,0 +1,274 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3017"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="left_bottom.svg">
+ <defs
+ id="defs3019" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.328398"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3022">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.4415842"
+ y="15.279208"
+ id="text3025"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3027"
+ x="1.4415842"
+ y="15.279208"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.3336635"
+ y="8.7019796"
+ id="text3029"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3031" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.1985149"
+ y="11.314851"
+ id="text3033"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3035" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.8920792"
+ y="14.062871"
+ id="text3056"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3058"
+ x="1.8920792"
+ y="14.062871"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans"> </tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122%;letter-spacing:0px;word-spacing:0px;writing-mode:lr;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.2163366"
+ y="3.791584"
+ id="text3117"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3119"
+ x="1.2163366"
+ y="3.791584" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="8.9198017"
+ y="4.5123763"
+ id="text3154"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="4.5123763"
+ id="tspan3158" /><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="14.272377"
+ id="tspan3160" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="2.0722773"
+ y="14.738614"
+ id="text3162"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3164"
+ x="2.0722773"
+ y="14.738614" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0361387"
+ y="15.144059"
+ id="text3166"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3168"
+ x="1.0361387"
+ y="15.144059" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="4.1069307"
+ id="text3191"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3193" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="10.278713"
+ id="text3216"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="1.0811881"
+ y="10.278713"
+ id="tspan3220" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="15.459406"
+ id="text3224"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3226"
+ x="1.0811881"
+ y="15.459406">L-B</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.4415842"
+ y="6.8099008"
+ id="text3228"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3230"
+ x="1.4415842"
+ y="6.8099008" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.27029702"
+ y="6.8099008"
+ id="text3232"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3234"
+ x="0.27029702"
+ y="6.8099008">text edit</tspan></text>
+ <path
+ style="fill:#ffffff"
+ d="m 1.0080716,5.5679881 c 7.03e-5,-0.3468812 0.00714,-0.4828321 0.015715,-0.3021132 0.00857,0.1807189 0.00852,0.4645308 -1.278e-4,0.6306931 -0.00864,0.1661623 -0.015657,0.018301 -0.015587,-0.3285799 z"
+ id="path3078"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 0.9998435,6.8024538 C 0.91017723,6.775747 0.81690142,6.7012352 0.76350199,6.6136564 0.6849364,6.4848036 0.6752688,6.3839765 0.67511883,5.6918743 l -1.684e-4,-0.777104 -0.15767327,0 c -0.20450798,0 -0.20678618,-0.095041 -0.004589,-0.1914616 0.12121407,-0.057803 0.1688609,-0.1205011 0.228868,-0.3011655 0.0560095,-0.1686289 0.0983495,-0.228165 0.16226278,-0.228165 0.0747894,0 0.0864785,0.033491 0.0864785,0.2477723 l 0,0.2477723 0.27029703,0 c 0.2582838,0 0.270297,0.00501 0.270297,0.1126237 0,0.1076183 -0.012013,0.1126238 -0.270297,0.1126238 l -0.27029703,0 0,0.7588595 c 0,0.8685819 0.0153003,0.9079722 0.35268093,0.9079722 0.1559756,0 0.1879131,0.014861 0.1879131,0.08744 0,0.048092 -0.015359,0.096933 -0.03413,0.108534 -0.082981,0.051285 -0.3614494,0.065226 -0.4969173,0.024878 z"
+ id="path3080"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 2.5531488,6.7885855 C 2.2891192,6.7163791 2.0544255,6.5002315 1.9537124,6.236518 1.8388849,5.9358466 1.8826174,5.3522224 2.0401126,5.0834771 2.3240392,4.598993 2.9171221,4.5202209 3.2947898,4.9168334 c 0.1610986,0.1691798 0.263329,0.4578123 0.263329,0.7434711 l 0,0.1104064 -0.6820176,0 -0.6820176,0 0.026866,0.2268396 c 0.053704,0.4534426 0.3428047,0.6417658 0.8416248,0.5482426 0.1362748,-0.02555 0.2832488,-0.060523 0.3266089,-0.077717 0.064326,-0.025508 0.078837,-0.00681 0.078837,0.1015753 0,0.1148629 -0.022859,0.1393871 -0.1689357,0.1812414 -0.302798,0.086759 -0.5253919,0.098006 -0.7459353,0.037693 l 0,0 z M 3.1977227,5.4080777 c 0,-0.1698534 -0.1072985,-0.3851335 -0.2227522,-0.4469224 -0.1459494,-0.07811 -0.4301392,-0.054802 -0.5579871,0.045763 -0.1038301,0.081673 -0.2093924,0.323967 -0.2100986,0.4822331 -1.858e-4,0.041652 0.1287567,0.056312 0.4952934,0.056312 l 0.4955445,0 0,-0.1373857 z"
+ id="path3082"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 4.2369013,6.2617179 4.6021163,5.7165864 4.2600996,5.2030546 3.9180829,4.6895228 l 0.1712048,0 c 0.1673451,0 0.1767445,0.00914 0.4169239,0.4052906 C 4.6413571,5.3177232 4.7620668,5.49969 4.7744554,5.4991839 4.786844,5.4986779 4.9042885,5.3162972 5.0354432,5.0938934 5.2625701,4.7087458 5.2813088,4.6895228 5.4296264,4.6895228 c 0.085646,0 0.1557201,0.010812 0.1557201,0.024027 0,0.013214 -0.1434506,0.2378362 -0.3187793,0.4991592 -0.1753285,0.261323 -0.3079789,0.5032778 -0.2947785,0.5376775 0.0132,0.0344 0.1667872,0.2753646 0.341304,0.5354777 0.1745168,0.2601131 0.3173033,0.4837448 0.3173033,0.4969594 0,0.013215 -0.070111,0.024026 -0.1558024,0.024026 -0.1484334,0 -0.1681427,-0.020223 -0.416708,-0.4275671 C 4.9143876,6.1441205 4.7845492,5.9515339 4.7693558,5.9513121 4.7541624,5.9510903 4.6252122,6.1434956 4.4827998,6.3788792 4.2266091,6.8023195 4.2220044,6.8068495 4.0477773,6.8068495 l -0.176091,0 0.365215,-0.5451316 z"
+ id="path3084"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 6.4863366,6.7944955 C 6.2143608,6.6992443 6.1711413,6.5450045 6.1710601,5.6693495 l -7e-5,-0.7545792 -0.1576733,0 c -0.204508,0 -0.2067862,-0.095041 -0.00459,-0.1914616 0.1212141,-0.057803 0.1688609,-0.1205011 0.228868,-0.3011655 0.05601,-0.1686289 0.09835,-0.228165 0.1622628,-0.228165 0.074789,0 0.086478,0.033491 0.086478,0.2477723 l 0,0.2477723 0.2702971,0 c 0.2582838,0 0.270297,0.00501 0.270297,0.1126237 0,0.1076183 -0.012013,0.1126238 -0.270297,0.1126238 l -0.2702971,0 0,0.7332931 c 0,0.8719524 0.022458,0.9335386 0.3404258,0.9335386 0.1681065,0 0.2001683,0.014006 0.2001683,0.08744 0,0.048092 -0.015359,0.096933 -0.03413,0.108534 -0.092212,0.05699 -0.3659374,0.066134 -0.5064639,0.016919 z"
+ id="path3086"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d=""
+ id="path3088"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 9.1169877,6.8025921 C 8.6876913,6.7015798 8.4234654,6.2989609 8.4234654,5.745825 c 0,-0.3581282 0.097634,-0.6309877 0.2999517,-0.8382781 0.5297235,-0.542744 1.3654209,-0.1182216 1.3666289,0.6942284 l 2.51e-4,0.1689356 -0.6757425,0 -0.6757426,0 0,0.1481277 c 0,0.08147 0.045613,0.2385805 0.1013614,0.3491337 0.1341402,0.2660084 0.3397027,0.3440888 0.7320544,0.278062 0.1486634,-0.025018 0.3057736,-0.059827 0.3491337,-0.077354 0.064463,-0.026057 0.078837,-0.00765 0.078837,0.1009709 0,0.1145403 -0.023269,0.1397547 -0.1689356,0.1830565 -0.2679836,0.079663 -0.5145374,0.096882 -0.7142747,0.049884 l 0,0 z M 9.729901,5.4080777 C 9.729901,5.2382243 9.6226025,5.0229442 9.5071488,4.9611553 9.2579802,4.8278043 8.9205372,4.9579619 8.8119701,5.2292978 8.6842446,5.5485152 8.6801676,5.5454634 9.2343564,5.5454634 l 0.4955446,0 0,-0.1373857 z"
+ id="path3090"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 11.177017,6.806756 C 10.954583,6.7542393 10.805634,6.625006 10.679377,6.3749855 10.584704,6.1875088 10.56775,6.0915802 10.568603,5.7481862 c 0.0015,-0.6218861 0.191126,-0.9477891 0.618246,-1.0628004 0.225892,-0.060827 0.456978,-0.00187 0.633338,0.16157 0.06698,0.062075 0.130874,0.112864 0.141986,0.112864 0.01111,0 0.0202,-0.2635396 0.0202,-0.5856436 l 0,-0.5856435 0.157674,0 0.157673,0 0,1.5091584 0,1.5091584 -0.135149,0 c -0.109916,0 -0.135277,-0.018924 -0.135838,-0.1013614 -0.0014,-0.1988149 -0.06381,-0.2231121 -0.2022,-0.078663 -0.106728,0.1114004 -0.379271,0.2383256 -0.472853,0.2202111 -0.01239,-0.0024 -0.09099,-0.020524 -0.174666,-0.04028 z m 0.530954,-0.2906038 c 0.18785,-0.097141 0.25201,-0.299197 0.251007,-0.7904908 -0.0012,-0.6116031 -0.138456,-0.8108911 -0.55829,-0.8108911 -0.347892,0 -0.508317,0.2592534 -0.511932,0.8273036 -0.0025,0.3872383 0.06932,0.6266705 0.224491,0.7487247 0.137527,0.1081785 0.411936,0.1198768 0.594724,0.025354 z"
+ id="path3092"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 13.018515,5.7481862 0,-1.0586634 0.157673,0 0.157673,0 0,1.0586634 0,1.0586633 -0.157673,0 -0.157673,0 0,-1.0586633 z"
+ id="path3094"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 14.424596,6.8024538 C 14.33493,6.775747 14.241654,6.7012352 14.188255,6.6136564 14.109689,6.4848036 14.100021,6.3839765 14.099871,5.6918743 l -1.68e-4,-0.777104 -0.157673,0 c -0.200928,0 -0.208019,-0.094959 -0.01395,-0.1868666 0.10999,-0.05209 0.16107,-0.1200886 0.217646,-0.2897326 0.05461,-0.163758 0.09945,-0.2254289 0.171627,-0.2360637 0.09026,-0.0133 0.0977,0.0047 0.0977,0.2365099 l 0,0.2509055 0.270297,0 c 0.258283,0 0.270297,0.00501 0.270297,0.1126237 0,0.1076183 -0.01201,0.1126238 -0.270297,0.1126238 l -0.270297,0 0,0.7588595 c 0,0.8685819 0.0153,0.9079722 0.35268,0.9079722 0.155976,0 0.187914,0.014861 0.187914,0.08744 0,0.048092 -0.01536,0.096933 -0.03413,0.108534 -0.08298,0.051285 -0.361449,0.065226 -0.496917,0.024878 z"
+ id="path3096"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 1.4858415,14.037295 0,-1.419059 0.1576733,0 0.1576733,0 0,1.283911 0,1.283911 0.5631188,0 0.5631188,0 0,0.135148 0,0.135149 -0.7207921,0 -0.7207921,0 0,-1.41906 z"
+ id="path3098"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 3.2427722,14.397691 0,-0.157673 0.4504951,0 0.450495,0 0,0.157673 0,0.157673 -0.450495,0 -0.4504951,0 0,-0.157673 z"
+ id="path3100"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 4.7294059,14.034868 0,-1.430127 0.599739,0.02515 c 0.6491718,0.02722 0.837052,0.07851 1.0220432,0.278995 0.09545,0.103447 0.1126238,0.165604 0.1126238,0.407622 0,0.240143 -0.017354,0.304035 -0.1091055,0.401701 -0.060008,0.06387 -0.1765736,0.140534 -0.2590347,0.170352 -0.1560684,0.05644 -0.2006072,0.126214 -0.08056,0.126214 0.038153,0 0.1511839,0.04174 0.2511799,0.09275 0.4664891,0.237985 0.3901501,0.986188 -0.1265456,1.240281 -0.1357129,0.06674 -0.3006743,0.08775 -0.7909092,0.100759 l -0.6194307,0.01643 0,-1.430127 z m 1.2090694,1.081267 c 0.2099752,-0.08402 0.2925808,-0.222204 0.2925808,-0.48945 0,-0.391733 -0.2020517,-0.521093 -0.8146452,-0.521564 l -0.3266089,-2.52e-4 0,0.540594 0,0.540595 0.3369596,0 c 0.1994567,0 0.4082714,-0.02853 0.5117137,-0.06992 l 0,0 z m 0.1287829,-1.436227 c 0.096934,-0.112745 0.1105012,-0.164483 0.092435,-0.352497 -0.011646,-0.121197 -0.05156,-0.250744 -0.088699,-0.287883 -0.091184,-0.09118 -0.3592259,-0.148621 -0.6996336,-0.149921 l -0.2815594,-0.0011 0,0.477368 0,0.477369 0.4319236,-0.01561 c 0.4132738,-0.01494 0.4368292,-0.02132 0.5455326,-0.147751 z"
+ id="path3102"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d=""
+ id="path3235"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 13.01996,4.1966781 c -0.03401,-0.063551 -0.03536,-0.1195254 -0.0043,-0.1775979 0.107644,-0.2011345 0.336943,-0.1203114 0.310786,0.1095453 -0.01206,0.1060051 -0.0396,0.1357548 -0.135983,0.1468893 -0.08514,0.00984 -0.135534,-0.013462 -0.170521,-0.078837 z"
+ id="path3237"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/left_top.svg b/icons/left_top.svg
new file mode 100644
index 0000000..e5033f5
--- /dev/null
+++ b/icons/left_top.svg
@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3017"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="left_top.svg">
+ <defs
+ id="defs3019" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.328398"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3022">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.4415842"
+ y="15.279208"
+ id="text3025"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3027"
+ x="1.4415842"
+ y="15.279208"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.3336635"
+ y="8.7019796"
+ id="text3029"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3031" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.1985149"
+ y="11.314851"
+ id="text3033"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3035" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.8920792"
+ y="14.062871"
+ id="text3056"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3058"
+ x="1.8920792"
+ y="14.062871"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans"> </tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122%;letter-spacing:0px;word-spacing:0px;writing-mode:lr;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.2163366"
+ y="3.791584"
+ id="text3117"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3119"
+ x="1.2163366"
+ y="3.791584" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="8.9198017"
+ y="4.5123763"
+ id="text3154"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="4.5123763"
+ id="tspan3158" /><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="14.272377"
+ id="tspan3160" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="2.0722773"
+ y="14.738614"
+ id="text3162"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3164"
+ x="2.0722773"
+ y="14.738614" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0361387"
+ y="15.144059"
+ id="text3166"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3168"
+ x="1.0361387"
+ y="15.144059" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="4.1069307"
+ id="text3191"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3193" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="10.278713"
+ id="text3216"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="1.0811881"
+ y="10.278713"
+ id="tspan3220" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="15.459406"
+ id="text3224"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3226"
+ x="1.0811881"
+ y="15.459406" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.4415842"
+ y="6.8099008"
+ id="text3228"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3230"
+ x="1.4415842"
+ y="6.8099008" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.27029702"
+ y="6.8099008"
+ id="text3232"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3234"
+ x="0.27029702"
+ y="6.8099008" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="9.7757425"
+ y="15.234158"
+ id="text3255"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3257"
+ x="9.7757425"
+ y="15.234158" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="4.3321781"
+ id="text3278"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3280"
+ x="0.58564359"
+ y="4.3321781">L-T</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="12.035644"
+ id="text3282"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3284"
+ x="0.58564359"
+ y="12.035644">text edit</tspan></text>
+ <path
+ style="fill:#ffffff"
+ d="m 0.99029697,2.9100673 0,-1.4190594 0.15767323,0 0.1576733,0 0,1.2839109 0,1.2839109 0.5631188,0 0.5631188,0 0,0.1351485 0,0.1351485 -0.720792,0 -0.72079213,0 0,-1.4190594 z"
+ id="path3209"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 2.7472277,3.2704634 0,-0.1576733 0.450495,0 0.4504951,0 0,0.1576733 0,0.1576732 -0.4504951,0 -0.450495,0 0,-0.1576732 z"
+ id="path3211"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 4.5041584,3.0452158 0,-1.2839109 -0.4054456,0 -0.4054455,0 0,-0.1351485 0,-0.1351485 0.9910891,0 0.9910891,0 0,0.1351485 0,0.1351485 -0.4054455,0 -0.4054456,0 0,1.2839109 0,1.2839109 -0.180198,0 -0.180198,0 0,-1.2839109 z"
+ id="path3213"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 1.31519,12.028196 C 1.2255238,12.00149 1.132248,11.926978 1.0788485,11.839399 1.0002829,11.710546 0.99061534,11.609719 0.99046537,10.917617 l -1.684e-4,-0.777104 -0.15767327,0 c -0.20450798,0 -0.20678618,-0.09504 -0.004589,-0.1914617 0.12121407,-0.057803 0.1688609,-0.1205011 0.22886799,-0.3011655 0.05601,-0.1686289 0.09835,-0.228165 0.1622628,-0.228165 0.074789,0 0.086478,0.033491 0.086478,0.2477723 l 0,0.2477723 0.270297,0 c 0.2582839,0 0.2702971,0.00501 0.2702971,0.1126236 0,0.107618 -0.012013,0.112624 -0.2702971,0.112624 l -0.270297,0 0,0.758859 c 0,0.868582 0.0153,0.907973 0.3526809,0.907973 0.1559756,0 0.1879132,0.01486 0.1879132,0.08744 0,0.04809 -0.015359,0.09693 -0.03413,0.108534 -0.082981,0.05129 -0.3614494,0.06523 -0.4969173,0.02488 z"
+ id="path3215"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 2.8684953,12.014328 C 2.6044657,11.942122 2.369772,11.725974 2.2690589,11.462261 2.1542315,11.161589 2.1979639,10.577965 2.3554591,10.30922 2.6393857,9.8247356 3.2324686,9.7459636 3.6101364,10.142576 c 0.1610986,0.16918 0.2633289,0.457812 0.2633289,0.743471 l 0,0.110407 -0.6820176,0 -0.6820175,0 0.026866,0.226839 c 0.053703,0.453443 0.3428046,0.641766 0.8416248,0.548243 0.1362747,-0.02555 0.2832487,-0.06052 0.3266089,-0.07772 0.064326,-0.02551 0.078837,-0.0068 0.078837,0.101575 0,0.114863 -0.022859,0.139387 -0.1689356,0.181242 -0.3027981,0.08676 -0.525392,0.09801 -0.7459354,0.03769 l 0,0 z m 0.644574,-1.380508 c 0,-0.169853 -0.1072985,-0.385133 -0.2227522,-0.446922 -0.1459495,-0.07811 -0.4301392,-0.0548 -0.5579872,0.04576 -0.1038301,0.08167 -0.2093924,0.323967 -0.2100985,0.482233 -1.858e-4,0.04165 0.1287567,0.05631 0.4952933,0.05631 l 0.4955446,0 0,-0.137386 z"
+ id="path3217"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 4.5522478,11.487461 4.9174629,10.942329 4.5754461,10.428797 4.2334294,9.9152654 l 0.1712048,0 c 0.1673451,0 0.1767446,0.00914 0.4169239,0.4052906 0.1351455,0.22291 0.2558552,0.404877 0.2682439,0.404371 0.012389,-5.06e-4 0.1298331,-0.182887 0.2609877,-0.405291 0.2271269,-0.3851475 0.2458657,-0.4043706 0.3941832,-0.4043706 0.085646,0 0.1557202,0.010812 0.1557202,0.024027 0,0.013215 -0.1434507,0.2378361 -0.3187793,0.4991591 -0.1753286,0.261323 -0.3079789,0.503278 -0.2947786,0.537678 0.0132,0.0344 0.1667872,0.275364 0.341304,0.535477 0.1745169,0.260113 0.3173034,0.483745 0.3173034,0.49696 0,0.01321 -0.070111,0.02403 -0.1558025,0.02403 -0.1484333,0 -0.1681426,-0.02022 -0.4167079,-0.427567 -0.143498,-0.235162 -0.2733365,-0.427749 -0.2885299,-0.42797 -0.015193,-2.22e-4 -0.1441435,0.192183 -0.2865559,0.427567 -0.2561908,0.42344 -0.2607955,0.42797 -0.4350226,0.42797 l -0.176091,0 0.365215,-0.545131 z"
+ id="path3219"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 6.8016832,12.020238 C 6.5297073,11.924987 6.4864879,11.770747 6.4864066,10.895092 l -7e-5,-0.754579 -0.1576732,0 c -0.204508,0 -0.2067862,-0.09504 -0.00459,-0.1914617 0.121214,-0.057803 0.1688609,-0.1205011 0.228868,-0.3011655 0.056009,-0.1686289 0.098349,-0.228165 0.1622627,-0.228165 0.07479,0 0.086479,0.033491 0.086479,0.2477723 l 0,0.2477723 0.270297,0 c 0.2582838,0 0.270297,0.00501 0.270297,0.1126236 0,0.107618 -0.012013,0.112624 -0.270297,0.112624 l -0.270297,0 0,0.733293 c 0,0.871952 0.022458,0.933539 0.3404257,0.933539 0.1681065,0 0.2001683,0.01401 0.2001683,0.08744 0,0.04809 -0.015359,0.09693 -0.03413,0.108534 -0.092212,0.05699 -0.3659374,0.06613 -0.5064638,0.01692 z"
+ id="path3221"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 9.4323342,12.028335 C 9.0030378,11.927322 8.7388119,11.524703 8.7388119,10.971568 c 0,-0.358129 0.097634,-0.630988 0.2999517,-0.838278 0.5297235,-0.5427444 1.3654204,-0.118222 1.3666284,0.694228 l 2.52e-4,0.168936 -0.675743,0 -0.6757426,0 0,0.148127 c 0,0.08147 0.045613,0.238581 0.1013614,0.349134 0.1341402,0.266008 0.3397027,0.344089 0.7320545,0.278062 0.1486637,-0.02502 0.3057737,-0.05983 0.3491337,-0.07735 0.06446,-0.02606 0.07884,-0.0076 0.07884,0.100971 0,0.11454 -0.02327,0.139755 -0.168936,0.183057 -0.2679836,0.07966 -0.5145375,0.09688 -0.7142748,0.04988 l 0,0 z M 10.045248,10.63382 c 0,-0.169853 -0.107299,-0.385133 -0.2227526,-0.446922 -0.2491687,-0.133351 -0.5866117,-0.0032 -0.6951788,0.268142 -0.1277254,0.319218 -0.1318025,0.316166 0.4223864,0.316166 l 0.495545,0 0,-0.137386 z"
+ id="path3223"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 11.496598,11.691986 c -0.168243,-0.117842 -0.244741,-0.323737 -0.248635,-0.6692 -0.0043,-0.381224 0.06881,-0.623029 0.22568,-0.74642 0.222429,-0.174963 0.58167,-0.0773 0.693848,0.188631 0.08179,0.193894 0.08179,0.82397 0,1.017863 -0.10625,0.251878 -0.454759,0.360512 -0.670893,0.209126 z"
+ id="path3225"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 11.492364,12.032499 c -0.222435,-0.05252 -0.371384,-0.18175 -0.497641,-0.431771 -0.09467,-0.187477 -0.111626,-0.283405 -0.110774,-0.626799 0.0015,-0.621886 0.191127,-0.947789 0.618246,-1.0628006 0.225892,-0.060827 0.456979,-0.00187 0.633338,0.1615696 0.06698,0.06208 0.130874,0.112864 0.141986,0.112864 0.01111,0 0.0202,-0.2635392 0.0202,-0.5856431 l 0,-0.5856436 0.157673,0 0.157673,0 0,1.5091587 0,1.509158 -0.135148,0 c -0.109917,0 -0.135277,-0.01892 -0.135839,-0.101361 -0.0014,-0.198815 -0.06381,-0.223112 -0.202199,-0.07866 -0.106728,0.1114 -0.379271,0.238326 -0.472853,0.220211 -0.01239,-0.0024 -0.09099,-0.02052 -0.174666,-0.04028 z m 0.530954,-0.290604 c 0.18785,-0.09714 0.252009,-0.299197 0.251006,-0.790491 -0.0012,-0.611603 -0.138456,-0.810891 -0.558289,-0.810891 -0.347893,0 -0.508318,0.259253 -0.511933,0.827304 -0.0025,0.387238 0.06932,0.62667 0.224492,0.748724 0.137527,0.108179 0.411936,0.119877 0.594724,0.02535 z"
+ id="path3227"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 13.333861,10.973929 0,-1.0586636 0.157674,0 0.157673,0 0,1.0586636 0,1.058663 -0.157673,0 -0.157674,0 0,-1.058663 z"
+ id="path3229"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 13.335306,9.4224208 c -0.03401,-0.063551 -0.03536,-0.1195255 -0.0043,-0.1775979 0.107644,-0.2011346 0.336943,-0.1203115 0.310785,0.1095452 -0.01206,0.1060051 -0.0396,0.1357548 -0.135983,0.1468893 -0.08514,0.00984 -0.135533,-0.013462 -0.170521,-0.078837 z"
+ id="path3231"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 14.739943,12.028196 c -0.08967,-0.02671 -0.182942,-0.101218 -0.236342,-0.188797 -0.07856,-0.128853 -0.08823,-0.22968 -0.08838,-0.921782 l -1.68e-4,-0.777104 -0.157674,0 c -0.200927,0 -0.208018,-0.09496 -0.01395,-0.1868667 0.109989,-0.05209 0.161069,-0.1200886 0.217645,-0.2897326 0.05461,-0.1637579 0.09945,-0.2254289 0.171627,-0.2360637 0.09026,-0.0133 0.0977,0.0047 0.0977,0.2365099 l 0,0.2509055 0.270297,0 c 0.258284,0 0.270297,0.00501 0.270297,0.1126236 0,0.107618 -0.01201,0.112624 -0.270297,0.112624 l -0.270297,0 0,0.758859 c 0,0.868582 0.0153,0.907973 0.352681,0.907973 0.155976,0 0.187913,0.01486 0.187913,0.08744 0,0.04809 -0.01536,0.09693 -0.03413,0.108534 -0.08298,0.05129 -0.361449,0.06523 -0.496917,0.02488 z"
+ id="path3233"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/mirror.svg b/icons/mirror.svg
new file mode 100644
index 0000000..4a90bf0
--- /dev/null
+++ b/icons/mirror.svg
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3043"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="mirror.svg">
+ <defs
+ id="defs3045" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="7.7888027"
+ inkscape:cy="7.1205451"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ showguides="true"
+ inkscape:guide-bbox="true" />
+ <metadata
+ id="metadata3048">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.9821782"
+ y="5.5485148"
+ id="text3297"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3299" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="13.740099"
+ y="14.423267"
+ id="text3305"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3307" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.67574257"
+ y="14.918812"
+ id="text3309"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3311" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="6.3519802"
+ y="14.243069"
+ id="text3313"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3315" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:14.17995739px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.5153806"
+ y="13.443896"
+ id="text3336"
+ sodipodi:linespacing="125%"
+ transform="scale(1.0249348,0.97567183)"><tspan
+ sodipodi:role="line"
+ id="tspan3338"
+ x="1.5153806"
+ y="13.443896">M</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.8782956px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 7.7860733,3.264443 C 7.8911545,13.872024 7.8911545,13.842227 7.8911545,13.842227"
+ id="path3340"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#f5f5f5"
+ d="m 7.491476,13.630417 c 0,-0.342406 -0.043788,-0.507299 -0.1347148,-0.507299 -0.063046,0 -0.3862303,-0.847068 -1.6499381,-4.3244936 C 4.7046661,6.0409247 4.1137112,4.4740365 4.0757344,4.4738717 c -0.045223,-1.963e-4 -0.053116,0.056656 -0.032802,0.2362515 0.082817,0.7321783 0.1140548,2.1216682 0.1144442,5.0905941 l 4.358e-4,3.3224007 -0.5856436,0 -0.5856436,0 0,-4.9329205 0,-4.9329209 0.957302,9.29e-5 0.957302,9.29e-5 1.2353374,3.4124071 c 0.6794355,1.8768241 1.2516916,3.4428155 1.2716802,3.4799815 0.034077,0.06336 0.02195,-2.1392938 -0.029824,-5.4172031 l -0.022592,-1.4303218 0.4282686,0 0.4282686,0 0.0065,1.3402228 c 0.00358,0.7371226 0.018779,2.3132921 0.033787,3.5025991 l 0.027287,2.162376 1.2839109,-3.524715 1.2839108,-3.5247152 0.957302,-4.086e-4 0.957302,-4.086e-4 0,4.9329209 0,4.9329205 -0.63457,0 -0.63457,0 0.02519,-4.0657176 c 0.01385,-2.2361448 0.03788,-4.1846438 0.0534,-4.3299977 0.02248,-0.2105865 0.01586,-0.2602314 -0.03257,-0.2443529 -0.03365,0.011033 -0.767967,1.9386419 -1.6451989,4.3187352 -1.560773,4.234666 -1.5844677,4.304018 -1.5879951,4.647942 l -0.00358,0.349134 -0.1964353,0 c -0.108039,0 -0.2904895,0.01289 -0.4054455,0.02865 l -0.2090103,0.02865 0,-0.22574 z"
+ id="path3552"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/offset.svg b/icons/offset.svg
new file mode 100644
index 0000000..87f89ed
--- /dev/null
+++ b/icons/offset.svg
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg2985"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="offset.svg">
+ <defs
+ id="defs2987" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="5.4681565"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata2990">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="4.1896038"
+ y="14.243069"
+ id="text3011"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3013"
+ x="4.1896038"
+ y="14.243069" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:15.23624706px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="4.0866394"
+ y="11.582109"
+ id="text3015"
+ sodipodi:linespacing="125%"
+ transform="scale(0.97870254,1.0217609)"><tspan
+ sodipodi:role="line"
+ id="tspan3017"
+ x="4.0866394"
+ y="11.582109">o</tspan></text>
+ <path
+ style="fill:#f5f5f5"
+ d="M 7.8170503,11.934786 C 6.616538,11.74889 5.6744154,10.978525 5.2262043,9.8162749 4.93377,9.0579674 4.8894445,8.7748889 4.8882043,7.6576851 4.887016,6.5871402 4.9097066,6.4009086 5.1214808,5.7430811 5.5195175,4.5066731 6.2992968,3.7420527 7.4414155,3.4682461 7.9460934,3.3472567 8.6890871,3.3459072 9.1552563,3.4651332 10.77394,3.8791227 11.757129,5.4575136 11.757129,7.6421237 c 0,2.2553998 -0.928829,3.7917423 -2.5417799,4.2042653 -0.4022664,0.102883 -1.0431194,0.143396 -1.3982988,0.0884 l 0,0 z M 8.8379559,10.805503 C 9.8058806,10.59995 10.27471,9.8418923 10.410845,8.2622701 10.500866,7.2177429 10.359527,6.1312969 10.048967,5.4805781 9.6892064,4.7267666 8.8876304,4.3534742 7.9288084,4.4932239 6.7756252,4.661302 6.2581551,5.5006428 6.18616,7.3198138 c -0.069034,1.744342 0.3205647,2.9224892 1.0943519,3.3093242 0.4291195,0.214528 1.0478242,0.28459 1.557444,0.176365 l 0,0 z"
+ id="path3032"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/original.svg b/icons/original.svg
new file mode 100644
index 0000000..78c5be8
--- /dev/null
+++ b/icons/original.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3055"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="original.svg">
+ <defs
+ id="defs3057" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="8"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3060">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:11.73679543000000081px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="4.3141747"
+ y="11.85775"
+ id="text3402"
+ sodipodi:linespacing="125%"
+ transform="scale(0.95574243,1.046307)"><tspan
+ sodipodi:role="line"
+ id="tspan3404"
+ x="4.3141747"
+ y="11.85775">U</tspan></text>
+ </g>
+</svg>
diff --git a/icons/paste.svg b/icons/paste.svg
new file mode 100644
index 0000000..ce04736
--- /dev/null
+++ b/icons/paste.svg
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3634"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="New document 18">
+ <defs
+ id="defs3636" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="12.463147"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3639">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#000000"
+ id="rect3644"
+ width="7.2529702"
+ height="8.0188122"
+ x="5.1806931"
+ y="3.6564355" />
+ <path
+ style="fill:#ffffff"
+ d="m 5.2249505,7.6711386 0,-3.9868812 3.6039604,0 3.6039601,0 0,3.9868812 0,3.9868814 -3.6039601,0 -3.6039604,0 0,-3.9868814 z"
+ id="path3648"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 7.3881189,4.1970296 9.9559407,4.1519801 9.2801981,3.791584 9.2351486,3.2059405 l -1.3965347,0 0.090099,0.6306931 z"
+ id="path3652"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 6.570498,4.1729953 7.3979763,3.6182969 7.3711162,3.3613141 C 7.3563433,3.2199736 7.3310839,3.0219558 7.3149843,2.9212746 l -0.029272,-0.1830568 1.1955256,0 1.1955255,0 0.031863,0.3825135 0.031863,0.3825135 0.9970572,0.529739 0.997057,0.5297389 -0.341896,0.027806 C 11.204666,4.6058218 9.9883294,4.631801 8.6897382,4.6482601 7.391147,4.6647193 6.1968935,4.6893251 6.0358416,4.7029398 L 5.7430198,4.7276938 6.570498,4.1729953 z"
+ id="path3656"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="-11.532673"
+ y="10.458911"
+ id="text3658"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3660"
+ x="-11.532673"
+ y="10.458911" /></text>
+ <rect
+ style="fill:#000000"
+ id="rect3662"
+ width="5.6311884"
+ height="6.8024755"
+ x="8.0188122"
+ y="5.5034652" />
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="-15.046535"
+ y="8.3415842"
+ id="text3664"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3666"
+ x="-15.046535"
+ y="8.3415842" /></text>
+ <path
+ style="fill:#ffffff"
+ d="m 8.0630693,8.8884656 0,-3.3787129 2.7930697,0 2.793069,0 0,3.3787129 0,3.3787124 -2.793069,0 -2.7930697,0 0,-3.3787124 z"
+ id="path3668"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-size:1px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="-18.200001"
+ y="6.9000001"
+ id="text3672"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3674"
+ x="-18.200001"
+ y="6.9000001">a</tspan></text>
+ <g
+ transform="matrix(0.07805137,0,0,0.0811663,8.8727494,6.9573355)"
+ id="g3534">
+ <g
+ transform="scale(0.92,0.92)"
+ id="g3536">
+ <path
+ style="fill:#ffffff;stroke:#010101;stroke-width:3"
+ inkscape:connector-curvature="0"
+ id="Left"
+ d="M 12.99,13.58 C 6.25,14.6 1.54,5.1 11.04,1.5 21.21,1.49 20.24,12.49 12.99,13.58 z M 17,50.37 c -0.21,10.84 -9.79,10.84 -10,0 0,0 0,-24.36 0,-24.36 0.01,-3.75 -0.58,-10.27 5.04,-10.27 4.52,0 14.97,10.11 16.12,14.17 1.08,3.83 -2.27,6.92 -6.16,6.18 -1.59,-0.3 -3.58,-1.51 -5,-2.28 0,0 0,16.56 0,16.56 z" />
+ <path
+ style="fill:#010101;stroke:#ffffff;stroke-width:3"
+ inkscape:connector-curvature="0"
+ id="Right"
+ d="M 47.98,13.8 C 41.36,14.71 35.84,4.88 46.01,1.5 c 10.41,0.26 8.5,11.4 1.97,12.3 z M 30.84,29.99 c 1.14,-4.06 11.61,-14.18 16.12,-14.18 5.28,0 5.02,5.68 5.04,9.31 0,0 0,24.36 0,24.36 -0.08,10.9 -6.33,10.31 -9.15,6.59 C 40.77,53.32 41,48.85 41,45.58 c 0,0 0,-11.69 0,-11.69 -8.15,5.58 -11.2,-0.18 -10.16,-3.9 z" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/icons/right_bottom.svg b/icons/right_bottom.svg
new file mode 100644
index 0000000..1131ac9
--- /dev/null
+++ b/icons/right_bottom.svg
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3017"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="right_bottom.svg">
+ <defs
+ id="defs3019" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.328398"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3022">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.4415842"
+ y="15.279208"
+ id="text3025"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3027"
+ x="1.4415842"
+ y="15.279208"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.3336635"
+ y="8.7019796"
+ id="text3029"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3031" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.1985149"
+ y="11.314851"
+ id="text3033"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3035" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.8920792"
+ y="14.062871"
+ id="text3056"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3058"
+ x="1.8920792"
+ y="14.062871"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans"> </tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122%;letter-spacing:0px;word-spacing:0px;writing-mode:lr;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.2163366"
+ y="3.791584"
+ id="text3117"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3119"
+ x="1.2163366"
+ y="3.791584" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="8.9198017"
+ y="4.5123763"
+ id="text3154"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="4.5123763"
+ id="tspan3158" /><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="14.272377"
+ id="tspan3160" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="2.0722773"
+ y="14.738614"
+ id="text3162"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3164"
+ x="2.0722773"
+ y="14.738614" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0361387"
+ y="15.144059"
+ id="text3166"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3168"
+ x="1.0361387"
+ y="15.144059" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="4.1069307"
+ id="text3191"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3193" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="10.278713"
+ id="text3216"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="1.0811881"
+ y="10.278713"
+ id="tspan3220" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="15.459406"
+ id="text3224"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3226"
+ x="1.0811881"
+ y="15.459406" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.4415842"
+ y="6.8099008"
+ id="text3228"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3230"
+ x="1.4415842"
+ y="6.8099008" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.27029702"
+ y="6.8099008"
+ id="text3232"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3234"
+ x="0.27029702"
+ y="6.8099008">text edit</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="9.7757425"
+ y="15.234158"
+ id="text3255"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3257"
+ x="9.7757425"
+ y="15.234158">R-B</tspan></text>
+ <path
+ style="fill:#ffffff"
+ d="M 0.9998435,6.8024538 C 0.91017723,6.775747 0.81690142,6.7012352 0.76350199,6.6136564 0.6849364,6.4848036 0.6752688,6.3839765 0.67511883,5.6918743 l -1.684e-4,-0.777104 -0.15767327,0 c -0.20450798,0 -0.20678618,-0.095041 -0.004589,-0.1914616 0.12121407,-0.057803 0.1688609,-0.1205011 0.228868,-0.3011655 0.0560095,-0.1686289 0.0983495,-0.228165 0.16226278,-0.228165 0.0747894,0 0.0864785,0.033491 0.0864785,0.2477723 l 0,0.2477723 0.27029703,0 c 0.2582838,0 0.270297,0.00501 0.270297,0.1126237 0,0.1076183 -0.012013,0.1126238 -0.270297,0.1126238 l -0.27029703,0 0,0.7588595 c 0,0.8685819 0.0153003,0.9079722 0.35268093,0.9079722 0.1559756,0 0.1879131,0.014861 0.1879131,0.08744 0,0.048092 -0.015359,0.096933 -0.03413,0.108534 -0.082981,0.051285 -0.3614494,0.065226 -0.4969173,0.024878 z"
+ id="path3336"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 2.5531488,6.7885855 C 2.2891192,6.7163791 2.0544255,6.5002315 1.9537124,6.236518 1.8388849,5.9358466 1.8826174,5.3522224 2.0401126,5.0834771 2.3240392,4.598993 2.9171221,4.5202209 3.2947898,4.9168334 c 0.1610986,0.1691798 0.263329,0.4578123 0.263329,0.7434711 l 0,0.1104064 -0.6820176,0 -0.6820176,0 0.026866,0.2268396 c 0.053704,0.4534426 0.3428047,0.6417658 0.8416248,0.5482426 0.1362748,-0.02555 0.2832488,-0.060523 0.3266089,-0.077717 0.064326,-0.025508 0.078837,-0.00681 0.078837,0.1015753 0,0.1148629 -0.022859,0.1393871 -0.1689357,0.1812414 -0.302798,0.086759 -0.5253919,0.098006 -0.7459353,0.037693 l 0,0 z M 3.1977227,5.4080777 c 0,-0.1698534 -0.1072985,-0.3851335 -0.2227522,-0.4469224 -0.1459494,-0.07811 -0.4301392,-0.054802 -0.5579871,0.045763 -0.1038301,0.081673 -0.2093924,0.323967 -0.2100986,0.4822331 -1.858e-4,0.041652 0.1287567,0.056312 0.4952934,0.056312 l 0.4955445,0 0,-0.1373857 z"
+ id="path3338"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 4.2369013,6.2617179 4.6021163,5.7165864 4.2600996,5.2030546 3.9180829,4.6895228 l 0.1712048,0 c 0.1673451,0 0.1767445,0.00914 0.4169239,0.4052906 C 4.6413571,5.3177232 4.7620668,5.49969 4.7744554,5.4991839 4.786844,5.4986779 4.9042885,5.3162972 5.0354432,5.0938934 5.2625701,4.7087458 5.2813088,4.6895228 5.4296264,4.6895228 c 0.085646,0 0.1557201,0.010812 0.1557201,0.024027 0,0.013214 -0.1434506,0.2378362 -0.3187793,0.4991592 -0.1753285,0.261323 -0.3079789,0.5032778 -0.2947785,0.5376775 0.0132,0.0344 0.1667872,0.2753646 0.341304,0.5354777 0.1745168,0.2601131 0.3173033,0.4837448 0.3173033,0.4969594 0,0.013215 -0.070111,0.024026 -0.1558024,0.024026 -0.1484334,0 -0.1681427,-0.020223 -0.416708,-0.4275671 C 4.9143876,6.1441205 4.7845492,5.9515339 4.7693558,5.9513121 4.7541624,5.9510903 4.6252122,6.1434956 4.4827998,6.3788792 4.2266091,6.8023195 4.2220044,6.8068495 4.0477773,6.8068495 l -0.176091,0 0.365215,-0.5451316 z"
+ id="path3340"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 9.1169877,6.8025921 C 8.6876913,6.7015798 8.4234654,6.2989609 8.4234654,5.745825 c 0,-0.3581282 0.097634,-0.6309877 0.2999517,-0.8382781 0.5297235,-0.542744 1.3654209,-0.1182216 1.3666289,0.6942284 l 2.51e-4,0.1689356 -0.6757425,0 -0.6757426,0 0,0.1481277 c 0,0.08147 0.045613,0.2385805 0.1013614,0.3491337 0.1341402,0.2660084 0.3397027,0.3440888 0.7320544,0.278062 0.1486634,-0.025018 0.3057736,-0.059827 0.3491337,-0.077354 0.064463,-0.026057 0.078837,-0.00765 0.078837,0.1009709 0,0.1145403 -0.023269,0.1397547 -0.1689356,0.1830565 -0.2679836,0.079663 -0.5145374,0.096882 -0.7142747,0.049884 l 0,0 z M 9.729901,5.4080777 C 9.729901,5.2382243 9.6226025,5.0229442 9.5071488,4.9611553 9.2579802,4.8278043 8.9205372,4.9579619 8.8119701,5.2292978 8.6842446,5.5485152 8.6801676,5.5454634 9.2343564,5.5454634 l 0.4955446,0 0,-0.1373857 z"
+ id="path3342"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 11.177017,6.806756 C 10.954583,6.7542393 10.805634,6.625006 10.679377,6.3749855 10.584704,6.1875088 10.56775,6.0915802 10.568603,5.7481862 c 0.0015,-0.6218861 0.191126,-0.9477891 0.618246,-1.0628004 0.225892,-0.060827 0.456978,-0.00187 0.633338,0.16157 0.06698,0.062075 0.130874,0.112864 0.141986,0.112864 0.01111,0 0.0202,-0.2635396 0.0202,-0.5856436 l 0,-0.5856435 0.157674,0 0.157673,0 0,1.5091584 0,1.5091584 -0.135149,0 c -0.109916,0 -0.135277,-0.018924 -0.135838,-0.1013614 -0.0014,-0.1988149 -0.06381,-0.2231121 -0.2022,-0.078663 -0.106728,0.1114004 -0.379271,0.2383256 -0.472853,0.2202111 -0.01239,-0.0024 -0.09099,-0.020524 -0.174666,-0.04028 z m 0.530954,-0.2906038 c 0.18785,-0.097141 0.25201,-0.299197 0.251007,-0.7904908 -0.0012,-0.6116031 -0.138456,-0.8108911 -0.55829,-0.8108911 -0.347892,0 -0.508317,0.2592534 -0.511932,0.8273036 -0.0025,0.3872383 0.06932,0.6266705 0.224491,0.7487247 0.137527,0.1081785 0.411936,0.1198768 0.594724,0.025354 z"
+ id="path3344"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 13.018515,5.7481862 0,-1.0586634 0.157673,0 0.157673,0 0,1.0586634 0,1.0586633 -0.157673,0 -0.157673,0 0,-1.0586633 z"
+ id="path3346"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 13.01996,4.1966781 c -0.03401,-0.063551 -0.03536,-0.1195254 -0.0043,-0.1775979 0.107644,-0.2011345 0.336943,-0.1203114 0.310786,0.1095453 -0.01206,0.1060051 -0.0396,0.1357548 -0.135983,0.1468893 -0.08514,0.00984 -0.135534,-0.013462 -0.170521,-0.078837 z"
+ id="path3348"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 14.424596,6.8024538 C 14.33493,6.775747 14.241654,6.7012352 14.188255,6.6136564 14.109689,6.4848036 14.100021,6.3839765 14.099871,5.6918743 l -1.68e-4,-0.777104 -0.157673,0 c -0.200928,0 -0.208019,-0.094959 -0.01395,-0.1868666 0.10999,-0.05209 0.16107,-0.1200886 0.217646,-0.2897326 0.05461,-0.163758 0.09945,-0.2254289 0.171627,-0.2360637 0.09026,-0.0133 0.0977,0.0047 0.0977,0.2365099 l 0,0.2509055 0.270297,0 c 0.258283,0 0.270297,0.00501 0.270297,0.1126237 0,0.1076183 -0.01201,0.1126238 -0.270297,0.1126238 l -0.270297,0 0,0.7588595 c 0,0.8685819 0.0153,0.9079722 0.35268,0.9079722 0.155976,0 0.187914,0.014861 0.187914,0.08744 0,0.048092 -0.01536,0.096933 -0.03413,0.108534 -0.08298,0.051285 -0.361449,0.065226 -0.496917,0.024878 z"
+ id="path3350"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 10.180396,13.812048 0,-1.41906 0.349134,2.61e-4 c 0.896308,6.7e-4 1.257204,0.19691 1.306548,0.710446 0.03035,0.315873 -0.102239,0.599892 -0.344703,0.738379 -0.101226,0.05782 -0.184204,0.120137 -0.184394,0.138488 -1.91e-4,0.01835 0.161831,0.297285 0.360049,0.619852 0.198218,0.322567 0.360396,0.596433 0.360396,0.608589 0,0.01216 -0.07722,0.0221 -0.171609,0.0221 l -0.171608,0 -0.335199,-0.584163 -0.335198,-0.584163 -0.259035,-0.0015 -0.259034,-0.0015 0,0.585643 0,0.585644 -0.157674,0 -0.157673,0 0,-1.419059 z m 0.957595,-0.07002 c 0.317186,-0.08809 0.457155,-0.374586 0.35294,-0.722426 -0.06852,-0.228699 -0.243269,-0.319389 -0.668579,-0.346977 l -0.326609,-0.02118 0,0.569043 0,0.569043 0.236509,-2.52e-4 c 0.130081,-1.4e-4 0.312663,-0.0214 0.405739,-0.04725 l 0,1e-6 z"
+ id="path3352"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 12.297723,14.172444 0,-0.157674 0.47302,0 0.473019,0 0,0.157674 0,0.157673 -0.473019,0 -0.47302,0 0,-0.157673 z"
+ id="path3354"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 13.829406,13.81716 0,-1.424172 0.394183,3.4e-4 c 0.504972,4.36e-4 0.949176,0.07091 1.092451,0.173331 0.177108,0.126601 0.278311,0.359024 0.255183,0.586055 -0.02614,0.256601 -0.150315,0.416536 -0.394975,0.508718 l -0.19808,0.07463 0.135149,0.04854 c 0.402858,0.144687 0.541576,0.312906 0.538376,0.652871 -0.0028,0.300273 -0.142773,0.536311 -0.396505,0.668755 -0.158242,0.0826 -0.281956,0.100449 -0.806351,0.116336 l -0.619431,0.01877 0,-1.424172 z m 1.189371,1.080834 c 0.205986,-0.08607 0.297263,-0.232772 0.297263,-0.477778 0,-0.391932 -0.222596,-0.540594 -0.809444,-0.540594 l -0.361843,0 0,0.540594 0,0.540594 0.361843,0 c 0.207542,0 0.425951,-0.02679 0.512181,-0.06282 z m 0.02747,-1.356506 c 0.161495,-0.08351 0.257324,-0.352482 0.193645,-0.543518 -0.07596,-0.227892 -0.238346,-0.304742 -0.689489,-0.326311 l -0.405653,-0.01939 0,0.478531 0,0.47853 0.385157,0 c 0.254915,0 0.429518,-0.02294 0.51634,-0.06784 z"
+ id="path3356"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 6.4998363,4.4192258 c 0,-0.1362748 0.00845,-0.1920236 0.018767,-0.1238862 0.010322,0.068137 0.010322,0.1796349 0,0.2477723 -0.010322,0.068137 -0.018767,0.012389 -0.018767,-0.1238861 z"
+ id="path3358"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d=""
+ id="path3360"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 6.4863366,6.7944955 C 6.2143608,6.6992443 6.1711413,6.5450045 6.1710601,5.6693495 l -7e-5,-0.7545792 -0.1576733,0 c -0.204508,0 -0.2067862,-0.095041 -0.00459,-0.1914616 0.1212141,-0.057803 0.1688609,-0.1205011 0.228868,-0.3011655 0.05601,-0.1686289 0.09835,-0.228165 0.1622628,-0.228165 0.074789,0 0.086478,0.033491 0.086478,0.2477723 l 0,0.2477723 0.2702971,0 c 0.2582838,0 0.270297,0.00501 0.270297,0.1126237 0,0.1076183 -0.012013,0.1126238 -0.270297,0.1126238 l -0.2702971,0 0,0.7332931 c 0,0.8719524 0.022458,0.9335386 0.3404258,0.9335386 0.1681065,0 0.2001683,0.014006 0.2001683,0.08744 0,0.048092 -0.015359,0.096933 -0.03413,0.108534 -0.092212,0.05699 -0.3659374,0.066134 -0.5064639,0.016919 z"
+ id="path3362"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/right_top.svg b/icons/right_top.svg
new file mode 100644
index 0000000..bba9810
--- /dev/null
+++ b/icons/right_top.svg
@@ -0,0 +1,314 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3017"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="right_top.svg">
+ <defs
+ id="defs3019" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.328398"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3022">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.4415842"
+ y="15.279208"
+ id="text3025"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3027"
+ x="1.4415842"
+ y="15.279208"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.3336635"
+ y="8.7019796"
+ id="text3029"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3031" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.1985149"
+ y="11.314851"
+ id="text3033"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3035" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.8920792"
+ y="14.062871"
+ id="text3056"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3058"
+ x="1.8920792"
+ y="14.062871"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans"> </tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122%;letter-spacing:0px;word-spacing:0px;writing-mode:lr;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.2163366"
+ y="3.791584"
+ id="text3117"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3119"
+ x="1.2163366"
+ y="3.791584" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="8.9198017"
+ y="4.5123763"
+ id="text3154"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="4.5123763"
+ id="tspan3158" /><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="14.272377"
+ id="tspan3160" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="2.0722773"
+ y="14.738614"
+ id="text3162"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3164"
+ x="2.0722773"
+ y="14.738614" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0361387"
+ y="15.144059"
+ id="text3166"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3168"
+ x="1.0361387"
+ y="15.144059" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="4.1069307"
+ id="text3191"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3193" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="10.278713"
+ id="text3216"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="1.0811881"
+ y="10.278713"
+ id="tspan3220" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="15.459406"
+ id="text3224"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3226"
+ x="1.0811881"
+ y="15.459406" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.4415842"
+ y="6.8099008"
+ id="text3228"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3230"
+ x="1.4415842"
+ y="6.8099008" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.27029702"
+ y="6.8099008"
+ id="text3232"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3234"
+ x="0.27029702"
+ y="6.8099008" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="9.7757425"
+ y="15.234158"
+ id="text3255"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3257"
+ x="9.7757425"
+ y="15.234158" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="4.3321781"
+ id="text3278"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3280"
+ x="0.58564359"
+ y="4.3321781" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="12.035644"
+ id="text3282"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3284"
+ x="0.58564359"
+ y="12.035644">text edit</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="11.127228"
+ y="3.9717822"
+ id="text3305"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3307"
+ x="11.127228"
+ y="3.9717822" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="9.7306929"
+ y="4.0618811"
+ id="text3309"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3311"
+ x="9.7306929"
+ y="4.0618811">R-T</tspan></text>
+ <path
+ style="fill:#ffffff"
+ d="M 1.31519,12.028196 C 1.2255238,12.00149 1.132248,11.926978 1.0788485,11.839399 1.0002829,11.710546 0.99061534,11.609719 0.99046537,10.917617 l -1.684e-4,-0.777104 -0.15767327,0 c -0.20450798,0 -0.20678618,-0.09504 -0.004589,-0.1914617 0.12121407,-0.057803 0.1688609,-0.1205011 0.22886799,-0.3011655 0.05601,-0.1686289 0.09835,-0.228165 0.1622628,-0.228165 0.074789,0 0.086478,0.033491 0.086478,0.2477723 l 0,0.2477723 0.270297,0 c 0.2582839,0 0.2702971,0.00501 0.2702971,0.1126236 0,0.107618 -0.012013,0.112624 -0.2702971,0.112624 l -0.270297,0 0,0.758859 c 0,0.868582 0.0153,0.907973 0.3526809,0.907973 0.1559756,0 0.1879132,0.01486 0.1879132,0.08744 0,0.04809 -0.015359,0.09693 -0.03413,0.108534 -0.082981,0.05129 -0.3614494,0.06523 -0.4969173,0.02488 z"
+ id="path3477"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 2.8684953,12.014328 C 2.6044657,11.942122 2.369772,11.725974 2.2690589,11.462261 2.1542315,11.161589 2.1979639,10.577965 2.3554591,10.30922 2.6393857,9.8247356 3.2324686,9.7459636 3.6101364,10.142576 c 0.1610986,0.16918 0.2633289,0.457812 0.2633289,0.743471 l 0,0.110407 -0.6820176,0 -0.6820175,0 0.026866,0.226839 c 0.053703,0.453443 0.3428046,0.641766 0.8416248,0.548243 0.1362747,-0.02555 0.2832487,-0.06052 0.3266089,-0.07772 0.064326,-0.02551 0.078837,-0.0068 0.078837,0.101575 0,0.114863 -0.022859,0.139387 -0.1689356,0.181242 -0.3027981,0.08676 -0.525392,0.09801 -0.7459354,0.03769 l 0,0 z m 0.644574,-1.380508 c 0,-0.169853 -0.1072985,-0.385133 -0.2227522,-0.446922 -0.1459495,-0.07811 -0.4301392,-0.0548 -0.5579872,0.04576 -0.1038301,0.08167 -0.2093924,0.323967 -0.2100985,0.482233 -1.858e-4,0.04165 0.1287567,0.05631 0.4952933,0.05631 l 0.4955446,0 0,-0.137386 z"
+ id="path3479"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 4.5522478,11.487461 4.9174629,10.942329 4.5754461,10.428797 4.2334294,9.9152654 l 0.1712048,0 c 0.1673451,0 0.1767446,0.00914 0.4169239,0.4052906 0.1351455,0.22291 0.2558552,0.404877 0.2682439,0.404371 0.012389,-5.06e-4 0.1298331,-0.182887 0.2609877,-0.405291 0.2271269,-0.3851475 0.2458657,-0.4043706 0.3941832,-0.4043706 0.085646,0 0.1557202,0.010812 0.1557202,0.024027 0,0.013215 -0.1434507,0.2378361 -0.3187793,0.4991591 -0.1753286,0.261323 -0.3079789,0.503278 -0.2947786,0.537678 0.0132,0.0344 0.1667872,0.275364 0.341304,0.535477 0.1745169,0.260113 0.3173034,0.483745 0.3173034,0.49696 0,0.01321 -0.070111,0.02403 -0.1558025,0.02403 -0.1484333,0 -0.1681426,-0.02022 -0.4167079,-0.427567 -0.143498,-0.235162 -0.2733365,-0.427749 -0.2885299,-0.42797 -0.015193,-2.22e-4 -0.1441435,0.192183 -0.2865559,0.427567 -0.2561908,0.42344 -0.2607955,0.42797 -0.4350226,0.42797 l -0.176091,0 0.365215,-0.545131 z"
+ id="path3481"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 6.8016832,12.020238 C 6.5297073,11.924987 6.4864879,11.770747 6.4864066,10.895092 l -7e-5,-0.754579 -0.1576732,0 c -0.204508,0 -0.2067862,-0.09504 -0.00459,-0.1914617 0.121214,-0.057803 0.1688609,-0.1205011 0.228868,-0.3011655 0.056009,-0.1686289 0.098349,-0.228165 0.1622627,-0.228165 0.07479,0 0.086479,0.033491 0.086479,0.2477723 l 0,0.2477723 0.270297,0 c 0.2582838,0 0.270297,0.00501 0.270297,0.1126236 0,0.107618 -0.012013,0.112624 -0.270297,0.112624 l -0.270297,0 0,0.733293 c 0,0.871952 0.022458,0.933539 0.3404257,0.933539 0.1681065,0 0.2001683,0.01401 0.2001683,0.08744 0,0.04809 -0.015359,0.09693 -0.03413,0.108534 -0.092212,0.05699 -0.3659374,0.06613 -0.5064638,0.01692 z"
+ id="path3483"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 9.4323342,12.028335 C 9.0030378,11.927322 8.7388119,11.524703 8.7388119,10.971568 c 0,-0.358129 0.097634,-0.630988 0.2999517,-0.838278 0.5297235,-0.5427444 1.3654204,-0.118222 1.3666284,0.694228 l 2.52e-4,0.168936 -0.675743,0 -0.6757426,0 0,0.148127 c 0,0.08147 0.045613,0.238581 0.1013614,0.349134 0.1341402,0.266008 0.3397027,0.344089 0.7320545,0.278062 0.1486637,-0.02502 0.3057737,-0.05983 0.3491337,-0.07735 0.06446,-0.02606 0.07884,-0.0076 0.07884,0.100971 0,0.11454 -0.02327,0.139755 -0.168936,0.183057 -0.2679836,0.07966 -0.5145375,0.09688 -0.7142748,0.04988 l 0,0 z M 10.045248,10.63382 c 0,-0.169853 -0.107299,-0.385133 -0.2227526,-0.446922 -0.2491687,-0.133351 -0.5866117,-0.0032 -0.6951788,0.268142 -0.1277254,0.319218 -0.1318025,0.316166 0.4223864,0.316166 l 0.495545,0 0,-0.137386 z"
+ id="path3485"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 11.492364,12.032499 c -0.222435,-0.05252 -0.371384,-0.18175 -0.497641,-0.431771 -0.09467,-0.187477 -0.111626,-0.283405 -0.110774,-0.626799 0.0015,-0.621886 0.191127,-0.947789 0.618246,-1.0628006 0.225892,-0.060827 0.456979,-0.00187 0.633338,0.1615696 0.06698,0.06208 0.130874,0.112864 0.141986,0.112864 0.01111,0 0.0202,-0.2635392 0.0202,-0.5856431 l 0,-0.5856436 0.157673,0 0.157673,0 0,1.5091587 0,1.509158 -0.135148,0 c -0.109917,0 -0.135277,-0.01892 -0.135839,-0.101361 -0.0014,-0.198815 -0.06381,-0.223112 -0.202199,-0.07866 -0.106728,0.1114 -0.379271,0.238326 -0.472853,0.220211 -0.01239,-0.0024 -0.09099,-0.02052 -0.174666,-0.04028 z m 0.530954,-0.290604 c 0.18785,-0.09714 0.252009,-0.299197 0.251006,-0.790491 -0.0012,-0.611603 -0.138456,-0.810891 -0.558289,-0.810891 -0.347893,0 -0.508318,0.259253 -0.511933,0.827304 -0.0025,0.387238 0.06932,0.62667 0.224492,0.748724 0.137527,0.108179 0.411936,0.119877 0.594724,0.02535 z"
+ id="path3487"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 13.333861,10.973929 0,-1.0586636 0.157674,0 0.157673,0 0,1.0586636 0,1.058663 -0.157673,0 -0.157674,0 0,-1.058663 z"
+ id="path3489"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 13.335306,9.4224208 c -0.03401,-0.063551 -0.03536,-0.1195255 -0.0043,-0.1775979 0.107644,-0.2011346 0.336943,-0.1203115 0.310785,0.1095452 -0.01206,0.1060051 -0.0396,0.1357548 -0.135983,0.1468893 -0.08514,0.00984 -0.135533,-0.013462 -0.170521,-0.078837 z"
+ id="path3491"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 14.739943,12.028196 c -0.08967,-0.02671 -0.182942,-0.101218 -0.236342,-0.188797 -0.07856,-0.128853 -0.08823,-0.22968 -0.08838,-0.921782 l -1.68e-4,-0.777104 -0.157674,0 c -0.200927,0 -0.208018,-0.09496 -0.01395,-0.1868667 0.109989,-0.05209 0.161069,-0.1200886 0.217645,-0.2897326 0.05461,-0.1637579 0.09945,-0.2254289 0.171627,-0.2360637 0.09026,-0.0133 0.0977,0.0047 0.0977,0.2365099 l 0,0.2509055 0.270297,0 c 0.258284,0 0.270297,0.00501 0.270297,0.1126236 0,0.107618 -0.01201,0.112624 -0.270297,0.112624 l -0.270297,0 0,0.758859 c 0,0.868582 0.0153,0.907973 0.352681,0.907973 0.155976,0 0.187913,0.01486 0.187913,0.08744 0,0.04809 -0.01536,0.09693 -0.03413,0.108534 -0.08298,0.05129 -0.361449,0.06523 -0.496917,0.02488 z"
+ id="path3493"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 14.054654,2.7749188 0,-1.2839109 -0.405446,0 -0.405446,0 0,-0.1351485 0,-0.1351485 0.968565,0 0.968564,0 0,0.1333946 0,0.1333946 -0.394183,0.013016 -0.394183,0.013016 -0.01196,1.2726485 -0.01196,1.2726485 -0.156972,0 -0.156971,0 0,-1.2839109 z"
+ id="path3495"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 12.252673,3.0001663 0,-0.1576732 0.47302,0 0.47302,0 0,0.1576732 0,0.1576733 -0.47302,0 -0.47302,0 0,-0.1576733 z"
+ id="path3497"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="m 10.135347,2.6397703 0,-1.4190594 0.349133,2.609e-4 c 0.896309,6.699e-4 1.257205,0.1969098 1.306549,0.7104456 0.03035,0.3158735 -0.102239,0.5998924 -0.344703,0.7383796 -0.101226,0.057817 -0.184204,0.1201364 -0.184395,0.1384877 -1.91e-4,0.018351 0.161831,0.2972846 0.360049,0.6198519 0.198218,0.3225673 0.360396,0.5964326 0.360396,0.6085896 0,0.012157 -0.07722,0.022104 -0.171608,0.022104 l -0.171609,0 -0.335198,-0.5841633 -0.335199,-0.5841632 -0.259034,-0.00148 -0.259035,-0.00148 0,0.5856435 0,0.5856436 -0.157673,0 -0.157673,0 0,-1.4190594 z m 0.957595,-0.070024 C 11.410128,2.4816602 11.550097,2.1951607 11.445881,1.8473208 11.377361,1.6186216 11.202613,1.5279312 10.777302,1.5003442 l -0.326609,-0.021185 0,0.5690432 0,0.5690431 0.23651,-2.522e-4 c 0.13008,-1.397e-4 0.312663,-0.0214 0.405739,-0.047248 l 0,1.1e-6 z"
+ id="path3499"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/rotate_anticlockwise.svg b/icons/rotate_anticlockwise.svg
new file mode 100644
index 0000000..55a9c61
--- /dev/null
+++ b/icons/rotate_anticlockwise.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ x="0px"
+ y="0px"
+ width="55px"
+ height="55px"
+ viewBox="0 0 55 55"
+ enable-background="new 0 0 55 55"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="rotate_anticlockwise.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
+ id="metadata14"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs12"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 27.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="55 : 27.5 : 1"
+ inkscape:persp3d-origin="27.5 : 18.333333 : 1"
+ id="perspective16" /></defs><sodipodi:namedview
+ inkscape:window-height="976"
+ inkscape:window-width="1680"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="8.3636364"
+ inkscape:cx="27.5"
+ inkscape:cy="27.5"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:current-layer="svg2" />
+
+<g
+ id="Undo"
+ transform="matrix(0,-1,1,0,0.975043,54.158)">
+ <g
+ display="inline"
+ id="g5"
+ style="display:inline">
+ <polyline
+ points=" 22.903,27.523 15.091,20.935 22.903,14.344 "
+ id="polyline7"
+ style="fill:none;stroke:#ffffff;stroke-width:2.98670006;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="M 15.091,20.935 L 32.345,20.935 C 37.038,20.935 40.879,24.776 40.879,29.469 C 40.879,34.162 40.983652,31.904174 40.83413,38.838957"
+ id="path9"
+ style="fill:none;stroke:#ffffff;stroke-width:2.98670006;stroke-linecap:round"
+ sodipodi:nodetypes="ccsc" />
+ </g>
+</g>
+</svg> \ No newline at end of file
diff --git a/icons/rotate_clockwise.svg b/icons/rotate_clockwise.svg
new file mode 100644
index 0000000..474f6f0
--- /dev/null
+++ b/icons/rotate_clockwise.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ x="0px"
+ y="0px"
+ width="55px"
+ height="55px"
+ viewBox="0 0 55 55"
+ enable-background="new 0 0 55 55"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="rotate_clockwise.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
+ id="metadata14"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs12"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 27.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="55 : 27.5 : 1"
+ inkscape:persp3d-origin="27.5 : 18.333333 : 1"
+ id="perspective16" /></defs><sodipodi:namedview
+ inkscape:window-height="976"
+ inkscape:window-width="1680"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="8.3636364"
+ inkscape:cx="27.5"
+ inkscape:cy="27.5"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:current-layer="svg2" />
+
+<g
+ id="Undo"
+ transform="matrix(0,-1,-1,0,54.158,54.158)">
+ <g
+ display="inline"
+ id="g5"
+ style="display:inline">
+ <polyline
+ points=" 22.903,27.523 15.091,20.935 22.903,14.344 "
+ id="polyline7"
+ style="fill:none;stroke:#ffffff;stroke-width:2.98670006;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="M 15.091,20.935 L 32.345,20.935 C 37.038,20.935 40.879,24.776 40.879,29.469 C 40.879,34.162 40.983652,31.904174 40.83413,38.838957"
+ id="path9"
+ style="fill:none;stroke:#ffffff;stroke-width:2.98670006;stroke-linecap:round"
+ sodipodi:nodetypes="ccsc" />
+ </g>
+</g>
+</svg> \ No newline at end of file
diff --git a/icons/sharpen.svg b/icons/sharpen.svg
new file mode 100644
index 0000000..c6b9524
--- /dev/null
+++ b/icons/sharpen.svg
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3017"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="sharpen.svg">
+ <defs
+ id="defs3019" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.328398"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3022">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.4415842"
+ y="15.279208"
+ id="text3025"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3027"
+ x="1.4415842"
+ y="15.279208"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.3336635"
+ y="8.7019796"
+ id="text3029"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3031" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.1985149"
+ y="11.314851"
+ id="text3033"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3035" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.8920792"
+ y="14.062871"
+ id="text3056"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3058"
+ x="1.8920792"
+ y="14.062871"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans"> </tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122%;letter-spacing:0px;word-spacing:0px;writing-mode:lr;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.2163366"
+ y="3.791584"
+ id="text3117"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3119"
+ x="1.2163366"
+ y="3.791584" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="8.9198017"
+ y="4.5123763"
+ id="text3154"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="4.5123763"
+ id="tspan3158" /><tspan
+ sodipodi:role="line"
+ x="8.9198017"
+ y="14.272377"
+ id="tspan3160" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="2.0722773"
+ y="14.738614"
+ id="text3162"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3164"
+ x="2.0722773"
+ y="14.738614" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0361387"
+ y="15.144059"
+ id="text3166"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3168"
+ x="1.0361387"
+ y="15.144059" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="4.1069307"
+ id="text3191"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3193" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="10.278713"
+ id="text3216"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ x="1.0811881"
+ y="10.278713"
+ id="tspan3220" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.0811881"
+ y="15.459406"
+ id="text3224"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3226"
+ x="1.0811881"
+ y="15.459406" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.4415842"
+ y="6.8099008"
+ id="text3228"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3230"
+ x="1.4415842"
+ y="6.8099008" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.27029702"
+ y="6.8099008"
+ id="text3232"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3234"
+ x="0.27029702"
+ y="6.8099008" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="9.7757425"
+ y="15.234158"
+ id="text3255"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3257"
+ x="9.7757425"
+ y="15.234158" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="4.3321781"
+ id="text3278"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3280"
+ x="0.58564359"
+ y="4.3321781" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="0.58564359"
+ y="12.035644"
+ id="text3282"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3284"
+ x="0.58564359"
+ y="12.035644" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="11.127228"
+ y="3.9717822"
+ id="text3305"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3307"
+ x="11.127228"
+ y="3.9717822" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="9.7306929"
+ y="4.0618811"
+ id="text3309"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3311"
+ x="9.7306929"
+ y="4.0618811" /></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.8239994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 11.002501,3.2080392 4.9124948,6.2985844 11.080081,9.1759885 5.0288643,12.799386"
+ id="path3332"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#ffffff"
+ d="M 5.0515223,12.788829 C 4.9460106,12.613439 4.8759831,12.455484 4.8959058,12.437817 4.9158284,12.420151 6.1249882,11.691116 7.5829277,10.81774 9.040867,9.9443642 10.216659,9.2151245 10.195799,9.1972069 10.174939,9.1792898 8.7945607,8.5296426 7.1282921,7.7535469 5.4620235,6.9774511 4.0987128,6.3276481 4.0987128,6.3095402 c 0,-0.023931 6.4309442,-3.3134065 6.6745142,-3.4140634 0.03891,-0.016078 0.383746,0.6540164 0.35104,0.6821429 -0.0112,0.00963 -1.200777,0.6134422 -2.6435132,1.3418086 -1.4427364,0.7283663 -2.6174723,1.3426753 -2.6105242,1.365131 0.00941,0.030426 3.5294761,1.6921331 5.9037704,2.7869793 0.127945,0.058999 0.08993,0.083372 -3.1759903,2.0364084 -1.8180167,1.087183 -3.3165467,1.981888 -3.3300665,1.988234 -0.01352,0.0063 -0.1109092,-0.131962 -0.2164209,-0.307352 l 0,0 z"
+ id="path3538"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#f5f5f5"
+ d="m 5.1900049,12.937614 c -0.04856,-0.06568 -0.12958,-0.200353 -0.1800445,-0.299272 L 4.9182066,12.45849 7.0409741,11.175615 C 9.2857494,9.8190063 10.226455,9.2337313 10.223877,9.1953207 10.223015,9.1824658 9.3158303,8.7474872 8.2079119,8.2287007 5.3291274,6.8807003 4.1901288,6.3370951 4.1904549,6.311305 4.1907985,6.2841324 5.0537343,5.8368084 8.2863131,4.1881158 9.9109802,3.3594968 10.756581,2.9510973 10.790059,2.978882 c 0.08901,0.073869 0.30051,0.5482253 0.261434,0.5863396 C 11.030768,3.585436 9.9999205,4.1186275 8.7607204,4.7500914 6.3041893,6.001875 5.8708344,6.2352285 5.892471,6.2945896 5.9088771,6.3396005 7.287877,7.0047805 9.7270805,8.1442675 10.657778,8.579048 11.498892,8.9751917 11.596223,9.0245876 l 0.176965,0.089811 -0.109391,0.085416 C 11.53377,9.3013447 9.0793965,10.790151 6.8621429,12.112466 l -1.5838472,0.944568 -0.088291,-0.11942 0,0 z"
+ id="path3720"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/solarize.svg b/icons/solarize.svg
new file mode 100644
index 0000000..fcd3abe
--- /dev/null
+++ b/icons/solarize.svg
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg3017"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="solarize.svg">
+ <defs
+ id="defs3019" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.328398"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3022">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.4415842"
+ y="15.279208"
+ id="text3025"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3027"
+ x="1.4415842"
+ y="15.279208"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.3336635"
+ y="8.7019796"
+ id="text3029"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3031" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="3.1985149"
+ y="11.314851"
+ id="text3033"
+ sodipodi:linespacing="122%"><tspan
+ sodipodi:role="line"
+ id="tspan3035" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:30.08130264px;font-style:normal;font-weight:normal;line-height:122.00000286%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="4.5824504"
+ y="13.687631"
+ id="text3056"
+ sodipodi:linespacing="122%"
+ transform="scale(1.0539496,0.94881196)"><tspan
+ sodipodi:role="line"
+ id="tspan3058"
+ x="4.5824504"
+ y="13.687631"
+ style="font-size:13.53658676px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:122.00000286%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans">S</tspan></text>
+ <path
+ style="fill:#ffffff"
+ d="M 7.4999505,13.064482 C 6.9258785,13.028615 6.1748071,12.893436 5.8215159,12.762394 l -0.2136446,-0.07925 -0.012692,-0.528001 c -0.00698,-0.290401 -0.00546,-0.528001 0.00337,-0.528001 0.00884,0 0.1631654,0.05003 0.3429562,0.111183 0.1797907,0.06115 0.5634467,0.166567 0.8525688,0.234259 0.4638097,0.108591 0.6184589,0.123341 1.3140427,0.125327 0.6430186,0.0018 0.8379499,-0.01305 1.0573076,-0.08072 C 9.9677676,11.769649 10.271086,11.426141 10.305411,10.726157 10.351238,9.7915928 9.9739995,9.4457212 8.1531683,8.7528683 7.1087414,8.3554488 6.7434343,8.1450961 6.3219796,7.6984261 5.877459,7.22731 5.6971974,6.6861798 5.7323423,5.9283842 5.7455458,5.6436887 5.7831181,5.4032794 5.8344303,5.2751664 6.2833773,4.1542629 7.5778363,3.5820961 9.3133958,3.7374209 c 0.3157982,0.028262 0.7258332,0.083542 0.9111882,0.1228437 0.359693,0.076267 0.976627,0.2637692 1.072353,0.3259161 0.04442,0.02884 0.0087,0.1443807 -0.153824,0.4969893 -0.117248,0.2544405 -0.233518,0.4500062 -0.2606,0.4383279 C 10.716379,5.0498574 10.054547,4.8620103 9.729901,4.7943531 9.2104709,4.6861025 8.272587,4.6847335 7.922008,4.7917143 7.5754612,4.8974645 7.2563861,5.127574 7.0945049,5.38849 6.9665394,5.5947416 6.9593684,5.6316379 6.9595806,6.0826981 c 1.892e-4,0.4016614 0.015492,0.5049599 0.097564,0.6586021 0.2268515,0.424671 0.6841002,0.7099947 1.8168154,1.1336945 1.2079186,0.4518301 1.9234206,0.8540271 2.2267296,1.2516859 0.59917,0.7855521 0.600482,1.9996624 0.003,2.8112324 -0.632423,0.859086 -1.8404812,1.236735 -3.6037769,1.126569 l 1.4e-6,0 z"
+ id="path3597"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#f5f5f5"
+ d="M 7.2651854,13.022494 C 6.8554715,12.982874 5.9949121,12.806924 5.7542822,12.713575 l -0.1238862,-0.04806 0,-0.501479 c 0,-0.41176 0.011304,-0.497142 0.063183,-0.477234 0.241125,0.09253 1.2937767,0.354857 1.5655107,0.390138 0.4482986,0.0582 1.4027252,0.05671 1.7304833,-0.0027 0.3781161,-0.06855 0.8844873,-0.321344 1.0538462,-0.526113 0.180225,-0.217905 0.272189,-0.532993 0.271874,-0.931499 -3.15e-4,-0.401639 -0.05556,-0.60077 -0.226255,-0.8155618 C 9.8196439,9.4620692 9.3683244,9.2093433 8.2047314,8.7459014 7.1084139,8.3092546 6.710208,8.077644 6.3313781,7.6562941 5.8931816,7.1689141 5.741832,6.6960343 5.7740174,5.9148654 5.7924072,5.4685297 5.8090731,5.3888229 5.9375925,5.132546 6.2402425,4.52904 6.8447818,4.0842876 7.651158,3.8718961 8.152342,3.7398892 9.0274894,3.7127982 9.6848515,3.8089413 c 0.8924795,0.1305304 1.5767325,0.3347723 1.5767325,0.4706372 0,0.0687 -0.25715,0.6282467 -0.349051,0.759518 -0.03138,0.044823 -0.0912,0.039056 -0.284572,-0.027436 C 9.960238,4.7820651 9.5540953,4.716874 8.7838614,4.7156587 8.1159788,4.714605 8.0147273,4.7248012 7.7861458,4.8161304 7.4692447,4.9427474 7.1537943,5.2158239 7.0307038,5.470095 6.9557717,5.6248841 6.9369205,5.7497603 6.9372718,6.0890183 6.9381962,6.9815854 7.196787,7.2221711 8.896851,7.9121547 c 1.770157,0.7184313 2.235733,1.0646714 2.496612,1.8566827 0.135073,0.4100736 0.160583,0.8245586 0.07556,1.2276166 -0.219305,1.039583 -0.982164,1.70704 -2.2361042,1.956462 -0.432887,0.08611 -1.4316239,0.121421 -1.9677294,0.06958 l 0,0 z"
+ id="path3780"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/icons/watermark_scale.svg b/icons/watermark_scale.svg
new file mode 100644
index 0000000..292bba2
--- /dev/null
+++ b/icons/watermark_scale.svg
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg2989"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="watermark_scale.svg">
+ <defs
+ id="defs2991" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.084341"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="2.9282179"
+ y="5.2331681"
+ id="text2997"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan2999"
+ x="2.9282179"
+ y="5.2331681"
+ style="-inkscape-font-specification:Sans;font-family:Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:2px;text-anchor:start;text-align:start;writing-mode:lr;line-height:125%" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:2px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="4.1445546"
+ y="12.891584"
+ id="text3001"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3003"
+ x="4.1445546"
+ y="12.891584"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans;fill:#ffffff">scale</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__5_-1"
+ style="fill:#808080;stroke:#ffffff;stroke-width:0.80050951"
+ d="M 12.639948,7.1622548 C 12.410847,5.0139759 10.85801,2.6555368 8.1286968,1.4555305 6.6058038,0.78653668 4.6868558,1.1260071 3.9234608,3.1322235 c -0.824926,2.1661323 0.693045,4.7681435 2.808892,4.7464641 1.40824,-0.014283 2.818942,0.6608322 3.6621222,1.3173288 0.965012,0.7531602 2.443398,-0.1764941 2.245473,-2.0337616 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__4_"
+ style="fill:#404040"
+ d="M 11.257345,7.1839341 C 11.138386,7.6116509 10.763457,7.8389002 10.416216,7.6935222 10.072258,7.5466137 9.8893058,7.0793635 10.007446,6.6503711 c 0.117319,-0.4300125 0.492452,-0.6575166 0.838462,-0.5106089 0.344779,0.1469085 0.529372,0.6149239 0.411437,1.0441719 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__3_"
+ style="fill:#ffffff"
+ d="M 7.4971838,6.4825492 C 7.3782238,6.910266 7.0032948,7.1375146 6.6568748,6.9921373 6.3129158,6.8452288 6.1291428,6.3779786 6.2472828,5.9489862 c 0.11814,-0.4300133 0.493274,-0.6575171 0.838463,-0.5106086 0.344779,0.1469082 0.529373,0.6149236 0.411438,1.0441716 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__2_"
+ style="fill:#e5e5e5"
+ d="M 6.0274118,5.632215 C 5.9084528,6.0599326 5.5335238,6.2871811 5.1871038,6.141803 4.8431448,5.9948945 4.6593718,5.5276444 4.7775118,5.0986522 c 0.118139,-0.430013 0.493274,-0.6582818 0.838463,-0.5116285 0.344779,0.1479283 0.529167,0.6159434 0.411437,1.0451913 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__1_"
+ style="fill:#c0c0c0"
+ d="m 6.0444358,3.5920771 c -0.11896,0.4277174 -0.49389,0.6559865 -0.840309,0.5095881 -0.343959,-0.1469084 -0.527732,-0.6139034 -0.409592,-1.0426406 0.11814,-0.4287376 0.493274,-0.6575169 0.838463,-0.5106086 0.344779,0.1461431 0.529167,0.6141585 0.411438,1.0436611 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E_"
+ style="fill:#a6a6a6"
+ d="m 7.6680348,3.1243168 c -0.11896,0.4277174 -0.493889,0.6559865 -0.840309,0.5095881 -0.343958,-0.146908 -0.527731,-0.6141582 -0.409591,-1.0431507 0.118139,-0.4289927 0.493273,-0.6575167 0.838462,-0.5106084 0.344779,0.1469084 0.529168,0.6149234 0.411438,1.044171 z" />
+ </g>
+</svg>
diff --git a/icons/watermark_tile.svg b/icons/watermark_tile.svg
new file mode 100644
index 0000000..84a5547
--- /dev/null
+++ b/icons/watermark_tile.svg
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg2989"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="watermark_tile.svg">
+ <defs
+ id="defs2991" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="10.084341"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="2.9282179"
+ y="5.2331681"
+ id="text2997"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan2999"
+ x="2.9282179"
+ y="5.2331681"
+ style="-inkscape-font-specification:Sans;font-family:Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:2px;text-anchor:start;text-align:start;writing-mode:lr;line-height:125%" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:2px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="4.1445546"
+ y="12.891584"
+ id="text3001"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3003"
+ x="4.1445546"
+ y="12.891584"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans;fill:#ffffff"> tile</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__5_-1"
+ style="fill:#808080;stroke:#ffffff;stroke-width:0.80050951"
+ d="M 12.639948,7.1622548 C 12.410847,5.0139759 10.85801,2.6555368 8.1286968,1.4555305 6.6058038,0.78653668 4.6868558,1.1260071 3.9234608,3.1322235 c -0.824926,2.1661323 0.693045,4.7681435 2.808892,4.7464641 1.40824,-0.014283 2.818942,0.6608322 3.6621222,1.3173288 0.965012,0.7531602 2.443398,-0.1764941 2.245473,-2.0337616 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__4_"
+ style="fill:#404040"
+ d="M 11.257345,7.1839341 C 11.138386,7.6116509 10.763457,7.8389002 10.416216,7.6935222 10.072258,7.5466137 9.8893058,7.0793635 10.007446,6.6503711 c 0.117319,-0.4300125 0.492452,-0.6575166 0.838462,-0.5106089 0.344779,0.1469085 0.529372,0.6149239 0.411437,1.0441719 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__3_"
+ style="fill:#ffffff"
+ d="M 7.4971838,6.4825492 C 7.3782238,6.910266 7.0032948,7.1375146 6.6568748,6.9921373 6.3129158,6.8452288 6.1291428,6.3779786 6.2472828,5.9489862 c 0.11814,-0.4300133 0.493274,-0.6575171 0.838463,-0.5106086 0.344779,0.1469082 0.529373,0.6149236 0.411438,1.0441716 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__2_"
+ style="fill:#e5e5e5"
+ d="M 6.0274118,5.632215 C 5.9084528,6.0599326 5.5335238,6.2871811 5.1871038,6.141803 4.8431448,5.9948945 4.6593718,5.5276444 4.7775118,5.0986522 c 0.118139,-0.430013 0.493274,-0.6582818 0.838463,-0.5116285 0.344779,0.1479283 0.529167,0.6159434 0.411437,1.0451913 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__1_"
+ style="fill:#c0c0c0"
+ d="m 6.0444358,3.5920771 c -0.11896,0.4277174 -0.49389,0.6559865 -0.840309,0.5095881 -0.343959,-0.1469084 -0.527732,-0.6139034 -0.409592,-1.0426406 0.11814,-0.4287376 0.493274,-0.6575169 0.838463,-0.5106086 0.344779,0.1461431 0.529167,0.6141585 0.411438,1.0436611 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E_"
+ style="fill:#a6a6a6"
+ d="m 7.6680348,3.1243168 c -0.11896,0.4277174 -0.493889,0.6559865 -0.840309,0.5095881 -0.343958,-0.146908 -0.527731,-0.6141582 -0.409591,-1.0431507 0.118139,-0.4289927 0.493273,-0.6575167 0.838462,-0.5106084 0.344779,0.1469084 0.529168,0.6149234 0.411438,1.044171 z" />
+ </g>
+</svg>
diff --git a/icons/watermark_tl.svg b/icons/watermark_tl.svg
new file mode 100644
index 0000000..aacac3c
--- /dev/null
+++ b/icons/watermark_tl.svg
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16px"
+ height="16px"
+ id="svg2989"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="watermark_tl.svg">
+ <defs
+ id="defs2991" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.197802"
+ inkscape:cx="8"
+ inkscape:cy="6.4803806"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="701"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata2994">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="2.9282179"
+ y="5.2331681"
+ id="text2997"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan2999"
+ x="2.9282179"
+ y="5.2331681"
+ style="-inkscape-font-specification:Sans;font-family:Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:2px;text-anchor:start;text-align:start;writing-mode:lr;line-height:125%" /></text>
+ <text
+ xml:space="preserve"
+ style="font-size:2px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="1.5316833"
+ y="13.071782"
+ id="text3001"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3003"
+ x="1.5316833"
+ y="13.071782"
+ style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans;fill:#ffffff">top-left</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__5_-1"
+ style="fill:#808080;stroke:#ffffff;stroke-width:0.80050951"
+ d="M 12.639948,7.1622548 C 12.410847,5.0139759 10.85801,2.6555368 8.1286968,1.4555305 6.6058038,0.78653668 4.6868558,1.1260071 3.9234608,3.1322235 c -0.824926,2.1661323 0.693045,4.7681435 2.808892,4.7464641 1.40824,-0.014283 2.818942,0.6608322 3.6621222,1.3173288 0.965012,0.7531602 2.443398,-0.1764941 2.245473,-2.0337616 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__4_"
+ style="fill:#404040"
+ d="M 11.257345,7.1839341 C 11.138386,7.6116509 10.763457,7.8389002 10.416216,7.6935222 10.072258,7.5466137 9.8893058,7.0793635 10.007446,6.6503711 c 0.117319,-0.4300125 0.492452,-0.6575166 0.838462,-0.5106089 0.344779,0.1469085 0.529372,0.6149239 0.411437,1.0441719 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__3_"
+ style="fill:#ffffff"
+ d="M 7.4971838,6.4825492 C 7.3782238,6.910266 7.0032948,7.1375146 6.6568748,6.9921373 6.3129158,6.8452288 6.1291428,6.3779786 6.2472828,5.9489862 c 0.11814,-0.4300133 0.493274,-0.6575171 0.838463,-0.5106086 0.344779,0.1469082 0.529373,0.6149236 0.411438,1.0441716 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__2_"
+ style="fill:#e5e5e5"
+ d="M 6.0274118,5.632215 C 5.9084528,6.0599326 5.5335238,6.2871811 5.1871038,6.141803 4.8431448,5.9948945 4.6593718,5.5276444 4.7775118,5.0986522 c 0.118139,-0.430013 0.493274,-0.6582818 0.838463,-0.5116285 0.344779,0.1479283 0.529167,0.6159434 0.411437,1.0451913 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E__1_"
+ style="fill:#c0c0c0"
+ d="m 6.0444358,3.5920771 c -0.11896,0.4277174 -0.49389,0.6559865 -0.840309,0.5095881 -0.343959,-0.1469084 -0.527732,-0.6139034 -0.409592,-1.0426406 0.11814,-0.4287376 0.493274,-0.6575169 0.838463,-0.5106086 0.344779,0.1461431 0.529167,0.6141585 0.411438,1.0436611 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="_x3C_Path_x3E_"
+ style="fill:#a6a6a6"
+ d="m 7.6680348,3.1243168 c -0.11896,0.4277174 -0.493889,0.6559865 -0.840309,0.5095881 -0.343958,-0.146908 -0.527731,-0.6141582 -0.409591,-1.0431507 0.118139,-0.4289927 0.493273,-0.6575167 0.838462,-0.5106084 0.344779,0.1469084 0.529168,0.6149234 0.411438,1.044171 z" />
+ </g>
+</svg>
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..df4ce0d
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,6 @@
+import os
+os.system("find ./ | sed 's,^./,ImageProcessor.activity/,g' > MANIFEST")
+os.chdir('..')
+os.system('zip -r ImageProcessor.xo ImageProcessor.activity')
+os.system('mv ImageProcessor.xo ./ImageProcessor.activity')
+os.chdir('ImageProcessor.activity')
diff --git a/toolbar.py b/toolbar.py
new file mode 100644
index 0000000..e0bf81c
--- /dev/null
+++ b/toolbar.py
@@ -0,0 +1,355 @@
+# Copyright (C) 2006, Red Hat, Inc.
+#Author: Keshav Sharma <keshav7890@gmail.com> & Vaibhav Sharma
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+import re
+
+import pango
+import gobject
+import gtk
+import evince
+
+try:
+ import epubadapter
+except:
+ pass
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toggletoolbutton import ToggleToolButton
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics import iconentry
+from sugar.activity import activity
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+
+
+class ViewToolbar(gtk.Toolbar):
+ __gtype_name__ = 'ViewToolbar'
+
+ __gsignals__ = {
+ 'zoom_in': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'zoom_out': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'zoom_to_fit': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'zoom_original': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'rotate_clockwise': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'rotate_anticlockwise': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'copy': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'paste': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ }
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self._zoom_out_button = None
+ self._zoom_in_button = None
+
+ self._zoom_out_button = ToolButton('zoom-out')
+ self._zoom_out_button.set_tooltip(_('Zoom out'))
+ self._zoom_out_button.connect('clicked', self.zoom_out_cb)
+ self.insert(self._zoom_out_button, -1)
+ self._zoom_out_button.show()
+
+ self._zoom_in_button = ToolButton('zoom-in')
+ self._zoom_in_button.set_tooltip(_('Zoom in'))
+ self._zoom_in_button.connect('clicked', self.zoom_in_cb)
+ self.insert(self._zoom_in_button, -1)
+ self._zoom_in_button.show()
+
+ zoom_tofit_button = ToolButton('zoom-best-fit')
+ zoom_tofit_button.set_tooltip(_('Fit to window'))
+ zoom_tofit_button.connect('clicked', self.zoom_to_fit_cb)
+ self.insert(zoom_tofit_button, -1)
+ zoom_tofit_button.show()
+
+ zoom_original_button = ToolButton('zoom-original')
+ zoom_original_button.set_tooltip(_('Original size'))
+ zoom_original_button.connect('clicked', self.zoom_original_cb)
+ self.insert(zoom_original_button, -1)
+ zoom_original_button.show()
+
+ spacer = gtk.SeparatorToolItem()
+ spacer.props.draw = False
+ self.insert(spacer, -1)
+ spacer.show()
+
+ rotate_anticlockwise_button = ToolButton('rotate_anticlockwise')
+ rotate_anticlockwise_button.set_tooltip(_('Rotate anticlockwise'))
+ rotate_anticlockwise_button.connect('clicked',
+ self.rotate_anticlockwise_cb)
+ self.insert(rotate_anticlockwise_button, -1)
+ rotate_anticlockwise_button.show()
+
+ rotate_clockwise_button = ToolButton('rotate_clockwise')
+ rotate_clockwise_button.set_tooltip(_('Rotate clockwise'))
+ rotate_clockwise_button.connect('clicked', self.rotate_clockwise_cb)
+ self.insert(rotate_clockwise_button, -1)
+ rotate_clockwise_button.show()
+
+ copy_button = ToolButton('copy')
+ copy_button.set_tooltip(_('copy image'))
+ copy_button.connect('clicked', self.copy_cb)
+ self.insert(copy_button, -1)
+ copy_button.show()
+
+ paste_button = ToolButton('paste')
+ paste_button.set_tooltip(_('get back copied'))
+ paste_button.connect('clicked', self.paste_cb)
+ self.insert(paste_button, -1)
+ paste_button.show()
+
+ def zoom_in_cb(self, button):
+ self.emit('zoom_in')
+
+ def zoom_out_cb(self, button):
+ self.emit('zoom_out')
+
+ def zoom_to_fit_cb(self, button):
+ self.emit('zoom_to_fit')
+ def zoom_original_cb(self, button):
+ self.emit('zoom_original')
+ def rotate_clockwise_cb(self, button):
+ self.emit('rotate_clockwise')
+ def rotate_anticlockwise_cb(self, button):
+ self.emit('rotate_anticlockwise')
+ def copy_cb(self, button):
+ self.emit('copy')
+ def paste_cb(self, button):
+ self.emit('paste')
+
+ def set_activity(self, activity):
+ self.activity = activity
+
+class EditToolbar(gtk.Toolbar):
+ __gtype_name__ = 'EditToolbar'
+
+ __gsignals__ = {
+ 'grey': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'blur': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'transpose': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'offset': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'contour': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'text': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'finedges': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'solarize': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'invert': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'watermark_tl': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'watermark_tile': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'watermark_scale': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'ambross': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'left_top': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'right_top': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'left_bottom': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'right_bottom': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'sharpen': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+
+ }
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self.grey = ToolButton('grey')
+ self.grey.set_tooltip(_('grey'))
+ self.grey.connect('clicked', self.grey_cb)
+ self.insert(self.grey, -1)
+ self.grey.show()
+
+ self.blur = ToolButton('blur')
+ self.blur.set_tooltip(_('blur'))
+ self.blur.connect('clicked', self.blur_cb)
+ self.insert(self.blur, -1)
+ self.blur.show()
+
+ self.left_top = ToolButton('left_top')
+ self.left_top.set_tooltip(_('left_top'))
+ self.left_top.connect('clicked', self.left_top_cb)
+ self.insert(self.left_top, -1)
+ self.left_top.show()
+
+ self.right_top = ToolButton('right_top')
+ self.right_top.set_tooltip(_('right_top'))
+ self.right_top.connect('clicked', self.right_top_cb)
+ self.insert(self.right_top, -1)
+ self.right_top.show()
+
+ self.left_bottom = ToolButton('left_bottom')
+ self.left_bottom.set_tooltip(_('left_bottom'))
+ self.left_bottom.connect('clicked', self.left_bottom_cb)
+ self.insert(self.left_bottom, -1)
+ self.left_bottom.show()
+
+ self.right_bottom = ToolButton('right_bottom')
+ self.right_bottom.set_tooltip(_('right_bottom'))
+ self.right_bottom.connect('clicked', self.right_bottom_cb)
+ self.insert(self.right_bottom, -1)
+ self.right_bottom.show()
+
+ self.watermark_tl = ToolButton('watermark_tl')
+ self.watermark_tl.set_tooltip(_('watermark top left'))
+ self.watermark_tl.connect('clicked', self.watermark_tl_cb)
+ self.insert(self.watermark_tl, -1)
+ self.watermark_tl.show()
+
+ self.watermark_til = ToolButton('watermark_tile')
+ self.watermark_til.set_tooltip(_('watermark tile form'))
+ self.watermark_til.connect('clicked', self.watermark_tile_cb)
+ self.insert(self.watermark_til, -1)
+ self.watermark_til.show()
+
+ self.watermark_scale = ToolButton('watermark_scale')
+ self.watermark_scale.set_tooltip(_('watermark scale form'))
+ self.watermark_scale.connect('clicked', self.watermark_scale_cb)
+ self.insert(self.watermark_scale, -1)
+ self.watermark_scale.show()
+
+ self.transpose = ToolButton('mirror')
+ self.transpose.set_tooltip(_('mirror'))
+ self.transpose.connect('clicked', self.transpose_cb)
+ self.insert(self.transpose, -1)
+ self.transpose.show()
+
+ self.offset = ToolButton('offset')
+ self.offset.set_tooltip(_('offset'))
+ self.offset.connect('clicked', self.offset_cb)
+ self.insert(self.offset, -1)
+ self.offset.show()
+
+ self.contour = ToolButton('contour')
+ self.contour.set_tooltip(_('contour'))
+ self.contour.connect('clicked', self.contour_cb)
+ self.insert(self.contour, -1)
+ self.contour.show()
+
+ self.finedges = ToolButton('finedges')
+ self.finedges.set_tooltip(_('findedges'))
+ self.finedges.connect('clicked', self.finedges_cb)
+ self.insert(self.finedges, -1)
+ self.finedges.show()
+
+ self.solarize = ToolButton('solarize')
+ self.solarize.set_tooltip(_('solarize'))
+ self.solarize.connect('clicked', self.solarize_cb)
+ self.insert(self.solarize, -1)
+ self.solarize.show()
+
+ self.invert = ToolButton('invert')
+ self.invert.set_tooltip(_('invert'))
+ self.invert.connect('clicked', self.invert_cb)
+ self.insert(self.invert, -1)
+ self.invert.show()
+
+ self.ambross = ToolButton('embross')
+ self.ambross.set_tooltip(_('emboss'))
+ self.ambross.connect('clicked', self.ambross_cb)
+ self.insert(self.ambross, -1)
+ self.ambross.show()
+
+ self.sharpen = ToolButton('sharpen')
+ self.sharpen.set_tooltip(_('sharpen'))
+ self.sharpen.connect('clicked', self.sharpen_cb)
+ self.insert(self.sharpen, -1)
+ self.sharpen.show()
+
+
+ def grey_cb(self, button):
+ self.emit('grey')
+ def blur_cb(self, button):
+ self.emit('blur')
+ def transpose_cb(self, button):
+ self.emit('transpose')
+ def offset_cb(self, button):
+ self.emit('offset')
+ def contour_cb(self, button):
+ self.emit('contour')
+ def finedges_cb(self, button):
+ self.emit('finedges')
+ def solarize_cb(self, button):
+ self.emit('solarize')
+ def invert_cb(self, button):
+ self.emit('invert')
+ def watermark_tl_cb(self, button):
+ self.emit('watermark_tl')
+ def watermark_tile_cb(self, button):
+ self.emit('watermark_tile')
+ def watermark_scale_cb(self, button):
+ self.emit('watermark_scale')
+ def ambross_cb(self, button):
+ self.emit('ambross')
+ def left_top_cb(self, button):
+ self.emit('left_top')
+ def right_top_cb(self, button):
+ self.emit('right_top')
+ def left_bottom_cb(self, button):
+ self.emit('left_bottom')
+ def right_bottom_cb(self, button):
+ self.emit('right_bottom')
+ def sharpen_cb(self, button):
+ self.emit('sharpen')
+
+