def test_16_image_process_format(self): """Test the format parameter of image_process.""" image = tools.base64_to_image( tools.image_process(self.base64_1920x1080_jpeg, output_format='PNG')) self.assertEqual(image.format, 'PNG', "change format to PNG") image = tools.base64_to_image( tools.image_process(self.base64_1x1_png, output_format='JpEg')) self.assertEqual(image.format, 'JPEG', "change format to JPEG (case insensitive)") image = tools.base64_to_image( tools.image_process(self.base64_1920x1080_jpeg, output_format='BMP')) self.assertEqual(image.format, 'PNG', "change format to BMP converted to PNG") self.base64_image_1080_1920_rgba = tools.image_to_base64( Image.new('RGBA', (108, 192)), 'PNG') image = tools.base64_to_image( tools.image_process(self.base64_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 self.base64_image_1080_1920_tiff = tools.image_to_base64( Image.new('RGB', (108, 192)), 'TIFF') image = tools.base64_to_image( tools.image_process(self.base64_image_1080_1920_tiff, quality=95)) self.assertEqual(image.format, 'JPEG', "unsupported format to JPEG")
def test_00_base64_to_image(self): """Test that base64 is correctly opened as a PIL image.""" image = tools.base64_to_image(self.base64_1x1_png) self.assertEqual(type(image), PngImagePlugin.PngImageFile, "base64 as bytes, correct format") self.assertEqual(image.size, (1, 1), "base64 as bytes, correct size") image = tools.base64_to_image(self.base64_1x1_png.decode('ASCII')) self.assertEqual(type(image), PngImagePlugin.PngImageFile, "base64 as string, correct format") self.assertEqual(image.size, (1, 1), "base64 as string, 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_11_image_process_size(self): """Test the size parameter of image_process.""" # Format of `tests`: (original base64 image, size parameter, expected result, text) tests = [ (self.base64_1920x1080_jpeg, (192, 108), (192, 108), "resize to given size"), (self.base64_1920x1080_jpeg, (1920, 1080), (1920, 1080), "same size, no change"), (self.base64_1920x1080_jpeg, (192, None), (192, 108), "set height from ratio"), (self.base64_1920x1080_jpeg, (0, 108), (192, 108), "set width from ratio"), (self.base64_1920x1080_jpeg, (192, 200), (192, 108), "adapt to width"), (self.base64_1920x1080_jpeg, (400, 108), (192, 108), "adapt to height"), (self.base64_1920x1080_jpeg, (3000, 2000), (1920, 1080), "don't resize above original, both set"), (self.base64_1920x1080_jpeg, (3000, False), (1920, 1080), "don't resize above original, width set"), (self.base64_1920x1080_jpeg, (None, 2000), (1920, 1080), "don't resize above original, height set"), (self.base64_1080x1920_png, (3000, 192), (108, 192), "vertical image, resize if below"), ] count = 0 for test in tests: image = tools.base64_to_image( 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_10_image_process_base64_source(self): """Test the base64_source parameter of image_process.""" wrong_base64 = b'oazdazpodazdpok' self.assertFalse(tools.image_process(False), "return False if base64_source is falsy") self.assertEqual(tools.image_process(self.base64_svg), self.base64_svg, "return base64_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(wrong_base64, quality=95) 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 = tools.base64_to_image( tools.image_process(self.base64_1920x1080_jpeg, quality=95)) self.assertEqual(image.size, (1920, 1080), "OK return the image") # test that nothing happens if no operation has been requested # (otherwise those would raise because of wrong base64) self.assertEqual(tools.image_process(wrong_base64), wrong_base64) self.assertEqual(tools.image_process(wrong_base64, size=False), wrong_base64)
def _compute_image_size(self): for attachment in self: try: image = tools.base64_to_image(attachment.datas) attachment.image_width = image.width attachment.image_height = image.height except Exception: attachment.image_width = 0 attachment.image_height = 0
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)) base64_rgba = tools.image_to_base64(image_rgba, 'PNG') # CASE: color random, color has changed image = tools.base64_to_image( tools.image_process(base64_rgba, colorize=True)) self.assertEqual(image.mode, 'RGB') self.assertNotEqual(image.getpixel((0, 0)), (0, 0, 0))
def _orientation_test(self, orientation, colors, size, expected): # Generate the test image based on orientation and order of colors. b64_image = self._get_exif_colored_square_b64(orientation, colors, size) # The image is read again now that it has orientation added. fixed_image = tools.image_fix_orientation( tools.base64_to_image(b64_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_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.base64_1920x1080_png, None, None, (1920, 1080), (fill, fill, bg, bg), "horizontal, verify initial"), (self.base64_1920x1080_png, (2000, 2000), 'center', (1080, 1080), (fill, fill, fill, fill), "horizontal, crop biggest possible"), (self.base64_1920x1080_png, (2000, 4000), 'center', (540, 1080), (fill, fill, fill, fill), "horizontal, size vertical, limit height"), (self.base64_1920x1080_png, (4000, 2000), 'center', (1920, 960), (fill, fill, bg, bg), "horizontal, size horizontal, limit width"), (self.base64_1920x1080_png, (512, 512), 'center', (512, 512), (fill, fill, fill, fill), "horizontal, type center"), (self.base64_1920x1080_png, (512, 512), 'top', (512, 512), (fill, fill, fill, fill), "horizontal, type top"), (self.base64_1920x1080_png, (512, 512), 'bottom', (512, 512), (fill, fill, fill, fill), "horizontal, type bottom"), (self.base64_1920x1080_png, (512, 512), 'wrong', (512, 512), (fill, fill, fill, fill), "horizontal, wrong crop value, use center"), (self.base64_1920x1080_png, (192, 0), None, (192, 108), (fill, fill, bg, bg), "horizontal, not cropped, just do resize"), (self.base64_1080x1920_png, None, None, (1080, 1920), (bg, bg, fill, fill), "vertical, verify initial"), (self.base64_1080x1920_png, (2000, 2000), 'center', (1080, 1080), (fill, fill, fill, fill), "vertical, crop biggest possible"), (self.base64_1080x1920_png, (2000, 4000), 'center', (960, 1920), (bg, bg, fill, fill), "vertical, size vertical, limit height"), (self.base64_1080x1920_png, (4000, 2000), 'center', (1080, 540), (fill, fill, fill, fill), "vertical, size horizontal, limit width"), (self.base64_1080x1920_png, (512, 512), 'center', (512, 512), (fill, fill, fill, fill), "vertical, type center"), (self.base64_1080x1920_png, (512, 512), 'top', (512, 512), (bg, fill, fill, fill), "vertical, type top"), (self.base64_1080x1920_png, (512, 512), 'bottom', (512, 512), (fill, bg, fill, fill), "vertical, type bottom"), (self.base64_1080x1920_png, (512, 512), 'wrong', (512, 512), (fill, fill, fill, fill), "vertical, wrong crop value, use center"), (self.base64_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 = tools.base64_to_image( 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 _parse_logo_colors(self, logo=None, white_threshold=225): """ Identifies dominant colors First resizes the original image to improve performance, then discards transparent colors and white-ish colors, then calls the averaging method twice to evaluate both primary and secondary colors. :param logo: alternate logo to process :param white_threshold: arbitrary value defining the maximum value a color can reach :return colors: hex values of primary and secondary colors """ self.ensure_one() logo = logo or self.logo if not logo: return False, False # The "===" gives different base64 encoding a correct padding logo += b'===' if type(logo) == bytes else '===' try: # Catches exceptions caused by logo not being an image image = tools.image_fix_orientation(tools.base64_to_image(logo)) except Exception: return False, False base_w, base_h = image.size w = int(50 * base_w / base_h) h = 50 # Converts to RGBA if no alpha detected image_converted = image.convert( 'RGBA') if 'A' not in image.getbands() else image image_resized = image_converted.resize((w, h)) colors = [] for color in image_resized.getcolors(w * h): if not (color[1][0] > white_threshold and color[1][1] > white_threshold and color[1][2] > white_threshold) and color[1][3] > 0: colors.append(color) if not colors: # May happen when the whole image is white return False, False primary, remaining = tools.average_dominant_color(colors) secondary = tools.average_dominant_color( remaining)[0] if len(remaining) > 0 else primary # Lightness and saturation are calculated here. # - If both colors have a similar lightness, the most colorful becomes primary # - When the difference in lightness is too great, the brightest color becomes primary l_primary = tools.get_lightness(primary) l_secondary = tools.get_lightness(secondary) if (l_primary < 0.2 and l_secondary < 0.2) or (l_primary >= 0.2 and l_secondary >= 0.2): s_primary = tools.get_saturation(primary) s_secondary = tools.get_saturation(secondary) if s_primary < s_secondary: primary, secondary = secondary, primary elif l_secondary > l_primary: primary, secondary = secondary, primary return tools.rgb_to_hex(primary), tools.rgb_to_hex(secondary)