Merge branch 'optimizeimg' of http://github.com/kbrantley/Minecraft-Overviewer
This commit is contained in:
8
gmap.py
8
gmap.py
@@ -49,6 +49,7 @@ def main():
|
|||||||
parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.")
|
parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.")
|
||||||
parser.add_option("--chunklist", dest="chunklist", help="A file containing, on each line, a path to a chunkfile to update. Instead of scanning the world directory for chunks, it will just use this list. Normal caching rules still apply.")
|
parser.add_option("--chunklist", dest="chunklist", help="A file containing, on each line, a path to a chunkfile to update. Instead of scanning the world directory for chunks, it will just use this list. Normal caching rules still apply.")
|
||||||
parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.")
|
parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.")
|
||||||
|
parser.add_option("--optimize-img", dest="optimizeimg", help="If using png, perform image file size optimizations on the output. Specify 1 for pngcrush, 2 for pngcrush+optipng+advdef. This may double (or more) render times, but will produce up to 30% smaller images. NOTE: requires corresponding programs in $PATH or %PATH%")
|
||||||
parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.")
|
parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.")
|
||||||
parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.")
|
parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.")
|
||||||
|
|
||||||
@@ -98,6 +99,11 @@ def main():
|
|||||||
else:
|
else:
|
||||||
imgformat = 'png'
|
imgformat = 'png'
|
||||||
|
|
||||||
|
if options.optimizeimg:
|
||||||
|
optimizeimg = options.optimizeimg
|
||||||
|
else:
|
||||||
|
optimizeimg = None
|
||||||
|
|
||||||
logging.getLogger().setLevel(
|
logging.getLogger().setLevel(
|
||||||
logging.getLogger().level + 10*options.quiet)
|
logging.getLogger().level + 10*options.quiet)
|
||||||
logging.getLogger().setLevel(
|
logging.getLogger().setLevel(
|
||||||
@@ -111,7 +117,7 @@ def main():
|
|||||||
w.go(options.procs)
|
w.go(options.procs)
|
||||||
|
|
||||||
# Now generate the tiles
|
# Now generate the tiles
|
||||||
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat)
|
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg)
|
||||||
q.go(options.procs)
|
q.go(options.procs)
|
||||||
|
|
||||||
def delete_all(worlddir, tiledir):
|
def delete_all(worlddir, tiledir):
|
||||||
|
|||||||
36
optimizeimages.py
Normal file
36
optimizeimages.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
def optimize_image(imgpath, imgformat, optimizeimg):
|
||||||
|
if imgformat == 'png':
|
||||||
|
if optimizeimg == "1" or optimizeimg == "2":
|
||||||
|
# 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(shlex.split("pngcrush " + imgpath + " " + imgpath + ".tmp"),
|
||||||
|
stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
os.remove(imgpath)
|
||||||
|
os.rename(imgpath+".tmp", imgpath)
|
||||||
|
|
||||||
|
if optimizeimg == "2":
|
||||||
|
subprocess.Popen(shlex.split("optipng " + imgpath), stderr=subprocess.STDOUT,
|
||||||
|
stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
subprocess.Popen(shlex.split("advdef -z4 " + imgpath), stderr=subprocess.STDOUT,
|
||||||
|
stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
|
||||||
24
quadtree.py
24
quadtree.py
@@ -24,10 +24,11 @@ import shutil
|
|||||||
import collections
|
import collections
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import util
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from optimizeimages import optimize_image
|
||||||
|
|
||||||
import util
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module has routines related to generating a quadtree of tiles
|
This module has routines related to generating a quadtree of tiles
|
||||||
@@ -55,7 +56,7 @@ def catch_keyboardinterrupt(func):
|
|||||||
return newfunc
|
return newfunc
|
||||||
|
|
||||||
class QuadtreeGen(object):
|
class QuadtreeGen(object):
|
||||||
def __init__(self, worldobj, destdir, depth=None, imgformat=None):
|
def __init__(self, worldobj, destdir, depth=None, imgformat=None, optimizeimg=None):
|
||||||
"""Generates a quadtree from the world given into the
|
"""Generates a quadtree from the world given into the
|
||||||
given dest directory
|
given dest directory
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ class QuadtreeGen(object):
|
|||||||
"""
|
"""
|
||||||
assert(imgformat)
|
assert(imgformat)
|
||||||
self.imgformat = imgformat
|
self.imgformat = imgformat
|
||||||
|
self.optimizeimg = optimizeimg
|
||||||
|
|
||||||
if depth is None:
|
if depth is None:
|
||||||
# Determine quadtree depth (midpoint is always 0,0)
|
# Determine quadtree depth (midpoint is always 0,0)
|
||||||
@@ -244,7 +246,8 @@ class QuadtreeGen(object):
|
|||||||
# (even if tilechunks is empty, render_worldtile will delete
|
# (even if tilechunks is empty, render_worldtile will delete
|
||||||
# existing images if appropriate)
|
# existing images if appropriate)
|
||||||
yield pool.apply_async(func=render_worldtile, args= (tilechunks,
|
yield pool.apply_async(func=render_worldtile, args= (tilechunks,
|
||||||
colstart, colend, rowstart, rowend, dest, self.imgformat))
|
colstart, colend, rowstart, rowend, dest, self.imgformat,
|
||||||
|
self.optimizeimg))
|
||||||
|
|
||||||
def _apply_render_inntertile(self, pool, zoom):
|
def _apply_render_inntertile(self, pool, zoom):
|
||||||
"""Same as _apply_render_worltiles but for the inntertile routine.
|
"""Same as _apply_render_worltiles but for the inntertile routine.
|
||||||
@@ -256,7 +259,7 @@ class QuadtreeGen(object):
|
|||||||
dest = os.path.join(self.destdir, "tiles", *(str(x) for x in path[:-1]))
|
dest = os.path.join(self.destdir, "tiles", *(str(x) for x in path[:-1]))
|
||||||
name = str(path[-1])
|
name = str(path[-1])
|
||||||
|
|
||||||
yield pool.apply_async(func=render_innertile, args= (dest, name, self.imgformat))
|
yield pool.apply_async(func=render_innertile, args= (dest, name, self.imgformat, self.optimizeimg))
|
||||||
|
|
||||||
def go(self, procs):
|
def go(self, procs):
|
||||||
"""Renders all tiles"""
|
"""Renders all tiles"""
|
||||||
@@ -340,7 +343,7 @@ class QuadtreeGen(object):
|
|||||||
pool.join()
|
pool.join()
|
||||||
|
|
||||||
# Do the final one right here:
|
# Do the final one right here:
|
||||||
render_innertile(os.path.join(self.destdir, "tiles"), "base", self.imgformat)
|
render_innertile(os.path.join(self.destdir, "tiles"), "base", self.imgformat, self.optimizeimg)
|
||||||
|
|
||||||
def _get_range_by_path(self, path):
|
def _get_range_by_path(self, path):
|
||||||
"""Returns the x, y chunk coordinates of this tile"""
|
"""Returns the x, y chunk coordinates of this tile"""
|
||||||
@@ -371,7 +374,7 @@ class QuadtreeGen(object):
|
|||||||
return chunklist
|
return chunklist
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
def render_innertile(dest, name, imgformat):
|
def render_innertile(dest, name, imgformat, optimizeimg):
|
||||||
"""
|
"""
|
||||||
Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from
|
Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from
|
||||||
os.path.join(dest, name, "{0,1,2,3}.png")
|
os.path.join(dest, name, "{0,1,2,3}.png")
|
||||||
@@ -461,12 +464,15 @@ def render_innertile(dest, name, imgformat):
|
|||||||
img.save(imgpath, quality=95, subsampling=0)
|
img.save(imgpath, quality=95, subsampling=0)
|
||||||
else: # png
|
else: # png
|
||||||
img.save(imgpath)
|
img.save(imgpath)
|
||||||
|
if optimizeimg:
|
||||||
|
optimize_image(imgpath, imgformat, optimizeimg)
|
||||||
|
|
||||||
with open(hashpath, "wb") as hashout:
|
with open(hashpath, "wb") as hashout:
|
||||||
hashout.write(newhash)
|
hashout.write(newhash)
|
||||||
|
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat):
|
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat, optimizeimg):
|
||||||
"""Renders just the specified chunks into a tile and save it. Unlike usual
|
"""Renders just the specified chunks into a tile and save it. Unlike usual
|
||||||
python conventions, rowend and colend are inclusive. Additionally, the
|
python conventions, rowend and colend are inclusive. Additionally, the
|
||||||
chunks around the edges are half-way cut off (so that neighboring tiles
|
chunks around the edges are half-way cut off (so that neighboring tiles
|
||||||
@@ -595,6 +601,10 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
|
|||||||
|
|
||||||
# Save them
|
# Save them
|
||||||
tileimg.save(imgpath)
|
tileimg.save(imgpath)
|
||||||
|
|
||||||
|
if optimizeimg:
|
||||||
|
optimize_image(imgpath, imgformat, optimizeimg)
|
||||||
|
|
||||||
with open(hashpath, "wb") as hashout:
|
with open(hashpath, "wb") as hashout:
|
||||||
hashout.write(digest)
|
hashout.write(digest)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user