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