[IMP] base, tools: compute webp image size

This commit introduces the computation of the image size from a webp
binary source without relying on PIL.

This is needed by eCommerce to determine which image to fetch when using
the zoom functionality on the product page.

task-2774352

Part-of: odoo/odoo#85494
This commit is contained in:
Benoit Socias
2023-07-15 05:10:47 +02:00
parent f8a9669c8d
commit 706e696e3d
2 changed files with 68 additions and 7 deletions
+15
View File
@@ -272,6 +272,21 @@ class TestImage(TransactionCase):
image = img_open(tools.image_process(image_1080_1920_tiff, quality=95))
self.assertEqual(image.format, 'JPEG', "unsupported format to JPEG")
def test_17_get_webp_size(self):
# Using 32 bytes image headers as data.
# Lossy webp: 550x368
webp_lossy = b'RIFFhv\x00\x00WEBPVP8 \\v\x00\x00\xd2\xbe\x01\x9d\x01*&\x02p\x01>\xd5'
size = tools.get_webp_size(webp_lossy)
self.assertEqual((550, 368), size, "Wrong resolution for lossy webp")
# Lossless webp: 421x163
webp_lossless = b'RIFF\xba\x84\x00\x00WEBPVP8L\xad\x84\x00\x00/\xa4\x81(\x10MHr\x1bI\x92\xa4'
size = tools.get_webp_size(webp_lossless)
self.assertEqual((421, 163), size, "Wrong resolution for lossless webp")
# Extended webp: 800x600
webp_extended = b'RIFF\x80\xce\x00\x00WEBPVP8X\n\x00\x00\x00\x10\x00\x00\x00\x1f\x03\x00W\x02\x00AL'
size = tools.get_webp_size(webp_extended)
self.assertEqual((800, 600), size, "Wrong resolution for extended webp")
def test_20_image_data_uri(self):
"""Test that image_data_uri is working as expected."""
self.assertEqual(tools.image_data_uri(base64.b64encode(self.img_1x1_png)), 'data:image/png;base64,' + base64.b64encode(self.img_1x1_png).decode('ascii'))
+53 -7
View File
@@ -15,6 +15,7 @@ except ImportError:
from random import randrange
from odoo.exceptions import UserError
from odoo.tools.misc import DotDict
from odoo.tools.translate import _
@@ -433,6 +434,43 @@ def image_to_base64(image, output_format, **params):
return base64.b64encode(stream)
def get_webp_size(source):
"""
Returns the size of the provided webp binary source for VP8, VP8X and
VP8L, otherwise returns None.
See https://developers.google.com/speed/webp/docs/riff_container.
:param source: binary source
:return: (width, height) tuple, or None if not supported
"""
if not (source[0:4] == b'RIFF' and source[8:15] == b'WEBPVP8'):
raise UserError(_("This file is not a webp file."))
vp8_type = source[15]
if vp8_type == 0x20: # 0x20 = ' '
# Sizes on big-endian 16 bits at offset 26.
width_low, width_high, height_low, height_high = source[26:30]
width = (width_high << 8) + width_low
height = (height_high << 8) + height_low
return (width, height)
elif vp8_type == 0x58: # 0x48 = 'X'
# Sizes (minus one) on big-endian 24 bits at offset 24.
width_low, width_medium, width_high, height_low, height_medium, height_high = source[24:30]
width = 1 + (width_high << 16) + (width_medium << 8) + width_low
height = 1 + (height_high << 16) + (height_medium << 8) + height_low
return (width, height)
elif vp8_type == 0x4C and source[20] == 0x2F: # 0x4C = 'L'
# Sizes (minus one) on big-endian-ish 14 bits at offset 21.
# E.g. [@20] 2F ab cd ef gh
# - width = 1 + (c&0x3)d ab: ignore the two high bits of the second byte
# - height= 1 + hef(c&0xC>>2): used them as the first two bits of the height
ab, cd, ef, gh = source[21:25]
width = 1 + ((cd & 0x3F) << 8) + ab
height = 1 + ((gh & 0xF) << 10) + (ef << 2) + (cd >> 6)
return (width, height)
return None
def is_image_size_above(base64_source_1, base64_source_2):
"""Return whether or not the size of the given image `base64_source_1` is
above the size of the given image `base64_source_2`.
@@ -442,13 +480,21 @@ def is_image_size_above(base64_source_1, base64_source_2):
if base64_source_1[:1] in (b'P', 'P') or base64_source_2[:1] in (b'P', 'P'):
# False for SVG
return False
source_1 = base64.b64decode(base64_source_1)
source_2 = base64.b64decode(base64_source_2)
if (source_1[0:4] == b'RIFF' and source_1[8:15] == b'WEBPVP8') or (source_2[0:4] == b'RIFF' and source_2[8:15] == b'WEBPVP8'):
# False for WEBP
return False
image_source = image_fix_orientation(binary_to_image(source_1))
image_target = image_fix_orientation(binary_to_image(source_2))
def get_image_size(base64_source):
source = base64.b64decode(base64_source)
if (source[0:4] == b'RIFF' and source[8:15] == b'WEBPVP8'):
size = get_webp_size(source)
if size:
return DotDict({'width': size[0], 'height': size[0]})
else:
# False for unknown WEBP format
return False
else:
return image_fix_orientation(binary_to_image(source))
image_source = get_image_size(base64_source_1)
image_target = get_image_size(base64_source_2)
return image_source.width > image_target.width or image_source.height > image_target.height