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_12_image_process_verify_resolution(self): """Test the verify_resolution parameter of image_process.""" res = tools.image_process(self.base64_1920x1080_jpeg, verify_resolution=True) self.assertNotEqual(res, False, "size ok") base64_image_excessive = tools.image_to_base64( Image.new('RGB', (45001, 1000)), 'PNG') with self.assertRaises(ValueError, msg="size excessive"): tools.image_process(base64_image_excessive, verify_resolution=True)
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 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 attachment_update(self, attachment, name=None, width=0, height=0, quality=0, copy=False, **kwargs): if attachment.type == 'url': raise UserError( _("You cannot change the quality, the width or the name of an URL attachment." )) if copy: attachment = attachment.copy() data = {} if name: data['name'] = name try: data['datas'] = tools.image_process(attachment.datas, size=(width, height), quality=quality) except UserError: pass # not an image attachment.write(data) return attachment._get_media_info()
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_to_base64(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_to_base64(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.base64_1920x1080_jpeg) self.assertLessEqual(len(res), len(self.base64_1920x1080_jpeg)) # CASE: GIF doesn't apply quality, just optimize image = tools.image_to_base64(Image.new('RGB', (1080, 1920)), 'GIF') res = tools.image_process(image) self.assertLessEqual(len(res), len(image))
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 get_user_profile_avatar(self, user_id, field='image_256', width=0, height=0, crop=False, **post): if field not in ('image_128', 'image_256'): return werkzeug.exceptions.Forbidden() can_sudo = self._check_avatar_access(user_id, **post) if can_sudo: status, headers, image_base64 = request.env['ir.http'].sudo( ).binary_content(model='res.users', id=user_id, field=field, default_mimetype='image/png') else: status, headers, image_base64 = request.env[ 'ir.http'].binary_content(model='res.users', id=user_id, field=field, default_mimetype='image/png') if status == 301: return request.env['ir.http']._response_by_status( status, headers, image_base64) if status == 304: return werkzeug.wrappers.Response(status=304) if not image_base64: image_base64 = self._get_default_avatar() if not (width or height): width, height = tools.image_guess_size_from_field_name(field) image_base64 = tools.image_process(image_base64, size=(int(width), int(height)), crop=crop) content = base64.b64decode(image_base64) headers = http.set_safe_image_headers(headers, content) response = request.make_response(content, headers) response.status_code = status return response
def add_data(self, name, data, quality=0, width=0, height=0, res_id=False, res_model='ir.ui.view', filters=False, **kwargs): try: data = tools.image_process(data, size=(width, height), quality=quality, verify_resolution=True) except UserError: pass # not an image attachment = self._attachment_create(name=name, data=data, res_id=res_id, res_model=res_model, filters=filters) return attachment._get_media_info()
def save_unsplash_url(self, unsplashurls=None, **kwargs): """ unsplashurls = { image_id1: { url: image_url, download_url: download_url, }, image_id2: { url: image_url, download_url: download_url, }, ..... } """ def slugify(s): ''' Keeps only alphanumeric characters, hyphens and spaces from a string. The string will also be truncated to 1024 characters max. :param s: the string to be filtered :return: the sanitized string ''' return "".join([c for c in s if c.isalnum() or c in list("- ")])[:1024] if not unsplashurls: return [] uploads = [] Attachments = request.env['ir.attachment'] query = kwargs.get('query', '') query = slugify(query) res_model = kwargs.get('res_model', 'ir.ui.view') if res_model != 'ir.ui.view' and kwargs.get('res_id'): res_id = int(kwargs['res_id']) else: res_id = None for key, value in unsplashurls.items(): url = value.get('url') try: if not url.startswith('https://images.unsplash.com/'): logger.exception("ERROR: Unknown Unsplash URL!: " + url) raise Exception(_("ERROR: Unknown Unsplash URL!")) req = requests.get(url) if req.status_code != requests.codes.ok: continue # get mime-type of image url because unsplash url dosn't contains mime-types in url image_base64 = base64.b64encode(req.content) except requests.exceptions.ConnectionError as e: logger.exception("Connection Error: " + str(e)) continue except requests.exceptions.Timeout as e: logger.exception("Timeout: " + str(e)) continue image_base64 = tools.image_process(image_base64, verify_resolution=True) mimetype = guess_mimetype(base64.b64decode(image_base64)) # append image extension in name query += mimetypes.guess_extension(mimetype) or '' # /unsplash/5gR788gfd/lion url_frags = ['unsplash', key, query] attachment = Attachments.create({ 'name': '_'.join(url_frags), 'url': '/' + '/'.join(url_frags), 'mimetype': mimetype, 'datas': image_base64, 'public': res_model == 'ir.ui.view', 'res_id': res_id, 'res_model': res_model, }) attachment.generate_access_token() uploads.append(attachment._get_media_info()) # Notifies Unsplash from an image download. (API requirement) self._notify_download(value.get('download_url')) return uploads
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 _handle_favicon(self, vals): if 'favicon' in vals: vals['favicon'] = tools.image_process(vals['favicon'], size=(256, 256), crop='center', output_format='ICO')
def resize_to_48(b64source): if not b64source: b64source = base64.b64encode(Binary().placeholder()) return image_process(b64source, size=(48, 48))