def _get_virtual_double_page(self, page: int = None): """ Return True if the current state warrants use of virtual double page mode (i.e. if double page mode is on, the corresponding preference is set, and one of the two images that should normally be displayed has a width that exceeds its height), or if currently on the first page :returns: True if the current state warrants use of virtual double page mode """ if page is None: page = self.imagehandler.get_current_page() if (page == 1 and config['VIRTUAL_DOUBLE_PAGE_FOR_FITTING_IMAGES'] & Constants.DOUBLE_PAGE['AS_ONE_TITLE'] and self.filehandler.get_archive_type() is not None): return True if (not config['DEFAULT_DOUBLE_PAGE'] or not config['VIRTUAL_DOUBLE_PAGE_FOR_FITTING_IMAGES'] & Constants.DOUBLE_PAGE['AS_ONE_WIDE'] or page == self.imagehandler.get_number_of_pages()): return False for page in (page, page + 1): if not self.imagehandler.page_is_available(page): return False pixbuf = self.imagehandler.get_pixbuf(page - 1) width, height = pixbuf.get_width(), pixbuf.get_height() if config['AUTO_ROTATE_FROM_EXIF']: rotation = ImageTools.get_implied_rotation(pixbuf) # if rotation not in (0, 90, 180, 270): # return if rotation in (90, 270): width, height = height, width if width > height: return True return False
def _draw_image(self, scroll_to: int): if not self.filehandler.get_file_loaded(): self.thumbnailsidebar.hide() self._clear_main_area() self.__waiting_for_redraw = False return if not self.imagehandler.page_is_available(): # Save scroll destination for when the page becomes available. self.__last_scroll_destination = scroll_to # If the pixbuf for the current page(s) isn't available clear old pixbufs. self._clear_main_area() self.__waiting_for_redraw = False return distribution_axis = Constants.AXIS['DISTRIBUTION'] alignment_axis = Constants.AXIS['ALIGNMENT'] pixbuf_count = 2 if self.displayed_double( ) else 1 # XXX limited to at most 2 pages pixbuf_list = list(self.imagehandler.get_pixbufs(pixbuf_count)) do_not_transform = [ ImageTools.disable_transform(x) for x in pixbuf_list ] size_list = [[pixbuf.get_width(), pixbuf.get_height()] for pixbuf in pixbuf_list] orientation = self.__page_orientation # Rotation handling: # - apply Exif rotation on individual images # - apply automatic rotation (size based) on whole page # - apply manual rotation on whole page if config['AUTO_ROTATE_FROM_EXIF']: rotation_list = [ ImageTools.get_implied_rotation(pixbuf) for pixbuf in pixbuf_list ] else: rotation_list = [0] * len(pixbuf_list) virtual_size = [0, 0] for i in range(pixbuf_count): if rotation_list[i] in (90, 270): size_list[i].reverse() size = size_list[i] virtual_size[distribution_axis] += size[distribution_axis] virtual_size[alignment_axis] = max(virtual_size[alignment_axis], size[alignment_axis]) rotation = (self._get_size_rotation(*virtual_size) + config['ROTATION']) % 360 if rotation in (90, 270): distribution_axis, alignment_axis = alignment_axis, distribution_axis orientation.reverse() for i in range(pixbuf_count): size_list[i].reverse() elif rotation in (180, 270): orientation.reverse() for i in range(pixbuf_count): rotation_list[i] = (rotation_list[i] + rotation) % 360 if config['VERTICAL_FLIP']: orientation.reverse() if config['HORIZONTAL_FLIP']: orientation.reverse() viewport_size = () # dummy scaled_sizes = [(0, 0)] union_scaled_size = (0, 0) # Visible area size is recomputed depending on scrollbar visibility while True: new_viewport_size = self.get_visible_area_size() if new_viewport_size == viewport_size: break viewport_size = new_viewport_size zoom_dummy_size = list(viewport_size) dasize = zoom_dummy_size[distribution_axis] - self.__spacing * ( pixbuf_count - 1) if dasize <= 0: dasize = 1 zoom_dummy_size[distribution_axis] = dasize scaled_sizes = self.zoom.get_zoomed_size(size_list, zoom_dummy_size, distribution_axis, do_not_transform) self.__layout = FiniteLayout(scaled_sizes, viewport_size, orientation, self.__spacing, distribution_axis, alignment_axis) union_scaled_size = self.__layout.get_union_box().get_size() for i in range(pixbuf_count): pixbuf_list[i] = ImageTools.fit_pixbuf_to_rectangle( pixbuf_list[i], scaled_sizes[i], rotation_list[i]) for i in range(pixbuf_count): pixbuf_list[i] = ImageTools.trans_pixbuf( pixbuf_list[i], flip=config['VERTICAL_FLIP'], flop=config['HORIZONTAL_FLIP']) pixbuf_list[i] = self.enhancer.enhance(pixbuf_list[i]) for i in range(pixbuf_count): ImageTools.set_from_pixbuf(self.images[i], pixbuf_list[i]) resolutions = [(*size, scaled_size[0] / size[0]) for scaled_size, size in zip(scaled_sizes, size_list)] if self.is_manga_mode: resolutions.reverse() self.statusbar.set_resolution(resolutions) self.statusbar.update() self.__main_layout.get_bin_window().freeze_updates() self.__main_layout.set_size(*union_scaled_size) content_boxes = self.__layout.get_content_boxes() for i in range(pixbuf_count): self.__main_layout.move(self.images[i], *content_boxes[i].get_position()) for i in range(pixbuf_count): self.images[i].show() for i in range(pixbuf_count, len(self.images)): self.images[i].hide() # Reset orientation so scrolling behaviour is sane. self.__layout.set_orientation(self.__page_orientation) if scroll_to is not None: destination = (scroll_to, ) * 2 self.scroll_to_predefined(destination) self.__main_layout.get_bin_window().thaw_updates() self.__waiting_for_redraw = False
def _add_subpixbuf(self, canvas, x: int, y: int, image_size: tuple, source_pixbuf): """ Copy a subpixbuf from <source_pixbuf> to <canvas> as it should be in the lens if the coordinates <x>, <y> are the mouse pointer position on the main window layout area. The displayed image (scaled from the <source_pixbuf>) must have size <image_size> """ # Prevent division by zero exceptions further down if not image_size[0]: return # FIXME This merely prevents Errors being raised if source_pixbuf is an # animation. The result might be broken, though, since animation, # rotation etc. might not match or will be ignored: source_pixbuf = ImageTools.static_image(source_pixbuf) rotation = config['ROTATION'] if config['AUTO_ROTATE_FROM_EXIF']: rotation += ImageTools.get_implied_rotation(source_pixbuf) rotation %= 360 if rotation in (90, 270): scale = source_pixbuf.get_height() / image_size[0] else: scale = source_pixbuf.get_width() / image_size[0] x *= scale y *= scale source_mag = config['LENS_MAGNIFICATION'] / scale width = height = config['LENS_SIZE'] / source_mag paste_left = x > width / 2 paste_top = y > height / 2 dest_x = max(0, int(math.ceil((width / 2 - x) * source_mag))) dest_y = max(0, int(math.ceil((height / 2 - y) * source_mag))) if rotation == 90: x, y = y, source_pixbuf.get_height() - x elif rotation == 180: x = source_pixbuf.get_width() - x y = source_pixbuf.get_height() - y elif rotation == 270: x, y = source_pixbuf.get_width() - y, x if config['HORIZONTAL_FLIP']: if rotation in (90, 270): y = source_pixbuf.get_height() - y else: x = source_pixbuf.get_width() - x if config['VERTICAL_FLIP']: if rotation in (90, 270): x = source_pixbuf.get_width() - x else: y = source_pixbuf.get_height() - y src_x = x - width / 2 src_y = y - height / 2 if src_x < 0: width += src_x src_x = 0 if src_y < 0: height += src_y src_y = 0 width = max(0, min(source_pixbuf.get_width() - src_x, width)) height = max(0, min(source_pixbuf.get_height() - src_y, height)) if width < 1 or height < 1: return subpixbuf = source_pixbuf.new_subpixbuf(int(src_x), int(src_y), int(width), int(height)) subpixbuf = subpixbuf.scale_simple( int(math.ceil(source_mag * subpixbuf.get_width())), int(math.ceil(source_mag * subpixbuf.get_height())), config['SCALING_QUALITY']) if rotation == 0: subpixbuf = subpixbuf.rotate_simple(GdkPixbuf.PixbufRotation.NONE) elif rotation == 90: subpixbuf = subpixbuf.rotate_simple(GdkPixbuf.PixbufRotation.CLOCKWISE) elif rotation == 180: subpixbuf = subpixbuf.rotate_simple(GdkPixbuf.PixbufRotation.UPSIDEDOWN) elif rotation == 270: subpixbuf = subpixbuf.rotate_simple(GdkPixbuf.PixbufRotation.COUNTERCLOCKWISE) if config['HORIZONTAL_FLIP']: subpixbuf = subpixbuf.flip(horizontal=True) if config['VERTICAL_FLIP']: subpixbuf = subpixbuf.flip(horizontal=False) subpixbuf = self.__window.enhancer.enhance(subpixbuf) if paste_left: dest_x = 0 else: dest_x = min(canvas.get_width() - subpixbuf.get_width(), dest_x) if paste_top: dest_y = 0 else: dest_y = min(canvas.get_height() - subpixbuf.get_height(), dest_y) if subpixbuf.get_has_alpha() and config['CHECKERED_BG_FOR_TRANSPARENT_IMAGES']: subpixbuf = subpixbuf.composite_color_simple(subpixbuf.get_width(), subpixbuf.get_height(), GdkPixbuf.InterpType.NEAREST, 255, 8, 0x777777, 0x999999) subpixbuf.copy_area(0, 0, subpixbuf.get_width(), subpixbuf.get_height(), canvas, dest_x, dest_y)
def _draw_image(self, scroll_to: int): # hides old images before showing new ones # also if in double page mode and only a single # image is going to be shown, prevents a ghost second image for i in self.__images: i.clear() if not self.__file_handler.get_file_loaded(): self.__thumbnailsidebar.hide() self.__waiting_for_redraw = False return self.__thumbnailsidebar.show() if not self.__image_handler.page_is_available(): # Save scroll destination for when the page becomes available. self.__last_scroll_destination = scroll_to self.__waiting_for_redraw = False return distribution_axis = ZoomAxis.DISTRIBUTION.value alignment_axis = ZoomAxis.ALIGNMENT.value # XXX limited to at most 2 pages pixbuf_count = 2 if ViewState.is_displaying_double else 1 pixbuf_count_iter = range(pixbuf_count) pixbuf_list = list(self.__image_handler.get_pixbufs(pixbuf_count)) do_not_transform = [ImageTools.disable_transform(x) for x in pixbuf_list] size_list = [[pixbuf.get_width(), pixbuf.get_height()] for pixbuf in pixbuf_list] # Rotation handling: # - apply Exif rotation on individual images # - apply manual rotation on whole page orientation = self.__page_orientation if config['AUTO_ROTATE_FROM_EXIF']: rotation_list = [ImageTools.get_implied_rotation(pixbuf) for pixbuf in pixbuf_list] for i in pixbuf_count_iter: if rotation_list[i] in (90, 270): size_list[i].reverse() else: # no auto rotation rotation_list = [0] * len(pixbuf_list) rotation = config['ROTATION'] % 360 match rotation: case (90 | 270): distribution_axis, alignment_axis = alignment_axis, distribution_axis orientation.reverse() for i in pixbuf_count_iter: size_list[i].reverse() case 180: orientation.reverse() # Recompute the visible area size viewport_size = self.get_visible_area_size() zoom_dummy_size = list(viewport_size) scaled_sizes = self.__zoom.get_zoomed_size(size_list, zoom_dummy_size, distribution_axis, do_not_transform) self.__layout = FiniteLayout(scaled_sizes, viewport_size, orientation, distribution_axis, alignment_axis) self.__main_layout.set_size(*self.__layout.get_union_box().get_size()) content_boxes = self.__layout.get_content_boxes() for i in pixbuf_count_iter: rotation_list[i] = (rotation_list[i] + rotation) % 360 pixbuf_list[i] = ImageTools.fit_pixbuf_to_rectangle(pixbuf_list[i], scaled_sizes[i], rotation_list[i]) pixbuf_list[i] = ImageTools.enhance(pixbuf_list[i]) ImageTools.set_from_pixbuf(self.__images[i], pixbuf_list[i]) self.__main_layout.move(self.__images[i], *content_boxes[i].get_position()) self.__images[i].show() # Reset orientation so scrolling behaviour is sane. self.__layout.set_orientation(self.__page_orientation) if scroll_to is not None: destination = (scroll_to,) * 2 self.scroll_to_predefined(destination) # update statusbar resolutions = [(*size, scaled_size[0] / size[0]) for scaled_size, size in zip(scaled_sizes, size_list, strict=True)] if ViewState.is_manga_mode: resolutions.reverse() self.__statusbar.set_resolution(resolutions) self.__statusbar.update() self.__waiting_for_redraw = False
def _add_subpixbuf(self, canvas, x: int, y: int, image_size: tuple, source_pixbuf): """ Copy a subpixbuf from <source_pixbuf> to <canvas> as it should be in the lens if the coordinates <x>, <y> are the mouse pointer position on the main window layout area. The displayed image (scaled from the <source_pixbuf>) must have size <image_size> """ # Prevent division by zero exceptions further down if not image_size[0]: return # FIXME This merely prevents Errors being raised if source_pixbuf is an # animation. The result might be broken, though, since animation, # rotation etc. might not match or will be ignored: source_pixbuf = ImageTools.static_image(source_pixbuf) rotation = config['ROTATION'] if config['AUTO_ROTATE_FROM_EXIF']: rotation += ImageTools.get_implied_rotation(source_pixbuf) rotation %= 360 if rotation in (90, 270): scale = source_pixbuf.get_height() / image_size[0] else: scale = source_pixbuf.get_width() / image_size[0] x *= scale y *= scale source_mag = config['LENS_MAGNIFICATION'] / scale width = height = config['LENS_SIZE'] / source_mag paste_left = x > width / 2 paste_top = y > height / 2 dest_x = max(0, int(math.ceil((width / 2 - x) * source_mag))) dest_y = max(0, int(math.ceil((height / 2 - y) * source_mag))) match rotation: case 90: x, y = y, source_pixbuf.get_height() - x case 180: x = source_pixbuf.get_width() - x y = source_pixbuf.get_height() - y case 270: x, y = source_pixbuf.get_width() - y, x src_x = x - width / 2 src_y = y - height / 2 if src_x < 0: width += src_x src_x = 0 if src_y < 0: height += src_y src_y = 0 width = max(0, min(source_pixbuf.get_width() - src_x, width)) height = max(0, min(source_pixbuf.get_height() - src_y, height)) if width < 1 or height < 1: return subpixbuf = source_pixbuf.new_subpixbuf(int(src_x), int(src_y), int(width), int(height)) subpixbuf = subpixbuf.scale_simple( int(math.ceil(source_mag * subpixbuf.get_width())), int(math.ceil(source_mag * subpixbuf.get_height())), config['GDK_SCALING_FILTER']) subpixbuf = ImageTools.rotate_pixbuf(subpixbuf, rotation) subpixbuf = ImageTools.enhance(subpixbuf) if paste_left: dest_x = 0 else: dest_x = min(canvas.get_width() - subpixbuf.get_width(), dest_x) if paste_top: dest_y = 0 else: dest_y = min(canvas.get_height() - subpixbuf.get_height(), dest_y) if subpixbuf.get_has_alpha(): subpixbuf = ImageTools.add_alpha_background(subpixbuf, subpixbuf.get_width(), subpixbuf.get_height()) subpixbuf.copy_area(0, 0, subpixbuf.get_width(), subpixbuf.get_height(), canvas, dest_x, dest_y)