diff --git a/LICENSE b/LICENSE index 09d493b..d0bba14 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2017, +Copyright (c) 2020, All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/iris/logo/__init__.py b/iris/logo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/iris/logo/generate_logo.py b/iris/logo/generate_logo.py new file mode 100644 index 0000000..8edfd03 --- /dev/null +++ b/iris/logo/generate_logo.py @@ -0,0 +1,731 @@ +""" +Module to generate the Iris logo in every required format. +Uses `xml.ElementTree` for SVG file editing. +""" + +from argparse import ArgumentParser +from copy import deepcopy +from io import BytesIO +from pathlib import Path +from shutil import make_archive +from subprocess import CalledProcessError, PIPE, run +from tempfile import TemporaryDirectory +from typing import Dict, Iterable, List, Tuple, Union +from warnings import warn +from xml.etree import ElementTree as ET +from xml.dom import minidom + +from cartopy import crs as ccrs +from cartopy.feature import LAND +import matplotlib +from matplotlib import pyplot as plt +import numpy as np + +matplotlib.use("agg") + +# Main dimensions and proportions. The logo's SVG elements can be configured +# within their respective private functions. + +# How much bigger than the globe the iris clip should be. +CLIP_GLOBE_RATIO = 1.28 +# Pixel size of the largest dimension of the main logo. +# (Proportions dictated by those of the clip). +LOGO_SIZE = 1024 +# Pixel height of the banner (the height of the globe within the banner). +BANNER_HEIGHT = 256 +# How much smaller than the globe the banner text should be. +TEXT_GLOBE_RATIO = 0.6 +# Frame rate for rotating logos. +ROTATION_FPS = 30 + +# Allows re-use of rotating coastline clips - slow to generate. +COASTLINE_CLIPS_CACHE = [] + +# XML ElementTree setup. +NAMESPACES = {"svg": "http://www.w3.org/2000/svg"} +ET.register_namespace("", NAMESPACES["svg"]) + + +class _SvgNamedElement(ET.Element): + def __init__( + self, + xml_id: str, + tag: str, + is_def: bool = False, + attrib: Dict[str, str] = None, + **extra: str, + ): + """ + `ET.Element` with extra properties to help construct SVG. + + xml_id = mandatory `id` string in `attrib` dict, referencable as a class attribute. + is_def = attrib denoting whether to store in the SVG `defs` section. + + """ + if attrib is None: + attrib = dict() + super().__init__(tag, attrib, **extra) + self.xml_id = xml_id + self.attrib["id"] = self.xml_id + self.is_def = is_def + + +class _SvgGradientElement(_SvgNamedElement): + """`_SvgNamedElement` with convenience method for adding gradient stops.""" + + def add_stop(self, offset: float, color: str, opacity: float = None): + stop_element = ET.Element( + "stop", + attrib={ + "offset": str(offset), + "stop-color": color, + }, + ) + if opacity is not None: + stop_element.attrib["stop-opacity"] = str(opacity) + self.append(stop_element) + + +def _imagemagick_run(*args): + """Try two different possible ImageMagick CLI commands.""" + magick_commands = [["convert"], ["magick", "convert"]] + success = False + for cmd in magick_commands: + try: + run(cmd + list(args), check=True, stdout=PIPE, stderr=PIPE) + success = True + break + except (FileNotFoundError, CalledProcessError): + continue + if not success: + raise RuntimeError + + +def _matrix_transform_string( + scaling_xy: np.ndarray, + centre_xy: np.ndarray, + translation_xy: np.ndarray, +) -> str: + """ + Common script for generating SVG matrix transformation string. Note this + doesn't [currently] allow for skew arguments. + """ + recentre_xy = centre_xy - (scaling_xy * centre_xy) + translation_xy += recentre_xy + matrix_sequence = [ + scaling_xy[0], + 0, + 0, + scaling_xy[1], + translation_xy[0], + translation_xy[1], + ] + matrix_string = " ".join([str(i) for i in matrix_sequence]) + return f"matrix({matrix_string})" + + +def _svg_clip() -> Tuple[_SvgNamedElement, np.ndarray]: + """Generate the clip for masking the entire logo contents.""" + # SVG string representing bezier curves, drawn in a GUI then size-converted + # for this file. + path_string = ( + "M 66.582031,73.613283 C 62.196206,73.820182 51.16069," + "82.105643 33.433594,80.496096 18.711759,79.15939 " + "10.669958,73.392913 8.4375,74.619143 7.1228015,75.508541 " + "6.7582985,76.436912 6.703125,78.451174 6.64868,80.465549 " + "5.5985568,94.091611 12.535156,107.18359 c 8.745259," + "16.50554 21.06813,20.14551 26.152344,20.24414 12.671346," + "0.24601 24.681745,-21.45054 27.345703,-30.62304 3.143758," + "-10.82453 4.457007,-22.654297 1.335938,-23.119141 " + "-0.233298,-0.06335 -0.494721,-0.08606 -0.78711,-0.07227 z " + "m 7.119141,-5.857422 c -0.859687,0.02602 -1.458448," + "0.280361 -1.722656,0.806641 -2.123362,3.215735 3.515029," + "16.843803 -3.970704,34.189448 -5.828231,13.50478 " + "-13.830895,19.32496 -13.347656,21.81446 0.444722,1.5177 " + "1.220596,2.14788 3.13086,2.82226 1.910471,0.67381 " + "14.623496,5.87722 29.292968,3.36524 18.494166,-3.16726 " + "25.783696,-13.69084 27.449216,-18.4668 C 118.68435," + "100.38386 101.63623,82.323612 93.683594,76.970705 " + "86.058314,71.837998 77.426483,67.643118 73.701172," + "67.755861 Z M 114.02539,33.224611 C 103.06524,33.255401 " + "88.961605,40.28151 83.277344,44.67969 74.333356,51.599967 " + "66.27534,60.401955 68.525391,62.601564 c 2.420462," + "3.001097 17.201948,1.879418 31.484379,14.316407 11.11971," + "9.683149 14.21486,19.049139 16.74609,19.361328 1.58953," + "0.0486 2.43239,-0.488445 3.66797,-2.085938 1.23506," + "-1.597905 10.14187,-12.009723 12.27148,-26.654297 " + "2.68478,-18.462878 -5.13068,-28.607634 -9.18554," + "-31.658203 -2.52647,-1.900653 -5.831,-2.666512 -9.48438," + "-2.65625 z M 39.621094,14.64258 C 39.094212,14.665 " + "38.496575,14.789793 37.767578,15.003908 35.823484," + "15.574873 22.460486,18.793044 12.078125,29.396486 " + "-1.0113962,42.764595 -0.68566506,55.540029 0.79101563," + "60.376955 4.4713185,72.432363 28.943765,77.081596 " + "38.542969,76.765627 49.870882,76.392777 61.593892," + "73.978953 61.074219,70.884768 60.890477,67.042613 " + "48.270811,59.312854 44.070312,40.906252 40.799857," + "26.575361 43.83381,17.190581 41.970703,15.458986 " + "41.184932,14.853981 40.49923,14.605209 39.621094,14.64258 " + "Z M 67.228516,0.08984563 C 60.427428,0.11193533 " + "55.565192,2.1689455 53.21875,3.7949238 42.82192,10.999553 " + "45.934544,35.571547 49.203125,44.54883 c 3.857276," + "10.594054 9.790034,20.931896 12.589844,19.484375 " + "3.619302,-1.360857 7.113072,-15.680732 23.425781," + "-25.339844 12.700702,-7.520306 22.61812,-7.553206 " + "23.69922,-9.849609 0.53758,-1.487663 0.28371,-2.45185 " + "-0.86328,-4.113281 C 106.90759,23.069051 99.699016," + "11.431303 86.345703,4.89258 78.980393,1.2860119 72.51825," + "0.07266475 67.228516,0.08984563 Z" + ) + size_xy = np.array([133.334, 131.521]) + visual_centre_xy = np.array([66.149, 67.952]) + + scaling = LOGO_SIZE / max(size_xy) + size_offset_xy = (size_xy - max(size_xy)) / 2 + centre_offset_xy = visual_centre_xy - (size_xy / 2) + offset_xy = (size_offset_xy + centre_offset_xy) * scaling + + clip = _SvgNamedElement(xml_id="iris_clip", tag="clipPath", is_def=True) + clip.append( + ET.Element("path", attrib={"d": path_string, "transform": f"scale({scaling})"}) + ) + return clip, offset_xy + + +def _svg_background() -> List[_SvgNamedElement]: + """Generate the background rectangle for the logo.""" + gradient = _SvgGradientElement( + xml_id="background_gradient", + tag="linearGradient", + is_def=True, + attrib={ + "y1": "0%", + "y2": "100%", + }, + ) + offset_color = [(0, "#13385d"), (0.43, "#0b3849"), (1, "#272b2c")] + for offset, color in offset_color: + gradient.add_stop(offset, color) + background = _SvgNamedElement( + xml_id="background", + tag="rect", + attrib={ + "height": "100%", + "width": "100%", + "fill": f"url(#{gradient.xml_id})", + }, + ) + return [background, gradient] + + +def _svg_sea() -> List[_SvgNamedElement]: + """Generate the circle representing the globe's sea in the logo.""" + # Not using Cartopy for sea since it doesn't actually render curves/circles. + gradient = _SvgGradientElement( + xml_id="sea_gradient", tag="radialGradient", is_def=True + ) + offset_color = [(0, "#20b0ea"), (1, "#156475")] + for offset, color in offset_color: + gradient.add_stop(offset, color) + sea = _SvgNamedElement( + xml_id="sea", + tag="circle", + attrib={ + "cx": "50%", + "cy": "50%", + "r": f"{50.5 / CLIP_GLOBE_RATIO}%", + "fill": f"url(#{gradient.xml_id})", + }, + ) + return [sea, gradient] + + +def _svg_glow() -> List[_SvgNamedElement]: + """Generate the coloured glow to go behind the globe in the logo.""" + gradient_scale_xy = np.array([1.15, 1.35]) + gradient_centre_xy = np.array([0.5, 0.5]) + gradient_translation_xy = np.array([0, -0.25]) + matrix_string = _matrix_transform_string( + gradient_scale_xy, gradient_centre_xy, gradient_translation_xy + ) + gradient = _SvgGradientElement( + xml_id="glow_gradient", + tag="radialGradient", + is_def=True, + attrib={"gradientTransform": matrix_string}, + ) + offset_color_opacity = [ + (0, "#0aaea7", 0.85882354), + (0.67322218, "#18685d", 0.74117649), + (1, "#b6df34", None), + ] + for offset, color, opacity in offset_color_opacity: + gradient.add_stop(offset, color, opacity) + blur = _SvgNamedElement(xml_id="glow_blur", tag="filter", is_def=True) + blur.append(ET.Element("feGaussianBlur", attrib={"stdDeviation": "14"})) + glow = _SvgNamedElement( + xml_id="glow", + tag="circle", + attrib={ + "cx": "50%", + "cy": "50%", + "r": f"{52 / CLIP_GLOBE_RATIO}%", + "fill": f"url(#{gradient.xml_id})", + "filter": f"url(#{blur.xml_id})", + "stroke": "#ffffff", + "stroke-width": "2", + "stroke-opacity": "0.797414", + }, + ) + return [glow, gradient, blur] + + +def _svg_land( + rotate_fixed: bool = False, + rotate_animate: bool = False, +) -> List[List[_SvgNamedElement]]: + """ + Generate the circle representing the globe's land in the logo, clipped by + appropriate coastline shapes (using Matplotlib and Cartopy). + + rotate_fixed = generate a series of independent element list representing + Earth's longitudinal rotation. + rotate_animate = generate a single list of elements with the coastline clip + animated to represent Earth's longitudinal rotation. + + """ + if rotate_fixed is True and rotate_animate is True: + message = "rotate_fixed and rotate_animate are both True - not permitted." + raise ValueError(message) + + # Set plotting size/proportions. + mpl_points_per_inch = 72 + plot_inches = LOGO_SIZE / mpl_points_per_inch + plot_padding = (1 - (1 / CLIP_GLOBE_RATIO)) / 2 + + # Create land with simplified coastlines. + simple_geometries = [geometry.simplify(0.8, True) for geometry in LAND.geometries()] + LAND.geometries = lambda: iter(simple_geometries) + + # Create a sequence of longitude values. + central_longitude = -30 + central_latitude = 22.9 + perspective_tilt = -4.99 + rotate = rotate_fixed or rotate_animate + rotation_frames = 180 if rotate else 1 + rotation_longitudes = np.linspace( + start=central_longitude + 360, + stop=central_longitude, + num=rotation_frames, + endpoint=False, + ) + # Normalise to -180..+180 + rotation_longitudes = (rotation_longitudes + 360.0 + 180.0) % 360.0 - 180.0 + + # Generate/re-use coastline SVG clip(s) for each longitude. + global COASTLINE_CLIPS_CACHE + if rotate and COASTLINE_CLIPS_CACHE: + # Re-use rotating coastline clips if possible due to slow generation. + land_clips = COASTLINE_CLIPS_CACHE + else: + # Freshly generate coastline clips. + land_clips = [] + + # Create the coastline clips. + for lon in rotation_longitudes: + # Use Matplotlib and Cartopy to generate land-shaped SVG clips for + # each longitude. + projection_rotated = ccrs.Orthographic( + central_longitude=lon, central_latitude=central_latitude + ) + + # Use constants set earlier to achieve desired dimensions. + plt.figure(0, figsize=[plot_inches] * 2) + ax = plt.subplot(projection=projection_rotated) + plt.subplots_adjust( + left=plot_padding, + bottom=plot_padding, + right=1 - plot_padding, + top=1 - plot_padding, + ) + ax.add_feature(LAND) + + # Save as SVG and extract the resultant code. + svg_bytes = BytesIO() + plt.savefig(svg_bytes, format="svg") + svg_mpl = ET.fromstring(svg_bytes.getvalue()) + + # Find land paths and convert to clip paths. + land_clip = _SvgNamedElement( + xml_id=f"land_clip_{lon}", + tag="clipPath", + is_def=True, + ) + mpl_land = svg_mpl.find(".//svg:g[@id='figure_1']", NAMESPACES) + land_paths = mpl_land.find(".//svg:g[@id='PathCollection_1']", NAMESPACES) + land_clip.extend(list(land_paths)) + for path in land_clip: + # Remove all other attribute items. + path.attrib = {"d": path.attrib["d"], "stroke-linejoin": "round"} + land_clips.append(land_clip) + plt.close() + + if rotate: + # Cache the generated coastline clips if they were a full rotating set. + COASTLINE_CLIPS_CACHE = land_clips + + # Create the land fill. + gradient = _SvgGradientElement( + xml_id="land_gradient", tag="radialGradient", is_def=True + ) + offset_color = [(0, "#d5e488"), (1, "#aec928")] + for offset, color in offset_color: + gradient.add_stop(offset, color) + logo_centre = LOGO_SIZE / 2 + land = _SvgNamedElement( + xml_id="land", + tag="circle", + attrib={ + "cx": "50%", + "cy": "50%", + "r": f"{50 / CLIP_GLOBE_RATIO}%", + "fill": f"url(#{gradient.xml_id})", + "clip-path": f"url(#{land_clips[0].xml_id})", + "transform": f"rotate({perspective_tilt} {logo_centre} {logo_centre})", + }, + ) + + # Return a single set of SVG elements for animated or single rotation. + result = [[land, gradient, *land_clips]] + if rotate_animate: + animation_values = ";".join([f"url(#{clip.xml_id})" for clip in land_clips]) + frames = len(land_clips) + duration = frames / ROTATION_FPS + land.append( + ET.Element( + "animate", + attrib={ + "attributeName": "clip-path", + "values": animation_values, + "begin": "0s", + "repeatCount": "indefinite", + "dur": f"{duration}s", + }, + ) + ) + elif rotate_fixed: + # Return a set of SVG elements for each fixed rotation. + fixed_clip_name = "land_clip" + land.attrib["clip-path"] = f"url(#{fixed_clip_name})" + for clip in land_clips: + clip.attrib["id"] = fixed_clip_name + result = [[land, gradient, clip] for clip in land_clips] + + return result + + +def _svg_logos( + iris_clip: _SvgNamedElement, + other_elements: Iterable[_SvgNamedElement], + offset_xy: np.ndarray, + banner_size_xy: np.ndarray, + banner_text: str, + banner_version: str = None, +) -> Dict[str, _SvgNamedElement]: + """Assemble sub-elements into SVG's for the logo and banner.""" + # Make the logo SVG first. + logo_root = _SvgNamedElement( + "logo", "svg", attrib={"viewBox": f"0 0 {LOGO_SIZE} {LOGO_SIZE}"} + ) + + # The elements that will just be referenced by artwork elements. + defs_element = _SvgNamedElement("defs", "defs") + defs_element.append(iris_clip) + # The elements that are displayed (not just referenced). + # All artwork is clipped by the Iris shape. + artwork_element = _SvgNamedElement( + "artwork", "g", attrib={"clip-path": f"url(#{iris_clip.xml_id})"} + ) + for element in other_elements: + assert isinstance(element, _SvgNamedElement) + if element.is_def: + target_parent = defs_element + else: + target_parent = artwork_element + target_parent.append(element) + logo_root.extend((defs_element, artwork_element)) + + # Shrink and translate contents - aligning the offset centre with the image + # dimensional centre. + offset_scaling_xy = (LOGO_SIZE - abs(offset_xy * 3)) / LOGO_SIZE + # Lock aspect ratio. + offset_scaling_xy = np.repeat(min(offset_scaling_xy), 2) + logo_centre_xy = np.repeat(LOGO_SIZE / 2, 2) + matrix_string = _matrix_transform_string( + offset_scaling_xy, logo_centre_xy, offset_xy * -1 + ) + artwork_element.attrib["transform"] = matrix_string + + # Take a copy for including in the banner, BEFORE adding final properties. + banner_logo = deepcopy(logo_root) + + logo_root.attrib.update(dict.fromkeys(("width", "height"), str(LOGO_SIZE))) + logo_desc = ET.Element("desc") + logo_desc.text = ( + "Logo for the SciTools Iris project - https://github.com/SciTools/iris/" + ) + logo_root.insert(0, logo_desc) + + result = {"logo": logo_root} + + ########################################################################### + # Make the banner SVG, incorporating the logo SVG. + banner_root = _SvgNamedElement("banner", "svg") + for dimension, name in enumerate(("width", "height")): + banner_root.attrib[name] = str(banner_size_xy[dimension]) + + banner_desc = ET.Element("desc") + banner_desc.text = ( + "Banner logo for the SciTools Iris project - " + "https://github.com/SciTools/iris/" + ) + banner_root.insert(0, banner_desc) + + # Left-align the logo. + banner_logo.attrib["preserveAspectRatio"] = "xMinYMin meet" + banner_root.append(banner_logo) + + # Text element(s). + banner_height = banner_size_xy[1] + text_size = banner_height * TEXT_GLOBE_RATIO + text_x = banner_size_xy[0] - (banner_height / 16) + # Manual y centring since SVG dominant-baseline not widely supported. + text_y = banner_height - (banner_height - text_size) / 2 + text_y *= 0.975 # Slight offset + text_common_attrib = { + "x": str(text_x), + "fill": "#156475", + "font-family": "georgia", + "text-anchor": "end", + } + + text_element = _SvgNamedElement( + "text", + "text", + attrib=dict( + {"y": str(text_y), "font-size": f"{text_size}pt"}, + **text_common_attrib, + ), + ) + text_element.text = banner_text + banner_root.append(text_element) + + if banner_version is not None: + version_size = text_size / 6 + version_y = text_y + version_size + (banner_height / 16) + version_element = _SvgNamedElement( + "version", + "text", + attrib=dict( + {"y": str(version_y), "font-size": f"{version_size}pt"}, + **text_common_attrib, + ), + ) + version_element.text = banner_version + banner_root.append(version_element) + + result[f"logo-title"] = banner_root + + ########################################################################### + + return result + + +def _write_svg_file( + filename: str, + svg_root: _SvgNamedElement, + write_dir: Union[Path, str] = None, +) -> Path: + """Format the svg then write the svg to a file in write_dir.""" + # Add a credit comment at top of SVG. + comment = ( + f"Created by https://github.com/SciTools/marketing/iris/logo/generate_logo.py" + ) + svg_root.insert(0, ET.Comment(comment)) + + input_string = ET.tostring(svg_root) + pretty_xml = minidom.parseString(input_string).toprettyxml() + # Remove extra empty lines from Matplotlib. + pretty_xml = "\n".join([line for line in pretty_xml.split("\n") if line.strip()]) + + if Path(write_dir).is_dir(): + write_path = write_dir.joinpath(filename) + with open(write_path, "w") as f: + f.write(pretty_xml) + result = write_path + else: + raise ValueError("No valid write_dir provided.") + + return result + + +def generate_logo( + filename_prefix: str = "iris", + write_dir: Union[str, Path] = Path.cwd(), + banner_text: str = "Iris", + banner_width: int = 588, + banner_version: str = None, + rotate: bool = False, +) -> Dict[str, Path]: + """Generate the Iris logo and accompanying banner with configurable text. + + Images written in SVG format using `xml.ElementTree`. + + Parameters + ---------- + filename_prefix : str + How each created logo file name should start. + write_dir : str or pathlib.Path + The directory in which to create the logo files. + banner_text : str + The text to include in the banner logo. + banner_width : int + Pixel width of the banner logo - must be manually adjusted to fit + `banner_text`. + banner_version : str, optional + A version string to include in the banner logo. Default is None. + rotate : bool + Whether to also generate rotating earth logos. + NOTE: takes several minutes to generate this. Also, animated SVG files + are known to cause high web-browser CPU demand - consider using + the lower quality GIF files. + + Returns + ------- + dict of pathlib.Path + Paths of the created logo files. + + """ + + print("LOGO GENERATION START ...") + + # Sanitise inputs that may have come from command line. + write_dir = Path(write_dir) + banner_width = int(banner_width) + rotate = bool(rotate) + + # Pixel dimensions of text banner. + banner_size_xy = [banner_width, BANNER_HEIGHT] + + # Get SVG and info for the logo's clip. + # clip_offset_xy: used to align clip visual centre with image dimensional centre. + iris_clip, clip_offset_xy = _svg_clip() + + # Make a list of the SVG elements that don't need explicit naming in + # _svg_logo(). Ordering is important. + svg_elements = [*_svg_background(), *_svg_glow(), *_svg_sea()] + + logo_kwargs = { + "iris_clip": iris_clip, + "offset_xy": clip_offset_xy, + "banner_size_xy": banner_size_xy, + "banner_text": banner_text, + "banner_version": banner_version, + } + + written_paths = {} + # Create the main logos. + # Specialised SVG objects for the land and the coastlines clip. + svg_land = _svg_land()[0] + logos_static = _svg_logos(other_elements=svg_elements + svg_land, **logo_kwargs) + for key, logo in logos_static.items(): + filename = f"{filename_prefix}-{key}.svg" + logo_path = _write_svg_file(filename, logo, write_dir=write_dir) + written_paths[key] = logo_path + + ########################################################################### + if rotate: + # SVG logos animated to rotate. + svg_land_rotating = _svg_land(rotate_animate=True)[0] + logos_rotating = _svg_logos( + other_elements=svg_elements + svg_land_rotating, **logo_kwargs + ) + for key, logo in logos_rotating.items(): + suffix = f"{key}_rotate" + filename = f"{filename_prefix}-{suffix}.svg" + logo_path = _write_svg_file(filename, logo, write_dir=write_dir) + written_paths[f"{suffix}_svg"] = logo_path + + # GIF logos animated to rotate, stitched from static SVG's using ImageMagick. + # Create static rotation SVG's. + svg_land_rotations = _svg_land(rotate_fixed=True) + logos_rotated = [ + _svg_logos(other_elements=svg_elements + svg_land, **logo_kwargs) + for svg_land in svg_land_rotations + ] + logo_types = logos_rotated[0].keys() + # Iterate by logo type, then by rotation within each type. + for logo_type in logo_types: + suffix = f"{logo_type}_rotate" + with TemporaryDirectory() as write_dir_temp: + # Write each static SVG to temp directory. + for ix, logo_dict in enumerate(logos_rotated): + logo_write = logo_dict[logo_type] + _write_svg_file( + f"{suffix}{ix:03d}.svg", + logo_write, + write_dir=Path(write_dir_temp), + ) + + # Stitch the static SVG's into a rotating GIF. + logo_path = write_dir.joinpath(f"{filename_prefix}-{suffix}.gif") + try: + _imagemagick_run( + "-delay", + str(100 / ROTATION_FPS), + "-dispose", + "background", + "-background", + "none", + f"{write_dir_temp}/*.svg", + str(logo_path), + ) + written_paths[f"{suffix}_gif"] = logo_path + except RuntimeError: + message = "ImageMagick CLI not found. Rotating GIFs not created." + warn(message) + + # Write the static rotations to a zip directory. + zip_path = write_dir.joinpath(f"{filename_prefix}-{suffix}") + make_archive(zip_path, "zip", write_dir_temp) + written_paths[f"{suffix}_zip"] = zip_path + + ########################################################################### + + print("LOGO GENERATION COMPLETE") + + return written_paths + + +def main(): + description = ( + "Command line access for the generate_logo() " + "function - docstring above. Input the parameters " + "as optional arguments (--my_arg value). The return value(" + "s) will be printed." + ) + docstring = str(generate_logo.__doc__) + parser = ArgumentParser(description=description, usage=docstring) + _, args = parser.parse_known_args() + + keys = args[::2] + values = args[1::2] + assert all([key[:2] == "--" for key in keys]) + keys = [key[2:] for key in keys] + kwargs = dict(zip(keys, values)) + + print(generate_logo(**kwargs)) + + +if __name__ == "__main__": + main() diff --git a/iris/logo/logo.100x100.png b/iris/logo/logo.100x100.png deleted file mode 100644 index 7fff970..0000000 Binary files a/iris/logo/logo.100x100.png and /dev/null differ diff --git a/iris/logo/logo.1586x1567.png b/iris/logo/logo.1586x1567.png deleted file mode 100644 index d3fe901..0000000 Binary files a/iris/logo/logo.1586x1567.png and /dev/null differ diff --git a/iris/logo/logo.400x400.png b/iris/logo/logo.400x400.png deleted file mode 100644 index 952ab95..0000000 Binary files a/iris/logo/logo.400x400.png and /dev/null differ