2010-07-31 06:52:47 +04:00
|
|
|
#
|
|
|
|
# The Python Imaging Library.
|
|
|
|
# $Id$
|
|
|
|
#
|
|
|
|
# image palette object
|
|
|
|
#
|
|
|
|
# History:
|
|
|
|
# 1996-03-11 fl Rewritten.
|
|
|
|
# 1997-01-03 fl Up and running.
|
|
|
|
# 1997-08-23 fl Added load hack
|
|
|
|
# 2001-04-16 fl Fixed randint shadow bug in random()
|
|
|
|
#
|
|
|
|
# Copyright (c) 1997-2001 by Secret Labs AB
|
|
|
|
# Copyright (c) 1996-1997 by Fredrik Lundh
|
|
|
|
#
|
|
|
|
# See the README file for information on usage and redistribution.
|
|
|
|
#
|
|
|
|
|
|
|
|
import array
|
2019-07-06 23:40:53 +03:00
|
|
|
|
|
|
|
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
|
2010-07-31 06:52:47 +04:00
|
|
|
|
|
|
|
|
2019-09-30 17:56:31 +03:00
|
|
|
class ImagePalette:
|
2015-08-20 22:25:08 +03:00
|
|
|
"""
|
|
|
|
Color palette for palette mapped images
|
|
|
|
|
|
|
|
:param mode: The mode to use for the Palette. See:
|
|
|
|
:ref:`concept-modes`. Defaults to "RGB"
|
|
|
|
:param palette: An optional palette. If given, it must be a bytearray,
|
2015-09-11 12:28:19 +03:00
|
|
|
an array or a list of ints between 0-255 and of length ``size``
|
2015-08-20 22:25:08 +03:00
|
|
|
times the number of colors in ``mode``. The list must be aligned
|
|
|
|
by channel (All R values must be contiguous in the list before G
|
|
|
|
and B values.) Defaults to 0 through 255 per channel.
|
|
|
|
:param size: An optional palette size. If given, it cannot be equal to
|
|
|
|
or greater than 256. Defaults to 0.
|
|
|
|
"""
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
def __init__(self, mode="RGB", palette=None, size=0):
|
2010-07-31 06:52:47 +04:00
|
|
|
self.mode = mode
|
2014-07-15 19:38:10 +04:00
|
|
|
self.rawmode = None # if set, palette contains raw data
|
2021-06-23 12:22:21 +03:00
|
|
|
self.palette = palette or bytearray()
|
2010-07-31 06:52:47 +04:00
|
|
|
self.dirty = None
|
2021-06-23 12:22:21 +03:00
|
|
|
if size != 0 and size != len(self.palette):
|
2012-10-11 07:52:53 +04:00
|
|
|
raise ValueError("wrong palette size")
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2021-06-23 12:16:04 +03:00
|
|
|
@property
|
|
|
|
def palette(self):
|
|
|
|
return self._palette
|
|
|
|
|
|
|
|
@palette.setter
|
|
|
|
def palette(self, palette):
|
|
|
|
self._palette = palette
|
|
|
|
|
|
|
|
mode_len = len(self.mode)
|
|
|
|
self.colors = {
|
|
|
|
tuple(self.palette[i : i + mode_len]): i // mode_len
|
|
|
|
for i in range(0, len(self.palette), mode_len)
|
|
|
|
}
|
|
|
|
|
2015-05-14 02:57:56 +03:00
|
|
|
def copy(self):
|
|
|
|
new = ImagePalette()
|
|
|
|
|
|
|
|
new.mode = self.mode
|
|
|
|
new.rawmode = self.rawmode
|
|
|
|
if self.palette is not None:
|
|
|
|
new.palette = self.palette[:]
|
|
|
|
new.dirty = self.dirty
|
|
|
|
|
|
|
|
return new
|
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
def getdata(self):
|
2013-10-14 08:22:18 +04:00
|
|
|
"""
|
2018-08-11 09:39:49 +03:00
|
|
|
Get palette contents in format suitable for the low-level
|
2013-10-14 08:22:18 +04:00
|
|
|
``im.putpalette`` primitive.
|
|
|
|
|
|
|
|
.. warning:: This method is experimental.
|
|
|
|
"""
|
2010-07-31 06:52:47 +04:00
|
|
|
if self.rawmode:
|
|
|
|
return self.rawmode, self.palette
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
return self.mode + ";L", self.tobytes()
|
2010-07-31 06:52:47 +04:00
|
|
|
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
def tobytes(self):
|
2013-10-14 08:22:18 +04:00
|
|
|
"""Convert palette to bytes.
|
|
|
|
|
|
|
|
.. warning:: This method is experimental.
|
|
|
|
"""
|
2010-07-31 06:52:47 +04:00
|
|
|
if self.rawmode:
|
|
|
|
raise ValueError("palette contains raw palette data")
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
if isinstance(self.palette, bytes):
|
2010-07-31 06:52:47 +04:00
|
|
|
return self.palette
|
2013-05-23 21:33:27 +04:00
|
|
|
arr = array.array("B", self.palette)
|
2021-06-22 14:57:25 +03:00
|
|
|
return arr.tobytes()
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2013-03-07 13:23:40 +04:00
|
|
|
# Declare tostring as an alias for tobytes
|
|
|
|
tostring = tobytes
|
py3k: The big push
There are two main issues fixed with this commit:
* bytes vs. str: All file, image, and palette data are now handled as
bytes. A new _binary module consolidates the hacks needed to do this
across Python versions. tostring/fromstring methods have been renamed to
tobytes/frombytes, but the Python 2.6/2.7 versions alias them to the old
names for compatibility. Users should move to tobytes/frombytes.
One other potentially-breaking change is that text data in image files
(such as tags, comments) are now explicitly handled with a specific
character encoding in mind. This works well with the Unicode str in
Python 3, but may trip up old code expecting a straight byte-for-byte
translation to a Python string. This also required a change to Gohlke's
tags tests (in Tests/test_file_png.py) to expect Unicode strings from
the code.
* True div vs. floor div: Many division operations used the "/" operator
to do floor division, which is now the "//" operator in Python 3. These
were fixed.
As of this commit, on the first pass, I have one failing test (improper
handling of a slice object in a C module, test_imagepath.py) in Python 3,
and three that that I haven't tried running yet (test_imagegl,
test_imagegrab, and test_imageqt). I also haven't tested anything on
Windows. All but the three skipped tests run flawlessly against Pythons
2.6 and 2.7.
2012-10-21 01:01:53 +04:00
|
|
|
|
2021-06-23 11:41:46 +03:00
|
|
|
def getcolor(self, color, image=None):
|
2013-10-14 08:22:18 +04:00
|
|
|
"""Given an rgb tuple, allocate palette entry.
|
|
|
|
|
|
|
|
.. warning:: This method is experimental.
|
|
|
|
"""
|
2010-07-31 06:52:47 +04:00
|
|
|
if self.rawmode:
|
|
|
|
raise ValueError("palette contains raw palette data")
|
2012-10-16 07:14:10 +04:00
|
|
|
if isinstance(color, tuple):
|
2021-06-23 12:26:53 +03:00
|
|
|
if self.mode == "RGB":
|
|
|
|
if len(color) == 4 and color[3] == 255:
|
|
|
|
color = color[:3]
|
|
|
|
elif self.mode == "RGBA":
|
|
|
|
if len(color) == 3:
|
|
|
|
color += (255,)
|
2010-07-31 06:52:47 +04:00
|
|
|
try:
|
|
|
|
return self.colors[color]
|
2020-06-21 13:13:35 +03:00
|
|
|
except KeyError as e:
|
2010-07-31 06:52:47 +04:00
|
|
|
# allocate new color slot
|
2021-06-22 14:21:38 +03:00
|
|
|
if not isinstance(self.palette, bytearray):
|
2021-06-23 12:16:04 +03:00
|
|
|
self._palette = bytearray(self.palette)
|
2021-06-23 12:22:21 +03:00
|
|
|
index = len(self.palette) // 3
|
2010-07-31 06:52:47 +04:00
|
|
|
if index >= 256:
|
2021-06-23 11:41:46 +03:00
|
|
|
if image:
|
|
|
|
# Search for an unused index
|
|
|
|
for i, count in reversed(list(enumerate(image.histogram()))):
|
|
|
|
if count == 0:
|
|
|
|
index = i
|
|
|
|
break
|
|
|
|
if index >= 256:
|
|
|
|
raise ValueError("cannot allocate more than 256 colors") from e
|
2010-07-31 06:52:47 +04:00
|
|
|
self.colors[color] = index
|
2021-06-23 12:22:21 +03:00
|
|
|
if index * 3 < len(self.palette):
|
2021-06-22 14:21:38 +03:00
|
|
|
self._palette = (
|
|
|
|
self.palette[: index * 3]
|
|
|
|
+ bytes(color)
|
|
|
|
+ self.palette[index * 3 + 3 :]
|
|
|
|
)
|
2021-06-23 12:22:21 +03:00
|
|
|
else:
|
|
|
|
self._palette += bytes(color)
|
2010-07-31 06:52:47 +04:00
|
|
|
self.dirty = 1
|
|
|
|
return index
|
|
|
|
else:
|
2020-07-16 12:43:29 +03:00
|
|
|
raise ValueError(f"unknown color specifier: {repr(color)}")
|
2010-07-31 06:52:47 +04:00
|
|
|
|
|
|
|
def save(self, fp):
|
2013-10-14 08:22:18 +04:00
|
|
|
"""Save palette to text file.
|
|
|
|
|
|
|
|
.. warning:: This method is experimental.
|
|
|
|
"""
|
2010-07-31 06:52:47 +04:00
|
|
|
if self.rawmode:
|
|
|
|
raise ValueError("palette contains raw palette data")
|
2012-10-17 07:39:56 +04:00
|
|
|
if isinstance(fp, str):
|
2010-07-31 06:52:47 +04:00
|
|
|
fp = open(fp, "w")
|
|
|
|
fp.write("# Palette\n")
|
2020-07-16 12:43:29 +03:00
|
|
|
fp.write(f"# Mode: {self.mode}\n")
|
2010-07-31 06:52:47 +04:00
|
|
|
for i in range(256):
|
2020-07-16 12:43:29 +03:00
|
|
|
fp.write(f"{i}")
|
2019-03-21 16:28:20 +03:00
|
|
|
for j in range(i * len(self.mode), (i + 1) * len(self.mode)):
|
2014-06-24 11:34:43 +04:00
|
|
|
try:
|
2020-07-16 12:43:29 +03:00
|
|
|
fp.write(f" {self.palette[j]}")
|
2014-06-24 11:34:43 +04:00
|
|
|
except IndexError:
|
|
|
|
fp.write(" 0")
|
2010-07-31 06:52:47 +04:00
|
|
|
fp.write("\n")
|
|
|
|
fp.close()
|
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
# --------------------------------------------------------------------
|
|
|
|
# Internal
|
|
|
|
|
2019-03-21 16:28:20 +03:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
def raw(rawmode, data):
|
|
|
|
palette = ImagePalette()
|
|
|
|
palette.rawmode = rawmode
|
|
|
|
palette.palette = data
|
|
|
|
palette.dirty = 1
|
|
|
|
return palette
|
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
# --------------------------------------------------------------------
|
|
|
|
# Factories
|
|
|
|
|
2019-03-21 16:28:20 +03:00
|
|
|
|
2014-07-20 11:13:26 +04:00
|
|
|
def make_linear_lut(black, white):
|
2010-07-31 06:52:47 +04:00
|
|
|
lut = []
|
|
|
|
if black == 0:
|
|
|
|
for i in range(256):
|
2019-03-21 16:28:20 +03:00
|
|
|
lut.append(white * i // 255)
|
2010-07-31 06:52:47 +04:00
|
|
|
else:
|
2014-07-15 19:38:10 +04:00
|
|
|
raise NotImplementedError # FIXME
|
2010-07-31 06:52:47 +04:00
|
|
|
return lut
|
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
|
2014-07-19 02:45:57 +04:00
|
|
|
def make_gamma_lut(exp):
|
2010-07-31 06:52:47 +04:00
|
|
|
lut = []
|
|
|
|
for i in range(256):
|
|
|
|
lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
|
|
|
|
return lut
|
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
def negative(mode="RGB"):
|
2012-10-16 01:18:27 +04:00
|
|
|
palette = list(range(256))
|
2010-07-31 06:52:47 +04:00
|
|
|
palette.reverse()
|
|
|
|
return ImagePalette(mode, palette * len(mode))
|
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
def random(mode="RGB"):
|
|
|
|
from random import randint
|
2019-03-21 16:28:20 +03:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
palette = []
|
2019-03-21 16:28:20 +03:00
|
|
|
for i in range(256 * len(mode)):
|
2010-07-31 06:52:47 +04:00
|
|
|
palette.append(randint(0, 255))
|
|
|
|
return ImagePalette(mode, palette)
|
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
def sepia(white="#fff0c0"):
|
|
|
|
r, g, b = ImageColor.getrgb(white)
|
2014-07-20 22:08:14 +04:00
|
|
|
r = make_linear_lut(0, r)
|
|
|
|
g = make_linear_lut(0, g)
|
|
|
|
b = make_linear_lut(0, b)
|
2010-07-31 06:52:47 +04:00
|
|
|
return ImagePalette("RGB", r + g + b)
|
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
def wedge(mode="RGB"):
|
2012-10-16 01:18:27 +04:00
|
|
|
return ImagePalette(mode, list(range(256)) * len(mode))
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
|
2010-07-31 06:52:47 +04:00
|
|
|
def load(filename):
|
|
|
|
|
|
|
|
# FIXME: supports GIMP gradients only
|
|
|
|
|
2016-11-12 03:59:32 +03:00
|
|
|
with open(filename, "rb") as fp:
|
|
|
|
|
|
|
|
for paletteHandler in [
|
|
|
|
GimpPaletteFile.GimpPaletteFile,
|
|
|
|
GimpGradientFile.GimpGradientFile,
|
2019-03-21 16:28:20 +03:00
|
|
|
PaletteFile.PaletteFile,
|
2016-11-12 03:59:32 +03:00
|
|
|
]:
|
|
|
|
try:
|
|
|
|
fp.seek(0)
|
|
|
|
lut = paletteHandler(fp).getpalette()
|
|
|
|
if lut:
|
|
|
|
break
|
|
|
|
except (SyntaxError, ValueError):
|
|
|
|
# import traceback
|
|
|
|
# traceback.print_exc()
|
|
|
|
pass
|
|
|
|
else:
|
2019-09-30 17:56:31 +03:00
|
|
|
raise OSError("cannot load palette")
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2014-07-15 19:38:10 +04:00
|
|
|
return lut # data, rawmode
|