2023-02-01 16:25:50 +01:00

116 lines
3.7 KiB
Python

from typing import Tuple
import cv2 as cv
import numpy as np
from PIL import ImageOps, Image
from loguru import logger
from cv_analysis.utils.conversion import normalize_image_format_to_pil
Color = Tuple[int, int, int]
def blur(image: np.ndarray):
return cv.blur(image, (3, 3))
def sharpen(image: np.ndarray):
return cv.filter2D(image, -1, np.array([[-1, -1, -1], [-1, 6, -1], [-1, -1, -1]]))
def overlay(images, mode=np.sum):
assert mode in [np.sum, np.max]
images = np.stack(list(images))
image = mode(images, axis=0)
image = (image / image.max() * 255).astype(np.uint8)
return image
def tint_image(src, color="#FFFFFF"):
src.load()
r, g, b, alpha = src.split()
gray = ImageOps.grayscale(src)
result = ImageOps.colorize(gray, (0, 0, 0), color)
result.putalpha(alpha)
return result
def color_shift_array(image: np.ndarray, color: Color):
"""Creates a 3-tensor from a 2-tensor by stacking the 2-tensor three times weighted by the color tuple."""
assert image.ndim == 3
assert image.shape[-1] == 3
assert isinstance(color, tuple)
assert max(color) <= 255
assert image.max() <= 255
color = np.array(color)
weights = color / color.sum() / 10
assert max(weights) <= 1
colored = (image * weights).astype(np.uint8)
assert colored.shape == image.shape
return colored
def superimpose(
base_image: Image,
image_to_superimpose: Image,
crop_to_content=True,
pad=True,
) -> Image:
"""Superimposes an image with transparency onto another image.
Args:
base_image: The page image.
image_to_superimpose: The texture image.
crop_to_content: If True, the texture will be cropped to content (i.e. the bounding box of all non-transparent
parts of the texture image).
pad: If True, the texture will be padded to the size of the page.
Returns:
Image where the texture is superimposed onto the page.
"""
base_image = normalize_image_format_to_pil(base_image)
image_to_superimpose = normalize_image_format_to_pil(image_to_superimpose)
if crop_to_content:
image_to_superimpose = image_to_superimpose.crop(image_to_superimpose.getbbox())
if base_image.size != image_to_superimpose.size:
logger.trace(f"Size of page and texture do not match: {base_image.size} != {image_to_superimpose.size}")
if pad:
logger.trace(f"Padding texture before pasting to fit size {base_image.size}")
image_to_superimpose = pad_image_to_size(image_to_superimpose, base_image.size)
else:
logger.trace(f"Resizing texture before pasting to fit size {base_image.size}")
image_to_superimpose = image_to_superimpose.resize(base_image.size)
assert base_image.size == image_to_superimpose.size
assert image_to_superimpose.mode == "RGBA"
base_image.paste(image_to_superimpose, (0, 0), image_to_superimpose)
return base_image
def pad_image_to_size(image: Image, size: Tuple[int, int]) -> Image:
"""Pads an image to a given size."""
if image.size == size:
return image
if image.size[0] > size[0] or image.size[1] > size[1]:
raise ValueError(f"Image size {image.size} is larger than target size {size}.")
padded = Image.new(image.mode, size, color=255)
pasting_coords = compute_pasting_coordinates(image, padded)
assert image.mode == "RGBA"
padded.paste(image, pasting_coords)
return padded
def compute_pasting_coordinates(smaller: Image, larger: Image.Image):
"""Computes the coordinates for centrally pasting a smaller image onto a larger image."""
return abs(larger.width - smaller.width) // 2, abs(larger.height - smaller.height) // 2