diff --git a/odoo/addons/base/tests/test_image.py b/odoo/addons/base/tests/test_image.py index 3acf6249883..7ef40bc2926 100644 --- a/odoo/addons/base/tests/test_image.py +++ b/odoo/addons/base/tests/test_image.py @@ -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')) diff --git a/odoo/tools/image.py b/odoo/tools/image.py index bf121e26ea1..9b8b4efda1b 100644 --- a/odoo/tools/image.py +++ b/odoo/tools/image.py @@ -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