Automatically crop black borders from covers
This commit is contained in:
parent
20e7de5b5f
commit
7df2e3fdc0
@ -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"],
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
Loading…
Reference in New Issue
Block a user