Skip to content

Commit f8867fb

Browse files
committed
PhotoImage.write dump added, mostly for fun
1 parent d22ed62 commit f8867fb

File tree

3 files changed

+59
-2
lines changed

3 files changed

+59
-2
lines changed

README.RU.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@
151151

152152
для открытия файлов. В принципе, вы можете даже зарегистрировать его в качестве системного смотрела для файлов PPM, PGM и PBM.
153153

154+
> [!NOTE]
155+
> Начиная с версии 2.21.22.23 Viewer снабжен пунктом "Export via Tkinter..." а основном меню, позволяющим сохранить открытое изображение с помощью 100% натуральных экологически чистых механизмов Tkinter `PhotoImage.write`. Данная опция включена исключительно для иллюстрации ограничений Tkinter. Например, изображение с цветовой глубиной 16 бит на канал после сохранения таким способом превращается в 8 бит на канал, потому что иначе Tkinter не умеет. Это несколько объясняет одну из причин, по которым я был вынужден написать свой собственный модуль, которому всё равно, что у вас за картинка - он обработает её правильно.
156+
154157
## Заключение
155158

156159
Использование *PyPNM* и Tkinter позволяет легко визуализировать любые данные, которые могут быть представлены в виде L/RGB изображений, и в первую очередь собственно картинки. Используемая PyPNM структура данных (вложенные списки) облегчает написание алгоритмов обработки изображений.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ Beside having simple yet fully functional GUI with mouse events handling a-la Ph
163163

164164
for opening files. In theory, you may even register it as system viewer for PPM, PGM and PBM files.
165165

166+
> [!NOTE]
167+
> Since Viewer version 2.21.22.23 "Export via Tkinter..." option added to main menu, allowing to export opened image using Tkinter native `PhotoImage.write` method. This option added only to illustrate Tkinter limitations. For example, 16 bit per channel images, saved this way, become 8 bpc due to Tkinter internal limitations.
168+
166169
## Conclusion
167170

168171
Using *PyPNM* and Tkinter you may easily visualize any data that can be represented as greyscale or RGB images (images first and foremost), without large external packages and writing files on disk. Nested list data structures used by PyPNM are well suited for arbitrary color mode image processing using nested loop/map .

viewer.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
__copyright__ = '(c) 2025 Ilya Razmanov'
1616
__credits__ = 'Ilya Razmanov'
1717
__license__ = 'unlicense'
18-
__version__ = '2.21.21.21'
18+
__version__ = '2.21.22.23'
1919
__maintainer__ = 'Ilya Razmanov'
2020
__email__ = 'ilyarazmanov@gmail.com'
2121
__status__ = 'Production'
@@ -162,6 +162,7 @@ def GetSource(event=None) -> None:
162162
# ↓ enabling "Save as..."
163163
menu01.entryconfig('Save binary PNM...', state='normal') # Instead of name numbers from 0 may be used
164164
menu01.entryconfig('Save ASCII PNM...', state='normal')
165+
menu01.entryconfig('Export via Tkinter...', state='normal')
165166
menu01.entryconfig('Info', state='normal')
166167
UINormal()
167168
sortir.geometry(f'+{(sortir.winfo_screenwidth() - sortir.winfo_width()) // 2}+{(sortir.winfo_screenheight() - sortir.winfo_height()) // 2 - 32}')
@@ -170,6 +171,8 @@ def GetSource(event=None) -> None:
170171
def SaveAsPNM(bin: bool) -> None:
171172
"""Once pressed on any of Save PNM"""
172173

174+
global sourcefilename
175+
173176
# ↓ Adjusting "Save to" formats to be displayed according to channel number
174177
if Z < 3:
175178
format = [('Portable grey map', '.pgm')]
@@ -194,6 +197,54 @@ def SaveAsPNM(bin: bool) -> None:
194197
# ↓ Saving "savefilename" in PNM format depending on "bin" value
195198
UIBusy()
196199
pnmlpnm.list2pnm(savefilename, image3D, maxcolors, bin)
200+
# ↓ Changing filename to new saved one
201+
sourcefilename = savefilename
202+
sortir.title(f'PNMViewer: {Path(sourcefilename).name}')
203+
UINormal()
204+
205+
206+
def ExportPhotoImage() -> None:
207+
"""Use Tkinter PhotoImage.write to export image.
208+
209+
This function writes Tkinter PhotoImage object to file.
210+
It does not create editable 3D list as PyPNM does. It just dumps.
211+
All Tkinter limitations apply, like color depth ones.
212+
213+
"""
214+
215+
if Z == 1:
216+
format_list = [('Portable network graphics', '.png'), ('Graphics interchange format', '.gif'), ('Portable any map', '.pnm')]
217+
proposed_name = Path(sourcefilename).stem + '.png'
218+
elif Z == 3:
219+
format_list = [('Portable network graphics', '.png'), ('Portable any map', '.pnm')]
220+
proposed_name = Path(sourcefilename).stem + '.png'
221+
else:
222+
format_list = [('Portable network graphics', '.png')]
223+
proposed_name = Path(sourcefilename).stem + '.png'
224+
225+
# ↓ Open "Save as..." file
226+
savefilename = asksaveasfilename(
227+
title='Export via Tinter',
228+
filetypes=format_list,
229+
defaultextension='.png',
230+
initialdir=Path(sourcefilename).parent,
231+
initialfile=proposed_name,
232+
)
233+
if savefilename == '':
234+
return
235+
UIBusy()
236+
237+
# ↓ Recreates preview_data from global image3D,
238+
# does not show it but feeds to Tkinter to create and
239+
# dump PhotoImage.
240+
preview_data = pnmlpnm.list2bin(image3D, maxcolors)
241+
preview_dump = PhotoImage(data=preview_data)
242+
if Path(savefilename).suffix in ('.pgm', '.ppm', '.pnm'):
243+
preview_dump.write(savefilename, format='ppm')
244+
elif Path(savefilename).suffix == '.gif':
245+
preview_dump.write(savefilename, format='gif')
246+
else:
247+
preview_dump.write(savefilename, format='png')
197248
UINormal()
198249

199250

@@ -253,7 +304,6 @@ def zoomWheel(event) -> None:
253304

254305
sortir = Tk()
255306
sortir.title('PNMViewer')
256-
sortir.geometry('+200+100')
257307
sortir.minsize(128, 128)
258308
sortir.iconphoto(True, PhotoImage(data=b'P6\n2 2\n255\n\xff\x00\x00\xff\xff\x00\x00\x00\xff\x00\xff\x00'))
259309

@@ -263,6 +313,7 @@ def zoomWheel(event) -> None:
263313
menu01.add_separator()
264314
menu01.add_command(label='Save binary PNM...', state='disabled', command=lambda: SaveAsPNM(bin=True))
265315
menu01.add_command(label='Save ASCII PNM...', state='disabled', command=lambda: SaveAsPNM(bin=False))
316+
menu01.add_command(label='Export via Tkinter...', state='disabled', command=ExportPhotoImage)
266317
menu01.add_separator()
267318
menu01.add_command(label='Info', accelerator='Ctrl+I', state='disabled', command=ShowInfo)
268319
menu01.add_separator()

0 commit comments

Comments
 (0)