@ai-ecoverse/py-pillow 12.3.0-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +4 -0
  3. package/lib/python3.14/site-packages/PIL/AvifImagePlugin.py +295 -0
  4. package/lib/python3.14/site-packages/PIL/BdfFontFile.py +124 -0
  5. package/lib/python3.14/site-packages/PIL/BlpImagePlugin.py +498 -0
  6. package/lib/python3.14/site-packages/PIL/BmpImagePlugin.py +514 -0
  7. package/lib/python3.14/site-packages/PIL/BufrStubImagePlugin.py +72 -0
  8. package/lib/python3.14/site-packages/PIL/ContainerIO.py +173 -0
  9. package/lib/python3.14/site-packages/PIL/CurImagePlugin.py +103 -0
  10. package/lib/python3.14/site-packages/PIL/DcxImagePlugin.py +84 -0
  11. package/lib/python3.14/site-packages/PIL/DdsImagePlugin.py +632 -0
  12. package/lib/python3.14/site-packages/PIL/EpsImagePlugin.py +484 -0
  13. package/lib/python3.14/site-packages/PIL/ExifTags.py +384 -0
  14. package/lib/python3.14/site-packages/PIL/FitsImagePlugin.py +153 -0
  15. package/lib/python3.14/site-packages/PIL/FliImagePlugin.py +184 -0
  16. package/lib/python3.14/site-packages/PIL/FontFile.py +160 -0
  17. package/lib/python3.14/site-packages/PIL/FpxImagePlugin.py +262 -0
  18. package/lib/python3.14/site-packages/PIL/FtexImagePlugin.py +115 -0
  19. package/lib/python3.14/site-packages/PIL/GbrImagePlugin.py +103 -0
  20. package/lib/python3.14/site-packages/PIL/GdImageFile.py +104 -0
  21. package/lib/python3.14/site-packages/PIL/GifImagePlugin.py +1223 -0
  22. package/lib/python3.14/site-packages/PIL/GimpGradientFile.py +154 -0
  23. package/lib/python3.14/site-packages/PIL/GimpPaletteFile.py +75 -0
  24. package/lib/python3.14/site-packages/PIL/GribStubImagePlugin.py +72 -0
  25. package/lib/python3.14/site-packages/PIL/Hdf5StubImagePlugin.py +72 -0
  26. package/lib/python3.14/site-packages/PIL/IcnsImagePlugin.py +401 -0
  27. package/lib/python3.14/site-packages/PIL/IcoImagePlugin.py +396 -0
  28. package/lib/python3.14/site-packages/PIL/ImImagePlugin.py +390 -0
  29. package/lib/python3.14/site-packages/PIL/Image.py +4385 -0
  30. package/lib/python3.14/site-packages/PIL/ImageChops.py +311 -0
  31. package/lib/python3.14/site-packages/PIL/ImageCms.py +1078 -0
  32. package/lib/python3.14/site-packages/PIL/ImageColor.py +320 -0
  33. package/lib/python3.14/site-packages/PIL/ImageDraw.py +1002 -0
  34. package/lib/python3.14/site-packages/PIL/ImageDraw2.py +244 -0
  35. package/lib/python3.14/site-packages/PIL/ImageEnhance.py +113 -0
  36. package/lib/python3.14/site-packages/PIL/ImageFile.py +938 -0
  37. package/lib/python3.14/site-packages/PIL/ImageFilter.py +617 -0
  38. package/lib/python3.14/site-packages/PIL/ImageFont.py +1330 -0
  39. package/lib/python3.14/site-packages/PIL/ImageGrab.py +234 -0
  40. package/lib/python3.14/site-packages/PIL/ImageMath.py +314 -0
  41. package/lib/python3.14/site-packages/PIL/ImageMode.py +85 -0
  42. package/lib/python3.14/site-packages/PIL/ImageMorph.py +317 -0
  43. package/lib/python3.14/site-packages/PIL/ImageOps.py +749 -0
  44. package/lib/python3.14/site-packages/PIL/ImagePalette.py +290 -0
  45. package/lib/python3.14/site-packages/PIL/ImagePath.py +20 -0
  46. package/lib/python3.14/site-packages/PIL/ImageQt.py +219 -0
  47. package/lib/python3.14/site-packages/PIL/ImageSequence.py +88 -0
  48. package/lib/python3.14/site-packages/PIL/ImageShow.py +367 -0
  49. package/lib/python3.14/site-packages/PIL/ImageStat.py +167 -0
  50. package/lib/python3.14/site-packages/PIL/ImageText.py +509 -0
  51. package/lib/python3.14/site-packages/PIL/ImageTk.py +266 -0
  52. package/lib/python3.14/site-packages/PIL/ImageTransform.py +136 -0
  53. package/lib/python3.14/site-packages/PIL/ImageWin.py +247 -0
  54. package/lib/python3.14/site-packages/PIL/ImtImagePlugin.py +103 -0
  55. package/lib/python3.14/site-packages/PIL/IptcImagePlugin.py +226 -0
  56. package/lib/python3.14/site-packages/PIL/Jpeg2KImagePlugin.py +466 -0
  57. package/lib/python3.14/site-packages/PIL/JpegImagePlugin.py +889 -0
  58. package/lib/python3.14/site-packages/PIL/JpegPresets.py +242 -0
  59. package/lib/python3.14/site-packages/PIL/McIdasImagePlugin.py +78 -0
  60. package/lib/python3.14/site-packages/PIL/MicImagePlugin.py +103 -0
  61. package/lib/python3.14/site-packages/PIL/MpegImagePlugin.py +84 -0
  62. package/lib/python3.14/site-packages/PIL/MpoImagePlugin.py +203 -0
  63. package/lib/python3.14/site-packages/PIL/MspImagePlugin.py +200 -0
  64. package/lib/python3.14/site-packages/PIL/PSDraw.py +238 -0
  65. package/lib/python3.14/site-packages/PIL/PaletteFile.py +54 -0
  66. package/lib/python3.14/site-packages/PIL/PalmImagePlugin.py +217 -0
  67. package/lib/python3.14/site-packages/PIL/PcdImagePlugin.py +68 -0
  68. package/lib/python3.14/site-packages/PIL/PcfFontFile.py +256 -0
  69. package/lib/python3.14/site-packages/PIL/PcxImagePlugin.py +232 -0
  70. package/lib/python3.14/site-packages/PIL/PdfImagePlugin.py +315 -0
  71. package/lib/python3.14/site-packages/PIL/PdfParser.py +1092 -0
  72. package/lib/python3.14/site-packages/PIL/PixarImagePlugin.py +72 -0
  73. package/lib/python3.14/site-packages/PIL/PngImagePlugin.py +1578 -0
  74. package/lib/python3.14/site-packages/PIL/PpmImagePlugin.py +375 -0
  75. package/lib/python3.14/site-packages/PIL/PsdImagePlugin.py +339 -0
  76. package/lib/python3.14/site-packages/PIL/QoiImagePlugin.py +235 -0
  77. package/lib/python3.14/site-packages/PIL/SgiImagePlugin.py +231 -0
  78. package/lib/python3.14/site-packages/PIL/SpiderImagePlugin.py +332 -0
  79. package/lib/python3.14/site-packages/PIL/SunImagePlugin.py +145 -0
  80. package/lib/python3.14/site-packages/PIL/TarIO.py +61 -0
  81. package/lib/python3.14/site-packages/PIL/TgaImagePlugin.py +284 -0
  82. package/lib/python3.14/site-packages/PIL/TiffImagePlugin.py +2354 -0
  83. package/lib/python3.14/site-packages/PIL/TiffTags.py +566 -0
  84. package/lib/python3.14/site-packages/PIL/WalImageFile.py +129 -0
  85. package/lib/python3.14/site-packages/PIL/WebPImagePlugin.py +322 -0
  86. package/lib/python3.14/site-packages/PIL/WmfImagePlugin.py +183 -0
  87. package/lib/python3.14/site-packages/PIL/XVThumbImagePlugin.py +83 -0
  88. package/lib/python3.14/site-packages/PIL/XbmImagePlugin.py +98 -0
  89. package/lib/python3.14/site-packages/PIL/XpmImagePlugin.py +157 -0
  90. package/lib/python3.14/site-packages/PIL/__init__.py +87 -0
  91. package/lib/python3.14/site-packages/PIL/__main__.py +7 -0
  92. package/lib/python3.14/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-314.pyc +0 -0
  93. package/lib/python3.14/site-packages/PIL/__pycache__/BdfFontFile.cpython-314.pyc +0 -0
  94. package/lib/python3.14/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-314.pyc +0 -0
  95. package/lib/python3.14/site-packages/PIL/__pycache__/BmpImagePlugin.cpython-314.pyc +0 -0
  96. package/lib/python3.14/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-314.pyc +0 -0
  97. package/lib/python3.14/site-packages/PIL/__pycache__/ContainerIO.cpython-314.pyc +0 -0
  98. package/lib/python3.14/site-packages/PIL/__pycache__/CurImagePlugin.cpython-314.pyc +0 -0
  99. package/lib/python3.14/site-packages/PIL/__pycache__/DcxImagePlugin.cpython-314.pyc +0 -0
  100. package/lib/python3.14/site-packages/PIL/__pycache__/DdsImagePlugin.cpython-314.pyc +0 -0
  101. package/lib/python3.14/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-314.pyc +0 -0
  102. package/lib/python3.14/site-packages/PIL/__pycache__/ExifTags.cpython-314.pyc +0 -0
  103. package/lib/python3.14/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-314.pyc +0 -0
  104. package/lib/python3.14/site-packages/PIL/__pycache__/FliImagePlugin.cpython-314.pyc +0 -0
  105. package/lib/python3.14/site-packages/PIL/__pycache__/FontFile.cpython-314.pyc +0 -0
  106. package/lib/python3.14/site-packages/PIL/__pycache__/FpxImagePlugin.cpython-314.pyc +0 -0
  107. package/lib/python3.14/site-packages/PIL/__pycache__/FtexImagePlugin.cpython-314.pyc +0 -0
  108. package/lib/python3.14/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-314.pyc +0 -0
  109. package/lib/python3.14/site-packages/PIL/__pycache__/GdImageFile.cpython-314.pyc +0 -0
  110. package/lib/python3.14/site-packages/PIL/__pycache__/GifImagePlugin.cpython-314.pyc +0 -0
  111. package/lib/python3.14/site-packages/PIL/__pycache__/GimpGradientFile.cpython-314.pyc +0 -0
  112. package/lib/python3.14/site-packages/PIL/__pycache__/GimpPaletteFile.cpython-314.pyc +0 -0
  113. package/lib/python3.14/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-314.pyc +0 -0
  114. package/lib/python3.14/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-314.pyc +0 -0
  115. package/lib/python3.14/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-314.pyc +0 -0
  116. package/lib/python3.14/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-314.pyc +0 -0
  117. package/lib/python3.14/site-packages/PIL/__pycache__/ImImagePlugin.cpython-314.pyc +0 -0
  118. package/lib/python3.14/site-packages/PIL/__pycache__/Image.cpython-314.pyc +0 -0
  119. package/lib/python3.14/site-packages/PIL/__pycache__/ImageChops.cpython-314.pyc +0 -0
  120. package/lib/python3.14/site-packages/PIL/__pycache__/ImageCms.cpython-314.pyc +0 -0
  121. package/lib/python3.14/site-packages/PIL/__pycache__/ImageColor.cpython-314.pyc +0 -0
  122. package/lib/python3.14/site-packages/PIL/__pycache__/ImageDraw.cpython-314.pyc +0 -0
  123. package/lib/python3.14/site-packages/PIL/__pycache__/ImageDraw2.cpython-314.pyc +0 -0
  124. package/lib/python3.14/site-packages/PIL/__pycache__/ImageEnhance.cpython-314.pyc +0 -0
  125. package/lib/python3.14/site-packages/PIL/__pycache__/ImageFile.cpython-314.pyc +0 -0
  126. package/lib/python3.14/site-packages/PIL/__pycache__/ImageFilter.cpython-314.pyc +0 -0
  127. package/lib/python3.14/site-packages/PIL/__pycache__/ImageFont.cpython-314.pyc +0 -0
  128. package/lib/python3.14/site-packages/PIL/__pycache__/ImageGrab.cpython-314.pyc +0 -0
  129. package/lib/python3.14/site-packages/PIL/__pycache__/ImageMath.cpython-314.pyc +0 -0
  130. package/lib/python3.14/site-packages/PIL/__pycache__/ImageMode.cpython-314.pyc +0 -0
  131. package/lib/python3.14/site-packages/PIL/__pycache__/ImageMorph.cpython-314.pyc +0 -0
  132. package/lib/python3.14/site-packages/PIL/__pycache__/ImageOps.cpython-314.pyc +0 -0
  133. package/lib/python3.14/site-packages/PIL/__pycache__/ImagePalette.cpython-314.pyc +0 -0
  134. package/lib/python3.14/site-packages/PIL/__pycache__/ImagePath.cpython-314.pyc +0 -0
  135. package/lib/python3.14/site-packages/PIL/__pycache__/ImageQt.cpython-314.pyc +0 -0
  136. package/lib/python3.14/site-packages/PIL/__pycache__/ImageSequence.cpython-314.pyc +0 -0
  137. package/lib/python3.14/site-packages/PIL/__pycache__/ImageShow.cpython-314.pyc +0 -0
  138. package/lib/python3.14/site-packages/PIL/__pycache__/ImageStat.cpython-314.pyc +0 -0
  139. package/lib/python3.14/site-packages/PIL/__pycache__/ImageText.cpython-314.pyc +0 -0
  140. package/lib/python3.14/site-packages/PIL/__pycache__/ImageTk.cpython-314.pyc +0 -0
  141. package/lib/python3.14/site-packages/PIL/__pycache__/ImageTransform.cpython-314.pyc +0 -0
  142. package/lib/python3.14/site-packages/PIL/__pycache__/ImageWin.cpython-314.pyc +0 -0
  143. package/lib/python3.14/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-314.pyc +0 -0
  144. package/lib/python3.14/site-packages/PIL/__pycache__/IptcImagePlugin.cpython-314.pyc +0 -0
  145. package/lib/python3.14/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-314.pyc +0 -0
  146. package/lib/python3.14/site-packages/PIL/__pycache__/JpegImagePlugin.cpython-314.pyc +0 -0
  147. package/lib/python3.14/site-packages/PIL/__pycache__/JpegPresets.cpython-314.pyc +0 -0
  148. package/lib/python3.14/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-314.pyc +0 -0
  149. package/lib/python3.14/site-packages/PIL/__pycache__/MicImagePlugin.cpython-314.pyc +0 -0
  150. package/lib/python3.14/site-packages/PIL/__pycache__/MpegImagePlugin.cpython-314.pyc +0 -0
  151. package/lib/python3.14/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-314.pyc +0 -0
  152. package/lib/python3.14/site-packages/PIL/__pycache__/MspImagePlugin.cpython-314.pyc +0 -0
  153. package/lib/python3.14/site-packages/PIL/__pycache__/PSDraw.cpython-314.pyc +0 -0
  154. package/lib/python3.14/site-packages/PIL/__pycache__/PaletteFile.cpython-314.pyc +0 -0
  155. package/lib/python3.14/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-314.pyc +0 -0
  156. package/lib/python3.14/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-314.pyc +0 -0
  157. package/lib/python3.14/site-packages/PIL/__pycache__/PcfFontFile.cpython-314.pyc +0 -0
  158. package/lib/python3.14/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-314.pyc +0 -0
  159. package/lib/python3.14/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-314.pyc +0 -0
  160. package/lib/python3.14/site-packages/PIL/__pycache__/PdfParser.cpython-314.pyc +0 -0
  161. package/lib/python3.14/site-packages/PIL/__pycache__/PixarImagePlugin.cpython-314.pyc +0 -0
  162. package/lib/python3.14/site-packages/PIL/__pycache__/PngImagePlugin.cpython-314.pyc +0 -0
  163. package/lib/python3.14/site-packages/PIL/__pycache__/PpmImagePlugin.cpython-314.pyc +0 -0
  164. package/lib/python3.14/site-packages/PIL/__pycache__/PsdImagePlugin.cpython-314.pyc +0 -0
  165. package/lib/python3.14/site-packages/PIL/__pycache__/QoiImagePlugin.cpython-314.pyc +0 -0
  166. package/lib/python3.14/site-packages/PIL/__pycache__/SgiImagePlugin.cpython-314.pyc +0 -0
  167. package/lib/python3.14/site-packages/PIL/__pycache__/SpiderImagePlugin.cpython-314.pyc +0 -0
  168. package/lib/python3.14/site-packages/PIL/__pycache__/SunImagePlugin.cpython-314.pyc +0 -0
  169. package/lib/python3.14/site-packages/PIL/__pycache__/TarIO.cpython-314.pyc +0 -0
  170. package/lib/python3.14/site-packages/PIL/__pycache__/TgaImagePlugin.cpython-314.pyc +0 -0
  171. package/lib/python3.14/site-packages/PIL/__pycache__/TiffImagePlugin.cpython-314.pyc +0 -0
  172. package/lib/python3.14/site-packages/PIL/__pycache__/TiffTags.cpython-314.pyc +0 -0
  173. package/lib/python3.14/site-packages/PIL/__pycache__/WalImageFile.cpython-314.pyc +0 -0
  174. package/lib/python3.14/site-packages/PIL/__pycache__/WebPImagePlugin.cpython-314.pyc +0 -0
  175. package/lib/python3.14/site-packages/PIL/__pycache__/WmfImagePlugin.cpython-314.pyc +0 -0
  176. package/lib/python3.14/site-packages/PIL/__pycache__/XVThumbImagePlugin.cpython-314.pyc +0 -0
  177. package/lib/python3.14/site-packages/PIL/__pycache__/XbmImagePlugin.cpython-314.pyc +0 -0
  178. package/lib/python3.14/site-packages/PIL/__pycache__/XpmImagePlugin.cpython-314.pyc +0 -0
  179. package/lib/python3.14/site-packages/PIL/__pycache__/__init__.cpython-314.pyc +0 -0
  180. package/lib/python3.14/site-packages/PIL/__pycache__/__main__.cpython-314.pyc +0 -0
  181. package/lib/python3.14/site-packages/PIL/__pycache__/_binary.cpython-314.pyc +0 -0
  182. package/lib/python3.14/site-packages/PIL/__pycache__/_deprecate.cpython-314.pyc +0 -0
  183. package/lib/python3.14/site-packages/PIL/__pycache__/_tkinter_finder.cpython-314.pyc +0 -0
  184. package/lib/python3.14/site-packages/PIL/__pycache__/_typing.cpython-314.pyc +0 -0
  185. package/lib/python3.14/site-packages/PIL/__pycache__/_util.cpython-314.pyc +0 -0
  186. package/lib/python3.14/site-packages/PIL/__pycache__/_version.cpython-314.pyc +0 -0
  187. package/lib/python3.14/site-packages/PIL/__pycache__/features.cpython-314.pyc +0 -0
  188. package/lib/python3.14/site-packages/PIL/__pycache__/report.cpython-314.pyc +0 -0
  189. package/lib/python3.14/site-packages/PIL/_avif.pyi +3 -0
  190. package/lib/python3.14/site-packages/PIL/_binary.py +113 -0
  191. package/lib/python3.14/site-packages/PIL/_deprecate.py +72 -0
  192. package/lib/python3.14/site-packages/PIL/_imaging.cpython-314-wasm32-wasix.so +0 -0
  193. package/lib/python3.14/site-packages/PIL/_imaging.pyi +31 -0
  194. package/lib/python3.14/site-packages/PIL/_imagingcms.pyi +143 -0
  195. package/lib/python3.14/site-packages/PIL/_imagingft.cpython-314-wasm32-wasix.so +0 -0
  196. package/lib/python3.14/site-packages/PIL/_imagingft.pyi +70 -0
  197. package/lib/python3.14/site-packages/PIL/_imagingmath.cpython-314-wasm32-wasix.so +0 -0
  198. package/lib/python3.14/site-packages/PIL/_imagingmath.pyi +3 -0
  199. package/lib/python3.14/site-packages/PIL/_imagingmorph.cpython-314-wasm32-wasix.so +0 -0
  200. package/lib/python3.14/site-packages/PIL/_imagingmorph.pyi +3 -0
  201. package/lib/python3.14/site-packages/PIL/_imagingtk.cpython-314-wasm32-wasix.so +0 -0
  202. package/lib/python3.14/site-packages/PIL/_imagingtk.pyi +3 -0
  203. package/lib/python3.14/site-packages/PIL/_tkinter_finder.py +20 -0
  204. package/lib/python3.14/site-packages/PIL/_typing.py +45 -0
  205. package/lib/python3.14/site-packages/PIL/_util.py +29 -0
  206. package/lib/python3.14/site-packages/PIL/_version.py +4 -0
  207. package/lib/python3.14/site-packages/PIL/_webp.pyi +3 -0
  208. package/lib/python3.14/site-packages/PIL/features.py +343 -0
  209. package/lib/python3.14/site-packages/PIL/py.typed +0 -0
  210. package/lib/python3.14/site-packages/PIL/report.py +5 -0
  211. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/METADATA +180 -0
  212. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/RECORD +117 -0
  213. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/WHEEL +5 -0
  214. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/licenses/LICENSE +30 -0
  215. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/top_level.txt +1 -0
  216. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/zip-safe +1 -0
  217. package/package.json +46 -0
@@ -0,0 +1,1002 @@
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # drawing interface operations
6
+ #
7
+ # History:
8
+ # 1996-04-13 fl Created (experimental)
9
+ # 1996-08-07 fl Filled polygons, ellipses.
10
+ # 1996-08-13 fl Added text support
11
+ # 1998-06-28 fl Handle I and F images
12
+ # 1998-12-29 fl Added arc; use arc primitive to draw ellipses
13
+ # 1999-01-10 fl Added shape stuff (experimental)
14
+ # 1999-02-06 fl Added bitmap support
15
+ # 1999-02-11 fl Changed all primitives to take options
16
+ # 1999-02-20 fl Fixed backwards compatibility
17
+ # 2000-10-12 fl Copy on write, when necessary
18
+ # 2001-02-18 fl Use default ink for bitmap/text also in fill mode
19
+ # 2002-10-24 fl Added support for CSS-style color strings
20
+ # 2002-12-10 fl Added experimental support for RGBA-on-RGB drawing
21
+ # 2002-12-11 fl Refactored low-level drawing API (work in progress)
22
+ # 2004-08-26 fl Made Draw() a factory function, added getdraw() support
23
+ # 2004-09-04 fl Added width support to line primitive
24
+ # 2004-09-10 fl Added font mode handling
25
+ # 2006-06-19 fl Added font bearing support (getmask2)
26
+ #
27
+ # Copyright (c) 1997-2006 by Secret Labs AB
28
+ # Copyright (c) 1996-2006 by Fredrik Lundh
29
+ #
30
+ # See the README file for information on usage and redistribution.
31
+ #
32
+ from __future__ import annotations
33
+
34
+ import math
35
+ import struct
36
+ from collections.abc import Sequence
37
+ from typing import cast
38
+
39
+ from . import Image, ImageColor, ImageFont, ImageText
40
+
41
+ TYPE_CHECKING = False
42
+ if TYPE_CHECKING:
43
+ from collections.abc import Callable
44
+ from types import ModuleType
45
+ from typing import Any, AnyStr
46
+
47
+ from . import ImageDraw2
48
+ from ._typing import Coords, _Ink
49
+
50
+ # experimental access to the outline API
51
+ Outline: Callable[[], Image.core._Outline] = Image.core.outline
52
+
53
+ """
54
+ A simple 2D drawing interface for PIL images.
55
+ <p>
56
+ Application code should use the <b>Draw</b> factory, instead of
57
+ directly.
58
+ """
59
+
60
+
61
+ class ImageDraw:
62
+ font: ImageFont.BaseImageFont | None = None
63
+
64
+ def __init__(self, im: Image.Image, mode: str | None = None) -> None:
65
+ """
66
+ Create a drawing instance.
67
+
68
+ :param im: The image to draw in.
69
+ :param mode: Optional mode to use for color values. For RGB
70
+ images, this argument can be RGB or RGBA (to blend the
71
+ drawing into the image). For all other modes, this argument
72
+ must be the same as the image mode. If omitted, the mode
73
+ defaults to the mode of the image.
74
+ """
75
+ im._ensure_mutable()
76
+ blend = 0
77
+ if mode is None:
78
+ mode = im.mode
79
+ if mode != im.mode:
80
+ if mode == "RGBA" and im.mode == "RGB":
81
+ blend = 1
82
+ else:
83
+ msg = "mode mismatch"
84
+ raise ValueError(msg)
85
+ if mode == "P":
86
+ self.palette = im.palette
87
+ else:
88
+ self.palette = None
89
+ self._image = im
90
+ self.im = im.im
91
+ self.draw = Image.core.draw(self.im, blend)
92
+ self.mode = mode
93
+ if mode in ("I", "F"):
94
+ self.ink = self.draw.draw_ink(1)
95
+ else:
96
+ self.ink = self.draw.draw_ink(-1)
97
+ if mode in ("1", "P", "I", "F"):
98
+ # FIXME: fix Fill2 to properly support matte for I+F images
99
+ self.fontmode = "1"
100
+ else:
101
+ self.fontmode = "L" # aliasing is okay for other modes
102
+ self.fill = False
103
+
104
+ def getfont(
105
+ self,
106
+ ) -> ImageFont.BaseImageFont:
107
+ """
108
+ Get the current default font.
109
+
110
+ To set the default font for this ImageDraw instance::
111
+
112
+ from PIL import ImageDraw, ImageFont
113
+ draw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
114
+
115
+ To set the default font for all future ImageDraw instances::
116
+
117
+ from PIL import ImageDraw, ImageFont
118
+ ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
119
+
120
+ If the current default font is ``None``,
121
+ it is initialized with ``ImageFont.load_default()``.
122
+
123
+ :returns: An image font."""
124
+ if not self.font:
125
+ # FIXME: should add a font repository
126
+ self.font = ImageFont.load_default()
127
+ return self.font
128
+
129
+ def _getfont(self, font_size: float | None) -> ImageFont.BaseImageFont:
130
+ if font_size is not None:
131
+ return ImageFont.load_default(font_size)
132
+ else:
133
+ return self.getfont()
134
+
135
+ def _getink(
136
+ self, ink: _Ink | None, fill: _Ink | None = None
137
+ ) -> tuple[int | None, int | None]:
138
+ result_ink = None
139
+ result_fill = None
140
+ if ink is None and fill is None:
141
+ if self.fill:
142
+ result_fill = self.ink
143
+ else:
144
+ result_ink = self.ink
145
+ else:
146
+ if ink is not None:
147
+ if isinstance(ink, str):
148
+ ink = ImageColor.getcolor(ink, self.mode)
149
+ if self.palette and isinstance(ink, tuple):
150
+ ink = self.palette.getcolor(ink, self._image)
151
+ result_ink = self.draw.draw_ink(ink)
152
+ if fill is not None:
153
+ if isinstance(fill, str):
154
+ fill = ImageColor.getcolor(fill, self.mode)
155
+ if self.palette and isinstance(fill, tuple):
156
+ fill = self.palette.getcolor(fill, self._image)
157
+ result_fill = self.draw.draw_ink(fill)
158
+ return result_ink, result_fill
159
+
160
+ def arc(
161
+ self,
162
+ xy: Coords,
163
+ start: float,
164
+ end: float,
165
+ fill: _Ink | None = None,
166
+ width: int = 1,
167
+ ) -> None:
168
+ """Draw an arc."""
169
+ ink, fill = self._getink(fill)
170
+ if ink is not None and width != 0:
171
+ self.draw.draw_arc(xy, start, end, ink, width)
172
+
173
+ def bitmap(
174
+ self, xy: Sequence[int], bitmap: Image.Image, fill: _Ink | None = None
175
+ ) -> None:
176
+ """Draw a bitmap."""
177
+ bitmap.load()
178
+ ink, fill = self._getink(fill)
179
+ if ink is None:
180
+ ink = fill
181
+ if ink is not None:
182
+ self.draw.draw_bitmap(xy, bitmap.im, ink)
183
+
184
+ def chord(
185
+ self,
186
+ xy: Coords,
187
+ start: float,
188
+ end: float,
189
+ fill: _Ink | None = None,
190
+ outline: _Ink | None = None,
191
+ width: int = 1,
192
+ ) -> None:
193
+ """Draw a chord."""
194
+ ink, fill_ink = self._getink(outline, fill)
195
+ if fill_ink is not None:
196
+ self.draw.draw_chord(xy, start, end, fill_ink, 1)
197
+ if ink is not None and ink != fill_ink and width != 0:
198
+ self.draw.draw_chord(xy, start, end, ink, 0, width)
199
+
200
+ def ellipse(
201
+ self,
202
+ xy: Coords,
203
+ fill: _Ink | None = None,
204
+ outline: _Ink | None = None,
205
+ width: int = 1,
206
+ ) -> None:
207
+ """Draw an ellipse."""
208
+ ink, fill_ink = self._getink(outline, fill)
209
+ if fill_ink is not None:
210
+ self.draw.draw_ellipse(xy, fill_ink, 1)
211
+ if ink is not None and ink != fill_ink and width != 0:
212
+ self.draw.draw_ellipse(xy, ink, 0, width)
213
+
214
+ def circle(
215
+ self,
216
+ xy: Sequence[float],
217
+ radius: float,
218
+ fill: _Ink | None = None,
219
+ outline: _Ink | None = None,
220
+ width: int = 1,
221
+ ) -> None:
222
+ """Draw a circle given center coordinates and a radius."""
223
+ ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)
224
+ self.ellipse(ellipse_xy, fill, outline, width)
225
+
226
+ def line(
227
+ self,
228
+ xy: Coords,
229
+ fill: _Ink | None = None,
230
+ width: int = 1,
231
+ joint: str | None = None,
232
+ ) -> None:
233
+ """Draw a line, or a connected sequence of line segments."""
234
+ ink = self._getink(fill)[0]
235
+ if ink is not None and width != 0:
236
+ self.draw.draw_lines(xy, ink, width)
237
+ if joint == "curve" and width > 4:
238
+ points: Sequence[Sequence[float]]
239
+ if isinstance(xy[0], (list, tuple)):
240
+ points = cast(Sequence[Sequence[float]], xy)
241
+ else:
242
+ points = [
243
+ cast(Sequence[float], tuple(xy[i : i + 2]))
244
+ for i in range(0, len(xy), 2)
245
+ ]
246
+ for i in range(1, len(points) - 1):
247
+ point = points[i]
248
+ angles = [
249
+ math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))
250
+ % 360
251
+ for start, end in (
252
+ (points[i - 1], point),
253
+ (point, points[i + 1]),
254
+ )
255
+ ]
256
+ if angles[0] == angles[1]:
257
+ # This is a straight line, so no joint is required
258
+ continue
259
+
260
+ def coord_at_angle(
261
+ coord: Sequence[float], angle: float
262
+ ) -> tuple[float, ...]:
263
+ x, y = coord
264
+ angle -= 90
265
+ distance = width / 2 - 1
266
+ return tuple(
267
+ p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))
268
+ for p, p_d in (
269
+ (x, distance * math.cos(math.radians(angle))),
270
+ (y, distance * math.sin(math.radians(angle))),
271
+ )
272
+ )
273
+
274
+ flipped = (
275
+ angles[1] > angles[0] and angles[1] - 180 > angles[0]
276
+ ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])
277
+ coords = [
278
+ (point[0] - width / 2 + 1, point[1] - width / 2 + 1),
279
+ (point[0] + width / 2 - 1, point[1] + width / 2 - 1),
280
+ ]
281
+ if flipped:
282
+ start, end = (angles[1] + 90, angles[0] + 90)
283
+ else:
284
+ start, end = (angles[0] - 90, angles[1] - 90)
285
+ self.pieslice(coords, start - 90, end - 90, fill)
286
+
287
+ if width > 8:
288
+ # Cover potential gaps between the line and the joint
289
+ if flipped:
290
+ gap_coords = [
291
+ coord_at_angle(point, angles[0] + 90),
292
+ point,
293
+ coord_at_angle(point, angles[1] + 90),
294
+ ]
295
+ else:
296
+ gap_coords = [
297
+ coord_at_angle(point, angles[0] - 90),
298
+ point,
299
+ coord_at_angle(point, angles[1] - 90),
300
+ ]
301
+ self.line(gap_coords, fill, width=3)
302
+
303
+ def shape(
304
+ self,
305
+ shape: Image.core._Outline,
306
+ fill: _Ink | None = None,
307
+ outline: _Ink | None = None,
308
+ ) -> None:
309
+ """(Experimental) Draw a shape."""
310
+ shape.close()
311
+ ink, fill_ink = self._getink(outline, fill)
312
+ if fill_ink is not None:
313
+ self.draw.draw_outline(shape, fill_ink, 1)
314
+ if ink is not None and ink != fill_ink:
315
+ self.draw.draw_outline(shape, ink, 0)
316
+
317
+ def pieslice(
318
+ self,
319
+ xy: Coords,
320
+ start: float,
321
+ end: float,
322
+ fill: _Ink | None = None,
323
+ outline: _Ink | None = None,
324
+ width: int = 1,
325
+ ) -> None:
326
+ """Draw a pieslice."""
327
+ ink, fill_ink = self._getink(outline, fill)
328
+ if fill_ink is not None:
329
+ self.draw.draw_pieslice(xy, start, end, fill_ink, 1)
330
+ if ink is not None and ink != fill_ink and width != 0:
331
+ self.draw.draw_pieslice(xy, start, end, ink, 0, width)
332
+
333
+ def point(self, xy: Coords, fill: _Ink | None = None) -> None:
334
+ """Draw one or more individual pixels."""
335
+ ink, fill = self._getink(fill)
336
+ if ink is not None:
337
+ self.draw.draw_points(xy, ink)
338
+
339
+ def polygon(
340
+ self,
341
+ xy: Coords,
342
+ fill: _Ink | None = None,
343
+ outline: _Ink | None = None,
344
+ width: int = 1,
345
+ ) -> None:
346
+ """Draw a polygon."""
347
+ ink, fill_ink = self._getink(outline, fill)
348
+ if fill_ink is not None:
349
+ self.draw.draw_polygon(xy, fill_ink, 1)
350
+ if ink is not None and ink != fill_ink and width != 0:
351
+ if width == 1:
352
+ self.draw.draw_polygon(xy, ink, 0, width)
353
+ elif self.im is not None:
354
+ # To avoid expanding the polygon outwards,
355
+ # use the fill as a mask
356
+ mask = Image.new("1", self.im.size)
357
+ mask_ink = self._getink(1)[0]
358
+ draw = Draw(mask)
359
+ draw.draw.draw_polygon(xy, mask_ink, 1)
360
+
361
+ self.draw.draw_polygon(xy, ink, 0, width * 2 - 1, mask.im)
362
+
363
+ def regular_polygon(
364
+ self,
365
+ bounding_circle: Sequence[Sequence[float] | float],
366
+ n_sides: int,
367
+ rotation: float = 0,
368
+ fill: _Ink | None = None,
369
+ outline: _Ink | None = None,
370
+ width: int = 1,
371
+ ) -> None:
372
+ """Draw a regular polygon."""
373
+ xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
374
+ self.polygon(xy, fill, outline, width)
375
+
376
+ def rectangle(
377
+ self,
378
+ xy: Coords,
379
+ fill: _Ink | None = None,
380
+ outline: _Ink | None = None,
381
+ width: int = 1,
382
+ ) -> None:
383
+ """Draw a rectangle."""
384
+ ink, fill_ink = self._getink(outline, fill)
385
+ if fill_ink is not None:
386
+ self.draw.draw_rectangle(xy, fill_ink, 1)
387
+ if ink is not None and ink != fill_ink and width != 0:
388
+ self.draw.draw_rectangle(xy, ink, 0, width)
389
+
390
+ def rounded_rectangle(
391
+ self,
392
+ xy: Coords,
393
+ radius: float = 0,
394
+ fill: _Ink | None = None,
395
+ outline: _Ink | None = None,
396
+ width: int = 1,
397
+ *,
398
+ corners: tuple[bool, bool, bool, bool] | None = None,
399
+ ) -> None:
400
+ """Draw a rounded rectangle."""
401
+ if isinstance(xy[0], (list, tuple)):
402
+ (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy)
403
+ else:
404
+ x0, y0, x1, y1 = cast(Sequence[float], xy)
405
+ if x1 < x0:
406
+ msg = "x1 must be greater than or equal to x0"
407
+ raise ValueError(msg)
408
+ if y1 < y0:
409
+ msg = "y1 must be greater than or equal to y0"
410
+ raise ValueError(msg)
411
+ if corners is None:
412
+ corners = (True, True, True, True)
413
+
414
+ d = min(x1 - x0, y1 - y0, radius * 2)
415
+
416
+ x0 = round(x0)
417
+ y0 = round(y0)
418
+ x1 = round(x1)
419
+ y1 = round(y1)
420
+ full_x, full_y = False, False
421
+ if all(corners):
422
+ full_x = d >= x1 - x0 - 1
423
+ if full_x:
424
+ # The two left and two right corners are joined
425
+ d = x1 - x0
426
+ full_y = d >= y1 - y0 - 1
427
+ if full_y:
428
+ # The two top and two bottom corners are joined
429
+ d = y1 - y0
430
+ if full_x and full_y:
431
+ # If all corners are joined, that is a circle
432
+ return self.ellipse(xy, fill, outline, width)
433
+
434
+ if d == 0 or not any(corners):
435
+ # If the corners have no curve,
436
+ # or there are no corners,
437
+ # that is a rectangle
438
+ return self.rectangle(xy, fill, outline, width)
439
+
440
+ r = int(d // 2)
441
+ ink, fill_ink = self._getink(outline, fill)
442
+
443
+ def draw_corners(pieslice: bool) -> None:
444
+ parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]
445
+ if full_x:
446
+ # Draw top and bottom halves
447
+ parts = (
448
+ ((x0, y0, x0 + d, y0 + d), 180, 360),
449
+ ((x0, y1 - d, x0 + d, y1), 0, 180),
450
+ )
451
+ elif full_y:
452
+ # Draw left and right halves
453
+ parts = (
454
+ ((x0, y0, x0 + d, y0 + d), 90, 270),
455
+ ((x1 - d, y0, x1, y0 + d), 270, 90),
456
+ )
457
+ else:
458
+ # Draw four separate corners
459
+ parts = tuple(
460
+ part
461
+ for i, part in enumerate(
462
+ (
463
+ ((x0, y0, x0 + d, y0 + d), 180, 270),
464
+ ((x1 - d, y0, x1, y0 + d), 270, 360),
465
+ ((x1 - d, y1 - d, x1, y1), 0, 90),
466
+ ((x0, y1 - d, x0 + d, y1), 90, 180),
467
+ )
468
+ )
469
+ if corners[i]
470
+ )
471
+ for part in parts:
472
+ if pieslice:
473
+ self.draw.draw_pieslice(*(part + (fill_ink, 1)))
474
+ else:
475
+ self.draw.draw_arc(*(part + (ink, width)))
476
+
477
+ if fill_ink is not None:
478
+ draw_corners(True)
479
+
480
+ if full_x:
481
+ self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill_ink, 1)
482
+ elif x1 - r - 1 >= x0 + r + 1:
483
+ self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill_ink, 1)
484
+ if not full_x and not full_y:
485
+ left = [x0, y0, x0 + r, y1]
486
+ if corners[0]:
487
+ left[1] += r + 1
488
+ if corners[3]:
489
+ left[3] -= r + 1
490
+ self.draw.draw_rectangle(left, fill_ink, 1)
491
+
492
+ right = [x1 - r, y0, x1, y1]
493
+ if corners[1]:
494
+ right[1] += r + 1
495
+ if corners[2]:
496
+ right[3] -= r + 1
497
+ self.draw.draw_rectangle(right, fill_ink, 1)
498
+ if ink is not None and ink != fill_ink and width != 0:
499
+ draw_corners(False)
500
+
501
+ if not full_x:
502
+ top = [x0, y0, x1, y0 + width - 1]
503
+ if corners[0]:
504
+ top[0] += r + 1
505
+ if corners[1]:
506
+ top[2] -= r + 1
507
+ self.draw.draw_rectangle(top, ink, 1)
508
+
509
+ bottom = [x0, y1 - width + 1, x1, y1]
510
+ if corners[3]:
511
+ bottom[0] += r + 1
512
+ if corners[2]:
513
+ bottom[2] -= r + 1
514
+ self.draw.draw_rectangle(bottom, ink, 1)
515
+ if not full_y:
516
+ left = [x0, y0, x0 + width - 1, y1]
517
+ if corners[0]:
518
+ left[1] += r + 1
519
+ if corners[3]:
520
+ left[3] -= r + 1
521
+ self.draw.draw_rectangle(left, ink, 1)
522
+
523
+ right = [x1 - width + 1, y0, x1, y1]
524
+ if corners[1]:
525
+ right[1] += r + 1
526
+ if corners[2]:
527
+ right[3] -= r + 1
528
+ self.draw.draw_rectangle(right, ink, 1)
529
+
530
+ def text(
531
+ self,
532
+ xy: tuple[float, float],
533
+ text: AnyStr | ImageText.Text[AnyStr],
534
+ fill: _Ink | None = None,
535
+ font: ImageFont.BaseImageFont | None = None,
536
+ anchor: str | None = None,
537
+ spacing: float = 4,
538
+ align: str = "left",
539
+ direction: str | None = None,
540
+ features: list[str] | None = None,
541
+ language: str | None = None,
542
+ stroke_width: float = 0,
543
+ stroke_fill: _Ink | None = None,
544
+ embedded_color: bool = False,
545
+ *args: Any,
546
+ **kwargs: Any,
547
+ ) -> None:
548
+ """Draw text."""
549
+ if isinstance(text, ImageText.Text):
550
+ image_text = text
551
+ else:
552
+ if font is None:
553
+ font = self._getfont(kwargs.get("font_size"))
554
+ image_text = ImageText.Text(
555
+ text, font, self.mode, spacing, direction, features, language
556
+ )
557
+ if embedded_color:
558
+ image_text.embed_color()
559
+ if stroke_width:
560
+ image_text.stroke(stroke_width, stroke_fill)
561
+
562
+ def getink(fill: _Ink | None) -> int:
563
+ ink, fill_ink = self._getink(fill)
564
+ if ink is None:
565
+ assert fill_ink is not None
566
+ return fill_ink
567
+ return ink
568
+
569
+ ink = getink(fill)
570
+ if ink is None:
571
+ return
572
+
573
+ stroke_ink = None
574
+ if image_text.stroke_width:
575
+ stroke_ink = (
576
+ getink(image_text.stroke_fill)
577
+ if image_text.stroke_fill is not None
578
+ else ink
579
+ )
580
+
581
+ for line in image_text._split(xy, anchor, align):
582
+
583
+ def draw_text(ink: int, stroke_width: float = 0) -> None:
584
+ mode = self.fontmode
585
+ if stroke_width == 0 and embedded_color:
586
+ mode = "RGBA"
587
+ x = int(line.x)
588
+ y = int(line.y)
589
+ start = (math.modf(line.x)[0], math.modf(line.y)[0])
590
+ if isinstance(image_text.font, ImageFont.FreeTypeFont):
591
+ mask, offset = image_text.font.getmask2(
592
+ line.text,
593
+ mode,
594
+ direction,
595
+ features,
596
+ language,
597
+ stroke_width,
598
+ line.anchor,
599
+ ink,
600
+ start,
601
+ stroke_filled=True,
602
+ *args,
603
+ **kwargs,
604
+ )
605
+ x += offset[0]
606
+ y += offset[1]
607
+ else:
608
+ try:
609
+ mask = image_text.font.getmask(
610
+ line.text,
611
+ mode,
612
+ direction,
613
+ features,
614
+ language,
615
+ stroke_width,
616
+ line.anchor,
617
+ ink,
618
+ start=start,
619
+ *args,
620
+ **kwargs,
621
+ )
622
+ except TypeError:
623
+ mask = image_text.font.getmask(line.text)
624
+ if mode == "RGBA":
625
+ # image_text.font.getmask2(mode="RGBA")
626
+ # returns color in RGB bands and mask in A
627
+ # extract mask and set text alpha
628
+ color, mask = mask, mask.getband(3)
629
+ ink_alpha = struct.pack("i", ink)[3]
630
+ color.fillband(3, ink_alpha)
631
+ if self.im is not None:
632
+ self.im.paste(
633
+ color, (x, y, x + mask.size[0], y + mask.size[1]), mask
634
+ )
635
+ else:
636
+ self.draw.draw_bitmap((x, y), mask, ink)
637
+
638
+ if stroke_ink is not None:
639
+ # Draw stroked text
640
+ draw_text(stroke_ink, image_text.stroke_width)
641
+
642
+ # Draw normal text
643
+ if ink != stroke_ink:
644
+ draw_text(ink)
645
+ else:
646
+ # Only draw normal text
647
+ draw_text(ink)
648
+
649
+ def multiline_text(
650
+ self,
651
+ xy: tuple[float, float],
652
+ text: AnyStr,
653
+ fill: _Ink | None = None,
654
+ font: ImageFont.BaseImageFont | None = None,
655
+ anchor: str | None = None,
656
+ spacing: float = 4,
657
+ align: str = "left",
658
+ direction: str | None = None,
659
+ features: list[str] | None = None,
660
+ language: str | None = None,
661
+ stroke_width: float = 0,
662
+ stroke_fill: _Ink | None = None,
663
+ embedded_color: bool = False,
664
+ *,
665
+ font_size: float | None = None,
666
+ ) -> None:
667
+ return self.text(
668
+ xy,
669
+ text,
670
+ fill,
671
+ font,
672
+ anchor,
673
+ spacing,
674
+ align,
675
+ direction,
676
+ features,
677
+ language,
678
+ stroke_width,
679
+ stroke_fill,
680
+ embedded_color,
681
+ font_size=font_size,
682
+ )
683
+
684
+ def textlength(
685
+ self,
686
+ text: AnyStr,
687
+ font: ImageFont.BaseImageFont | None = None,
688
+ direction: str | None = None,
689
+ features: list[str] | None = None,
690
+ language: str | None = None,
691
+ embedded_color: bool = False,
692
+ *,
693
+ font_size: float | None = None,
694
+ ) -> float:
695
+ """Get the length of a given string, in pixels with 1/64 precision."""
696
+ if font is None:
697
+ font = self._getfont(font_size)
698
+ image_text = ImageText.Text(
699
+ text,
700
+ font,
701
+ self.mode,
702
+ direction=direction,
703
+ features=features,
704
+ language=language,
705
+ )
706
+ if embedded_color:
707
+ image_text.embed_color()
708
+ return image_text.get_length()
709
+
710
+ def textbbox(
711
+ self,
712
+ xy: tuple[float, float],
713
+ text: AnyStr,
714
+ font: ImageFont.BaseImageFont | None = None,
715
+ anchor: str | None = None,
716
+ spacing: float = 4,
717
+ align: str = "left",
718
+ direction: str | None = None,
719
+ features: list[str] | None = None,
720
+ language: str | None = None,
721
+ stroke_width: float = 0,
722
+ embedded_color: bool = False,
723
+ *,
724
+ font_size: float | None = None,
725
+ ) -> tuple[float, float, float, float]:
726
+ """Get the bounding box of a given string, in pixels."""
727
+ if font is None:
728
+ font = self._getfont(font_size)
729
+ image_text = ImageText.Text(
730
+ text, font, self.mode, spacing, direction, features, language
731
+ )
732
+ if embedded_color:
733
+ image_text.embed_color()
734
+ if stroke_width:
735
+ image_text.stroke(stroke_width)
736
+ return image_text.get_bbox(xy, anchor, align)
737
+
738
+ def multiline_textbbox(
739
+ self,
740
+ xy: tuple[float, float],
741
+ text: AnyStr,
742
+ font: ImageFont.BaseImageFont | None = None,
743
+ anchor: str | None = None,
744
+ spacing: float = 4,
745
+ align: str = "left",
746
+ direction: str | None = None,
747
+ features: list[str] | None = None,
748
+ language: str | None = None,
749
+ stroke_width: float = 0,
750
+ embedded_color: bool = False,
751
+ *,
752
+ font_size: float | None = None,
753
+ ) -> tuple[float, float, float, float]:
754
+ return self.textbbox(
755
+ xy,
756
+ text,
757
+ font,
758
+ anchor,
759
+ spacing,
760
+ align,
761
+ direction,
762
+ features,
763
+ language,
764
+ stroke_width,
765
+ embedded_color,
766
+ font_size=font_size,
767
+ )
768
+
769
+
770
+ def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
771
+ """
772
+ A simple 2D drawing interface for PIL images.
773
+
774
+ :param im: The image to draw in.
775
+ :param mode: Optional mode to use for color values. For RGB
776
+ images, this argument can be RGB or RGBA (to blend the
777
+ drawing into the image). For all other modes, this argument
778
+ must be the same as the image mode. If omitted, the mode
779
+ defaults to the mode of the image.
780
+ """
781
+ try:
782
+ return getattr(im, "getdraw")(mode)
783
+ except AttributeError:
784
+ return ImageDraw(im, mode)
785
+
786
+
787
+ def getdraw(im: Image.Image | None = None) -> tuple[ImageDraw2.Draw | None, ModuleType]:
788
+ """
789
+ :param im: The image to draw in.
790
+ :returns: A (drawing context, drawing resource factory) tuple.
791
+ """
792
+ from . import ImageDraw2
793
+
794
+ draw = ImageDraw2.Draw(im) if im is not None else None
795
+ return draw, ImageDraw2
796
+
797
+
798
+ def floodfill(
799
+ image: Image.Image,
800
+ xy: tuple[int, int],
801
+ value: float | tuple[int, ...],
802
+ border: float | tuple[int, ...] | None = None,
803
+ thresh: float = 0,
804
+ ) -> None:
805
+ """
806
+ .. warning:: This method is experimental.
807
+
808
+ Fills a bounded region with a given color.
809
+
810
+ :param image: Target image.
811
+ :param xy: Seed position (a 2-item coordinate tuple). See
812
+ :ref:`coordinate-system`.
813
+ :param value: Fill color.
814
+ :param border: Optional border value. If given, the region consists of
815
+ pixels with a color different from the border color. If not given,
816
+ the region consists of pixels having the same color as the seed
817
+ pixel.
818
+ :param thresh: Optional threshold value which specifies a maximum
819
+ tolerable difference of a pixel value from the 'background' in
820
+ order for it to be replaced. Useful for filling regions of
821
+ non-homogeneous, but similar, colors.
822
+ """
823
+ # based on an implementation by Eric S. Raymond
824
+ # amended by yo1995 @20180806
825
+ pixel = image.load()
826
+ assert pixel is not None
827
+ x, y = xy
828
+ try:
829
+ background = pixel[x, y]
830
+ if _color_diff(value, background) <= thresh:
831
+ return # seed point already has fill color
832
+ pixel[x, y] = value
833
+ except (ValueError, IndexError):
834
+ return # seed point outside image
835
+ edge = {(x, y)}
836
+ # use a set to keep record of current and previous edge pixels
837
+ # to reduce memory consumption
838
+ full_edge = set()
839
+ while edge:
840
+ new_edge = set()
841
+ for x, y in edge: # 4 adjacent method
842
+ for s, t in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
843
+ # If already processed, or if a coordinate is negative, skip
844
+ if (s, t) in full_edge or s < 0 or t < 0:
845
+ continue
846
+ try:
847
+ p = pixel[s, t]
848
+ except (ValueError, IndexError):
849
+ pass
850
+ else:
851
+ full_edge.add((s, t))
852
+ if border is None:
853
+ fill = _color_diff(p, background) <= thresh
854
+ else:
855
+ fill = p not in (value, border)
856
+ if fill:
857
+ pixel[s, t] = value
858
+ new_edge.add((s, t))
859
+ full_edge = edge # discard pixels processed
860
+ edge = new_edge
861
+
862
+
863
+ def _compute_regular_polygon_vertices(
864
+ bounding_circle: Sequence[Sequence[float] | float], n_sides: int, rotation: float
865
+ ) -> list[tuple[float, float]]:
866
+ """
867
+ Generate a list of vertices for a 2D regular polygon.
868
+
869
+ :param bounding_circle: The bounding circle is a sequence defined
870
+ by a point and radius. The polygon is inscribed in this circle.
871
+ (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)
872
+ :param n_sides: Number of sides
873
+ (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon)
874
+ :param rotation: Apply an arbitrary rotation to the polygon
875
+ (e.g. ``rotation=90``, applies a 90 degree rotation)
876
+ :return: List of regular polygon vertices
877
+ (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``)
878
+
879
+ How are the vertices computed?
880
+ 1. Compute the following variables
881
+ - theta: Angle between the apothem & the nearest polygon vertex
882
+ - side_length: Length of each polygon edge
883
+ - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle)
884
+ - polygon_radius: Polygon radius (last element of bounding_circle)
885
+ - angles: Location of each polygon vertex in polar grid
886
+ (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0])
887
+
888
+ 2. For each angle in angles, get the polygon vertex at that angle
889
+ The vertex is computed using the equation below.
890
+ X= xcos(φ) + ysin(φ)
891
+ Y= −xsin(φ) + ycos(φ)
892
+
893
+ Note:
894
+ φ = angle in degrees
895
+ x = 0
896
+ y = polygon_radius
897
+
898
+ The formula above assumes rotation around the origin.
899
+ In our case, we are rotating around the centroid.
900
+ To account for this, we use the formula below
901
+ X = xcos(φ) + ysin(φ) + centroid_x
902
+ Y = −xsin(φ) + ycos(φ) + centroid_y
903
+ """
904
+ # 1. Error Handling
905
+ # 1.1 Check `n_sides` has an appropriate value
906
+ if not isinstance(n_sides, int):
907
+ msg = "n_sides should be an int" # type: ignore[unreachable]
908
+ raise TypeError(msg)
909
+ if n_sides < 3:
910
+ msg = "n_sides should be an int > 2"
911
+ raise ValueError(msg)
912
+
913
+ # 1.2 Check `bounding_circle` has an appropriate value
914
+ if not isinstance(bounding_circle, (list, tuple)):
915
+ msg = "bounding_circle should be a sequence"
916
+ raise TypeError(msg)
917
+
918
+ if len(bounding_circle) == 3:
919
+ if not all(isinstance(i, (int, float)) for i in bounding_circle):
920
+ msg = "bounding_circle should only contain numeric data"
921
+ raise ValueError(msg)
922
+
923
+ *centroid, polygon_radius = cast(list[float], list(bounding_circle))
924
+ elif len(bounding_circle) == 2 and isinstance(bounding_circle[0], (list, tuple)):
925
+ if not all(
926
+ isinstance(i, (int, float)) for i in bounding_circle[0]
927
+ ) or not isinstance(bounding_circle[1], (int, float)):
928
+ msg = "bounding_circle should only contain numeric data"
929
+ raise ValueError(msg)
930
+
931
+ if len(bounding_circle[0]) != 2:
932
+ msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
933
+ raise ValueError(msg)
934
+
935
+ centroid = cast(list[float], list(bounding_circle[0]))
936
+ polygon_radius = cast(float, bounding_circle[1])
937
+ else:
938
+ msg = (
939
+ "bounding_circle should contain 2D coordinates "
940
+ "and a radius (e.g. (x, y, r) or ((x, y), r) )"
941
+ )
942
+ raise ValueError(msg)
943
+
944
+ if polygon_radius <= 0:
945
+ msg = "bounding_circle radius should be > 0"
946
+ raise ValueError(msg)
947
+
948
+ # 1.3 Check `rotation` has an appropriate value
949
+ if not isinstance(rotation, (int, float)):
950
+ msg = "rotation should be an int or float" # type: ignore[unreachable]
951
+ raise ValueError(msg)
952
+
953
+ # 2. Define Helper Functions
954
+ def _apply_rotation(point: list[float], degrees: float) -> tuple[float, float]:
955
+ return (
956
+ round(
957
+ point[0] * math.cos(math.radians(360 - degrees))
958
+ - point[1] * math.sin(math.radians(360 - degrees))
959
+ + centroid[0],
960
+ 2,
961
+ ),
962
+ round(
963
+ point[1] * math.cos(math.radians(360 - degrees))
964
+ + point[0] * math.sin(math.radians(360 - degrees))
965
+ + centroid[1],
966
+ 2,
967
+ ),
968
+ )
969
+
970
+ def _compute_polygon_vertex(angle: float) -> tuple[float, float]:
971
+ start_point = [polygon_radius, 0]
972
+ return _apply_rotation(start_point, angle)
973
+
974
+ def _get_angles(n_sides: int, rotation: float) -> list[float]:
975
+ angles = []
976
+ degrees = 360 / n_sides
977
+ # Start with the bottom left polygon vertex
978
+ current_angle = (270 - 0.5 * degrees) + rotation
979
+ for _ in range(n_sides):
980
+ angles.append(current_angle)
981
+ current_angle += degrees
982
+ if current_angle > 360:
983
+ current_angle -= 360
984
+ return angles
985
+
986
+ # 3. Variable Declarations
987
+ angles = _get_angles(n_sides, rotation)
988
+
989
+ # 4. Compute Vertices
990
+ return [_compute_polygon_vertex(angle) for angle in angles]
991
+
992
+
993
+ def _color_diff(
994
+ color1: float | tuple[int, ...], color2: float | tuple[int, ...]
995
+ ) -> float:
996
+ """
997
+ Uses 1-norm distance to calculate difference between two values.
998
+ """
999
+ first = color1 if isinstance(color1, tuple) else (color1,)
1000
+ second = color2 if isinstance(color2, tuple) else (color2,)
1001
+
1002
+ return sum(abs(first[i] - second[i]) for i in range(len(second)))