ImagePalette.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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. import array
  19. from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
  20. class ImagePalette:
  21. """
  22. Color palette for palette mapped images
  23. :param mode: The mode to use for the Palette. See:
  24. :ref:`concept-modes`. Defaults to "RGB"
  25. :param palette: An optional palette. If given, it must be a bytearray,
  26. an array or a list of ints between 0-255 and of length ``size``
  27. times the number of colors in ``mode``. The list must be aligned
  28. by channel (All R values must be contiguous in the list before G
  29. and B values.) Defaults to 0 through 255 per channel.
  30. :param size: An optional palette size. If given, it cannot be equal to
  31. or greater than 256. Defaults to 0.
  32. """
  33. def __init__(self, mode="RGB", palette=None, size=0):
  34. self.mode = mode
  35. self.rawmode = None # if set, palette contains raw data
  36. self.palette = palette or bytearray(range(256)) * len(self.mode)
  37. self.colors = {}
  38. self.dirty = None
  39. if (size == 0 and len(self.mode) * 256 != len(self.palette)) or (
  40. size != 0 and size != len(self.palette)
  41. ):
  42. raise ValueError("wrong palette size")
  43. def copy(self):
  44. new = ImagePalette()
  45. new.mode = self.mode
  46. new.rawmode = self.rawmode
  47. if self.palette is not None:
  48. new.palette = self.palette[:]
  49. new.colors = self.colors.copy()
  50. new.dirty = self.dirty
  51. return new
  52. def getdata(self):
  53. """
  54. Get palette contents in format suitable for the low-level
  55. ``im.putpalette`` primitive.
  56. .. warning:: This method is experimental.
  57. """
  58. if self.rawmode:
  59. return self.rawmode, self.palette
  60. return self.mode + ";L", self.tobytes()
  61. def tobytes(self):
  62. """Convert palette to bytes.
  63. .. warning:: This method is experimental.
  64. """
  65. if self.rawmode:
  66. raise ValueError("palette contains raw palette data")
  67. if isinstance(self.palette, bytes):
  68. return self.palette
  69. arr = array.array("B", self.palette)
  70. if hasattr(arr, "tobytes"):
  71. return arr.tobytes()
  72. return arr.tostring()
  73. # Declare tostring as an alias for tobytes
  74. tostring = tobytes
  75. def getcolor(self, color):
  76. """Given an rgb tuple, allocate palette entry.
  77. .. warning:: This method is experimental.
  78. """
  79. if self.rawmode:
  80. raise ValueError("palette contains raw palette data")
  81. if isinstance(color, tuple):
  82. try:
  83. return self.colors[color]
  84. except KeyError as e:
  85. # allocate new color slot
  86. if isinstance(self.palette, bytes):
  87. self.palette = bytearray(self.palette)
  88. index = len(self.colors)
  89. if index >= 256:
  90. raise ValueError("cannot allocate more than 256 colors") from e
  91. self.colors[color] = index
  92. self.palette[index] = color[0]
  93. self.palette[index + 256] = color[1]
  94. self.palette[index + 512] = color[2]
  95. self.dirty = 1
  96. return index
  97. else:
  98. raise ValueError(f"unknown color specifier: {repr(color)}")
  99. def save(self, fp):
  100. """Save palette to text file.
  101. .. warning:: This method is experimental.
  102. """
  103. if self.rawmode:
  104. raise ValueError("palette contains raw palette data")
  105. if isinstance(fp, str):
  106. fp = open(fp, "w")
  107. fp.write("# Palette\n")
  108. fp.write(f"# Mode: {self.mode}\n")
  109. for i in range(256):
  110. fp.write(f"{i}")
  111. for j in range(i * len(self.mode), (i + 1) * len(self.mode)):
  112. try:
  113. fp.write(f" {self.palette[j]}")
  114. except IndexError:
  115. fp.write(" 0")
  116. fp.write("\n")
  117. fp.close()
  118. # --------------------------------------------------------------------
  119. # Internal
  120. def raw(rawmode, data):
  121. palette = ImagePalette()
  122. palette.rawmode = rawmode
  123. palette.palette = data
  124. palette.dirty = 1
  125. return palette
  126. # --------------------------------------------------------------------
  127. # Factories
  128. def make_linear_lut(black, white):
  129. lut = []
  130. if black == 0:
  131. for i in range(256):
  132. lut.append(white * i // 255)
  133. else:
  134. raise NotImplementedError # FIXME
  135. return lut
  136. def make_gamma_lut(exp):
  137. lut = []
  138. for i in range(256):
  139. lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
  140. return lut
  141. def negative(mode="RGB"):
  142. palette = list(range(256))
  143. palette.reverse()
  144. return ImagePalette(mode, palette * len(mode))
  145. def random(mode="RGB"):
  146. from random import randint
  147. palette = []
  148. for i in range(256 * len(mode)):
  149. palette.append(randint(0, 255))
  150. return ImagePalette(mode, palette)
  151. def sepia(white="#fff0c0"):
  152. r, g, b = ImageColor.getrgb(white)
  153. r = make_linear_lut(0, r)
  154. g = make_linear_lut(0, g)
  155. b = make_linear_lut(0, b)
  156. return ImagePalette("RGB", r + g + b)
  157. def wedge(mode="RGB"):
  158. return ImagePalette(mode, list(range(256)) * len(mode))
  159. def load(filename):
  160. # FIXME: supports GIMP gradients only
  161. with open(filename, "rb") as fp:
  162. for paletteHandler in [
  163. GimpPaletteFile.GimpPaletteFile,
  164. GimpGradientFile.GimpGradientFile,
  165. PaletteFile.PaletteFile,
  166. ]:
  167. try:
  168. fp.seek(0)
  169. lut = paletteHandler(fp).getpalette()
  170. if lut:
  171. break
  172. except (SyntaxError, ValueError):
  173. # import traceback
  174. # traceback.print_exc()
  175. pass
  176. else:
  177. raise OSError("cannot load palette")
  178. return lut # data, rawmode