import numpy as np
from typing import Tuple
# Define a 5x5 pixel font for uppercase letters, numbers, and some punctuation
FONT = {
'A': np.array([
[0,1,1,0,0],
[1,0,0,1,0],
[1,1,1,1,0],
[1,0,0,1,0],
[1,0,0,1,0]
]),
'B': np.array([
[1,1,1,0,0],
[1,0,0,1,0],
[1,1,1,0,0],
[1,0,0,1,0],
[1,1,1,0,0]
]),
'C': np.array([
[0,1,1,1,0],
[1,0,0,0,0],
[1,0,0,0,0],
[1,0,0,0,0],
[0,1,1,1,0]
]),
'D': np.array([
[1,1,1,0,0],
[1,0,0,1,0],
[1,0,0,1,0],
[1,0,0,1,0],
[1,1,1,0,0]
]),
'E': np.array([
[1,1,1,1,0],
[1,0,0,0,0],
[1,1,1,0,0],
[1,0,0,0,0],
[1,1,1,1,0]
]),
'F': np.array([
[1,1,1,1,0],
[1,0,0,0,0],
[1,1,1,0,0],
[1,0,0,0,0],
[1,0,0,0,0]
]),
'G': np.array([
[0,1,1,1,0],
[1,0,0,0,0],
[1,0,1,1,0],
[1,0,0,1,0],
[0,1,1,1,0]
]),
'H': np.array([
[1,0,0,1,0],
[1,0,0,1,0],
[1,1,1,1,0],
[1,0,0,1,0],
[1,0,0,1,0]
]),
'I': np.array([
[1,1,1,0,0],
[0,1,0,0,0],
[0,1,0,0,0],
[0,1,0,0,0],
[1,1,1,0,0]
]),
'J': np.array([
[0,0,1,1,0],
[0,0,0,1,0],
[0,0,0,1,0],
[1,0,0,1,0],
[0,1,1,0,0]
]),
'K': np.array([
[1,0,0,1,0],
[1,0,1,0,0],
[1,1,0,0,0],
[1,0,1,0,0],
[1,0,0,1,0]
]),
'L': np.array([
[1,0,0,0,0],
[1,0,0,0,0],
[1,0,0,0,0],
[1,0,0,0,0],
[1,1,1,1,0]
]),
'M': np.array([
[1,0,0,0,1],
[1,1,0,1,1],
[1,0,1,0,1],
[1,0,0,0,1],
[1,0,0,0,1]
]),
'N': np.array([
[1,0,0,0,1],
[1,1,0,0,1],
[1,0,1,0,1],
[1,0,0,1,1],
[1,0,0,0,1]
]),
'O': np.array([
[0,1,1,1,0],
[1,0,0,0,1],
[1,0,0,0,1],
[1,0,0,0,1],
[0,1,1,1,0]
]),
'P': np.array([
[1,1,1,0,0],
[1,0,0,1,0],
[1,1,1,0,0],
[1,0,0,0,0],
[1,0,0,0,0]
]),
'Q': np.array([
[0,1,1,0,0],
[1,0,0,1,0],
[1,0,0,1,0],
[1,0,1,0,0],
[0,1,0,1,0]
]),
'R': np.array([
[1,1,1,0,0],
[1,0,0,1,0],
[1,1,1,0,0],
[1,0,1,0,0],
[1,0,0,1,0]
]),
'S': np.array([
[0,1,1,1,0],
[1,0,0,0,0],
[0,1,1,0,0],
[0,0,0,1,0],
[1,1,1,0,0]
]),
'T': np.array([
[1,1,1,1,1],
[0,0,1,0,0],
[0,0,1,0,0],
[0,0,1,0,0],
[0,0,1,0,0]
]),
'U': np.array([
[1,0,0,0,1],
[1,0,0,0,1],
[1,0,0,0,1],
[1,0,0,0,1],
[0,1,1,1,0]
]),
'V': np.array([
[1,0,0,0,1],
[1,0,0,0,1],
[1,0,0,0,1],
[0,1,0,1,0],
[0,0,1,0,0]
]),
'W': np.array([
[1,0,0,0,1],
[1,0,0,0,1],
[1,0,1,0,1],
[1,0,1,0,1],
[0,1,0,1,0]
]),
'X': np.array([
[1,0,0,0,1],
[0,1,0,1,0],
[0,0,1,0,0],
[0,1,0,1,0],
[1,0,0,0,1]
]),
'Y': np.array([
[1,0,0,0,1],
[0,1,0,1,0],
[0,0,1,0,0],
[0,0,1,0,0],
[0,0,1,0,0]
]),
'Z': np.array([
[1,1,1,1,1],
[0,0,0,1,0],
[0,0,1,0,0],
[0,1,0,0,0],
[1,1,1,1,1]
]),
'0': np.array([
[0,1,1,1,0],
[1,0,0,0,1],
[1,0,0,0,1],
[1,0,0,0,1],
[0,1,1,1,0]
]),
'1': np.array([
[0,0,1,0,0],
[0,1,1,0,0],
[0,0,1,0,0],
[0,0,1,0,0],
[0,1,1,1,0]
]),
'2': np.array([
[0,1,1,1,0],
[1,0,0,0,1],
[0,0,1,1,0],
[0,1,0,0,0],
[1,1,1,1,1]
]),
'3': np.array([
[1,1,1,1,0],
[0,0,0,0,1],
[0,1,1,1,0],
[0,0,0,0,1],
[1,1,1,1,0]
]),
'4': np.array([
[0,0,1,1,0],
[0,1,0,1,0],
[1,0,0,1,0],
[1,1,1,1,1],
[0,0,0,1,0]
]),
'5': np.array([
[1,1,1,1,1],
[1,0,0,0,0],
[1,1,1,1,0],
[0,0,0,0,1],
[1,1,1,1,0]
]),
'6': np.array([
[0,1,1,1,0],
[1,0,0,0,0],
[1,1,1,1,0],
[1,0,0,0,1],
[0,1,1,1,0]
]),
'7': np.array([
[1,1,1,1,1],
[0,0,0,0,1],
[0,0,0,1,0],
[0,0,1,0,0],
[0,1,0,0,0]
]),
'8': np.array([
[0,1,1,1,0],
[1,0,0,0,1],
[0,1,1,1,0],
[1,0,0,0,1],
[0,1,1,1,0]
]),
'9': np.array([
[0,1,1,1,0],
[1,0,0,0,1],
[0,1,1,1,1],
[0,0,0,0,1],
[0,1,1,1,0]
]),
'.': np.array([
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,1,0,0]
]),
',': np.array([
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,1,0,0],
[0,1,0,0,0]
]),
'!': np.array([
[0,0,1,0,0],
[0,0,1,0,0],
[0,0,1,0,0],
[0,0,0,0,0],
[0,0,1,0,0]
]),
'?': np.array([
[0,1,1,1,0],
[1,0,0,0,1],
[0,0,0,1,0],
[0,0,0,0,0],
[0,0,1,0,0]
]),
'-': np.array([
[0,0,0,0,0],
[0,0,0,0,0],
[1,1,1,1,0],
[0,0,0,0,0],
[0,0,0,0,0]
]),
'+': np.array([
[0,0,1,0,0],
[0,0,1,0,0],
[1,1,1,1,1],
[0,0,1,0,0],
[0,0,1,0,0]
]),
'=': np.array([
[0,0,0,0,0],
[1,1,1,1,0],
[0,0,0,0,0],
[1,1,1,1,0],
[0,0,0,0,0]
]),
':': np.array([
[0,0,0,0,0],
[0,0,1,0,0],
[0,0,0,0,0],
[0,0,1,0,0],
[0,0,0,0,0]
]),
';': np.array([
[0,0,0,0,0],
[0,0,1,0,0],
[0,0,0,0,0],
[0,0,1,0,0],
[0,1,0,0,0]
]),
'(': np.array([
[0,0,1,0,0],
[0,1,0,0,0],
[0,1,0,0,0],
[0,1,0,0,0],
[0,0,1,0,0]
]),
')': np.array([
[0,0,1,0,0],
[0,0,0,1,0],
[0,0,0,1,0],
[0,0,0,1,0],
[0,0,1,0,0]
]),
'[': np.array([
[0,1,1,0,0],
[0,1,0,0,0],
[0,1,0,0,0],
[0,1,0,0,0],
[0,1,1,0,0]
]),
']': np.array([
[0,0,1,1,0],
[0,0,0,1,0],
[0,0,0,1,0],
[0,0,0,1,0],
[0,0,1,1,0]
]),
'/': np.array([
[0,0,0,0,1],
[0,0,0,1,0],
[0,0,1,0,0],
[0,1,0,0,0],
[1,0,0,0,0]
]),
'\\': np.array([
[1,0,0,0,0],
[0,1,0,0,0],
[0,0,1,0,0],
[0,0,0,1,0],
[0,0,0,0,1]
]),
' ': np.zeros((5, 5)),
'^': np.array([
[0,0,1,0,0],
[0,1,0,1,0],
[1,0,0,0,1],
[0,0,0,0,0],
[0,0,0,0,0]
])
}
[docs]def render_text(text: str, scale: int = 1) -> np.ndarray:
"""Render the entire text as a single numpy array."""
if not text:
return np.zeros((5 * scale, 1), dtype=np.uint8) # Return a minimal array for empty text
char_arrays = [FONT.get(char.upper(), np.zeros((5, 5))) for char in text]
text_array = np.hstack(char_arrays)
return np.kron(text_array, np.ones((scale, scale)))
[docs]def vectorized_text(
img_array: np.ndarray,
text: str,
position: Tuple[int, int],
color: Tuple[int, int, int] = (255, 255, 255),
font_size: float = 1,
spacing: float = 1.0
) -> np.ndarray:
"""
Render text onto a NumPy array using optimized vectorized operations.
Args:
img_array (np.ndarray): The input image as a NumPy array.
text (str): The text to render.
position (Tuple[int, int]): The (x, y) position to place the text.
color (Tuple[int, int, int]): RGB color of the text.
font_size (float): Font size, similar to CV2's font scale.
spacing (float): Spacing between characters.
Returns:
np.ndarray: The image array with the text rendered on it.
"""
x, y = position
# Calculate scale based on font_size
scale = int(font_size*2)
# if scale < 1:
# scale = 1
# Render the entire text at once
text_array = render_text(text, scale)
# Add spacing between characters
if spacing > 0 and text:
char_width = 5 * scale
# Calculate total width with fractional spacing
total_width = int(text_array.shape[1] + spacing * (len(text) - 1) * char_width)
spaced_text_array = np.zeros((text_array.shape[0], total_width), dtype=text_array.dtype)
# Adjust spacing for '.' character
char_positions = []
current_position = 0
for char in text:
char_positions.append(current_position)
if char == '.':
current_position += char_width # No additional spacing for '.'
else:
current_position += char_width + int(spacing * char_width)
char_positions = np.array(char_positions)
spaced_text_array[:, char_positions[:, None] + np.arange(char_width)] = text_array.reshape(text_array.shape[0], -1, char_width)
text_array = spaced_text_array
# Calculate the region where the text will be placed
y_start = max(0, y)
x_start = max(0, x)
y_end = min(img_array.shape[0], y + text_array.shape[0])
x_end = min(img_array.shape[1], x + text_array.shape[1])
# Check if the text is completely out of bounds
if y_end <= y_start or x_end <= x_start:
return img_array # Text is completely out of bounds, return original image
# Calculate the visible portion of the text array
text_y_start = max(0, -y)
text_x_start = max(0, -x)
text_y_end = text_y_start + (y_end - y_start)
text_x_end = text_x_start + (x_end - x_start)
# Crop text_array to the visible portion
visible_text_array = text_array[text_y_start:text_y_end, text_x_start:text_x_end]
# Get the section of the image we're working with
img_section = img_array[y_start:y_end, x_start:x_end]
# Create a mask for the text, matching the dimensions of img_section
mask = np.repeat(visible_text_array[:, :, np.newaxis], img_section.shape[2], axis=2)
# Prepare the color array
color_array = np.array(color)
if img_section.shape[2] == 4: # If the image has an alpha channel
color_array = np.append(color_array, 255) # Add full opacity
# Create a color overlay
color_overlay = np.tile(color_array, (mask.shape[0], mask.shape[1], 1))
# Blend the text with the image
blended = img_section * (1 - mask) + color_overlay * mask
# Assign the blended result back to the image array
img_array[y_start:y_end, x_start:x_end] = blended.astype(img_array.dtype)
return img_array