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

1#!/usr/bin/env python3 

2 

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]_). 

11 

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``. 

18 

19 Returns 

20 ------- 

21 stream : bytearray 

22 The PNG image data stream. 

23 

24 Notes 

25 ----- 

26 

27 Copyright 2017 Thomas Guymer [1]_ 

28 

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 """ 

34 

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 

40 

41 # Import sub-functions ... 

42 from .paethFilter import paethFilter 

43 

44 # ************************************************************************** 

45 

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" 

58 

59 # ************************************************************************** 

60 

61 # Create short-hands ... 

62 ny, nx, nc = arrUint8.shape 

63 

64 # Initialize array and bytearray ... 

65 scanline = numpy.zeros( 

66 (nc, nx), 

67 dtype = numpy.uint8, 

68 ) 

69 stream = bytearray() 

70 

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() 

93 

94 # Return answer ... 

95 return stream