[IMP] base, tools, web_editor, *: use original image if size increases
*: web_tour, website When no transformation is applied on an image, changing the quality sometimes increases its storage size. This commit makes sure that the original image remains used if only the image quality is modified and if this makes its storage size bigger. Fixes #61619 task-2835144 closes odoo/odoo#103398 Signed-off-by: Romain Derie (rde) <rde@odoo.com>
This commit is contained in:
@@ -334,7 +334,13 @@ async function applyModifications(img, dataOptions = {}) {
|
||||
ctx.fillRect(0, 0, result.width, result.height);
|
||||
|
||||
// Quality
|
||||
return result.toDataURL(mimetype, quality / 100);
|
||||
const dataURL = result.toDataURL(mimetype, quality / 100);
|
||||
const newSize = getDataURLBinarySize(dataURL);
|
||||
const originalSize = _getImageSizeFromCache(originalSrc);
|
||||
const isChanged = !!perspective || !!glFilter ||
|
||||
original.width !== result.width || original.height !== result.height ||
|
||||
original.width !== croppedImg.width || original.height !== croppedImg.height;
|
||||
return (isChanged || originalSize >= newSize) ? dataURL : await _loadImageDataURL(originalSrc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,9 +397,18 @@ async function _updateImageData(src, key = 'objectURL') {
|
||||
} else {
|
||||
value = URL.createObjectURL(blob);
|
||||
}
|
||||
imageCache.set(src, Object.assign(currentImageData || {}, {[key]: value}));
|
||||
imageCache.set(src, Object.assign(currentImageData || {}, {[key]: value, size: blob.size}));
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* Returns the size of a cached image.
|
||||
*
|
||||
* @param {String} src used as a key on the image cache map.
|
||||
* @returns {Number} size of the image in bytes.
|
||||
*/
|
||||
function _getImageSizeFromCache(src) {
|
||||
return imageCache.get(src).size;
|
||||
}
|
||||
/**
|
||||
* Activates the cropper on a given image.
|
||||
*
|
||||
|
||||
@@ -211,6 +211,9 @@ export class RunningTourActionHelper {
|
||||
text_blur(text, element) {
|
||||
this._text_blur(this._get_action_values(element), text);
|
||||
}
|
||||
range(text, element) {
|
||||
this._range(this._get_action_values(element), text);
|
||||
}
|
||||
drag_and_drop(to, element) {
|
||||
this._drag_and_drop_jquery(this._get_action_values(element), to);
|
||||
}
|
||||
@@ -313,6 +316,10 @@ export class RunningTourActionHelper {
|
||||
values.$element.trigger("focusout");
|
||||
values.$element.trigger("blur");
|
||||
}
|
||||
_range(values, text) {
|
||||
values.$element[0].value = text;
|
||||
values.$element[0].dispatchEvent(new Event('change', { bubbles: true, cancelable: false }));
|
||||
}
|
||||
_calculateCenter($el, selector) {
|
||||
const center = $el.offset();
|
||||
if (selector && selector.indexOf("iframe") !== -1) {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import wTourUtils from 'website.tour_utils';
|
||||
|
||||
wTourUtils.registerWebsitePreviewTour('website_image_quality', {
|
||||
test: true,
|
||||
url: '/',
|
||||
edition: true,
|
||||
}, [
|
||||
wTourUtils.dragNDrop({
|
||||
id: 's_text_image',
|
||||
name: 'Text - Image',
|
||||
}),
|
||||
{
|
||||
content: "Select image",
|
||||
trigger: 'iframe .s_text_image img',
|
||||
},
|
||||
{
|
||||
content: "Set low quality",
|
||||
trigger: 'we-customizeblock-options:has(we-title:contains("Image")) we-range[data-set-quality] input',
|
||||
run: 'range 5',
|
||||
},
|
||||
{
|
||||
content: "Wait for image update: NOT original image",
|
||||
trigger: 'iframe .s_text_image img:not([src$="s_text_image_default_image"])',
|
||||
run: () => {}, // It is a check.
|
||||
},
|
||||
{
|
||||
content: "Check image size",
|
||||
// Reached size cannot be hardcoded because it changes with
|
||||
// different versions of Chrome.
|
||||
trigger: 'we-customizeblock-options:has(we-title:contains("Image")) .o_we_image_weight:contains(" kb"):not(:contains("16.1 kb"))',
|
||||
run() {
|
||||
// Make sure the reached size is smaller than the original one.
|
||||
if (parseFloat(this.$anchor[0].innerText) >= 47.5) {
|
||||
console.error("Image size should be smaller than original");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Set high quality",
|
||||
trigger: 'we-customizeblock-options:has(we-title:contains("Image")) we-range[data-set-quality] input',
|
||||
run: 'range 99',
|
||||
},
|
||||
{
|
||||
content: "Wait for image update: back to original image",
|
||||
trigger: 'iframe .s_text_image img[src$="QUntVvdqEdljipEUu7ez5lb6IqyGKzQmRxVnZX/2Q=="]',
|
||||
run: () => {}, // It is a check.
|
||||
},
|
||||
{
|
||||
content: "Check image size",
|
||||
trigger: 'we-customizeblock-options:has(we-title:contains("Image")) .o_we_image_weight:contains("47.5 kb")',
|
||||
run: () => {}, // It is a check.
|
||||
},
|
||||
]);
|
||||
@@ -1,3 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import odoo.tests
|
||||
@@ -51,3 +54,6 @@ class TestWebsiteAttachment(odoo.tests.HttpCase):
|
||||
|
||||
req = self.opener.get(base + '/web/image/test.an_image_redirect_301', allow_redirects=True)
|
||||
self.assertEqual(req.status_code, 200)
|
||||
|
||||
def test_02_image_quality(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'website_image_quality', login="admin")
|
||||
|
||||
@@ -177,6 +177,19 @@ class TestImage(TransactionCase):
|
||||
res = tools.image_process(self.img_1920x1080_jpeg)
|
||||
self.assertLessEqual(len(res), len(self.img_1920x1080_jpeg))
|
||||
|
||||
# CASE: JPEG optimize + bigger size => original
|
||||
pil_image = Image.new('RGB', (1920, 1080), color=self.bg_color)
|
||||
# Drawing non trivial content so that optimization matters.
|
||||
ImageDraw.Draw(pil_image).ellipse(xy=[
|
||||
(400, 0),
|
||||
(1500, 1080)
|
||||
], fill=self.fill_color, outline=(240, 25, 40), width=10)
|
||||
image = tools.image_apply_opt(pil_image, 'JPEG')
|
||||
res = tools.image_process(image, quality=50)
|
||||
self.assertLess(len(res), len(image), "Low quality image should be smaller than original")
|
||||
res = tools.image_process(image, quality=99)
|
||||
self.assertEqual(len(res), len(image), "Original should be returned if size increased")
|
||||
|
||||
# CASE: GIF doesn't apply quality, just optimize
|
||||
image = tools.image_apply_opt(Image.new('RGB', (1080, 1920)), 'GIF')
|
||||
res = tools.image_process(image)
|
||||
|
||||
+6
-1
@@ -151,7 +151,12 @@ class ImageProcess():
|
||||
if output_image.mode not in ["1", "L", "P", "RGB", "RGBA"] or (output_format == 'JPEG' and output_image.mode == 'RGBA'):
|
||||
output_image = output_image.convert("RGB")
|
||||
|
||||
return image_apply_opt(output_image, **opt)
|
||||
output_bytes = image_apply_opt(output_image, **opt)
|
||||
if len(output_bytes) >= len(self.source) and self.original_format == output_format and not self.operationsCount:
|
||||
# Format has not changed and image content is unchanged but the
|
||||
# reached binary is bigger: rather use the original.
|
||||
return self.source
|
||||
return output_bytes
|
||||
|
||||
def resize(self, max_width=0, max_height=0):
|
||||
"""Resize the image.
|
||||
|
||||
Reference in New Issue
Block a user