From 7df2e3fdc01e66a9369a80d8396d4defcfb6ee75 Mon Sep 17 00:00:00 2001 From: Timmy Welch Date: Mon, 19 Sep 2022 20:24:42 -0700 Subject: [PATCH] Automatically crop black borders from covers --- comictaggerlib/ctoptions/file.py | 7 +++++ comictaggerlib/issueidentifier.py | 52 +++++++++++++++++++++++++++++-- tests/issueidentifier_test.py | 24 ++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/comictaggerlib/ctoptions/file.py b/comictaggerlib/ctoptions/file.py index 7791feb..0d6e4f7 100644 --- a/comictaggerlib/ctoptions/file.py +++ b/comictaggerlib/ctoptions/file.py @@ -42,6 +42,13 @@ def internal(parser: settngs.Manager) -> None: def identifier(parser: settngs.Manager) -> None: # identifier settings parser.add_setting("--series-match-identify-thresh", default=91, type=int, help="") + parser.add_setting( + "-b", + "--border-crop-percent", + default=10, + type=int, + help="ComicTagger will automatically add an additional cover that has any black borders cropped. If the difference in height is less than %(default)s%% the cover will not be cropped.", + ) parser.add_setting( "--publisher-filter", default=["Panini Comics", "Abril", "Planeta DeAgostini", "Editorial Televisa", "Dino Comics"], diff --git a/comictaggerlib/issueidentifier.py b/comictaggerlib/issueidentifier.py index d919b1d..dade198 100644 --- a/comictaggerlib/issueidentifier.py +++ b/comictaggerlib/issueidentifier.py @@ -35,7 +35,7 @@ from comictalker.talkerbase import ComicTalker, TalkerError logger = logging.getLogger(__name__) try: - from PIL import Image + from PIL import Image, ImageChops pil_available = True except ImportError: @@ -166,6 +166,48 @@ class IssueIdentifier: return cropped_image_data + # Adapted from https://stackoverflow.com/a/10616717/20629671 + def crop_border(self, image_data: bytes, ratio: int) -> bytes | None: + im = Image.open(io.BytesIO(image_data)) + + # RGBA doesn't work???? + tmp = im.convert("RGB") + + bg = Image.new("RGB", tmp.size, "black") + diff = ImageChops.difference(tmp, bg) + diff = ImageChops.add(diff, diff, 2.0, -100) + + bbox = diff.getbbox() + + width_percent = 0 + height_percent = 0 + + # If bbox is None that should mean it's solid black + if bbox: + width = bbox[2] - bbox[0] + height = bbox[3] - bbox[1] + + # Convert to percent + width_percent = 100 - ((width / im.width) * 100) + height_percent = 100 - ((height / im.height) * 100) + logger.debug( + "Width: %s Height: %s, ratio: %s %s ratio met: %s", + im.width, + im.height, + width_percent, + height_percent, + width_percent > ratio or height_percent > ratio, + ) + + # If there is a difference return the image otherwise return None + if width_percent > ratio or height_percent > ratio: + output = io.BytesIO() + im.crop(bbox).save(output, format="PNG") + cropped_image_data = output.getvalue() + output.close() + return cropped_image_data + return None + def set_progress_callback(self, cb_func: Callable[[int, int], None]) -> None: self.callback = cb_func @@ -308,8 +350,7 @@ class IssueIdentifier: self.log_msg(score, False) if score <= self.strong_score_thresh: - # such a good score, we can quit now, since for sure we - # have a winner + # such a good score, we can quit now, since for sure we have a winner done = True break if done: @@ -464,6 +505,11 @@ class IssueIdentifier: if narrow_cover_hash is not None: hash_list.append(narrow_cover_hash) + cropped_border = self.crop_border(cover_image_data, self.options.identifier_border_crop_percent) + if cropped_border is not None: + hash_list.append(self.calculate_hash(cropped_border)) + logger.info("Adding cropped cover to the hashlist") + try: image_url = issue.image_url alt_urls = issue.alt_image_urls diff --git a/tests/issueidentifier_test.py b/tests/issueidentifier_test.py index 9a62d02..f8a8acf 100644 --- a/tests/issueidentifier_test.py +++ b/tests/issueidentifier_test.py @@ -1,6 +1,9 @@ from __future__ import annotations +import io + import pytest +from PIL import Image import comicapi.comicarchive import comicapi.issuestring @@ -73,3 +76,24 @@ def test_search(cbz, options, comicvine_api): for r, e in zip(results, [cv_expected]): del r["url_image_hash"] assert r == e + + +def test_crop_border(cbz, options, comicvine_api): + settings, definitions = options + ii = comictaggerlib.issueidentifier.IssueIdentifier(cbz, settings, comicvine_api) + + # This creates a white square centered on a black background + bg = Image.new("RGBA", (100, 100), (0, 0, 0, 255)) + fg = Image.new("RGBA", (50, 50), (255, 255, 255, 255)) + bg.paste(fg, (bg.width // 2 - (fg.width // 2), bg.height // 2 - (fg.height // 2))) + output = io.BytesIO() + bg.save(output, format="PNG") + image_data = output.getvalue() + output.close() + + cropped = ii.crop_border(image_data, 49) + + im = Image.open(io.BytesIO(cropped)) + assert im.width == fg.width + assert im.height == fg.height + assert list(im.getdata()) == list(fg.getdata())