def test_paletted_merge(self): if not hasattr(Image, 'FASTOCTREE'): raise SkipTest() # generate RGBA images with a transparent rectangle in the lower right img1 = ImageSource(Image.new('RGBA', (50, 50), (0, 255, 0, 255))).as_image() draw = ImageDraw.Draw(img1) draw.rectangle((25, 25, 49, 49), fill=(0, 0, 0, 0)) paletted_img = quantize(img1, alpha=True) assert img_has_transparency(paletted_img) assert paletted_img.mode == 'P' rgba_img = Image.new('RGBA', (50, 50), (255, 0, 0, 255)) draw = ImageDraw.Draw(rgba_img) draw.rectangle((25, 25, 49, 49), fill=(0, 0, 0, 0)) img1 = ImageSource(paletted_img) img2 = ImageSource(rgba_img) # generate base image and merge the others above img3 = ImageSource(Image.new('RGBA', (50, 50), (0, 0, 255, 255))) result = merge_images([img3, img1, img2], ImageOptions(transparent=True)) img = result.as_image() assert img.mode == 'RGBA' eq_(img.getpixel((49, 49)), (0, 0, 255, 255)) eq_(img.getpixel((0, 0)), (255, 0, 0, 255))
def test_solid_merge(self): img1 = ImageSource(Image.new('RGB', (10, 10), (255, 0, 255))) img2 = ImageSource(Image.new('RGB', (10, 10), (0, 255, 255))) result = merge_images([img1, img2], ImageOptions(transparent=False)) img = result.as_image() eq_(img.getpixel((0, 0)), (0, 255, 255))
def test_composite_merge(self): # http://stackoverflow.com/questions/3374878 if not hasattr(Image, 'alpha_composite'): raise SkipTest() img1 = Image.new('RGBA', size=(100, 100), color=(255, 0, 0, 255)) draw = ImageDraw.Draw(img1) draw.rectangle((33, 0, 66, 100), fill=(255, 0, 0, 128)) draw.rectangle((67, 0, 100, 100), fill=(255, 0, 0, 0)) img1 = ImageSource(img1) img2 = Image.new('RGBA', size =(100, 100), color=(0, 255, 0, 255)) draw = ImageDraw.Draw(img2) draw.rectangle((0, 33, 100, 66), fill=(0, 255, 0, 128)) draw.rectangle((0, 67, 100, 100), fill=(0, 255, 0, 0)) img2 = ImageSource(img2) result = merge_images([img2, img1], ImageOptions(transparent=True)) img = result.as_image() eq_(img.mode, 'RGBA') assert_img_colors_eq(img, [ (1089, (0, 255, 0, 255)), (1089, (255, 255, 255, 0)), (1122, (0, 255, 0, 128)), (1122, (128, 126, 0, 255)), (1122, (255, 0, 0, 128)), (1156, (170, 84, 0, 191)), (3300, (255, 0, 0, 255))])
def test_output_formats_png24(self): img = Image.new('RGBA', (100, 100)) image_opts = PNG_FORMAT.copy() image_opts.colors = 0 # TODO image_opts ir = ImageSource(img, image_opts=image_opts) img = Image.open(ir.as_buffer()) eq_(img.mode, 'RGBA') assert img.getpixel((0, 0)) == (0, 0, 0, 0)
def create_debug_img(size, transparent=True): if transparent: img = Image.new("RGBA", size) else: img = Image.new("RGB", size, ImageColor.getrgb("#EEE")) draw = ImageDraw.Draw(img) draw_pattern(draw, size) return img
def test_merge_rgb_with_transp(self): img1 = ImageSource(Image.new('RGB', (10, 10), (255, 0, 255))) raw = Image.new('RGB', (10, 10), (0, 255, 255)) raw.info = {'transparency': (0, 255, 255)} # make full transparent img2 = ImageSource(raw) result = merge_images([img1, img2], ImageOptions(transparent=False)) img = result.as_image() eq_(img.getpixel((0, 0)), (255, 0, 255))
def merge(self, sources, image_opts, size=None, bbox=None, bbox_srs=None, coverage=None): if not sources: return BlankImageSource(size=size, image_opts=image_opts, cacheable=True) if size is None: size = sources[0].size # load src bands src_img_bands = [] for i, layer_img in enumerate(sources): img = layer_img.as_image() if i not in self.max_band: # do not split img if not requested by any op src_img_bands.append(None) continue if self.max_band[i] == 3 and img.mode != 'RGBA': # convert to RGBA if band idx 3 is requestd (e.g. P or RGB src) img = img.convert('RGBA') elif img.mode == 'P': img = img.convert('RGB') src_img_bands.append(img.split()) tmp_mode = self.mode if tmp_mode == 'RGBA': result_bands = [None, None, None, None] elif tmp_mode == 'RGB': result_bands = [None, None, None] elif tmp_mode == 'L': result_bands = [None] else: raise ValueError("unsupported destination mode %s", image_opts.mode) for op in self.ops: chan = src_img_bands[op.src_img][op.src_band] if op.factor != 1.0: chan = ImageMath.eval("convert(int(float(a) * %f), 'L')" % op.factor, a=chan) if result_bands[op.dst_band] is None: result_bands[op.dst_band] = chan else: result_bands[op.dst_band] = ImageChops.add( result_bands[op.dst_band], chan, ) else: result_bands[op.dst_band] = chan for i, b in enumerate(result_bands): if b is None: # band not set b = Image.new("L", size, 255 if i == 3 else 0) result_bands[i] = b result = Image.merge(tmp_mode, result_bands) return ImageSource(result, size=size, image_opts=image_opts)
def test_opacity_merge_mixed_modes(self): img1 = ImageSource(Image.new('RGBA', (10, 10), (255, 0, 255, 255))) img2 = ImageSource(Image.new('RGB', (10, 10), (0, 255, 255)).convert('P'), image_opts=ImageOptions(opacity=0.5)) result = merge_images([img1, img2], ImageOptions(transparent=True)) img = result.as_image() assert_img_colors_eq(img, [ (10*10, (127, 127, 255, 255)), ])
def test_merge_L(self): img1 = ImageSource(Image.new('RGBA', (10, 10), (255, 0, 255, 255))) img2 = ImageSource(Image.new('L', (10, 10), 100)) # img2 overlays img1 result = merge_images([img1, img2], ImageOptions(transparent=True)) img = result.as_image() assert_img_colors_eq(img, [ (10*10, (100, 100, 100, 255)), ])
def test_save_with_unsupported_transparency(self): # check if encoding of non-RGB image with tuple as transparency # works. workaround for Pillow #2633 img = Image.new('P', (100, 100)) img.info['transparency'] = (0, 0, 0) image_opts = PNG_FORMAT.copy() ir = ImageSource(img, image_opts=image_opts) img = Image.open(ir.as_buffer()) eq_(img.mode, 'P')
def create_image(size, color=None, mode=None): if color is not None: if isinstance(color, string_type): if mode is None: mode = 'RGB' img = Image.new(mode, size, color=color) else: if mode is None: mode = 'RGBA' if len(color) == 4 else 'RGB' img = Image.new(mode, size, color=tuple(color)) else: img = create_debug_img(size) return img
def test_concatenation(self): legends = [] img_1 = Image.new(mode='RGBA', size=(30,10), color="red") img_2 = Image.new(mode='RGBA', size=(10,10), color="black") img_3 = Image.new(mode='RGBA', size=(50,80), color="blue") legends.append(ImageSource(img_1)) legends.append(ImageSource(img_2)) legends.append(ImageSource(img_3)) source = concat_legends(legends) src_img = source.as_image() assert src_img.getpixel((0,90)) == (255,0,0,255) assert src_img.getpixel((0,80)) == (0,0,0,255) assert src_img.getpixel((0,0)) == (0,0,255,255) assert src_img.getpixel((49,99)) == (255,255,255,0) assert is_png(source.as_buffer())
def test_mask_partial_image_bgcolor(self): img = ImageSource(Image.new('RGB', (100, 100), color=(100, 0, 200)), image_opts=ImageOptions(bgcolor=(200, 30, 120))) result = mask_image_source_from_coverage(img, [0, 0, 10, 10], SRS(4326), coverage([5, 5, 30, 30])) eq_(result.as_image().getcolors(), [(7500, (200, 30, 120)), (2500, (100, 0, 200))])
def test_solid_paletted_image(self): img = Image.new('P', (100, 100), color=20) palette = [] for i in range(256): palette.extend((i, i//2, i%3)) img.putpalette(palette) eq_(is_single_color_image(img), (20, 10, 2))
def create_image(size, image_opts=None): """ Create a new image that is compatible with the given `image_opts`. Takes into account mode, transparent, bgcolor. """ from mapproxy.compat.image import Image, ImageColor if image_opts is None: mode = 'RGB' bgcolor = (255, 255, 255) else: mode = image_opts.mode if mode in (None, 'P'): if image_opts.transparent: mode = 'RGBA' else: mode = 'RGB' bgcolor = image_opts.bgcolor or (255, 255, 255) if isinstance(bgcolor, string_type): bgcolor = ImageColor.getrgb(bgcolor) if image_opts.transparent and len(bgcolor) == 3: bgcolor = bgcolor + (0, ) if image_opts.mode == 'I': bgcolor = bgcolor[0] return Image.new(mode, size, bgcolor)
def assert_image_mode(img, mode): pos = img.tell() try: img = Image.open(img) eq_(img.mode, mode) finally: img.seek(pos)
def test_layers_with_opacity(self): # overlay with opacity -> request should not be combined common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' '&WIDTH=200') img_bg = create_tmp_image((200, 200), color=(0, 0, 0)) img_fg = create_tmp_image((200, 200), color=(255, 0, 128)) expected_req = [ ({'path': '/service_a' + common_params + '&layers=a_one'}, {'body': img_bg, 'headers': {'content-type': 'image/png'}}), ({'path': '/service_a' + common_params + '&layers=a_two'}, {'body': img_fg, 'headers': {'content-type': 'image/png'}}), ] with mock_httpd(('localhost', 42423), expected_req): self.common_map_req.params.layers = 'opacity_base,opacity_overlay' resp = self.app.get(self.common_map_req) eq_(resp.content_type, 'image/png') data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) eq_(img.getcolors()[0], ((200*200),(127, 0, 64)))
def test_mask_partial_image_transparent(self): img = ImageSource(Image.new('RGB', (100, 100), color=(100, 0, 200)), image_opts=ImageOptions(transparent=True)) result = mask_image_source_from_coverage(img, [0, 0, 10, 10], SRS(4326), coverage([5, 5, 30, 30])) assert_img_colors_eq(result.as_image().getcolors(), [(7500, (255, 255, 255, 0)), (2500, (100, 0, 200, 255))])
def test_one_point(self): img = Image.new('RGB', (100, 100), color='#ff0000') draw = ImageDraw.Draw(img) draw.point((99, 99)) del draw assert not is_single_color_image(img)
def image_mask_from_geom(size, bbox, polygons): mask = Image.new('L', size, 255) if len(polygons) == 0: return mask transf = make_lin_transf(bbox, (0, 0) + size) # use negative ~.1 pixel buffer buffer = -0.1 * min((bbox[2] - bbox[0]) / size[0], (bbox[3] - bbox[1]) / size[1]) draw = ImageDraw.Draw(mask) def draw_polygon(p): draw.polygon([transf(coord) for coord in p.exterior.coords], fill=0) for ring in p.interiors: draw.polygon([transf(coord) for coord in ring.coords], fill=255) for p in polygons: # little bit smaller polygon does not include touched pixels outside coverage buffered = p.buffer(buffer, resolution=1, join_style=2) if buffered.is_empty: # can be empty after negative buffer continue if buffered.type == 'MultiPolygon': # negative buffer can turn polygon into multipolygon for p in buffered: draw_polygon(p) else: draw_polygon(buffered) return mask
def test_composite_merge_opacity(self): if not hasattr(Image, 'alpha_composite'): raise SkipTest() bg = Image.new('RGBA', size=(100, 100), color=(255, 0, 255, 255)) bg = ImageSource(bg) fg = Image.new('RGBA', size =(100, 100), color=(0, 0, 0, 0)) draw = ImageDraw.Draw(fg) draw.rectangle((10, 10, 89, 89), fill=(0, 255, 255, 255)) fg = ImageSource(fg, image_opts=ImageOptions(opacity=0.5)) result = merge_images([bg, fg], ImageOptions(transparent=True)) img = result.as_image() eq_(img.mode, 'RGBA') assert_img_colors_eq(img, [ (3600, (255, 0, 255, 255)), (6400, (128, 127, 255, 255))])
def test_ul(self): font = ImageFont.load_default() td = TextDraw('Hello', font) img = Image.new('RGB', (100, 100)) draw = ImageDraw.Draw(img) total_box, boxes = td.text_boxes(draw, (100, 100)) eq_(total_box, boxes[0]) eq_(len(boxes), 1)
def test_get_map(self): resp = self.app.get(self.common_map_req) eq_(resp.content_type, 'image/png') data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) img = img.convert('RGB') eq_(img.getcolors(), [(200*200, (255, 0, 0))])
def test_get_tile_uncached(self): resp = self.app.get('/tms/1.0.0/wms_cache/0/0/0.jpeg') eq_(resp.content_type, 'image/png') data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) eq_(img.mode, 'RGBA') eq_(img.getcolors(), [(256*256, (255, 255, 255, 0))])
def test_multiline_ul(self): font = ImageFont.load_default() td = TextDraw('Hello\nWorld', font) img = Image.new('RGB', (100, 100)) draw = ImageDraw.Draw(img) total_box, boxes = td.text_boxes(draw, (100, 100)) eq_(total_box, (5, 5, 35, 30)) eq_(boxes, [(5, 5, 35, 16), (5, 19, 35, 30)])
def check_placement(self, x, y): font = ImageFont.load_default() td = TextDraw('Hello\nWorld\n%s %s' % (x, y), font, placement=x+y, padding=5, linespacing=2) img = Image.new('RGB', (100, 100)) draw = ImageDraw.Draw(img) td.draw(draw, img.size) img.show()
def test_multiline_lr(self): font = ImageFont.load_default() td = TextDraw('Hello\nWorld', font, placement='lr') img = Image.new('RGB', (100, 100)) draw = ImageDraw.Draw(img) total_box, boxes = td.text_boxes(draw, (100, 100)) eq_(total_box, (65, 70, 95, 95)) eq_(boxes, [(65, 70, 95, 81), (65, 84, 95, 95)])
def test_multiline_center(self): font = ImageFont.load_default() td = TextDraw('Hello\nWorld', font, placement='cc') img = Image.new('RGB', (100, 100)) draw = ImageDraw.Draw(img) total_box, boxes = td.text_boxes(draw, (100, 100)) eq_(total_box, (35, 38, 65, 63)) eq_(boxes, [(35, 38, 65, 49), (35, 52, 65, 63)])
def test_returns_imagesource(self): img_src1 = ImageSource(Image.new('RGBA', (100, 100))) env = make_wsgi_env('', extra_environ={}) img_src2 = self.server.decorate_img( img_src1, 'wms.map', ['layer1'], env, self.query_extent ) assert isinstance(img_src2, ImageSource)
def test_merge_single_coverage(self): merger = LayerMerger() merger.add(ImageSource(Image.new('RGB', (10, 10), (255, 255, 255))), self.coverage1) result = merger.merge(image_opts=ImageOptions(transparent=True), bbox=(5, 0, 15, 10), bbox_srs=3857) img = result.as_image() eq_(img.mode, 'RGBA') eq_(img.getpixel((4, 0)), (255, 255, 255, 255)) eq_(img.getpixel((6, 0)), (255, 255, 255, 0))
def return_new_imagesource_callback(self, img_src, service, layers, **kw): new_img_src = ImageSource(Image.new('RGBA', (100, 100))) self.new_img_src = new_img_src return new_img_src
def test_original_imagesource_returned_when_no_callback(self): img_src1 = ImageSource(Image.new('RGBA', (100, 100))) env = make_wsgi_env('', extra_environ={}) img_src2 = self.server.decorate_img(img_src1, 'wms.map', ['layer1'], env, self.query_extent) eq_(img_src1, img_src2)
def test_solid_w_alpha(self): img = Image.new('RGBA', (100, 100), color='#ff0102') eq_(is_single_color_image(img), (255, 1, 2, 255))
def check(self, format, expected_format): buf = BytesIO() Image.new('RGB', (100, 100)).save(buf, format) eq_(peek_image_format(buf), expected_format)
def test_solid_w_alpha(self): img = Image.new("RGBA", (100, 100), color="#ff0102") assert is_single_color_image(img) == (255, 1, 2, 255)
def test_output_formats(self): img = Image.new('RGB', (100, 100)) for format in ['png', 'gif', 'tiff', 'jpeg', 'GeoTIFF', 'bmp']: ir = ImageSource(img, (100, 100), image_opts=ImageOptions(format=format)) yield check_format, ir.as_buffer(), format
def test_rgb(self): img = Image.new("RGB", (10, 10)) assert not img_has_transparency(img) img = quantize(img, alpha=False) assert not img_has_transparency(img)
def merge(self, image_opts, size=None, bbox=None, bbox_srs=None, coverage=None): """ Merge the layers. If the format is not 'png' just return the last image. :param format: The image format for the result. :param size: The size for the merged output. :rtype: `ImageSource` """ if not self.layers: return BlankImageSource(size=size, image_opts=image_opts, cacheable=True) if len(self.layers) == 1: layer_img, layer = self.layers[0] layer_opts = layer_img.image_opts if (((layer_opts and not layer_opts.transparent) or image_opts.transparent) and (not size or size == layer_img.size) and (not layer or not layer.coverage or not layer.coverage.clip) and not coverage): # layer is opaque, no need to make transparent or add bgcolor return layer_img if size is None: size = self.layers[0][0].size cacheable = self.cacheable result = create_image(size, image_opts) for layer_img, layer in self.layers: if not layer_img.cacheable: cacheable = False img = layer_img.as_image() layer_image_opts = layer_img.image_opts if layer_image_opts is None: opacity = None else: opacity = layer_image_opts.opacity if layer and layer.coverage and layer.coverage.clip: img = mask_image(img, bbox, bbox_srs, layer.coverage) if result.mode != 'RGBA': merge_composite = False else: merge_composite = has_alpha_composite_support() if 'transparency' in img.info: # non-paletted PNGs can have a fixed transparency value # convert to RGBA to have full alpha img = img.convert('RGBA') if merge_composite: if opacity is not None and opacity < 1.0: # fade-out img to add opacity value img = img.convert("RGBA") alpha = img.split()[3] alpha = ImageChops.multiply( alpha, ImageChops.constant(alpha, int(255 * opacity))) img.putalpha(alpha) if img.mode in ('RGBA', 'P'): # assume paletted images have transparency if img.mode == 'P': img = img.convert('RGBA') result = Image.alpha_composite(result, img) else: result.paste(img, (0, 0)) else: if opacity is not None and opacity < 1.0: img = img.convert(result.mode) result = Image.blend(result, img, layer_image_opts.opacity) elif img.mode in ('RGBA', 'P'): # assume paletted images have transparency if img.mode == 'P': img = img.convert('RGBA') # paste w transparency mask from layer result.paste(img, (0, 0), img) else: result.paste(img, (0, 0)) # apply global clip coverage if coverage: bg = create_image(size, image_opts) mask = mask_image(result, bbox, bbox_srs, coverage) bg.paste(result, (0, 0), mask) result = bg return ImageSource(result, size=size, image_opts=image_opts, cacheable=cacheable)
def _image(self, size): return Image.new('RGB', size, self.color)
def setup(self): self.img0 = ImageSource(Image.new('RGB', (10, 10), (0, 10, 20))) self.img1 = ImageSource(Image.new('RGB', (10, 10), (100, 110, 120))) self.img2 = ImageSource(Image.new('RGB', (10, 10), (200, 210, 220))) self.img3 = ImageSource(Image.new('RGB', (10, 10), (0, 255, 0)))
def test_output_formats_greyscale_png(self): img = Image.new('L', (100, 100)) ir = ImageSource(img, image_opts=PNG_FORMAT) img = Image.open(ir.as_buffer(ImageOptions(colors=256, transparent=True, format='image/png'))) assert img.mode == 'P' assert img.getpixel((0, 0)) == 255
def test_peek_format(self, format, expected_format): buf = BytesIO() Image.new("RGB", (100, 100)).save(buf, format) assert peek_image_format(buf) == expected_format
def test_mask_outside_of_image_bgcolor(self): img = ImageSource(Image.new('RGB', (100, 100), color=(100, 0, 200)), image_opts=ImageOptions(bgcolor=(200, 30, 120))) result = mask_image_source_from_coverage(img, [0, 0, 10, 10], SRS(4326), coverage([20, 20, 30, 30])) assert_img_colors_eq(result.as_image().getcolors(), [((100*100), (200, 30, 120))])
def test_output_formats(self): img = Image.new("RGB", (100, 100)) for format in ["png", "gif", "tiff", "jpeg", "GeoTIFF", "bmp"]: ir = ImageSource(img, (100, 100), image_opts=ImageOptions(format=format)) check_format(ir.as_buffer(), format)
def setup(self): self.img0 = ImageSource(Image.new("RGB", (10, 10), (0, 10, 20))) self.img1 = ImageSource(Image.new("RGB", (10, 10), (100, 110, 120))) self.img2 = ImageSource(Image.new("RGB", (10, 10), (200, 210, 220))) self.img3 = ImageSource(Image.new("RGB", (10, 10), (0, 255, 0))) self.blank = BlankImageSource(size=(10, 10), image_opts=ImageOptions())
def test_solid(self): img = Image.new('RGB', (100, 100), color='#ff0102') eq_(is_single_color_image(img), (255, 1, 2))
def test_returns_imagesource(self): img_src1 = ImageSource(Image.new('RGBA', (100, 100))) env = make_wsgi_env('', extra_environ={}) img_src2 = self.server.decorate_img(img_src1, 'wms.map', ['layer1'], env, self.query_extent) assert isinstance(img_src2, ImageSource)
def new_image(self, size): return Image.new('RGBA', size)
def _make_transp_test_image(self): img = Image.new('RGBA', (50, 50), (130, 140, 120, 100)) draw = ImageDraw.Draw(img) draw.rectangle((10, 10, 39, 39), fill=(130, 150, 120, 120)) return img
def img_from_buf(buf): data = BytesIO(buf) return Image.open(data)
def test_from_image(self): img = Image.new('RGBA', (100, 100)) ir = ImageSource(img, (100, 100), PNG_FORMAT) assert ir.as_image() == img assert is_png(ir.as_buffer())
def test_refresh_tile_source_error_no_stale(self, app, cache_dir): source_request = { "path": r"/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng" "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" "&VERSION=1.1.1&BBOX=-20037508.3428,-20037508.3428,0.0,0.0" "&WIDTH=256" } with tmp_image((256, 256), format="png") as img: expected_req = ( source_request, { "body": img.read(), "headers": { "content-type": "image/png" } }, ) with mock_httpd(("localhost", 42423), [expected_req], bbox_aware_query_comparator=True): resp = app.get("/tiles/wms_cache_png/1/0/0.png") assert resp.content_type == "image/png" img.seek(0) assert resp.body == img.read() resp = app.get("/tiles/wms_cache_png/1/0/0.png") assert resp.content_type == "image/png" img.seek(0) assert resp.body == img.read() # tile is expired after 1 sec, so it will be requested again from mock server time.sleep(1.2) expected_req = ( source_request, { "body": "", "status": 404 }, ) with mock_httpd(("localhost", 42423), [expected_req], bbox_aware_query_comparator=True): resp = app.get("/tiles/wms_cache_png/1/0/0.png") assert resp.content_type == "image/png" # error handler for 404 does not authorise stale tiles, so transparent tile will be rendered resp_img = Image.open(BytesIO(resp.body)) # check response transparency assert resp_img.getbands() == ('R', 'G', 'B', 'A') assert resp_img.getextrema()[3] == (0, 0) expected_req = ( source_request, { "body": "", "status": 405 }, ) with mock_httpd(("localhost", 42423), [expected_req], bbox_aware_query_comparator=True): resp = app.get("/tiles/wms_cache_png/1/0/0.png") assert resp.content_type == "image/png" # error handler for 405 does not authorise stale tiles, so red tile will be rendered resp_img = Image.open(BytesIO(resp.body)) # check response red color assert resp_img.getbands() == ('R', 'G', 'B') assert resp_img.getextrema() == ((255, 255), (0, 0), (0, 0))