"""Convert knitting patterns to png files.
These png files are used to be fed into the ayab-desktop software.
They only contain which meshes will be knit with a contrast color.
They just contain colors.
"""
import webcolors
import PIL.Image
from .color import convert_color_to_rrggbb
[docs]class AYABPNGBuilder(object):
"""Convert knitting patterns to png files that only contain the color
information and ``(x, y)`` coordinates.
.. _png-color:
Througout this class the term `color` refers to either
- a valid html5 color name such as ``"black"``, ``"white"``
- colors of the form ``"#RGB"``, ``"#RRGGBB"`` and ``"#RRRGGGBBB"``
"""
[docs] def __init__(self, min_x, min_y, max_x, max_y,
default_color="white"):
"""Initialize the builder with the bounding box and a default color.
.. _png-builder-bounds:
``min_x <= x < max_x`` and ``min_y <= y < max_y`` are the bounds of the
instructions.
Instructions outside the bounds are not rendered.
Any Pixel that is not set has the :paramref:`default_color`.
:param int min_x: the lower bound of the x coordinates
:param int max_x: the upper bound of the x coordinates
:param int min_y: the lower bound of the y coordinates
:param int max_y: the upper bound of the y coordinates
:param default_color: a valid :ref:`color <png-color>`
"""
self._min_x = min_x
self._min_y = min_y
self._max_x = max_x
self._max_y = max_y
self._default_color = default_color
self._image = PIL.Image.new(
"RGB", (max_x - min_x, max_y - min_y),
self._convert_to_image_color(default_color))
[docs] def write_to_file(self, file):
"""write the png to the file
:param file: a file-like object
"""
self._image.save(file, format="PNG")
@staticmethod
def _convert_color_to_rrggbb(color):
"""takes a :ref:`color <png-color>` and converts it into a 24 bit
color "#RRGGBB"
"""
return convert_color_to_rrggbb(color)
def _convert_rrggbb_to_image_color(self, rrggbb):
""":return: the color that is used by the image"""
return webcolors.hex_to_rgb(rrggbb)
def _convert_to_image_color(self, color):
""":return: a color that can be used by the image"""
rgb = self._convert_color_to_rrggbb(color)
return self._convert_rrggbb_to_image_color(rgb)
def _set_pixel_and_convert_color(self, x, y, color):
"""set the pixel but convert the color before."""
if color is None:
return
color = self._convert_color_to_rrggbb(color)
self._set_pixel(x, y, color)
def _set_pixel(self, x, y, color):
"""set the color of the pixel.
:param color: must be a valid color in the form of "#RRGGBB".
If you need to convert color, use `_set_pixel_and_convert_color()`.
"""
if not self.is_in_bounds(x, y):
return
rgb = self._convert_rrggbb_to_image_color(color)
x -= self._min_x
y -= self._min_y
self._image.putpixel((x, y), rgb)
[docs] def set_pixel(self, x, y, color):
"""set the pixel at ``(x, y)`` position to :paramref:`color`
If ``(x, y)`` is out of the :ref:`bounds <png-builder-bounds>`
this does not change the image.
.. seealso:: :meth:`set_color_in_grid`
"""
self._set_pixel_and_convert_color(x, y, color)
[docs] def is_in_bounds(self, x, y):
"""
:return: whether ``(x, y)`` is inside the :ref:`bounds
<png-builder-bounds>`
:rtype: bool
"""
lower = self._min_x <= x and self._min_y <= y
upper = self._max_x > x and self._max_y > y
return lower and upper
[docs] def set_color_in_grid(self, color_in_grid):
"""Set the pixel at the position of the :paramref:`color_in_grid`
to its color.
:param color_in_grid: must have the following attributes:
- ``color`` is the :ref:`color <png-color>` to set the pixel to
- ``x`` is the x position of the pixel
- ``y`` is the y position of the pixel
.. seealso:: :meth:`set_pixel`, :meth:`set_colors_in_grid`
"""
self._set_pixel_and_convert_color(
color_in_grid.x, color_in_grid.y, color_in_grid.color)
[docs] def set_colors_in_grid(self, some_colors_in_grid):
"""Same as :meth:`set_color_in_grid` but with a collection of
colors in grid.
:param iterable some_colors_in_grid: a collection of colors in grid for
:meth:`set_color_in_grid`
"""
for color_in_grid in some_colors_in_grid:
self._set_pixel_and_convert_color(
color_in_grid.x, color_in_grid.y, color_in_grid.color)
@property
def default_color(self):
""":return: the :ref:`color <png-color>` of the pixels that are not set
You can set this color by passing it to the :meth:`constructor
<__init__>`.
"""
return self._default_color
__all__ = ["AYABPNGBuilder"]