add new dither methods
This commit is contained in:
@ -4,13 +4,13 @@ version = "0.1.0"
|
|||||||
description = "Process images for Waveshare E6 display"
|
description = "Process images for Waveshare E6 display"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = ["pillow>=12.1.1"]
|
dependencies = ["numpy>=2.0", "pillow>=12.1.1"]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = ["ruff"]
|
dev = ["ruff"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 150
|
line-length = 180
|
||||||
target-version = "py313"
|
target-version = "py313"
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
|
|||||||
@ -6,127 +6,199 @@
|
|||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
from PIL import Image, ImageEnhance, ImageOps
|
from PIL import Image, ImageEnhance, ImageOps
|
||||||
|
|
||||||
# E Ink Spectra 6 six basic ink colors: Black, White, Red, Green, Blue, Yellow
|
|
||||||
SPECTRA6_PALETTE = [
|
class EPaperConverter:
|
||||||
0, 0, 0,
|
"""Converts photos for Waveshare E-Paper (E Ink Spectra 6) display output."""
|
||||||
255, 255, 255,
|
|
||||||
255, 0, 0,
|
# E Ink Spectra 6 palette: [R, G, B] triplets
|
||||||
0, 255, 0,
|
SPECTRA6_COLORS = [
|
||||||
0, 0, 255,
|
[0, 0, 0], # Black
|
||||||
255, 255, 0
|
[255, 255, 255], # White
|
||||||
|
[255, 0, 0], # Red
|
||||||
|
[0, 255, 0], # Green
|
||||||
|
[0, 0, 255], # Blue
|
||||||
|
[255, 255, 0], # Yellow
|
||||||
]
|
]
|
||||||
|
|
||||||
# Pad the palette to 256 colors (required by PIL), fill the rest with 0
|
BW_COLORS = [
|
||||||
SPECTRA6_PALETTE += [0] * (256 * 3 - len(SPECTRA6_PALETTE))
|
[0, 0, 0], # Black
|
||||||
|
[255, 255, 255], # White
|
||||||
|
]
|
||||||
|
|
||||||
|
# Optimization presets: {enhancement_attr: multiplier}
|
||||||
|
ENHANCE_PRESETS = {
|
||||||
|
'normal': {'brightness': 1.1, 'color': 1.5, 'contrast': 1.2, 'sharpness': 1.0},
|
||||||
|
'portrait': {'brightness': 1.2, 'color': 1.2, 'contrast': 1.05, 'sharpness': 1.0},
|
||||||
|
'landscape': {'brightness': 1.0, 'color': 1.8, 'contrast': 1.4, 'sharpness': 2.0},
|
||||||
|
}
|
||||||
|
|
||||||
def create_spectra_palette():
|
# Error diffusion kernels: list of (dy, dx, weight) tuples
|
||||||
"""Create a reference image with the Spectra 6 palette."""
|
DITHER_KERNELS = {
|
||||||
pal_img = Image.new('P', (1, 1))
|
'atkinson': [
|
||||||
pal_img.putpalette(SPECTRA6_PALETTE)
|
# * 1/8 1/8
|
||||||
return pal_img
|
# 1/8 1/8 1/8
|
||||||
|
# 1/8
|
||||||
|
# Only diffuses 75% of error → preserves highlights, cleaner look
|
||||||
|
(0, 1, 1 / 8), (0, 2, 1 / 8),
|
||||||
|
(1, -1, 1 / 8), (1, 0, 1 / 8), (1, 1, 1 / 8),
|
||||||
|
(2, 0, 1 / 8),
|
||||||
|
],
|
||||||
|
'stucki': [
|
||||||
|
# * 8/42 4/42
|
||||||
|
# 2/42 4/42 8/42 4/42 2/42
|
||||||
|
# 1/42 2/42 4/42 2/42 1/42
|
||||||
|
# Largest diffusion area → smoothest gradients
|
||||||
|
(0, 1, 8 / 42), (0, 2, 4 / 42),
|
||||||
|
(1, -2, 2 / 42), (1, -1, 4 / 42), (1, 0, 8 / 42), (1, 1, 4 / 42), (1, 2, 2 / 42),
|
||||||
|
(2, -2, 1 / 42), (2, -1, 2 / 42), (2, 0, 4 / 42), (2, 1, 2 / 42), (2, 2, 1 / 42),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, width=800, height=480, color='rgb', mode='normal', dither='floyd-steinberg'):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.color = color
|
||||||
|
self.mode = mode
|
||||||
|
self.dither = dither
|
||||||
|
|
||||||
def apply_epaper_gamma_correction(img, gamma=1.2):
|
# -- Image enhancement ---------------------------------------------------
|
||||||
"""
|
|
||||||
Apply E-paper specific Gamma correction.
|
def _apply_gamma_correction(self, img, gamma=1.3):
|
||||||
Since e-paper is non-emissive, dark areas tend to blend together.
|
"""Brighten midtones via gamma correction to compensate for e-paper's non-emissive display."""
|
||||||
This function brightens the midtones to rescue dark details.
|
|
||||||
"""
|
|
||||||
# Convert Gamma value to a lookup table for the PIL point function
|
|
||||||
inv_gamma = 1.0 / gamma
|
inv_gamma = 1.0 / gamma
|
||||||
table = [int((i / 255.0) ** inv_gamma * 255) for i in range(256)]
|
lut = [int((i / 255.0) ** inv_gamma * 255) for i in range(256)]
|
||||||
|
|
||||||
# Separate RGB channels and apply correction
|
|
||||||
if img.mode == 'RGB':
|
|
||||||
r, g, b = img.split()
|
r, g, b = img.split()
|
||||||
r = r.point(table)
|
return Image.merge('RGB', (r.point(lut), g.point(lut), b.point(lut)))
|
||||||
g = g.point(table)
|
|
||||||
b = b.point(table)
|
|
||||||
return Image.merge('RGB', (r, g, b))
|
|
||||||
|
|
||||||
return img.point(table)
|
|
||||||
|
|
||||||
|
|
||||||
def optimize_image(img, mode):
|
|
||||||
"""Apply extreme color compensation based on the selected mode to offset physical e-paper attenuation."""
|
|
||||||
|
|
||||||
# Apply base Gamma brightening for all color images before conversion
|
|
||||||
img = apply_epaper_gamma_correction(img, gamma=1.3)
|
|
||||||
|
|
||||||
if mode == 'normal':
|
|
||||||
# Normal mode: Boost saturation and contrast globally
|
|
||||||
img = ImageEnhance.Color(img).enhance(1.5) # Significantly increase vibrance
|
|
||||||
img = ImageEnhance.Contrast(img).enhance(1.2) # Strengthen light/dark contrast
|
|
||||||
img = ImageEnhance.Brightness(img).enhance(1.1) # Slightly brighten overall
|
|
||||||
|
|
||||||
elif mode == 'portrait':
|
|
||||||
# Portrait mode: Protect skin tones. Brighten faces, but limit saturation.
|
|
||||||
img = ImageEnhance.Brightness(img).enhance(1.2) # Faces must be bright enough
|
|
||||||
img = ImageEnhance.Color(img).enhance(1.2) # Moderately increase color
|
|
||||||
img = ImageEnhance.Contrast(img).enhance(1.05) # Maintain soft transitions
|
|
||||||
|
|
||||||
elif mode == 'landscape':
|
|
||||||
# Landscape mode: Maximize greens, blues, and 3D depth
|
|
||||||
img = ImageEnhance.Color(img).enhance(1.8) # Extreme vibrance for mountains and skies
|
|
||||||
img = ImageEnhance.Contrast(img).enhance(1.4) # Strong contrast for ridges and clouds
|
|
||||||
img = ImageEnhance.Sharpness(img).enhance(2.0) # Sharpen edge details
|
|
||||||
|
|
||||||
|
def _optimize(self, img):
|
||||||
|
"""Apply gamma correction and preset-based enhancement for e-paper output."""
|
||||||
|
img = self._apply_gamma_correction(img)
|
||||||
|
preset = self.ENHANCE_PRESETS[self.mode]
|
||||||
|
for attr, factor in preset.items():
|
||||||
|
if factor != 1.0:
|
||||||
|
enhancer_cls = getattr(ImageEnhance, attr.capitalize())
|
||||||
|
img = enhancer_cls(img).enhance(factor)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
# -- Dithering -----------------------------------------------------------
|
||||||
|
|
||||||
def main():
|
def _build_pil_palette(self, colors):
|
||||||
parser = argparse.ArgumentParser(description='Spectra 6 E-Paper Image Conversion Engine')
|
"""Build a PIL palette image from a list of [R, G, B] triplets."""
|
||||||
parser.add_argument('--img_path', type=str, required=True, help='Path to the original photo (e.g., my_photo.jpg)')
|
flat = [v for rgb in colors for v in rgb]
|
||||||
parser.add_argument(
|
flat += [0] * (256 * 3 - len(flat))
|
||||||
'--color', type=str, choices=['rgb', 'bw'], required=True, help="Color mode: 'rgb' (Full color Spectra 6) or 'bw' (Pure black and white)"
|
pal_img = Image.new('P', (1, 1))
|
||||||
)
|
pal_img.putpalette(flat)
|
||||||
parser.add_argument(
|
return pal_img
|
||||||
'--mode',
|
|
||||||
type=str,
|
|
||||||
choices=['normal', 'portrait', 'landscape'],
|
|
||||||
default='normal',
|
|
||||||
help="Optimization mode: 'normal', 'portrait', 'landscape'. Ignored if color is 'bw'.",
|
|
||||||
)
|
|
||||||
parser.add_argument('--width', type=int, default=800, help='E-paper width (default: 800)')
|
|
||||||
parser.add_argument('--height', type=int, default=480, help='E-paper height (default: 480)')
|
|
||||||
parser.add_argument('--out', type=str, default=None, help='Custom output filename (default: auto-generated)')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
def _error_diffusion_dither(self, img, palette_colors):
|
||||||
|
"""Apply custom error diffusion dithering (atkinson / stucki) using NumPy."""
|
||||||
|
kernel = self.DITHER_KERNELS[self.dither]
|
||||||
|
palette = np.array(palette_colors, dtype=np.float64)
|
||||||
|
pixels = np.array(img, dtype=np.float64)
|
||||||
|
h, w, _ = pixels.shape
|
||||||
|
|
||||||
img_path = Path(args.img_path)
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
old_pixel = pixels[y, x].copy()
|
||||||
|
distances = np.sum((palette - old_pixel) ** 2, axis=1)
|
||||||
|
new_pixel = palette[np.argmin(distances)]
|
||||||
|
pixels[y, x] = new_pixel
|
||||||
|
|
||||||
|
error = old_pixel - new_pixel
|
||||||
|
for dy, dx, weight in kernel:
|
||||||
|
ny, nx = y + dy, x + dx
|
||||||
|
if 0 <= ny < h and 0 <= nx < w:
|
||||||
|
pixels[ny, nx] += error * weight
|
||||||
|
|
||||||
|
return Image.fromarray(np.clip(pixels, 0, 255).astype(np.uint8), 'RGB')
|
||||||
|
|
||||||
|
def _dither_rgb(self, img):
|
||||||
|
"""Quantize an RGB image to the Spectra 6 palette."""
|
||||||
|
if self.dither == 'floyd-steinberg':
|
||||||
|
pal_img = self._build_pil_palette(self.SPECTRA6_COLORS)
|
||||||
|
return img.quantize(palette=pal_img, dither=Image.FLOYDSTEINBERG).convert('RGB')
|
||||||
|
return self._error_diffusion_dither(img, self.SPECTRA6_COLORS)
|
||||||
|
|
||||||
|
def _dither_bw(self, img):
|
||||||
|
"""Convert an image to black and white with dithering."""
|
||||||
|
img = ImageEnhance.Contrast(img).enhance(1.5)
|
||||||
|
if self.dither == 'floyd-steinberg':
|
||||||
|
return img.convert('1').convert('RGB')
|
||||||
|
return self._error_diffusion_dither(img, self.BW_COLORS)
|
||||||
|
|
||||||
|
# -- Public API ----------------------------------------------------------
|
||||||
|
|
||||||
|
def convert(self, img_path, output_path=None):
|
||||||
|
"""
|
||||||
|
Main conversion pipeline: load → resize → enhance → dither → save.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
img_path: Path to the source image.
|
||||||
|
output_path: Optional output path. Auto-generated if None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the saved output file.
|
||||||
|
"""
|
||||||
|
img_path = Path(img_path)
|
||||||
if not img_path.exists():
|
if not img_path.exists():
|
||||||
raise FileNotFoundError(f'Error: Image file not found: {img_path}')
|
raise FileNotFoundError(f'Image file not found: {img_path}')
|
||||||
|
|
||||||
print(f'Processing: {img_path}')
|
print(f'Processing: {img_path}')
|
||||||
img = Image.open(img_path).convert('RGB')
|
img = Image.open(img_path).convert('RGB')
|
||||||
|
|
||||||
# 1. Smart crop and resize to fill the frame
|
# Resize to fit the e-paper frame
|
||||||
img = ImageOps.fit(img, (args.width, args.height), Image.Resampling.LANCZOS)
|
img = ImageOps.fit(img, (self.width, self.height), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
# Generate automatic filename if not provided
|
# Enhance and dither
|
||||||
output_name = Path(args.out) if args.out else img_path.with_name(f'{img_path.stem}_{args.color}_{args.mode}.bmp')
|
print(f'Color: {self.color} | Mode: {self.mode} | Dither: {self.dither}')
|
||||||
|
if self.color == 'rgb':
|
||||||
|
img = self._optimize(img)
|
||||||
|
final_img = self._dither_rgb(img)
|
||||||
|
else:
|
||||||
|
final_img = self._dither_bw(img)
|
||||||
|
|
||||||
# 2. Core conversion logic
|
# Save as 24-bit RGB BMP (required by Waveshare e-paper frames)
|
||||||
if args.color == 'rgb':
|
if output_path is None:
|
||||||
print(f'Applying color optimization mode: {args.mode}')
|
output_path = img_path.with_name(f'{img_path.stem}_{self.color}_{self.mode}_{self.dither}.bmp')
|
||||||
img_optimized = optimize_image(img, args.mode)
|
else:
|
||||||
pal_img = create_spectra_palette()
|
output_path = Path(output_path)
|
||||||
# Convert and apply Floyd-Steinberg dithering, then back to 24-bit RGB
|
|
||||||
# Waveshare e-paper frames only recognize 24-bit RGB BMP files
|
|
||||||
final_img = img_optimized.quantize(palette=pal_img, dither=Image.FLOYDSTEINBERG).convert('RGB')
|
|
||||||
|
|
||||||
elif args.color == 'bw':
|
final_img.save(output_path)
|
||||||
print(f'Applying pure black and white mode (ignoring optimization mode: {args.mode})')
|
print(f'Saved: {output_path}')
|
||||||
# Black and white mode only needs basic contrast stretching
|
return output_path
|
||||||
img_bw = ImageEnhance.Contrast(img).enhance(1.5)
|
|
||||||
final_img = img_bw.convert('1').convert('RGB')
|
|
||||||
|
|
||||||
# 3. Save file (24-bit RGB BMP, required by Waveshare e-paper frames)
|
|
||||||
final_img.save(output_name)
|
# ---------------------------------------------------------------------------
|
||||||
print(f'Success! Image saved as: {output_name}')
|
# CLI
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser():
|
||||||
|
"""Build and return the argument parser."""
|
||||||
|
parser = argparse.ArgumentParser(description='Spectra 6 E-Paper Image Conversion Engine')
|
||||||
|
parser.add_argument('--img_path', type=str, required=True, help='Path to the original photo')
|
||||||
|
parser.add_argument('--color', type=str, choices=['rgb', 'bw'], required=True, help="'rgb' (Spectra 6) or 'bw' (pure black and white)")
|
||||||
|
parser.add_argument('--mode', type=str, choices=['normal', 'portrait', 'landscape'], default='normal', help='Optimization preset (default: normal)')
|
||||||
|
parser.add_argument('--dither', type=str, choices=['floyd-steinberg', 'atkinson', 'stucki'], default='floyd-steinberg', help='Dithering algorithm (default: floyd-steinberg)')
|
||||||
|
parser.add_argument('--width', type=int, default=800, help='E-paper width (default: 800)')
|
||||||
|
parser.add_argument('--height', type=int, default=480, help='E-paper height (default: 480)')
|
||||||
|
parser.add_argument('--out', type=str, default=None, help='Custom output filename')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = build_parser().parse_args()
|
||||||
|
converter = EPaperConverter(
|
||||||
|
width=args.width,
|
||||||
|
height=args.height,
|
||||||
|
color=args.color,
|
||||||
|
mode=args.mode,
|
||||||
|
dither=args.dither,
|
||||||
|
)
|
||||||
|
converter.convert(args.img_path, output_path=args.out)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
56
uv.lock
generated
56
uv.lock
generated
@ -2,6 +2,56 @@ version = 1
|
|||||||
revision = 3
|
revision = 3
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numpy"
|
||||||
|
version = "2.4.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "12.1.1"
|
version = "12.1.1"
|
||||||
@ -90,6 +140,7 @@ name = "waveshare-e6-image-processor"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "numpy" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -99,7 +150,10 @@ dev = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [{ name = "pillow", specifier = ">=12.1.1" }]
|
requires-dist = [
|
||||||
|
{ name = "numpy", specifier = ">=2.0" },
|
||||||
|
{ name = "pillow", specifier = ">=12.1.1" },
|
||||||
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [{ name = "ruff" }]
|
dev = [{ name = "ruff" }]
|
||||||
|
|||||||
Reference in New Issue
Block a user