mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-09-27 22:47:07 +03:00
Added support for Windows 1.0 icons
This commit is contained in:
parent
a088d54509
commit
505c848c2b
BIN
Tests/images/ico1.ico
Normal file
BIN
Tests/images/ico1.ico
Normal file
Binary file not shown.
BIN
Tests/images/ico1.png
Normal file
BIN
Tests/images/ico1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 432 B |
|
@ -227,3 +227,31 @@ def test_draw_reloaded(tmp_path):
|
||||||
|
|
||||||
with Image.open(outfile) as im:
|
with Image.open(outfile) as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico")
|
assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ico1_open():
|
||||||
|
with Image.open("Tests/images/ico1.ico") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/ico1.png")
|
||||||
|
|
||||||
|
with open("Tests/images/flower.jpg", "rb") as fp:
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
IcoImagePlugin.Ico1ImageFile(fp)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ico1_save(tmp_path):
|
||||||
|
outfile = str(tmp_path / "temp.ico")
|
||||||
|
with Image.open("Tests/images/l_trns.png") as im:
|
||||||
|
l_channel = im.convert("1").convert("L")
|
||||||
|
a_channel = im.convert("LA").getchannel("A")
|
||||||
|
la = Image.merge("LA", (l_channel, a_channel))
|
||||||
|
|
||||||
|
la.save(outfile, "ICO1")
|
||||||
|
|
||||||
|
with Image.open(outfile) as im:
|
||||||
|
assert_image_equal(im, la)
|
||||||
|
|
||||||
|
# Test saving in an incorrect mode
|
||||||
|
output = io.BytesIO()
|
||||||
|
im = hopper()
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
im.save(output, "ICO1")
|
||||||
|
|
|
@ -342,6 +342,23 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
|
||||||
|
|
||||||
.. versionadded:: 8.3.0
|
.. versionadded:: 8.3.0
|
||||||
|
|
||||||
|
ICO1
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
Pillow also reads and writes device-independent Windows 1.0 icons.
|
||||||
|
|
||||||
|
.. versionadded:: 10.1.0
|
||||||
|
|
||||||
|
.. _ico1-saving:
|
||||||
|
|
||||||
|
Saving
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
Since the .ico extension is already used for the ICO format, when saving a
|
||||||
|
Windows 1.0 icon the output format must be specified explicitly::
|
||||||
|
|
||||||
|
im.save("newimage.ico", format="ICO1")
|
||||||
|
|
||||||
IM
|
IM
|
||||||
^^
|
^^
|
||||||
|
|
||||||
|
@ -446,7 +463,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
If this parameter is not provided, the image will be saved with no profile
|
If this parameter is not provided, the image will be saved with no profile
|
||||||
attached. To preserve the existing profile::
|
attached. To preserve the existing profile::
|
||||||
|
|
||||||
im.save(filename, 'jpeg', icc_profile=im.info.get('icc_profile'))
|
im.save(filename, "jpeg", icc_profile=im.info.get("icc_profile"))
|
||||||
|
|
||||||
**exif**
|
**exif**
|
||||||
If present, the image will be stored with the provided raw EXIF data.
|
If present, the image will be stored with the provided raw EXIF data.
|
||||||
|
@ -910,7 +927,7 @@ Saving
|
||||||
The extension of SPIDER files may be any 3 alphanumeric characters. Therefore
|
The extension of SPIDER files may be any 3 alphanumeric characters. Therefore
|
||||||
the output format must be specified explicitly::
|
the output format must be specified explicitly::
|
||||||
|
|
||||||
im.save('newimage.spi', format='SPIDER')
|
im.save("newimage.spi", format="SPIDER")
|
||||||
|
|
||||||
For more information about the SPIDER image processing package, see
|
For more information about the SPIDER image processing package, see
|
||||||
https://github.com/spider-em/SPIDER
|
https://github.com/spider-em/SPIDER
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
|
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from math import ceil, log
|
from math import ceil, log
|
||||||
|
@ -347,6 +348,85 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _ico1_accept(prefix):
|
||||||
|
return prefix[:2] == b"\1\0"
|
||||||
|
|
||||||
|
|
||||||
|
class Ico1ImageFile(ImageFile.ImageFile):
|
||||||
|
format = "ICO1"
|
||||||
|
format_description = "Windows 1.0 Icon"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
if not _ico1_accept(self.fp.read(2)):
|
||||||
|
msg = "not an ICO1 file"
|
||||||
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
|
self.fp.seek(4, os.SEEK_CUR)
|
||||||
|
width = i16(self.fp.read(2))
|
||||||
|
height = i16(self.fp.read(2))
|
||||||
|
self._size = (width, height)
|
||||||
|
self._mode = "LA"
|
||||||
|
|
||||||
|
self.tile = [("ico1", (0, 0) + self.size, 14, None)]
|
||||||
|
|
||||||
|
|
||||||
|
class Ico1Decoder(ImageFile.PyDecoder):
|
||||||
|
_pulls_fd = True
|
||||||
|
|
||||||
|
def decode(self, buffer):
|
||||||
|
data = bytearray()
|
||||||
|
bitmapLength = self.state.xsize * self.state.ysize // 8
|
||||||
|
firstBitmap = self.fd.read(bitmapLength)
|
||||||
|
secondBitmap = self.fd.read(bitmapLength)
|
||||||
|
for i, byte in enumerate(firstBitmap):
|
||||||
|
secondByte = byte ^ secondBitmap[i]
|
||||||
|
for j in reversed(range(8)):
|
||||||
|
first = byte >> j & 1
|
||||||
|
second = secondByte >> j & 1
|
||||||
|
data += b"\x00" if (first == second) else b"\xff"
|
||||||
|
data += b"\x00" if first else b"\xff"
|
||||||
|
self.set_as_raw(bytes(data))
|
||||||
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
|
class Ico1Encoder(ImageFile.PyEncoder):
|
||||||
|
_pushes_fd = True
|
||||||
|
|
||||||
|
def encode(self, bufsize):
|
||||||
|
firstBitmap = bytearray()
|
||||||
|
secondBitmap = bytearray()
|
||||||
|
w, h = self.im.size
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
l, a = self.im.getpixel((x, y))
|
||||||
|
if x % 8 == 0:
|
||||||
|
firstBitmap += b"\x00"
|
||||||
|
secondBitmap += b"\xff"
|
||||||
|
if not a:
|
||||||
|
firstBitmap[-1] ^= 1 << (7 - x % 8)
|
||||||
|
if not l:
|
||||||
|
secondBitmap[-1] ^= 1 << (7 - x % 8)
|
||||||
|
data = firstBitmap + secondBitmap
|
||||||
|
return len(data), 0, data
|
||||||
|
|
||||||
|
|
||||||
|
def _ico1_save(im, fp, filename):
|
||||||
|
if im.mode != "LA":
|
||||||
|
msg = f"cannot write {im.mode} as ICO1"
|
||||||
|
raise OSError(msg)
|
||||||
|
|
||||||
|
fp.write(
|
||||||
|
o16(1) # device-independent format
|
||||||
|
+ o32(0) # not used
|
||||||
|
+ o16(im.size[0]) # width in pixels
|
||||||
|
+ o16(im.size[1]) # height in pixels
|
||||||
|
+ o16(im.size[0] // 8) # width in bytes
|
||||||
|
+ o16(0) # not used
|
||||||
|
)
|
||||||
|
|
||||||
|
ImageFile._save(im, fp, [("ico1", (0, 0) + im.size, 0, None)])
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -356,3 +436,9 @@ Image.register_save(IcoImageFile.format, _save)
|
||||||
Image.register_extension(IcoImageFile.format, ".ico")
|
Image.register_extension(IcoImageFile.format, ".ico")
|
||||||
|
|
||||||
Image.register_mime(IcoImageFile.format, "image/x-icon")
|
Image.register_mime(IcoImageFile.format, "image/x-icon")
|
||||||
|
|
||||||
|
Image.register_open(Ico1ImageFile.format, Ico1ImageFile, _ico1_accept)
|
||||||
|
Image.register_save(Ico1ImageFile.format, _ico1_save)
|
||||||
|
|
||||||
|
Image.register_decoder("ico1", Ico1Decoder)
|
||||||
|
Image.register_encoder("ico1", Ico1Encoder)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user