From 2864f8a6cf502aeebc61ca0145f918fc14c25cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Theys?= Date: Fri, 26 Apr 2019 10:55:17 +0000 Subject: [PATCH] [IMP] base: add tests for the image tools task-1958000 PR: #31811 --- odoo/addons/base/tests/__init__.py | 1 + odoo/addons/base/tests/test_image.py | 270 +++++++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 odoo/addons/base/tests/test_image.py diff --git a/odoo/addons/base/tests/__init__.py b/odoo/addons/base/tests/__init__.py index 281aec864c8..bba0d5b6742 100644 --- a/odoo/addons/base/tests/__init__.py +++ b/odoo/addons/base/tests/__init__.py @@ -7,6 +7,7 @@ from . import test_db_cursor from . import test_expression from . import test_float from . import test_func +from . import test_image from . import test_ir_actions from . import test_ir_attachment from . import test_ir_http diff --git a/odoo/addons/base/tests/test_image.py b/odoo/addons/base/tests/test_image.py new file mode 100644 index 00000000000..58ed6c245e1 --- /dev/null +++ b/odoo/addons/base/tests/test_image.py @@ -0,0 +1,270 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import base64 +import binascii +import PIL + +from odoo import tools +from odoo.tests.common import TransactionCase + + +class TestImage(TransactionCase): + """Tests for the different image tools helpers. + + The following helpers are not tested here because they are wrappers for the + other methods or they are tested elsewhere (eg. on TestWebsiteSaleImage): + - image_resize_image_big + - image_resize_image_large + - image_resize_image_medium + - image_resize_image_small + - image_get_resized_images + - image_resize_images + - image_resize_and_sharpen + - is_image_size_above + """ + def setUp(self): + super(TestImage, self).setUp() + # This is the base64 of a 1px * 1px image saved as PNG. + self.base64_image_1x1 = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGNgYGAAAAAEAAH2FzhVAAAAAElFTkSuQmCC' + self.base64_svg = base64.b64encode(b'') + self.base64_image_1920_1080 = tools.image_to_base64(PIL.Image.new('RGB', (1920, 1080)), 'JPEG') + + def test_00_base64_to_image(self): + """Test that base64 is correctly opened as a PIL image.""" + + # CASE: base64 as bytes + image = tools.base64_to_image(self.base64_image_1x1) + self.assertEqual(type(image), PIL.PngImagePlugin.PngImageFile) + self.assertEqual(image.size, (1, 1)) + + # CASE: base64 as string + image = tools.base64_to_image(self.base64_image_1x1.decode('ASCII')) + self.assertEqual(type(image), PIL.PngImagePlugin.PngImageFile) + self.assertEqual(image.size, (1, 1)) + + # CASE: wrong base64: binascii.Error: Incorrect padding + with self.assertRaises(binascii.Error): + image = tools.base64_to_image(b'oazdazpodazdpok') + + # CASE: wrong base64: OSError: cannot identify image file + with self.assertRaises(OSError): + image = tools.base64_to_image(b'oazdazpodazdpokd') + + def test_01_image_to_base64(self): + """Test that a PIL image is correctly saved as base64.""" + image = PIL.Image.new('RGB', (1, 1)) + image_base64 = tools.image_to_base64(image, 'PNG') + self.assertEqual(image_base64, self.base64_image_1x1) + + def test_10_image_resize_image(self): + """Test that image_resize_image is working as expected.""" + + # CASE: return False if base64_source is falsy + res = tools.image_resize_image(False) + self.assertFalse(res) + + # CASE: return base64_source if size == (None, None) + res = tools.image_resize_image(self.base64_image_1x1, size=(None, None)) + self.assertEqual(res, self.base64_image_1x1) + + # CASE: return base64_source if format is SVG + res = tools.image_resize_image(self.base64_svg) + self.assertEqual(res, self.base64_svg) + + # CASE: ok with default parameters + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080)) + self.assertEqual(image.size, tools.IMAGE_BIG_SIZE) + + # CASE: correct resize + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, size=(800, 600))) + self.assertEqual(image.size, (800, 600)) + + # CASE: change filetype to PNG + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, filetype='PNG')) + self.assertEqual(image.format, 'PNG') + + # CASE: change filetype to JPEG (case insensitive) + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1x1, filetype='jpeg')) + self.assertEqual(image.format, 'JPEG') + + # CASE: change filetype to BMP converted to PNG + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1x1, filetype='BMP')) + self.assertEqual(image.format, 'PNG') + + # CASE: change filetype PNG with RGBA to JPEG + base64_image_1080_1920_rgba = tools.image_to_base64(PIL.Image.new('RGBA', (1080, 1920)), 'PNG') + image = tools.base64_to_image(tools.image_resize_image(base64_image_1080_1920_rgba, filetype='jpeg')) + self.assertEqual(image.format, 'JPEG') + + # CASE: keep ratio if no height given + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, size=(192, None))) + self.assertEqual(image.size, (192, 108)) + + # CASE: keep ratio if no width given + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, size=(None, 108))) + self.assertEqual(image.size, (192, 108)) + + # CASE: resize above original size + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, size=(3000, 2000))) + self.assertEqual(image.size, (3000, 2000)) + + # CASE: avoid_if_small=True and size above, return original + res = tools.image_resize_image(self.base64_image_1920_1080, size=(3000, 2000), avoid_if_small=True) + self.assertEqual(res, self.base64_image_1920_1080) + + # CASE: same size, no sharpen + res = tools.image_resize_image(self.base64_image_1920_1080, size=(1920, 1080)) + self.assertEqual(res, self.base64_image_1920_1080) + + # CASE: upper_limit=True, no resize if above + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, size=(3000, 2000), upper_limit=True)) + self.assertEqual(image.size, (1920, 1080)) + + # CASE: upper_limit=True, keep ratio, no resize if above + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, size=(3000, None), upper_limit=True)) + self.assertEqual(image.size, (1920, 1080)) + + # CASE: upper_limit=True, keep ratio, no resize if above + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, size=(None, 2000), upper_limit=True)) + self.assertEqual(image.size, (1920, 1080)) + + # CASE: upper_limit=True, vertical image, correct resize if below + base64_image_1080_1920 = tools.image_to_base64(PIL.Image.new('RGB', (1080, 1920)), 'PNG') + image = tools.base64_to_image(tools.image_resize_image(base64_image_1080_1920, size=(3000, 192), upper_limit=True)) + self.assertEqual(image.size, (108, 192)) + + # CASE: preserve_aspect_ratio=True, adapt to width + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, size=(192, 200), preserve_aspect_ratio=True)) + self.assertEqual(image.size, (192, 108)) + + # CASE: preserve_aspect_ratio=True, adapt to height + image = tools.base64_to_image(tools.image_resize_image(self.base64_image_1920_1080, size=(400, 108), preserve_aspect_ratio=True)) + self.assertEqual(image.size, (192, 108)) + + def test_11_image_optimize_for_web(self): + """Test that image_optimize_for_web is working as expected.""" + # CASE: return base64_source if format is SVG + res = tools.image_optimize_for_web(self.base64_svg) + self.assertEqual(res, self.base64_svg) + + # CASE: size excessive + base64_image_excessive = tools.image_to_base64(PIL.Image.new('RGB', (45001, 1000)), 'PNG') + with self.assertRaises(ValueError): + res = tools.image_optimize_for_web(base64_image_excessive) + + # CASE: max_width + image = tools.base64_to_image(tools.image_optimize_for_web(self.base64_image_1920_1080, max_width=192)) + self.assertEqual(image.size, (192, 108)) + + # CASE: PNG RGBA doesn't apply quality, just optimize + base64_image_1080_1920_rgba = tools.image_to_base64(PIL.Image.new('RGBA', (1080, 1920)), 'PNG') + res = tools.image_optimize_for_web(base64_image_1080_1920_rgba) + self.assertLessEqual(len(res), len(base64_image_1080_1920_rgba)) + + # CASE: PNG RGB doesn't apply quality, just optimize + base64_image_1080_1920_rgb = tools.image_to_base64(PIL.Image.new('P', (1080, 1920)), 'PNG') + res = tools.image_optimize_for_web(base64_image_1080_1920_rgb) + self.assertLessEqual(len(res), len(base64_image_1080_1920_rgb)) + + # CASE: JPEG strictly reduced quality + res = tools.image_optimize_for_web(self.base64_image_1920_1080) + self.assertLessEqual(len(res), len(self.base64_image_1920_1080)) + + # CASE: GIF doesn't apply quality, just optimize + base64_image_1080_1920_gif = tools.image_to_base64(PIL.Image.new('RGB', (1080, 1920)), 'GIF') + res = tools.image_optimize_for_web(base64_image_1080_1920_gif) + self.assertLessEqual(len(res), len(base64_image_1080_1920_gif)) + + # CASE: unsupported format + base64_image_1080_1920_tiff = tools.image_to_base64(PIL.Image.new('RGB', (1080, 1920)), 'TIFF') + with self.assertRaises(ValueError): + res = tools.image_optimize_for_web(base64_image_1080_1920_tiff) + + def test_12_crop_image(self): + """Test that crop_image is working as expected.""" + + # CASE: return False if base64_source is falsy + res = tools.crop_image(False) + self.assertFalse(res) + + # CASE: no param, no crop + image = tools.base64_to_image(tools.crop_image(self.base64_image_1920_1080)) + self.assertEqual(image.size, (1920, 1080)) + + # CASE: ratio, crop biggest possible + image = tools.base64_to_image(tools.crop_image(self.base64_image_1920_1080, ratio=(1, 1))) + self.assertEqual(image.size, (1080, 1080)) + + # CASE: ratio vertical, limit height + image = tools.base64_to_image(tools.crop_image(self.base64_image_1920_1080, ratio=(1, 2))) + self.assertEqual(image.size, (540, 1080)) + + # CASE: ratio horizontal, limit width + image = tools.base64_to_image(tools.crop_image(self.base64_image_1920_1080, ratio=(2, 1))) + self.assertEqual(image.size, (1920, 960)) + + # CASE: type center + image = tools.base64_to_image(tools.crop_image(self.base64_image_1920_1080, ratio=(1, 1), type='center')) + self.assertEqual(image.size, (1080, 1080)) + + # CASE: type bottom + image = tools.base64_to_image(tools.crop_image(self.base64_image_1920_1080, ratio=(1, 1), type='bottom')) + self.assertEqual(image.size, (1080, 1080)) + + # CASE: wrong type + with self.assertRaises(ValueError): + tools.crop_image(self.base64_image_1920_1080, ratio=(1, 1), type='wrong') + + # CASE: size given, resize too (but keep ratio) + image = tools.base64_to_image(tools.crop_image(self.base64_image_1920_1080, ratio=(2, 1), size=(512, 512))) + self.assertEqual(image.size, (512, 256)) + + def test_13_image_colorize(self): + """Test that image_colorize is working as expected.""" + + # verify initial condition + image_rgba = PIL.Image.new('RGBA', (1, 1)) + self.assertEqual(image_rgba.getpixel((0, 0)), (0, 0, 0, 0)) + base64_rgba = tools.image_to_base64(image_rgba, 'PNG') + + # CASE: color random, color has changed + image = tools.base64_to_image(tools.image_colorize(base64_rgba)) + self.assertNotEqual(image.getpixel((0, 0)), (0, 0, 0)) + + # CASE: not random, fixed color + image = tools.base64_to_image(tools.image_colorize(base64_rgba, randomize=False)) + self.assertEqual(image.getpixel((0, 0)), (255, 255, 255)) + + def test_16_limited_image_resize(self): + """Test that limited_image_resize is working as expected.""" + + # CASE: return input if base64_source is falsy + self.assertEqual(tools.limited_image_resize(False), False) + + # CASE: return input if no width or height + self.assertEqual(tools.limited_image_resize(self.base64_image_1x1), self.base64_image_1x1) + + # CASE: given width, resize and keep ratio + image = tools.base64_to_image(tools.limited_image_resize(self.base64_image_1920_1080, width=192)) + self.assertEqual(image.size, (192, 108)) + + # CASE: given height, resize and keep ratio + image = tools.base64_to_image(tools.limited_image_resize(self.base64_image_1920_1080, height=108)) + self.assertEqual(image.size, (192, 108)) + + # CASE: if crop, ratio always 1:1 + image = tools.base64_to_image(tools.limited_image_resize(self.base64_image_1920_1080, width=192, height=108, crop=True)) + self.assertEqual(image.size, (108, 108)) + + # CASE: if not upper_limit, max 500px * 500px + image = tools.base64_to_image(tools.limited_image_resize(self.base64_image_1920_1080, width=1000, height=1000)) + self.assertEqual(image.size, (500, 500)) + + # CASE: if upper_limit=True, no limit + image = tools.base64_to_image(tools.limited_image_resize(self.base64_image_1920_1080, width=1000, height=1000, upper_limit=True)) + self.assertEqual(image.size, (1000, 562)) + + def test_17_image_data_uri(self): + """Test that image_data_uri is working as expected.""" + self.assertEqual(tools.image_data_uri(self.base64_image_1x1), 'data:image/png;base64,' + self.base64_image_1x1.decode('ascii'))