"""
Image non-geospatial operations and manipulations
Note: Functions in this module are experimental and are not considered
a stable API, i.e. may change in future releases of GRASS GIS.
It heavily relies on PIL but unlike PIL, the functions operate on
files instead of PIL Image objects (which are used internally).
These functions are convenient for post-processing outputs from GRASS
modules, e.g. after rendering. However, if you have multiple operations
you may want to consider using PIL directly for efficiency (to avoid
writing and reading from the files).
Usage
=====
Use keyword arguments for all parameters other than those for input,
output, and format. All function provide reasonable defaults if possible,
but note that they may not be applicable to you case or when developing
a general tool.
>>> import grass.imaging.operations as iop
>>> # replace white color in the image by 100% transparency
>>> iop.change_rbg_to_transparent("map.png", color=(255, 255, 255))
>>> # crop the image in place
>>> iop.crop_image("map.png")
>>> # create a new image with inverted colors of the original image
>>> iop.invert_image_colors("map.png", "map_inverted.png")
>>> # create a thumbnail of the original image
>>> iop.thumbnail_image("map.png", "map_thumbnail.png", size=(64, 64))
Error handling
==============
When PIL or a required submodule is not available, a RuntimeError
exception is raised with a message mentioning the missing dependency.
Additionally, any of the exceptions raised by PIL may be raised too,
for example, when the file is not found.
Authors, copyright and license
==============================
(C) 2018 by Vaclav Petras and the GRASS Development Team
This program is free software under the GNU General Public
License (>=v2). Read the file COPYING that comes with GRASS
for details.
.. sectionauthor:: Vaclav Petras <wenzeslaus gmail com>
"""
# import similar to what is in visvis
try:
import PIL
from PIL import Image
try:
import PIL.ImageOps as ImageOps
except ImportError:
ImageOps = None
except ImportError:
PIL = None
[docs]def crop_image(input_file, output_file=None, format=None):
"""Crop to non-zero area of the image
:param input_file: Name of the file to manipulate
:param output_file: filename for the new file (same as input by default)
:param format: format to be used new file (if different from extension)
"""
if PIL is None:
raise RuntimeError(_("Install PIL or Pillow to use this function"))
if not output_file:
output_file = input_file
img = Image.open(input_file)
box = img.getbbox()
cropped_image = img.crop(box)
cropped_image.save(output_file, format)
[docs]def thumbnail_image(input_file, output_file=None, size=(200, 200), format=None):
"""Create a thumbnail of an image
The image aspect ratio is kept and its height and width are adjusted
accordingly to fit the ``size`` parameter.
:param input_file: Name of the file to manipulate
:param size: Size of the new image in pixels as tuple
:param output_file: filename for the new file (same as input by default)
:param format: format to be used new file (if different from extension)
"""
if PIL is None:
raise RuntimeError(_("Install PIL or Pillow to use this function"))
if not output_file:
output_file = input_file
img = Image.open(input_file)
img.thumbnail(size, Image.ANTIALIAS)
img.save(output_file, format)
[docs]def change_rbg_to_transparent(
input_file, output_file=None, color="white", alpha=0, format=None
):
"""Make a specified RGB color in the image transparent
The color is specified as a RGB tuple (triplet) or string 'white'
or 'black'. Note that GRASS color names are not supported.
The white (255, 255, 255) is replaced by default but each application
is encouraged to consider color to use and explicitly specify it.
:param input_file: Name of the file to manipulate
:param color: Color to be replaced by transparency (tuple of three ints)
:param alpha: Level of opacity (0 fully transparent, 255 fully opaque)
:param output_file: filename for the new file (same as input by default)
:param format: format to be used new file (if different from extension)
"""
if PIL is None:
raise RuntimeError(_("Install PIL or Pillow to use this function"))
if color == "white":
rgb = (255, 255, 255)
elif color == "black":
rgb = (0, 0, 0)
else:
rgb = color # pylint: disable=redefined-variable-type
if not output_file:
output_file = input_file
img = Image.open(input_file)
img = img.convert("RGBA")
old_data = img.getdata()
new_data = []
for item in old_data:
if item[0] == rgb[0] and item[1] == rgb[1] and item[2] == rgb[2]:
new_data.append((rgb[0], rgb[1], rgb[2], alpha))
else:
new_data.append(item)
img.putdata(new_data)
img.save(output_file, format)
[docs]def invert_image_colors(input_file, output_file=None, format=None):
"""Invert colors in the image
The alpha channel, if present, is untouched by this function.
:param input_file: Name of the file to manipulate
:param output_file: filename for the new file (same as input by default)
:param format: format to be used new file (if different from extension)
"""
if PIL is None:
raise RuntimeError(_("Install PIL or Pillow to use this function"))
if ImageOps is None:
raise RuntimeError(
_(
"Install a newer version of PIL or Pillow to"
" use this function (missing ImageOps module)"
)
)
if not output_file:
output_file = input_file
original_img = Image.open(input_file)
# according to documentation (3.0.x) the module can work only on RGB
# so we need to specifically take care of transparency if present
if original_img.mode == "RGBA":
# split into bands
red1, green1, blue1, alpha = original_img.split()
rgb_img = Image.merge("RGB", (red1, green1, blue1))
# invert RGB
inverted_rgb_img = ImageOps.invert(rgb_img)
# put back the original alpha
red2, green2, blue2 = inverted_rgb_img.split()
new_image = Image.merge("RGBA", (red2, green2, blue2, alpha))
else:
new_image = ImageOps.invert(original_img)
new_image.save(output_file, format)