Add WebP image format support
Since Firefox 65 added support for WebP, users may be interested in having maps that use WebP images. Support for this is added in this commit, along with documentation for it. A new option, "imglossless", controls whether we write out lossless or lossy WebP images. The generic name "imglossless" as opposed to a more specific "webplossless" was chosen in case future image formats we also implement also support lossless/lossy modes in the same format (JPEG-XL? AV1 image format?). It's an okay meme but lossy mode really falls apart on our sorts of images on the more zoomed out composite tiles, resulting in pretty blurry messes. Might be due to a PSNR bias in the encoder, which is to be expected from Google.
This commit is contained in:
@@ -694,16 +694,29 @@ Image options
|
|||||||
|
|
||||||
``imgformat``
|
``imgformat``
|
||||||
This is which image format to render the tiles into. Its value should be a
|
This is which image format to render the tiles into. Its value should be a
|
||||||
string containing "png", "jpg", or "jpeg".
|
string containing "png", "jpg", "jpeg" or "webp".
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
For WebP, your PIL/Pillow needs to be built with WebP support. Do
|
||||||
|
keep in mind that not all browsers support WebP images.
|
||||||
|
|
||||||
**Default:** ``"png"``
|
**Default:** ``"png"``
|
||||||
|
|
||||||
``imgquality``
|
``imgquality``
|
||||||
This is the image quality used when saving the tiles into the JPEG image
|
This is the image quality used when saving the tiles into the JPEG or WebP
|
||||||
format. Its value should be an integer between 0 and 100.
|
image format. Its value should be an integer between 0 and 100.
|
||||||
|
|
||||||
|
For WebP images in lossless mode, it determines how much effort is spent
|
||||||
|
on compressing the image.
|
||||||
|
|
||||||
**Default:** ``95``
|
**Default:** ``95``
|
||||||
|
|
||||||
|
``imglossless``
|
||||||
|
Determines whether a WebP image is saved in lossless or lossy mode. Has
|
||||||
|
no effect on other image formats.
|
||||||
|
|
||||||
|
**Default:** ``True``
|
||||||
|
|
||||||
``optimizeimg``
|
``optimizeimg``
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|||||||
@@ -533,7 +533,11 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
|
|||||||
|
|
||||||
# only pass to the TileSet the options it really cares about
|
# only pass to the TileSet the options it really cares about
|
||||||
render['name'] = render_name # perhaps a hack. This is stored here for the asset manager
|
render['name'] = render_name # perhaps a hack. This is stored here for the asset manager
|
||||||
tileSetOpts = util.dict_subset(render, ["name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "defaultzoom", "imgquality", "optimizeimg", "rendermode", "worldname_orig", "title", "dimension", "changelist", "showspawn", "overlay", "base", "poititle", "maxzoom", "showlocationmarker", "minzoom"])
|
tileSetOpts = util.dict_subset(render, [
|
||||||
|
"name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "defaultzoom",
|
||||||
|
"imgquality", "imglossless", "optimizeimg", "rendermode", "worldname_orig", "title",
|
||||||
|
"dimension", "changelist", "showspawn", "overlay", "base", "poititle", "maxzoom",
|
||||||
|
"showlocationmarker", "minzoom"])
|
||||||
tileSetOpts.update({"spawn": w.find_true_spawn()}) # TODO find a better way to do this
|
tileSetOpts.update({"spawn": w.find_true_spawn()}) # TODO find a better way to do this
|
||||||
for rset in rsets:
|
for rset in rsets:
|
||||||
tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir)
|
tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir)
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ renders = Setting(required=True, default=util.OrderedDict(),
|
|||||||
"forcerender": Setting(required=False, validator=validateBool, default=None),
|
"forcerender": Setting(required=False, validator=validateBool, default=None),
|
||||||
"imgformat": Setting(required=True, validator=validateImgFormat, default="png"),
|
"imgformat": Setting(required=True, validator=validateImgFormat, default="png"),
|
||||||
"imgquality": Setting(required=False, validator=validateImgQuality, default=95),
|
"imgquality": Setting(required=False, validator=validateImgQuality, default=95),
|
||||||
|
"imglossless": Setting(required=False, validator=validateBool,
|
||||||
|
default=True),
|
||||||
"bgcolor": Setting(required=True, validator=validateBGColor, default="1a1a1a"),
|
"bgcolor": Setting(required=True, validator=validateBGColor, default="1a1a1a"),
|
||||||
"defaultzoom": Setting(required=True, validator=validateDefaultZoom, default=1),
|
"defaultzoom": Setting(required=True, validator=validateDefaultZoom, default=1),
|
||||||
"optimizeimg": Setting(required=True, validator=validateOptImg, default=[]),
|
"optimizeimg": Setting(required=True, validator=validateOptImg, default=[]),
|
||||||
|
|||||||
@@ -115,9 +115,14 @@ def validateRerenderprob(s):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
def validateImgFormat(fmt):
|
def validateImgFormat(fmt):
|
||||||
if fmt not in ("png", "jpg", "jpeg"):
|
if fmt not in ("png", "jpg", "jpeg", "webp"):
|
||||||
raise ValidationException("%r is not a valid image format" % fmt)
|
raise ValidationException("%r is not a valid image format" % fmt)
|
||||||
if fmt == "jpeg": fmt = "jpg"
|
if fmt == "jpeg": fmt = "jpg"
|
||||||
|
if fmt == "webp":
|
||||||
|
try:
|
||||||
|
from PIL import _webp
|
||||||
|
except ImportError:
|
||||||
|
raise ValidationException("WebP is not supported by your PIL/Pillow installation")
|
||||||
return fmt
|
return fmt
|
||||||
|
|
||||||
def validateImgQuality(qual):
|
def validateImgQuality(qual):
|
||||||
|
|||||||
@@ -261,12 +261,15 @@ class TileSet(object):
|
|||||||
rest of this discussion.
|
rest of this discussion.
|
||||||
|
|
||||||
imgformat
|
imgformat
|
||||||
A string indicating the output format. Must be one of 'png' or
|
A string indicating the output format. Must be one of 'png',
|
||||||
'jpeg'
|
'jpeg' or 'webp'
|
||||||
|
|
||||||
imgquality
|
imgquality
|
||||||
An integer 1-100 indicating the quality of the jpeg output. Only
|
An integer 1-100 indicating the quality of the jpeg output. Only
|
||||||
relevant in jpeg mode.
|
relevant in jpeg and webp mode.
|
||||||
|
|
||||||
|
imglossless
|
||||||
|
A boolean indicating whether to save a webp image in lossless mode.
|
||||||
|
|
||||||
optimizeimg
|
optimizeimg
|
||||||
A list of optimizer instances to use.
|
A list of optimizer instances to use.
|
||||||
@@ -387,8 +390,10 @@ class TileSet(object):
|
|||||||
self.imgextension = 'png'
|
self.imgextension = 'png'
|
||||||
elif self.options['imgformat'] in ('jpeg', 'jpg'):
|
elif self.options['imgformat'] in ('jpeg', 'jpg'):
|
||||||
self.imgextension = 'jpg'
|
self.imgextension = 'jpg'
|
||||||
|
elif self.options['imgformat'] == 'webp':
|
||||||
|
self.imgextension = 'webp'
|
||||||
else:
|
else:
|
||||||
raise ValueError("imgformat must be one of: 'png' or 'jpg'")
|
raise ValueError("imgformat must be one of: 'png', 'jpg' or 'webp'")
|
||||||
|
|
||||||
# This sets self.treedepth, self.xradius, and self.yradius
|
# This sets self.treedepth, self.xradius, and self.yradius
|
||||||
self._set_map_size()
|
self._set_map_size()
|
||||||
@@ -1000,8 +1005,11 @@ class TileSet(object):
|
|||||||
if imgformat == 'jpg':
|
if imgformat == 'jpg':
|
||||||
img.convert('RGB').save(tmppath, "jpeg", quality=self.options['imgquality'],
|
img.convert('RGB').save(tmppath, "jpeg", quality=self.options['imgquality'],
|
||||||
subsampling=0)
|
subsampling=0)
|
||||||
else: # PNG
|
elif imgformat == 'png': # PNG
|
||||||
img.save(tmppath, "png")
|
img.save(tmppath, "png")
|
||||||
|
elif imgformat == 'webp':
|
||||||
|
img.save(tmppath, "webp", quality=self.options['imgquality'],
|
||||||
|
lossless=self.options['imglossless'])
|
||||||
|
|
||||||
if self.options['optimizeimg']:
|
if self.options['optimizeimg']:
|
||||||
optimize_image(tmppath, imgformat, self.options['optimizeimg'])
|
optimize_image(tmppath, imgformat, self.options['optimizeimg'])
|
||||||
@@ -1101,8 +1109,11 @@ class TileSet(object):
|
|||||||
if self.imgextension == 'jpg':
|
if self.imgextension == 'jpg':
|
||||||
tileimg.convert('RGB').save(tmppath, "jpeg", quality=self.options['imgquality'],
|
tileimg.convert('RGB').save(tmppath, "jpeg", quality=self.options['imgquality'],
|
||||||
subsampling=0)
|
subsampling=0)
|
||||||
else: # PNG
|
elif self.imgextension == 'png': # PNG
|
||||||
tileimg.save(tmppath, "png")
|
tileimg.save(tmppath, "png")
|
||||||
|
elif self.imgextension == 'webp':
|
||||||
|
tileimg.save(tmppath, "webp", quality=self.options['imgquality'],
|
||||||
|
lossless=self.options['imglossless'])
|
||||||
if self.options['optimizeimg']:
|
if self.options['optimizeimg']:
|
||||||
optimize_image(tmppath, self.imgextension, self.options['optimizeimg'])
|
optimize_image(tmppath, self.imgextension, self.options['optimizeimg'])
|
||||||
os.utime(tmppath, (max_chunk_mtime, max_chunk_mtime))
|
os.utime(tmppath, (max_chunk_mtime, max_chunk_mtime))
|
||||||
|
|||||||
Reference in New Issue
Block a user