Coverage for pyguymer3/image/makePngSrc/createStreamPaeth.py: 87%
38 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
1#!/usr/bin/env python3
3# Define function ...
4def createStreamPaeth(
5 arrUint8,
6 arrInt16,
7 /,
8) -> bytearray:
9 """Create a PNG image data stream of an image using the "Paeth" filter (as
10 defined in the PNG specification [2]_).
12 Parameters
13 ----------
14 arrUint8 : numpy.ndarray
15 A "height * width * colour" unsigned 8-bit integer NumPy array.
16 arrInt16 : numpy.ndarray
17 A signed 16-bit integer NumPy array copy of ``arrUint8``.
19 Returns
20 -------
21 stream : bytearray
22 The PNG image data stream.
24 Notes
25 -----
27 Copyright 2017 Thomas Guymer [1]_
29 References
30 ----------
31 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3
32 .. [2] PNG Specification (Third Edition), https://www.w3.org/TR/png-3/
33 """
35 # Import special modules ...
36 try:
37 import numpy
38 except:
39 raise Exception("\"numpy\" is not installed; run \"pip install --user numpy\"") from None
41 # Import sub-functions ...
42 from .paethFilter import paethFilter
44 # **************************************************************************
46 # Check input ...
47 assert arrUint8.dtype == "uint8", f"the NumPy array is not 8-bit (\"{arrUint8.dtype}\")"
48 assert arrInt16.dtype == "int16", f"the NumPy array is not 16-bit (\"{arrInt16.dtype}\")"
49 assert arrUint8.ndim == 3, f"the NumPy array is not 3D (\"{arrUint8.ndim:d}\")"
50 match arrUint8.shape[2]:
51 case 1:
52 pass
53 case 3:
54 pass
55 case _:
56 raise ValueError(f"the NumPy array does not have either 1 or 3 colour channels (\"{arrUint8.shape[2]:d}\")") from None
57 assert arrUint8.shape == arrInt16.shape, "the NumPy arrays do not have the same shape"
59 # **************************************************************************
61 # Create short-hands ...
62 ny, nx, nc = arrUint8.shape
64 # Initialize array and bytearray ...
65 scanline = numpy.zeros(
66 (nc, nx),
67 dtype = numpy.uint8,
68 )
69 stream = bytearray()
71 # Loop over scanlines ...
72 for iy in range(ny):
73 # Calculate stream for "Paeth" filter ...
74 stream += numpy.uint8(4).tobytes()
75 for ix in range(nx):
76 for ic in range(nc):
77 if ix == 0:
78 p1 = numpy.int16(0)
79 else:
80 p1 = arrInt16[iy, ix - 1, ic]
81 if iy == 0:
82 p2 = numpy.int16(0)
83 else:
84 p2 = arrInt16[iy - 1, ix, ic]
85 if ix == 0 or iy == 0:
86 p3 = numpy.int16(0)
87 else:
88 p3 = arrInt16[iy - 1, ix - 1, ic]
89 diff = arrInt16[iy, ix, ic] - paethFilter(p1, p2, p3)
90 diff = numpy.mod(diff, 256)
91 scanline[ic, ix] = diff.astype(numpy.uint8)
92 stream += scanline[:, ix].tobytes()
94 # Return answer ...
95 return stream