Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +7 -0
- my_container_sandbox/workspace/anaconda3/lib/libnvvm.so +3 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/BdfFontFile.py +110 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/BufrStubImagePlugin.py +73 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ContainerIO.py +120 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/CurImagePlugin.py +75 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/DdsImagePlugin.py +249 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/EpsImagePlugin.py +411 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ExifTags.py +331 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FitsStubImagePlugin.py +77 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FliImagePlugin.py +171 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FontFile.py +111 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FpxImagePlugin.py +242 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FtexImagePlugin.py +135 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/GbrImagePlugin.py +98 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/GimpGradientFile.py +140 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/GimpPaletteFile.py +56 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/GribStubImagePlugin.py +73 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/IcnsImagePlugin.py +392 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/IcoImagePlugin.py +355 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/Image.py +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageChops.py +328 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageColor.py +302 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageDraw2.py +179 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageEnhance.py +103 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageFile.py +748 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageFont.py +1083 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageGrab.py +124 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageMode.py +91 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImagePalette.py +261 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImagePath.py +19 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageQt.py +223 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageSequence.py +75 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageStat.py +147 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageWin.py +230 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImtImagePlugin.py +93 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/IptcImagePlugin.py +230 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/JpegPresets.py +240 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/MicImagePlugin.py +107 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/MspImagePlugin.py +194 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PaletteFile.py +53 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PcdImagePlugin.py +63 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PcfFontFile.py +248 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PcxImagePlugin.py +218 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PdfImagePlugin.py +239 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PdfParser.py +998 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PixarImagePlugin.py +70 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PngImagePlugin.py +1432 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PpmImagePlugin.py +199 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PsdImagePlugin.py +311 -0
.gitattributes
CHANGED
|
@@ -311,3 +311,10 @@ my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/Pillow.libs
|
|
| 311 |
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/Pillow.libs/liblcms2-1e643a89.so.2.0.13 filter=lfs diff=lfs merge=lfs -text
|
| 312 |
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/Pillow.libs/libtiff-d0580107.so.5.7.0 filter=lfs diff=lfs merge=lfs -text
|
| 313 |
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/psutil/_psutil_linux.abi3.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/Pillow.libs/liblcms2-1e643a89.so.2.0.13 filter=lfs diff=lfs merge=lfs -text
|
| 312 |
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/Pillow.libs/libtiff-d0580107.so.5.7.0 filter=lfs diff=lfs merge=lfs -text
|
| 313 |
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/psutil/_psutil_linux.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 314 |
+
my_container_sandbox/workspace/anaconda3/lib/libnvvm.so filter=lfs diff=lfs merge=lfs -text
|
| 315 |
+
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/multidict/_multidict.cpython-38-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 316 |
+
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/aiohttp/_websocket.cpython-38-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 317 |
+
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/Pillow.libs/libwebp-8efe125f.so.7.1.3 filter=lfs diff=lfs merge=lfs -text
|
| 318 |
+
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/lxml/builder.cpython-38-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 319 |
+
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/Pillow.libs/libjpeg-1b553ed5.so.62.3.0 filter=lfs diff=lfs merge=lfs -text
|
| 320 |
+
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/lxml/_elementpath.cpython-38-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
my_container_sandbox/workspace/anaconda3/lib/libnvvm.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f0c95d249f3d60d67dbd191174fe1d8b9f393b1887ae1a54b4988823c7e42a31
|
| 3 |
+
size 26650200
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/BdfFontFile.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# bitmap distribution font (bdf) file parser
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 1996-05-16 fl created (as bdf2pil)
|
| 9 |
+
# 1997-08-25 fl converted to FontFile driver
|
| 10 |
+
# 2001-05-25 fl removed bogus __init__ call
|
| 11 |
+
# 2002-11-20 fl robustification (from Kevin Cazabon, Dmitry Vasiliev)
|
| 12 |
+
# 2003-04-22 fl more robustification (from Graham Dumpleton)
|
| 13 |
+
#
|
| 14 |
+
# Copyright (c) 1997-2003 by Secret Labs AB.
|
| 15 |
+
# Copyright (c) 1997-2003 by Fredrik Lundh.
|
| 16 |
+
#
|
| 17 |
+
# See the README file for information on usage and redistribution.
|
| 18 |
+
#
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
Parse X Bitmap Distribution Format (BDF)
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
from . import FontFile, Image
|
| 26 |
+
|
| 27 |
+
bdf_slant = {
|
| 28 |
+
"R": "Roman",
|
| 29 |
+
"I": "Italic",
|
| 30 |
+
"O": "Oblique",
|
| 31 |
+
"RI": "Reverse Italic",
|
| 32 |
+
"RO": "Reverse Oblique",
|
| 33 |
+
"OT": "Other",
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def bdf_char(f):
|
| 40 |
+
# skip to STARTCHAR
|
| 41 |
+
while True:
|
| 42 |
+
s = f.readline()
|
| 43 |
+
if not s:
|
| 44 |
+
return None
|
| 45 |
+
if s[:9] == b"STARTCHAR":
|
| 46 |
+
break
|
| 47 |
+
id = s[9:].strip().decode("ascii")
|
| 48 |
+
|
| 49 |
+
# load symbol properties
|
| 50 |
+
props = {}
|
| 51 |
+
while True:
|
| 52 |
+
s = f.readline()
|
| 53 |
+
if not s or s[:6] == b"BITMAP":
|
| 54 |
+
break
|
| 55 |
+
i = s.find(b" ")
|
| 56 |
+
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
|
| 57 |
+
|
| 58 |
+
# load bitmap
|
| 59 |
+
bitmap = []
|
| 60 |
+
while True:
|
| 61 |
+
s = f.readline()
|
| 62 |
+
if not s or s[:7] == b"ENDCHAR":
|
| 63 |
+
break
|
| 64 |
+
bitmap.append(s[:-1])
|
| 65 |
+
bitmap = b"".join(bitmap)
|
| 66 |
+
|
| 67 |
+
[x, y, l, d] = [int(p) for p in props["BBX"].split()]
|
| 68 |
+
[dx, dy] = [int(p) for p in props["DWIDTH"].split()]
|
| 69 |
+
|
| 70 |
+
bbox = (dx, dy), (l, -d - y, x + l, -d), (0, 0, x, y)
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
im = Image.frombytes("1", (x, y), bitmap, "hex", "1")
|
| 74 |
+
except ValueError:
|
| 75 |
+
# deal with zero-width characters
|
| 76 |
+
im = Image.new("1", (x, y))
|
| 77 |
+
|
| 78 |
+
return id, int(props["ENCODING"]), bbox, im
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class BdfFontFile(FontFile.FontFile):
|
| 82 |
+
"""Font file plugin for the X11 BDF format."""
|
| 83 |
+
|
| 84 |
+
def __init__(self, fp):
|
| 85 |
+
super().__init__()
|
| 86 |
+
|
| 87 |
+
s = fp.readline()
|
| 88 |
+
if s[:13] != b"STARTFONT 2.1":
|
| 89 |
+
raise SyntaxError("not a valid BDF file")
|
| 90 |
+
|
| 91 |
+
props = {}
|
| 92 |
+
comments = []
|
| 93 |
+
|
| 94 |
+
while True:
|
| 95 |
+
s = fp.readline()
|
| 96 |
+
if not s or s[:13] == b"ENDPROPERTIES":
|
| 97 |
+
break
|
| 98 |
+
i = s.find(b" ")
|
| 99 |
+
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
|
| 100 |
+
if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
|
| 101 |
+
if s.find(b"LogicalFontDescription") < 0:
|
| 102 |
+
comments.append(s[i + 1 : -1].decode("ascii"))
|
| 103 |
+
|
| 104 |
+
while True:
|
| 105 |
+
c = bdf_char(fp)
|
| 106 |
+
if not c:
|
| 107 |
+
break
|
| 108 |
+
id, ch, (xy, dst, src), im = c
|
| 109 |
+
if 0 <= ch < len(self.glyph):
|
| 110 |
+
self.glyph[ch] = xy, dst, src, im
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/BufrStubImagePlugin.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# BUFR stub adapter
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 1996-2003 by Fredrik Lundh
|
| 8 |
+
#
|
| 9 |
+
# See the README file for information on usage and redistribution.
|
| 10 |
+
#
|
| 11 |
+
|
| 12 |
+
from . import Image, ImageFile
|
| 13 |
+
|
| 14 |
+
_handler = None
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def register_handler(handler):
|
| 18 |
+
"""
|
| 19 |
+
Install application-specific BUFR image handler.
|
| 20 |
+
|
| 21 |
+
:param handler: Handler object.
|
| 22 |
+
"""
|
| 23 |
+
global _handler
|
| 24 |
+
_handler = handler
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# --------------------------------------------------------------------
|
| 28 |
+
# Image adapter
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _accept(prefix):
|
| 32 |
+
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class BufrStubImageFile(ImageFile.StubImageFile):
|
| 36 |
+
|
| 37 |
+
format = "BUFR"
|
| 38 |
+
format_description = "BUFR"
|
| 39 |
+
|
| 40 |
+
def _open(self):
|
| 41 |
+
|
| 42 |
+
offset = self.fp.tell()
|
| 43 |
+
|
| 44 |
+
if not _accept(self.fp.read(4)):
|
| 45 |
+
raise SyntaxError("Not a BUFR file")
|
| 46 |
+
|
| 47 |
+
self.fp.seek(offset)
|
| 48 |
+
|
| 49 |
+
# make something up
|
| 50 |
+
self.mode = "F"
|
| 51 |
+
self._size = 1, 1
|
| 52 |
+
|
| 53 |
+
loader = self._load()
|
| 54 |
+
if loader:
|
| 55 |
+
loader.open(self)
|
| 56 |
+
|
| 57 |
+
def _load(self):
|
| 58 |
+
return _handler
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _save(im, fp, filename):
|
| 62 |
+
if _handler is None or not hasattr(_handler, "save"):
|
| 63 |
+
raise OSError("BUFR save handler not installed")
|
| 64 |
+
_handler.save(im, fp, filename)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# --------------------------------------------------------------------
|
| 68 |
+
# Registry
|
| 69 |
+
|
| 70 |
+
Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept)
|
| 71 |
+
Image.register_save(BufrStubImageFile.format, _save)
|
| 72 |
+
|
| 73 |
+
Image.register_extension(BufrStubImageFile.format, ".bufr")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ContainerIO.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# a class to read from a container file
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1995-06-18 fl Created
|
| 9 |
+
# 1995-09-07 fl Added readline(), readlines()
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) 1997-2001 by Secret Labs AB
|
| 12 |
+
# Copyright (c) 1995 by Fredrik Lundh
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
import io
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class ContainerIO:
|
| 22 |
+
"""
|
| 23 |
+
A file object that provides read access to a part of an existing
|
| 24 |
+
file (for example a TAR file).
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
def __init__(self, file, offset, length):
|
| 28 |
+
"""
|
| 29 |
+
Create file object.
|
| 30 |
+
|
| 31 |
+
:param file: Existing file.
|
| 32 |
+
:param offset: Start of region, in bytes.
|
| 33 |
+
:param length: Size of region, in bytes.
|
| 34 |
+
"""
|
| 35 |
+
self.fh = file
|
| 36 |
+
self.pos = 0
|
| 37 |
+
self.offset = offset
|
| 38 |
+
self.length = length
|
| 39 |
+
self.fh.seek(offset)
|
| 40 |
+
|
| 41 |
+
##
|
| 42 |
+
# Always false.
|
| 43 |
+
|
| 44 |
+
def isatty(self):
|
| 45 |
+
return False
|
| 46 |
+
|
| 47 |
+
def seek(self, offset, mode=io.SEEK_SET):
|
| 48 |
+
"""
|
| 49 |
+
Move file pointer.
|
| 50 |
+
|
| 51 |
+
:param offset: Offset in bytes.
|
| 52 |
+
:param mode: Starting position. Use 0 for beginning of region, 1
|
| 53 |
+
for current offset, and 2 for end of region. You cannot move
|
| 54 |
+
the pointer outside the defined region.
|
| 55 |
+
"""
|
| 56 |
+
if mode == 1:
|
| 57 |
+
self.pos = self.pos + offset
|
| 58 |
+
elif mode == 2:
|
| 59 |
+
self.pos = self.length + offset
|
| 60 |
+
else:
|
| 61 |
+
self.pos = offset
|
| 62 |
+
# clamp
|
| 63 |
+
self.pos = max(0, min(self.pos, self.length))
|
| 64 |
+
self.fh.seek(self.offset + self.pos)
|
| 65 |
+
|
| 66 |
+
def tell(self):
|
| 67 |
+
"""
|
| 68 |
+
Get current file pointer.
|
| 69 |
+
|
| 70 |
+
:returns: Offset from start of region, in bytes.
|
| 71 |
+
"""
|
| 72 |
+
return self.pos
|
| 73 |
+
|
| 74 |
+
def read(self, n=0):
|
| 75 |
+
"""
|
| 76 |
+
Read data.
|
| 77 |
+
|
| 78 |
+
:param n: Number of bytes to read. If omitted or zero,
|
| 79 |
+
read until end of region.
|
| 80 |
+
:returns: An 8-bit string.
|
| 81 |
+
"""
|
| 82 |
+
if n:
|
| 83 |
+
n = min(n, self.length - self.pos)
|
| 84 |
+
else:
|
| 85 |
+
n = self.length - self.pos
|
| 86 |
+
if not n: # EOF
|
| 87 |
+
return b"" if "b" in self.fh.mode else ""
|
| 88 |
+
self.pos = self.pos + n
|
| 89 |
+
return self.fh.read(n)
|
| 90 |
+
|
| 91 |
+
def readline(self):
|
| 92 |
+
"""
|
| 93 |
+
Read a line of text.
|
| 94 |
+
|
| 95 |
+
:returns: An 8-bit string.
|
| 96 |
+
"""
|
| 97 |
+
s = b"" if "b" in self.fh.mode else ""
|
| 98 |
+
newline_character = b"\n" if "b" in self.fh.mode else "\n"
|
| 99 |
+
while True:
|
| 100 |
+
c = self.read(1)
|
| 101 |
+
if not c:
|
| 102 |
+
break
|
| 103 |
+
s = s + c
|
| 104 |
+
if c == newline_character:
|
| 105 |
+
break
|
| 106 |
+
return s
|
| 107 |
+
|
| 108 |
+
def readlines(self):
|
| 109 |
+
"""
|
| 110 |
+
Read multiple lines of text.
|
| 111 |
+
|
| 112 |
+
:returns: A list of 8-bit strings.
|
| 113 |
+
"""
|
| 114 |
+
lines = []
|
| 115 |
+
while True:
|
| 116 |
+
s = self.readline()
|
| 117 |
+
if not s:
|
| 118 |
+
break
|
| 119 |
+
lines.append(s)
|
| 120 |
+
return lines
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/CurImagePlugin.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# Windows Cursor support for PIL
|
| 6 |
+
#
|
| 7 |
+
# notes:
|
| 8 |
+
# uses BmpImagePlugin.py to read the bitmap data.
|
| 9 |
+
#
|
| 10 |
+
# history:
|
| 11 |
+
# 96-05-27 fl Created
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 14 |
+
# Copyright (c) Fredrik Lundh 1996.
|
| 15 |
+
#
|
| 16 |
+
# See the README file for information on usage and redistribution.
|
| 17 |
+
#
|
| 18 |
+
from . import BmpImagePlugin, Image
|
| 19 |
+
from ._binary import i16le as i16
|
| 20 |
+
from ._binary import i32le as i32
|
| 21 |
+
|
| 22 |
+
#
|
| 23 |
+
# --------------------------------------------------------------------
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _accept(prefix):
|
| 27 |
+
return prefix[:4] == b"\0\0\2\0"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
##
|
| 31 |
+
# Image plugin for Windows Cursor files.
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class CurImageFile(BmpImagePlugin.BmpImageFile):
|
| 35 |
+
|
| 36 |
+
format = "CUR"
|
| 37 |
+
format_description = "Windows Cursor"
|
| 38 |
+
|
| 39 |
+
def _open(self):
|
| 40 |
+
|
| 41 |
+
offset = self.fp.tell()
|
| 42 |
+
|
| 43 |
+
# check magic
|
| 44 |
+
s = self.fp.read(6)
|
| 45 |
+
if not _accept(s):
|
| 46 |
+
raise SyntaxError("not a CUR file")
|
| 47 |
+
|
| 48 |
+
# pick the largest cursor in the file
|
| 49 |
+
m = b""
|
| 50 |
+
for i in range(i16(s, 4)):
|
| 51 |
+
s = self.fp.read(16)
|
| 52 |
+
if not m:
|
| 53 |
+
m = s
|
| 54 |
+
elif s[0] > m[0] and s[1] > m[1]:
|
| 55 |
+
m = s
|
| 56 |
+
if not m:
|
| 57 |
+
raise TypeError("No cursors were found")
|
| 58 |
+
|
| 59 |
+
# load as bitmap
|
| 60 |
+
self._bitmap(i32(m, 12) + offset)
|
| 61 |
+
|
| 62 |
+
# patch up the bitmap height
|
| 63 |
+
self._size = self.size[0], self.size[1] // 2
|
| 64 |
+
d, e, o, a = self.tile[0]
|
| 65 |
+
self.tile[0] = d, (0, 0) + self.size, o, a
|
| 66 |
+
|
| 67 |
+
return
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
#
|
| 71 |
+
# --------------------------------------------------------------------
|
| 72 |
+
|
| 73 |
+
Image.register_open(CurImageFile.format, CurImageFile, _accept)
|
| 74 |
+
|
| 75 |
+
Image.register_extension(CurImageFile.format, ".cur")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/DdsImagePlugin.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
A Pillow loader for .dds files (S3TC-compressed aka DXTC)
|
| 3 |
+
Jerome Leclanche <jerome@leclan.ch>
|
| 4 |
+
|
| 5 |
+
Documentation:
|
| 6 |
+
https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
|
| 7 |
+
|
| 8 |
+
The contents of this file are hereby released in the public domain (CC0)
|
| 9 |
+
Full text of the CC0 license:
|
| 10 |
+
https://creativecommons.org/publicdomain/zero/1.0/
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import struct
|
| 14 |
+
from io import BytesIO
|
| 15 |
+
|
| 16 |
+
from . import Image, ImageFile
|
| 17 |
+
from ._binary import o32le as o32
|
| 18 |
+
|
| 19 |
+
# Magic ("DDS ")
|
| 20 |
+
DDS_MAGIC = 0x20534444
|
| 21 |
+
|
| 22 |
+
# DDS flags
|
| 23 |
+
DDSD_CAPS = 0x1
|
| 24 |
+
DDSD_HEIGHT = 0x2
|
| 25 |
+
DDSD_WIDTH = 0x4
|
| 26 |
+
DDSD_PITCH = 0x8
|
| 27 |
+
DDSD_PIXELFORMAT = 0x1000
|
| 28 |
+
DDSD_MIPMAPCOUNT = 0x20000
|
| 29 |
+
DDSD_LINEARSIZE = 0x80000
|
| 30 |
+
DDSD_DEPTH = 0x800000
|
| 31 |
+
|
| 32 |
+
# DDS caps
|
| 33 |
+
DDSCAPS_COMPLEX = 0x8
|
| 34 |
+
DDSCAPS_TEXTURE = 0x1000
|
| 35 |
+
DDSCAPS_MIPMAP = 0x400000
|
| 36 |
+
|
| 37 |
+
DDSCAPS2_CUBEMAP = 0x200
|
| 38 |
+
DDSCAPS2_CUBEMAP_POSITIVEX = 0x400
|
| 39 |
+
DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800
|
| 40 |
+
DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000
|
| 41 |
+
DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000
|
| 42 |
+
DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000
|
| 43 |
+
DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000
|
| 44 |
+
DDSCAPS2_VOLUME = 0x200000
|
| 45 |
+
|
| 46 |
+
# Pixel Format
|
| 47 |
+
DDPF_ALPHAPIXELS = 0x1
|
| 48 |
+
DDPF_ALPHA = 0x2
|
| 49 |
+
DDPF_FOURCC = 0x4
|
| 50 |
+
DDPF_PALETTEINDEXED8 = 0x20
|
| 51 |
+
DDPF_RGB = 0x40
|
| 52 |
+
DDPF_LUMINANCE = 0x20000
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# dds.h
|
| 56 |
+
|
| 57 |
+
DDS_FOURCC = DDPF_FOURCC
|
| 58 |
+
DDS_RGB = DDPF_RGB
|
| 59 |
+
DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS
|
| 60 |
+
DDS_LUMINANCE = DDPF_LUMINANCE
|
| 61 |
+
DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
|
| 62 |
+
DDS_ALPHA = DDPF_ALPHA
|
| 63 |
+
DDS_PAL8 = DDPF_PALETTEINDEXED8
|
| 64 |
+
|
| 65 |
+
DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
|
| 66 |
+
DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
|
| 67 |
+
DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
|
| 68 |
+
DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
|
| 69 |
+
DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE
|
| 70 |
+
|
| 71 |
+
DDS_HEIGHT = DDSD_HEIGHT
|
| 72 |
+
DDS_WIDTH = DDSD_WIDTH
|
| 73 |
+
|
| 74 |
+
DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE
|
| 75 |
+
DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
|
| 76 |
+
DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX
|
| 77 |
+
|
| 78 |
+
DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
|
| 79 |
+
DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
|
| 80 |
+
DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
|
| 81 |
+
DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
|
| 82 |
+
DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
|
| 83 |
+
DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# DXT1
|
| 87 |
+
DXT1_FOURCC = 0x31545844
|
| 88 |
+
|
| 89 |
+
# DXT3
|
| 90 |
+
DXT3_FOURCC = 0x33545844
|
| 91 |
+
|
| 92 |
+
# DXT5
|
| 93 |
+
DXT5_FOURCC = 0x35545844
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# dxgiformat.h
|
| 97 |
+
|
| 98 |
+
DXGI_FORMAT_R8G8B8A8_TYPELESS = 27
|
| 99 |
+
DXGI_FORMAT_R8G8B8A8_UNORM = 28
|
| 100 |
+
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
|
| 101 |
+
DXGI_FORMAT_BC5_TYPELESS = 82
|
| 102 |
+
DXGI_FORMAT_BC5_UNORM = 83
|
| 103 |
+
DXGI_FORMAT_BC5_SNORM = 84
|
| 104 |
+
DXGI_FORMAT_BC7_TYPELESS = 97
|
| 105 |
+
DXGI_FORMAT_BC7_UNORM = 98
|
| 106 |
+
DXGI_FORMAT_BC7_UNORM_SRGB = 99
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class DdsImageFile(ImageFile.ImageFile):
|
| 110 |
+
format = "DDS"
|
| 111 |
+
format_description = "DirectDraw Surface"
|
| 112 |
+
|
| 113 |
+
def _open(self):
|
| 114 |
+
if not _accept(self.fp.read(4)):
|
| 115 |
+
raise SyntaxError("not a DDS file")
|
| 116 |
+
(header_size,) = struct.unpack("<I", self.fp.read(4))
|
| 117 |
+
if header_size != 124:
|
| 118 |
+
raise OSError(f"Unsupported header size {repr(header_size)}")
|
| 119 |
+
header_bytes = self.fp.read(header_size - 4)
|
| 120 |
+
if len(header_bytes) != 120:
|
| 121 |
+
raise OSError(f"Incomplete header: {len(header_bytes)} bytes")
|
| 122 |
+
header = BytesIO(header_bytes)
|
| 123 |
+
|
| 124 |
+
flags, height, width = struct.unpack("<3I", header.read(12))
|
| 125 |
+
self._size = (width, height)
|
| 126 |
+
self.mode = "RGBA"
|
| 127 |
+
|
| 128 |
+
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
| 129 |
+
struct.unpack("<11I", header.read(44)) # reserved
|
| 130 |
+
|
| 131 |
+
# pixel format
|
| 132 |
+
pfsize, pfflags = struct.unpack("<2I", header.read(8))
|
| 133 |
+
fourcc = header.read(4)
|
| 134 |
+
(bitcount,) = struct.unpack("<I", header.read(4))
|
| 135 |
+
masks = struct.unpack("<4I", header.read(16))
|
| 136 |
+
if pfflags & DDPF_RGB:
|
| 137 |
+
# Texture contains uncompressed RGB data
|
| 138 |
+
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
|
| 139 |
+
rawmode = ""
|
| 140 |
+
if bitcount == 32:
|
| 141 |
+
rawmode += masks[0xFF000000]
|
| 142 |
+
else:
|
| 143 |
+
self.mode = "RGB"
|
| 144 |
+
rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
|
| 145 |
+
|
| 146 |
+
self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
|
| 147 |
+
else:
|
| 148 |
+
data_start = header_size + 4
|
| 149 |
+
n = 0
|
| 150 |
+
if fourcc == b"DXT1":
|
| 151 |
+
self.pixel_format = "DXT1"
|
| 152 |
+
n = 1
|
| 153 |
+
elif fourcc == b"DXT3":
|
| 154 |
+
self.pixel_format = "DXT3"
|
| 155 |
+
n = 2
|
| 156 |
+
elif fourcc == b"DXT5":
|
| 157 |
+
self.pixel_format = "DXT5"
|
| 158 |
+
n = 3
|
| 159 |
+
elif fourcc == b"BC5S":
|
| 160 |
+
self.pixel_format = "BC5S"
|
| 161 |
+
n = 5
|
| 162 |
+
self.mode = "RGB"
|
| 163 |
+
elif fourcc == b"DX10":
|
| 164 |
+
data_start += 20
|
| 165 |
+
# ignoring flags which pertain to volume textures and cubemaps
|
| 166 |
+
(dxgi_format,) = struct.unpack("<I", self.fp.read(4))
|
| 167 |
+
self.fp.read(16)
|
| 168 |
+
if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
|
| 169 |
+
self.pixel_format = "BC5"
|
| 170 |
+
n = 5
|
| 171 |
+
self.mode = "RGB"
|
| 172 |
+
elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
|
| 173 |
+
self.pixel_format = "BC5S"
|
| 174 |
+
n = 5
|
| 175 |
+
self.mode = "RGB"
|
| 176 |
+
elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
|
| 177 |
+
self.pixel_format = "BC7"
|
| 178 |
+
n = 7
|
| 179 |
+
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
|
| 180 |
+
self.pixel_format = "BC7"
|
| 181 |
+
self.info["gamma"] = 1 / 2.2
|
| 182 |
+
n = 7
|
| 183 |
+
elif dxgi_format in (
|
| 184 |
+
DXGI_FORMAT_R8G8B8A8_TYPELESS,
|
| 185 |
+
DXGI_FORMAT_R8G8B8A8_UNORM,
|
| 186 |
+
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
|
| 187 |
+
):
|
| 188 |
+
self.tile = [("raw", (0, 0) + self.size, 0, ("RGBA", 0, 1))]
|
| 189 |
+
if dxgi_format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
| 190 |
+
self.info["gamma"] = 1 / 2.2
|
| 191 |
+
return
|
| 192 |
+
else:
|
| 193 |
+
raise NotImplementedError(
|
| 194 |
+
f"Unimplemented DXGI format {dxgi_format}"
|
| 195 |
+
)
|
| 196 |
+
else:
|
| 197 |
+
raise NotImplementedError(f"Unimplemented pixel format {repr(fourcc)}")
|
| 198 |
+
|
| 199 |
+
self.tile = [
|
| 200 |
+
("bcn", (0, 0) + self.size, data_start, (n, self.pixel_format))
|
| 201 |
+
]
|
| 202 |
+
|
| 203 |
+
def load_seek(self, pos):
|
| 204 |
+
pass
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def _save(im, fp, filename):
|
| 208 |
+
if im.mode not in ("RGB", "RGBA"):
|
| 209 |
+
raise OSError(f"cannot write mode {im.mode} as DDS")
|
| 210 |
+
|
| 211 |
+
fp.write(
|
| 212 |
+
o32(DDS_MAGIC)
|
| 213 |
+
+ o32(124) # header size
|
| 214 |
+
+ o32(
|
| 215 |
+
DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PITCH | DDSD_PIXELFORMAT
|
| 216 |
+
) # flags
|
| 217 |
+
+ o32(im.height)
|
| 218 |
+
+ o32(im.width)
|
| 219 |
+
+ o32((im.width * (32 if im.mode == "RGBA" else 24) + 7) // 8) # pitch
|
| 220 |
+
+ o32(0) # depth
|
| 221 |
+
+ o32(0) # mipmaps
|
| 222 |
+
+ o32(0) * 11 # reserved
|
| 223 |
+
+ o32(32) # pfsize
|
| 224 |
+
+ o32(DDS_RGBA if im.mode == "RGBA" else DDPF_RGB) # pfflags
|
| 225 |
+
+ o32(0) # fourcc
|
| 226 |
+
+ o32(32 if im.mode == "RGBA" else 24) # bitcount
|
| 227 |
+
+ o32(0xFF0000) # rbitmask
|
| 228 |
+
+ o32(0xFF00) # gbitmask
|
| 229 |
+
+ o32(0xFF) # bbitmask
|
| 230 |
+
+ o32(0xFF000000 if im.mode == "RGBA" else 0) # abitmask
|
| 231 |
+
+ o32(DDSCAPS_TEXTURE) # dwCaps
|
| 232 |
+
+ o32(0) # dwCaps2
|
| 233 |
+
+ o32(0) # dwCaps3
|
| 234 |
+
+ o32(0) # dwCaps4
|
| 235 |
+
+ o32(0) # dwReserved2
|
| 236 |
+
)
|
| 237 |
+
if im.mode == "RGBA":
|
| 238 |
+
r, g, b, a = im.split()
|
| 239 |
+
im = Image.merge("RGBA", (a, r, g, b))
|
| 240 |
+
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (im.mode[::-1], 0, 1))])
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def _accept(prefix):
|
| 244 |
+
return prefix[:4] == b"DDS "
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
|
| 248 |
+
Image.register_save(DdsImageFile.format, _save)
|
| 249 |
+
Image.register_extension(DdsImageFile.format, ".dds")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/EpsImagePlugin.py
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# EPS file handling
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1995-09-01 fl Created (0.1)
|
| 9 |
+
# 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2)
|
| 10 |
+
# 1996-08-22 fl Don't choke on floating point BoundingBox values
|
| 11 |
+
# 1996-08-23 fl Handle files from Macintosh (0.3)
|
| 12 |
+
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
|
| 13 |
+
# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
|
| 14 |
+
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution
|
| 15 |
+
# resizing
|
| 16 |
+
#
|
| 17 |
+
# Copyright (c) 1997-2003 by Secret Labs AB.
|
| 18 |
+
# Copyright (c) 1995-2003 by Fredrik Lundh
|
| 19 |
+
#
|
| 20 |
+
# See the README file for information on usage and redistribution.
|
| 21 |
+
#
|
| 22 |
+
|
| 23 |
+
import io
|
| 24 |
+
import os
|
| 25 |
+
import re
|
| 26 |
+
import subprocess
|
| 27 |
+
import sys
|
| 28 |
+
import tempfile
|
| 29 |
+
|
| 30 |
+
from . import Image, ImageFile
|
| 31 |
+
from ._binary import i32le as i32
|
| 32 |
+
|
| 33 |
+
#
|
| 34 |
+
# --------------------------------------------------------------------
|
| 35 |
+
|
| 36 |
+
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
| 37 |
+
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
| 38 |
+
|
| 39 |
+
gs_windows_binary = None
|
| 40 |
+
if sys.platform.startswith("win"):
|
| 41 |
+
import shutil
|
| 42 |
+
|
| 43 |
+
for binary in ("gswin32c", "gswin64c", "gs"):
|
| 44 |
+
if shutil.which(binary) is not None:
|
| 45 |
+
gs_windows_binary = binary
|
| 46 |
+
break
|
| 47 |
+
else:
|
| 48 |
+
gs_windows_binary = False
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def has_ghostscript():
|
| 52 |
+
if gs_windows_binary:
|
| 53 |
+
return True
|
| 54 |
+
if not sys.platform.startswith("win"):
|
| 55 |
+
try:
|
| 56 |
+
subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
|
| 57 |
+
return True
|
| 58 |
+
except OSError:
|
| 59 |
+
# No Ghostscript
|
| 60 |
+
pass
|
| 61 |
+
return False
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
| 65 |
+
"""Render an image using Ghostscript"""
|
| 66 |
+
|
| 67 |
+
# Unpack decoder tile
|
| 68 |
+
decoder, tile, offset, data = tile[0]
|
| 69 |
+
length, bbox = data
|
| 70 |
+
|
| 71 |
+
# Hack to support hi-res rendering
|
| 72 |
+
scale = int(scale) or 1
|
| 73 |
+
# orig_size = size
|
| 74 |
+
# orig_bbox = bbox
|
| 75 |
+
size = (size[0] * scale, size[1] * scale)
|
| 76 |
+
# resolution is dependent on bbox and size
|
| 77 |
+
res = (
|
| 78 |
+
72.0 * size[0] / (bbox[2] - bbox[0]),
|
| 79 |
+
72.0 * size[1] / (bbox[3] - bbox[1]),
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
out_fd, outfile = tempfile.mkstemp()
|
| 83 |
+
os.close(out_fd)
|
| 84 |
+
|
| 85 |
+
infile_temp = None
|
| 86 |
+
if hasattr(fp, "name") and os.path.exists(fp.name):
|
| 87 |
+
infile = fp.name
|
| 88 |
+
else:
|
| 89 |
+
in_fd, infile_temp = tempfile.mkstemp()
|
| 90 |
+
os.close(in_fd)
|
| 91 |
+
infile = infile_temp
|
| 92 |
+
|
| 93 |
+
# Ignore length and offset!
|
| 94 |
+
# Ghostscript can read it
|
| 95 |
+
# Copy whole file to read in Ghostscript
|
| 96 |
+
with open(infile_temp, "wb") as f:
|
| 97 |
+
# fetch length of fp
|
| 98 |
+
fp.seek(0, io.SEEK_END)
|
| 99 |
+
fsize = fp.tell()
|
| 100 |
+
# ensure start position
|
| 101 |
+
# go back
|
| 102 |
+
fp.seek(0)
|
| 103 |
+
lengthfile = fsize
|
| 104 |
+
while lengthfile > 0:
|
| 105 |
+
s = fp.read(min(lengthfile, 100 * 1024))
|
| 106 |
+
if not s:
|
| 107 |
+
break
|
| 108 |
+
lengthfile -= len(s)
|
| 109 |
+
f.write(s)
|
| 110 |
+
|
| 111 |
+
device = "pngalpha" if transparency else "ppmraw"
|
| 112 |
+
|
| 113 |
+
# Build Ghostscript command
|
| 114 |
+
command = [
|
| 115 |
+
"gs",
|
| 116 |
+
"-q", # quiet mode
|
| 117 |
+
"-g%dx%d" % size, # set output geometry (pixels)
|
| 118 |
+
"-r%fx%f" % res, # set input DPI (dots per inch)
|
| 119 |
+
"-dBATCH", # exit after processing
|
| 120 |
+
"-dNOPAUSE", # don't pause between pages
|
| 121 |
+
"-dSAFER", # safe mode
|
| 122 |
+
f"-sDEVICE={device}",
|
| 123 |
+
f"-sOutputFile={outfile}", # output file
|
| 124 |
+
# adjust for image origin
|
| 125 |
+
"-c",
|
| 126 |
+
f"{-bbox[0]} {-bbox[1]} translate",
|
| 127 |
+
"-f",
|
| 128 |
+
infile, # input file
|
| 129 |
+
# showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
|
| 130 |
+
"-c",
|
| 131 |
+
"showpage",
|
| 132 |
+
]
|
| 133 |
+
|
| 134 |
+
if gs_windows_binary is not None:
|
| 135 |
+
if not gs_windows_binary:
|
| 136 |
+
raise OSError("Unable to locate Ghostscript on paths")
|
| 137 |
+
command[0] = gs_windows_binary
|
| 138 |
+
|
| 139 |
+
# push data through Ghostscript
|
| 140 |
+
try:
|
| 141 |
+
startupinfo = None
|
| 142 |
+
if sys.platform.startswith("win"):
|
| 143 |
+
startupinfo = subprocess.STARTUPINFO()
|
| 144 |
+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
| 145 |
+
subprocess.check_call(command, startupinfo=startupinfo)
|
| 146 |
+
out_im = Image.open(outfile)
|
| 147 |
+
out_im.load()
|
| 148 |
+
finally:
|
| 149 |
+
try:
|
| 150 |
+
os.unlink(outfile)
|
| 151 |
+
if infile_temp:
|
| 152 |
+
os.unlink(infile_temp)
|
| 153 |
+
except OSError:
|
| 154 |
+
pass
|
| 155 |
+
|
| 156 |
+
im = out_im.im.copy()
|
| 157 |
+
out_im.close()
|
| 158 |
+
return im
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
class PSFile:
|
| 162 |
+
"""
|
| 163 |
+
Wrapper for bytesio object that treats either CR or LF as end of line.
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def __init__(self, fp):
|
| 167 |
+
self.fp = fp
|
| 168 |
+
self.char = None
|
| 169 |
+
|
| 170 |
+
def seek(self, offset, whence=io.SEEK_SET):
|
| 171 |
+
self.char = None
|
| 172 |
+
self.fp.seek(offset, whence)
|
| 173 |
+
|
| 174 |
+
def readline(self):
|
| 175 |
+
s = [self.char or b""]
|
| 176 |
+
self.char = None
|
| 177 |
+
|
| 178 |
+
c = self.fp.read(1)
|
| 179 |
+
while (c not in b"\r\n") and len(c):
|
| 180 |
+
s.append(c)
|
| 181 |
+
c = self.fp.read(1)
|
| 182 |
+
|
| 183 |
+
self.char = self.fp.read(1)
|
| 184 |
+
# line endings can be 1 or 2 of \r \n, in either order
|
| 185 |
+
if self.char in b"\r\n":
|
| 186 |
+
self.char = None
|
| 187 |
+
|
| 188 |
+
return b"".join(s).decode("latin-1")
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def _accept(prefix):
|
| 192 |
+
return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
##
|
| 196 |
+
# Image plugin for Encapsulated PostScript. This plugin supports only
|
| 197 |
+
# a few variants of this format.
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
class EpsImageFile(ImageFile.ImageFile):
|
| 201 |
+
"""EPS File Parser for the Python Imaging Library"""
|
| 202 |
+
|
| 203 |
+
format = "EPS"
|
| 204 |
+
format_description = "Encapsulated Postscript"
|
| 205 |
+
|
| 206 |
+
mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
|
| 207 |
+
|
| 208 |
+
def _open(self):
|
| 209 |
+
(length, offset) = self._find_offset(self.fp)
|
| 210 |
+
|
| 211 |
+
# Rewrap the open file pointer in something that will
|
| 212 |
+
# convert line endings and decode to latin-1.
|
| 213 |
+
fp = PSFile(self.fp)
|
| 214 |
+
|
| 215 |
+
# go to offset - start of "%!PS"
|
| 216 |
+
fp.seek(offset)
|
| 217 |
+
|
| 218 |
+
box = None
|
| 219 |
+
|
| 220 |
+
self.mode = "RGB"
|
| 221 |
+
self._size = 1, 1 # FIXME: huh?
|
| 222 |
+
|
| 223 |
+
#
|
| 224 |
+
# Load EPS header
|
| 225 |
+
|
| 226 |
+
s_raw = fp.readline()
|
| 227 |
+
s = s_raw.strip("\r\n")
|
| 228 |
+
|
| 229 |
+
while s_raw:
|
| 230 |
+
if s:
|
| 231 |
+
if len(s) > 255:
|
| 232 |
+
raise SyntaxError("not an EPS file")
|
| 233 |
+
|
| 234 |
+
try:
|
| 235 |
+
m = split.match(s)
|
| 236 |
+
except re.error as e:
|
| 237 |
+
raise SyntaxError("not an EPS file") from e
|
| 238 |
+
|
| 239 |
+
if m:
|
| 240 |
+
k, v = m.group(1, 2)
|
| 241 |
+
self.info[k] = v
|
| 242 |
+
if k == "BoundingBox":
|
| 243 |
+
try:
|
| 244 |
+
# Note: The DSC spec says that BoundingBox
|
| 245 |
+
# fields should be integers, but some drivers
|
| 246 |
+
# put floating point values there anyway.
|
| 247 |
+
box = [int(float(i)) for i in v.split()]
|
| 248 |
+
self._size = box[2] - box[0], box[3] - box[1]
|
| 249 |
+
self.tile = [
|
| 250 |
+
("eps", (0, 0) + self.size, offset, (length, box))
|
| 251 |
+
]
|
| 252 |
+
except Exception:
|
| 253 |
+
pass
|
| 254 |
+
|
| 255 |
+
else:
|
| 256 |
+
m = field.match(s)
|
| 257 |
+
if m:
|
| 258 |
+
k = m.group(1)
|
| 259 |
+
|
| 260 |
+
if k == "EndComments":
|
| 261 |
+
break
|
| 262 |
+
if k[:8] == "PS-Adobe":
|
| 263 |
+
self.info[k[:8]] = k[9:]
|
| 264 |
+
else:
|
| 265 |
+
self.info[k] = ""
|
| 266 |
+
elif s[0] == "%":
|
| 267 |
+
# handle non-DSC PostScript comments that some
|
| 268 |
+
# tools mistakenly put in the Comments section
|
| 269 |
+
pass
|
| 270 |
+
else:
|
| 271 |
+
raise OSError("bad EPS header")
|
| 272 |
+
|
| 273 |
+
s_raw = fp.readline()
|
| 274 |
+
s = s_raw.strip("\r\n")
|
| 275 |
+
|
| 276 |
+
if s and s[:1] != "%":
|
| 277 |
+
break
|
| 278 |
+
|
| 279 |
+
#
|
| 280 |
+
# Scan for an "ImageData" descriptor
|
| 281 |
+
|
| 282 |
+
while s[:1] == "%":
|
| 283 |
+
|
| 284 |
+
if len(s) > 255:
|
| 285 |
+
raise SyntaxError("not an EPS file")
|
| 286 |
+
|
| 287 |
+
if s[:11] == "%ImageData:":
|
| 288 |
+
# Encoded bitmapped image.
|
| 289 |
+
x, y, bi, mo = s[11:].split(None, 7)[:4]
|
| 290 |
+
|
| 291 |
+
if int(bi) != 8:
|
| 292 |
+
break
|
| 293 |
+
try:
|
| 294 |
+
self.mode = self.mode_map[int(mo)]
|
| 295 |
+
except ValueError:
|
| 296 |
+
break
|
| 297 |
+
|
| 298 |
+
self._size = int(x), int(y)
|
| 299 |
+
return
|
| 300 |
+
|
| 301 |
+
s = fp.readline().strip("\r\n")
|
| 302 |
+
if not s:
|
| 303 |
+
break
|
| 304 |
+
|
| 305 |
+
if not box:
|
| 306 |
+
raise OSError("cannot determine EPS bounding box")
|
| 307 |
+
|
| 308 |
+
def _find_offset(self, fp):
|
| 309 |
+
|
| 310 |
+
s = fp.read(160)
|
| 311 |
+
|
| 312 |
+
if s[:4] == b"%!PS":
|
| 313 |
+
# for HEAD without binary preview
|
| 314 |
+
fp.seek(0, io.SEEK_END)
|
| 315 |
+
length = fp.tell()
|
| 316 |
+
offset = 0
|
| 317 |
+
elif i32(s, 0) == 0xC6D3D0C5:
|
| 318 |
+
# FIX for: Some EPS file not handled correctly / issue #302
|
| 319 |
+
# EPS can contain binary data
|
| 320 |
+
# or start directly with latin coding
|
| 321 |
+
# more info see:
|
| 322 |
+
# https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
| 323 |
+
offset = i32(s, 4)
|
| 324 |
+
length = i32(s, 8)
|
| 325 |
+
else:
|
| 326 |
+
raise SyntaxError("not an EPS file")
|
| 327 |
+
|
| 328 |
+
return (length, offset)
|
| 329 |
+
|
| 330 |
+
def load(self, scale=1, transparency=False):
|
| 331 |
+
# Load EPS via Ghostscript
|
| 332 |
+
if self.tile:
|
| 333 |
+
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
|
| 334 |
+
self.mode = self.im.mode
|
| 335 |
+
self._size = self.im.size
|
| 336 |
+
self.tile = []
|
| 337 |
+
return Image.Image.load(self)
|
| 338 |
+
|
| 339 |
+
def load_seek(self, *args, **kwargs):
|
| 340 |
+
# we can't incrementally load, so force ImageFile.parser to
|
| 341 |
+
# use our custom load method by defining this method.
|
| 342 |
+
pass
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
#
|
| 346 |
+
# --------------------------------------------------------------------
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def _save(im, fp, filename, eps=1):
|
| 350 |
+
"""EPS Writer for the Python Imaging Library."""
|
| 351 |
+
|
| 352 |
+
#
|
| 353 |
+
# make sure image data is available
|
| 354 |
+
im.load()
|
| 355 |
+
|
| 356 |
+
#
|
| 357 |
+
# determine PostScript image mode
|
| 358 |
+
if im.mode == "L":
|
| 359 |
+
operator = (8, 1, b"image")
|
| 360 |
+
elif im.mode == "RGB":
|
| 361 |
+
operator = (8, 3, b"false 3 colorimage")
|
| 362 |
+
elif im.mode == "CMYK":
|
| 363 |
+
operator = (8, 4, b"false 4 colorimage")
|
| 364 |
+
else:
|
| 365 |
+
raise ValueError("image mode is not supported")
|
| 366 |
+
|
| 367 |
+
if eps:
|
| 368 |
+
#
|
| 369 |
+
# write EPS header
|
| 370 |
+
fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n")
|
| 371 |
+
fp.write(b"%%Creator: PIL 0.1 EpsEncode\n")
|
| 372 |
+
# fp.write("%%CreationDate: %s"...)
|
| 373 |
+
fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size)
|
| 374 |
+
fp.write(b"%%Pages: 1\n")
|
| 375 |
+
fp.write(b"%%EndComments\n")
|
| 376 |
+
fp.write(b"%%Page: 1 1\n")
|
| 377 |
+
fp.write(b"%%ImageData: %d %d " % im.size)
|
| 378 |
+
fp.write(b'%d %d 0 1 1 "%s"\n' % operator)
|
| 379 |
+
|
| 380 |
+
#
|
| 381 |
+
# image header
|
| 382 |
+
fp.write(b"gsave\n")
|
| 383 |
+
fp.write(b"10 dict begin\n")
|
| 384 |
+
fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1]))
|
| 385 |
+
fp.write(b"%d %d scale\n" % im.size)
|
| 386 |
+
fp.write(b"%d %d 8\n" % im.size) # <= bits
|
| 387 |
+
fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
|
| 388 |
+
fp.write(b"{ currentfile buf readhexstring pop } bind\n")
|
| 389 |
+
fp.write(operator[2] + b"\n")
|
| 390 |
+
if hasattr(fp, "flush"):
|
| 391 |
+
fp.flush()
|
| 392 |
+
|
| 393 |
+
ImageFile._save(im, fp, [("eps", (0, 0) + im.size, 0, None)])
|
| 394 |
+
|
| 395 |
+
fp.write(b"\n%%%%EndBinary\n")
|
| 396 |
+
fp.write(b"grestore end\n")
|
| 397 |
+
if hasattr(fp, "flush"):
|
| 398 |
+
fp.flush()
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
#
|
| 402 |
+
# --------------------------------------------------------------------
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
|
| 406 |
+
|
| 407 |
+
Image.register_save(EpsImageFile.format, _save)
|
| 408 |
+
|
| 409 |
+
Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
|
| 410 |
+
|
| 411 |
+
Image.register_mime(EpsImageFile.format, "application/postscript")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ExifTags.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# EXIF tags
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 2003 by Secret Labs AB
|
| 8 |
+
#
|
| 9 |
+
# See the README file for information on usage and redistribution.
|
| 10 |
+
#
|
| 11 |
+
|
| 12 |
+
"""
|
| 13 |
+
This module provides constants and clear-text names for various
|
| 14 |
+
well-known EXIF tags.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
TAGS = {
|
| 19 |
+
# possibly incomplete
|
| 20 |
+
0x0001: "InteropIndex",
|
| 21 |
+
0x000B: "ProcessingSoftware",
|
| 22 |
+
0x00FE: "NewSubfileType",
|
| 23 |
+
0x00FF: "SubfileType",
|
| 24 |
+
0x0100: "ImageWidth",
|
| 25 |
+
0x0101: "ImageLength",
|
| 26 |
+
0x0102: "BitsPerSample",
|
| 27 |
+
0x0103: "Compression",
|
| 28 |
+
0x0106: "PhotometricInterpretation",
|
| 29 |
+
0x0107: "Thresholding",
|
| 30 |
+
0x0108: "CellWidth",
|
| 31 |
+
0x0109: "CellLength",
|
| 32 |
+
0x010A: "FillOrder",
|
| 33 |
+
0x010D: "DocumentName",
|
| 34 |
+
0x010E: "ImageDescription",
|
| 35 |
+
0x010F: "Make",
|
| 36 |
+
0x0110: "Model",
|
| 37 |
+
0x0111: "StripOffsets",
|
| 38 |
+
0x0112: "Orientation",
|
| 39 |
+
0x0115: "SamplesPerPixel",
|
| 40 |
+
0x0116: "RowsPerStrip",
|
| 41 |
+
0x0117: "StripByteCounts",
|
| 42 |
+
0x0118: "MinSampleValue",
|
| 43 |
+
0x0119: "MaxSampleValue",
|
| 44 |
+
0x011A: "XResolution",
|
| 45 |
+
0x011B: "YResolution",
|
| 46 |
+
0x011C: "PlanarConfiguration",
|
| 47 |
+
0x011D: "PageName",
|
| 48 |
+
0x0120: "FreeOffsets",
|
| 49 |
+
0x0121: "FreeByteCounts",
|
| 50 |
+
0x0122: "GrayResponseUnit",
|
| 51 |
+
0x0123: "GrayResponseCurve",
|
| 52 |
+
0x0124: "T4Options",
|
| 53 |
+
0x0125: "T6Options",
|
| 54 |
+
0x0128: "ResolutionUnit",
|
| 55 |
+
0x0129: "PageNumber",
|
| 56 |
+
0x012D: "TransferFunction",
|
| 57 |
+
0x0131: "Software",
|
| 58 |
+
0x0132: "DateTime",
|
| 59 |
+
0x013B: "Artist",
|
| 60 |
+
0x013C: "HostComputer",
|
| 61 |
+
0x013D: "Predictor",
|
| 62 |
+
0x013E: "WhitePoint",
|
| 63 |
+
0x013F: "PrimaryChromaticities",
|
| 64 |
+
0x0140: "ColorMap",
|
| 65 |
+
0x0141: "HalftoneHints",
|
| 66 |
+
0x0142: "TileWidth",
|
| 67 |
+
0x0143: "TileLength",
|
| 68 |
+
0x0144: "TileOffsets",
|
| 69 |
+
0x0145: "TileByteCounts",
|
| 70 |
+
0x014A: "SubIFDs",
|
| 71 |
+
0x014C: "InkSet",
|
| 72 |
+
0x014D: "InkNames",
|
| 73 |
+
0x014E: "NumberOfInks",
|
| 74 |
+
0x0150: "DotRange",
|
| 75 |
+
0x0151: "TargetPrinter",
|
| 76 |
+
0x0152: "ExtraSamples",
|
| 77 |
+
0x0153: "SampleFormat",
|
| 78 |
+
0x0154: "SMinSampleValue",
|
| 79 |
+
0x0155: "SMaxSampleValue",
|
| 80 |
+
0x0156: "TransferRange",
|
| 81 |
+
0x0157: "ClipPath",
|
| 82 |
+
0x0158: "XClipPathUnits",
|
| 83 |
+
0x0159: "YClipPathUnits",
|
| 84 |
+
0x015A: "Indexed",
|
| 85 |
+
0x015B: "JPEGTables",
|
| 86 |
+
0x015F: "OPIProxy",
|
| 87 |
+
0x0200: "JPEGProc",
|
| 88 |
+
0x0201: "JpegIFOffset",
|
| 89 |
+
0x0202: "JpegIFByteCount",
|
| 90 |
+
0x0203: "JpegRestartInterval",
|
| 91 |
+
0x0205: "JpegLosslessPredictors",
|
| 92 |
+
0x0206: "JpegPointTransforms",
|
| 93 |
+
0x0207: "JpegQTables",
|
| 94 |
+
0x0208: "JpegDCTables",
|
| 95 |
+
0x0209: "JpegACTables",
|
| 96 |
+
0x0211: "YCbCrCoefficients",
|
| 97 |
+
0x0212: "YCbCrSubSampling",
|
| 98 |
+
0x0213: "YCbCrPositioning",
|
| 99 |
+
0x0214: "ReferenceBlackWhite",
|
| 100 |
+
0x02BC: "XMLPacket",
|
| 101 |
+
0x1000: "RelatedImageFileFormat",
|
| 102 |
+
0x1001: "RelatedImageWidth",
|
| 103 |
+
0x1002: "RelatedImageLength",
|
| 104 |
+
0x4746: "Rating",
|
| 105 |
+
0x4749: "RatingPercent",
|
| 106 |
+
0x800D: "ImageID",
|
| 107 |
+
0x828D: "CFARepeatPatternDim",
|
| 108 |
+
0x828E: "CFAPattern",
|
| 109 |
+
0x828F: "BatteryLevel",
|
| 110 |
+
0x8298: "Copyright",
|
| 111 |
+
0x829A: "ExposureTime",
|
| 112 |
+
0x829D: "FNumber",
|
| 113 |
+
0x83BB: "IPTCNAA",
|
| 114 |
+
0x8649: "ImageResources",
|
| 115 |
+
0x8769: "ExifOffset",
|
| 116 |
+
0x8773: "InterColorProfile",
|
| 117 |
+
0x8822: "ExposureProgram",
|
| 118 |
+
0x8824: "SpectralSensitivity",
|
| 119 |
+
0x8825: "GPSInfo",
|
| 120 |
+
0x8827: "ISOSpeedRatings",
|
| 121 |
+
0x8828: "OECF",
|
| 122 |
+
0x8829: "Interlace",
|
| 123 |
+
0x882A: "TimeZoneOffset",
|
| 124 |
+
0x882B: "SelfTimerMode",
|
| 125 |
+
0x8830: "SensitivityType",
|
| 126 |
+
0x8831: "StandardOutputSensitivity",
|
| 127 |
+
0x8832: "RecommendedExposureIndex",
|
| 128 |
+
0x8833: "ISOSpeed",
|
| 129 |
+
0x8834: "ISOSpeedLatitudeyyy",
|
| 130 |
+
0x8835: "ISOSpeedLatitudezzz",
|
| 131 |
+
0x9000: "ExifVersion",
|
| 132 |
+
0x9003: "DateTimeOriginal",
|
| 133 |
+
0x9004: "DateTimeDigitized",
|
| 134 |
+
0x9010: "OffsetTime",
|
| 135 |
+
0x9011: "OffsetTimeOriginal",
|
| 136 |
+
0x9012: "OffsetTimeDigitized",
|
| 137 |
+
0x9101: "ComponentsConfiguration",
|
| 138 |
+
0x9102: "CompressedBitsPerPixel",
|
| 139 |
+
0x9201: "ShutterSpeedValue",
|
| 140 |
+
0x9202: "ApertureValue",
|
| 141 |
+
0x9203: "BrightnessValue",
|
| 142 |
+
0x9204: "ExposureBiasValue",
|
| 143 |
+
0x9205: "MaxApertureValue",
|
| 144 |
+
0x9206: "SubjectDistance",
|
| 145 |
+
0x9207: "MeteringMode",
|
| 146 |
+
0x9208: "LightSource",
|
| 147 |
+
0x9209: "Flash",
|
| 148 |
+
0x920A: "FocalLength",
|
| 149 |
+
0x920B: "FlashEnergy",
|
| 150 |
+
0x920C: "SpatialFrequencyResponse",
|
| 151 |
+
0x920D: "Noise",
|
| 152 |
+
0x9211: "ImageNumber",
|
| 153 |
+
0x9212: "SecurityClassification",
|
| 154 |
+
0x9213: "ImageHistory",
|
| 155 |
+
0x9214: "SubjectLocation",
|
| 156 |
+
0x9215: "ExposureIndex",
|
| 157 |
+
0x9216: "TIFF/EPStandardID",
|
| 158 |
+
0x927C: "MakerNote",
|
| 159 |
+
0x9286: "UserComment",
|
| 160 |
+
0x9290: "SubsecTime",
|
| 161 |
+
0x9291: "SubsecTimeOriginal",
|
| 162 |
+
0x9292: "SubsecTimeDigitized",
|
| 163 |
+
0x9400: "AmbientTemperature",
|
| 164 |
+
0x9401: "Humidity",
|
| 165 |
+
0x9402: "Pressure",
|
| 166 |
+
0x9403: "WaterDepth",
|
| 167 |
+
0x9404: "Acceleration",
|
| 168 |
+
0x9405: "CameraElevationAngle",
|
| 169 |
+
0x9C9B: "XPTitle",
|
| 170 |
+
0x9C9C: "XPComment",
|
| 171 |
+
0x9C9D: "XPAuthor",
|
| 172 |
+
0x9C9E: "XPKeywords",
|
| 173 |
+
0x9C9F: "XPSubject",
|
| 174 |
+
0xA000: "FlashPixVersion",
|
| 175 |
+
0xA001: "ColorSpace",
|
| 176 |
+
0xA002: "ExifImageWidth",
|
| 177 |
+
0xA003: "ExifImageHeight",
|
| 178 |
+
0xA004: "RelatedSoundFile",
|
| 179 |
+
0xA005: "ExifInteroperabilityOffset",
|
| 180 |
+
0xA20B: "FlashEnergy",
|
| 181 |
+
0xA20C: "SpatialFrequencyResponse",
|
| 182 |
+
0xA20E: "FocalPlaneXResolution",
|
| 183 |
+
0xA20F: "FocalPlaneYResolution",
|
| 184 |
+
0xA210: "FocalPlaneResolutionUnit",
|
| 185 |
+
0xA214: "SubjectLocation",
|
| 186 |
+
0xA215: "ExposureIndex",
|
| 187 |
+
0xA217: "SensingMethod",
|
| 188 |
+
0xA300: "FileSource",
|
| 189 |
+
0xA301: "SceneType",
|
| 190 |
+
0xA302: "CFAPattern",
|
| 191 |
+
0xA401: "CustomRendered",
|
| 192 |
+
0xA402: "ExposureMode",
|
| 193 |
+
0xA403: "WhiteBalance",
|
| 194 |
+
0xA404: "DigitalZoomRatio",
|
| 195 |
+
0xA405: "FocalLengthIn35mmFilm",
|
| 196 |
+
0xA406: "SceneCaptureType",
|
| 197 |
+
0xA407: "GainControl",
|
| 198 |
+
0xA408: "Contrast",
|
| 199 |
+
0xA409: "Saturation",
|
| 200 |
+
0xA40A: "Sharpness",
|
| 201 |
+
0xA40B: "DeviceSettingDescription",
|
| 202 |
+
0xA40C: "SubjectDistanceRange",
|
| 203 |
+
0xA420: "ImageUniqueID",
|
| 204 |
+
0xA430: "CameraOwnerName",
|
| 205 |
+
0xA431: "BodySerialNumber",
|
| 206 |
+
0xA432: "LensSpecification",
|
| 207 |
+
0xA433: "LensMake",
|
| 208 |
+
0xA434: "LensModel",
|
| 209 |
+
0xA435: "LensSerialNumber",
|
| 210 |
+
0xA460: "CompositeImage",
|
| 211 |
+
0xA461: "CompositeImageCount",
|
| 212 |
+
0xA462: "CompositeImageExposureTimes",
|
| 213 |
+
0xA500: "Gamma",
|
| 214 |
+
0xC4A5: "PrintImageMatching",
|
| 215 |
+
0xC612: "DNGVersion",
|
| 216 |
+
0xC613: "DNGBackwardVersion",
|
| 217 |
+
0xC614: "UniqueCameraModel",
|
| 218 |
+
0xC615: "LocalizedCameraModel",
|
| 219 |
+
0xC616: "CFAPlaneColor",
|
| 220 |
+
0xC617: "CFALayout",
|
| 221 |
+
0xC618: "LinearizationTable",
|
| 222 |
+
0xC619: "BlackLevelRepeatDim",
|
| 223 |
+
0xC61A: "BlackLevel",
|
| 224 |
+
0xC61B: "BlackLevelDeltaH",
|
| 225 |
+
0xC61C: "BlackLevelDeltaV",
|
| 226 |
+
0xC61D: "WhiteLevel",
|
| 227 |
+
0xC61E: "DefaultScale",
|
| 228 |
+
0xC61F: "DefaultCropOrigin",
|
| 229 |
+
0xC620: "DefaultCropSize",
|
| 230 |
+
0xC621: "ColorMatrix1",
|
| 231 |
+
0xC622: "ColorMatrix2",
|
| 232 |
+
0xC623: "CameraCalibration1",
|
| 233 |
+
0xC624: "CameraCalibration2",
|
| 234 |
+
0xC625: "ReductionMatrix1",
|
| 235 |
+
0xC626: "ReductionMatrix2",
|
| 236 |
+
0xC627: "AnalogBalance",
|
| 237 |
+
0xC628: "AsShotNeutral",
|
| 238 |
+
0xC629: "AsShotWhiteXY",
|
| 239 |
+
0xC62A: "BaselineExposure",
|
| 240 |
+
0xC62B: "BaselineNoise",
|
| 241 |
+
0xC62C: "BaselineSharpness",
|
| 242 |
+
0xC62D: "BayerGreenSplit",
|
| 243 |
+
0xC62E: "LinearResponseLimit",
|
| 244 |
+
0xC62F: "CameraSerialNumber",
|
| 245 |
+
0xC630: "LensInfo",
|
| 246 |
+
0xC631: "ChromaBlurRadius",
|
| 247 |
+
0xC632: "AntiAliasStrength",
|
| 248 |
+
0xC633: "ShadowScale",
|
| 249 |
+
0xC634: "DNGPrivateData",
|
| 250 |
+
0xC635: "MakerNoteSafety",
|
| 251 |
+
0xC65A: "CalibrationIlluminant1",
|
| 252 |
+
0xC65B: "CalibrationIlluminant2",
|
| 253 |
+
0xC65C: "BestQualityScale",
|
| 254 |
+
0xC65D: "RawDataUniqueID",
|
| 255 |
+
0xC68B: "OriginalRawFileName",
|
| 256 |
+
0xC68C: "OriginalRawFileData",
|
| 257 |
+
0xC68D: "ActiveArea",
|
| 258 |
+
0xC68E: "MaskedAreas",
|
| 259 |
+
0xC68F: "AsShotICCProfile",
|
| 260 |
+
0xC690: "AsShotPreProfileMatrix",
|
| 261 |
+
0xC691: "CurrentICCProfile",
|
| 262 |
+
0xC692: "CurrentPreProfileMatrix",
|
| 263 |
+
0xC6BF: "ColorimetricReference",
|
| 264 |
+
0xC6F3: "CameraCalibrationSignature",
|
| 265 |
+
0xC6F4: "ProfileCalibrationSignature",
|
| 266 |
+
0xC6F6: "AsShotProfileName",
|
| 267 |
+
0xC6F7: "NoiseReductionApplied",
|
| 268 |
+
0xC6F8: "ProfileName",
|
| 269 |
+
0xC6F9: "ProfileHueSatMapDims",
|
| 270 |
+
0xC6FA: "ProfileHueSatMapData1",
|
| 271 |
+
0xC6FB: "ProfileHueSatMapData2",
|
| 272 |
+
0xC6FC: "ProfileToneCurve",
|
| 273 |
+
0xC6FD: "ProfileEmbedPolicy",
|
| 274 |
+
0xC6FE: "ProfileCopyright",
|
| 275 |
+
0xC714: "ForwardMatrix1",
|
| 276 |
+
0xC715: "ForwardMatrix2",
|
| 277 |
+
0xC716: "PreviewApplicationName",
|
| 278 |
+
0xC717: "PreviewApplicationVersion",
|
| 279 |
+
0xC718: "PreviewSettingsName",
|
| 280 |
+
0xC719: "PreviewSettingsDigest",
|
| 281 |
+
0xC71A: "PreviewColorSpace",
|
| 282 |
+
0xC71B: "PreviewDateTime",
|
| 283 |
+
0xC71C: "RawImageDigest",
|
| 284 |
+
0xC71D: "OriginalRawFileDigest",
|
| 285 |
+
0xC71E: "SubTileBlockSize",
|
| 286 |
+
0xC71F: "RowInterleaveFactor",
|
| 287 |
+
0xC725: "ProfileLookTableDims",
|
| 288 |
+
0xC726: "ProfileLookTableData",
|
| 289 |
+
0xC740: "OpcodeList1",
|
| 290 |
+
0xC741: "OpcodeList2",
|
| 291 |
+
0xC74E: "OpcodeList3",
|
| 292 |
+
0xC761: "NoiseProfile",
|
| 293 |
+
}
|
| 294 |
+
"""Maps EXIF tags to tag names."""
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
GPSTAGS = {
|
| 298 |
+
0: "GPSVersionID",
|
| 299 |
+
1: "GPSLatitudeRef",
|
| 300 |
+
2: "GPSLatitude",
|
| 301 |
+
3: "GPSLongitudeRef",
|
| 302 |
+
4: "GPSLongitude",
|
| 303 |
+
5: "GPSAltitudeRef",
|
| 304 |
+
6: "GPSAltitude",
|
| 305 |
+
7: "GPSTimeStamp",
|
| 306 |
+
8: "GPSSatellites",
|
| 307 |
+
9: "GPSStatus",
|
| 308 |
+
10: "GPSMeasureMode",
|
| 309 |
+
11: "GPSDOP",
|
| 310 |
+
12: "GPSSpeedRef",
|
| 311 |
+
13: "GPSSpeed",
|
| 312 |
+
14: "GPSTrackRef",
|
| 313 |
+
15: "GPSTrack",
|
| 314 |
+
16: "GPSImgDirectionRef",
|
| 315 |
+
17: "GPSImgDirection",
|
| 316 |
+
18: "GPSMapDatum",
|
| 317 |
+
19: "GPSDestLatitudeRef",
|
| 318 |
+
20: "GPSDestLatitude",
|
| 319 |
+
21: "GPSDestLongitudeRef",
|
| 320 |
+
22: "GPSDestLongitude",
|
| 321 |
+
23: "GPSDestBearingRef",
|
| 322 |
+
24: "GPSDestBearing",
|
| 323 |
+
25: "GPSDestDistanceRef",
|
| 324 |
+
26: "GPSDestDistance",
|
| 325 |
+
27: "GPSProcessingMethod",
|
| 326 |
+
28: "GPSAreaInformation",
|
| 327 |
+
29: "GPSDateStamp",
|
| 328 |
+
30: "GPSDifferential",
|
| 329 |
+
31: "GPSHPositioningError",
|
| 330 |
+
}
|
| 331 |
+
"""Maps EXIF GPS tags to tag names."""
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FitsStubImagePlugin.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# FITS stub adapter
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 1998-2003 by Fredrik Lundh
|
| 8 |
+
#
|
| 9 |
+
# See the README file for information on usage and redistribution.
|
| 10 |
+
#
|
| 11 |
+
|
| 12 |
+
import warnings
|
| 13 |
+
|
| 14 |
+
from . import FitsImagePlugin, Image, ImageFile
|
| 15 |
+
|
| 16 |
+
_handler = None
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def register_handler(handler):
|
| 20 |
+
"""
|
| 21 |
+
Install application-specific FITS image handler.
|
| 22 |
+
|
| 23 |
+
:param handler: Handler object.
|
| 24 |
+
"""
|
| 25 |
+
global _handler
|
| 26 |
+
_handler = handler
|
| 27 |
+
|
| 28 |
+
warnings.warn(
|
| 29 |
+
"FitsStubImagePlugin is deprecated and will be removed in Pillow "
|
| 30 |
+
"10 (2023-07-01). FITS images can now be read without a handler through "
|
| 31 |
+
"FitsImagePlugin instead.",
|
| 32 |
+
DeprecationWarning,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# Override FitsImagePlugin with this handler
|
| 36 |
+
# for backwards compatibility
|
| 37 |
+
try:
|
| 38 |
+
Image.ID.remove(FITSStubImageFile.format)
|
| 39 |
+
except ValueError:
|
| 40 |
+
pass
|
| 41 |
+
|
| 42 |
+
Image.register_open(
|
| 43 |
+
FITSStubImageFile.format, FITSStubImageFile, FitsImagePlugin._accept
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class FITSStubImageFile(ImageFile.StubImageFile):
|
| 48 |
+
|
| 49 |
+
format = FitsImagePlugin.FitsImageFile.format
|
| 50 |
+
format_description = FitsImagePlugin.FitsImageFile.format_description
|
| 51 |
+
|
| 52 |
+
def _open(self):
|
| 53 |
+
offset = self.fp.tell()
|
| 54 |
+
|
| 55 |
+
im = FitsImagePlugin.FitsImageFile(self.fp)
|
| 56 |
+
self._size = im.size
|
| 57 |
+
self.mode = im.mode
|
| 58 |
+
self.tile = []
|
| 59 |
+
|
| 60 |
+
self.fp.seek(offset)
|
| 61 |
+
|
| 62 |
+
loader = self._load()
|
| 63 |
+
if loader:
|
| 64 |
+
loader.open(self)
|
| 65 |
+
|
| 66 |
+
def _load(self):
|
| 67 |
+
return _handler
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _save(im, fp, filename):
|
| 71 |
+
raise OSError("FITS save handler not installed")
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# --------------------------------------------------------------------
|
| 75 |
+
# Registry
|
| 76 |
+
|
| 77 |
+
Image.register_save(FITSStubImageFile.format, _save)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FliImagePlugin.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# FLI/FLC file handling.
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 95-09-01 fl Created
|
| 9 |
+
# 97-01-03 fl Fixed parser, setup decoder tile
|
| 10 |
+
# 98-07-15 fl Renamed offset attribute to avoid name clash
|
| 11 |
+
#
|
| 12 |
+
# Copyright (c) Secret Labs AB 1997-98.
|
| 13 |
+
# Copyright (c) Fredrik Lundh 1995-97.
|
| 14 |
+
#
|
| 15 |
+
# See the README file for information on usage and redistribution.
|
| 16 |
+
#
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from . import Image, ImageFile, ImagePalette
|
| 20 |
+
from ._binary import i16le as i16
|
| 21 |
+
from ._binary import i32le as i32
|
| 22 |
+
from ._binary import o8
|
| 23 |
+
|
| 24 |
+
#
|
| 25 |
+
# decoder
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _accept(prefix):
|
| 29 |
+
return (
|
| 30 |
+
len(prefix) >= 6
|
| 31 |
+
and i16(prefix, 4) in [0xAF11, 0xAF12]
|
| 32 |
+
and i16(prefix, 14) in [0, 3] # flags
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
##
|
| 37 |
+
# Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
|
| 38 |
+
# method to load individual frames.
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class FliImageFile(ImageFile.ImageFile):
|
| 42 |
+
|
| 43 |
+
format = "FLI"
|
| 44 |
+
format_description = "Autodesk FLI/FLC Animation"
|
| 45 |
+
_close_exclusive_fp_after_loading = False
|
| 46 |
+
|
| 47 |
+
def _open(self):
|
| 48 |
+
|
| 49 |
+
# HEAD
|
| 50 |
+
s = self.fp.read(128)
|
| 51 |
+
if not (_accept(s) and s[20:22] == b"\x00\x00"):
|
| 52 |
+
raise SyntaxError("not an FLI/FLC file")
|
| 53 |
+
|
| 54 |
+
# frames
|
| 55 |
+
self.n_frames = i16(s, 6)
|
| 56 |
+
self.is_animated = self.n_frames > 1
|
| 57 |
+
|
| 58 |
+
# image characteristics
|
| 59 |
+
self.mode = "P"
|
| 60 |
+
self._size = i16(s, 8), i16(s, 10)
|
| 61 |
+
|
| 62 |
+
# animation speed
|
| 63 |
+
duration = i32(s, 16)
|
| 64 |
+
magic = i16(s, 4)
|
| 65 |
+
if magic == 0xAF11:
|
| 66 |
+
duration = (duration * 1000) // 70
|
| 67 |
+
self.info["duration"] = duration
|
| 68 |
+
|
| 69 |
+
# look for palette
|
| 70 |
+
palette = [(a, a, a) for a in range(256)]
|
| 71 |
+
|
| 72 |
+
s = self.fp.read(16)
|
| 73 |
+
|
| 74 |
+
self.__offset = 128
|
| 75 |
+
|
| 76 |
+
if i16(s, 4) == 0xF100:
|
| 77 |
+
# prefix chunk; ignore it
|
| 78 |
+
self.__offset = self.__offset + i32(s)
|
| 79 |
+
s = self.fp.read(16)
|
| 80 |
+
|
| 81 |
+
if i16(s, 4) == 0xF1FA:
|
| 82 |
+
# look for palette chunk
|
| 83 |
+
s = self.fp.read(6)
|
| 84 |
+
if i16(s, 4) == 11:
|
| 85 |
+
self._palette(palette, 2)
|
| 86 |
+
elif i16(s, 4) == 4:
|
| 87 |
+
self._palette(palette, 0)
|
| 88 |
+
|
| 89 |
+
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
|
| 90 |
+
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
| 91 |
+
|
| 92 |
+
# set things up to decode first frame
|
| 93 |
+
self.__frame = -1
|
| 94 |
+
self.__fp = self.fp
|
| 95 |
+
self.__rewind = self.fp.tell()
|
| 96 |
+
self.seek(0)
|
| 97 |
+
|
| 98 |
+
def _palette(self, palette, shift):
|
| 99 |
+
# load palette
|
| 100 |
+
|
| 101 |
+
i = 0
|
| 102 |
+
for e in range(i16(self.fp.read(2))):
|
| 103 |
+
s = self.fp.read(2)
|
| 104 |
+
i = i + s[0]
|
| 105 |
+
n = s[1]
|
| 106 |
+
if n == 0:
|
| 107 |
+
n = 256
|
| 108 |
+
s = self.fp.read(n * 3)
|
| 109 |
+
for n in range(0, len(s), 3):
|
| 110 |
+
r = s[n] << shift
|
| 111 |
+
g = s[n + 1] << shift
|
| 112 |
+
b = s[n + 2] << shift
|
| 113 |
+
palette[i] = (r, g, b)
|
| 114 |
+
i += 1
|
| 115 |
+
|
| 116 |
+
def seek(self, frame):
|
| 117 |
+
if not self._seek_check(frame):
|
| 118 |
+
return
|
| 119 |
+
if frame < self.__frame:
|
| 120 |
+
self._seek(0)
|
| 121 |
+
|
| 122 |
+
for f in range(self.__frame + 1, frame + 1):
|
| 123 |
+
self._seek(f)
|
| 124 |
+
|
| 125 |
+
def _seek(self, frame):
|
| 126 |
+
if frame == 0:
|
| 127 |
+
self.__frame = -1
|
| 128 |
+
self.__fp.seek(self.__rewind)
|
| 129 |
+
self.__offset = 128
|
| 130 |
+
else:
|
| 131 |
+
# ensure that the previous frame was loaded
|
| 132 |
+
self.load()
|
| 133 |
+
|
| 134 |
+
if frame != self.__frame + 1:
|
| 135 |
+
raise ValueError(f"cannot seek to frame {frame}")
|
| 136 |
+
self.__frame = frame
|
| 137 |
+
|
| 138 |
+
# move to next frame
|
| 139 |
+
self.fp = self.__fp
|
| 140 |
+
self.fp.seek(self.__offset)
|
| 141 |
+
|
| 142 |
+
s = self.fp.read(4)
|
| 143 |
+
if not s:
|
| 144 |
+
raise EOFError
|
| 145 |
+
|
| 146 |
+
framesize = i32(s)
|
| 147 |
+
|
| 148 |
+
self.decodermaxblock = framesize
|
| 149 |
+
self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
|
| 150 |
+
|
| 151 |
+
self.__offset += framesize
|
| 152 |
+
|
| 153 |
+
def tell(self):
|
| 154 |
+
return self.__frame
|
| 155 |
+
|
| 156 |
+
def _close__fp(self):
|
| 157 |
+
try:
|
| 158 |
+
if self.__fp != self.fp:
|
| 159 |
+
self.__fp.close()
|
| 160 |
+
except AttributeError:
|
| 161 |
+
pass
|
| 162 |
+
finally:
|
| 163 |
+
self.__fp = None
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
#
|
| 167 |
+
# registry
|
| 168 |
+
|
| 169 |
+
Image.register_open(FliImageFile.format, FliImageFile, _accept)
|
| 170 |
+
|
| 171 |
+
Image.register_extensions(FliImageFile.format, [".fli", ".flc"])
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FontFile.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# base class for raster font file parsers
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 1997-06-05 fl created
|
| 9 |
+
# 1997-08-19 fl restrict image width
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) 1997-1998 by Secret Labs AB
|
| 12 |
+
# Copyright (c) 1997-1998 by Fredrik Lundh
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
|
| 20 |
+
from . import Image, _binary
|
| 21 |
+
|
| 22 |
+
WIDTH = 800
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def puti16(fp, values):
|
| 26 |
+
"""Write network order (big-endian) 16-bit sequence"""
|
| 27 |
+
for v in values:
|
| 28 |
+
if v < 0:
|
| 29 |
+
v += 65536
|
| 30 |
+
fp.write(_binary.o16be(v))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class FontFile:
|
| 34 |
+
"""Base class for raster font file handlers."""
|
| 35 |
+
|
| 36 |
+
bitmap = None
|
| 37 |
+
|
| 38 |
+
def __init__(self):
|
| 39 |
+
|
| 40 |
+
self.info = {}
|
| 41 |
+
self.glyph = [None] * 256
|
| 42 |
+
|
| 43 |
+
def __getitem__(self, ix):
|
| 44 |
+
return self.glyph[ix]
|
| 45 |
+
|
| 46 |
+
def compile(self):
|
| 47 |
+
"""Create metrics and bitmap"""
|
| 48 |
+
|
| 49 |
+
if self.bitmap:
|
| 50 |
+
return
|
| 51 |
+
|
| 52 |
+
# create bitmap large enough to hold all data
|
| 53 |
+
h = w = maxwidth = 0
|
| 54 |
+
lines = 1
|
| 55 |
+
for glyph in self:
|
| 56 |
+
if glyph:
|
| 57 |
+
d, dst, src, im = glyph
|
| 58 |
+
h = max(h, src[3] - src[1])
|
| 59 |
+
w = w + (src[2] - src[0])
|
| 60 |
+
if w > WIDTH:
|
| 61 |
+
lines += 1
|
| 62 |
+
w = src[2] - src[0]
|
| 63 |
+
maxwidth = max(maxwidth, w)
|
| 64 |
+
|
| 65 |
+
xsize = maxwidth
|
| 66 |
+
ysize = lines * h
|
| 67 |
+
|
| 68 |
+
if xsize == 0 and ysize == 0:
|
| 69 |
+
return ""
|
| 70 |
+
|
| 71 |
+
self.ysize = h
|
| 72 |
+
|
| 73 |
+
# paste glyphs into bitmap
|
| 74 |
+
self.bitmap = Image.new("1", (xsize, ysize))
|
| 75 |
+
self.metrics = [None] * 256
|
| 76 |
+
x = y = 0
|
| 77 |
+
for i in range(256):
|
| 78 |
+
glyph = self[i]
|
| 79 |
+
if glyph:
|
| 80 |
+
d, dst, src, im = glyph
|
| 81 |
+
xx = src[2] - src[0]
|
| 82 |
+
# yy = src[3] - src[1]
|
| 83 |
+
x0, y0 = x, y
|
| 84 |
+
x = x + xx
|
| 85 |
+
if x > WIDTH:
|
| 86 |
+
x, y = 0, y + h
|
| 87 |
+
x0, y0 = x, y
|
| 88 |
+
x = xx
|
| 89 |
+
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
|
| 90 |
+
self.bitmap.paste(im.crop(src), s)
|
| 91 |
+
self.metrics[i] = d, dst, s
|
| 92 |
+
|
| 93 |
+
def save(self, filename):
|
| 94 |
+
"""Save font"""
|
| 95 |
+
|
| 96 |
+
self.compile()
|
| 97 |
+
|
| 98 |
+
# font data
|
| 99 |
+
self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
|
| 100 |
+
|
| 101 |
+
# font metrics
|
| 102 |
+
with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
|
| 103 |
+
fp.write(b"PILfont\n")
|
| 104 |
+
fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!!
|
| 105 |
+
fp.write(b"DATA\n")
|
| 106 |
+
for id in range(256):
|
| 107 |
+
m = self.metrics[id]
|
| 108 |
+
if not m:
|
| 109 |
+
puti16(fp, [0] * 10)
|
| 110 |
+
else:
|
| 111 |
+
puti16(fp, m[0] + m[1] + m[2])
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FpxImagePlugin.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# THIS IS WORK IN PROGRESS
|
| 3 |
+
#
|
| 4 |
+
# The Python Imaging Library.
|
| 5 |
+
# $Id$
|
| 6 |
+
#
|
| 7 |
+
# FlashPix support for PIL
|
| 8 |
+
#
|
| 9 |
+
# History:
|
| 10 |
+
# 97-01-25 fl Created (reads uncompressed RGB images only)
|
| 11 |
+
#
|
| 12 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 13 |
+
# Copyright (c) Fredrik Lundh 1997.
|
| 14 |
+
#
|
| 15 |
+
# See the README file for information on usage and redistribution.
|
| 16 |
+
#
|
| 17 |
+
import olefile
|
| 18 |
+
|
| 19 |
+
from . import Image, ImageFile
|
| 20 |
+
from ._binary import i32le as i32
|
| 21 |
+
|
| 22 |
+
# we map from colour field tuples to (mode, rawmode) descriptors
|
| 23 |
+
MODES = {
|
| 24 |
+
# opacity
|
| 25 |
+
(0x00007FFE): ("A", "L"),
|
| 26 |
+
# monochrome
|
| 27 |
+
(0x00010000,): ("L", "L"),
|
| 28 |
+
(0x00018000, 0x00017FFE): ("RGBA", "LA"),
|
| 29 |
+
# photo YCC
|
| 30 |
+
(0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
|
| 31 |
+
(0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"),
|
| 32 |
+
# standard RGB (NIFRGB)
|
| 33 |
+
(0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
|
| 34 |
+
(0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"),
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
#
|
| 39 |
+
# --------------------------------------------------------------------
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _accept(prefix):
|
| 43 |
+
return prefix[:8] == olefile.MAGIC
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
##
|
| 47 |
+
# Image plugin for the FlashPix images.
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class FpxImageFile(ImageFile.ImageFile):
|
| 51 |
+
|
| 52 |
+
format = "FPX"
|
| 53 |
+
format_description = "FlashPix"
|
| 54 |
+
|
| 55 |
+
def _open(self):
|
| 56 |
+
#
|
| 57 |
+
# read the OLE directory and see if this is a likely
|
| 58 |
+
# to be a FlashPix file
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
self.ole = olefile.OleFileIO(self.fp)
|
| 62 |
+
except OSError as e:
|
| 63 |
+
raise SyntaxError("not an FPX file; invalid OLE file") from e
|
| 64 |
+
|
| 65 |
+
if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
|
| 66 |
+
raise SyntaxError("not an FPX file; bad root CLSID")
|
| 67 |
+
|
| 68 |
+
self._open_index(1)
|
| 69 |
+
|
| 70 |
+
def _open_index(self, index=1):
|
| 71 |
+
#
|
| 72 |
+
# get the Image Contents Property Set
|
| 73 |
+
|
| 74 |
+
prop = self.ole.getproperties(
|
| 75 |
+
[f"Data Object Store {index:06d}", "\005Image Contents"]
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
# size (highest resolution)
|
| 79 |
+
|
| 80 |
+
self._size = prop[0x1000002], prop[0x1000003]
|
| 81 |
+
|
| 82 |
+
size = max(self.size)
|
| 83 |
+
i = 1
|
| 84 |
+
while size > 64:
|
| 85 |
+
size = size / 2
|
| 86 |
+
i += 1
|
| 87 |
+
self.maxid = i - 1
|
| 88 |
+
|
| 89 |
+
# mode. instead of using a single field for this, flashpix
|
| 90 |
+
# requires you to specify the mode for each channel in each
|
| 91 |
+
# resolution subimage, and leaves it to the decoder to make
|
| 92 |
+
# sure that they all match. for now, we'll cheat and assume
|
| 93 |
+
# that this is always the case.
|
| 94 |
+
|
| 95 |
+
id = self.maxid << 16
|
| 96 |
+
|
| 97 |
+
s = prop[0x2000002 | id]
|
| 98 |
+
|
| 99 |
+
colors = []
|
| 100 |
+
bands = i32(s, 4)
|
| 101 |
+
if bands > 4:
|
| 102 |
+
raise OSError("Invalid number of bands")
|
| 103 |
+
for i in range(bands):
|
| 104 |
+
# note: for now, we ignore the "uncalibrated" flag
|
| 105 |
+
colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
|
| 106 |
+
|
| 107 |
+
self.mode, self.rawmode = MODES[tuple(colors)]
|
| 108 |
+
|
| 109 |
+
# load JPEG tables, if any
|
| 110 |
+
self.jpeg = {}
|
| 111 |
+
for i in range(256):
|
| 112 |
+
id = 0x3000001 | (i << 16)
|
| 113 |
+
if id in prop:
|
| 114 |
+
self.jpeg[i] = prop[id]
|
| 115 |
+
|
| 116 |
+
self._open_subimage(1, self.maxid)
|
| 117 |
+
|
| 118 |
+
def _open_subimage(self, index=1, subimage=0):
|
| 119 |
+
#
|
| 120 |
+
# setup tile descriptors for a given subimage
|
| 121 |
+
|
| 122 |
+
stream = [
|
| 123 |
+
f"Data Object Store {index:06d}",
|
| 124 |
+
f"Resolution {subimage:04d}",
|
| 125 |
+
"Subimage 0000 Header",
|
| 126 |
+
]
|
| 127 |
+
|
| 128 |
+
fp = self.ole.openstream(stream)
|
| 129 |
+
|
| 130 |
+
# skip prefix
|
| 131 |
+
fp.read(28)
|
| 132 |
+
|
| 133 |
+
# header stream
|
| 134 |
+
s = fp.read(36)
|
| 135 |
+
|
| 136 |
+
size = i32(s, 4), i32(s, 8)
|
| 137 |
+
# tilecount = i32(s, 12)
|
| 138 |
+
tilesize = i32(s, 16), i32(s, 20)
|
| 139 |
+
# channels = i32(s, 24)
|
| 140 |
+
offset = i32(s, 28)
|
| 141 |
+
length = i32(s, 32)
|
| 142 |
+
|
| 143 |
+
if size != self.size:
|
| 144 |
+
raise OSError("subimage mismatch")
|
| 145 |
+
|
| 146 |
+
# get tile descriptors
|
| 147 |
+
fp.seek(28 + offset)
|
| 148 |
+
s = fp.read(i32(s, 12) * length)
|
| 149 |
+
|
| 150 |
+
x = y = 0
|
| 151 |
+
xsize, ysize = size
|
| 152 |
+
xtile, ytile = tilesize
|
| 153 |
+
self.tile = []
|
| 154 |
+
|
| 155 |
+
for i in range(0, len(s), length):
|
| 156 |
+
|
| 157 |
+
compression = i32(s, i + 8)
|
| 158 |
+
|
| 159 |
+
if compression == 0:
|
| 160 |
+
self.tile.append(
|
| 161 |
+
(
|
| 162 |
+
"raw",
|
| 163 |
+
(x, y, x + xtile, y + ytile),
|
| 164 |
+
i32(s, i) + 28,
|
| 165 |
+
(self.rawmode),
|
| 166 |
+
)
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
elif compression == 1:
|
| 170 |
+
|
| 171 |
+
# FIXME: the fill decoder is not implemented
|
| 172 |
+
self.tile.append(
|
| 173 |
+
(
|
| 174 |
+
"fill",
|
| 175 |
+
(x, y, x + xtile, y + ytile),
|
| 176 |
+
i32(s, i) + 28,
|
| 177 |
+
(self.rawmode, s[12:16]),
|
| 178 |
+
)
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
elif compression == 2:
|
| 182 |
+
|
| 183 |
+
internal_color_conversion = s[14]
|
| 184 |
+
jpeg_tables = s[15]
|
| 185 |
+
rawmode = self.rawmode
|
| 186 |
+
|
| 187 |
+
if internal_color_conversion:
|
| 188 |
+
# The image is stored as usual (usually YCbCr).
|
| 189 |
+
if rawmode == "RGBA":
|
| 190 |
+
# For "RGBA", data is stored as YCbCrA based on
|
| 191 |
+
# negative RGB. The following trick works around
|
| 192 |
+
# this problem :
|
| 193 |
+
jpegmode, rawmode = "YCbCrK", "CMYK"
|
| 194 |
+
else:
|
| 195 |
+
jpegmode = None # let the decoder decide
|
| 196 |
+
|
| 197 |
+
else:
|
| 198 |
+
# The image is stored as defined by rawmode
|
| 199 |
+
jpegmode = rawmode
|
| 200 |
+
|
| 201 |
+
self.tile.append(
|
| 202 |
+
(
|
| 203 |
+
"jpeg",
|
| 204 |
+
(x, y, x + xtile, y + ytile),
|
| 205 |
+
i32(s, i) + 28,
|
| 206 |
+
(rawmode, jpegmode),
|
| 207 |
+
)
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
# FIXME: jpeg tables are tile dependent; the prefix
|
| 211 |
+
# data must be placed in the tile descriptor itself!
|
| 212 |
+
|
| 213 |
+
if jpeg_tables:
|
| 214 |
+
self.tile_prefix = self.jpeg[jpeg_tables]
|
| 215 |
+
|
| 216 |
+
else:
|
| 217 |
+
raise OSError("unknown/invalid compression")
|
| 218 |
+
|
| 219 |
+
x = x + xtile
|
| 220 |
+
if x >= xsize:
|
| 221 |
+
x, y = 0, y + ytile
|
| 222 |
+
if y >= ysize:
|
| 223 |
+
break # isn't really required
|
| 224 |
+
|
| 225 |
+
self.stream = stream
|
| 226 |
+
self.fp = None
|
| 227 |
+
|
| 228 |
+
def load(self):
|
| 229 |
+
|
| 230 |
+
if not self.fp:
|
| 231 |
+
self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
|
| 232 |
+
|
| 233 |
+
return ImageFile.ImageFile.load(self)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
#
|
| 237 |
+
# --------------------------------------------------------------------
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
|
| 241 |
+
|
| 242 |
+
Image.register_extension(FpxImageFile.format, ".fpx")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/FtexImagePlugin.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
A Pillow loader for .ftc and .ftu files (FTEX)
|
| 3 |
+
Jerome Leclanche <jerome@leclan.ch>
|
| 4 |
+
|
| 5 |
+
The contents of this file are hereby released in the public domain (CC0)
|
| 6 |
+
Full text of the CC0 license:
|
| 7 |
+
https://creativecommons.org/publicdomain/zero/1.0/
|
| 8 |
+
|
| 9 |
+
Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
|
| 10 |
+
|
| 11 |
+
The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
|
| 12 |
+
packed custom format called FTEX. This file format uses file extensions FTC
|
| 13 |
+
and FTU.
|
| 14 |
+
* FTC files are compressed textures (using standard texture compression).
|
| 15 |
+
* FTU files are not compressed.
|
| 16 |
+
Texture File Format
|
| 17 |
+
The FTC and FTU texture files both use the same format. This
|
| 18 |
+
has the following structure:
|
| 19 |
+
{header}
|
| 20 |
+
{format_directory}
|
| 21 |
+
{data}
|
| 22 |
+
Where:
|
| 23 |
+
{header} = {
|
| 24 |
+
u32:magic,
|
| 25 |
+
u32:version,
|
| 26 |
+
u32:width,
|
| 27 |
+
u32:height,
|
| 28 |
+
u32:mipmap_count,
|
| 29 |
+
u32:format_count
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
* The "magic" number is "FTEX".
|
| 33 |
+
* "width" and "height" are the dimensions of the texture.
|
| 34 |
+
* "mipmap_count" is the number of mipmaps in the texture.
|
| 35 |
+
* "format_count" is the number of texture formats (different versions of the
|
| 36 |
+
same texture) in this file.
|
| 37 |
+
|
| 38 |
+
{format_directory} = format_count * { u32:format, u32:where }
|
| 39 |
+
|
| 40 |
+
The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB
|
| 41 |
+
uncompressed textures.
|
| 42 |
+
The texture data for a format starts at the position "where" in the file.
|
| 43 |
+
|
| 44 |
+
Each set of texture data in the file has the following structure:
|
| 45 |
+
{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
|
| 46 |
+
* "mipmap_size" is the number of bytes in that mip level. For compressed
|
| 47 |
+
textures this is the size of the texture data compressed with DXT1. For 24 bit
|
| 48 |
+
uncompressed textures, this is 3 * width * height. Following this are the image
|
| 49 |
+
bytes for that mipmap level.
|
| 50 |
+
|
| 51 |
+
Note: All data is stored in little-Endian (Intel) byte order.
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
import struct
|
| 55 |
+
import warnings
|
| 56 |
+
from enum import IntEnum
|
| 57 |
+
from io import BytesIO
|
| 58 |
+
|
| 59 |
+
from . import Image, ImageFile
|
| 60 |
+
|
| 61 |
+
MAGIC = b"FTEX"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class Format(IntEnum):
|
| 65 |
+
DXT1 = 0
|
| 66 |
+
UNCOMPRESSED = 1
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def __getattr__(name):
|
| 70 |
+
deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
|
| 71 |
+
for enum, prefix in {Format: "FORMAT_"}.items():
|
| 72 |
+
if name.startswith(prefix):
|
| 73 |
+
name = name[len(prefix) :]
|
| 74 |
+
if name in enum.__members__:
|
| 75 |
+
warnings.warn(
|
| 76 |
+
prefix
|
| 77 |
+
+ name
|
| 78 |
+
+ " is "
|
| 79 |
+
+ deprecated
|
| 80 |
+
+ "Use "
|
| 81 |
+
+ enum.__name__
|
| 82 |
+
+ "."
|
| 83 |
+
+ name
|
| 84 |
+
+ " instead.",
|
| 85 |
+
DeprecationWarning,
|
| 86 |
+
stacklevel=2,
|
| 87 |
+
)
|
| 88 |
+
return enum[name]
|
| 89 |
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class FtexImageFile(ImageFile.ImageFile):
|
| 93 |
+
format = "FTEX"
|
| 94 |
+
format_description = "Texture File Format (IW2:EOC)"
|
| 95 |
+
|
| 96 |
+
def _open(self):
|
| 97 |
+
if not _accept(self.fp.read(4)):
|
| 98 |
+
raise SyntaxError("not an FTEX file")
|
| 99 |
+
struct.unpack("<i", self.fp.read(4)) # version
|
| 100 |
+
self._size = struct.unpack("<2i", self.fp.read(8))
|
| 101 |
+
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
|
| 102 |
+
|
| 103 |
+
self.mode = "RGB"
|
| 104 |
+
|
| 105 |
+
# Only support single-format files.
|
| 106 |
+
# I don't know of any multi-format file.
|
| 107 |
+
assert format_count == 1
|
| 108 |
+
|
| 109 |
+
format, where = struct.unpack("<2i", self.fp.read(8))
|
| 110 |
+
self.fp.seek(where)
|
| 111 |
+
(mipmap_size,) = struct.unpack("<i", self.fp.read(4))
|
| 112 |
+
|
| 113 |
+
data = self.fp.read(mipmap_size)
|
| 114 |
+
|
| 115 |
+
if format == Format.DXT1:
|
| 116 |
+
self.mode = "RGBA"
|
| 117 |
+
self.tile = [("bcn", (0, 0) + self.size, 0, (1))]
|
| 118 |
+
elif format == Format.UNCOMPRESSED:
|
| 119 |
+
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
|
| 120 |
+
else:
|
| 121 |
+
raise ValueError(f"Invalid texture compression format: {repr(format)}")
|
| 122 |
+
|
| 123 |
+
self.fp.close()
|
| 124 |
+
self.fp = BytesIO(data)
|
| 125 |
+
|
| 126 |
+
def load_seek(self, pos):
|
| 127 |
+
pass
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def _accept(prefix):
|
| 131 |
+
return prefix[:4] == MAGIC
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
Image.register_open(FtexImageFile.format, FtexImageFile, _accept)
|
| 135 |
+
Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/GbrImagePlugin.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
#
|
| 4 |
+
# load a GIMP brush file
|
| 5 |
+
#
|
| 6 |
+
# History:
|
| 7 |
+
# 96-03-14 fl Created
|
| 8 |
+
# 16-01-08 es Version 2
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 11 |
+
# Copyright (c) Fredrik Lundh 1996.
|
| 12 |
+
# Copyright (c) Eric Soroos 2016.
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
#
|
| 17 |
+
# See https://github.com/GNOME/gimp/blob/mainline/devel-docs/gbr.txt for
|
| 18 |
+
# format documentation.
|
| 19 |
+
#
|
| 20 |
+
# This code Interprets version 1 and 2 .gbr files.
|
| 21 |
+
# Version 1 files are obsolete, and should not be used for new
|
| 22 |
+
# brushes.
|
| 23 |
+
# Version 2 files are saved by GIMP v2.8 (at least)
|
| 24 |
+
# Version 3 files have a format specifier of 18 for 16bit floats in
|
| 25 |
+
# the color depth field. This is currently unsupported by Pillow.
|
| 26 |
+
|
| 27 |
+
from . import Image, ImageFile
|
| 28 |
+
from ._binary import i32be as i32
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _accept(prefix):
|
| 32 |
+
return len(prefix) >= 8 and i32(prefix, 0) >= 20 and i32(prefix, 4) in (1, 2)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
##
|
| 36 |
+
# Image plugin for the GIMP brush format.
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class GbrImageFile(ImageFile.ImageFile):
|
| 40 |
+
|
| 41 |
+
format = "GBR"
|
| 42 |
+
format_description = "GIMP brush file"
|
| 43 |
+
|
| 44 |
+
def _open(self):
|
| 45 |
+
header_size = i32(self.fp.read(4))
|
| 46 |
+
if header_size < 20:
|
| 47 |
+
raise SyntaxError("not a GIMP brush")
|
| 48 |
+
version = i32(self.fp.read(4))
|
| 49 |
+
if version not in (1, 2):
|
| 50 |
+
raise SyntaxError(f"Unsupported GIMP brush version: {version}")
|
| 51 |
+
|
| 52 |
+
width = i32(self.fp.read(4))
|
| 53 |
+
height = i32(self.fp.read(4))
|
| 54 |
+
color_depth = i32(self.fp.read(4))
|
| 55 |
+
if width <= 0 or height <= 0:
|
| 56 |
+
raise SyntaxError("not a GIMP brush")
|
| 57 |
+
if color_depth not in (1, 4):
|
| 58 |
+
raise SyntaxError(f"Unsupported GIMP brush color depth: {color_depth}")
|
| 59 |
+
|
| 60 |
+
if version == 1:
|
| 61 |
+
comment_length = header_size - 20
|
| 62 |
+
else:
|
| 63 |
+
comment_length = header_size - 28
|
| 64 |
+
magic_number = self.fp.read(4)
|
| 65 |
+
if magic_number != b"GIMP":
|
| 66 |
+
raise SyntaxError("not a GIMP brush, bad magic number")
|
| 67 |
+
self.info["spacing"] = i32(self.fp.read(4))
|
| 68 |
+
|
| 69 |
+
comment = self.fp.read(comment_length)[:-1]
|
| 70 |
+
|
| 71 |
+
if color_depth == 1:
|
| 72 |
+
self.mode = "L"
|
| 73 |
+
else:
|
| 74 |
+
self.mode = "RGBA"
|
| 75 |
+
|
| 76 |
+
self._size = width, height
|
| 77 |
+
|
| 78 |
+
self.info["comment"] = comment
|
| 79 |
+
|
| 80 |
+
# Image might not be small
|
| 81 |
+
Image._decompression_bomb_check(self.size)
|
| 82 |
+
|
| 83 |
+
# Data is an uncompressed block of w * h * bytes/pixel
|
| 84 |
+
self._data_size = width * height * color_depth
|
| 85 |
+
|
| 86 |
+
def load(self):
|
| 87 |
+
if not self.im:
|
| 88 |
+
self.im = Image.core.new(self.mode, self.size)
|
| 89 |
+
self.frombytes(self.fp.read(self._data_size))
|
| 90 |
+
return Image.Image.load(self)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
#
|
| 94 |
+
# registry
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
|
| 98 |
+
Image.register_extension(GbrImageFile.format, ".gbr")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/GimpGradientFile.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# stuff to read (and render) GIMP gradient files
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 97-08-23 fl Created
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 11 |
+
# Copyright (c) Fredrik Lundh 1997.
|
| 12 |
+
#
|
| 13 |
+
# See the README file for information on usage and redistribution.
|
| 14 |
+
#
|
| 15 |
+
|
| 16 |
+
"""
|
| 17 |
+
Stuff to translate curve segments to palette values (derived from
|
| 18 |
+
the corresponding code in GIMP, written by Federico Mena Quintero.
|
| 19 |
+
See the GIMP distribution for more information.)
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
from math import log, pi, sin, sqrt
|
| 24 |
+
|
| 25 |
+
from ._binary import o8
|
| 26 |
+
|
| 27 |
+
EPSILON = 1e-10
|
| 28 |
+
"""""" # Enable auto-doc for data member
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def linear(middle, pos):
|
| 32 |
+
if pos <= middle:
|
| 33 |
+
if middle < EPSILON:
|
| 34 |
+
return 0.0
|
| 35 |
+
else:
|
| 36 |
+
return 0.5 * pos / middle
|
| 37 |
+
else:
|
| 38 |
+
pos = pos - middle
|
| 39 |
+
middle = 1.0 - middle
|
| 40 |
+
if middle < EPSILON:
|
| 41 |
+
return 1.0
|
| 42 |
+
else:
|
| 43 |
+
return 0.5 + 0.5 * pos / middle
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def curved(middle, pos):
|
| 47 |
+
return pos ** (log(0.5) / log(max(middle, EPSILON)))
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def sine(middle, pos):
|
| 51 |
+
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def sphere_increasing(middle, pos):
|
| 55 |
+
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def sphere_decreasing(middle, pos):
|
| 59 |
+
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
|
| 63 |
+
"""""" # Enable auto-doc for data member
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class GradientFile:
|
| 67 |
+
|
| 68 |
+
gradient = None
|
| 69 |
+
|
| 70 |
+
def getpalette(self, entries=256):
|
| 71 |
+
|
| 72 |
+
palette = []
|
| 73 |
+
|
| 74 |
+
ix = 0
|
| 75 |
+
x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
|
| 76 |
+
|
| 77 |
+
for i in range(entries):
|
| 78 |
+
|
| 79 |
+
x = i / (entries - 1)
|
| 80 |
+
|
| 81 |
+
while x1 < x:
|
| 82 |
+
ix += 1
|
| 83 |
+
x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
|
| 84 |
+
|
| 85 |
+
w = x1 - x0
|
| 86 |
+
|
| 87 |
+
if w < EPSILON:
|
| 88 |
+
scale = segment(0.5, 0.5)
|
| 89 |
+
else:
|
| 90 |
+
scale = segment((xm - x0) / w, (x - x0) / w)
|
| 91 |
+
|
| 92 |
+
# expand to RGBA
|
| 93 |
+
r = o8(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5))
|
| 94 |
+
g = o8(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5))
|
| 95 |
+
b = o8(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5))
|
| 96 |
+
a = o8(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5))
|
| 97 |
+
|
| 98 |
+
# add to palette
|
| 99 |
+
palette.append(r + g + b + a)
|
| 100 |
+
|
| 101 |
+
return b"".join(palette), "RGBA"
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class GimpGradientFile(GradientFile):
|
| 105 |
+
"""File handler for GIMP's gradient format."""
|
| 106 |
+
|
| 107 |
+
def __init__(self, fp):
|
| 108 |
+
|
| 109 |
+
if fp.readline()[:13] != b"GIMP Gradient":
|
| 110 |
+
raise SyntaxError("not a GIMP gradient file")
|
| 111 |
+
|
| 112 |
+
line = fp.readline()
|
| 113 |
+
|
| 114 |
+
# GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
|
| 115 |
+
if line.startswith(b"Name: "):
|
| 116 |
+
line = fp.readline().strip()
|
| 117 |
+
|
| 118 |
+
count = int(line)
|
| 119 |
+
|
| 120 |
+
gradient = []
|
| 121 |
+
|
| 122 |
+
for i in range(count):
|
| 123 |
+
|
| 124 |
+
s = fp.readline().split()
|
| 125 |
+
w = [float(x) for x in s[:11]]
|
| 126 |
+
|
| 127 |
+
x0, x1 = w[0], w[2]
|
| 128 |
+
xm = w[1]
|
| 129 |
+
rgb0 = w[3:7]
|
| 130 |
+
rgb1 = w[7:11]
|
| 131 |
+
|
| 132 |
+
segment = SEGMENTS[int(s[11])]
|
| 133 |
+
cspace = int(s[12])
|
| 134 |
+
|
| 135 |
+
if cspace != 0:
|
| 136 |
+
raise OSError("cannot handle HSV colour space")
|
| 137 |
+
|
| 138 |
+
gradient.append((x0, x1, xm, rgb0, rgb1, segment))
|
| 139 |
+
|
| 140 |
+
self.gradient = gradient
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/GimpPaletteFile.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# stuff to read GIMP palette files
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1997-08-23 fl Created
|
| 9 |
+
# 2004-09-07 fl Support GIMP 2.0 palette files.
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) Secret Labs AB 1997-2004. All rights reserved.
|
| 12 |
+
# Copyright (c) Fredrik Lundh 1997-2004.
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
|
| 17 |
+
import re
|
| 18 |
+
|
| 19 |
+
from ._binary import o8
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class GimpPaletteFile:
|
| 23 |
+
"""File handler for GIMP's palette format."""
|
| 24 |
+
|
| 25 |
+
rawmode = "RGB"
|
| 26 |
+
|
| 27 |
+
def __init__(self, fp):
|
| 28 |
+
|
| 29 |
+
self.palette = [o8(i) * 3 for i in range(256)]
|
| 30 |
+
|
| 31 |
+
if fp.readline()[:12] != b"GIMP Palette":
|
| 32 |
+
raise SyntaxError("not a GIMP palette file")
|
| 33 |
+
|
| 34 |
+
for i in range(256):
|
| 35 |
+
|
| 36 |
+
s = fp.readline()
|
| 37 |
+
if not s:
|
| 38 |
+
break
|
| 39 |
+
|
| 40 |
+
# skip fields and comment lines
|
| 41 |
+
if re.match(rb"\w+:|#", s):
|
| 42 |
+
continue
|
| 43 |
+
if len(s) > 100:
|
| 44 |
+
raise SyntaxError("bad palette file")
|
| 45 |
+
|
| 46 |
+
v = tuple(map(int, s.split()[:3]))
|
| 47 |
+
if len(v) != 3:
|
| 48 |
+
raise ValueError("bad palette entry")
|
| 49 |
+
|
| 50 |
+
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
|
| 51 |
+
|
| 52 |
+
self.palette = b"".join(self.palette)
|
| 53 |
+
|
| 54 |
+
def getpalette(self):
|
| 55 |
+
|
| 56 |
+
return self.palette, self.rawmode
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/GribStubImagePlugin.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# GRIB stub adapter
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 1996-2003 by Fredrik Lundh
|
| 8 |
+
#
|
| 9 |
+
# See the README file for information on usage and redistribution.
|
| 10 |
+
#
|
| 11 |
+
|
| 12 |
+
from . import Image, ImageFile
|
| 13 |
+
|
| 14 |
+
_handler = None
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def register_handler(handler):
|
| 18 |
+
"""
|
| 19 |
+
Install application-specific GRIB image handler.
|
| 20 |
+
|
| 21 |
+
:param handler: Handler object.
|
| 22 |
+
"""
|
| 23 |
+
global _handler
|
| 24 |
+
_handler = handler
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# --------------------------------------------------------------------
|
| 28 |
+
# Image adapter
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _accept(prefix):
|
| 32 |
+
return prefix[0:4] == b"GRIB" and prefix[7] == 1
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class GribStubImageFile(ImageFile.StubImageFile):
|
| 36 |
+
|
| 37 |
+
format = "GRIB"
|
| 38 |
+
format_description = "GRIB"
|
| 39 |
+
|
| 40 |
+
def _open(self):
|
| 41 |
+
|
| 42 |
+
offset = self.fp.tell()
|
| 43 |
+
|
| 44 |
+
if not _accept(self.fp.read(8)):
|
| 45 |
+
raise SyntaxError("Not a GRIB file")
|
| 46 |
+
|
| 47 |
+
self.fp.seek(offset)
|
| 48 |
+
|
| 49 |
+
# make something up
|
| 50 |
+
self.mode = "F"
|
| 51 |
+
self._size = 1, 1
|
| 52 |
+
|
| 53 |
+
loader = self._load()
|
| 54 |
+
if loader:
|
| 55 |
+
loader.open(self)
|
| 56 |
+
|
| 57 |
+
def _load(self):
|
| 58 |
+
return _handler
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _save(im, fp, filename):
|
| 62 |
+
if _handler is None or not hasattr(_handler, "save"):
|
| 63 |
+
raise OSError("GRIB save handler not installed")
|
| 64 |
+
_handler.save(im, fp, filename)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# --------------------------------------------------------------------
|
| 68 |
+
# Registry
|
| 69 |
+
|
| 70 |
+
Image.register_open(GribStubImageFile.format, GribStubImageFile, _accept)
|
| 71 |
+
Image.register_save(GribStubImageFile.format, _save)
|
| 72 |
+
|
| 73 |
+
Image.register_extension(GribStubImageFile.format, ".grib")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/IcnsImagePlugin.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# macOS icns file decoder, based on icns.py by Bob Ippolito.
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
|
| 9 |
+
# 2020-04-04 Allow saving on all operating systems.
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) 2004 by Bob Ippolito.
|
| 12 |
+
# Copyright (c) 2004 by Secret Labs.
|
| 13 |
+
# Copyright (c) 2004 by Fredrik Lundh.
|
| 14 |
+
# Copyright (c) 2014 by Alastair Houghton.
|
| 15 |
+
# Copyright (c) 2020 by Pan Jing.
|
| 16 |
+
#
|
| 17 |
+
# See the README file for information on usage and redistribution.
|
| 18 |
+
#
|
| 19 |
+
|
| 20 |
+
import io
|
| 21 |
+
import os
|
| 22 |
+
import struct
|
| 23 |
+
import sys
|
| 24 |
+
|
| 25 |
+
from PIL import Image, ImageFile, PngImagePlugin, features
|
| 26 |
+
|
| 27 |
+
enable_jpeg2k = features.check_codec("jpg_2000")
|
| 28 |
+
if enable_jpeg2k:
|
| 29 |
+
from PIL import Jpeg2KImagePlugin
|
| 30 |
+
|
| 31 |
+
MAGIC = b"icns"
|
| 32 |
+
HEADERSIZE = 8
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def nextheader(fobj):
|
| 36 |
+
return struct.unpack(">4sI", fobj.read(HEADERSIZE))
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def read_32t(fobj, start_length, size):
|
| 40 |
+
# The 128x128 icon seems to have an extra header for some reason.
|
| 41 |
+
(start, length) = start_length
|
| 42 |
+
fobj.seek(start)
|
| 43 |
+
sig = fobj.read(4)
|
| 44 |
+
if sig != b"\x00\x00\x00\x00":
|
| 45 |
+
raise SyntaxError("Unknown signature, expecting 0x00000000")
|
| 46 |
+
return read_32(fobj, (start + 4, length - 4), size)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def read_32(fobj, start_length, size):
|
| 50 |
+
"""
|
| 51 |
+
Read a 32bit RGB icon resource. Seems to be either uncompressed or
|
| 52 |
+
an RLE packbits-like scheme.
|
| 53 |
+
"""
|
| 54 |
+
(start, length) = start_length
|
| 55 |
+
fobj.seek(start)
|
| 56 |
+
pixel_size = (size[0] * size[2], size[1] * size[2])
|
| 57 |
+
sizesq = pixel_size[0] * pixel_size[1]
|
| 58 |
+
if length == sizesq * 3:
|
| 59 |
+
# uncompressed ("RGBRGBGB")
|
| 60 |
+
indata = fobj.read(length)
|
| 61 |
+
im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1)
|
| 62 |
+
else:
|
| 63 |
+
# decode image
|
| 64 |
+
im = Image.new("RGB", pixel_size, None)
|
| 65 |
+
for band_ix in range(3):
|
| 66 |
+
data = []
|
| 67 |
+
bytesleft = sizesq
|
| 68 |
+
while bytesleft > 0:
|
| 69 |
+
byte = fobj.read(1)
|
| 70 |
+
if not byte:
|
| 71 |
+
break
|
| 72 |
+
byte = byte[0]
|
| 73 |
+
if byte & 0x80:
|
| 74 |
+
blocksize = byte - 125
|
| 75 |
+
byte = fobj.read(1)
|
| 76 |
+
for i in range(blocksize):
|
| 77 |
+
data.append(byte)
|
| 78 |
+
else:
|
| 79 |
+
blocksize = byte + 1
|
| 80 |
+
data.append(fobj.read(blocksize))
|
| 81 |
+
bytesleft -= blocksize
|
| 82 |
+
if bytesleft <= 0:
|
| 83 |
+
break
|
| 84 |
+
if bytesleft != 0:
|
| 85 |
+
raise SyntaxError(f"Error reading channel [{repr(bytesleft)} left]")
|
| 86 |
+
band = Image.frombuffer("L", pixel_size, b"".join(data), "raw", "L", 0, 1)
|
| 87 |
+
im.im.putband(band.im, band_ix)
|
| 88 |
+
return {"RGB": im}
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def read_mk(fobj, start_length, size):
|
| 92 |
+
# Alpha masks seem to be uncompressed
|
| 93 |
+
start = start_length[0]
|
| 94 |
+
fobj.seek(start)
|
| 95 |
+
pixel_size = (size[0] * size[2], size[1] * size[2])
|
| 96 |
+
sizesq = pixel_size[0] * pixel_size[1]
|
| 97 |
+
band = Image.frombuffer("L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1)
|
| 98 |
+
return {"A": band}
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def read_png_or_jpeg2000(fobj, start_length, size):
|
| 102 |
+
(start, length) = start_length
|
| 103 |
+
fobj.seek(start)
|
| 104 |
+
sig = fobj.read(12)
|
| 105 |
+
if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
|
| 106 |
+
fobj.seek(start)
|
| 107 |
+
im = PngImagePlugin.PngImageFile(fobj)
|
| 108 |
+
Image._decompression_bomb_check(im.size)
|
| 109 |
+
return {"RGBA": im}
|
| 110 |
+
elif (
|
| 111 |
+
sig[:4] == b"\xff\x4f\xff\x51"
|
| 112 |
+
or sig[:4] == b"\x0d\x0a\x87\x0a"
|
| 113 |
+
or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
|
| 114 |
+
):
|
| 115 |
+
if not enable_jpeg2k:
|
| 116 |
+
raise ValueError(
|
| 117 |
+
"Unsupported icon subimage format (rebuild PIL "
|
| 118 |
+
"with JPEG 2000 support to fix this)"
|
| 119 |
+
)
|
| 120 |
+
# j2k, jpc or j2c
|
| 121 |
+
fobj.seek(start)
|
| 122 |
+
jp2kstream = fobj.read(length)
|
| 123 |
+
f = io.BytesIO(jp2kstream)
|
| 124 |
+
im = Jpeg2KImagePlugin.Jpeg2KImageFile(f)
|
| 125 |
+
Image._decompression_bomb_check(im.size)
|
| 126 |
+
if im.mode != "RGBA":
|
| 127 |
+
im = im.convert("RGBA")
|
| 128 |
+
return {"RGBA": im}
|
| 129 |
+
else:
|
| 130 |
+
raise ValueError("Unsupported icon subimage format")
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
class IcnsFile:
|
| 134 |
+
|
| 135 |
+
SIZES = {
|
| 136 |
+
(512, 512, 2): [(b"ic10", read_png_or_jpeg2000)],
|
| 137 |
+
(512, 512, 1): [(b"ic09", read_png_or_jpeg2000)],
|
| 138 |
+
(256, 256, 2): [(b"ic14", read_png_or_jpeg2000)],
|
| 139 |
+
(256, 256, 1): [(b"ic08", read_png_or_jpeg2000)],
|
| 140 |
+
(128, 128, 2): [(b"ic13", read_png_or_jpeg2000)],
|
| 141 |
+
(128, 128, 1): [
|
| 142 |
+
(b"ic07", read_png_or_jpeg2000),
|
| 143 |
+
(b"it32", read_32t),
|
| 144 |
+
(b"t8mk", read_mk),
|
| 145 |
+
],
|
| 146 |
+
(64, 64, 1): [(b"icp6", read_png_or_jpeg2000)],
|
| 147 |
+
(32, 32, 2): [(b"ic12", read_png_or_jpeg2000)],
|
| 148 |
+
(48, 48, 1): [(b"ih32", read_32), (b"h8mk", read_mk)],
|
| 149 |
+
(32, 32, 1): [
|
| 150 |
+
(b"icp5", read_png_or_jpeg2000),
|
| 151 |
+
(b"il32", read_32),
|
| 152 |
+
(b"l8mk", read_mk),
|
| 153 |
+
],
|
| 154 |
+
(16, 16, 2): [(b"ic11", read_png_or_jpeg2000)],
|
| 155 |
+
(16, 16, 1): [
|
| 156 |
+
(b"icp4", read_png_or_jpeg2000),
|
| 157 |
+
(b"is32", read_32),
|
| 158 |
+
(b"s8mk", read_mk),
|
| 159 |
+
],
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
def __init__(self, fobj):
|
| 163 |
+
"""
|
| 164 |
+
fobj is a file-like object as an icns resource
|
| 165 |
+
"""
|
| 166 |
+
# signature : (start, length)
|
| 167 |
+
self.dct = dct = {}
|
| 168 |
+
self.fobj = fobj
|
| 169 |
+
sig, filesize = nextheader(fobj)
|
| 170 |
+
if not _accept(sig):
|
| 171 |
+
raise SyntaxError("not an icns file")
|
| 172 |
+
i = HEADERSIZE
|
| 173 |
+
while i < filesize:
|
| 174 |
+
sig, blocksize = nextheader(fobj)
|
| 175 |
+
if blocksize <= 0:
|
| 176 |
+
raise SyntaxError("invalid block header")
|
| 177 |
+
i += HEADERSIZE
|
| 178 |
+
blocksize -= HEADERSIZE
|
| 179 |
+
dct[sig] = (i, blocksize)
|
| 180 |
+
fobj.seek(blocksize, io.SEEK_CUR)
|
| 181 |
+
i += blocksize
|
| 182 |
+
|
| 183 |
+
def itersizes(self):
|
| 184 |
+
sizes = []
|
| 185 |
+
for size, fmts in self.SIZES.items():
|
| 186 |
+
for (fmt, reader) in fmts:
|
| 187 |
+
if fmt in self.dct:
|
| 188 |
+
sizes.append(size)
|
| 189 |
+
break
|
| 190 |
+
return sizes
|
| 191 |
+
|
| 192 |
+
def bestsize(self):
|
| 193 |
+
sizes = self.itersizes()
|
| 194 |
+
if not sizes:
|
| 195 |
+
raise SyntaxError("No 32bit icon resources found")
|
| 196 |
+
return max(sizes)
|
| 197 |
+
|
| 198 |
+
def dataforsize(self, size):
|
| 199 |
+
"""
|
| 200 |
+
Get an icon resource as {channel: array}. Note that
|
| 201 |
+
the arrays are bottom-up like windows bitmaps and will likely
|
| 202 |
+
need to be flipped or transposed in some way.
|
| 203 |
+
"""
|
| 204 |
+
dct = {}
|
| 205 |
+
for code, reader in self.SIZES[size]:
|
| 206 |
+
desc = self.dct.get(code)
|
| 207 |
+
if desc is not None:
|
| 208 |
+
dct.update(reader(self.fobj, desc, size))
|
| 209 |
+
return dct
|
| 210 |
+
|
| 211 |
+
def getimage(self, size=None):
|
| 212 |
+
if size is None:
|
| 213 |
+
size = self.bestsize()
|
| 214 |
+
if len(size) == 2:
|
| 215 |
+
size = (size[0], size[1], 1)
|
| 216 |
+
channels = self.dataforsize(size)
|
| 217 |
+
|
| 218 |
+
im = channels.get("RGBA", None)
|
| 219 |
+
if im:
|
| 220 |
+
return im
|
| 221 |
+
|
| 222 |
+
im = channels.get("RGB").copy()
|
| 223 |
+
try:
|
| 224 |
+
im.putalpha(channels["A"])
|
| 225 |
+
except KeyError:
|
| 226 |
+
pass
|
| 227 |
+
return im
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
##
|
| 231 |
+
# Image plugin for Mac OS icons.
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
class IcnsImageFile(ImageFile.ImageFile):
|
| 235 |
+
"""
|
| 236 |
+
PIL image support for Mac OS .icns files.
|
| 237 |
+
Chooses the best resolution, but will possibly load
|
| 238 |
+
a different size image if you mutate the size attribute
|
| 239 |
+
before calling 'load'.
|
| 240 |
+
|
| 241 |
+
The info dictionary has a key 'sizes' that is a list
|
| 242 |
+
of sizes that the icns file has.
|
| 243 |
+
"""
|
| 244 |
+
|
| 245 |
+
format = "ICNS"
|
| 246 |
+
format_description = "Mac OS icns resource"
|
| 247 |
+
|
| 248 |
+
def _open(self):
|
| 249 |
+
self.icns = IcnsFile(self.fp)
|
| 250 |
+
self.mode = "RGBA"
|
| 251 |
+
self.info["sizes"] = self.icns.itersizes()
|
| 252 |
+
self.best_size = self.icns.bestsize()
|
| 253 |
+
self.size = (
|
| 254 |
+
self.best_size[0] * self.best_size[2],
|
| 255 |
+
self.best_size[1] * self.best_size[2],
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
@property
|
| 259 |
+
def size(self):
|
| 260 |
+
return self._size
|
| 261 |
+
|
| 262 |
+
@size.setter
|
| 263 |
+
def size(self, value):
|
| 264 |
+
info_size = value
|
| 265 |
+
if info_size not in self.info["sizes"] and len(info_size) == 2:
|
| 266 |
+
info_size = (info_size[0], info_size[1], 1)
|
| 267 |
+
if (
|
| 268 |
+
info_size not in self.info["sizes"]
|
| 269 |
+
and len(info_size) == 3
|
| 270 |
+
and info_size[2] == 1
|
| 271 |
+
):
|
| 272 |
+
simple_sizes = [
|
| 273 |
+
(size[0] * size[2], size[1] * size[2]) for size in self.info["sizes"]
|
| 274 |
+
]
|
| 275 |
+
if value in simple_sizes:
|
| 276 |
+
info_size = self.info["sizes"][simple_sizes.index(value)]
|
| 277 |
+
if info_size not in self.info["sizes"]:
|
| 278 |
+
raise ValueError("This is not one of the allowed sizes of this image")
|
| 279 |
+
self._size = value
|
| 280 |
+
|
| 281 |
+
def load(self):
|
| 282 |
+
if len(self.size) == 3:
|
| 283 |
+
self.best_size = self.size
|
| 284 |
+
self.size = (
|
| 285 |
+
self.best_size[0] * self.best_size[2],
|
| 286 |
+
self.best_size[1] * self.best_size[2],
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
px = Image.Image.load(self)
|
| 290 |
+
if self.im is not None and self.im.size == self.size:
|
| 291 |
+
# Already loaded
|
| 292 |
+
return px
|
| 293 |
+
self.load_prepare()
|
| 294 |
+
# This is likely NOT the best way to do it, but whatever.
|
| 295 |
+
im = self.icns.getimage(self.best_size)
|
| 296 |
+
|
| 297 |
+
# If this is a PNG or JPEG 2000, it won't be loaded yet
|
| 298 |
+
px = im.load()
|
| 299 |
+
|
| 300 |
+
self.im = im.im
|
| 301 |
+
self.mode = im.mode
|
| 302 |
+
self.size = im.size
|
| 303 |
+
|
| 304 |
+
return px
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def _save(im, fp, filename):
|
| 308 |
+
"""
|
| 309 |
+
Saves the image as a series of PNG files,
|
| 310 |
+
that are then combined into a .icns file.
|
| 311 |
+
"""
|
| 312 |
+
if hasattr(fp, "flush"):
|
| 313 |
+
fp.flush()
|
| 314 |
+
|
| 315 |
+
sizes = {
|
| 316 |
+
b"ic07": 128,
|
| 317 |
+
b"ic08": 256,
|
| 318 |
+
b"ic09": 512,
|
| 319 |
+
b"ic10": 1024,
|
| 320 |
+
b"ic11": 32,
|
| 321 |
+
b"ic12": 64,
|
| 322 |
+
b"ic13": 256,
|
| 323 |
+
b"ic14": 512,
|
| 324 |
+
}
|
| 325 |
+
provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])}
|
| 326 |
+
size_streams = {}
|
| 327 |
+
for size in set(sizes.values()):
|
| 328 |
+
image = (
|
| 329 |
+
provided_images[size]
|
| 330 |
+
if size in provided_images
|
| 331 |
+
else im.resize((size, size))
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
temp = io.BytesIO()
|
| 335 |
+
image.save(temp, "png")
|
| 336 |
+
size_streams[size] = temp.getvalue()
|
| 337 |
+
|
| 338 |
+
entries = []
|
| 339 |
+
for type, size in sizes.items():
|
| 340 |
+
stream = size_streams[size]
|
| 341 |
+
entries.append(
|
| 342 |
+
{"type": type, "size": HEADERSIZE + len(stream), "stream": stream}
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
# Header
|
| 346 |
+
fp.write(MAGIC)
|
| 347 |
+
file_length = HEADERSIZE # Header
|
| 348 |
+
file_length += HEADERSIZE + 8 * len(entries) # TOC
|
| 349 |
+
file_length += sum(entry["size"] for entry in entries)
|
| 350 |
+
fp.write(struct.pack(">i", file_length))
|
| 351 |
+
|
| 352 |
+
# TOC
|
| 353 |
+
fp.write(b"TOC ")
|
| 354 |
+
fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE))
|
| 355 |
+
for entry in entries:
|
| 356 |
+
fp.write(entry["type"])
|
| 357 |
+
fp.write(struct.pack(">i", entry["size"]))
|
| 358 |
+
|
| 359 |
+
# Data
|
| 360 |
+
for entry in entries:
|
| 361 |
+
fp.write(entry["type"])
|
| 362 |
+
fp.write(struct.pack(">i", entry["size"]))
|
| 363 |
+
fp.write(entry["stream"])
|
| 364 |
+
|
| 365 |
+
if hasattr(fp, "flush"):
|
| 366 |
+
fp.flush()
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def _accept(prefix):
|
| 370 |
+
return prefix[:4] == MAGIC
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept)
|
| 374 |
+
Image.register_extension(IcnsImageFile.format, ".icns")
|
| 375 |
+
|
| 376 |
+
Image.register_save(IcnsImageFile.format, _save)
|
| 377 |
+
Image.register_mime(IcnsImageFile.format, "image/icns")
|
| 378 |
+
|
| 379 |
+
if __name__ == "__main__":
|
| 380 |
+
if len(sys.argv) < 2:
|
| 381 |
+
print("Syntax: python3 IcnsImagePlugin.py [file]")
|
| 382 |
+
sys.exit()
|
| 383 |
+
|
| 384 |
+
with open(sys.argv[1], "rb") as fp:
|
| 385 |
+
imf = IcnsImageFile(fp)
|
| 386 |
+
for size in imf.info["sizes"]:
|
| 387 |
+
imf.size = size
|
| 388 |
+
imf.save("out-%s-%s-%s.png" % size)
|
| 389 |
+
with Image.open(sys.argv[1]) as im:
|
| 390 |
+
im.save("out.png")
|
| 391 |
+
if sys.platform == "windows":
|
| 392 |
+
os.startfile("out.png")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/IcoImagePlugin.py
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# Windows Icon support for PIL
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 96-05-27 fl Created
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 11 |
+
# Copyright (c) Fredrik Lundh 1996.
|
| 12 |
+
#
|
| 13 |
+
# See the README file for information on usage and redistribution.
|
| 14 |
+
#
|
| 15 |
+
|
| 16 |
+
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
| 17 |
+
# <casadebender@gmail.com>.
|
| 18 |
+
# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
| 19 |
+
#
|
| 20 |
+
# Icon format references:
|
| 21 |
+
# * https://en.wikipedia.org/wiki/ICO_(file_format)
|
| 22 |
+
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
import warnings
|
| 26 |
+
from io import BytesIO
|
| 27 |
+
from math import ceil, log
|
| 28 |
+
|
| 29 |
+
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
|
| 30 |
+
from ._binary import i16le as i16
|
| 31 |
+
from ._binary import i32le as i32
|
| 32 |
+
from ._binary import o8
|
| 33 |
+
from ._binary import o16le as o16
|
| 34 |
+
from ._binary import o32le as o32
|
| 35 |
+
|
| 36 |
+
#
|
| 37 |
+
# --------------------------------------------------------------------
|
| 38 |
+
|
| 39 |
+
_MAGIC = b"\0\0\1\0"
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _save(im, fp, filename):
|
| 43 |
+
fp.write(_MAGIC) # (2+2)
|
| 44 |
+
bmp = im.encoderinfo.get("bitmap_format") == "bmp"
|
| 45 |
+
sizes = im.encoderinfo.get(
|
| 46 |
+
"sizes",
|
| 47 |
+
[(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)],
|
| 48 |
+
)
|
| 49 |
+
frames = []
|
| 50 |
+
provided_ims = [im] + im.encoderinfo.get("append_images", [])
|
| 51 |
+
width, height = im.size
|
| 52 |
+
for size in sorted(set(sizes)):
|
| 53 |
+
if size[0] > width or size[1] > height or size[0] > 256 or size[1] > 256:
|
| 54 |
+
continue
|
| 55 |
+
|
| 56 |
+
for provided_im in provided_ims:
|
| 57 |
+
if provided_im.size != size:
|
| 58 |
+
continue
|
| 59 |
+
frames.append(provided_im)
|
| 60 |
+
if bmp:
|
| 61 |
+
bits = BmpImagePlugin.SAVE[provided_im.mode][1]
|
| 62 |
+
bits_used = [bits]
|
| 63 |
+
for other_im in provided_ims:
|
| 64 |
+
if other_im.size != size:
|
| 65 |
+
continue
|
| 66 |
+
bits = BmpImagePlugin.SAVE[other_im.mode][1]
|
| 67 |
+
if bits not in bits_used:
|
| 68 |
+
# Another image has been supplied for this size
|
| 69 |
+
# with a different bit depth
|
| 70 |
+
frames.append(other_im)
|
| 71 |
+
bits_used.append(bits)
|
| 72 |
+
break
|
| 73 |
+
else:
|
| 74 |
+
# TODO: invent a more convenient method for proportional scalings
|
| 75 |
+
frame = provided_im.copy()
|
| 76 |
+
frame.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None)
|
| 77 |
+
frames.append(frame)
|
| 78 |
+
fp.write(o16(len(frames))) # idCount(2)
|
| 79 |
+
offset = fp.tell() + len(frames) * 16
|
| 80 |
+
for frame in frames:
|
| 81 |
+
width, height = frame.size
|
| 82 |
+
# 0 means 256
|
| 83 |
+
fp.write(o8(width if width < 256 else 0)) # bWidth(1)
|
| 84 |
+
fp.write(o8(height if height < 256 else 0)) # bHeight(1)
|
| 85 |
+
|
| 86 |
+
bits, colors = BmpImagePlugin.SAVE[frame.mode][1:] if bmp else (32, 0)
|
| 87 |
+
fp.write(o8(colors)) # bColorCount(1)
|
| 88 |
+
fp.write(b"\0") # bReserved(1)
|
| 89 |
+
fp.write(b"\0\0") # wPlanes(2)
|
| 90 |
+
fp.write(o16(bits)) # wBitCount(2)
|
| 91 |
+
|
| 92 |
+
image_io = BytesIO()
|
| 93 |
+
if bmp:
|
| 94 |
+
frame.save(image_io, "dib")
|
| 95 |
+
|
| 96 |
+
if bits != 32:
|
| 97 |
+
and_mask = Image.new("1", size)
|
| 98 |
+
ImageFile._save(
|
| 99 |
+
and_mask, image_io, [("raw", (0, 0) + size, 0, ("1", 0, -1))]
|
| 100 |
+
)
|
| 101 |
+
else:
|
| 102 |
+
frame.save(image_io, "png")
|
| 103 |
+
image_io.seek(0)
|
| 104 |
+
image_bytes = image_io.read()
|
| 105 |
+
if bmp:
|
| 106 |
+
image_bytes = image_bytes[:8] + o32(height * 2) + image_bytes[12:]
|
| 107 |
+
bytes_len = len(image_bytes)
|
| 108 |
+
fp.write(o32(bytes_len)) # dwBytesInRes(4)
|
| 109 |
+
fp.write(o32(offset)) # dwImageOffset(4)
|
| 110 |
+
current = fp.tell()
|
| 111 |
+
fp.seek(offset)
|
| 112 |
+
fp.write(image_bytes)
|
| 113 |
+
offset = offset + bytes_len
|
| 114 |
+
fp.seek(current)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _accept(prefix):
|
| 118 |
+
return prefix[:4] == _MAGIC
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
class IcoFile:
|
| 122 |
+
def __init__(self, buf):
|
| 123 |
+
"""
|
| 124 |
+
Parse image from file-like object containing ico file data
|
| 125 |
+
"""
|
| 126 |
+
|
| 127 |
+
# check magic
|
| 128 |
+
s = buf.read(6)
|
| 129 |
+
if not _accept(s):
|
| 130 |
+
raise SyntaxError("not an ICO file")
|
| 131 |
+
|
| 132 |
+
self.buf = buf
|
| 133 |
+
self.entry = []
|
| 134 |
+
|
| 135 |
+
# Number of items in file
|
| 136 |
+
self.nb_items = i16(s, 4)
|
| 137 |
+
|
| 138 |
+
# Get headers for each item
|
| 139 |
+
for i in range(self.nb_items):
|
| 140 |
+
s = buf.read(16)
|
| 141 |
+
|
| 142 |
+
icon_header = {
|
| 143 |
+
"width": s[0],
|
| 144 |
+
"height": s[1],
|
| 145 |
+
"nb_color": s[2], # No. of colors in image (0 if >=8bpp)
|
| 146 |
+
"reserved": s[3],
|
| 147 |
+
"planes": i16(s, 4),
|
| 148 |
+
"bpp": i16(s, 6),
|
| 149 |
+
"size": i32(s, 8),
|
| 150 |
+
"offset": i32(s, 12),
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
# See Wikipedia
|
| 154 |
+
for j in ("width", "height"):
|
| 155 |
+
if not icon_header[j]:
|
| 156 |
+
icon_header[j] = 256
|
| 157 |
+
|
| 158 |
+
# See Wikipedia notes about color depth.
|
| 159 |
+
# We need this just to differ images with equal sizes
|
| 160 |
+
icon_header["color_depth"] = (
|
| 161 |
+
icon_header["bpp"]
|
| 162 |
+
or (
|
| 163 |
+
icon_header["nb_color"] != 0
|
| 164 |
+
and ceil(log(icon_header["nb_color"], 2))
|
| 165 |
+
)
|
| 166 |
+
or 256
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
icon_header["dim"] = (icon_header["width"], icon_header["height"])
|
| 170 |
+
icon_header["square"] = icon_header["width"] * icon_header["height"]
|
| 171 |
+
|
| 172 |
+
self.entry.append(icon_header)
|
| 173 |
+
|
| 174 |
+
self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
|
| 175 |
+
# ICO images are usually squares
|
| 176 |
+
# self.entry = sorted(self.entry, key=lambda x: x['width'])
|
| 177 |
+
self.entry = sorted(self.entry, key=lambda x: x["square"])
|
| 178 |
+
self.entry.reverse()
|
| 179 |
+
|
| 180 |
+
def sizes(self):
|
| 181 |
+
"""
|
| 182 |
+
Get a list of all available icon sizes and color depths.
|
| 183 |
+
"""
|
| 184 |
+
return {(h["width"], h["height"]) for h in self.entry}
|
| 185 |
+
|
| 186 |
+
def getentryindex(self, size, bpp=False):
|
| 187 |
+
for (i, h) in enumerate(self.entry):
|
| 188 |
+
if size == h["dim"] and (bpp is False or bpp == h["color_depth"]):
|
| 189 |
+
return i
|
| 190 |
+
return 0
|
| 191 |
+
|
| 192 |
+
def getimage(self, size, bpp=False):
|
| 193 |
+
"""
|
| 194 |
+
Get an image from the icon
|
| 195 |
+
"""
|
| 196 |
+
return self.frame(self.getentryindex(size, bpp))
|
| 197 |
+
|
| 198 |
+
def frame(self, idx):
|
| 199 |
+
"""
|
| 200 |
+
Get an image from frame idx
|
| 201 |
+
"""
|
| 202 |
+
|
| 203 |
+
header = self.entry[idx]
|
| 204 |
+
|
| 205 |
+
self.buf.seek(header["offset"])
|
| 206 |
+
data = self.buf.read(8)
|
| 207 |
+
self.buf.seek(header["offset"])
|
| 208 |
+
|
| 209 |
+
if data[:8] == PngImagePlugin._MAGIC:
|
| 210 |
+
# png frame
|
| 211 |
+
im = PngImagePlugin.PngImageFile(self.buf)
|
| 212 |
+
Image._decompression_bomb_check(im.size)
|
| 213 |
+
else:
|
| 214 |
+
# XOR + AND mask bmp frame
|
| 215 |
+
im = BmpImagePlugin.DibImageFile(self.buf)
|
| 216 |
+
Image._decompression_bomb_check(im.size)
|
| 217 |
+
|
| 218 |
+
# change tile dimension to only encompass XOR image
|
| 219 |
+
im._size = (im.size[0], int(im.size[1] / 2))
|
| 220 |
+
d, e, o, a = im.tile[0]
|
| 221 |
+
im.tile[0] = d, (0, 0) + im.size, o, a
|
| 222 |
+
|
| 223 |
+
# figure out where AND mask image starts
|
| 224 |
+
bpp = header["bpp"]
|
| 225 |
+
if 32 == bpp:
|
| 226 |
+
# 32-bit color depth icon image allows semitransparent areas
|
| 227 |
+
# PIL's DIB format ignores transparency bits, recover them.
|
| 228 |
+
# The DIB is packed in BGRX byte order where X is the alpha
|
| 229 |
+
# channel.
|
| 230 |
+
|
| 231 |
+
# Back up to start of bmp data
|
| 232 |
+
self.buf.seek(o)
|
| 233 |
+
# extract every 4th byte (eg. 3,7,11,15,...)
|
| 234 |
+
alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
|
| 235 |
+
|
| 236 |
+
# convert to an 8bpp grayscale image
|
| 237 |
+
mask = Image.frombuffer(
|
| 238 |
+
"L", # 8bpp
|
| 239 |
+
im.size, # (w, h)
|
| 240 |
+
alpha_bytes, # source chars
|
| 241 |
+
"raw", # raw decoder
|
| 242 |
+
("L", 0, -1), # 8bpp inverted, unpadded, reversed
|
| 243 |
+
)
|
| 244 |
+
else:
|
| 245 |
+
# get AND image from end of bitmap
|
| 246 |
+
w = im.size[0]
|
| 247 |
+
if (w % 32) > 0:
|
| 248 |
+
# bitmap row data is aligned to word boundaries
|
| 249 |
+
w += 32 - (im.size[0] % 32)
|
| 250 |
+
|
| 251 |
+
# the total mask data is
|
| 252 |
+
# padded row size * height / bits per char
|
| 253 |
+
|
| 254 |
+
total_bytes = int((w * im.size[1]) / 8)
|
| 255 |
+
and_mask_offset = header["offset"] + header["size"] - total_bytes
|
| 256 |
+
|
| 257 |
+
self.buf.seek(and_mask_offset)
|
| 258 |
+
mask_data = self.buf.read(total_bytes)
|
| 259 |
+
|
| 260 |
+
# convert raw data to image
|
| 261 |
+
mask = Image.frombuffer(
|
| 262 |
+
"1", # 1 bpp
|
| 263 |
+
im.size, # (w, h)
|
| 264 |
+
mask_data, # source chars
|
| 265 |
+
"raw", # raw decoder
|
| 266 |
+
("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
+
# now we have two images, im is XOR image and mask is AND image
|
| 270 |
+
|
| 271 |
+
# apply mask image as alpha channel
|
| 272 |
+
im = im.convert("RGBA")
|
| 273 |
+
im.putalpha(mask)
|
| 274 |
+
|
| 275 |
+
return im
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
##
|
| 279 |
+
# Image plugin for Windows Icon files.
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
class IcoImageFile(ImageFile.ImageFile):
|
| 283 |
+
"""
|
| 284 |
+
PIL read-only image support for Microsoft Windows .ico files.
|
| 285 |
+
|
| 286 |
+
By default the largest resolution image in the file will be loaded. This
|
| 287 |
+
can be changed by altering the 'size' attribute before calling 'load'.
|
| 288 |
+
|
| 289 |
+
The info dictionary has a key 'sizes' that is a list of the sizes available
|
| 290 |
+
in the icon file.
|
| 291 |
+
|
| 292 |
+
Handles classic, XP and Vista icon formats.
|
| 293 |
+
|
| 294 |
+
When saving, PNG compression is used. Support for this was only added in
|
| 295 |
+
Windows Vista. If you are unable to view the icon in Windows, convert the
|
| 296 |
+
image to "RGBA" mode before saving.
|
| 297 |
+
|
| 298 |
+
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
| 299 |
+
<casadebender@gmail.com>.
|
| 300 |
+
https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
| 301 |
+
"""
|
| 302 |
+
|
| 303 |
+
format = "ICO"
|
| 304 |
+
format_description = "Windows Icon"
|
| 305 |
+
|
| 306 |
+
def _open(self):
|
| 307 |
+
self.ico = IcoFile(self.fp)
|
| 308 |
+
self.info["sizes"] = self.ico.sizes()
|
| 309 |
+
self.size = self.ico.entry[0]["dim"]
|
| 310 |
+
self.load()
|
| 311 |
+
|
| 312 |
+
@property
|
| 313 |
+
def size(self):
|
| 314 |
+
return self._size
|
| 315 |
+
|
| 316 |
+
@size.setter
|
| 317 |
+
def size(self, value):
|
| 318 |
+
if value not in self.info["sizes"]:
|
| 319 |
+
raise ValueError("This is not one of the allowed sizes of this image")
|
| 320 |
+
self._size = value
|
| 321 |
+
|
| 322 |
+
def load(self):
|
| 323 |
+
if self.im is not None and self.im.size == self.size:
|
| 324 |
+
# Already loaded
|
| 325 |
+
return Image.Image.load(self)
|
| 326 |
+
im = self.ico.getimage(self.size)
|
| 327 |
+
# if tile is PNG, it won't really be loaded yet
|
| 328 |
+
im.load()
|
| 329 |
+
self.im = im.im
|
| 330 |
+
self.mode = im.mode
|
| 331 |
+
if im.size != self.size:
|
| 332 |
+
warnings.warn("Image was not the expected size")
|
| 333 |
+
|
| 334 |
+
index = self.ico.getentryindex(self.size)
|
| 335 |
+
sizes = list(self.info["sizes"])
|
| 336 |
+
sizes[index] = im.size
|
| 337 |
+
self.info["sizes"] = set(sizes)
|
| 338 |
+
|
| 339 |
+
self.size = im.size
|
| 340 |
+
|
| 341 |
+
def load_seek(self):
|
| 342 |
+
# Flag the ImageFile.Parser so that it
|
| 343 |
+
# just does all the decode at the end.
|
| 344 |
+
pass
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
#
|
| 348 |
+
# --------------------------------------------------------------------
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
|
| 352 |
+
Image.register_save(IcoImageFile.format, _save)
|
| 353 |
+
Image.register_extension(IcoImageFile.format, ".ico")
|
| 354 |
+
|
| 355 |
+
Image.register_mime(IcoImageFile.format, "image/x-icon")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/Image.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageChops.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# standard channel operations
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1996-03-24 fl Created
|
| 9 |
+
# 1996-08-13 fl Added logical operations (for "1" images)
|
| 10 |
+
# 2000-10-12 fl Added offset method (from Image.py)
|
| 11 |
+
#
|
| 12 |
+
# Copyright (c) 1997-2000 by Secret Labs AB
|
| 13 |
+
# Copyright (c) 1996-2000 by Fredrik Lundh
|
| 14 |
+
#
|
| 15 |
+
# See the README file for information on usage and redistribution.
|
| 16 |
+
#
|
| 17 |
+
|
| 18 |
+
from . import Image
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def constant(image, value):
|
| 22 |
+
"""Fill a channel with a given grey level.
|
| 23 |
+
|
| 24 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
return Image.new("L", image.size, value)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def duplicate(image):
|
| 31 |
+
"""Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`.
|
| 32 |
+
|
| 33 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
return image.copy()
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def invert(image):
|
| 40 |
+
"""
|
| 41 |
+
Invert an image (channel).
|
| 42 |
+
|
| 43 |
+
.. code-block:: python
|
| 44 |
+
|
| 45 |
+
out = MAX - image
|
| 46 |
+
|
| 47 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
image.load()
|
| 51 |
+
return image._new(image.im.chop_invert())
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def lighter(image1, image2):
|
| 55 |
+
"""
|
| 56 |
+
Compares the two images, pixel by pixel, and returns a new image containing
|
| 57 |
+
the lighter values.
|
| 58 |
+
|
| 59 |
+
.. code-block:: python
|
| 60 |
+
|
| 61 |
+
out = max(image1, image2)
|
| 62 |
+
|
| 63 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
image1.load()
|
| 67 |
+
image2.load()
|
| 68 |
+
return image1._new(image1.im.chop_lighter(image2.im))
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def darker(image1, image2):
|
| 72 |
+
"""
|
| 73 |
+
Compares the two images, pixel by pixel, and returns a new image containing
|
| 74 |
+
the darker values.
|
| 75 |
+
|
| 76 |
+
.. code-block:: python
|
| 77 |
+
|
| 78 |
+
out = min(image1, image2)
|
| 79 |
+
|
| 80 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 81 |
+
"""
|
| 82 |
+
|
| 83 |
+
image1.load()
|
| 84 |
+
image2.load()
|
| 85 |
+
return image1._new(image1.im.chop_darker(image2.im))
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def difference(image1, image2):
|
| 89 |
+
"""
|
| 90 |
+
Returns the absolute value of the pixel-by-pixel difference between the two
|
| 91 |
+
images.
|
| 92 |
+
|
| 93 |
+
.. code-block:: python
|
| 94 |
+
|
| 95 |
+
out = abs(image1 - image2)
|
| 96 |
+
|
| 97 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 98 |
+
"""
|
| 99 |
+
|
| 100 |
+
image1.load()
|
| 101 |
+
image2.load()
|
| 102 |
+
return image1._new(image1.im.chop_difference(image2.im))
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def multiply(image1, image2):
|
| 106 |
+
"""
|
| 107 |
+
Superimposes two images on top of each other.
|
| 108 |
+
|
| 109 |
+
If you multiply an image with a solid black image, the result is black. If
|
| 110 |
+
you multiply with a solid white image, the image is unaffected.
|
| 111 |
+
|
| 112 |
+
.. code-block:: python
|
| 113 |
+
|
| 114 |
+
out = image1 * image2 / MAX
|
| 115 |
+
|
| 116 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 117 |
+
"""
|
| 118 |
+
|
| 119 |
+
image1.load()
|
| 120 |
+
image2.load()
|
| 121 |
+
return image1._new(image1.im.chop_multiply(image2.im))
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def screen(image1, image2):
|
| 125 |
+
"""
|
| 126 |
+
Superimposes two inverted images on top of each other.
|
| 127 |
+
|
| 128 |
+
.. code-block:: python
|
| 129 |
+
|
| 130 |
+
out = MAX - ((MAX - image1) * (MAX - image2) / MAX)
|
| 131 |
+
|
| 132 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 133 |
+
"""
|
| 134 |
+
|
| 135 |
+
image1.load()
|
| 136 |
+
image2.load()
|
| 137 |
+
return image1._new(image1.im.chop_screen(image2.im))
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def soft_light(image1, image2):
|
| 141 |
+
"""
|
| 142 |
+
Superimposes two images on top of each other using the Soft Light algorithm
|
| 143 |
+
|
| 144 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 145 |
+
"""
|
| 146 |
+
|
| 147 |
+
image1.load()
|
| 148 |
+
image2.load()
|
| 149 |
+
return image1._new(image1.im.chop_soft_light(image2.im))
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def hard_light(image1, image2):
|
| 153 |
+
"""
|
| 154 |
+
Superimposes two images on top of each other using the Hard Light algorithm
|
| 155 |
+
|
| 156 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 157 |
+
"""
|
| 158 |
+
|
| 159 |
+
image1.load()
|
| 160 |
+
image2.load()
|
| 161 |
+
return image1._new(image1.im.chop_hard_light(image2.im))
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def overlay(image1, image2):
|
| 165 |
+
"""
|
| 166 |
+
Superimposes two images on top of each other using the Overlay algorithm
|
| 167 |
+
|
| 168 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
image1.load()
|
| 172 |
+
image2.load()
|
| 173 |
+
return image1._new(image1.im.chop_overlay(image2.im))
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def add(image1, image2, scale=1.0, offset=0):
|
| 177 |
+
"""
|
| 178 |
+
Adds two images, dividing the result by scale and adding the
|
| 179 |
+
offset. If omitted, scale defaults to 1.0, and offset to 0.0.
|
| 180 |
+
|
| 181 |
+
.. code-block:: python
|
| 182 |
+
|
| 183 |
+
out = ((image1 + image2) / scale + offset)
|
| 184 |
+
|
| 185 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 186 |
+
"""
|
| 187 |
+
|
| 188 |
+
image1.load()
|
| 189 |
+
image2.load()
|
| 190 |
+
return image1._new(image1.im.chop_add(image2.im, scale, offset))
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def subtract(image1, image2, scale=1.0, offset=0):
|
| 194 |
+
"""
|
| 195 |
+
Subtracts two images, dividing the result by scale and adding the offset.
|
| 196 |
+
If omitted, scale defaults to 1.0, and offset to 0.0.
|
| 197 |
+
|
| 198 |
+
.. code-block:: python
|
| 199 |
+
|
| 200 |
+
out = ((image1 - image2) / scale + offset)
|
| 201 |
+
|
| 202 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 203 |
+
"""
|
| 204 |
+
|
| 205 |
+
image1.load()
|
| 206 |
+
image2.load()
|
| 207 |
+
return image1._new(image1.im.chop_subtract(image2.im, scale, offset))
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def add_modulo(image1, image2):
|
| 211 |
+
"""Add two images, without clipping the result.
|
| 212 |
+
|
| 213 |
+
.. code-block:: python
|
| 214 |
+
|
| 215 |
+
out = ((image1 + image2) % MAX)
|
| 216 |
+
|
| 217 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 218 |
+
"""
|
| 219 |
+
|
| 220 |
+
image1.load()
|
| 221 |
+
image2.load()
|
| 222 |
+
return image1._new(image1.im.chop_add_modulo(image2.im))
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def subtract_modulo(image1, image2):
|
| 226 |
+
"""Subtract two images, without clipping the result.
|
| 227 |
+
|
| 228 |
+
.. code-block:: python
|
| 229 |
+
|
| 230 |
+
out = ((image1 - image2) % MAX)
|
| 231 |
+
|
| 232 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 233 |
+
"""
|
| 234 |
+
|
| 235 |
+
image1.load()
|
| 236 |
+
image2.load()
|
| 237 |
+
return image1._new(image1.im.chop_subtract_modulo(image2.im))
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def logical_and(image1, image2):
|
| 241 |
+
"""Logical AND between two images.
|
| 242 |
+
|
| 243 |
+
Both of the images must have mode "1". If you would like to perform a
|
| 244 |
+
logical AND on an image with a mode other than "1", try
|
| 245 |
+
:py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask
|
| 246 |
+
as the second image.
|
| 247 |
+
|
| 248 |
+
.. code-block:: python
|
| 249 |
+
|
| 250 |
+
out = ((image1 and image2) % MAX)
|
| 251 |
+
|
| 252 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 253 |
+
"""
|
| 254 |
+
|
| 255 |
+
image1.load()
|
| 256 |
+
image2.load()
|
| 257 |
+
return image1._new(image1.im.chop_and(image2.im))
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def logical_or(image1, image2):
|
| 261 |
+
"""Logical OR between two images.
|
| 262 |
+
|
| 263 |
+
Both of the images must have mode "1".
|
| 264 |
+
|
| 265 |
+
.. code-block:: python
|
| 266 |
+
|
| 267 |
+
out = ((image1 or image2) % MAX)
|
| 268 |
+
|
| 269 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 270 |
+
"""
|
| 271 |
+
|
| 272 |
+
image1.load()
|
| 273 |
+
image2.load()
|
| 274 |
+
return image1._new(image1.im.chop_or(image2.im))
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def logical_xor(image1, image2):
|
| 278 |
+
"""Logical XOR between two images.
|
| 279 |
+
|
| 280 |
+
Both of the images must have mode "1".
|
| 281 |
+
|
| 282 |
+
.. code-block:: python
|
| 283 |
+
|
| 284 |
+
out = ((bool(image1) != bool(image2)) % MAX)
|
| 285 |
+
|
| 286 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 287 |
+
"""
|
| 288 |
+
|
| 289 |
+
image1.load()
|
| 290 |
+
image2.load()
|
| 291 |
+
return image1._new(image1.im.chop_xor(image2.im))
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def blend(image1, image2, alpha):
|
| 295 |
+
"""Blend images using constant transparency weight. Alias for
|
| 296 |
+
:py:func:`PIL.Image.blend`.
|
| 297 |
+
|
| 298 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 299 |
+
"""
|
| 300 |
+
|
| 301 |
+
return Image.blend(image1, image2, alpha)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def composite(image1, image2, mask):
|
| 305 |
+
"""Create composite using transparency mask. Alias for
|
| 306 |
+
:py:func:`PIL.Image.composite`.
|
| 307 |
+
|
| 308 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 309 |
+
"""
|
| 310 |
+
|
| 311 |
+
return Image.composite(image1, image2, mask)
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def offset(image, xoffset, yoffset=None):
|
| 315 |
+
"""Returns a copy of the image where data has been offset by the given
|
| 316 |
+
distances. Data wraps around the edges. If ``yoffset`` is omitted, it
|
| 317 |
+
is assumed to be equal to ``xoffset``.
|
| 318 |
+
|
| 319 |
+
:param xoffset: The horizontal distance.
|
| 320 |
+
:param yoffset: The vertical distance. If omitted, both
|
| 321 |
+
distances are set to the same value.
|
| 322 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 323 |
+
"""
|
| 324 |
+
|
| 325 |
+
if yoffset is None:
|
| 326 |
+
yoffset = xoffset
|
| 327 |
+
image.load()
|
| 328 |
+
return image._new(image.im.offset(xoffset, yoffset))
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageColor.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# map CSS3-style colour description strings to RGB
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 2002-10-24 fl Added support for CSS-style color strings
|
| 9 |
+
# 2002-12-15 fl Added RGBA support
|
| 10 |
+
# 2004-03-27 fl Fixed remaining int() problems for Python 1.5.2
|
| 11 |
+
# 2004-07-19 fl Fixed gray/grey spelling issues
|
| 12 |
+
# 2009-03-05 fl Fixed rounding error in grayscale calculation
|
| 13 |
+
#
|
| 14 |
+
# Copyright (c) 2002-2004 by Secret Labs AB
|
| 15 |
+
# Copyright (c) 2002-2004 by Fredrik Lundh
|
| 16 |
+
#
|
| 17 |
+
# See the README file for information on usage and redistribution.
|
| 18 |
+
#
|
| 19 |
+
|
| 20 |
+
import re
|
| 21 |
+
|
| 22 |
+
from . import Image
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def getrgb(color):
|
| 26 |
+
"""
|
| 27 |
+
Convert a color string to an RGB or RGBA tuple. If the string cannot be
|
| 28 |
+
parsed, this function raises a :py:exc:`ValueError` exception.
|
| 29 |
+
|
| 30 |
+
.. versionadded:: 1.1.4
|
| 31 |
+
|
| 32 |
+
:param color: A color string
|
| 33 |
+
:return: ``(red, green, blue[, alpha])``
|
| 34 |
+
"""
|
| 35 |
+
if len(color) > 100:
|
| 36 |
+
raise ValueError("color specifier is too long")
|
| 37 |
+
color = color.lower()
|
| 38 |
+
|
| 39 |
+
rgb = colormap.get(color, None)
|
| 40 |
+
if rgb:
|
| 41 |
+
if isinstance(rgb, tuple):
|
| 42 |
+
return rgb
|
| 43 |
+
colormap[color] = rgb = getrgb(rgb)
|
| 44 |
+
return rgb
|
| 45 |
+
|
| 46 |
+
# check for known string formats
|
| 47 |
+
if re.match("#[a-f0-9]{3}$", color):
|
| 48 |
+
return (int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16))
|
| 49 |
+
|
| 50 |
+
if re.match("#[a-f0-9]{4}$", color):
|
| 51 |
+
return (
|
| 52 |
+
int(color[1] * 2, 16),
|
| 53 |
+
int(color[2] * 2, 16),
|
| 54 |
+
int(color[3] * 2, 16),
|
| 55 |
+
int(color[4] * 2, 16),
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
if re.match("#[a-f0-9]{6}$", color):
|
| 59 |
+
return (int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16))
|
| 60 |
+
|
| 61 |
+
if re.match("#[a-f0-9]{8}$", color):
|
| 62 |
+
return (
|
| 63 |
+
int(color[1:3], 16),
|
| 64 |
+
int(color[3:5], 16),
|
| 65 |
+
int(color[5:7], 16),
|
| 66 |
+
int(color[7:9], 16),
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
| 70 |
+
if m:
|
| 71 |
+
return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
|
| 72 |
+
|
| 73 |
+
m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
| 74 |
+
if m:
|
| 75 |
+
return (
|
| 76 |
+
int((int(m.group(1)) * 255) / 100.0 + 0.5),
|
| 77 |
+
int((int(m.group(2)) * 255) / 100.0 + 0.5),
|
| 78 |
+
int((int(m.group(3)) * 255) / 100.0 + 0.5),
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
m = re.match(
|
| 82 |
+
r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
|
| 83 |
+
)
|
| 84 |
+
if m:
|
| 85 |
+
from colorsys import hls_to_rgb
|
| 86 |
+
|
| 87 |
+
rgb = hls_to_rgb(
|
| 88 |
+
float(m.group(1)) / 360.0,
|
| 89 |
+
float(m.group(3)) / 100.0,
|
| 90 |
+
float(m.group(2)) / 100.0,
|
| 91 |
+
)
|
| 92 |
+
return (
|
| 93 |
+
int(rgb[0] * 255 + 0.5),
|
| 94 |
+
int(rgb[1] * 255 + 0.5),
|
| 95 |
+
int(rgb[2] * 255 + 0.5),
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
m = re.match(
|
| 99 |
+
r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
|
| 100 |
+
)
|
| 101 |
+
if m:
|
| 102 |
+
from colorsys import hsv_to_rgb
|
| 103 |
+
|
| 104 |
+
rgb = hsv_to_rgb(
|
| 105 |
+
float(m.group(1)) / 360.0,
|
| 106 |
+
float(m.group(2)) / 100.0,
|
| 107 |
+
float(m.group(3)) / 100.0,
|
| 108 |
+
)
|
| 109 |
+
return (
|
| 110 |
+
int(rgb[0] * 255 + 0.5),
|
| 111 |
+
int(rgb[1] * 255 + 0.5),
|
| 112 |
+
int(rgb[2] * 255 + 0.5),
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
| 116 |
+
if m:
|
| 117 |
+
return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)))
|
| 118 |
+
raise ValueError(f"unknown color specifier: {repr(color)}")
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def getcolor(color, mode):
|
| 122 |
+
"""
|
| 123 |
+
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
|
| 124 |
+
greyscale value if the mode is not color or a palette image. If the string
|
| 125 |
+
cannot be parsed, this function raises a :py:exc:`ValueError` exception.
|
| 126 |
+
|
| 127 |
+
.. versionadded:: 1.1.4
|
| 128 |
+
|
| 129 |
+
:param color: A color string
|
| 130 |
+
:return: ``(graylevel [, alpha]) or (red, green, blue[, alpha])``
|
| 131 |
+
"""
|
| 132 |
+
# same as getrgb, but converts the result to the given mode
|
| 133 |
+
color, alpha = getrgb(color), 255
|
| 134 |
+
if len(color) == 4:
|
| 135 |
+
color, alpha = color[0:3], color[3]
|
| 136 |
+
|
| 137 |
+
if Image.getmodebase(mode) == "L":
|
| 138 |
+
r, g, b = color
|
| 139 |
+
# ITU-R Recommendation 601-2 for nonlinear RGB
|
| 140 |
+
# scaled to 24 bits to match the convert's implementation.
|
| 141 |
+
color = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16
|
| 142 |
+
if mode[-1] == "A":
|
| 143 |
+
return (color, alpha)
|
| 144 |
+
else:
|
| 145 |
+
if mode[-1] == "A":
|
| 146 |
+
return color + (alpha,)
|
| 147 |
+
return color
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
colormap = {
|
| 151 |
+
# X11 colour table from https://drafts.csswg.org/css-color-4/, with
|
| 152 |
+
# gray/grey spelling issues fixed. This is a superset of HTML 4.0
|
| 153 |
+
# colour names used in CSS 1.
|
| 154 |
+
"aliceblue": "#f0f8ff",
|
| 155 |
+
"antiquewhite": "#faebd7",
|
| 156 |
+
"aqua": "#00ffff",
|
| 157 |
+
"aquamarine": "#7fffd4",
|
| 158 |
+
"azure": "#f0ffff",
|
| 159 |
+
"beige": "#f5f5dc",
|
| 160 |
+
"bisque": "#ffe4c4",
|
| 161 |
+
"black": "#000000",
|
| 162 |
+
"blanchedalmond": "#ffebcd",
|
| 163 |
+
"blue": "#0000ff",
|
| 164 |
+
"blueviolet": "#8a2be2",
|
| 165 |
+
"brown": "#a52a2a",
|
| 166 |
+
"burlywood": "#deb887",
|
| 167 |
+
"cadetblue": "#5f9ea0",
|
| 168 |
+
"chartreuse": "#7fff00",
|
| 169 |
+
"chocolate": "#d2691e",
|
| 170 |
+
"coral": "#ff7f50",
|
| 171 |
+
"cornflowerblue": "#6495ed",
|
| 172 |
+
"cornsilk": "#fff8dc",
|
| 173 |
+
"crimson": "#dc143c",
|
| 174 |
+
"cyan": "#00ffff",
|
| 175 |
+
"darkblue": "#00008b",
|
| 176 |
+
"darkcyan": "#008b8b",
|
| 177 |
+
"darkgoldenrod": "#b8860b",
|
| 178 |
+
"darkgray": "#a9a9a9",
|
| 179 |
+
"darkgrey": "#a9a9a9",
|
| 180 |
+
"darkgreen": "#006400",
|
| 181 |
+
"darkkhaki": "#bdb76b",
|
| 182 |
+
"darkmagenta": "#8b008b",
|
| 183 |
+
"darkolivegreen": "#556b2f",
|
| 184 |
+
"darkorange": "#ff8c00",
|
| 185 |
+
"darkorchid": "#9932cc",
|
| 186 |
+
"darkred": "#8b0000",
|
| 187 |
+
"darksalmon": "#e9967a",
|
| 188 |
+
"darkseagreen": "#8fbc8f",
|
| 189 |
+
"darkslateblue": "#483d8b",
|
| 190 |
+
"darkslategray": "#2f4f4f",
|
| 191 |
+
"darkslategrey": "#2f4f4f",
|
| 192 |
+
"darkturquoise": "#00ced1",
|
| 193 |
+
"darkviolet": "#9400d3",
|
| 194 |
+
"deeppink": "#ff1493",
|
| 195 |
+
"deepskyblue": "#00bfff",
|
| 196 |
+
"dimgray": "#696969",
|
| 197 |
+
"dimgrey": "#696969",
|
| 198 |
+
"dodgerblue": "#1e90ff",
|
| 199 |
+
"firebrick": "#b22222",
|
| 200 |
+
"floralwhite": "#fffaf0",
|
| 201 |
+
"forestgreen": "#228b22",
|
| 202 |
+
"fuchsia": "#ff00ff",
|
| 203 |
+
"gainsboro": "#dcdcdc",
|
| 204 |
+
"ghostwhite": "#f8f8ff",
|
| 205 |
+
"gold": "#ffd700",
|
| 206 |
+
"goldenrod": "#daa520",
|
| 207 |
+
"gray": "#808080",
|
| 208 |
+
"grey": "#808080",
|
| 209 |
+
"green": "#008000",
|
| 210 |
+
"greenyellow": "#adff2f",
|
| 211 |
+
"honeydew": "#f0fff0",
|
| 212 |
+
"hotpink": "#ff69b4",
|
| 213 |
+
"indianred": "#cd5c5c",
|
| 214 |
+
"indigo": "#4b0082",
|
| 215 |
+
"ivory": "#fffff0",
|
| 216 |
+
"khaki": "#f0e68c",
|
| 217 |
+
"lavender": "#e6e6fa",
|
| 218 |
+
"lavenderblush": "#fff0f5",
|
| 219 |
+
"lawngreen": "#7cfc00",
|
| 220 |
+
"lemonchiffon": "#fffacd",
|
| 221 |
+
"lightblue": "#add8e6",
|
| 222 |
+
"lightcoral": "#f08080",
|
| 223 |
+
"lightcyan": "#e0ffff",
|
| 224 |
+
"lightgoldenrodyellow": "#fafad2",
|
| 225 |
+
"lightgreen": "#90ee90",
|
| 226 |
+
"lightgray": "#d3d3d3",
|
| 227 |
+
"lightgrey": "#d3d3d3",
|
| 228 |
+
"lightpink": "#ffb6c1",
|
| 229 |
+
"lightsalmon": "#ffa07a",
|
| 230 |
+
"lightseagreen": "#20b2aa",
|
| 231 |
+
"lightskyblue": "#87cefa",
|
| 232 |
+
"lightslategray": "#778899",
|
| 233 |
+
"lightslategrey": "#778899",
|
| 234 |
+
"lightsteelblue": "#b0c4de",
|
| 235 |
+
"lightyellow": "#ffffe0",
|
| 236 |
+
"lime": "#00ff00",
|
| 237 |
+
"limegreen": "#32cd32",
|
| 238 |
+
"linen": "#faf0e6",
|
| 239 |
+
"magenta": "#ff00ff",
|
| 240 |
+
"maroon": "#800000",
|
| 241 |
+
"mediumaquamarine": "#66cdaa",
|
| 242 |
+
"mediumblue": "#0000cd",
|
| 243 |
+
"mediumorchid": "#ba55d3",
|
| 244 |
+
"mediumpurple": "#9370db",
|
| 245 |
+
"mediumseagreen": "#3cb371",
|
| 246 |
+
"mediumslateblue": "#7b68ee",
|
| 247 |
+
"mediumspringgreen": "#00fa9a",
|
| 248 |
+
"mediumturquoise": "#48d1cc",
|
| 249 |
+
"mediumvioletred": "#c71585",
|
| 250 |
+
"midnightblue": "#191970",
|
| 251 |
+
"mintcream": "#f5fffa",
|
| 252 |
+
"mistyrose": "#ffe4e1",
|
| 253 |
+
"moccasin": "#ffe4b5",
|
| 254 |
+
"navajowhite": "#ffdead",
|
| 255 |
+
"navy": "#000080",
|
| 256 |
+
"oldlace": "#fdf5e6",
|
| 257 |
+
"olive": "#808000",
|
| 258 |
+
"olivedrab": "#6b8e23",
|
| 259 |
+
"orange": "#ffa500",
|
| 260 |
+
"orangered": "#ff4500",
|
| 261 |
+
"orchid": "#da70d6",
|
| 262 |
+
"palegoldenrod": "#eee8aa",
|
| 263 |
+
"palegreen": "#98fb98",
|
| 264 |
+
"paleturquoise": "#afeeee",
|
| 265 |
+
"palevioletred": "#db7093",
|
| 266 |
+
"papayawhip": "#ffefd5",
|
| 267 |
+
"peachpuff": "#ffdab9",
|
| 268 |
+
"peru": "#cd853f",
|
| 269 |
+
"pink": "#ffc0cb",
|
| 270 |
+
"plum": "#dda0dd",
|
| 271 |
+
"powderblue": "#b0e0e6",
|
| 272 |
+
"purple": "#800080",
|
| 273 |
+
"rebeccapurple": "#663399",
|
| 274 |
+
"red": "#ff0000",
|
| 275 |
+
"rosybrown": "#bc8f8f",
|
| 276 |
+
"royalblue": "#4169e1",
|
| 277 |
+
"saddlebrown": "#8b4513",
|
| 278 |
+
"salmon": "#fa8072",
|
| 279 |
+
"sandybrown": "#f4a460",
|
| 280 |
+
"seagreen": "#2e8b57",
|
| 281 |
+
"seashell": "#fff5ee",
|
| 282 |
+
"sienna": "#a0522d",
|
| 283 |
+
"silver": "#c0c0c0",
|
| 284 |
+
"skyblue": "#87ceeb",
|
| 285 |
+
"slateblue": "#6a5acd",
|
| 286 |
+
"slategray": "#708090",
|
| 287 |
+
"slategrey": "#708090",
|
| 288 |
+
"snow": "#fffafa",
|
| 289 |
+
"springgreen": "#00ff7f",
|
| 290 |
+
"steelblue": "#4682b4",
|
| 291 |
+
"tan": "#d2b48c",
|
| 292 |
+
"teal": "#008080",
|
| 293 |
+
"thistle": "#d8bfd8",
|
| 294 |
+
"tomato": "#ff6347",
|
| 295 |
+
"turquoise": "#40e0d0",
|
| 296 |
+
"violet": "#ee82ee",
|
| 297 |
+
"wheat": "#f5deb3",
|
| 298 |
+
"white": "#ffffff",
|
| 299 |
+
"whitesmoke": "#f5f5f5",
|
| 300 |
+
"yellow": "#ffff00",
|
| 301 |
+
"yellowgreen": "#9acd32",
|
| 302 |
+
}
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageDraw2.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# WCK-style drawing interface operations
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 2003-12-07 fl created
|
| 9 |
+
# 2005-05-15 fl updated; added to PIL as ImageDraw2
|
| 10 |
+
# 2005-05-15 fl added text support
|
| 11 |
+
# 2005-05-20 fl added arc/chord/pieslice support
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) 2003-2005 by Secret Labs AB
|
| 14 |
+
# Copyright (c) 2003-2005 by Fredrik Lundh
|
| 15 |
+
#
|
| 16 |
+
# See the README file for information on usage and redistribution.
|
| 17 |
+
#
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
(Experimental) WCK-style drawing interface operations
|
| 22 |
+
|
| 23 |
+
.. seealso:: :py:mod:`PIL.ImageDraw`
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class Pen:
|
| 31 |
+
"""Stores an outline color and width."""
|
| 32 |
+
|
| 33 |
+
def __init__(self, color, width=1, opacity=255):
|
| 34 |
+
self.color = ImageColor.getrgb(color)
|
| 35 |
+
self.width = width
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class Brush:
|
| 39 |
+
"""Stores a fill color"""
|
| 40 |
+
|
| 41 |
+
def __init__(self, color, opacity=255):
|
| 42 |
+
self.color = ImageColor.getrgb(color)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class Font:
|
| 46 |
+
"""Stores a TrueType font and color"""
|
| 47 |
+
|
| 48 |
+
def __init__(self, color, file, size=12):
|
| 49 |
+
# FIXME: add support for bitmap fonts
|
| 50 |
+
self.color = ImageColor.getrgb(color)
|
| 51 |
+
self.font = ImageFont.truetype(file, size)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class Draw:
|
| 55 |
+
"""
|
| 56 |
+
(Experimental) WCK-style drawing interface
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
def __init__(self, image, size=None, color=None):
|
| 60 |
+
if not hasattr(image, "im"):
|
| 61 |
+
image = Image.new(image, size, color)
|
| 62 |
+
self.draw = ImageDraw.Draw(image)
|
| 63 |
+
self.image = image
|
| 64 |
+
self.transform = None
|
| 65 |
+
|
| 66 |
+
def flush(self):
|
| 67 |
+
return self.image
|
| 68 |
+
|
| 69 |
+
def render(self, op, xy, pen, brush=None):
|
| 70 |
+
# handle color arguments
|
| 71 |
+
outline = fill = None
|
| 72 |
+
width = 1
|
| 73 |
+
if isinstance(pen, Pen):
|
| 74 |
+
outline = pen.color
|
| 75 |
+
width = pen.width
|
| 76 |
+
elif isinstance(brush, Pen):
|
| 77 |
+
outline = brush.color
|
| 78 |
+
width = brush.width
|
| 79 |
+
if isinstance(brush, Brush):
|
| 80 |
+
fill = brush.color
|
| 81 |
+
elif isinstance(pen, Brush):
|
| 82 |
+
fill = pen.color
|
| 83 |
+
# handle transformation
|
| 84 |
+
if self.transform:
|
| 85 |
+
xy = ImagePath.Path(xy)
|
| 86 |
+
xy.transform(self.transform)
|
| 87 |
+
# render the item
|
| 88 |
+
if op == "line":
|
| 89 |
+
self.draw.line(xy, fill=outline, width=width)
|
| 90 |
+
else:
|
| 91 |
+
getattr(self.draw, op)(xy, fill=fill, outline=outline)
|
| 92 |
+
|
| 93 |
+
def settransform(self, offset):
|
| 94 |
+
"""Sets a transformation offset."""
|
| 95 |
+
(xoffset, yoffset) = offset
|
| 96 |
+
self.transform = (1, 0, xoffset, 0, 1, yoffset)
|
| 97 |
+
|
| 98 |
+
def arc(self, xy, start, end, *options):
|
| 99 |
+
"""
|
| 100 |
+
Draws an arc (a portion of a circle outline) between the start and end
|
| 101 |
+
angles, inside the given bounding box.
|
| 102 |
+
|
| 103 |
+
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.arc`
|
| 104 |
+
"""
|
| 105 |
+
self.render("arc", xy, start, end, *options)
|
| 106 |
+
|
| 107 |
+
def chord(self, xy, start, end, *options):
|
| 108 |
+
"""
|
| 109 |
+
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
|
| 110 |
+
with a straight line.
|
| 111 |
+
|
| 112 |
+
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord`
|
| 113 |
+
"""
|
| 114 |
+
self.render("chord", xy, start, end, *options)
|
| 115 |
+
|
| 116 |
+
def ellipse(self, xy, *options):
|
| 117 |
+
"""
|
| 118 |
+
Draws an ellipse inside the given bounding box.
|
| 119 |
+
|
| 120 |
+
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse`
|
| 121 |
+
"""
|
| 122 |
+
self.render("ellipse", xy, *options)
|
| 123 |
+
|
| 124 |
+
def line(self, xy, *options):
|
| 125 |
+
"""
|
| 126 |
+
Draws a line between the coordinates in the ``xy`` list.
|
| 127 |
+
|
| 128 |
+
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line`
|
| 129 |
+
"""
|
| 130 |
+
self.render("line", xy, *options)
|
| 131 |
+
|
| 132 |
+
def pieslice(self, xy, start, end, *options):
|
| 133 |
+
"""
|
| 134 |
+
Same as arc, but also draws straight lines between the end points and the
|
| 135 |
+
center of the bounding box.
|
| 136 |
+
|
| 137 |
+
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.pieslice`
|
| 138 |
+
"""
|
| 139 |
+
self.render("pieslice", xy, start, end, *options)
|
| 140 |
+
|
| 141 |
+
def polygon(self, xy, *options):
|
| 142 |
+
"""
|
| 143 |
+
Draws a polygon.
|
| 144 |
+
|
| 145 |
+
The polygon outline consists of straight lines between the given
|
| 146 |
+
coordinates, plus a straight line between the last and the first
|
| 147 |
+
coordinate.
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.polygon`
|
| 151 |
+
"""
|
| 152 |
+
self.render("polygon", xy, *options)
|
| 153 |
+
|
| 154 |
+
def rectangle(self, xy, *options):
|
| 155 |
+
"""
|
| 156 |
+
Draws a rectangle.
|
| 157 |
+
|
| 158 |
+
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle`
|
| 159 |
+
"""
|
| 160 |
+
self.render("rectangle", xy, *options)
|
| 161 |
+
|
| 162 |
+
def text(self, xy, text, font):
|
| 163 |
+
"""
|
| 164 |
+
Draws the string at the given position.
|
| 165 |
+
|
| 166 |
+
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
|
| 167 |
+
"""
|
| 168 |
+
if self.transform:
|
| 169 |
+
xy = ImagePath.Path(xy)
|
| 170 |
+
xy.transform(self.transform)
|
| 171 |
+
self.draw.text(xy, text, font=font.font, fill=font.color)
|
| 172 |
+
|
| 173 |
+
def textsize(self, text, font):
|
| 174 |
+
"""
|
| 175 |
+
Return the size of the given string, in pixels.
|
| 176 |
+
|
| 177 |
+
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textsize`
|
| 178 |
+
"""
|
| 179 |
+
return self.draw.textsize(text, font=font.font)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageEnhance.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# image enhancement classes
|
| 6 |
+
#
|
| 7 |
+
# For a background, see "Image Processing By Interpolation and
|
| 8 |
+
# Extrapolation", Paul Haeberli and Douglas Voorhies. Available
|
| 9 |
+
# at http://www.graficaobscura.com/interp/index.html
|
| 10 |
+
#
|
| 11 |
+
# History:
|
| 12 |
+
# 1996-03-23 fl Created
|
| 13 |
+
# 2009-06-16 fl Fixed mean calculation
|
| 14 |
+
#
|
| 15 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 16 |
+
# Copyright (c) Fredrik Lundh 1996.
|
| 17 |
+
#
|
| 18 |
+
# See the README file for information on usage and redistribution.
|
| 19 |
+
#
|
| 20 |
+
|
| 21 |
+
from . import Image, ImageFilter, ImageStat
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class _Enhance:
|
| 25 |
+
def enhance(self, factor):
|
| 26 |
+
"""
|
| 27 |
+
Returns an enhanced image.
|
| 28 |
+
|
| 29 |
+
:param factor: A floating point value controlling the enhancement.
|
| 30 |
+
Factor 1.0 always returns a copy of the original image,
|
| 31 |
+
lower factors mean less color (brightness, contrast,
|
| 32 |
+
etc), and higher values more. There are no restrictions
|
| 33 |
+
on this value.
|
| 34 |
+
:rtype: :py:class:`~PIL.Image.Image`
|
| 35 |
+
"""
|
| 36 |
+
return Image.blend(self.degenerate, self.image, factor)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class Color(_Enhance):
|
| 40 |
+
"""Adjust image color balance.
|
| 41 |
+
|
| 42 |
+
This class can be used to adjust the colour balance of an image, in
|
| 43 |
+
a manner similar to the controls on a colour TV set. An enhancement
|
| 44 |
+
factor of 0.0 gives a black and white image. A factor of 1.0 gives
|
| 45 |
+
the original image.
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
def __init__(self, image):
|
| 49 |
+
self.image = image
|
| 50 |
+
self.intermediate_mode = "L"
|
| 51 |
+
if "A" in image.getbands():
|
| 52 |
+
self.intermediate_mode = "LA"
|
| 53 |
+
|
| 54 |
+
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class Contrast(_Enhance):
|
| 58 |
+
"""Adjust image contrast.
|
| 59 |
+
|
| 60 |
+
This class can be used to control the contrast of an image, similar
|
| 61 |
+
to the contrast control on a TV set. An enhancement factor of 0.0
|
| 62 |
+
gives a solid grey image. A factor of 1.0 gives the original image.
|
| 63 |
+
"""
|
| 64 |
+
|
| 65 |
+
def __init__(self, image):
|
| 66 |
+
self.image = image
|
| 67 |
+
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
|
| 68 |
+
self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
|
| 69 |
+
|
| 70 |
+
if "A" in image.getbands():
|
| 71 |
+
self.degenerate.putalpha(image.getchannel("A"))
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class Brightness(_Enhance):
|
| 75 |
+
"""Adjust image brightness.
|
| 76 |
+
|
| 77 |
+
This class can be used to control the brightness of an image. An
|
| 78 |
+
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
|
| 79 |
+
original image.
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
def __init__(self, image):
|
| 83 |
+
self.image = image
|
| 84 |
+
self.degenerate = Image.new(image.mode, image.size, 0)
|
| 85 |
+
|
| 86 |
+
if "A" in image.getbands():
|
| 87 |
+
self.degenerate.putalpha(image.getchannel("A"))
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class Sharpness(_Enhance):
|
| 91 |
+
"""Adjust image sharpness.
|
| 92 |
+
|
| 93 |
+
This class can be used to adjust the sharpness of an image. An
|
| 94 |
+
enhancement factor of 0.0 gives a blurred image, a factor of 1.0 gives the
|
| 95 |
+
original image, and a factor of 2.0 gives a sharpened image.
|
| 96 |
+
"""
|
| 97 |
+
|
| 98 |
+
def __init__(self, image):
|
| 99 |
+
self.image = image
|
| 100 |
+
self.degenerate = image.filter(ImageFilter.SMOOTH)
|
| 101 |
+
|
| 102 |
+
if "A" in image.getbands():
|
| 103 |
+
self.degenerate.putalpha(image.getchannel("A"))
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageFile.py
ADDED
|
@@ -0,0 +1,748 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# base class for image file handlers
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 1995-09-09 fl Created
|
| 9 |
+
# 1996-03-11 fl Fixed load mechanism.
|
| 10 |
+
# 1996-04-15 fl Added pcx/xbm decoders.
|
| 11 |
+
# 1996-04-30 fl Added encoders.
|
| 12 |
+
# 1996-12-14 fl Added load helpers
|
| 13 |
+
# 1997-01-11 fl Use encode_to_file where possible
|
| 14 |
+
# 1997-08-27 fl Flush output in _save
|
| 15 |
+
# 1998-03-05 fl Use memory mapping for some modes
|
| 16 |
+
# 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B"
|
| 17 |
+
# 1999-05-31 fl Added image parser
|
| 18 |
+
# 2000-10-12 fl Set readonly flag on memory-mapped images
|
| 19 |
+
# 2002-03-20 fl Use better messages for common decoder errors
|
| 20 |
+
# 2003-04-21 fl Fall back on mmap/map_buffer if map is not available
|
| 21 |
+
# 2003-10-30 fl Added StubImageFile class
|
| 22 |
+
# 2004-02-25 fl Made incremental parser more robust
|
| 23 |
+
#
|
| 24 |
+
# Copyright (c) 1997-2004 by Secret Labs AB
|
| 25 |
+
# Copyright (c) 1995-2004 by Fredrik Lundh
|
| 26 |
+
#
|
| 27 |
+
# See the README file for information on usage and redistribution.
|
| 28 |
+
#
|
| 29 |
+
|
| 30 |
+
import io
|
| 31 |
+
import itertools
|
| 32 |
+
import struct
|
| 33 |
+
import sys
|
| 34 |
+
|
| 35 |
+
from . import Image
|
| 36 |
+
from ._util import isPath
|
| 37 |
+
|
| 38 |
+
MAXBLOCK = 65536
|
| 39 |
+
|
| 40 |
+
SAFEBLOCK = 1024 * 1024
|
| 41 |
+
|
| 42 |
+
LOAD_TRUNCATED_IMAGES = False
|
| 43 |
+
"""Whether or not to load truncated image files. User code may change this."""
|
| 44 |
+
|
| 45 |
+
ERRORS = {
|
| 46 |
+
-1: "image buffer overrun error",
|
| 47 |
+
-2: "decoding error",
|
| 48 |
+
-3: "unknown error",
|
| 49 |
+
-8: "bad configuration",
|
| 50 |
+
-9: "out of memory error",
|
| 51 |
+
}
|
| 52 |
+
"""
|
| 53 |
+
Dict of known error codes returned from :meth:`.PyDecoder.decode`,
|
| 54 |
+
:meth:`.PyEncoder.encode` :meth:`.PyEncoder.encode_to_pyfd` and
|
| 55 |
+
:meth:`.PyEncoder.encode_to_file`.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
#
|
| 60 |
+
# --------------------------------------------------------------------
|
| 61 |
+
# Helpers
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def raise_oserror(error):
|
| 65 |
+
try:
|
| 66 |
+
message = Image.core.getcodecstatus(error)
|
| 67 |
+
except AttributeError:
|
| 68 |
+
message = ERRORS.get(error)
|
| 69 |
+
if not message:
|
| 70 |
+
message = f"decoder error {error}"
|
| 71 |
+
raise OSError(message + " when reading image file")
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _tilesort(t):
|
| 75 |
+
# sort on offset
|
| 76 |
+
return t[2]
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
#
|
| 80 |
+
# --------------------------------------------------------------------
|
| 81 |
+
# ImageFile base class
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class ImageFile(Image.Image):
|
| 85 |
+
"""Base class for image file format handlers."""
|
| 86 |
+
|
| 87 |
+
def __init__(self, fp=None, filename=None):
|
| 88 |
+
super().__init__()
|
| 89 |
+
|
| 90 |
+
self._min_frame = 0
|
| 91 |
+
|
| 92 |
+
self.custom_mimetype = None
|
| 93 |
+
|
| 94 |
+
self.tile = None
|
| 95 |
+
""" A list of tile descriptors, or ``None`` """
|
| 96 |
+
|
| 97 |
+
self.readonly = 1 # until we know better
|
| 98 |
+
|
| 99 |
+
self.decoderconfig = ()
|
| 100 |
+
self.decodermaxblock = MAXBLOCK
|
| 101 |
+
|
| 102 |
+
if isPath(fp):
|
| 103 |
+
# filename
|
| 104 |
+
self.fp = open(fp, "rb")
|
| 105 |
+
self.filename = fp
|
| 106 |
+
self._exclusive_fp = True
|
| 107 |
+
else:
|
| 108 |
+
# stream
|
| 109 |
+
self.fp = fp
|
| 110 |
+
self.filename = filename
|
| 111 |
+
# can be overridden
|
| 112 |
+
self._exclusive_fp = None
|
| 113 |
+
|
| 114 |
+
try:
|
| 115 |
+
try:
|
| 116 |
+
self._open()
|
| 117 |
+
except (
|
| 118 |
+
IndexError, # end of data
|
| 119 |
+
TypeError, # end of data (ord)
|
| 120 |
+
KeyError, # unsupported mode
|
| 121 |
+
EOFError, # got header but not the first frame
|
| 122 |
+
struct.error,
|
| 123 |
+
) as v:
|
| 124 |
+
raise SyntaxError(v) from v
|
| 125 |
+
|
| 126 |
+
if not self.mode or self.size[0] <= 0 or self.size[1] <= 0:
|
| 127 |
+
raise SyntaxError("not identified by this driver")
|
| 128 |
+
except BaseException:
|
| 129 |
+
# close the file only if we have opened it this constructor
|
| 130 |
+
if self._exclusive_fp:
|
| 131 |
+
self.fp.close()
|
| 132 |
+
raise
|
| 133 |
+
|
| 134 |
+
def get_format_mimetype(self):
|
| 135 |
+
if self.custom_mimetype:
|
| 136 |
+
return self.custom_mimetype
|
| 137 |
+
if self.format is not None:
|
| 138 |
+
return Image.MIME.get(self.format.upper())
|
| 139 |
+
|
| 140 |
+
def verify(self):
|
| 141 |
+
"""Check file integrity"""
|
| 142 |
+
|
| 143 |
+
# raise exception if something's wrong. must be called
|
| 144 |
+
# directly after open, and closes file when finished.
|
| 145 |
+
if self._exclusive_fp:
|
| 146 |
+
self.fp.close()
|
| 147 |
+
self.fp = None
|
| 148 |
+
|
| 149 |
+
def load(self):
|
| 150 |
+
"""Load image data based on tile list"""
|
| 151 |
+
|
| 152 |
+
if self.tile is None:
|
| 153 |
+
raise OSError("cannot load this image")
|
| 154 |
+
|
| 155 |
+
pixel = Image.Image.load(self)
|
| 156 |
+
if not self.tile:
|
| 157 |
+
return pixel
|
| 158 |
+
|
| 159 |
+
self.map = None
|
| 160 |
+
use_mmap = self.filename and len(self.tile) == 1
|
| 161 |
+
# As of pypy 2.1.0, memory mapping was failing here.
|
| 162 |
+
use_mmap = use_mmap and not hasattr(sys, "pypy_version_info")
|
| 163 |
+
|
| 164 |
+
readonly = 0
|
| 165 |
+
|
| 166 |
+
# look for read/seek overrides
|
| 167 |
+
try:
|
| 168 |
+
read = self.load_read
|
| 169 |
+
# don't use mmap if there are custom read/seek functions
|
| 170 |
+
use_mmap = False
|
| 171 |
+
except AttributeError:
|
| 172 |
+
read = self.fp.read
|
| 173 |
+
|
| 174 |
+
try:
|
| 175 |
+
seek = self.load_seek
|
| 176 |
+
use_mmap = False
|
| 177 |
+
except AttributeError:
|
| 178 |
+
seek = self.fp.seek
|
| 179 |
+
|
| 180 |
+
if use_mmap:
|
| 181 |
+
# try memory mapping
|
| 182 |
+
decoder_name, extents, offset, args = self.tile[0]
|
| 183 |
+
if (
|
| 184 |
+
decoder_name == "raw"
|
| 185 |
+
and len(args) >= 3
|
| 186 |
+
and args[0] == self.mode
|
| 187 |
+
and args[0] in Image._MAPMODES
|
| 188 |
+
):
|
| 189 |
+
try:
|
| 190 |
+
# use mmap, if possible
|
| 191 |
+
import mmap
|
| 192 |
+
|
| 193 |
+
with open(self.filename) as fp:
|
| 194 |
+
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
|
| 195 |
+
self.im = Image.core.map_buffer(
|
| 196 |
+
self.map, self.size, decoder_name, offset, args
|
| 197 |
+
)
|
| 198 |
+
readonly = 1
|
| 199 |
+
# After trashing self.im,
|
| 200 |
+
# we might need to reload the palette data.
|
| 201 |
+
if self.palette:
|
| 202 |
+
self.palette.dirty = 1
|
| 203 |
+
except (AttributeError, OSError, ImportError):
|
| 204 |
+
self.map = None
|
| 205 |
+
|
| 206 |
+
self.load_prepare()
|
| 207 |
+
err_code = -3 # initialize to unknown error
|
| 208 |
+
if not self.map:
|
| 209 |
+
# sort tiles in file order
|
| 210 |
+
self.tile.sort(key=_tilesort)
|
| 211 |
+
|
| 212 |
+
try:
|
| 213 |
+
# FIXME: This is a hack to handle TIFF's JpegTables tag.
|
| 214 |
+
prefix = self.tile_prefix
|
| 215 |
+
except AttributeError:
|
| 216 |
+
prefix = b""
|
| 217 |
+
|
| 218 |
+
# Remove consecutive duplicates that only differ by their offset
|
| 219 |
+
self.tile = [
|
| 220 |
+
list(tiles)[-1]
|
| 221 |
+
for _, tiles in itertools.groupby(
|
| 222 |
+
self.tile, lambda tile: (tile[0], tile[1], tile[3])
|
| 223 |
+
)
|
| 224 |
+
]
|
| 225 |
+
for decoder_name, extents, offset, args in self.tile:
|
| 226 |
+
seek(offset)
|
| 227 |
+
decoder = Image._getdecoder(
|
| 228 |
+
self.mode, decoder_name, args, self.decoderconfig
|
| 229 |
+
)
|
| 230 |
+
try:
|
| 231 |
+
decoder.setimage(self.im, extents)
|
| 232 |
+
if decoder.pulls_fd:
|
| 233 |
+
decoder.setfd(self.fp)
|
| 234 |
+
err_code = decoder.decode(b"")[1]
|
| 235 |
+
else:
|
| 236 |
+
b = prefix
|
| 237 |
+
while True:
|
| 238 |
+
try:
|
| 239 |
+
s = read(self.decodermaxblock)
|
| 240 |
+
except (IndexError, struct.error) as e:
|
| 241 |
+
# truncated png/gif
|
| 242 |
+
if LOAD_TRUNCATED_IMAGES:
|
| 243 |
+
break
|
| 244 |
+
else:
|
| 245 |
+
raise OSError("image file is truncated") from e
|
| 246 |
+
|
| 247 |
+
if not s: # truncated jpeg
|
| 248 |
+
if LOAD_TRUNCATED_IMAGES:
|
| 249 |
+
break
|
| 250 |
+
else:
|
| 251 |
+
raise OSError(
|
| 252 |
+
"image file is truncated "
|
| 253 |
+
f"({len(b)} bytes not processed)"
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
b = b + s
|
| 257 |
+
n, err_code = decoder.decode(b)
|
| 258 |
+
if n < 0:
|
| 259 |
+
break
|
| 260 |
+
b = b[n:]
|
| 261 |
+
finally:
|
| 262 |
+
# Need to cleanup here to prevent leaks
|
| 263 |
+
decoder.cleanup()
|
| 264 |
+
|
| 265 |
+
self.tile = []
|
| 266 |
+
self.readonly = readonly
|
| 267 |
+
|
| 268 |
+
self.load_end()
|
| 269 |
+
|
| 270 |
+
if self._exclusive_fp and self._close_exclusive_fp_after_loading:
|
| 271 |
+
self.fp.close()
|
| 272 |
+
self.fp = None
|
| 273 |
+
|
| 274 |
+
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
|
| 275 |
+
# still raised if decoder fails to return anything
|
| 276 |
+
raise_oserror(err_code)
|
| 277 |
+
|
| 278 |
+
return Image.Image.load(self)
|
| 279 |
+
|
| 280 |
+
def load_prepare(self):
|
| 281 |
+
# create image memory if necessary
|
| 282 |
+
if not self.im or self.im.mode != self.mode or self.im.size != self.size:
|
| 283 |
+
self.im = Image.core.new(self.mode, self.size)
|
| 284 |
+
# create palette (optional)
|
| 285 |
+
if self.mode == "P":
|
| 286 |
+
Image.Image.load(self)
|
| 287 |
+
|
| 288 |
+
def load_end(self):
|
| 289 |
+
# may be overridden
|
| 290 |
+
pass
|
| 291 |
+
|
| 292 |
+
# may be defined for contained formats
|
| 293 |
+
# def load_seek(self, pos):
|
| 294 |
+
# pass
|
| 295 |
+
|
| 296 |
+
# may be defined for blocked formats (e.g. PNG)
|
| 297 |
+
# def load_read(self, bytes):
|
| 298 |
+
# pass
|
| 299 |
+
|
| 300 |
+
def _seek_check(self, frame):
|
| 301 |
+
if (
|
| 302 |
+
frame < self._min_frame
|
| 303 |
+
# Only check upper limit on frames if additional seek operations
|
| 304 |
+
# are not required to do so
|
| 305 |
+
or (
|
| 306 |
+
not (hasattr(self, "_n_frames") and self._n_frames is None)
|
| 307 |
+
and frame >= self.n_frames + self._min_frame
|
| 308 |
+
)
|
| 309 |
+
):
|
| 310 |
+
raise EOFError("attempt to seek outside sequence")
|
| 311 |
+
|
| 312 |
+
return self.tell() != frame
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
class StubImageFile(ImageFile):
|
| 316 |
+
"""
|
| 317 |
+
Base class for stub image loaders.
|
| 318 |
+
|
| 319 |
+
A stub loader is an image loader that can identify files of a
|
| 320 |
+
certain format, but relies on external code to load the file.
|
| 321 |
+
"""
|
| 322 |
+
|
| 323 |
+
def _open(self):
|
| 324 |
+
raise NotImplementedError("StubImageFile subclass must implement _open")
|
| 325 |
+
|
| 326 |
+
def load(self):
|
| 327 |
+
loader = self._load()
|
| 328 |
+
if loader is None:
|
| 329 |
+
raise OSError(f"cannot find loader for this {self.format} file")
|
| 330 |
+
image = loader.load(self)
|
| 331 |
+
assert image is not None
|
| 332 |
+
# become the other object (!)
|
| 333 |
+
self.__class__ = image.__class__
|
| 334 |
+
self.__dict__ = image.__dict__
|
| 335 |
+
return image.load()
|
| 336 |
+
|
| 337 |
+
def _load(self):
|
| 338 |
+
"""(Hook) Find actual image loader."""
|
| 339 |
+
raise NotImplementedError("StubImageFile subclass must implement _load")
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
class Parser:
|
| 343 |
+
"""
|
| 344 |
+
Incremental image parser. This class implements the standard
|
| 345 |
+
feed/close consumer interface.
|
| 346 |
+
"""
|
| 347 |
+
|
| 348 |
+
incremental = None
|
| 349 |
+
image = None
|
| 350 |
+
data = None
|
| 351 |
+
decoder = None
|
| 352 |
+
offset = 0
|
| 353 |
+
finished = 0
|
| 354 |
+
|
| 355 |
+
def reset(self):
|
| 356 |
+
"""
|
| 357 |
+
(Consumer) Reset the parser. Note that you can only call this
|
| 358 |
+
method immediately after you've created a parser; parser
|
| 359 |
+
instances cannot be reused.
|
| 360 |
+
"""
|
| 361 |
+
assert self.data is None, "cannot reuse parsers"
|
| 362 |
+
|
| 363 |
+
def feed(self, data):
|
| 364 |
+
"""
|
| 365 |
+
(Consumer) Feed data to the parser.
|
| 366 |
+
|
| 367 |
+
:param data: A string buffer.
|
| 368 |
+
:exception OSError: If the parser failed to parse the image file.
|
| 369 |
+
"""
|
| 370 |
+
# collect data
|
| 371 |
+
|
| 372 |
+
if self.finished:
|
| 373 |
+
return
|
| 374 |
+
|
| 375 |
+
if self.data is None:
|
| 376 |
+
self.data = data
|
| 377 |
+
else:
|
| 378 |
+
self.data = self.data + data
|
| 379 |
+
|
| 380 |
+
# parse what we have
|
| 381 |
+
if self.decoder:
|
| 382 |
+
|
| 383 |
+
if self.offset > 0:
|
| 384 |
+
# skip header
|
| 385 |
+
skip = min(len(self.data), self.offset)
|
| 386 |
+
self.data = self.data[skip:]
|
| 387 |
+
self.offset = self.offset - skip
|
| 388 |
+
if self.offset > 0 or not self.data:
|
| 389 |
+
return
|
| 390 |
+
|
| 391 |
+
n, e = self.decoder.decode(self.data)
|
| 392 |
+
|
| 393 |
+
if n < 0:
|
| 394 |
+
# end of stream
|
| 395 |
+
self.data = None
|
| 396 |
+
self.finished = 1
|
| 397 |
+
if e < 0:
|
| 398 |
+
# decoding error
|
| 399 |
+
self.image = None
|
| 400 |
+
raise_oserror(e)
|
| 401 |
+
else:
|
| 402 |
+
# end of image
|
| 403 |
+
return
|
| 404 |
+
self.data = self.data[n:]
|
| 405 |
+
|
| 406 |
+
elif self.image:
|
| 407 |
+
|
| 408 |
+
# if we end up here with no decoder, this file cannot
|
| 409 |
+
# be incrementally parsed. wait until we've gotten all
|
| 410 |
+
# available data
|
| 411 |
+
pass
|
| 412 |
+
|
| 413 |
+
else:
|
| 414 |
+
|
| 415 |
+
# attempt to open this file
|
| 416 |
+
try:
|
| 417 |
+
with io.BytesIO(self.data) as fp:
|
| 418 |
+
im = Image.open(fp)
|
| 419 |
+
except OSError:
|
| 420 |
+
# traceback.print_exc()
|
| 421 |
+
pass # not enough data
|
| 422 |
+
else:
|
| 423 |
+
flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
|
| 424 |
+
if flag or len(im.tile) != 1:
|
| 425 |
+
# custom load code, or multiple tiles
|
| 426 |
+
self.decode = None
|
| 427 |
+
else:
|
| 428 |
+
# initialize decoder
|
| 429 |
+
im.load_prepare()
|
| 430 |
+
d, e, o, a = im.tile[0]
|
| 431 |
+
im.tile = []
|
| 432 |
+
self.decoder = Image._getdecoder(im.mode, d, a, im.decoderconfig)
|
| 433 |
+
self.decoder.setimage(im.im, e)
|
| 434 |
+
|
| 435 |
+
# calculate decoder offset
|
| 436 |
+
self.offset = o
|
| 437 |
+
if self.offset <= len(self.data):
|
| 438 |
+
self.data = self.data[self.offset :]
|
| 439 |
+
self.offset = 0
|
| 440 |
+
|
| 441 |
+
self.image = im
|
| 442 |
+
|
| 443 |
+
def __enter__(self):
|
| 444 |
+
return self
|
| 445 |
+
|
| 446 |
+
def __exit__(self, *args):
|
| 447 |
+
self.close()
|
| 448 |
+
|
| 449 |
+
def close(self):
|
| 450 |
+
"""
|
| 451 |
+
(Consumer) Close the stream.
|
| 452 |
+
|
| 453 |
+
:returns: An image object.
|
| 454 |
+
:exception OSError: If the parser failed to parse the image file either
|
| 455 |
+
because it cannot be identified or cannot be
|
| 456 |
+
decoded.
|
| 457 |
+
"""
|
| 458 |
+
# finish decoding
|
| 459 |
+
if self.decoder:
|
| 460 |
+
# get rid of what's left in the buffers
|
| 461 |
+
self.feed(b"")
|
| 462 |
+
self.data = self.decoder = None
|
| 463 |
+
if not self.finished:
|
| 464 |
+
raise OSError("image was incomplete")
|
| 465 |
+
if not self.image:
|
| 466 |
+
raise OSError("cannot parse this image")
|
| 467 |
+
if self.data:
|
| 468 |
+
# incremental parsing not possible; reopen the file
|
| 469 |
+
# not that we have all data
|
| 470 |
+
with io.BytesIO(self.data) as fp:
|
| 471 |
+
try:
|
| 472 |
+
self.image = Image.open(fp)
|
| 473 |
+
finally:
|
| 474 |
+
self.image.load()
|
| 475 |
+
return self.image
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
# --------------------------------------------------------------------
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
def _save(im, fp, tile, bufsize=0):
|
| 482 |
+
"""Helper to save image based on tile list
|
| 483 |
+
|
| 484 |
+
:param im: Image object.
|
| 485 |
+
:param fp: File object.
|
| 486 |
+
:param tile: Tile list.
|
| 487 |
+
:param bufsize: Optional buffer size
|
| 488 |
+
"""
|
| 489 |
+
|
| 490 |
+
im.load()
|
| 491 |
+
if not hasattr(im, "encoderconfig"):
|
| 492 |
+
im.encoderconfig = ()
|
| 493 |
+
tile.sort(key=_tilesort)
|
| 494 |
+
# FIXME: make MAXBLOCK a configuration parameter
|
| 495 |
+
# It would be great if we could have the encoder specify what it needs
|
| 496 |
+
# But, it would need at least the image size in most cases. RawEncode is
|
| 497 |
+
# a tricky case.
|
| 498 |
+
bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
|
| 499 |
+
try:
|
| 500 |
+
fh = fp.fileno()
|
| 501 |
+
fp.flush()
|
| 502 |
+
exc = None
|
| 503 |
+
except (AttributeError, io.UnsupportedOperation) as e:
|
| 504 |
+
exc = e
|
| 505 |
+
for e, b, o, a in tile:
|
| 506 |
+
if o > 0:
|
| 507 |
+
fp.seek(o)
|
| 508 |
+
encoder = Image._getencoder(im.mode, e, a, im.encoderconfig)
|
| 509 |
+
try:
|
| 510 |
+
encoder.setimage(im.im, b)
|
| 511 |
+
if encoder.pushes_fd:
|
| 512 |
+
encoder.setfd(fp)
|
| 513 |
+
l, s = encoder.encode_to_pyfd()
|
| 514 |
+
else:
|
| 515 |
+
if exc:
|
| 516 |
+
# compress to Python file-compatible object
|
| 517 |
+
while True:
|
| 518 |
+
l, s, d = encoder.encode(bufsize)
|
| 519 |
+
fp.write(d)
|
| 520 |
+
if s:
|
| 521 |
+
break
|
| 522 |
+
else:
|
| 523 |
+
# slight speedup: compress to real file object
|
| 524 |
+
s = encoder.encode_to_file(fh, bufsize)
|
| 525 |
+
if s < 0:
|
| 526 |
+
raise OSError(f"encoder error {s} when writing image file") from exc
|
| 527 |
+
finally:
|
| 528 |
+
encoder.cleanup()
|
| 529 |
+
if hasattr(fp, "flush"):
|
| 530 |
+
fp.flush()
|
| 531 |
+
|
| 532 |
+
|
| 533 |
+
def _safe_read(fp, size):
|
| 534 |
+
"""
|
| 535 |
+
Reads large blocks in a safe way. Unlike fp.read(n), this function
|
| 536 |
+
doesn't trust the user. If the requested size is larger than
|
| 537 |
+
SAFEBLOCK, the file is read block by block.
|
| 538 |
+
|
| 539 |
+
:param fp: File handle. Must implement a <b>read</b> method.
|
| 540 |
+
:param size: Number of bytes to read.
|
| 541 |
+
:returns: A string containing <i>size</i> bytes of data.
|
| 542 |
+
|
| 543 |
+
Raises an OSError if the file is truncated and the read cannot be completed
|
| 544 |
+
|
| 545 |
+
"""
|
| 546 |
+
if size <= 0:
|
| 547 |
+
return b""
|
| 548 |
+
if size <= SAFEBLOCK:
|
| 549 |
+
data = fp.read(size)
|
| 550 |
+
if len(data) < size:
|
| 551 |
+
raise OSError("Truncated File Read")
|
| 552 |
+
return data
|
| 553 |
+
data = []
|
| 554 |
+
remaining_size = size
|
| 555 |
+
while remaining_size > 0:
|
| 556 |
+
block = fp.read(min(remaining_size, SAFEBLOCK))
|
| 557 |
+
if not block:
|
| 558 |
+
break
|
| 559 |
+
data.append(block)
|
| 560 |
+
remaining_size -= len(block)
|
| 561 |
+
if sum(len(d) for d in data) < size:
|
| 562 |
+
raise OSError("Truncated File Read")
|
| 563 |
+
return b"".join(data)
|
| 564 |
+
|
| 565 |
+
|
| 566 |
+
class PyCodecState:
|
| 567 |
+
def __init__(self):
|
| 568 |
+
self.xsize = 0
|
| 569 |
+
self.ysize = 0
|
| 570 |
+
self.xoff = 0
|
| 571 |
+
self.yoff = 0
|
| 572 |
+
|
| 573 |
+
def extents(self):
|
| 574 |
+
return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
class PyCodec:
|
| 578 |
+
def __init__(self, mode, *args):
|
| 579 |
+
self.im = None
|
| 580 |
+
self.state = PyCodecState()
|
| 581 |
+
self.fd = None
|
| 582 |
+
self.mode = mode
|
| 583 |
+
self.init(args)
|
| 584 |
+
|
| 585 |
+
def init(self, args):
|
| 586 |
+
"""
|
| 587 |
+
Override to perform codec specific initialization
|
| 588 |
+
|
| 589 |
+
:param args: Array of args items from the tile entry
|
| 590 |
+
:returns: None
|
| 591 |
+
"""
|
| 592 |
+
self.args = args
|
| 593 |
+
|
| 594 |
+
def cleanup(self):
|
| 595 |
+
"""
|
| 596 |
+
Override to perform codec specific cleanup
|
| 597 |
+
|
| 598 |
+
:returns: None
|
| 599 |
+
"""
|
| 600 |
+
pass
|
| 601 |
+
|
| 602 |
+
def setfd(self, fd):
|
| 603 |
+
"""
|
| 604 |
+
Called from ImageFile to set the Python file-like object
|
| 605 |
+
|
| 606 |
+
:param fd: A Python file-like object
|
| 607 |
+
:returns: None
|
| 608 |
+
"""
|
| 609 |
+
self.fd = fd
|
| 610 |
+
|
| 611 |
+
def setimage(self, im, extents=None):
|
| 612 |
+
"""
|
| 613 |
+
Called from ImageFile to set the core output image for the codec
|
| 614 |
+
|
| 615 |
+
:param im: A core image object
|
| 616 |
+
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
|
| 617 |
+
for this tile
|
| 618 |
+
:returns: None
|
| 619 |
+
"""
|
| 620 |
+
|
| 621 |
+
# following c code
|
| 622 |
+
self.im = im
|
| 623 |
+
|
| 624 |
+
if extents:
|
| 625 |
+
(x0, y0, x1, y1) = extents
|
| 626 |
+
else:
|
| 627 |
+
(x0, y0, x1, y1) = (0, 0, 0, 0)
|
| 628 |
+
|
| 629 |
+
if x0 == 0 and x1 == 0:
|
| 630 |
+
self.state.xsize, self.state.ysize = self.im.size
|
| 631 |
+
else:
|
| 632 |
+
self.state.xoff = x0
|
| 633 |
+
self.state.yoff = y0
|
| 634 |
+
self.state.xsize = x1 - x0
|
| 635 |
+
self.state.ysize = y1 - y0
|
| 636 |
+
|
| 637 |
+
if self.state.xsize <= 0 or self.state.ysize <= 0:
|
| 638 |
+
raise ValueError("Size cannot be negative")
|
| 639 |
+
|
| 640 |
+
if (
|
| 641 |
+
self.state.xsize + self.state.xoff > self.im.size[0]
|
| 642 |
+
or self.state.ysize + self.state.yoff > self.im.size[1]
|
| 643 |
+
):
|
| 644 |
+
raise ValueError("Tile cannot extend outside image")
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
class PyDecoder(PyCodec):
|
| 648 |
+
"""
|
| 649 |
+
Python implementation of a format decoder. Override this class and
|
| 650 |
+
add the decoding logic in the :meth:`decode` method.
|
| 651 |
+
|
| 652 |
+
See :ref:`Writing Your Own File Codec in Python<file-codecs-py>`
|
| 653 |
+
"""
|
| 654 |
+
|
| 655 |
+
_pulls_fd = False
|
| 656 |
+
|
| 657 |
+
@property
|
| 658 |
+
def pulls_fd(self):
|
| 659 |
+
return self._pulls_fd
|
| 660 |
+
|
| 661 |
+
def decode(self, buffer):
|
| 662 |
+
"""
|
| 663 |
+
Override to perform the decoding process.
|
| 664 |
+
|
| 665 |
+
:param buffer: A bytes object with the data to be decoded.
|
| 666 |
+
:returns: A tuple of ``(bytes consumed, errcode)``.
|
| 667 |
+
If finished with decoding return -1 for the bytes consumed.
|
| 668 |
+
Err codes are from :data:`.ImageFile.ERRORS`.
|
| 669 |
+
"""
|
| 670 |
+
raise NotImplementedError()
|
| 671 |
+
|
| 672 |
+
def set_as_raw(self, data, rawmode=None):
|
| 673 |
+
"""
|
| 674 |
+
Convenience method to set the internal image from a stream of raw data
|
| 675 |
+
|
| 676 |
+
:param data: Bytes to be set
|
| 677 |
+
:param rawmode: The rawmode to be used for the decoder.
|
| 678 |
+
If not specified, it will default to the mode of the image
|
| 679 |
+
:returns: None
|
| 680 |
+
"""
|
| 681 |
+
|
| 682 |
+
if not rawmode:
|
| 683 |
+
rawmode = self.mode
|
| 684 |
+
d = Image._getdecoder(self.mode, "raw", (rawmode))
|
| 685 |
+
d.setimage(self.im, self.state.extents())
|
| 686 |
+
s = d.decode(data)
|
| 687 |
+
|
| 688 |
+
if s[0] >= 0:
|
| 689 |
+
raise ValueError("not enough image data")
|
| 690 |
+
if s[1] != 0:
|
| 691 |
+
raise ValueError("cannot decode image data")
|
| 692 |
+
|
| 693 |
+
|
| 694 |
+
class PyEncoder(PyCodec):
|
| 695 |
+
"""
|
| 696 |
+
Python implementation of a format encoder. Override this class and
|
| 697 |
+
add the decoding logic in the :meth:`encode` method.
|
| 698 |
+
|
| 699 |
+
See :ref:`Writing Your Own File Codec in Python<file-codecs-py>`
|
| 700 |
+
"""
|
| 701 |
+
|
| 702 |
+
_pushes_fd = False
|
| 703 |
+
|
| 704 |
+
@property
|
| 705 |
+
def pushes_fd(self):
|
| 706 |
+
return self._pushes_fd
|
| 707 |
+
|
| 708 |
+
def encode(self, bufsize):
|
| 709 |
+
"""
|
| 710 |
+
Override to perform the encoding process.
|
| 711 |
+
|
| 712 |
+
:param bufsize: Buffer size.
|
| 713 |
+
:returns: A tuple of ``(bytes encoded, errcode, bytes)``.
|
| 714 |
+
If finished with encoding return 1 for the error code.
|
| 715 |
+
Err codes are from :data:`.ImageFile.ERRORS`.
|
| 716 |
+
"""
|
| 717 |
+
raise NotImplementedError()
|
| 718 |
+
|
| 719 |
+
def encode_to_pyfd(self):
|
| 720 |
+
"""
|
| 721 |
+
If ``pushes_fd`` is ``True``, then this method will be used,
|
| 722 |
+
and ``encode()`` will only be called once.
|
| 723 |
+
|
| 724 |
+
:returns: A tuple of ``(bytes consumed, errcode)``.
|
| 725 |
+
Err codes are from :data:`.ImageFile.ERRORS`.
|
| 726 |
+
"""
|
| 727 |
+
if not self.pushes_fd:
|
| 728 |
+
return 0, -8 # bad configuration
|
| 729 |
+
bytes_consumed, errcode, data = self.encode(0)
|
| 730 |
+
if data:
|
| 731 |
+
self.fd.write(data)
|
| 732 |
+
return bytes_consumed, errcode
|
| 733 |
+
|
| 734 |
+
def encode_to_file(self, fh, bufsize):
|
| 735 |
+
"""
|
| 736 |
+
:param fh: File handle.
|
| 737 |
+
:param bufsize: Buffer size.
|
| 738 |
+
|
| 739 |
+
:returns: If finished successfully, return 0.
|
| 740 |
+
Otherwise, return an error code. Err codes are from
|
| 741 |
+
:data:`.ImageFile.ERRORS`.
|
| 742 |
+
"""
|
| 743 |
+
errcode = 0
|
| 744 |
+
while errcode == 0:
|
| 745 |
+
status, errcode, buf = self.encode(bufsize)
|
| 746 |
+
if status > 0:
|
| 747 |
+
fh.write(buf[status:])
|
| 748 |
+
return errcode
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageFont.py
ADDED
|
@@ -0,0 +1,1083 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# PIL raster font management
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1996-08-07 fl created (experimental)
|
| 9 |
+
# 1997-08-25 fl minor adjustments to handle fonts from pilfont 0.3
|
| 10 |
+
# 1999-02-06 fl rewrote most font management stuff in C
|
| 11 |
+
# 1999-03-17 fl take pth files into account in load_path (from Richard Jones)
|
| 12 |
+
# 2001-02-17 fl added freetype support
|
| 13 |
+
# 2001-05-09 fl added TransposedFont wrapper class
|
| 14 |
+
# 2002-03-04 fl make sure we have a "L" or "1" font
|
| 15 |
+
# 2002-12-04 fl skip non-directory entries in the system path
|
| 16 |
+
# 2003-04-29 fl add embedded default font
|
| 17 |
+
# 2003-09-27 fl added support for truetype charmap encodings
|
| 18 |
+
#
|
| 19 |
+
# Todo:
|
| 20 |
+
# Adapt to PILFONT2 format (16-bit fonts, compressed, single file)
|
| 21 |
+
#
|
| 22 |
+
# Copyright (c) 1997-2003 by Secret Labs AB
|
| 23 |
+
# Copyright (c) 1996-2003 by Fredrik Lundh
|
| 24 |
+
#
|
| 25 |
+
# See the README file for information on usage and redistribution.
|
| 26 |
+
#
|
| 27 |
+
|
| 28 |
+
import base64
|
| 29 |
+
import os
|
| 30 |
+
import sys
|
| 31 |
+
import warnings
|
| 32 |
+
from enum import IntEnum
|
| 33 |
+
from io import BytesIO
|
| 34 |
+
|
| 35 |
+
from . import Image
|
| 36 |
+
from ._util import isDirectory, isPath
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class Layout(IntEnum):
|
| 40 |
+
BASIC = 0
|
| 41 |
+
RAQM = 1
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def __getattr__(name):
|
| 45 |
+
deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
|
| 46 |
+
for enum, prefix in {Layout: "LAYOUT_"}.items():
|
| 47 |
+
if name.startswith(prefix):
|
| 48 |
+
name = name[len(prefix) :]
|
| 49 |
+
if name in enum.__members__:
|
| 50 |
+
warnings.warn(
|
| 51 |
+
prefix
|
| 52 |
+
+ name
|
| 53 |
+
+ " is "
|
| 54 |
+
+ deprecated
|
| 55 |
+
+ "Use "
|
| 56 |
+
+ enum.__name__
|
| 57 |
+
+ "."
|
| 58 |
+
+ name
|
| 59 |
+
+ " instead.",
|
| 60 |
+
DeprecationWarning,
|
| 61 |
+
stacklevel=2,
|
| 62 |
+
)
|
| 63 |
+
return enum[name]
|
| 64 |
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class _imagingft_not_installed:
|
| 68 |
+
# module placeholder
|
| 69 |
+
def __getattr__(self, id):
|
| 70 |
+
raise ImportError("The _imagingft C module is not installed")
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
try:
|
| 74 |
+
from . import _imagingft as core
|
| 75 |
+
except ImportError:
|
| 76 |
+
core = _imagingft_not_installed()
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# FIXME: add support for pilfont2 format (see FontFile.py)
|
| 80 |
+
|
| 81 |
+
# --------------------------------------------------------------------
|
| 82 |
+
# Font metrics format:
|
| 83 |
+
# "PILfont" LF
|
| 84 |
+
# fontdescriptor LF
|
| 85 |
+
# (optional) key=value... LF
|
| 86 |
+
# "DATA" LF
|
| 87 |
+
# binary data: 256*10*2 bytes (dx, dy, dstbox, srcbox)
|
| 88 |
+
#
|
| 89 |
+
# To place a character, cut out srcbox and paste at dstbox,
|
| 90 |
+
# relative to the character position. Then move the character
|
| 91 |
+
# position according to dx, dy.
|
| 92 |
+
# --------------------------------------------------------------------
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class ImageFont:
|
| 96 |
+
"PIL font wrapper"
|
| 97 |
+
|
| 98 |
+
def _load_pilfont(self, filename):
|
| 99 |
+
|
| 100 |
+
with open(filename, "rb") as fp:
|
| 101 |
+
image = None
|
| 102 |
+
for ext in (".png", ".gif", ".pbm"):
|
| 103 |
+
if image:
|
| 104 |
+
image.close()
|
| 105 |
+
try:
|
| 106 |
+
fullname = os.path.splitext(filename)[0] + ext
|
| 107 |
+
image = Image.open(fullname)
|
| 108 |
+
except Exception:
|
| 109 |
+
pass
|
| 110 |
+
else:
|
| 111 |
+
if image and image.mode in ("1", "L"):
|
| 112 |
+
break
|
| 113 |
+
else:
|
| 114 |
+
if image:
|
| 115 |
+
image.close()
|
| 116 |
+
raise OSError("cannot find glyph data file")
|
| 117 |
+
|
| 118 |
+
self.file = fullname
|
| 119 |
+
|
| 120 |
+
self._load_pilfont_data(fp, image)
|
| 121 |
+
image.close()
|
| 122 |
+
|
| 123 |
+
def _load_pilfont_data(self, file, image):
|
| 124 |
+
|
| 125 |
+
# read PILfont header
|
| 126 |
+
if file.readline() != b"PILfont\n":
|
| 127 |
+
raise SyntaxError("Not a PILfont file")
|
| 128 |
+
file.readline().split(b";")
|
| 129 |
+
self.info = [] # FIXME: should be a dictionary
|
| 130 |
+
while True:
|
| 131 |
+
s = file.readline()
|
| 132 |
+
if not s or s == b"DATA\n":
|
| 133 |
+
break
|
| 134 |
+
self.info.append(s)
|
| 135 |
+
|
| 136 |
+
# read PILfont metrics
|
| 137 |
+
data = file.read(256 * 20)
|
| 138 |
+
|
| 139 |
+
# check image
|
| 140 |
+
if image.mode not in ("1", "L"):
|
| 141 |
+
raise TypeError("invalid font image mode")
|
| 142 |
+
|
| 143 |
+
image.load()
|
| 144 |
+
|
| 145 |
+
self.font = Image.core.font(image.im, data)
|
| 146 |
+
|
| 147 |
+
def getsize(self, text, *args, **kwargs):
|
| 148 |
+
"""
|
| 149 |
+
Returns width and height (in pixels) of given text.
|
| 150 |
+
|
| 151 |
+
:param text: Text to measure.
|
| 152 |
+
|
| 153 |
+
:return: (width, height)
|
| 154 |
+
"""
|
| 155 |
+
return self.font.getsize(text)
|
| 156 |
+
|
| 157 |
+
def getmask(self, text, mode="", *args, **kwargs):
|
| 158 |
+
"""
|
| 159 |
+
Create a bitmap for the text.
|
| 160 |
+
|
| 161 |
+
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
|
| 162 |
+
maximum value of 255. Otherwise, it should have mode ``1``.
|
| 163 |
+
|
| 164 |
+
:param text: Text to render.
|
| 165 |
+
:param mode: Used by some graphics drivers to indicate what mode the
|
| 166 |
+
driver prefers; if empty, the renderer may return either
|
| 167 |
+
mode. Note that the mode is always a string, to simplify
|
| 168 |
+
C-level implementations.
|
| 169 |
+
|
| 170 |
+
.. versionadded:: 1.1.5
|
| 171 |
+
|
| 172 |
+
:return: An internal PIL storage memory instance as defined by the
|
| 173 |
+
:py:mod:`PIL.Image.core` interface module.
|
| 174 |
+
"""
|
| 175 |
+
return self.font.getmask(text, mode)
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
##
|
| 179 |
+
# Wrapper for FreeType fonts. Application code should use the
|
| 180 |
+
# <b>truetype</b> factory function to create font objects.
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
class FreeTypeFont:
|
| 184 |
+
"FreeType font wrapper (requires _imagingft service)"
|
| 185 |
+
|
| 186 |
+
def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
|
| 187 |
+
# FIXME: use service provider instead
|
| 188 |
+
|
| 189 |
+
self.path = font
|
| 190 |
+
self.size = size
|
| 191 |
+
self.index = index
|
| 192 |
+
self.encoding = encoding
|
| 193 |
+
|
| 194 |
+
if layout_engine not in (Layout.BASIC, Layout.RAQM):
|
| 195 |
+
layout_engine = Layout.BASIC
|
| 196 |
+
if core.HAVE_RAQM:
|
| 197 |
+
layout_engine = Layout.RAQM
|
| 198 |
+
elif layout_engine == Layout.RAQM and not core.HAVE_RAQM:
|
| 199 |
+
import warnings
|
| 200 |
+
|
| 201 |
+
warnings.warn(
|
| 202 |
+
"Raqm layout was requested, but Raqm is not available. "
|
| 203 |
+
"Falling back to basic layout."
|
| 204 |
+
)
|
| 205 |
+
layout_engine = Layout.BASIC
|
| 206 |
+
|
| 207 |
+
self.layout_engine = layout_engine
|
| 208 |
+
|
| 209 |
+
def load_from_bytes(f):
|
| 210 |
+
self.font_bytes = f.read()
|
| 211 |
+
self.font = core.getfont(
|
| 212 |
+
"", size, index, encoding, self.font_bytes, layout_engine
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
if isPath(font):
|
| 216 |
+
if sys.platform == "win32":
|
| 217 |
+
font_bytes_path = font if isinstance(font, bytes) else font.encode()
|
| 218 |
+
try:
|
| 219 |
+
font_bytes_path.decode("ascii")
|
| 220 |
+
except UnicodeDecodeError:
|
| 221 |
+
# FreeType cannot load fonts with non-ASCII characters on Windows
|
| 222 |
+
# So load it into memory first
|
| 223 |
+
with open(font, "rb") as f:
|
| 224 |
+
load_from_bytes(f)
|
| 225 |
+
return
|
| 226 |
+
self.font = core.getfont(
|
| 227 |
+
font, size, index, encoding, layout_engine=layout_engine
|
| 228 |
+
)
|
| 229 |
+
else:
|
| 230 |
+
load_from_bytes(font)
|
| 231 |
+
|
| 232 |
+
def __getstate__(self):
|
| 233 |
+
return [self.path, self.size, self.index, self.encoding, self.layout_engine]
|
| 234 |
+
|
| 235 |
+
def __setstate__(self, state):
|
| 236 |
+
path, size, index, encoding, layout_engine = state
|
| 237 |
+
self.__init__(path, size, index, encoding, layout_engine)
|
| 238 |
+
|
| 239 |
+
def _multiline_split(self, text):
|
| 240 |
+
split_character = "\n" if isinstance(text, str) else b"\n"
|
| 241 |
+
return text.split(split_character)
|
| 242 |
+
|
| 243 |
+
def getname(self):
|
| 244 |
+
"""
|
| 245 |
+
:return: A tuple of the font family (e.g. Helvetica) and the font style
|
| 246 |
+
(e.g. Bold)
|
| 247 |
+
"""
|
| 248 |
+
return self.font.family, self.font.style
|
| 249 |
+
|
| 250 |
+
def getmetrics(self):
|
| 251 |
+
"""
|
| 252 |
+
:return: A tuple of the font ascent (the distance from the baseline to
|
| 253 |
+
the highest outline point) and descent (the distance from the
|
| 254 |
+
baseline to the lowest outline point, a negative value)
|
| 255 |
+
"""
|
| 256 |
+
return self.font.ascent, self.font.descent
|
| 257 |
+
|
| 258 |
+
def getlength(self, text, mode="", direction=None, features=None, language=None):
|
| 259 |
+
"""
|
| 260 |
+
Returns length (in pixels with 1/64 precision) of given text when rendered
|
| 261 |
+
in font with provided direction, features, and language.
|
| 262 |
+
|
| 263 |
+
This is the amount by which following text should be offset.
|
| 264 |
+
Text bounding box may extend past the length in some fonts,
|
| 265 |
+
e.g. when using italics or accents.
|
| 266 |
+
|
| 267 |
+
The result is returned as a float; it is a whole number if using basic layout.
|
| 268 |
+
|
| 269 |
+
Note that the sum of two lengths may not equal the length of a concatenated
|
| 270 |
+
string due to kerning. If you need to adjust for kerning, include the following
|
| 271 |
+
character and subtract its length.
|
| 272 |
+
|
| 273 |
+
For example, instead of
|
| 274 |
+
|
| 275 |
+
.. code-block:: python
|
| 276 |
+
|
| 277 |
+
hello = font.getlength("Hello")
|
| 278 |
+
world = font.getlength("World")
|
| 279 |
+
hello_world = hello + world # not adjusted for kerning
|
| 280 |
+
assert hello_world == font.getlength("HelloWorld") # may fail
|
| 281 |
+
|
| 282 |
+
use
|
| 283 |
+
|
| 284 |
+
.. code-block:: python
|
| 285 |
+
|
| 286 |
+
hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning
|
| 287 |
+
world = font.getlength("World")
|
| 288 |
+
hello_world = hello + world # adjusted for kerning
|
| 289 |
+
assert hello_world == font.getlength("HelloWorld") # True
|
| 290 |
+
|
| 291 |
+
or disable kerning with (requires libraqm)
|
| 292 |
+
|
| 293 |
+
.. code-block:: python
|
| 294 |
+
|
| 295 |
+
hello = draw.textlength("Hello", font, features=["-kern"])
|
| 296 |
+
world = draw.textlength("World", font, features=["-kern"])
|
| 297 |
+
hello_world = hello + world # kerning is disabled, no need to adjust
|
| 298 |
+
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"])
|
| 299 |
+
|
| 300 |
+
.. versionadded:: 8.0.0
|
| 301 |
+
|
| 302 |
+
:param text: Text to measure.
|
| 303 |
+
:param mode: Used by some graphics drivers to indicate what mode the
|
| 304 |
+
driver prefers; if empty, the renderer may return either
|
| 305 |
+
mode. Note that the mode is always a string, to simplify
|
| 306 |
+
C-level implementations.
|
| 307 |
+
|
| 308 |
+
:param direction: Direction of the text. It can be 'rtl' (right to
|
| 309 |
+
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
| 310 |
+
Requires libraqm.
|
| 311 |
+
|
| 312 |
+
:param features: A list of OpenType font features to be used during text
|
| 313 |
+
layout. This is usually used to turn on optional
|
| 314 |
+
font features that are not enabled by default,
|
| 315 |
+
for example 'dlig' or 'ss01', but can be also
|
| 316 |
+
used to turn off default font features for
|
| 317 |
+
example '-liga' to disable ligatures or '-kern'
|
| 318 |
+
to disable kerning. To get all supported
|
| 319 |
+
features, see
|
| 320 |
+
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
|
| 321 |
+
Requires libraqm.
|
| 322 |
+
|
| 323 |
+
:param language: Language of the text. Different languages may use
|
| 324 |
+
different glyph shapes or ligatures. This parameter tells
|
| 325 |
+
the font which language the text is in, and to apply the
|
| 326 |
+
correct substitutions as appropriate, if available.
|
| 327 |
+
It should be a `BCP 47 language code
|
| 328 |
+
<https://www.w3.org/International/articles/language-tags/>`_
|
| 329 |
+
Requires libraqm.
|
| 330 |
+
|
| 331 |
+
:return: Width for horizontal, height for vertical text.
|
| 332 |
+
"""
|
| 333 |
+
return self.font.getlength(text, mode, direction, features, language) / 64
|
| 334 |
+
|
| 335 |
+
def getbbox(
|
| 336 |
+
self,
|
| 337 |
+
text,
|
| 338 |
+
mode="",
|
| 339 |
+
direction=None,
|
| 340 |
+
features=None,
|
| 341 |
+
language=None,
|
| 342 |
+
stroke_width=0,
|
| 343 |
+
anchor=None,
|
| 344 |
+
):
|
| 345 |
+
"""
|
| 346 |
+
Returns bounding box (in pixels) of given text relative to given anchor
|
| 347 |
+
when rendered in font with provided direction, features, and language.
|
| 348 |
+
|
| 349 |
+
Use :py:meth:`getlength()` to get the offset of following text with
|
| 350 |
+
1/64 pixel precision. The bounding box includes extra margins for
|
| 351 |
+
some fonts, e.g. italics or accents.
|
| 352 |
+
|
| 353 |
+
.. versionadded:: 8.0.0
|
| 354 |
+
|
| 355 |
+
:param text: Text to render.
|
| 356 |
+
:param mode: Used by some graphics drivers to indicate what mode the
|
| 357 |
+
driver prefers; if empty, the renderer may return either
|
| 358 |
+
mode. Note that the mode is always a string, to simplify
|
| 359 |
+
C-level implementations.
|
| 360 |
+
|
| 361 |
+
:param direction: Direction of the text. It can be 'rtl' (right to
|
| 362 |
+
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
| 363 |
+
Requires libraqm.
|
| 364 |
+
|
| 365 |
+
:param features: A list of OpenType font features to be used during text
|
| 366 |
+
layout. This is usually used to turn on optional
|
| 367 |
+
font features that are not enabled by default,
|
| 368 |
+
for example 'dlig' or 'ss01', but can be also
|
| 369 |
+
used to turn off default font features for
|
| 370 |
+
example '-liga' to disable ligatures or '-kern'
|
| 371 |
+
to disable kerning. To get all supported
|
| 372 |
+
features, see
|
| 373 |
+
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
|
| 374 |
+
Requires libraqm.
|
| 375 |
+
|
| 376 |
+
:param language: Language of the text. Different languages may use
|
| 377 |
+
different glyph shapes or ligatures. This parameter tells
|
| 378 |
+
the font which language the text is in, and to apply the
|
| 379 |
+
correct substitutions as appropriate, if available.
|
| 380 |
+
It should be a `BCP 47 language code
|
| 381 |
+
<https://www.w3.org/International/articles/language-tags/>`_
|
| 382 |
+
Requires libraqm.
|
| 383 |
+
|
| 384 |
+
:param stroke_width: The width of the text stroke.
|
| 385 |
+
|
| 386 |
+
:param anchor: The text anchor alignment. Determines the relative location of
|
| 387 |
+
the anchor to the text. The default alignment is top left.
|
| 388 |
+
See :ref:`text-anchors` for valid values.
|
| 389 |
+
|
| 390 |
+
:return: ``(left, top, right, bottom)`` bounding box
|
| 391 |
+
"""
|
| 392 |
+
size, offset = self.font.getsize(
|
| 393 |
+
text, mode, direction, features, language, anchor
|
| 394 |
+
)
|
| 395 |
+
left, top = offset[0] - stroke_width, offset[1] - stroke_width
|
| 396 |
+
width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width
|
| 397 |
+
return left, top, left + width, top + height
|
| 398 |
+
|
| 399 |
+
def getsize(
|
| 400 |
+
self, text, direction=None, features=None, language=None, stroke_width=0
|
| 401 |
+
):
|
| 402 |
+
"""
|
| 403 |
+
Returns width and height (in pixels) of given text if rendered in font with
|
| 404 |
+
provided direction, features, and language.
|
| 405 |
+
|
| 406 |
+
Use :py:meth:`getlength()` to measure the offset of following text with
|
| 407 |
+
1/64 pixel precision.
|
| 408 |
+
Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor.
|
| 409 |
+
|
| 410 |
+
.. note:: For historical reasons this function measures text height from
|
| 411 |
+
the ascender line instead of the top, see :ref:`text-anchors`.
|
| 412 |
+
If you wish to measure text height from the top, it is recommended
|
| 413 |
+
to use the bottom value of :meth:`getbbox` with ``anchor='lt'`` instead.
|
| 414 |
+
|
| 415 |
+
:param text: Text to measure.
|
| 416 |
+
|
| 417 |
+
:param direction: Direction of the text. It can be 'rtl' (right to
|
| 418 |
+
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
| 419 |
+
Requires libraqm.
|
| 420 |
+
|
| 421 |
+
.. versionadded:: 4.2.0
|
| 422 |
+
|
| 423 |
+
:param features: A list of OpenType font features to be used during text
|
| 424 |
+
layout. This is usually used to turn on optional
|
| 425 |
+
font features that are not enabled by default,
|
| 426 |
+
for example 'dlig' or 'ss01', but can be also
|
| 427 |
+
used to turn off default font features for
|
| 428 |
+
example '-liga' to disable ligatures or '-kern'
|
| 429 |
+
to disable kerning. To get all supported
|
| 430 |
+
features, see
|
| 431 |
+
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
|
| 432 |
+
Requires libraqm.
|
| 433 |
+
|
| 434 |
+
.. versionadded:: 4.2.0
|
| 435 |
+
|
| 436 |
+
:param language: Language of the text. Different languages may use
|
| 437 |
+
different glyph shapes or ligatures. This parameter tells
|
| 438 |
+
the font which language the text is in, and to apply the
|
| 439 |
+
correct substitutions as appropriate, if available.
|
| 440 |
+
It should be a `BCP 47 language code
|
| 441 |
+
<https://www.w3.org/International/articles/language-tags/>`_
|
| 442 |
+
Requires libraqm.
|
| 443 |
+
|
| 444 |
+
.. versionadded:: 6.0.0
|
| 445 |
+
|
| 446 |
+
:param stroke_width: The width of the text stroke.
|
| 447 |
+
|
| 448 |
+
.. versionadded:: 6.2.0
|
| 449 |
+
|
| 450 |
+
:return: (width, height)
|
| 451 |
+
"""
|
| 452 |
+
# vertical offset is added for historical reasons
|
| 453 |
+
# see https://github.com/python-pillow/Pillow/pull/4910#discussion_r486682929
|
| 454 |
+
size, offset = self.font.getsize(text, "L", direction, features, language)
|
| 455 |
+
return (
|
| 456 |
+
size[0] + stroke_width * 2,
|
| 457 |
+
size[1] + stroke_width * 2 + offset[1],
|
| 458 |
+
)
|
| 459 |
+
|
| 460 |
+
def getsize_multiline(
|
| 461 |
+
self,
|
| 462 |
+
text,
|
| 463 |
+
direction=None,
|
| 464 |
+
spacing=4,
|
| 465 |
+
features=None,
|
| 466 |
+
language=None,
|
| 467 |
+
stroke_width=0,
|
| 468 |
+
):
|
| 469 |
+
"""
|
| 470 |
+
Returns width and height (in pixels) of given text if rendered in font
|
| 471 |
+
with provided direction, features, and language, while respecting
|
| 472 |
+
newline characters.
|
| 473 |
+
|
| 474 |
+
:param text: Text to measure.
|
| 475 |
+
|
| 476 |
+
:param direction: Direction of the text. It can be 'rtl' (right to
|
| 477 |
+
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
| 478 |
+
Requires libraqm.
|
| 479 |
+
|
| 480 |
+
:param spacing: The vertical gap between lines, defaulting to 4 pixels.
|
| 481 |
+
|
| 482 |
+
:param features: A list of OpenType font features to be used during text
|
| 483 |
+
layout. This is usually used to turn on optional
|
| 484 |
+
font features that are not enabled by default,
|
| 485 |
+
for example 'dlig' or 'ss01', but can be also
|
| 486 |
+
used to turn off default font features for
|
| 487 |
+
example '-liga' to disable ligatures or '-kern'
|
| 488 |
+
to disable kerning. To get all supported
|
| 489 |
+
features, see
|
| 490 |
+
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
|
| 491 |
+
Requires libraqm.
|
| 492 |
+
|
| 493 |
+
:param language: Language of the text. Different languages may use
|
| 494 |
+
different glyph shapes or ligatures. This parameter tells
|
| 495 |
+
the font which language the text is in, and to apply the
|
| 496 |
+
correct substitutions as appropriate, if available.
|
| 497 |
+
It should be a `BCP 47 language code
|
| 498 |
+
<https://www.w3.org/International/articles/language-tags/>`_
|
| 499 |
+
Requires libraqm.
|
| 500 |
+
|
| 501 |
+
.. versionadded:: 6.0.0
|
| 502 |
+
|
| 503 |
+
:param stroke_width: The width of the text stroke.
|
| 504 |
+
|
| 505 |
+
.. versionadded:: 6.2.0
|
| 506 |
+
|
| 507 |
+
:return: (width, height)
|
| 508 |
+
"""
|
| 509 |
+
max_width = 0
|
| 510 |
+
lines = self._multiline_split(text)
|
| 511 |
+
line_spacing = self.getsize("A", stroke_width=stroke_width)[1] + spacing
|
| 512 |
+
for line in lines:
|
| 513 |
+
line_width, line_height = self.getsize(
|
| 514 |
+
line, direction, features, language, stroke_width
|
| 515 |
+
)
|
| 516 |
+
max_width = max(max_width, line_width)
|
| 517 |
+
|
| 518 |
+
return max_width, len(lines) * line_spacing - spacing
|
| 519 |
+
|
| 520 |
+
def getoffset(self, text):
|
| 521 |
+
"""
|
| 522 |
+
Returns the offset of given text. This is the gap between the
|
| 523 |
+
starting coordinate and the first marking. Note that this gap is
|
| 524 |
+
included in the result of :py:func:`~PIL.ImageFont.FreeTypeFont.getsize`.
|
| 525 |
+
|
| 526 |
+
:param text: Text to measure.
|
| 527 |
+
|
| 528 |
+
:return: A tuple of the x and y offset
|
| 529 |
+
"""
|
| 530 |
+
return self.font.getsize(text)[1]
|
| 531 |
+
|
| 532 |
+
def getmask(
|
| 533 |
+
self,
|
| 534 |
+
text,
|
| 535 |
+
mode="",
|
| 536 |
+
direction=None,
|
| 537 |
+
features=None,
|
| 538 |
+
language=None,
|
| 539 |
+
stroke_width=0,
|
| 540 |
+
anchor=None,
|
| 541 |
+
ink=0,
|
| 542 |
+
):
|
| 543 |
+
"""
|
| 544 |
+
Create a bitmap for the text.
|
| 545 |
+
|
| 546 |
+
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
|
| 547 |
+
maximum value of 255. If the font has embedded color data, the bitmap
|
| 548 |
+
should have mode ``RGBA``. Otherwise, it should have mode ``1``.
|
| 549 |
+
|
| 550 |
+
:param text: Text to render.
|
| 551 |
+
:param mode: Used by some graphics drivers to indicate what mode the
|
| 552 |
+
driver prefers; if empty, the renderer may return either
|
| 553 |
+
mode. Note that the mode is always a string, to simplify
|
| 554 |
+
C-level implementations.
|
| 555 |
+
|
| 556 |
+
.. versionadded:: 1.1.5
|
| 557 |
+
|
| 558 |
+
:param direction: Direction of the text. It can be 'rtl' (right to
|
| 559 |
+
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
| 560 |
+
Requires libraqm.
|
| 561 |
+
|
| 562 |
+
.. versionadded:: 4.2.0
|
| 563 |
+
|
| 564 |
+
:param features: A list of OpenType font features to be used during text
|
| 565 |
+
layout. This is usually used to turn on optional
|
| 566 |
+
font features that are not enabled by default,
|
| 567 |
+
for example 'dlig' or 'ss01', but can be also
|
| 568 |
+
used to turn off default font features for
|
| 569 |
+
example '-liga' to disable ligatures or '-kern'
|
| 570 |
+
to disable kerning. To get all supported
|
| 571 |
+
features, see
|
| 572 |
+
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
|
| 573 |
+
Requires libraqm.
|
| 574 |
+
|
| 575 |
+
.. versionadded:: 4.2.0
|
| 576 |
+
|
| 577 |
+
:param language: Language of the text. Different languages may use
|
| 578 |
+
different glyph shapes or ligatures. This parameter tells
|
| 579 |
+
the font which language the text is in, and to apply the
|
| 580 |
+
correct substitutions as appropriate, if available.
|
| 581 |
+
It should be a `BCP 47 language code
|
| 582 |
+
<https://www.w3.org/International/articles/language-tags/>`_
|
| 583 |
+
Requires libraqm.
|
| 584 |
+
|
| 585 |
+
.. versionadded:: 6.0.0
|
| 586 |
+
|
| 587 |
+
:param stroke_width: The width of the text stroke.
|
| 588 |
+
|
| 589 |
+
.. versionadded:: 6.2.0
|
| 590 |
+
|
| 591 |
+
:param anchor: The text anchor alignment. Determines the relative location of
|
| 592 |
+
the anchor to the text. The default alignment is top left.
|
| 593 |
+
See :ref:`text-anchors` for valid values.
|
| 594 |
+
|
| 595 |
+
.. versionadded:: 8.0.0
|
| 596 |
+
|
| 597 |
+
:param ink: Foreground ink for rendering in RGBA mode.
|
| 598 |
+
|
| 599 |
+
.. versionadded:: 8.0.0
|
| 600 |
+
|
| 601 |
+
:return: An internal PIL storage memory instance as defined by the
|
| 602 |
+
:py:mod:`PIL.Image.core` interface module.
|
| 603 |
+
"""
|
| 604 |
+
return self.getmask2(
|
| 605 |
+
text,
|
| 606 |
+
mode,
|
| 607 |
+
direction=direction,
|
| 608 |
+
features=features,
|
| 609 |
+
language=language,
|
| 610 |
+
stroke_width=stroke_width,
|
| 611 |
+
anchor=anchor,
|
| 612 |
+
ink=ink,
|
| 613 |
+
)[0]
|
| 614 |
+
|
| 615 |
+
def getmask2(
|
| 616 |
+
self,
|
| 617 |
+
text,
|
| 618 |
+
mode="",
|
| 619 |
+
fill=Image.core.fill,
|
| 620 |
+
direction=None,
|
| 621 |
+
features=None,
|
| 622 |
+
language=None,
|
| 623 |
+
stroke_width=0,
|
| 624 |
+
anchor=None,
|
| 625 |
+
ink=0,
|
| 626 |
+
*args,
|
| 627 |
+
**kwargs,
|
| 628 |
+
):
|
| 629 |
+
"""
|
| 630 |
+
Create a bitmap for the text.
|
| 631 |
+
|
| 632 |
+
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
|
| 633 |
+
maximum value of 255. If the font has embedded color data, the bitmap
|
| 634 |
+
should have mode ``RGBA``. Otherwise, it should have mode ``1``.
|
| 635 |
+
|
| 636 |
+
:param text: Text to render.
|
| 637 |
+
:param mode: Used by some graphics drivers to indicate what mode the
|
| 638 |
+
driver prefers; if empty, the renderer may return either
|
| 639 |
+
mode. Note that the mode is always a string, to simplify
|
| 640 |
+
C-level implementations.
|
| 641 |
+
|
| 642 |
+
.. versionadded:: 1.1.5
|
| 643 |
+
|
| 644 |
+
:param direction: Direction of the text. It can be 'rtl' (right to
|
| 645 |
+
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
| 646 |
+
Requires libraqm.
|
| 647 |
+
|
| 648 |
+
.. versionadded:: 4.2.0
|
| 649 |
+
|
| 650 |
+
:param features: A list of OpenType font features to be used during text
|
| 651 |
+
layout. This is usually used to turn on optional
|
| 652 |
+
font features that are not enabled by default,
|
| 653 |
+
for example 'dlig' or 'ss01', but can be also
|
| 654 |
+
used to turn off default font features for
|
| 655 |
+
example '-liga' to disable ligatures or '-kern'
|
| 656 |
+
to disable kerning. To get all supported
|
| 657 |
+
features, see
|
| 658 |
+
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
|
| 659 |
+
Requires libraqm.
|
| 660 |
+
|
| 661 |
+
.. versionadded:: 4.2.0
|
| 662 |
+
|
| 663 |
+
:param language: Language of the text. Different languages may use
|
| 664 |
+
different glyph shapes or ligatures. This parameter tells
|
| 665 |
+
the font which language the text is in, and to apply the
|
| 666 |
+
correct substitutions as appropriate, if available.
|
| 667 |
+
It should be a `BCP 47 language code
|
| 668 |
+
<https://www.w3.org/International/articles/language-tags/>`_
|
| 669 |
+
Requires libraqm.
|
| 670 |
+
|
| 671 |
+
.. versionadded:: 6.0.0
|
| 672 |
+
|
| 673 |
+
:param stroke_width: The width of the text stroke.
|
| 674 |
+
|
| 675 |
+
.. versionadded:: 6.2.0
|
| 676 |
+
|
| 677 |
+
:param anchor: The text anchor alignment. Determines the relative location of
|
| 678 |
+
the anchor to the text. The default alignment is top left.
|
| 679 |
+
See :ref:`text-anchors` for valid values.
|
| 680 |
+
|
| 681 |
+
.. versionadded:: 8.0.0
|
| 682 |
+
|
| 683 |
+
:param ink: Foreground ink for rendering in RGBA mode.
|
| 684 |
+
|
| 685 |
+
.. versionadded:: 8.0.0
|
| 686 |
+
|
| 687 |
+
:return: A tuple of an internal PIL storage memory instance as defined by the
|
| 688 |
+
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
| 689 |
+
gap between the starting coordinate and the first marking
|
| 690 |
+
"""
|
| 691 |
+
size, offset = self.font.getsize(
|
| 692 |
+
text, mode, direction, features, language, anchor
|
| 693 |
+
)
|
| 694 |
+
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
|
| 695 |
+
offset = offset[0] - stroke_width, offset[1] - stroke_width
|
| 696 |
+
Image._decompression_bomb_check(size)
|
| 697 |
+
im = fill("RGBA" if mode == "RGBA" else "L", size, 0)
|
| 698 |
+
self.font.render(
|
| 699 |
+
text, im.id, mode, direction, features, language, stroke_width, ink
|
| 700 |
+
)
|
| 701 |
+
return im, offset
|
| 702 |
+
|
| 703 |
+
def font_variant(
|
| 704 |
+
self, font=None, size=None, index=None, encoding=None, layout_engine=None
|
| 705 |
+
):
|
| 706 |
+
"""
|
| 707 |
+
Create a copy of this FreeTypeFont object,
|
| 708 |
+
using any specified arguments to override the settings.
|
| 709 |
+
|
| 710 |
+
Parameters are identical to the parameters used to initialize this
|
| 711 |
+
object.
|
| 712 |
+
|
| 713 |
+
:return: A FreeTypeFont object.
|
| 714 |
+
"""
|
| 715 |
+
return FreeTypeFont(
|
| 716 |
+
font=self.path if font is None else font,
|
| 717 |
+
size=self.size if size is None else size,
|
| 718 |
+
index=self.index if index is None else index,
|
| 719 |
+
encoding=self.encoding if encoding is None else encoding,
|
| 720 |
+
layout_engine=layout_engine or self.layout_engine,
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
def get_variation_names(self):
|
| 724 |
+
"""
|
| 725 |
+
:returns: A list of the named styles in a variation font.
|
| 726 |
+
:exception OSError: If the font is not a variation font.
|
| 727 |
+
"""
|
| 728 |
+
try:
|
| 729 |
+
names = self.font.getvarnames()
|
| 730 |
+
except AttributeError as e:
|
| 731 |
+
raise NotImplementedError("FreeType 2.9.1 or greater is required") from e
|
| 732 |
+
return [name.replace(b"\x00", b"") for name in names]
|
| 733 |
+
|
| 734 |
+
def set_variation_by_name(self, name):
|
| 735 |
+
"""
|
| 736 |
+
:param name: The name of the style.
|
| 737 |
+
:exception OSError: If the font is not a variation font.
|
| 738 |
+
"""
|
| 739 |
+
names = self.get_variation_names()
|
| 740 |
+
if not isinstance(name, bytes):
|
| 741 |
+
name = name.encode()
|
| 742 |
+
index = names.index(name)
|
| 743 |
+
|
| 744 |
+
if index == getattr(self, "_last_variation_index", None):
|
| 745 |
+
# When the same name is set twice in a row,
|
| 746 |
+
# there is an 'unknown freetype error'
|
| 747 |
+
# https://savannah.nongnu.org/bugs/?56186
|
| 748 |
+
return
|
| 749 |
+
self._last_variation_index = index
|
| 750 |
+
|
| 751 |
+
self.font.setvarname(index)
|
| 752 |
+
|
| 753 |
+
def get_variation_axes(self):
|
| 754 |
+
"""
|
| 755 |
+
:returns: A list of the axes in a variation font.
|
| 756 |
+
:exception OSError: If the font is not a variation font.
|
| 757 |
+
"""
|
| 758 |
+
try:
|
| 759 |
+
axes = self.font.getvaraxes()
|
| 760 |
+
except AttributeError as e:
|
| 761 |
+
raise NotImplementedError("FreeType 2.9.1 or greater is required") from e
|
| 762 |
+
for axis in axes:
|
| 763 |
+
axis["name"] = axis["name"].replace(b"\x00", b"")
|
| 764 |
+
return axes
|
| 765 |
+
|
| 766 |
+
def set_variation_by_axes(self, axes):
|
| 767 |
+
"""
|
| 768 |
+
:param axes: A list of values for each axis.
|
| 769 |
+
:exception OSError: If the font is not a variation font.
|
| 770 |
+
"""
|
| 771 |
+
try:
|
| 772 |
+
self.font.setvaraxes(axes)
|
| 773 |
+
except AttributeError as e:
|
| 774 |
+
raise NotImplementedError("FreeType 2.9.1 or greater is required") from e
|
| 775 |
+
|
| 776 |
+
|
| 777 |
+
class TransposedFont:
|
| 778 |
+
"Wrapper for writing rotated or mirrored text"
|
| 779 |
+
|
| 780 |
+
def __init__(self, font, orientation=None):
|
| 781 |
+
"""
|
| 782 |
+
Wrapper that creates a transposed font from any existing font
|
| 783 |
+
object.
|
| 784 |
+
|
| 785 |
+
:param font: A font object.
|
| 786 |
+
:param orientation: An optional orientation. If given, this should
|
| 787 |
+
be one of Image.Transpose.FLIP_LEFT_RIGHT, Image.Transpose.FLIP_TOP_BOTTOM,
|
| 788 |
+
Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_180, or
|
| 789 |
+
Image.Transpose.ROTATE_270.
|
| 790 |
+
"""
|
| 791 |
+
self.font = font
|
| 792 |
+
self.orientation = orientation # any 'transpose' argument, or None
|
| 793 |
+
|
| 794 |
+
def getsize(self, text, *args, **kwargs):
|
| 795 |
+
w, h = self.font.getsize(text)
|
| 796 |
+
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
|
| 797 |
+
return h, w
|
| 798 |
+
return w, h
|
| 799 |
+
|
| 800 |
+
def getmask(self, text, mode="", *args, **kwargs):
|
| 801 |
+
im = self.font.getmask(text, mode, *args, **kwargs)
|
| 802 |
+
if self.orientation is not None:
|
| 803 |
+
return im.transpose(self.orientation)
|
| 804 |
+
return im
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
def load(filename):
|
| 808 |
+
"""
|
| 809 |
+
Load a font file. This function loads a font object from the given
|
| 810 |
+
bitmap font file, and returns the corresponding font object.
|
| 811 |
+
|
| 812 |
+
:param filename: Name of font file.
|
| 813 |
+
:return: A font object.
|
| 814 |
+
:exception OSError: If the file could not be read.
|
| 815 |
+
"""
|
| 816 |
+
f = ImageFont()
|
| 817 |
+
f._load_pilfont(filename)
|
| 818 |
+
return f
|
| 819 |
+
|
| 820 |
+
|
| 821 |
+
def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
| 822 |
+
"""
|
| 823 |
+
Load a TrueType or OpenType font from a file or file-like object,
|
| 824 |
+
and create a font object.
|
| 825 |
+
This function loads a font object from the given file or file-like
|
| 826 |
+
object, and creates a font object for a font of the given size.
|
| 827 |
+
|
| 828 |
+
Pillow uses FreeType to open font files. If you are opening many fonts
|
| 829 |
+
simultaneously on Windows, be aware that Windows limits the number of files
|
| 830 |
+
that can be open in C at once to 512. If you approach that limit, an
|
| 831 |
+
``OSError`` may be thrown, reporting that FreeType "cannot open resource".
|
| 832 |
+
|
| 833 |
+
This function requires the _imagingft service.
|
| 834 |
+
|
| 835 |
+
:param font: A filename or file-like object containing a TrueType font.
|
| 836 |
+
If the file is not found in this filename, the loader may also
|
| 837 |
+
search in other directories, such as the :file:`fonts/`
|
| 838 |
+
directory on Windows or :file:`/Library/Fonts/`,
|
| 839 |
+
:file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
|
| 840 |
+
macOS.
|
| 841 |
+
|
| 842 |
+
:param size: The requested size, in pixels.
|
| 843 |
+
:param index: Which font face to load (default is first available face).
|
| 844 |
+
:param encoding: Which font encoding to use (default is Unicode). Possible
|
| 845 |
+
encodings include (see the FreeType documentation for more
|
| 846 |
+
information):
|
| 847 |
+
|
| 848 |
+
* "unic" (Unicode)
|
| 849 |
+
* "symb" (Microsoft Symbol)
|
| 850 |
+
* "ADOB" (Adobe Standard)
|
| 851 |
+
* "ADBE" (Adobe Expert)
|
| 852 |
+
* "ADBC" (Adobe Custom)
|
| 853 |
+
* "armn" (Apple Roman)
|
| 854 |
+
* "sjis" (Shift JIS)
|
| 855 |
+
* "gb " (PRC)
|
| 856 |
+
* "big5"
|
| 857 |
+
* "wans" (Extended Wansung)
|
| 858 |
+
* "joha" (Johab)
|
| 859 |
+
* "lat1" (Latin-1)
|
| 860 |
+
|
| 861 |
+
This specifies the character set to use. It does not alter the
|
| 862 |
+
encoding of any text provided in subsequent operations.
|
| 863 |
+
:param layout_engine: Which layout engine to use, if available:
|
| 864 |
+
:data:`.ImageFont.Layout.BASIC` or :data:`.ImageFont.Layout.RAQM`.
|
| 865 |
+
|
| 866 |
+
You can check support for Raqm layout using
|
| 867 |
+
:py:func:`PIL.features.check_feature` with ``feature="raqm"``.
|
| 868 |
+
|
| 869 |
+
.. versionadded:: 4.2.0
|
| 870 |
+
:return: A font object.
|
| 871 |
+
:exception OSError: If the file could not be read.
|
| 872 |
+
"""
|
| 873 |
+
|
| 874 |
+
def freetype(font):
|
| 875 |
+
return FreeTypeFont(font, size, index, encoding, layout_engine)
|
| 876 |
+
|
| 877 |
+
try:
|
| 878 |
+
return freetype(font)
|
| 879 |
+
except OSError:
|
| 880 |
+
if not isPath(font):
|
| 881 |
+
raise
|
| 882 |
+
ttf_filename = os.path.basename(font)
|
| 883 |
+
|
| 884 |
+
dirs = []
|
| 885 |
+
if sys.platform == "win32":
|
| 886 |
+
# check the windows font repository
|
| 887 |
+
# NOTE: must use uppercase WINDIR, to work around bugs in
|
| 888 |
+
# 1.5.2's os.environ.get()
|
| 889 |
+
windir = os.environ.get("WINDIR")
|
| 890 |
+
if windir:
|
| 891 |
+
dirs.append(os.path.join(windir, "fonts"))
|
| 892 |
+
elif sys.platform in ("linux", "linux2"):
|
| 893 |
+
lindirs = os.environ.get("XDG_DATA_DIRS", "")
|
| 894 |
+
if not lindirs:
|
| 895 |
+
# According to the freedesktop spec, XDG_DATA_DIRS should
|
| 896 |
+
# default to /usr/share
|
| 897 |
+
lindirs = "/usr/share"
|
| 898 |
+
dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
|
| 899 |
+
elif sys.platform == "darwin":
|
| 900 |
+
dirs += [
|
| 901 |
+
"/Library/Fonts",
|
| 902 |
+
"/System/Library/Fonts",
|
| 903 |
+
os.path.expanduser("~/Library/Fonts"),
|
| 904 |
+
]
|
| 905 |
+
|
| 906 |
+
ext = os.path.splitext(ttf_filename)[1]
|
| 907 |
+
first_font_with_a_different_extension = None
|
| 908 |
+
for directory in dirs:
|
| 909 |
+
for walkroot, walkdir, walkfilenames in os.walk(directory):
|
| 910 |
+
for walkfilename in walkfilenames:
|
| 911 |
+
if ext and walkfilename == ttf_filename:
|
| 912 |
+
return freetype(os.path.join(walkroot, walkfilename))
|
| 913 |
+
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
|
| 914 |
+
fontpath = os.path.join(walkroot, walkfilename)
|
| 915 |
+
if os.path.splitext(fontpath)[1] == ".ttf":
|
| 916 |
+
return freetype(fontpath)
|
| 917 |
+
if not ext and first_font_with_a_different_extension is None:
|
| 918 |
+
first_font_with_a_different_extension = fontpath
|
| 919 |
+
if first_font_with_a_different_extension:
|
| 920 |
+
return freetype(first_font_with_a_different_extension)
|
| 921 |
+
raise
|
| 922 |
+
|
| 923 |
+
|
| 924 |
+
def load_path(filename):
|
| 925 |
+
"""
|
| 926 |
+
Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a
|
| 927 |
+
bitmap font along the Python path.
|
| 928 |
+
|
| 929 |
+
:param filename: Name of font file.
|
| 930 |
+
:return: A font object.
|
| 931 |
+
:exception OSError: If the file could not be read.
|
| 932 |
+
"""
|
| 933 |
+
for directory in sys.path:
|
| 934 |
+
if isDirectory(directory):
|
| 935 |
+
if not isinstance(filename, str):
|
| 936 |
+
filename = filename.decode("utf-8")
|
| 937 |
+
try:
|
| 938 |
+
return load(os.path.join(directory, filename))
|
| 939 |
+
except OSError:
|
| 940 |
+
pass
|
| 941 |
+
raise OSError("cannot find font file")
|
| 942 |
+
|
| 943 |
+
|
| 944 |
+
def load_default():
|
| 945 |
+
"""Load a "better than nothing" default font.
|
| 946 |
+
|
| 947 |
+
.. versionadded:: 1.1.4
|
| 948 |
+
|
| 949 |
+
:return: A font object.
|
| 950 |
+
"""
|
| 951 |
+
f = ImageFont()
|
| 952 |
+
f._load_pilfont_data(
|
| 953 |
+
# courB08
|
| 954 |
+
BytesIO(
|
| 955 |
+
base64.b64decode(
|
| 956 |
+
b"""
|
| 957 |
+
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 958 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 959 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 960 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 961 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 962 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 963 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 964 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 965 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 966 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 967 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 968 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
|
| 969 |
+
BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
|
| 970 |
+
AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
|
| 971 |
+
AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
|
| 972 |
+
ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
|
| 973 |
+
BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
|
| 974 |
+
//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
|
| 975 |
+
AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
|
| 976 |
+
AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
|
| 977 |
+
ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
|
| 978 |
+
AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
|
| 979 |
+
/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
|
| 980 |
+
AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
|
| 981 |
+
AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
|
| 982 |
+
AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
|
| 983 |
+
BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
|
| 984 |
+
AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
|
| 985 |
+
2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
|
| 986 |
+
AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
|
| 987 |
+
+gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
|
| 988 |
+
////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
|
| 989 |
+
BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
|
| 990 |
+
AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
|
| 991 |
+
AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
|
| 992 |
+
AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
|
| 993 |
+
BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
|
| 994 |
+
//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
|
| 995 |
+
AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
|
| 996 |
+
AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
|
| 997 |
+
mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
|
| 998 |
+
AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
|
| 999 |
+
AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
|
| 1000 |
+
AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
|
| 1001 |
+
Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
|
| 1002 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1003 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1004 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1005 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1006 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1007 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1008 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1009 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1010 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1011 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1012 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
| 1013 |
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
|
| 1014 |
+
//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
|
| 1015 |
+
AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
|
| 1016 |
+
AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
|
| 1017 |
+
DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
|
| 1018 |
+
AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
|
| 1019 |
+
+wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
|
| 1020 |
+
AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
|
| 1021 |
+
///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
|
| 1022 |
+
AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
|
| 1023 |
+
BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
|
| 1024 |
+
Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
|
| 1025 |
+
eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
|
| 1026 |
+
AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
|
| 1027 |
+
+gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
|
| 1028 |
+
////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
|
| 1029 |
+
BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
|
| 1030 |
+
AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
|
| 1031 |
+
AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
|
| 1032 |
+
Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
|
| 1033 |
+
Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
|
| 1034 |
+
//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
|
| 1035 |
+
AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
|
| 1036 |
+
AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
|
| 1037 |
+
LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
|
| 1038 |
+
AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
|
| 1039 |
+
AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
|
| 1040 |
+
AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
|
| 1041 |
+
AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
|
| 1042 |
+
AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
|
| 1043 |
+
EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
|
| 1044 |
+
AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
|
| 1045 |
+
pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
|
| 1046 |
+
AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
|
| 1047 |
+
+QAGAAIAzgAKANUAEw==
|
| 1048 |
+
"""
|
| 1049 |
+
)
|
| 1050 |
+
),
|
| 1051 |
+
Image.open(
|
| 1052 |
+
BytesIO(
|
| 1053 |
+
base64.b64decode(
|
| 1054 |
+
b"""
|
| 1055 |
+
iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
|
| 1056 |
+
Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
|
| 1057 |
+
M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
|
| 1058 |
+
LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
|
| 1059 |
+
IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
|
| 1060 |
+
Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
|
| 1061 |
+
NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
|
| 1062 |
+
in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
|
| 1063 |
+
SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
|
| 1064 |
+
AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
|
| 1065 |
+
y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
|
| 1066 |
+
ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
|
| 1067 |
+
lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
|
| 1068 |
+
/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
|
| 1069 |
+
AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
|
| 1070 |
+
c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
|
| 1071 |
+
/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
|
| 1072 |
+
pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
|
| 1073 |
+
oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
|
| 1074 |
+
evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
|
| 1075 |
+
AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
|
| 1076 |
+
Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
|
| 1077 |
+
w7IkEbzhVQAAAABJRU5ErkJggg==
|
| 1078 |
+
"""
|
| 1079 |
+
)
|
| 1080 |
+
)
|
| 1081 |
+
),
|
| 1082 |
+
)
|
| 1083 |
+
return f
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageGrab.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# screen grabber
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 2001-04-26 fl created
|
| 9 |
+
# 2001-09-17 fl use builtin driver, if present
|
| 10 |
+
# 2002-11-19 fl added grabclipboard support
|
| 11 |
+
#
|
| 12 |
+
# Copyright (c) 2001-2002 by Secret Labs AB
|
| 13 |
+
# Copyright (c) 2001-2002 by Fredrik Lundh
|
| 14 |
+
#
|
| 15 |
+
# See the README file for information on usage and redistribution.
|
| 16 |
+
#
|
| 17 |
+
|
| 18 |
+
import sys
|
| 19 |
+
|
| 20 |
+
from . import Image
|
| 21 |
+
|
| 22 |
+
if sys.platform == "darwin":
|
| 23 |
+
import os
|
| 24 |
+
import subprocess
|
| 25 |
+
import tempfile
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
|
| 29 |
+
if xdisplay is None:
|
| 30 |
+
if sys.platform == "darwin":
|
| 31 |
+
fh, filepath = tempfile.mkstemp(".png")
|
| 32 |
+
os.close(fh)
|
| 33 |
+
args = ["screencapture"]
|
| 34 |
+
if bbox:
|
| 35 |
+
left, top, right, bottom = bbox
|
| 36 |
+
args += ["-R", f"{left},{right},{right-left},{bottom-top}"]
|
| 37 |
+
subprocess.call(args + ["-x", filepath])
|
| 38 |
+
im = Image.open(filepath)
|
| 39 |
+
im.load()
|
| 40 |
+
os.unlink(filepath)
|
| 41 |
+
if bbox:
|
| 42 |
+
im_resized = im.resize((right - left, bottom - top))
|
| 43 |
+
im.close()
|
| 44 |
+
return im_resized
|
| 45 |
+
return im
|
| 46 |
+
elif sys.platform == "win32":
|
| 47 |
+
offset, size, data = Image.core.grabscreen_win32(
|
| 48 |
+
include_layered_windows, all_screens
|
| 49 |
+
)
|
| 50 |
+
im = Image.frombytes(
|
| 51 |
+
"RGB",
|
| 52 |
+
size,
|
| 53 |
+
data,
|
| 54 |
+
# RGB, 32-bit line padding, origin lower left corner
|
| 55 |
+
"raw",
|
| 56 |
+
"BGR",
|
| 57 |
+
(size[0] * 3 + 3) & -4,
|
| 58 |
+
-1,
|
| 59 |
+
)
|
| 60 |
+
if bbox:
|
| 61 |
+
x0, y0 = offset
|
| 62 |
+
left, top, right, bottom = bbox
|
| 63 |
+
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
| 64 |
+
return im
|
| 65 |
+
# use xdisplay=None for default display on non-win32/macOS systems
|
| 66 |
+
if not Image.core.HAVE_XCB:
|
| 67 |
+
raise OSError("Pillow was built without XCB support")
|
| 68 |
+
size, data = Image.core.grabscreen_x11(xdisplay)
|
| 69 |
+
im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1)
|
| 70 |
+
if bbox:
|
| 71 |
+
im = im.crop(bbox)
|
| 72 |
+
return im
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def grabclipboard():
|
| 76 |
+
if sys.platform == "darwin":
|
| 77 |
+
fh, filepath = tempfile.mkstemp(".jpg")
|
| 78 |
+
os.close(fh)
|
| 79 |
+
commands = [
|
| 80 |
+
'set theFile to (open for access POSIX file "'
|
| 81 |
+
+ filepath
|
| 82 |
+
+ '" with write permission)',
|
| 83 |
+
"try",
|
| 84 |
+
" write (the clipboard as JPEG picture) to theFile",
|
| 85 |
+
"end try",
|
| 86 |
+
"close access theFile",
|
| 87 |
+
]
|
| 88 |
+
script = ["osascript"]
|
| 89 |
+
for command in commands:
|
| 90 |
+
script += ["-e", command]
|
| 91 |
+
subprocess.call(script)
|
| 92 |
+
|
| 93 |
+
im = None
|
| 94 |
+
if os.stat(filepath).st_size != 0:
|
| 95 |
+
im = Image.open(filepath)
|
| 96 |
+
im.load()
|
| 97 |
+
os.unlink(filepath)
|
| 98 |
+
return im
|
| 99 |
+
elif sys.platform == "win32":
|
| 100 |
+
fmt, data = Image.core.grabclipboard_win32()
|
| 101 |
+
if fmt == "file": # CF_HDROP
|
| 102 |
+
import struct
|
| 103 |
+
|
| 104 |
+
o = struct.unpack_from("I", data)[0]
|
| 105 |
+
if data[16] != 0:
|
| 106 |
+
files = data[o:].decode("utf-16le").split("\0")
|
| 107 |
+
else:
|
| 108 |
+
files = data[o:].decode("mbcs").split("\0")
|
| 109 |
+
return files[: files.index("")]
|
| 110 |
+
if isinstance(data, bytes):
|
| 111 |
+
import io
|
| 112 |
+
|
| 113 |
+
data = io.BytesIO(data)
|
| 114 |
+
if fmt == "png":
|
| 115 |
+
from . import PngImagePlugin
|
| 116 |
+
|
| 117 |
+
return PngImagePlugin.PngImageFile(data)
|
| 118 |
+
elif fmt == "DIB":
|
| 119 |
+
from . import BmpImagePlugin
|
| 120 |
+
|
| 121 |
+
return BmpImagePlugin.DibImageFile(data)
|
| 122 |
+
return None
|
| 123 |
+
else:
|
| 124 |
+
raise NotImplementedError("ImageGrab.grabclipboard() is macOS and Windows only")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageMode.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# standard mode descriptors
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 2006-03-20 fl Added
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) 2006 by Secret Labs AB.
|
| 11 |
+
# Copyright (c) 2006 by Fredrik Lundh.
|
| 12 |
+
#
|
| 13 |
+
# See the README file for information on usage and redistribution.
|
| 14 |
+
#
|
| 15 |
+
|
| 16 |
+
import sys
|
| 17 |
+
|
| 18 |
+
# mode descriptor cache
|
| 19 |
+
_modes = None
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class ModeDescriptor:
|
| 23 |
+
"""Wrapper for mode strings."""
|
| 24 |
+
|
| 25 |
+
def __init__(self, mode, bands, basemode, basetype, typestr):
|
| 26 |
+
self.mode = mode
|
| 27 |
+
self.bands = bands
|
| 28 |
+
self.basemode = basemode
|
| 29 |
+
self.basetype = basetype
|
| 30 |
+
self.typestr = typestr
|
| 31 |
+
|
| 32 |
+
def __str__(self):
|
| 33 |
+
return self.mode
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def getmode(mode):
|
| 37 |
+
"""Gets a mode descriptor for the given mode."""
|
| 38 |
+
global _modes
|
| 39 |
+
if not _modes:
|
| 40 |
+
# initialize mode cache
|
| 41 |
+
modes = {}
|
| 42 |
+
endian = "<" if sys.byteorder == "little" else ">"
|
| 43 |
+
for m, (basemode, basetype, bands, typestr) in {
|
| 44 |
+
# core modes
|
| 45 |
+
# Bits need to be extended to bytes
|
| 46 |
+
"1": ("L", "L", ("1",), "|b1"),
|
| 47 |
+
"L": ("L", "L", ("L",), "|u1"),
|
| 48 |
+
"I": ("L", "I", ("I",), endian + "i4"),
|
| 49 |
+
"F": ("L", "F", ("F",), endian + "f4"),
|
| 50 |
+
"P": ("P", "L", ("P",), "|u1"),
|
| 51 |
+
"RGB": ("RGB", "L", ("R", "G", "B"), "|u1"),
|
| 52 |
+
"RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"),
|
| 53 |
+
"RGBA": ("RGB", "L", ("R", "G", "B", "A"), "|u1"),
|
| 54 |
+
"CMYK": ("RGB", "L", ("C", "M", "Y", "K"), "|u1"),
|
| 55 |
+
"YCbCr": ("RGB", "L", ("Y", "Cb", "Cr"), "|u1"),
|
| 56 |
+
# UNDONE - unsigned |u1i1i1
|
| 57 |
+
"LAB": ("RGB", "L", ("L", "A", "B"), "|u1"),
|
| 58 |
+
"HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
|
| 59 |
+
# extra experimental modes
|
| 60 |
+
"RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
|
| 61 |
+
"BGR;15": ("RGB", "L", ("B", "G", "R"), endian + "u2"),
|
| 62 |
+
"BGR;16": ("RGB", "L", ("B", "G", "R"), endian + "u2"),
|
| 63 |
+
"BGR;24": ("RGB", "L", ("B", "G", "R"), endian + "u3"),
|
| 64 |
+
"BGR;32": ("RGB", "L", ("B", "G", "R"), endian + "u4"),
|
| 65 |
+
"LA": ("L", "L", ("L", "A"), "|u1"),
|
| 66 |
+
"La": ("L", "L", ("L", "a"), "|u1"),
|
| 67 |
+
"PA": ("RGB", "L", ("P", "A"), "|u1"),
|
| 68 |
+
}.items():
|
| 69 |
+
modes[m] = ModeDescriptor(m, bands, basemode, basetype, typestr)
|
| 70 |
+
# mapping modes
|
| 71 |
+
for i16mode, typestr in {
|
| 72 |
+
# I;16 == I;16L, and I;32 == I;32L
|
| 73 |
+
"I;16": "<u2",
|
| 74 |
+
"I;16S": "<i2",
|
| 75 |
+
"I;16L": "<u2",
|
| 76 |
+
"I;16LS": "<i2",
|
| 77 |
+
"I;16B": ">u2",
|
| 78 |
+
"I;16BS": ">i2",
|
| 79 |
+
"I;16N": endian + "u2",
|
| 80 |
+
"I;16NS": endian + "i2",
|
| 81 |
+
"I;32": "<u4",
|
| 82 |
+
"I;32B": ">u4",
|
| 83 |
+
"I;32L": "<u4",
|
| 84 |
+
"I;32S": "<i4",
|
| 85 |
+
"I;32BS": ">i4",
|
| 86 |
+
"I;32LS": "<i4",
|
| 87 |
+
}.items():
|
| 88 |
+
modes[i16mode] = ModeDescriptor(i16mode, ("I",), "L", "L", typestr)
|
| 89 |
+
# set global mode cache atomically
|
| 90 |
+
_modes = modes
|
| 91 |
+
return _modes[mode]
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImagePalette.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# image palette object
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1996-03-11 fl Rewritten.
|
| 9 |
+
# 1997-01-03 fl Up and running.
|
| 10 |
+
# 1997-08-23 fl Added load hack
|
| 11 |
+
# 2001-04-16 fl Fixed randint shadow bug in random()
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) 1997-2001 by Secret Labs AB
|
| 14 |
+
# Copyright (c) 1996-1997 by Fredrik Lundh
|
| 15 |
+
#
|
| 16 |
+
# See the README file for information on usage and redistribution.
|
| 17 |
+
#
|
| 18 |
+
|
| 19 |
+
import array
|
| 20 |
+
import warnings
|
| 21 |
+
|
| 22 |
+
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class ImagePalette:
|
| 26 |
+
"""
|
| 27 |
+
Color palette for palette mapped images
|
| 28 |
+
|
| 29 |
+
:param mode: The mode to use for the palette. See:
|
| 30 |
+
:ref:`concept-modes`. Defaults to "RGB"
|
| 31 |
+
:param palette: An optional palette. If given, it must be a bytearray,
|
| 32 |
+
an array or a list of ints between 0-255. The list must consist of
|
| 33 |
+
all channels for one color followed by the next color (e.g. RGBRGBRGB).
|
| 34 |
+
Defaults to an empty palette.
|
| 35 |
+
:param size: An optional palette size. If given, an error is raised
|
| 36 |
+
if ``palette`` is not of equal length.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
def __init__(self, mode="RGB", palette=None, size=0):
|
| 40 |
+
self.mode = mode
|
| 41 |
+
self.rawmode = None # if set, palette contains raw data
|
| 42 |
+
self.palette = palette or bytearray()
|
| 43 |
+
self.dirty = None
|
| 44 |
+
if size != 0:
|
| 45 |
+
warnings.warn(
|
| 46 |
+
"The size parameter is deprecated and will be removed in Pillow 10 "
|
| 47 |
+
"(2023-07-01).",
|
| 48 |
+
DeprecationWarning,
|
| 49 |
+
)
|
| 50 |
+
if size != len(self.palette):
|
| 51 |
+
raise ValueError("wrong palette size")
|
| 52 |
+
|
| 53 |
+
@property
|
| 54 |
+
def palette(self):
|
| 55 |
+
return self._palette
|
| 56 |
+
|
| 57 |
+
@palette.setter
|
| 58 |
+
def palette(self, palette):
|
| 59 |
+
self._palette = palette
|
| 60 |
+
|
| 61 |
+
mode_len = len(self.mode)
|
| 62 |
+
self.colors = {}
|
| 63 |
+
for i in range(0, len(self.palette), mode_len):
|
| 64 |
+
color = tuple(self.palette[i : i + mode_len])
|
| 65 |
+
if color in self.colors:
|
| 66 |
+
continue
|
| 67 |
+
self.colors[color] = i // mode_len
|
| 68 |
+
|
| 69 |
+
def copy(self):
|
| 70 |
+
new = ImagePalette()
|
| 71 |
+
|
| 72 |
+
new.mode = self.mode
|
| 73 |
+
new.rawmode = self.rawmode
|
| 74 |
+
if self.palette is not None:
|
| 75 |
+
new.palette = self.palette[:]
|
| 76 |
+
new.dirty = self.dirty
|
| 77 |
+
|
| 78 |
+
return new
|
| 79 |
+
|
| 80 |
+
def getdata(self):
|
| 81 |
+
"""
|
| 82 |
+
Get palette contents in format suitable for the low-level
|
| 83 |
+
``im.putpalette`` primitive.
|
| 84 |
+
|
| 85 |
+
.. warning:: This method is experimental.
|
| 86 |
+
"""
|
| 87 |
+
if self.rawmode:
|
| 88 |
+
return self.rawmode, self.palette
|
| 89 |
+
return self.mode, self.tobytes()
|
| 90 |
+
|
| 91 |
+
def tobytes(self):
|
| 92 |
+
"""Convert palette to bytes.
|
| 93 |
+
|
| 94 |
+
.. warning:: This method is experimental.
|
| 95 |
+
"""
|
| 96 |
+
if self.rawmode:
|
| 97 |
+
raise ValueError("palette contains raw palette data")
|
| 98 |
+
if isinstance(self.palette, bytes):
|
| 99 |
+
return self.palette
|
| 100 |
+
arr = array.array("B", self.palette)
|
| 101 |
+
return arr.tobytes()
|
| 102 |
+
|
| 103 |
+
# Declare tostring as an alias for tobytes
|
| 104 |
+
tostring = tobytes
|
| 105 |
+
|
| 106 |
+
def getcolor(self, color, image=None):
|
| 107 |
+
"""Given an rgb tuple, allocate palette entry.
|
| 108 |
+
|
| 109 |
+
.. warning:: This method is experimental.
|
| 110 |
+
"""
|
| 111 |
+
if self.rawmode:
|
| 112 |
+
raise ValueError("palette contains raw palette data")
|
| 113 |
+
if isinstance(color, tuple):
|
| 114 |
+
if self.mode == "RGB":
|
| 115 |
+
if len(color) == 4 and color[3] == 255:
|
| 116 |
+
color = color[:3]
|
| 117 |
+
elif self.mode == "RGBA":
|
| 118 |
+
if len(color) == 3:
|
| 119 |
+
color += (255,)
|
| 120 |
+
try:
|
| 121 |
+
return self.colors[color]
|
| 122 |
+
except KeyError as e:
|
| 123 |
+
# allocate new color slot
|
| 124 |
+
if not isinstance(self.palette, bytearray):
|
| 125 |
+
self._palette = bytearray(self.palette)
|
| 126 |
+
index = len(self.palette) // 3
|
| 127 |
+
special_colors = ()
|
| 128 |
+
if image:
|
| 129 |
+
special_colors = (
|
| 130 |
+
image.info.get("background"),
|
| 131 |
+
image.info.get("transparency"),
|
| 132 |
+
)
|
| 133 |
+
while index in special_colors:
|
| 134 |
+
index += 1
|
| 135 |
+
if index >= 256:
|
| 136 |
+
if image:
|
| 137 |
+
# Search for an unused index
|
| 138 |
+
for i, count in reversed(list(enumerate(image.histogram()))):
|
| 139 |
+
if count == 0 and i not in special_colors:
|
| 140 |
+
index = i
|
| 141 |
+
break
|
| 142 |
+
if index >= 256:
|
| 143 |
+
raise ValueError("cannot allocate more than 256 colors") from e
|
| 144 |
+
self.colors[color] = index
|
| 145 |
+
if index * 3 < len(self.palette):
|
| 146 |
+
self._palette = (
|
| 147 |
+
self.palette[: index * 3]
|
| 148 |
+
+ bytes(color)
|
| 149 |
+
+ self.palette[index * 3 + 3 :]
|
| 150 |
+
)
|
| 151 |
+
else:
|
| 152 |
+
self._palette += bytes(color)
|
| 153 |
+
self.dirty = 1
|
| 154 |
+
return index
|
| 155 |
+
else:
|
| 156 |
+
raise ValueError(f"unknown color specifier: {repr(color)}")
|
| 157 |
+
|
| 158 |
+
def save(self, fp):
|
| 159 |
+
"""Save palette to text file.
|
| 160 |
+
|
| 161 |
+
.. warning:: This method is experimental.
|
| 162 |
+
"""
|
| 163 |
+
if self.rawmode:
|
| 164 |
+
raise ValueError("palette contains raw palette data")
|
| 165 |
+
if isinstance(fp, str):
|
| 166 |
+
fp = open(fp, "w")
|
| 167 |
+
fp.write("# Palette\n")
|
| 168 |
+
fp.write(f"# Mode: {self.mode}\n")
|
| 169 |
+
for i in range(256):
|
| 170 |
+
fp.write(f"{i}")
|
| 171 |
+
for j in range(i * len(self.mode), (i + 1) * len(self.mode)):
|
| 172 |
+
try:
|
| 173 |
+
fp.write(f" {self.palette[j]}")
|
| 174 |
+
except IndexError:
|
| 175 |
+
fp.write(" 0")
|
| 176 |
+
fp.write("\n")
|
| 177 |
+
fp.close()
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
# --------------------------------------------------------------------
|
| 181 |
+
# Internal
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def raw(rawmode, data):
|
| 185 |
+
palette = ImagePalette()
|
| 186 |
+
palette.rawmode = rawmode
|
| 187 |
+
palette.palette = data
|
| 188 |
+
palette.dirty = 1
|
| 189 |
+
return palette
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
# --------------------------------------------------------------------
|
| 193 |
+
# Factories
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def make_linear_lut(black, white):
|
| 197 |
+
lut = []
|
| 198 |
+
if black == 0:
|
| 199 |
+
for i in range(256):
|
| 200 |
+
lut.append(white * i // 255)
|
| 201 |
+
else:
|
| 202 |
+
raise NotImplementedError # FIXME
|
| 203 |
+
return lut
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def make_gamma_lut(exp):
|
| 207 |
+
lut = []
|
| 208 |
+
for i in range(256):
|
| 209 |
+
lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
|
| 210 |
+
return lut
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def negative(mode="RGB"):
|
| 214 |
+
palette = list(range(256 * len(mode)))
|
| 215 |
+
palette.reverse()
|
| 216 |
+
return ImagePalette(mode, [i // len(mode) for i in palette])
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def random(mode="RGB"):
|
| 220 |
+
from random import randint
|
| 221 |
+
|
| 222 |
+
palette = []
|
| 223 |
+
for i in range(256 * len(mode)):
|
| 224 |
+
palette.append(randint(0, 255))
|
| 225 |
+
return ImagePalette(mode, palette)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def sepia(white="#fff0c0"):
|
| 229 |
+
bands = [make_linear_lut(0, band) for band in ImageColor.getrgb(white)]
|
| 230 |
+
return ImagePalette("RGB", [bands[i % 3][i // 3] for i in range(256 * 3)])
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def wedge(mode="RGB"):
|
| 234 |
+
palette = list(range(256 * len(mode)))
|
| 235 |
+
return ImagePalette(mode, [i // len(mode) for i in palette])
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def load(filename):
|
| 239 |
+
|
| 240 |
+
# FIXME: supports GIMP gradients only
|
| 241 |
+
|
| 242 |
+
with open(filename, "rb") as fp:
|
| 243 |
+
|
| 244 |
+
for paletteHandler in [
|
| 245 |
+
GimpPaletteFile.GimpPaletteFile,
|
| 246 |
+
GimpGradientFile.GimpGradientFile,
|
| 247 |
+
PaletteFile.PaletteFile,
|
| 248 |
+
]:
|
| 249 |
+
try:
|
| 250 |
+
fp.seek(0)
|
| 251 |
+
lut = paletteHandler(fp).getpalette()
|
| 252 |
+
if lut:
|
| 253 |
+
break
|
| 254 |
+
except (SyntaxError, ValueError):
|
| 255 |
+
# import traceback
|
| 256 |
+
# traceback.print_exc()
|
| 257 |
+
pass
|
| 258 |
+
else:
|
| 259 |
+
raise OSError("cannot load palette")
|
| 260 |
+
|
| 261 |
+
return lut # data, rawmode
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImagePath.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# path interface
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1996-11-04 fl Created
|
| 9 |
+
# 2002-04-14 fl Added documentation stub class
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 12 |
+
# Copyright (c) Fredrik Lundh 1996.
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
|
| 17 |
+
from . import Image
|
| 18 |
+
|
| 19 |
+
Path = Image.core.path
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageQt.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# a simple Qt image interface.
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 2006-06-03 fl: created
|
| 9 |
+
# 2006-06-04 fl: inherit from QImage instead of wrapping it
|
| 10 |
+
# 2006-06-05 fl: removed toimage helper; move string support to ImageQt
|
| 11 |
+
# 2013-11-13 fl: add support for Qt5 (aurelien.ballier@cyclonit.com)
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) 2006 by Secret Labs AB
|
| 14 |
+
# Copyright (c) 2006 by Fredrik Lundh
|
| 15 |
+
#
|
| 16 |
+
# See the README file for information on usage and redistribution.
|
| 17 |
+
#
|
| 18 |
+
|
| 19 |
+
import sys
|
| 20 |
+
from io import BytesIO
|
| 21 |
+
|
| 22 |
+
from . import Image
|
| 23 |
+
from ._util import isPath
|
| 24 |
+
|
| 25 |
+
qt_versions = [
|
| 26 |
+
["6", "PyQt6"],
|
| 27 |
+
["side6", "PySide6"],
|
| 28 |
+
["5", "PyQt5"],
|
| 29 |
+
["side2", "PySide2"],
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
# If a version has already been imported, attempt it first
|
| 33 |
+
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
|
| 34 |
+
for qt_version, qt_module in qt_versions:
|
| 35 |
+
try:
|
| 36 |
+
if qt_module == "PyQt6":
|
| 37 |
+
from PyQt6.QtCore import QBuffer, QIODevice
|
| 38 |
+
from PyQt6.QtGui import QImage, QPixmap, qRgba
|
| 39 |
+
elif qt_module == "PySide6":
|
| 40 |
+
from PySide6.QtCore import QBuffer, QIODevice
|
| 41 |
+
from PySide6.QtGui import QImage, QPixmap, qRgba
|
| 42 |
+
elif qt_module == "PyQt5":
|
| 43 |
+
from PyQt5.QtCore import QBuffer, QIODevice
|
| 44 |
+
from PyQt5.QtGui import QImage, QPixmap, qRgba
|
| 45 |
+
elif qt_module == "PySide2":
|
| 46 |
+
from PySide2.QtCore import QBuffer, QIODevice
|
| 47 |
+
from PySide2.QtGui import QImage, QPixmap, qRgba
|
| 48 |
+
except (ImportError, RuntimeError):
|
| 49 |
+
continue
|
| 50 |
+
qt_is_installed = True
|
| 51 |
+
break
|
| 52 |
+
else:
|
| 53 |
+
qt_is_installed = False
|
| 54 |
+
qt_version = None
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def rgb(r, g, b, a=255):
|
| 58 |
+
"""(Internal) Turns an RGB color into a Qt compatible color integer."""
|
| 59 |
+
# use qRgb to pack the colors, and then turn the resulting long
|
| 60 |
+
# into a negative integer with the same bitpattern.
|
| 61 |
+
return qRgba(r, g, b, a) & 0xFFFFFFFF
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def fromqimage(im):
|
| 65 |
+
"""
|
| 66 |
+
:param im: QImage or PIL ImageQt object
|
| 67 |
+
"""
|
| 68 |
+
buffer = QBuffer()
|
| 69 |
+
if qt_version == "6":
|
| 70 |
+
try:
|
| 71 |
+
qt_openmode = QIODevice.OpenModeFlag
|
| 72 |
+
except AttributeError:
|
| 73 |
+
qt_openmode = QIODevice.OpenMode
|
| 74 |
+
else:
|
| 75 |
+
qt_openmode = QIODevice
|
| 76 |
+
buffer.open(qt_openmode.ReadWrite)
|
| 77 |
+
# preserve alpha channel with png
|
| 78 |
+
# otherwise ppm is more friendly with Image.open
|
| 79 |
+
if im.hasAlphaChannel():
|
| 80 |
+
im.save(buffer, "png")
|
| 81 |
+
else:
|
| 82 |
+
im.save(buffer, "ppm")
|
| 83 |
+
|
| 84 |
+
b = BytesIO()
|
| 85 |
+
b.write(buffer.data())
|
| 86 |
+
buffer.close()
|
| 87 |
+
b.seek(0)
|
| 88 |
+
|
| 89 |
+
return Image.open(b)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def fromqpixmap(im):
|
| 93 |
+
return fromqimage(im)
|
| 94 |
+
# buffer = QBuffer()
|
| 95 |
+
# buffer.open(QIODevice.ReadWrite)
|
| 96 |
+
# # im.save(buffer)
|
| 97 |
+
# # What if png doesn't support some image features like animation?
|
| 98 |
+
# im.save(buffer, 'ppm')
|
| 99 |
+
# bytes_io = BytesIO()
|
| 100 |
+
# bytes_io.write(buffer.data())
|
| 101 |
+
# buffer.close()
|
| 102 |
+
# bytes_io.seek(0)
|
| 103 |
+
# return Image.open(bytes_io)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def align8to32(bytes, width, mode):
|
| 107 |
+
"""
|
| 108 |
+
converts each scanline of data from 8 bit to 32 bit aligned
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
bits_per_pixel = {"1": 1, "L": 8, "P": 8, "I;16": 16}[mode]
|
| 112 |
+
|
| 113 |
+
# calculate bytes per line and the extra padding if needed
|
| 114 |
+
bits_per_line = bits_per_pixel * width
|
| 115 |
+
full_bytes_per_line, remaining_bits_per_line = divmod(bits_per_line, 8)
|
| 116 |
+
bytes_per_line = full_bytes_per_line + (1 if remaining_bits_per_line else 0)
|
| 117 |
+
|
| 118 |
+
extra_padding = -bytes_per_line % 4
|
| 119 |
+
|
| 120 |
+
# already 32 bit aligned by luck
|
| 121 |
+
if not extra_padding:
|
| 122 |
+
return bytes
|
| 123 |
+
|
| 124 |
+
new_data = []
|
| 125 |
+
for i in range(len(bytes) // bytes_per_line):
|
| 126 |
+
new_data.append(
|
| 127 |
+
bytes[i * bytes_per_line : (i + 1) * bytes_per_line]
|
| 128 |
+
+ b"\x00" * extra_padding
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
return b"".join(new_data)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def _toqclass_helper(im):
|
| 135 |
+
data = None
|
| 136 |
+
colortable = None
|
| 137 |
+
exclusive_fp = False
|
| 138 |
+
|
| 139 |
+
# handle filename, if given instead of image name
|
| 140 |
+
if hasattr(im, "toUtf8"):
|
| 141 |
+
# FIXME - is this really the best way to do this?
|
| 142 |
+
im = str(im.toUtf8(), "utf-8")
|
| 143 |
+
if isPath(im):
|
| 144 |
+
im = Image.open(im)
|
| 145 |
+
exclusive_fp = True
|
| 146 |
+
|
| 147 |
+
qt_format = QImage.Format if qt_version == "6" else QImage
|
| 148 |
+
if im.mode == "1":
|
| 149 |
+
format = qt_format.Format_Mono
|
| 150 |
+
elif im.mode == "L":
|
| 151 |
+
format = qt_format.Format_Indexed8
|
| 152 |
+
colortable = []
|
| 153 |
+
for i in range(256):
|
| 154 |
+
colortable.append(rgb(i, i, i))
|
| 155 |
+
elif im.mode == "P":
|
| 156 |
+
format = qt_format.Format_Indexed8
|
| 157 |
+
colortable = []
|
| 158 |
+
palette = im.getpalette()
|
| 159 |
+
for i in range(0, len(palette), 3):
|
| 160 |
+
colortable.append(rgb(*palette[i : i + 3]))
|
| 161 |
+
elif im.mode == "RGB":
|
| 162 |
+
# Populate the 4th channel with 255
|
| 163 |
+
im = im.convert("RGBA")
|
| 164 |
+
|
| 165 |
+
data = im.tobytes("raw", "BGRA")
|
| 166 |
+
format = qt_format.Format_RGB32
|
| 167 |
+
elif im.mode == "RGBA":
|
| 168 |
+
data = im.tobytes("raw", "BGRA")
|
| 169 |
+
format = qt_format.Format_ARGB32
|
| 170 |
+
elif im.mode == "I;16" and hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+
|
| 171 |
+
im = im.point(lambda i: i * 256)
|
| 172 |
+
|
| 173 |
+
format = qt_format.Format_Grayscale16
|
| 174 |
+
else:
|
| 175 |
+
if exclusive_fp:
|
| 176 |
+
im.close()
|
| 177 |
+
raise ValueError(f"unsupported image mode {repr(im.mode)}")
|
| 178 |
+
|
| 179 |
+
size = im.size
|
| 180 |
+
__data = data or align8to32(im.tobytes(), size[0], im.mode)
|
| 181 |
+
if exclusive_fp:
|
| 182 |
+
im.close()
|
| 183 |
+
return {"data": __data, "size": size, "format": format, "colortable": colortable}
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
if qt_is_installed:
|
| 187 |
+
|
| 188 |
+
class ImageQt(QImage):
|
| 189 |
+
def __init__(self, im):
|
| 190 |
+
"""
|
| 191 |
+
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
| 192 |
+
class.
|
| 193 |
+
|
| 194 |
+
:param im: A PIL Image object, or a file name (given either as
|
| 195 |
+
Python string or a PyQt string object).
|
| 196 |
+
"""
|
| 197 |
+
im_data = _toqclass_helper(im)
|
| 198 |
+
# must keep a reference, or Qt will crash!
|
| 199 |
+
# All QImage constructors that take data operate on an existing
|
| 200 |
+
# buffer, so this buffer has to hang on for the life of the image.
|
| 201 |
+
# Fixes https://github.com/python-pillow/Pillow/issues/1370
|
| 202 |
+
self.__data = im_data["data"]
|
| 203 |
+
super().__init__(
|
| 204 |
+
self.__data,
|
| 205 |
+
im_data["size"][0],
|
| 206 |
+
im_data["size"][1],
|
| 207 |
+
im_data["format"],
|
| 208 |
+
)
|
| 209 |
+
if im_data["colortable"]:
|
| 210 |
+
self.setColorTable(im_data["colortable"])
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def toqimage(im):
|
| 214 |
+
return ImageQt(im)
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def toqpixmap(im):
|
| 218 |
+
# # This doesn't work. For now using a dumb approach.
|
| 219 |
+
# im_data = _toqclass_helper(im)
|
| 220 |
+
# result = QPixmap(im_data["size"][0], im_data["size"][1])
|
| 221 |
+
# result.loadFromData(im_data["data"])
|
| 222 |
+
qimage = toqimage(im)
|
| 223 |
+
return QPixmap.fromImage(qimage)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageSequence.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# sequence support classes
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 1997-02-20 fl Created
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) 1997 by Secret Labs AB.
|
| 11 |
+
# Copyright (c) 1997 by Fredrik Lundh.
|
| 12 |
+
#
|
| 13 |
+
# See the README file for information on usage and redistribution.
|
| 14 |
+
#
|
| 15 |
+
|
| 16 |
+
##
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Iterator:
|
| 20 |
+
"""
|
| 21 |
+
This class implements an iterator object that can be used to loop
|
| 22 |
+
over an image sequence.
|
| 23 |
+
|
| 24 |
+
You can use the ``[]`` operator to access elements by index. This operator
|
| 25 |
+
will raise an :py:exc:`IndexError` if you try to access a nonexistent
|
| 26 |
+
frame.
|
| 27 |
+
|
| 28 |
+
:param im: An image object.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
def __init__(self, im):
|
| 32 |
+
if not hasattr(im, "seek"):
|
| 33 |
+
raise AttributeError("im must have seek method")
|
| 34 |
+
self.im = im
|
| 35 |
+
self.position = getattr(self.im, "_min_frame", 0)
|
| 36 |
+
|
| 37 |
+
def __getitem__(self, ix):
|
| 38 |
+
try:
|
| 39 |
+
self.im.seek(ix)
|
| 40 |
+
return self.im
|
| 41 |
+
except EOFError as e:
|
| 42 |
+
raise IndexError from e # end of sequence
|
| 43 |
+
|
| 44 |
+
def __iter__(self):
|
| 45 |
+
return self
|
| 46 |
+
|
| 47 |
+
def __next__(self):
|
| 48 |
+
try:
|
| 49 |
+
self.im.seek(self.position)
|
| 50 |
+
self.position += 1
|
| 51 |
+
return self.im
|
| 52 |
+
except EOFError as e:
|
| 53 |
+
raise StopIteration from e
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def all_frames(im, func=None):
|
| 57 |
+
"""
|
| 58 |
+
Applies a given function to all frames in an image or a list of images.
|
| 59 |
+
The frames are returned as a list of separate images.
|
| 60 |
+
|
| 61 |
+
:param im: An image, or a list of images.
|
| 62 |
+
:param func: The function to apply to all of the image frames.
|
| 63 |
+
:returns: A list of images.
|
| 64 |
+
"""
|
| 65 |
+
if not isinstance(im, list):
|
| 66 |
+
im = [im]
|
| 67 |
+
|
| 68 |
+
ims = []
|
| 69 |
+
for imSequence in im:
|
| 70 |
+
current = imSequence.tell()
|
| 71 |
+
|
| 72 |
+
ims += [im_frame.copy() for im_frame in Iterator(imSequence)]
|
| 73 |
+
|
| 74 |
+
imSequence.seek(current)
|
| 75 |
+
return [func(im) for im in ims] if func else ims
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageStat.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# global image statistics
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1996-04-05 fl Created
|
| 9 |
+
# 1997-05-21 fl Added mask; added rms, var, stddev attributes
|
| 10 |
+
# 1997-08-05 fl Added median
|
| 11 |
+
# 1998-07-05 hk Fixed integer overflow error
|
| 12 |
+
#
|
| 13 |
+
# Notes:
|
| 14 |
+
# This class shows how to implement delayed evaluation of attributes.
|
| 15 |
+
# To get a certain value, simply access the corresponding attribute.
|
| 16 |
+
# The __getattr__ dispatcher takes care of the rest.
|
| 17 |
+
#
|
| 18 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 19 |
+
# Copyright (c) Fredrik Lundh 1996-97.
|
| 20 |
+
#
|
| 21 |
+
# See the README file for information on usage and redistribution.
|
| 22 |
+
#
|
| 23 |
+
|
| 24 |
+
import functools
|
| 25 |
+
import math
|
| 26 |
+
import operator
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class Stat:
|
| 30 |
+
def __init__(self, image_or_list, mask=None):
|
| 31 |
+
try:
|
| 32 |
+
if mask:
|
| 33 |
+
self.h = image_or_list.histogram(mask)
|
| 34 |
+
else:
|
| 35 |
+
self.h = image_or_list.histogram()
|
| 36 |
+
except AttributeError:
|
| 37 |
+
self.h = image_or_list # assume it to be a histogram list
|
| 38 |
+
if not isinstance(self.h, list):
|
| 39 |
+
raise TypeError("first argument must be image or list")
|
| 40 |
+
self.bands = list(range(len(self.h) // 256))
|
| 41 |
+
|
| 42 |
+
def __getattr__(self, id):
|
| 43 |
+
"""Calculate missing attribute"""
|
| 44 |
+
if id[:4] == "_get":
|
| 45 |
+
raise AttributeError(id)
|
| 46 |
+
# calculate missing attribute
|
| 47 |
+
v = getattr(self, "_get" + id)()
|
| 48 |
+
setattr(self, id, v)
|
| 49 |
+
return v
|
| 50 |
+
|
| 51 |
+
def _getextrema(self):
|
| 52 |
+
"""Get min/max values for each band in the image"""
|
| 53 |
+
|
| 54 |
+
def minmax(histogram):
|
| 55 |
+
n = 255
|
| 56 |
+
x = 0
|
| 57 |
+
for i in range(256):
|
| 58 |
+
if histogram[i]:
|
| 59 |
+
n = min(n, i)
|
| 60 |
+
x = max(x, i)
|
| 61 |
+
return n, x # returns (255, 0) if there's no data in the histogram
|
| 62 |
+
|
| 63 |
+
v = []
|
| 64 |
+
for i in range(0, len(self.h), 256):
|
| 65 |
+
v.append(minmax(self.h[i:]))
|
| 66 |
+
return v
|
| 67 |
+
|
| 68 |
+
def _getcount(self):
|
| 69 |
+
"""Get total number of pixels in each layer"""
|
| 70 |
+
|
| 71 |
+
v = []
|
| 72 |
+
for i in range(0, len(self.h), 256):
|
| 73 |
+
v.append(functools.reduce(operator.add, self.h[i : i + 256]))
|
| 74 |
+
return v
|
| 75 |
+
|
| 76 |
+
def _getsum(self):
|
| 77 |
+
"""Get sum of all pixels in each layer"""
|
| 78 |
+
|
| 79 |
+
v = []
|
| 80 |
+
for i in range(0, len(self.h), 256):
|
| 81 |
+
layerSum = 0.0
|
| 82 |
+
for j in range(256):
|
| 83 |
+
layerSum += j * self.h[i + j]
|
| 84 |
+
v.append(layerSum)
|
| 85 |
+
return v
|
| 86 |
+
|
| 87 |
+
def _getsum2(self):
|
| 88 |
+
"""Get squared sum of all pixels in each layer"""
|
| 89 |
+
|
| 90 |
+
v = []
|
| 91 |
+
for i in range(0, len(self.h), 256):
|
| 92 |
+
sum2 = 0.0
|
| 93 |
+
for j in range(256):
|
| 94 |
+
sum2 += (j**2) * float(self.h[i + j])
|
| 95 |
+
v.append(sum2)
|
| 96 |
+
return v
|
| 97 |
+
|
| 98 |
+
def _getmean(self):
|
| 99 |
+
"""Get average pixel level for each layer"""
|
| 100 |
+
|
| 101 |
+
v = []
|
| 102 |
+
for i in self.bands:
|
| 103 |
+
v.append(self.sum[i] / self.count[i])
|
| 104 |
+
return v
|
| 105 |
+
|
| 106 |
+
def _getmedian(self):
|
| 107 |
+
"""Get median pixel level for each layer"""
|
| 108 |
+
|
| 109 |
+
v = []
|
| 110 |
+
for i in self.bands:
|
| 111 |
+
s = 0
|
| 112 |
+
half = self.count[i] // 2
|
| 113 |
+
b = i * 256
|
| 114 |
+
for j in range(256):
|
| 115 |
+
s = s + self.h[b + j]
|
| 116 |
+
if s > half:
|
| 117 |
+
break
|
| 118 |
+
v.append(j)
|
| 119 |
+
return v
|
| 120 |
+
|
| 121 |
+
def _getrms(self):
|
| 122 |
+
"""Get RMS for each layer"""
|
| 123 |
+
|
| 124 |
+
v = []
|
| 125 |
+
for i in self.bands:
|
| 126 |
+
v.append(math.sqrt(self.sum2[i] / self.count[i]))
|
| 127 |
+
return v
|
| 128 |
+
|
| 129 |
+
def _getvar(self):
|
| 130 |
+
"""Get variance for each layer"""
|
| 131 |
+
|
| 132 |
+
v = []
|
| 133 |
+
for i in self.bands:
|
| 134 |
+
n = self.count[i]
|
| 135 |
+
v.append((self.sum2[i] - (self.sum[i] ** 2.0) / n) / n)
|
| 136 |
+
return v
|
| 137 |
+
|
| 138 |
+
def _getstddev(self):
|
| 139 |
+
"""Get standard deviation for each layer"""
|
| 140 |
+
|
| 141 |
+
v = []
|
| 142 |
+
for i in self.bands:
|
| 143 |
+
v.append(math.sqrt(self.var[i]))
|
| 144 |
+
return v
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
Global = Stat # compatibility
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImageWin.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# a Windows DIB display interface
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1996-05-20 fl Created
|
| 9 |
+
# 1996-09-20 fl Fixed subregion exposure
|
| 10 |
+
# 1997-09-21 fl Added draw primitive (for tzPrint)
|
| 11 |
+
# 2003-05-21 fl Added experimental Window/ImageWindow classes
|
| 12 |
+
# 2003-09-05 fl Added fromstring/tostring methods
|
| 13 |
+
#
|
| 14 |
+
# Copyright (c) Secret Labs AB 1997-2003.
|
| 15 |
+
# Copyright (c) Fredrik Lundh 1996-2003.
|
| 16 |
+
#
|
| 17 |
+
# See the README file for information on usage and redistribution.
|
| 18 |
+
#
|
| 19 |
+
|
| 20 |
+
from . import Image
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class HDC:
|
| 24 |
+
"""
|
| 25 |
+
Wraps an HDC integer. The resulting object can be passed to the
|
| 26 |
+
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
|
| 27 |
+
methods.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self, dc):
|
| 31 |
+
self.dc = dc
|
| 32 |
+
|
| 33 |
+
def __int__(self):
|
| 34 |
+
return self.dc
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class HWND:
|
| 38 |
+
"""
|
| 39 |
+
Wraps an HWND integer. The resulting object can be passed to the
|
| 40 |
+
:py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
|
| 41 |
+
methods, instead of a DC.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
def __init__(self, wnd):
|
| 45 |
+
self.wnd = wnd
|
| 46 |
+
|
| 47 |
+
def __int__(self):
|
| 48 |
+
return self.wnd
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class Dib:
|
| 52 |
+
"""
|
| 53 |
+
A Windows bitmap with the given mode and size. The mode can be one of "1",
|
| 54 |
+
"L", "P", or "RGB".
|
| 55 |
+
|
| 56 |
+
If the display requires a palette, this constructor creates a suitable
|
| 57 |
+
palette and associates it with the image. For an "L" image, 128 greylevels
|
| 58 |
+
are allocated. For an "RGB" image, a 6x6x6 colour cube is used, together
|
| 59 |
+
with 20 greylevels.
|
| 60 |
+
|
| 61 |
+
To make sure that palettes work properly under Windows, you must call the
|
| 62 |
+
``palette`` method upon certain events from Windows.
|
| 63 |
+
|
| 64 |
+
:param image: Either a PIL image, or a mode string. If a mode string is
|
| 65 |
+
used, a size must also be given. The mode can be one of "1",
|
| 66 |
+
"L", "P", or "RGB".
|
| 67 |
+
:param size: If the first argument is a mode string, this
|
| 68 |
+
defines the size of the image.
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
def __init__(self, image, size=None):
|
| 72 |
+
if hasattr(image, "mode") and hasattr(image, "size"):
|
| 73 |
+
mode = image.mode
|
| 74 |
+
size = image.size
|
| 75 |
+
else:
|
| 76 |
+
mode = image
|
| 77 |
+
image = None
|
| 78 |
+
if mode not in ["1", "L", "P", "RGB"]:
|
| 79 |
+
mode = Image.getmodebase(mode)
|
| 80 |
+
self.image = Image.core.display(mode, size)
|
| 81 |
+
self.mode = mode
|
| 82 |
+
self.size = size
|
| 83 |
+
if image:
|
| 84 |
+
self.paste(image)
|
| 85 |
+
|
| 86 |
+
def expose(self, handle):
|
| 87 |
+
"""
|
| 88 |
+
Copy the bitmap contents to a device context.
|
| 89 |
+
|
| 90 |
+
:param handle: Device context (HDC), cast to a Python integer, or an
|
| 91 |
+
HDC or HWND instance. In PythonWin, you can use
|
| 92 |
+
``CDC.GetHandleAttrib()`` to get a suitable handle.
|
| 93 |
+
"""
|
| 94 |
+
if isinstance(handle, HWND):
|
| 95 |
+
dc = self.image.getdc(handle)
|
| 96 |
+
try:
|
| 97 |
+
result = self.image.expose(dc)
|
| 98 |
+
finally:
|
| 99 |
+
self.image.releasedc(handle, dc)
|
| 100 |
+
else:
|
| 101 |
+
result = self.image.expose(handle)
|
| 102 |
+
return result
|
| 103 |
+
|
| 104 |
+
def draw(self, handle, dst, src=None):
|
| 105 |
+
"""
|
| 106 |
+
Same as expose, but allows you to specify where to draw the image, and
|
| 107 |
+
what part of it to draw.
|
| 108 |
+
|
| 109 |
+
The destination and source areas are given as 4-tuple rectangles. If
|
| 110 |
+
the source is omitted, the entire image is copied. If the source and
|
| 111 |
+
the destination have different sizes, the image is resized as
|
| 112 |
+
necessary.
|
| 113 |
+
"""
|
| 114 |
+
if not src:
|
| 115 |
+
src = (0, 0) + self.size
|
| 116 |
+
if isinstance(handle, HWND):
|
| 117 |
+
dc = self.image.getdc(handle)
|
| 118 |
+
try:
|
| 119 |
+
result = self.image.draw(dc, dst, src)
|
| 120 |
+
finally:
|
| 121 |
+
self.image.releasedc(handle, dc)
|
| 122 |
+
else:
|
| 123 |
+
result = self.image.draw(handle, dst, src)
|
| 124 |
+
return result
|
| 125 |
+
|
| 126 |
+
def query_palette(self, handle):
|
| 127 |
+
"""
|
| 128 |
+
Installs the palette associated with the image in the given device
|
| 129 |
+
context.
|
| 130 |
+
|
| 131 |
+
This method should be called upon **QUERYNEWPALETTE** and
|
| 132 |
+
**PALETTECHANGED** events from Windows. If this method returns a
|
| 133 |
+
non-zero value, one or more display palette entries were changed, and
|
| 134 |
+
the image should be redrawn.
|
| 135 |
+
|
| 136 |
+
:param handle: Device context (HDC), cast to a Python integer, or an
|
| 137 |
+
HDC or HWND instance.
|
| 138 |
+
:return: A true value if one or more entries were changed (this
|
| 139 |
+
indicates that the image should be redrawn).
|
| 140 |
+
"""
|
| 141 |
+
if isinstance(handle, HWND):
|
| 142 |
+
handle = self.image.getdc(handle)
|
| 143 |
+
try:
|
| 144 |
+
result = self.image.query_palette(handle)
|
| 145 |
+
finally:
|
| 146 |
+
self.image.releasedc(handle, handle)
|
| 147 |
+
else:
|
| 148 |
+
result = self.image.query_palette(handle)
|
| 149 |
+
return result
|
| 150 |
+
|
| 151 |
+
def paste(self, im, box=None):
|
| 152 |
+
"""
|
| 153 |
+
Paste a PIL image into the bitmap image.
|
| 154 |
+
|
| 155 |
+
:param im: A PIL image. The size must match the target region.
|
| 156 |
+
If the mode does not match, the image is converted to the
|
| 157 |
+
mode of the bitmap image.
|
| 158 |
+
:param box: A 4-tuple defining the left, upper, right, and
|
| 159 |
+
lower pixel coordinate. See :ref:`coordinate-system`. If
|
| 160 |
+
None is given instead of a tuple, all of the image is
|
| 161 |
+
assumed.
|
| 162 |
+
"""
|
| 163 |
+
im.load()
|
| 164 |
+
if self.mode != im.mode:
|
| 165 |
+
im = im.convert(self.mode)
|
| 166 |
+
if box:
|
| 167 |
+
self.image.paste(im.im, box)
|
| 168 |
+
else:
|
| 169 |
+
self.image.paste(im.im)
|
| 170 |
+
|
| 171 |
+
def frombytes(self, buffer):
|
| 172 |
+
"""
|
| 173 |
+
Load display memory contents from byte data.
|
| 174 |
+
|
| 175 |
+
:param buffer: A buffer containing display data (usually
|
| 176 |
+
data returned from :py:func:`~PIL.ImageWin.Dib.tobytes`)
|
| 177 |
+
"""
|
| 178 |
+
return self.image.frombytes(buffer)
|
| 179 |
+
|
| 180 |
+
def tobytes(self):
|
| 181 |
+
"""
|
| 182 |
+
Copy display memory contents to bytes object.
|
| 183 |
+
|
| 184 |
+
:return: A bytes object containing display data.
|
| 185 |
+
"""
|
| 186 |
+
return self.image.tobytes()
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
class Window:
|
| 190 |
+
"""Create a Window with the given title size."""
|
| 191 |
+
|
| 192 |
+
def __init__(self, title="PIL", width=None, height=None):
|
| 193 |
+
self.hwnd = Image.core.createwindow(
|
| 194 |
+
title, self.__dispatcher, width or 0, height or 0
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
def __dispatcher(self, action, *args):
|
| 198 |
+
return getattr(self, "ui_handle_" + action)(*args)
|
| 199 |
+
|
| 200 |
+
def ui_handle_clear(self, dc, x0, y0, x1, y1):
|
| 201 |
+
pass
|
| 202 |
+
|
| 203 |
+
def ui_handle_damage(self, x0, y0, x1, y1):
|
| 204 |
+
pass
|
| 205 |
+
|
| 206 |
+
def ui_handle_destroy(self):
|
| 207 |
+
pass
|
| 208 |
+
|
| 209 |
+
def ui_handle_repair(self, dc, x0, y0, x1, y1):
|
| 210 |
+
pass
|
| 211 |
+
|
| 212 |
+
def ui_handle_resize(self, width, height):
|
| 213 |
+
pass
|
| 214 |
+
|
| 215 |
+
def mainloop(self):
|
| 216 |
+
Image.core.eventloop()
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
class ImageWindow(Window):
|
| 220 |
+
"""Create an image window which displays the given image."""
|
| 221 |
+
|
| 222 |
+
def __init__(self, image, title="PIL"):
|
| 223 |
+
if not isinstance(image, Dib):
|
| 224 |
+
image = Dib(image)
|
| 225 |
+
self.image = image
|
| 226 |
+
width, height = image.size
|
| 227 |
+
super().__init__(title, width=width, height=height)
|
| 228 |
+
|
| 229 |
+
def ui_handle_repair(self, dc, x0, y0, x1, y1):
|
| 230 |
+
self.image.draw(dc, (x0, y0, x1, y1))
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/ImtImagePlugin.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# IM Tools support for PIL
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 1996-05-27 fl Created (read 8-bit images only)
|
| 9 |
+
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.2)
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) Secret Labs AB 1997-2001.
|
| 12 |
+
# Copyright (c) Fredrik Lundh 1996-2001.
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
import re
|
| 19 |
+
|
| 20 |
+
from . import Image, ImageFile
|
| 21 |
+
|
| 22 |
+
#
|
| 23 |
+
# --------------------------------------------------------------------
|
| 24 |
+
|
| 25 |
+
field = re.compile(rb"([a-z]*) ([^ \r\n]*)")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
##
|
| 29 |
+
# Image plugin for IM Tools images.
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class ImtImageFile(ImageFile.ImageFile):
|
| 33 |
+
|
| 34 |
+
format = "IMT"
|
| 35 |
+
format_description = "IM Tools"
|
| 36 |
+
|
| 37 |
+
def _open(self):
|
| 38 |
+
|
| 39 |
+
# Quick rejection: if there's not a LF among the first
|
| 40 |
+
# 100 bytes, this is (probably) not a text header.
|
| 41 |
+
|
| 42 |
+
if b"\n" not in self.fp.read(100):
|
| 43 |
+
raise SyntaxError("not an IM file")
|
| 44 |
+
self.fp.seek(0)
|
| 45 |
+
|
| 46 |
+
xsize = ysize = 0
|
| 47 |
+
|
| 48 |
+
while True:
|
| 49 |
+
|
| 50 |
+
s = self.fp.read(1)
|
| 51 |
+
if not s:
|
| 52 |
+
break
|
| 53 |
+
|
| 54 |
+
if s == b"\x0C":
|
| 55 |
+
|
| 56 |
+
# image data begins
|
| 57 |
+
self.tile = [
|
| 58 |
+
("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1))
|
| 59 |
+
]
|
| 60 |
+
|
| 61 |
+
break
|
| 62 |
+
|
| 63 |
+
else:
|
| 64 |
+
|
| 65 |
+
# read key/value pair
|
| 66 |
+
# FIXME: dangerous, may read whole file
|
| 67 |
+
s = s + self.fp.readline()
|
| 68 |
+
if len(s) == 1 or len(s) > 100:
|
| 69 |
+
break
|
| 70 |
+
if s[0] == ord(b"*"):
|
| 71 |
+
continue # comment
|
| 72 |
+
|
| 73 |
+
m = field.match(s)
|
| 74 |
+
if not m:
|
| 75 |
+
break
|
| 76 |
+
k, v = m.group(1, 2)
|
| 77 |
+
if k == "width":
|
| 78 |
+
xsize = int(v)
|
| 79 |
+
self._size = xsize, ysize
|
| 80 |
+
elif k == "height":
|
| 81 |
+
ysize = int(v)
|
| 82 |
+
self._size = xsize, ysize
|
| 83 |
+
elif k == "pixel" and v == "n8":
|
| 84 |
+
self.mode = "L"
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
#
|
| 88 |
+
# --------------------------------------------------------------------
|
| 89 |
+
|
| 90 |
+
Image.register_open(ImtImageFile.format, ImtImageFile)
|
| 91 |
+
|
| 92 |
+
#
|
| 93 |
+
# no extension registered (".im" is simply too common)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/IptcImagePlugin.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# IPTC/NAA file handling
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 1995-10-01 fl Created
|
| 9 |
+
# 1998-03-09 fl Cleaned up and added to PIL
|
| 10 |
+
# 2002-06-18 fl Added getiptcinfo helper
|
| 11 |
+
#
|
| 12 |
+
# Copyright (c) Secret Labs AB 1997-2002.
|
| 13 |
+
# Copyright (c) Fredrik Lundh 1995.
|
| 14 |
+
#
|
| 15 |
+
# See the README file for information on usage and redistribution.
|
| 16 |
+
#
|
| 17 |
+
import os
|
| 18 |
+
import tempfile
|
| 19 |
+
|
| 20 |
+
from . import Image, ImageFile
|
| 21 |
+
from ._binary import i8
|
| 22 |
+
from ._binary import i16be as i16
|
| 23 |
+
from ._binary import i32be as i32
|
| 24 |
+
from ._binary import o8
|
| 25 |
+
|
| 26 |
+
COMPRESSION = {1: "raw", 5: "jpeg"}
|
| 27 |
+
|
| 28 |
+
PAD = o8(0) * 4
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
#
|
| 32 |
+
# Helpers
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def i(c):
|
| 36 |
+
return i32((PAD + c)[-4:])
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def dump(c):
|
| 40 |
+
for i in c:
|
| 41 |
+
print("%02x" % i8(i), end=" ")
|
| 42 |
+
print()
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
##
|
| 46 |
+
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
| 47 |
+
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class IptcImageFile(ImageFile.ImageFile):
|
| 51 |
+
|
| 52 |
+
format = "IPTC"
|
| 53 |
+
format_description = "IPTC/NAA"
|
| 54 |
+
|
| 55 |
+
def getint(self, key):
|
| 56 |
+
return i(self.info[key])
|
| 57 |
+
|
| 58 |
+
def field(self):
|
| 59 |
+
#
|
| 60 |
+
# get a IPTC field header
|
| 61 |
+
s = self.fp.read(5)
|
| 62 |
+
if not len(s):
|
| 63 |
+
return None, 0
|
| 64 |
+
|
| 65 |
+
tag = s[1], s[2]
|
| 66 |
+
|
| 67 |
+
# syntax
|
| 68 |
+
if s[0] != 0x1C or tag[0] < 1 or tag[0] > 9:
|
| 69 |
+
raise SyntaxError("invalid IPTC/NAA file")
|
| 70 |
+
|
| 71 |
+
# field size
|
| 72 |
+
size = s[3]
|
| 73 |
+
if size > 132:
|
| 74 |
+
raise OSError("illegal field length in IPTC/NAA file")
|
| 75 |
+
elif size == 128:
|
| 76 |
+
size = 0
|
| 77 |
+
elif size > 128:
|
| 78 |
+
size = i(self.fp.read(size - 128))
|
| 79 |
+
else:
|
| 80 |
+
size = i16(s, 3)
|
| 81 |
+
|
| 82 |
+
return tag, size
|
| 83 |
+
|
| 84 |
+
def _open(self):
|
| 85 |
+
|
| 86 |
+
# load descriptive fields
|
| 87 |
+
while True:
|
| 88 |
+
offset = self.fp.tell()
|
| 89 |
+
tag, size = self.field()
|
| 90 |
+
if not tag or tag == (8, 10):
|
| 91 |
+
break
|
| 92 |
+
if size:
|
| 93 |
+
tagdata = self.fp.read(size)
|
| 94 |
+
else:
|
| 95 |
+
tagdata = None
|
| 96 |
+
if tag in self.info:
|
| 97 |
+
if isinstance(self.info[tag], list):
|
| 98 |
+
self.info[tag].append(tagdata)
|
| 99 |
+
else:
|
| 100 |
+
self.info[tag] = [self.info[tag], tagdata]
|
| 101 |
+
else:
|
| 102 |
+
self.info[tag] = tagdata
|
| 103 |
+
|
| 104 |
+
# mode
|
| 105 |
+
layers = i8(self.info[(3, 60)][0])
|
| 106 |
+
component = i8(self.info[(3, 60)][1])
|
| 107 |
+
if (3, 65) in self.info:
|
| 108 |
+
id = i8(self.info[(3, 65)][0]) - 1
|
| 109 |
+
else:
|
| 110 |
+
id = 0
|
| 111 |
+
if layers == 1 and not component:
|
| 112 |
+
self.mode = "L"
|
| 113 |
+
elif layers == 3 and component:
|
| 114 |
+
self.mode = "RGB"[id]
|
| 115 |
+
elif layers == 4 and component:
|
| 116 |
+
self.mode = "CMYK"[id]
|
| 117 |
+
|
| 118 |
+
# size
|
| 119 |
+
self._size = self.getint((3, 20)), self.getint((3, 30))
|
| 120 |
+
|
| 121 |
+
# compression
|
| 122 |
+
try:
|
| 123 |
+
compression = COMPRESSION[self.getint((3, 120))]
|
| 124 |
+
except KeyError as e:
|
| 125 |
+
raise OSError("Unknown IPTC image compression") from e
|
| 126 |
+
|
| 127 |
+
# tile
|
| 128 |
+
if tag == (8, 10):
|
| 129 |
+
self.tile = [
|
| 130 |
+
("iptc", (compression, offset), (0, 0, self.size[0], self.size[1]))
|
| 131 |
+
]
|
| 132 |
+
|
| 133 |
+
def load(self):
|
| 134 |
+
|
| 135 |
+
if len(self.tile) != 1 or self.tile[0][0] != "iptc":
|
| 136 |
+
return ImageFile.ImageFile.load(self)
|
| 137 |
+
|
| 138 |
+
type, tile, box = self.tile[0]
|
| 139 |
+
|
| 140 |
+
encoding, offset = tile
|
| 141 |
+
|
| 142 |
+
self.fp.seek(offset)
|
| 143 |
+
|
| 144 |
+
# Copy image data to temporary file
|
| 145 |
+
o_fd, outfile = tempfile.mkstemp(text=False)
|
| 146 |
+
o = os.fdopen(o_fd)
|
| 147 |
+
if encoding == "raw":
|
| 148 |
+
# To simplify access to the extracted file,
|
| 149 |
+
# prepend a PPM header
|
| 150 |
+
o.write("P5\n%d %d\n255\n" % self.size)
|
| 151 |
+
while True:
|
| 152 |
+
type, size = self.field()
|
| 153 |
+
if type != (8, 10):
|
| 154 |
+
break
|
| 155 |
+
while size > 0:
|
| 156 |
+
s = self.fp.read(min(size, 8192))
|
| 157 |
+
if not s:
|
| 158 |
+
break
|
| 159 |
+
o.write(s)
|
| 160 |
+
size -= len(s)
|
| 161 |
+
o.close()
|
| 162 |
+
|
| 163 |
+
try:
|
| 164 |
+
with Image.open(outfile) as _im:
|
| 165 |
+
_im.load()
|
| 166 |
+
self.im = _im.im
|
| 167 |
+
finally:
|
| 168 |
+
try:
|
| 169 |
+
os.unlink(outfile)
|
| 170 |
+
except OSError:
|
| 171 |
+
pass
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
Image.register_open(IptcImageFile.format, IptcImageFile)
|
| 175 |
+
|
| 176 |
+
Image.register_extension(IptcImageFile.format, ".iim")
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def getiptcinfo(im):
|
| 180 |
+
"""
|
| 181 |
+
Get IPTC information from TIFF, JPEG, or IPTC file.
|
| 182 |
+
|
| 183 |
+
:param im: An image containing IPTC data.
|
| 184 |
+
:returns: A dictionary containing IPTC information, or None if
|
| 185 |
+
no IPTC information block was found.
|
| 186 |
+
"""
|
| 187 |
+
import io
|
| 188 |
+
|
| 189 |
+
from . import JpegImagePlugin, TiffImagePlugin
|
| 190 |
+
|
| 191 |
+
data = None
|
| 192 |
+
|
| 193 |
+
if isinstance(im, IptcImageFile):
|
| 194 |
+
# return info dictionary right away
|
| 195 |
+
return im.info
|
| 196 |
+
|
| 197 |
+
elif isinstance(im, JpegImagePlugin.JpegImageFile):
|
| 198 |
+
# extract the IPTC/NAA resource
|
| 199 |
+
photoshop = im.info.get("photoshop")
|
| 200 |
+
if photoshop:
|
| 201 |
+
data = photoshop.get(0x0404)
|
| 202 |
+
|
| 203 |
+
elif isinstance(im, TiffImagePlugin.TiffImageFile):
|
| 204 |
+
# get raw data from the IPTC/NAA tag (PhotoShop tags the data
|
| 205 |
+
# as 4-byte integers, so we cannot use the get method...)
|
| 206 |
+
try:
|
| 207 |
+
data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
|
| 208 |
+
except (AttributeError, KeyError):
|
| 209 |
+
pass
|
| 210 |
+
|
| 211 |
+
if data is None:
|
| 212 |
+
return None # no properties
|
| 213 |
+
|
| 214 |
+
# create an IptcImagePlugin object without initializing it
|
| 215 |
+
class FakeImage:
|
| 216 |
+
pass
|
| 217 |
+
|
| 218 |
+
im = FakeImage()
|
| 219 |
+
im.__class__ = IptcImageFile
|
| 220 |
+
|
| 221 |
+
# parse the IPTC information chunk
|
| 222 |
+
im.info = {}
|
| 223 |
+
im.fp = io.BytesIO(data)
|
| 224 |
+
|
| 225 |
+
try:
|
| 226 |
+
im._open()
|
| 227 |
+
except (IndexError, KeyError):
|
| 228 |
+
pass # expected failure
|
| 229 |
+
|
| 230 |
+
return im.info
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/JpegPresets.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
JPEG quality settings equivalent to the Photoshop settings.
|
| 3 |
+
Can be used when saving JPEG files.
|
| 4 |
+
|
| 5 |
+
The following presets are available by default:
|
| 6 |
+
``web_low``, ``web_medium``, ``web_high``, ``web_very_high``, ``web_maximum``,
|
| 7 |
+
``low``, ``medium``, ``high``, ``maximum``.
|
| 8 |
+
More presets can be added to the :py:data:`presets` dict if needed.
|
| 9 |
+
|
| 10 |
+
To apply the preset, specify::
|
| 11 |
+
|
| 12 |
+
quality="preset_name"
|
| 13 |
+
|
| 14 |
+
To apply only the quantization table::
|
| 15 |
+
|
| 16 |
+
qtables="preset_name"
|
| 17 |
+
|
| 18 |
+
To apply only the subsampling setting::
|
| 19 |
+
|
| 20 |
+
subsampling="preset_name"
|
| 21 |
+
|
| 22 |
+
Example::
|
| 23 |
+
|
| 24 |
+
im.save("image_name.jpg", quality="web_high")
|
| 25 |
+
|
| 26 |
+
Subsampling
|
| 27 |
+
-----------
|
| 28 |
+
|
| 29 |
+
Subsampling is the practice of encoding images by implementing less resolution
|
| 30 |
+
for chroma information than for luma information.
|
| 31 |
+
(ref.: https://en.wikipedia.org/wiki/Chroma_subsampling)
|
| 32 |
+
|
| 33 |
+
Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and
|
| 34 |
+
4:2:0.
|
| 35 |
+
|
| 36 |
+
You can get the subsampling of a JPEG with the
|
| 37 |
+
:func:`.JpegImagePlugin.get_sampling` function.
|
| 38 |
+
|
| 39 |
+
In JPEG compressed data a JPEG marker is used instead of an EXIF tag.
|
| 40 |
+
(ref.: https://www.exiv2.org/tags.html)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
Quantization tables
|
| 44 |
+
-------------------
|
| 45 |
+
|
| 46 |
+
They are values use by the DCT (Discrete cosine transform) to remove
|
| 47 |
+
*unnecessary* information from the image (the lossy part of the compression).
|
| 48 |
+
(ref.: https://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
|
| 49 |
+
https://en.wikipedia.org/wiki/JPEG#Quantization)
|
| 50 |
+
|
| 51 |
+
You can get the quantization tables of a JPEG with::
|
| 52 |
+
|
| 53 |
+
im.quantization
|
| 54 |
+
|
| 55 |
+
This will return a dict with a number of lists. You can pass this dict
|
| 56 |
+
directly as the qtables argument when saving a JPEG.
|
| 57 |
+
|
| 58 |
+
The quantization table format in presets is a list with sublists. These formats
|
| 59 |
+
are interchangeable.
|
| 60 |
+
|
| 61 |
+
Libjpeg ref.:
|
| 62 |
+
https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
| 63 |
+
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
# fmt: off
|
| 67 |
+
presets = {
|
| 68 |
+
'web_low': {'subsampling': 2, # "4:2:0"
|
| 69 |
+
'quantization': [
|
| 70 |
+
[20, 16, 25, 39, 50, 46, 62, 68,
|
| 71 |
+
16, 18, 23, 38, 38, 53, 65, 68,
|
| 72 |
+
25, 23, 31, 38, 53, 65, 68, 68,
|
| 73 |
+
39, 38, 38, 53, 65, 68, 68, 68,
|
| 74 |
+
50, 38, 53, 65, 68, 68, 68, 68,
|
| 75 |
+
46, 53, 65, 68, 68, 68, 68, 68,
|
| 76 |
+
62, 65, 68, 68, 68, 68, 68, 68,
|
| 77 |
+
68, 68, 68, 68, 68, 68, 68, 68],
|
| 78 |
+
[21, 25, 32, 38, 54, 68, 68, 68,
|
| 79 |
+
25, 28, 24, 38, 54, 68, 68, 68,
|
| 80 |
+
32, 24, 32, 43, 66, 68, 68, 68,
|
| 81 |
+
38, 38, 43, 53, 68, 68, 68, 68,
|
| 82 |
+
54, 54, 66, 68, 68, 68, 68, 68,
|
| 83 |
+
68, 68, 68, 68, 68, 68, 68, 68,
|
| 84 |
+
68, 68, 68, 68, 68, 68, 68, 68,
|
| 85 |
+
68, 68, 68, 68, 68, 68, 68, 68]
|
| 86 |
+
]},
|
| 87 |
+
'web_medium': {'subsampling': 2, # "4:2:0"
|
| 88 |
+
'quantization': [
|
| 89 |
+
[16, 11, 11, 16, 23, 27, 31, 30,
|
| 90 |
+
11, 12, 12, 15, 20, 23, 23, 30,
|
| 91 |
+
11, 12, 13, 16, 23, 26, 35, 47,
|
| 92 |
+
16, 15, 16, 23, 26, 37, 47, 64,
|
| 93 |
+
23, 20, 23, 26, 39, 51, 64, 64,
|
| 94 |
+
27, 23, 26, 37, 51, 64, 64, 64,
|
| 95 |
+
31, 23, 35, 47, 64, 64, 64, 64,
|
| 96 |
+
30, 30, 47, 64, 64, 64, 64, 64],
|
| 97 |
+
[17, 15, 17, 21, 20, 26, 38, 48,
|
| 98 |
+
15, 19, 18, 17, 20, 26, 35, 43,
|
| 99 |
+
17, 18, 20, 22, 26, 30, 46, 53,
|
| 100 |
+
21, 17, 22, 28, 30, 39, 53, 64,
|
| 101 |
+
20, 20, 26, 30, 39, 48, 64, 64,
|
| 102 |
+
26, 26, 30, 39, 48, 63, 64, 64,
|
| 103 |
+
38, 35, 46, 53, 64, 64, 64, 64,
|
| 104 |
+
48, 43, 53, 64, 64, 64, 64, 64]
|
| 105 |
+
]},
|
| 106 |
+
'web_high': {'subsampling': 0, # "4:4:4"
|
| 107 |
+
'quantization': [
|
| 108 |
+
[6, 4, 4, 6, 9, 11, 12, 16,
|
| 109 |
+
4, 5, 5, 6, 8, 10, 12, 12,
|
| 110 |
+
4, 5, 5, 6, 10, 12, 14, 19,
|
| 111 |
+
6, 6, 6, 11, 12, 15, 19, 28,
|
| 112 |
+
9, 8, 10, 12, 16, 20, 27, 31,
|
| 113 |
+
11, 10, 12, 15, 20, 27, 31, 31,
|
| 114 |
+
12, 12, 14, 19, 27, 31, 31, 31,
|
| 115 |
+
16, 12, 19, 28, 31, 31, 31, 31],
|
| 116 |
+
[7, 7, 13, 24, 26, 31, 31, 31,
|
| 117 |
+
7, 12, 16, 21, 31, 31, 31, 31,
|
| 118 |
+
13, 16, 17, 31, 31, 31, 31, 31,
|
| 119 |
+
24, 21, 31, 31, 31, 31, 31, 31,
|
| 120 |
+
26, 31, 31, 31, 31, 31, 31, 31,
|
| 121 |
+
31, 31, 31, 31, 31, 31, 31, 31,
|
| 122 |
+
31, 31, 31, 31, 31, 31, 31, 31,
|
| 123 |
+
31, 31, 31, 31, 31, 31, 31, 31]
|
| 124 |
+
]},
|
| 125 |
+
'web_very_high': {'subsampling': 0, # "4:4:4"
|
| 126 |
+
'quantization': [
|
| 127 |
+
[2, 2, 2, 2, 3, 4, 5, 6,
|
| 128 |
+
2, 2, 2, 2, 3, 4, 5, 6,
|
| 129 |
+
2, 2, 2, 2, 4, 5, 7, 9,
|
| 130 |
+
2, 2, 2, 4, 5, 7, 9, 12,
|
| 131 |
+
3, 3, 4, 5, 8, 10, 12, 12,
|
| 132 |
+
4, 4, 5, 7, 10, 12, 12, 12,
|
| 133 |
+
5, 5, 7, 9, 12, 12, 12, 12,
|
| 134 |
+
6, 6, 9, 12, 12, 12, 12, 12],
|
| 135 |
+
[3, 3, 5, 9, 13, 15, 15, 15,
|
| 136 |
+
3, 4, 6, 11, 14, 12, 12, 12,
|
| 137 |
+
5, 6, 9, 14, 12, 12, 12, 12,
|
| 138 |
+
9, 11, 14, 12, 12, 12, 12, 12,
|
| 139 |
+
13, 14, 12, 12, 12, 12, 12, 12,
|
| 140 |
+
15, 12, 12, 12, 12, 12, 12, 12,
|
| 141 |
+
15, 12, 12, 12, 12, 12, 12, 12,
|
| 142 |
+
15, 12, 12, 12, 12, 12, 12, 12]
|
| 143 |
+
]},
|
| 144 |
+
'web_maximum': {'subsampling': 0, # "4:4:4"
|
| 145 |
+
'quantization': [
|
| 146 |
+
[1, 1, 1, 1, 1, 1, 1, 1,
|
| 147 |
+
1, 1, 1, 1, 1, 1, 1, 1,
|
| 148 |
+
1, 1, 1, 1, 1, 1, 1, 2,
|
| 149 |
+
1, 1, 1, 1, 1, 1, 2, 2,
|
| 150 |
+
1, 1, 1, 1, 1, 2, 2, 3,
|
| 151 |
+
1, 1, 1, 1, 2, 2, 3, 3,
|
| 152 |
+
1, 1, 1, 2, 2, 3, 3, 3,
|
| 153 |
+
1, 1, 2, 2, 3, 3, 3, 3],
|
| 154 |
+
[1, 1, 1, 2, 2, 3, 3, 3,
|
| 155 |
+
1, 1, 1, 2, 3, 3, 3, 3,
|
| 156 |
+
1, 1, 1, 3, 3, 3, 3, 3,
|
| 157 |
+
2, 2, 3, 3, 3, 3, 3, 3,
|
| 158 |
+
2, 3, 3, 3, 3, 3, 3, 3,
|
| 159 |
+
3, 3, 3, 3, 3, 3, 3, 3,
|
| 160 |
+
3, 3, 3, 3, 3, 3, 3, 3,
|
| 161 |
+
3, 3, 3, 3, 3, 3, 3, 3]
|
| 162 |
+
]},
|
| 163 |
+
'low': {'subsampling': 2, # "4:2:0"
|
| 164 |
+
'quantization': [
|
| 165 |
+
[18, 14, 14, 21, 30, 35, 34, 17,
|
| 166 |
+
14, 16, 16, 19, 26, 23, 12, 12,
|
| 167 |
+
14, 16, 17, 21, 23, 12, 12, 12,
|
| 168 |
+
21, 19, 21, 23, 12, 12, 12, 12,
|
| 169 |
+
30, 26, 23, 12, 12, 12, 12, 12,
|
| 170 |
+
35, 23, 12, 12, 12, 12, 12, 12,
|
| 171 |
+
34, 12, 12, 12, 12, 12, 12, 12,
|
| 172 |
+
17, 12, 12, 12, 12, 12, 12, 12],
|
| 173 |
+
[20, 19, 22, 27, 20, 20, 17, 17,
|
| 174 |
+
19, 25, 23, 14, 14, 12, 12, 12,
|
| 175 |
+
22, 23, 14, 14, 12, 12, 12, 12,
|
| 176 |
+
27, 14, 14, 12, 12, 12, 12, 12,
|
| 177 |
+
20, 14, 12, 12, 12, 12, 12, 12,
|
| 178 |
+
20, 12, 12, 12, 12, 12, 12, 12,
|
| 179 |
+
17, 12, 12, 12, 12, 12, 12, 12,
|
| 180 |
+
17, 12, 12, 12, 12, 12, 12, 12]
|
| 181 |
+
]},
|
| 182 |
+
'medium': {'subsampling': 2, # "4:2:0"
|
| 183 |
+
'quantization': [
|
| 184 |
+
[12, 8, 8, 12, 17, 21, 24, 17,
|
| 185 |
+
8, 9, 9, 11, 15, 19, 12, 12,
|
| 186 |
+
8, 9, 10, 12, 19, 12, 12, 12,
|
| 187 |
+
12, 11, 12, 21, 12, 12, 12, 12,
|
| 188 |
+
17, 15, 19, 12, 12, 12, 12, 12,
|
| 189 |
+
21, 19, 12, 12, 12, 12, 12, 12,
|
| 190 |
+
24, 12, 12, 12, 12, 12, 12, 12,
|
| 191 |
+
17, 12, 12, 12, 12, 12, 12, 12],
|
| 192 |
+
[13, 11, 13, 16, 20, 20, 17, 17,
|
| 193 |
+
11, 14, 14, 14, 14, 12, 12, 12,
|
| 194 |
+
13, 14, 14, 14, 12, 12, 12, 12,
|
| 195 |
+
16, 14, 14, 12, 12, 12, 12, 12,
|
| 196 |
+
20, 14, 12, 12, 12, 12, 12, 12,
|
| 197 |
+
20, 12, 12, 12, 12, 12, 12, 12,
|
| 198 |
+
17, 12, 12, 12, 12, 12, 12, 12,
|
| 199 |
+
17, 12, 12, 12, 12, 12, 12, 12]
|
| 200 |
+
]},
|
| 201 |
+
'high': {'subsampling': 0, # "4:4:4"
|
| 202 |
+
'quantization': [
|
| 203 |
+
[6, 4, 4, 6, 9, 11, 12, 16,
|
| 204 |
+
4, 5, 5, 6, 8, 10, 12, 12,
|
| 205 |
+
4, 5, 5, 6, 10, 12, 12, 12,
|
| 206 |
+
6, 6, 6, 11, 12, 12, 12, 12,
|
| 207 |
+
9, 8, 10, 12, 12, 12, 12, 12,
|
| 208 |
+
11, 10, 12, 12, 12, 12, 12, 12,
|
| 209 |
+
12, 12, 12, 12, 12, 12, 12, 12,
|
| 210 |
+
16, 12, 12, 12, 12, 12, 12, 12],
|
| 211 |
+
[7, 7, 13, 24, 20, 20, 17, 17,
|
| 212 |
+
7, 12, 16, 14, 14, 12, 12, 12,
|
| 213 |
+
13, 16, 14, 14, 12, 12, 12, 12,
|
| 214 |
+
24, 14, 14, 12, 12, 12, 12, 12,
|
| 215 |
+
20, 14, 12, 12, 12, 12, 12, 12,
|
| 216 |
+
20, 12, 12, 12, 12, 12, 12, 12,
|
| 217 |
+
17, 12, 12, 12, 12, 12, 12, 12,
|
| 218 |
+
17, 12, 12, 12, 12, 12, 12, 12]
|
| 219 |
+
]},
|
| 220 |
+
'maximum': {'subsampling': 0, # "4:4:4"
|
| 221 |
+
'quantization': [
|
| 222 |
+
[2, 2, 2, 2, 3, 4, 5, 6,
|
| 223 |
+
2, 2, 2, 2, 3, 4, 5, 6,
|
| 224 |
+
2, 2, 2, 2, 4, 5, 7, 9,
|
| 225 |
+
2, 2, 2, 4, 5, 7, 9, 12,
|
| 226 |
+
3, 3, 4, 5, 8, 10, 12, 12,
|
| 227 |
+
4, 4, 5, 7, 10, 12, 12, 12,
|
| 228 |
+
5, 5, 7, 9, 12, 12, 12, 12,
|
| 229 |
+
6, 6, 9, 12, 12, 12, 12, 12],
|
| 230 |
+
[3, 3, 5, 9, 13, 15, 15, 15,
|
| 231 |
+
3, 4, 6, 10, 14, 12, 12, 12,
|
| 232 |
+
5, 6, 9, 14, 12, 12, 12, 12,
|
| 233 |
+
9, 10, 14, 12, 12, 12, 12, 12,
|
| 234 |
+
13, 14, 12, 12, 12, 12, 12, 12,
|
| 235 |
+
15, 12, 12, 12, 12, 12, 12, 12,
|
| 236 |
+
15, 12, 12, 12, 12, 12, 12, 12,
|
| 237 |
+
15, 12, 12, 12, 12, 12, 12, 12]
|
| 238 |
+
]},
|
| 239 |
+
}
|
| 240 |
+
# fmt: on
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/MicImagePlugin.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# Microsoft Image Composer support for PIL
|
| 6 |
+
#
|
| 7 |
+
# Notes:
|
| 8 |
+
# uses TiffImagePlugin.py to read the actual image streams
|
| 9 |
+
#
|
| 10 |
+
# History:
|
| 11 |
+
# 97-01-20 fl Created
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 14 |
+
# Copyright (c) Fredrik Lundh 1997.
|
| 15 |
+
#
|
| 16 |
+
# See the README file for information on usage and redistribution.
|
| 17 |
+
#
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
import olefile
|
| 21 |
+
|
| 22 |
+
from . import Image, TiffImagePlugin
|
| 23 |
+
|
| 24 |
+
#
|
| 25 |
+
# --------------------------------------------------------------------
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _accept(prefix):
|
| 29 |
+
return prefix[:8] == olefile.MAGIC
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
##
|
| 33 |
+
# Image plugin for Microsoft's Image Composer file format.
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class MicImageFile(TiffImagePlugin.TiffImageFile):
|
| 37 |
+
|
| 38 |
+
format = "MIC"
|
| 39 |
+
format_description = "Microsoft Image Composer"
|
| 40 |
+
_close_exclusive_fp_after_loading = False
|
| 41 |
+
|
| 42 |
+
def _open(self):
|
| 43 |
+
|
| 44 |
+
# read the OLE directory and see if this is a likely
|
| 45 |
+
# to be a Microsoft Image Composer file
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
self.ole = olefile.OleFileIO(self.fp)
|
| 49 |
+
except OSError as e:
|
| 50 |
+
raise SyntaxError("not an MIC file; invalid OLE file") from e
|
| 51 |
+
|
| 52 |
+
# find ACI subfiles with Image members (maybe not the
|
| 53 |
+
# best way to identify MIC files, but what the... ;-)
|
| 54 |
+
|
| 55 |
+
self.images = []
|
| 56 |
+
for path in self.ole.listdir():
|
| 57 |
+
if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image":
|
| 58 |
+
self.images.append(path)
|
| 59 |
+
|
| 60 |
+
# if we didn't find any images, this is probably not
|
| 61 |
+
# an MIC file.
|
| 62 |
+
if not self.images:
|
| 63 |
+
raise SyntaxError("not an MIC file; no image entries")
|
| 64 |
+
|
| 65 |
+
self.__fp = self.fp
|
| 66 |
+
self.frame = None
|
| 67 |
+
self._n_frames = len(self.images)
|
| 68 |
+
self.is_animated = self._n_frames > 1
|
| 69 |
+
|
| 70 |
+
if len(self.images) > 1:
|
| 71 |
+
self._category = Image.CONTAINER
|
| 72 |
+
|
| 73 |
+
self.seek(0)
|
| 74 |
+
|
| 75 |
+
def seek(self, frame):
|
| 76 |
+
if not self._seek_check(frame):
|
| 77 |
+
return
|
| 78 |
+
try:
|
| 79 |
+
filename = self.images[frame]
|
| 80 |
+
except IndexError as e:
|
| 81 |
+
raise EOFError("no such frame") from e
|
| 82 |
+
|
| 83 |
+
self.fp = self.ole.openstream(filename)
|
| 84 |
+
|
| 85 |
+
TiffImagePlugin.TiffImageFile._open(self)
|
| 86 |
+
|
| 87 |
+
self.frame = frame
|
| 88 |
+
|
| 89 |
+
def tell(self):
|
| 90 |
+
return self.frame
|
| 91 |
+
|
| 92 |
+
def _close__fp(self):
|
| 93 |
+
try:
|
| 94 |
+
if self.__fp != self.fp:
|
| 95 |
+
self.__fp.close()
|
| 96 |
+
except AttributeError:
|
| 97 |
+
pass
|
| 98 |
+
finally:
|
| 99 |
+
self.__fp = None
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
#
|
| 103 |
+
# --------------------------------------------------------------------
|
| 104 |
+
|
| 105 |
+
Image.register_open(MicImageFile.format, MicImageFile, _accept)
|
| 106 |
+
|
| 107 |
+
Image.register_extension(MicImageFile.format, ".mic")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/MspImagePlugin.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
#
|
| 4 |
+
# MSP file handling
|
| 5 |
+
#
|
| 6 |
+
# This is the format used by the Paint program in Windows 1 and 2.
|
| 7 |
+
#
|
| 8 |
+
# History:
|
| 9 |
+
# 95-09-05 fl Created
|
| 10 |
+
# 97-01-03 fl Read/write MSP images
|
| 11 |
+
# 17-02-21 es Fixed RLE interpretation
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 14 |
+
# Copyright (c) Fredrik Lundh 1995-97.
|
| 15 |
+
# Copyright (c) Eric Soroos 2017.
|
| 16 |
+
#
|
| 17 |
+
# See the README file for information on usage and redistribution.
|
| 18 |
+
#
|
| 19 |
+
# More info on this format: https://archive.org/details/gg243631
|
| 20 |
+
# Page 313:
|
| 21 |
+
# Figure 205. Windows Paint Version 1: "DanM" Format
|
| 22 |
+
# Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03
|
| 23 |
+
#
|
| 24 |
+
# See also: https://www.fileformat.info/format/mspaint/egff.htm
|
| 25 |
+
|
| 26 |
+
import io
|
| 27 |
+
import struct
|
| 28 |
+
|
| 29 |
+
from . import Image, ImageFile
|
| 30 |
+
from ._binary import i16le as i16
|
| 31 |
+
from ._binary import o16le as o16
|
| 32 |
+
|
| 33 |
+
#
|
| 34 |
+
# read MSP files
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _accept(prefix):
|
| 38 |
+
return prefix[:4] in [b"DanM", b"LinS"]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
##
|
| 42 |
+
# Image plugin for Windows MSP images. This plugin supports both
|
| 43 |
+
# uncompressed (Windows 1.0).
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class MspImageFile(ImageFile.ImageFile):
|
| 47 |
+
|
| 48 |
+
format = "MSP"
|
| 49 |
+
format_description = "Windows Paint"
|
| 50 |
+
|
| 51 |
+
def _open(self):
|
| 52 |
+
|
| 53 |
+
# Header
|
| 54 |
+
s = self.fp.read(32)
|
| 55 |
+
if not _accept(s):
|
| 56 |
+
raise SyntaxError("not an MSP file")
|
| 57 |
+
|
| 58 |
+
# Header checksum
|
| 59 |
+
checksum = 0
|
| 60 |
+
for i in range(0, 32, 2):
|
| 61 |
+
checksum = checksum ^ i16(s, i)
|
| 62 |
+
if checksum != 0:
|
| 63 |
+
raise SyntaxError("bad MSP checksum")
|
| 64 |
+
|
| 65 |
+
self.mode = "1"
|
| 66 |
+
self._size = i16(s, 4), i16(s, 6)
|
| 67 |
+
|
| 68 |
+
if s[:4] == b"DanM":
|
| 69 |
+
self.tile = [("raw", (0, 0) + self.size, 32, ("1", 0, 1))]
|
| 70 |
+
else:
|
| 71 |
+
self.tile = [("MSP", (0, 0) + self.size, 32, None)]
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class MspDecoder(ImageFile.PyDecoder):
|
| 75 |
+
# The algo for the MSP decoder is from
|
| 76 |
+
# https://www.fileformat.info/format/mspaint/egff.htm
|
| 77 |
+
# cc-by-attribution -- That page references is taken from the
|
| 78 |
+
# Encyclopedia of Graphics File Formats and is licensed by
|
| 79 |
+
# O'Reilly under the Creative Common/Attribution license
|
| 80 |
+
#
|
| 81 |
+
# For RLE encoded files, the 32byte header is followed by a scan
|
| 82 |
+
# line map, encoded as one 16bit word of encoded byte length per
|
| 83 |
+
# line.
|
| 84 |
+
#
|
| 85 |
+
# NOTE: the encoded length of the line can be 0. This was not
|
| 86 |
+
# handled in the previous version of this encoder, and there's no
|
| 87 |
+
# mention of how to handle it in the documentation. From the few
|
| 88 |
+
# examples I've seen, I've assumed that it is a fill of the
|
| 89 |
+
# background color, in this case, white.
|
| 90 |
+
#
|
| 91 |
+
#
|
| 92 |
+
# Pseudocode of the decoder:
|
| 93 |
+
# Read a BYTE value as the RunType
|
| 94 |
+
# If the RunType value is zero
|
| 95 |
+
# Read next byte as the RunCount
|
| 96 |
+
# Read the next byte as the RunValue
|
| 97 |
+
# Write the RunValue byte RunCount times
|
| 98 |
+
# If the RunType value is non-zero
|
| 99 |
+
# Use this value as the RunCount
|
| 100 |
+
# Read and write the next RunCount bytes literally
|
| 101 |
+
#
|
| 102 |
+
# e.g.:
|
| 103 |
+
# 0x00 03 ff 05 00 01 02 03 04
|
| 104 |
+
# would yield the bytes:
|
| 105 |
+
# 0xff ff ff 00 01 02 03 04
|
| 106 |
+
#
|
| 107 |
+
# which are then interpreted as a bit packed mode '1' image
|
| 108 |
+
|
| 109 |
+
_pulls_fd = True
|
| 110 |
+
|
| 111 |
+
def decode(self, buffer):
|
| 112 |
+
|
| 113 |
+
img = io.BytesIO()
|
| 114 |
+
blank_line = bytearray((0xFF,) * ((self.state.xsize + 7) // 8))
|
| 115 |
+
try:
|
| 116 |
+
self.fd.seek(32)
|
| 117 |
+
rowmap = struct.unpack_from(
|
| 118 |
+
f"<{self.state.ysize}H", self.fd.read(self.state.ysize * 2)
|
| 119 |
+
)
|
| 120 |
+
except struct.error as e:
|
| 121 |
+
raise OSError("Truncated MSP file in row map") from e
|
| 122 |
+
|
| 123 |
+
for x, rowlen in enumerate(rowmap):
|
| 124 |
+
try:
|
| 125 |
+
if rowlen == 0:
|
| 126 |
+
img.write(blank_line)
|
| 127 |
+
continue
|
| 128 |
+
row = self.fd.read(rowlen)
|
| 129 |
+
if len(row) != rowlen:
|
| 130 |
+
raise OSError(
|
| 131 |
+
"Truncated MSP file, expected %d bytes on row %s", (rowlen, x)
|
| 132 |
+
)
|
| 133 |
+
idx = 0
|
| 134 |
+
while idx < rowlen:
|
| 135 |
+
runtype = row[idx]
|
| 136 |
+
idx += 1
|
| 137 |
+
if runtype == 0:
|
| 138 |
+
(runcount, runval) = struct.unpack_from("Bc", row, idx)
|
| 139 |
+
img.write(runval * runcount)
|
| 140 |
+
idx += 2
|
| 141 |
+
else:
|
| 142 |
+
runcount = runtype
|
| 143 |
+
img.write(row[idx : idx + runcount])
|
| 144 |
+
idx += runcount
|
| 145 |
+
|
| 146 |
+
except struct.error as e:
|
| 147 |
+
raise OSError(f"Corrupted MSP file in row {x}") from e
|
| 148 |
+
|
| 149 |
+
self.set_as_raw(img.getvalue(), ("1", 0, 1))
|
| 150 |
+
|
| 151 |
+
return -1, 0
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
Image.register_decoder("MSP", MspDecoder)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
#
|
| 158 |
+
# write MSP files (uncompressed only)
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def _save(im, fp, filename):
|
| 162 |
+
|
| 163 |
+
if im.mode != "1":
|
| 164 |
+
raise OSError(f"cannot write mode {im.mode} as MSP")
|
| 165 |
+
|
| 166 |
+
# create MSP header
|
| 167 |
+
header = [0] * 16
|
| 168 |
+
|
| 169 |
+
header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1
|
| 170 |
+
header[2], header[3] = im.size
|
| 171 |
+
header[4], header[5] = 1, 1
|
| 172 |
+
header[6], header[7] = 1, 1
|
| 173 |
+
header[8], header[9] = im.size
|
| 174 |
+
|
| 175 |
+
checksum = 0
|
| 176 |
+
for h in header:
|
| 177 |
+
checksum = checksum ^ h
|
| 178 |
+
header[12] = checksum # FIXME: is this the right field?
|
| 179 |
+
|
| 180 |
+
# header
|
| 181 |
+
for h in header:
|
| 182 |
+
fp.write(o16(h))
|
| 183 |
+
|
| 184 |
+
# image body
|
| 185 |
+
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 32, ("1", 0, 1))])
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
#
|
| 189 |
+
# registry
|
| 190 |
+
|
| 191 |
+
Image.register_open(MspImageFile.format, MspImageFile, _accept)
|
| 192 |
+
Image.register_save(MspImageFile.format, _save)
|
| 193 |
+
|
| 194 |
+
Image.register_extension(MspImageFile.format, ".msp")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PaletteFile.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# stuff to read simple, teragon-style palette files
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 97-08-23 fl Created
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 11 |
+
# Copyright (c) Fredrik Lundh 1997.
|
| 12 |
+
#
|
| 13 |
+
# See the README file for information on usage and redistribution.
|
| 14 |
+
#
|
| 15 |
+
|
| 16 |
+
from ._binary import o8
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class PaletteFile:
|
| 20 |
+
"""File handler for Teragon-style palette files."""
|
| 21 |
+
|
| 22 |
+
rawmode = "RGB"
|
| 23 |
+
|
| 24 |
+
def __init__(self, fp):
|
| 25 |
+
|
| 26 |
+
self.palette = [(i, i, i) for i in range(256)]
|
| 27 |
+
|
| 28 |
+
while True:
|
| 29 |
+
|
| 30 |
+
s = fp.readline()
|
| 31 |
+
|
| 32 |
+
if not s:
|
| 33 |
+
break
|
| 34 |
+
if s[0:1] == b"#":
|
| 35 |
+
continue
|
| 36 |
+
if len(s) > 100:
|
| 37 |
+
raise SyntaxError("bad palette file")
|
| 38 |
+
|
| 39 |
+
v = [int(x) for x in s.split()]
|
| 40 |
+
try:
|
| 41 |
+
[i, r, g, b] = v
|
| 42 |
+
except ValueError:
|
| 43 |
+
[i, r] = v
|
| 44 |
+
g = b = r
|
| 45 |
+
|
| 46 |
+
if 0 <= i <= 255:
|
| 47 |
+
self.palette[i] = o8(r) + o8(g) + o8(b)
|
| 48 |
+
|
| 49 |
+
self.palette = b"".join(self.palette)
|
| 50 |
+
|
| 51 |
+
def getpalette(self):
|
| 52 |
+
|
| 53 |
+
return self.palette, self.rawmode
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PcdImagePlugin.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# PCD file handling
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 96-05-10 fl Created
|
| 9 |
+
# 96-05-27 fl Added draft mode (128x192, 256x384)
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 12 |
+
# Copyright (c) Fredrik Lundh 1996.
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
from . import Image, ImageFile
|
| 19 |
+
|
| 20 |
+
##
|
| 21 |
+
# Image plugin for PhotoCD images. This plugin only reads the 768x512
|
| 22 |
+
# image from the file; higher resolutions are encoded in a proprietary
|
| 23 |
+
# encoding.
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class PcdImageFile(ImageFile.ImageFile):
|
| 27 |
+
|
| 28 |
+
format = "PCD"
|
| 29 |
+
format_description = "Kodak PhotoCD"
|
| 30 |
+
|
| 31 |
+
def _open(self):
|
| 32 |
+
|
| 33 |
+
# rough
|
| 34 |
+
self.fp.seek(2048)
|
| 35 |
+
s = self.fp.read(2048)
|
| 36 |
+
|
| 37 |
+
if s[:4] != b"PCD_":
|
| 38 |
+
raise SyntaxError("not a PCD file")
|
| 39 |
+
|
| 40 |
+
orientation = s[1538] & 3
|
| 41 |
+
self.tile_post_rotate = None
|
| 42 |
+
if orientation == 1:
|
| 43 |
+
self.tile_post_rotate = 90
|
| 44 |
+
elif orientation == 3:
|
| 45 |
+
self.tile_post_rotate = -90
|
| 46 |
+
|
| 47 |
+
self.mode = "RGB"
|
| 48 |
+
self._size = 768, 512 # FIXME: not correct for rotated images!
|
| 49 |
+
self.tile = [("pcd", (0, 0) + self.size, 96 * 2048, None)]
|
| 50 |
+
|
| 51 |
+
def load_end(self):
|
| 52 |
+
if self.tile_post_rotate:
|
| 53 |
+
# Handle rotated PCDs
|
| 54 |
+
self.im = self.im.rotate(self.tile_post_rotate)
|
| 55 |
+
self._size = self.im.size
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
#
|
| 59 |
+
# registry
|
| 60 |
+
|
| 61 |
+
Image.register_open(PcdImageFile.format, PcdImageFile)
|
| 62 |
+
|
| 63 |
+
Image.register_extension(PcdImageFile.format, ".pcd")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PcfFontFile.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# THIS IS WORK IN PROGRESS
|
| 3 |
+
#
|
| 4 |
+
# The Python Imaging Library
|
| 5 |
+
# $Id$
|
| 6 |
+
#
|
| 7 |
+
# portable compiled font file parser
|
| 8 |
+
#
|
| 9 |
+
# history:
|
| 10 |
+
# 1997-08-19 fl created
|
| 11 |
+
# 2003-09-13 fl fixed loading of unicode fonts
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) 1997-2003 by Secret Labs AB.
|
| 14 |
+
# Copyright (c) 1997-2003 by Fredrik Lundh.
|
| 15 |
+
#
|
| 16 |
+
# See the README file for information on usage and redistribution.
|
| 17 |
+
#
|
| 18 |
+
|
| 19 |
+
import io
|
| 20 |
+
|
| 21 |
+
from . import FontFile, Image
|
| 22 |
+
from ._binary import i8
|
| 23 |
+
from ._binary import i16be as b16
|
| 24 |
+
from ._binary import i16le as l16
|
| 25 |
+
from ._binary import i32be as b32
|
| 26 |
+
from ._binary import i32le as l32
|
| 27 |
+
|
| 28 |
+
# --------------------------------------------------------------------
|
| 29 |
+
# declarations
|
| 30 |
+
|
| 31 |
+
PCF_MAGIC = 0x70636601 # "\x01fcp"
|
| 32 |
+
|
| 33 |
+
PCF_PROPERTIES = 1 << 0
|
| 34 |
+
PCF_ACCELERATORS = 1 << 1
|
| 35 |
+
PCF_METRICS = 1 << 2
|
| 36 |
+
PCF_BITMAPS = 1 << 3
|
| 37 |
+
PCF_INK_METRICS = 1 << 4
|
| 38 |
+
PCF_BDF_ENCODINGS = 1 << 5
|
| 39 |
+
PCF_SWIDTHS = 1 << 6
|
| 40 |
+
PCF_GLYPH_NAMES = 1 << 7
|
| 41 |
+
PCF_BDF_ACCELERATORS = 1 << 8
|
| 42 |
+
|
| 43 |
+
BYTES_PER_ROW = [
|
| 44 |
+
lambda bits: ((bits + 7) >> 3),
|
| 45 |
+
lambda bits: ((bits + 15) >> 3) & ~1,
|
| 46 |
+
lambda bits: ((bits + 31) >> 3) & ~3,
|
| 47 |
+
lambda bits: ((bits + 63) >> 3) & ~7,
|
| 48 |
+
]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def sz(s, o):
|
| 52 |
+
return s[o : s.index(b"\0", o)]
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class PcfFontFile(FontFile.FontFile):
|
| 56 |
+
"""Font file plugin for the X11 PCF format."""
|
| 57 |
+
|
| 58 |
+
name = "name"
|
| 59 |
+
|
| 60 |
+
def __init__(self, fp, charset_encoding="iso8859-1"):
|
| 61 |
+
|
| 62 |
+
self.charset_encoding = charset_encoding
|
| 63 |
+
|
| 64 |
+
magic = l32(fp.read(4))
|
| 65 |
+
if magic != PCF_MAGIC:
|
| 66 |
+
raise SyntaxError("not a PCF file")
|
| 67 |
+
|
| 68 |
+
super().__init__()
|
| 69 |
+
|
| 70 |
+
count = l32(fp.read(4))
|
| 71 |
+
self.toc = {}
|
| 72 |
+
for i in range(count):
|
| 73 |
+
type = l32(fp.read(4))
|
| 74 |
+
self.toc[type] = l32(fp.read(4)), l32(fp.read(4)), l32(fp.read(4))
|
| 75 |
+
|
| 76 |
+
self.fp = fp
|
| 77 |
+
|
| 78 |
+
self.info = self._load_properties()
|
| 79 |
+
|
| 80 |
+
metrics = self._load_metrics()
|
| 81 |
+
bitmaps = self._load_bitmaps(metrics)
|
| 82 |
+
encoding = self._load_encoding()
|
| 83 |
+
|
| 84 |
+
#
|
| 85 |
+
# create glyph structure
|
| 86 |
+
|
| 87 |
+
for ch in range(256):
|
| 88 |
+
ix = encoding[ch]
|
| 89 |
+
if ix is not None:
|
| 90 |
+
x, y, l, r, w, a, d, f = metrics[ix]
|
| 91 |
+
glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix]
|
| 92 |
+
self.glyph[ch] = glyph
|
| 93 |
+
|
| 94 |
+
def _getformat(self, tag):
|
| 95 |
+
|
| 96 |
+
format, size, offset = self.toc[tag]
|
| 97 |
+
|
| 98 |
+
fp = self.fp
|
| 99 |
+
fp.seek(offset)
|
| 100 |
+
|
| 101 |
+
format = l32(fp.read(4))
|
| 102 |
+
|
| 103 |
+
if format & 4:
|
| 104 |
+
i16, i32 = b16, b32
|
| 105 |
+
else:
|
| 106 |
+
i16, i32 = l16, l32
|
| 107 |
+
|
| 108 |
+
return fp, format, i16, i32
|
| 109 |
+
|
| 110 |
+
def _load_properties(self):
|
| 111 |
+
|
| 112 |
+
#
|
| 113 |
+
# font properties
|
| 114 |
+
|
| 115 |
+
properties = {}
|
| 116 |
+
|
| 117 |
+
fp, format, i16, i32 = self._getformat(PCF_PROPERTIES)
|
| 118 |
+
|
| 119 |
+
nprops = i32(fp.read(4))
|
| 120 |
+
|
| 121 |
+
# read property description
|
| 122 |
+
p = []
|
| 123 |
+
for i in range(nprops):
|
| 124 |
+
p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
|
| 125 |
+
if nprops & 3:
|
| 126 |
+
fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad
|
| 127 |
+
|
| 128 |
+
data = fp.read(i32(fp.read(4)))
|
| 129 |
+
|
| 130 |
+
for k, s, v in p:
|
| 131 |
+
k = sz(data, k)
|
| 132 |
+
if s:
|
| 133 |
+
v = sz(data, v)
|
| 134 |
+
properties[k] = v
|
| 135 |
+
|
| 136 |
+
return properties
|
| 137 |
+
|
| 138 |
+
def _load_metrics(self):
|
| 139 |
+
|
| 140 |
+
#
|
| 141 |
+
# font metrics
|
| 142 |
+
|
| 143 |
+
metrics = []
|
| 144 |
+
|
| 145 |
+
fp, format, i16, i32 = self._getformat(PCF_METRICS)
|
| 146 |
+
|
| 147 |
+
append = metrics.append
|
| 148 |
+
|
| 149 |
+
if (format & 0xFF00) == 0x100:
|
| 150 |
+
|
| 151 |
+
# "compressed" metrics
|
| 152 |
+
for i in range(i16(fp.read(2))):
|
| 153 |
+
left = i8(fp.read(1)) - 128
|
| 154 |
+
right = i8(fp.read(1)) - 128
|
| 155 |
+
width = i8(fp.read(1)) - 128
|
| 156 |
+
ascent = i8(fp.read(1)) - 128
|
| 157 |
+
descent = i8(fp.read(1)) - 128
|
| 158 |
+
xsize = right - left
|
| 159 |
+
ysize = ascent + descent
|
| 160 |
+
append((xsize, ysize, left, right, width, ascent, descent, 0))
|
| 161 |
+
|
| 162 |
+
else:
|
| 163 |
+
|
| 164 |
+
# "jumbo" metrics
|
| 165 |
+
for i in range(i32(fp.read(4))):
|
| 166 |
+
left = i16(fp.read(2))
|
| 167 |
+
right = i16(fp.read(2))
|
| 168 |
+
width = i16(fp.read(2))
|
| 169 |
+
ascent = i16(fp.read(2))
|
| 170 |
+
descent = i16(fp.read(2))
|
| 171 |
+
attributes = i16(fp.read(2))
|
| 172 |
+
xsize = right - left
|
| 173 |
+
ysize = ascent + descent
|
| 174 |
+
append((xsize, ysize, left, right, width, ascent, descent, attributes))
|
| 175 |
+
|
| 176 |
+
return metrics
|
| 177 |
+
|
| 178 |
+
def _load_bitmaps(self, metrics):
|
| 179 |
+
|
| 180 |
+
#
|
| 181 |
+
# bitmap data
|
| 182 |
+
|
| 183 |
+
bitmaps = []
|
| 184 |
+
|
| 185 |
+
fp, format, i16, i32 = self._getformat(PCF_BITMAPS)
|
| 186 |
+
|
| 187 |
+
nbitmaps = i32(fp.read(4))
|
| 188 |
+
|
| 189 |
+
if nbitmaps != len(metrics):
|
| 190 |
+
raise OSError("Wrong number of bitmaps")
|
| 191 |
+
|
| 192 |
+
offsets = []
|
| 193 |
+
for i in range(nbitmaps):
|
| 194 |
+
offsets.append(i32(fp.read(4)))
|
| 195 |
+
|
| 196 |
+
bitmapSizes = []
|
| 197 |
+
for i in range(4):
|
| 198 |
+
bitmapSizes.append(i32(fp.read(4)))
|
| 199 |
+
|
| 200 |
+
# byteorder = format & 4 # non-zero => MSB
|
| 201 |
+
bitorder = format & 8 # non-zero => MSB
|
| 202 |
+
padindex = format & 3
|
| 203 |
+
|
| 204 |
+
bitmapsize = bitmapSizes[padindex]
|
| 205 |
+
offsets.append(bitmapsize)
|
| 206 |
+
|
| 207 |
+
data = fp.read(bitmapsize)
|
| 208 |
+
|
| 209 |
+
pad = BYTES_PER_ROW[padindex]
|
| 210 |
+
mode = "1;R"
|
| 211 |
+
if bitorder:
|
| 212 |
+
mode = "1"
|
| 213 |
+
|
| 214 |
+
for i in range(nbitmaps):
|
| 215 |
+
x, y, l, r, w, a, d, f = metrics[i]
|
| 216 |
+
b, e = offsets[i], offsets[i + 1]
|
| 217 |
+
bitmaps.append(Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x)))
|
| 218 |
+
|
| 219 |
+
return bitmaps
|
| 220 |
+
|
| 221 |
+
def _load_encoding(self):
|
| 222 |
+
|
| 223 |
+
# map character code to bitmap index
|
| 224 |
+
encoding = [None] * 256
|
| 225 |
+
|
| 226 |
+
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
|
| 227 |
+
|
| 228 |
+
firstCol, lastCol = i16(fp.read(2)), i16(fp.read(2))
|
| 229 |
+
firstRow, lastRow = i16(fp.read(2)), i16(fp.read(2))
|
| 230 |
+
|
| 231 |
+
i16(fp.read(2)) # default
|
| 232 |
+
|
| 233 |
+
nencoding = (lastCol - firstCol + 1) * (lastRow - firstRow + 1)
|
| 234 |
+
|
| 235 |
+
encodingOffsets = [i16(fp.read(2)) for _ in range(nencoding)]
|
| 236 |
+
|
| 237 |
+
for i in range(firstCol, len(encoding)):
|
| 238 |
+
try:
|
| 239 |
+
encodingOffset = encodingOffsets[
|
| 240 |
+
ord(bytearray([i]).decode(self.charset_encoding))
|
| 241 |
+
]
|
| 242 |
+
if encodingOffset != 0xFFFF:
|
| 243 |
+
encoding[i] = encodingOffset
|
| 244 |
+
except UnicodeDecodeError:
|
| 245 |
+
# character is not supported in selected encoding
|
| 246 |
+
pass
|
| 247 |
+
|
| 248 |
+
return encoding
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PcxImagePlugin.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# PCX file handling
|
| 6 |
+
#
|
| 7 |
+
# This format was originally used by ZSoft's popular PaintBrush
|
| 8 |
+
# program for the IBM PC. It is also supported by many MS-DOS and
|
| 9 |
+
# Windows applications, including the Windows PaintBrush program in
|
| 10 |
+
# Windows 3.
|
| 11 |
+
#
|
| 12 |
+
# history:
|
| 13 |
+
# 1995-09-01 fl Created
|
| 14 |
+
# 1996-05-20 fl Fixed RGB support
|
| 15 |
+
# 1997-01-03 fl Fixed 2-bit and 4-bit support
|
| 16 |
+
# 1999-02-03 fl Fixed 8-bit support (broken in 1.0b1)
|
| 17 |
+
# 1999-02-07 fl Added write support
|
| 18 |
+
# 2002-06-09 fl Made 2-bit and 4-bit support a bit more robust
|
| 19 |
+
# 2002-07-30 fl Seek from to current position, not beginning of file
|
| 20 |
+
# 2003-06-03 fl Extract DPI settings (info["dpi"])
|
| 21 |
+
#
|
| 22 |
+
# Copyright (c) 1997-2003 by Secret Labs AB.
|
| 23 |
+
# Copyright (c) 1995-2003 by Fredrik Lundh.
|
| 24 |
+
#
|
| 25 |
+
# See the README file for information on usage and redistribution.
|
| 26 |
+
#
|
| 27 |
+
|
| 28 |
+
import io
|
| 29 |
+
import logging
|
| 30 |
+
|
| 31 |
+
from . import Image, ImageFile, ImagePalette
|
| 32 |
+
from ._binary import i16le as i16
|
| 33 |
+
from ._binary import o8
|
| 34 |
+
from ._binary import o16le as o16
|
| 35 |
+
|
| 36 |
+
logger = logging.getLogger(__name__)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _accept(prefix):
|
| 40 |
+
return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
##
|
| 44 |
+
# Image plugin for Paintbrush images.
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class PcxImageFile(ImageFile.ImageFile):
|
| 48 |
+
|
| 49 |
+
format = "PCX"
|
| 50 |
+
format_description = "Paintbrush"
|
| 51 |
+
|
| 52 |
+
def _open(self):
|
| 53 |
+
|
| 54 |
+
# header
|
| 55 |
+
s = self.fp.read(128)
|
| 56 |
+
if not _accept(s):
|
| 57 |
+
raise SyntaxError("not a PCX file")
|
| 58 |
+
|
| 59 |
+
# image
|
| 60 |
+
bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1
|
| 61 |
+
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
|
| 62 |
+
raise SyntaxError("bad PCX image size")
|
| 63 |
+
logger.debug("BBox: %s %s %s %s", *bbox)
|
| 64 |
+
|
| 65 |
+
# format
|
| 66 |
+
version = s[1]
|
| 67 |
+
bits = s[3]
|
| 68 |
+
planes = s[65]
|
| 69 |
+
provided_stride = i16(s, 66)
|
| 70 |
+
logger.debug(
|
| 71 |
+
"PCX version %s, bits %s, planes %s, stride %s",
|
| 72 |
+
version,
|
| 73 |
+
bits,
|
| 74 |
+
planes,
|
| 75 |
+
provided_stride,
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
self.info["dpi"] = i16(s, 12), i16(s, 14)
|
| 79 |
+
|
| 80 |
+
if bits == 1 and planes == 1:
|
| 81 |
+
mode = rawmode = "1"
|
| 82 |
+
|
| 83 |
+
elif bits == 1 and planes in (2, 4):
|
| 84 |
+
mode = "P"
|
| 85 |
+
rawmode = "P;%dL" % planes
|
| 86 |
+
self.palette = ImagePalette.raw("RGB", s[16:64])
|
| 87 |
+
|
| 88 |
+
elif version == 5 and bits == 8 and planes == 1:
|
| 89 |
+
mode = rawmode = "L"
|
| 90 |
+
# FIXME: hey, this doesn't work with the incremental loader !!!
|
| 91 |
+
self.fp.seek(-769, io.SEEK_END)
|
| 92 |
+
s = self.fp.read(769)
|
| 93 |
+
if len(s) == 769 and s[0] == 12:
|
| 94 |
+
# check if the palette is linear greyscale
|
| 95 |
+
for i in range(256):
|
| 96 |
+
if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3:
|
| 97 |
+
mode = rawmode = "P"
|
| 98 |
+
break
|
| 99 |
+
if mode == "P":
|
| 100 |
+
self.palette = ImagePalette.raw("RGB", s[1:])
|
| 101 |
+
self.fp.seek(128)
|
| 102 |
+
|
| 103 |
+
elif version == 5 and bits == 8 and planes == 3:
|
| 104 |
+
mode = "RGB"
|
| 105 |
+
rawmode = "RGB;L"
|
| 106 |
+
|
| 107 |
+
else:
|
| 108 |
+
raise OSError("unknown PCX mode")
|
| 109 |
+
|
| 110 |
+
self.mode = mode
|
| 111 |
+
self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
| 112 |
+
|
| 113 |
+
# Don't trust the passed in stride.
|
| 114 |
+
# Calculate the approximate position for ourselves.
|
| 115 |
+
# CVE-2020-35653
|
| 116 |
+
stride = (self._size[0] * bits + 7) // 8
|
| 117 |
+
|
| 118 |
+
# While the specification states that this must be even,
|
| 119 |
+
# not all images follow this
|
| 120 |
+
if provided_stride != stride:
|
| 121 |
+
stride += stride % 2
|
| 122 |
+
|
| 123 |
+
bbox = (0, 0) + self.size
|
| 124 |
+
logger.debug("size: %sx%s", *self.size)
|
| 125 |
+
|
| 126 |
+
self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
# --------------------------------------------------------------------
|
| 130 |
+
# save PCX files
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
SAVE = {
|
| 134 |
+
# mode: (version, bits, planes, raw mode)
|
| 135 |
+
"1": (2, 1, 1, "1"),
|
| 136 |
+
"L": (5, 8, 1, "L"),
|
| 137 |
+
"P": (5, 8, 1, "P"),
|
| 138 |
+
"RGB": (5, 8, 3, "RGB;L"),
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def _save(im, fp, filename):
|
| 143 |
+
|
| 144 |
+
try:
|
| 145 |
+
version, bits, planes, rawmode = SAVE[im.mode]
|
| 146 |
+
except KeyError as e:
|
| 147 |
+
raise ValueError(f"Cannot save {im.mode} images as PCX") from e
|
| 148 |
+
|
| 149 |
+
# bytes per plane
|
| 150 |
+
stride = (im.size[0] * bits + 7) // 8
|
| 151 |
+
# stride should be even
|
| 152 |
+
stride += stride % 2
|
| 153 |
+
# Stride needs to be kept in sync with the PcxEncode.c version.
|
| 154 |
+
# Ideally it should be passed in in the state, but the bytes value
|
| 155 |
+
# gets overwritten.
|
| 156 |
+
|
| 157 |
+
logger.debug(
|
| 158 |
+
"PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
|
| 159 |
+
im.size[0],
|
| 160 |
+
bits,
|
| 161 |
+
stride,
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
# under windows, we could determine the current screen size with
|
| 165 |
+
# "Image.core.display_mode()[1]", but I think that's overkill...
|
| 166 |
+
|
| 167 |
+
screen = im.size
|
| 168 |
+
|
| 169 |
+
dpi = 100, 100
|
| 170 |
+
|
| 171 |
+
# PCX header
|
| 172 |
+
fp.write(
|
| 173 |
+
o8(10)
|
| 174 |
+
+ o8(version)
|
| 175 |
+
+ o8(1)
|
| 176 |
+
+ o8(bits)
|
| 177 |
+
+ o16(0)
|
| 178 |
+
+ o16(0)
|
| 179 |
+
+ o16(im.size[0] - 1)
|
| 180 |
+
+ o16(im.size[1] - 1)
|
| 181 |
+
+ o16(dpi[0])
|
| 182 |
+
+ o16(dpi[1])
|
| 183 |
+
+ b"\0" * 24
|
| 184 |
+
+ b"\xFF" * 24
|
| 185 |
+
+ b"\0"
|
| 186 |
+
+ o8(planes)
|
| 187 |
+
+ o16(stride)
|
| 188 |
+
+ o16(1)
|
| 189 |
+
+ o16(screen[0])
|
| 190 |
+
+ o16(screen[1])
|
| 191 |
+
+ b"\0" * 54
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
assert fp.tell() == 128
|
| 195 |
+
|
| 196 |
+
ImageFile._save(im, fp, [("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))])
|
| 197 |
+
|
| 198 |
+
if im.mode == "P":
|
| 199 |
+
# colour palette
|
| 200 |
+
fp.write(o8(12))
|
| 201 |
+
fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
|
| 202 |
+
elif im.mode == "L":
|
| 203 |
+
# greyscale palette
|
| 204 |
+
fp.write(o8(12))
|
| 205 |
+
for i in range(256):
|
| 206 |
+
fp.write(o8(i) * 3)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
# --------------------------------------------------------------------
|
| 210 |
+
# registry
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
Image.register_open(PcxImageFile.format, PcxImageFile, _accept)
|
| 214 |
+
Image.register_save(PcxImageFile.format, _save)
|
| 215 |
+
|
| 216 |
+
Image.register_extension(PcxImageFile.format, ".pcx")
|
| 217 |
+
|
| 218 |
+
Image.register_mime(PcxImageFile.format, "image/x-pcx")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PdfImagePlugin.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# PDF (Acrobat) file handling
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1996-07-16 fl Created
|
| 9 |
+
# 1997-01-18 fl Fixed header
|
| 10 |
+
# 2004-02-21 fl Fixes for 1/L/CMYK images, etc.
|
| 11 |
+
# 2004-02-24 fl Fixes for 1 and P images.
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) 1997-2004 by Secret Labs AB. All rights reserved.
|
| 14 |
+
# Copyright (c) 1996-1997 by Fredrik Lundh.
|
| 15 |
+
#
|
| 16 |
+
# See the README file for information on usage and redistribution.
|
| 17 |
+
#
|
| 18 |
+
|
| 19 |
+
##
|
| 20 |
+
# Image plugin for PDF images (output only).
|
| 21 |
+
##
|
| 22 |
+
|
| 23 |
+
import io
|
| 24 |
+
import os
|
| 25 |
+
import time
|
| 26 |
+
|
| 27 |
+
from . import Image, ImageFile, ImageSequence, PdfParser, __version__
|
| 28 |
+
|
| 29 |
+
#
|
| 30 |
+
# --------------------------------------------------------------------
|
| 31 |
+
|
| 32 |
+
# object ids:
|
| 33 |
+
# 1. catalogue
|
| 34 |
+
# 2. pages
|
| 35 |
+
# 3. image
|
| 36 |
+
# 4. page
|
| 37 |
+
# 5. page contents
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _save_all(im, fp, filename):
|
| 41 |
+
_save(im, fp, filename, save_all=True)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
##
|
| 45 |
+
# (Internal) Image save plugin for the PDF format.
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _save(im, fp, filename, save_all=False):
|
| 49 |
+
is_appending = im.encoderinfo.get("append", False)
|
| 50 |
+
if is_appending:
|
| 51 |
+
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="r+b")
|
| 52 |
+
else:
|
| 53 |
+
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b")
|
| 54 |
+
|
| 55 |
+
resolution = im.encoderinfo.get("resolution", 72.0)
|
| 56 |
+
|
| 57 |
+
info = {
|
| 58 |
+
"title": None
|
| 59 |
+
if is_appending
|
| 60 |
+
else os.path.splitext(os.path.basename(filename))[0],
|
| 61 |
+
"author": None,
|
| 62 |
+
"subject": None,
|
| 63 |
+
"keywords": None,
|
| 64 |
+
"creator": None,
|
| 65 |
+
"producer": None,
|
| 66 |
+
"creationDate": None if is_appending else time.gmtime(),
|
| 67 |
+
"modDate": None if is_appending else time.gmtime(),
|
| 68 |
+
}
|
| 69 |
+
for k, default in info.items():
|
| 70 |
+
v = im.encoderinfo.get(k) if k in im.encoderinfo else default
|
| 71 |
+
if v:
|
| 72 |
+
existing_pdf.info[k[0].upper() + k[1:]] = v
|
| 73 |
+
|
| 74 |
+
#
|
| 75 |
+
# make sure image data is available
|
| 76 |
+
im.load()
|
| 77 |
+
|
| 78 |
+
existing_pdf.start_writing()
|
| 79 |
+
existing_pdf.write_header()
|
| 80 |
+
existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver")
|
| 81 |
+
|
| 82 |
+
#
|
| 83 |
+
# pages
|
| 84 |
+
ims = [im]
|
| 85 |
+
if save_all:
|
| 86 |
+
append_images = im.encoderinfo.get("append_images", [])
|
| 87 |
+
for append_im in append_images:
|
| 88 |
+
append_im.encoderinfo = im.encoderinfo.copy()
|
| 89 |
+
ims.append(append_im)
|
| 90 |
+
numberOfPages = 0
|
| 91 |
+
image_refs = []
|
| 92 |
+
page_refs = []
|
| 93 |
+
contents_refs = []
|
| 94 |
+
for im in ims:
|
| 95 |
+
im_numberOfPages = 1
|
| 96 |
+
if save_all:
|
| 97 |
+
try:
|
| 98 |
+
im_numberOfPages = im.n_frames
|
| 99 |
+
except AttributeError:
|
| 100 |
+
# Image format does not have n_frames.
|
| 101 |
+
# It is a single frame image
|
| 102 |
+
pass
|
| 103 |
+
numberOfPages += im_numberOfPages
|
| 104 |
+
for i in range(im_numberOfPages):
|
| 105 |
+
image_refs.append(existing_pdf.next_object_id(0))
|
| 106 |
+
page_refs.append(existing_pdf.next_object_id(0))
|
| 107 |
+
contents_refs.append(existing_pdf.next_object_id(0))
|
| 108 |
+
existing_pdf.pages.append(page_refs[-1])
|
| 109 |
+
|
| 110 |
+
#
|
| 111 |
+
# catalog and list of pages
|
| 112 |
+
existing_pdf.write_catalog()
|
| 113 |
+
|
| 114 |
+
pageNumber = 0
|
| 115 |
+
for imSequence in ims:
|
| 116 |
+
im_pages = ImageSequence.Iterator(imSequence) if save_all else [imSequence]
|
| 117 |
+
for im in im_pages:
|
| 118 |
+
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode
|
| 119 |
+
# (packbits) or LZWDecode (tiff/lzw compression). Note that
|
| 120 |
+
# PDF 1.2 also supports Flatedecode (zip compression).
|
| 121 |
+
|
| 122 |
+
bits = 8
|
| 123 |
+
params = None
|
| 124 |
+
decode = None
|
| 125 |
+
|
| 126 |
+
if im.mode == "1":
|
| 127 |
+
filter = "DCTDecode"
|
| 128 |
+
colorspace = PdfParser.PdfName("DeviceGray")
|
| 129 |
+
procset = "ImageB" # grayscale
|
| 130 |
+
elif im.mode == "L":
|
| 131 |
+
filter = "DCTDecode"
|
| 132 |
+
# params = f"<< /Predictor 15 /Columns {width-2} >>"
|
| 133 |
+
colorspace = PdfParser.PdfName("DeviceGray")
|
| 134 |
+
procset = "ImageB" # grayscale
|
| 135 |
+
elif im.mode == "P":
|
| 136 |
+
filter = "ASCIIHexDecode"
|
| 137 |
+
palette = im.getpalette()
|
| 138 |
+
colorspace = [
|
| 139 |
+
PdfParser.PdfName("Indexed"),
|
| 140 |
+
PdfParser.PdfName("DeviceRGB"),
|
| 141 |
+
255,
|
| 142 |
+
PdfParser.PdfBinary(palette),
|
| 143 |
+
]
|
| 144 |
+
procset = "ImageI" # indexed color
|
| 145 |
+
elif im.mode == "RGB":
|
| 146 |
+
filter = "DCTDecode"
|
| 147 |
+
colorspace = PdfParser.PdfName("DeviceRGB")
|
| 148 |
+
procset = "ImageC" # color images
|
| 149 |
+
elif im.mode == "CMYK":
|
| 150 |
+
filter = "DCTDecode"
|
| 151 |
+
colorspace = PdfParser.PdfName("DeviceCMYK")
|
| 152 |
+
procset = "ImageC" # color images
|
| 153 |
+
decode = [1, 0, 1, 0, 1, 0, 1, 0]
|
| 154 |
+
else:
|
| 155 |
+
raise ValueError(f"cannot save mode {im.mode}")
|
| 156 |
+
|
| 157 |
+
#
|
| 158 |
+
# image
|
| 159 |
+
|
| 160 |
+
op = io.BytesIO()
|
| 161 |
+
|
| 162 |
+
if filter == "ASCIIHexDecode":
|
| 163 |
+
ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)])
|
| 164 |
+
elif filter == "DCTDecode":
|
| 165 |
+
Image.SAVE["JPEG"](im, op, filename)
|
| 166 |
+
elif filter == "FlateDecode":
|
| 167 |
+
ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)])
|
| 168 |
+
elif filter == "RunLengthDecode":
|
| 169 |
+
ImageFile._save(im, op, [("packbits", (0, 0) + im.size, 0, im.mode)])
|
| 170 |
+
else:
|
| 171 |
+
raise ValueError(f"unsupported PDF filter ({filter})")
|
| 172 |
+
|
| 173 |
+
#
|
| 174 |
+
# Get image characteristics
|
| 175 |
+
|
| 176 |
+
width, height = im.size
|
| 177 |
+
|
| 178 |
+
existing_pdf.write_obj(
|
| 179 |
+
image_refs[pageNumber],
|
| 180 |
+
stream=op.getvalue(),
|
| 181 |
+
Type=PdfParser.PdfName("XObject"),
|
| 182 |
+
Subtype=PdfParser.PdfName("Image"),
|
| 183 |
+
Width=width, # * 72.0 / resolution,
|
| 184 |
+
Height=height, # * 72.0 / resolution,
|
| 185 |
+
Filter=PdfParser.PdfName(filter),
|
| 186 |
+
BitsPerComponent=bits,
|
| 187 |
+
Decode=decode,
|
| 188 |
+
DecodeParams=params,
|
| 189 |
+
ColorSpace=colorspace,
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
#
|
| 193 |
+
# page
|
| 194 |
+
|
| 195 |
+
existing_pdf.write_page(
|
| 196 |
+
page_refs[pageNumber],
|
| 197 |
+
Resources=PdfParser.PdfDict(
|
| 198 |
+
ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)],
|
| 199 |
+
XObject=PdfParser.PdfDict(image=image_refs[pageNumber]),
|
| 200 |
+
),
|
| 201 |
+
MediaBox=[
|
| 202 |
+
0,
|
| 203 |
+
0,
|
| 204 |
+
width * 72.0 / resolution,
|
| 205 |
+
height * 72.0 / resolution,
|
| 206 |
+
],
|
| 207 |
+
Contents=contents_refs[pageNumber],
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
#
|
| 211 |
+
# page contents
|
| 212 |
+
|
| 213 |
+
page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % (
|
| 214 |
+
width * 72.0 / resolution,
|
| 215 |
+
height * 72.0 / resolution,
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
existing_pdf.write_obj(contents_refs[pageNumber], stream=page_contents)
|
| 219 |
+
|
| 220 |
+
pageNumber += 1
|
| 221 |
+
|
| 222 |
+
#
|
| 223 |
+
# trailer
|
| 224 |
+
existing_pdf.write_xref_and_trailer()
|
| 225 |
+
if hasattr(fp, "flush"):
|
| 226 |
+
fp.flush()
|
| 227 |
+
existing_pdf.close()
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
#
|
| 231 |
+
# --------------------------------------------------------------------
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
Image.register_save("PDF", _save)
|
| 235 |
+
Image.register_save_all("PDF", _save_all)
|
| 236 |
+
|
| 237 |
+
Image.register_extension("PDF", ".pdf")
|
| 238 |
+
|
| 239 |
+
Image.register_mime("PDF", "application/pdf")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PdfParser.py
ADDED
|
@@ -0,0 +1,998 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import calendar
|
| 2 |
+
import codecs
|
| 3 |
+
import collections
|
| 4 |
+
import mmap
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
import time
|
| 8 |
+
import zlib
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
|
| 12 |
+
# on page 656
|
| 13 |
+
def encode_text(s):
|
| 14 |
+
return codecs.BOM_UTF16_BE + s.encode("utf_16_be")
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
PDFDocEncoding = {
|
| 18 |
+
0x16: "\u0017",
|
| 19 |
+
0x18: "\u02D8",
|
| 20 |
+
0x19: "\u02C7",
|
| 21 |
+
0x1A: "\u02C6",
|
| 22 |
+
0x1B: "\u02D9",
|
| 23 |
+
0x1C: "\u02DD",
|
| 24 |
+
0x1D: "\u02DB",
|
| 25 |
+
0x1E: "\u02DA",
|
| 26 |
+
0x1F: "\u02DC",
|
| 27 |
+
0x80: "\u2022",
|
| 28 |
+
0x81: "\u2020",
|
| 29 |
+
0x82: "\u2021",
|
| 30 |
+
0x83: "\u2026",
|
| 31 |
+
0x84: "\u2014",
|
| 32 |
+
0x85: "\u2013",
|
| 33 |
+
0x86: "\u0192",
|
| 34 |
+
0x87: "\u2044",
|
| 35 |
+
0x88: "\u2039",
|
| 36 |
+
0x89: "\u203A",
|
| 37 |
+
0x8A: "\u2212",
|
| 38 |
+
0x8B: "\u2030",
|
| 39 |
+
0x8C: "\u201E",
|
| 40 |
+
0x8D: "\u201C",
|
| 41 |
+
0x8E: "\u201D",
|
| 42 |
+
0x8F: "\u2018",
|
| 43 |
+
0x90: "\u2019",
|
| 44 |
+
0x91: "\u201A",
|
| 45 |
+
0x92: "\u2122",
|
| 46 |
+
0x93: "\uFB01",
|
| 47 |
+
0x94: "\uFB02",
|
| 48 |
+
0x95: "\u0141",
|
| 49 |
+
0x96: "\u0152",
|
| 50 |
+
0x97: "\u0160",
|
| 51 |
+
0x98: "\u0178",
|
| 52 |
+
0x99: "\u017D",
|
| 53 |
+
0x9A: "\u0131",
|
| 54 |
+
0x9B: "\u0142",
|
| 55 |
+
0x9C: "\u0153",
|
| 56 |
+
0x9D: "\u0161",
|
| 57 |
+
0x9E: "\u017E",
|
| 58 |
+
0xA0: "\u20AC",
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def decode_text(b):
|
| 63 |
+
if b[: len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:
|
| 64 |
+
return b[len(codecs.BOM_UTF16_BE) :].decode("utf_16_be")
|
| 65 |
+
else:
|
| 66 |
+
return "".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class PdfFormatError(RuntimeError):
|
| 70 |
+
"""An error that probably indicates a syntactic or semantic error in the
|
| 71 |
+
PDF file structure"""
|
| 72 |
+
|
| 73 |
+
pass
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def check_format_condition(condition, error_message):
|
| 77 |
+
if not condition:
|
| 78 |
+
raise PdfFormatError(error_message)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class IndirectReference(
|
| 82 |
+
collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])
|
| 83 |
+
):
|
| 84 |
+
def __str__(self):
|
| 85 |
+
return "%s %s R" % self
|
| 86 |
+
|
| 87 |
+
def __bytes__(self):
|
| 88 |
+
return self.__str__().encode("us-ascii")
|
| 89 |
+
|
| 90 |
+
def __eq__(self, other):
|
| 91 |
+
return (
|
| 92 |
+
other.__class__ is self.__class__
|
| 93 |
+
and other.object_id == self.object_id
|
| 94 |
+
and other.generation == self.generation
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
def __ne__(self, other):
|
| 98 |
+
return not (self == other)
|
| 99 |
+
|
| 100 |
+
def __hash__(self):
|
| 101 |
+
return hash((self.object_id, self.generation))
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class IndirectObjectDef(IndirectReference):
|
| 105 |
+
def __str__(self):
|
| 106 |
+
return "%s %s obj" % self
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class XrefTable:
|
| 110 |
+
def __init__(self):
|
| 111 |
+
self.existing_entries = {} # object ID => (offset, generation)
|
| 112 |
+
self.new_entries = {} # object ID => (offset, generation)
|
| 113 |
+
self.deleted_entries = {0: 65536} # object ID => generation
|
| 114 |
+
self.reading_finished = False
|
| 115 |
+
|
| 116 |
+
def __setitem__(self, key, value):
|
| 117 |
+
if self.reading_finished:
|
| 118 |
+
self.new_entries[key] = value
|
| 119 |
+
else:
|
| 120 |
+
self.existing_entries[key] = value
|
| 121 |
+
if key in self.deleted_entries:
|
| 122 |
+
del self.deleted_entries[key]
|
| 123 |
+
|
| 124 |
+
def __getitem__(self, key):
|
| 125 |
+
try:
|
| 126 |
+
return self.new_entries[key]
|
| 127 |
+
except KeyError:
|
| 128 |
+
return self.existing_entries[key]
|
| 129 |
+
|
| 130 |
+
def __delitem__(self, key):
|
| 131 |
+
if key in self.new_entries:
|
| 132 |
+
generation = self.new_entries[key][1] + 1
|
| 133 |
+
del self.new_entries[key]
|
| 134 |
+
self.deleted_entries[key] = generation
|
| 135 |
+
elif key in self.existing_entries:
|
| 136 |
+
generation = self.existing_entries[key][1] + 1
|
| 137 |
+
self.deleted_entries[key] = generation
|
| 138 |
+
elif key in self.deleted_entries:
|
| 139 |
+
generation = self.deleted_entries[key]
|
| 140 |
+
else:
|
| 141 |
+
raise IndexError(
|
| 142 |
+
"object ID " + str(key) + " cannot be deleted because it doesn't exist"
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
def __contains__(self, key):
|
| 146 |
+
return key in self.existing_entries or key in self.new_entries
|
| 147 |
+
|
| 148 |
+
def __len__(self):
|
| 149 |
+
return len(
|
| 150 |
+
set(self.existing_entries.keys())
|
| 151 |
+
| set(self.new_entries.keys())
|
| 152 |
+
| set(self.deleted_entries.keys())
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
def keys(self):
|
| 156 |
+
return (
|
| 157 |
+
set(self.existing_entries.keys()) - set(self.deleted_entries.keys())
|
| 158 |
+
) | set(self.new_entries.keys())
|
| 159 |
+
|
| 160 |
+
def write(self, f):
|
| 161 |
+
keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys()))
|
| 162 |
+
deleted_keys = sorted(set(self.deleted_entries.keys()))
|
| 163 |
+
startxref = f.tell()
|
| 164 |
+
f.write(b"xref\n")
|
| 165 |
+
while keys:
|
| 166 |
+
# find a contiguous sequence of object IDs
|
| 167 |
+
prev = None
|
| 168 |
+
for index, key in enumerate(keys):
|
| 169 |
+
if prev is None or prev + 1 == key:
|
| 170 |
+
prev = key
|
| 171 |
+
else:
|
| 172 |
+
contiguous_keys = keys[:index]
|
| 173 |
+
keys = keys[index:]
|
| 174 |
+
break
|
| 175 |
+
else:
|
| 176 |
+
contiguous_keys = keys
|
| 177 |
+
keys = None
|
| 178 |
+
f.write(b"%d %d\n" % (contiguous_keys[0], len(contiguous_keys)))
|
| 179 |
+
for object_id in contiguous_keys:
|
| 180 |
+
if object_id in self.new_entries:
|
| 181 |
+
f.write(b"%010d %05d n \n" % self.new_entries[object_id])
|
| 182 |
+
else:
|
| 183 |
+
this_deleted_object_id = deleted_keys.pop(0)
|
| 184 |
+
check_format_condition(
|
| 185 |
+
object_id == this_deleted_object_id,
|
| 186 |
+
f"expected the next deleted object ID to be {object_id}, "
|
| 187 |
+
f"instead found {this_deleted_object_id}",
|
| 188 |
+
)
|
| 189 |
+
try:
|
| 190 |
+
next_in_linked_list = deleted_keys[0]
|
| 191 |
+
except IndexError:
|
| 192 |
+
next_in_linked_list = 0
|
| 193 |
+
f.write(
|
| 194 |
+
b"%010d %05d f \n"
|
| 195 |
+
% (next_in_linked_list, self.deleted_entries[object_id])
|
| 196 |
+
)
|
| 197 |
+
return startxref
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
class PdfName:
|
| 201 |
+
def __init__(self, name):
|
| 202 |
+
if isinstance(name, PdfName):
|
| 203 |
+
self.name = name.name
|
| 204 |
+
elif isinstance(name, bytes):
|
| 205 |
+
self.name = name
|
| 206 |
+
else:
|
| 207 |
+
self.name = name.encode("us-ascii")
|
| 208 |
+
|
| 209 |
+
def name_as_str(self):
|
| 210 |
+
return self.name.decode("us-ascii")
|
| 211 |
+
|
| 212 |
+
def __eq__(self, other):
|
| 213 |
+
return (
|
| 214 |
+
isinstance(other, PdfName) and other.name == self.name
|
| 215 |
+
) or other == self.name
|
| 216 |
+
|
| 217 |
+
def __hash__(self):
|
| 218 |
+
return hash(self.name)
|
| 219 |
+
|
| 220 |
+
def __repr__(self):
|
| 221 |
+
return f"PdfName({repr(self.name)})"
|
| 222 |
+
|
| 223 |
+
@classmethod
|
| 224 |
+
def from_pdf_stream(cls, data):
|
| 225 |
+
return cls(PdfParser.interpret_name(data))
|
| 226 |
+
|
| 227 |
+
allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"}
|
| 228 |
+
|
| 229 |
+
def __bytes__(self):
|
| 230 |
+
result = bytearray(b"/")
|
| 231 |
+
for b in self.name:
|
| 232 |
+
if b in self.allowed_chars:
|
| 233 |
+
result.append(b)
|
| 234 |
+
else:
|
| 235 |
+
result.extend(b"#%02X" % b)
|
| 236 |
+
return bytes(result)
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
class PdfArray(list):
|
| 240 |
+
def __bytes__(self):
|
| 241 |
+
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
class PdfDict(collections.UserDict):
|
| 245 |
+
def __setattr__(self, key, value):
|
| 246 |
+
if key == "data":
|
| 247 |
+
collections.UserDict.__setattr__(self, key, value)
|
| 248 |
+
else:
|
| 249 |
+
self[key.encode("us-ascii")] = value
|
| 250 |
+
|
| 251 |
+
def __getattr__(self, key):
|
| 252 |
+
try:
|
| 253 |
+
value = self[key.encode("us-ascii")]
|
| 254 |
+
except KeyError as e:
|
| 255 |
+
raise AttributeError(key) from e
|
| 256 |
+
if isinstance(value, bytes):
|
| 257 |
+
value = decode_text(value)
|
| 258 |
+
if key.endswith("Date"):
|
| 259 |
+
if value.startswith("D:"):
|
| 260 |
+
value = value[2:]
|
| 261 |
+
|
| 262 |
+
relationship = "Z"
|
| 263 |
+
if len(value) > 17:
|
| 264 |
+
relationship = value[14]
|
| 265 |
+
offset = int(value[15:17]) * 60
|
| 266 |
+
if len(value) > 20:
|
| 267 |
+
offset += int(value[18:20])
|
| 268 |
+
|
| 269 |
+
format = "%Y%m%d%H%M%S"[: len(value) - 2]
|
| 270 |
+
value = time.strptime(value[: len(format) + 2], format)
|
| 271 |
+
if relationship in ["+", "-"]:
|
| 272 |
+
offset *= 60
|
| 273 |
+
if relationship == "+":
|
| 274 |
+
offset *= -1
|
| 275 |
+
value = time.gmtime(calendar.timegm(value) + offset)
|
| 276 |
+
return value
|
| 277 |
+
|
| 278 |
+
def __bytes__(self):
|
| 279 |
+
out = bytearray(b"<<")
|
| 280 |
+
for key, value in self.items():
|
| 281 |
+
if value is None:
|
| 282 |
+
continue
|
| 283 |
+
value = pdf_repr(value)
|
| 284 |
+
out.extend(b"\n")
|
| 285 |
+
out.extend(bytes(PdfName(key)))
|
| 286 |
+
out.extend(b" ")
|
| 287 |
+
out.extend(value)
|
| 288 |
+
out.extend(b"\n>>")
|
| 289 |
+
return bytes(out)
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
class PdfBinary:
|
| 293 |
+
def __init__(self, data):
|
| 294 |
+
self.data = data
|
| 295 |
+
|
| 296 |
+
def __bytes__(self):
|
| 297 |
+
return b"<%s>" % b"".join(b"%02X" % b for b in self.data)
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
class PdfStream:
|
| 301 |
+
def __init__(self, dictionary, buf):
|
| 302 |
+
self.dictionary = dictionary
|
| 303 |
+
self.buf = buf
|
| 304 |
+
|
| 305 |
+
def decode(self):
|
| 306 |
+
try:
|
| 307 |
+
filter = self.dictionary.Filter
|
| 308 |
+
except AttributeError:
|
| 309 |
+
return self.buf
|
| 310 |
+
if filter == b"FlateDecode":
|
| 311 |
+
try:
|
| 312 |
+
expected_length = self.dictionary.DL
|
| 313 |
+
except AttributeError:
|
| 314 |
+
expected_length = self.dictionary.Length
|
| 315 |
+
return zlib.decompress(self.buf, bufsize=int(expected_length))
|
| 316 |
+
else:
|
| 317 |
+
raise NotImplementedError(
|
| 318 |
+
f"stream filter {repr(self.dictionary.Filter)} unknown/unsupported"
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def pdf_repr(x):
|
| 323 |
+
if x is True:
|
| 324 |
+
return b"true"
|
| 325 |
+
elif x is False:
|
| 326 |
+
return b"false"
|
| 327 |
+
elif x is None:
|
| 328 |
+
return b"null"
|
| 329 |
+
elif isinstance(x, (PdfName, PdfDict, PdfArray, PdfBinary)):
|
| 330 |
+
return bytes(x)
|
| 331 |
+
elif isinstance(x, int):
|
| 332 |
+
return str(x).encode("us-ascii")
|
| 333 |
+
elif isinstance(x, float):
|
| 334 |
+
return str(x).encode("us-ascii")
|
| 335 |
+
elif isinstance(x, time.struct_time):
|
| 336 |
+
return b"(D:" + time.strftime("%Y%m%d%H%M%SZ", x).encode("us-ascii") + b")"
|
| 337 |
+
elif isinstance(x, dict):
|
| 338 |
+
return bytes(PdfDict(x))
|
| 339 |
+
elif isinstance(x, list):
|
| 340 |
+
return bytes(PdfArray(x))
|
| 341 |
+
elif isinstance(x, str):
|
| 342 |
+
return pdf_repr(encode_text(x))
|
| 343 |
+
elif isinstance(x, bytes):
|
| 344 |
+
# XXX escape more chars? handle binary garbage
|
| 345 |
+
x = x.replace(b"\\", b"\\\\")
|
| 346 |
+
x = x.replace(b"(", b"\\(")
|
| 347 |
+
x = x.replace(b")", b"\\)")
|
| 348 |
+
return b"(" + x + b")"
|
| 349 |
+
else:
|
| 350 |
+
return bytes(x)
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
class PdfParser:
|
| 354 |
+
"""Based on
|
| 355 |
+
https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf
|
| 356 |
+
Supports PDF up to 1.4
|
| 357 |
+
"""
|
| 358 |
+
|
| 359 |
+
def __init__(self, filename=None, f=None, buf=None, start_offset=0, mode="rb"):
|
| 360 |
+
if buf and f:
|
| 361 |
+
raise RuntimeError("specify buf or f or filename, but not both buf and f")
|
| 362 |
+
self.filename = filename
|
| 363 |
+
self.buf = buf
|
| 364 |
+
self.f = f
|
| 365 |
+
self.start_offset = start_offset
|
| 366 |
+
self.should_close_buf = False
|
| 367 |
+
self.should_close_file = False
|
| 368 |
+
if filename is not None and f is None:
|
| 369 |
+
self.f = f = open(filename, mode)
|
| 370 |
+
self.should_close_file = True
|
| 371 |
+
if f is not None:
|
| 372 |
+
self.buf = buf = self.get_buf_from_file(f)
|
| 373 |
+
self.should_close_buf = True
|
| 374 |
+
if not filename and hasattr(f, "name"):
|
| 375 |
+
self.filename = f.name
|
| 376 |
+
self.cached_objects = {}
|
| 377 |
+
if buf:
|
| 378 |
+
self.read_pdf_info()
|
| 379 |
+
else:
|
| 380 |
+
self.file_size_total = self.file_size_this = 0
|
| 381 |
+
self.root = PdfDict()
|
| 382 |
+
self.root_ref = None
|
| 383 |
+
self.info = PdfDict()
|
| 384 |
+
self.info_ref = None
|
| 385 |
+
self.page_tree_root = {}
|
| 386 |
+
self.pages = []
|
| 387 |
+
self.orig_pages = []
|
| 388 |
+
self.pages_ref = None
|
| 389 |
+
self.last_xref_section_offset = None
|
| 390 |
+
self.trailer_dict = {}
|
| 391 |
+
self.xref_table = XrefTable()
|
| 392 |
+
self.xref_table.reading_finished = True
|
| 393 |
+
if f:
|
| 394 |
+
self.seek_end()
|
| 395 |
+
|
| 396 |
+
def __enter__(self):
|
| 397 |
+
return self
|
| 398 |
+
|
| 399 |
+
def __exit__(self, exc_type, exc_value, traceback):
|
| 400 |
+
self.close()
|
| 401 |
+
return False # do not suppress exceptions
|
| 402 |
+
|
| 403 |
+
def start_writing(self):
|
| 404 |
+
self.close_buf()
|
| 405 |
+
self.seek_end()
|
| 406 |
+
|
| 407 |
+
def close_buf(self):
|
| 408 |
+
try:
|
| 409 |
+
self.buf.close()
|
| 410 |
+
except AttributeError:
|
| 411 |
+
pass
|
| 412 |
+
self.buf = None
|
| 413 |
+
|
| 414 |
+
def close(self):
|
| 415 |
+
if self.should_close_buf:
|
| 416 |
+
self.close_buf()
|
| 417 |
+
if self.f is not None and self.should_close_file:
|
| 418 |
+
self.f.close()
|
| 419 |
+
self.f = None
|
| 420 |
+
|
| 421 |
+
def seek_end(self):
|
| 422 |
+
self.f.seek(0, os.SEEK_END)
|
| 423 |
+
|
| 424 |
+
def write_header(self):
|
| 425 |
+
self.f.write(b"%PDF-1.4\n")
|
| 426 |
+
|
| 427 |
+
def write_comment(self, s):
|
| 428 |
+
self.f.write(f"% {s}\n".encode())
|
| 429 |
+
|
| 430 |
+
def write_catalog(self):
|
| 431 |
+
self.del_root()
|
| 432 |
+
self.root_ref = self.next_object_id(self.f.tell())
|
| 433 |
+
self.pages_ref = self.next_object_id(0)
|
| 434 |
+
self.rewrite_pages()
|
| 435 |
+
self.write_obj(self.root_ref, Type=PdfName(b"Catalog"), Pages=self.pages_ref)
|
| 436 |
+
self.write_obj(
|
| 437 |
+
self.pages_ref,
|
| 438 |
+
Type=PdfName(b"Pages"),
|
| 439 |
+
Count=len(self.pages),
|
| 440 |
+
Kids=self.pages,
|
| 441 |
+
)
|
| 442 |
+
return self.root_ref
|
| 443 |
+
|
| 444 |
+
def rewrite_pages(self):
|
| 445 |
+
pages_tree_nodes_to_delete = []
|
| 446 |
+
for i, page_ref in enumerate(self.orig_pages):
|
| 447 |
+
page_info = self.cached_objects[page_ref]
|
| 448 |
+
del self.xref_table[page_ref.object_id]
|
| 449 |
+
pages_tree_nodes_to_delete.append(page_info[PdfName(b"Parent")])
|
| 450 |
+
if page_ref not in self.pages:
|
| 451 |
+
# the page has been deleted
|
| 452 |
+
continue
|
| 453 |
+
# make dict keys into strings for passing to write_page
|
| 454 |
+
stringified_page_info = {}
|
| 455 |
+
for key, value in page_info.items():
|
| 456 |
+
# key should be a PdfName
|
| 457 |
+
stringified_page_info[key.name_as_str()] = value
|
| 458 |
+
stringified_page_info["Parent"] = self.pages_ref
|
| 459 |
+
new_page_ref = self.write_page(None, **stringified_page_info)
|
| 460 |
+
for j, cur_page_ref in enumerate(self.pages):
|
| 461 |
+
if cur_page_ref == page_ref:
|
| 462 |
+
# replace the page reference with the new one
|
| 463 |
+
self.pages[j] = new_page_ref
|
| 464 |
+
# delete redundant Pages tree nodes from xref table
|
| 465 |
+
for pages_tree_node_ref in pages_tree_nodes_to_delete:
|
| 466 |
+
while pages_tree_node_ref:
|
| 467 |
+
pages_tree_node = self.cached_objects[pages_tree_node_ref]
|
| 468 |
+
if pages_tree_node_ref.object_id in self.xref_table:
|
| 469 |
+
del self.xref_table[pages_tree_node_ref.object_id]
|
| 470 |
+
pages_tree_node_ref = pages_tree_node.get(b"Parent", None)
|
| 471 |
+
self.orig_pages = []
|
| 472 |
+
|
| 473 |
+
def write_xref_and_trailer(self, new_root_ref=None):
|
| 474 |
+
if new_root_ref:
|
| 475 |
+
self.del_root()
|
| 476 |
+
self.root_ref = new_root_ref
|
| 477 |
+
if self.info:
|
| 478 |
+
self.info_ref = self.write_obj(None, self.info)
|
| 479 |
+
start_xref = self.xref_table.write(self.f)
|
| 480 |
+
num_entries = len(self.xref_table)
|
| 481 |
+
trailer_dict = {b"Root": self.root_ref, b"Size": num_entries}
|
| 482 |
+
if self.last_xref_section_offset is not None:
|
| 483 |
+
trailer_dict[b"Prev"] = self.last_xref_section_offset
|
| 484 |
+
if self.info:
|
| 485 |
+
trailer_dict[b"Info"] = self.info_ref
|
| 486 |
+
self.last_xref_section_offset = start_xref
|
| 487 |
+
self.f.write(
|
| 488 |
+
b"trailer\n"
|
| 489 |
+
+ bytes(PdfDict(trailer_dict))
|
| 490 |
+
+ b"\nstartxref\n%d\n%%%%EOF" % start_xref
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
def write_page(self, ref, *objs, **dict_obj):
|
| 494 |
+
if isinstance(ref, int):
|
| 495 |
+
ref = self.pages[ref]
|
| 496 |
+
if "Type" not in dict_obj:
|
| 497 |
+
dict_obj["Type"] = PdfName(b"Page")
|
| 498 |
+
if "Parent" not in dict_obj:
|
| 499 |
+
dict_obj["Parent"] = self.pages_ref
|
| 500 |
+
return self.write_obj(ref, *objs, **dict_obj)
|
| 501 |
+
|
| 502 |
+
def write_obj(self, ref, *objs, **dict_obj):
|
| 503 |
+
f = self.f
|
| 504 |
+
if ref is None:
|
| 505 |
+
ref = self.next_object_id(f.tell())
|
| 506 |
+
else:
|
| 507 |
+
self.xref_table[ref.object_id] = (f.tell(), ref.generation)
|
| 508 |
+
f.write(bytes(IndirectObjectDef(*ref)))
|
| 509 |
+
stream = dict_obj.pop("stream", None)
|
| 510 |
+
if stream is not None:
|
| 511 |
+
dict_obj["Length"] = len(stream)
|
| 512 |
+
if dict_obj:
|
| 513 |
+
f.write(pdf_repr(dict_obj))
|
| 514 |
+
for obj in objs:
|
| 515 |
+
f.write(pdf_repr(obj))
|
| 516 |
+
if stream is not None:
|
| 517 |
+
f.write(b"stream\n")
|
| 518 |
+
f.write(stream)
|
| 519 |
+
f.write(b"\nendstream\n")
|
| 520 |
+
f.write(b"endobj\n")
|
| 521 |
+
return ref
|
| 522 |
+
|
| 523 |
+
def del_root(self):
|
| 524 |
+
if self.root_ref is None:
|
| 525 |
+
return
|
| 526 |
+
del self.xref_table[self.root_ref.object_id]
|
| 527 |
+
del self.xref_table[self.root[b"Pages"].object_id]
|
| 528 |
+
|
| 529 |
+
@staticmethod
|
| 530 |
+
def get_buf_from_file(f):
|
| 531 |
+
if hasattr(f, "getbuffer"):
|
| 532 |
+
return f.getbuffer()
|
| 533 |
+
elif hasattr(f, "getvalue"):
|
| 534 |
+
return f.getvalue()
|
| 535 |
+
else:
|
| 536 |
+
try:
|
| 537 |
+
return mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
|
| 538 |
+
except ValueError: # cannot mmap an empty file
|
| 539 |
+
return b""
|
| 540 |
+
|
| 541 |
+
def read_pdf_info(self):
|
| 542 |
+
self.file_size_total = len(self.buf)
|
| 543 |
+
self.file_size_this = self.file_size_total - self.start_offset
|
| 544 |
+
self.read_trailer()
|
| 545 |
+
self.root_ref = self.trailer_dict[b"Root"]
|
| 546 |
+
self.info_ref = self.trailer_dict.get(b"Info", None)
|
| 547 |
+
self.root = PdfDict(self.read_indirect(self.root_ref))
|
| 548 |
+
if self.info_ref is None:
|
| 549 |
+
self.info = PdfDict()
|
| 550 |
+
else:
|
| 551 |
+
self.info = PdfDict(self.read_indirect(self.info_ref))
|
| 552 |
+
check_format_condition(b"Type" in self.root, "/Type missing in Root")
|
| 553 |
+
check_format_condition(
|
| 554 |
+
self.root[b"Type"] == b"Catalog", "/Type in Root is not /Catalog"
|
| 555 |
+
)
|
| 556 |
+
check_format_condition(b"Pages" in self.root, "/Pages missing in Root")
|
| 557 |
+
check_format_condition(
|
| 558 |
+
isinstance(self.root[b"Pages"], IndirectReference),
|
| 559 |
+
"/Pages in Root is not an indirect reference",
|
| 560 |
+
)
|
| 561 |
+
self.pages_ref = self.root[b"Pages"]
|
| 562 |
+
self.page_tree_root = self.read_indirect(self.pages_ref)
|
| 563 |
+
self.pages = self.linearize_page_tree(self.page_tree_root)
|
| 564 |
+
# save the original list of page references
|
| 565 |
+
# in case the user modifies, adds or deletes some pages
|
| 566 |
+
# and we need to rewrite the pages and their list
|
| 567 |
+
self.orig_pages = self.pages[:]
|
| 568 |
+
|
| 569 |
+
def next_object_id(self, offset=None):
|
| 570 |
+
try:
|
| 571 |
+
# TODO: support reuse of deleted objects
|
| 572 |
+
reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)
|
| 573 |
+
except ValueError:
|
| 574 |
+
reference = IndirectReference(1, 0)
|
| 575 |
+
if offset is not None:
|
| 576 |
+
self.xref_table[reference.object_id] = (offset, 0)
|
| 577 |
+
return reference
|
| 578 |
+
|
| 579 |
+
delimiter = rb"[][()<>{}/%]"
|
| 580 |
+
delimiter_or_ws = rb"[][()<>{}/%\000\011\012\014\015\040]"
|
| 581 |
+
whitespace = rb"[\000\011\012\014\015\040]"
|
| 582 |
+
whitespace_or_hex = rb"[\000\011\012\014\015\0400-9a-fA-F]"
|
| 583 |
+
whitespace_optional = whitespace + b"*"
|
| 584 |
+
whitespace_mandatory = whitespace + b"+"
|
| 585 |
+
# No "\012" aka "\n" or "\015" aka "\r":
|
| 586 |
+
whitespace_optional_no_nl = rb"[\000\011\014\040]*"
|
| 587 |
+
newline_only = rb"[\r\n]+"
|
| 588 |
+
newline = whitespace_optional_no_nl + newline_only + whitespace_optional_no_nl
|
| 589 |
+
re_trailer_end = re.compile(
|
| 590 |
+
whitespace_mandatory
|
| 591 |
+
+ rb"trailer"
|
| 592 |
+
+ whitespace_optional
|
| 593 |
+
+ rb"\<\<(.*\>\>)"
|
| 594 |
+
+ newline
|
| 595 |
+
+ rb"startxref"
|
| 596 |
+
+ newline
|
| 597 |
+
+ rb"([0-9]+)"
|
| 598 |
+
+ newline
|
| 599 |
+
+ rb"%%EOF"
|
| 600 |
+
+ whitespace_optional
|
| 601 |
+
+ rb"$",
|
| 602 |
+
re.DOTALL,
|
| 603 |
+
)
|
| 604 |
+
re_trailer_prev = re.compile(
|
| 605 |
+
whitespace_optional
|
| 606 |
+
+ rb"trailer"
|
| 607 |
+
+ whitespace_optional
|
| 608 |
+
+ rb"\<\<(.*?\>\>)"
|
| 609 |
+
+ newline
|
| 610 |
+
+ rb"startxref"
|
| 611 |
+
+ newline
|
| 612 |
+
+ rb"([0-9]+)"
|
| 613 |
+
+ newline
|
| 614 |
+
+ rb"%%EOF"
|
| 615 |
+
+ whitespace_optional,
|
| 616 |
+
re.DOTALL,
|
| 617 |
+
)
|
| 618 |
+
|
| 619 |
+
def read_trailer(self):
|
| 620 |
+
search_start_offset = len(self.buf) - 16384
|
| 621 |
+
if search_start_offset < self.start_offset:
|
| 622 |
+
search_start_offset = self.start_offset
|
| 623 |
+
m = self.re_trailer_end.search(self.buf, search_start_offset)
|
| 624 |
+
check_format_condition(m, "trailer end not found")
|
| 625 |
+
# make sure we found the LAST trailer
|
| 626 |
+
last_match = m
|
| 627 |
+
while m:
|
| 628 |
+
last_match = m
|
| 629 |
+
m = self.re_trailer_end.search(self.buf, m.start() + 16)
|
| 630 |
+
if not m:
|
| 631 |
+
m = last_match
|
| 632 |
+
trailer_data = m.group(1)
|
| 633 |
+
self.last_xref_section_offset = int(m.group(2))
|
| 634 |
+
self.trailer_dict = self.interpret_trailer(trailer_data)
|
| 635 |
+
self.xref_table = XrefTable()
|
| 636 |
+
self.read_xref_table(xref_section_offset=self.last_xref_section_offset)
|
| 637 |
+
if b"Prev" in self.trailer_dict:
|
| 638 |
+
self.read_prev_trailer(self.trailer_dict[b"Prev"])
|
| 639 |
+
|
| 640 |
+
def read_prev_trailer(self, xref_section_offset):
|
| 641 |
+
trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)
|
| 642 |
+
m = self.re_trailer_prev.search(
|
| 643 |
+
self.buf[trailer_offset : trailer_offset + 16384]
|
| 644 |
+
)
|
| 645 |
+
check_format_condition(m, "previous trailer not found")
|
| 646 |
+
trailer_data = m.group(1)
|
| 647 |
+
check_format_condition(
|
| 648 |
+
int(m.group(2)) == xref_section_offset,
|
| 649 |
+
"xref section offset in previous trailer doesn't match what was expected",
|
| 650 |
+
)
|
| 651 |
+
trailer_dict = self.interpret_trailer(trailer_data)
|
| 652 |
+
if b"Prev" in trailer_dict:
|
| 653 |
+
self.read_prev_trailer(trailer_dict[b"Prev"])
|
| 654 |
+
|
| 655 |
+
re_whitespace_optional = re.compile(whitespace_optional)
|
| 656 |
+
re_name = re.compile(
|
| 657 |
+
whitespace_optional
|
| 658 |
+
+ rb"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?="
|
| 659 |
+
+ delimiter_or_ws
|
| 660 |
+
+ rb")"
|
| 661 |
+
)
|
| 662 |
+
re_dict_start = re.compile(whitespace_optional + rb"\<\<")
|
| 663 |
+
re_dict_end = re.compile(whitespace_optional + rb"\>\>" + whitespace_optional)
|
| 664 |
+
|
| 665 |
+
@classmethod
|
| 666 |
+
def interpret_trailer(cls, trailer_data):
|
| 667 |
+
trailer = {}
|
| 668 |
+
offset = 0
|
| 669 |
+
while True:
|
| 670 |
+
m = cls.re_name.match(trailer_data, offset)
|
| 671 |
+
if not m:
|
| 672 |
+
m = cls.re_dict_end.match(trailer_data, offset)
|
| 673 |
+
check_format_condition(
|
| 674 |
+
m and m.end() == len(trailer_data),
|
| 675 |
+
"name not found in trailer, remaining data: "
|
| 676 |
+
+ repr(trailer_data[offset:]),
|
| 677 |
+
)
|
| 678 |
+
break
|
| 679 |
+
key = cls.interpret_name(m.group(1))
|
| 680 |
+
value, offset = cls.get_value(trailer_data, m.end())
|
| 681 |
+
trailer[key] = value
|
| 682 |
+
check_format_condition(
|
| 683 |
+
b"Size" in trailer and isinstance(trailer[b"Size"], int),
|
| 684 |
+
"/Size not in trailer or not an integer",
|
| 685 |
+
)
|
| 686 |
+
check_format_condition(
|
| 687 |
+
b"Root" in trailer and isinstance(trailer[b"Root"], IndirectReference),
|
| 688 |
+
"/Root not in trailer or not an indirect reference",
|
| 689 |
+
)
|
| 690 |
+
return trailer
|
| 691 |
+
|
| 692 |
+
re_hashes_in_name = re.compile(rb"([^#]*)(#([0-9a-fA-F]{2}))?")
|
| 693 |
+
|
| 694 |
+
@classmethod
|
| 695 |
+
def interpret_name(cls, raw, as_text=False):
|
| 696 |
+
name = b""
|
| 697 |
+
for m in cls.re_hashes_in_name.finditer(raw):
|
| 698 |
+
if m.group(3):
|
| 699 |
+
name += m.group(1) + bytearray.fromhex(m.group(3).decode("us-ascii"))
|
| 700 |
+
else:
|
| 701 |
+
name += m.group(1)
|
| 702 |
+
if as_text:
|
| 703 |
+
return name.decode("utf-8")
|
| 704 |
+
else:
|
| 705 |
+
return bytes(name)
|
| 706 |
+
|
| 707 |
+
re_null = re.compile(whitespace_optional + rb"null(?=" + delimiter_or_ws + rb")")
|
| 708 |
+
re_true = re.compile(whitespace_optional + rb"true(?=" + delimiter_or_ws + rb")")
|
| 709 |
+
re_false = re.compile(whitespace_optional + rb"false(?=" + delimiter_or_ws + rb")")
|
| 710 |
+
re_int = re.compile(
|
| 711 |
+
whitespace_optional + rb"([-+]?[0-9]+)(?=" + delimiter_or_ws + rb")"
|
| 712 |
+
)
|
| 713 |
+
re_real = re.compile(
|
| 714 |
+
whitespace_optional
|
| 715 |
+
+ rb"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?="
|
| 716 |
+
+ delimiter_or_ws
|
| 717 |
+
+ rb")"
|
| 718 |
+
)
|
| 719 |
+
re_array_start = re.compile(whitespace_optional + rb"\[")
|
| 720 |
+
re_array_end = re.compile(whitespace_optional + rb"]")
|
| 721 |
+
re_string_hex = re.compile(
|
| 722 |
+
whitespace_optional + rb"\<(" + whitespace_or_hex + rb"*)\>"
|
| 723 |
+
)
|
| 724 |
+
re_string_lit = re.compile(whitespace_optional + rb"\(")
|
| 725 |
+
re_indirect_reference = re.compile(
|
| 726 |
+
whitespace_optional
|
| 727 |
+
+ rb"([-+]?[0-9]+)"
|
| 728 |
+
+ whitespace_mandatory
|
| 729 |
+
+ rb"([-+]?[0-9]+)"
|
| 730 |
+
+ whitespace_mandatory
|
| 731 |
+
+ rb"R(?="
|
| 732 |
+
+ delimiter_or_ws
|
| 733 |
+
+ rb")"
|
| 734 |
+
)
|
| 735 |
+
re_indirect_def_start = re.compile(
|
| 736 |
+
whitespace_optional
|
| 737 |
+
+ rb"([-+]?[0-9]+)"
|
| 738 |
+
+ whitespace_mandatory
|
| 739 |
+
+ rb"([-+]?[0-9]+)"
|
| 740 |
+
+ whitespace_mandatory
|
| 741 |
+
+ rb"obj(?="
|
| 742 |
+
+ delimiter_or_ws
|
| 743 |
+
+ rb")"
|
| 744 |
+
)
|
| 745 |
+
re_indirect_def_end = re.compile(
|
| 746 |
+
whitespace_optional + rb"endobj(?=" + delimiter_or_ws + rb")"
|
| 747 |
+
)
|
| 748 |
+
re_comment = re.compile(
|
| 749 |
+
rb"(" + whitespace_optional + rb"%[^\r\n]*" + newline + rb")*"
|
| 750 |
+
)
|
| 751 |
+
re_stream_start = re.compile(whitespace_optional + rb"stream\r?\n")
|
| 752 |
+
re_stream_end = re.compile(
|
| 753 |
+
whitespace_optional + rb"endstream(?=" + delimiter_or_ws + rb")"
|
| 754 |
+
)
|
| 755 |
+
|
| 756 |
+
@classmethod
|
| 757 |
+
def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1):
|
| 758 |
+
if max_nesting == 0:
|
| 759 |
+
return None, None
|
| 760 |
+
m = cls.re_comment.match(data, offset)
|
| 761 |
+
if m:
|
| 762 |
+
offset = m.end()
|
| 763 |
+
m = cls.re_indirect_def_start.match(data, offset)
|
| 764 |
+
if m:
|
| 765 |
+
check_format_condition(
|
| 766 |
+
int(m.group(1)) > 0,
|
| 767 |
+
"indirect object definition: object ID must be greater than 0",
|
| 768 |
+
)
|
| 769 |
+
check_format_condition(
|
| 770 |
+
int(m.group(2)) >= 0,
|
| 771 |
+
"indirect object definition: generation must be non-negative",
|
| 772 |
+
)
|
| 773 |
+
check_format_condition(
|
| 774 |
+
expect_indirect is None
|
| 775 |
+
or expect_indirect
|
| 776 |
+
== IndirectReference(int(m.group(1)), int(m.group(2))),
|
| 777 |
+
"indirect object definition different than expected",
|
| 778 |
+
)
|
| 779 |
+
object, offset = cls.get_value(data, m.end(), max_nesting=max_nesting - 1)
|
| 780 |
+
if offset is None:
|
| 781 |
+
return object, None
|
| 782 |
+
m = cls.re_indirect_def_end.match(data, offset)
|
| 783 |
+
check_format_condition(m, "indirect object definition end not found")
|
| 784 |
+
return object, m.end()
|
| 785 |
+
check_format_condition(
|
| 786 |
+
not expect_indirect, "indirect object definition not found"
|
| 787 |
+
)
|
| 788 |
+
m = cls.re_indirect_reference.match(data, offset)
|
| 789 |
+
if m:
|
| 790 |
+
check_format_condition(
|
| 791 |
+
int(m.group(1)) > 0,
|
| 792 |
+
"indirect object reference: object ID must be greater than 0",
|
| 793 |
+
)
|
| 794 |
+
check_format_condition(
|
| 795 |
+
int(m.group(2)) >= 0,
|
| 796 |
+
"indirect object reference: generation must be non-negative",
|
| 797 |
+
)
|
| 798 |
+
return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()
|
| 799 |
+
m = cls.re_dict_start.match(data, offset)
|
| 800 |
+
if m:
|
| 801 |
+
offset = m.end()
|
| 802 |
+
result = {}
|
| 803 |
+
m = cls.re_dict_end.match(data, offset)
|
| 804 |
+
while not m:
|
| 805 |
+
key, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
|
| 806 |
+
if offset is None:
|
| 807 |
+
return result, None
|
| 808 |
+
value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
|
| 809 |
+
result[key] = value
|
| 810 |
+
if offset is None:
|
| 811 |
+
return result, None
|
| 812 |
+
m = cls.re_dict_end.match(data, offset)
|
| 813 |
+
offset = m.end()
|
| 814 |
+
m = cls.re_stream_start.match(data, offset)
|
| 815 |
+
if m:
|
| 816 |
+
try:
|
| 817 |
+
stream_len = int(result[b"Length"])
|
| 818 |
+
except (TypeError, KeyError, ValueError) as e:
|
| 819 |
+
raise PdfFormatError(
|
| 820 |
+
"bad or missing Length in stream dict (%r)"
|
| 821 |
+
% result.get(b"Length", None)
|
| 822 |
+
) from e
|
| 823 |
+
stream_data = data[m.end() : m.end() + stream_len]
|
| 824 |
+
m = cls.re_stream_end.match(data, m.end() + stream_len)
|
| 825 |
+
check_format_condition(m, "stream end not found")
|
| 826 |
+
offset = m.end()
|
| 827 |
+
result = PdfStream(PdfDict(result), stream_data)
|
| 828 |
+
else:
|
| 829 |
+
result = PdfDict(result)
|
| 830 |
+
return result, offset
|
| 831 |
+
m = cls.re_array_start.match(data, offset)
|
| 832 |
+
if m:
|
| 833 |
+
offset = m.end()
|
| 834 |
+
result = []
|
| 835 |
+
m = cls.re_array_end.match(data, offset)
|
| 836 |
+
while not m:
|
| 837 |
+
value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
|
| 838 |
+
result.append(value)
|
| 839 |
+
if offset is None:
|
| 840 |
+
return result, None
|
| 841 |
+
m = cls.re_array_end.match(data, offset)
|
| 842 |
+
return result, m.end()
|
| 843 |
+
m = cls.re_null.match(data, offset)
|
| 844 |
+
if m:
|
| 845 |
+
return None, m.end()
|
| 846 |
+
m = cls.re_true.match(data, offset)
|
| 847 |
+
if m:
|
| 848 |
+
return True, m.end()
|
| 849 |
+
m = cls.re_false.match(data, offset)
|
| 850 |
+
if m:
|
| 851 |
+
return False, m.end()
|
| 852 |
+
m = cls.re_name.match(data, offset)
|
| 853 |
+
if m:
|
| 854 |
+
return PdfName(cls.interpret_name(m.group(1))), m.end()
|
| 855 |
+
m = cls.re_int.match(data, offset)
|
| 856 |
+
if m:
|
| 857 |
+
return int(m.group(1)), m.end()
|
| 858 |
+
m = cls.re_real.match(data, offset)
|
| 859 |
+
if m:
|
| 860 |
+
# XXX Decimal instead of float???
|
| 861 |
+
return float(m.group(1)), m.end()
|
| 862 |
+
m = cls.re_string_hex.match(data, offset)
|
| 863 |
+
if m:
|
| 864 |
+
# filter out whitespace
|
| 865 |
+
hex_string = bytearray(
|
| 866 |
+
b for b in m.group(1) if b in b"0123456789abcdefABCDEF"
|
| 867 |
+
)
|
| 868 |
+
if len(hex_string) % 2 == 1:
|
| 869 |
+
# append a 0 if the length is not even - yes, at the end
|
| 870 |
+
hex_string.append(ord(b"0"))
|
| 871 |
+
return bytearray.fromhex(hex_string.decode("us-ascii")), m.end()
|
| 872 |
+
m = cls.re_string_lit.match(data, offset)
|
| 873 |
+
if m:
|
| 874 |
+
return cls.get_literal_string(data, m.end())
|
| 875 |
+
# return None, offset # fallback (only for debugging)
|
| 876 |
+
raise PdfFormatError("unrecognized object: " + repr(data[offset : offset + 32]))
|
| 877 |
+
|
| 878 |
+
re_lit_str_token = re.compile(
|
| 879 |
+
rb"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))"
|
| 880 |
+
)
|
| 881 |
+
escaped_chars = {
|
| 882 |
+
b"n": b"\n",
|
| 883 |
+
b"r": b"\r",
|
| 884 |
+
b"t": b"\t",
|
| 885 |
+
b"b": b"\b",
|
| 886 |
+
b"f": b"\f",
|
| 887 |
+
b"(": b"(",
|
| 888 |
+
b")": b")",
|
| 889 |
+
b"\\": b"\\",
|
| 890 |
+
ord(b"n"): b"\n",
|
| 891 |
+
ord(b"r"): b"\r",
|
| 892 |
+
ord(b"t"): b"\t",
|
| 893 |
+
ord(b"b"): b"\b",
|
| 894 |
+
ord(b"f"): b"\f",
|
| 895 |
+
ord(b"("): b"(",
|
| 896 |
+
ord(b")"): b")",
|
| 897 |
+
ord(b"\\"): b"\\",
|
| 898 |
+
}
|
| 899 |
+
|
| 900 |
+
@classmethod
|
| 901 |
+
def get_literal_string(cls, data, offset):
|
| 902 |
+
nesting_depth = 0
|
| 903 |
+
result = bytearray()
|
| 904 |
+
for m in cls.re_lit_str_token.finditer(data, offset):
|
| 905 |
+
result.extend(data[offset : m.start()])
|
| 906 |
+
if m.group(1):
|
| 907 |
+
result.extend(cls.escaped_chars[m.group(1)[1]])
|
| 908 |
+
elif m.group(2):
|
| 909 |
+
result.append(int(m.group(2)[1:], 8))
|
| 910 |
+
elif m.group(3):
|
| 911 |
+
pass
|
| 912 |
+
elif m.group(5):
|
| 913 |
+
result.extend(b"\n")
|
| 914 |
+
elif m.group(6):
|
| 915 |
+
result.extend(b"(")
|
| 916 |
+
nesting_depth += 1
|
| 917 |
+
elif m.group(7):
|
| 918 |
+
if nesting_depth == 0:
|
| 919 |
+
return bytes(result), m.end()
|
| 920 |
+
result.extend(b")")
|
| 921 |
+
nesting_depth -= 1
|
| 922 |
+
offset = m.end()
|
| 923 |
+
raise PdfFormatError("unfinished literal string")
|
| 924 |
+
|
| 925 |
+
re_xref_section_start = re.compile(whitespace_optional + rb"xref" + newline)
|
| 926 |
+
re_xref_subsection_start = re.compile(
|
| 927 |
+
whitespace_optional
|
| 928 |
+
+ rb"([0-9]+)"
|
| 929 |
+
+ whitespace_mandatory
|
| 930 |
+
+ rb"([0-9]+)"
|
| 931 |
+
+ whitespace_optional
|
| 932 |
+
+ newline_only
|
| 933 |
+
)
|
| 934 |
+
re_xref_entry = re.compile(rb"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)")
|
| 935 |
+
|
| 936 |
+
def read_xref_table(self, xref_section_offset):
|
| 937 |
+
subsection_found = False
|
| 938 |
+
m = self.re_xref_section_start.match(
|
| 939 |
+
self.buf, xref_section_offset + self.start_offset
|
| 940 |
+
)
|
| 941 |
+
check_format_condition(m, "xref section start not found")
|
| 942 |
+
offset = m.end()
|
| 943 |
+
while True:
|
| 944 |
+
m = self.re_xref_subsection_start.match(self.buf, offset)
|
| 945 |
+
if not m:
|
| 946 |
+
check_format_condition(
|
| 947 |
+
subsection_found, "xref subsection start not found"
|
| 948 |
+
)
|
| 949 |
+
break
|
| 950 |
+
subsection_found = True
|
| 951 |
+
offset = m.end()
|
| 952 |
+
first_object = int(m.group(1))
|
| 953 |
+
num_objects = int(m.group(2))
|
| 954 |
+
for i in range(first_object, first_object + num_objects):
|
| 955 |
+
m = self.re_xref_entry.match(self.buf, offset)
|
| 956 |
+
check_format_condition(m, "xref entry not found")
|
| 957 |
+
offset = m.end()
|
| 958 |
+
is_free = m.group(3) == b"f"
|
| 959 |
+
generation = int(m.group(2))
|
| 960 |
+
if not is_free:
|
| 961 |
+
new_entry = (int(m.group(1)), generation)
|
| 962 |
+
check_format_condition(
|
| 963 |
+
i not in self.xref_table or self.xref_table[i] == new_entry,
|
| 964 |
+
"xref entry duplicated (and not identical)",
|
| 965 |
+
)
|
| 966 |
+
self.xref_table[i] = new_entry
|
| 967 |
+
return offset
|
| 968 |
+
|
| 969 |
+
def read_indirect(self, ref, max_nesting=-1):
|
| 970 |
+
offset, generation = self.xref_table[ref[0]]
|
| 971 |
+
check_format_condition(
|
| 972 |
+
generation == ref[1],
|
| 973 |
+
f"expected to find generation {ref[1]} for object ID {ref[0]} in xref "
|
| 974 |
+
f"table, instead found generation {generation} at offset {offset}",
|
| 975 |
+
)
|
| 976 |
+
value = self.get_value(
|
| 977 |
+
self.buf,
|
| 978 |
+
offset + self.start_offset,
|
| 979 |
+
expect_indirect=IndirectReference(*ref),
|
| 980 |
+
max_nesting=max_nesting,
|
| 981 |
+
)[0]
|
| 982 |
+
self.cached_objects[ref] = value
|
| 983 |
+
return value
|
| 984 |
+
|
| 985 |
+
def linearize_page_tree(self, node=None):
|
| 986 |
+
if node is None:
|
| 987 |
+
node = self.page_tree_root
|
| 988 |
+
check_format_condition(
|
| 989 |
+
node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages"
|
| 990 |
+
)
|
| 991 |
+
pages = []
|
| 992 |
+
for kid in node[b"Kids"]:
|
| 993 |
+
kid_object = self.read_indirect(kid)
|
| 994 |
+
if kid_object[b"Type"] == b"Page":
|
| 995 |
+
pages.append(kid)
|
| 996 |
+
else:
|
| 997 |
+
pages.extend(self.linearize_page_tree(node=kid_object))
|
| 998 |
+
return pages
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PixarImagePlugin.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# PIXAR raster support for PIL
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 97-01-29 fl Created
|
| 9 |
+
#
|
| 10 |
+
# notes:
|
| 11 |
+
# This is incomplete; it is based on a few samples created with
|
| 12 |
+
# Photoshop 2.5 and 3.0, and a summary description provided by
|
| 13 |
+
# Greg Coats <gcoats@labiris.er.usgs.gov>. Hopefully, "L" and
|
| 14 |
+
# "RGBA" support will be added in future versions.
|
| 15 |
+
#
|
| 16 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 17 |
+
# Copyright (c) Fredrik Lundh 1997.
|
| 18 |
+
#
|
| 19 |
+
# See the README file for information on usage and redistribution.
|
| 20 |
+
#
|
| 21 |
+
|
| 22 |
+
from . import Image, ImageFile
|
| 23 |
+
from ._binary import i16le as i16
|
| 24 |
+
|
| 25 |
+
#
|
| 26 |
+
# helpers
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _accept(prefix):
|
| 30 |
+
return prefix[:4] == b"\200\350\000\000"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
##
|
| 34 |
+
# Image plugin for PIXAR raster images.
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class PixarImageFile(ImageFile.ImageFile):
|
| 38 |
+
|
| 39 |
+
format = "PIXAR"
|
| 40 |
+
format_description = "PIXAR raster image"
|
| 41 |
+
|
| 42 |
+
def _open(self):
|
| 43 |
+
|
| 44 |
+
# assuming a 4-byte magic label
|
| 45 |
+
s = self.fp.read(4)
|
| 46 |
+
if not _accept(s):
|
| 47 |
+
raise SyntaxError("not a PIXAR file")
|
| 48 |
+
|
| 49 |
+
# read rest of header
|
| 50 |
+
s = s + self.fp.read(508)
|
| 51 |
+
|
| 52 |
+
self._size = i16(s, 418), i16(s, 416)
|
| 53 |
+
|
| 54 |
+
# get channel/depth descriptions
|
| 55 |
+
mode = i16(s, 424), i16(s, 426)
|
| 56 |
+
|
| 57 |
+
if mode == (14, 2):
|
| 58 |
+
self.mode = "RGB"
|
| 59 |
+
# FIXME: to be continued...
|
| 60 |
+
|
| 61 |
+
# create tile descriptor (assuming "dumped")
|
| 62 |
+
self.tile = [("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))]
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
#
|
| 66 |
+
# --------------------------------------------------------------------
|
| 67 |
+
|
| 68 |
+
Image.register_open(PixarImageFile.format, PixarImageFile, _accept)
|
| 69 |
+
|
| 70 |
+
Image.register_extension(PixarImageFile.format, ".pxr")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PngImagePlugin.py
ADDED
|
@@ -0,0 +1,1432 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# PNG support code
|
| 6 |
+
#
|
| 7 |
+
# See "PNG (Portable Network Graphics) Specification, version 1.0;
|
| 8 |
+
# W3C Recommendation", 1996-10-01, Thomas Boutell (ed.).
|
| 9 |
+
#
|
| 10 |
+
# history:
|
| 11 |
+
# 1996-05-06 fl Created (couldn't resist it)
|
| 12 |
+
# 1996-12-14 fl Upgraded, added read and verify support (0.2)
|
| 13 |
+
# 1996-12-15 fl Separate PNG stream parser
|
| 14 |
+
# 1996-12-29 fl Added write support, added getchunks
|
| 15 |
+
# 1996-12-30 fl Eliminated circular references in decoder (0.3)
|
| 16 |
+
# 1998-07-12 fl Read/write 16-bit images as mode I (0.4)
|
| 17 |
+
# 2001-02-08 fl Added transparency support (from Zircon) (0.5)
|
| 18 |
+
# 2001-04-16 fl Don't close data source in "open" method (0.6)
|
| 19 |
+
# 2004-02-24 fl Don't even pretend to support interlaced files (0.7)
|
| 20 |
+
# 2004-08-31 fl Do basic sanity check on chunk identifiers (0.8)
|
| 21 |
+
# 2004-09-20 fl Added PngInfo chunk container
|
| 22 |
+
# 2004-12-18 fl Added DPI read support (based on code by Niki Spahiev)
|
| 23 |
+
# 2008-08-13 fl Added tRNS support for RGB images
|
| 24 |
+
# 2009-03-06 fl Support for preserving ICC profiles (by Florian Hoech)
|
| 25 |
+
# 2009-03-08 fl Added zTXT support (from Lowell Alleman)
|
| 26 |
+
# 2009-03-29 fl Read interlaced PNG files (from Conrado Porto Lopes Gouvua)
|
| 27 |
+
#
|
| 28 |
+
# Copyright (c) 1997-2009 by Secret Labs AB
|
| 29 |
+
# Copyright (c) 1996 by Fredrik Lundh
|
| 30 |
+
#
|
| 31 |
+
# See the README file for information on usage and redistribution.
|
| 32 |
+
#
|
| 33 |
+
|
| 34 |
+
import itertools
|
| 35 |
+
import logging
|
| 36 |
+
import re
|
| 37 |
+
import struct
|
| 38 |
+
import warnings
|
| 39 |
+
import zlib
|
| 40 |
+
from enum import IntEnum
|
| 41 |
+
|
| 42 |
+
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
| 43 |
+
from ._binary import i16be as i16
|
| 44 |
+
from ._binary import i32be as i32
|
| 45 |
+
from ._binary import o8
|
| 46 |
+
from ._binary import o16be as o16
|
| 47 |
+
from ._binary import o32be as o32
|
| 48 |
+
|
| 49 |
+
logger = logging.getLogger(__name__)
|
| 50 |
+
|
| 51 |
+
is_cid = re.compile(rb"\w\w\w\w").match
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
_MAGIC = b"\211PNG\r\n\032\n"
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
_MODES = {
|
| 58 |
+
# supported bits/color combinations, and corresponding modes/rawmodes
|
| 59 |
+
# Greyscale
|
| 60 |
+
(1, 0): ("1", "1"),
|
| 61 |
+
(2, 0): ("L", "L;2"),
|
| 62 |
+
(4, 0): ("L", "L;4"),
|
| 63 |
+
(8, 0): ("L", "L"),
|
| 64 |
+
(16, 0): ("I", "I;16B"),
|
| 65 |
+
# Truecolour
|
| 66 |
+
(8, 2): ("RGB", "RGB"),
|
| 67 |
+
(16, 2): ("RGB", "RGB;16B"),
|
| 68 |
+
# Indexed-colour
|
| 69 |
+
(1, 3): ("P", "P;1"),
|
| 70 |
+
(2, 3): ("P", "P;2"),
|
| 71 |
+
(4, 3): ("P", "P;4"),
|
| 72 |
+
(8, 3): ("P", "P"),
|
| 73 |
+
# Greyscale with alpha
|
| 74 |
+
(8, 4): ("LA", "LA"),
|
| 75 |
+
(16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
|
| 76 |
+
# Truecolour with alpha
|
| 77 |
+
(8, 6): ("RGBA", "RGBA"),
|
| 78 |
+
(16, 6): ("RGBA", "RGBA;16B"),
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
_simple_palette = re.compile(b"^\xff*\x00\xff*$")
|
| 83 |
+
|
| 84 |
+
MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK
|
| 85 |
+
"""
|
| 86 |
+
Maximum decompressed size for a iTXt or zTXt chunk.
|
| 87 |
+
Eliminates decompression bombs where compressed chunks can expand 1000x.
|
| 88 |
+
See :ref:`Text in PNG File Format<png-text>`.
|
| 89 |
+
"""
|
| 90 |
+
MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK
|
| 91 |
+
"""
|
| 92 |
+
Set the maximum total text chunk size.
|
| 93 |
+
See :ref:`Text in PNG File Format<png-text>`.
|
| 94 |
+
"""
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# APNG frame disposal modes
|
| 98 |
+
class Disposal(IntEnum):
|
| 99 |
+
OP_NONE = 0
|
| 100 |
+
"""
|
| 101 |
+
No disposal is done on this frame before rendering the next frame.
|
| 102 |
+
See :ref:`Saving APNG sequences<apng-saving>`.
|
| 103 |
+
"""
|
| 104 |
+
OP_BACKGROUND = 1
|
| 105 |
+
"""
|
| 106 |
+
This frame’s modified region is cleared to fully transparent black before rendering
|
| 107 |
+
the next frame.
|
| 108 |
+
See :ref:`Saving APNG sequences<apng-saving>`.
|
| 109 |
+
"""
|
| 110 |
+
OP_PREVIOUS = 2
|
| 111 |
+
"""
|
| 112 |
+
This frame’s modified region is reverted to the previous frame’s contents before
|
| 113 |
+
rendering the next frame.
|
| 114 |
+
See :ref:`Saving APNG sequences<apng-saving>`.
|
| 115 |
+
"""
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# APNG frame blend modes
|
| 119 |
+
class Blend(IntEnum):
|
| 120 |
+
OP_SOURCE = 0
|
| 121 |
+
"""
|
| 122 |
+
All color components of this frame, including alpha, overwrite the previous output
|
| 123 |
+
image contents.
|
| 124 |
+
See :ref:`Saving APNG sequences<apng-saving>`.
|
| 125 |
+
"""
|
| 126 |
+
OP_OVER = 1
|
| 127 |
+
"""
|
| 128 |
+
This frame should be alpha composited with the previous output image contents.
|
| 129 |
+
See :ref:`Saving APNG sequences<apng-saving>`.
|
| 130 |
+
"""
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def __getattr__(name):
|
| 134 |
+
deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
|
| 135 |
+
for enum, prefix in {Disposal: "APNG_DISPOSE_", Blend: "APNG_BLEND_"}.items():
|
| 136 |
+
if name.startswith(prefix):
|
| 137 |
+
name = name[len(prefix) :]
|
| 138 |
+
if name in enum.__members__:
|
| 139 |
+
warnings.warn(
|
| 140 |
+
prefix
|
| 141 |
+
+ name
|
| 142 |
+
+ " is "
|
| 143 |
+
+ deprecated
|
| 144 |
+
+ "Use "
|
| 145 |
+
+ enum.__name__
|
| 146 |
+
+ "."
|
| 147 |
+
+ name
|
| 148 |
+
+ " instead.",
|
| 149 |
+
DeprecationWarning,
|
| 150 |
+
stacklevel=2,
|
| 151 |
+
)
|
| 152 |
+
return enum[name]
|
| 153 |
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _safe_zlib_decompress(s):
|
| 157 |
+
dobj = zlib.decompressobj()
|
| 158 |
+
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
|
| 159 |
+
if dobj.unconsumed_tail:
|
| 160 |
+
raise ValueError("Decompressed Data Too Large")
|
| 161 |
+
return plaintext
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def _crc32(data, seed=0):
|
| 165 |
+
return zlib.crc32(data, seed) & 0xFFFFFFFF
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
# --------------------------------------------------------------------
|
| 169 |
+
# Support classes. Suitable for PNG and related formats like MNG etc.
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
class ChunkStream:
|
| 173 |
+
def __init__(self, fp):
|
| 174 |
+
|
| 175 |
+
self.fp = fp
|
| 176 |
+
self.queue = []
|
| 177 |
+
|
| 178 |
+
def read(self):
|
| 179 |
+
"""Fetch a new chunk. Returns header information."""
|
| 180 |
+
cid = None
|
| 181 |
+
|
| 182 |
+
if self.queue:
|
| 183 |
+
cid, pos, length = self.queue.pop()
|
| 184 |
+
self.fp.seek(pos)
|
| 185 |
+
else:
|
| 186 |
+
s = self.fp.read(8)
|
| 187 |
+
cid = s[4:]
|
| 188 |
+
pos = self.fp.tell()
|
| 189 |
+
length = i32(s)
|
| 190 |
+
|
| 191 |
+
if not is_cid(cid):
|
| 192 |
+
if not ImageFile.LOAD_TRUNCATED_IMAGES:
|
| 193 |
+
raise SyntaxError(f"broken PNG file (chunk {repr(cid)})")
|
| 194 |
+
|
| 195 |
+
return cid, pos, length
|
| 196 |
+
|
| 197 |
+
def __enter__(self):
|
| 198 |
+
return self
|
| 199 |
+
|
| 200 |
+
def __exit__(self, *args):
|
| 201 |
+
self.close()
|
| 202 |
+
|
| 203 |
+
def close(self):
|
| 204 |
+
self.queue = self.crc = self.fp = None
|
| 205 |
+
|
| 206 |
+
def push(self, cid, pos, length):
|
| 207 |
+
|
| 208 |
+
self.queue.append((cid, pos, length))
|
| 209 |
+
|
| 210 |
+
def call(self, cid, pos, length):
|
| 211 |
+
"""Call the appropriate chunk handler"""
|
| 212 |
+
|
| 213 |
+
logger.debug("STREAM %r %s %s", cid, pos, length)
|
| 214 |
+
return getattr(self, "chunk_" + cid.decode("ascii"))(pos, length)
|
| 215 |
+
|
| 216 |
+
def crc(self, cid, data):
|
| 217 |
+
"""Read and verify checksum"""
|
| 218 |
+
|
| 219 |
+
# Skip CRC checks for ancillary chunks if allowed to load truncated
|
| 220 |
+
# images
|
| 221 |
+
# 5th byte of first char is 1 [specs, section 5.4]
|
| 222 |
+
if ImageFile.LOAD_TRUNCATED_IMAGES and (cid[0] >> 5 & 1):
|
| 223 |
+
self.crc_skip(cid, data)
|
| 224 |
+
return
|
| 225 |
+
|
| 226 |
+
try:
|
| 227 |
+
crc1 = _crc32(data, _crc32(cid))
|
| 228 |
+
crc2 = i32(self.fp.read(4))
|
| 229 |
+
if crc1 != crc2:
|
| 230 |
+
raise SyntaxError(
|
| 231 |
+
f"broken PNG file (bad header checksum in {repr(cid)})"
|
| 232 |
+
)
|
| 233 |
+
except struct.error as e:
|
| 234 |
+
raise SyntaxError(
|
| 235 |
+
f"broken PNG file (incomplete checksum in {repr(cid)})"
|
| 236 |
+
) from e
|
| 237 |
+
|
| 238 |
+
def crc_skip(self, cid, data):
|
| 239 |
+
"""Read checksum. Used if the C module is not present"""
|
| 240 |
+
|
| 241 |
+
self.fp.read(4)
|
| 242 |
+
|
| 243 |
+
def verify(self, endchunk=b"IEND"):
|
| 244 |
+
|
| 245 |
+
# Simple approach; just calculate checksum for all remaining
|
| 246 |
+
# blocks. Must be called directly after open.
|
| 247 |
+
|
| 248 |
+
cids = []
|
| 249 |
+
|
| 250 |
+
while True:
|
| 251 |
+
try:
|
| 252 |
+
cid, pos, length = self.read()
|
| 253 |
+
except struct.error as e:
|
| 254 |
+
raise OSError("truncated PNG file") from e
|
| 255 |
+
|
| 256 |
+
if cid == endchunk:
|
| 257 |
+
break
|
| 258 |
+
self.crc(cid, ImageFile._safe_read(self.fp, length))
|
| 259 |
+
cids.append(cid)
|
| 260 |
+
|
| 261 |
+
return cids
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
class iTXt(str):
|
| 265 |
+
"""
|
| 266 |
+
Subclass of string to allow iTXt chunks to look like strings while
|
| 267 |
+
keeping their extra information
|
| 268 |
+
|
| 269 |
+
"""
|
| 270 |
+
|
| 271 |
+
@staticmethod
|
| 272 |
+
def __new__(cls, text, lang=None, tkey=None):
|
| 273 |
+
"""
|
| 274 |
+
:param cls: the class to use when creating the instance
|
| 275 |
+
:param text: value for this key
|
| 276 |
+
:param lang: language code
|
| 277 |
+
:param tkey: UTF-8 version of the key name
|
| 278 |
+
"""
|
| 279 |
+
|
| 280 |
+
self = str.__new__(cls, text)
|
| 281 |
+
self.lang = lang
|
| 282 |
+
self.tkey = tkey
|
| 283 |
+
return self
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
class PngInfo:
|
| 287 |
+
"""
|
| 288 |
+
PNG chunk container (for use with save(pnginfo=))
|
| 289 |
+
|
| 290 |
+
"""
|
| 291 |
+
|
| 292 |
+
def __init__(self):
|
| 293 |
+
self.chunks = []
|
| 294 |
+
|
| 295 |
+
def add(self, cid, data, after_idat=False):
|
| 296 |
+
"""Appends an arbitrary chunk. Use with caution.
|
| 297 |
+
|
| 298 |
+
:param cid: a byte string, 4 bytes long.
|
| 299 |
+
:param data: a byte string of the encoded data
|
| 300 |
+
:param after_idat: for use with private chunks. Whether the chunk
|
| 301 |
+
should be written after IDAT
|
| 302 |
+
|
| 303 |
+
"""
|
| 304 |
+
|
| 305 |
+
chunk = [cid, data]
|
| 306 |
+
if after_idat:
|
| 307 |
+
chunk.append(True)
|
| 308 |
+
self.chunks.append(tuple(chunk))
|
| 309 |
+
|
| 310 |
+
def add_itxt(self, key, value, lang="", tkey="", zip=False):
|
| 311 |
+
"""Appends an iTXt chunk.
|
| 312 |
+
|
| 313 |
+
:param key: latin-1 encodable text key name
|
| 314 |
+
:param value: value for this key
|
| 315 |
+
:param lang: language code
|
| 316 |
+
:param tkey: UTF-8 version of the key name
|
| 317 |
+
:param zip: compression flag
|
| 318 |
+
|
| 319 |
+
"""
|
| 320 |
+
|
| 321 |
+
if not isinstance(key, bytes):
|
| 322 |
+
key = key.encode("latin-1", "strict")
|
| 323 |
+
if not isinstance(value, bytes):
|
| 324 |
+
value = value.encode("utf-8", "strict")
|
| 325 |
+
if not isinstance(lang, bytes):
|
| 326 |
+
lang = lang.encode("utf-8", "strict")
|
| 327 |
+
if not isinstance(tkey, bytes):
|
| 328 |
+
tkey = tkey.encode("utf-8", "strict")
|
| 329 |
+
|
| 330 |
+
if zip:
|
| 331 |
+
self.add(
|
| 332 |
+
b"iTXt",
|
| 333 |
+
key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value),
|
| 334 |
+
)
|
| 335 |
+
else:
|
| 336 |
+
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)
|
| 337 |
+
|
| 338 |
+
def add_text(self, key, value, zip=False):
|
| 339 |
+
"""Appends a text chunk.
|
| 340 |
+
|
| 341 |
+
:param key: latin-1 encodable text key name
|
| 342 |
+
:param value: value for this key, text or an
|
| 343 |
+
:py:class:`PIL.PngImagePlugin.iTXt` instance
|
| 344 |
+
:param zip: compression flag
|
| 345 |
+
|
| 346 |
+
"""
|
| 347 |
+
if isinstance(value, iTXt):
|
| 348 |
+
return self.add_itxt(key, value, value.lang, value.tkey, zip=zip)
|
| 349 |
+
|
| 350 |
+
# The tEXt chunk stores latin-1 text
|
| 351 |
+
if not isinstance(value, bytes):
|
| 352 |
+
try:
|
| 353 |
+
value = value.encode("latin-1", "strict")
|
| 354 |
+
except UnicodeError:
|
| 355 |
+
return self.add_itxt(key, value, zip=zip)
|
| 356 |
+
|
| 357 |
+
if not isinstance(key, bytes):
|
| 358 |
+
key = key.encode("latin-1", "strict")
|
| 359 |
+
|
| 360 |
+
if zip:
|
| 361 |
+
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
|
| 362 |
+
else:
|
| 363 |
+
self.add(b"tEXt", key + b"\0" + value)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
# --------------------------------------------------------------------
|
| 367 |
+
# PNG image stream (IHDR/IEND)
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
class PngStream(ChunkStream):
|
| 371 |
+
def __init__(self, fp):
|
| 372 |
+
super().__init__(fp)
|
| 373 |
+
|
| 374 |
+
# local copies of Image attributes
|
| 375 |
+
self.im_info = {}
|
| 376 |
+
self.im_text = {}
|
| 377 |
+
self.im_size = (0, 0)
|
| 378 |
+
self.im_mode = None
|
| 379 |
+
self.im_tile = None
|
| 380 |
+
self.im_palette = None
|
| 381 |
+
self.im_custom_mimetype = None
|
| 382 |
+
self.im_n_frames = None
|
| 383 |
+
self._seq_num = None
|
| 384 |
+
self.rewind_state = None
|
| 385 |
+
|
| 386 |
+
self.text_memory = 0
|
| 387 |
+
|
| 388 |
+
def check_text_memory(self, chunklen):
|
| 389 |
+
self.text_memory += chunklen
|
| 390 |
+
if self.text_memory > MAX_TEXT_MEMORY:
|
| 391 |
+
raise ValueError(
|
| 392 |
+
"Too much memory used in text chunks: "
|
| 393 |
+
f"{self.text_memory}>MAX_TEXT_MEMORY"
|
| 394 |
+
)
|
| 395 |
+
|
| 396 |
+
def save_rewind(self):
|
| 397 |
+
self.rewind_state = {
|
| 398 |
+
"info": self.im_info.copy(),
|
| 399 |
+
"tile": self.im_tile,
|
| 400 |
+
"seq_num": self._seq_num,
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
def rewind(self):
|
| 404 |
+
self.im_info = self.rewind_state["info"]
|
| 405 |
+
self.im_tile = self.rewind_state["tile"]
|
| 406 |
+
self._seq_num = self.rewind_state["seq_num"]
|
| 407 |
+
|
| 408 |
+
def chunk_iCCP(self, pos, length):
|
| 409 |
+
|
| 410 |
+
# ICC profile
|
| 411 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 412 |
+
# according to PNG spec, the iCCP chunk contains:
|
| 413 |
+
# Profile name 1-79 bytes (character string)
|
| 414 |
+
# Null separator 1 byte (null character)
|
| 415 |
+
# Compression method 1 byte (0)
|
| 416 |
+
# Compressed profile n bytes (zlib with deflate compression)
|
| 417 |
+
i = s.find(b"\0")
|
| 418 |
+
logger.debug("iCCP profile name %r", s[:i])
|
| 419 |
+
logger.debug("Compression method %s", s[i])
|
| 420 |
+
comp_method = s[i]
|
| 421 |
+
if comp_method != 0:
|
| 422 |
+
raise SyntaxError(f"Unknown compression method {comp_method} in iCCP chunk")
|
| 423 |
+
try:
|
| 424 |
+
icc_profile = _safe_zlib_decompress(s[i + 2 :])
|
| 425 |
+
except ValueError:
|
| 426 |
+
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
| 427 |
+
icc_profile = None
|
| 428 |
+
else:
|
| 429 |
+
raise
|
| 430 |
+
except zlib.error:
|
| 431 |
+
icc_profile = None # FIXME
|
| 432 |
+
self.im_info["icc_profile"] = icc_profile
|
| 433 |
+
return s
|
| 434 |
+
|
| 435 |
+
def chunk_IHDR(self, pos, length):
|
| 436 |
+
|
| 437 |
+
# image header
|
| 438 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 439 |
+
self.im_size = i32(s, 0), i32(s, 4)
|
| 440 |
+
try:
|
| 441 |
+
self.im_mode, self.im_rawmode = _MODES[(s[8], s[9])]
|
| 442 |
+
except Exception:
|
| 443 |
+
pass
|
| 444 |
+
if s[12]:
|
| 445 |
+
self.im_info["interlace"] = 1
|
| 446 |
+
if s[11]:
|
| 447 |
+
raise SyntaxError("unknown filter category")
|
| 448 |
+
return s
|
| 449 |
+
|
| 450 |
+
def chunk_IDAT(self, pos, length):
|
| 451 |
+
|
| 452 |
+
# image data
|
| 453 |
+
if "bbox" in self.im_info:
|
| 454 |
+
tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)]
|
| 455 |
+
else:
|
| 456 |
+
if self.im_n_frames is not None:
|
| 457 |
+
self.im_info["default_image"] = True
|
| 458 |
+
tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)]
|
| 459 |
+
self.im_tile = tile
|
| 460 |
+
self.im_idat = length
|
| 461 |
+
raise EOFError
|
| 462 |
+
|
| 463 |
+
def chunk_IEND(self, pos, length):
|
| 464 |
+
|
| 465 |
+
# end of PNG image
|
| 466 |
+
raise EOFError
|
| 467 |
+
|
| 468 |
+
def chunk_PLTE(self, pos, length):
|
| 469 |
+
|
| 470 |
+
# palette
|
| 471 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 472 |
+
if self.im_mode == "P":
|
| 473 |
+
self.im_palette = "RGB", s
|
| 474 |
+
return s
|
| 475 |
+
|
| 476 |
+
def chunk_tRNS(self, pos, length):
|
| 477 |
+
|
| 478 |
+
# transparency
|
| 479 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 480 |
+
if self.im_mode == "P":
|
| 481 |
+
if _simple_palette.match(s):
|
| 482 |
+
# tRNS contains only one full-transparent entry,
|
| 483 |
+
# other entries are full opaque
|
| 484 |
+
i = s.find(b"\0")
|
| 485 |
+
if i >= 0:
|
| 486 |
+
self.im_info["transparency"] = i
|
| 487 |
+
else:
|
| 488 |
+
# otherwise, we have a byte string with one alpha value
|
| 489 |
+
# for each palette entry
|
| 490 |
+
self.im_info["transparency"] = s
|
| 491 |
+
elif self.im_mode in ("1", "L", "I"):
|
| 492 |
+
self.im_info["transparency"] = i16(s)
|
| 493 |
+
elif self.im_mode == "RGB":
|
| 494 |
+
self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4)
|
| 495 |
+
return s
|
| 496 |
+
|
| 497 |
+
def chunk_gAMA(self, pos, length):
|
| 498 |
+
# gamma setting
|
| 499 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 500 |
+
self.im_info["gamma"] = i32(s) / 100000.0
|
| 501 |
+
return s
|
| 502 |
+
|
| 503 |
+
def chunk_cHRM(self, pos, length):
|
| 504 |
+
# chromaticity, 8 unsigned ints, actual value is scaled by 100,000
|
| 505 |
+
# WP x,y, Red x,y, Green x,y Blue x,y
|
| 506 |
+
|
| 507 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 508 |
+
raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
|
| 509 |
+
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
|
| 510 |
+
return s
|
| 511 |
+
|
| 512 |
+
def chunk_sRGB(self, pos, length):
|
| 513 |
+
# srgb rendering intent, 1 byte
|
| 514 |
+
# 0 perceptual
|
| 515 |
+
# 1 relative colorimetric
|
| 516 |
+
# 2 saturation
|
| 517 |
+
# 3 absolute colorimetric
|
| 518 |
+
|
| 519 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 520 |
+
self.im_info["srgb"] = s[0]
|
| 521 |
+
return s
|
| 522 |
+
|
| 523 |
+
def chunk_pHYs(self, pos, length):
|
| 524 |
+
|
| 525 |
+
# pixels per unit
|
| 526 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 527 |
+
px, py = i32(s, 0), i32(s, 4)
|
| 528 |
+
unit = s[8]
|
| 529 |
+
if unit == 1: # meter
|
| 530 |
+
dpi = px * 0.0254, py * 0.0254
|
| 531 |
+
self.im_info["dpi"] = dpi
|
| 532 |
+
elif unit == 0:
|
| 533 |
+
self.im_info["aspect"] = px, py
|
| 534 |
+
return s
|
| 535 |
+
|
| 536 |
+
def chunk_tEXt(self, pos, length):
|
| 537 |
+
|
| 538 |
+
# text
|
| 539 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 540 |
+
try:
|
| 541 |
+
k, v = s.split(b"\0", 1)
|
| 542 |
+
except ValueError:
|
| 543 |
+
# fallback for broken tEXt tags
|
| 544 |
+
k = s
|
| 545 |
+
v = b""
|
| 546 |
+
if k:
|
| 547 |
+
k = k.decode("latin-1", "strict")
|
| 548 |
+
v_str = v.decode("latin-1", "replace")
|
| 549 |
+
|
| 550 |
+
self.im_info[k] = v if k == "exif" else v_str
|
| 551 |
+
self.im_text[k] = v_str
|
| 552 |
+
self.check_text_memory(len(v_str))
|
| 553 |
+
|
| 554 |
+
return s
|
| 555 |
+
|
| 556 |
+
def chunk_zTXt(self, pos, length):
|
| 557 |
+
|
| 558 |
+
# compressed text
|
| 559 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 560 |
+
try:
|
| 561 |
+
k, v = s.split(b"\0", 1)
|
| 562 |
+
except ValueError:
|
| 563 |
+
k = s
|
| 564 |
+
v = b""
|
| 565 |
+
if v:
|
| 566 |
+
comp_method = v[0]
|
| 567 |
+
else:
|
| 568 |
+
comp_method = 0
|
| 569 |
+
if comp_method != 0:
|
| 570 |
+
raise SyntaxError(f"Unknown compression method {comp_method} in zTXt chunk")
|
| 571 |
+
try:
|
| 572 |
+
v = _safe_zlib_decompress(v[1:])
|
| 573 |
+
except ValueError:
|
| 574 |
+
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
| 575 |
+
v = b""
|
| 576 |
+
else:
|
| 577 |
+
raise
|
| 578 |
+
except zlib.error:
|
| 579 |
+
v = b""
|
| 580 |
+
|
| 581 |
+
if k:
|
| 582 |
+
k = k.decode("latin-1", "strict")
|
| 583 |
+
v = v.decode("latin-1", "replace")
|
| 584 |
+
|
| 585 |
+
self.im_info[k] = self.im_text[k] = v
|
| 586 |
+
self.check_text_memory(len(v))
|
| 587 |
+
|
| 588 |
+
return s
|
| 589 |
+
|
| 590 |
+
def chunk_iTXt(self, pos, length):
|
| 591 |
+
|
| 592 |
+
# international text
|
| 593 |
+
r = s = ImageFile._safe_read(self.fp, length)
|
| 594 |
+
try:
|
| 595 |
+
k, r = r.split(b"\0", 1)
|
| 596 |
+
except ValueError:
|
| 597 |
+
return s
|
| 598 |
+
if len(r) < 2:
|
| 599 |
+
return s
|
| 600 |
+
cf, cm, r = r[0], r[1], r[2:]
|
| 601 |
+
try:
|
| 602 |
+
lang, tk, v = r.split(b"\0", 2)
|
| 603 |
+
except ValueError:
|
| 604 |
+
return s
|
| 605 |
+
if cf != 0:
|
| 606 |
+
if cm == 0:
|
| 607 |
+
try:
|
| 608 |
+
v = _safe_zlib_decompress(v)
|
| 609 |
+
except ValueError:
|
| 610 |
+
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
| 611 |
+
return s
|
| 612 |
+
else:
|
| 613 |
+
raise
|
| 614 |
+
except zlib.error:
|
| 615 |
+
return s
|
| 616 |
+
else:
|
| 617 |
+
return s
|
| 618 |
+
try:
|
| 619 |
+
k = k.decode("latin-1", "strict")
|
| 620 |
+
lang = lang.decode("utf-8", "strict")
|
| 621 |
+
tk = tk.decode("utf-8", "strict")
|
| 622 |
+
v = v.decode("utf-8", "strict")
|
| 623 |
+
except UnicodeError:
|
| 624 |
+
return s
|
| 625 |
+
|
| 626 |
+
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
|
| 627 |
+
self.check_text_memory(len(v))
|
| 628 |
+
|
| 629 |
+
return s
|
| 630 |
+
|
| 631 |
+
def chunk_eXIf(self, pos, length):
|
| 632 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 633 |
+
self.im_info["exif"] = b"Exif\x00\x00" + s
|
| 634 |
+
return s
|
| 635 |
+
|
| 636 |
+
# APNG chunks
|
| 637 |
+
def chunk_acTL(self, pos, length):
|
| 638 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 639 |
+
if self.im_n_frames is not None:
|
| 640 |
+
self.im_n_frames = None
|
| 641 |
+
warnings.warn("Invalid APNG, will use default PNG image if possible")
|
| 642 |
+
return s
|
| 643 |
+
n_frames = i32(s)
|
| 644 |
+
if n_frames == 0 or n_frames > 0x80000000:
|
| 645 |
+
warnings.warn("Invalid APNG, will use default PNG image if possible")
|
| 646 |
+
return s
|
| 647 |
+
self.im_n_frames = n_frames
|
| 648 |
+
self.im_info["loop"] = i32(s, 4)
|
| 649 |
+
self.im_custom_mimetype = "image/apng"
|
| 650 |
+
return s
|
| 651 |
+
|
| 652 |
+
def chunk_fcTL(self, pos, length):
|
| 653 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 654 |
+
seq = i32(s)
|
| 655 |
+
if (self._seq_num is None and seq != 0) or (
|
| 656 |
+
self._seq_num is not None and self._seq_num != seq - 1
|
| 657 |
+
):
|
| 658 |
+
raise SyntaxError("APNG contains frame sequence errors")
|
| 659 |
+
self._seq_num = seq
|
| 660 |
+
width, height = i32(s, 4), i32(s, 8)
|
| 661 |
+
px, py = i32(s, 12), i32(s, 16)
|
| 662 |
+
im_w, im_h = self.im_size
|
| 663 |
+
if px + width > im_w or py + height > im_h:
|
| 664 |
+
raise SyntaxError("APNG contains invalid frames")
|
| 665 |
+
self.im_info["bbox"] = (px, py, px + width, py + height)
|
| 666 |
+
delay_num, delay_den = i16(s, 20), i16(s, 22)
|
| 667 |
+
if delay_den == 0:
|
| 668 |
+
delay_den = 100
|
| 669 |
+
self.im_info["duration"] = float(delay_num) / float(delay_den) * 1000
|
| 670 |
+
self.im_info["disposal"] = s[24]
|
| 671 |
+
self.im_info["blend"] = s[25]
|
| 672 |
+
return s
|
| 673 |
+
|
| 674 |
+
def chunk_fdAT(self, pos, length):
|
| 675 |
+
s = ImageFile._safe_read(self.fp, 4)
|
| 676 |
+
seq = i32(s)
|
| 677 |
+
if self._seq_num != seq - 1:
|
| 678 |
+
raise SyntaxError("APNG contains frame sequence errors")
|
| 679 |
+
self._seq_num = seq
|
| 680 |
+
return self.chunk_IDAT(pos + 4, length - 4)
|
| 681 |
+
|
| 682 |
+
|
| 683 |
+
# --------------------------------------------------------------------
|
| 684 |
+
# PNG reader
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def _accept(prefix):
|
| 688 |
+
return prefix[:8] == _MAGIC
|
| 689 |
+
|
| 690 |
+
|
| 691 |
+
##
|
| 692 |
+
# Image plugin for PNG images.
|
| 693 |
+
|
| 694 |
+
|
| 695 |
+
class PngImageFile(ImageFile.ImageFile):
|
| 696 |
+
|
| 697 |
+
format = "PNG"
|
| 698 |
+
format_description = "Portable network graphics"
|
| 699 |
+
|
| 700 |
+
def _open(self):
|
| 701 |
+
|
| 702 |
+
if not _accept(self.fp.read(8)):
|
| 703 |
+
raise SyntaxError("not a PNG file")
|
| 704 |
+
self.__fp = self.fp
|
| 705 |
+
self.__frame = 0
|
| 706 |
+
|
| 707 |
+
#
|
| 708 |
+
# Parse headers up to the first IDAT or fDAT chunk
|
| 709 |
+
|
| 710 |
+
self.private_chunks = []
|
| 711 |
+
self.png = PngStream(self.fp)
|
| 712 |
+
|
| 713 |
+
while True:
|
| 714 |
+
|
| 715 |
+
#
|
| 716 |
+
# get next chunk
|
| 717 |
+
|
| 718 |
+
cid, pos, length = self.png.read()
|
| 719 |
+
|
| 720 |
+
try:
|
| 721 |
+
s = self.png.call(cid, pos, length)
|
| 722 |
+
except EOFError:
|
| 723 |
+
break
|
| 724 |
+
except AttributeError:
|
| 725 |
+
logger.debug("%r %s %s (unknown)", cid, pos, length)
|
| 726 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 727 |
+
if cid[1:2].islower():
|
| 728 |
+
self.private_chunks.append((cid, s))
|
| 729 |
+
|
| 730 |
+
self.png.crc(cid, s)
|
| 731 |
+
|
| 732 |
+
#
|
| 733 |
+
# Copy relevant attributes from the PngStream. An alternative
|
| 734 |
+
# would be to let the PngStream class modify these attributes
|
| 735 |
+
# directly, but that introduces circular references which are
|
| 736 |
+
# difficult to break if things go wrong in the decoder...
|
| 737 |
+
# (believe me, I've tried ;-)
|
| 738 |
+
|
| 739 |
+
self.mode = self.png.im_mode
|
| 740 |
+
self._size = self.png.im_size
|
| 741 |
+
self.info = self.png.im_info
|
| 742 |
+
self._text = None
|
| 743 |
+
self.tile = self.png.im_tile
|
| 744 |
+
self.custom_mimetype = self.png.im_custom_mimetype
|
| 745 |
+
self.n_frames = self.png.im_n_frames or 1
|
| 746 |
+
self.default_image = self.info.get("default_image", False)
|
| 747 |
+
|
| 748 |
+
if self.png.im_palette:
|
| 749 |
+
rawmode, data = self.png.im_palette
|
| 750 |
+
self.palette = ImagePalette.raw(rawmode, data)
|
| 751 |
+
|
| 752 |
+
if cid == b"fdAT":
|
| 753 |
+
self.__prepare_idat = length - 4
|
| 754 |
+
else:
|
| 755 |
+
self.__prepare_idat = length # used by load_prepare()
|
| 756 |
+
|
| 757 |
+
if self.png.im_n_frames is not None:
|
| 758 |
+
self._close_exclusive_fp_after_loading = False
|
| 759 |
+
self.png.save_rewind()
|
| 760 |
+
self.__rewind_idat = self.__prepare_idat
|
| 761 |
+
self.__rewind = self.__fp.tell()
|
| 762 |
+
if self.default_image:
|
| 763 |
+
# IDAT chunk contains default image and not first animation frame
|
| 764 |
+
self.n_frames += 1
|
| 765 |
+
self._seek(0)
|
| 766 |
+
self.is_animated = self.n_frames > 1
|
| 767 |
+
|
| 768 |
+
@property
|
| 769 |
+
def text(self):
|
| 770 |
+
# experimental
|
| 771 |
+
if self._text is None:
|
| 772 |
+
# iTxt, tEXt and zTXt chunks may appear at the end of the file
|
| 773 |
+
# So load the file to ensure that they are read
|
| 774 |
+
if self.is_animated:
|
| 775 |
+
frame = self.__frame
|
| 776 |
+
# for APNG, seek to the final frame before loading
|
| 777 |
+
self.seek(self.n_frames - 1)
|
| 778 |
+
self.load()
|
| 779 |
+
if self.is_animated:
|
| 780 |
+
self.seek(frame)
|
| 781 |
+
return self._text
|
| 782 |
+
|
| 783 |
+
def verify(self):
|
| 784 |
+
"""Verify PNG file"""
|
| 785 |
+
|
| 786 |
+
if self.fp is None:
|
| 787 |
+
raise RuntimeError("verify must be called directly after open")
|
| 788 |
+
|
| 789 |
+
# back up to beginning of IDAT block
|
| 790 |
+
self.fp.seek(self.tile[0][2] - 8)
|
| 791 |
+
|
| 792 |
+
self.png.verify()
|
| 793 |
+
self.png.close()
|
| 794 |
+
|
| 795 |
+
if self._exclusive_fp:
|
| 796 |
+
self.fp.close()
|
| 797 |
+
self.fp = None
|
| 798 |
+
|
| 799 |
+
def seek(self, frame):
|
| 800 |
+
if not self._seek_check(frame):
|
| 801 |
+
return
|
| 802 |
+
if frame < self.__frame:
|
| 803 |
+
self._seek(0, True)
|
| 804 |
+
|
| 805 |
+
last_frame = self.__frame
|
| 806 |
+
for f in range(self.__frame + 1, frame + 1):
|
| 807 |
+
try:
|
| 808 |
+
self._seek(f)
|
| 809 |
+
except EOFError as e:
|
| 810 |
+
self.seek(last_frame)
|
| 811 |
+
raise EOFError("no more images in APNG file") from e
|
| 812 |
+
|
| 813 |
+
def _seek(self, frame, rewind=False):
|
| 814 |
+
if frame == 0:
|
| 815 |
+
if rewind:
|
| 816 |
+
self.__fp.seek(self.__rewind)
|
| 817 |
+
self.png.rewind()
|
| 818 |
+
self.__prepare_idat = self.__rewind_idat
|
| 819 |
+
self.im = None
|
| 820 |
+
if self.pyaccess:
|
| 821 |
+
self.pyaccess = None
|
| 822 |
+
self.info = self.png.im_info
|
| 823 |
+
self.tile = self.png.im_tile
|
| 824 |
+
self.fp = self.__fp
|
| 825 |
+
self._prev_im = None
|
| 826 |
+
self.dispose = None
|
| 827 |
+
self.default_image = self.info.get("default_image", False)
|
| 828 |
+
self.dispose_op = self.info.get("disposal")
|
| 829 |
+
self.blend_op = self.info.get("blend")
|
| 830 |
+
self.dispose_extent = self.info.get("bbox")
|
| 831 |
+
self.__frame = 0
|
| 832 |
+
else:
|
| 833 |
+
if frame != self.__frame + 1:
|
| 834 |
+
raise ValueError(f"cannot seek to frame {frame}")
|
| 835 |
+
|
| 836 |
+
# ensure previous frame was loaded
|
| 837 |
+
self.load()
|
| 838 |
+
|
| 839 |
+
if self.dispose:
|
| 840 |
+
self.im.paste(self.dispose, self.dispose_extent)
|
| 841 |
+
self._prev_im = self.im.copy()
|
| 842 |
+
|
| 843 |
+
self.fp = self.__fp
|
| 844 |
+
|
| 845 |
+
# advance to the next frame
|
| 846 |
+
if self.__prepare_idat:
|
| 847 |
+
ImageFile._safe_read(self.fp, self.__prepare_idat)
|
| 848 |
+
self.__prepare_idat = 0
|
| 849 |
+
frame_start = False
|
| 850 |
+
while True:
|
| 851 |
+
self.fp.read(4) # CRC
|
| 852 |
+
|
| 853 |
+
try:
|
| 854 |
+
cid, pos, length = self.png.read()
|
| 855 |
+
except (struct.error, SyntaxError):
|
| 856 |
+
break
|
| 857 |
+
|
| 858 |
+
if cid == b"IEND":
|
| 859 |
+
raise EOFError("No more images in APNG file")
|
| 860 |
+
if cid == b"fcTL":
|
| 861 |
+
if frame_start:
|
| 862 |
+
# there must be at least one fdAT chunk between fcTL chunks
|
| 863 |
+
raise SyntaxError("APNG missing frame data")
|
| 864 |
+
frame_start = True
|
| 865 |
+
|
| 866 |
+
try:
|
| 867 |
+
self.png.call(cid, pos, length)
|
| 868 |
+
except UnicodeDecodeError:
|
| 869 |
+
break
|
| 870 |
+
except EOFError:
|
| 871 |
+
if cid == b"fdAT":
|
| 872 |
+
length -= 4
|
| 873 |
+
if frame_start:
|
| 874 |
+
self.__prepare_idat = length
|
| 875 |
+
break
|
| 876 |
+
ImageFile._safe_read(self.fp, length)
|
| 877 |
+
except AttributeError:
|
| 878 |
+
logger.debug("%r %s %s (unknown)", cid, pos, length)
|
| 879 |
+
ImageFile._safe_read(self.fp, length)
|
| 880 |
+
|
| 881 |
+
self.__frame = frame
|
| 882 |
+
self.tile = self.png.im_tile
|
| 883 |
+
self.dispose_op = self.info.get("disposal")
|
| 884 |
+
self.blend_op = self.info.get("blend")
|
| 885 |
+
self.dispose_extent = self.info.get("bbox")
|
| 886 |
+
|
| 887 |
+
if not self.tile:
|
| 888 |
+
raise EOFError
|
| 889 |
+
|
| 890 |
+
# setup frame disposal (actual disposal done when needed in the next _seek())
|
| 891 |
+
if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS:
|
| 892 |
+
self.dispose_op = Disposal.OP_BACKGROUND
|
| 893 |
+
|
| 894 |
+
if self.dispose_op == Disposal.OP_PREVIOUS:
|
| 895 |
+
self.dispose = self._prev_im.copy()
|
| 896 |
+
self.dispose = self._crop(self.dispose, self.dispose_extent)
|
| 897 |
+
elif self.dispose_op == Disposal.OP_BACKGROUND:
|
| 898 |
+
self.dispose = Image.core.fill(self.mode, self.size)
|
| 899 |
+
self.dispose = self._crop(self.dispose, self.dispose_extent)
|
| 900 |
+
else:
|
| 901 |
+
self.dispose = None
|
| 902 |
+
|
| 903 |
+
def tell(self):
|
| 904 |
+
return self.__frame
|
| 905 |
+
|
| 906 |
+
def load_prepare(self):
|
| 907 |
+
"""internal: prepare to read PNG file"""
|
| 908 |
+
|
| 909 |
+
if self.info.get("interlace"):
|
| 910 |
+
self.decoderconfig = self.decoderconfig + (1,)
|
| 911 |
+
|
| 912 |
+
self.__idat = self.__prepare_idat # used by load_read()
|
| 913 |
+
ImageFile.ImageFile.load_prepare(self)
|
| 914 |
+
|
| 915 |
+
def load_read(self, read_bytes):
|
| 916 |
+
"""internal: read more image data"""
|
| 917 |
+
|
| 918 |
+
while self.__idat == 0:
|
| 919 |
+
# end of chunk, skip forward to next one
|
| 920 |
+
|
| 921 |
+
self.fp.read(4) # CRC
|
| 922 |
+
|
| 923 |
+
cid, pos, length = self.png.read()
|
| 924 |
+
|
| 925 |
+
if cid not in [b"IDAT", b"DDAT", b"fdAT"]:
|
| 926 |
+
self.png.push(cid, pos, length)
|
| 927 |
+
return b""
|
| 928 |
+
|
| 929 |
+
if cid == b"fdAT":
|
| 930 |
+
try:
|
| 931 |
+
self.png.call(cid, pos, length)
|
| 932 |
+
except EOFError:
|
| 933 |
+
pass
|
| 934 |
+
self.__idat = length - 4 # sequence_num has already been read
|
| 935 |
+
else:
|
| 936 |
+
self.__idat = length # empty chunks are allowed
|
| 937 |
+
|
| 938 |
+
# read more data from this chunk
|
| 939 |
+
if read_bytes <= 0:
|
| 940 |
+
read_bytes = self.__idat
|
| 941 |
+
else:
|
| 942 |
+
read_bytes = min(read_bytes, self.__idat)
|
| 943 |
+
|
| 944 |
+
self.__idat = self.__idat - read_bytes
|
| 945 |
+
|
| 946 |
+
return self.fp.read(read_bytes)
|
| 947 |
+
|
| 948 |
+
def load_end(self):
|
| 949 |
+
"""internal: finished reading image data"""
|
| 950 |
+
if self.__idat != 0:
|
| 951 |
+
self.fp.read(self.__idat)
|
| 952 |
+
while True:
|
| 953 |
+
self.fp.read(4) # CRC
|
| 954 |
+
|
| 955 |
+
try:
|
| 956 |
+
cid, pos, length = self.png.read()
|
| 957 |
+
except (struct.error, SyntaxError):
|
| 958 |
+
break
|
| 959 |
+
|
| 960 |
+
if cid == b"IEND":
|
| 961 |
+
break
|
| 962 |
+
elif cid == b"fcTL" and self.is_animated:
|
| 963 |
+
# start of the next frame, stop reading
|
| 964 |
+
self.__prepare_idat = 0
|
| 965 |
+
self.png.push(cid, pos, length)
|
| 966 |
+
break
|
| 967 |
+
|
| 968 |
+
try:
|
| 969 |
+
self.png.call(cid, pos, length)
|
| 970 |
+
except UnicodeDecodeError:
|
| 971 |
+
break
|
| 972 |
+
except EOFError:
|
| 973 |
+
if cid == b"fdAT":
|
| 974 |
+
length -= 4
|
| 975 |
+
ImageFile._safe_read(self.fp, length)
|
| 976 |
+
except AttributeError:
|
| 977 |
+
logger.debug("%r %s %s (unknown)", cid, pos, length)
|
| 978 |
+
s = ImageFile._safe_read(self.fp, length)
|
| 979 |
+
if cid[1:2].islower():
|
| 980 |
+
self.private_chunks.append((cid, s, True))
|
| 981 |
+
self._text = self.png.im_text
|
| 982 |
+
if not self.is_animated:
|
| 983 |
+
self.png.close()
|
| 984 |
+
self.png = None
|
| 985 |
+
else:
|
| 986 |
+
if self._prev_im and self.blend_op == Blend.OP_OVER:
|
| 987 |
+
updated = self._crop(self.im, self.dispose_extent)
|
| 988 |
+
self._prev_im.paste(
|
| 989 |
+
updated, self.dispose_extent, updated.convert("RGBA")
|
| 990 |
+
)
|
| 991 |
+
self.im = self._prev_im
|
| 992 |
+
if self.pyaccess:
|
| 993 |
+
self.pyaccess = None
|
| 994 |
+
|
| 995 |
+
def _getexif(self):
|
| 996 |
+
if "exif" not in self.info:
|
| 997 |
+
self.load()
|
| 998 |
+
if "exif" not in self.info and "Raw profile type exif" not in self.info:
|
| 999 |
+
return None
|
| 1000 |
+
return self.getexif()._get_merged_dict()
|
| 1001 |
+
|
| 1002 |
+
def getexif(self):
|
| 1003 |
+
if "exif" not in self.info:
|
| 1004 |
+
self.load()
|
| 1005 |
+
|
| 1006 |
+
return super().getexif()
|
| 1007 |
+
|
| 1008 |
+
def getxmp(self):
|
| 1009 |
+
"""
|
| 1010 |
+
Returns a dictionary containing the XMP tags.
|
| 1011 |
+
Requires defusedxml to be installed.
|
| 1012 |
+
|
| 1013 |
+
:returns: XMP tags in a dictionary.
|
| 1014 |
+
"""
|
| 1015 |
+
return (
|
| 1016 |
+
self._getxmp(self.info["XML:com.adobe.xmp"])
|
| 1017 |
+
if "XML:com.adobe.xmp" in self.info
|
| 1018 |
+
else {}
|
| 1019 |
+
)
|
| 1020 |
+
|
| 1021 |
+
def _close__fp(self):
|
| 1022 |
+
try:
|
| 1023 |
+
if self.__fp != self.fp:
|
| 1024 |
+
self.__fp.close()
|
| 1025 |
+
except AttributeError:
|
| 1026 |
+
pass
|
| 1027 |
+
finally:
|
| 1028 |
+
self.__fp = None
|
| 1029 |
+
|
| 1030 |
+
|
| 1031 |
+
# --------------------------------------------------------------------
|
| 1032 |
+
# PNG writer
|
| 1033 |
+
|
| 1034 |
+
_OUTMODES = {
|
| 1035 |
+
# supported PIL modes, and corresponding rawmodes/bits/color combinations
|
| 1036 |
+
"1": ("1", b"\x01\x00"),
|
| 1037 |
+
"L;1": ("L;1", b"\x01\x00"),
|
| 1038 |
+
"L;2": ("L;2", b"\x02\x00"),
|
| 1039 |
+
"L;4": ("L;4", b"\x04\x00"),
|
| 1040 |
+
"L": ("L", b"\x08\x00"),
|
| 1041 |
+
"LA": ("LA", b"\x08\x04"),
|
| 1042 |
+
"I": ("I;16B", b"\x10\x00"),
|
| 1043 |
+
"I;16": ("I;16B", b"\x10\x00"),
|
| 1044 |
+
"P;1": ("P;1", b"\x01\x03"),
|
| 1045 |
+
"P;2": ("P;2", b"\x02\x03"),
|
| 1046 |
+
"P;4": ("P;4", b"\x04\x03"),
|
| 1047 |
+
"P": ("P", b"\x08\x03"),
|
| 1048 |
+
"RGB": ("RGB", b"\x08\x02"),
|
| 1049 |
+
"RGBA": ("RGBA", b"\x08\x06"),
|
| 1050 |
+
}
|
| 1051 |
+
|
| 1052 |
+
|
| 1053 |
+
def putchunk(fp, cid, *data):
|
| 1054 |
+
"""Write a PNG chunk (including CRC field)"""
|
| 1055 |
+
|
| 1056 |
+
data = b"".join(data)
|
| 1057 |
+
|
| 1058 |
+
fp.write(o32(len(data)) + cid)
|
| 1059 |
+
fp.write(data)
|
| 1060 |
+
crc = _crc32(data, _crc32(cid))
|
| 1061 |
+
fp.write(o32(crc))
|
| 1062 |
+
|
| 1063 |
+
|
| 1064 |
+
class _idat:
|
| 1065 |
+
# wrap output from the encoder in IDAT chunks
|
| 1066 |
+
|
| 1067 |
+
def __init__(self, fp, chunk):
|
| 1068 |
+
self.fp = fp
|
| 1069 |
+
self.chunk = chunk
|
| 1070 |
+
|
| 1071 |
+
def write(self, data):
|
| 1072 |
+
self.chunk(self.fp, b"IDAT", data)
|
| 1073 |
+
|
| 1074 |
+
|
| 1075 |
+
class _fdat:
|
| 1076 |
+
# wrap encoder output in fdAT chunks
|
| 1077 |
+
|
| 1078 |
+
def __init__(self, fp, chunk, seq_num):
|
| 1079 |
+
self.fp = fp
|
| 1080 |
+
self.chunk = chunk
|
| 1081 |
+
self.seq_num = seq_num
|
| 1082 |
+
|
| 1083 |
+
def write(self, data):
|
| 1084 |
+
self.chunk(self.fp, b"fdAT", o32(self.seq_num), data)
|
| 1085 |
+
self.seq_num += 1
|
| 1086 |
+
|
| 1087 |
+
|
| 1088 |
+
def _write_multiple_frames(im, fp, chunk, rawmode):
|
| 1089 |
+
default_image = im.encoderinfo.get("default_image", im.info.get("default_image"))
|
| 1090 |
+
duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
|
| 1091 |
+
loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
|
| 1092 |
+
disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE))
|
| 1093 |
+
blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE))
|
| 1094 |
+
|
| 1095 |
+
if default_image:
|
| 1096 |
+
chain = itertools.chain(im.encoderinfo.get("append_images", []))
|
| 1097 |
+
else:
|
| 1098 |
+
chain = itertools.chain([im], im.encoderinfo.get("append_images", []))
|
| 1099 |
+
|
| 1100 |
+
im_frames = []
|
| 1101 |
+
frame_count = 0
|
| 1102 |
+
for im_seq in chain:
|
| 1103 |
+
for im_frame in ImageSequence.Iterator(im_seq):
|
| 1104 |
+
im_frame = im_frame.copy()
|
| 1105 |
+
if im_frame.mode != im.mode:
|
| 1106 |
+
if im.mode == "P":
|
| 1107 |
+
im_frame = im_frame.convert(im.mode, palette=im.palette)
|
| 1108 |
+
else:
|
| 1109 |
+
im_frame = im_frame.convert(im.mode)
|
| 1110 |
+
encoderinfo = im.encoderinfo.copy()
|
| 1111 |
+
if isinstance(duration, (list, tuple)):
|
| 1112 |
+
encoderinfo["duration"] = duration[frame_count]
|
| 1113 |
+
if isinstance(disposal, (list, tuple)):
|
| 1114 |
+
encoderinfo["disposal"] = disposal[frame_count]
|
| 1115 |
+
if isinstance(blend, (list, tuple)):
|
| 1116 |
+
encoderinfo["blend"] = blend[frame_count]
|
| 1117 |
+
frame_count += 1
|
| 1118 |
+
|
| 1119 |
+
if im_frames:
|
| 1120 |
+
previous = im_frames[-1]
|
| 1121 |
+
prev_disposal = previous["encoderinfo"].get("disposal")
|
| 1122 |
+
prev_blend = previous["encoderinfo"].get("blend")
|
| 1123 |
+
if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2:
|
| 1124 |
+
prev_disposal = Disposal.OP_BACKGROUND
|
| 1125 |
+
|
| 1126 |
+
if prev_disposal == Disposal.OP_BACKGROUND:
|
| 1127 |
+
base_im = previous["im"]
|
| 1128 |
+
dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0))
|
| 1129 |
+
bbox = previous["bbox"]
|
| 1130 |
+
if bbox:
|
| 1131 |
+
dispose = dispose.crop(bbox)
|
| 1132 |
+
else:
|
| 1133 |
+
bbox = (0, 0) + im.size
|
| 1134 |
+
base_im.paste(dispose, bbox)
|
| 1135 |
+
elif prev_disposal == Disposal.OP_PREVIOUS:
|
| 1136 |
+
base_im = im_frames[-2]["im"]
|
| 1137 |
+
else:
|
| 1138 |
+
base_im = previous["im"]
|
| 1139 |
+
delta = ImageChops.subtract_modulo(
|
| 1140 |
+
im_frame.convert("RGB"), base_im.convert("RGB")
|
| 1141 |
+
)
|
| 1142 |
+
bbox = delta.getbbox()
|
| 1143 |
+
if (
|
| 1144 |
+
not bbox
|
| 1145 |
+
and prev_disposal == encoderinfo.get("disposal")
|
| 1146 |
+
and prev_blend == encoderinfo.get("blend")
|
| 1147 |
+
):
|
| 1148 |
+
if isinstance(duration, (list, tuple)):
|
| 1149 |
+
previous["encoderinfo"]["duration"] += encoderinfo["duration"]
|
| 1150 |
+
continue
|
| 1151 |
+
else:
|
| 1152 |
+
bbox = None
|
| 1153 |
+
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
|
| 1154 |
+
|
| 1155 |
+
# animation control
|
| 1156 |
+
chunk(
|
| 1157 |
+
fp,
|
| 1158 |
+
b"acTL",
|
| 1159 |
+
o32(len(im_frames)), # 0: num_frames
|
| 1160 |
+
o32(loop), # 4: num_plays
|
| 1161 |
+
)
|
| 1162 |
+
|
| 1163 |
+
# default image IDAT (if it exists)
|
| 1164 |
+
if default_image:
|
| 1165 |
+
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
|
| 1166 |
+
|
| 1167 |
+
seq_num = 0
|
| 1168 |
+
for frame, frame_data in enumerate(im_frames):
|
| 1169 |
+
im_frame = frame_data["im"]
|
| 1170 |
+
if not frame_data["bbox"]:
|
| 1171 |
+
bbox = (0, 0) + im_frame.size
|
| 1172 |
+
else:
|
| 1173 |
+
bbox = frame_data["bbox"]
|
| 1174 |
+
im_frame = im_frame.crop(bbox)
|
| 1175 |
+
size = im_frame.size
|
| 1176 |
+
encoderinfo = frame_data["encoderinfo"]
|
| 1177 |
+
frame_duration = int(round(encoderinfo.get("duration", duration)))
|
| 1178 |
+
frame_disposal = encoderinfo.get("disposal", disposal)
|
| 1179 |
+
frame_blend = encoderinfo.get("blend", blend)
|
| 1180 |
+
# frame control
|
| 1181 |
+
chunk(
|
| 1182 |
+
fp,
|
| 1183 |
+
b"fcTL",
|
| 1184 |
+
o32(seq_num), # sequence_number
|
| 1185 |
+
o32(size[0]), # width
|
| 1186 |
+
o32(size[1]), # height
|
| 1187 |
+
o32(bbox[0]), # x_offset
|
| 1188 |
+
o32(bbox[1]), # y_offset
|
| 1189 |
+
o16(frame_duration), # delay_numerator
|
| 1190 |
+
o16(1000), # delay_denominator
|
| 1191 |
+
o8(frame_disposal), # dispose_op
|
| 1192 |
+
o8(frame_blend), # blend_op
|
| 1193 |
+
)
|
| 1194 |
+
seq_num += 1
|
| 1195 |
+
# frame data
|
| 1196 |
+
if frame == 0 and not default_image:
|
| 1197 |
+
# first frame must be in IDAT chunks for backwards compatibility
|
| 1198 |
+
ImageFile._save(
|
| 1199 |
+
im_frame,
|
| 1200 |
+
_idat(fp, chunk),
|
| 1201 |
+
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
|
| 1202 |
+
)
|
| 1203 |
+
else:
|
| 1204 |
+
fdat_chunks = _fdat(fp, chunk, seq_num)
|
| 1205 |
+
ImageFile._save(
|
| 1206 |
+
im_frame,
|
| 1207 |
+
fdat_chunks,
|
| 1208 |
+
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
|
| 1209 |
+
)
|
| 1210 |
+
seq_num = fdat_chunks.seq_num
|
| 1211 |
+
|
| 1212 |
+
|
| 1213 |
+
def _save_all(im, fp, filename):
|
| 1214 |
+
_save(im, fp, filename, save_all=True)
|
| 1215 |
+
|
| 1216 |
+
|
| 1217 |
+
def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
| 1218 |
+
# save an image to disk (called by the save method)
|
| 1219 |
+
|
| 1220 |
+
mode = im.mode
|
| 1221 |
+
|
| 1222 |
+
if mode == "P":
|
| 1223 |
+
|
| 1224 |
+
#
|
| 1225 |
+
# attempt to minimize storage requirements for palette images
|
| 1226 |
+
if "bits" in im.encoderinfo:
|
| 1227 |
+
# number of bits specified by user
|
| 1228 |
+
colors = min(1 << im.encoderinfo["bits"], 256)
|
| 1229 |
+
else:
|
| 1230 |
+
# check palette contents
|
| 1231 |
+
if im.palette:
|
| 1232 |
+
colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1)
|
| 1233 |
+
else:
|
| 1234 |
+
colors = 256
|
| 1235 |
+
|
| 1236 |
+
if colors <= 16:
|
| 1237 |
+
if colors <= 2:
|
| 1238 |
+
bits = 1
|
| 1239 |
+
elif colors <= 4:
|
| 1240 |
+
bits = 2
|
| 1241 |
+
else:
|
| 1242 |
+
bits = 4
|
| 1243 |
+
mode = f"{mode};{bits}"
|
| 1244 |
+
|
| 1245 |
+
# encoder options
|
| 1246 |
+
im.encoderconfig = (
|
| 1247 |
+
im.encoderinfo.get("optimize", False),
|
| 1248 |
+
im.encoderinfo.get("compress_level", -1),
|
| 1249 |
+
im.encoderinfo.get("compress_type", -1),
|
| 1250 |
+
im.encoderinfo.get("dictionary", b""),
|
| 1251 |
+
)
|
| 1252 |
+
|
| 1253 |
+
# get the corresponding PNG mode
|
| 1254 |
+
try:
|
| 1255 |
+
rawmode, mode = _OUTMODES[mode]
|
| 1256 |
+
except KeyError as e:
|
| 1257 |
+
raise OSError(f"cannot write mode {mode} as PNG") from e
|
| 1258 |
+
|
| 1259 |
+
#
|
| 1260 |
+
# write minimal PNG file
|
| 1261 |
+
|
| 1262 |
+
fp.write(_MAGIC)
|
| 1263 |
+
|
| 1264 |
+
chunk(
|
| 1265 |
+
fp,
|
| 1266 |
+
b"IHDR",
|
| 1267 |
+
o32(im.size[0]), # 0: size
|
| 1268 |
+
o32(im.size[1]),
|
| 1269 |
+
mode, # 8: depth/type
|
| 1270 |
+
b"\0", # 10: compression
|
| 1271 |
+
b"\0", # 11: filter category
|
| 1272 |
+
b"\0", # 12: interlace flag
|
| 1273 |
+
)
|
| 1274 |
+
|
| 1275 |
+
chunks = [b"cHRM", b"gAMA", b"sBIT", b"sRGB", b"tIME"]
|
| 1276 |
+
|
| 1277 |
+
icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
|
| 1278 |
+
if icc:
|
| 1279 |
+
# ICC profile
|
| 1280 |
+
# according to PNG spec, the iCCP chunk contains:
|
| 1281 |
+
# Profile name 1-79 bytes (character string)
|
| 1282 |
+
# Null separator 1 byte (null character)
|
| 1283 |
+
# Compression method 1 byte (0)
|
| 1284 |
+
# Compressed profile n bytes (zlib with deflate compression)
|
| 1285 |
+
name = b"ICC Profile"
|
| 1286 |
+
data = name + b"\0\0" + zlib.compress(icc)
|
| 1287 |
+
chunk(fp, b"iCCP", data)
|
| 1288 |
+
|
| 1289 |
+
# You must either have sRGB or iCCP.
|
| 1290 |
+
# Disallow sRGB chunks when an iCCP-chunk has been emitted.
|
| 1291 |
+
chunks.remove(b"sRGB")
|
| 1292 |
+
|
| 1293 |
+
info = im.encoderinfo.get("pnginfo")
|
| 1294 |
+
if info:
|
| 1295 |
+
chunks_multiple_allowed = [b"sPLT", b"iTXt", b"tEXt", b"zTXt"]
|
| 1296 |
+
for info_chunk in info.chunks:
|
| 1297 |
+
cid, data = info_chunk[:2]
|
| 1298 |
+
if cid in chunks:
|
| 1299 |
+
chunks.remove(cid)
|
| 1300 |
+
chunk(fp, cid, data)
|
| 1301 |
+
elif cid in chunks_multiple_allowed:
|
| 1302 |
+
chunk(fp, cid, data)
|
| 1303 |
+
elif cid[1:2].islower():
|
| 1304 |
+
# Private chunk
|
| 1305 |
+
after_idat = info_chunk[2:3]
|
| 1306 |
+
if not after_idat:
|
| 1307 |
+
chunk(fp, cid, data)
|
| 1308 |
+
|
| 1309 |
+
if im.mode == "P":
|
| 1310 |
+
palette_byte_number = colors * 3
|
| 1311 |
+
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
|
| 1312 |
+
while len(palette_bytes) < palette_byte_number:
|
| 1313 |
+
palette_bytes += b"\0"
|
| 1314 |
+
chunk(fp, b"PLTE", palette_bytes)
|
| 1315 |
+
|
| 1316 |
+
transparency = im.encoderinfo.get("transparency", im.info.get("transparency", None))
|
| 1317 |
+
|
| 1318 |
+
if transparency or transparency == 0:
|
| 1319 |
+
if im.mode == "P":
|
| 1320 |
+
# limit to actual palette size
|
| 1321 |
+
alpha_bytes = colors
|
| 1322 |
+
if isinstance(transparency, bytes):
|
| 1323 |
+
chunk(fp, b"tRNS", transparency[:alpha_bytes])
|
| 1324 |
+
else:
|
| 1325 |
+
transparency = max(0, min(255, transparency))
|
| 1326 |
+
alpha = b"\xFF" * transparency + b"\0"
|
| 1327 |
+
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
| 1328 |
+
elif im.mode in ("1", "L", "I"):
|
| 1329 |
+
transparency = max(0, min(65535, transparency))
|
| 1330 |
+
chunk(fp, b"tRNS", o16(transparency))
|
| 1331 |
+
elif im.mode == "RGB":
|
| 1332 |
+
red, green, blue = transparency
|
| 1333 |
+
chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue))
|
| 1334 |
+
else:
|
| 1335 |
+
if "transparency" in im.encoderinfo:
|
| 1336 |
+
# don't bother with transparency if it's an RGBA
|
| 1337 |
+
# and it's in the info dict. It's probably just stale.
|
| 1338 |
+
raise OSError("cannot use transparency for this mode")
|
| 1339 |
+
else:
|
| 1340 |
+
if im.mode == "P" and im.im.getpalettemode() == "RGBA":
|
| 1341 |
+
alpha = im.im.getpalette("RGBA", "A")
|
| 1342 |
+
alpha_bytes = colors
|
| 1343 |
+
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
| 1344 |
+
|
| 1345 |
+
dpi = im.encoderinfo.get("dpi")
|
| 1346 |
+
if dpi:
|
| 1347 |
+
chunk(
|
| 1348 |
+
fp,
|
| 1349 |
+
b"pHYs",
|
| 1350 |
+
o32(int(dpi[0] / 0.0254 + 0.5)),
|
| 1351 |
+
o32(int(dpi[1] / 0.0254 + 0.5)),
|
| 1352 |
+
b"\x01",
|
| 1353 |
+
)
|
| 1354 |
+
|
| 1355 |
+
if info:
|
| 1356 |
+
chunks = [b"bKGD", b"hIST"]
|
| 1357 |
+
for info_chunk in info.chunks:
|
| 1358 |
+
cid, data = info_chunk[:2]
|
| 1359 |
+
if cid in chunks:
|
| 1360 |
+
chunks.remove(cid)
|
| 1361 |
+
chunk(fp, cid, data)
|
| 1362 |
+
|
| 1363 |
+
exif = im.encoderinfo.get("exif", im.info.get("exif"))
|
| 1364 |
+
if exif:
|
| 1365 |
+
if isinstance(exif, Image.Exif):
|
| 1366 |
+
exif = exif.tobytes(8)
|
| 1367 |
+
if exif.startswith(b"Exif\x00\x00"):
|
| 1368 |
+
exif = exif[6:]
|
| 1369 |
+
chunk(fp, b"eXIf", exif)
|
| 1370 |
+
|
| 1371 |
+
if save_all:
|
| 1372 |
+
_write_multiple_frames(im, fp, chunk, rawmode)
|
| 1373 |
+
else:
|
| 1374 |
+
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
|
| 1375 |
+
|
| 1376 |
+
if info:
|
| 1377 |
+
for info_chunk in info.chunks:
|
| 1378 |
+
cid, data = info_chunk[:2]
|
| 1379 |
+
if cid[1:2].islower():
|
| 1380 |
+
# Private chunk
|
| 1381 |
+
after_idat = info_chunk[2:3]
|
| 1382 |
+
if after_idat:
|
| 1383 |
+
chunk(fp, cid, data)
|
| 1384 |
+
|
| 1385 |
+
chunk(fp, b"IEND", b"")
|
| 1386 |
+
|
| 1387 |
+
if hasattr(fp, "flush"):
|
| 1388 |
+
fp.flush()
|
| 1389 |
+
|
| 1390 |
+
|
| 1391 |
+
# --------------------------------------------------------------------
|
| 1392 |
+
# PNG chunk converter
|
| 1393 |
+
|
| 1394 |
+
|
| 1395 |
+
def getchunks(im, **params):
|
| 1396 |
+
"""Return a list of PNG chunks representing this image."""
|
| 1397 |
+
|
| 1398 |
+
class collector:
|
| 1399 |
+
data = []
|
| 1400 |
+
|
| 1401 |
+
def write(self, data):
|
| 1402 |
+
pass
|
| 1403 |
+
|
| 1404 |
+
def append(self, chunk):
|
| 1405 |
+
self.data.append(chunk)
|
| 1406 |
+
|
| 1407 |
+
def append(fp, cid, *data):
|
| 1408 |
+
data = b"".join(data)
|
| 1409 |
+
crc = o32(_crc32(data, _crc32(cid)))
|
| 1410 |
+
fp.append((cid, data, crc))
|
| 1411 |
+
|
| 1412 |
+
fp = collector()
|
| 1413 |
+
|
| 1414 |
+
try:
|
| 1415 |
+
im.encoderinfo = params
|
| 1416 |
+
_save(im, fp, None, append)
|
| 1417 |
+
finally:
|
| 1418 |
+
del im.encoderinfo
|
| 1419 |
+
|
| 1420 |
+
return fp.data
|
| 1421 |
+
|
| 1422 |
+
|
| 1423 |
+
# --------------------------------------------------------------------
|
| 1424 |
+
# Registry
|
| 1425 |
+
|
| 1426 |
+
Image.register_open(PngImageFile.format, PngImageFile, _accept)
|
| 1427 |
+
Image.register_save(PngImageFile.format, _save)
|
| 1428 |
+
Image.register_save_all(PngImageFile.format, _save_all)
|
| 1429 |
+
|
| 1430 |
+
Image.register_extensions(PngImageFile.format, [".png", ".apng"])
|
| 1431 |
+
|
| 1432 |
+
Image.register_mime(PngImageFile.format, "image/png")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PpmImagePlugin.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# PPM support for PIL
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 96-03-24 fl Created
|
| 9 |
+
# 98-03-06 fl Write RGBA images (as RGB, that is)
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) Secret Labs AB 1997-98.
|
| 12 |
+
# Copyright (c) Fredrik Lundh 1996.
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
from . import Image, ImageFile
|
| 19 |
+
from ._binary import i16be as i16
|
| 20 |
+
from ._binary import o8
|
| 21 |
+
from ._binary import o32le as o32
|
| 22 |
+
|
| 23 |
+
#
|
| 24 |
+
# --------------------------------------------------------------------
|
| 25 |
+
|
| 26 |
+
b_whitespace = b"\x20\x09\x0a\x0b\x0c\x0d"
|
| 27 |
+
|
| 28 |
+
MODES = {
|
| 29 |
+
# standard
|
| 30 |
+
b"P4": "1",
|
| 31 |
+
b"P5": "L",
|
| 32 |
+
b"P6": "RGB",
|
| 33 |
+
# extensions
|
| 34 |
+
b"P0CMYK": "CMYK",
|
| 35 |
+
# PIL extensions (for test purposes only)
|
| 36 |
+
b"PyP": "P",
|
| 37 |
+
b"PyRGBA": "RGBA",
|
| 38 |
+
b"PyCMYK": "CMYK",
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _accept(prefix):
|
| 43 |
+
return prefix[0:1] == b"P" and prefix[1] in b"0456y"
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
##
|
| 47 |
+
# Image plugin for PBM, PGM, and PPM images.
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class PpmImageFile(ImageFile.ImageFile):
|
| 51 |
+
|
| 52 |
+
format = "PPM"
|
| 53 |
+
format_description = "Pbmplus image"
|
| 54 |
+
|
| 55 |
+
def _read_magic(self):
|
| 56 |
+
magic = b""
|
| 57 |
+
# read until whitespace or longest available magic number
|
| 58 |
+
for _ in range(6):
|
| 59 |
+
c = self.fp.read(1)
|
| 60 |
+
if not c or c in b_whitespace:
|
| 61 |
+
break
|
| 62 |
+
magic += c
|
| 63 |
+
return magic
|
| 64 |
+
|
| 65 |
+
def _read_token(self):
|
| 66 |
+
token = b""
|
| 67 |
+
while len(token) <= 10: # read until next whitespace or limit of 10 characters
|
| 68 |
+
c = self.fp.read(1)
|
| 69 |
+
if not c:
|
| 70 |
+
break
|
| 71 |
+
elif c in b_whitespace: # token ended
|
| 72 |
+
if not token:
|
| 73 |
+
# skip whitespace at start
|
| 74 |
+
continue
|
| 75 |
+
break
|
| 76 |
+
elif c == b"#":
|
| 77 |
+
# ignores rest of the line; stops at CR, LF or EOF
|
| 78 |
+
while self.fp.read(1) not in b"\r\n":
|
| 79 |
+
pass
|
| 80 |
+
continue
|
| 81 |
+
token += c
|
| 82 |
+
if not token:
|
| 83 |
+
# Token was not even 1 byte
|
| 84 |
+
raise ValueError("Reached EOF while reading header")
|
| 85 |
+
elif len(token) > 10:
|
| 86 |
+
raise ValueError(f"Token too long in file header: {token}")
|
| 87 |
+
return token
|
| 88 |
+
|
| 89 |
+
def _open(self):
|
| 90 |
+
magic_number = self._read_magic()
|
| 91 |
+
try:
|
| 92 |
+
mode = MODES[magic_number]
|
| 93 |
+
except KeyError:
|
| 94 |
+
raise SyntaxError("not a PPM file")
|
| 95 |
+
|
| 96 |
+
self.custom_mimetype = {
|
| 97 |
+
b"P4": "image/x-portable-bitmap",
|
| 98 |
+
b"P5": "image/x-portable-graymap",
|
| 99 |
+
b"P6": "image/x-portable-pixmap",
|
| 100 |
+
}.get(magic_number)
|
| 101 |
+
|
| 102 |
+
if mode == "1":
|
| 103 |
+
self.mode = "1"
|
| 104 |
+
rawmode = "1;I"
|
| 105 |
+
else:
|
| 106 |
+
self.mode = rawmode = mode
|
| 107 |
+
|
| 108 |
+
decoder_name = "raw"
|
| 109 |
+
for ix in range(3):
|
| 110 |
+
token = int(self._read_token())
|
| 111 |
+
if ix == 0: # token is the x size
|
| 112 |
+
xsize = token
|
| 113 |
+
elif ix == 1: # token is the y size
|
| 114 |
+
ysize = token
|
| 115 |
+
if mode == "1":
|
| 116 |
+
break
|
| 117 |
+
elif ix == 2: # token is maxval
|
| 118 |
+
maxval = token
|
| 119 |
+
if maxval > 255 and mode == "L":
|
| 120 |
+
self.mode = "I"
|
| 121 |
+
|
| 122 |
+
# If maxval matches a bit depth, use the raw decoder directly
|
| 123 |
+
if maxval == 65535 and mode == "L":
|
| 124 |
+
rawmode = "I;16B"
|
| 125 |
+
elif maxval != 255:
|
| 126 |
+
decoder_name = "ppm"
|
| 127 |
+
args = (rawmode, 0, 1) if decoder_name == "raw" else (rawmode, maxval)
|
| 128 |
+
|
| 129 |
+
self._size = xsize, ysize
|
| 130 |
+
self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
class PpmDecoder(ImageFile.PyDecoder):
|
| 134 |
+
_pulls_fd = True
|
| 135 |
+
|
| 136 |
+
def decode(self, buffer):
|
| 137 |
+
data = bytearray()
|
| 138 |
+
maxval = min(self.args[-1], 65535)
|
| 139 |
+
in_byte_count = 1 if maxval < 256 else 2
|
| 140 |
+
out_byte_count = 4 if self.mode == "I" else 1
|
| 141 |
+
out_max = 65535 if self.mode == "I" else 255
|
| 142 |
+
bands = Image.getmodebands(self.mode)
|
| 143 |
+
while len(data) < self.state.xsize * self.state.ysize * bands * out_byte_count:
|
| 144 |
+
pixels = self.fd.read(in_byte_count * bands)
|
| 145 |
+
if len(pixels) < in_byte_count * bands:
|
| 146 |
+
# eof
|
| 147 |
+
break
|
| 148 |
+
for b in range(bands):
|
| 149 |
+
value = (
|
| 150 |
+
pixels[b] if in_byte_count == 1 else i16(pixels, b * in_byte_count)
|
| 151 |
+
)
|
| 152 |
+
value = min(out_max, round(value / maxval * out_max))
|
| 153 |
+
data += o32(value) if self.mode == "I" else o8(value)
|
| 154 |
+
rawmode = "I;32" if self.mode == "I" else self.mode
|
| 155 |
+
self.set_as_raw(bytes(data), (rawmode, 0, 1))
|
| 156 |
+
return -1, 0
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
#
|
| 160 |
+
# --------------------------------------------------------------------
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def _save(im, fp, filename):
|
| 164 |
+
if im.mode == "1":
|
| 165 |
+
rawmode, head = "1;I", b"P4"
|
| 166 |
+
elif im.mode == "L":
|
| 167 |
+
rawmode, head = "L", b"P5"
|
| 168 |
+
elif im.mode == "I":
|
| 169 |
+
rawmode, head = "I;16B", b"P5"
|
| 170 |
+
elif im.mode in ("RGB", "RGBA"):
|
| 171 |
+
rawmode, head = "RGB", b"P6"
|
| 172 |
+
else:
|
| 173 |
+
raise OSError(f"cannot write mode {im.mode} as PPM")
|
| 174 |
+
fp.write(head + b"\n%d %d\n" % im.size)
|
| 175 |
+
if head == b"P6":
|
| 176 |
+
fp.write(b"255\n")
|
| 177 |
+
elif head == b"P5":
|
| 178 |
+
if rawmode == "L":
|
| 179 |
+
fp.write(b"255\n")
|
| 180 |
+
else:
|
| 181 |
+
fp.write(b"65535\n")
|
| 182 |
+
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
|
| 183 |
+
|
| 184 |
+
# ALTERNATIVE: save via builtin debug function
|
| 185 |
+
# im._dump(filename)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
#
|
| 189 |
+
# --------------------------------------------------------------------
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
|
| 193 |
+
Image.register_save(PpmImageFile.format, _save)
|
| 194 |
+
|
| 195 |
+
Image.register_decoder("ppm", PpmDecoder)
|
| 196 |
+
|
| 197 |
+
Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"])
|
| 198 |
+
|
| 199 |
+
Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/site-packages/PIL/PsdImagePlugin.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# Adobe PSD 2.5/3.0 file handling
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1995-09-01 fl Created
|
| 9 |
+
# 1997-01-03 fl Read most PSD images
|
| 10 |
+
# 1997-01-18 fl Fixed P and CMYK support
|
| 11 |
+
# 2001-10-21 fl Added seek/tell support (for layers)
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) 1997-2001 by Secret Labs AB.
|
| 14 |
+
# Copyright (c) 1995-2001 by Fredrik Lundh
|
| 15 |
+
#
|
| 16 |
+
# See the README file for information on usage and redistribution.
|
| 17 |
+
#
|
| 18 |
+
|
| 19 |
+
import io
|
| 20 |
+
|
| 21 |
+
from . import Image, ImageFile, ImagePalette
|
| 22 |
+
from ._binary import i8
|
| 23 |
+
from ._binary import i16be as i16
|
| 24 |
+
from ._binary import i32be as i32
|
| 25 |
+
from ._binary import si16be as si16
|
| 26 |
+
|
| 27 |
+
MODES = {
|
| 28 |
+
# (photoshop mode, bits) -> (pil mode, required channels)
|
| 29 |
+
(0, 1): ("1", 1),
|
| 30 |
+
(0, 8): ("L", 1),
|
| 31 |
+
(1, 8): ("L", 1),
|
| 32 |
+
(2, 8): ("P", 1),
|
| 33 |
+
(3, 8): ("RGB", 3),
|
| 34 |
+
(4, 8): ("CMYK", 4),
|
| 35 |
+
(7, 8): ("L", 1), # FIXME: multilayer
|
| 36 |
+
(8, 8): ("L", 1), # duotone
|
| 37 |
+
(9, 8): ("LAB", 3),
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# --------------------------------------------------------------------.
|
| 42 |
+
# read PSD images
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _accept(prefix):
|
| 46 |
+
return prefix[:4] == b"8BPS"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
##
|
| 50 |
+
# Image plugin for Photoshop images.
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class PsdImageFile(ImageFile.ImageFile):
|
| 54 |
+
|
| 55 |
+
format = "PSD"
|
| 56 |
+
format_description = "Adobe Photoshop"
|
| 57 |
+
_close_exclusive_fp_after_loading = False
|
| 58 |
+
|
| 59 |
+
def _open(self):
|
| 60 |
+
|
| 61 |
+
read = self.fp.read
|
| 62 |
+
|
| 63 |
+
#
|
| 64 |
+
# header
|
| 65 |
+
|
| 66 |
+
s = read(26)
|
| 67 |
+
if not _accept(s) or i16(s, 4) != 1:
|
| 68 |
+
raise SyntaxError("not a PSD file")
|
| 69 |
+
|
| 70 |
+
psd_bits = i16(s, 22)
|
| 71 |
+
psd_channels = i16(s, 12)
|
| 72 |
+
psd_mode = i16(s, 24)
|
| 73 |
+
|
| 74 |
+
mode, channels = MODES[(psd_mode, psd_bits)]
|
| 75 |
+
|
| 76 |
+
if channels > psd_channels:
|
| 77 |
+
raise OSError("not enough channels")
|
| 78 |
+
|
| 79 |
+
self.mode = mode
|
| 80 |
+
self._size = i32(s, 18), i32(s, 14)
|
| 81 |
+
|
| 82 |
+
#
|
| 83 |
+
# color mode data
|
| 84 |
+
|
| 85 |
+
size = i32(read(4))
|
| 86 |
+
if size:
|
| 87 |
+
data = read(size)
|
| 88 |
+
if mode == "P" and size == 768:
|
| 89 |
+
self.palette = ImagePalette.raw("RGB;L", data)
|
| 90 |
+
|
| 91 |
+
#
|
| 92 |
+
# image resources
|
| 93 |
+
|
| 94 |
+
self.resources = []
|
| 95 |
+
|
| 96 |
+
size = i32(read(4))
|
| 97 |
+
if size:
|
| 98 |
+
# load resources
|
| 99 |
+
end = self.fp.tell() + size
|
| 100 |
+
while self.fp.tell() < end:
|
| 101 |
+
read(4) # signature
|
| 102 |
+
id = i16(read(2))
|
| 103 |
+
name = read(i8(read(1)))
|
| 104 |
+
if not (len(name) & 1):
|
| 105 |
+
read(1) # padding
|
| 106 |
+
data = read(i32(read(4)))
|
| 107 |
+
if len(data) & 1:
|
| 108 |
+
read(1) # padding
|
| 109 |
+
self.resources.append((id, name, data))
|
| 110 |
+
if id == 1039: # ICC profile
|
| 111 |
+
self.info["icc_profile"] = data
|
| 112 |
+
|
| 113 |
+
#
|
| 114 |
+
# layer and mask information
|
| 115 |
+
|
| 116 |
+
self.layers = []
|
| 117 |
+
|
| 118 |
+
size = i32(read(4))
|
| 119 |
+
if size:
|
| 120 |
+
end = self.fp.tell() + size
|
| 121 |
+
size = i32(read(4))
|
| 122 |
+
if size:
|
| 123 |
+
_layer_data = io.BytesIO(ImageFile._safe_read(self.fp, size))
|
| 124 |
+
self.layers = _layerinfo(_layer_data, size)
|
| 125 |
+
self.fp.seek(end)
|
| 126 |
+
self.n_frames = len(self.layers)
|
| 127 |
+
self.is_animated = self.n_frames > 1
|
| 128 |
+
|
| 129 |
+
#
|
| 130 |
+
# image descriptor
|
| 131 |
+
|
| 132 |
+
self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels)
|
| 133 |
+
|
| 134 |
+
# keep the file open
|
| 135 |
+
self.__fp = self.fp
|
| 136 |
+
self.frame = 1
|
| 137 |
+
self._min_frame = 1
|
| 138 |
+
|
| 139 |
+
def seek(self, layer):
|
| 140 |
+
if not self._seek_check(layer):
|
| 141 |
+
return
|
| 142 |
+
|
| 143 |
+
# seek to given layer (1..max)
|
| 144 |
+
try:
|
| 145 |
+
name, mode, bbox, tile = self.layers[layer - 1]
|
| 146 |
+
self.mode = mode
|
| 147 |
+
self.tile = tile
|
| 148 |
+
self.frame = layer
|
| 149 |
+
self.fp = self.__fp
|
| 150 |
+
return name, bbox
|
| 151 |
+
except IndexError as e:
|
| 152 |
+
raise EOFError("no such layer") from e
|
| 153 |
+
|
| 154 |
+
def tell(self):
|
| 155 |
+
# return layer number (0=image, 1..max=layers)
|
| 156 |
+
return self.frame
|
| 157 |
+
|
| 158 |
+
def _close__fp(self):
|
| 159 |
+
try:
|
| 160 |
+
if self.__fp != self.fp:
|
| 161 |
+
self.__fp.close()
|
| 162 |
+
except AttributeError:
|
| 163 |
+
pass
|
| 164 |
+
finally:
|
| 165 |
+
self.__fp = None
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def _layerinfo(fp, ct_bytes):
|
| 169 |
+
# read layerinfo block
|
| 170 |
+
layers = []
|
| 171 |
+
|
| 172 |
+
def read(size):
|
| 173 |
+
return ImageFile._safe_read(fp, size)
|
| 174 |
+
|
| 175 |
+
ct = si16(read(2))
|
| 176 |
+
|
| 177 |
+
# sanity check
|
| 178 |
+
if ct_bytes < (abs(ct) * 20):
|
| 179 |
+
raise SyntaxError("Layer block too short for number of layers requested")
|
| 180 |
+
|
| 181 |
+
for i in range(abs(ct)):
|
| 182 |
+
|
| 183 |
+
# bounding box
|
| 184 |
+
y0 = i32(read(4))
|
| 185 |
+
x0 = i32(read(4))
|
| 186 |
+
y1 = i32(read(4))
|
| 187 |
+
x1 = i32(read(4))
|
| 188 |
+
|
| 189 |
+
# image info
|
| 190 |
+
mode = []
|
| 191 |
+
ct_types = i16(read(2))
|
| 192 |
+
types = list(range(ct_types))
|
| 193 |
+
if len(types) > 4:
|
| 194 |
+
continue
|
| 195 |
+
|
| 196 |
+
for i in types:
|
| 197 |
+
type = i16(read(2))
|
| 198 |
+
|
| 199 |
+
if type == 65535:
|
| 200 |
+
m = "A"
|
| 201 |
+
else:
|
| 202 |
+
m = "RGBA"[type]
|
| 203 |
+
|
| 204 |
+
mode.append(m)
|
| 205 |
+
read(4) # size
|
| 206 |
+
|
| 207 |
+
# figure out the image mode
|
| 208 |
+
mode.sort()
|
| 209 |
+
if mode == ["R"]:
|
| 210 |
+
mode = "L"
|
| 211 |
+
elif mode == ["B", "G", "R"]:
|
| 212 |
+
mode = "RGB"
|
| 213 |
+
elif mode == ["A", "B", "G", "R"]:
|
| 214 |
+
mode = "RGBA"
|
| 215 |
+
else:
|
| 216 |
+
mode = None # unknown
|
| 217 |
+
|
| 218 |
+
# skip over blend flags and extra information
|
| 219 |
+
read(12) # filler
|
| 220 |
+
name = ""
|
| 221 |
+
size = i32(read(4)) # length of the extra data field
|
| 222 |
+
if size:
|
| 223 |
+
data_end = fp.tell() + size
|
| 224 |
+
|
| 225 |
+
length = i32(read(4))
|
| 226 |
+
if length:
|
| 227 |
+
fp.seek(length - 16, io.SEEK_CUR)
|
| 228 |
+
|
| 229 |
+
length = i32(read(4))
|
| 230 |
+
if length:
|
| 231 |
+
fp.seek(length, io.SEEK_CUR)
|
| 232 |
+
|
| 233 |
+
length = i8(read(1))
|
| 234 |
+
if length:
|
| 235 |
+
# Don't know the proper encoding,
|
| 236 |
+
# Latin-1 should be a good guess
|
| 237 |
+
name = read(length).decode("latin-1", "replace")
|
| 238 |
+
|
| 239 |
+
fp.seek(data_end)
|
| 240 |
+
layers.append((name, mode, (x0, y0, x1, y1)))
|
| 241 |
+
|
| 242 |
+
# get tiles
|
| 243 |
+
i = 0
|
| 244 |
+
for name, mode, bbox in layers:
|
| 245 |
+
tile = []
|
| 246 |
+
for m in mode:
|
| 247 |
+
t = _maketile(fp, m, bbox, 1)
|
| 248 |
+
if t:
|
| 249 |
+
tile.extend(t)
|
| 250 |
+
layers[i] = name, mode, bbox, tile
|
| 251 |
+
i += 1
|
| 252 |
+
|
| 253 |
+
return layers
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def _maketile(file, mode, bbox, channels):
|
| 257 |
+
|
| 258 |
+
tile = None
|
| 259 |
+
read = file.read
|
| 260 |
+
|
| 261 |
+
compression = i16(read(2))
|
| 262 |
+
|
| 263 |
+
xsize = bbox[2] - bbox[0]
|
| 264 |
+
ysize = bbox[3] - bbox[1]
|
| 265 |
+
|
| 266 |
+
offset = file.tell()
|
| 267 |
+
|
| 268 |
+
if compression == 0:
|
| 269 |
+
#
|
| 270 |
+
# raw compression
|
| 271 |
+
tile = []
|
| 272 |
+
for channel in range(channels):
|
| 273 |
+
layer = mode[channel]
|
| 274 |
+
if mode == "CMYK":
|
| 275 |
+
layer += ";I"
|
| 276 |
+
tile.append(("raw", bbox, offset, layer))
|
| 277 |
+
offset = offset + xsize * ysize
|
| 278 |
+
|
| 279 |
+
elif compression == 1:
|
| 280 |
+
#
|
| 281 |
+
# packbits compression
|
| 282 |
+
i = 0
|
| 283 |
+
tile = []
|
| 284 |
+
bytecount = read(channels * ysize * 2)
|
| 285 |
+
offset = file.tell()
|
| 286 |
+
for channel in range(channels):
|
| 287 |
+
layer = mode[channel]
|
| 288 |
+
if mode == "CMYK":
|
| 289 |
+
layer += ";I"
|
| 290 |
+
tile.append(("packbits", bbox, offset, layer))
|
| 291 |
+
for y in range(ysize):
|
| 292 |
+
offset = offset + i16(bytecount, i)
|
| 293 |
+
i += 2
|
| 294 |
+
|
| 295 |
+
file.seek(offset)
|
| 296 |
+
|
| 297 |
+
if offset & 1:
|
| 298 |
+
read(1) # padding
|
| 299 |
+
|
| 300 |
+
return tile
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
# --------------------------------------------------------------------
|
| 304 |
+
# registry
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
Image.register_open(PsdImageFile.format, PsdImageFile, _accept)
|
| 308 |
+
|
| 309 |
+
Image.register_extension(PsdImageFile.format, ".psd")
|
| 310 |
+
|
| 311 |
+
Image.register_mime(PsdImageFile.format, "image/vnd.adobe.photoshop")
|