00001
00002
00003
00004
00005 from tempimage import *
00006 from imagesettings import *
00007 from referencefile import *
00008 from multiprocessing import *
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 class PNGProcData:
00019 """Data specific to a process using PNGProcessor.
00020
00021 Contains things like optimization specific data that
00022 has to be stored separately for each process.
00023 """
00024 def __init__(self):
00025 """Constructor.
00026
00027 Data members:
00028
00029 FCount : Number of generated files already processed by a call
00030 to process_png() in this process. Used for %
00031 feedback.
00032 OptUsePal : Each entry of this dictionary specifies whether or not
00033 to generate indexed files with this bitdepth.
00034 None means that this has not been determined yet.
00035 This is reset for each call to process_png()
00036 OptUseGray : Same as OptUsePal but with grayscale files.
00037 OptMaxFilesDec : Number of files that were not generated due to
00038 optimizations so far. This is used to decrease
00039 value returned by get_max_files() to provide
00040 precise % feedback.
00041 DoAdvpng : Do we need to generate advpng (special case) images?
00042 """
00043 self.FCount = 0.0
00044 self.OptUsePal = { 1: None, 2: None, 4: None, 8: None}
00045 self.OptUseGray = { 2: None, 4: None, 8: None}
00046 self.OptMaxFilesDec = 0
00047 self.DoAdvpng = True
00048
00049 class PNGProcessor:
00050 """Handles PNG file generation and it's settings.
00051 """
00052
00053 def __init__(self, numproc = 1):
00054 """Constructor.
00055
00056 Data members:
00057 MinLevel : Minimum compression level to use. Can be int 0-9.
00058 Filters : PNG filters to use. String of numbers representing
00059 filters, for instance "125" for filters 1, 2 and 5.
00060 Available filters:
00061 0 : none
00062 1 : sub
00063 2 : up
00064 3 : average
00065 4 : Paeth
00066 5 : Adaptive when not palleted and level more than 5
00067 6 : Adaptive
00068 MinPalDepth : Minimum bit depth to use for paletted images.
00069 e.g. bit depth 4 means there is maximum of 16 colors
00070 in the palette. Can be 0, 1, 2, 4 or 8. 0 means
00071 paletted images are not generated.
00072 MinGrayDepth : Minimum bit depth to use for grayscale images.
00073 e.g. bit depth 4 means there is maximum of 16 levels of
00074 gray. Can be 0, 2, 3, 4 or 8. 0 means grayscale images
00075 are not generated.
00076 Interlace : Generate interlaced images?
00077 Optimizations : Level of optimization to use:
00078 0 - none
00079 1 - lossless optimizations (same results as with none)
00080 2 - some optimizations
00081 3 - more optimizations
00082 OptUseGray : Optimization - dictionary of bools specifying whether
00083 or not to generate indexed files with bitdepth equal
00084 to dict key. Reset to None with each processes file,
00085 which triggers a test that sets it to True or False.
00086 OptUseGray : Optimization - dictionary of bools specifying whether
00087 or not to generate grayscale files with bitdepth equal
00088 to dict key. Reset to None with each processes file,
00089 which triggers a test that sets it to True or False.
00090 OptMaxFilesDec : Number which is substracted from expected number of
00091 generated files in get_max_files() to remove
00092 difference caused by optimizations.
00093 ProcList : List containing data specific to each process that
00094 uses this PNGProcessor.
00095 """
00096 self.set_all("light")
00097 self.TrueColor = True
00098 self.ProcList=[]
00099
00100 self.set_proc_count(numproc)
00101
00102 def set_all(self, all):
00103 """Sets all PNG settings according to given mode.
00104
00105 fastest means all optimizations and least generated files
00106 extreme means no optimizations and all generated files
00107 heavy means lossless optimizations only - i.e. same result as extreme
00108 """
00109 if all == "extreme":
00110 self.MinLevel = 0
00111 self.Filters = "0123456"
00112 self.Interlace = True
00113 self.MinPalDepth = 1
00114 self.MinGrayDepth = 2
00115 self.Optimizations = 0
00116 elif all == "heavy":
00117 self.MinLevel = 0
00118 self.Filters = "0123456"
00119 self.Interlace = True
00120 self.MinPalDepth = 1
00121 self.MinGrayDepth = 2
00122 self.Optimizations = 1
00123 elif all == "medium":
00124 self.MinLevel = 0
00125 self.Filters = "0146"
00126 self.Interlace = False
00127 self.MinPalDepth = 2
00128 self.MinGrayDepth = 4
00129 self.Optimizations = 2
00130 elif all == "light":
00131 self.MinLevel = 3
00132 self.Filters = "046"
00133 self.Interlace = False
00134 self.MinPalDepth = 4
00135 self.MinGrayDepth = 4
00136 self.Optimizations = 3
00137 elif all == "fastest":
00138 self.MinLevel = 6
00139 self.Filters = "06"
00140 self.Interlace = False
00141 self.MinPalDepth = 4
00142 self.MinGrayDepth = 8
00143 self.Optimizations = 4
00144 else:
00145 warning("wrong value passed by --p-setl : using default")
00146
00147 def set_proc_count(self, numproc):
00148 """Set the number of processes that will use PNGProcessor.
00149
00150 Used to allocate process specific data.
00151 """
00152 if numproc == 0:
00153 numproc = 1
00154 for p in range (0, numproc):
00155 self.ProcList.append(PNGProcData())
00156
00157 def set_optimizations(self, opt):
00158 """Set optimization level to use.
00159 """
00160 try:
00161 self.Optimizations = clamp(int(opt),0,5)
00162 except ValueException:
00163 warning("wrong value passed by --p-opt-lv : using default")
00164
00165 def set_min_level(self, l):
00166 """Sets minimum compression level to use.
00167 """
00168 self.MinLevel = valid_number(l, 7, "--p-lev",0,9)
00169
00170 def set_filters(self, filters):
00171 """Sets PNG filters to use.
00172 """
00173 self.Filters = ""
00174 for f in filters:
00175 if f in ("0", "1", "2", "3", "4", "5", "6")\
00176 and (self.Filters.find(f) == -1):
00177 self.Filters = self.Filters + f
00178 if(self.Filters == ""):
00179 warning("wrong value passed by --p-filt : using default")
00180 self.Filters = "15"
00181
00182 def set_min_pal_depth(self, d):
00183 """Sets minimum bit depth to use with paletted images.
00184 """
00185 try:
00186 if(int(d) in (0, 1, 2, 4, 8)):
00187 self.MinPalDepth = int(d)
00188 elif(int(d)==0):
00189 pass
00190 else:
00191 warning("wrong value passed by --p-pal : using default")
00192 except ValueException:
00193 warning("wrong value passed by --p-pal : using default")
00194
00195 def set_min_gray_depth(self, d):
00196 """Sets the minimum bit depth to use with grayscale images.
00197 """
00198 try:
00199 if(int(d) in (0, 2, 4, 8)):
00200 self.MinGrayDepth = int(d)
00201 else:
00202 warning("wrong value passed by --p-gray : using default")
00203 except ValueException:
00204 warning("wrong value passed by --p-gray : using default")
00205
00206 def set_interlace(self):
00207 """Turns generation of interlaced images on.
00208 """
00209 self.Interlace = True
00210
00211 def set_truecolor(self, t):
00212 """Turns generation of truecolor images on or off.
00213 """
00214 self.TrueColor = t
00215
00216 def get_level_vals(self):
00217 """Parses stored PNG level settings, returns list of used level values.
00218 """
00219 out = range(self.MinLevel, 10)
00220
00221
00222
00223
00224
00225 if self.Optimizations >= 1:
00226 if 1 in out:
00227 out.remove(1)
00228 if self.Optimizations >= 2:
00229 if 2 in out:
00230 out.remove(2)
00231 return out
00232
00233 def get_filter_vals(self):
00234 """Parses stored filter settings, returns list of used filter values.
00235 """
00236 return list(self.Filters)
00237
00238 def get_mode_vals(self):
00239 """Parses stored color storage mode data, returns possible values.
00240 """
00241 out = ["truecolor"]
00242 if self.BitMap:
00243 out.append("bitmap")
00244 if self.MinPalDepth:
00245 out.append("palette")
00246 if self.MinGrayDepth:
00247 out.append("gray")
00248 return out
00249
00250 def get_bitdepth_vals(self):
00251 """Parses stored bitdepth data, returns list of used bitdepth values.
00252 """
00253
00254 out = [24]
00255 if self.BitMap or self.MinPalDepth == 1:
00256 out.append(1)
00257 if (self.MinPalDepth <= 2) or (self.MinGrayDepth <= 2):
00258 out.append(2)
00259 if (self.MinPalDepth <= 4) or (self.MinGrayDepth <= 4):
00260 out.append(4)
00261 if (self.MinPalDepth <= 8) or (self.MinGrayDepth <= 8):
00262 out.append(8)
00263 return out
00264
00265 def get_interlace_vals(self):
00266 """Parses stored interlace data, returns list of used interlace values.
00267 """
00268 out = [False]
00269 if self.Interlace:
00270 out.append(True)
00271 return out
00272
00273 def get_vals_str(self, str):
00274 """Returns all possible values of setting requested by input string.
00275 """
00276 if str == "level":
00277 return self.get_level_vals()
00278 if str == "filter":
00279 return self.get_filter_vals()
00280 if str == "mode":
00281 return self.get_mode_vals()
00282 if str == "bitdepth":
00283 return self.get_bitdepth_vals()
00284 if str == "interlace":
00285 return self.get_interlace_vals()
00286 else:
00287 warning("unknown value passed to PNGProcessor.get_vals_str()")
00288
00289 def get_max_files(self, proc = 0):
00290 """Returns maximum number of files generated in optimization.
00291
00292 Used for % feedback.
00293 """
00294 out = len(self.get_level_vals()) * len(self.get_filter_vals()) *\
00295 len(self.get_interlace_vals())
00296
00297 outmult = 0
00298
00299 if self.TrueColor:
00300 outmult += 1
00301
00302 if self.MinPalDepth == 1:
00303 outmult += 4
00304 elif self.MinPalDepth == 2:
00305 outmult += 3
00306 elif self.MinPalDepth == 4:
00307 outmult += 2
00308 elif self.MinPalDepth == 8:
00309 outmult += 1
00310
00311 if self.MinGrayDepth == 2:
00312 outmult += 3
00313 elif self.MinGrayDepth == 4:
00314 outmult += 2
00315 elif self.MinGrayDepth == 8:
00316 outmult += 1
00317 base = out * outmult
00318 if globals.ADVPNG:
00319 base += 2
00320 return base - self.ProcList[proc].OptMaxFilesDec
00321
00322 def reset(self, proc = 0):
00323 """Resets variables that are set separately for each file .
00324 """
00325 self.ProcList[proc].OptUsePal[1] = None
00326 self.ProcList[proc].OptUsePal[2] = None
00327 self.ProcList[proc].OptUsePal[4] = None
00328 self.ProcList[proc].OptUsePal[8] = None
00329 self.ProcList[proc].OptUseGray[2] = None
00330 self.ProcList[proc].OptUseGray[4] = None
00331 self.ProcList[proc].OptUseGray[8] = None
00332 self.ProcList[proc].OptMaxFilesDec = 0
00333 if globals.ADVPNG:
00334 self.ProcList[proc].DoAdvpng = True
00335
00336 def generate_image(self, fnoext, reffile, lv, f, m, d, i, p, advpng=False):
00337 """Generates image with given settings.
00338 """
00339 outfile = (globals.TEMPDIR + fnoext + str(i) + m + str(lv) + f +
00340 str(d) + str(advpng) + ".png")
00341 set = PNGSettings(lv, f, m, d, i, advpng)
00342 lossless = False
00343 if m == "truecolor":
00344 lossless = True
00345 self.ProcList[p].FCount += 1.0
00346 percent = self.ProcList[p].FCount / self.get_max_files(p) * 100
00347 output("png " + str(p) + " " + str(percent) +" %",3)
00348 return TempImage(outfile, "png", set, reffile, lossless)
00349
00350 def helper(self, level, filter, ilace, reffile, fnoext, proc = 0):
00351 """Inner part of PNG generation.
00352
00353 Taken out of inner loop of process_png.
00354 This generates PNG files using specified settings in all allowed
00355 modes, e.g. truecolor, paletted, grayscale, bitmap.
00356 """
00357 outfilelist = []
00358
00359 if self.TrueColor:
00360
00361 if self.ProcList[proc].DoAdvpng:
00362 outfilelist.append(self.generate_image(fnoext, reffile, level,
00363 filter, "truecolor", 24, False, proc, True))
00364
00365 outfilelist.append(self.generate_image(fnoext, reffile, level,
00366 filter, "truecolor", 24, ilace, proc))
00367
00368 if self.MinPalDepth:
00369 depth = self.MinPalDepth
00370 while depth <= 8:
00371 if ((self.Optimizations >= 1) and
00372 (self.ProcList[proc].OptUsePal[depth] == False)):
00373
00374 self.ProcList[proc].OptMaxFilesDec += 1
00375 depth *= 2
00376 continue
00377
00378 if self.ProcList[proc].DoAdvpng:
00379 outfilelist.append(self.generate_image(fnoext, reffile,
00380 level, filter, "palette", depth, False,
00381 proc, True))
00382
00383 outfilelist.append(self.generate_image(fnoext, reffile, level,
00384 filter, "palette", depth, ilace, proc))
00385
00386
00387
00388
00389
00390
00391
00392
00393 if (self.Optimizations >= 1 and globals.TESTER.Mode == "q"
00394 and self.ProcList[proc].OptUsePal[depth] == None):
00395 if outfilelist[-1].Quality<globals.TESTER.abs_min_quality():
00396 self.ProcList[proc].OptUsePal[depth] = False
00397 else:
00398 self.ProcList[proc].OptUsePal[depth] = True
00399 depth *= 2
00400 if self.ProcList[proc].DoAdvpng:
00401 self.ProcList[proc].DoAdvpng = False
00402
00403 if self.MinGrayDepth:
00404 depthlist=[2, 4, 8]
00405
00406 for d in depthlist[depthlist.index(self.MinGrayDepth):]:
00407 if ((self.Optimizations >= 1) and
00408 (self.ProcList[proc].OptUseGray[d] == False)):
00409
00410 self.ProcList[proc].OptMaxFilesDec += 1
00411 continue
00412 outfilelist.append(self.generate_image(fnoext, reffile, level,
00413 filter, "gray", d, ilace, proc))
00414
00415
00416
00417
00418
00419
00420 if (self.Optimizations >= 1 and globals.TESTER.Mode == "q"
00421 and self.ProcList[proc].OptUseGray[d] == None):
00422 if outfilelist[-1].Quality<globals.TESTER.abs_min_quality():
00423 self.ProcList[proc].OptUseGray[d] = False
00424 else:
00425 self.ProcList[proc].OptUseGray[d] = True
00426 return outfilelist
00427
00428 def process(self, reffile, fnoext, queue=None, proc = 0):
00429 """Generates temporary png files and returns a list of them.
00430 """
00431 self.reset(proc)
00432 outfilelist = []
00433 output(str(proc) + " processing png")
00434 self.ProcList[proc].FCount = 0.0
00435 for level in self.get_level_vals():
00436 for f in self.get_filter_vals():
00437 for i in self.get_interlace_vals():
00438 outfilelist.extend(self.helper(level, f, i, reffile,
00439 fnoext, proc))
00440 if queue is not None:
00441 queue.put(outfilelist)
00442 else:
00443 return outfilelist