[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:
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user