Compare commits

..

No commits in common. "master" and "python3" have entirely different histories.

3 changed files with 738 additions and 3 deletions

326
bmpimageplugin.py Normal file
View file

@ -0,0 +1,326 @@
#
# The Python Imaging Library.
# $Id$
#
# BMP file handler
#
# Windows (and OS/2) native bitmap storage format.
#
# history:
# 1995-09-01 fl Created
# 1996-04-30 fl Added save
# 1997-08-27 fl Fixed save of 1-bit images
# 1998-03-06 fl Load P images as L where possible
# 1998-07-03 fl Load P images as 1 where possible
# 1998-12-29 fl Handle small palettes
# 2002-12-30 fl Fixed load of 1-bit palette images
# 2003-04-21 fl Fixed load of 1-bit monochrome images
# 2003-04-23 fl Added limited support for BI_BITFIELDS compression
#
# Copyright (c) 1997-2003 by Secret Labs AB
# Copyright (c) 1995-2003 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
__version__ = "0.7"
from PIL import Image, ImageFile, ImagePalette, _binary
from PIL.BmpImagePlugin import * #This is a hack to override the default bmp plugin for PIL
from io import BytesIO
import math
i8 = _binary.i8
i16 = _binary.i16le
i32 = _binary.i32le
o8 = _binary.o8
o16 = _binary.o16le
o32 = _binary.o32le
#
# --------------------------------------------------------------------
# Read BMP file
BIT2MODE = {
# bits => mode, rawmode
1: ("P", "P;1"),
4: ("P", "P;4"),
8: ("P", "P"),
16: ("RGB", "BGR;15"),
24: ("RGB", "BGR"),
32: ("RGBA", "BGRA")
}
def _accept(prefix):
return prefix[:2] == b"BM"
##
# Image plugin for the Windows BMP format.
class BmpImageFile(ImageFile.ImageFile):
format = "BMP"
format_description = "Windows Bitmap"
def _bitmap(self, header=0, offset=0):
if header:
self.fp.seek(header)
read = self.fp.read
if not offset:
offset = self.fp.tell()
# CORE/INFO
s = read(4)
s = s + ImageFile._safe_read(self.fp, i32(s)-4)
if len(s) == 12:
# OS/2 1.0 CORE
bits = i16(s[10:])
self.size = i16(s[4:]), i16(s[6:])
compression = 0
lutsize = 3
colors = 0
direction = -1
elif len(s) in [40, 64, 108, 124]:
# WIN 3.1 or OS/2 2.0 INFO
bits = i16(s[14:])
self.size = i32(s[4:]), i32(s[8:])
compression = i32(s[16:])
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
lutsize = 4
colors = i32(s[32:])
direction = -1
if i8(s[11]) == 0xff:
# upside-down storage
self.size = self.size[0], 2**32 - self.size[1]
direction = 0
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701),
pxperm))
else:
raise IOError("Unsupported BMP header type (%d)" % len(s))
if (self.size[0]*self.size[1]) > 2**31:
# Prevent DOS for > 2gb images
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
if not colors:
colors = 1 << bits
# MODE
try:
self.mode, rawmode = BIT2MODE[bits]
except KeyError:
raise IOError("Unsupported BMP pixel depth (%d)" % bits)
if compression == 3:
# BI_BITFIELDS compression
mask = i32(s[0x36-14:]), i32(s[0x3a-14:]), i32(s[0x3E-14:]), i32(s[0x42-14:])
if bits == 32 and mask == (0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000):
rawmode = "BGRA"
elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f):
rawmode = "BGR;16"
elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f):
rawmode = "BGR;15"
else:
print(bits, map(hex, mask))
raise IOError("Unsupported BMP bitfields layout")
elif compression != 0:
raise IOError("Unsupported BMP compression (%d)" % compression)
# LUT
if self.mode == "P":
palette = []
greyscale = 1
if colors == 2:
indices = (0, 255)
elif colors > 2**16 or colors <= 0: # We're reading a i32.
raise IOError("Unsupported BMP Palette size (%d)" % colors)
else:
indices = list(range(colors))
for i in indices:
rgb = read(lutsize)[:3]
if rgb != o8(i)*3:
greyscale = 0
palette.append(rgb)
if greyscale:
if colors == 2:
self.mode = rawmode = "1"
else:
self.mode = rawmode = "L"
else:
self.mode = "P"
self.palette = ImagePalette.raw(
"BGR", b"".join(palette)
)
if not offset:
offset = self.fp.tell()
self.tile = [("raw",
(0, 0) + self.size,
offset,
(rawmode, ((self.size[0]*bits+31) >> 3) & (~3),
direction))]
self.info["compression"] = compression
def _open(self):
# HEAD
s = self.fp.read(14)
if s[:2] != b"BM":
raise SyntaxError("Not a BMP file")
offset = i32(s[10:])
self._bitmap(offset=offset)
class DibImageFile(BmpImageFile):
format = "DIB"
format_description = "Windows Bitmap"
def __init__(self, buf, *args, **kwargs):
BmpImageFile.__init__(self, buf, *args, **kwargs)
buf.seek(0)
self.buf = buf.read()
buf.seek(0)
def _open(self):
self._bitmap()
def get_file_data(self):
return self.buf
def to_bitmapimage(self, header):
self.fp.seek(header['offset'])
d = bytearray(self.fp.read(header['size']))
print(self.info)
dpi = (96,96)
ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
d[24:28] = o32(ppm[0])
d[28:32] = o32(ppm[1])
dib_size = i32(str(d[:4]))
offset = 14 + dib_size
data = BytesIO()
data.write(b'BM'+
o32(14+len(d)) +
o32(0) +
o32(offset))
data.write(d)
data.seek(0)
new_image = Image.open(data)
return new_image
#
# --------------------------------------------------------------------
# Write BMP file
SAVE = {
"1": ("1", 1, 2),
"L": ("L", 8, 256),
"P": ("P", 8, 256),
"RGB": ("BGR", 24, 0),
"RGBA": ("BGRA", 32, 0),
}
def _save(im, fp, filename, check=0):
try:
rawmode, bits, colors = SAVE[im.mode]
except KeyError:
raise IOError("cannot write mode %s as BMP" % im.mode)
if check:
return check
info = im.encoderinfo
dpi = info.get("dpi", (96, 96))
# 1 meter == 39.3701 inches
ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
stride = ((im.size[0]*bits+7)//8+3) & (~3)
header = 108 if im.mode == 'RGBA' else 40 # or 64 for OS/2 version 2
offset = 14 + header + colors*4
image = stride * im.size[1]
red_mask = 0x00ff0000
green_mask = 0x0000ff00
blue_mask = 0x000000ff
alpha_mask = 0xff000000
# bitmap header
fp.write(b"BM" + # file type (magic)
o32(offset+image+16) + # file size
o32(0) + # reserved
o32(offset+16)) # image data offset
width,height = im.size
# bitmap info header
fp.write(o32(header+16) + # info header size
o32(width) + # width
o32(height) + # height
o16(1) + # planes
o16(bits) + # depth
o32(3) + # compression (0=uncompressed)
o32(image) + # size of bitmap
o32(ppm[0]) + o32(ppm[1]) + # resolution
o32(colors) + # colors used
o32(colors) # colors important
#o32(red_mask) + # red channel ma
#o32(green_mask) + # green channel mask
#o32(blue_mask) + # blue channel mask
#o32(alpha_mask)
)
# This was commented out because although it works, some
# decoders do not support images with a BI_BITFIELDS compression
#
if im.mode == 'RGBA':
fp.write(o32(red_mask) + # red channel mask
o32(green_mask) + # green channel mask
o32(blue_mask) + # blue channel mask
o32(alpha_mask) + # alpha channel mask
b'BGRs' + # Color Space
o8(0)*0x24 + # ciexyztriple color space endpoints
o32(0) + # red gamma
o32(0) + # green gamma
o32(0) # blue gamma
)
fp.write(bytearray(16))
if im.mode == "1":
for i in (0, 255):
fp.write(o8(i) * 4)
elif im.mode == "L":
for i in range(256):
fp.write(o8(i) * 4)
elif im.mode == "P":
fp.write(im.im.getpalette("RGB", "BGRX"))
#fp.write(
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0,
(rawmode, stride, -1))])
#
# --------------------------------------------------------------------
# Registry
Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
Image.register_save(BmpImageFile.format, _save)
Image.register_extension(BmpImageFile.format, ".bmp")

410
ico_plugin.py Normal file
View file

@ -0,0 +1,410 @@
#
# The Python Imaging Library.
# $Id$
#
# Windows Icon support for PIL
#
# History:
# 96-05-27 fl Created
#
# Copyright (c) Secret Labs AB 1997.
# Copyright (c) Fredrik Lundh 1996.
#
# See the README file for information on usage and redistribution.
#
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
# <casadebender@gmail.com>.
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
#
# Icon format references:
# * http://en.wikipedia.org/wiki/ICO_(file_format)
# * http://msdn.microsoft.com/en-us/library/ms997538.aspx
__version__ = "0.1"
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
from .bmpimageplugin import *
from io import BytesIO, StringIO
from math import log, ceil
i8 = _binary.i8
i16 = _binary.i16le
i32 = _binary.i32le
o8 = _binary.o8
o16 = _binary.o16le
o32 = _binary.o32le
_MAGIC = b"\0\0\1\0"
def _accept(prefix):
return prefix[:4] == _MAGIC
class IcoFile:
format = 'ICO'
def __init__(self, buf):
"""
Parse image from file-like object containing ico file data
"""
# check magic
s = buf.read(6)
if not _accept(s):
raise SyntaxError("not an ICO file")
self.buf = buf
self.entries = []
# Number of items in file
self.nb_items = i16(s[4:])
# Get headers for each item
for i in range(self.nb_items):
s = buf.read(16)
icon_header = {
'width': i8(s[0]),
'height': i8(s[1]),
'nb_color': i8(s[2]), # No. of colors in image (0 if >=8bpp)
'reserved': i8(s[3]),
'planes': i16(s[4:]),
'bpp': i16(s[6:]),
'size': i32(s[8:]),
'offset': i32(s[12:])
}
# See Wikipedia
for j in ('width', 'height'):
if not icon_header[j]:
icon_header[j] = 256
# See Wikipedia notes about color depth.
# We need this just to differ images with equal sizes
icon_header['color_depth'] = (icon_header['bpp'] or
(icon_header['nb_color'] != 0 and
ceil(log(icon_header['nb_color'],
2))) or 256)
icon_header['dim'] = (icon_header['width'], icon_header['height'])
icon_header['square'] = (icon_header['width'] *
icon_header['height'])
self.entries.append(icon_header)
self.entries = sorted(self.entries, key=lambda x: x['color_depth'])
# ICO images are usually squares
# self.entries = sorted(self.entries, key=lambda x: x['width'])
self.entries = sorted(self.entries, key=lambda x: x['square'])
self.entries.reverse()
def sizes(self):
"""
Get a list of all available icon sizes and color depths.
"""
return set((h['width'], h['height']) for h in self.entries)
def getimage(self, size, bpp=False):
"""
Get an image from the icon
"""
for (i, h) in enumerate(self.entries):
if size == h['dim'] and (bpp is False or bpp == h['color_depth']):
return self.frame(i)
return self.frame(0)
def get_images(self):
images = []
for i, header in enumerate(self.entries):
f = self.frame(i)
if f.format:
images.append(self.frame(i))
return images
def frame(self, idx):
"""
Get an image from frame idx
"""
header = self.entries[idx]
self.buf.seek(header['offset'])
data = self.buf.read(8)
self.buf.seek(header['offset'])
if data[:8] == PngImagePlugin._MAGIC:
# png frame
s = BytesIO()
s.write(self.buf.read(header['size']))
s.seek(0)
self.buf.seek(header['offset'])
im = Image.open(s)
else:
# XOR + AND mask bmp frame
s = BytesIO()
s.write(self.buf.read(header['size']))
s.seek(0)
self.buf.seek(header['offset'])
im = DibImageFile(s)
#im = im.to_bitmapimage(header)
#print im.tile
# change tile dimension to only encompass XOR image
im.size = (im.size[0], int(im.size[1] / 2))
#print im.size
d, e, o, a = im.tile[0]
im.tile[0] = d, (0, 0) + im.size, o, a
# figure out where AND mask image starts
mode = a[0]
bpp = 8
for k in BmpImagePlugin.BIT2MODE.keys():
if mode == BmpImagePlugin.BIT2MODE[k][1]:
bpp = k
break
if 32 == bpp:
# 32-bit color depth icon image allows semitransparent areas
# PIL's DIB format ignores transparency bits, recover them.
# The DIB is packed in BGRX byte order where X is the alpha
# channel.
# Back up to start of bmp data
self.buf.seek(o)
# extract every 4th byte (eg. 3,7,11,15,...)
alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
# convert to an 8bpp grayscale image
mask = Image.frombuffer(
'L', # 8bpp
im.size, # (w, h)
alpha_bytes, # source chars
'raw', # raw decoder
('L', 0, -1) # 8bpp inverted, unpadded, reversed
)
else:
# get AND image from end of bitmap
w = im.size[0]
if (w % 32) > 0:
# bitmap row data is aligned to word boundaries
w += 32 - (im.size[0] % 32)
# the total mask data is
# padded row size * height / bits per char
and_mask_offset = o + int(im.size[0] * im.size[1] *
(bpp / 8.0))
total_bytes = int((w * im.size[1]) / 8)
self.buf.seek(and_mask_offset)
maskData = self.buf.read(total_bytes)
# convert raw data to image
mask = Image.frombuffer(
'1', # 1 bpp
im.size, # (w, h)
maskData, # source chars
'raw', # raw decoder
('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed
)
# now we have two images, im is XOR image and mask is AND image
if im.mode != 'RGBA':
im = im.convert('RGBA')
im.putalpha(mask)
return im
##
# Image plugin for Windows Icon files.
class IcoImageFile(ImageFile.ImageFile):
"""
PIL read-only image support for Microsoft Windows .ico files.
By default the largest resolution image in the file will be loaded. This
can be changed by altering the 'size' attribute before calling 'load'.
The info dictionary has a key 'sizes' that is a list of the sizes available
in the icon file.
Handles classic, XP and Vista icon formats.
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
<casadebender@gmail.com>.
https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
"""
format = "ICO"
format_description = "Windows Icon"
def _open(self):
self.ico = IcoFile(self.fp)
self.info['sizes'] = self.ico.sizes()
self.size = self.ico.entries[0]['dim']
self.load()
def load(self):
im = self.ico.getimage(self.size)
# if tile is PNG, it won't really be loaded yet
im.load()
self.im = im.im
self.mode = im.mode
self.size = im.size
def get_images(self):
return self.ico.get_images()
def load_seek(self):
# Flage the ImageFile.Parser so that it
# just does all the decode at the end.
pass
SAVE = {
"1": ("1", 1, 2),
"L": ("L", 8, 256),
"P": ("P", 8, 256),
"RGB": ("BGR", 24, 0),
"RGBA": ("BGRA", 32, 0),
}
def get_data(im):
s = BytesIO()
if im.format != 'DIB':
im.save(s, im.format)
else:
s.write(im.buf)
s.seek(0)
if im.format == 'BMP':
bmp_f = s
bmp_f.seek(10)
offset = i32(bmp_f.read(4))
dib_size = i32(bmp_f.read(4))
dib = bytearray(o32(dib_size)+bytearray(bmp_f.read(36)))
dib[:4] = o32(40)
dib[8:12] = o32(i32(dib[8:12])*2)
dib[16:20] = o32(0)
dib = dib[:40]
bmp_f.seek(offset)
data = bytearray(bmp_f.read())
data = dib+data
else:
data = bytearray(s.read())
return data
def _save(image, fp, filename, check=0):
data = None
images = []
if image.format != 'ICO':
bmp_f = BytesIO()
image.save(bmp_f, format='BMP')
bmp_f.seek(0)
im2 = Image.open(bmp_f)
if im2.format:
images.append(im2)
else:
images = image.get_images()
number_of_images = len(images)
header_size = 6+16*number_of_images
# ico header
fp.write(o16(0) + # reserved
o16(1) + # file type
o16(number_of_images)) # number of images
current_offset = header_size
for i, im in enumerate(images):
data = get_data(im)
try:
rawmode, bits, colors = SAVE[im.mode]
except KeyError:
raise IOError("cannot write mode %s as BMP" % im.mode)
matte_data = bytearray()
if im.format == 'BMP':
#write matte data. Taken from imagemagick
scanline_pad = (((im.size[0]+31) & ~31)-im.size[0]) >> 3
row_len = im.size[0]*4
for y in reversed(range(im.size[1])):
d = data[40:]
#BGRA
row_pixels = d[row_len*y:row_len*y+row_len]
bit=0
byte=0
for x in range(im.size[0]):
p=row_pixels[x]
byte<<=1
bit+=1
if bit == 8:
matte_data+=o8(byte)
bit=0
byte=0
if not bit == 0:
matte_data+=o8(byte<<(8-bit))
for i in range(scanline_pad):
matte_data+=o8(0)
fp.write(o8(im.size[0]) + # width
o8(im.size[1]) + # height
o8(0) + # color palette
o8(0) + # reserved
o16(1) + # planes
o16(bits) + # depth
o32(len(data+matte_data)) + # size of image in bytes
o32(current_offset) # offset
)
current_offset += len(data+matte_data)
for im in images:
data = get_data(im)
fp.write(data)
if im.format == 'BMP':
#write matte data. Taken from imagemagick
scanline_pad = (((im.size[0]+31) & ~31)-im.size[0]) >> 3
row_len = im.size[0]*4
for y in reversed(range(im.size[1])):
d = data[40:]
#BGRA
row_pixels = d[row_len*y:row_len*y+row_len]
bit=0
byte=0
for x in range(im.size[0]):
p=row_pixels[x]
byte<<=1
bit+=1
if bit == 8:
fp.write(o8(byte))
bit=0
byte=0
if not bit == 0:
fp.write(o8(byte<<(8-bit)))
for i in range(scanline_pad):
fp.write(o8(0))
fp.flush()
#
# --------------------------------------------------------------------
Image.register_open(IcoFile.format, IcoImageFile, _accept)
Image.register_save(IcoFile.format, _save)
Image.register_extension(IcoFile.format, '.ico')

5
pe.py
View file

@ -2,7 +2,7 @@ import os
import struct import struct
from io import BytesIO from io import BytesIO
from PIL import Image from .ico_plugin import *
def resize(image, size, format=None): def resize(image, size, format=None):
output = BytesIO() output = BytesIO()
@ -28,7 +28,7 @@ def resize(image, size, format=None):
back.paste(image, tuple(offset)) back.paste(image, tuple(offset))
format = format or image.format format = format or image.format
back.save(output, format, sizes=[size]) back.save(output, format)
contents = output.getvalue() contents = output.getvalue()
output.close() output.close()
return contents return contents
@ -1279,7 +1279,6 @@ class PEFile(Printable):
#9662 is the exact length of the icon in nw.exe #9662 is the exact length of the icon in nw.exe
extra_size = icon_file_size-new_icon_size extra_size = icon_file_size-new_icon_size
if extra_size < 0: if extra_size < 0:
extra_size = 0 extra_size = 0
icon_data = bytearray(i_data) + bytearray(extra_size) icon_data = bytearray(i_data) + bytearray(extra_size)