diff --git a/docs/config.rst b/docs/config.rst index eb2bfa8..5db549c 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -557,7 +557,9 @@ values. The valid configuration keys are listed below. The option is a list of Optimizer objects, which are then executed in the order in which they're specified:: + # Import the optimizers we need from optimizeimages import pngnq, optipng + worlds["world"] = "/path/to/world" renders["daytime"] = { @@ -566,6 +568,10 @@ values. The valid configuration keys are listed below. "rendermode":smooth_lighting, "optimizeimg":[pngnq(sampling=1), optipng(olevel=3)], } + + .. note:: + Don't forget to import the optimizers you use in your config file, as shown in the + example above. Here is a list of supported image optimization programs: @@ -612,8 +618,9 @@ values. The valid configuration keys are listed below. **Default:** ``2`` ``pngcrush`` - pngcrush is very slow and not very good, you should use optipng in probably all cases. - However, Overviewer still allows you to use it because we're nice people like that. + pngcrush, like optipng, is a lossless PNG recompressor. If you are able to do so, it + is recommended to use optipng instead, as it generally yields better results in less + time. Available settings: ``brute`` @@ -626,10 +633,6 @@ values. The valid configuration keys are listed below. **Default:** ``False`` - .. note:: - Don't forget to import the optimizers you use in your settings file, as shown in the - example above. - **Default:** ``[]`` ``bgcolor`` diff --git a/overviewer_core/optimizeimages.py b/overviewer_core/optimizeimages.py index da8d306..1d79f71 100644 --- a/overviewer_core/optimizeimages.py +++ b/overviewer_core/optimizeimages.py @@ -39,6 +39,10 @@ class Optimizer: if (not exists_in_path(self.binaryname)) and (not exists_in_path(self.binaryname + ".exe")): raise Exception("Optimization program '%s' was not found!" % self.binaryname) + + def is_crusher(self): + """Should return True if the optimization is lossless, i.e. none of the actual image data will be changed.""" + raise NotImplementedError("I'm so abstract I can't even say whether I'm a crusher.") class NonAtomicOptimizer(Optimizer): @@ -84,6 +88,9 @@ class pngnq(NonAtomicOptimizer, PNGOptimizer): NonAtomicOptimizer.fire_and_forget(self, args, img) + def is_crusher(self): + return False + class pngcrush(NonAtomicOptimizer, PNGOptimizer): binaryname = "pngcrush" # really can't be bothered to add some interface for all @@ -98,6 +105,9 @@ class pngcrush(NonAtomicOptimizer, PNGOptimizer): NonAtomicOptimizer.fire_and_forget(self, args, img) + def is_crusher(self): + return True + class optipng(Optimizer, PNGOptimizer): binaryname = "optipng" @@ -106,6 +116,9 @@ class optipng(Optimizer, PNGOptimizer): def optimize(self, img): Optimizer.fire_and_forget(self, [self.binaryname, "-o" + str(self.olevel), "-quiet", img]) + + def is_crusher(self): + return True def optimize_image(imgpath, imgformat, optimizers): diff --git a/overviewer_core/settingsValidators.py b/overviewer_core/settingsValidators.py index e61a1ab..29553a7 100644 --- a/overviewer_core/settingsValidators.py +++ b/overviewer_core/settingsValidators.py @@ -160,15 +160,25 @@ def validateBGColor(color): def validateOptImg(optimizers): if isinstance(optimizers, (int, long)): from optimizeimages import pngcrush - logging.warning("You're using a deprecated definition of optimizeimg. We'll do what you say for now, but please fix this as soon as possible.") + logging.warning("You're using a deprecated definition of optimizeimg. "\ + "We'll do what you say for now, but please fix this as soon as possible.") optimizers = [pngcrush()] if not isinstance(optimizers, list): - raise ValidationException("optimizeimg is not a list. Make sure you specify them like [foo()], with square brackets.") - for opt in optimizers: - if not isinstance(opt, Optimizer): - raise ValidationException("Invalid Optimizer!") + raise ValidationException("What you passed to optimizeimg is not a list. "\ + "Make sure you specify them like [foo()], with square brackets.") - opt.check_availability() + if optimizers: + for opt, next_opt in zip(optimizers, optimizers[1:]) + [(optimizers[-1], None)]: + if not isinstance(opt, Optimizer): + raise ValidationException("Invalid Optimizer!") + + opt.check_availability() + + # Check whether the chaining is somewhat sane + if next_opt: + if opt.is_crusher() and not next_opt.is_crusher(): + logging.warning("You're feeding a crushed output into an optimizer that does not crush. "\ + "This is most likely pointless, and wastes time.") return optimizers diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 78b1d97..36e8a65 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -161,17 +161,6 @@ class Textures(object): return None if verbose: logging.info('search_zip_paths: ' + ', '.join(search_zip_paths)) - # we've sucessfully loaded something from here before, so let's quickly try - # this before searching again - if self.jar is not None: - for jarfilename in search_zip_paths: - try: - self.jar.getinfo(jarfilename) - if verbose: logging.info("Found (cached) %s in '%s'", jarfilename, self.jarpath) - return self.jar.open(jarfilename) - except (KeyError, IOError), e: - pass - # A texture path was given on the command line. Search this location # for the file first. if self.find_file_local_path: @@ -227,6 +216,17 @@ class Textures(object): if verbose: logging.info("Did not find the file in overviewer executable directory") if verbose: logging.info("Looking for installed minecraft jar files...") + # we've sucessfully loaded something from here before, so let's quickly try + # this before searching again + if self.jar is not None: + for jarfilename in search_zip_paths: + try: + self.jar.getinfo(jarfilename) + if verbose: logging.info("Found (cached) %s in '%s'", jarfilename, self.jarpath) + return self.jar.open(jarfilename) + except (KeyError, IOError), e: + pass + # Find an installed minecraft client jar and look in it for the texture # file we need. versiondir = ""