Initial commit
parent
27dd0a481a
commit
53e1c9046a
23
Makefile
23
Makefile
|
|
@ -8,9 +8,9 @@ arch:=armv7hl
|
|||
version:=0.0.1
|
||||
iteration:=1
|
||||
rpmname:=$(Appname)-$(version)-$(iteration).$(arch).rpm
|
||||
ssh_user:=nemo
|
||||
jolla_usb_ip:=192.168.1.15
|
||||
jolla_wifi_ip:=Jolla
|
||||
ssh_user:=defaultuser
|
||||
jolla_usb_ip:=192.168.2.15
|
||||
jolla_wifi_ip:=192.168.100.34
|
||||
|
||||
|
||||
all: clean build-tmp rpm-virt rpm-jolla
|
||||
|
|
@ -21,15 +21,12 @@ make-jolla-ap: build-tmp rpm-jolla send-jolla-ap
|
|||
make-virt: arch:=i686
|
||||
make-virt: build-tmp rpm-virt send-virt
|
||||
|
||||
build-tmp:
|
||||
rm -rf $(temp)
|
||||
build-tmp:
|
||||
mkdir -p $(temp)/usr/share/applications
|
||||
mkdir -p $(temp)/usr/share/$(Appname)/src/pyPackages
|
||||
mkdir -p $(temp)/usr/share/icons/hicolor/86x86/apps
|
||||
mkdir -p $(temp)/usr/bin
|
||||
cp -ar ./qml $(temp)/usr/share/$(Appname)
|
||||
cp -ar ./src/* $(temp)/usr/share/$(Appname)/src
|
||||
cp -ar ./pyPackages/*$(arch) $(temp)/usr/share/$(Appname)/src/pyPackages
|
||||
cp ./dat/$(Appname).desktop $(temp)/usr/share/applications/
|
||||
cp -ar ./dat/appicon.png $(temp)/usr/share/icons/hicolor/86x86/apps/$(Appname).png
|
||||
install -m 755 ./dat/$(Appname).sh $(temp)/usr/bin/$(Appname)
|
||||
|
|
@ -38,9 +35,8 @@ rpm-virt: arch:=i686
|
|||
rpm-virt: build-tmp
|
||||
cd $(temp);fpm -f -s dir -t rpm \
|
||||
--after-install $(sourcePath)/dat/upgradeScript.sh \
|
||||
--after-upgrade $(sourcePath)/dat/upgradeScript.sh \
|
||||
--after-remove $(sourcePath)/dat/removeScript.sh \
|
||||
--rpm-changelog $(sourcePath)/changelog.txt\
|
||||
--rpm-changelog $(sourcePath)/changelog.txt \
|
||||
--directories "/usr/share/$(Appname)" \
|
||||
-v $(version) \
|
||||
--iteration $(iteration) \
|
||||
|
|
@ -53,9 +49,8 @@ rpm-virt: build-tmp
|
|||
rpm-jolla: build-tmp
|
||||
cd $(temp);fpm -f -s dir -t rpm \
|
||||
--after-install $(sourcePath)/dat/upgradeScript.sh \
|
||||
--after-upgrade $(sourcePath)/dat/upgradeScript.sh \
|
||||
--after-remove $(sourcePath)/dat/removeScript.sh \
|
||||
--rpm-changelog $(sourcePath)/changelog.txt\
|
||||
--rpm-changelog $(sourcePath)/changelog.txt \
|
||||
--directories "/usr/share/$(Appname)" \
|
||||
-v $(version) \
|
||||
--iteration $(iteration) \
|
||||
|
|
@ -70,7 +65,7 @@ send-virt:
|
|||
cat ">" /tmp/$(rpmname) "&&" \
|
||||
pkcon install-local -y /tmp/$(rpmname) "&&" \
|
||||
rm /tmp/$(rpmname)
|
||||
|
||||
|
||||
send-jolla-wifi:
|
||||
cat $(temp)/$(rpmname) | ssh $(ssh_user)@$(jolla_wifi_ip) \
|
||||
cat ">" /tmp/$(rpmname) "&&" \
|
||||
|
|
@ -94,9 +89,7 @@ send-only-virt:
|
|||
cat $(temp)/$(rpmname) | ssh -i '$(sdkpath)/vmshare/ssh/private_keys/SailfishOS_Emulator/nemo' -p2223 $(ssh_user)@localhost \
|
||||
cat ">" /tmp/$(rpmname)
|
||||
|
||||
clean:
|
||||
clean:
|
||||
rm -rf $(temp)
|
||||
rm -rf $(builddir)
|
||||
rm -rf ./$(rpmname)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
sailfish-python
|
||||
harbour-muchkin
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
X-Nemo-Application-Type=silica-qt5
|
||||
Name=sailfish-python
|
||||
Name=harbour-muchkin
|
||||
Icon=icon-launcher-component-gallery
|
||||
Exec=sailfish-qml sailfish-python
|
||||
Exec=sailfish-qml harbour-muchkin
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
sailfish-qml harbour-muchkin
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "removing /usr/share/sailfish-python"
|
||||
rm -rf /usr/share/sailfish-python
|
||||
echo "removing /usr/share/harbour-muchkin"
|
||||
rm -rf /usr/share/harbour-muchkin
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
sailfish-qml sailfish-python
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
#rm -rf /usr/share/sailfish-python
|
||||
#rm -rf /usr/share/harbour-muchkin
|
||||
echo "Upgrading...."
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
libsailfishapp-launcher
|
||||
python3-base
|
||||
pyotherside-qml-plugin-python3-qt5
|
||||
python3-base
|
||||
python3-numpy
|
||||
python3-pillow
|
||||
|
|
|
|||
|
|
@ -1,133 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL import FontFile
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 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 True:
|
||||
s = f.readline()
|
||||
if not s:
|
||||
return None
|
||||
if s[:9] == b"STARTCHAR":
|
||||
break
|
||||
id = s[9:].strip().decode('ascii')
|
||||
|
||||
# load symbol properties
|
||||
props = {}
|
||||
while True:
|
||||
s = f.readline()
|
||||
if not s or s[:6] == b"BITMAP":
|
||||
break
|
||||
i = s.find(b" ")
|
||||
props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii')
|
||||
|
||||
# load bitmap
|
||||
bitmap = []
|
||||
while True:
|
||||
s = f.readline()
|
||||
if not s or s[:7] == b"ENDCHAR":
|
||||
break
|
||||
bitmap.append(s[:-1])
|
||||
bitmap = b"".join(bitmap)
|
||||
|
||||
[x, y, l, d] = [int(s) for s in props["BBX"].split()]
|
||||
[dx, dy] = [int(s) for s in props["DWIDTH"].split()]
|
||||
|
||||
bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
|
||||
|
||||
try:
|
||||
im = Image.frombytes("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] != b"STARTFONT 2.1":
|
||||
raise SyntaxError("not a valid BDF file")
|
||||
|
||||
props = {}
|
||||
comments = []
|
||||
|
||||
while True:
|
||||
s = fp.readline()
|
||||
if not s or s[:13] == b"ENDPROPERTIES":
|
||||
break
|
||||
i = s.find(b" ")
|
||||
props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii')
|
||||
if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
|
||||
if s.find(b"LogicalFontDescription") < 0:
|
||||
comments.append(s[i+1:-1].decode('ascii'))
|
||||
|
||||
font = props["FONT"].split("-")
|
||||
|
||||
font[4] = bdf_slant[font[4].upper()]
|
||||
font[11] = bdf_spacing[font[11].upper()]
|
||||
|
||||
# ascent = int(props["FONT_ASCENT"])
|
||||
# descent = int(props["FONT_DESCENT"])
|
||||
|
||||
# fontname = ";".join(font[1:])
|
||||
|
||||
# print "#", fontname
|
||||
# for i in comments:
|
||||
# print "#", i
|
||||
|
||||
font = []
|
||||
while True:
|
||||
c = bdf_char(fp)
|
||||
if not c:
|
||||
break
|
||||
id, ch, (xy, dst, src), im = c
|
||||
if 0 <= ch < len(self.glyph):
|
||||
self.glyph[ch] = xy, dst, src, im
|
||||
|
|
@ -1,264 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
import math
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
o8 = _binary.o8
|
||||
o16 = _binary.o16le
|
||||
o32 = _binary.o32le
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Read BMP file
|
||||
|
||||
BIT2MODE = {
|
||||
# bits => mode, rawmode
|
||||
1: ("P", "P;1"),
|
||||
4: ("P", "P;4"),
|
||||
8: ("P", "P"),
|
||||
16: ("RGB", "BGR;15"),
|
||||
24: ("RGB", "BGR"),
|
||||
32: ("RGB", "BGRX")
|
||||
}
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:2] == b"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
|
||||
direction = -1
|
||||
|
||||
elif len(s) in [40, 64, 108, 124]:
|
||||
|
||||
# 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:])
|
||||
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
|
||||
lutsize = 4
|
||||
colors = i32(s[32:])
|
||||
direction = -1
|
||||
if i8(s[11]) == 0xff:
|
||||
# upside-down storage
|
||||
self.size = self.size[0], 2**32 - self.size[1]
|
||||
direction = 0
|
||||
|
||||
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701),
|
||||
pxperm))
|
||||
|
||||
else:
|
||||
raise IOError("Unsupported BMP header type (%d)" % len(s))
|
||||
|
||||
if (self.size[0]*self.size[1]) > 2**31:
|
||||
# Prevent DOS for > 2gb images
|
||||
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
|
||||
|
||||
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)
|
||||
elif colors > 2**16 or colors <= 0: # We're reading a i32.
|
||||
raise IOError("Unsupported BMP Palette size (%d)" % colors)
|
||||
else:
|
||||
indices = list(range(colors))
|
||||
for i in indices:
|
||||
rgb = read(lutsize)[:3]
|
||||
if rgb != o8(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", b"".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),
|
||||
direction))]
|
||||
|
||||
self.info["compression"] = compression
|
||||
|
||||
def _open(self):
|
||||
|
||||
# HEAD
|
||||
s = self.fp.read(14)
|
||||
if s[:2] != b"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
|
||||
|
||||
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
|
||||
|
||||
info = im.encoderinfo
|
||||
|
||||
dpi = info.get("dpi", (96, 96))
|
||||
|
||||
# 1 meter == 39.3701 inches
|
||||
ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
|
||||
|
||||
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(b"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(ppm[0]) + o32(ppm[1]) + # resolution
|
||||
o32(colors) + # colors used
|
||||
o32(colors)) # colors important
|
||||
|
||||
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
|
||||
|
||||
if im.mode == "1":
|
||||
for i in (0, 255):
|
||||
fp.write(o8(i) * 4)
|
||||
elif im.mode == "L":
|
||||
for i in range(256):
|
||||
fp.write(o8(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")
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# BUFR stub adapter
|
||||
#
|
||||
# Copyright (c) 1996-2003 by Fredrik Lundh
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL 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] == b"BUFR" or prefix[:4] == b"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")
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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 True:
|
||||
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 True:
|
||||
s = self.readline()
|
||||
if not s:
|
||||
break
|
||||
l.append(s)
|
||||
return l
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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"
|
||||
|
||||
from PIL import Image, BmpImagePlugin, _binary
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == b"\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 a CUR file")
|
||||
|
||||
# pick the largest cursor in the file
|
||||
m = b""
|
||||
for i in range(i16(s[4:])):
|
||||
s = self.fp.read(16)
|
||||
if not m:
|
||||
m = s
|
||||
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
||||
m = s
|
||||
# print "width", i8(s[0])
|
||||
# print "height", i8(s[1])
|
||||
# print "colors", i8(s[2])
|
||||
# print "reserved", i8(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")
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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"
|
||||
|
||||
from PIL import Image, _binary
|
||||
|
||||
from PIL.PcxImagePlugin import PcxImageFile
|
||||
|
||||
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
||||
|
||||
i32 = _binary.i32le
|
||||
|
||||
|
||||
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")
|
||||
|
|
@ -1,424 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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)
|
||||
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution
|
||||
# resizing
|
||||
#
|
||||
# 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
|
||||
import io
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
i32 = _binary.i32le
|
||||
o32 = _binary.o32le
|
||||
|
||||
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
||||
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
||||
|
||||
gs_windows_binary = None
|
||||
import sys
|
||||
if sys.platform.startswith('win'):
|
||||
import shutil
|
||||
if hasattr(shutil, 'which'):
|
||||
which = shutil.which
|
||||
else:
|
||||
# Python < 3.3
|
||||
import distutils.spawn
|
||||
which = distutils.spawn.find_executable
|
||||
for binary in ('gswin32c', 'gswin64c', 'gs'):
|
||||
if which(binary) is not None:
|
||||
gs_windows_binary = binary
|
||||
break
|
||||
else:
|
||||
gs_windows_binary = False
|
||||
|
||||
|
||||
def has_ghostscript():
|
||||
if gs_windows_binary:
|
||||
return True
|
||||
if not sys.platform.startswith('win'):
|
||||
import subprocess
|
||||
try:
|
||||
gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
|
||||
gs.stdout.read()
|
||||
return True
|
||||
except OSError:
|
||||
# no ghostscript
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def Ghostscript(tile, size, fp, scale=1):
|
||||
"""Render an image using Ghostscript"""
|
||||
|
||||
# Unpack decoder tile
|
||||
decoder, tile, offset, data = tile[0]
|
||||
length, bbox = data
|
||||
|
||||
# Hack to support hi-res rendering
|
||||
scale = int(scale) or 1
|
||||
# orig_size = size
|
||||
# orig_bbox = bbox
|
||||
size = (size[0] * scale, size[1] * scale)
|
||||
# resolution is dependent on bbox and size
|
||||
res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
|
||||
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
|
||||
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
out_fd, outfile = tempfile.mkstemp()
|
||||
os.close(out_fd)
|
||||
|
||||
infile_temp = None
|
||||
if hasattr(fp, 'name') and os.path.exists(fp.name):
|
||||
infile = fp.name
|
||||
else:
|
||||
in_fd, infile_temp = tempfile.mkstemp()
|
||||
os.close(in_fd)
|
||||
infile = infile_temp
|
||||
|
||||
# ignore length and offset!
|
||||
# ghostscript can read it
|
||||
# copy whole file to read in ghostscript
|
||||
with open(infile_temp, 'wb') as f:
|
||||
# fetch length of fp
|
||||
fp.seek(0, 2)
|
||||
fsize = fp.tell()
|
||||
# ensure start position
|
||||
# go back
|
||||
fp.seek(0)
|
||||
lengthfile = fsize
|
||||
while lengthfile > 0:
|
||||
s = fp.read(min(lengthfile, 100*1024))
|
||||
if not s:
|
||||
break
|
||||
lengthfile -= len(s)
|
||||
f.write(s)
|
||||
|
||||
# Build ghostscript command
|
||||
command = ["gs",
|
||||
"-q", # quiet mode
|
||||
"-g%dx%d" % size, # set output geometry (pixels)
|
||||
"-r%fx%f" % res, # set input DPI (dots per inch)
|
||||
"-dNOPAUSE -dSAFER", # don't pause between pages,
|
||||
# safe mode
|
||||
"-sDEVICE=ppmraw", # ppm driver
|
||||
"-sOutputFile=%s" % outfile, # output file
|
||||
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
|
||||
# adjust for image origin
|
||||
"-f", infile, # input file
|
||||
]
|
||||
|
||||
if gs_windows_binary is not None:
|
||||
if not gs_windows_binary:
|
||||
raise WindowsError('Unable to locate Ghostscript on paths')
|
||||
command[0] = gs_windows_binary
|
||||
|
||||
# push data through ghostscript
|
||||
try:
|
||||
gs = subprocess.Popen(command, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
gs.stdin.close()
|
||||
status = gs.wait()
|
||||
if status:
|
||||
raise IOError("gs failed (status %d)" % status)
|
||||
im = Image.core.open_ppm(outfile)
|
||||
finally:
|
||||
try:
|
||||
os.unlink(outfile)
|
||||
if infile_temp:
|
||||
os.unlink(infile_temp)
|
||||
except:
|
||||
pass
|
||||
|
||||
return im
|
||||
|
||||
|
||||
class PSFile:
|
||||
"""
|
||||
Wrapper for bytesio object that treats either CR or LF as end of line.
|
||||
"""
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
self.char = None
|
||||
|
||||
def seek(self, offset, whence=0):
|
||||
self.char = None
|
||||
self.fp.seek(offset, whence)
|
||||
|
||||
def readline(self):
|
||||
s = self.char or b""
|
||||
self.char = None
|
||||
|
||||
c = self.fp.read(1)
|
||||
while c not in b"\r\n":
|
||||
s = s + c
|
||||
c = self.fp.read(1)
|
||||
|
||||
self.char = self.fp.read(1)
|
||||
# line endings can be 1 or 2 of \r \n, in either order
|
||||
if self.char in b"\r\n":
|
||||
self.char = None
|
||||
|
||||
return s.decode('latin-1')
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5
|
||||
|
||||
##
|
||||
# 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"
|
||||
|
||||
mode_map = {1: "L", 2: "LAB", 3: "RGB"}
|
||||
|
||||
def _open(self):
|
||||
(length, offset) = self._find_offset(self.fp)
|
||||
|
||||
# Rewrap the open file pointer in something that will
|
||||
# convert line endings and decode to latin-1.
|
||||
try:
|
||||
if bytes is str:
|
||||
# Python2, no encoding conversion necessary
|
||||
fp = open(self.fp.name, "Ur")
|
||||
else:
|
||||
# Python3, can use bare open command.
|
||||
fp = open(self.fp.name, "Ur", encoding='latin-1')
|
||||
except:
|
||||
# Expect this for bytesio/stringio
|
||||
fp = PSFile(self.fp)
|
||||
|
||||
# go to offset - start of "%!PS"
|
||||
fp.seek(offset)
|
||||
|
||||
box = None
|
||||
|
||||
self.mode = "RGB"
|
||||
self.size = 1, 1 # FIXME: huh?
|
||||
|
||||
#
|
||||
# Load EPS header
|
||||
|
||||
s = fp.readline().strip('\r\n')
|
||||
|
||||
while s:
|
||||
if len(s) > 255:
|
||||
raise SyntaxError("not an EPS file")
|
||||
|
||||
try:
|
||||
m = split.match(s)
|
||||
except re.error as 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 = [int(float(s)) for s in v.split()]
|
||||
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] = ""
|
||||
elif s[0] == '%':
|
||||
# handle non-DSC Postscript comments that some
|
||||
# tools mistakenly put in the Comments section
|
||||
pass
|
||||
else:
|
||||
raise IOError("bad EPS header")
|
||||
|
||||
s = fp.readline().strip('\r\n')
|
||||
|
||||
if s[0] != "%":
|
||||
break
|
||||
|
||||
#
|
||||
# Scan for an "ImageData" descriptor
|
||||
|
||||
while s[0] == "%":
|
||||
|
||||
if len(s) > 255:
|
||||
raise SyntaxError("not an EPS file")
|
||||
|
||||
if s[:11] == "%ImageData:":
|
||||
# Encoded bitmapped image.
|
||||
[x, y, bi, mo, z3, z4, en, id] = s[11:].split(None, 7)
|
||||
|
||||
if int(bi) != 8:
|
||||
break
|
||||
try:
|
||||
self.mode = self.mode_map[int(mo)]
|
||||
except:
|
||||
break
|
||||
|
||||
self.size = int(x), int(y)
|
||||
return
|
||||
|
||||
s = fp.readline().strip('\r\n')
|
||||
if not s:
|
||||
break
|
||||
|
||||
if not box:
|
||||
raise IOError("cannot determine EPS bounding box")
|
||||
|
||||
def _find_offset(self, fp):
|
||||
|
||||
s = fp.read(160)
|
||||
|
||||
if s[:4] == b"%!PS":
|
||||
# for HEAD without binary preview
|
||||
fp.seek(0, 2)
|
||||
length = fp.tell()
|
||||
offset = 0
|
||||
elif i32(s[0:4]) == 0xC6D3D0C5:
|
||||
# FIX for: Some EPS file not handled correctly / issue #302
|
||||
# EPS can contain binary data
|
||||
# or start directly with latin coding
|
||||
# more info see:
|
||||
# http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
||||
offset = i32(s[4:8])
|
||||
length = i32(s[8:12])
|
||||
else:
|
||||
raise SyntaxError("not an EPS file")
|
||||
|
||||
return (length, offset)
|
||||
|
||||
def load(self, scale=1):
|
||||
# Load EPS via Ghostscript
|
||||
if not self.tile:
|
||||
return
|
||||
self.im = Ghostscript(self.tile, self.size, self.fp, scale)
|
||||
self.mode = self.im.mode
|
||||
self.size = self.im.size
|
||||
self.tile = []
|
||||
|
||||
def load_seek(self, *args, **kwargs):
|
||||
# we can't incrementally load, so force ImageFile.parser to
|
||||
# use our custom load method by defining this method.
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
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")
|
||||
|
||||
class NoCloseStream:
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.fp, name)
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
base_fp = fp
|
||||
fp = NoCloseStream(fp)
|
||||
if sys.version_info[0] > 2:
|
||||
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
||||
|
||||
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(operator[2] + "\n")
|
||||
fp.flush()
|
||||
|
||||
ImageFile._save(im, base_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")
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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
|
||||
0x00fe: "NewSubfileType",
|
||||
0x00ff: "SubfileType",
|
||||
0x0100: "ImageWidth",
|
||||
0x0101: "ImageLength",
|
||||
0x0102: "BitsPerSample",
|
||||
0x0103: "Compression",
|
||||
0x0106: "PhotometricInterpretation",
|
||||
0x0107: "Threshholding",
|
||||
0x0108: "CellWidth",
|
||||
0x0109: "CellLenght",
|
||||
0x010a: "FillOrder",
|
||||
0x010d: "DocumentName",
|
||||
0x011d: "PageName",
|
||||
0x010e: "ImageDescription",
|
||||
0x010f: "Make",
|
||||
0x0110: "Model",
|
||||
0x0111: "StripOffsets",
|
||||
0x0112: "Orientation",
|
||||
0x0115: "SamplesPerPixel",
|
||||
0x0116: "RowsPerStrip",
|
||||
0x0117: "StripByteConunts",
|
||||
0x0118: "MinSampleValue",
|
||||
0x0119: "MaxSampleValue",
|
||||
0x011a: "XResolution",
|
||||
0x011b: "YResolution",
|
||||
0x011c: "PlanarConfiguration",
|
||||
0x0120: "FreeOffsets",
|
||||
0x0121: "FreeByteCounts",
|
||||
0x0122: "GrayResponseUnit",
|
||||
0x0123: "GrayResponseCurve",
|
||||
0x0128: "ResolutionUnit",
|
||||
0x012d: "TransferFunction",
|
||||
0x0131: "Software",
|
||||
0x0132: "DateTime",
|
||||
0x013b: "Artist",
|
||||
0x013c: "HostComputer",
|
||||
0x013e: "WhitePoint",
|
||||
0x013f: "PrimaryChromaticities",
|
||||
0x0140: "ColorMap",
|
||||
0x0152: "ExtraSamples",
|
||||
0x0201: "JpegIFOffset",
|
||||
0x0202: "JpegIFByteCount",
|
||||
0x0211: "YCbCrCoefficients",
|
||||
0x0212: "YCbCrSubSampling",
|
||||
0x0213: "YCbCrPositioning",
|
||||
0x0214: "ReferenceBlackWhite",
|
||||
0x1000: "RelatedImageFileFormat",
|
||||
0x1001: "RelatedImageWidth",
|
||||
0x1002: "RelatedImageLength",
|
||||
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",
|
||||
0xa401: "CustomRendered",
|
||||
0xa402: "ExposureMode",
|
||||
0xa403: "WhiteBalance",
|
||||
0xa404: "DigitalZoomRatio",
|
||||
0xa405: "FocalLengthIn35mmFilm",
|
||||
0xa406: "SceneCaptureType",
|
||||
0xa407: "GainControl",
|
||||
0xa408: "Contrast",
|
||||
0xa409: "Saturation",
|
||||
0xa40a: "Sharpness",
|
||||
0xa40b: "DeviceSettingDescription",
|
||||
0xa40c: "SubjectDistanceRange",
|
||||
0xa420: "ImageUniqueID",
|
||||
0xa430: "CameraOwnerName",
|
||||
0xa431: "BodySerialNumber",
|
||||
0xa432: "LensSpecification",
|
||||
0xa433: "LensMake",
|
||||
0xa434: "LensModel",
|
||||
0xa435: "LensSerialNumber",
|
||||
0xa500: "Gamma",
|
||||
|
||||
}
|
||||
|
||||
##
|
||||
# Maps EXIF GPS 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",
|
||||
27: "GPSProcessingMethod",
|
||||
28: "GPSAreaInformation",
|
||||
29: "GPSDateStamp",
|
||||
30: "GPSDifferential",
|
||||
31: "GPSHPositioningError",
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# FITS stub adapter
|
||||
#
|
||||
# Copyright (c) 1998-2003 by Fredrik Lundh
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL 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] == b"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")
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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"
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
o8 = _binary.o8
|
||||
|
||||
|
||||
#
|
||||
# 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 not (magic in [0xAF11, 0xAF12] and
|
||||
i16(s[14:16]) in [0, 3] and # flags
|
||||
s[20:22] == b"\x00\x00"): # reserved
|
||||
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 = [(a, a, a) for a in 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 = [o8(r)+o8(g)+o8(b) for (r, g, b) in palette]
|
||||
self.palette = ImagePalette.raw("RGB", b"".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 + i8(s[0])
|
||||
n = i8(s[1])
|
||||
if n == 0:
|
||||
n = 256
|
||||
s = self.fp.read(n * 3)
|
||||
for n in range(0, len(s), 3):
|
||||
r = i8(s[n]) << shift
|
||||
g = i8(s[n+1]) << shift
|
||||
b = i8(s[n+2]) << shift
|
||||
palette[i] = (r, g, b)
|
||||
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")
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# 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
|
||||
from PIL import Image, _binary
|
||||
|
||||
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 += 65536
|
||||
fp.write(_binary.o16be(v))
|
||||
|
||||
|
||||
##
|
||||
# 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 += 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 save(self, filename):
|
||||
"Save font"
|
||||
|
||||
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(b"PILfont\n")
|
||||
fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!!
|
||||
fp.write(b"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()
|
||||
|
||||
# End of file
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
#
|
||||
# THIS IS WORK IN PROGRESS
|
||||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile
|
||||
from PIL.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 += 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 id in prop:
|
||||
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 = i8(s[14])
|
||||
jpeg_tables = i8(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")
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, _binary
|
||||
|
||||
i32 = _binary.i32be
|
||||
|
||||
|
||||
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.frombytes(self.data)
|
||||
self.data = b""
|
||||
|
||||
#
|
||||
# registry
|
||||
|
||||
Image.register_open("GBR", GbrImageFile, _accept)
|
||||
|
||||
Image.register_extension("GBR", ".gbr")
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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"
|
||||
|
||||
from PIL import ImageFile, ImagePalette, _binary
|
||||
from PIL._util import isPath
|
||||
|
||||
try:
|
||||
import builtins
|
||||
except ImportError:
|
||||
import __builtin__
|
||||
builtins = __builtin__
|
||||
|
||||
i16 = _binary.i16be
|
||||
|
||||
|
||||
##
|
||||
# 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 isPath(fp):
|
||||
filename = fp
|
||||
fp = builtins.open(fp, "rb")
|
||||
else:
|
||||
filename = ""
|
||||
|
||||
try:
|
||||
return GdImageFile(fp, filename)
|
||||
except SyntaxError:
|
||||
raise IOError("cannot identify this image file")
|
||||
|
|
@ -1,544 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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"
|
||||
|
||||
|
||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
o8 = _binary.o8
|
||||
o16 = _binary.o16le
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Identify/read GIF files
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:6] in [b"GIF87a", b"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 i8(s):
|
||||
return self.fp.read(i8(s))
|
||||
return None
|
||||
|
||||
def _open(self):
|
||||
|
||||
# Screen
|
||||
s = self.fp.read(13)
|
||||
if s[:6] not in [b"GIF87a", b"GIF89a"]:
|
||||
raise SyntaxError("not a GIF file")
|
||||
|
||||
self.info["version"] = s[:6]
|
||||
self.size = i16(s[6:]), i16(s[8:])
|
||||
self.tile = []
|
||||
flags = i8(s[10])
|
||||
bits = (flags & 7) + 1
|
||||
|
||||
if flags & 128:
|
||||
# get global palette
|
||||
self.info["background"] = i8(s[11])
|
||||
# check if palette contains colour indices
|
||||
p = self.fp.read(3 << bits)
|
||||
for i in range(0, len(p), 3):
|
||||
if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(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.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1
|
||||
self.__frame = -1
|
||||
self.__fp.seek(self.__rewind)
|
||||
self._prev_im = None
|
||||
self.disposal_method = 0
|
||||
else:
|
||||
# ensure that the previous frame was loaded
|
||||
if not self.im:
|
||||
self.load()
|
||||
|
||||
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.paste(self.dispose, self.dispose_extent)
|
||||
|
||||
from copy import copy
|
||||
self.palette = copy(self.global_palette)
|
||||
|
||||
while True:
|
||||
|
||||
s = self.fp.read(1)
|
||||
if not s or s == b";":
|
||||
break
|
||||
|
||||
elif s == b"!":
|
||||
#
|
||||
# extensions
|
||||
#
|
||||
s = self.fp.read(1)
|
||||
block = self.data()
|
||||
if i8(s) == 249:
|
||||
#
|
||||
# graphic control extension
|
||||
#
|
||||
flags = i8(block[0])
|
||||
if flags & 1:
|
||||
self.info["transparency"] = i8(block[3])
|
||||
self.info["duration"] = i16(block[1:3]) * 10
|
||||
|
||||
# disposal method - find the value of bits 4 - 6
|
||||
dispose_bits = 0b00011100 & flags
|
||||
dispose_bits = dispose_bits >> 2
|
||||
if dispose_bits:
|
||||
# only set the dispose if it is not
|
||||
# unspecified. I'm not sure if this is
|
||||
# correct, but it seems to prevent the last
|
||||
# frame from looking odd for some animations
|
||||
self.disposal_method = dispose_bits
|
||||
elif i8(s) == 255:
|
||||
#
|
||||
# application extension
|
||||
#
|
||||
self.info["extension"] = block, self.fp.tell()
|
||||
if block[:11] == b"NETSCAPE2.0":
|
||||
block = self.data()
|
||||
if len(block) >= 3 and i8(block[0]) == 1:
|
||||
self.info["loop"] = i16(block[1:3])
|
||||
while self.data():
|
||||
pass
|
||||
|
||||
elif s == b",":
|
||||
#
|
||||
# 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:])
|
||||
self.dispose_extent = x0, y0, x1, y1
|
||||
flags = i8(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 = i8(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`" % i8(s)
|
||||
|
||||
try:
|
||||
if self.disposal_method < 2:
|
||||
# do not dispose or none specified
|
||||
self.dispose = None
|
||||
elif self.disposal_method == 2:
|
||||
# replace with background colour
|
||||
self.dispose = Image.core.fill("P", self.size,
|
||||
self.info["background"])
|
||||
else:
|
||||
# replace with previous contents
|
||||
if self.im:
|
||||
self.dispose = self.im.copy()
|
||||
|
||||
# only dispose the extent in this frame
|
||||
if self.dispose:
|
||||
self.dispose = self.dispose.crop(self.dispose_extent)
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
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
|
||||
|
||||
def load_end(self):
|
||||
ImageFile.ImageFile.load_end(self)
|
||||
|
||||
# if the disposal method is 'do not dispose', transparent
|
||||
# pixels should show the content of the previous frame
|
||||
if self._prev_im and self.disposal_method == 1:
|
||||
# we do this by pasting the updated area onto the previous
|
||||
# frame which we then use as the current image content
|
||||
updated = self.im.crop(self.dispose_extent)
|
||||
self._prev_im.paste(updated, self.dispose_extent,
|
||||
updated.convert('RGBA'))
|
||||
self.im = self._prev_im
|
||||
self._prev_im = self.im.copy()
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 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
|
||||
|
||||
if im.mode in RAWMODE:
|
||||
imOut = im
|
||||
else:
|
||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
||||
# should automatically convert images on save...)
|
||||
if Image.getmodebase(im.mode) == "RGB":
|
||||
palette_size = 256
|
||||
if im.palette:
|
||||
palette_size = len(im.palette.getdata()[1]) // 3
|
||||
imOut = im.convert("P", palette=1, colors=palette_size)
|
||||
else:
|
||||
imOut = im.convert("L")
|
||||
|
||||
# header
|
||||
try:
|
||||
palette = im.encoderinfo["palette"]
|
||||
except KeyError:
|
||||
palette = None
|
||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||
|
||||
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
|
||||
for s in header:
|
||||
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 = int(transparency)
|
||||
# optimize the block away if transparent color is not used
|
||||
transparentColorExists = True
|
||||
# adjust the transparency index after optimize
|
||||
if usedPaletteColors is not None and len(usedPaletteColors) < 256:
|
||||
for i in range(len(usedPaletteColors)):
|
||||
if usedPaletteColors[i] == transparency:
|
||||
transparency = i
|
||||
transparentColorExists = True
|
||||
break
|
||||
else:
|
||||
transparentColorExists = False
|
||||
|
||||
# transparency extension block
|
||||
if transparentColorExists:
|
||||
fp.write(b"!" +
|
||||
o8(249) + # extension intro
|
||||
o8(4) + # length
|
||||
o8(1) + # transparency info present
|
||||
o16(0) + # duration
|
||||
o8(transparency) # transparency index
|
||||
+ o8(0))
|
||||
|
||||
# local image header
|
||||
fp.write(b"," +
|
||||
o16(0) + o16(0) + # bounding box
|
||||
o16(im.size[0]) + # size
|
||||
o16(im.size[1]) +
|
||||
o8(flags) + # flags
|
||||
o8(8)) # bits
|
||||
|
||||
imOut.encoderconfig = (8, interlace)
|
||||
ImageFile._save(imOut, fp, [("gif", (0, 0)+im.size, 0,
|
||||
RAWMODE[imOut.mode])])
|
||||
|
||||
fp.write(b"\0") # end of image data
|
||||
|
||||
fp.write(b";") # 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
|
||||
from subprocess import Popen, check_call, PIPE, CalledProcessError
|
||||
import tempfile
|
||||
file = im._dump()
|
||||
|
||||
if im.mode != "RGB":
|
||||
with open(filename, 'wb') as f:
|
||||
stderr = tempfile.TemporaryFile()
|
||||
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
|
||||
else:
|
||||
with open(filename, 'wb') as f:
|
||||
|
||||
# Pipe ppmquant output into ppmtogif
|
||||
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
|
||||
quant_cmd = ["ppmquant", "256", file]
|
||||
togif_cmd = ["ppmtogif"]
|
||||
stderr = tempfile.TemporaryFile()
|
||||
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
|
||||
stderr = tempfile.TemporaryFile()
|
||||
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
|
||||
stderr=stderr)
|
||||
|
||||
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
||||
quant_proc.stdout.close()
|
||||
|
||||
retcode = quant_proc.wait()
|
||||
if retcode:
|
||||
raise CalledProcessError(retcode, quant_cmd)
|
||||
|
||||
retcode = togif_proc.wait()
|
||||
if retcode:
|
||||
raise CalledProcessError(retcode, togif_cmd)
|
||||
|
||||
try:
|
||||
os.unlink(file)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# GIF utilities
|
||||
|
||||
def getheader(im, palette=None, info=None):
|
||||
"""Return a list of strings representing a GIF header"""
|
||||
|
||||
optimize = info and info.get("optimize", 0)
|
||||
|
||||
# Header Block
|
||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||
header = [
|
||||
b"GIF87a" + # signature + version
|
||||
o16(im.size[0]) + # canvas width
|
||||
o16(im.size[1]) # canvas height
|
||||
]
|
||||
|
||||
if im.mode == "P":
|
||||
if palette and isinstance(palette, bytes):
|
||||
sourcePalette = palette[:768]
|
||||
else:
|
||||
sourcePalette = im.im.getpalette("RGB")[:768]
|
||||
else: # L-mode
|
||||
if palette and isinstance(palette, bytes):
|
||||
sourcePalette = palette[:768]
|
||||
else:
|
||||
sourcePalette = bytearray([i//3 for i in range(768)])
|
||||
|
||||
usedPaletteColors = paletteBytes = None
|
||||
|
||||
if im.mode in ("P", "L") and optimize:
|
||||
usedPaletteColors = []
|
||||
|
||||
# check which colors are used
|
||||
i = 0
|
||||
for count in im.histogram():
|
||||
if count:
|
||||
usedPaletteColors.append(i)
|
||||
i += 1
|
||||
|
||||
# create the new palette if not every color is used
|
||||
if len(usedPaletteColors) < 256:
|
||||
paletteBytes = b""
|
||||
newPositions = {}
|
||||
|
||||
i = 0
|
||||
# pick only the used colors from the palette
|
||||
for oldPosition in usedPaletteColors:
|
||||
paletteBytes += sourcePalette[oldPosition*3:oldPosition*3+3]
|
||||
newPositions[oldPosition] = i
|
||||
i += 1
|
||||
|
||||
# replace the palette color id of all pixel with the new id
|
||||
imageBytes = bytearray(im.tobytes())
|
||||
for i in range(len(imageBytes)):
|
||||
imageBytes[i] = newPositions[imageBytes[i]]
|
||||
im.frombytes(bytes(imageBytes))
|
||||
newPaletteBytes = (paletteBytes +
|
||||
(768 - len(paletteBytes)) * b'\x00')
|
||||
im.putpalette(newPaletteBytes)
|
||||
im.palette = ImagePalette.ImagePalette("RGB", palette=paletteBytes,
|
||||
size=len(paletteBytes))
|
||||
|
||||
if not paletteBytes:
|
||||
paletteBytes = sourcePalette
|
||||
|
||||
# Logical Screen Descriptor
|
||||
# calculate the palette size for the header
|
||||
import math
|
||||
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1
|
||||
if colorTableSize < 0:
|
||||
colorTableSize = 0
|
||||
# size of global color table + global color table flag
|
||||
header.append(o8(colorTableSize + 128))
|
||||
# background + reserved/aspect
|
||||
header.append(o8(0) + o8(0))
|
||||
# end of Logical Screen Descriptor
|
||||
|
||||
# add the missing amount of bytes
|
||||
# the palette has to be 2<<n in size
|
||||
actualTargetSizeDiff = (2 << colorTableSize) - len(paletteBytes)//3
|
||||
if actualTargetSizeDiff > 0:
|
||||
paletteBytes += o8(0) * 3 * actualTargetSizeDiff
|
||||
|
||||
# Header + Logical Screen Descriptor + Global Color Table
|
||||
header.append(paletteBytes)
|
||||
return header, usedPaletteColors
|
||||
|
||||
|
||||
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(b"," +
|
||||
o16(offset[0]) + # offset
|
||||
o16(offset[1]) +
|
||||
o16(im.size[0]) + # size
|
||||
o16(im.size[1]) +
|
||||
o8(0) + # flags
|
||||
o8(8)) # bits
|
||||
|
||||
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
||||
|
||||
fp.write(b"\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)
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
#
|
||||
# Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# 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
|
||||
from PIL._binary import o8
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 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 += 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 = o8(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5))
|
||||
g = o8(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5))
|
||||
b = o8(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5))
|
||||
a = o8(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5))
|
||||
|
||||
# add to palette
|
||||
palette.append(r + g + b + a)
|
||||
|
||||
return b"".join(palette), "RGBA"
|
||||
|
||||
|
||||
##
|
||||
# File handler for GIMP's gradient format.
|
||||
|
||||
class GimpGradientFile(GradientFile):
|
||||
|
||||
def __init__(self, fp):
|
||||
|
||||
if fp.readline()[:13] != b"GIMP Gradient":
|
||||
raise SyntaxError("not a GIMP gradient file")
|
||||
|
||||
line = fp.readline()
|
||||
|
||||
# GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
|
||||
if line.startswith(b"Name: "):
|
||||
line = fp.readline().strip()
|
||||
|
||||
count = int(line)
|
||||
|
||||
gradient = []
|
||||
|
||||
for i in range(count):
|
||||
|
||||
s = fp.readline().split()
|
||||
w = [float(x) for x in 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
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
#
|
||||
# Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# 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
|
||||
from PIL._binary import o8
|
||||
|
||||
|
||||
##
|
||||
# File handler for GIMP's palette format.
|
||||
|
||||
class GimpPaletteFile:
|
||||
|
||||
rawmode = "RGB"
|
||||
|
||||
def __init__(self, fp):
|
||||
|
||||
self.palette = [o8(i)*3 for i in range(256)]
|
||||
|
||||
if fp.readline()[:12] != b"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(b"\w+:|#", s):
|
||||
continue
|
||||
if len(s) > 100:
|
||||
raise SyntaxError("bad palette file")
|
||||
|
||||
v = tuple(map(int, s.split()[:3]))
|
||||
if len(v) != 3:
|
||||
raise ValueError("bad palette entry")
|
||||
|
||||
if 0 <= i <= 255:
|
||||
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
|
||||
|
||||
i += 1
|
||||
|
||||
self.palette = b"".join(self.palette)
|
||||
|
||||
def getpalette(self):
|
||||
|
||||
return self.palette, self.rawmode
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# GRIB stub adapter
|
||||
#
|
||||
# Copyright (c) 1996-2003 by Fredrik Lundh
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL 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] == b"GRIB" and prefix[7] == b'\x01'
|
||||
|
||||
|
||||
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")
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# HDF5 stub adapter
|
||||
#
|
||||
# Copyright (c) 2000-2003 by Fredrik Lundh
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL 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] == b"\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")
|
||||
|
|
@ -1,311 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
# Copyright (c) 2014 by Alastair Houghton.
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFile, PngImagePlugin, _binary
|
||||
import io
|
||||
import struct
|
||||
|
||||
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
|
||||
if enable_jpeg2k:
|
||||
from PIL import Jpeg2KImagePlugin
|
||||
|
||||
i8 = _binary.i8
|
||||
|
||||
HEADERSIZE = 8
|
||||
|
||||
|
||||
def nextheader(fobj):
|
||||
return struct.unpack('>4sI', fobj.read(HEADERSIZE))
|
||||
|
||||
|
||||
def read_32t(fobj, start_length, size):
|
||||
# The 128x128 icon seems to have an extra header for some reason.
|
||||
(start, length) = start_length
|
||||
fobj.seek(start)
|
||||
sig = fobj.read(4)
|
||||
if sig != b'\x00\x00\x00\x00':
|
||||
raise SyntaxError('Unknown signature, expecting 0x00000000')
|
||||
return read_32(fobj, (start + 4, length - 4), size)
|
||||
|
||||
|
||||
def read_32(fobj, start_length, size):
|
||||
"""
|
||||
Read a 32bit RGB icon resource. Seems to be either uncompressed or
|
||||
an RLE packbits-like scheme.
|
||||
"""
|
||||
(start, length) = start_length
|
||||
fobj.seek(start)
|
||||
pixel_size = (size[0] * size[2], size[1] * size[2])
|
||||
sizesq = pixel_size[0] * pixel_size[1]
|
||||
if length == sizesq * 3:
|
||||
# uncompressed ("RGBRGBGB")
|
||||
indata = fobj.read(length)
|
||||
im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1)
|
||||
else:
|
||||
# decode image
|
||||
im = Image.new("RGB", pixel_size, None)
|
||||
for band_ix in range(3):
|
||||
data = []
|
||||
bytesleft = sizesq
|
||||
while bytesleft > 0:
|
||||
byte = fobj.read(1)
|
||||
if not byte:
|
||||
break
|
||||
byte = i8(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 -= blocksize
|
||||
if bytesleft <= 0:
|
||||
break
|
||||
if bytesleft != 0:
|
||||
raise SyntaxError(
|
||||
"Error reading channel [%r left]" % bytesleft
|
||||
)
|
||||
band = Image.frombuffer(
|
||||
"L", pixel_size, b"".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
|
||||
(start, length) = start_length
|
||||
fobj.seek(start)
|
||||
pixel_size = (size[0] * size[2], size[1] * size[2])
|
||||
sizesq = pixel_size[0] * pixel_size[1]
|
||||
band = Image.frombuffer(
|
||||
"L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1
|
||||
)
|
||||
return {"A": band}
|
||||
|
||||
|
||||
def read_png_or_jpeg2000(fobj, start_length, size):
|
||||
(start, length) = start_length
|
||||
fobj.seek(start)
|
||||
sig = fobj.read(12)
|
||||
if sig[:8] == b'\x89PNG\x0d\x0a\x1a\x0a':
|
||||
fobj.seek(start)
|
||||
im = PngImagePlugin.PngImageFile(fobj)
|
||||
return {"RGBA": im}
|
||||
elif sig[:4] == b'\xff\x4f\xff\x51' \
|
||||
or sig[:4] == b'\x0d\x0a\x87\x0a' \
|
||||
or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
|
||||
if not enable_jpeg2k:
|
||||
raise ValueError('Unsupported icon subimage format (rebuild PIL '
|
||||
'with JPEG 2000 support to fix this)')
|
||||
# j2k, jpc or j2c
|
||||
fobj.seek(start)
|
||||
jp2kstream = fobj.read(length)
|
||||
f = io.BytesIO(jp2kstream)
|
||||
im = Jpeg2KImagePlugin.Jpeg2KImageFile(f)
|
||||
if im.mode != 'RGBA':
|
||||
im = im.convert('RGBA')
|
||||
return {"RGBA": im}
|
||||
else:
|
||||
raise ValueError('Unsupported icon subimage format')
|
||||
|
||||
|
||||
class IcnsFile:
|
||||
|
||||
SIZES = {
|
||||
(512, 512, 2): [
|
||||
(b'ic10', read_png_or_jpeg2000),
|
||||
],
|
||||
(512, 512, 1): [
|
||||
(b'ic09', read_png_or_jpeg2000),
|
||||
],
|
||||
(256, 256, 2): [
|
||||
(b'ic14', read_png_or_jpeg2000),
|
||||
],
|
||||
(256, 256, 1): [
|
||||
(b'ic08', read_png_or_jpeg2000),
|
||||
],
|
||||
(128, 128, 2): [
|
||||
(b'ic13', read_png_or_jpeg2000),
|
||||
],
|
||||
(128, 128, 1): [
|
||||
(b'ic07', read_png_or_jpeg2000),
|
||||
(b'it32', read_32t),
|
||||
(b't8mk', read_mk),
|
||||
],
|
||||
(64, 64, 1): [
|
||||
(b'icp6', read_png_or_jpeg2000),
|
||||
],
|
||||
(32, 32, 2): [
|
||||
(b'ic12', read_png_or_jpeg2000),
|
||||
],
|
||||
(48, 48, 1): [
|
||||
(b'ih32', read_32),
|
||||
(b'h8mk', read_mk),
|
||||
],
|
||||
(32, 32, 1): [
|
||||
(b'icp5', read_png_or_jpeg2000),
|
||||
(b'il32', read_32),
|
||||
(b'l8mk', read_mk),
|
||||
],
|
||||
(16, 16, 2): [
|
||||
(b'ic11', read_png_or_jpeg2000),
|
||||
],
|
||||
(16, 16, 1): [
|
||||
(b'icp4', read_png_or_jpeg2000),
|
||||
(b'is32', read_32),
|
||||
(b'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 != b'icns':
|
||||
raise SyntaxError('not an icns file')
|
||||
i = HEADERSIZE
|
||||
while i < filesize:
|
||||
sig, blocksize = nextheader(fobj)
|
||||
if blocksize <= 0:
|
||||
raise SyntaxError('invalid block header')
|
||||
i += HEADERSIZE
|
||||
blocksize -= HEADERSIZE
|
||||
dct[sig] = (i, blocksize)
|
||||
fobj.seek(blocksize, 1)
|
||||
i += blocksize
|
||||
|
||||
def itersizes(self):
|
||||
sizes = []
|
||||
for size, fmts in self.SIZES.items():
|
||||
for (fmt, reader) in fmts:
|
||||
if fmt in self.dct:
|
||||
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()
|
||||
if len(size) == 2:
|
||||
size = (size[0], size[1], 1)
|
||||
channels = self.dataforsize(size)
|
||||
|
||||
im = channels.get('RGBA', None)
|
||||
if im:
|
||||
return im
|
||||
|
||||
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.best_size = self.icns.bestsize()
|
||||
self.size = (self.best_size[0] * self.best_size[2],
|
||||
self.best_size[1] * self.best_size[2])
|
||||
self.info['sizes'] = self.icns.itersizes()
|
||||
# Just use this to see if it's loaded or not yet.
|
||||
self.tile = ('',)
|
||||
|
||||
def load(self):
|
||||
if len(self.size) == 3:
|
||||
self.best_size = self.size
|
||||
self.size = (self.best_size[0] * self.best_size[2],
|
||||
self.best_size[1] * self.best_size[2])
|
||||
|
||||
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.best_size)
|
||||
|
||||
# If this is a PNG or JPEG 2000, it won't be loaded yet
|
||||
im.load()
|
||||
|
||||
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] == b'icns')
|
||||
Image.register_extension("ICNS", '.icns')
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
import sys
|
||||
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
|
||||
for size in imf.info['sizes']:
|
||||
imf.size = size
|
||||
imf.load()
|
||||
im = imf.im
|
||||
im.save('out-%s-%s-%s.png' % size)
|
||||
im = Image.open(open(sys.argv[1], "rb"))
|
||||
im.save("out.png")
|
||||
if sys.platform == 'windows':
|
||||
os.startfile("out.png")
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# Windows Icon support for PIL
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||
# <casadebender@gmail.com>.
|
||||
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
||||
#
|
||||
# Icon format references:
|
||||
# * http://en.wikipedia.org/wiki/ICO_(file_format)
|
||||
# * http://msdn.microsoft.com/en-us/library/ms997538.aspx
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
||||
from math import log, ceil
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
i8 = _binary.i8
|
||||
i16 = _binary.i16le
|
||||
i32 = _binary.i32le
|
||||
|
||||
_MAGIC = b"\0\0\1\0"
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
fp.write(_MAGIC) # (2+2)
|
||||
sizes = im.encoderinfo.get("sizes",
|
||||
[(16, 16), (24, 24), (32, 32), (48, 48),
|
||||
(64, 64), (128, 128), (255, 255)])
|
||||
width, height = im.size
|
||||
filter(lambda x: False if (x[0] > width or x[1] > height or
|
||||
x[0] > 255 or x[1] > 255) else True, sizes)
|
||||
sizes = sorted(sizes, key=lambda x: x[0])
|
||||
fp.write(struct.pack("H", len(sizes))) # idCount(2)
|
||||
offset = fp.tell() + len(sizes)*16
|
||||
for size in sizes:
|
||||
width, height = size
|
||||
fp.write(struct.pack("B", width)) # bWidth(1)
|
||||
fp.write(struct.pack("B", height)) # bHeight(1)
|
||||
fp.write(b"\0") # bColorCount(1)
|
||||
fp.write(b"\0") # bReserved(1)
|
||||
fp.write(b"\0\0") # wPlanes(2)
|
||||
fp.write(struct.pack("H", 32)) # wBitCount(2)
|
||||
|
||||
image_io = BytesIO()
|
||||
tmp = im.copy()
|
||||
tmp.thumbnail(size, Image.LANCZOS)
|
||||
tmp.save(image_io, "png")
|
||||
image_io.seek(0)
|
||||
image_bytes = image_io.read()
|
||||
bytes_len = len(image_bytes)
|
||||
fp.write(struct.pack("I", bytes_len)) # dwBytesInRes(4)
|
||||
fp.write(struct.pack("I", offset)) # dwImageOffset(4)
|
||||
current = fp.tell()
|
||||
fp.seek(offset)
|
||||
fp.write(image_bytes)
|
||||
offset = offset + bytes_len
|
||||
fp.seek(current)
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] == _MAGIC
|
||||
|
||||
|
||||
class IcoFile:
|
||||
def __init__(self, buf):
|
||||
"""
|
||||
Parse image from file-like object containing ico file data
|
||||
"""
|
||||
|
||||
# check magic
|
||||
s = buf.read(6)
|
||||
if not _accept(s):
|
||||
raise SyntaxError("not an ICO file")
|
||||
|
||||
self.buf = buf
|
||||
self.entry = []
|
||||
|
||||
# Number of items in file
|
||||
self.nb_items = i16(s[4:])
|
||||
|
||||
# Get headers for each item
|
||||
for i in range(self.nb_items):
|
||||
s = buf.read(16)
|
||||
|
||||
icon_header = {
|
||||
'width': i8(s[0]),
|
||||
'height': i8(s[1]),
|
||||
'nb_color': i8(s[2]), # No. of colors in image (0 if >=8bpp)
|
||||
'reserved': i8(s[3]),
|
||||
'planes': i16(s[4:]),
|
||||
'bpp': i16(s[6:]),
|
||||
'size': i32(s[8:]),
|
||||
'offset': i32(s[12:])
|
||||
}
|
||||
|
||||
# See Wikipedia
|
||||
for j in ('width', 'height'):
|
||||
if not icon_header[j]:
|
||||
icon_header[j] = 256
|
||||
|
||||
# See Wikipedia notes about color depth.
|
||||
# We need this just to differ images with equal sizes
|
||||
icon_header['color_depth'] = (icon_header['bpp'] or
|
||||
(icon_header['nb_color'] != 0 and
|
||||
ceil(log(icon_header['nb_color'],
|
||||
2))) or 256)
|
||||
|
||||
icon_header['dim'] = (icon_header['width'], icon_header['height'])
|
||||
icon_header['square'] = (icon_header['width'] *
|
||||
icon_header['height'])
|
||||
|
||||
self.entry.append(icon_header)
|
||||
|
||||
self.entry = sorted(self.entry, key=lambda x: x['color_depth'])
|
||||
# ICO images are usually squares
|
||||
# self.entry = sorted(self.entry, key=lambda x: x['width'])
|
||||
self.entry = sorted(self.entry, key=lambda x: x['square'])
|
||||
self.entry.reverse()
|
||||
|
||||
def sizes(self):
|
||||
"""
|
||||
Get a list of all available icon sizes and color depths.
|
||||
"""
|
||||
return set((h['width'], h['height']) for h in self.entry)
|
||||
|
||||
def getimage(self, size, bpp=False):
|
||||
"""
|
||||
Get an image from the icon
|
||||
"""
|
||||
for (i, h) in enumerate(self.entry):
|
||||
if size == h['dim'] and (bpp is False or bpp == h['color_depth']):
|
||||
return self.frame(i)
|
||||
return self.frame(0)
|
||||
|
||||
def frame(self, idx):
|
||||
"""
|
||||
Get an image from frame idx
|
||||
"""
|
||||
|
||||
header = self.entry[idx]
|
||||
|
||||
self.buf.seek(header['offset'])
|
||||
data = self.buf.read(8)
|
||||
self.buf.seek(header['offset'])
|
||||
|
||||
if data[:8] == PngImagePlugin._MAGIC:
|
||||
# png frame
|
||||
im = PngImagePlugin.PngImageFile(self.buf)
|
||||
else:
|
||||
# XOR + AND mask bmp frame
|
||||
im = BmpImagePlugin.DibImageFile(self.buf)
|
||||
|
||||
# change tile dimension to only encompass XOR image
|
||||
im.size = (im.size[0], int(im.size[1] / 2))
|
||||
d, e, o, a = im.tile[0]
|
||||
im.tile[0] = d, (0, 0) + im.size, o, a
|
||||
|
||||
# figure out where AND mask image starts
|
||||
mode = a[0]
|
||||
bpp = 8
|
||||
for k in BmpImagePlugin.BIT2MODE.keys():
|
||||
if mode == BmpImagePlugin.BIT2MODE[k][1]:
|
||||
bpp = k
|
||||
break
|
||||
|
||||
if 32 == bpp:
|
||||
# 32-bit color depth icon image allows semitransparent areas
|
||||
# PIL's DIB format ignores transparency bits, recover them.
|
||||
# The DIB is packed in BGRX byte order where X is the alpha
|
||||
# channel.
|
||||
|
||||
# Back up to start of bmp data
|
||||
self.buf.seek(o)
|
||||
# extract every 4th byte (eg. 3,7,11,15,...)
|
||||
alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
|
||||
|
||||
# convert to an 8bpp grayscale image
|
||||
mask = Image.frombuffer(
|
||||
'L', # 8bpp
|
||||
im.size, # (w, h)
|
||||
alpha_bytes, # source chars
|
||||
'raw', # raw decoder
|
||||
('L', 0, -1) # 8bpp inverted, unpadded, reversed
|
||||
)
|
||||
else:
|
||||
# get AND image from end of bitmap
|
||||
w = im.size[0]
|
||||
if (w % 32) > 0:
|
||||
# bitmap row data is aligned to word boundaries
|
||||
w += 32 - (im.size[0] % 32)
|
||||
|
||||
# the total mask data is
|
||||
# padded row size * height / bits per char
|
||||
|
||||
and_mask_offset = o + int(im.size[0] * im.size[1] *
|
||||
(bpp / 8.0))
|
||||
total_bytes = int((w * im.size[1]) / 8)
|
||||
|
||||
self.buf.seek(and_mask_offset)
|
||||
maskData = self.buf.read(total_bytes)
|
||||
|
||||
# convert raw data to image
|
||||
mask = Image.frombuffer(
|
||||
'1', # 1 bpp
|
||||
im.size, # (w, h)
|
||||
maskData, # source chars
|
||||
'raw', # raw decoder
|
||||
('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed
|
||||
)
|
||||
|
||||
# now we have two images, im is XOR image and mask is AND image
|
||||
|
||||
# apply mask image as alpha channel
|
||||
im = im.convert('RGBA')
|
||||
im.putalpha(mask)
|
||||
|
||||
return im
|
||||
|
||||
|
||||
##
|
||||
# Image plugin for Windows Icon files.
|
||||
|
||||
class IcoImageFile(ImageFile.ImageFile):
|
||||
"""
|
||||
PIL read-only image support for Microsoft Windows .ico files.
|
||||
|
||||
By default the largest resolution image in the file will be loaded. This
|
||||
can be changed by altering the 'size' attribute before calling 'load'.
|
||||
|
||||
The info dictionary has a key 'sizes' that is a list of the sizes available
|
||||
in the icon file.
|
||||
|
||||
Handles classic, XP and Vista icon formats.
|
||||
|
||||
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||
<casadebender@gmail.com>.
|
||||
https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
||||
"""
|
||||
format = "ICO"
|
||||
format_description = "Windows Icon"
|
||||
|
||||
def _open(self):
|
||||
self.ico = IcoFile(self.fp)
|
||||
self.info['sizes'] = self.ico.sizes()
|
||||
self.size = self.ico.entry[0]['dim']
|
||||
self.load()
|
||||
|
||||
def load(self):
|
||||
im = self.ico.getimage(self.size)
|
||||
# if tile is PNG, it won't really be loaded yet
|
||||
im.load()
|
||||
self.im = im.im
|
||||
self.mode = im.mode
|
||||
self.size = im.size
|
||||
|
||||
def load_seek(self):
|
||||
# Flage the ImageFile.Parser so that it
|
||||
# just does all the decode at the end.
|
||||
pass
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
Image.register_open("ICO", IcoImageFile, _accept)
|
||||
Image.register_save("ICO", _save)
|
||||
Image.register_extension("ICO", ".ico")
|
||||
|
|
@ -1,347 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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
|
||||
from PIL import Image, ImageFile, ImagePalette
|
||||
from PIL._binary import i8
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 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", "16L", "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(br"^([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 b"\n" not 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 True:
|
||||
|
||||
s = self.fp.read(1)
|
||||
|
||||
# Some versions of IFUNC uses \n\r instead of \r\n...
|
||||
if s == b"\r":
|
||||
continue
|
||||
|
||||
if not s or s == b'\0' or s == b'\x1A':
|
||||
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:] == b'\r\n':
|
||||
s = s[:-2]
|
||||
elif s[-1:] == b'\n':
|
||||
s = s[:-1]
|
||||
|
||||
try:
|
||||
m = split.match(s)
|
||||
except re.error as v:
|
||||
raise SyntaxError("not an IM file")
|
||||
|
||||
if m:
|
||||
|
||||
k, v = m.group(1, 2)
|
||||
|
||||
# Don't know if this is the correct encoding,
|
||||
# but a decent guess (I guess)
|
||||
k = k.decode('latin-1', 'replace')
|
||||
v = v.decode('latin-1', 'replace')
|
||||
|
||||
# Convert value as appropriate
|
||||
if k in [FRAMES, SCALE, SIZE]:
|
||||
v = v.replace("*", ",")
|
||||
v = tuple(map(number, v.split(",")))
|
||||
if len(v) == 1:
|
||||
v = v[0]
|
||||
elif k == MODE and v in OPEN:
|
||||
v, self.rawmode = OPEN[v]
|
||||
|
||||
# Add to dictionary. Note that COMMENT tags are
|
||||
# combined into a list of strings.
|
||||
if k == COMMENT:
|
||||
if k in self.info:
|
||||
self.info[k].append(v)
|
||||
else:
|
||||
self.info[k] = [v]
|
||||
else:
|
||||
self.info[k] = v
|
||||
|
||||
if k in TAGS:
|
||||
n += 1
|
||||
|
||||
else:
|
||||
|
||||
raise SyntaxError("Syntax error in IM header: " +
|
||||
s.decode('ascii', 'replace'))
|
||||
|
||||
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:1] != b'\x1A':
|
||||
s = self.fp.read(1)
|
||||
if not s:
|
||||
raise SyntaxError("File truncated")
|
||||
|
||||
if LUT in self.info:
|
||||
# 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 i8(palette[i]) != i:
|
||||
linear = 0
|
||||
else:
|
||||
greyscale = 0
|
||||
if self.mode == "L" or self.mode == "LA":
|
||||
if greyscale:
|
||||
if not linear:
|
||||
self.lut = [i8(c) for c in 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 = [i8(c) for c in 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;16L": ("L 16L", "I;16L"),
|
||||
"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).encode('ascii'))
|
||||
if filename:
|
||||
fp.write(("Name: %s\r\n" % filename).encode('ascii'))
|
||||
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
|
||||
fp.write(("File size (no of images): %d\r\n" % frames).encode('ascii'))
|
||||
if im.mode == "P":
|
||||
fp.write(b"Lut: 1\r\n")
|
||||
fp.write(b"\000" * (511-fp.tell()) + b"\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")
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,283 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def constant(image, value):
|
||||
"""Fill a channel with a given grey level.
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
return Image.new("L", image.size, value)
|
||||
|
||||
|
||||
def duplicate(image):
|
||||
"""Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`.
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
return image.copy()
|
||||
|
||||
|
||||
def invert(image):
|
||||
"""
|
||||
Invert an image (channel).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = MAX - image
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image.load()
|
||||
return image._new(image.im.chop_invert())
|
||||
|
||||
|
||||
def lighter(image1, image2):
|
||||
"""
|
||||
Compares the two images, pixel by pixel, and returns a new image containing
|
||||
the lighter values.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = max(image1, image2)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_lighter(image2.im))
|
||||
|
||||
|
||||
def darker(image1, image2):
|
||||
"""
|
||||
Compares the two images, pixel by pixel, and returns a new image
|
||||
containing the darker values.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = min(image1, image2)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_darker(image2.im))
|
||||
|
||||
|
||||
def difference(image1, image2):
|
||||
"""
|
||||
Returns the absolute value of the pixel-by-pixel difference between the two
|
||||
images.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = abs(image1 - image2)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_difference(image2.im))
|
||||
|
||||
|
||||
def multiply(image1, image2):
|
||||
"""
|
||||
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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = image1 * image2 / MAX
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_multiply(image2.im))
|
||||
|
||||
|
||||
def screen(image1, image2):
|
||||
"""
|
||||
Superimposes two inverted images on top of each other.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = MAX - ((MAX - image1) * (MAX - image2) / MAX)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_screen(image2.im))
|
||||
|
||||
|
||||
def add(image1, image2, scale=1.0, offset=0):
|
||||
"""
|
||||
Adds two images, dividing the result by scale and adding the
|
||||
offset. If omitted, scale defaults to 1.0, and offset to 0.0.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = ((image1 + image2) / scale + offset)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_add(image2.im, scale, offset))
|
||||
|
||||
|
||||
def subtract(image1, image2, scale=1.0, offset=0):
|
||||
"""
|
||||
Subtracts two images, dividing the result by scale and adding the
|
||||
offset. If omitted, scale defaults to 1.0, and offset to 0.0.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = ((image1 - image2) / scale + offset)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_subtract(image2.im, scale, offset))
|
||||
|
||||
|
||||
def add_modulo(image1, image2):
|
||||
"""Add two images, without clipping the result.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = ((image1 + image2) % MAX)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_add_modulo(image2.im))
|
||||
|
||||
|
||||
def subtract_modulo(image1, image2):
|
||||
"""Subtract two images, without clipping the result.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = ((image1 - image2) % MAX)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_subtract_modulo(image2.im))
|
||||
|
||||
|
||||
def logical_and(image1, image2):
|
||||
"""Logical AND between two images.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = ((image1 and image2) % MAX)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_and(image2.im))
|
||||
|
||||
|
||||
def logical_or(image1, image2):
|
||||
"""Logical OR between two images.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = ((image1 or image2) % MAX)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_or(image2.im))
|
||||
|
||||
|
||||
def logical_xor(image1, image2):
|
||||
"""Logical XOR between two images.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
out = ((bool(image1) != bool(image2)) % MAX)
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
image1.load()
|
||||
image2.load()
|
||||
return image1._new(image1.im.chop_xor(image2.im))
|
||||
|
||||
|
||||
def blend(image1, image2, alpha):
|
||||
"""Blend images using constant transparency weight. Alias for
|
||||
:py:meth:`PIL.Image.Image.blend`.
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
return Image.blend(image1, image2, alpha)
|
||||
|
||||
|
||||
def composite(image1, image2, mask):
|
||||
"""Create composite using transparency mask. Alias for
|
||||
:py:meth:`PIL.Image.Image.composite`.
|
||||
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
return Image.composite(image1, image2, mask)
|
||||
|
||||
|
||||
def offset(image, xoffset, yoffset=None):
|
||||
"""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 xoffset: The horizontal distance.
|
||||
:param yoffset: The vertical distance. If omitted, both
|
||||
distances are set to the same value.
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
|
||||
if yoffset is None:
|
||||
yoffset = xoffset
|
||||
image.load()
|
||||
return image._new(image.im.offset(xoffset, yoffset))
|
||||
|
|
@ -1,972 +0,0 @@
|
|||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
|
||||
# Optional color managment support, based on Kevin Cazabon's PyCMS
|
||||
# library.
|
||||
|
||||
# History:
|
||||
|
||||
# 2009-03-08 fl Added to PIL.
|
||||
|
||||
# Copyright (C) 2002-2003 Kevin Cazabon
|
||||
# Copyright (c) 2009 by Fredrik Lundh
|
||||
# Copyright (c) 2013 by Eric Soroos
|
||||
|
||||
# See the README file for information on usage and redistribution. See
|
||||
# below for the original description.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
DESCRIPTION = """
|
||||
pyCMS
|
||||
|
||||
a Python / PIL interface to the littleCMS ICC Color Management System
|
||||
Copyright (C) 2002-2003 Kevin Cazabon
|
||||
kevin@cazabon.com
|
||||
http://www.cazabon.com
|
||||
|
||||
pyCMS home page: http://www.cazabon.com/pyCMS
|
||||
littleCMS home page: http://www.littlecms.com
|
||||
(littleCMS is Copyright (C) 1998-2001 Marti Maria)
|
||||
|
||||
Originally released under LGPL. Graciously donated to PIL in
|
||||
March 2009, for distribution under the standard PIL license
|
||||
|
||||
The pyCMS.py module provides a "clean" interface between Python/PIL and
|
||||
pyCMSdll, taking care of some of the more complex handling of the direct
|
||||
pyCMSdll functions, as well as error-checking and making sure that all
|
||||
relevant data is kept together.
|
||||
|
||||
While it is possible to call pyCMSdll functions directly, it's not highly
|
||||
recommended.
|
||||
|
||||
Version History:
|
||||
|
||||
1.0.0 pil Oct 2013 Port to LCMS 2.
|
||||
|
||||
0.1.0 pil mod March 10, 2009
|
||||
|
||||
Renamed display profile to proof profile. The proof
|
||||
profile is the profile of the device that is being
|
||||
simulated, not the profile of the device which is
|
||||
actually used to display/print the final simulation
|
||||
(that'd be the output profile) - also see LCMSAPI.txt
|
||||
input colorspace -> using 'renderingIntent' -> proof
|
||||
colorspace -> using 'proofRenderingIntent' -> output
|
||||
colorspace
|
||||
|
||||
Added LCMS FLAGS support.
|
||||
Added FLAGS["SOFTPROOFING"] as default flag for
|
||||
buildProofTransform (otherwise the proof profile/intent
|
||||
would be ignored).
|
||||
|
||||
0.1.0 pil March 2009 - added to PIL, as PIL.ImageCms
|
||||
|
||||
0.0.2 alpha Jan 6, 2002
|
||||
|
||||
Added try/except statements arount type() checks of
|
||||
potential CObjects... Python won't let you use type()
|
||||
on them, and raises a TypeError (stupid, if you ask
|
||||
me!)
|
||||
|
||||
Added buildProofTransformFromOpenProfiles() function.
|
||||
Additional fixes in DLL, see DLL code for details.
|
||||
|
||||
0.0.1 alpha first public release, Dec. 26, 2002
|
||||
|
||||
Known to-do list with current version (of Python interface, not pyCMSdll):
|
||||
|
||||
none
|
||||
|
||||
"""
|
||||
|
||||
VERSION = "1.0.0 pil"
|
||||
|
||||
# --------------------------------------------------------------------.
|
||||
|
||||
from PIL import Image
|
||||
try:
|
||||
from PIL import _imagingcms
|
||||
except ImportError as ex:
|
||||
# Allow error import for doc purposes, but error out when accessing
|
||||
# anything in core.
|
||||
from _util import deferred_error
|
||||
_imagingcms = deferred_error(ex)
|
||||
from PIL._util import isStringType
|
||||
|
||||
core = _imagingcms
|
||||
|
||||
#
|
||||
# intent/direction values
|
||||
|
||||
INTENT_PERCEPTUAL = 0
|
||||
INTENT_RELATIVE_COLORIMETRIC = 1
|
||||
INTENT_SATURATION = 2
|
||||
INTENT_ABSOLUTE_COLORIMETRIC = 3
|
||||
|
||||
DIRECTION_INPUT = 0
|
||||
DIRECTION_OUTPUT = 1
|
||||
DIRECTION_PROOF = 2
|
||||
|
||||
#
|
||||
# flags
|
||||
|
||||
FLAGS = {
|
||||
"MATRIXINPUT": 1,
|
||||
"MATRIXOUTPUT": 2,
|
||||
"MATRIXONLY": (1 | 2),
|
||||
"NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
|
||||
# Don't create prelinearization tables on precalculated transforms
|
||||
# (internal use):
|
||||
"NOPRELINEARIZATION": 16,
|
||||
"GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
|
||||
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
||||
"NOTPRECALC": 256,
|
||||
"NULLTRANSFORM": 512, # Don't transform anyway
|
||||
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
|
||||
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces
|
||||
"WHITEBLACKCOMPENSATION": 8192,
|
||||
"BLACKPOINTCOMPENSATION": 8192,
|
||||
"GAMUTCHECK": 4096, # Out of Gamut alarm
|
||||
"SOFTPROOFING": 16384, # Do softproofing
|
||||
"PRESERVEBLACK": 32768, # Black preservation
|
||||
"NODEFAULTRESOURCEDEF": 16777216, # CRD special
|
||||
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints
|
||||
}
|
||||
|
||||
_MAX_FLAG = 0
|
||||
for flag in FLAGS.values():
|
||||
if isinstance(flag, int):
|
||||
_MAX_FLAG = _MAX_FLAG | flag
|
||||
|
||||
|
||||
# --------------------------------------------------------------------.
|
||||
# Experimental PIL-level API
|
||||
# --------------------------------------------------------------------.
|
||||
|
||||
##
|
||||
# Profile.
|
||||
|
||||
class ImageCmsProfile:
|
||||
|
||||
def __init__(self, profile):
|
||||
"""
|
||||
:param profile: Either a string representing a filename,
|
||||
a file like object containing a profile or a
|
||||
low-level profile object
|
||||
|
||||
"""
|
||||
|
||||
if isStringType(profile):
|
||||
self._set(core.profile_open(profile), profile)
|
||||
elif hasattr(profile, "read"):
|
||||
self._set(core.profile_frombytes(profile.read()))
|
||||
else:
|
||||
self._set(profile) # assume it's already a profile
|
||||
|
||||
def _set(self, profile, filename=None):
|
||||
self.profile = profile
|
||||
self.filename = filename
|
||||
if profile:
|
||||
self.product_name = None # profile.product_name
|
||||
self.product_info = None # profile.product_info
|
||||
else:
|
||||
self.product_name = None
|
||||
self.product_info = None
|
||||
|
||||
def tobytes(self):
|
||||
"""
|
||||
Returns the profile in a format suitable for embedding in
|
||||
saved images.
|
||||
|
||||
:returns: a bytes object containing the ICC profile.
|
||||
"""
|
||||
|
||||
return core.profile_tobytes(self.profile)
|
||||
|
||||
|
||||
class ImageCmsTransform(Image.ImagePointHandler):
|
||||
|
||||
# Transform. This can be used with the procedural API, or with the
|
||||
# standard Image.point() method.
|
||||
#
|
||||
# Will return the output profile in the output.info['icc_profile'].
|
||||
|
||||
def __init__(self, input, output, input_mode, output_mode,
|
||||
intent=INTENT_PERCEPTUAL, proof=None,
|
||||
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
|
||||
if proof is None:
|
||||
self.transform = core.buildTransform(
|
||||
input.profile, output.profile,
|
||||
input_mode, output_mode,
|
||||
intent,
|
||||
flags
|
||||
)
|
||||
else:
|
||||
self.transform = core.buildProofTransform(
|
||||
input.profile, output.profile, proof.profile,
|
||||
input_mode, output_mode,
|
||||
intent, proof_intent,
|
||||
flags
|
||||
)
|
||||
# Note: inputMode and outputMode are for pyCMS compatibility only
|
||||
self.input_mode = self.inputMode = input_mode
|
||||
self.output_mode = self.outputMode = output_mode
|
||||
|
||||
self.output_profile = output
|
||||
|
||||
def point(self, im):
|
||||
return self.apply(im)
|
||||
|
||||
def apply(self, im, imOut=None):
|
||||
im.load()
|
||||
if imOut is None:
|
||||
imOut = Image.new(self.output_mode, im.size, None)
|
||||
self.transform.apply(im.im.id, imOut.im.id)
|
||||
imOut.info['icc_profile'] = self.output_profile.tobytes()
|
||||
return imOut
|
||||
|
||||
def apply_in_place(self, im):
|
||||
im.load()
|
||||
if im.mode != self.output_mode:
|
||||
raise ValueError("mode mismatch") # wrong output mode
|
||||
self.transform.apply(im.im.id, im.im.id)
|
||||
im.info['icc_profile'] = self.output_profile.tobytes()
|
||||
return im
|
||||
|
||||
|
||||
def get_display_profile(handle=None):
|
||||
""" (experimental) Fetches the profile for the current display device.
|
||||
:returns: None if the profile is not known.
|
||||
"""
|
||||
|
||||
import sys
|
||||
if sys.platform == "win32":
|
||||
from PIL import ImageWin
|
||||
if isinstance(handle, ImageWin.HDC):
|
||||
profile = core.get_display_profile_win32(handle, 1)
|
||||
else:
|
||||
profile = core.get_display_profile_win32(handle or 0)
|
||||
else:
|
||||
try:
|
||||
get = _imagingcms.get_display_profile
|
||||
except AttributeError:
|
||||
return None
|
||||
else:
|
||||
profile = get()
|
||||
return ImageCmsProfile(profile)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------.
|
||||
# pyCMS compatible layer
|
||||
# --------------------------------------------------------------------.
|
||||
|
||||
class PyCMSError(Exception):
|
||||
|
||||
""" (pyCMS) Exception class.
|
||||
This is used for all errors in the pyCMS API. """
|
||||
pass
|
||||
|
||||
|
||||
def profileToProfile(
|
||||
im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL,
|
||||
outputMode=None, inPlace=0, flags=0):
|
||||
"""
|
||||
(pyCMS) Applies an ICC transformation to a given image, mapping from
|
||||
inputProfile to outputProfile.
|
||||
|
||||
If the input or output profiles specified are not valid filenames, a
|
||||
PyCMSError will be raised. If inPlace == TRUE and outputMode != im.mode,
|
||||
a PyCMSError will be raised. If an error occurs during application of
|
||||
the profiles, a PyCMSError will be raised. If outputMode is not a mode
|
||||
supported by the outputProfile (or by pyCMS), a PyCMSError will be
|
||||
raised.
|
||||
|
||||
This function applies an ICC transformation to im from inputProfile's
|
||||
color space to outputProfile's color space using the specified rendering
|
||||
intent to decide how to handle out-of-gamut colors.
|
||||
|
||||
OutputMode can be used to specify that a color mode conversion is to
|
||||
be done using these profiles, but the specified profiles must be able
|
||||
to handle that mode. I.e., if converting im from RGB to CMYK using
|
||||
profiles, the input profile must handle RGB data, and the output
|
||||
profile must handle CMYK data.
|
||||
|
||||
:param im: An open PIL image object (i.e. Image.new(...) or
|
||||
Image.open(...), etc.)
|
||||
:param inputProfile: String, as a valid filename path to the ICC input
|
||||
profile you wish to use for this image, or a profile object
|
||||
:param outputProfile: String, as a valid filename path to the ICC output
|
||||
profile you wish to use for this image, or a profile object
|
||||
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
||||
wish to use for the transform
|
||||
|
||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||
|
||||
see the pyCMS documentation for details on rendering intents and what
|
||||
they do.
|
||||
:param outputMode: A valid PIL mode for the output image (i.e. "RGB",
|
||||
"CMYK", etc.). Note: if rendering the image "inPlace", outputMode
|
||||
MUST be the same mode as the input, or omitted completely. If
|
||||
omitted, the outputMode will be the same as the mode of the input
|
||||
image (im.mode)
|
||||
:param inPlace: Boolean (1 = True, None or 0 = False). If True, the
|
||||
original image is modified in-place, and None is returned. If False
|
||||
(default), a new Image object is returned with the transform applied.
|
||||
:param flags: Integer (0-...) specifying additional flags
|
||||
:returns: Either None or a new PIL image object, depending on value of
|
||||
inPlace
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
if outputMode is None:
|
||||
outputMode = im.mode
|
||||
|
||||
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
|
||||
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
||||
|
||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||
raise PyCMSError(
|
||||
"flags must be an integer between 0 and %s" + _MAX_FLAG)
|
||||
|
||||
try:
|
||||
if not isinstance(inputProfile, ImageCmsProfile):
|
||||
inputProfile = ImageCmsProfile(inputProfile)
|
||||
if not isinstance(outputProfile, ImageCmsProfile):
|
||||
outputProfile = ImageCmsProfile(outputProfile)
|
||||
transform = ImageCmsTransform(
|
||||
inputProfile, outputProfile, im.mode, outputMode,
|
||||
renderingIntent, flags=flags
|
||||
)
|
||||
if inPlace:
|
||||
transform.apply_in_place(im)
|
||||
imOut = None
|
||||
else:
|
||||
imOut = transform.apply(im)
|
||||
except (IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
return imOut
|
||||
|
||||
|
||||
def getOpenProfile(profileFilename):
|
||||
"""
|
||||
(pyCMS) Opens an ICC profile file.
|
||||
|
||||
The PyCMSProfile object can be passed back into pyCMS for use in creating
|
||||
transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
|
||||
|
||||
If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
|
||||
will be raised.
|
||||
|
||||
:param profileFilename: String, as a valid filename path to the ICC profile
|
||||
you wish to open, or a file-like object.
|
||||
:returns: A CmsProfile class object.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
try:
|
||||
return ImageCmsProfile(profileFilename)
|
||||
except (IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def buildTransform(
|
||||
inputProfile, outputProfile, inMode, outMode,
|
||||
renderingIntent=INTENT_PERCEPTUAL, flags=0):
|
||||
"""
|
||||
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
|
||||
outputProfile. Use applyTransform to apply the transform to a given
|
||||
image.
|
||||
|
||||
If the input or output profiles specified are not valid filenames, a
|
||||
PyCMSError will be raised. If an error occurs during creation of the
|
||||
transform, a PyCMSError will be raised.
|
||||
|
||||
If inMode or outMode are not a mode supported by the outputProfile (or
|
||||
by pyCMS), a PyCMSError will be raised.
|
||||
|
||||
This function builds and returns an ICC transform from the inputProfile
|
||||
to the outputProfile using the renderingIntent to determine what to do
|
||||
with out-of-gamut colors. It will ONLY work for converting images that
|
||||
are in inMode to images that are in outMode color format (PIL mode,
|
||||
i.e. "RGB", "RGBA", "CMYK", etc.).
|
||||
|
||||
Building the transform is a fair part of the overhead in
|
||||
ImageCms.profileToProfile(), so if you're planning on converting multiple
|
||||
images using the same input/output settings, this can save you time.
|
||||
Once you have a transform object, it can be used with
|
||||
ImageCms.applyProfile() to convert images without the need to re-compute
|
||||
the lookup table for the transform.
|
||||
|
||||
The reason pyCMS returns a class object rather than a handle directly
|
||||
to the transform is that it needs to keep track of the PIL input/output
|
||||
modes that the transform is meant for. These attributes are stored in
|
||||
the "inMode" and "outMode" attributes of the object (which can be
|
||||
manually overridden if you really want to, but I don't know of any
|
||||
time that would be of use, or would even work).
|
||||
|
||||
:param inputProfile: String, as a valid filename path to the ICC input
|
||||
profile you wish to use for this transform, or a profile object
|
||||
:param outputProfile: String, as a valid filename path to the ICC output
|
||||
profile you wish to use for this transform, or a profile object
|
||||
:param inMode: String, as a valid PIL mode that the appropriate profile
|
||||
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||
:param outMode: String, as a valid PIL mode that the appropriate profile
|
||||
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
||||
wish to use for the transform
|
||||
|
||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||
|
||||
see the pyCMS documentation for details on rendering intents and what
|
||||
they do.
|
||||
:param flags: Integer (0-...) specifying additional flags
|
||||
:returns: A CmsTransform class object.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
|
||||
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
||||
|
||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||
raise PyCMSError(
|
||||
"flags must be an integer between 0 and %s" + _MAX_FLAG)
|
||||
|
||||
try:
|
||||
if not isinstance(inputProfile, ImageCmsProfile):
|
||||
inputProfile = ImageCmsProfile(inputProfile)
|
||||
if not isinstance(outputProfile, ImageCmsProfile):
|
||||
outputProfile = ImageCmsProfile(outputProfile)
|
||||
return ImageCmsTransform(
|
||||
inputProfile, outputProfile, inMode, outMode,
|
||||
renderingIntent, flags=flags)
|
||||
except (IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def buildProofTransform(
|
||||
inputProfile, outputProfile, proofProfile, inMode, outMode,
|
||||
renderingIntent=INTENT_PERCEPTUAL,
|
||||
proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC,
|
||||
flags=FLAGS["SOFTPROOFING"]):
|
||||
"""
|
||||
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
|
||||
outputProfile, but tries to simulate the result that would be
|
||||
obtained on the proofProfile device.
|
||||
|
||||
If the input, output, or proof profiles specified are not valid
|
||||
filenames, a PyCMSError will be raised.
|
||||
|
||||
If an error occurs during creation of the transform, a PyCMSError will
|
||||
be raised.
|
||||
|
||||
If inMode or outMode are not a mode supported by the outputProfile
|
||||
(or by pyCMS), a PyCMSError will be raised.
|
||||
|
||||
This function builds and returns an ICC transform from the inputProfile
|
||||
to the outputProfile, but tries to simulate the result that would be
|
||||
obtained on the proofProfile device using renderingIntent and
|
||||
proofRenderingIntent to determine what to do with out-of-gamut
|
||||
colors. This is known as "soft-proofing". It will ONLY work for
|
||||
converting images that are in inMode to images that are in outMode
|
||||
color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.).
|
||||
|
||||
Usage of the resulting transform object is exactly the same as with
|
||||
ImageCms.buildTransform().
|
||||
|
||||
Proof profiling is generally used when using an output device to get a
|
||||
good idea of what the final printed/displayed image would look like on
|
||||
the proofProfile device when it's quicker and easier to use the
|
||||
output device for judging color. Generally, this means that the
|
||||
output device is a monitor, or a dye-sub printer (etc.), and the simulated
|
||||
device is something more expensive, complicated, or time consuming
|
||||
(making it difficult to make a real print for color judgement purposes).
|
||||
|
||||
Soft-proofing basically functions by adjusting the colors on the
|
||||
output device to match the colors of the device being simulated. However,
|
||||
when the simulated device has a much wider gamut than the output
|
||||
device, you may obtain marginal results.
|
||||
|
||||
:param inputProfile: String, as a valid filename path to the ICC input
|
||||
profile you wish to use for this transform, or a profile object
|
||||
:param outputProfile: String, as a valid filename path to the ICC output
|
||||
(monitor, usually) profile you wish to use for this transform, or a
|
||||
profile object
|
||||
:param proofProfile: String, as a valid filename path to the ICC proof
|
||||
profile you wish to use for this transform, or a profile object
|
||||
:param inMode: String, as a valid PIL mode that the appropriate profile
|
||||
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||
:param outMode: String, as a valid PIL mode that the appropriate profile
|
||||
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
||||
wish to use for the input->proof (simulated) transform
|
||||
|
||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||
|
||||
see the pyCMS documentation for details on rendering intents and what
|
||||
they do.
|
||||
:param proofRenderingIntent: Integer (0-3) specifying the rendering intent you
|
||||
wish to use for proof->output transform
|
||||
|
||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||
|
||||
see the pyCMS documentation for details on rendering intents and what
|
||||
they do.
|
||||
:param flags: Integer (0-...) specifying additional flags
|
||||
:returns: A CmsTransform class object.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
|
||||
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
||||
|
||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||
raise PyCMSError(
|
||||
"flags must be an integer between 0 and %s" + _MAX_FLAG)
|
||||
|
||||
try:
|
||||
if not isinstance(inputProfile, ImageCmsProfile):
|
||||
inputProfile = ImageCmsProfile(inputProfile)
|
||||
if not isinstance(outputProfile, ImageCmsProfile):
|
||||
outputProfile = ImageCmsProfile(outputProfile)
|
||||
if not isinstance(proofProfile, ImageCmsProfile):
|
||||
proofProfile = ImageCmsProfile(proofProfile)
|
||||
return ImageCmsTransform(
|
||||
inputProfile, outputProfile, inMode, outMode, renderingIntent,
|
||||
proofProfile, proofRenderingIntent, flags)
|
||||
except (IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
buildTransformFromOpenProfiles = buildTransform
|
||||
buildProofTransformFromOpenProfiles = buildProofTransform
|
||||
|
||||
|
||||
def applyTransform(im, transform, inPlace=0):
|
||||
"""
|
||||
(pyCMS) Applies a transform to a given image.
|
||||
|
||||
If im.mode != transform.inMode, a PyCMSError is raised.
|
||||
|
||||
If inPlace == TRUE and transform.inMode != transform.outMode, a
|
||||
PyCMSError is raised.
|
||||
|
||||
If im.mode, transfer.inMode, or transfer.outMode is not supported by
|
||||
pyCMSdll or the profiles you used for the transform, a PyCMSError is
|
||||
raised.
|
||||
|
||||
If an error occurs while the transform is being applied, a PyCMSError
|
||||
is raised.
|
||||
|
||||
This function applies a pre-calculated transform (from
|
||||
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
|
||||
to an image. The transform can be used for multiple images, saving
|
||||
considerable calcuation time if doing the same conversion multiple times.
|
||||
|
||||
If you want to modify im in-place instead of receiving a new image as
|
||||
the return value, set inPlace to TRUE. This can only be done if
|
||||
transform.inMode and transform.outMode are the same, because we can't
|
||||
change the mode in-place (the buffer sizes for some modes are
|
||||
different). The default behavior is to return a new Image object of
|
||||
the same dimensions in mode transform.outMode.
|
||||
|
||||
:param im: A PIL Image object, and im.mode must be the same as the inMode
|
||||
supported by the transform.
|
||||
:param transform: A valid CmsTransform class object
|
||||
:param inPlace: Bool (1 == True, 0 or None == False). If True, im is
|
||||
modified in place and None is returned, if False, a new Image object
|
||||
with the transform applied is returned (and im is not changed). The
|
||||
default is False.
|
||||
:returns: Either None, or a new PIL Image object, depending on the value of
|
||||
inPlace. The profile will be returned in the image's info['icc_profile'].
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
try:
|
||||
if inPlace:
|
||||
transform.apply_in_place(im)
|
||||
imOut = None
|
||||
else:
|
||||
imOut = transform.apply(im)
|
||||
except (TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
return imOut
|
||||
|
||||
|
||||
def createProfile(colorSpace, colorTemp=-1):
|
||||
"""
|
||||
(pyCMS) Creates a profile.
|
||||
|
||||
If colorSpace not in ["LAB", "XYZ", "sRGB"], a PyCMSError is raised
|
||||
|
||||
If using LAB and colorTemp != a positive integer, a PyCMSError is raised.
|
||||
|
||||
If an error occurs while creating the profile, a PyCMSError is raised.
|
||||
|
||||
Use this function to create common profiles on-the-fly instead of
|
||||
having to supply a profile on disk and knowing the path to it. It
|
||||
returns a normal CmsProfile object that can be passed to
|
||||
ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
|
||||
to images.
|
||||
|
||||
:param colorSpace: String, the color space of the profile you wish to
|
||||
create.
|
||||
Currently only "LAB", "XYZ", and "sRGB" are supported.
|
||||
:param colorTemp: Positive integer for the white point for the profile, in
|
||||
degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
|
||||
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
|
||||
profiles, and is ignored for XYZ and sRGB.
|
||||
:returns: A CmsProfile class object
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
if colorSpace not in ["LAB", "XYZ", "sRGB"]:
|
||||
raise PyCMSError(
|
||||
"Color space not supported for on-the-fly profile creation (%s)"
|
||||
% colorSpace)
|
||||
|
||||
if colorSpace == "LAB":
|
||||
try:
|
||||
colorTemp = float(colorTemp)
|
||||
except:
|
||||
raise PyCMSError(
|
||||
"Color temperature must be numeric, \"%s\" not valid"
|
||||
% colorTemp)
|
||||
|
||||
try:
|
||||
return core.createProfile(colorSpace, colorTemp)
|
||||
except (TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def getProfileName(profile):
|
||||
"""
|
||||
|
||||
(pyCMS) Gets the internal product name for the given profile.
|
||||
|
||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||
a PyCMSError is raised If an error occurs while trying to obtain the
|
||||
name tag, a PyCMSError is raised.
|
||||
|
||||
Use this function to obtain the INTERNAL name of the profile (stored
|
||||
in an ICC tag in the profile itself), usually the one used when the
|
||||
profile was originally created. Sometimes this tag also contains
|
||||
additional information supplied by the creator.
|
||||
|
||||
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||
filename of an ICC profile.
|
||||
:returns: A string containing the internal name of the profile as stored
|
||||
in an ICC tag.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
try:
|
||||
# add an extra newline to preserve pyCMS compatibility
|
||||
if not isinstance(profile, ImageCmsProfile):
|
||||
profile = ImageCmsProfile(profile)
|
||||
# do it in python, not c.
|
||||
# // name was "%s - %s" (model, manufacturer) || Description ,
|
||||
# // but if the Model and Manufacturer were the same or the model
|
||||
# // was long, Just the model, in 1.x
|
||||
model = profile.profile.product_model
|
||||
manufacturer = profile.profile.product_manufacturer
|
||||
|
||||
if not (model or manufacturer):
|
||||
return profile.profile.product_description + "\n"
|
||||
if not manufacturer or len(model) > 30:
|
||||
return model + "\n"
|
||||
return "%s - %s\n" % (model, manufacturer)
|
||||
|
||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def getProfileInfo(profile):
|
||||
"""
|
||||
(pyCMS) Gets the internal product information for the given profile.
|
||||
|
||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||
a PyCMSError is raised.
|
||||
|
||||
If an error occurs while trying to obtain the info tag, a PyCMSError
|
||||
is raised
|
||||
|
||||
Use this function to obtain the information stored in the profile's
|
||||
info tag. This often contains details about the profile, and how it
|
||||
was created, as supplied by the creator.
|
||||
|
||||
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||
filename of an ICC profile.
|
||||
:returns: A string containing the internal profile information stored in
|
||||
an ICC tag.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
try:
|
||||
if not isinstance(profile, ImageCmsProfile):
|
||||
profile = ImageCmsProfile(profile)
|
||||
# add an extra newline to preserve pyCMS compatibility
|
||||
# Python, not C. the white point bits weren't working well,
|
||||
# so skipping.
|
||||
# // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
|
||||
description = profile.profile.product_description
|
||||
cpright = profile.profile.product_copyright
|
||||
arr = []
|
||||
for elt in (description, cpright):
|
||||
if elt:
|
||||
arr.append(elt)
|
||||
return "\r\n\r\n".join(arr) + "\r\n\r\n"
|
||||
|
||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def getProfileCopyright(profile):
|
||||
"""
|
||||
(pyCMS) Gets the copyright for the given profile.
|
||||
|
||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||
a PyCMSError is raised.
|
||||
|
||||
If an error occurs while trying to obtain the copyright tag, a PyCMSError
|
||||
is raised
|
||||
|
||||
Use this function to obtain the information stored in the profile's
|
||||
copyright tag.
|
||||
|
||||
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||
filename of an ICC profile.
|
||||
:returns: A string containing the internal profile information stored in
|
||||
an ICC tag.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
try:
|
||||
# add an extra newline to preserve pyCMS compatibility
|
||||
if not isinstance(profile, ImageCmsProfile):
|
||||
profile = ImageCmsProfile(profile)
|
||||
return profile.profile.product_copyright + "\n"
|
||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def getProfileManufacturer(profile):
|
||||
"""
|
||||
(pyCMS) Gets the manufacturer for the given profile.
|
||||
|
||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||
a PyCMSError is raised.
|
||||
|
||||
If an error occurs while trying to obtain the manufacturer tag, a
|
||||
PyCMSError is raised
|
||||
|
||||
Use this function to obtain the information stored in the profile's
|
||||
manufacturer tag.
|
||||
|
||||
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||
filename of an ICC profile.
|
||||
:returns: A string containing the internal profile information stored in
|
||||
an ICC tag.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
try:
|
||||
# add an extra newline to preserve pyCMS compatibility
|
||||
if not isinstance(profile, ImageCmsProfile):
|
||||
profile = ImageCmsProfile(profile)
|
||||
return profile.profile.product_manufacturer + "\n"
|
||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def getProfileModel(profile):
|
||||
"""
|
||||
(pyCMS) Gets the model for the given profile.
|
||||
|
||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||
a PyCMSError is raised.
|
||||
|
||||
If an error occurs while trying to obtain the model tag, a PyCMSError
|
||||
is raised
|
||||
|
||||
Use this function to obtain the information stored in the profile's
|
||||
model tag.
|
||||
|
||||
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||
filename of an ICC profile.
|
||||
:returns: A string containing the internal profile information stored in
|
||||
an ICC tag.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
try:
|
||||
# add an extra newline to preserve pyCMS compatibility
|
||||
if not isinstance(profile, ImageCmsProfile):
|
||||
profile = ImageCmsProfile(profile)
|
||||
return profile.profile.product_model + "\n"
|
||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def getProfileDescription(profile):
|
||||
"""
|
||||
(pyCMS) Gets the description for the given profile.
|
||||
|
||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||
a PyCMSError is raised.
|
||||
|
||||
If an error occurs while trying to obtain the description tag, a PyCMSError
|
||||
is raised
|
||||
|
||||
Use this function to obtain the information stored in the profile's
|
||||
description tag.
|
||||
|
||||
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||
filename of an ICC profile.
|
||||
:returns: A string containing the internal profile information stored in an
|
||||
ICC tag.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
try:
|
||||
# add an extra newline to preserve pyCMS compatibility
|
||||
if not isinstance(profile, ImageCmsProfile):
|
||||
profile = ImageCmsProfile(profile)
|
||||
return profile.profile.product_description + "\n"
|
||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def getDefaultIntent(profile):
|
||||
"""
|
||||
(pyCMS) Gets the default intent name for the given profile.
|
||||
|
||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||
a PyCMSError is raised.
|
||||
|
||||
If an error occurs while trying to obtain the default intent, a
|
||||
PyCMSError is raised.
|
||||
|
||||
Use this function to determine the default (and usually best optomized)
|
||||
rendering intent for this profile. Most profiles support multiple
|
||||
rendering intents, but are intended mostly for one type of conversion.
|
||||
If you wish to use a different intent than returned, use
|
||||
ImageCms.isIntentSupported() to verify it will work first.
|
||||
|
||||
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||
filename of an ICC profile.
|
||||
:returns: Integer 0-3 specifying the default rendering intent for this
|
||||
profile.
|
||||
|
||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||
|
||||
see the pyCMS documentation for details on rendering intents and what
|
||||
they do.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
try:
|
||||
if not isinstance(profile, ImageCmsProfile):
|
||||
profile = ImageCmsProfile(profile)
|
||||
return profile.profile.rendering_intent
|
||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def isIntentSupported(profile, intent, direction):
|
||||
"""
|
||||
(pyCMS) Checks if a given intent is supported.
|
||||
|
||||
Use this function to verify that you can use your desired
|
||||
renderingIntent with profile, and that profile can be used for the
|
||||
input/output/proof profile as you desire.
|
||||
|
||||
Some profiles are created specifically for one "direction", can cannot
|
||||
be used for others. Some profiles can only be used for certain
|
||||
rendering intents... so it's best to either verify this before trying
|
||||
to create a transform with them (using this function), or catch the
|
||||
potential PyCMSError that will occur if they don't support the modes
|
||||
you select.
|
||||
|
||||
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||
filename of an ICC profile.
|
||||
:param intent: Integer (0-3) specifying the rendering intent you wish to
|
||||
use with this profile
|
||||
|
||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||
|
||||
see the pyCMS documentation for details on rendering intents and what
|
||||
they do.
|
||||
:param direction: Integer specifing if the profile is to be used for input,
|
||||
output, or proof
|
||||
|
||||
INPUT = 0 (or use ImageCms.DIRECTION_INPUT)
|
||||
OUTPUT = 1 (or use ImageCms.DIRECTION_OUTPUT)
|
||||
PROOF = 2 (or use ImageCms.DIRECTION_PROOF)
|
||||
|
||||
:returns: 1 if the intent/direction are supported, -1 if they are not.
|
||||
:exception PyCMSError:
|
||||
"""
|
||||
|
||||
try:
|
||||
if not isinstance(profile, ImageCmsProfile):
|
||||
profile = ImageCmsProfile(profile)
|
||||
# FIXME: I get different results for the same data w. different
|
||||
# compilers. Bug in LittleCMS or in the binding?
|
||||
if profile.profile.is_intent_supported(intent, direction):
|
||||
return 1
|
||||
else:
|
||||
return -1
|
||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v)
|
||||
|
||||
|
||||
def versions():
|
||||
"""
|
||||
(pyCMS) Fetches versions.
|
||||
"""
|
||||
|
||||
import sys
|
||||
return (
|
||||
VERSION, core.littlecms_version,
|
||||
sys.version.split()[0], Image.VERSION
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
# create a cheap manual from the __doc__ strings for the functions above
|
||||
|
||||
from PIL import ImageCms
|
||||
print(__doc__)
|
||||
|
||||
for f in dir(ImageCms):
|
||||
doc = None
|
||||
try:
|
||||
exec("doc = %s.__doc__" % (f))
|
||||
if "pyCMS" in doc:
|
||||
# so we don't get the __doc__ string for imported modules
|
||||
print("=" * 80)
|
||||
print("%s" % f)
|
||||
print(doc)
|
||||
except (AttributeError, TypeError):
|
||||
pass
|
||||
|
||||
# End of file
|
||||
|
|
@ -1,279 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# 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
|
||||
# 2009-03-05 fl Fixed rounding error in grayscale calculation
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
import re
|
||||
|
||||
|
||||
def getrgb(color):
|
||||
"""
|
||||
Convert a color string to an RGB tuple. If the string cannot be parsed,
|
||||
this function raises a :py:exc:`ValueError` exception.
|
||||
|
||||
.. versionadded:: 1.1.4
|
||||
|
||||
:param color: A color string
|
||||
:return: ``(red, green, blue[, alpha])``
|
||||
"""
|
||||
try:
|
||||
rgb = colormap[color]
|
||||
except KeyError:
|
||||
try:
|
||||
# fall back on case-insensitive lookup
|
||||
rgb = colormap[color.lower()]
|
||||
except KeyError:
|
||||
rgb = None
|
||||
# found color in cache
|
||||
if rgb:
|
||||
if isinstance(rgb, tuple):
|
||||
return rgb
|
||||
colormap[color] = rgb = getrgb(rgb)
|
||||
return rgb
|
||||
# check for known string formats
|
||||
m = re.match("#\w\w\w$", color)
|
||||
if m:
|
||||
return (
|
||||
int(color[1]*2, 16),
|
||||
int(color[2]*2, 16),
|
||||
int(color[3]*2, 16)
|
||||
)
|
||||
m = re.match("#\w\w\w\w\w\w$", color)
|
||||
if m:
|
||||
return (
|
||||
int(color[1:3], 16),
|
||||
int(color[3:5], 16),
|
||||
int(color[5:7], 16)
|
||||
)
|
||||
m = re.match("rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
||||
if m:
|
||||
return (
|
||||
int(m.group(1)),
|
||||
int(m.group(2)),
|
||||
int(m.group(3))
|
||||
)
|
||||
m = re.match("rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
||||
if m:
|
||||
return (
|
||||
int((int(m.group(1)) * 255) / 100.0 + 0.5),
|
||||
int((int(m.group(2)) * 255) / 100.0 + 0.5),
|
||||
int((int(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)
|
||||
)
|
||||
m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$",
|
||||
color)
|
||||
if m:
|
||||
return (
|
||||
int(m.group(1)),
|
||||
int(m.group(2)),
|
||||
int(m.group(3)),
|
||||
int(m.group(4))
|
||||
)
|
||||
raise ValueError("unknown color specifier: %r" % color)
|
||||
|
||||
|
||||
def getcolor(color, mode):
|
||||
"""
|
||||
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
|
||||
greyscale value if the mode is not color or a palette image. If the string
|
||||
cannot be parsed, this function raises a :py:exc:`ValueError` exception.
|
||||
|
||||
.. versionadded:: 1.1.4
|
||||
|
||||
:param color: A color string
|
||||
:return: ``(graylevel [, alpha]) or (red, green, blue[, alpha])``
|
||||
"""
|
||||
# same as getrgb, but converts the result to the given mode
|
||||
color, alpha = getrgb(color), 255
|
||||
if len(color) == 4:
|
||||
color, alpha = color[0:3], color[3]
|
||||
|
||||
if Image.getmodebase(mode) == "L":
|
||||
r, g, b = color
|
||||
color = (r*299 + g*587 + b*114)//1000
|
||||
if mode[-1] == 'A':
|
||||
return (color, alpha)
|
||||
else:
|
||||
if mode[-1] == 'A':
|
||||
return color + (alpha,)
|
||||
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",
|
||||
}
|
||||
|
|
@ -1,383 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# 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 numbers
|
||||
|
||||
from PIL import Image, ImageColor
|
||||
from PIL._util import isStringType
|
||||
|
||||
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 writeable
|
||||
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 isStringType(ink):
|
||||
ink = ImageColor.getcolor(ink, self.mode)
|
||||
if self.palette and not isinstance(ink, numbers.Number):
|
||||
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
|
||||
from PIL 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 isStringType(ink):
|
||||
ink = ImageColor.getcolor(ink, self.mode)
|
||||
if self.palette and not isinstance(ink, numbers.Number):
|
||||
ink = self.palette.getcolor(ink)
|
||||
ink = self.draw.draw_ink(ink, self.mode)
|
||||
if fill is not None:
|
||||
if isStringType(fill):
|
||||
fill = ImageColor.getcolor(fill, self.mode)
|
||||
if self.palette and not isinstance(fill, numbers.Number):
|
||||
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:
|
||||
from PIL import _imagingagg as handler
|
||||
except ImportError:
|
||||
pass
|
||||
if handler is None:
|
||||
from PIL import ImageDraw2 as handler
|
||||
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
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from PIL 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, offset):
|
||||
(xoffset, yoffset) = offset
|
||||
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 line(self, xy, *options):
|
||||
self.render("line", xy, *options)
|
||||
|
||||
def pieslice(self, xy, start, end, *options):
|
||||
self.render("pieslice", xy, start, end, *options)
|
||||
|
||||
def polygon(self, xy, *options):
|
||||
self.render("polygon", xy, *options)
|
||||
|
||||
def rectangle(self, xy, *options):
|
||||
self.render("rectangle", 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)
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# image enhancement classes
|
||||
#
|
||||
# For a background, see "Image Processing By Interpolation and
|
||||
# Extrapolation", Paul Haeberli and Douglas Voorhies. Available
|
||||
# at http://www.graficaobscura.com/interp/index.html
|
||||
#
|
||||
# History:
|
||||
# 1996-03-23 fl Created
|
||||
# 2009-06-16 fl Fixed mean calculation
|
||||
#
|
||||
# Copyright (c) Secret Labs AB 1997.
|
||||
# Copyright (c) Fredrik Lundh 1996.
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from PIL import Image, ImageFilter, ImageStat
|
||||
|
||||
|
||||
class _Enhance:
|
||||
|
||||
def enhance(self, factor):
|
||||
"""
|
||||
Returns an enhanced image.
|
||||
|
||||
:param factor: A floating point value controlling the enhancement.
|
||||
Factor 1.0 always returns a copy of the original image,
|
||||
lower factors mean less color (brightness, contrast,
|
||||
etc), and higher values more. There are no restrictions
|
||||
on this value.
|
||||
:rtype: :py:class:`~PIL.Image.Image`
|
||||
"""
|
||||
return Image.blend(self.degenerate, self.image, factor)
|
||||
|
||||
|
||||
class Color(_Enhance):
|
||||
"""Adjust image color balance.
|
||||
|
||||
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.
|
||||
"""
|
||||
def __init__(self, image):
|
||||
self.image = image
|
||||
self.intermediate_mode = 'L'
|
||||
if 'A' in image.getbands():
|
||||
self.intermediate_mode = 'LA'
|
||||
|
||||
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
|
||||
|
||||
class Contrast(_Enhance):
|
||||
"""Adjust image contrast.
|
||||
|
||||
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 a solid grey image. A factor of 1.0 gives the original image.
|
||||
"""
|
||||
def __init__(self, image):
|
||||
self.image = image
|
||||
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
|
||||
self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
|
||||
|
||||
if 'A' in image.getbands():
|
||||
self.degenerate.putalpha(image.split()[-1])
|
||||
|
||||
|
||||
class Brightness(_Enhance):
|
||||
"""Adjust image brightness.
|
||||
|
||||
This class can be used to control the brighntess of an image. An
|
||||
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
|
||||
original image.
|
||||
"""
|
||||
def __init__(self, image):
|
||||
self.image = image
|
||||
self.degenerate = Image.new(image.mode, image.size, 0)
|
||||
|
||||
if 'A' in image.getbands():
|
||||
self.degenerate.putalpha(image.split()[-1])
|
||||
|
||||
|
||||
class Sharpness(_Enhance):
|
||||
"""Adjust image sharpness.
|
||||
|
||||
This class can be used to adjust the sharpness of an image. An
|
||||
enhancement factor of 0.0 gives a blurred image, a factor of 1.0 gives the
|
||||
original image, and a factor of 2.0 gives a sharpened image.
|
||||
"""
|
||||
def __init__(self, image):
|
||||
self.image = image
|
||||
self.degenerate = image.filter(ImageFilter.SMOOTH)
|
||||
|
||||
if 'A' in image.getbands():
|
||||
self.degenerate.putalpha(image.split()[-1])
|
||||
|
|
@ -1,523 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isPath
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
MAXBLOCK = 65536
|
||||
|
||||
SAFEBLOCK = 1024*1024
|
||||
|
||||
LOAD_TRUNCATED_IMAGES = False
|
||||
|
||||
ERRORS = {
|
||||
-1: "image buffer overrun error",
|
||||
-2: "decoding error",
|
||||
-3: "unknown error",
|
||||
-8: "bad configuration",
|
||||
-9: "out of memory error"
|
||||
}
|
||||
|
||||
|
||||
def raise_ioerror(error):
|
||||
try:
|
||||
message = Image.core.getcodecstatus(error)
|
||||
except AttributeError:
|
||||
message = ERRORS.get(error)
|
||||
if not message:
|
||||
message = "decoder error %d" % error
|
||||
raise IOError(message + " when reading image file")
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
def _tilesort(t):
|
||||
# sort on offset
|
||||
return t[2]
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# ImageFile base class
|
||||
|
||||
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 isPath(fp):
|
||||
# filename
|
||||
self.fp = open(fp, "rb")
|
||||
self.filename = fp
|
||||
else:
|
||||
# stream
|
||||
self.fp = fp
|
||||
self.filename = filename
|
||||
|
||||
try:
|
||||
self._open()
|
||||
except IndexError as v: # end of data
|
||||
if Image.DEBUG > 1:
|
||||
traceback.print_exc()
|
||||
raise SyntaxError(v)
|
||||
except TypeError as v: # end of data (ord)
|
||||
if Image.DEBUG > 1:
|
||||
traceback.print_exc()
|
||||
raise SyntaxError(v)
|
||||
except KeyError as v: # unsupported mode
|
||||
if Image.DEBUG > 1:
|
||||
traceback.print_exc()
|
||||
raise SyntaxError(v)
|
||||
except EOFError as 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
|
||||
use_mmap = self.filename and len(self.tile) == 1
|
||||
# As of pypy 2.1.0, memory mapping was failing here.
|
||||
use_mmap = use_mmap and not hasattr(sys, 'pypy_version_info')
|
||||
|
||||
readonly = 0
|
||||
|
||||
# look for read/seek overrides
|
||||
try:
|
||||
read = self.load_read
|
||||
# don't use mmap if there are custom read/seek functions
|
||||
use_mmap = False
|
||||
except AttributeError:
|
||||
read = self.fp.read
|
||||
|
||||
try:
|
||||
seek = self.load_seek
|
||||
use_mmap = False
|
||||
except AttributeError:
|
||||
seek = self.fp.seek
|
||||
|
||||
if use_mmap:
|
||||
# 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()
|
||||
|
||||
if not self.map:
|
||||
# sort tiles in file order
|
||||
self.tile.sort(key=_tilesort)
|
||||
|
||||
try:
|
||||
# FIXME: This is a hack to handle TIFF's JpegTables tag.
|
||||
prefix = self.tile_prefix
|
||||
except AttributeError:
|
||||
prefix = b""
|
||||
|
||||
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 True:
|
||||
try:
|
||||
s = read(self.decodermaxblock)
|
||||
except IndexError as ie: # truncated png/gif
|
||||
if LOAD_TRUNCATED_IMAGES:
|
||||
break
|
||||
else:
|
||||
raise IndexError(ie)
|
||||
|
||||
if not s and not d.handles_eof: # truncated jpeg
|
||||
self.tile = []
|
||||
|
||||
# JpegDecode needs to clean things up here either way
|
||||
# If we don't destroy the decompressor,
|
||||
# we have a memory leak.
|
||||
d.cleanup()
|
||||
|
||||
if LOAD_TRUNCATED_IMAGES:
|
||||
break
|
||||
else:
|
||||
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
|
||||
# Need to cleanup here to prevent leaks in PyPy
|
||||
d.cleanup()
|
||||
|
||||
self.tile = []
|
||||
self.readonly = readonly
|
||||
|
||||
self.fp = None # might be shared
|
||||
|
||||
if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0:
|
||||
# still raised if decoder fails to return anything
|
||||
raise_ioerror(e)
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
class StubImageFile(ImageFile):
|
||||
"""
|
||||
Base class for stub image loaders.
|
||||
|
||||
A stub loader is an image loader that can identify files of a
|
||||
certain format, but relies on external code to load the file.
|
||||
"""
|
||||
|
||||
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__
|
||||
|
||||
def _load(self):
|
||||
"(Hook) Find actual image loader."
|
||||
raise NotImplementedError(
|
||||
"StubImageFile subclass must implement _load"
|
||||
)
|
||||
|
||||
|
||||
class Parser:
|
||||
"""
|
||||
Incremental image parser. This class implements the standard
|
||||
feed/close consumer interface.
|
||||
|
||||
In Python 2.x, this is an old-style class.
|
||||
"""
|
||||
incremental = None
|
||||
image = None
|
||||
data = None
|
||||
decoder = None
|
||||
finished = 0
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
(Consumer) Reset the parser. Note that you can only call this
|
||||
method immediately after you've created a parser; parser
|
||||
instances cannot be reused.
|
||||
"""
|
||||
assert self.data is None, "cannot reuse parsers"
|
||||
|
||||
def feed(self, data):
|
||||
"""
|
||||
(Consumer) Feed data to the parser.
|
||||
|
||||
:param data: A string buffer.
|
||||
:exception IOError: If the parser failed to parse the image file.
|
||||
"""
|
||||
# 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
|
||||
raise_ioerror(e)
|
||||
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 = io.BytesIO(self.data)
|
||||
im = Image.open(fp)
|
||||
finally:
|
||||
fp.close() # explicitly close the virtual file
|
||||
except IOError:
|
||||
# traceback.print_exc()
|
||||
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
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
(Consumer) Close the stream.
|
||||
|
||||
:returns: An image object.
|
||||
:exception IOError: If the parser failed to parse the image file either
|
||||
because it cannot be identified or cannot be
|
||||
decoded.
|
||||
"""
|
||||
# finish decoding
|
||||
if self.decoder:
|
||||
# get rid of what's left in the buffers
|
||||
self.feed(b"")
|
||||
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 = io.BytesIO(self.data)
|
||||
self.image = Image.open(fp)
|
||||
finally:
|
||||
self.image.load()
|
||||
fp.close() # explicitly close the virtual file
|
||||
return self.image
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
def _save(im, fp, tile, bufsize=0):
|
||||
"""Helper to save image based on tile list
|
||||
|
||||
:param im: Image object.
|
||||
:param fp: File object.
|
||||
:param tile: Tile list.
|
||||
:param bufsize: Optional buffer size
|
||||
"""
|
||||
|
||||
im.load()
|
||||
if not hasattr(im, "encoderconfig"):
|
||||
im.encoderconfig = ()
|
||||
tile.sort(key=_tilesort)
|
||||
# FIXME: make MAXBLOCK a configuration parameter
|
||||
# It would be great if we could have the encoder specify what it needs
|
||||
# But, it would need at least the image size in most cases. RawEncode is
|
||||
# a tricky case.
|
||||
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
|
||||
try:
|
||||
fh = fp.fileno()
|
||||
fp.flush()
|
||||
except (AttributeError, io.UnsupportedOperation):
|
||||
# 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 True:
|
||||
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)
|
||||
e.cleanup()
|
||||
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)
|
||||
e.cleanup()
|
||||
try:
|
||||
fp.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def _safe_read(fp, size):
|
||||
"""
|
||||
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.
|
||||
:returns: A string containing up to <i>size</i> bytes of data.
|
||||
"""
|
||||
if size <= 0:
|
||||
return b""
|
||||
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 -= len(block)
|
||||
return b"".join(data)
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
"""
|
||||
The **ImageFileIO** module can be used to read an image from a
|
||||
socket, or any other stream device.
|
||||
|
||||
Deprecated. New code should use the :class:`PIL.ImageFile.Parser`
|
||||
class in the :mod:`PIL.ImageFile` module instead.
|
||||
|
||||
.. seealso:: modules :class:`PIL.ImageFile.Parser`
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
class ImageFileIO(BytesIO):
|
||||
def __init__(self, fp):
|
||||
"""
|
||||
Adds buffering to a stream file object, in order to
|
||||
provide **seek** and **tell** methods required
|
||||
by the :func:`PIL.Image.Image.open` method. The stream object must
|
||||
implement **read** and **close** methods.
|
||||
|
||||
:param fp: Stream file handle.
|
||||
|
||||
.. seealso:: modules :func:`PIL.Image.open`
|
||||
"""
|
||||
data = fp.read()
|
||||
BytesIO.__init__(self, data)
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from functools import reduce
|
||||
|
||||
|
||||
class Filter(object):
|
||||
pass
|
||||
|
||||
|
||||
class Kernel(Filter):
|
||||
"""
|
||||
Create a convolution kernel. The current version only
|
||||
supports 3x3 and 5x5 integer and floating point kernels.
|
||||
|
||||
In the current version, kernels can only be applied to
|
||||
"L" and "RGB" images.
|
||||
|
||||
: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 scale: Scale factor. If given, the result for each pixel is
|
||||
divided by this value. the default is the sum of the
|
||||
kernel weights.
|
||||
:param 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 image.filter(*self.filterargs)
|
||||
|
||||
|
||||
class BuiltinFilter(Kernel):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
class RankFilter(Filter):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
name = "Rank"
|
||||
|
||||
def __init__(self, size, rank):
|
||||
self.size = size
|
||||
self.rank = rank
|
||||
|
||||
def filter(self, image):
|
||||
if image.mode == "P":
|
||||
raise ValueError("cannot filter palette images")
|
||||
image = image.expand(self.size//2, self.size//2)
|
||||
return image.rankfilter(self.size, self.rank)
|
||||
|
||||
|
||||
class MedianFilter(RankFilter):
|
||||
"""
|
||||
Create a median filter. Picks the median pixel value in a window with the
|
||||
given size.
|
||||
|
||||
:param size: The kernel size, in pixels.
|
||||
"""
|
||||
name = "Median"
|
||||
|
||||
def __init__(self, size=3):
|
||||
self.size = size
|
||||
self.rank = size*size//2
|
||||
|
||||
|
||||
class MinFilter(RankFilter):
|
||||
"""
|
||||
Create a min filter. Picks the lowest pixel value in a window with the
|
||||
given size.
|
||||
|
||||
:param size: The kernel size, in pixels.
|
||||
"""
|
||||
name = "Min"
|
||||
|
||||
def __init__(self, size=3):
|
||||
self.size = size
|
||||
self.rank = 0
|
||||
|
||||
|
||||
class MaxFilter(RankFilter):
|
||||
"""
|
||||
Create a max filter. Picks the largest pixel value in a window with the
|
||||
given size.
|
||||
|
||||
:param size: The kernel size, in pixels.
|
||||
"""
|
||||
name = "Max"
|
||||
|
||||
def __init__(self, size=3):
|
||||
self.size = size
|
||||
self.rank = size*size-1
|
||||
|
||||
|
||||
class ModeFilter(Filter):
|
||||
"""
|
||||
|
||||
Create a 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.
|
||||
|
||||
:param size: The kernel size, in pixels.
|
||||
"""
|
||||
name = "Mode"
|
||||
|
||||
def __init__(self, size=3):
|
||||
self.size = size
|
||||
|
||||
def filter(self, image):
|
||||
return image.modefilter(self.size)
|
||||
|
||||
|
||||
class GaussianBlur(Filter):
|
||||
"""Gaussian blur filter.
|
||||
|
||||
:param radius: Blur radius.
|
||||
"""
|
||||
name = "GaussianBlur"
|
||||
|
||||
def __init__(self, radius=2):
|
||||
self.radius = radius
|
||||
|
||||
def filter(self, image):
|
||||
return image.gaussian_blur(self.radius)
|
||||
|
||||
|
||||
class UnsharpMask(Filter):
|
||||
"""Unsharp mask filter.
|
||||
|
||||
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
|
||||
the parameters.
|
||||
|
||||
:param radius: Blur Radius
|
||||
:param percent: Unsharp strength, in percent
|
||||
:param threshold: Threshold controls the minimum brightness change that
|
||||
will be sharpened
|
||||
|
||||
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
|
||||
|
||||
"""
|
||||
name = "UnsharpMask"
|
||||
|
||||
def __init__(self, radius=2, percent=150, threshold=3):
|
||||
self.radius = radius
|
||||
self.percent = percent
|
||||
self.threshold = threshold
|
||||
|
||||
def filter(self, image):
|
||||
return image.unsharp_mask(self.radius, self.percent, self.threshold)
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
class CONTOUR(BuiltinFilter):
|
||||
name = "Contour"
|
||||
filterargs = (3, 3), 1, 255, (
|
||||
-1, -1, -1,
|
||||
-1, 8, -1,
|
||||
-1, -1, -1
|
||||
)
|
||||
|
||||
|
||||
class DETAIL(BuiltinFilter):
|
||||
name = "Detail"
|
||||
filterargs = (3, 3), 6, 0, (
|
||||
0, -1, 0,
|
||||
-1, 10, -1,
|
||||
0, -1, 0
|
||||
)
|
||||
|
||||
|
||||
class EDGE_ENHANCE(BuiltinFilter):
|
||||
name = "Edge-enhance"
|
||||
filterargs = (3, 3), 2, 0, (
|
||||
-1, -1, -1,
|
||||
-1, 10, -1,
|
||||
-1, -1, -1
|
||||
)
|
||||
|
||||
|
||||
class EDGE_ENHANCE_MORE(BuiltinFilter):
|
||||
name = "Edge-enhance More"
|
||||
filterargs = (3, 3), 1, 0, (
|
||||
-1, -1, -1,
|
||||
-1, 9, -1,
|
||||
-1, -1, -1
|
||||
)
|
||||
|
||||
|
||||
class EMBOSS(BuiltinFilter):
|
||||
name = "Emboss"
|
||||
filterargs = (3, 3), 1, 128, (
|
||||
-1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 0
|
||||
)
|
||||
|
||||
|
||||
class FIND_EDGES(BuiltinFilter):
|
||||
name = "Find Edges"
|
||||
filterargs = (3, 3), 1, 0, (
|
||||
-1, -1, -1,
|
||||
-1, 8, -1,
|
||||
-1, -1, -1
|
||||
)
|
||||
|
||||
|
||||
class SMOOTH(BuiltinFilter):
|
||||
name = "Smooth"
|
||||
filterargs = (3, 3), 13, 0, (
|
||||
1, 1, 1,
|
||||
1, 5, 1,
|
||||
1, 1, 1
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
class SHARPEN(BuiltinFilter):
|
||||
name = "Sharpen"
|
||||
filterargs = (3, 3), 16, 0, (
|
||||
-2, -2, -2,
|
||||
-2, 32, -2,
|
||||
-2, -2, -2
|
||||
)
|
||||
|
|
@ -1,430 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library.
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from PIL import Image
|
||||
from PIL._util import isDirectory, isPath
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import warnings
|
||||
except ImportError:
|
||||
warnings = None
|
||||
|
||||
|
||||
class _imagingft_not_installed:
|
||||
# module placeholder
|
||||
def __getattr__(self, id):
|
||||
raise ImportError("The _imagingft C module is not installed")
|
||||
|
||||
try:
|
||||
from PIL import _imagingft as core
|
||||
except ImportError:
|
||||
core = _imagingft_not_installed()
|
||||
|
||||
# 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.
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
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() != b"PILfont\n":
|
||||
raise SyntaxError("Not a PILfont file")
|
||||
file.readline().split(b";")
|
||||
self.info = [] # FIXME: should be a dictionary
|
||||
while True:
|
||||
s = file.readline()
|
||||
if not s or s == b"DATA\n":
|
||||
break
|
||||
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, font=None, size=10, index=0, encoding="", file=None):
|
||||
# FIXME: use service provider instead
|
||||
if file:
|
||||
if warnings:
|
||||
warnings.warn(
|
||||
'file parameter deprecated, '
|
||||
'please use font parameter instead.',
|
||||
DeprecationWarning)
|
||||
font = file
|
||||
|
||||
if isPath(font):
|
||||
self.font = core.getfont(font, size, index, encoding)
|
||||
else:
|
||||
self.font_bytes = font.read()
|
||||
self.font = core.getfont(
|
||||
"", size, index, encoding, self.font_bytes)
|
||||
|
||||
def getname(self):
|
||||
return self.font.family, self.font.style
|
||||
|
||||
def getmetrics(self):
|
||||
return self.font.ascent, self.font.descent
|
||||
|
||||
def getsize(self, text):
|
||||
size, offset = self.font.getsize(text)
|
||||
return (size[0] + offset[0], size[1] + offset[1])
|
||||
|
||||
def getoffset(self, text):
|
||||
return self.font.getsize(text)[1]
|
||||
|
||||
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
|
||||
|
||||
|
||||
def load(filename):
|
||||
"""
|
||||
Load a 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.
|
||||
"""
|
||||
f = ImageFont()
|
||||
f._load_pilfont(filename)
|
||||
return f
|
||||
|
||||
|
||||
def truetype(font=None, size=10, index=0, encoding="", filename=None):
|
||||
"""
|
||||
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.
|
||||
|
||||
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 :file:`fonts/` 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.
|
||||
"""
|
||||
|
||||
if filename:
|
||||
if warnings:
|
||||
warnings.warn(
|
||||
'filename parameter deprecated, '
|
||||
'please use font parameter instead.',
|
||||
DeprecationWarning)
|
||||
font = filename
|
||||
|
||||
try:
|
||||
return FreeTypeFont(font, size, index, encoding)
|
||||
except IOError:
|
||||
if font.endswith(".ttf"):
|
||||
ttf_filename = font
|
||||
else:
|
||||
ttf_filename = "%s.ttf" % font
|
||||
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", font)
|
||||
return FreeTypeFont(filename, size, index, encoding)
|
||||
elif sys.platform in ('linux', 'linux2'):
|
||||
lindirs = os.environ.get("XDG_DATA_DIRS", "")
|
||||
if not lindirs:
|
||||
#According to the freedesktop spec, XDG_DATA_DIRS should
|
||||
#default to /usr/share
|
||||
lindirs = '/usr/share'
|
||||
lindirs = lindirs.split(":")
|
||||
for lindir in lindirs:
|
||||
parentpath = os.path.join(lindir, "fonts")
|
||||
for walkroot, walkdir, walkfilenames in os.walk(parentpath):
|
||||
if ttf_filename in walkfilenames:
|
||||
filepath = os.path.join(walkroot, ttf_filename)
|
||||
return FreeTypeFont(filepath, size, index, encoding)
|
||||
elif sys.platform == 'darwin':
|
||||
macdirs = ['/Library/Fonts/', '/System/Library/Fonts/', os.path.expanduser('~/Library/Fonts/')]
|
||||
for macdir in macdirs:
|
||||
filepath = os.path.join(macdir, ttf_filename)
|
||||
if os.path.exists(filepath):
|
||||
return FreeTypeFont(filepath, size, index, encoding)
|
||||
raise
|
||||
|
||||
|
||||
def load_path(filename):
|
||||
"""
|
||||
Load font file. Same as :py:func:`~PIL.ImageFont.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.
|
||||
"""
|
||||
for dir in sys.path:
|
||||
if isDirectory(dir):
|
||||
if not isinstance(filename, str):
|
||||
if bytes is str:
|
||||
filename = filename.encode("utf-8")
|
||||
else:
|
||||
filename = filename.decode("utf-8")
|
||||
try:
|
||||
return load(os.path.join(dir, filename))
|
||||
except IOError:
|
||||
pass
|
||||
raise IOError("cannot find font file")
|
||||
|
||||
|
||||
def load_default():
|
||||
"""Load a "better than nothing" default font.
|
||||
|
||||
.. versionadded:: 1.1.4
|
||||
|
||||
:return: A font object.
|
||||
"""
|
||||
from io import BytesIO
|
||||
import base64
|
||||
f = ImageFont()
|
||||
f._load_pilfont_data(
|
||||
# courB08
|
||||
BytesIO(base64.decodestring(b'''
|
||||
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(BytesIO(base64.decodestring(b'''
|
||||
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
|
||||
|
||||
# End of file
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# $Id$
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from PIL import Image
|
||||
|
||||
import sys
|
||||
if sys.platform != "win32":
|
||||
raise ImportError("ImageGrab is Windows only")
|
||||
|
||||
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
|
||||
|
||||
|
||||
def grab(bbox=None):
|
||||
size, data = grabber()
|
||||
im = Image.frombytes(
|
||||
"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
|
||||
|
||||
|
||||
def grabclipboard():
|
||||
debug = 0 # temporary interface
|
||||
data = Image.core.grabclipboard(debug) | ||||