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 _get_pixbuf(self, uid): """ Get or create the thumbnail for the selected book <uid>. """ assert isinstance(uid, int) book = self._library.backend.get_book_by_id(uid) if self._cache.exists(book.path): pixbuf = self._cache.get(book.path) else: width, height = self._pixbuf_size(border_size=0) pixbuf = self._library.backend.get_book_thumbnail(book.path) or image_tools.MISSING_IMAGE_ICON pixbuf = image_tools.fit_in_rectangle(pixbuf, width, height, scale_up=True) pixbuf = image_tools.add_border(pixbuf, 1, 0xFFFFFFFF) self._cache.add(book.path, pixbuf) # Display indicator of having finished reading the book. # This information isn't cached in the pixbuf cache, as it changes frequently. # Anything smaller than 50px means that the status icon will not fit if prefs['library cover size'] < 50: return pixbuf last_read_page = book.get_last_read_page() if last_read_page is None or last_read_page != book.pages: return pixbuf # Composite icon on the lower right corner of the book cover pixbuf. book_pixbuf = self.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_LARGE_TOOLBAR) translation_x = pixbuf.get_width() - book_pixbuf.get_width() - 1 translation_y = pixbuf.get_height() - book_pixbuf.get_height() - 1 book_pixbuf.composite(pixbuf, translation_x, translation_y, book_pixbuf.get_width(), book_pixbuf.get_height(), translation_x, translation_y, 1.0, 1.0, gtk.gdk.INTERP_NEAREST, 0xFF) return pixbuf
def _get_pixbuf(self, uid): ''' Get or create the thumbnail for the selected book <uid>. ''' assert isinstance(uid, int) book = self._library.backend.get_book_by_id(uid) if self._cache.exists(book.path): pixbuf = self._cache.get(book.path) else: width, height = self._pixbuf_size(border_size=0) # cover thumbnail of book cover = self._library.backend.get_book_thumbnail(book.path) if not cover: cover = image_tools.MISSING_IMAGE_ICON cover = image_tools.fit_in_rectangle(cover, width - 2, height - 2, scale_up=True) cover = image_tools.add_border(cover, 1, 0xFFFFFFFF) src_width, src_height = cover.get_width(), cover.get_height() # icon background of book pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, width, height) pixbuf.fill(0x00000000) # full transparency offset_x = (width - src_width) // 2 offset_y = (height - src_height) // 2 cover.copy_area(0, 0, src_width, src_height, pixbuf, offset_x, offset_y) self._cache.add(book.path, pixbuf) # Display indicator of having finished reading the book. # This information isn't cached in the pixbuf cache # as it changes frequently. # Anything smaller than 50px means that the status icon will not fit if prefs['library cover size'] < 50: return pixbuf last_read_page = book.get_last_read_page() if last_read_page is None or last_read_page != book.pages: return pixbuf # Composite icon on the lower right corner of the book cover pixbuf. book_pixbuf = self.render_icon(Gtk.STOCK_APPLY, Gtk.IconSize.LARGE_TOOLBAR) translation_x = pixbuf.get_width() - book_pixbuf.get_width() - 1 translation_y = pixbuf.get_height() - book_pixbuf.get_height() - 1 book_pixbuf.composite(pixbuf, translation_x, translation_y, book_pixbuf.get_width(), book_pixbuf.get_height(), translation_x, translation_y, 1.0, 1.0, GdkPixbuf.InterpType.NEAREST, 0xFF) return pixbuf
def add_extra_image(self, path): """Add an imported image (at <path>) to the end of the image list.""" thumbnailer = thumbnail_tools.Thumbnailer() thumbnailer.set_store_on_disk(False) thumb = thumbnailer.thumbnail(path) if thumb is None: thumb = self.render_icon(gtk.STOCK_MISSING_IMAGE, gtk.ICON_SIZE_DIALOG) thumb = image_tools.fit_in_rectangle(thumb, 128, 128) self._liststore.append([thumb, os.path.basename(path), path])
def _get_pixbuf(self, path, model, model_path): """ Get or create the thumbnail for the selected book at <path>. """ if self._cache.exists(path): return self._cache.get(path) else: pixbuf = self._library.backend.get_book_thumbnail(path) or constants.MISSING_IMAGE_ICON # The ratio (0.67) is just above the normal aspect ratio for books. pixbuf = image_tools.fit_in_rectangle(pixbuf, int(0.67 * prefs['library cover size']), prefs['library cover size'], True) pixbuf = image_tools.add_border(pixbuf, 1, 0xFFFFFFFF) self._cache.add(path, pixbuf) return pixbuf
def _get_pixbuf(self, path): """ Get or create the thumbnail for the selected book at <path>. """ if self._cache.exists(path): return self._cache.get(path) else: pixbuf = self._library.backend.get_book_thumbnail( path) or constants.MISSING_IMAGE_ICON # The ratio (0.67) is just above the normal aspect ratio for books. pixbuf = image_tools.fit_in_rectangle( pixbuf, int(0.67 * prefs['library cover size']), prefs['library cover size'], True) pixbuf = image_tools.add_border(pixbuf, 1, 0xFFFFFFFF) self._cache.add(path, pixbuf) return pixbuf
def test_fit_in_rectangle_opaque_no_resize(self): # Check opaque image is unchanged when not resizing. for image in ( 'pattern-opaque-rgb.png', 'pattern-opaque-rgba.png', ): input = image_tools.load_pixbuf(get_image_path(image)) width, height = input.get_width(), input.get_height() for scaling_quality in range(4): prefs['scaling quality'] = scaling_quality result = image_tools.fit_in_rectangle( input, width, height, scaling_quality=scaling_quality) msg = ('fit_in_rectangle("%s", scaling quality=%d) failed; ' 'result %%(diff_type)s differs: %%(diff)s' % (image, scaling_quality)) self.assertImagesEqual(result, input, msg=msg)
def test_fit_in_rectangle_dimensions(self): # Test dimensions handling. for input_size, target_size, scale_up, keep_ratio, expected_size in ( # Exactly the same size. ((200, 100), (200, 100), False, False, (200, 100)), ((200, 100), (200, 100), False, True, (200, 100)), ((200, 100), (200, 100), True, False, (200, 100)), ((200, 100), (200, 100), True, True, (200, 100)), # Smaller. ((200, 100), (400, 400), False, False, (200, 100)), ((200, 100), (400, 400), False, True, (200, 100)), ((200, 100), (400, 400), True, False, (400, 400)), ((200, 100), (400, 400), True, True, (400, 200)), # Bigger. ((800, 600), (200, 200), False, False, (200, 200)), ((800, 600), (200, 200), False, True, (200, 150)), ((800, 600), (200, 200), True, False, (200, 200)), ((800, 600), (200, 200), True, True, (200, 150)), # One dimension bigger, the other smaller. ((200, 400), (200, 200), False, False, (200, 200)), ((200, 400), (200, 200), False, True, (100, 200)), ((200, 400), (200, 200), True, False, (200, 200)), ((200, 400), (200, 200), True, True, (100, 200)), ): for invert_dimensions in (False, True): if invert_dimensions: input_size = input_size[1], input_size[0] target_size = target_size[1], target_size[0] expected_size = expected_size[1], expected_size[0] pixbuf = new_pixbuf(input_size, False, 0) result = image_tools.fit_in_rectangle(pixbuf, target_size[0], target_size[1], scale_up=scale_up, keep_ratio=keep_ratio) result_size = result.get_width(), result.get_height() msg = ( 'fit_in_rectangle(%dx%d => %dx%d, scale up=%s, keep ratio=%s) failed; ' 'result size differs: %dx%d instead of %dx%d' % ( input_size + target_size + (scale_up, keep_ratio) + result_size + expected_size ) ) self.assertEqual(result_size, expected_size, msg=msg)
def test_fit_in_rectangle_opaque_no_resize(self): # Check opaque image is unchanged when not resizing. for image in ( 'pattern-opaque-rgb.png', 'pattern-opaque-rgba.png', ): input = image_tools.load_pixbuf(get_image_path(image)) width, height = input.get_width(), input.get_height() for scaling_quality in range(4): prefs['scaling quality'] = scaling_quality result = image_tools.fit_in_rectangle(input, width, height, scaling_quality=scaling_quality) msg = ( 'fit_in_rectangle("%s", scaling quality=%d) failed; ' 'result %%(diff_type)s differs: %%(diff)s' % (image, scaling_quality) ) self.assertImagesEqual(result, input, msg=msg)
def _get_pixbuf(self, uid): """ Get or create the thumbnail for the selected book <uid>. """ assert isinstance(uid, int) book = self._library.backend.get_book_by_id(uid) if self._cache.exists(book.path): pixbuf = self._cache.get(book.path) else: width, height = self._pixbuf_size(border_size=0) pixbuf = self._library.backend.get_book_thumbnail( book.path) or image_tools.MISSING_IMAGE_ICON pixbuf = image_tools.fit_in_rectangle(pixbuf, width, height, scale_up=True) pixbuf = image_tools.add_border(pixbuf, 1, 0xFFFFFFFF) self._cache.add(book.path, pixbuf) # Display indicator of having finished reading the book. # This information isn't cached in the pixbuf cache, as it changes frequently. # Anything smaller than 50px means that the status icon will not fit if prefs['library cover size'] < 50: return pixbuf last_read_page = book.get_last_read_page() if last_read_page is None or last_read_page != book.pages: return pixbuf # Composite icon on the lower right corner of the book cover pixbuf. book_pixbuf = self.render_icon(Gtk.STOCK_APPLY, Gtk.IconSize.LARGE_TOOLBAR) translation_x = pixbuf.get_width() - book_pixbuf.get_width() - 1 translation_y = pixbuf.get_height() - book_pixbuf.get_height() - 1 book_pixbuf.composite(pixbuf, translation_x, translation_y, book_pixbuf.get_width(), book_pixbuf.get_height(), translation_x, translation_y, 1.0, 1.0, GdkPixbuf.InterpType.NEAREST, 0xFF) return pixbuf
def _get_pixbuf(self, path, model, model_path): """ Get or create the thumbnail for the selected book at <path>. """ if self._cache.exists(path): pixbuf = self._cache.get(path) else: pixbuf = self._library.backend.get_book_thumbnail( path) or constants.MISSING_IMAGE_ICON # The ratio (0.67) is just above the normal aspect ratio for books. pixbuf = image_tools.fit_in_rectangle( pixbuf, int(0.67 * prefs['library cover size']), prefs['library cover size'], True) pixbuf = image_tools.add_border(pixbuf, 1, 0xFFFFFFFF) self._cache.add(path, pixbuf) # Display indicator of having finished reading the book. # This information isn't cached in the pixbuf cache, as it changes frequently. # Anything smaller than 50px means that the status icon will not fit if prefs['library cover size'] < 50: return pixbuf book = self._library.backend.get_book_by_path(path) last_read_page = book.get_last_read_page() if last_read_page is None or last_read_page != book.pages: return pixbuf if last_read_page == book.pages: book_pixbuf = self.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_LARGE_TOOLBAR) # Composite icon on the lower right corner of the book cover pixbuf. translation_x = pixbuf.get_width() - book_pixbuf.get_width() - 1 translation_y = pixbuf.get_height() - book_pixbuf.get_height() - 1 book_pixbuf.composite(pixbuf, translation_x, translation_y, book_pixbuf.get_width(), book_pixbuf.get_height(), translation_x, translation_y, 1.0, 1.0, gtk.gdk.INTERP_NEAREST, 0xFF) return pixbuf
def _get_pixbuf(self, path, model, model_path): """ Get or create the thumbnail for the selected book at <path>. """ if self._cache.exists(path): pixbuf = self._cache.get(path) else: pixbuf = self._library.backend.get_book_thumbnail(path) or constants.MISSING_IMAGE_ICON # The ratio (0.67) is just above the normal aspect ratio for books. pixbuf = image_tools.fit_in_rectangle(pixbuf, int(0.67 * prefs['library cover size']), prefs['library cover size'], True) pixbuf = image_tools.add_border(pixbuf, 1, 0xFFFFFFFF) self._cache.add(path, pixbuf) # Display indicator of having finished reading the book. # This information isn't cached in the pixbuf cache, as it changes frequently. # Anything smaller than 50px means that the status icon will not fit if prefs['library cover size'] < 50: return pixbuf book = self._library.backend.get_book_by_path(path) last_read_page = book.get_last_read_page() if last_read_page is None or last_read_page != book.pages: return pixbuf if last_read_page == book.pages: book_pixbuf = self.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_LARGE_TOOLBAR) # Composite icon on the lower right corner of the book cover pixbuf. translation_x = pixbuf.get_width() - book_pixbuf.get_width() - 1 translation_y = pixbuf.get_height() - book_pixbuf.get_height() - 1 book_pixbuf.composite(pixbuf, translation_x, translation_y, book_pixbuf.get_width(), book_pixbuf.get_height(), translation_x, translation_y, 1.0, 1.0, gtk.gdk.INTERP_NEAREST, 0xFF) return pixbuf
def _draw_image(self, at_bottom, scroll): self._display_active_widgets() while gtk.events_pending(): gtk.main_iteration(False) if not self.filehandler.file_loaded: self._waiting_for_redraw = False return False area_width, area_height = self.get_visible_area_size() if prefs['zoom mode'] == constants.ZOOM_MODE_HEIGHT: scaled_width = -1 else: scaled_width = area_width if prefs['zoom mode'] == constants.ZOOM_MODE_WIDTH: scaled_height = -1 else: scaled_height = area_height scale_up = prefs['stretch'] self.is_virtual_double_page = \ self.imagehandler.get_virtual_double_page() skip_pixbuf = not self.imagehandler.page_is_available() if self.displayed_double() and not skip_pixbuf: left_pixbuf, right_pixbuf = self.imagehandler.get_pixbufs() if self.is_manga_mode: right_pixbuf, left_pixbuf = left_pixbuf, right_pixbuf left_unscaled_x = left_pixbuf.get_width() left_unscaled_y = left_pixbuf.get_height() right_unscaled_x = right_pixbuf.get_width() right_unscaled_y = right_pixbuf.get_height() left_rotation = prefs['rotation'] right_rotation = prefs['rotation'] if prefs['auto rotate from exif']: left_rotation += image_tools.get_implied_rotation(left_pixbuf) left_rotation = left_rotation % 360 right_rotation += image_tools.get_implied_rotation( right_pixbuf) right_rotation = right_rotation % 360 if prefs['zoom mode'] == constants.ZOOM_MODE_MANUAL: if left_rotation in (90, 270): total_width = left_unscaled_y total_height = left_unscaled_x else: total_width = left_unscaled_x total_height = left_unscaled_y if right_rotation in (90, 270): total_width += right_unscaled_y total_height += right_unscaled_x else: total_width += right_unscaled_x total_height += right_unscaled_y total_width += 2 # For the 2 px gap between images. scaled_width = int(self._manual_zoom * total_width / 100) scaled_height = int(self._manual_zoom * total_height / 100) scale_up = True left_pixbuf, right_pixbuf = image_tools.fit_2_in_rectangle( left_pixbuf, right_pixbuf, scaled_width, scaled_height, scale_up=scale_up, rotation1=left_rotation, rotation2=right_rotation) if prefs['horizontal flip']: left_pixbuf = left_pixbuf.flip(horizontal=True) right_pixbuf = right_pixbuf.flip(horizontal=True) if prefs['vertical flip']: left_pixbuf = left_pixbuf.flip(horizontal=False) right_pixbuf = right_pixbuf.flip(horizontal=False) left_pixbuf = self.enhancer.enhance(left_pixbuf) right_pixbuf = self.enhancer.enhance(right_pixbuf) self.left_image.set_from_pixbuf(left_pixbuf) self.right_image.set_from_pixbuf(right_pixbuf) x_padding = (area_width - left_pixbuf.get_width() - right_pixbuf.get_width()) / 2 y_padding = (area_height - max(left_pixbuf.get_height(), right_pixbuf.get_height())) / 2 if left_rotation in (90, 270): left_scale_percent = (100.0 * left_pixbuf.get_width() / left_unscaled_y) else: left_scale_percent = (100.0 * left_pixbuf.get_width() / left_unscaled_x) if right_rotation in (90, 270): right_scale_percent = (100.0 * right_pixbuf.get_width() / right_unscaled_y) else: right_scale_percent = (100.0 * right_pixbuf.get_width() / right_unscaled_x) self.statusbar.set_resolution( (left_unscaled_x, left_unscaled_y, left_scale_percent), (right_unscaled_x, right_unscaled_y, right_scale_percent)) elif not skip_pixbuf: pixbuf = self.imagehandler.get_pixbufs(single=True)[0] unscaled_x = pixbuf.get_width() unscaled_y = pixbuf.get_height() rotation = prefs['rotation'] if prefs['auto rotate from exif']: rotation += image_tools.get_implied_rotation(pixbuf) rotation = rotation % 360 if prefs['zoom mode'] == constants.ZOOM_MODE_MANUAL: # If 'Scale small images' is true, scale up the image's base size scale_x = max(scale_up and scaled_width or unscaled_x, unscaled_x) scale_y = max(scale_up and scaled_height or unscaled_y, unscaled_y) scaled_width = int(self._manual_zoom * scale_x / 100) scaled_height = int(self._manual_zoom * scale_y / 100) if rotation in (90, 270): scaled_width, scaled_height = scaled_height, scaled_width scale_up = True pixbuf = image_tools.fit_in_rectangle(pixbuf, scaled_width, scaled_height, scale_up=scale_up, rotation=rotation) if prefs['horizontal flip']: pixbuf = pixbuf.flip(horizontal=True) if prefs['vertical flip']: pixbuf = pixbuf.flip(horizontal=False) pixbuf = self.enhancer.enhance(pixbuf) self.left_image.set_from_pixbuf(pixbuf) self.right_image.clear() x_padding = (area_width - pixbuf.get_width()) / 2 y_padding = (area_height - pixbuf.get_height()) / 2 if rotation in (90, 270): scale_percent = 100.0 * pixbuf.get_width() / unscaled_y else: scale_percent = 100.0 * pixbuf.get_width() / unscaled_x self.statusbar.set_resolution( (unscaled_x, unscaled_y, scale_percent)) if prefs['smart bg'] and not skip_pixbuf: bg_colour = self.imagehandler.get_pixbuf_auto_background( not self.displayed_double()) self.set_bg_colour(bg_colour) if prefs['smart thumb bg'] and prefs['show thumbnails']: self.thumbnailsidebar.change_thumbnail_background_color( bg_colour) elif prefs['smart thumb bg'] and prefs[ 'show thumbnails'] and not skip_pixbuf: bg_colour = image_tools.get_most_common_edge_colour( self.left_image.get_pixbuf()) self.thumbnailsidebar.change_thumbnail_background_color(bg_colour) if not skip_pixbuf: self._image_box.window.freeze_updates() self._main_layout.move(self._image_box, max(0, x_padding), max(0, y_padding)) self.left_image.show() if self.displayed_double(): self.right_image.show() else: self.right_image.hide() self._main_layout.set_size(*self._image_box.size_request()) if scroll: if at_bottom: self.scroll_to_fixed(horiz='endsecond', vert='bottom') else: self.scroll_to_fixed(horiz='startfirst', vert='top') self._image_box.window.thaw_updates() else: # If the pixbuf for the current page(s) isn't available, # hide both images to clear any old pixbufs. self.left_image.hide() self.right_image.hide() self._update_page_information() self._waiting_for_redraw = False while gtk.events_pending(): gtk.main_iteration(False) return False
def _draw_image(self, at_bottom, scroll): self._display_active_widgets() if not self.filehandler.file_loaded: self._waiting_for_redraw = False return False self.is_virtual_double_page = \ self.imagehandler.get_virtual_double_page() skip_pixbuf = not self.imagehandler.page_is_available() if self.displayed_double() and not skip_pixbuf: left_pixbuf, right_pixbuf = self.imagehandler.get_pixbufs() if self.is_manga_mode: right_pixbuf, left_pixbuf = left_pixbuf, right_pixbuf left_unscaled_x = left_pixbuf.get_width() left_unscaled_y = left_pixbuf.get_height() right_unscaled_x = right_pixbuf.get_width() right_unscaled_y = right_pixbuf.get_height() left_rotation = self._get_pixbuf_rotation(left_pixbuf, True) right_rotation = self._get_pixbuf_rotation(right_pixbuf, True) if left_rotation in (90, 270): left_width = left_unscaled_y left_height = left_unscaled_x else: left_width = left_unscaled_x left_height = left_unscaled_y if right_rotation in (90, 270): right_width = right_unscaled_y right_height = right_unscaled_x else: right_width = right_unscaled_x right_height = right_unscaled_y width, height = image_tools.get_double_page_rectangle( left_width, left_height, right_width, right_height) scaled_width, scaled_height = self.zoom.get_zoomed_size( (width, height), self.get_visible_area_size()) # Visible area size is recomputed depending on scrollbar visibility self._show_scrollbars((scaled_width, scaled_height), self.get_visible_area_size()) area_width, area_height = self.get_visible_area_size() scaled_width, scaled_height = self.zoom.get_zoomed_size( (width, height), (area_width, area_height)) # 100000 just some big enough constant. # We need to ensure that images # are limited only by height during scaling left_pixbuf = image_tools.fit_in_rectangle(left_pixbuf, 100000, scaled_height, prefs['stretch'], left_rotation) right_pixbuf = image_tools.fit_in_rectangle( right_pixbuf, 100000, scaled_height, prefs['stretch'], right_rotation) if prefs['horizontal flip']: left_pixbuf = left_pixbuf.flip(horizontal=True) right_pixbuf = right_pixbuf.flip(horizontal=True) if prefs['vertical flip']: left_pixbuf = left_pixbuf.flip(horizontal=False) right_pixbuf = right_pixbuf.flip(horizontal=False) left_pixbuf = self.enhancer.enhance(left_pixbuf) right_pixbuf = self.enhancer.enhance(right_pixbuf) self.left_image.set_from_pixbuf(left_pixbuf) self.right_image.set_from_pixbuf(right_pixbuf) x_padding = int( round((area_width - left_pixbuf.get_width() - right_pixbuf.get_width()) / 2.0)) y_padding = int( round((area_height - max(left_pixbuf.get_height(), right_pixbuf.get_height())) / 2.0)) if left_rotation in (90, 270): left_scale_percent = (100.0 * left_pixbuf.get_width() / left_unscaled_y) else: left_scale_percent = (100.0 * left_pixbuf.get_width() / left_unscaled_x) if right_rotation in (90, 270): right_scale_percent = (100.0 * right_pixbuf.get_width() / right_unscaled_y) else: right_scale_percent = (100.0 * right_pixbuf.get_width() / right_unscaled_x) self.statusbar.set_resolution( (left_unscaled_x, left_unscaled_y, left_scale_percent), (right_unscaled_x, right_unscaled_y, right_scale_percent)) elif not skip_pixbuf: pixbuf = self.imagehandler.get_pixbufs(single=True)[0] width, height = pixbuf.get_width(), pixbuf.get_height() rotation = self._get_pixbuf_rotation(pixbuf) if rotation in (90, 270): width, height = height, width scaled_width, scaled_height = self.zoom.get_zoomed_size( (width, height), self.get_visible_area_size()) # Visible area size is recomputed depending on scrollbar visibility self._show_scrollbars((scaled_width, scaled_height), self.get_visible_area_size()) area_width, area_height = self.get_visible_area_size() scaled_width, scaled_height = self.zoom.get_zoomed_size( (width, height), (area_width, area_height)) pixbuf = image_tools.fit_in_rectangle(pixbuf, scaled_width, scaled_height, scale_up=True, rotation=rotation) if prefs['horizontal flip']: pixbuf = pixbuf.flip(horizontal=True) if prefs['vertical flip']: pixbuf = pixbuf.flip(horizontal=False) pixbuf = self.enhancer.enhance(pixbuf) self.left_image.set_from_pixbuf(pixbuf) self.right_image.clear() x_padding = int(round((area_width - pixbuf.get_width()) / 2.0)) y_padding = int(round((area_height - pixbuf.get_height()) / 2.0)) if rotation in (90, 270): scale_percent = 100.0 * pixbuf.get_width() / height else: scale_percent = 100.0 * pixbuf.get_width() / width self.statusbar.set_resolution((width, height, scale_percent)) if prefs['smart bg'] and not skip_pixbuf: bg_colour = self.imagehandler.get_pixbuf_auto_background( not self.displayed_double()) self.set_bg_colour(bg_colour) if prefs['smart thumb bg'] and prefs['show thumbnails']: self.thumbnailsidebar.change_thumbnail_background_color( bg_colour) elif prefs['smart thumb bg'] and prefs[ 'show thumbnails'] and not skip_pixbuf: bg_colour = image_tools.get_most_common_edge_colour( self.left_image.get_pixbuf()) self.thumbnailsidebar.change_thumbnail_background_color(bg_colour) if not skip_pixbuf: self._image_box.window.freeze_updates() self._main_layout.move(self._image_box, max(0, x_padding), max(0, y_padding)) self.left_image.show() if self.displayed_double(): self.right_image.show() else: self.right_image.hide() self._main_layout.set_size(*self._image_box.size_request()) if scroll: if at_bottom: self.scroll_to_fixed(horiz='endsecond', vert='bottom') else: self.scroll_to_fixed(horiz='startfirst', vert='top') self._image_box.window.thaw_updates() else: # If the pixbuf for the current page(s) isn't available, # hide both images to clear any old pixbufs. self.left_image.hide() self.right_image.hide() self._update_page_information() self._waiting_for_redraw = False return False
def _draw_image(self, at_bottom, scroll): self._display_active_widgets() if not self.filehandler.file_loaded: self._waiting_for_redraw = False return False self.is_virtual_double_page = self.imagehandler.get_virtual_double_page() skip_pixbuf = not self.imagehandler.page_is_available() if self.displayed_double() and not skip_pixbuf: left_pixbuf, right_pixbuf = self.imagehandler.get_pixbufs() if self.is_manga_mode: right_pixbuf, left_pixbuf = left_pixbuf, right_pixbuf left_unscaled_x = left_pixbuf.get_width() left_unscaled_y = left_pixbuf.get_height() right_unscaled_x = right_pixbuf.get_width() right_unscaled_y = right_pixbuf.get_height() left_rotation = self._get_pixbuf_rotation(left_pixbuf, True) right_rotation = self._get_pixbuf_rotation(right_pixbuf, True) if left_rotation in (90, 270): left_width = left_unscaled_y left_height = left_unscaled_x else: left_width = left_unscaled_x left_height = left_unscaled_y if right_rotation in (90, 270): right_width = right_unscaled_y right_height = right_unscaled_x else: right_width = right_unscaled_x right_height = right_unscaled_y width, height = image_tools.get_double_page_rectangle(left_width, left_height, right_width, right_height) scaled_width, scaled_height = self.zoom.get_zoomed_size((width, height), self.get_visible_area_size()) # Visible area size is recomputed depending on scrollbar visibility self._show_scrollbars((scaled_width, scaled_height), self.get_visible_area_size()) area_width, area_height = self.get_visible_area_size() scaled_width, scaled_height = self.zoom.get_zoomed_size((width, height), (area_width, area_height)) # 100000 just some big enough constant. # We need to ensure that images # are limited only by height during scaling left_pixbuf = image_tools.fit_in_rectangle( left_pixbuf, 100000, scaled_height, prefs["stretch"], left_rotation ) right_pixbuf = image_tools.fit_in_rectangle( right_pixbuf, 100000, scaled_height, prefs["stretch"], right_rotation ) if prefs["horizontal flip"]: left_pixbuf = left_pixbuf.flip(horizontal=True) right_pixbuf = right_pixbuf.flip(horizontal=True) if prefs["vertical flip"]: left_pixbuf = left_pixbuf.flip(horizontal=False) right_pixbuf = right_pixbuf.flip(horizontal=False) left_pixbuf = self.enhancer.enhance(left_pixbuf) right_pixbuf = self.enhancer.enhance(right_pixbuf) self.left_image.set_from_pixbuf(left_pixbuf) self.right_image.set_from_pixbuf(right_pixbuf) x_padding = int(round((area_width - left_pixbuf.get_width() - right_pixbuf.get_width()) / 2.0)) y_padding = int(round((area_height - max(left_pixbuf.get_height(), right_pixbuf.get_height())) / 2.0)) if left_rotation in (90, 270): left_scale_percent = 100.0 * left_pixbuf.get_width() / left_unscaled_y else: left_scale_percent = 100.0 * left_pixbuf.get_width() / left_unscaled_x if right_rotation in (90, 270): right_scale_percent = 100.0 * right_pixbuf.get_width() / right_unscaled_y else: right_scale_percent = 100.0 * right_pixbuf.get_width() / right_unscaled_x self.statusbar.set_resolution( (left_unscaled_x, left_unscaled_y, left_scale_percent), (right_unscaled_x, right_unscaled_y, right_scale_percent), ) elif not skip_pixbuf: pixbuf = self.imagehandler.get_pixbufs(single=True)[0] width, height = pixbuf.get_width(), pixbuf.get_height() rotation = self._get_pixbuf_rotation(pixbuf) if rotation in (90, 270): width, height = height, width scaled_width, scaled_height = self.zoom.get_zoomed_size((width, height), self.get_visible_area_size()) # Visible area size is recomputed depending on scrollbar visibility self._show_scrollbars((scaled_width, scaled_height), self.get_visible_area_size()) area_width, area_height = self.get_visible_area_size() scaled_width, scaled_height = self.zoom.get_zoomed_size((width, height), (area_width, area_height)) pixbuf = image_tools.fit_in_rectangle(pixbuf, scaled_width, scaled_height, scale_up=True, rotation=rotation) if prefs["horizontal flip"]: pixbuf = pixbuf.flip(horizontal=True) if prefs["vertical flip"]: pixbuf = pixbuf.flip(horizontal=False) pixbuf = self.enhancer.enhance(pixbuf) self.left_image.set_from_pixbuf(pixbuf) self.right_image.clear() x_padding = int(round((area_width - pixbuf.get_width()) / 2.0)) y_padding = int(round((area_height - pixbuf.get_height()) / 2.0)) if rotation in (90, 270): scale_percent = 100.0 * pixbuf.get_width() / height else: scale_percent = 100.0 * pixbuf.get_width() / width self.statusbar.set_resolution((width, height, scale_percent)) if prefs["smart bg"] and not skip_pixbuf: bg_colour = self.imagehandler.get_pixbuf_auto_background(not self.displayed_double()) self.set_bg_colour(bg_colour) if prefs["smart thumb bg"] and prefs["show thumbnails"]: self.thumbnailsidebar.change_thumbnail_background_color(bg_colour) elif prefs["smart thumb bg"] and prefs["show thumbnails"] and not skip_pixbuf: bg_colour = image_tools.get_most_common_edge_colour(self.left_image.get_pixbuf()) self.thumbnailsidebar.change_thumbnail_background_color(bg_colour) if not skip_pixbuf: self._image_box.window.freeze_updates() self._main_layout.move(self._image_box, max(0, x_padding), max(0, y_padding)) self.left_image.show() if self.displayed_double(): self.right_image.show() else: self.right_image.hide() self._main_layout.set_size(*self._image_box.size_request()) if scroll: if at_bottom: self.scroll_to_fixed(horiz="endsecond", vert="bottom") else: self.scroll_to_fixed(horiz="startfirst", vert="top") self._image_box.window.thaw_updates() else: # If the pixbuf for the current page(s) isn't available, # hide both images to clear any old pixbufs. self.left_image.hide() self.right_image.hide() self._update_page_information() self._waiting_for_redraw = False return False
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 _draw_image(self, at_bottom, scroll): self._display_active_widgets() while gtk.events_pending(): gtk.main_iteration(False) if not self.filehandler.file_loaded: self._waiting_for_redraw = False return False area_width, area_height = self.get_visible_area_size() if prefs['zoom mode'] == constants.ZOOM_MODE_HEIGHT: scaled_width = -1 else: scaled_width = area_width if prefs['zoom mode'] == constants.ZOOM_MODE_WIDTH: scaled_height = -1 else: scaled_height = area_height scale_up = prefs['stretch'] self.is_virtual_double_page = \ self.imagehandler.get_virtual_double_page() skip_pixbuf = not self.imagehandler.page_is_available() if self.displayed_double() and not skip_pixbuf: left_pixbuf, right_pixbuf = self.imagehandler.get_pixbufs() if self.is_manga_mode: right_pixbuf, left_pixbuf = left_pixbuf, right_pixbuf left_unscaled_x = left_pixbuf.get_width() left_unscaled_y = left_pixbuf.get_height() right_unscaled_x = right_pixbuf.get_width() right_unscaled_y = right_pixbuf.get_height() left_rotation = prefs['rotation'] right_rotation = prefs['rotation'] if prefs['auto rotate from exif']: left_rotation += image_tools.get_implied_rotation(left_pixbuf) left_rotation = left_rotation % 360 right_rotation += image_tools.get_implied_rotation(right_pixbuf) right_rotation = right_rotation % 360 if prefs['zoom mode'] == constants.ZOOM_MODE_MANUAL: if left_rotation in (90, 270): total_width = left_unscaled_y total_height = left_unscaled_x else: total_width = left_unscaled_x total_height = left_unscaled_y if right_rotation in (90, 270): total_width += right_unscaled_y total_height += right_unscaled_x else: total_width += right_unscaled_x total_height += right_unscaled_y total_width += 2 # For the 2 px gap between images. scaled_width = int(self._manual_zoom * total_width / 100) scaled_height = int(self._manual_zoom * total_height / 100) scale_up = True left_pixbuf, right_pixbuf = image_tools.fit_2_in_rectangle( left_pixbuf, right_pixbuf, scaled_width, scaled_height, scale_up=scale_up, rotation1=left_rotation, rotation2=right_rotation) if prefs['horizontal flip']: left_pixbuf = left_pixbuf.flip(horizontal=True) right_pixbuf = right_pixbuf.flip(horizontal=True) if prefs['vertical flip']: left_pixbuf = left_pixbuf.flip(horizontal=False) right_pixbuf = right_pixbuf.flip(horizontal=False) left_pixbuf = self.enhancer.enhance(left_pixbuf) right_pixbuf = self.enhancer.enhance(right_pixbuf) self.left_image.set_from_pixbuf(left_pixbuf) self.right_image.set_from_pixbuf(right_pixbuf) x_padding = (area_width - left_pixbuf.get_width() - right_pixbuf.get_width()) / 2 y_padding = (area_height - max(left_pixbuf.get_height(), right_pixbuf.get_height())) / 2 if left_rotation in (90, 270): left_scale_percent = (100.0 * left_pixbuf.get_width() / left_unscaled_y) else: left_scale_percent = (100.0 * left_pixbuf.get_width() / left_unscaled_x) if right_rotation in (90, 270): right_scale_percent = (100.0 * right_pixbuf.get_width() / right_unscaled_y) else: right_scale_percent = (100.0 * right_pixbuf.get_width() / right_unscaled_x) self.statusbar.set_resolution( (left_unscaled_x, left_unscaled_y, left_scale_percent), (right_unscaled_x, right_unscaled_y, right_scale_percent)) elif not skip_pixbuf: pixbuf = self.imagehandler.get_pixbufs(single=True)[ 0 ] unscaled_x = pixbuf.get_width() unscaled_y = pixbuf.get_height() rotation = prefs['rotation'] if prefs['auto rotate from exif']: rotation += image_tools.get_implied_rotation(pixbuf) rotation = rotation % 360 if prefs['zoom mode'] == constants.ZOOM_MODE_MANUAL: # If 'Scale small images' is true, scale up the image's base size scale_x = max(scale_up and scaled_width or unscaled_x, unscaled_x) scale_y = max(scale_up and scaled_height or unscaled_y, unscaled_y) scaled_width = int(self._manual_zoom * scale_x / 100) scaled_height = int(self._manual_zoom * scale_y / 100) if rotation in (90, 270): scaled_width, scaled_height = scaled_height, scaled_width scale_up = True pixbuf = image_tools.fit_in_rectangle(pixbuf, scaled_width, scaled_height, scale_up=scale_up, rotation=rotation) if prefs['horizontal flip']: pixbuf = pixbuf.flip(horizontal=True) if prefs['vertical flip']: pixbuf = pixbuf.flip(horizontal=False) pixbuf = self.enhancer.enhance(pixbuf) self.left_image.set_from_pixbuf(pixbuf) self.right_image.clear() x_padding = (area_width - pixbuf.get_width()) / 2 y_padding = (area_height - pixbuf.get_height()) / 2 if rotation in (90, 270): scale_percent = 100.0 * pixbuf.get_width() / unscaled_y else: scale_percent = 100.0 * pixbuf.get_width() / unscaled_x self.statusbar.set_resolution((unscaled_x, unscaled_y, scale_percent)) if prefs['smart bg'] and not skip_pixbuf: bg_colour = self.imagehandler.get_pixbuf_auto_background( not self.displayed_double()) self.set_bg_colour(bg_colour) if prefs['smart thumb bg'] and prefs['show thumbnails']: self.thumbnailsidebar.change_thumbnail_background_color(bg_colour) elif prefs['smart thumb bg'] and prefs['show thumbnails'] and not skip_pixbuf: bg_colour = image_tools.get_most_common_edge_colour( self.left_image.get_pixbuf()) self.thumbnailsidebar.change_thumbnail_background_color(bg_colour) if not skip_pixbuf: self._image_box.window.freeze_updates() self._main_layout.move(self._image_box, max(0, x_padding), max(0, y_padding)) self.left_image.show() if self.displayed_double(): self.right_image.show() else: self.right_image.hide() self._main_layout.set_size(*self._image_box.size_request()) if scroll: if at_bottom: self.scroll_to_fixed(horiz='endsecond', vert='bottom') else: self.scroll_to_fixed(horiz='startfirst', vert='top') self._image_box.window.thaw_updates() else: # If the pixbuf for the current page(s) isn't available, # hide both images to clear any old pixbufs. self.left_image.hide() self.right_image.hide() self._update_page_information() self._waiting_for_redraw = False while gtk.events_pending(): gtk.main_iteration(False) return False
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)