*: 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>
367 lines
21 KiB
Python
367 lines
21 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import base64
|
|
import io
|
|
import binascii
|
|
|
|
from PIL import Image, ImageDraw, PngImagePlugin
|
|
|
|
from odoo import tools
|
|
from odoo.exceptions import UserError
|
|
from odoo.tests.common import TransactionCase
|
|
|
|
|
|
def img_open(data):
|
|
return Image.open(io.BytesIO(data))
|
|
|
|
|
|
class TestImage(TransactionCase):
|
|
"""Tests for the different image tools helpers."""
|
|
def setUp(self):
|
|
super(TestImage, self).setUp()
|
|
self.bg_color = (135, 90, 123)
|
|
self.fill_color = (0, 160, 157)
|
|
|
|
self.img_1x1_png = base64.b64decode(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGNgYGAAAAAEAAH2FzhVAAAAAElFTkSuQmCC')
|
|
self.img_svg = b'<svg></svg>'
|
|
self.img_1920x1080_jpeg = tools.image_apply_opt(Image.new('RGB', (1920, 1080)), 'JPEG')
|
|
# The following image contains a tag `Lens Info` with a value of `3.99mm f/1.8`
|
|
# This particular tag 0xa432 makes the `exif_transpose` method fail in 5.4.1 < Pillow < 7.2.0
|
|
self.img_exif_jpg = base64.b64decode(b"""/9j/4AAQSkZJRgABAQAAAQABAAD/4QDQRXhpZgAATU0AKgAAAAgABgESAAMAAAABAAYAAAEaAAUA
|
|
AAABAAAAVgEbAAUAAAABAAAAXgEoAAMAAAABAAEAAAITAAMAAAABAAEAAIdpAAQAAAABAAAAZgAA
|
|
AAAAAAABAAAAAQAAAAEAAAABAAWQAAAHAAAABDAyMzGRAQAHAAAABAECAwCgAAAHAAAABDAxMDCg
|
|
AQADAAAAAf//AACkMgAFAAAABAAAAKgAAAAAAAABjwAAAGQAAAGPAAAAZAAAAAkAAAAFAAAACQAA
|
|
AAX/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAx
|
|
NDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy
|
|
MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAADAAYDASIAAhEBAxEB/8QAHwAAAQUBAQEB
|
|
AQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1Fh
|
|
ByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZ
|
|
WmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXG
|
|
x8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAEC
|
|
AwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHB
|
|
CSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0
|
|
dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX
|
|
2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iiigD//2Q==""")
|
|
|
|
# Draw a red square in the middle of the image, this will be used to
|
|
# verify crop is working. The border is going to be `self.bg_color` and
|
|
# the middle is going to be `self.fill_color`.
|
|
|
|
# horizontal image (border is left/right)
|
|
image = Image.new('RGB', (1920, 1080), color=self.bg_color)
|
|
offset = (image.size[0] - image.size[1]) / 2
|
|
draw = ImageDraw.Draw(image)
|
|
draw.rectangle(xy=[
|
|
(offset, 0),
|
|
(image.size[0] - offset, image.size[1])
|
|
], fill=self.fill_color)
|
|
self.img_1920x1080_png = tools.image_apply_opt(image, 'PNG')
|
|
|
|
# vertical image (border is top/bottom)
|
|
image = Image.new('RGB', (1080, 1920), color=self.bg_color)
|
|
offset = (image.size[1] - image.size[0]) / 2
|
|
draw = ImageDraw.Draw(image)
|
|
draw.rectangle(xy=[
|
|
(0, offset),
|
|
(image.size[0], image.size[1] - offset)
|
|
], fill=self.fill_color)
|
|
self.img_1080x1920_png = tools.image_apply_opt(image, 'PNG')
|
|
|
|
def test_00_base64_to_image(self):
|
|
"""Test that base64 is correctly opened as a PIL image."""
|
|
image = img_open(self.img_1x1_png)
|
|
self.assertEqual(type(image), PngImagePlugin.PngImageFile, "base64 as bytes, correct format")
|
|
self.assertEqual(image.size, (1, 1), "base64 as bytes, correct size")
|
|
|
|
with self.assertRaises(UserError, msg="This file could not be decoded as an image file. Please try with a different file."):
|
|
image = tools.base64_to_image(b'oazdazpodazdpok')
|
|
|
|
with self.assertRaises(UserError, msg="This file could not be decoded as an image file. Please try with a different file."):
|
|
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 = Image.new('RGB', (1, 1))
|
|
image_base64 = tools.image_to_base64(image, 'PNG')
|
|
self.assertEqual(image_base64, base64.b64encode(self.img_1x1_png))
|
|
|
|
def test_02_image_fix_orientation(self):
|
|
"""Test that the orientation of images is correct."""
|
|
|
|
# Colors that can be distinguished among themselves even with jpeg loss.
|
|
blue = (0, 0, 255)
|
|
yellow = (255, 255, 0)
|
|
green = (0, 255, 0)
|
|
pink = (255, 0, 255)
|
|
# Image large enough so jpeg loss is not a huge factor in the corners.
|
|
size = 50
|
|
expected = (blue, yellow, green, pink)
|
|
|
|
# They are all supposed to be same image: (blue, yellow, green, pink) in
|
|
# that order, but each encoded with a different orientation.
|
|
self._orientation_test(1, (blue, yellow, green, pink), size, expected) # top/left
|
|
self._orientation_test(2, (yellow, blue, pink, green), size, expected) # top/right
|
|
self._orientation_test(3, (pink, green, yellow, blue), size, expected) # bottom/right
|
|
self._orientation_test(4, (green, pink, blue, yellow), size, expected) # bottom/left
|
|
self._orientation_test(5, (blue, green, yellow, pink), size, expected) # left/top
|
|
self._orientation_test(6, (yellow, pink, blue, green), size, expected) # right/top
|
|
self._orientation_test(7, (pink, yellow, green, blue), size, expected) # right/bottom
|
|
self._orientation_test(8, (green, blue, pink, yellow), size, expected) # left/bottom
|
|
|
|
def test_03_image_fix_orientation_exif(self):
|
|
"""Test that a jpg image with exif orientation tag gets rotated"""
|
|
image = img_open(self.img_exif_jpg)
|
|
self.assertEqual(image.size, (6,3))
|
|
image = tools.image_fix_orientation(image)
|
|
self.assertEqual(image.size, (3,6))
|
|
|
|
def test_10_image_process_source(self):
|
|
"""Test the source parameter of image_process."""
|
|
self.assertFalse(tools.image_process(False), "return False if source is falsy")
|
|
self.assertEqual(tools.image_process(self.img_svg), self.img_svg, "return source if format is SVG")
|
|
|
|
# in the following tests, pass `quality` to force the processing
|
|
with self.assertRaises(UserError, msg="This file could not be decoded as an image file. Please try with a different file."):
|
|
tools.image_process(b'oazdazpodazdpokd', quality=95)
|
|
|
|
image = img_open(tools.image_process(self.img_1920x1080_jpeg, quality=95))
|
|
self.assertEqual(image.size, (1920, 1080), "OK return the image")
|
|
|
|
def test_11_image_process_size(self):
|
|
"""Test the size parameter of image_process."""
|
|
|
|
# Format of `tests`: (original image, size parameter, expected result, text)
|
|
tests = [
|
|
(self.img_1920x1080_jpeg, (192, 108), (192, 108), "resize to given size"),
|
|
(self.img_1920x1080_jpeg, (1920, 1080), (1920, 1080), "same size, no change"),
|
|
(self.img_1920x1080_jpeg, (192, None), (192, 108), "set height from ratio"),
|
|
(self.img_1920x1080_jpeg, (0, 108), (192, 108), "set width from ratio"),
|
|
(self.img_1920x1080_jpeg, (192, 200), (192, 108), "adapt to width"),
|
|
(self.img_1920x1080_jpeg, (400, 108), (192, 108), "adapt to height"),
|
|
(self.img_1920x1080_jpeg, (3000, 2000), (1920, 1080), "don't resize above original, both set"),
|
|
(self.img_1920x1080_jpeg, (3000, False), (1920, 1080), "don't resize above original, width set"),
|
|
(self.img_1920x1080_jpeg, (None, 2000), (1920, 1080), "don't resize above original, height set"),
|
|
(self.img_1080x1920_png, (3000, 192), (108, 192), "vertical image, resize if below"),
|
|
]
|
|
|
|
count = 0
|
|
for test in tests:
|
|
image = img_open(tools.image_process(test[0], size=test[1]))
|
|
self.assertEqual(image.size, test[2], test[3])
|
|
count = count + 1
|
|
self.assertEqual(count, 10, "ensure the loop is ran")
|
|
|
|
def test_12_image_process_verify_resolution(self):
|
|
"""Test the verify_resolution parameter of image_process."""
|
|
res = tools.image_process(self.img_1920x1080_jpeg, verify_resolution=True)
|
|
self.assertNotEqual(res, False, "size ok")
|
|
image_excessive = tools.image_apply_opt(Image.new('RGB', (50001, 1000)), 'PNG')
|
|
with self.assertRaises(UserError, msg="size excessive"):
|
|
tools.image_process(image_excessive, verify_resolution=True)
|
|
|
|
def test_13_image_process_quality(self):
|
|
"""Test the quality parameter of image_process."""
|
|
|
|
# CASE: PNG RGBA doesn't apply quality, just optimize
|
|
image = tools.image_apply_opt(Image.new('RGBA', (1080, 1920)), 'PNG')
|
|
res = tools.image_process(image)
|
|
self.assertLessEqual(len(res), len(image))
|
|
|
|
# CASE: PNG RGB doesn't apply quality, just optimize
|
|
image = tools.image_apply_opt(Image.new('P', (1080, 1920)), 'PNG')
|
|
res = tools.image_process(image)
|
|
self.assertLessEqual(len(res), len(image))
|
|
|
|
# CASE: JPEG optimize + reduced quality
|
|
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)
|
|
self.assertLessEqual(len(res), len(image))
|
|
|
|
def test_14_image_process_crop(self):
|
|
"""Test the crop parameter of image_process."""
|
|
|
|
# Optimized PNG use palette, getpixel below will return palette value.
|
|
fill = 0
|
|
bg = 1
|
|
|
|
# Format of `tests`: (original base64 image, size parameter, crop parameter, res size, res color (top, bottom, left, right), text)
|
|
tests = [
|
|
(self.img_1920x1080_png, None, None, (1920, 1080), (fill, fill, bg, bg), "horizontal, verify initial"),
|
|
(self.img_1920x1080_png, (2000, 2000), 'center', (1080, 1080), (fill, fill, fill, fill), "horizontal, crop biggest possible"),
|
|
(self.img_1920x1080_png, (2000, 4000), 'center', (540, 1080), (fill, fill, fill, fill), "horizontal, size vertical, limit height"),
|
|
(self.img_1920x1080_png, (4000, 2000), 'center', (1920, 960), (fill, fill, bg, bg), "horizontal, size horizontal, limit width"),
|
|
(self.img_1920x1080_png, (512, 512), 'center', (512, 512), (fill, fill, fill, fill), "horizontal, type center"),
|
|
(self.img_1920x1080_png, (512, 512), 'top', (512, 512), (fill, fill, fill, fill), "horizontal, type top"),
|
|
(self.img_1920x1080_png, (512, 512), 'bottom', (512, 512), (fill, fill, fill, fill), "horizontal, type bottom"),
|
|
(self.img_1920x1080_png, (512, 512), 'wrong', (512, 512), (fill, fill, fill, fill), "horizontal, wrong crop value, use center"),
|
|
(self.img_1920x1080_png, (192, 0), None, (192, 108), (fill, fill, bg, bg), "horizontal, not cropped, just do resize"),
|
|
|
|
(self.img_1080x1920_png, None, None, (1080, 1920), (bg, bg, fill, fill), "vertical, verify initial"),
|
|
(self.img_1080x1920_png, (2000, 2000), 'center', (1080, 1080), (fill, fill, fill, fill), "vertical, crop biggest possible"),
|
|
(self.img_1080x1920_png, (2000, 4000), 'center', (960, 1920), (bg, bg, fill, fill), "vertical, size vertical, limit height"),
|
|
(self.img_1080x1920_png, (4000, 2000), 'center', (1080, 540), (fill, fill, fill, fill), "vertical, size horizontal, limit width"),
|
|
(self.img_1080x1920_png, (512, 512), 'center', (512, 512), (fill, fill, fill, fill), "vertical, type center"),
|
|
(self.img_1080x1920_png, (512, 512), 'top', (512, 512), (bg, fill, fill, fill), "vertical, type top"),
|
|
(self.img_1080x1920_png, (512, 512), 'bottom', (512, 512), (fill, bg, fill, fill), "vertical, type bottom"),
|
|
(self.img_1080x1920_png, (512, 512), 'wrong', (512, 512), (fill, fill, fill, fill), "vertical, wrong crop value, use center"),
|
|
(self.img_1080x1920_png, (108, 0), None, (108, 192), (bg, bg, fill, fill), "vertical, not cropped, just do resize"),
|
|
]
|
|
|
|
count = 0
|
|
for test in tests:
|
|
count = count + 1
|
|
# process the image, pass quality to make sure the result is palette
|
|
image = img_open(tools.image_process(test[0], size=test[1], crop=test[2], quality=95))
|
|
# verify size
|
|
self.assertEqual(image.size, test[3], "%s - correct size" % test[5])
|
|
|
|
half_width, half_height = image.size[0] / 2, image.size[1] / 2
|
|
top, bottom, left, right = 0, image.size[1] - 1, 0, image.size[0] - 1
|
|
# verify top
|
|
px = (half_width, top)
|
|
self.assertEqual(image.getpixel(px), test[4][0], "%s - color top (%s, %s)" % (test[5], px[0], px[1]))
|
|
# verify bottom
|
|
px = (half_width, bottom)
|
|
self.assertEqual(image.getpixel(px), test[4][1], "%s - color bottom (%s, %s)" % (test[5], px[0], px[1]))
|
|
# verify left
|
|
px = (left, half_height)
|
|
self.assertEqual(image.getpixel(px), test[4][2], "%s - color left (%s, %s)" % (test[5], px[0], px[1]))
|
|
# verify right
|
|
px = (right, half_height)
|
|
self.assertEqual(image.getpixel(px), test[4][3], "%s - color right (%s, %s)" % (test[5], px[0], px[1]))
|
|
|
|
self.assertEqual(count, 2 * 9, "ensure the loop is ran")
|
|
|
|
def test_15_image_process_colorize(self):
|
|
"""Test the colorize parameter of image_process."""
|
|
|
|
# verify initial condition
|
|
image_rgba = Image.new('RGBA', (1, 1))
|
|
self.assertEqual(image_rgba.mode, 'RGBA')
|
|
self.assertEqual(image_rgba.getpixel((0, 0)), (0, 0, 0, 0))
|
|
rgba = tools.image_apply_opt(image_rgba, 'PNG')
|
|
|
|
# CASE: color random, color has changed
|
|
image = img_open(tools.image_process(rgba, colorize=True))
|
|
self.assertEqual(image.mode, 'RGB')
|
|
self.assertNotEqual(image.getpixel((0, 0)), (0, 0, 0))
|
|
|
|
def test_16_image_process_format(self):
|
|
"""Test the format parameter of image_process."""
|
|
|
|
image = img_open(tools.image_process(self.img_1920x1080_jpeg, output_format='PNG'))
|
|
self.assertEqual(image.format, 'PNG', "change format to PNG")
|
|
|
|
image = img_open(tools.image_process(self.img_1x1_png, output_format='JpEg'))
|
|
self.assertEqual(image.format, 'JPEG', "change format to JPEG (case insensitive)")
|
|
|
|
image = img_open(tools.image_process(self.img_1920x1080_jpeg, output_format='BMP'))
|
|
self.assertEqual(image.format, 'PNG', "change format to BMP converted to PNG")
|
|
|
|
image_1080_1920_rgba = tools.image_apply_opt(Image.new('RGBA', (108, 192)), 'PNG')
|
|
image = img_open(tools.image_process(image_1080_1920_rgba, output_format='jpeg'))
|
|
self.assertEqual(image.format, 'JPEG', "change format PNG with RGBA to JPEG")
|
|
|
|
# pass quality to force the image to be processed
|
|
image_1080_1920_tiff = tools.image_apply_opt(Image.new('RGB', (108, 192)), 'TIFF')
|
|
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'))
|
|
|
|
def test_21_image_guess_size_from_field_name(self):
|
|
f = tools.image_guess_size_from_field_name
|
|
# Test case: empty field_name input
|
|
self.assertEqual(f(''), (0, 0))
|
|
# Test case: custom field_name input
|
|
self.assertEqual(f('custom_field'), (0, 0))
|
|
# Test case: field_name input that starts with 'x_'
|
|
self.assertEqual(f('x_field'), (0, 0))
|
|
# Test case: field_name input that starts with 'x_' and ends with a number less than 16
|
|
self.assertEqual(f('x_studio_image_1'), (0, 0))
|
|
# Test case: field_name input that starts with 'x_' and ends with a number greater than 16
|
|
self.assertEqual(f('x_studio_image_32'), (0, 0))
|
|
# Test case: field_name input that has a suffix less than 16
|
|
self.assertEqual(f('image_15'), (0, 0))
|
|
# Test case: field_name input that has a suffix equal to 16
|
|
self.assertEqual(f('image_16'), (16, 16))
|
|
# Test case: field_name input that has a suffix greater than 16
|
|
self.assertEqual(f('image_32'), (32, 32))
|
|
# Test case: field_name input that has a suffix with 2 numbers
|
|
self.assertEqual(f('image_1920_1080'), (1080, 1080))
|
|
# Test case: field_name input that has a float as suffix
|
|
self.assertEqual(f('image_32.5'), (0, 0))
|
|
# Test case: field_name input that has a suffix greater than 16 but no underscore
|
|
self.assertEqual(f('image32'), (0, 0))
|
|
|
|
def _assertAlmostEqualSequence(self, rgb1, rgb2, delta=10):
|
|
self.assertEqual(len(rgb1), len(rgb2))
|
|
for index, t in enumerate(zip(rgb1, rgb2)):
|
|
self.assertAlmostEqual(t[0], t[1], delta=delta, msg="%s vs %s at %d" % (rgb1, rgb2, index))
|
|
|
|
def _get_exif_colored_square(self, orientation, colors, size):
|
|
image = Image.new('RGB', (size, size), color=self.bg_color)
|
|
draw = ImageDraw.Draw(image)
|
|
# Paint the colors on the 4 corners, to be able to test which colors
|
|
# move on which corners.
|
|
draw.rectangle(xy=[(0, 0), (size // 2, size // 2)], fill=colors[0]) # top/left
|
|
draw.rectangle(xy=[(size // 2, 0), (size, size // 2)], fill=colors[1]) # top/right
|
|
draw.rectangle(xy=[(0, size // 2), (size // 2, size)], fill=colors[2]) # bottom/left
|
|
draw.rectangle(xy=[(size // 2, size // 2), (size, size)], fill=colors[3]) # bottom/right
|
|
# Set the proper exif tag based on orientation params.
|
|
exif = b'Exif\x00\x00II*\x00\x08\x00\x00\x00\x01\x00\x12\x01\x03\x00\x01\x00\x00\x00' + bytes([orientation]) + b'\x00\x00\x00\x00\x00\x00\x00'
|
|
# The image image is saved with the exif tag.
|
|
return tools.image_apply_opt(image, 'JPEG', exif=exif)
|
|
|
|
def _orientation_test(self, orientation, colors, size, expected):
|
|
# Generate the test image based on orientation and order of colors.
|
|
image = self._get_exif_colored_square(orientation, colors, size)
|
|
# The image is read again now that it has orientation added.
|
|
fixed_image = tools.image_fix_orientation(img_open(image))
|
|
# Ensure colors are in the right order (blue, yellow, green, pink).
|
|
self._assertAlmostEqualSequence(fixed_image.getpixel((0, 0)), expected[0]) # top/left
|
|
self._assertAlmostEqualSequence(fixed_image.getpixel((size - 1, 0)), expected[1]) # top/right
|
|
self._assertAlmostEqualSequence(fixed_image.getpixel((0, size - 1)), expected[2]) # bottom/left
|
|
self._assertAlmostEqualSequence(fixed_image.getpixel((size - 1, size - 1)), expected[3]) # bottom/right
|
|
|
|
def test_ptype_image_to_jpeg(self):
|
|
"""converts to RGB when saving as JPEG"""
|
|
image1 = Image.new('P', (1, 1), color='red')
|
|
image2 = Image.new('RGB', (1, 1), color='red')
|
|
self.assertEqual(tools.image.image_apply_opt(image1, 'JPEG'), tools.image.image_apply_opt(image2, 'JPEG'))
|