0

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:
Nicolas F
2019-03-04 17:04:09 +01:00
parent 7f63dfe315
commit 61ebd35240
5 changed files with 46 additions and 11 deletions

View File

@@ -694,16 +694,29 @@ Image options
``imgformat``
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"``
``imgquality``
This is the image quality used when saving the tiles into the JPEG image
format. Its value should be an integer between 0 and 100.
This is the image quality used when saving the tiles into the JPEG or WebP
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``
``imglossless``
Determines whether a WebP image is saved in lossless or lossy mode. Has
no effect on other image formats.
**Default:** ``True``
``optimizeimg``
.. warning::

View File

@@ -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
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
for rset in rsets:
tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir)

View File

@@ -71,6 +71,8 @@ renders = Setting(required=True, default=util.OrderedDict(),
"forcerender": Setting(required=False, validator=validateBool, default=None),
"imgformat": Setting(required=True, validator=validateImgFormat, default="png"),
"imgquality": Setting(required=False, validator=validateImgQuality, default=95),
"imglossless": Setting(required=False, validator=validateBool,
default=True),
"bgcolor": Setting(required=True, validator=validateBGColor, default="1a1a1a"),
"defaultzoom": Setting(required=True, validator=validateDefaultZoom, default=1),
"optimizeimg": Setting(required=True, validator=validateOptImg, default=[]),

View File

@@ -115,9 +115,14 @@ def validateRerenderprob(s):
return val
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)
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
def validateImgQuality(qual):

View File

@@ -261,12 +261,15 @@ class TileSet(object):
rest of this discussion.
imgformat
A string indicating the output format. Must be one of 'png' or
'jpeg'
A string indicating the output format. Must be one of 'png',
'jpeg' or 'webp'
imgquality
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
A list of optimizer instances to use.
@@ -387,8 +390,10 @@ class TileSet(object):
self.imgextension = 'png'
elif self.options['imgformat'] in ('jpeg', 'jpg'):
self.imgextension = 'jpg'
elif self.options['imgformat'] == 'webp':
self.imgextension = 'webp'
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
self._set_map_size()
@@ -1000,8 +1005,11 @@ class TileSet(object):
if imgformat == 'jpg':
img.convert('RGB').save(tmppath, "jpeg", quality=self.options['imgquality'],
subsampling=0)
else: # PNG
elif imgformat == 'png': # PNG
img.save(tmppath, "png")
elif imgformat == 'webp':
img.save(tmppath, "webp", quality=self.options['imgquality'],
lossless=self.options['imglossless'])
if self.options['optimizeimg']:
optimize_image(tmppath, imgformat, self.options['optimizeimg'])
@@ -1101,8 +1109,11 @@ class TileSet(object):
if self.imgextension == 'jpg':
tileimg.convert('RGB').save(tmppath, "jpeg", quality=self.options['imgquality'],
subsampling=0)
else: # PNG
elif self.imgextension == 'png': # 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']:
optimize_image(tmppath, self.imgextension, self.options['optimizeimg'])
os.utime(tmppath, (max_chunk_mtime, max_chunk_mtime))