0

Merge pull request #1074 from CounterPillow/optimizerewrite

Rewrote image optimisation stuff.
This commit is contained in:
Aaron Griffith
2014-05-06 18:25:09 -04:00
5 changed files with 198 additions and 43 deletions

View File

@@ -16,37 +16,103 @@
import os
import subprocess
import shlex
import logging
pngcrush = "pngcrush"
optipng = "optipng"
advdef = "advdef"
class Optimizer:
binaryname = ""
def check_programs(level):
path = os.environ.get("PATH").split(os.pathsep)
def __init__(self):
raise NotImplementedError("I can't let you do that, Dave.")
def optimize(self, img):
raise NotImplementedError("I can't let you do that, Dave.")
def exists_in_path(prog):
result = filter(lambda x: os.path.exists(os.path.join(x, prog)), path)
return len(result) != 0
def fire_and_forget(self, args):
subprocess.check_call(args)
def check_availability(self):
path = os.environ.get("PATH").split(os.pathsep)
def exists_in_path(prog):
result = filter(lambda x: os.path.exists(os.path.join(x, prog)), path)
return len(result) != 0
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)
class NonAtomicOptimizer(Optimizer):
def cleanup(self, img):
os.rename(img + ".tmp", img)
def fire_and_forget(self, args, img):
subprocess.check_call(args)
self.cleanup(img)
class PNGOptimizer:
def __init__(self):
raise NotImplementedError("I can't let you do that, Dave.")
class JPEGOptimizer:
def __init__(self):
raise NotImplementedError("I can't let you do that, Dave.")
class pngnq(NonAtomicOptimizer, PNGOptimizer):
binaryname = "pngnq"
def __init__(self, sampling=3, dither="n"):
if sampling < 1 or sampling > 10:
raise Exception("Invalid sampling value '%d' for pngnq!" % sampling)
if dither not in ["n", "f"]:
raise Exception("Invalid dither method '%s' for pngnq!" % dither)
self.sampling = sampling
self.dither = dither
for prog,l in [(pngcrush,1), (advdef,2)]:
if l <= level:
if (not exists_in_path(prog)) and (not exists_in_path(prog + ".exe")):
raise Exception("Optimization prog %s for level %d not found!" % (prog, l))
def optimize(self, img):
if img.endswith(".tmp"):
extension = ".tmp"
else:
extension = ".png.tmp"
def optimize_image(imgpath, imgformat, optimizeimg):
if imgformat == 'png':
if optimizeimg >= 1:
# we can't do an atomic replace here because windows is terrible
# so instead, we make temp files, delete the old ones, and rename
# the temp files. go windows!
subprocess.Popen([pngcrush, imgpath, imgpath + ".tmp"],
stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
os.remove(imgpath)
os.rename(imgpath+".tmp", imgpath)
args = [self.binaryname, "-s", str(self.sampling), "-f", "-e", extension, img]
# Workaround for poopbuntu 12.04 which ships an old broken pngnq
if self.dither != "n":
args.insert(1, "-Q")
args.insert(2, self.dither)
if optimizeimg >= 2:
# the "-nc" it's needed to no broke the transparency of tiles
recompress_option = "-z2" if optimizeimg == 2 else "-z4"
subprocess.Popen([advdef, recompress_option,imgpath], stderr=subprocess.STDOUT,
stdout=subprocess.PIPE).communicate()[0]
NonAtomicOptimizer.fire_and_forget(self, args, img)
class pngcrush(NonAtomicOptimizer, PNGOptimizer):
binaryname = "pngcrush"
# really can't be bothered to add some interface for all
# the pngcrush options, it sucks anyway
def __init__(self, brute=False):
self.brute = brute
def optimize(self, img):
args = [self.binaryname, img, img + ".tmp"]
if self.brute == True: # Was the user an idiot?
args.insert(1, "-brute")
NonAtomicOptimizer.fire_and_forget(self, args, img)
class optipng(Optimizer, PNGOptimizer):
binaryname = "optipng"
def __init__(self, olevel=2):
self.olevel = olevel
def optimize(self, img):
Optimizer.fire_and_forget(self, [self.binaryname, "-o" + str(self.olevel), "-quiet", img])
def optimize_image(imgpath, imgformat, optimizers):
for opt in optimizers:
if imgformat == 'png':
if isinstance(opt, PNGOptimizer):
opt.optimize(imgpath)
elif imgformat == 'jpg':
if isinstance(opt, JPEGOptimizer):
opt.optimize(imgpath)

View File

@@ -46,6 +46,7 @@
from settingsValidators import *
import util
from observer import ProgressBarObserver, LoggingObserver, JSObserver
from optimizeimages import pngnq, optipng, pngcrush
import platform
import sys
@@ -72,7 +73,7 @@ renders = Setting(required=True, default=util.OrderedDict(),
"imgquality": Setting(required=False, validator=validateImgQuality, default=95),
"bgcolor": Setting(required=True, validator=validateBGColor, default="1a1a1a"),
"defaultzoom": Setting(required=True, validator=validateDefaultZoom, default=1),
"optimizeimg": Setting(required=True, validator=validateOptImg, default=0),
"optimizeimg": Setting(required=True, validator=validateOptImg, default=[]),
"nomarkers": Setting(required=False, validator=validateBool, default=None),
"texturepath": Setting(required=False, validator=validateTexturePath, default=None),
"renderchecks": Setting(required=False, validator=validateInt, default=None),

View File

@@ -5,7 +5,9 @@ from collections import namedtuple
import rendermodes
import util
from optimizeimages import Optimizer
from world import UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, LOWER_RIGHT
import logging
class ValidationException(Exception):
pass
@@ -155,8 +157,20 @@ def validateBGColor(color):
return color
def validateOptImg(opt):
return bool(opt)
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.")
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!")
opt.check_availability()
return optimizers
def validateTexturePath(path):
# Expand user dir in directories strings

View File

@@ -262,11 +262,7 @@ class TileSet(object):
relevant in jpeg mode.
optimizeimg
an integer indiating optimizations to perform on png outputs. 0
indicates no optimizations. Only relevant in png mode.
1 indicates pngcrush is run on all output images
2 indicates pngcrush and advdef are run on all output images with advdef -z2
3 indicates pngcrush and advdef are run on all output images with advdef -z4
A list of optimizer instances to use.
rendermode
Perhaps the most important/relevant option: a string indicating the
@@ -925,7 +921,11 @@ class TileSet(object):
try:
#quad = Image.open(path[1]).resize((192,192), Image.ANTIALIAS)
src = Image.open(path[1])
# optimizeimg may have converted them to a palette image in the meantime
if src.mode != "RGB" and src.mode != "RGBA":
src = src.convert("RGBA")
src.load()
quad = Image.new("RGBA", (192, 192), self.options['bgcolor'])
resize_half(quad, src)
img.paste(quad, path[0])
@@ -1050,7 +1050,7 @@ class TileSet(object):
if self.options['optimizeimg']:
optimize_image(tmppath, self.imgextension, self.options['optimizeimg'])
os.utime(tmppath, (max_chunk_mtime, max_chunk_mtime))
def _iterate_and_check_tiles(self, path):