operations.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. """
  2. Image non-geospatial operations and manipulations
  3. Note: Functions in this module are experimental and are not considered
  4. a stable API, i.e. may change in future releases of GRASS GIS.
  5. It heavily relies on PIL but unlike PIL, the functions operate on
  6. files instead of PIL Image objects (which are used internally).
  7. These functions are convenient for post-processing outputs from GRASS
  8. modules, e.g. after rendering. However, if you have multiple operations
  9. you may want to consider using PIL directly for efficiency (to avoid
  10. writing and reading from the files).
  11. Usage
  12. =====
  13. Use keyword arguments for all parameters other than those for input,
  14. output, and format. All function provide reasonable defaults if possible,
  15. but note that they may not be applicable to you case or when developing
  16. a general tool.
  17. >>> import grass.imaging.operations as iop
  18. >>> # replace white color in the image by 100% transparency
  19. >>> iop.change_rbg_to_transparent("map.png", color=(255, 255, 255))
  20. >>> # crop the image in place
  21. >>> iop.crop_image("map.png")
  22. >>> # create a new image with inverted colors of the original image
  23. >>> iop.invert_image_colors("map.png", "map_inverted.png")
  24. >>> # create a thumbnail of the original image
  25. >>> iop.thumbnail_image("map.png", "map_thumbnail.png", size=(64, 64))
  26. Error handling
  27. ==============
  28. When PIL or a required submodule is not available, a RuntimeError
  29. exception is raised with a message mentioning the missing dependency.
  30. Additionally, any of the exceptions raised by PIL may be raised too,
  31. for example, when the file is not found.
  32. Authors, copyright and license
  33. ==============================
  34. (C) 2018 by Vaclav Petras and the GRASS Development Team
  35. This program is free software under the GNU General Public
  36. License (>=v2). Read the file COPYING that comes with GRASS
  37. for details.
  38. .. sectionauthor:: Vaclav Petras <wenzeslaus gmail com>
  39. """
  40. # import similar to what is in visvis
  41. try:
  42. import PIL
  43. from PIL import Image
  44. PILLOW = True
  45. try:
  46. from PIL import PILLOW_VERSION # pylint: disable=unused-import
  47. except ImportError:
  48. PILLOW = False
  49. try:
  50. import PIL.ImageOps as ImageOps
  51. except ImportError:
  52. ImageOps = None
  53. except ImportError:
  54. PIL = None
  55. def crop_image(input_file, output_file=None, format=None):
  56. """Crop to non-zero area of the image
  57. :param input_file: Name of the file to manipulate
  58. :param output_file: filename for the new file (same as input by default)
  59. :param format: format to be used new file (if different from extension)
  60. """
  61. if PIL is None:
  62. raise RuntimeError(_("Install PIL or Pillow to use this function"))
  63. if not output_file:
  64. output_file = input_file
  65. img = Image.open(input_file)
  66. box = img.getbbox()
  67. cropped_image = img.crop(box)
  68. cropped_image.save(output_file, format)
  69. def thumbnail_image(input_file, output_file=None, size=(200, 200),
  70. format=None):
  71. """Create a thumbnail of an image
  72. The image aspect ratio is kept and its height and width are adjusted
  73. accordingly to fit the ``size`` parameter.
  74. :param input_file: Name of the file to manipulate
  75. :param size: Size of the new image in pixels as tuple
  76. :param output_file: filename for the new file (same as input by default)
  77. :param format: format to be used new file (if different from extension)
  78. """
  79. if PIL is None:
  80. raise RuntimeError(_("Install PIL or Pillow to use this function"))
  81. if not output_file:
  82. output_file = input_file
  83. img = Image.open(input_file)
  84. img.thumbnail(size, Image.ANTIALIAS)
  85. img.save(output_file, format)
  86. def change_rbg_to_transparent(input_file, output_file=None, color='white',
  87. alpha=0, format=None):
  88. """Make a specified RGB color in the image transparent
  89. The color is specified as a RGB tuple (triplet) or string 'white'
  90. or 'black'. Note that GRASS color names are not supported.
  91. The white (255, 255, 255) is replaced by default but each application
  92. is encouraged to consider color to use and explicitly specify it.
  93. :param input_file: Name of the file to manipulate
  94. :param color: Color to be replaced by transparency (tuple of three ints)
  95. :param alpha: Level of opacity (0 fully transparent, 255 fully opaque)
  96. :param output_file: filename for the new file (same as input by default)
  97. :param format: format to be used new file (if different from extension)
  98. """
  99. if PIL is None:
  100. raise RuntimeError(_("Install PIL or Pillow to use this function"))
  101. if color == 'white':
  102. rgb = (255, 255, 255)
  103. elif color == 'black':
  104. rgb = (0, 0, 0)
  105. else:
  106. rgb = color # pylint: disable=redefined-variable-type
  107. if not output_file:
  108. output_file = input_file
  109. img = Image.open(input_file)
  110. img = img.convert("RGBA")
  111. old_data = img.getdata()
  112. new_data = []
  113. for item in old_data:
  114. if item[0] == rgb[0] and item[1] == rgb[1] and item[2] == rgb[2]:
  115. new_data.append((rgb[0], rgb[1], rgb[2], alpha))
  116. else:
  117. new_data.append(item)
  118. img.putdata(new_data)
  119. img.save(output_file, format)
  120. def invert_image_colors(input_file, output_file=None, format=None):
  121. """Invert colors in the image
  122. The alpha channel, if present, is untouched by this function.
  123. :param input_file: Name of the file to manipulate
  124. :param output_file: filename for the new file (same as input by default)
  125. :param format: format to be used new file (if different from extension)
  126. """
  127. if PIL is None:
  128. raise RuntimeError(_("Install PIL or Pillow to use this function"))
  129. if ImageOps is None:
  130. raise RuntimeError(_("Install a newer version of PIL or Pillow to"
  131. " use this function (missing ImageOps module)"))
  132. if not output_file:
  133. output_file = input_file
  134. original_img = Image.open(input_file)
  135. # according to documentation (3.0.x) the module can work only on RGB
  136. # so we need to specifically take care of transparency if present
  137. if original_img.mode == 'RGBA':
  138. # split into bands
  139. red1, green1, blue1, alpha = original_img.split()
  140. rgb_img = Image.merge('RGB', (red1, green1, blue1))
  141. # invert RGB
  142. inverted_rgb_img = ImageOps.invert(rgb_img)
  143. # put back the original alpha
  144. red2, green2, blue2 = inverted_rgb_img.split()
  145. new_image = Image.merge('RGBA', (red2, green2, blue2, alpha))
  146. else:
  147. new_image = ImageOps.invert(original_img)
  148. new_image.save(output_file, format)