Skip to content

Commit

Permalink
Make definition work with images pending of some adjustments and clea…
Browse files Browse the repository at this point in the history
…n ups
  • Loading branch information
pablerass committed Dec 17, 2023
1 parent 9201fd8 commit 8f78d5e
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 31 deletions.
4 changes: 2 additions & 2 deletions cartuli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .output import sheet_output, sheet_pdf_output
from .filters import MultipleFilter, StraightenFilter, InpaintFilter, CropFilter
from .processing import inpaint, straighten, crop
from .templater import SVGTemplate, svg_file_to_image, svg_content_to_image
from .template import Template, svg_file_to_image, svg_content_to_image
from .definition import Definition, DefinitionError


Expand All @@ -27,6 +27,6 @@
sheet_output, sheet_pdf_output,
MultipleFilter, StraightenFilter, InpaintFilter, CropFilter,
inpaint, straighten, crop,
SVGTemplate, svg_file_to_image, svg_content_to_image,
Template, svg_file_to_image, svg_content_to_image,
Definition, DefinitionError
]
80 changes: 63 additions & 17 deletions cartuli/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
from multiprocessing import Pool, cpu_count
from pathlib import Path
from PIL import Image
from typing import Iterable

from .card import CardImage, Card
from .deck import Deck
from .filters import Filter, NullFilter, from_dict as filter_from_dict
from .measure import Size, from_str
from .measure import Size, from_str as measure_from_str
from .sheet import Sheet
from .templater import svg_file_to_image
from .template import svg_file_to_image, from_dict as template_from_dict


_CONCURRENT_PROCESSES = cpu_count() - 1
Expand All @@ -29,6 +30,21 @@ class DefinitionError(Exception):
pass


# TUNE; Certain names should just not be written at certain hours
def _convert_dict_of_lists_to_list_of_dicts(dict_of_lists: dict) -> Iterable:
list_of_dicts = []

keys = list(dict_of_lists.keys())

for item in range(len(dict_of_lists[keys[0]])):
item_dict = {}
for key in keys:
item_dict |= {key: dict_of_lists[key][item]}
list_of_dicts.append(item_dict)

return list_of_dicts


def _load_image(image_file: str | Path) -> Image.Image:
# TUNE: I am tired of writting this and probably image_file = Path(image_file) will do the trick
if isinstance(image_file, str):
Expand Down Expand Up @@ -92,27 +108,52 @@ def decks(self) -> list[Deck]:

return self.__decks

def _load_template(self, template_definition: dict) -> tuple[list[Path], list[Image.Image]]:
# TODO: Support text values
template = template_from_dict(template_definition)
parameter_values = {}
template_files = []
for parameter in template.parameters:
# TODO: Could make sense to add filters in this step?
image_files = sorted(glob(template_definition['parameters'][parameter]))
parameter_values |= {
parameter: [_load_image(image_file) for image_file in image_files]
}
if not template_files:
template_files = image_files

images = [
template.create_image(parameters)
for parameters in _convert_dict_of_lists_to_list_of_dicts(parameter_values)
]

return image_files, images

def _load_images(self, images_definition: dict, size: Size,
deck_name: str, side: str = 'front') -> list[CardImage]:
logger = logging.getLogger('cartuli.definition.Definition.decks')

image_filter = images_definition.get('filter', '')
image_files = sorted(glob(images_definition['images']))
logger.debug(f"Found {len(image_files)} {side} images for '{deck_name}' deck")
if 'images' in images_definition:
image_files = sorted(glob(images_definition['images']))
images = [_load_image(file) for file in image_files if self.__cards_filter(file)]
elif 'template' in images_definition:
image_files, images = self._load_template(images_definition['template'])
logger.debug(f"Found {len(images)} {side} images for '{deck_name}' deck")
with Pool(processes=_CONCURRENT_PROCESSES) as pool:
images = pool.map(
card_images = pool.map(
self.filters[image_filter].apply,
(CardImage(
_load_image(path), size=size,
bleed=from_str(images_definition.get('bleed', str(CardImage.DEFAULT_BLEED))),
name=Path(path).stem
) for path in image_files if self.__cards_filter(path))
image, size=size,
bleed=measure_from_str(images_definition.get('bleed', str(CardImage.DEFAULT_BLEED))),
name=Path(file).stem
) for image, file in zip(images, image_files))
)
if len(image_files) != len(images):
logger.debug(f"{side.capitalize()} images filterd from {len(image_files)} to "
f" {len(images)} for '{deck_name}' deck")

return images
return card_images

def _load_deck(self, definition: dict, name: str) -> Deck:
logger = logging.getLogger('cartuli.definition.Definition.decks')
Expand All @@ -139,14 +180,19 @@ def _load_deck(self, definition: dict, name: str) -> Deck:

default_back = None
if 'default_back' in definition:
default_back_file = definition['default_back']['image']
if 'image' in definition['default_back']:
default_back_file = definition['default_back']['image']
default_back_image = _load_image(default_back_file)
elif 'template' in definition['default_back']:
default_back_file, default_back_image = self._load_template(definition['template'])

if self.__cards_filter(default_back_file):
default_back_filter = definition['default_back'].get('filter', '')
default_back = self.filters[default_back_filter].apply(
CardImage(
_load_image(default_back_file),
default_back_image,
size=size,
bleed=from_str(definition['default_back'].get('bleed', str(CardImage.DEFAULT_BLEED))),
bleed=measure_from_str(definition['default_back'].get('bleed', str(CardImage.DEFAULT_BLEED))),
name=Path(default_back_file).stem
)
)
Expand Down Expand Up @@ -174,10 +220,10 @@ def sheets(self) -> dict[tuple[str], Sheet]:
self.__sheets[deck_names] = Sheet(
cards,
size=Size.from_str(sheet_definition.get('size', str(Sheet.DEFAULT_SIZE))),
print_margin=from_str(sheet_definition.get('print_margin',
str(Sheet.DEFAULT_PRINT_MARGIN))),
padding=from_str(sheet_definition.get('padding', str(Sheet.DEFAULT_PADDING))),
crop_marks_padding=from_str(
print_margin=measure_from_str(
sheet_definition.get('print_margin', str(Sheet.DEFAULT_PRINT_MARGIN))),
padding=measure_from_str(sheet_definition.get('padding', str(Sheet.DEFAULT_PADDING))),
crop_marks_padding=measure_from_str(
sheet_definition.get('crop_marks_padding', str(Sheet.DEFAULT_CROP_MARKS_PADDING)))
)

Expand Down
25 changes: 19 additions & 6 deletions cartuli/templater.py → cartuli/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ def svg_content_to_image(svg_content: str, dpi: int = DEFAULT_SVG_DPI) -> Image.
return Image.open(io.BytesIO(image_data))


class SVGTemplate:
class Template:
def __init__(self, template: TemplateContent | etree._Element, parameters: Iterable[ParameterKey],
dpi: int = DEFAULT_SVG_DPI):
if not parameters:
raise ValueError("A template withoyt parameters does not make any sense")

if isinstance(template, etree._Element):
if isinstance(template, etree._ElementTree):
self.__xml_tree = template
self.__encoding = self.__xml_tree.docinfo.encoding
else:
Expand All @@ -90,19 +90,23 @@ def __init__(self, template: TemplateContent | etree._Element, parameters: Itera
if not (element.tag.endswith('image') or element.tag.endswith('text')):
raise ValueError(f"Parameter '{parameter}' element '{element.tag}' is unsupported")

self.__parameters = parameters
self.__parameters = tuple(parameters)
self.__dpi = dpi

@property
def _xml_tree(self) -> etree._ElementTree:
return self.__xml_tree

@property
def parameters(self) -> dict[ParameterKey, type]:
return self.__parameters.copy()
return deepcopy(self.__parameters)

@property
def dpi(self) -> int:
return self.__dpi

@classmethod
def from_file(cls, template_file: str | Path, parameters: Iterable[ParameterKey]) -> SVGTemplate:
def from_file(cls, template_file: str | Path, parameters: Iterable[ParameterKey]) -> Template:
if isinstance(template_file, str):
template_file = Path(template_file)

Expand Down Expand Up @@ -142,7 +146,7 @@ def get_values(self, content: TemplateContent | etree._Element,
if parameters is None:
parameters = tuple(self.__parameters)

if isinstance(content, etree._Element):
if isinstance(content, etree._ElementTree):
content_tree = content
else:
content_tree = etree.fromstring(bytes(content))
Expand Down Expand Up @@ -176,3 +180,12 @@ def get_values_from_file(self, content_file: str | Path,
content_file = Path(content_file)

return self.get_values(content_file.read_text(), parameters)

def __eq__(self, other: Template) -> bool:
return (self._xml_tree == other._xml_tree and
self.parameters == other.parameters)


def from_dict(template_dict: dict) -> Template:
# TUNE: It is strange that the definition contains also the values but here are not used at all
return Template.from_file(template_dict['definition'], template_dict['parameters'].keys())
14 changes: 13 additions & 1 deletion tests/test_definition.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from cartuli.definition import Definition
from cartuli.definition import Definition, _convert_dict_of_lists_to_list_of_dicts
from cartuli.filters import NullFilter, InpaintFilter
from cartuli.measure import Size, STANDARD, A4, mm

Expand Down Expand Up @@ -107,3 +107,15 @@ def test_definition(random_image_file):
def test_filters(random_image_file):
# TODO: Implement filters testing
pass


def test_convert_dict_of_lists_to_list_of_dicts():
assert _convert_dict_of_lists_to_list_of_dicts({
'a': [1, 2, 3, 4],
'b': [5, 6, 7, 8]
}) == [
{'a': 1, 'b': 5},
{'a': 2, 'b': 6},
{'a': 3, 'b': 7},
{'a': 4, 'b': 8},
]
18 changes: 13 additions & 5 deletions tests/test_templater.py → tests/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

from PIL import ImageChops

from cartuli.templater import SVGTemplate
from cartuli.template import Template


def test_templater(fixture_content, random_image):
def test_template(fixture_content, random_image):
template_content = fixture_content("template.svg")

with pytest.raises(ValueError):
SVGTemplate(template_content, [])
Template(template_content, [])

with pytest.raises(ValueError):
SVGTemplate(template_content, ('name', 'image'))
Template(template_content, ('name', 'image'))

template = SVGTemplate(template_content, ('image', 'text'))
template = Template(template_content, ('image', 'text'))

parameters = {
'text': 'otro_texto',
Expand All @@ -28,3 +28,11 @@ def test_templater(fixture_content, random_image):
rgb_parameter_image = parameters['image'].convert('RGB')
rgb_content_image = content_values['image'].convert('RGB')
assert not ImageChops.difference(rgb_parameter_image, rgb_content_image).getbbox()


# TODO: Make this tests work
# def test_template_from_file(fixture_content, fixture_file):
# template_content = fixture_content("template.svg")
# parameters = ('text', 'image')
#
# assert Template(template_content, parameters) == Template.from_file(fixture_file("template.svg"), parameters)

0 comments on commit 8f78d5e

Please sign in to comment.