Skip to content

Commit d42336b

Browse files
committed
Pre-release
1 parent a0e7a53 commit d42336b

File tree

3 files changed

+114
-112
lines changed

3 files changed

+114
-112
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ Improved robustness.
2828
Yet another final version 😉
2929

3030
1.16.1.9 General cleanup, minor speedup.
31+
32+
1.16.29.19 Added `list2pnm` function, previous `list2pnm` renamed to `list2pnmbin`.
33+
New `list2pnm` is a switch between `list2pnmbin` and `list2pnmascii`, controlled with `bin` bool; default is True that provides backward compatibility.

pypnm/pnmlpnm.py

Lines changed: 102 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@
55
Overview
66
---------
77
8-
pnmlpnm (pnm-list-pnm) module is a pack of functions for dealing with PPM and PGM image files.
8+
PyPNM module is a pack of functions for dealing with PPM and PGM image files.
99
Functions included are:
1010
11-
- pnm2list: reading binary or ascii RGB PPM or L PGM file and returning image data as nested list of int.
12-
- list2bin: getting image data as nested list of int
13-
and creating binary PPM (P6) or PGM (P5) data structure in memory.
14-
Suitable for generating data to display with Tkinter `PhotoImage(data=...)` class.
15-
- list2pnm: getting image data as nested list of int and writing binary PPM (P6) or PGM (P5) image file.
16-
Note that bytes generations procedure is optimized to save memory
17-
while working with large files and therefore is different from that used in 'list2bin'.
11+
- pnm2list: reading binary or ascii RGB PPM or L PGM file and returning image data
12+
as nested list of int.
13+
- list2bin: getting image data as nested list of int and creating binary PPM (P6) or PGM (P5)
14+
data structure in memory. Suitable for generating data to display with
15+
Tkinter `PhotoImage(data=...)` class.
16+
- list2pnmbin: getting image data as nested list of int and writing binary PPM (P6) or PGM (P5) image file.
17+
Note that bytes generations procedure is optimized to save memory while working with large files and
18+
therefore is different from that used in 'list2bin'.
1819
- list2pnmascii: alternative function to write ASCII PPM (P3) or PGM (P2) files.
20+
- list2pnm: getting image data as nested list of int and writing either binary or ASCII PNM
21+
depending on `bin` argument value.
1922
- create_image: creating empty nested 3D list for image representation.
2023
Not used within this particular module but often needed by programs this module is supposed to be used with.
2124
@@ -34,15 +37,16 @@
3437
3538
Usage
3639
------
37-
After `import pnmlpnm`, use something like:
40+
41+
After `from pypnm import pnmlpnm`, use something like:
3842
3943
`X, Y, Z, maxcolors, list_3d = pnmlpnm.pnm2list(in_filename)`
4044
4145
for reading data from PPM/PGM, where:
4246
43-
- `X`, `Y`, `Z`: image dimensions (int);
44-
- `maxcolors`: number of colors per channel for current image (int);
45-
- `list_3d`: image pixel data as list(list(list(int)));
47+
- `X`, `Y`, `Z`: image dimensions (int);
48+
- `maxcolors`: number of colors per channel for current image (int);
49+
- `list_3d`: image pixel data as list(list(list(int)));
4650
4751
and:
4852
@@ -52,39 +56,35 @@
5256
5357
or:
5458
55-
`pnmlpnm.list2pnm(out_filename, list_3d, maxcolors)`
56-
57-
for writing data from `list_3d` nested list to binary PPM/PGM file `out_filename`, or:
58-
59-
`pnmlpnm.list2pnmascii(out_filename, list_3d, maxcolors)`
60-
61-
for writing data from `list_3d` nested list to ASCII PPM/PGM file `out_filename`.
59+
`pnmlpnm.list2pnm(out_filename, list_3d, maxcolors, bin)`
6260
61+
for writing data from `list_3d` nested list to PPM/PGM file `out_filename`,
62+
where `bin` is a bool switch defining where resulting file will be binary or ASCII.
6363
6464
Copyright and redistribution
6565
-----------------------------
66+
6667
Written by `Ilya Razmanov <https://dnyarri.github.io/>`_ to facilitate working with PPM/PGM files
67-
and converting image-like data to PPM/PGM bytes to be displayed with Tkinter `PhotoImage` class.
68+
and displaying arbitrary image-like data with Tkinter `PhotoImage` class.
6869
6970
May be freely used, redistributed and modified.
7071
In case of introducing useful modifications, please report to the developer.
7172
7273
References
7374
-----------
7475
75-
`Netpbm specifications <https://netpbm.sourceforge.net/doc/>`_
76-
77-
`PyPNM at GitHub <https://github.com/Dnyarri/PyPNM/>`_
78-
79-
`PyPNM at PyPI <https://pypi.org/project/PyPNM/>`_
76+
1. Netpbm specifications: https://netpbm.sourceforge.net/doc/
77+
2. PyPNM at GitHub: https://github.com/Dnyarri/PyPNM/
78+
3. PyPNM at PyPI: https://pypi.org/project/PyPNM/
79+
4. PyPNM Documentation: https://dnyarri.github.io/pypnm/pypnm.pdf
8080
8181
"""
8282

8383
__author__ = 'Ilya Razmanov'
8484
__copyright__ = '(c) 2024-2025 Ilya Razmanov'
8585
__credits__ = 'Ilya Razmanov'
8686
__license__ = 'unlicense'
87-
__version__ = '1.16.1.9'
87+
__version__ = '1.16.29.19'
8888
__maintainer__ = 'Ilya Razmanov'
8989
__email__ = 'ilyarazmanov@gmail.com'
9090
__status__ = 'Production'
@@ -100,17 +100,16 @@
100100
def pnm2list(in_filename: str) -> tuple[int, int, int, int, list[list[list[int]]]]:
101101
"""Read PGM or PPM file to nested image data list.
102102
103-
Usage
104-
-
103+
Usage:
105104
106105
`X, Y, Z, maxcolors, list_3d = pnmlpnm.pnm2list(in_filename)`
107106
108107
for reading data from PPM/PGM, where:
109108
110-
- `X`, `Y`, `Z`: image dimensions (int);
111-
- `maxcolors`: number of colors per channel for current image (int);
112-
- `list_3d`: image pixel data as list(list(list(int)));
113-
- `in_filename`: PPM/PGM file name (str).
109+
- `X`, `Y`, `Z`: image dimensions (int);
110+
- `maxcolors`: number of colors per channel for current image (int);
111+
- `list_3d`: image pixel data as list(list(list(int)));
112+
- `in_filename`: PPM/PGM file name (str).
114113
115114
"""
116115

@@ -274,6 +273,8 @@ def pnm2list(in_filename: str) -> tuple[int, int, int, int, list[list[list[int]]
274273

275274
else:
276275
raise ValueError(f'Unsupported format {in_filename}: {full_bytes[:32]}')
276+
277+
277278
# End of pnm2list PNM reading function
278279

279280

@@ -287,20 +288,16 @@ def list2bin(list_3d: list[list[list[int]]], maxcolors: int, show_chessboard: bo
287288
288289
Based on `Netpbm specifications<https://netpbm.sourceforge.net/doc/>`_.
289290
290-
Usage
291-
-
291+
Usage:
292292
293-
`image_bytes = pnmlpnm.list2bin(list_3d, maxcolors, show_chessboard)` where:
293+
`image_bytes = pnmlpnm.list2bin(list_3d, maxcolors, show_chessboard)`
294294
295-
Input:
295+
where:
296296
297-
- `list_3d`: Y * X * Z list (image) of lists (rows) of lists (pixels) of ints (channel values);
298-
- `maxcolors`: number of colors per channel for current image (int);
299-
- `show_chessboard`: optional bool, set `True` to show LA and RGBA images against chessboard pattern; `False` or missing show existing L or RGB data for transparent areas as opaque. Default is `False` for backward compatibility.
300-
301-
Output:
302-
303-
- `image_bytes`: PNM-structured binary data.
297+
- `list_3d`: Y * X * Z list (image) of lists (rows) of lists (pixels) of ints (channel values);
298+
- `maxcolors`: number of colors per channel for current image (int);
299+
- `show_chessboard`: optional bool, set `True` to show LA and RGBA images against chessboard pattern; `False` or missing show existing L or RGB data for transparent areas as opaque. Default is `False` for backward compatibility.
300+
- `image_bytes`: PNM-structured binary data.
304301
305302
"""
306303

@@ -309,7 +306,7 @@ def _chess(x: int, y: int) -> int:
309306
310307
Photoshop chess pattern preset parameters:
311308
Small: 4 px; Medium: 8 px, Large: 16 px
312-
Light: 0.8, 1; Medium: 0.4, 0.6; Dark: 0.2, 0.4 of maxcolors
309+
Light: (0.8, 1.0); Medium: (0.4, 0.6); Dark: (0.2, 0.4) of maxcolors
313310
314311
"""
315312
return int(maxcolors * 0.8) if ((y // 8) % 2) == ((x // 8) % 2) else maxcolors
@@ -330,7 +327,7 @@ def _chess(x: int, y: int) -> int:
330327
list_1d = [list_3d[y][x][z] for y in range(Y) for x in range(X) for z in range(Z_READ)]
331328

332329
else: # Source has alpha
333-
Z_READ = min(Z, 4) - 1 # To fiddle with alpha; clipping anything above RGBA off
330+
Z_READ = min(Z, 4) - 1 # To fiddle with alpha; clipping anything above RGB off
334331

335332
if show_chessboard:
336333
# Flattening 3D list to 1D list, mixing with chessboard
@@ -353,25 +350,28 @@ def _chess(x: int, y: int) -> int:
353350
content.byteswap() # Critical for 16 bits per channel
354351

355352
return b''.join((f'{magic}\n{X} {Y}\n{maxcolors}\n'.encode('ascii'), content.tobytes()))
353+
354+
356355
# End of 'list2bin' list to in-memory PNM conversion function
357356

358357

359-
""" ╔══════════╗
360-
list2pnm
361-
╚══════════╝ """
358+
""" ╔═════════════
359+
list2pnmbin
360+
╚═════════════╝ """
362361

363362

364-
def list2pnm(out_filename: str, list_3d: list[list[list[int]]], maxcolors: int) -> None:
363+
def list2pnmbin(out_filename: str, list_3d: list[list[list[int]]], maxcolors: int) -> None:
365364
"""Write binary PNM `out_filename` file; writing performed per row to reduce RAM usage.
366365
367-
Usage
368-
-
366+
Usage:
367+
368+
`pnmlpnm.list2pnm(out_filename, list_3d, maxcolors)`
369369
370-
`pnmlpnm.list2pnm(out_filename, list_3d, maxcolors)` where:
370+
where:
371371
372-
- `list_3d`: X * Y * Z list (image) of lists (rows) of lists (pixels) of ints (channels);
373-
- `maxcolors`: number of colors per channel for current image (int);
374-
- `out_filename`: PNM file name.
372+
- `list_3d`: X * Y * Z list (image) of lists (rows) of lists (pixels) of ints (channels);
373+
- `maxcolors`: number of colors per channel for current image (int);
374+
- `out_filename`: PNM file name.
375375
376376
"""
377377

@@ -388,7 +388,7 @@ def list2pnm(out_filename: str, list_3d: list[list[list[int]]], maxcolors: int)
388388
if Z == 3 or Z == 1:
389389
Z_READ = Z
390390
else:
391-
Z_READ = min(Z, 4) - 1 # To skip alpha later
391+
Z_READ = min(Z, 4) - 1 # To skip alpha later; clipping anything above RGB off
392392

393393
if maxcolors < 256:
394394
datatype = 'B'
@@ -404,6 +404,8 @@ def list2pnm(out_filename: str, list_3d: list[list[list[int]]], maxcolors: int)
404404
file_pnm.write(row_array) # Adding row bytes array to file
405405

406406
return None
407+
408+
407409
# End of 'list2pnm' function writing binary PPM/PGM file
408410

409411

@@ -415,14 +417,15 @@ def list2pnm(out_filename: str, list_3d: list[list[list[int]]], maxcolors: int)
415417
def list2pnmascii(out_filename: str, list_3d: list[list[list[int]]], maxcolors: int) -> None:
416418
"""Write ASCII PNM `out_filename` file; writing performed per sample to reduce RAM usage.
417419
418-
Usage
419-
-
420+
Usage:
421+
422+
`pnmlpnm.list2pnmascii(out_filename, list_3d, maxcolors)`
420423
421-
`pnmlpnm.list2pnmascii(out_filename, list_3d, maxcolors)` where:
424+
where:
422425
423-
- `list_3d`: Y * X * Z list (image) of lists (rows) of lists (pixels) of ints (channels);
424-
- `maxcolors`: number of colors per channel for current image (int);
425-
- `out_filename`: PNM file name.
426+
- `list_3d`: Y * X * Z list (image) of lists (rows) of lists (pixels) of ints (channels);
427+
- `maxcolors`: number of colors per channel for current image (int);
428+
- `out_filename`: PNM file name.
426429
427430
"""
428431

@@ -431,22 +434,13 @@ def list2pnmascii(out_filename: str, list_3d: list[list[list[int]]], maxcolors:
431434
X = len(list_3d[0])
432435
Z = len(list_3d[0][0])
433436

434-
if Z == 1: # L image
437+
if Z < 3: # L or LA image
435438
magic = 'P2'
436439
Z_READ = 1
437-
438-
if Z == 2: # LA image
439-
magic = 'P2'
440-
Z_READ = 1 # To skip alpha later
441-
442-
if Z == 3: # RGB image
440+
else: # RGB or RGBA image
443441
magic = 'P3'
444442
Z_READ = 3
445443

446-
if Z > 3: # RGBA image
447-
magic = 'P3'
448-
Z_READ = 3 # To skip alpha later
449-
450444
with open(out_filename, 'w') as file_pnm:
451445
file_pnm.write(f'{magic}\n{X} {Y}\n{maxcolors}\n') # Writing PNM header to file
452446
sample_count = 0 # Start counting samples to break line <= 60 char
@@ -459,9 +453,42 @@ def list2pnmascii(out_filename: str, list_3d: list[list[list[int]]], maxcolors:
459453
file_pnm.write(f'{list_3d[y][x][z]} ') # Writing channel value to file
460454

461455
return None
456+
457+
462458
# End of 'list2pnmascii' function writing ASCII PPM/PGM file
463459

464460

461+
""" ╔══════════╗
462+
║ list2pnm ║
463+
╚══════════╝ """
464+
465+
466+
def list2pnm(out_filename: str, list_3d: list[list[list[int]]], maxcolors: int, bin: bool = True) -> None:
467+
"""Write PNM `out_filename` file using either `list2pnmbin` or `list2pnmascii` depending on `bin` switch.
468+
469+
Usage:
470+
471+
`pnmlpnm.list2pnm(out_filename, list_3d, maxcolors, bin)`
472+
473+
where:
474+
475+
- `list_3d`: X * Y * Z list (image) of lists (rows) of lists (pixels) of ints (channels);
476+
- `maxcolors`: number of colors per channel for current image (int);
477+
- `bin`: whether output file is binary (bool);
478+
- `out_filename`: PNM file name.
479+
480+
"""
481+
if bin:
482+
list2pnmbin(out_filename, list_3d, maxcolors)
483+
else:
484+
list2pnmascii(out_filename, list_3d, maxcolors)
485+
486+
return None
487+
488+
489+
# End of 'list2pnm' switch function writing any type of PPM/PGM file
490+
491+
465492
""" ╔════════════════════╗
466493
║ Create empty image ║
467494
╚════════════════════╝ """
@@ -477,6 +504,8 @@ def create_image(X: int, Y: int, Z: int) -> list[list[list[int]]]:
477504
]
478505

479506
return new_image
507+
508+
480509
# End of 'create_image' empty nested 3D list creation
481510

482511

0 commit comments

Comments
 (0)