Merge pull request #1074 from CounterPillow/optimizerewrite
Rewrote image optimisation stuff.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user