├── README.md
├── raster2laser_gcode.inx
├── raster2laser_gcode.py
└── png.py
/README.md:
--------------------------------------------------------------------------------
1 | # Various Inkscape extensions
2 |
3 | - Raster 2 Laser GCode generator
4 | -
5 |
6 | #Descriptions
7 | - Raster 2 Laser GCode generator is an extension to generate Gcode for a laser cutter/engraver (or pen plotter), it can generate various type of outputs from a simple B&W (on/off) to a more detailed Grayscale (pwm)
8 |
9 |
10 | #Installing:
11 |
12 | Simply copy all the files in the folder "Extensions" of Inkscape
13 |
14 | >Windows ) "C:\<...>\Inkscape\share\extensions"
15 |
16 | >Linux ) "/usr/share/inkscape/extensions"
17 |
18 | >Mac ) "/Applications/Inkscape.app/Contents/Resources/extensions"
19 |
20 |
21 | for unix (& mac maybe) change the permission on the file:
22 |
23 | >>chmod 755 for all the *.py files
24 |
25 | >>chmod 644 for all the *.inx files
26 |
27 |
28 |
29 | #Usage of "Raster 2 Laser GCode generator":
30 |
31 | [Required file: png.py / raster2laser_gcode.inx / raster2laser_gcode.py]
32 |
33 | - Step 1) Resize the inkscape document to match the dimension of your working area on the laser cutter/engraver (Shift+Ctrl+D)
34 |
35 | - Step 2) Draw or import the image
36 |
37 | - Step 3) To run the extension go to: Extension > 305 Engineering > Raster 2 Laser GCode generator
38 |
39 | - Step 4) Play!
40 |
41 |
42 |
43 |
44 | #Note
45 | I have created all the file except for png.py , see that file for details on the license
46 |
--------------------------------------------------------------------------------
/raster2laser_gcode.inx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Raster 2 Laser GCode generator
7 |
8 | com.305engineering.raster2laser_gcode
9 |
10 |
11 | raster2laser_gcode.py
12 | inkex.py
13 |
14 |
15 |
16 |
17 |
18 | Raster 2 Laser GCode generator
19 | created by 305 Engineering
20 |
21 |
22 |
23 |
24 | true
25 |
26 |
27 | <_item value="#ffffff">White
28 | <_item value="#000000">Black
29 |
30 |
31 |
32 | <_item value="1">1 pixel/mm
33 | <_item value="2">2 pixel/mm
34 | <_item value="5">5 pixel/mm
35 | <_item value="10">10 pixel/mm
36 |
37 |
38 |
39 |
40 | <_item value="1">0.21R + 0.71G + 0.07B
41 | <_item value="2">(R+G+B)/3
42 | <_item value="3">R
43 | <_item value="4">G
44 | <_item value="5">B
45 | <_item value="6">Max Color
46 | <_item value="7">Min Color
47 |
48 |
49 |
50 |
51 | <_item value="1">B/W fixed threshold
52 | <_item value="2">B/W random threshold
53 | <_item value="3">Halftone
54 | <_item value="4">Halftone row
55 | <_item value="5">Halftone column
56 | <_item value="6">Grayscale
57 |
58 |
59 |
60 | 128
61 |
62 |
63 | <_item value="1">256
64 | <_item value="2">128
65 | <_item value="4">64
66 | <_item value="8">32
67 | <_item value="16">16
68 | <_item value="32">8
69 |
70 |
71 |
72 |
73 | 200
74 |
75 |
76 | false
77 |
78 |
79 |
80 | <_item value="1">G28 (Standard)
81 | <_item value="2">$H (GRBL)
82 | <_item value="3">No Homing
83 |
84 |
85 | M03
86 | M05
87 |
88 |
89 | false
90 | If "Preview only" is true the gcode file will not be generated.
91 |
92 |
93 |
94 |
95 |
96 |
97 | all
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/raster2laser_gcode.py:
--------------------------------------------------------------------------------
1 | '''
2 | # ----------------------------------------------------------------------------
3 | # Copyright (C) 2014 305engineering <305engineering@gmail.com>
4 | # Original concept by 305engineering.
5 | #
6 | # "THE MODIFIED BEER-WARE LICENSE" (Revision: my own :P):
7 | # <305engineering@gmail.com> wrote this file. As long as you retain this notice you
8 | # can do whatever you want with this stuff (except sell). If we meet some day,
9 | # and you think this stuff is worth it, you can buy me a beer in return.
10 | #
11 | # The above copyright notice and this permission notice shall be
12 | # included in all copies or substantial portions of the Software.
13 | #
14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 | # ----------------------------------------------------------------------------
23 | '''
24 |
25 |
26 | import sys
27 | import os
28 | import re
29 |
30 | sys.path.append('/usr/share/inkscape/extensions')
31 | sys.path.append('/Applications/Inkscape.app/Contents/Resources/extensions')
32 |
33 | import subprocess
34 | import math
35 |
36 | import inkex
37 | import png
38 | import array
39 |
40 |
41 | class GcodeExport(inkex.Effect):
42 |
43 | ######## Richiamata da _main()
44 | def __init__(self):
45 | """init the effetc library and get options from gui"""
46 | inkex.Effect.__init__(self)
47 |
48 | # Opzioni di esportazione dell'immagine
49 | self.OptionParser.add_option("-d", "--directory",action="store", type="string", dest="directory", default="/home/",help="Directory for files") ####check_dir
50 | self.OptionParser.add_option("-f", "--filename", action="store", type="string", dest="filename", default="-1.0", help="File name")
51 | self.OptionParser.add_option("","--add-numeric-suffix-to-filename", action="store", type="inkbool", dest="add_numeric_suffix_to_filename", default=True,help="Add numeric suffix to filename")
52 | self.OptionParser.add_option("","--bg_color",action="store",type="string",dest="bg_color",default="",help="")
53 | self.OptionParser.add_option("","--resolution",action="store", type="int", dest="resolution", default="5",help="") #Usare il valore su float(xy)/resolution e un case per i DPI dell export
54 |
55 |
56 | # Come convertire in scala di grigi
57 | self.OptionParser.add_option("","--grayscale_type",action="store", type="int", dest="grayscale_type", default="1",help="")
58 |
59 | # Modalita di conversione in Bianco e Nero
60 | self.OptionParser.add_option("","--conversion_type",action="store", type="int", dest="conversion_type", default="1",help="")
61 |
62 | # Opzioni modalita
63 | self.OptionParser.add_option("","--BW_threshold",action="store", type="int", dest="BW_threshold", default="128",help="")
64 | self.OptionParser.add_option("","--grayscale_resolution",action="store", type="int", dest="grayscale_resolution", default="1",help="")
65 |
66 | #Velocita Nero e spostamento
67 | self.OptionParser.add_option("","--speed_ON",action="store", type="int", dest="speed_ON", default="200",help="")
68 |
69 | # Mirror Y
70 | self.OptionParser.add_option("","--flip_y",action="store", type="inkbool", dest="flip_y", default=False,help="")
71 |
72 | # Homing
73 | self.OptionParser.add_option("","--homing",action="store", type="int", dest="homing", default="1",help="")
74 |
75 | # Commands
76 | self.OptionParser.add_option("","--laseron", action="store", type="string", dest="laseron", default="M03", help="")
77 | self.OptionParser.add_option("","--laseroff", action="store", type="string", dest="laseroff", default="M05", help="")
78 |
79 |
80 | # Anteprima = Solo immagine BN
81 | self.OptionParser.add_option("","--preview_only",action="store", type="inkbool", dest="preview_only", default=False,help="")
82 |
83 | #inkex.errormsg("BLA BLA BLA Messaggio da visualizzare") #DEBUG
84 |
85 |
86 |
87 |
88 | ######## Richiamata da __init__()
89 | ######## Qui si svolge tutto
90 | def effect(self):
91 |
92 |
93 | current_file = self.args[-1]
94 | bg_color = self.options.bg_color
95 |
96 |
97 | ##Implementare check_dir
98 |
99 | if (os.path.isdir(self.options.directory)) == True:
100 |
101 | ##CODICE SE ESISTE LA DIRECTORY
102 | #inkex.errormsg("OK") #DEBUG
103 |
104 |
105 | #Aggiungo un suffisso al nomefile per non sovrascrivere dei file
106 | if self.options.add_numeric_suffix_to_filename :
107 | dir_list = os.listdir(self.options.directory) #List di tutti i file nella directory di lavoro
108 | temp_name = self.options.filename
109 | max_n = 0
110 | for s in dir_list :
111 | r = re.match(r"^%s_0*(\d+)%s$"%(re.escape(temp_name),'.png' ), s)
112 | if r :
113 | max_n = max(max_n,int(r.group(1)))
114 | self.options.filename = temp_name + "_" + ( "0"*(4-len(str(max_n+1))) + str(max_n+1) )
115 |
116 |
117 | #genero i percorsi file da usare
118 |
119 | suffix = ""
120 | if self.options.conversion_type == 1:
121 | suffix = "_BWfix_"+str(self.options.BW_threshold)+"_"
122 | elif self.options.conversion_type == 2:
123 | suffix = "_BWrnd_"
124 | elif self.options.conversion_type == 3:
125 | suffix = "_H_"
126 | elif self.options.conversion_type == 4:
127 | suffix = "_Hrow_"
128 | elif self.options.conversion_type == 5:
129 | suffix = "_Hcol_"
130 | else:
131 | if self.options.grayscale_resolution == 1:
132 | suffix = "_Gray_256_"
133 | elif self.options.grayscale_resolution == 2:
134 | suffix = "_Gray_128_"
135 | elif self.options.grayscale_resolution == 4:
136 | suffix = "_Gray_64_"
137 | elif self.options.grayscale_resolution == 8:
138 | suffix = "_Gray_32_"
139 | elif self.options.grayscale_resolution == 16:
140 | suffix = "_Gray_16_"
141 | elif self.options.grayscale_resolution == 32:
142 | suffix = "_Gray_8_"
143 | else:
144 | suffix = "_Gray_"
145 |
146 |
147 | pos_file_png_exported = os.path.join(self.options.directory,self.options.filename+".png")
148 | pos_file_png_BW = os.path.join(self.options.directory,self.options.filename+suffix+"preview.png")
149 | pos_file_gcode = os.path.join(self.options.directory,self.options.filename+suffix+"gcode.txt")
150 |
151 |
152 | #Esporto l'immagine in PNG
153 | self.exportPage(pos_file_png_exported,current_file,bg_color)
154 |
155 |
156 |
157 | #DA FARE
158 | #Manipolo l'immagine PNG per generare il file Gcode
159 | self.PNGtoGcode(pos_file_png_exported,pos_file_png_BW,pos_file_gcode)
160 |
161 |
162 | else:
163 | inkex.errormsg("Directory does not exist! Please specify existing directory!")
164 |
165 |
166 |
167 |
168 | ######## ESPORTA L IMMAGINE IN PNG
169 | ######## Richiamata da effect()
170 |
171 | def exportPage(self,pos_file_png_exported,current_file,bg_color):
172 | ######## CREAZIONE DEL FILE PNG ########
173 | #Crea l'immagine dentro la cartella indicata da "pos_file_png_exported"
174 | # -d 127 = risoluzione 127DPI => 5 pixel/mm 1pixel = 0.2mm
175 | ###command="inkscape -C -e \"%s\" -b\"%s\" %s -d 127" % (pos_file_png_exported,bg_color,current_file)
176 |
177 | if self.options.resolution == 1:
178 | DPI = 25.4
179 | elif self.options.resolution == 2:
180 | DPI = 50.8
181 | elif self.options.resolution == 5:
182 | DPI = 127
183 | else:
184 | DPI = 254
185 |
186 | command="inkscape -C -e \"%s\" -b\"%s\" %s -d %s" % (pos_file_png_exported,bg_color,current_file,DPI) #Comando da linea di comando per esportare in PNG
187 |
188 | p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
189 | return_code = p.wait()
190 | f = p.stdout
191 | err = p.stderr
192 |
193 |
194 | ######## CREA IMMAGINE IN B/N E POI GENERA GCODE
195 | ######## Richiamata da effect()
196 |
197 | def PNGtoGcode(self,pos_file_png_exported,pos_file_png_BW,pos_file_gcode):
198 |
199 | ######## GENERO IMMAGINE IN SCALA DI GRIGI ########
200 | #Scorro l immagine e la faccio diventare una matrice composta da list
201 |
202 |
203 | reader = png.Reader(pos_file_png_exported)#File PNG generato
204 |
205 | w, h, pixels, metadata = reader.read_flat()
206 |
207 |
208 | matrice = [[255 for i in range(w)]for j in range(h)] #List al posto di un array
209 |
210 |
211 | #Scrivo una nuova immagine in Scala di grigio 8bit
212 | #copia pixel per pixel
213 |
214 | if self.options.grayscale_type == 1:
215 | #0.21R + 0.71G + 0.07B
216 | for y in range(h): # y varia da 0 a h-1
217 | for x in range(w): # x varia da 0 a w-1
218 | pixel_position = (x + y * w)*4 if metadata['alpha'] else (x + y * w)*3
219 | matrice[y][x] = int(pixels[pixel_position]*0.21 + pixels[(pixel_position+1)]*0.71 + pixels[(pixel_position+2)]*0.07)
220 |
221 | elif self.options.grayscale_type == 2:
222 | #(R+G+B)/3
223 | for y in range(h): # y varia da 0 a h-1
224 | for x in range(w): # x varia da 0 a w-1
225 | pixel_position = (x + y * w)*4 if metadata['alpha'] else (x + y * w)*3
226 | matrice[y][x] = int((pixels[pixel_position] + pixels[(pixel_position+1)]+ pixels[(pixel_position+2)]) / 3 )
227 |
228 | elif self.options.grayscale_type == 3:
229 | #R
230 | for y in range(h): # y varia da 0 a h-1
231 | for x in range(w): # x varia da 0 a w-1
232 | pixel_position = (x + y * w)*4 if metadata['alpha'] else (x + y * w)*3
233 | matrice[y][x] = int(pixels[pixel_position])
234 |
235 | elif self.options.grayscale_type == 4:
236 | #G
237 | for y in range(h): # y varia da 0 a h-1
238 | for x in range(w): # x varia da 0 a w-1
239 | pixel_position = (x + y * w)*4 if metadata['alpha'] else (x + y * w)*3
240 | matrice[y][x] = int(pixels[(pixel_position+1)])
241 |
242 | elif self.options.grayscale_type == 5:
243 | #B
244 | for y in range(h): # y varia da 0 a h-1
245 | for x in range(w): # x varia da 0 a w-1
246 | pixel_position = (x + y * w)*4 if metadata['alpha'] else (x + y * w)*3
247 | matrice[y][x] = int(pixels[(pixel_position+2)])
248 |
249 | elif self.options.grayscale_type == 6:
250 | #Max Color
251 | for y in range(h): # y varia da 0 a h-1
252 | for x in range(w): # x varia da 0 a w-1
253 | pixel_position = (x + y * w)*4 if metadata['alpha'] else (x + y * w)*3
254 | list_RGB = pixels[pixel_position] , pixels[(pixel_position+1)] , pixels[(pixel_position+2)]
255 | matrice[y][x] = int(max(list_RGB))
256 |
257 | else:
258 | #Min Color
259 | for y in range(h): # y varia da 0 a h-1
260 | for x in range(w): # x varia da 0 a w-1
261 | pixel_position = (x + y * w)*4 if metadata['alpha'] else (x + y * w)*3
262 | list_RGB = pixels[pixel_position] , pixels[(pixel_position+1)] , pixels[(pixel_position+2)]
263 | matrice[y][x] = int(min(list_RGB))
264 |
265 |
266 | ####Ora matrice contiene l'immagine in scala di grigi
267 |
268 |
269 | ######## GENERO IMMAGINE IN BIANCO E NERO ########
270 | #Scorro matrice e genero matrice_BN
271 | B=255
272 | N=0
273 |
274 | matrice_BN = [[255 for i in range(w)]for j in range(h)]
275 |
276 |
277 | if self.options.conversion_type == 1:
278 | #B/W fixed threshold
279 | soglia = self.options.BW_threshold
280 | for y in range(h):
281 | for x in range(w):
282 | if matrice[y][x] >= soglia :
283 | matrice_BN[y][x] = B
284 | else:
285 | matrice_BN[y][x] = N
286 |
287 |
288 | elif self.options.conversion_type == 2:
289 | #B/W random threshold
290 | from random import randint
291 | for y in range(h):
292 | for x in range(w):
293 | soglia = randint(20,235)
294 | if matrice[y][x] >= soglia :
295 | matrice_BN[y][x] = B
296 | else:
297 | matrice_BN[y][x] = N
298 |
299 |
300 | elif self.options.conversion_type == 3:
301 | #Halftone
302 | Step1 = [[B,B,B,B,B],[B,B,B,B,B],[B,B,N,B,B],[B,B,B,B,B],[B,B,B,B,B]]
303 | Step2 = [[B,B,B,B,B],[B,B,N,B,B],[B,N,N,N,B],[B,B,N,B,B],[B,B,B,B,B]]
304 | Step3 = [[B,B,N,B,B],[B,N,N,N,B],[N,N,N,N,N],[B,N,N,N,B],[B,B,N,B,B]]
305 | Step4 = [[B,N,N,N,B],[N,N,N,N,N],[N,N,N,N,N],[N,N,N,N,N],[B,N,N,N,B]]
306 |
307 | for y in range(h/5):
308 | for x in range(w/5):
309 | media = 0
310 | for y2 in range(5):
311 | for x2 in range(5):
312 | media += matrice[y*5+y2][x*5+x2]
313 | media = media /25
314 | for y3 in range(5):
315 | for x3 in range(5):
316 | if media >= 250 and media <= 255:
317 | matrice_BN[y*5+y3][x*5+x3] = B
318 | if media >= 190 and media < 250:
319 | matrice_BN[y*5+y3][x*5+x3] = Step1[y3][x3]
320 | if media >= 130 and media < 190:
321 | matrice_BN[y*5+y3][x*5+x3] = Step2[y3][x3]
322 | if media >= 70 and media < 130:
323 | matrice_BN[y*5+y3][x*5+x3] = Step3[y3][x3]
324 | if media >= 10 and media < 70:
325 | matrice_BN[y*5+y3][x*5+x3] = Step4[y3][x3]
326 | if media >= 0 and media < 10:
327 | matrice_BN[y*5+y3][x*5+x3] = N
328 |
329 |
330 | elif self.options.conversion_type == 4:
331 | #Halftone row
332 | Step1r = [B,B,N,B,B]
333 | Step2r = [B,N,N,B,B]
334 | Step3r = [B,N,N,N,B]
335 | Step4r = [N,N,N,N,B]
336 |
337 | for y in range(h):
338 | for x in range(w/5):
339 | media = 0
340 | for x2 in range(5):
341 | media += matrice[y][x*5+x2]
342 | media = media /5
343 | for x3 in range(5):
344 | if media >= 250 and media <= 255:
345 | matrice_BN[y][x*5+x3] = B
346 | if media >= 190 and media < 250:
347 | matrice_BN[y][x*5+x3] = Step1r[x3]
348 | if media >= 130 and media < 190:
349 | matrice_BN[y][x*5+x3] = Step2r[x3]
350 | if media >= 70 and media < 130:
351 | matrice_BN[y][x*5+x3] = Step3r[x3]
352 | if media >= 10 and media < 70:
353 | matrice_BN[y][x*5+x3] = Step4r[x3]
354 | if media >= 0 and media < 10:
355 | matrice_BN[y][x*5+x3] = N
356 |
357 |
358 | elif self.options.conversion_type == 5:
359 | #Halftone column
360 | Step1c = [B,B,N,B,B]
361 | Step2c = [B,N,N,B,B]
362 | Step3c = [B,N,N,N,B]
363 | Step4c = [N,N,N,N,B]
364 |
365 | for y in range(h/5):
366 | for x in range(w):
367 | media = 0
368 | for y2 in range(5):
369 | media += matrice[y*5+y2][x]
370 | media = media /5
371 | for y3 in range(5):
372 | if media >= 250 and media <= 255:
373 | matrice_BN[y*5+y3][x] = B
374 | if media >= 190 and media < 250:
375 | matrice_BN[y*5+y3][x] = Step1c[y3]
376 | if media >= 130 and media < 190:
377 | matrice_BN[y*5+y3][x] = Step2c[y3]
378 | if media >= 70 and media < 130:
379 | matrice_BN[y*5+y3][x] = Step3c[y3]
380 | if media >= 10 and media < 70:
381 | matrice_BN[y*5+y3][x] = Step4c[y3]
382 | if media >= 0 and media < 10:
383 | matrice_BN[y*5+y3][x] = N
384 |
385 | else:
386 | #Grayscale
387 | if self.options.grayscale_resolution == 1:
388 | matrice_BN = matrice
389 | else:
390 | for y in range(h):
391 | for x in range(w):
392 | if matrice[y][x] <= 1:
393 | matrice_BN[y][x] = 0
394 |
395 | if matrice[y][x] >= 254:
396 | matrice_BN[y][x] = 255
397 |
398 | if matrice[y][x] > 1 and matrice[y][x] <254:
399 | matrice_BN[y][x] = ( matrice[y][x] // self.options.grayscale_resolution ) * self.options.grayscale_resolution
400 |
401 |
402 |
403 | ####Ora matrice_BN contiene l'immagine in Bianco (255) e Nero (0)
404 |
405 |
406 | #### SALVO IMMAGINE IN BIANCO E NERO ####
407 | file_img_BN = open(pos_file_png_BW, 'wb') #Creo il file
408 | Costruttore_img = png.Writer(w, h, greyscale=True, bitdepth=8) #Impostazione del file immagine
409 | Costruttore_img.write(file_img_BN, matrice_BN) #Costruttore del file immagine
410 | file_img_BN.close() #Chiudo il file
411 |
412 |
413 | #### GENERO IL FILE GCODE ####
414 | if self.options.preview_only == False: #Genero Gcode solo se devo
415 |
416 | if self.options.flip_y == False: #Inverto asse Y solo se flip_y = False
417 | #-> coordinate Cartesiane (False) Coordinate "informatiche" (True)
418 | matrice_BN.reverse()
419 |
420 |
421 | Laser_ON = False
422 | F_G01 = self.options.speed_ON
423 | Scala = self.options.resolution
424 |
425 | file_gcode = open(pos_file_gcode, 'w') #Creo il file
426 |
427 | #Configurazioni iniziali standard Gcode
428 | file_gcode.write('; Generated with:\n; "Raster 2 Laser Gcode generator"\n; by 305 Engineering\n;\n;\n;\n')
429 | #HOMING
430 | if self.options.homing == 1:
431 | file_gcode.write('G28; home all axes\n')
432 | elif self.options.homing == 2:
433 | file_gcode.write('$H; home all axes\n')
434 | else:
435 | pass
436 | file_gcode.write('G21; Set units to millimeters\n')
437 | file_gcode.write('G90; Use absolute coordinates\n')
438 | file_gcode.write('G92; Coordinate Offset\n')
439 |
440 | #Creazione del Gcode
441 |
442 | #allargo la matrice per lavorare su tutta l'immagine
443 | for y in range(h):
444 | matrice_BN[y].append(B)
445 | w = w+1
446 |
447 | if self.options.conversion_type != 6:
448 | for y in range(h):
449 | if y % 2 == 0 :
450 | for x in range(w):
451 | if matrice_BN[y][x] == N :
452 | if Laser_ON == False :
453 | #file_gcode.write('G00 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) + ' F' + str(F_G00) + '\n')
454 | file_gcode.write('G00 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) + '\n') #tolto il Feed sul G00
455 | file_gcode.write(self.options.laseron + '\n')
456 | Laser_ON = True
457 | if Laser_ON == True : #DEVO evitare di uscire dalla matrice
458 | if x == w-1 :
459 | file_gcode.write('G01 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) +' F' + str(F_G01) + '\n')
460 | file_gcode.write(self.options.laseroff + '\n')
461 | Laser_ON = False
462 | else:
463 | if matrice_BN[y][x+1] != N :
464 | file_gcode.write('G01 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) + ' F' + str(F_G01) +'\n')
465 | file_gcode.write(self.options.laseroff + '\n')
466 | Laser_ON = False
467 | else:
468 | for x in reversed(range(w)):
469 | if matrice_BN[y][x] == N :
470 | if Laser_ON == False :
471 | #file_gcode.write('G00 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) + ' F' + str(F_G00) + '\n')
472 | file_gcode.write('G00 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) + '\n') #tolto il Feed sul G00
473 | file_gcode.write(self.options.laseron + '\n')
474 | Laser_ON = True
475 | if Laser_ON == True : #DEVO evitare di uscire dalla matrice
476 | if x == 0 :
477 | file_gcode.write('G01 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) +' F' + str(F_G01) + '\n')
478 | file_gcode.write(self.options.laseroff + '\n')
479 | Laser_ON = False
480 | else:
481 | if matrice_BN[y][x-1] != N :
482 | file_gcode.write('G01 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) + ' F' + str(F_G01) +'\n')
483 | file_gcode.write(self.options.laseroff + '\n')
484 | Laser_ON = False
485 |
486 | else: ##SCALA DI GRIGI
487 | for y in range(h):
488 | if y % 2 == 0 :
489 | for x in range(w):
490 | if matrice_BN[y][x] != B :
491 | if Laser_ON == False :
492 | file_gcode.write('G00 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) +'\n')
493 | file_gcode.write(self.options.laseron + ' '+ ' S' + str(255 - matrice_BN[y][x]) +'\n')
494 | Laser_ON = True
495 |
496 | if Laser_ON == True : #DEVO evitare di uscire dalla matrice
497 | if x == w-1 : #controllo fine riga
498 | file_gcode.write('G01 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) +' F' + str(F_G01) + '\n')
499 | file_gcode.write(self.options.laseroff + '\n')
500 | Laser_ON = False
501 |
502 | else:
503 | if matrice_BN[y][x+1] == B :
504 | file_gcode.write('G01 X' + str(float(x+1)/Scala) + ' Y' + str(float(y)/Scala) + ' F' + str(F_G01) +'\n')
505 | file_gcode.write(self.options.laseroff + '\n')
506 | Laser_ON = False
507 |
508 | elif matrice_BN[y][x] != matrice_BN[y][x+1] :
509 | file_gcode.write('G01 X' + str(float(x+1)/Scala) + ' Y' + str(float(y)/Scala) + ' F' + str(F_G01) +'\n')
510 | file_gcode.write(self.options.laseron + ' '+ ' S' + str(255 - matrice_BN[y][x+1]) +'\n')
511 |
512 |
513 | else:
514 | for x in reversed(range(w)):
515 | if matrice_BN[y][x] != B :
516 | if Laser_ON == False :
517 | file_gcode.write('G00 X' + str(float(x+1)/Scala) + ' Y' + str(float(y)/Scala) +'\n')
518 | file_gcode.write(self.options.laseron + ' '+ ' S' + str(255 - matrice_BN[y][x]) +'\n')
519 | Laser_ON = True
520 |
521 | if Laser_ON == True : #DEVO evitare di uscire dalla matrice
522 | if x == 0 : #controllo fine riga ritorno
523 | file_gcode.write('G01 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) +' F' + str(F_G01) + '\n')
524 | file_gcode.write(self.options.laseroff + '\n')
525 | Laser_ON = False
526 |
527 | else:
528 | if matrice_BN[y][x-1] == B :
529 | file_gcode.write('G01 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) + ' F' + str(F_G01) +'\n')
530 | file_gcode.write(self.options.laseroff + '\n')
531 | Laser_ON = False
532 |
533 | elif matrice_BN[y][x] != matrice_BN[y][x-1] :
534 | file_gcode.write('G01 X' + str(float(x)/Scala) + ' Y' + str(float(y)/Scala) + ' F' + str(F_G01) +'\n')
535 | file_gcode.write(self.options.laseron + ' '+ ' S' + str(255 - matrice_BN[y][x-1]) +'\n')
536 |
537 |
538 |
539 | #Configurazioni finali standard Gcode
540 | file_gcode.write('G00 X0 Y0; home\n')
541 | #HOMING
542 | if self.options.homing == 1:
543 | file_gcode.write('G28; home all axes\n')
544 | elif self.options.homing == 2:
545 | file_gcode.write('$H; home all axes\n')
546 | else:
547 | pass
548 |
549 | file_gcode.close() #Chiudo il file
550 |
551 |
552 |
553 |
554 | ######## ######## ######## ######## ######## ######## ######## ######## ########
555 |
556 |
557 | def _main():
558 | e=GcodeExport()
559 | e.affect()
560 |
561 | exit()
562 |
563 | if __name__=="__main__":
564 | _main()
565 |
566 |
567 |
568 |
569 |
--------------------------------------------------------------------------------
/png.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # png.py - PNG encoder/decoder in pure Python
4 | #
5 | # Copyright (C) 2006 Johann C. Rocholl
6 | # Portions Copyright (C) 2009 David Jones
7 | # And probably portions Copyright (C) 2006 Nicko van Someren
8 | #
9 | # Original concept by Johann C. Rocholl.
10 | #
11 | # LICENCE (MIT)
12 | #
13 | # Permission is hereby granted, free of charge, to any person
14 | # obtaining a copy of this software and associated documentation files
15 | # (the "Software"), to deal in the Software without restriction,
16 | # including without limitation the rights to use, copy, modify, merge,
17 | # publish, distribute, sublicense, and/or sell copies of the Software,
18 | # and to permit persons to whom the Software is furnished to do so,
19 | # subject to the following conditions:
20 | #
21 | # The above copyright notice and this permission notice shall be
22 | # included in all copies or substantial portions of the Software.
23 | #
24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
28 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
29 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
30 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | # SOFTWARE.
32 |
33 | """
34 | Pure Python PNG Reader/Writer
35 |
36 | This Python module implements support for PNG images (see PNG
37 | specification at http://www.w3.org/TR/2003/REC-PNG-20031110/ ). It reads
38 | and writes PNG files with all allowable bit depths
39 | (1/2/4/8/16/24/32/48/64 bits per pixel) and colour combinations:
40 | greyscale (1/2/4/8/16 bit); RGB, RGBA, LA (greyscale with alpha) with
41 | 8/16 bits per channel; colour mapped images (1/2/4/8 bit).
42 | Adam7 interlacing is supported for reading and
43 | writing. A number of optional chunks can be specified (when writing)
44 | and understood (when reading): ``tRNS``, ``bKGD``, ``gAMA``.
45 |
46 | For help, type ``import png; help(png)`` in your python interpreter.
47 |
48 | A good place to start is the :class:`Reader` and :class:`Writer`
49 | classes.
50 |
51 | Requires Python 2.3. Limited support is available for Python 2.2, but
52 | not everything works. Best with Python 2.4 and higher. Installation is
53 | trivial, but see the ``README.txt`` file (with the source distribution)
54 | for details.
55 |
56 | This file can also be used as a command-line utility to convert
57 | `Netpbm `_ PNM files to PNG, and the
58 | reverse conversion from PNG to PNM. The interface is similar to that
59 | of the ``pnmtopng`` program from Netpbm. Type ``python png.py --help``
60 | at the shell prompt for usage and a list of options.
61 |
62 | A note on spelling and terminology
63 | ----------------------------------
64 |
65 | Generally British English spelling is used in the documentation. So
66 | that's "greyscale" and "colour". This not only matches the author's
67 | native language, it's also used by the PNG specification.
68 |
69 | The major colour models supported by PNG (and hence by PyPNG) are:
70 | greyscale, RGB, greyscale--alpha, RGB--alpha. These are sometimes
71 | referred to using the abbreviations: L, RGB, LA, RGBA. In this case
72 | each letter abbreviates a single channel: *L* is for Luminance or Luma
73 | or Lightness which is the channel used in greyscale images; *R*, *G*,
74 | *B* stand for Red, Green, Blue, the components of a colour image; *A*
75 | stands for Alpha, the opacity channel (used for transparency effects,
76 | but higher values are more opaque, so it makes sense to call it
77 | opacity).
78 |
79 | A note on formats
80 | -----------------
81 |
82 | When getting pixel data out of this module (reading) and presenting
83 | data to this module (writing) there are a number of ways the data could
84 | be represented as a Python value. Generally this module uses one of
85 | three formats called "flat row flat pixel", "boxed row flat pixel", and
86 | "boxed row boxed pixel". Basically the concern is whether each pixel
87 | and each row comes in its own little tuple (box), or not.
88 |
89 | Consider an image that is 3 pixels wide by 2 pixels high, and each pixel
90 | has RGB components:
91 |
92 | Boxed row flat pixel::
93 |
94 | list([R,G,B, R,G,B, R,G,B],
95 | [R,G,B, R,G,B, R,G,B])
96 |
97 | Each row appears as its own list, but the pixels are flattened so
98 | that three values for one pixel simply follow the three values for
99 | the previous pixel. This is the most common format used, because it
100 | provides a good compromise between space and convenience. PyPNG regards
101 | itself as at liberty to replace any sequence type with any sufficiently
102 | compatible other sequence type; in practice each row is an array (from
103 | the array module), and the outer list is sometimes an iterator rather
104 | than an explicit list (so that streaming is possible).
105 |
106 | Flat row flat pixel::
107 |
108 | [R,G,B, R,G,B, R,G,B,
109 | R,G,B, R,G,B, R,G,B]
110 |
111 | The entire image is one single giant sequence of colour values.
112 | Generally an array will be used (to save space), not a list.
113 |
114 | Boxed row boxed pixel::
115 |
116 | list([ (R,G,B), (R,G,B), (R,G,B) ],
117 | [ (R,G,B), (R,G,B), (R,G,B) ])
118 |
119 | Each row appears in its own list, but each pixel also appears in its own
120 | tuple. A serious memory burn in Python.
121 |
122 | In all cases the top row comes first, and for each row the pixels are
123 | ordered from left-to-right. Within a pixel the values appear in the
124 | order, R-G-B-A (or L-A for greyscale--alpha).
125 |
126 | There is a fourth format, mentioned because it is used internally,
127 | is close to what lies inside a PNG file itself, and has some support
128 | from the public API. This format is called packed. When packed,
129 | each row is a sequence of bytes (integers from 0 to 255), just as
130 | it is before PNG scanline filtering is applied. When the bit depth
131 | is 8 this is essentially the same as boxed row flat pixel; when the
132 | bit depth is less than 8, several pixels are packed into each byte;
133 | when the bit depth is 16 (the only value more than 8 that is supported
134 | by the PNG image format) each pixel value is decomposed into 2 bytes
135 | (and `packed` is a misnomer). This format is used by the
136 | :meth:`Writer.write_packed` method. It isn't usually a convenient
137 | format, but may be just right if the source data for the PNG image
138 | comes from something that uses a similar format (for example, 1-bit
139 | BMPs, or another PNG file).
140 |
141 | And now, my famous members
142 | --------------------------
143 | """
144 |
145 | # http://www.python.org/doc/2.2.3/whatsnew/node5.html
146 | from __future__ import generators
147 |
148 | __version__ = "0.0.16"
149 |
150 | from array import array
151 | try: # See :pyver:old
152 | import itertools
153 | except ImportError:
154 | pass
155 | import math
156 | # http://www.python.org/doc/2.4.4/lib/module-operator.html
157 | import operator
158 | import struct
159 | import sys
160 | import zlib
161 | # http://www.python.org/doc/2.4.4/lib/module-warnings.html
162 | import warnings
163 | try:
164 | # `cpngfilters` is a Cython module: it must be compiled by
165 | # Cython for this import to work.
166 | # If this import does work, then it overrides pure-python
167 | # filtering functions defined later in this file (see `class
168 | # pngfilters`).
169 | import cpngfilters as pngfilters
170 | except ImportError:
171 | pass
172 |
173 |
174 | __all__ = ['Image', 'Reader', 'Writer', 'write_chunks', 'from_array']
175 |
176 |
177 | # The PNG signature.
178 | # http://www.w3.org/TR/PNG/#5PNG-file-signature
179 | _signature = struct.pack('8B', 137, 80, 78, 71, 13, 10, 26, 10)
180 |
181 | _adam7 = ((0, 0, 8, 8),
182 | (4, 0, 8, 8),
183 | (0, 4, 4, 8),
184 | (2, 0, 4, 4),
185 | (0, 2, 2, 4),
186 | (1, 0, 2, 2),
187 | (0, 1, 1, 2))
188 |
189 | def group(s, n):
190 | # See http://www.python.org/doc/2.6/library/functions.html#zip
191 | return zip(*[iter(s)]*n)
192 |
193 | def isarray(x):
194 | """Same as ``isinstance(x, array)`` except on Python 2.2, where it
195 | always returns ``False``. This helps PyPNG work on Python 2.2.
196 | """
197 |
198 | try:
199 | return isinstance(x, array)
200 | except TypeError:
201 | # Because on Python 2.2 array.array is not a type.
202 | return False
203 |
204 | try:
205 | array.tobytes
206 | except AttributeError:
207 | try: # see :pyver:old
208 | array.tostring
209 | except AttributeError:
210 | def tostring(row):
211 | l = len(row)
212 | return struct.pack('%dB' % l, *row)
213 | else:
214 | def tostring(row):
215 | """Convert row of bytes to string. Expects `row` to be an
216 | ``array``.
217 | """
218 | return row.tostring()
219 | else:
220 | def tostring(row):
221 | """ Python3 definition, array.tostring() is deprecated in Python3
222 | """
223 | return row.tobytes()
224 |
225 | # Conditionally convert to bytes. Works on Python 2 and Python 3.
226 | try:
227 | bytes('', 'ascii')
228 | def strtobytes(x): return bytes(x, 'iso8859-1')
229 | def bytestostr(x): return str(x, 'iso8859-1')
230 | except (NameError, TypeError):
231 | # We get NameError when bytes() does not exist (most Python
232 | # 2.x versions), and TypeError when bytes() exists but is on
233 | # Python 2.x (when it is an alias for str() and takes at most
234 | # one argument).
235 | strtobytes = str
236 | bytestostr = str
237 |
238 | def interleave_planes(ipixels, apixels, ipsize, apsize):
239 | """
240 | Interleave (colour) planes, e.g. RGB + A = RGBA.
241 |
242 | Return an array of pixels consisting of the `ipsize` elements of
243 | data from each pixel in `ipixels` followed by the `apsize` elements
244 | of data from each pixel in `apixels`. Conventionally `ipixels`
245 | and `apixels` are byte arrays so the sizes are bytes, but it
246 | actually works with any arrays of the same type. The returned
247 | array is the same type as the input arrays which should be the
248 | same type as each other.
249 | """
250 |
251 | itotal = len(ipixels)
252 | atotal = len(apixels)
253 | newtotal = itotal + atotal
254 | newpsize = ipsize + apsize
255 | # Set up the output buffer
256 | # See http://www.python.org/doc/2.4.4/lib/module-array.html#l2h-1356
257 | out = array(ipixels.typecode)
258 | # It's annoying that there is no cheap way to set the array size :-(
259 | out.extend(ipixels)
260 | out.extend(apixels)
261 | # Interleave in the pixel data
262 | for i in range(ipsize):
263 | out[i:newtotal:newpsize] = ipixels[i:itotal:ipsize]
264 | for i in range(apsize):
265 | out[i+ipsize:newtotal:newpsize] = apixels[i:atotal:apsize]
266 | return out
267 |
268 | def check_palette(palette):
269 | """Check a palette argument (to the :class:`Writer` class)
270 | for validity. Returns the palette as a list if okay; raises an
271 | exception otherwise.
272 | """
273 |
274 | # None is the default and is allowed.
275 | if palette is None:
276 | return None
277 |
278 | p = list(palette)
279 | if not (0 < len(p) <= 256):
280 | raise ValueError("a palette must have between 1 and 256 entries")
281 | seen_triple = False
282 | for i,t in enumerate(p):
283 | if len(t) not in (3,4):
284 | raise ValueError(
285 | "palette entry %d: entries must be 3- or 4-tuples." % i)
286 | if len(t) == 3:
287 | seen_triple = True
288 | if seen_triple and len(t) == 4:
289 | raise ValueError(
290 | "palette entry %d: all 4-tuples must precede all 3-tuples" % i)
291 | for x in t:
292 | if int(x) != x or not(0 <= x <= 255):
293 | raise ValueError(
294 | "palette entry %d: values must be integer: 0 <= x <= 255" % i)
295 | return p
296 |
297 | def check_sizes(size, width, height):
298 | """Check that these arguments, in supplied, are consistent.
299 | Return a (width, height) pair.
300 | """
301 |
302 | if not size:
303 | return width, height
304 |
305 | if len(size) != 2:
306 | raise ValueError(
307 | "size argument should be a pair (width, height)")
308 | if width is not None and width != size[0]:
309 | raise ValueError(
310 | "size[0] (%r) and width (%r) should match when both are used."
311 | % (size[0], width))
312 | if height is not None and height != size[1]:
313 | raise ValueError(
314 | "size[1] (%r) and height (%r) should match when both are used."
315 | % (size[1], height))
316 | return size
317 |
318 | def check_color(c, greyscale, which):
319 | """Checks that a colour argument for transparent or
320 | background options is the right form. Returns the colour
321 | (which, if it's a bar integer, is "corrected" to a 1-tuple).
322 | """
323 |
324 | if c is None:
325 | return c
326 | if greyscale:
327 | try:
328 | l = len(c)
329 | except TypeError:
330 | c = (c,)
331 | if len(c) != 1:
332 | raise ValueError("%s for greyscale must be 1-tuple" %
333 | which)
334 | if not isinteger(c[0]):
335 | raise ValueError(
336 | "%s colour for greyscale must be integer" % which)
337 | else:
338 | if not (len(c) == 3 and
339 | isinteger(c[0]) and
340 | isinteger(c[1]) and
341 | isinteger(c[2])):
342 | raise ValueError(
343 | "%s colour must be a triple of integers" % which)
344 | return c
345 |
346 | class Error(Exception):
347 | def __str__(self):
348 | return self.__class__.__name__ + ': ' + ' '.join(self.args)
349 |
350 | class FormatError(Error):
351 | """Problem with input file format. In other words, PNG file does
352 | not conform to the specification in some way and is invalid.
353 | """
354 |
355 | class ChunkError(FormatError):
356 | pass
357 |
358 |
359 | class Writer:
360 | """
361 | PNG encoder in pure Python.
362 | """
363 |
364 | def __init__(self, width=None, height=None,
365 | size=None,
366 | greyscale=False,
367 | alpha=False,
368 | bitdepth=8,
369 | palette=None,
370 | transparent=None,
371 | background=None,
372 | gamma=None,
373 | compression=None,
374 | interlace=False,
375 | bytes_per_sample=None, # deprecated
376 | planes=None,
377 | colormap=None,
378 | maxval=None,
379 | chunk_limit=2**20):
380 | """
381 | Create a PNG encoder object.
382 |
383 | Arguments:
384 |
385 | width, height
386 | Image size in pixels, as two separate arguments.
387 | size
388 | Image size (w,h) in pixels, as single argument.
389 | greyscale
390 | Input data is greyscale, not RGB.
391 | alpha
392 | Input data has alpha channel (RGBA or LA).
393 | bitdepth
394 | Bit depth: from 1 to 16.
395 | palette
396 | Create a palette for a colour mapped image (colour type 3).
397 | transparent
398 | Specify a transparent colour (create a ``tRNS`` chunk).
399 | background
400 | Specify a default background colour (create a ``bKGD`` chunk).
401 | gamma
402 | Specify a gamma value (create a ``gAMA`` chunk).
403 | compression
404 | zlib compression level: 0 (none) to 9 (more compressed);
405 | default: -1 or None.
406 | interlace
407 | Create an interlaced image.
408 | chunk_limit
409 | Write multiple ``IDAT`` chunks to save memory.
410 |
411 | The image size (in pixels) can be specified either by using the
412 | `width` and `height` arguments, or with the single `size`
413 | argument. If `size` is used it should be a pair (*width*,
414 | *height*).
415 |
416 | `greyscale` and `alpha` are booleans that specify whether
417 | an image is greyscale (or colour), and whether it has an
418 | alpha channel (or not).
419 |
420 | `bitdepth` specifies the bit depth of the source pixel values.
421 | Each source pixel value must be an integer between 0 and
422 | ``2**bitdepth-1``. For example, 8-bit images have values
423 | between 0 and 255. PNG only stores images with bit depths of
424 | 1,2,4,8, or 16. When `bitdepth` is not one of these values,
425 | the next highest valid bit depth is selected, and an ``sBIT``
426 | (significant bits) chunk is generated that specifies the
427 | original precision of the source image. In this case the
428 | supplied pixel values will be rescaled to fit the range of
429 | the selected bit depth.
430 |
431 | The details of which bit depth / colour model combinations the
432 | PNG file format supports directly, are somewhat arcane
433 | (refer to the PNG specification for full details). Briefly:
434 | "small" bit depths (1,2,4) are only allowed with greyscale and
435 | colour mapped images; colour mapped images cannot have bit depth
436 | 16.
437 |
438 | For colour mapped images (in other words, when the `palette`
439 | argument is specified) the `bitdepth` argument must match one of
440 | the valid PNG bit depths: 1, 2, 4, or 8. (It is valid to have a
441 | PNG image with a palette and an ``sBIT`` chunk, but the meaning
442 | is slightly different; it would be awkward to press the
443 | `bitdepth` argument into service for this.)
444 |
445 | The `palette` option, when specified, causes a colour mapped
446 | image to be created: the PNG colour type is set to 3; greyscale
447 | must not be set; alpha must not be set; transparent must not be
448 | set; the bit depth must be 1,2,4, or 8. When a colour mapped
449 | image is created, the pixel values are palette indexes and
450 | the `bitdepth` argument specifies the size of these indexes
451 | (not the size of the colour values in the palette).
452 |
453 | The palette argument value should be a sequence of 3- or
454 | 4-tuples. 3-tuples specify RGB palette entries; 4-tuples
455 | specify RGBA palette entries. If both 4-tuples and 3-tuples
456 | appear in the sequence then all the 4-tuples must come
457 | before all the 3-tuples. A ``PLTE`` chunk is created; if there
458 | are 4-tuples then a ``tRNS`` chunk is created as well. The
459 | ``PLTE`` chunk will contain all the RGB triples in the same
460 | sequence; the ``tRNS`` chunk will contain the alpha channel for
461 | all the 4-tuples, in the same sequence. Palette entries
462 | are always 8-bit.
463 |
464 | If specified, the `transparent` and `background` parameters must
465 | be a tuple with three integer values for red, green, blue, or
466 | a simple integer (or singleton tuple) for a greyscale image.
467 |
468 | If specified, the `gamma` parameter must be a positive number
469 | (generally, a float). A ``gAMA`` chunk will be created.
470 | Note that this will not change the values of the pixels as
471 | they appear in the PNG file, they are assumed to have already
472 | been converted appropriately for the gamma specified.
473 |
474 | The `compression` argument specifies the compression level to
475 | be used by the ``zlib`` module. Values from 1 to 9 specify
476 | compression, with 9 being "more compressed" (usually smaller
477 | and slower, but it doesn't always work out that way). 0 means
478 | no compression. -1 and ``None`` both mean that the default
479 | level of compession will be picked by the ``zlib`` module
480 | (which is generally acceptable).
481 |
482 | If `interlace` is true then an interlaced image is created
483 | (using PNG's so far only interace method, *Adam7*). This does
484 | not affect how the pixels should be presented to the encoder,
485 | rather it changes how they are arranged into the PNG file.
486 | On slow connexions interlaced images can be partially decoded
487 | by the browser to give a rough view of the image that is
488 | successively refined as more image data appears.
489 |
490 | .. note ::
491 |
492 | Enabling the `interlace` option requires the entire image
493 | to be processed in working memory.
494 |
495 | `chunk_limit` is used to limit the amount of memory used whilst
496 | compressing the image. In order to avoid using large amounts of
497 | memory, multiple ``IDAT`` chunks may be created.
498 | """
499 |
500 | # At the moment the `planes` argument is ignored;
501 | # its purpose is to act as a dummy so that
502 | # ``Writer(x, y, **info)`` works, where `info` is a dictionary
503 | # returned by Reader.read and friends.
504 | # Ditto for `colormap`.
505 |
506 | width, height = check_sizes(size, width, height)
507 | del size
508 |
509 | if width <= 0 or height <= 0:
510 | raise ValueError("width and height must be greater than zero")
511 | if not isinteger(width) or not isinteger(height):
512 | raise ValueError("width and height must be integers")
513 | # http://www.w3.org/TR/PNG/#7Integers-and-byte-order
514 | if width > 2**32-1 or height > 2**32-1:
515 | raise ValueError("width and height cannot exceed 2**32-1")
516 |
517 | if alpha and transparent is not None:
518 | raise ValueError(
519 | "transparent colour not allowed with alpha channel")
520 |
521 | if bytes_per_sample is not None:
522 | warnings.warn('please use bitdepth instead of bytes_per_sample',
523 | DeprecationWarning)
524 | if bytes_per_sample not in (0.125, 0.25, 0.5, 1, 2):
525 | raise ValueError(
526 | "bytes per sample must be .125, .25, .5, 1, or 2")
527 | bitdepth = int(8*bytes_per_sample)
528 | del bytes_per_sample
529 | if not isinteger(bitdepth) or bitdepth < 1 or 16 < bitdepth:
530 | raise ValueError("bitdepth (%r) must be a postive integer <= 16" %
531 | bitdepth)
532 |
533 | self.rescale = None
534 | if palette:
535 | if bitdepth not in (1,2,4,8):
536 | raise ValueError("with palette, bitdepth must be 1, 2, 4, or 8")
537 | if transparent is not None:
538 | raise ValueError("transparent and palette not compatible")
539 | if alpha:
540 | raise ValueError("alpha and palette not compatible")
541 | if greyscale:
542 | raise ValueError("greyscale and palette not compatible")
543 | else:
544 | # No palette, check for sBIT chunk generation.
545 | if alpha or not greyscale:
546 | if bitdepth not in (8,16):
547 | targetbitdepth = (8,16)[bitdepth > 8]
548 | self.rescale = (bitdepth, targetbitdepth)
549 | bitdepth = targetbitdepth
550 | del targetbitdepth
551 | else:
552 | assert greyscale
553 | assert not alpha
554 | if bitdepth not in (1,2,4,8,16):
555 | if bitdepth > 8:
556 | targetbitdepth = 16
557 | elif bitdepth == 3:
558 | targetbitdepth = 4
559 | else:
560 | assert bitdepth in (5,6,7)
561 | targetbitdepth = 8
562 | self.rescale = (bitdepth, targetbitdepth)
563 | bitdepth = targetbitdepth
564 | del targetbitdepth
565 |
566 | if bitdepth < 8 and (alpha or not greyscale and not palette):
567 | raise ValueError(
568 | "bitdepth < 8 only permitted with greyscale or palette")
569 | if bitdepth > 8 and palette:
570 | raise ValueError(
571 | "bit depth must be 8 or less for images with palette")
572 |
573 | transparent = check_color(transparent, greyscale, 'transparent')
574 | background = check_color(background, greyscale, 'background')
575 |
576 | # It's important that the true boolean values (greyscale, alpha,
577 | # colormap, interlace) are converted to bool because Iverson's
578 | # convention is relied upon later on.
579 | self.width = width
580 | self.height = height
581 | self.transparent = transparent
582 | self.background = background
583 | self.gamma = gamma
584 | self.greyscale = bool(greyscale)
585 | self.alpha = bool(alpha)
586 | self.colormap = bool(palette)
587 | self.bitdepth = int(bitdepth)
588 | self.compression = compression
589 | self.chunk_limit = chunk_limit
590 | self.interlace = bool(interlace)
591 | self.palette = check_palette(palette)
592 |
593 | self.color_type = 4*self.alpha + 2*(not greyscale) + 1*self.colormap
594 | assert self.color_type in (0,2,3,4,6)
595 |
596 | self.color_planes = (3,1)[self.greyscale or self.colormap]
597 | self.planes = self.color_planes + self.alpha
598 | # :todo: fix for bitdepth < 8
599 | self.psize = (self.bitdepth/8) * self.planes
600 |
601 | def make_palette(self):
602 | """Create the byte sequences for a ``PLTE`` and if necessary a
603 | ``tRNS`` chunk. Returned as a pair (*p*, *t*). *t* will be
604 | ``None`` if no ``tRNS`` chunk is necessary.
605 | """
606 |
607 | p = array('B')
608 | t = array('B')
609 |
610 | for x in self.palette:
611 | p.extend(x[0:3])
612 | if len(x) > 3:
613 | t.append(x[3])
614 | p = tostring(p)
615 | t = tostring(t)
616 | if t:
617 | return p,t
618 | return p,None
619 |
620 | def write(self, outfile, rows):
621 | """Write a PNG image to the output file. `rows` should be
622 | an iterable that yields each row in boxed row flat pixel
623 | format. The rows should be the rows of the original image,
624 | so there should be ``self.height`` rows of ``self.width *
625 | self.planes`` values. If `interlace` is specified (when
626 | creating the instance), then an interlaced PNG file will
627 | be written. Supply the rows in the normal image order;
628 | the interlacing is carried out internally.
629 |
630 | .. note ::
631 |
632 | Interlacing will require the entire image to be in working
633 | memory.
634 | """
635 |
636 | if self.interlace:
637 | fmt = 'BH'[self.bitdepth > 8]
638 | a = array(fmt, itertools.chain(*rows))
639 | return self.write_array(outfile, a)
640 | else:
641 | nrows = self.write_passes(outfile, rows)
642 | if nrows != self.height:
643 | raise ValueError(
644 | "rows supplied (%d) does not match height (%d)" %
645 | (nrows, self.height))
646 |
647 | def write_passes(self, outfile, rows, packed=False):
648 | """
649 | Write a PNG image to the output file.
650 |
651 | Most users are expected to find the :meth:`write` or
652 | :meth:`write_array` method more convenient.
653 |
654 | The rows should be given to this method in the order that
655 | they appear in the output file. For straightlaced images,
656 | this is the usual top to bottom ordering, but for interlaced
657 | images the rows should have already been interlaced before
658 | passing them to this function.
659 |
660 | `rows` should be an iterable that yields each row. When
661 | `packed` is ``False`` the rows should be in boxed row flat pixel
662 | format; when `packed` is ``True`` each row should be a packed
663 | sequence of bytes.
664 | """
665 |
666 | # http://www.w3.org/TR/PNG/#5PNG-file-signature
667 | outfile.write(_signature)
668 |
669 | # http://www.w3.org/TR/PNG/#11IHDR
670 | write_chunk(outfile, 'IHDR',
671 | struct.pack("!2I5B", self.width, self.height,
672 | self.bitdepth, self.color_type,
673 | 0, 0, self.interlace))
674 |
675 | # See :chunk:order
676 | # http://www.w3.org/TR/PNG/#11gAMA
677 | if self.gamma is not None:
678 | write_chunk(outfile, 'gAMA',
679 | struct.pack("!L", int(round(self.gamma*1e5))))
680 |
681 | # See :chunk:order
682 | # http://www.w3.org/TR/PNG/#11sBIT
683 | if self.rescale:
684 | write_chunk(outfile, 'sBIT',
685 | struct.pack('%dB' % self.planes,
686 | *[self.rescale[0]]*self.planes))
687 |
688 | # :chunk:order: Without a palette (PLTE chunk), ordering is
689 | # relatively relaxed. With one, gAMA chunk must precede PLTE
690 | # chunk which must precede tRNS and bKGD.
691 | # See http://www.w3.org/TR/PNG/#5ChunkOrdering
692 | if self.palette:
693 | p,t = self.make_palette()
694 | write_chunk(outfile, 'PLTE', p)
695 | if t:
696 | # tRNS chunk is optional. Only needed if palette entries
697 | # have alpha.
698 | write_chunk(outfile, 'tRNS', t)
699 |
700 | # http://www.w3.org/TR/PNG/#11tRNS
701 | if self.transparent is not None:
702 | if self.greyscale:
703 | write_chunk(outfile, 'tRNS',
704 | struct.pack("!1H", *self.transparent))
705 | else:
706 | write_chunk(outfile, 'tRNS',
707 | struct.pack("!3H", *self.transparent))
708 |
709 | # http://www.w3.org/TR/PNG/#11bKGD
710 | if self.background is not None:
711 | if self.greyscale:
712 | write_chunk(outfile, 'bKGD',
713 | struct.pack("!1H", *self.background))
714 | else:
715 | write_chunk(outfile, 'bKGD',
716 | struct.pack("!3H", *self.background))
717 |
718 | # http://www.w3.org/TR/PNG/#11IDAT
719 | if self.compression is not None:
720 | compressor = zlib.compressobj(self.compression)
721 | else:
722 | compressor = zlib.compressobj()
723 |
724 | # Choose an extend function based on the bitdepth. The extend
725 | # function packs/decomposes the pixel values into bytes and
726 | # stuffs them onto the data array.
727 | data = array('B')
728 | if self.bitdepth == 8 or packed:
729 | extend = data.extend
730 | elif self.bitdepth == 16:
731 | # Decompose into bytes
732 | def extend(sl):
733 | fmt = '!%dH' % len(sl)
734 | data.extend(array('B', struct.pack(fmt, *sl)))
735 | else:
736 | # Pack into bytes
737 | assert self.bitdepth < 8
738 | # samples per byte
739 | spb = int(8/self.bitdepth)
740 | def extend(sl):
741 | a = array('B', sl)
742 | # Adding padding bytes so we can group into a whole
743 | # number of spb-tuples.
744 | l = float(len(a))
745 | extra = math.ceil(l / float(spb))*spb - l
746 | a.extend([0]*int(extra))
747 | # Pack into bytes
748 | l = group(a, spb)
749 | l = map(lambda e: reduce(lambda x,y:
750 | (x << self.bitdepth) + y, e), l)
751 | data.extend(l)
752 | if self.rescale:
753 | oldextend = extend
754 | factor = \
755 | float(2**self.rescale[1]-1) / float(2**self.rescale[0]-1)
756 | def extend(sl):
757 | oldextend(map(lambda x: int(round(factor*x)), sl))
758 |
759 | # Build the first row, testing mostly to see if we need to
760 | # changed the extend function to cope with NumPy integer types
761 | # (they cause our ordinary definition of extend to fail, so we
762 | # wrap it). See
763 | # http://code.google.com/p/pypng/issues/detail?id=44
764 | enumrows = enumerate(rows)
765 | del rows
766 |
767 | # First row's filter type.
768 | data.append(0)
769 | # :todo: Certain exceptions in the call to ``.next()`` or the
770 | # following try would indicate no row data supplied.
771 | # Should catch.
772 | i,row = enumrows.next()
773 | try:
774 | # If this fails...
775 | extend(row)
776 | except:
777 | # ... try a version that converts the values to int first.
778 | # Not only does this work for the (slightly broken) NumPy
779 | # types, there are probably lots of other, unknown, "nearly"
780 | # int types it works for.
781 | def wrapmapint(f):
782 | return lambda sl: f(map(int, sl))
783 | extend = wrapmapint(extend)
784 | del wrapmapint
785 | extend(row)
786 |
787 | for i,row in enumrows:
788 | # Add "None" filter type. Currently, it's essential that
789 | # this filter type be used for every scanline as we do not
790 | # mark the first row of a reduced pass image; that means we
791 | # could accidentally compute the wrong filtered scanline if
792 | # we used "up", "average", or "paeth" on such a line.
793 | data.append(0)
794 | extend(row)
795 | if len(data) > self.chunk_limit:
796 | compressed = compressor.compress(tostring(data))
797 | if len(compressed):
798 | write_chunk(outfile, 'IDAT', compressed)
799 | # Because of our very witty definition of ``extend``,
800 | # above, we must re-use the same ``data`` object. Hence
801 | # we use ``del`` to empty this one, rather than create a
802 | # fresh one (which would be my natural FP instinct).
803 | del data[:]
804 | if len(data):
805 | compressed = compressor.compress(tostring(data))
806 | else:
807 | compressed = ''
808 | flushed = compressor.flush()
809 | if len(compressed) or len(flushed):
810 | write_chunk(outfile, 'IDAT', compressed + flushed)
811 | # http://www.w3.org/TR/PNG/#11IEND
812 | write_chunk(outfile, 'IEND')
813 | return i+1
814 |
815 | def write_array(self, outfile, pixels):
816 | """
817 | Write an array in flat row flat pixel format as a PNG file on
818 | the output file. See also :meth:`write` method.
819 | """
820 |
821 | if self.interlace:
822 | self.write_passes(outfile, self.array_scanlines_interlace(pixels))
823 | else:
824 | self.write_passes(outfile, self.array_scanlines(pixels))
825 |
826 | def write_packed(self, outfile, rows):
827 | """
828 | Write PNG file to `outfile`. The pixel data comes from `rows`
829 | which should be in boxed row packed format. Each row should be
830 | a sequence of packed bytes.
831 |
832 | Technically, this method does work for interlaced images but it
833 | is best avoided. For interlaced images, the rows should be
834 | presented in the order that they appear in the file.
835 |
836 | This method should not be used when the source image bit depth
837 | is not one naturally supported by PNG; the bit depth should be
838 | 1, 2, 4, 8, or 16.
839 | """
840 |
841 | if self.rescale:
842 | raise Error("write_packed method not suitable for bit depth %d" %
843 | self.rescale[0])
844 | return self.write_passes(outfile, rows, packed=True)
845 |
846 | def convert_pnm(self, infile, outfile):
847 | """
848 | Convert a PNM file containing raw pixel data into a PNG file
849 | with the parameters set in the writer object. Works for
850 | (binary) PGM, PPM, and PAM formats.
851 | """
852 |
853 | if self.interlace:
854 | pixels = array('B')
855 | pixels.fromfile(infile,
856 | (self.bitdepth/8) * self.color_planes *
857 | self.width * self.height)
858 | self.write_passes(outfile, self.array_scanlines_interlace(pixels))
859 | else:
860 | self.write_passes(outfile, self.file_scanlines(infile))
861 |
862 | def convert_ppm_and_pgm(self, ppmfile, pgmfile, outfile):
863 | """
864 | Convert a PPM and PGM file containing raw pixel data into a
865 | PNG outfile with the parameters set in the writer object.
866 | """
867 | pixels = array('B')
868 | pixels.fromfile(ppmfile,
869 | (self.bitdepth/8) * self.color_planes *
870 | self.width * self.height)
871 | apixels = array('B')
872 | apixels.fromfile(pgmfile,
873 | (self.bitdepth/8) *
874 | self.width * self.height)
875 | pixels = interleave_planes(pixels, apixels,
876 | (self.bitdepth/8) * self.color_planes,
877 | (self.bitdepth/8))
878 | if self.interlace:
879 | self.write_passes(outfile, self.array_scanlines_interlace(pixels))
880 | else:
881 | self.write_passes(outfile, self.array_scanlines(pixels))
882 |
883 | def file_scanlines(self, infile):
884 | """
885 | Generates boxed rows in flat pixel format, from the input file
886 | `infile`. It assumes that the input file is in a "Netpbm-like"
887 | binary format, and is positioned at the beginning of the first
888 | pixel. The number of pixels to read is taken from the image
889 | dimensions (`width`, `height`, `planes`) and the number of bytes
890 | per value is implied by the image `bitdepth`.
891 | """
892 |
893 | # Values per row
894 | vpr = self.width * self.planes
895 | row_bytes = vpr
896 | if self.bitdepth > 8:
897 | assert self.bitdepth == 16
898 | row_bytes *= 2
899 | fmt = '>%dH' % vpr
900 | def line():
901 | return array('H', struct.unpack(fmt, infile.read(row_bytes)))
902 | else:
903 | def line():
904 | scanline = array('B', infile.read(row_bytes))
905 | return scanline
906 | for y in range(self.height):
907 | yield line()
908 |
909 | def array_scanlines(self, pixels):
910 | """
911 | Generates boxed rows (flat pixels) from flat rows (flat pixels)
912 | in an array.
913 | """
914 |
915 | # Values per row
916 | vpr = self.width * self.planes
917 | stop = 0
918 | for y in range(self.height):
919 | start = stop
920 | stop = start + vpr
921 | yield pixels[start:stop]
922 |
923 | def array_scanlines_interlace(self, pixels):
924 | """
925 | Generator for interlaced scanlines from an array. `pixels` is
926 | the full source image in flat row flat pixel format. The
927 | generator yields each scanline of the reduced passes in turn, in
928 | boxed row flat pixel format.
929 | """
930 |
931 | # http://www.w3.org/TR/PNG/#8InterlaceMethods
932 | # Array type.
933 | fmt = 'BH'[self.bitdepth > 8]
934 | # Value per row
935 | vpr = self.width * self.planes
936 | for xstart, ystart, xstep, ystep in _adam7:
937 | if xstart >= self.width:
938 | continue
939 | # Pixels per row (of reduced image)
940 | ppr = int(math.ceil((self.width-xstart)/float(xstep)))
941 | # number of values in reduced image row.
942 | row_len = ppr*self.planes
943 | for y in range(ystart, self.height, ystep):
944 | if xstep == 1:
945 | offset = y * vpr
946 | yield pixels[offset:offset+vpr]
947 | else:
948 | row = array(fmt)
949 | # There's no easier way to set the length of an array
950 | row.extend(pixels[0:row_len])
951 | offset = y * vpr + xstart * self.planes
952 | end_offset = (y+1) * vpr
953 | skip = self.planes * xstep
954 | for i in range(self.planes):
955 | row[i::self.planes] = \
956 | pixels[offset+i:end_offset:skip]
957 | yield row
958 |
959 | def write_chunk(outfile, tag, data=strtobytes('')):
960 | """
961 | Write a PNG chunk to the output file, including length and
962 | checksum.
963 | """
964 |
965 | # http://www.w3.org/TR/PNG/#5Chunk-layout
966 | outfile.write(struct.pack("!I", len(data)))
967 | tag = strtobytes(tag)
968 | outfile.write(tag)
969 | outfile.write(data)
970 | checksum = zlib.crc32(tag)
971 | checksum = zlib.crc32(data, checksum)
972 | checksum &= 2**32-1
973 | outfile.write(struct.pack("!I", checksum))
974 |
975 | def write_chunks(out, chunks):
976 | """Create a PNG file by writing out the chunks."""
977 |
978 | out.write(_signature)
979 | for chunk in chunks:
980 | write_chunk(out, *chunk)
981 |
982 | def filter_scanline(type, line, fo, prev=None):
983 | """Apply a scanline filter to a scanline. `type` specifies the
984 | filter type (0 to 4); `line` specifies the current (unfiltered)
985 | scanline as a sequence of bytes; `prev` specifies the previous
986 | (unfiltered) scanline as a sequence of bytes. `fo` specifies the
987 | filter offset; normally this is size of a pixel in bytes (the number
988 | of bytes per sample times the number of channels), but when this is
989 | < 1 (for bit depths < 8) then the filter offset is 1.
990 | """
991 |
992 | assert 0 <= type < 5
993 |
994 | # The output array. Which, pathetically, we extend one-byte at a
995 | # time (fortunately this is linear).
996 | out = array('B', [type])
997 |
998 | def sub():
999 | ai = -fo
1000 | for x in line:
1001 | if ai >= 0:
1002 | x = (x - line[ai]) & 0xff
1003 | out.append(x)
1004 | ai += 1
1005 | def up():
1006 | for i,x in enumerate(line):
1007 | x = (x - prev[i]) & 0xff
1008 | out.append(x)
1009 | def average():
1010 | ai = -fo
1011 | for i,x in enumerate(line):
1012 | if ai >= 0:
1013 | x = (x - ((line[ai] + prev[i]) >> 1)) & 0xff
1014 | else:
1015 | x = (x - (prev[i] >> 1)) & 0xff
1016 | out.append(x)
1017 | ai += 1
1018 | def paeth():
1019 | # http://www.w3.org/TR/PNG/#9Filter-type-4-Paeth
1020 | ai = -fo # also used for ci
1021 | for i,x in enumerate(line):
1022 | a = 0
1023 | b = prev[i]
1024 | c = 0
1025 |
1026 | if ai >= 0:
1027 | a = line[ai]
1028 | c = prev[ai]
1029 | p = a + b - c
1030 | pa = abs(p - a)
1031 | pb = abs(p - b)
1032 | pc = abs(p - c)
1033 | if pa <= pb and pa <= pc: Pr = a
1034 | elif pb <= pc: Pr = b
1035 | else: Pr = c
1036 |
1037 | x = (x - Pr) & 0xff
1038 | out.append(x)
1039 | ai += 1
1040 |
1041 | if not prev:
1042 | # We're on the first line. Some of the filters can be reduced
1043 | # to simpler cases which makes handling the line "off the top"
1044 | # of the image simpler. "up" becomes "none"; "paeth" becomes
1045 | # "left" (non-trivial, but true). "average" needs to be handled
1046 | # specially.
1047 | if type == 2: # "up"
1048 | type = 0
1049 | elif type == 3:
1050 | prev = [0]*len(line)
1051 | elif type == 4: # "paeth"
1052 | type = 1
1053 | if type == 0:
1054 | out.extend(line)
1055 | elif type == 1:
1056 | sub()
1057 | elif type == 2:
1058 | up()
1059 | elif type == 3:
1060 | average()
1061 | else: # type == 4
1062 | paeth()
1063 | return out
1064 |
1065 |
1066 | def from_array(a, mode=None, info={}):
1067 | """Create a PNG :class:`Image` object from a 2- or 3-dimensional
1068 | array. One application of this function is easy PIL-style saving:
1069 | ``png.from_array(pixels, 'L').save('foo.png')``.
1070 |
1071 | .. note :
1072 |
1073 | The use of the term *3-dimensional* is for marketing purposes
1074 | only. It doesn't actually work. Please bear with us. Meanwhile
1075 | enjoy the complimentary snacks (on request) and please use a
1076 | 2-dimensional array.
1077 |
1078 | Unless they are specified using the *info* parameter, the PNG's
1079 | height and width are taken from the array size. For a 3 dimensional
1080 | array the first axis is the height; the second axis is the width;
1081 | and the third axis is the channel number. Thus an RGB image that is
1082 | 16 pixels high and 8 wide will use an array that is 16x8x3. For 2
1083 | dimensional arrays the first axis is the height, but the second axis
1084 | is ``width*channels``, so an RGB image that is 16 pixels high and 8
1085 | wide will use a 2-dimensional array that is 16x24 (each row will be
1086 | 8*3==24 sample values).
1087 |
1088 | *mode* is a string that specifies the image colour format in a
1089 | PIL-style mode. It can be:
1090 |
1091 | ``'L'``
1092 | greyscale (1 channel)
1093 | ``'LA'``
1094 | greyscale with alpha (2 channel)
1095 | ``'RGB'``
1096 | colour image (3 channel)
1097 | ``'RGBA'``
1098 | colour image with alpha (4 channel)
1099 |
1100 | The mode string can also specify the bit depth (overriding how this
1101 | function normally derives the bit depth, see below). Appending
1102 | ``';16'`` to the mode will cause the PNG to be 16 bits per channel;
1103 | any decimal from 1 to 16 can be used to specify the bit depth.
1104 |
1105 | When a 2-dimensional array is used *mode* determines how many
1106 | channels the image has, and so allows the width to be derived from
1107 | the second array dimension.
1108 |
1109 | The array is expected to be a ``numpy`` array, but it can be any
1110 | suitable Python sequence. For example, a list of lists can be used:
1111 | ``png.from_array([[0, 255, 0], [255, 0, 255]], 'L')``. The exact
1112 | rules are: ``len(a)`` gives the first dimension, height;
1113 | ``len(a[0])`` gives the second dimension; ``len(a[0][0])`` gives the
1114 | third dimension, unless an exception is raised in which case a
1115 | 2-dimensional array is assumed. It's slightly more complicated than
1116 | that because an iterator of rows can be used, and it all still
1117 | works. Using an iterator allows data to be streamed efficiently.
1118 |
1119 | The bit depth of the PNG is normally taken from the array element's
1120 | datatype (but if *mode* specifies a bitdepth then that is used
1121 | instead). The array element's datatype is determined in a way which
1122 | is supposed to work both for ``numpy`` arrays and for Python
1123 | ``array.array`` objects. A 1 byte datatype will give a bit depth of
1124 | 8, a 2 byte datatype will give a bit depth of 16. If the datatype
1125 | does not have an implicit size, for example it is a plain Python
1126 | list of lists, as above, then a default of 8 is used.
1127 |
1128 | The *info* parameter is a dictionary that can be used to specify
1129 | metadata (in the same style as the arguments to the
1130 | :class:``png.Writer`` class). For this function the keys that are
1131 | useful are:
1132 |
1133 | height
1134 | overrides the height derived from the array dimensions and allows
1135 | *a* to be an iterable.
1136 | width
1137 | overrides the width derived from the array dimensions.
1138 | bitdepth
1139 | overrides the bit depth derived from the element datatype (but
1140 | must match *mode* if that also specifies a bit depth).
1141 |
1142 | Generally anything specified in the
1143 | *info* dictionary will override any implicit choices that this
1144 | function would otherwise make, but must match any explicit ones.
1145 | For example, if the *info* dictionary has a ``greyscale`` key then
1146 | this must be true when mode is ``'L'`` or ``'LA'`` and false when
1147 | mode is ``'RGB'`` or ``'RGBA'``.
1148 | """
1149 |
1150 | # We abuse the *info* parameter by modifying it. Take a copy here.
1151 | # (Also typechecks *info* to some extent).
1152 | info = dict(info)
1153 |
1154 | # Syntax check mode string.
1155 | bitdepth = None
1156 | try:
1157 | # Assign the 'L' or 'RGBA' part to `gotmode`.
1158 | if mode.startswith('L'):
1159 | gotmode = 'L'
1160 | mode = mode[1:]
1161 | elif mode.startswith('RGB'):
1162 | gotmode = 'RGB'
1163 | mode = mode[3:]
1164 | else:
1165 | raise Error()
1166 | if mode.startswith('A'):
1167 | gotmode += 'A'
1168 | mode = mode[1:]
1169 |
1170 | # Skip any optional ';'
1171 | while mode.startswith(';'):
1172 | mode = mode[1:]
1173 |
1174 | # Parse optional bitdepth
1175 | if mode:
1176 | try:
1177 | bitdepth = int(mode)
1178 | except (TypeError, ValueError):
1179 | raise Error()
1180 | except Error:
1181 | raise Error("mode string should be 'RGB' or 'L;16' or similar.")
1182 | mode = gotmode
1183 |
1184 | # Get bitdepth from *mode* if possible.
1185 | if bitdepth:
1186 | if info.get('bitdepth') and bitdepth != info['bitdepth']:
1187 | raise Error("mode bitdepth (%d) should match info bitdepth (%d)." %
1188 | (bitdepth, info['bitdepth']))
1189 | info['bitdepth'] = bitdepth
1190 |
1191 | # Fill in and/or check entries in *info*.
1192 | # Dimensions.
1193 | if 'size' in info:
1194 | # Check width, height, size all match where used.
1195 | for dimension,axis in [('width', 0), ('height', 1)]:
1196 | if dimension in info:
1197 | if info[dimension] != info['size'][axis]:
1198 | raise Error(
1199 | "info[%r] should match info['size'][%r]." %
1200 | (dimension, axis))
1201 | info['width'],info['height'] = info['size']
1202 | if 'height' not in info:
1203 | try:
1204 | l = len(a)
1205 | except TypeError:
1206 | raise Error(
1207 | "len(a) does not work, supply info['height'] instead.")
1208 | info['height'] = l
1209 | # Colour format.
1210 | if 'greyscale' in info:
1211 | if bool(info['greyscale']) != ('L' in mode):
1212 | raise Error("info['greyscale'] should match mode.")
1213 | info['greyscale'] = 'L' in mode
1214 | if 'alpha' in info:
1215 | if bool(info['alpha']) != ('A' in mode):
1216 | raise Error("info['alpha'] should match mode.")
1217 | info['alpha'] = 'A' in mode
1218 |
1219 | planes = len(mode)
1220 | if 'planes' in info:
1221 | if info['planes'] != planes:
1222 | raise Error("info['planes'] should match mode.")
1223 |
1224 | # In order to work out whether we the array is 2D or 3D we need its
1225 | # first row, which requires that we take a copy of its iterator.
1226 | # We may also need the first row to derive width and bitdepth.
1227 | a,t = itertools.tee(a)
1228 | row = t.next()
1229 | del t
1230 | try:
1231 | row[0][0]
1232 | threed = True
1233 | testelement = row[0]
1234 | except (IndexError, TypeError):
1235 | threed = False
1236 | testelement = row
1237 | if 'width' not in info:
1238 | if threed:
1239 | width = len(row)
1240 | else:
1241 | width = len(row) // planes
1242 | info['width'] = width
1243 |
1244 | # Not implemented yet
1245 | assert not threed
1246 |
1247 | if 'bitdepth' not in info:
1248 | try:
1249 | dtype = testelement.dtype
1250 | # goto the "else:" clause. Sorry.
1251 | except AttributeError:
1252 | try:
1253 | # Try a Python array.array.
1254 | bitdepth = 8 * testelement.itemsize
1255 | except AttributeError:
1256 | # We can't determine it from the array element's
1257 | # datatype, use a default of 8.
1258 | bitdepth = 8
1259 | else:
1260 | # If we got here without exception, we now assume that
1261 | # the array is a numpy array.
1262 | if dtype.kind == 'b':
1263 | bitdepth = 1
1264 | else:
1265 | bitdepth = 8 * dtype.itemsize
1266 | info['bitdepth'] = bitdepth
1267 |
1268 | for thing in 'width height bitdepth greyscale alpha'.split():
1269 | assert thing in info
1270 | return Image(a, info)
1271 |
1272 | # So that refugee's from PIL feel more at home. Not documented.
1273 | fromarray = from_array
1274 |
1275 | class Image:
1276 | """A PNG image. You can create an :class:`Image` object from
1277 | an array of pixels by calling :meth:`png.from_array`. It can be
1278 | saved to disk with the :meth:`save` method.
1279 | """
1280 |
1281 | def __init__(self, rows, info):
1282 | """
1283 | .. note ::
1284 |
1285 | The constructor is not public. Please do not call it.
1286 | """
1287 |
1288 | self.rows = rows
1289 | self.info = info
1290 |
1291 | def save(self, file):
1292 | """Save the image to *file*. If *file* looks like an open file
1293 | descriptor then it is used, otherwise it is treated as a
1294 | filename and a fresh file is opened.
1295 |
1296 | In general, you can only call this method once; after it has
1297 | been called the first time and the PNG image has been saved, the
1298 | source data will have been streamed, and cannot be streamed
1299 | again.
1300 | """
1301 |
1302 | w = Writer(**self.info)
1303 |
1304 | try:
1305 | file.write
1306 | def close(): pass
1307 | except AttributeError:
1308 | file = open(file, 'wb')
1309 | def close(): file.close()
1310 |
1311 | try:
1312 | w.write(file, self.rows)
1313 | finally:
1314 | close()
1315 |
1316 | class _readable:
1317 | """
1318 | A simple file-like interface for strings and arrays.
1319 | """
1320 |
1321 | def __init__(self, buf):
1322 | self.buf = buf
1323 | self.offset = 0
1324 |
1325 | def read(self, n):
1326 | r = self.buf[self.offset:self.offset+n]
1327 | if isarray(r):
1328 | r = r.tostring()
1329 | self.offset += n
1330 | return r
1331 |
1332 |
1333 | class Reader:
1334 | """
1335 | PNG decoder in pure Python.
1336 | """
1337 |
1338 | def __init__(self, _guess=None, **kw):
1339 | """
1340 | Create a PNG decoder object.
1341 |
1342 | The constructor expects exactly one keyword argument. If you
1343 | supply a positional argument instead, it will guess the input
1344 | type. You can choose among the following keyword arguments:
1345 |
1346 | filename
1347 | Name of input file (a PNG file).
1348 | file
1349 | A file-like object (object with a read() method).
1350 | bytes
1351 | ``array`` or ``string`` with PNG data.
1352 |
1353 | """
1354 | if ((_guess is not None and len(kw) != 0) or
1355 | (_guess is None and len(kw) != 1)):
1356 | raise TypeError("Reader() takes exactly 1 argument")
1357 |
1358 | # Will be the first 8 bytes, later on. See validate_signature.
1359 | self.signature = None
1360 | self.transparent = None
1361 | # A pair of (len,type) if a chunk has been read but its data and
1362 | # checksum have not (in other words the file position is just
1363 | # past the 4 bytes that specify the chunk type). See preamble
1364 | # method for how this is used.
1365 | self.atchunk = None
1366 |
1367 | if _guess is not None:
1368 | if isarray(_guess):
1369 | kw["bytes"] = _guess
1370 | elif isinstance(_guess, str):
1371 | kw["filename"] = _guess
1372 | elif hasattr(_guess, 'read'):
1373 | kw["file"] = _guess
1374 |
1375 | if "filename" in kw:
1376 | self.file = open(kw["filename"], "rb")
1377 | elif "file" in kw:
1378 | self.file = kw["file"]
1379 | elif "bytes" in kw:
1380 | self.file = _readable(kw["bytes"])
1381 | else:
1382 | raise TypeError("expecting filename, file or bytes array")
1383 |
1384 |
1385 | def chunk(self, seek=None, lenient=False):
1386 | """
1387 | Read the next PNG chunk from the input file; returns a
1388 | (*type*,*data*) tuple. *type* is the chunk's type as a string
1389 | (all PNG chunk types are 4 characters long). *data* is the
1390 | chunk's data content, as a string.
1391 |
1392 | If the optional `seek` argument is
1393 | specified then it will keep reading chunks until it either runs
1394 | out of file or finds the type specified by the argument. Note
1395 | that in general the order of chunks in PNGs is unspecified, so
1396 | using `seek` can cause you to miss chunks.
1397 |
1398 | If the optional `lenient` argument evaluates to True,
1399 | checksum failures will raise warnings rather than exceptions.
1400 | """
1401 |
1402 | self.validate_signature()
1403 |
1404 | while True:
1405 | # http://www.w3.org/TR/PNG/#5Chunk-layout
1406 | if not self.atchunk:
1407 | self.atchunk = self.chunklentype()
1408 | length,type = self.atchunk
1409 | self.atchunk = None
1410 | data = self.file.read(length)
1411 | if len(data) != length:
1412 | raise ChunkError('Chunk %s too short for required %i octets.'
1413 | % (type, length))
1414 | checksum = self.file.read(4)
1415 | if len(checksum) != 4:
1416 | raise ValueError('Chunk %s too short for checksum.', tag)
1417 | if seek and type != seek:
1418 | continue
1419 | verify = zlib.crc32(strtobytes(type))
1420 | verify = zlib.crc32(data, verify)
1421 | # Whether the output from zlib.crc32 is signed or not varies
1422 | # according to hideous implementation details, see
1423 | # http://bugs.python.org/issue1202 .
1424 | # We coerce it to be positive here (in a way which works on
1425 | # Python 2.3 and older).
1426 | verify &= 2**32 - 1
1427 | verify = struct.pack('!I', verify)
1428 | if checksum != verify:
1429 | (a, ) = struct.unpack('!I', checksum)
1430 | (b, ) = struct.unpack('!I', verify)
1431 | message = "Checksum error in %s chunk: 0x%08X != 0x%08X." % (type, a, b)
1432 | if lenient:
1433 | warnings.warn(message, RuntimeWarning)
1434 | else:
1435 | raise ChunkError(message)
1436 | return type, data
1437 |
1438 | def chunks(self):
1439 | """Return an iterator that will yield each chunk as a
1440 | (*chunktype*, *content*) pair.
1441 | """
1442 |
1443 | while True:
1444 | t,v = self.chunk()
1445 | yield t,v
1446 | if t == 'IEND':
1447 | break
1448 |
1449 | def undo_filter(self, filter_type, scanline, previous):
1450 | """Undo the filter for a scanline. `scanline` is a sequence of
1451 | bytes that does not include the initial filter type byte.
1452 | `previous` is decoded previous scanline (for straightlaced
1453 | images this is the previous pixel row, but for interlaced
1454 | images, it is the previous scanline in the reduced image, which
1455 | in general is not the previous pixel row in the final image).
1456 | When there is no previous scanline (the first row of a
1457 | straightlaced image, or the first row in one of the passes in an
1458 | interlaced image), then this argument should be ``None``.
1459 |
1460 | The scanline will have the effects of filtering removed, and the
1461 | result will be returned as a fresh sequence of bytes.
1462 | """
1463 |
1464 | # :todo: Would it be better to update scanline in place?
1465 | # Yes, with the Cython extension making the undo_filter fast,
1466 | # updating scanline inplace makes the code 3 times faster
1467 | # (reading 50 images of 800x800 went from 40s to 16s)
1468 | result = scanline
1469 |
1470 | if filter_type == 0:
1471 | return result
1472 |
1473 | if filter_type not in (1,2,3,4):
1474 | raise FormatError('Invalid PNG Filter Type.'
1475 | ' See http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters .')
1476 |
1477 | # Filter unit. The stride from one pixel to the corresponding
1478 | # byte from the previous pixel. Normally this is the pixel
1479 | # size in bytes, but when this is smaller than 1, the previous
1480 | # byte is used instead.
1481 | fu = max(1, self.psize)
1482 |
1483 | # For the first line of a pass, synthesize a dummy previous
1484 | # line. An alternative approach would be to observe that on the
1485 | # first line 'up' is the same as 'null', 'paeth' is the same
1486 | # as 'sub', with only 'average' requiring any special case.
1487 | if not previous:
1488 | previous = array('B', [0]*len(scanline))
1489 |
1490 | def sub():
1491 | """Undo sub filter."""
1492 |
1493 | ai = 0
1494 | # Loop starts at index fu. Observe that the initial part
1495 | # of the result is already filled in correctly with
1496 | # scanline.
1497 | for i in range(fu, len(result)):
1498 | x = scanline[i]
1499 | a = result[ai]
1500 | result[i] = (x + a) & 0xff
1501 | ai += 1
1502 |
1503 | def up():
1504 | """Undo up filter."""
1505 |
1506 | for i in range(len(result)):
1507 | x = scanline[i]
1508 | b = previous[i]
1509 | result[i] = (x + b) & 0xff
1510 |
1511 | def average():
1512 | """Undo average filter."""
1513 |
1514 | ai = -fu
1515 | for i in range(len(result)):
1516 | x = scanline[i]
1517 | if ai < 0:
1518 | a = 0
1519 | else:
1520 | a = result[ai]
1521 | b = previous[i]
1522 | result[i] = (x + ((a + b) >> 1)) & 0xff
1523 | ai += 1
1524 |
1525 | def paeth():
1526 | """Undo Paeth filter."""
1527 |
1528 | # Also used for ci.
1529 | ai = -fu
1530 | for i in range(len(result)):
1531 | x = scanline[i]
1532 | if ai < 0:
1533 | a = c = 0
1534 | else:
1535 | a = result[ai]
1536 | c = previous[ai]
1537 | b = previous[i]
1538 | p = a + b - c
1539 | pa = abs(p - a)
1540 | pb = abs(p - b)
1541 | pc = abs(p - c)
1542 | if pa <= pb and pa <= pc:
1543 | pr = a
1544 | elif pb <= pc:
1545 | pr = b
1546 | else:
1547 | pr = c
1548 | result[i] = (x + pr) & 0xff
1549 | ai += 1
1550 |
1551 | # Call appropriate filter algorithm. Note that 0 has already
1552 | # been dealt with.
1553 | (None,
1554 | pngfilters.undo_filter_sub,
1555 | pngfilters.undo_filter_up,
1556 | pngfilters.undo_filter_average,
1557 | pngfilters.undo_filter_paeth)[filter_type](fu, scanline, previous, result)
1558 | return result
1559 |
1560 | def deinterlace(self, raw):
1561 | """
1562 | Read raw pixel data, undo filters, deinterlace, and flatten.
1563 | Return in flat row flat pixel format.
1564 | """
1565 |
1566 | # Values per row (of the target image)
1567 | vpr = self.width * self.planes
1568 |
1569 | # Make a result array, and make it big enough. Interleaving
1570 | # writes to the output array randomly (well, not quite), so the
1571 | # entire output array must be in memory.
1572 | fmt = 'BH'[self.bitdepth > 8]
1573 | a = array(fmt, [0]*vpr*self.height)
1574 | source_offset = 0
1575 |
1576 | for xstart, ystart, xstep, ystep in _adam7:
1577 | if xstart >= self.width:
1578 | continue
1579 | # The previous (reconstructed) scanline. None at the
1580 | # beginning of a pass to indicate that there is no previous
1581 | # line.
1582 | recon = None
1583 | # Pixels per row (reduced pass image)
1584 | ppr = int(math.ceil((self.width-xstart)/float(xstep)))
1585 | # Row size in bytes for this pass.
1586 | row_size = int(math.ceil(self.psize * ppr))
1587 | for y in range(ystart, self.height, ystep):
1588 | filter_type = raw[source_offset]
1589 | source_offset += 1
1590 | scanline = raw[source_offset:source_offset+row_size]
1591 | source_offset += row_size
1592 | recon = self.undo_filter(filter_type, scanline, recon)
1593 | # Convert so that there is one element per pixel value
1594 | flat = self.serialtoflat(recon, ppr)
1595 | if xstep == 1:
1596 | assert xstart == 0
1597 | offset = y * vpr
1598 | a[offset:offset+vpr] = flat
1599 | else:
1600 | offset = y * vpr + xstart * self.planes
1601 | end_offset = (y+1) * vpr
1602 | skip = self.planes * xstep
1603 | for i in range(self.planes):
1604 | a[offset+i:end_offset:skip] = \
1605 | flat[i::self.planes]
1606 | return a
1607 |
1608 | def iterboxed(self, rows):
1609 | """Iterator that yields each scanline in boxed row flat pixel
1610 | format. `rows` should be an iterator that yields the bytes of
1611 | each row in turn.
1612 | """
1613 |
1614 | def asvalues(raw):
1615 | """Convert a row of raw bytes into a flat row. Result may
1616 | or may not share with argument"""
1617 |
1618 | if self.bitdepth == 8:
1619 | return raw
1620 | if self.bitdepth == 16:
1621 | raw = tostring(raw)
1622 | return array('H', struct.unpack('!%dH' % (len(raw)//2), raw))
1623 | assert self.bitdepth < 8
1624 | width = self.width
1625 | # Samples per byte
1626 | spb = 8//self.bitdepth
1627 | out = array('B')
1628 | mask = 2**self.bitdepth - 1
1629 | shifts = map(self.bitdepth.__mul__, reversed(range(spb)))
1630 | for o in raw:
1631 | out.extend(map(lambda i: mask&(o>>i), shifts))
1632 | return out[:width]
1633 |
1634 | return itertools.imap(asvalues, rows)
1635 |
1636 | def serialtoflat(self, bytes, width=None):
1637 | """Convert serial format (byte stream) pixel data to flat row
1638 | flat pixel.
1639 | """
1640 |
1641 | if self.bitdepth == 8:
1642 | return bytes
1643 | if self.bitdepth == 16:
1644 | bytes = tostring(bytes)
1645 | return array('H',
1646 | struct.unpack('!%dH' % (len(bytes)//2), bytes))
1647 | assert self.bitdepth < 8
1648 | if width is None:
1649 | width = self.width
1650 | # Samples per byte
1651 | spb = 8//self.bitdepth
1652 | out = array('B')
1653 | mask = 2**self.bitdepth - 1
1654 | shifts = map(self.bitdepth.__mul__, reversed(range(spb)))
1655 | l = width
1656 | for o in bytes:
1657 | out.extend([(mask&(o>>s)) for s in shifts][:l])
1658 | l -= spb
1659 | if l <= 0:
1660 | l = width
1661 | return out
1662 |
1663 | def iterstraight(self, raw):
1664 | """Iterator that undoes the effect of filtering, and yields
1665 | each row in serialised format (as a sequence of bytes).
1666 | Assumes input is straightlaced. `raw` should be an iterable
1667 | that yields the raw bytes in chunks of arbitrary size.
1668 | """
1669 |
1670 | # length of row, in bytes
1671 | rb = self.row_bytes
1672 | a = array('B')
1673 | # The previous (reconstructed) scanline. None indicates first
1674 | # line of image.
1675 | recon = None
1676 | for some in raw:
1677 | a.extend(some)
1678 | while len(a) >= rb + 1:
1679 | filter_type = a[0]
1680 | scanline = a[1:rb+1]
1681 | del a[:rb+1]
1682 | recon = self.undo_filter(filter_type, scanline, recon)
1683 | yield recon
1684 | if len(a) != 0:
1685 | # :file:format We get here with a file format error:
1686 | # when the available bytes (after decompressing) do not
1687 | # pack into exact rows.
1688 | raise FormatError(
1689 | 'Wrong size for decompressed IDAT chunk.')
1690 | assert len(a) == 0
1691 |
1692 | def validate_signature(self):
1693 | """If signature (header) has not been read then read and
1694 | validate it; otherwise do nothing.
1695 | """
1696 |
1697 | if self.signature:
1698 | return
1699 | self.signature = self.file.read(8)
1700 | if self.signature != _signature:
1701 | raise FormatError("PNG file has invalid signature.")
1702 |
1703 | def preamble(self, lenient=False):
1704 | """
1705 | Extract the image metadata by reading the initial part of
1706 | the PNG file up to the start of the ``IDAT`` chunk. All the
1707 | chunks that precede the ``IDAT`` chunk are read and either
1708 | processed for metadata or discarded.
1709 |
1710 | If the optional `lenient` argument evaluates to True, checksum
1711 | failures will raise warnings rather than exceptions.
1712 | """
1713 |
1714 | self.validate_signature()
1715 |
1716 | while True:
1717 | if not self.atchunk:
1718 | self.atchunk = self.chunklentype()
1719 | if self.atchunk is None:
1720 | raise FormatError(
1721 | 'This PNG file has no IDAT chunks.')
1722 | if self.atchunk[1] == 'IDAT':
1723 | return
1724 | self.process_chunk(lenient=lenient)
1725 |
1726 | def chunklentype(self):
1727 | """Reads just enough of the input to determine the next
1728 | chunk's length and type, returned as a (*length*, *type*) pair
1729 | where *type* is a string. If there are no more chunks, ``None``
1730 | is returned.
1731 | """
1732 |
1733 | x = self.file.read(8)
1734 | if not x:
1735 | return None
1736 | if len(x) != 8:
1737 | raise FormatError(
1738 | 'End of file whilst reading chunk length and type.')
1739 | length,type = struct.unpack('!I4s', x)
1740 | type = bytestostr(type)
1741 | if length > 2**31-1:
1742 | raise FormatError('Chunk %s is too large: %d.' % (type,length))
1743 | return length,type
1744 |
1745 | def process_chunk(self, lenient=False):
1746 | """Process the next chunk and its data. This only processes the
1747 | following chunk types, all others are ignored: ``IHDR``,
1748 | ``PLTE``, ``bKGD``, ``tRNS``, ``gAMA``, ``sBIT``.
1749 |
1750 | If the optional `lenient` argument evaluates to True,
1751 | checksum failures will raise warnings rather than exceptions.
1752 | """
1753 |
1754 | type, data = self.chunk(lenient=lenient)
1755 | method = '_process_' + type
1756 | m = getattr(self, method, None)
1757 | if m:
1758 | m(data)
1759 |
1760 | def _process_IHDR(self, data):
1761 | # http://www.w3.org/TR/PNG/#11IHDR
1762 | if len(data) != 13:
1763 | raise FormatError('IHDR chunk has incorrect length.')
1764 | (self.width, self.height, self.bitdepth, self.color_type,
1765 | self.compression, self.filter,
1766 | self.interlace) = struct.unpack("!2I5B", data)
1767 |
1768 | check_bitdepth_colortype(self.bitdepth, self.color_type)
1769 |
1770 | if self.compression != 0:
1771 | raise Error("unknown compression method %d" % self.compression)
1772 | if self.filter != 0:
1773 | raise FormatError("Unknown filter method %d,"
1774 | " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ."
1775 | % self.filter)
1776 | if self.interlace not in (0,1):
1777 | raise FormatError("Unknown interlace method %d,"
1778 | " see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ."
1779 | % self.interlace)
1780 |
1781 | # Derived values
1782 | # http://www.w3.org/TR/PNG/#6Colour-values
1783 | colormap = bool(self.color_type & 1)
1784 | greyscale = not (self.color_type & 2)
1785 | alpha = bool(self.color_type & 4)
1786 | color_planes = (3,1)[greyscale or colormap]
1787 | planes = color_planes + alpha
1788 |
1789 | self.colormap = colormap
1790 | self.greyscale = greyscale
1791 | self.alpha = alpha
1792 | self.color_planes = color_planes
1793 | self.planes = planes
1794 | self.psize = float(self.bitdepth)/float(8) * planes
1795 | if int(self.psize) == self.psize:
1796 | self.psize = int(self.psize)
1797 | self.row_bytes = int(math.ceil(self.width * self.psize))
1798 | # Stores PLTE chunk if present, and is used to check
1799 | # chunk ordering constraints.
1800 | self.plte = None
1801 | # Stores tRNS chunk if present, and is used to check chunk
1802 | # ordering constraints.
1803 | self.trns = None
1804 | # Stores sbit chunk if present.
1805 | self.sbit = None
1806 |
1807 | def _process_PLTE(self, data):
1808 | # http://www.w3.org/TR/PNG/#11PLTE
1809 | if self.plte:
1810 | warnings.warn("Multiple PLTE chunks present.")
1811 | self.plte = data
1812 | if len(data) % 3 != 0:
1813 | raise FormatError(
1814 | "PLTE chunk's length should be a multiple of 3.")
1815 | if len(data) > (2**self.bitdepth)*3:
1816 | raise FormatError("PLTE chunk is too long.")
1817 | if len(data) == 0:
1818 | raise FormatError("Empty PLTE is not allowed.")
1819 |
1820 | def _process_bKGD(self, data):
1821 | try:
1822 | if self.colormap:
1823 | if not self.plte:
1824 | warnings.warn(
1825 | "PLTE chunk is required before bKGD chunk.")
1826 | self.background = struct.unpack('B', data)
1827 | else:
1828 | self.background = struct.unpack("!%dH" % self.color_planes,
1829 | data)
1830 | except struct.error:
1831 | raise FormatError("bKGD chunk has incorrect length.")
1832 |
1833 | def _process_tRNS(self, data):
1834 | # http://www.w3.org/TR/PNG/#11tRNS
1835 | self.trns = data
1836 | if self.colormap:
1837 | if not self.plte:
1838 | warnings.warn("PLTE chunk is required before tRNS chunk.")
1839 | else:
1840 | if len(data) > len(self.plte)/3:
1841 | # Was warning, but promoted to Error as it
1842 | # would otherwise cause pain later on.
1843 | raise FormatError("tRNS chunk is too long.")
1844 | else:
1845 | if self.alpha:
1846 | raise FormatError(
1847 | "tRNS chunk is not valid with colour type %d." %
1848 | self.color_type)
1849 | try:
1850 | self.transparent = \
1851 | struct.unpack("!%dH" % self.color_planes, data)
1852 | except struct.error:
1853 | raise FormatError("tRNS chunk has incorrect length.")
1854 |
1855 | def _process_gAMA(self, data):
1856 | try:
1857 | self.gamma = struct.unpack("!L", data)[0] / 100000.0
1858 | except struct.error:
1859 | raise FormatError("gAMA chunk has incorrect length.")
1860 |
1861 | def _process_sBIT(self, data):
1862 | self.sbit = data
1863 | if (self.colormap and len(data) != 3 or
1864 | not self.colormap and len(data) != self.planes):
1865 | raise FormatError("sBIT chunk has incorrect length.")
1866 |
1867 | def read(self, lenient=False):
1868 | """
1869 | Read the PNG file and decode it. Returns (`width`, `height`,
1870 | `pixels`, `metadata`).
1871 |
1872 | May use excessive memory.
1873 |
1874 | `pixels` are returned in boxed row flat pixel format.
1875 |
1876 | If the optional `lenient` argument evaluates to True,
1877 | checksum failures will raise warnings rather than exceptions.
1878 | """
1879 |
1880 | def iteridat():
1881 | """Iterator that yields all the ``IDAT`` chunks as strings."""
1882 | while True:
1883 | try:
1884 | type, data = self.chunk(lenient=lenient)
1885 | except ValueError, e:
1886 | raise ChunkError(e.args[0])
1887 | if type == 'IEND':
1888 | # http://www.w3.org/TR/PNG/#11IEND
1889 | break
1890 | if type != 'IDAT':
1891 | continue
1892 | # type == 'IDAT'
1893 | # http://www.w3.org/TR/PNG/#11IDAT
1894 | if self.colormap and not self.plte:
1895 | warnings.warn("PLTE chunk is required before IDAT chunk")
1896 | yield data
1897 |
1898 | def iterdecomp(idat):
1899 | """Iterator that yields decompressed strings. `idat` should
1900 | be an iterator that yields the ``IDAT`` chunk data.
1901 | """
1902 |
1903 | # Currently, with no max_length paramter to decompress, this
1904 | # routine will do one yield per IDAT chunk. So not very
1905 | # incremental.
1906 | d = zlib.decompressobj()
1907 | # Each IDAT chunk is passed to the decompressor, then any
1908 | # remaining state is decompressed out.
1909 | for data in idat:
1910 | # :todo: add a max_length argument here to limit output
1911 | # size.
1912 | yield array('B', d.decompress(data))
1913 | yield array('B', d.flush())
1914 |
1915 | self.preamble(lenient=lenient)
1916 | raw = iterdecomp(iteridat())
1917 |
1918 | if self.interlace:
1919 | raw = array('B', itertools.chain(*raw))
1920 | arraycode = 'BH'[self.bitdepth>8]
1921 | # Like :meth:`group` but producing an array.array object for
1922 | # each row.
1923 | pixels = itertools.imap(lambda *row: array(arraycode, row),
1924 | *[iter(self.deinterlace(raw))]*self.width*self.planes)
1925 | else:
1926 | pixels = self.iterboxed(self.iterstraight(raw))
1927 | meta = dict()
1928 | for attr in 'greyscale alpha planes bitdepth interlace'.split():
1929 | meta[attr] = getattr(self, attr)
1930 | meta['size'] = (self.width, self.height)
1931 | for attr in 'gamma transparent background'.split():
1932 | a = getattr(self, attr, None)
1933 | if a is not None:
1934 | meta[attr] = a
1935 | if self.plte:
1936 | meta['palette'] = self.palette()
1937 | return self.width, self.height, pixels, meta
1938 |
1939 |
1940 | def read_flat(self):
1941 | """
1942 | Read a PNG file and decode it into flat row flat pixel format.
1943 | Returns (*width*, *height*, *pixels*, *metadata*).
1944 |
1945 | May use excessive memory.
1946 |
1947 | `pixels` are returned in flat row flat pixel format.
1948 |
1949 | See also the :meth:`read` method which returns pixels in the
1950 | more stream-friendly boxed row flat pixel format.
1951 | """
1952 |
1953 | x, y, pixel, meta = self.read()
1954 | arraycode = 'BH'[meta['bitdepth']>8]
1955 | pixel = array(arraycode, itertools.chain(*pixel))
1956 | return x, y, pixel, meta
1957 |
1958 | def palette(self, alpha='natural'):
1959 | """Returns a palette that is a sequence of 3-tuples or 4-tuples,
1960 | synthesizing it from the ``PLTE`` and ``tRNS`` chunks. These
1961 | chunks should have already been processed (for example, by
1962 | calling the :meth:`preamble` method). All the tuples are the
1963 | same size: 3-tuples if there is no ``tRNS`` chunk, 4-tuples when
1964 | there is a ``tRNS`` chunk. Assumes that the image is colour type
1965 | 3 and therefore a ``PLTE`` chunk is required.
1966 |
1967 | If the `alpha` argument is ``'force'`` then an alpha channel is
1968 | always added, forcing the result to be a sequence of 4-tuples.
1969 | """
1970 |
1971 | if not self.plte:
1972 | raise FormatError(
1973 | "Required PLTE chunk is missing in colour type 3 image.")
1974 | plte = group(array('B', self.plte), 3)
1975 | if self.trns or alpha == 'force':
1976 | trns = array('B', self.trns or '')
1977 | trns.extend([255]*(len(plte)-len(trns)))
1978 | plte = map(operator.add, plte, group(trns, 1))
1979 | return plte
1980 |
1981 | def asDirect(self):
1982 | """Returns the image data as a direct representation of an
1983 | ``x * y * planes`` array. This method is intended to remove the
1984 | need for callers to deal with palettes and transparency
1985 | themselves. Images with a palette (colour type 3)
1986 | are converted to RGB or RGBA; images with transparency (a
1987 | ``tRNS`` chunk) are converted to LA or RGBA as appropriate.
1988 | When returned in this format the pixel values represent the
1989 | colour value directly without needing to refer to palettes or
1990 | transparency information.
1991 |
1992 | Like the :meth:`read` method this method returns a 4-tuple:
1993 |
1994 | (*width*, *height*, *pixels*, *meta*)
1995 |
1996 | This method normally returns pixel values with the bit depth
1997 | they have in the source image, but when the source PNG has an
1998 | ``sBIT`` chunk it is inspected and can reduce the bit depth of
1999 | the result pixels; pixel values will be reduced according to
2000 | the bit depth specified in the ``sBIT`` chunk (PNG nerds should
2001 | note a single result bit depth is used for all channels; the
2002 | maximum of the ones specified in the ``sBIT`` chunk. An RGB565
2003 | image will be rescaled to 6-bit RGB666).
2004 |
2005 | The *meta* dictionary that is returned reflects the `direct`
2006 | format and not the original source image. For example, an RGB
2007 | source image with a ``tRNS`` chunk to represent a transparent
2008 | colour, will have ``planes=3`` and ``alpha=False`` for the
2009 | source image, but the *meta* dictionary returned by this method
2010 | will have ``planes=4`` and ``alpha=True`` because an alpha
2011 | channel is synthesized and added.
2012 |
2013 | *pixels* is the pixel data in boxed row flat pixel format (just
2014 | like the :meth:`read` method).
2015 |
2016 | All the other aspects of the image data are not changed.
2017 | """
2018 |
2019 | self.preamble()
2020 |
2021 | # Simple case, no conversion necessary.
2022 | if not self.colormap and not self.trns and not self.sbit:
2023 | return self.read()
2024 |
2025 | x,y,pixels,meta = self.read()
2026 |
2027 | if self.colormap:
2028 | meta['colormap'] = False
2029 | meta['alpha'] = bool(self.trns)
2030 | meta['bitdepth'] = 8
2031 | meta['planes'] = 3 + bool(self.trns)
2032 | plte = self.palette()
2033 | def iterpal(pixels):
2034 | for row in pixels:
2035 | row = map(plte.__getitem__, row)
2036 | yield array('B', itertools.chain(*row))
2037 | pixels = iterpal(pixels)
2038 | elif self.trns:
2039 | # It would be nice if there was some reasonable way
2040 | # of doing this without generating a whole load of
2041 | # intermediate tuples. But tuples does seem like the
2042 | # easiest way, with no other way clearly much simpler or
2043 | # much faster. (Actually, the L to LA conversion could
2044 | # perhaps go faster (all those 1-tuples!), but I still
2045 | # wonder whether the code proliferation is worth it)
2046 | it = self.transparent
2047 | maxval = 2**meta['bitdepth']-1
2048 | planes = meta['planes']
2049 | meta['alpha'] = True
2050 | meta['planes'] += 1
2051 | typecode = 'BH'[meta['bitdepth']>8]
2052 | def itertrns(pixels):
2053 | for row in pixels:
2054 | # For each row we group it into pixels, then form a
2055 | # characterisation vector that says whether each
2056 | # pixel is opaque or not. Then we convert
2057 | # True/False to 0/maxval (by multiplication),
2058 | # and add it as the extra channel.
2059 | row = group(row, planes)
2060 | opa = map(it.__ne__, row)
2061 | opa = map(maxval.__mul__, opa)
2062 | opa = zip(opa) # convert to 1-tuples
2063 | yield array(typecode,
2064 | itertools.chain(*map(operator.add, row, opa)))
2065 | pixels = itertrns(pixels)
2066 | targetbitdepth = None
2067 | if self.sbit:
2068 | sbit = struct.unpack('%dB' % len(self.sbit), self.sbit)
2069 | targetbitdepth = max(sbit)
2070 | if targetbitdepth > meta['bitdepth']:
2071 | raise Error('sBIT chunk %r exceeds bitdepth %d' %
2072 | (sbit,self.bitdepth))
2073 | if min(sbit) <= 0:
2074 | raise Error('sBIT chunk %r has a 0-entry' % sbit)
2075 | if targetbitdepth == meta['bitdepth']:
2076 | targetbitdepth = None
2077 | if targetbitdepth:
2078 | shift = meta['bitdepth'] - targetbitdepth
2079 | meta['bitdepth'] = targetbitdepth
2080 | def itershift(pixels):
2081 | for row in pixels:
2082 | yield map(shift.__rrshift__, row)
2083 | pixels = itershift(pixels)
2084 | return x,y,pixels,meta
2085 |
2086 | def asFloat(self, maxval=1.0):
2087 | """Return image pixels as per :meth:`asDirect` method, but scale
2088 | all pixel values to be floating point values between 0.0 and
2089 | *maxval*.
2090 | """
2091 |
2092 | x,y,pixels,info = self.asDirect()
2093 | sourcemaxval = 2**info['bitdepth']-1
2094 | del info['bitdepth']
2095 | info['maxval'] = float(maxval)
2096 | factor = float(maxval)/float(sourcemaxval)
2097 | def iterfloat():
2098 | for row in pixels:
2099 | yield map(factor.__mul__, row)
2100 | return x,y,iterfloat(),info
2101 |
2102 | def _as_rescale(self, get, targetbitdepth):
2103 | """Helper used by :meth:`asRGB8` and :meth:`asRGBA8`."""
2104 |
2105 | width,height,pixels,meta = get()
2106 | maxval = 2**meta['bitdepth'] - 1
2107 | targetmaxval = 2**targetbitdepth - 1
2108 | factor = float(targetmaxval) / float(maxval)
2109 | meta['bitdepth'] = targetbitdepth
2110 | def iterscale():
2111 | for row in pixels:
2112 | yield map(lambda x: int(round(x*factor)), row)
2113 | if maxval == targetmaxval:
2114 | return width, height, pixels, meta
2115 | else:
2116 | return width, height, iterscale(), meta
2117 |
2118 | def asRGB8(self):
2119 | """Return the image data as an RGB pixels with 8-bits per
2120 | sample. This is like the :meth:`asRGB` method except that
2121 | this method additionally rescales the values so that they
2122 | are all between 0 and 255 (8-bit). In the case where the
2123 | source image has a bit depth < 8 the transformation preserves
2124 | all the information; where the source image has bit depth
2125 | > 8, then rescaling to 8-bit values loses precision. No
2126 | dithering is performed. Like :meth:`asRGB`, an alpha channel
2127 | in the source image will raise an exception.
2128 |
2129 | This function returns a 4-tuple:
2130 | (*width*, *height*, *pixels*, *metadata*).
2131 | *width*, *height*, *metadata* are as per the
2132 | :meth:`read` method.
2133 |
2134 | *pixels* is the pixel data in boxed row flat pixel format.
2135 | """
2136 |
2137 | return self._as_rescale(self.asRGB, 8)
2138 |
2139 | def asRGBA8(self):
2140 | """Return the image data as RGBA pixels with 8-bits per
2141 | sample. This method is similar to :meth:`asRGB8` and
2142 | :meth:`asRGBA`: The result pixels have an alpha channel, *and*
2143 | values are rescaled to the range 0 to 255. The alpha channel is
2144 | synthesized if necessary (with a small speed penalty).
2145 | """
2146 |
2147 | return self._as_rescale(self.asRGBA, 8)
2148 |
2149 | def asRGB(self):
2150 | """Return image as RGB pixels. RGB colour images are passed
2151 | through unchanged; greyscales are expanded into RGB
2152 | triplets (there is a small speed overhead for doing this).
2153 |
2154 | An alpha channel in the source image will raise an
2155 | exception.
2156 |
2157 | The return values are as for the :meth:`read` method
2158 | except that the *metadata* reflect the returned pixels, not the
2159 | source image. In particular, for this method
2160 | ``metadata['greyscale']`` will be ``False``.
2161 | """
2162 |
2163 | width,height,pixels,meta = self.asDirect()
2164 | if meta['alpha']:
2165 | raise Error("will not convert image with alpha channel to RGB")
2166 | if not meta['greyscale']:
2167 | return width,height,pixels,meta
2168 | meta['greyscale'] = False
2169 | typecode = 'BH'[meta['bitdepth'] > 8]
2170 | def iterrgb():
2171 | for row in pixels:
2172 | a = array(typecode, [0]) * 3 * width
2173 | for i in range(3):
2174 | a[i::3] = row
2175 | yield a
2176 | return width,height,iterrgb(),meta
2177 |
2178 | def asRGBA(self):
2179 | """Return image as RGBA pixels. Greyscales are expanded into
2180 | RGB triplets; an alpha channel is synthesized if necessary.
2181 | The return values are as for the :meth:`read` method
2182 | except that the *metadata* reflect the returned pixels, not the
2183 | source image. In particular, for this method
2184 | ``metadata['greyscale']`` will be ``False``, and
2185 | ``metadata['alpha']`` will be ``True``.
2186 | """
2187 |
2188 | width,height,pixels,meta = self.asDirect()
2189 | if meta['alpha'] and not meta['greyscale']:
2190 | return width,height,pixels,meta
2191 | typecode = 'BH'[meta['bitdepth'] > 8]
2192 | maxval = 2**meta['bitdepth'] - 1
2193 | maxbuffer = struct.pack('=' + typecode, maxval) * 4 * width
2194 | def newarray():
2195 | return array(typecode, maxbuffer)
2196 |
2197 | if meta['alpha'] and meta['greyscale']:
2198 | # LA to RGBA
2199 | def convert():
2200 | for row in pixels:
2201 | # Create a fresh target row, then copy L channel
2202 | # into first three target channels, and A channel
2203 | # into fourth channel.
2204 | a = newarray()
2205 | pngfilters.convert_la_to_rgba(row, a)
2206 | yield a
2207 | elif meta['greyscale']:
2208 | # L to RGBA
2209 | def convert():
2210 | for row in pixels:
2211 | a = newarray()
2212 | pngfilters.convert_l_to_rgba(row, a)
2213 | yield a
2214 | else:
2215 | assert not meta['alpha'] and not meta['greyscale']
2216 | # RGB to RGBA
2217 | def convert():
2218 | for row in pixels:
2219 | a = newarray()
2220 | pngfilters.convert_rgb_to_rgba(row, a)
2221 | yield a
2222 | meta['alpha'] = True
2223 | meta['greyscale'] = False
2224 | return width,height,convert(),meta
2225 |
2226 | def check_bitdepth_colortype(bitdepth, colortype):
2227 | """Check that `bitdepth` and `colortype` are both valid,
2228 | and specified in a valid combination. Returns if valid,
2229 | raise an Exception if not valid.
2230 | """
2231 |
2232 | if bitdepth not in (1,2,4,8,16):
2233 | raise FormatError("invalid bit depth %d" % bitdepth)
2234 | if colortype not in (0,2,3,4,6):
2235 | raise FormatError("invalid colour type %d" % colortype)
2236 | # Check indexed (palettized) images have 8 or fewer bits
2237 | # per pixel; check only indexed or greyscale images have
2238 | # fewer than 8 bits per pixel.
2239 | if colortype & 1 and bitdepth > 8:
2240 | raise FormatError(
2241 | "Indexed images (colour type %d) cannot"
2242 | " have bitdepth > 8 (bit depth %d)."
2243 | " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ."
2244 | % (bitdepth, colortype))
2245 | if bitdepth < 8 and colortype not in (0,3):
2246 | raise FormatError("Illegal combination of bit depth (%d)"
2247 | " and colour type (%d)."
2248 | " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ."
2249 | % (bitdepth, colortype))
2250 |
2251 | def isinteger(x):
2252 | try:
2253 | return int(x) == x
2254 | except (TypeError, ValueError):
2255 | return False
2256 |
2257 |
2258 | # === Legacy Version Support ===
2259 |
2260 | # :pyver:old: PyPNG works on Python versions 2.3 and 2.2, but not
2261 | # without some awkward problems. Really PyPNG works on Python 2.4 (and
2262 | # above); it works on Pythons 2.3 and 2.2 by virtue of fixing up
2263 | # problems here. It's a bit ugly (which is why it's hidden down here).
2264 | #
2265 | # Generally the strategy is one of pretending that we're running on
2266 | # Python 2.4 (or above), and patching up the library support on earlier
2267 | # versions so that it looks enough like Python 2.4. When it comes to
2268 | # Python 2.2 there is one thing we cannot patch: extended slices
2269 | # http://www.python.org/doc/2.3/whatsnew/section-slices.html.
2270 | # Instead we simply declare that features that are implemented using
2271 | # extended slices will not work on Python 2.2.
2272 | #
2273 | # In order to work on Python 2.3 we fix up a recurring annoyance involving
2274 | # the array type. In Python 2.3 an array cannot be initialised with an
2275 | # array, and it cannot be extended with a list (or other sequence).
2276 | # Both of those are repeated issues in the code. Whilst I would not
2277 | # normally tolerate this sort of behaviour, here we "shim" a replacement
2278 | # for array into place (and hope no-one notices). You never read this.
2279 | #
2280 | # In an amusing case of warty hacks on top of warty hacks... the array
2281 | # shimming we try and do only works on Python 2.3 and above (you can't
2282 | # subclass array.array in Python 2.2). So to get it working on Python
2283 | # 2.2 we go for something much simpler and (probably) way slower.
2284 | try:
2285 | array('B').extend([])
2286 | array('B', array('B'))
2287 | # :todo:(drj) Check that TypeError is correct for Python 2.3
2288 | except TypeError:
2289 | # Expect to get here on Python 2.3
2290 | try:
2291 | class _array_shim(array):
2292 | true_array = array
2293 | def __new__(cls, typecode, init=None):
2294 | super_new = super(_array_shim, cls).__new__
2295 | it = super_new(cls, typecode)
2296 | if init is None:
2297 | return it
2298 | it.extend(init)
2299 | return it
2300 | def extend(self, extension):
2301 | super_extend = super(_array_shim, self).extend
2302 | if isinstance(extension, self.true_array):
2303 | return super_extend(extension)
2304 | if not isinstance(extension, (list, str)):
2305 | # Convert to list. Allows iterators to work.
2306 | extension = list(extension)
2307 | return super_extend(self.true_array(self.typecode, extension))
2308 | array = _array_shim
2309 | except TypeError:
2310 | # Expect to get here on Python 2.2
2311 | def array(typecode, init=()):
2312 | if type(init) == str:
2313 | return map(ord, init)
2314 | return list(init)
2315 |
2316 | # Further hacks to get it limping along on Python 2.2
2317 | try:
2318 | enumerate
2319 | except NameError:
2320 | def enumerate(seq):
2321 | i=0
2322 | for x in seq:
2323 | yield i,x
2324 | i += 1
2325 |
2326 | try:
2327 | reversed
2328 | except NameError:
2329 | def reversed(l):
2330 | l = list(l)
2331 | l.reverse()
2332 | for x in l:
2333 | yield x
2334 |
2335 | try:
2336 | itertools
2337 | except NameError:
2338 | class _dummy_itertools:
2339 | pass
2340 | itertools = _dummy_itertools()
2341 | def _itertools_imap(f, seq):
2342 | for x in seq:
2343 | yield f(x)
2344 | itertools.imap = _itertools_imap
2345 | def _itertools_chain(*iterables):
2346 | for it in iterables:
2347 | for element in it:
2348 | yield element
2349 | itertools.chain = _itertools_chain
2350 |
2351 |
2352 | # === Support for users without Cython ===
2353 |
2354 | try:
2355 | pngfilters
2356 | except NameError:
2357 | class pngfilters(object):
2358 | def undo_filter_sub(filter_unit, scanline, previous, result):
2359 | """Undo sub filter."""
2360 |
2361 | ai = 0
2362 | # Loops starts at index fu. Observe that the initial part
2363 | # of the result is already filled in correctly with
2364 | # scanline.
2365 | for i in range(filter_unit, len(result)):
2366 | x = scanline[i]
2367 | a = result[ai]
2368 | result[i] = (x + a) & 0xff
2369 | ai += 1
2370 | undo_filter_sub = staticmethod(undo_filter_sub)
2371 |
2372 | def undo_filter_up(filter_unit, scanline, previous, result):
2373 | """Undo up filter."""
2374 |
2375 | for i in range(len(result)):
2376 | x = scanline[i]
2377 | b = previous[i]
2378 | result[i] = (x + b) & 0xff
2379 | undo_filter_up = staticmethod(undo_filter_up)
2380 |
2381 | def undo_filter_average(filter_unit, scanline, previous, result):
2382 | """Undo up filter."""
2383 |
2384 | ai = -filter_unit
2385 | for i in range(len(result)):
2386 | x = scanline[i]
2387 | if ai < 0:
2388 | a = 0
2389 | else:
2390 | a = result[ai]
2391 | b = previous[i]
2392 | result[i] = (x + ((a + b) >> 1)) & 0xff
2393 | ai += 1
2394 | undo_filter_average = staticmethod(undo_filter_average)
2395 |
2396 | def undo_filter_paeth(filter_unit, scanline, previous, result):
2397 | """Undo Paeth filter."""
2398 |
2399 | # Also used for ci.
2400 | ai = -filter_unit
2401 | for i in range(len(result)):
2402 | x = scanline[i]
2403 | if ai < 0:
2404 | a = c = 0
2405 | else:
2406 | a = result[ai]
2407 | c = previous[ai]
2408 | b = previous[i]
2409 | p = a + b - c
2410 | pa = abs(p - a)
2411 | pb = abs(p - b)
2412 | pc = abs(p - c)
2413 | if pa <= pb and pa <= pc:
2414 | pr = a
2415 | elif pb <= pc:
2416 | pr = b
2417 | else:
2418 | pr = c
2419 | result[i] = (x + pr) & 0xff
2420 | ai += 1
2421 | undo_filter_paeth = staticmethod(undo_filter_paeth)
2422 |
2423 | def convert_la_to_rgba(row, result):
2424 | for i in range(3):
2425 | result[i::4] = row[0::2]
2426 | result[3::4] = row[1::2]
2427 | convert_la_to_rgba = staticmethod(convert_la_to_rgba)
2428 |
2429 | def convert_l_to_rgba(row, result):
2430 | """Convert a grayscale image to RGBA. This method assumes
2431 | the alpha channel in result is already correctly
2432 | initialized.
2433 | """
2434 | for i in range(3):
2435 | result[i::4] = row
2436 | convert_l_to_rgba = staticmethod(convert_l_to_rgba)
2437 |
2438 | def convert_rgb_to_rgba(row, result):
2439 | """Convert an RGB image to RGBA. This method assumes the
2440 | alpha channel in result is already correctly initialized.
2441 | """
2442 | for i in range(3):
2443 | result[i::4] = row[i::3]
2444 | convert_rgb_to_rgba = staticmethod(convert_rgb_to_rgba)
2445 |
2446 |
2447 | # === Command Line Support ===
2448 |
2449 | def read_pam_header(infile):
2450 | """
2451 | Read (the rest of a) PAM header. `infile` should be positioned
2452 | immediately after the initial 'P7' line (at the beginning of the
2453 | second line). Returns are as for `read_pnm_header`.
2454 | """
2455 |
2456 | # Unlike PBM, PGM, and PPM, we can read the header a line at a time.
2457 | header = dict()
2458 | while True:
2459 | l = infile.readline().strip()
2460 | if l == strtobytes('ENDHDR'):
2461 | break
2462 | if not l:
2463 | raise EOFError('PAM ended prematurely')
2464 | if l[0] == strtobytes('#'):
2465 | continue
2466 | l = l.split(None, 1)
2467 | if l[0] not in header:
2468 | header[l[0]] = l[1]
2469 | else:
2470 | header[l[0]] += strtobytes(' ') + l[1]
2471 |
2472 | required = ['WIDTH', 'HEIGHT', 'DEPTH', 'MAXVAL']
2473 | required = [strtobytes(x) for x in required]
2474 | WIDTH,HEIGHT,DEPTH,MAXVAL = required
2475 | present = [x for x in required if x in header]
2476 | if len(present) != len(required):
2477 | raise Error('PAM file must specify WIDTH, HEIGHT, DEPTH, and MAXVAL')
2478 | width = int(header[WIDTH])
2479 | height = int(header[HEIGHT])
2480 | depth = int(header[DEPTH])
2481 | maxval = int(header[MAXVAL])
2482 | if (width <= 0 or
2483 | height <= 0 or
2484 | depth <= 0 or
2485 | maxval <= 0):
2486 | raise Error(
2487 | 'WIDTH, HEIGHT, DEPTH, MAXVAL must all be positive integers')
2488 | return 'P7', width, height, depth, maxval
2489 |
2490 | def read_pnm_header(infile, supported=('P5','P6')):
2491 | """
2492 | Read a PNM header, returning (format,width,height,depth,maxval).
2493 | `width` and `height` are in pixels. `depth` is the number of
2494 | channels in the image; for PBM and PGM it is synthesized as 1, for
2495 | PPM as 3; for PAM images it is read from the header. `maxval` is
2496 | synthesized (as 1) for PBM images.
2497 | """
2498 |
2499 | # Generally, see http://netpbm.sourceforge.net/doc/ppm.html
2500 | # and http://netpbm.sourceforge.net/doc/pam.html
2501 |
2502 | supported = [strtobytes(x) for x in supported]
2503 |
2504 | # Technically 'P7' must be followed by a newline, so by using
2505 | # rstrip() we are being liberal in what we accept. I think this
2506 | # is acceptable.
2507 | type = infile.read(3).rstrip()
2508 | if type not in supported:
2509 | raise NotImplementedError('file format %s not supported' % type)
2510 | if type == strtobytes('P7'):
2511 | # PAM header parsing is completely different.
2512 | return read_pam_header(infile)
2513 | # Expected number of tokens in header (3 for P4, 4 for P6)
2514 | expected = 4
2515 | pbm = ('P1', 'P4')
2516 | if type in pbm:
2517 | expected = 3
2518 | header = [type]
2519 |
2520 | # We have to read the rest of the header byte by byte because the
2521 | # final whitespace character (immediately following the MAXVAL in
2522 | # the case of P6) may not be a newline. Of course all PNM files in
2523 | # the wild use a newline at this point, so it's tempting to use
2524 | # readline; but it would be wrong.
2525 | def getc():
2526 | c = infile.read(1)
2527 | if not c:
2528 | raise Error('premature EOF reading PNM header')
2529 | return c
2530 |
2531 | c = getc()
2532 | while True:
2533 | # Skip whitespace that precedes a token.
2534 | while c.isspace():
2535 | c = getc()
2536 | # Skip comments.
2537 | while c == '#':
2538 | while c not in '\n\r':
2539 | c = getc()
2540 | if not c.isdigit():
2541 | raise Error('unexpected character %s found in header' % c)
2542 | # According to the specification it is legal to have comments
2543 | # that appear in the middle of a token.
2544 | # This is bonkers; I've never seen it; and it's a bit awkward to
2545 | # code good lexers in Python (no goto). So we break on such
2546 | # cases.
2547 | token = strtobytes('')
2548 | while c.isdigit():
2549 | token += c
2550 | c = getc()
2551 | # Slight hack. All "tokens" are decimal integers, so convert
2552 | # them here.
2553 | header.append(int(token))
2554 | if len(header) == expected:
2555 | break
2556 | # Skip comments (again)
2557 | while c == '#':
2558 | while c not in '\n\r':
2559 | c = getc()
2560 | if not c.isspace():
2561 | raise Error('expected header to end with whitespace, not %s' % c)
2562 |
2563 | if type in pbm:
2564 | # synthesize a MAXVAL
2565 | header.append(1)
2566 | depth = (1,3)[type == strtobytes('P6')]
2567 | return header[0], header[1], header[2], depth, header[3]
2568 |
2569 | def write_pnm(file, width, height, pixels, meta):
2570 | """Write a Netpbm PNM/PAM file.
2571 | """
2572 |
2573 | bitdepth = meta['bitdepth']
2574 | maxval = 2**bitdepth - 1
2575 | # Rudely, the number of image planes can be used to determine
2576 | # whether we are L (PGM), LA (PAM), RGB (PPM), or RGBA (PAM).
2577 | planes = meta['planes']
2578 | # Can be an assert as long as we assume that pixels and meta came
2579 | # from a PNG file.
2580 | assert planes in (1,2,3,4)
2581 | if planes in (1,3):
2582 | if 1 == planes:
2583 | # PGM
2584 | # Could generate PBM if maxval is 1, but we don't (for one
2585 | # thing, we'd have to convert the data, not just blat it
2586 | # out).
2587 | fmt = 'P5'
2588 | else:
2589 | # PPM
2590 | fmt = 'P6'
2591 | file.write('%s %d %d %d\n' % (fmt, width, height, maxval))
2592 | if planes in (2,4):
2593 | # PAM
2594 | # See http://netpbm.sourceforge.net/doc/pam.html
2595 | if 2 == planes:
2596 | tupltype = 'GRAYSCALE_ALPHA'
2597 | else:
2598 | tupltype = 'RGB_ALPHA'
2599 | file.write('P7\nWIDTH %d\nHEIGHT %d\nDEPTH %d\nMAXVAL %d\n'
2600 | 'TUPLTYPE %s\nENDHDR\n' %
2601 | (width, height, planes, maxval, tupltype))
2602 | # Values per row
2603 | vpr = planes * width
2604 | # struct format
2605 | fmt = '>%d' % vpr
2606 | if maxval > 0xff:
2607 | fmt = fmt + 'H'
2608 | else:
2609 | fmt = fmt + 'B'
2610 | for row in pixels:
2611 | file.write(struct.pack(fmt, *row))
2612 | file.flush()
2613 |
2614 | def color_triple(color):
2615 | """
2616 | Convert a command line colour value to a RGB triple of integers.
2617 | FIXME: Somewhere we need support for greyscale backgrounds etc.
2618 | """
2619 | if color.startswith('#') and len(color) == 4:
2620 | return (int(color[1], 16),
2621 | int(color[2], 16),
2622 | int(color[3], 16))
2623 | if color.startswith('#') and len(color) == 7:
2624 | return (int(color[1:3], 16),
2625 | int(color[3:5], 16),
2626 | int(color[5:7], 16))
2627 | elif color.startswith('#') and len(color) == 13:
2628 | return (int(color[1:5], 16),
2629 | int(color[5:9], 16),
2630 | int(color[9:13], 16))
2631 |
2632 | def _add_common_options(parser):
2633 | """Call *parser.add_option* for each of the options that are
2634 | common between this PNG--PNM conversion tool and the gen
2635 | tool.
2636 | """
2637 | parser.add_option("-i", "--interlace",
2638 | default=False, action="store_true",
2639 | help="create an interlaced PNG file (Adam7)")
2640 | parser.add_option("-t", "--transparent",
2641 | action="store", type="string", metavar="#RRGGBB",
2642 | help="mark the specified colour as transparent")
2643 | parser.add_option("-b", "--background",
2644 | action="store", type="string", metavar="#RRGGBB",
2645 | help="save the specified background colour")
2646 | parser.add_option("-g", "--gamma",
2647 | action="store", type="float", metavar="value",
2648 | help="save the specified gamma value")
2649 | parser.add_option("-c", "--compression",
2650 | action="store", type="int", metavar="level",
2651 | help="zlib compression level (0-9)")
2652 | return parser
2653 |
2654 | def _main(argv):
2655 | """
2656 | Run the PNG encoder with options from the command line.
2657 | """
2658 |
2659 | # Parse command line arguments
2660 | from optparse import OptionParser
2661 | import re
2662 | version = '%prog ' + __version__
2663 | parser = OptionParser(version=version)
2664 | parser.set_usage("%prog [options] [imagefile]")
2665 | parser.add_option('-r', '--read-png', default=False,
2666 | action='store_true',
2667 | help='Read PNG, write PNM')
2668 | parser.add_option("-a", "--alpha",
2669 | action="store", type="string", metavar="pgmfile",
2670 | help="alpha channel transparency (RGBA)")
2671 | _add_common_options(parser)
2672 |
2673 | (options, args) = parser.parse_args(args=argv[1:])
2674 |
2675 | # Convert options
2676 | if options.transparent is not None:
2677 | options.transparent = color_triple(options.transparent)
2678 | if options.background is not None:
2679 | options.background = color_triple(options.background)
2680 |
2681 | # Prepare input and output files
2682 | if len(args) == 0:
2683 | infilename = '-'
2684 | infile = sys.stdin
2685 | elif len(args) == 1:
2686 | infilename = args[0]
2687 | infile = open(infilename, 'rb')
2688 | else:
2689 | parser.error("more than one input file")
2690 | outfile = sys.stdout
2691 | if sys.platform == "win32":
2692 | import msvcrt, os
2693 | msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2694 |
2695 | if options.read_png:
2696 | # Encode PNG to PPM
2697 | png = Reader(file=infile)
2698 | width,height,pixels,meta = png.asDirect()
2699 | write_pnm(outfile, width, height, pixels, meta)
2700 | else:
2701 | # Encode PNM to PNG
2702 | format, width, height, depth, maxval = \
2703 | read_pnm_header(infile, ('P5','P6','P7'))
2704 | # When it comes to the variety of input formats, we do something
2705 | # rather rude. Observe that L, LA, RGB, RGBA are the 4 colour
2706 | # types supported by PNG and that they correspond to 1, 2, 3, 4
2707 | # channels respectively. So we use the number of channels in
2708 | # the source image to determine which one we have. We do not
2709 | # care about TUPLTYPE.
2710 | greyscale = depth <= 2
2711 | pamalpha = depth in (2,4)
2712 | supported = map(lambda x: 2**x-1, range(1,17))
2713 | try:
2714 | mi = supported.index(maxval)
2715 | except ValueError:
2716 | raise NotImplementedError(
2717 | 'your maxval (%s) not in supported list %s' %
2718 | (maxval, str(supported)))
2719 | bitdepth = mi+1
2720 | writer = Writer(width, height,
2721 | greyscale=greyscale,
2722 | bitdepth=bitdepth,
2723 | interlace=options.interlace,
2724 | transparent=options.transparent,
2725 | background=options.background,
2726 | alpha=bool(pamalpha or options.alpha),
2727 | gamma=options.gamma,
2728 | compression=options.compression)
2729 | if options.alpha:
2730 | pgmfile = open(options.alpha, 'rb')
2731 | format, awidth, aheight, adepth, amaxval = \
2732 | read_pnm_header(pgmfile, 'P5')
2733 | if amaxval != '255':
2734 | raise NotImplementedError(
2735 | 'maxval %s not supported for alpha channel' % amaxval)
2736 | if (awidth, aheight) != (width, height):
2737 | raise ValueError("alpha channel image size mismatch"
2738 | " (%s has %sx%s but %s has %sx%s)"
2739 | % (infilename, width, height,
2740 | options.alpha, awidth, aheight))
2741 | writer.convert_ppm_and_pgm(infile, pgmfile, outfile)
2742 | else:
2743 | writer.convert_pnm(infile, outfile)
2744 |
2745 |
2746 | if __name__ == '__main__':
2747 | try:
2748 | _main(sys.argv)
2749 | except Error, e:
2750 | print >>sys.stderr, e
2751 |
2752 |
--------------------------------------------------------------------------------