operations.py 6.3 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. try:
  45. import PIL.ImageOps as ImageOps
  46. except ImportError:
  47. ImageOps = None
  48. except ImportError:
  49. PIL = None
  50. def crop_image(input_file, output_file=None, format=None):
  51. """Crop to non-zero area of the image
  52. :param input_file: Name of the file to manipulate
  53. :param output_file: filename for the new file (same as input by default)
  54. :param format: format to be used new file (if different from extension)
  55. """
  56. if PIL is None:
  57. raise RuntimeError(_("Install PIL or Pillow to use this function"))
  58. if not output_file:
  59. output_file = input_file
  60. img = Image.open(input_file)
  61. box = img.getbbox()
  62. cropped_image = img.crop(box)
  63. cropped_image.save(output_file, format)
  64. def thumbnail_image(input_file, output_file=None, size=(200, 200), format=None):
  65. """Create a thumbnail of an image
  66. The image aspect ratio is kept and its height and width are adjusted
  67. accordingly to fit the ``size`` parameter.
  68. :param input_file: Name of the file to manipulate
  69. :param size: Size of the new image in pixels as tuple
  70. :param output_file: filename for the new file (same as input by default)
  71. :param format: format to be used new file (if different from extension)
  72. """
  73. if PIL is None:
  74. raise RuntimeError(_("Install PIL or Pillow to use this function"))
  75. if not output_file:
  76. output_file = input_file
  77. img = Image.open(input_file)
  78. img.thumbnail(size, Image.ANTIALIAS)
  79. img.save(output_file, format)
  80. def change_rbg_to_transparent(
  81. input_file, output_file=None, color="white", alpha=0, format=None
  82. ):
  83. """Make a specified RGB color in the image transparent
  84. The color is specified as a RGB tuple (triplet) or string 'white'
  85. or 'black'. Note that GRASS color names are not supported.
  86. The white (255, 255, 255) is replaced by default but each application
  87. is encouraged to consider color to use and explicitly specify it.
  88. :param input_file: Name of the file to manipulate
  89. :param color: Color to be replaced by transparency (tuple of three ints)
  90. :param alpha: Level of opacity (0 fully transparent, 255 fully opaque)
  91. :param output_file: filename for the new file (same as input by default)
  92. :param format: format to be used new file (if different from extension)
  93. """
  94. if PIL is None:
  95. raise RuntimeError(_("Install PIL or Pillow to use this function"))
  96. if color == "white":
  97. rgb = (255, 255, 255)
  98. elif color == "black":
  99. rgb = (0, 0, 0)
  100. else:
  101. rgb = color # pylint: disable=redefined-variable-type
  102. if not output_file:
  103. output_file = input_file
  104. img = Image.open(input_file)
  105. img = img.convert("RGBA")
  106. old_data = img.getdata()
  107. new_data = []
  108. for item in old_data:
  109. if item[0] == rgb[0] and item[1] == rgb[1] and item[2] == rgb[2]:
  110. new_data.append((rgb[0], rgb[1], rgb[2], alpha))
  111. else:
  112. new_data.append(item)
  113. img.putdata(new_data)
  114. img.save(output_file, format)
  115. def invert_image_colors(input_file, output_file=None, format=None):
  116. """Invert colors in the image
  117. The alpha channel, if present, is untouched by this function.
  118. :param input_file: Name of the file to manipulate
  119. :param output_file: filename for the new file (same as input by default)
  120. :param format: format to be used new file (if different from extension)
  121. """
  122. if PIL is None:
  123. raise RuntimeError(_("Install PIL or Pillow to use this function"))
  124. if ImageOps is None:
  125. raise RuntimeError(
  126. _(
  127. "Install a newer version of PIL or Pillow to"
  128. " use this function (missing ImageOps module)"
  129. )
  130. )
  131. if not output_file:
  132. output_file = input_file
  133. original_img = Image.open(input_file)
  134. # according to documentation (3.0.x) the module can work only on RGB
  135. # so we need to specifically take care of transparency if present
  136. if original_img.mode == "RGBA":
  137. # split into bands
  138. red1, green1, blue1, alpha = original_img.split()
  139. rgb_img = Image.merge("RGB", (red1, green1, blue1))
  140. # invert RGB
  141. inverted_rgb_img = ImageOps.invert(rgb_img)
  142. # put back the original alpha
  143. red2, green2, blue2 = inverted_rgb_img.split()
  144. new_image = Image.merge("RGBA", (red2, green2, blue2, alpha))
  145. else:
  146. new_image = ImageOps.invert(original_img)
  147. new_image.save(output_file, format)