feat: add fill color -> Ink classification helper (#1688)
This commit is contained in:
@@ -15,7 +15,7 @@ from pikepdf import Matrix, Object, PdfInlineImage, parse_content_stream
|
||||
|
||||
from ocrmypdf.exceptions import InputFileError
|
||||
from ocrmypdf.helpers import Resolution
|
||||
from ocrmypdf.pdfinfo._types import UNIT_SQUARE
|
||||
from ocrmypdf.pdfinfo._types import UNIT_SQUARE, Ink
|
||||
|
||||
|
||||
class XobjectSettings(NamedTuple):
|
||||
@@ -67,6 +67,33 @@ def _is_unit_square(shorthand):
|
||||
return all(isclose(a, b, rel_tol=1e-3) for a, b in pairwise)
|
||||
|
||||
|
||||
_INK_EPSILON = 1e-3
|
||||
|
||||
|
||||
def _ink_from_components(space: str, comps: list[float]) -> Ink:
|
||||
"""Classify a device-color fill into mono/gray/color.
|
||||
|
||||
``space`` is one of 'gray', 'rgb', 'cmyk'. Any other value is treated
|
||||
conservatively as color, since we cannot prove it is achromatic.
|
||||
"""
|
||||
eps = _INK_EPSILON
|
||||
if space == 'gray' and len(comps) == 1:
|
||||
return Ink.mono if comps[0] <= eps else Ink.gray
|
||||
if space == 'rgb' and len(comps) == 3:
|
||||
r, g, b = comps
|
||||
if max(r, g, b) <= eps:
|
||||
return Ink.mono
|
||||
if abs(r - g) <= eps and abs(g - b) <= eps:
|
||||
return Ink.gray
|
||||
return Ink.color
|
||||
if space == 'cmyk' and len(comps) == 4:
|
||||
c, m, y, k = comps
|
||||
if c <= eps and m <= eps and y <= eps:
|
||||
return Ink.mono if k <= eps else Ink.gray
|
||||
return Ink.color
|
||||
return Ink.color # conservative-to-color
|
||||
|
||||
|
||||
def _normalize_stack(graphobjs):
|
||||
"""Convert runs of qQ's in the stack into single graphobjs."""
|
||||
for operands, operator in graphobjs:
|
||||
|
||||
+21
-1
@@ -19,7 +19,7 @@ from ocrmypdf import pdfinfo
|
||||
from ocrmypdf.exceptions import InputFileError
|
||||
from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution
|
||||
from ocrmypdf.pdfinfo import Colorspace, Encoding, Ink
|
||||
from ocrmypdf.pdfinfo._contentstream import _interpret_contents
|
||||
from ocrmypdf.pdfinfo._contentstream import _ink_from_components, _interpret_contents
|
||||
from ocrmypdf.pdfinfo.layout import PDFPage
|
||||
|
||||
warnings.filterwarnings(
|
||||
@@ -296,3 +296,23 @@ def test_ink_enum_is_picklable():
|
||||
# ImageInfo crosses the worker-process boundary, so Ink must pickle.
|
||||
for member in (Ink.mono, Ink.gray, Ink.color):
|
||||
assert pickle.loads(pickle.dumps(member)) is member
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"space, comps, expected",
|
||||
[
|
||||
('gray', [0.0], 'mono'),
|
||||
('gray', [0.263], 'gray'),
|
||||
('gray', [1.0], 'gray'), # white -> gray (harmless)
|
||||
('rgb', [0.0, 0.0, 0.0], 'mono'),
|
||||
('rgb', [0.263, 0.263, 0.263], 'gray'),
|
||||
('rgb', [0.8, 0.2, 0.2], 'color'),
|
||||
('rgb', [1.0, 1.0, 1.0], 'gray'),
|
||||
('cmyk', [0.0, 0.0, 0.0, 0.0], 'mono'), # white
|
||||
('cmyk', [0.0, 0.0, 0.0, 0.5], 'gray'),
|
||||
('cmyk', [0.5, 0.1, 0.0, 0.0], 'color'),
|
||||
('unknown', [0.5], 'color'), # conservative fallback
|
||||
],
|
||||
)
|
||||
def test_ink_from_components(space, comps, expected):
|
||||
assert _ink_from_components(space, comps) is Ink[expected]
|
||||
|
||||
Reference in New Issue
Block a user