Skip to content

Commit 088eaf5

Browse files
committed
PyPNG 0.20231004.0
1 parent 1337f0f commit 088eaf5

File tree

1 file changed

+75
-88
lines changed

1 file changed

+75
-88
lines changed

pypng/pnglpng.py

Lines changed: 75 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,75 @@
11
#!/usr/bin/env python3
22

3-
"""Joint between PyPNG module and 3D nested list data structures.
4-
NOTE: This is special Python 3.4 version!
3+
"""
4+
============
5+
PNG-list-PNG
6+
============
7+
--------------------------------------
8+
Joint between PyPNG and other programs
9+
--------------------------------------
10+
11+
Created by: `Ilya Razmanov<mailto:ilyarazmanov@gmail.com>`_
12+
aka `Ilyich the Toad<mailto:amphisoft@gmail.com>`_.
513
614
Overview
715
---------
816
9-
`pnglpng` (png-list-png) is a suitable joint between PyPNG and other Python programs, providing data conversion from/to used by PyPNG to/from understandable by ordinary average human.
17+
**pnglpng** (png-list-png) is a suitable joint between `PyPNG`_
18+
and other Python programs, providing data conversion from/to used by PyPNG
19+
to/from understandable by ordinary average human.
1020
11-
- `png2list`: reading PNG file and returning all data.
12-
- `list2png`: getting data and writing PNG file.
13-
- `create_image`: creating empty nested 3D list for image representation.
21+
Functions included are:
22+
23+
:png2list: reading PNG file and returning all data;
24+
:list2png: getting data and writing PNG file;
25+
:create_image: creating empty nested 3D list for image representation.
1426
1527
Installation
1628
-------------
17-
Should be kept together with png.py module. See `import` for detail.
29+
30+
Should be kept together with ``png.py`` module. See ``import`` for detail.
1831
1932
Usage
2033
------
21-
After `import pnglpng`, use something like:
22-
23-
`X, Y, Z, maxcolors, list_3d, info = pnglpng.png2list(in_filename)`
24-
25-
for reading data from `in_filename` PNG, where:
26-
27-
- `X`, `Y`, `Z` - image dimensions (int);
28-
- `maxcolors` - number of colors per channel for current image (int);
29-
- `list_3d` - image pixel data as list(list(list(int)));
30-
- `info` - PNG chunks like resolution etc (dictionary);
31-
32-
and:
3334
34-
`pnglpng.list2png(out_filename, list_3d, info)`
35+
After ``import pnglpng``, use something like
3536
36-
for writing data to `out_filename` PNG.
37+
::
3738
38-
Copyright and redistribution
39-
-----------------------------
40-
Written by `Ilya Razmanov <https://dnyarri.github.io/>`_ to simplify working with PyPNG module.
41-
May be freely used and redistributed.
39+
X, Y, Z, maxcolors, list_3d, info = pnglpng.png2list(in_filename)
4240
43-
Prerequisites and References
44-
-----------------------------
45-
`PyPNG download <https://gitlab.com/drj11/pypng>`_
41+
for reading data from ``in_filename`` PNG, where:
4642
47-
`PyPNG docs <https://drj11.gitlab.io/pypng>`_
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)));
46+
:info: PNG chunks like resolution etc (dictionary);
4847
49-
History
50-
--------
48+
and
5149
52-
24.07.25 Initial version.
50+
::
5351
54-
24.07.26 Fixed missing Z autodetection for `info` generation.
52+
pnglpng.list2png(out_filename, list_3d, info)
5553
56-
24.10.01 Internal restructure, incompatible with previous version.
54+
for writing data to ``out_filename`` PNG.
5755
58-
25.01.08 `list2png` - force rewrite more `info` parameters with those detected from 3D list;
59-
force remove `palette` due to accumulating problems with images promoted to full color,
60-
and `background` due to rare problems with changing color mode.
56+
Prerequisites and References
57+
----------------------------
6158
62-
25.02.06 PNG `write_array` replaced with `write` to replace flattening to 1D list
63-
with 2D rows generator, thus reducing RAM usage and eliminating memory errors on huge files.
64-
Fixed missing `maxcolors` for 1-bit images.
59+
1. `PyPNG`_
60+
2. `PyPNG docs`_
6561
66-
25.03.01 Improved robustness, `list2png` is skipping any possible input channel above 4-th.
62+
.. _PyPNG: https://gitlab.com/drj11/pypng
6763
68-
25.03.01.34 Special build of 25.03.01 version, downgraded to Python 3.4.
64+
.. _PyPNG docs: https://drj11.gitlab.io/pypng
6965
7066
"""
7167

7268
__author__ = 'Ilya Razmanov'
7369
__copyright__ = '(c) 2024-2025 Ilya Razmanov'
7470
__credits__ = 'Ilya Razmanov'
7571
__license__ = 'unlicense'
76-
__version__ = '25.03.01.34'
72+
__version__ = '25.11.11.34'
7773
__maintainer__ = 'Ilya Razmanov'
7874
__email__ = 'ilyarazmanov@gmail.com'
7975
__status__ = 'Production'
@@ -89,17 +85,18 @@ def png2list(in_filename):
8985
"""Take PNG filename and return PNG data in a human-friendly form.
9086
9187
Usage
92-
------
9388
94-
`X, Y, Z, maxcolors, list_3d, info = pnglpng.png2list(in_filename)`
89+
::
9590
96-
Takes PNG filename `in_filename` and returns the following tuple:
91+
X, Y, Z, maxcolors, list_3d, info = pnglpng.png2list(in_filename)
9792
98-
- `X`, `Y`, `Z`: PNG image dimensions (int);
99-
- `maxcolors`: number of colors per channel for current image (int),
100-
either 255 or 65535, for 8 bpc and 16 bpc PNG respectively;
101-
- `list_3d`: Y * X * Z list (image) of lists (rows) of lists (pixels) of ints (channels), from PNG iDAT;
102-
- `info`: dictionary from PNG chunks like resolution etc. as they are accessible by PyPNG.
93+
Takes PNG filename ``in_filename`` and return the following tuple
94+
95+
:X, Y, Z: PNG image dimensions (int);
96+
:maxcolors: number of colors per channel for current image (int),
97+
either 1, or 255, or 65535, for 1 bpc, 8 bpc and 16 bpc PNG respectively;
98+
:list_3d: Y * X * Z list (image) of lists (rows) of lists (pixels) of ints (channels), from PNG iDAT;
99+
:info: dictionary from PNG chunks like resolution etc. as they are accessible by PyPNG.
103100
104101
"""
105102

@@ -118,13 +115,7 @@ def png2list(in_filename):
118115
imagedata = tuple(pixels) # Freezes tuple of bytes or whatever "pixels" generator returns
119116

120117
# Forcedly create 3D list of int out of "imagedata" tuple of hell knows what
121-
list_3d = [
122-
[
123-
[
124-
int((imagedata[y])[(x * Z) + z]) for z in range(Z)
125-
] for x in range(X)
126-
] for y in range(Y)
127-
]
118+
list_3d = [[[int((imagedata[y])[(x * Z) + z]) for z in range(Z)] for x in range(X)] for y in range(Y)]
128119

129120
return (X, Y, Z, maxcolors, list_3d, info)
130121

@@ -138,34 +129,38 @@ def list2png(out_filename, list_3d, info):
138129
"""Take filename and image data, and create PNG file.
139130
140131
Usage
141-
------
142132
143-
`pnglpng.list2png(out_filename, list_3d, info)`
133+
::
134+
135+
pnglpng.list2png(out_filename, list_3d, info)
144136
145-
Takes data described below and writes PNG file `out_filename` out of it:
137+
Take data described below and write PNG file `out_filename` out of it.
146138
147-
- `list_3d`: Y * X * Z list (image) of lists (rows) of lists (pixels) of ints (channels);
148-
- `info`: dictionary, chunks like resolution etc. as you want them to be present in PNG.
139+
:list_3d: Y * X * Z list (image) of lists (rows) of lists (pixels) of ints (channels);
140+
:info: dictionary, chunks like resolution etc. as you want them to be present in PNG.
141+
142+
Note that ``X``, ``Y`` and ``Z`` detected from the list structure override those set in ``info``.
149143
150144
"""
151145

152-
# Determining list dimensions
146+
# Determining list dimensions
153147
Y = len(list_3d)
154148
X = len(list_3d[0])
155149
Z = len(list_3d[0][0])
150+
# ↓ Ignoring any possible list channels above 4-th.
151+
Z = min(Z, 4)
156152

157-
Z = min(Z, 4) # Skipping any possible list channels above 4-th.
158-
159-
# Overwriting "info" properties with ones determined from the list
153+
# ↓ Overwriting "info" properties with ones determined from the list.
154+
# Necessary when image is edited.
160155
info['size'] = (X, Y)
161156
info['planes'] = Z
162157
if 'palette' in info:
163-
del info['palette'] # images get promoted to smooth color when editing
158+
del info['palette'] # images get promoted to smooth color when editing.
164159
if 'background' in info:
165-
# as image tend to get promoted to smooth color when editing,
166-
# background must either be rebuilt to match channels structure every time,
167-
# or be deleted.
168-
# info['background'] = (0,) * (Z - 1 + Z % 2) # black for any color mode
160+
# as image tend to get promoted to smooth color when editing,
161+
# background must either be rebuilt to match channels structure every time,
162+
# or be deleted.
163+
# info['background'] = (0,) * (Z - 1 + Z % 2) # black for any color mode
169164
del info['background'] # Destroy is better than rebuild ;-)
170165
if (Z % 2) == 1:
171166
info['alpha'] = False
@@ -176,18 +171,14 @@ def list2png(out_filename, list_3d, info):
176171
else:
177172
info['greyscale'] = False
178173

179-
# Flattening 3D list to 2D list of rows for PNG `.write` method
174+
# Flattening 3D list to 2D list of rows for PNG `.write` method
180175
def flatten_2d(list_3d):
181176
"""Flatten `list_3d` to 2D list of rows, yield generator."""
182177

183-
yield from (
184-
[list_3d[y][x][z]
185-
for x in range(X)
186-
for z in range(Z)
187-
] for y in range(Y)
188-
)
178+
yield from ([list_3d[y][x][z] for x in range(X) for z in range(Z)] for y in range(Y))
189179

190-
# Writing PNG with `.write` method (row by row), using `flatten_2d` generator to save memory
180+
# ↓ Writing PNG with `.write` method (row by row),
181+
# using `flatten_2d` generator to save memory
191182
writer = png.Writer(X, Y, **info)
192183
with open(out_filename, 'wb') as result_png:
193184
writer.write(result_png, flatten_2d(list_3d))
@@ -199,19 +190,15 @@ def flatten_2d(list_3d):
199190
│ Create empty image │
200191
└────────────────────┘ """
201192

193+
202194
def create_image(X, Y, Z):
203-
"""Create empty 3D nested list of X * Y * Z size."""
195+
"""Create zero-filled 3D nested list of X * Y * Z size."""
204196

205-
new_image = [
206-
[
207-
[0 for z in range(Z)] for x in range(X)
208-
] for y in range(Y)
209-
]
197+
new_image = [[[0 for z in range(Z)] for x in range(X)] for y in range(Y)]
210198

211199
return new_image
212200

213201

214-
# --------------------------------------------------------------
215-
202+
# ↓ Dummy stub for standalone execution attempt
216203
if __name__ == '__main__':
217204
print('Module to be imported, not run as standalone')

0 commit comments

Comments
 (0)