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
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()
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()
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))
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)
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)
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
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)
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
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)
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)
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
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
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
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 _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'))]
def set_thumbnail(self, pixbuf): pixbuf = ImageTools.add_border(pixbuf, 1) self.__thumb.set_from_pixbuf(pixbuf)
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))) 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)
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))) 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)