Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 0 additions & 63 deletions .github/workflows/enscripten.yaml

This file was deleted.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ force_clean:
docker run --rm -v `pwd`:`pwd` -w `pwd` -it alpine/make make clean

pytest:
python3 -m pip install pytest numpy
python3 -m pip install pytest numpy pillow
pytest tests # --capture=tee-sys
.PHONY: test pytest

Expand Down
16 changes: 7 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,31 @@ build-backend = "scikit_build_core.build"

[project]
name = "pybind11_pixelmatch"
version = "0.1.5"
version = "0.1.6"
description="A C++17 port of the JavaScript pixelmatch library (with python binding), providing a small pixel-level image comparison library."
readme = "README.md"
authors = [
{ name = "district10", email = "dvorak4tzx@gmail.com" },
]
requires-python = ">=3.7"
requires-python = ">=3.9"
classifiers = [
"Development Status :: 4 - Beta",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dependencies = [
"numpy",
"opencv-python",
]
dependencies = []

[project.urls]
Homepage = "https://github.com/cubao/pybind11_pixelmatch"

[project.optional-dependencies]
test = ["pytest"]
test = ["pytest", "Pillow", "numpy", "opencv-python-headless"]


# docs: https://scikit-build-core.readthedocs.io/en/latest/configuration.html
Expand Down
89 changes: 68 additions & 21 deletions src/pybind11_pixelmatch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Literal

if TYPE_CHECKING:
import numpy as np

from ._core import (
Color,
Expand All @@ -11,31 +15,74 @@
rgb2yiq,
)

Backend = Literal["cv2", "pillow"]

def read_image(path):
import cv2
import numpy as np

def read_image(
path: str,
*,
backend: Backend | None = None,
) -> np.ndarray:
assert Path(path).is_file(), f"{path} does not exist"
img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
if img.shape[2] == 3:
B, G, R = cv2.split(img)
A = np.ones(B.shape, dtype=B.dtype) * 255
img = cv2.merge((R, G, B, A))
elif img.shape[2] == 4:
img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
return img


def write_image(path, img):
import cv2

if img.shape[2] == 3:
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
else:
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)

if backend is None:
try:
import PIL # noqa: F401

backend = "pillow"
except ImportError:
backend = "cv2"

if backend == "cv2":
import cv2
import numpy as np

img = cv2.imread(str(path), cv2.IMREAD_UNCHANGED)
if img.shape[2] == 3:
B, G, R = cv2.split(img)
A = np.ones(B.shape, dtype=B.dtype) * 255
img = cv2.merge((R, G, B, A))
elif img.shape[2] == 4:
img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
return img

# pillow backend
import numpy as np
from PIL import Image

return np.array(Image.open(path).convert("RGBA"))


def write_image(
path: str,
img: np.ndarray,
*,
backend: Backend | None = None,
) -> None:
Path(path).resolve().parent.mkdir(parents=True, exist_ok=True)
cv2.imwrite(path, img)

if backend is None:
try:
import PIL # noqa: F401

backend = "pillow"
except ImportError:
backend = "cv2"

if backend == "cv2":
import cv2

if img.shape[2] == 3:
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
else:
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
cv2.imwrite(str(path), img)
return

# pillow backend
from PIL import Image

Image.fromarray(img).save(path)


def normalize_color(rgba):
Expand Down
4 changes: 2 additions & 2 deletions src/pybind11_pixelmatch/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json
import sys
import tkinter as tk
from functools import lru_cache
from functools import cache
from pathlib import Path

import cv2
Expand Down Expand Up @@ -571,7 +571,7 @@ def align_image(
return warp_matrix, aligned


@lru_cache(maxsize=None)
@cache
def diff_image_options(
threshold: float = 0.1,
include_aa: bool = False,
Expand Down
23 changes: 23 additions & 0 deletions tests/test_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,26 @@ def test_pixelmatch():
num = pixelmatch(img1, img2, output=diff)
assert num == 163889
write_image("diff.png", diff)


def test_read_image_backends_equivalent():
project_source_dir = str(Path(__file__).resolve().parent.parent)
path = f"{project_source_dir}/data/pic1.png"

img_cv2 = read_image(path, backend="cv2")
img_pil = read_image(path, backend="pillow")

assert img_cv2.shape == img_pil.shape
assert img_cv2.dtype == img_pil.dtype
assert np.array_equal(img_cv2, img_pil)


def test_write_image_round_trip(tmp_path):
project_source_dir = str(Path(__file__).resolve().parent.parent)
original = read_image(f"{project_source_dir}/data/pic1.png")

for backend in ("cv2", "pillow"):
out = str(tmp_path / f"out_{backend}.png")
write_image(out, original, backend=backend)
reloaded = read_image(out, backend=backend)
assert np.array_equal(original, reloaded), f"round-trip failed for {backend}"
Loading