def test_fit_in_rectangle_transparent_no_resize(self): # And with a transparent test image, check alpha blending. image = 'pattern-transparent-rgba.png' control = Image.open(get_image_path(image)) # Create checkerboard background. checker_bg = Image.new('RGBA', control.size) checker = Image.open(get_image_path('checkerboard.png')) for x in range(0, control.size[0], checker.size[0]): for y in range(0, control.size[1], checker.size[1]): checker_bg.paste(checker, (x, y)) # Create whhite background. white_bg = Image.new('RGBA', control.size, color='white') assert control.size == white_bg.size width, height = control.size for use_checker_bg in (False, True): prefs['checkered bg for transparent images'] = use_checker_bg expected = Image.alpha_composite( checker_bg if use_checker_bg else white_bg, control) for scaling_quality in range(4): prefs['scaling quality'] = scaling_quality result = image_tools.fit_in_rectangle( image_tools.pil_to_pixbuf(control), width, height, scaling_quality=scaling_quality) msg = ( 'fit_in_rectangle("%s", scaling quality=%d, background=%s) failed; ' 'result %%(diff_type)s differs: %%(diff)s' % (image, scaling_quality, 'checker' if checker_bg else 'white')) self.assertImagesEqual(result, expected, msg=msg)
def test_fit_in_rectangle_transparent_no_resize(self): # And with a transparent test image, check alpha blending. image = 'pattern-transparent-rgba.png' control = Image.open(get_image_path(image)) # Create checkerboard background. checker_bg = Image.new('RGBA', control.size) checker = Image.open(get_image_path('checkerboard.png')) for x in range(0, control.size[0], checker.size[0]): for y in range(0, control.size[1], checker.size[1]): checker_bg.paste(checker, (x, y)) # Create whhite background. white_bg = Image.new('RGBA', control.size, color='white') assert control.size == white_bg.size width, height = control.size for use_checker_bg in (False, True): prefs['checkered bg for transparent images'] = use_checker_bg expected = Image.alpha_composite( checker_bg if use_checker_bg else white_bg, control ) for scaling_quality in range(4): prefs['scaling quality'] = scaling_quality result = image_tools.fit_in_rectangle(image_tools.pil_to_pixbuf(control), width, height, scaling_quality=scaling_quality) msg = ( 'fit_in_rectangle("%s", scaling quality=%d, background=%s) failed; ' 'result %%(diff_type)s differs: %%(diff)s' % (image, scaling_quality, 'checker' if checker_bg else 'white') ) self.assertImagesEqual(result, expected, msg=msg)
def test_pil_to_pixbuf(self): base_im = Image.open(get_image_path('transparent.png')) for _, expected_pixbuf_mode, mode in _IMAGE_MODES: input_im = base_im.convert(mode) pixbuf = image_tools.pil_to_pixbuf(input_im) expected_im = input_im.convert(expected_pixbuf_mode) msg = ('pil_to_pixbuf("%s") failed; ' 'result %%(diff_type)s differs: %%(diff)s' % (mode, )) self.assertImagesEqual(pixbuf, expected_im, msg=msg)
def draw_histogram(pixbuf, height=170, fill=170, text=True): """Draw a histogram from <pixbuf> and return it as another pixbuf. The returned prixbuf will be 262x<height> px. The value of <fill> determines the colour intensity of the filled graphs, valid values are between 0 and 255. If <text> is True a label with the maximum pixel value will be added to one corner. """ im = Image.new('RGB', (258, height - 4), (30, 30, 30)) hist_data = image_tools.pixbuf_to_pil(pixbuf).histogram() maximum = max(hist_data[:768] + [1]) y_scale = float(height - 6) / maximum r = [int(hist_data[n] * y_scale) for n in range(256)] g = [int(hist_data[n] * y_scale) for n in range(256, 512)] b = [int(hist_data[n] * y_scale) for n in range(512, 768)] im_data = im.getdata() # Draw the filling colours for x in range(256): for y in range(1, max(r[x], g[x], b[x]) + 1): r_px = y <= r[x] and fill or 0 g_px = y <= g[x] and fill or 0 b_px = y <= b[x] and fill or 0 im_data.putpixel((x + 1, height - 5 - y), (r_px, g_px, b_px)) # Draw the outlines for x in range(1, 256): for y in range(r[x - 1] + 1, r[x] + 1) + [r[x]] * (r[x] != 0): r_px, g_px, b_px = im_data.getpixel((x + 1, height - 5 - y)) im_data.putpixel((x + 1, height - 5 - y), (255, g_px, b_px)) for y in range(r[x] + 1, r[x - 1] + 1): r_px, g_px, b_px = im_data.getpixel((x, height - 5 - y)) im_data.putpixel((x, height - 5 - y), (255, g_px, b_px)) for y in range(g[x - 1] + 1, g[x] + 1) + [g[x]] * (g[x] != 0): r_px, g_px, b_px = im_data.getpixel((x + 1, height - 5 - y)) im_data.putpixel((x + 1, height - 5 - y), (r_px, 255, b_px)) for y in range(g[x] + 1, g[x - 1] + 1): r_px, g_px, b_px = im_data.getpixel((x, height - 5 - y)) im_data.putpixel((x, height - 5 - y), (r_px, 255, b_px)) for y in range(b[x - 1] + 1, b[x] + 1) + [b[x]] * (b[x] != 0): r_px, g_px, b_px = im_data.getpixel((x + 1, height - 5 - y)) im_data.putpixel((x + 1, height - 5 - y), (r_px, g_px, 255)) for y in range(b[x] + 1, b[x - 1] + 1): r_px, g_px, b_px = im_data.getpixel((x, height - 5 - y)) im_data.putpixel((x, height - 5 - y), (r_px, g_px, 255)) if text: maxstr = 'max: ' + str(maximum) draw = ImageDraw.Draw(im) draw.rectangle((0, 0, len(maxstr) * 6 + 2, 10), fill=(30, 30, 30)) draw.text((2, 0), maxstr, fill=(255, 255, 255)) im = ImageOps.expand(im, 1, (80, 80, 80)) im = ImageOps.expand(im, 1, (0, 0, 0)) return image_tools.pil_to_pixbuf(im)
def draw_histogram(pixbuf, height=170, fill=170, text=True): """Draw a histogram from <pixbuf> and return it as another pixbuf. The returned prixbuf will be 262x<height> px. The value of <fill> determines the colour intensity of the filled graphs, valid values are between 0 and 255. If <text> is True a label with the maximum pixel value will be added to one corner. """ im = Image.new('RGB', (258, height - 4), (30, 30, 30)) hist_data = image_tools.pixbuf_to_pil(pixbuf).histogram() maximum = max(hist_data[:768] + [1]) y_scale = float(height - 6) / maximum r = [int(hist_data[n] * y_scale) for n in xrange(256)] g = [int(hist_data[n] * y_scale) for n in xrange(256, 512)] b = [int(hist_data[n] * y_scale) for n in xrange(512, 768)] im_data = im.getdata() # Draw the filling colours for x in xrange(256): for y in xrange(1, max(r[x], g[x], b[x]) + 1): r_px = y <= r[x] and fill or 0 g_px = y <= g[x] and fill or 0 b_px = y <= b[x] and fill or 0 im_data.putpixel((x + 1, height - 5 - y), (r_px, g_px, b_px)) # Draw the outlines for x in xrange(1, 256): for y in range(r[x-1] + 1, r[x] + 1) + [r[x]] * (r[x] != 0): r_px, g_px, b_px = im_data.getpixel((x + 1, height - 5 - y)) im_data.putpixel((x + 1, height - 5 - y), (255, g_px, b_px)) for y in range(r[x] + 1, r[x-1] + 1): r_px, g_px, b_px = im_data.getpixel((x, height - 5 - y)) im_data.putpixel((x, height - 5 - y), (255, g_px, b_px)) for y in range(g[x-1] + 1, g[x] + 1) + [g[x]] * (g[x] != 0): r_px, g_px, b_px = im_data.getpixel((x + 1, height - 5 - y)) im_data.putpixel((x + 1, height - 5 - y), (r_px, 255, b_px)) for y in range(g[x] + 1, g[x-1] + 1): r_px, g_px, b_px = im_data.getpixel((x, height - 5 - y)) im_data.putpixel((x, height - 5 - y), (r_px, 255, b_px)) for y in range(b[x-1] + 1, b[x] + 1) + [b[x]] * (b[x] != 0): r_px, g_px, b_px = im_data.getpixel((x + 1, height - 5 - y)) im_data.putpixel((x + 1, height - 5 - y), (r_px, g_px, 255)) for y in range(b[x] + 1, b[x-1] + 1): r_px, g_px, b_px = im_data.getpixel((x, height - 5 - y)) im_data.putpixel((x, height - 5 - y), (r_px, g_px, 255)) if text: maxstr = 'max: ' + str(maximum) draw = ImageDraw.Draw(im) draw.rectangle((0, 0, len(maxstr) * 6 + 2, 10), fill=(30, 30, 30)) draw.text((2, 0), maxstr, fill=(255, 255, 255)) im = ImageOps.expand(im, 1, (80, 80, 80)) im = ImageOps.expand(im, 1, (0, 0, 0)) return image_tools.pil_to_pixbuf(im)
def test_pil_to_pixbuf(self): base_im = Image.open(get_image_path('transparent.png')) for _, expected_pixbuf_mode, mode in _IMAGE_MODES: input_im = base_im.convert(mode) pixbuf = image_tools.pil_to_pixbuf(input_im) expected_im = input_im.convert(expected_pixbuf_mode) msg = ( 'pil_to_pixbuf("%s") failed; ' 'result %%(diff_type)s differs: %%(diff)s' % (mode,) ) self.assertImagesEqual(pixbuf, expected_im, msg=msg)
def _drag_begin(self, iconview, context): """Create a cursor image for drag-n-drop from the library. This method relies on implementation details regarding PIL's drawing functions and default font to produce good looking results. If those are changed in a future release of PIL, this method might produce bad looking output (e.g. non-centered text). It's also used with connect_after() to overwrite the cursor automatically created when using enable_model_drag_source(), so in essence it's a hack, but at least it works. """ icon_path = iconview.get_cursor()[0] num_books = len(iconview.get_selected_items()) book = self.get_book_at_path(icon_path) cover = self._library.backend.get_book_cover(book) if cover is None: cover = image_tools.MISSING_IMAGE_ICON cover = cover.scale_simple(max(0, cover.get_width() // 2), max(0, cover.get_height() // 2), prefs['scaling quality']) cover = image_tools.add_border(cover, 1, 0xFFFFFFFF) cover = image_tools.add_border(cover, 1) if num_books > 1: cover_width = cover.get_width() cover_height = cover.get_height() pointer = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, max(30, cover_width + 15), max(30, cover_height + 10)) pointer.fill(0x00000000) cover.composite(pointer, 0, 0, cover_width, cover_height, 0, 0, 1, 1, prefs['scaling quality'], 255) im = Image.new('RGBA', (30, 30), 0x00000000) draw = ImageDraw.Draw(im) draw.polygon( (8, 0, 20, 0, 28, 8, 28, 20, 20, 28, 8, 28, 0, 20, 0, 8), fill=(0, 0, 0), outline=(0, 0, 0)) draw.polygon( (8, 1, 20, 1, 27, 8, 27, 20, 20, 27, 8, 27, 1, 20, 1, 8), fill=(128, 0, 0), outline=(255, 255, 255)) text = str(num_books) draw.text((15 - (6 * len(text) // 2), 9), text, fill=(255, 255, 255)) circle = image_tools.pil_to_pixbuf(im) circle.composite(pointer, max(0, cover_width - 15), max(0, cover_height - 20), 30, 30, max(0, cover_width - 15), max(0, cover_height - 20), 1, 1, prefs['scaling quality'], 255) else: pointer = cover context.set_icon_pixbuf(pointer, -5, -5)
def _drag_begin(self, iconview, context): """Create a cursor image for drag-n-drop from the library. This method relies on implementation details regarding PIL's drawing functions and default font to produce good looking results. If those are changed in a future release of PIL, this method might produce bad looking output (e.g. non-centered text). It's also used with connect_after() to overwrite the cursor automatically created when using enable_model_drag_source(), so in essence it's a hack, but at least it works. """ icon_path = iconview.get_cursor()[0] num_books = len(iconview.get_selected_items()) book = self.get_book_at_path(icon_path) cover = self._library.backend.get_book_cover(book) if cover is None: cover = constants.MISSING_IMAGE_ICON cover = cover.scale_simple(max(0, cover.get_width() // 2), max(0, cover.get_height() // 2), prefs['scaling quality']) cover = image_tools.add_border(cover, 1, 0xFFFFFFFF) cover = image_tools.add_border(cover, 1) if num_books > 1: cover_width = cover.get_width() cover_height = cover.get_height() pointer = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, max(30, cover_width + 15), max(30, cover_height + 10)) pointer.fill(0x00000000) cover.composite(pointer, 0, 0, cover_width, cover_height, 0, 0, 1, 1, prefs['scaling quality'], 255) im = Image.new('RGBA', (30, 30), 0x00000000) draw = ImageDraw.Draw(im) draw.polygon( (8, 0, 20, 0, 28, 8, 28, 20, 20, 28, 8, 28, 0, 20, 0, 8), fill=(0, 0, 0), outline=(0, 0, 0)) draw.polygon( (8, 1, 20, 1, 27, 8, 27, 20, 20, 27, 8, 27, 1, 20, 1, 8), fill=(128, 0, 0), outline=(255, 255, 255)) text = str(num_books) draw.text((15 - (6 * len(text) // 2), 9), text, fill=(255, 255, 255)) circle = image_tools.pil_to_pixbuf(im) circle.composite(pointer, max(0, cover_width - 15), max(0, cover_height - 20), 30, 30, max(0, cover_width - 15), max(0, cover_height - 20), 1, 1, prefs['scaling quality'], 255) else: pointer = cover context.set_icon_pixbuf(pointer, -5, -5)
def test_fit_in_rectangle_rotation(self): image_size = 128 rect_size = 32 # Start with a black image. im = Image.new('RGB', (image_size, image_size), color='black') draw = ImageDraw.Draw(im) # Paint top-left corner white. draw.rectangle((0, 0, rect_size, rect_size), fill='white') # Corner colors, starting top-left, rotating clock-wise. corners_colors = ('white', 'black', 'black', 'black') pixbuf = image_tools.pil_to_pixbuf(im) for rotation in (0, 90, 180, 270, -90, -180, -270, 90 * 5, -90 * 7): for target_size in ( (image_size, image_size), (image_size / 2, image_size / 2), ): result = image_tools.fit_in_rectangle(pixbuf, target_size[0], target_size[1], rotation=rotation) # First check size. input_size = (image_size, image_size) result_size = result.get_width(), result.get_height() msg = ('fit_in_rectangle(%dx%d => %dx%d, rotation=%d) failed; ' 'result size: %dx%d' % (input_size + target_size + (rotation, ) + result_size)) self.assertEqual(result_size, target_size, msg=msg) # And then check corners. expected_corners_colors = list(corners_colors) for _ in range(1, 1 + (rotation % 360) / 90): expected_corners_colors.insert( 0, expected_corners_colors.pop(-1)) result_corners_colors = [] corner = new_pixbuf((1, 1), False, 0x888888) corners_positions = [ 0, 0, target_size[0] - 1, target_size[0] - 1 ] for _ in range(4): x, y = corners_positions[0:2] result.copy_area(x, y, 1, 1, corner, 0, 0) color = corner.get_pixels()[0:3] color = binascii.hexlify(color) if 'ffffff' == color: color = 'white' elif '000000' == color: color = 'black' result_corners_colors.append(color) corners_positions.insert(0, corners_positions.pop(-1)) # Swap bottom corners for spatial display. result_corners_colors.append(result_corners_colors.pop(-2)) expected_corners_colors.append(expected_corners_colors.pop(-2)) msg = ('fit_in_rectangle(%dx%d => %dx%d, rotation=%d) failed; ' 'result corners differs:\n' '%s\t%s\n' '%s\t%s\n' 'instead of:\n' '%s\t%s\n' '%s\t%s\n' % (input_size + target_size + (rotation, ) + tuple(result_corners_colors) + tuple(expected_corners_colors))) self.assertEqual(result_corners_colors, expected_corners_colors, msg=msg)
def test_fit_in_rectangle_rotation(self): image_size = 128 rect_size = 32 # Start with a black image. im = Image.new('RGB', (image_size, image_size), color='black') draw = ImageDraw.Draw(im) # Paint top-left corner white. draw.rectangle((0, 0, rect_size, rect_size), fill='white') # Corner colors, starting top-left, rotating clock-wise. corners_colors = ('white', 'black', 'black', 'black') pixbuf = image_tools.pil_to_pixbuf(im) for rotation in ( 0, 90, 180, 270, -90, -180, -270, 90 * 5, -90 * 7 ): for target_size in ( (image_size, image_size), (image_size / 2, image_size / 2), ): result = image_tools.fit_in_rectangle(pixbuf, target_size[0], target_size[1], rotation=rotation) # First check size. input_size = (image_size, image_size) result_size = result.get_width(), result.get_height() msg = ( 'fit_in_rectangle(%dx%d => %dx%d, rotation=%d) failed; ' 'result size: %dx%d' % ( input_size + target_size + (rotation,) + result_size ) ) self.assertEqual(result_size, target_size, msg=msg) # And then check corners. expected_corners_colors = list(corners_colors) for _ in range(1, 1 + (rotation % 360) / 90): expected_corners_colors.insert(0, expected_corners_colors.pop(-1)) result_corners_colors = [] corner = new_pixbuf((1, 1), False, 0x888888) corners_positions = [0, 0, target_size[0] - 1, target_size[0] - 1] for _ in range(4): x, y = corners_positions[0:2] result.copy_area(x, y, 1, 1, corner, 0, 0) color = corner.get_pixels()[0:3] color = binascii.hexlify(color) if 'ffffff' == color: color = 'white' elif '000000' == color: color = 'black' result_corners_colors.append(color) corners_positions.insert(0, corners_positions.pop(-1)) # Swap bottom corners for spatial display. result_corners_colors.append(result_corners_colors.pop(-2)) expected_corners_colors.append(expected_corners_colors.pop(-2)) msg = ( 'fit_in_rectangle(%dx%d => %dx%d, rotation=%d) failed; ' 'result corners differs:\n' '%s\t%s\n' '%s\t%s\n' 'instead of:\n' '%s\t%s\n' '%s\t%s\n' % ( input_size + target_size + (rotation, ) + tuple(result_corners_colors) + tuple(expected_corners_colors) ) ) self.assertEqual(result_corners_colors, expected_corners_colors, msg=msg)