Source code for lotus.utils.imaging

import io

from PIL import Image as PILimage
from PIL import ImageDraw

from django.core.files import File


[docs] class SampleImageCrafter: """ Craft a basic sample image, either a bitmap or a SVG. Basically every supported format from PIL should work however this code only knows about JPEG, GIF, PNG and SVG. Keyword Arguments: font (Pil.ImageFont): A font object to use. This is only used for bitmap image. It is strongly recommended to use a TrueType font. Default is None, this will use the default PIL font. Note than text without a TrueType font will be badly positionned (almost centered but largely shifted). """ def __init__(self, *args, **kwargs): self.font = None if "font" in kwargs: self.font = kwargs.pop("font")
[docs] def get_text_content(self, text, width, height): """ Get the text content to include in image. Arguments: text (string or boolean): Either a string to use it as content or ``True`` to make automatic content from given sizes such as ``320x240`` (for width value ``320`` and height value ``240``). If string is given it should be a short text else it is not guaranteed to fit. If you don't want text content, just pass a empty string. width (integer): Width value to display in automatic text content. height (integer): Height value to display in automatic text content. Returns: string: Content to include in image. """ text_content = "" # Format default text if text is True: # width x height text_content = "{}x{}".format(width, height) # Or just use possible given custom string elif isinstance(text, str): text_content = text return text_content
[docs] def get_file_extension(self, format_name): """ Get correct file extension depending format name. Arguments: format_name (string): The format name to use to get the right file extension. It could be any format supported. ``JPEG`` and ``JPG`` will traduces to ``jpg`` file extension. Returns: string: File extension without leading dot. """ file_extension = format_name.lower() # "jpeg" is an allowed alias for "jpg" if file_extension == "jpeg": file_extension = "jpg" return file_extension
[docs] def get_mode(self, format_name): """ Get correct image color mode depending format name. Arguments: format_name (string): The format name to use to get the right file extension. It could be any format supported. ``PNG`` will be a ``RGBA`` mode, every other format will be ``RGB``. Returns: string: Image color mode name. """ if format_name == "PNG": return "RGBA" return "RGB"
[docs] def get_filename(self, file_extension, filename=None, bg_color=None): """ Get filename. Arguments: file_extension (string): File extension to use in automatic filename. Can be anything if ``filename`` argument is given since it will be ignored. Keyword Arguments: filename (string): Custom filename to use, every other arguments won't be used to compute filename. bg_color (string): A color name to use in automatic filename. For real, this can be anything since it is not validated. Returns: string: File name. """ if not filename: return "{}.{}".format(bg_color, file_extension) return filename
[docs] def build(self, filename=None, size=(100, 100), bg_color="blue", text_color="white", text=None, format_name="PNG"): """ Build config for content creation Keyword Arguments: filename (string): Custom file name (with file extension) to override the automatic file name (based on other given arguments). Default is ``None`` which will produce an automatic file name. size (tuple): A tuple of two integers respectively for width and height. Default to ``(100, 100)`` which will produce a square of 100 pixels. bg_color (string): Color name to use to paint image background. Default to ``blue``. text_color (string): Color name to use to draw possible content text. Default to ``white``. text (string): Custom content text to include in image. Default to ``None`` which will produce an automatic content based on size. Use an empty string to avoid content text in image. format_name (string): Image format name to use. Default to ``PNG``. Returns: dict: Configuration to use to create image file. """ # Split given size width, height = size config = { "size": size, "width": width, "height": height, "format_name": format_name, "mode": self.get_mode(format_name), "file_extension": self.get_file_extension(format_name), "bg_color": bg_color, "text": text, "text_color": text_color, "text_content": self.get_text_content(text, width, height), } config["filename"] = self.get_filename( config["file_extension"], filename=filename, bg_color=config["bg_color"], ) return config
[docs] def create_vectorial(self, width, height, bg_color, text_content=None, text_color=None): """ Create SVG content. Arguments: width (integer): Image width. height (integer): Image height. bg_color (object): Color name to paint image background. Keyword Arguments: text_content (string): Content text to include instead of automatic content text. text_color (string): Color name to draw text instead of default one. It is required if you want to use the custom context text. Returns: io.StringIO: SVG content in a string buffer. """ svg = ( '<svg xmlns="http://www.w3.org/2000/svg" ' 'role="img" aria-label="Placeholder" ' 'preserveAspectRatio="xMidYMid slice" focusable="false" ' 'viewBox="0 0 {width} {height}" style="text-anchor: middle">' '<rect width="100%" height="100%" fill="{bg_color}"></rect>' ).format( width=str(width), height=str(height), bg_color=bg_color, ) if text_color and text_content: svg += ( '<text x="50%" y="50%" fill="{text_color}" dy=".3em">{text}</text>' ).format( text=text_content, text_color=text_color, ) svg += '</svg>' return io.StringIO(svg)
[docs] def create_bitmap(self, mode, format_name, width, height, bg_color, text_content=None, text_color=None): """ Create Bitmap image object. Arguments: mode (string): Image color mode to use. format_name (string): Image format name to use. width (integer): Image width. height (integer): Image height. bg_color (object): Color name to paint image background. Keyword Arguments: text_content (string): Content text to include instead of automatic content text. text_color (string): Color name to draw text instead of default one. It is required if you want to use the custom context text. Returns: io.BytesIO: Image object in a byte buffer. """ img = PILimage.new(mode, (width, height), bg_color) # Optional text, always centered if text_color and text_content: draw = ImageDraw.Draw(img) draw.text( (width / 2, height / 2), text_content, fill=text_color, font=self.font, anchor="mm", ) output = io.BytesIO() img.save(output, format=format_name) return output
[docs] def create(self, filename=None, size=(100, 100), bg_color="blue", text_color="white", text=None, format_name="PNG"): """ Create an image inside a file object. Return a File object with a dummy generated image on the fly by PIL or possibly a SVG file. With default argument values the generated image will be a simple blue square in PNG with no text. Optionally, you can have any other supported format (JPEG, GIF, SVG), a custom background color and a text. Keyword Arguments: filename (string): Filename for created file, default to ``bg_color`` value joined to extension with ``format`` value in lowercase (or ``jpg`` if format is ``JPEG``). Note than final filename may be different if all tests use the same one since Django will append a hash for uniqueness. format_name (string): Format name as available from PIL: ``JPEG``, ``PNG`` or ``GIF``. ``SVG`` format is also possible to create a dummy SVG file. size (tuple): A tuple of two integers respectively for width and height. Default to ``(100, 100)`` which will produce a square of 100 pixels. bg_color (string): Color value to fill image, this should be a valid value for ``PIL.ImageColor``: https://pillow.readthedocs.io/en/stable/reference/ImageColor.html or a valid HTML color name for SVG format. Default to "blue". WARNING: If you don't use named color (like "white" or "yellow"), you should give a custom filename to ``filename`` argument else the filename may be weird (like ``#111111.png``). text_color (string): Color value for text. This should be a valid value for ``PIL.ImageColor``: https://pillow.readthedocs.io/en/stable/reference/ImageColor.html Default to "white". text (string or boolean): ``True`` for automatic image size like ``320x240`` (for width value ``320`` and height value ``240``). ``None`` or ``False`` to disable text drawing (this is the default value). A string for custom text, this should be a short text else it is not guaranteed to fit. Returns: object: File object. """ config = self.build( filename=filename, size=size, bg_color=bg_color, text_color=text_color, text=text, format_name=format_name ) if config["format_name"] == "SVG": output = self.create_vectorial( config["width"], config["height"], config["bg_color"], text_content=config["text_content"], text_color=config["text_color"], ) else: output = self.create_bitmap( config["mode"], config["format_name"], config["width"], config["height"], config["bg_color"], text_content=config["text_content"], text_color=config["text_color"], ) return output
[docs] class DjangoSampleImageCrafter(SampleImageCrafter): """ Alike SampleImageCrafter but return a Django File instead of a file object. """
[docs] def create(self, *args, **kwargs): """ Create an image inside a Django File object. Arguments: *args: The same positional arguments as from SampleImageCrafter. **kwargs: The same keyword arguments as from SampleImageCrafter. Returns: django.core.files.File: File object. """ config = self.build(*args, **kwargs) if config["format_name"] == "SVG": output = self.create_vectorial( config["width"], config["height"], config["bg_color"], text_content=config["text_content"], text_color=config["text_color"], ) else: output = self.create_bitmap( config["mode"], config["format_name"], config["width"], config["height"], config["bg_color"], text_content=config["text_content"], text_color=config["text_color"], ) return File(output, name=config["filename"])