0
This repository has been archived on 2025-04-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Minecraft-Overviewer/overviewer_core/optimizeimages.py
Nicolas F 6ea2758c31 optimizeimages: support pngnq-s9
pngnq-s9 is essentially just a more recently dead version of pngnq,
however, due to not being able to specify multiple binary names in
the past, we've not supported it. Refactor the code to allow specifying
multiple binary names, and add pngnq-s9 to it.
2016-12-06 16:11:45 +01:00

188 lines
5.8 KiB
Python

# This file is part of the Minecraft Overviewer.
#
# Minecraft Overviewer is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# Minecraft Overviewer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
import os
import subprocess
import shlex
import logging
class Optimizer:
binaryname = ""
binarynames = []
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 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
binaries = self.binarynames + [x + ".exe" for x in self.binarynames]
for b in binaries:
if (exists_in_path(b)):
self.binaryname = b
break
else:
raise Exception("Optimization programs '%s' were not found!" % binaries)
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):
def cleanup(self, img):
os.remove(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):
binarynames = ["pngnq-s9", "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
def optimize(self, img):
if img.endswith(".tmp"):
extension = ".tmp"
else:
extension = ".png.tmp"
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)
NonAtomicOptimizer.fire_and_forget(self, args, img)
def is_crusher(self):
return False
class pngcrush(NonAtomicOptimizer, PNGOptimizer):
binarynames = ["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)
def is_crusher(self):
return True
class optipng(Optimizer, PNGOptimizer):
binarynames = ["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 is_crusher(self):
return True
class advpng(Optimizer, PNGOptimizer):
binarynames = ["advpng"]
crusher = True
def __init__(self, olevel=3):
self.olevel = olevel
def optimize(self, img):
Optimizer.fire_and_forget(self, [self.binaryname, "-z" + str(self.olevel), "-q", img])
def is_crusher(self):
return True
class jpegoptim(Optimizer, JPEGOptimizer):
binarynames = ["jpegoptim"]
crusher = True
quality = None
target_size = None
def __init__(self, quality = None, target_size = None):
if quality is not None:
if quality < 0 or quality > 100:
raise Exception("Invalid target quality %d for jpegoptim" % quality)
self.quality = quality
if target_size is not None:
self.target_size = target_size
def optimize(self, img):
args = [self.binaryname, "-q", "-p"]
if self.quality is not None:
args.append("-m" + str(self.quality))
if self.target_size is not None:
args.append("-S" + str(self.target_size))
args.append(img)
Optimizer.fire_and_forget(self, args)
def is_crusher(self):
# Technically, optimisation is lossless if input image quality
# is below target quality, but this is irrelevant in this case
if (self.quality is not None) or (self.target_size is not None):
return False
else:
return True
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)