Пример #1
0
    def _create_thumbnail_pixbuf(self, filepath: Path):
        """
        Creates a thumbnail pixbuf from <filepath>, and returns it as a
        tuple: (pixbuf)
        """

        if ImageTools.is_image_file(filepath):
            pixbuf = ImageTools.load_pixbuf_size(filepath, self.__width, self.__height)
            return pixbuf

        return None, None
Пример #2
0
    def _load_icons(self):
        _icons = (('gimp-flip-horizontal.png', 'mcomix-flip-horizontal'),
                  ('gimp-flip-vertical.png', 'mcomix-flip-vertical'),
                  ('gimp-rotate-180.png', 'mcomix-rotate-180'),
                  ('gimp-rotate-270.png', 'mcomix-rotate-270'),
                  ('gimp-rotate-90.png', 'mcomix-rotate-90'),
                  ('gimp-transform.png', 'mcomix-transform'),
                  ('tango-enhance-image.png', 'mcomix-enhance-image'),
                  ('tango-add-bookmark.png', 'mcomix-add-bookmark'),
                  ('tango-archive.png', 'mcomix-archive'),
                  ('tango-image.png', 'mcomix-image'),
                  ('zoom.png', 'mcomix-zoom'),
                  ('lens.png', 'mcomix-lens'))

        # Load window title icons.
        Gtk.Window.set_default_icon_list(self.__mcomix_icons)

        # Load application icons.
        factory = Gtk.IconFactory()
        for filename, stockid in _icons:
            try:
                icon_data = resources.read_binary('mcomix.images', filename)
                pixbuf = ImageTools.load_pixbuf_data(icon_data)
                iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
                factory.add(stockid, iconset)
            except FileNotFoundError:
                logger.warning(f'Could not load icon: \'{filename}\'')
        factory.add_default()
Пример #3
0
    def __init__(self, window: MainWindow):
        super().__init__(title='About')

        self.__window = window

        self.set_modal(True)
        self.set_transient_for(self.__window)

        self.set_titlebar(Gtk.HeaderBar(title='About'))

        logo = Path(__file__).parent / 'images' / 'mcomix.png'
        self.set_logo(ImageTools.pil_to_pixbuf(Image.open(logo)))

        self.set_name(Mcomix.APP_NAME.value)
        self.set_program_name(Mcomix.APP_NAME.value)
        self.set_version(f'Version {Mcomix.VERSION.value}')
        self.set_copyright('Copyright (C) 2005-2021')

        self.set_license_type(Gtk.License.GPL_2_0)

        link = f'https://github.com/thermitegod/{Mcomix.PROG_NAME.value}'
        self.set_website(link)
        self.set_website_label(link)

        self.set_comments(f'{Mcomix.APP_NAME.value} is an image viewer specifically designed '
                          f'to handle manga, comics, and image files.')

        self.show_all()
Пример #4
0
    def draw_histogram(self, height: int = 170, fill: int = 170):
        """
        Draw a histogram (RGB) from self.__pixbuf and return it as another pixbuf.

        :param height: height of the returned pixbuf
        :param fill: determines the color intensity of the filled graphs,
               valid values are between 0 and 255.
        :returns: modified pixbuf, the returned prixbuf will be 262x<height> px.
        """

        self.__pixbuf = ImageTools.static_image(self.__pixbuf)

        im = Image.new('RGB', (258, height - 4), (30, 30, 30))
        hist_data = ImageTools.pixbuf_to_pil(self.__pixbuf).histogram()
        maximum = max(hist_data[:768] + [1])
        y_scale = (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 colors
        for x in range(256):
            for y in range(1, max(r[x], g[x], b[x]) + 1):
                r_px = fill if y <= r[x] else 0
                g_px = fill if y <= g[x] else 0
                b_px = fill if y <= b[x] else 0
                im_data.putpixel((x + 1, height - 5 - y), (r_px, g_px, b_px))

        # Draw the outlines
        for x in range(1, 256):
            self._im_putpixel(x=x, channel=r, height=height, im_data=im_data)
            self._im_putpixel(x=x, channel=g, height=height, im_data=im_data)
            self._im_putpixel(x=x, channel=b, height=height, im_data=im_data)

        if config['ENHANCE_EXTRA']:
            # if True a label with the maximum pixel value will be added to one corner
            maxstr = f'max pixel value: {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))

        self.__hist_image.set_from_pixbuf(ImageTools.pil_to_pixbuf(im))
Пример #5
0
    def get_mime_name(self, page: int = None):
        """
        Return a string with the name of the mime type of <page>. If
        <page> is None, return the mime type name of the current page
        """

        page_path = self.get_path_to_page(page)
        if not Path.is_file(page_path):
            return None

        return ImageTools.get_image_mime(page_path)
Пример #6
0
    def get_size(self, page: int = None):
        """
        Return a tuple (width, height) with the size of <page>. If <page>
        is None, return the size of the current page
        """

        page_path = self.get_path_to_page(page)
        if not Path.is_file(page_path):
            return 0, 0

        return ImageTools.get_image_size(page_path)
Пример #7
0
    def enhance(self, pixbuf):
        """
        Return an "enhanced" version of <pixbuf>
        """

        if (self.brightness != 1.0 or self.contrast != 1.0 or
                self.saturation != 1.0 or self.sharpness != 1.0 or
                self.autocontrast):
            return ImageTools.enhance(pixbuf, self.brightness, self.contrast,
                                      self.saturation, self.sharpness, self.autocontrast)
        return pixbuf
Пример #8
0
    def _get_lens_pixbuf(self, x: int, y: int):
        """
        Get a pixbuf containing the appropiate image data for the lens
        where <x> and <y> are the positions of the cursor
        """

        canvas = GdkPixbuf.Pixbuf.new(colorspace=GdkPixbuf.Colorspace.RGB,
                                      has_alpha=True, bits_per_sample=8,
                                      width=config['LENS_SIZE'],
                                      height=config['LENS_SIZE'])
        canvas.fill(0x000000FF)  # black
        cb = self.__window.get_layout().get_content_boxes()
        source_pixbufs = self.__window.imagehandler.get_pixbufs(len(cb))
        for idx, item in enumerate(cb):
            if ImageTools.is_animation(source_pixbufs[idx]):
                continue
            cpos = cb[idx].get_position()
            self._add_subpixbuf(canvas, x - cpos[0], y - cpos[1], cb[idx].get_size(), source_pixbufs[idx])

        return ImageTools.add_border(canvas, 1)
Пример #9
0
    def _generate_thumbnail(self, uid: int):
        """
        Generate the pixbuf for C{path} at demand
        """

        size = config['THUMBNAIL_SIZE']
        pixbuf = self.__window.imagehandler.get_thumbnail(page=uid,
                                                          size=(size, size))
        if pixbuf is not None:
            pixbuf = ImageTools.add_border(pixbuf, self.__border_size)

        return pixbuf
Пример #10
0
    def _generate_thumbnail(self, uid: int):
        """
        Generate the pixbuf for C{path} at demand
        """

        size = config['THUMBNAIL_SIZE']
        pixbuf = self.__image_handler.get_thumbnail(page=uid,
                                                    size=(size, size))
        if pixbuf is None:
            return None

        return ImageTools.add_border(pixbuf)
Пример #11
0
    def _listed_contents(self, archive, files):
        if not self.__file_loading:
            return
        self.__file_loading = False

        archive_images = [
            image for image in files if ImageTools.is_image_file(Path(image))
            and not image.endswith('.pdf')
        ]

        self._sort_archive_images(archive_images)
        self.__extractor.set_files(archive_images)
        self._archive_opened(archive_images)
Пример #12
0
    def __call__(self, size: tuple, filepath: Path):
        """
        Returns a thumbnail pixbuf for <filepath>, transparently handling
        both normal image files and archives. Returns None if thumbnail creation
        failed, or if the thumbnail creation is run asynchrounosly

        :param size: The dimensions for the created thumbnails (width, height).
        :param filepath: Path to the image that the thumbnail is generated from.
        """

        try:
            with LockedFileIO(filepath) as fio:
                with Image.open(fio) as im:
                    im.thumbnail(size, resample=Image.BOX)
                    pixbuf = ImageTools.pil_to_pixbuf(im)
                    if ImageTools.pil_has_alpha(im):
                        pixbuf = ImageTools.add_alpha_background(pixbuf, pixbuf.get_width(), pixbuf.get_height())
        except Exception as ex:
            logger.error(f'Failed to create thumbnail for image: \'{filepath}\'')
            logger.error(f'Exception: {ex}')
            pixbuf = None

        return pixbuf
Пример #13
0
 def _cache_pixbuf(self, index: int, force_return: bool = True):
     with self.__cache_lock[index]:
         if index in self.__raw_pixbufs:
             return
         with self.__lock:
             if index not in self.__wanted_pixbufs and force_return:
                 return
         logger.debug(f'Caching page: \'{index + 1}\'')
         try:
             pixbuf = ImageTools.load_pixbuf(self.__image_files[index])
         except Exception as ex:
             logger.error(
                 f'Could not load pixbuf for page: \'{index + 1}\'')
             logger.error(f'Exception: {ex}')
             pixbuf = None
         self.__raw_pixbufs[index] = pixbuf
Пример #14
0
 def _cache_pixbuf(self, page: int, force_return: bool = True):
     with self.__cache_lock[page]:
         if page in self.__raw_pixbufs:
             return
         with self.__lock:
             if page not in self.__wanted_pixbufs and force_return:
                 return
         logger.debug(f'Caching page: {page}')
         try:
             pixbuf = ImageTools.load_pixbuf(
                 self.__image_files.get_path_from_page(page))
         except Exception as ex:
             logger.error(f'Could not load pixbuf for page: {page}')
             logger.error(f'Exception: {ex}')
             pixbuf = None
         self.__raw_pixbufs[page] = pixbuf
Пример #15
0
    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
Пример #16
0
    def _mcomix_icons(self):
        """
        Returns a list of differently sized pixbufs for the application icon
        """

        self.__mcomix_icons = [ImageTools.load_pixbuf_data(resources.read_binary('mcomix.images', 'mcomix.png'))]
Пример #17
0
 def set_thumbnail(self, pixbuf):
     pixbuf = ImageTools.add_border(pixbuf, 1)
     self.__thumb.set_from_pixbuf(pixbuf)
Пример #18
0
    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
Пример #19
0
    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)
Пример #20
0
    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
Пример #21
0
    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)