def scale_proportionally(pixbuf, w, h, shrink_only=True): width, height = pixbuf.get_width(), pixbuf.get_height() scale = min(w / float(width), h / float(height)) if shrink_only and scale >= 1: return pixbuf new_width, new_height = int(width * scale), int(height * scale) new_width = max(new_width, 1) new_height = max(new_height, 1) return pixbuf.scale_simple(new_width, new_height, GdkPixbuf.InterpType.BILINEAR)
def load_background(filename, bloatmax=BLOAT_MAX_SIZE): """Load a pixbuf, testing it for suitability as a background :param str filename: Full path to the filename to load. :param int bloatmax: Repeat up to this size :rtype: tuple The returned tuple is a pair ``(PIXBUF, ERRORS)``, where ``ERRORS`` is a list of localized strings describing the errors encountered, and ``PIXBUF`` contains the loaded background pixbuf. If there were errors, ``PIXBUF`` is None. The MyPaint rendering engine can only manage background layers which fit into its tile structure. Formerly, only background images with dimensions which were exact multiples of the tile size were permitted. We have a couple of workarounds now: * "Bloating" the background by repetition (pixel-perfect) * Scaling the image down to fit (distorts the image) """ filename_display = _filename_to_display(filename) load_errors = [] try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) except Exception as ex: logger.error("Failed to load background %r: %s", filename, ex) msg = unicode( _('Gdk-Pixbuf couldn\'t load "{filename}", and reported "{error}"') ) load_errors.append( msg.format( filename=filename_display, error=repr(ex), )) return (None, load_errors) # Validity check w, h = pixbuf.get_width(), pixbuf.get_height() if w == 0 or h == 0: msg = unicode(_("{filename} has zero size (w={w}, h={h})")) load_errors.append(msg.format( filename=filename_display, w=w, h=h, )) return (None, load_errors) # Flatten if pixbuf.get_has_alpha(): logger.warning( "%r has an alpha channel, which should be removed manually", filename, ) new_pixbuf = new_blank_pixbuf((0, 0, 0), w, h) pixbuf.composite( dest=new_pixbuf, dest_x=0, dest_y=0, dest_width=w, dest_height=h, offset_x=0, offset_y=0, scale_x=1.0, scale_y=1.0, interp_type=GdkPixbuf.InterpType.NEAREST, overall_alpha=255, ) pixbuf = new_pixbuf logger.debug( "Flattened %s by compositing it onto a black backdrop", filename, ) # Attempt to fit the image into our grid. exact_fit = ((w % N, h % N) == (0, 0)) if not exact_fit: logger.warning( "%r (%dx%d) does not fit the %dx%d tile grid exactly", filename, w, h, N, N, ) repeats_x = _best_nrepeats_for_scaling(w, bloatmax) repeats_y = _best_nrepeats_for_scaling(h, bloatmax) if repeats_x > 1 or repeats_y > 1: logger.info( "Tiling %r to %dx%d (was: %dx%d, repeats: %d vert, %d horiz)", filename, w * repeats_x, h * repeats_y, w, h, repeats_x, repeats_y, ) pixbuf = _tile_pixbuf(pixbuf, repeats_x, repeats_y) w, h = pixbuf.get_width(), pixbuf.get_height() if (w % N != 0) or (h % N != 0): orig_w, orig_h = w, h w = max(1, w // N) * N h = max(1, h // N) * N logger.info( "Scaling %r to %dx%d (was: %dx%d)", filename, w, h, orig_w, orig_h, ) pixbuf = pixbuf.scale_simple( dest_width=w, dest_height=h, interp_type=GdkPixbuf.InterpType.BILINEAR, ) assert (w % N == 0) and (h % N == 0) if load_errors: pixbuf = None return pixbuf, load_errors
w, h = pixbuf.get_width(), pixbuf.get_height() if (w % N != 0) or (h % N != 0): orig_w, orig_h = w, h w = max(1, w // N) * N h = max(1, h // N) * N logger.info( "Scaling %r to %dx%d (was: %dx%d)", filename, w, h, orig_w, orig_h, ) pixbuf = pixbuf.scale_simple( dest_width=w, dest_height=h, interp_type=GdkPixbuf.InterpType.BILINEAR, ) assert (w % N == 0) and (h % N == 0) if load_errors: pixbuf = None return pixbuf, load_errors def _tile_pixbuf(pixbuf, repeats_x, repeats_y): """Make a repeated tiled image of a pixbuf""" w, h = pixbuf.get_width(), pixbuf.get_height() result = new_blank_pixbuf((0, 0, 0), repeats_x * w, repeats_y * h) for xi in xrange(repeats_x): for yi in xrange(repeats_y): pixbuf.copy_area(0, 0, w, h, result, w * xi, h * yi)
def load_background(filename, bloatmax=BLOAT_MAX_SIZE): """Load a pixbuf, testing it for suitability as a background :param str filename: Full path to the filename to load. :param int bloatmax: Repeat up to this size :rtype: tuple The returned tuple is a pair ``(PIXBUF, ERRORS)``, where ``ERRORS`` is a list of localized strings describing the errors encountered, and ``PIXBUF`` contains the loaded background pixbuf. If there were errors, ``PIXBUF`` is None. The MyPaint rendering engine can only manage background layers which fit into its tile structure. Formerly, only background images with dimensions which were exact multiples of the tile size were permitted. We have a couple of workarounds now: * "Bloating" the background by repetition (pixel-perfect) * Scaling the image down to fit (distorts the image) """ filename_display = _filename_to_display(filename) load_errors = [] try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) except Exception as ex: logger.error("Failed to load background %r: %s", filename, ex) msg = unicode(_( 'Gdk-Pixbuf couldn\'t load "{filename}", and reported "{error}"' )) load_errors.append(msg.format( filename=filename_display, error=repr(ex), )) return (None, load_errors) # Validity check w, h = pixbuf.get_width(), pixbuf.get_height() if w == 0 or h == 0: msg = unicode(_("{filename} has zero size (w={w}, h={h})")) load_errors.append(msg.format( filename=filename_display, w=w, h=h, )) return (None, load_errors) # Flatten if pixbuf.get_has_alpha(): logger.warning( "%r has an alpha channel, which should be removed manually", filename, ) new_pixbuf = new_blank_pixbuf((0, 0, 0), w, h) pixbuf.composite( dest=new_pixbuf, dest_x=0, dest_y=0, dest_width=w, dest_height=h, offset_x=0, offset_y=0, scale_x=1.0, scale_y=1.0, interp_type=GdkPixbuf.InterpType.NEAREST, overall_alpha=255, ) pixbuf = new_pixbuf logger.debug( "Flattened %s by compositing it onto a black backdrop", filename, ) # Attempt to fit the image into our grid. exact_fit = ((w % N, h % N) == (0, 0)) if not exact_fit: logger.warning( "%r (%dx%d) does not fit the %dx%d tile grid exactly", filename, w, h, N, N, ) repeats_x = _best_nrepeats_for_scaling(w, bloatmax) repeats_y = _best_nrepeats_for_scaling(h, bloatmax) if repeats_x > 1 or repeats_y > 1: logger.info( "Tiling %r to %dx%d (was: %dx%d, repeats: %d vert, %d horiz)", filename, w * repeats_x, h * repeats_y, w, h, repeats_x, repeats_y, ) pixbuf = _tile_pixbuf(pixbuf, repeats_x, repeats_y) w, h = pixbuf.get_width(), pixbuf.get_height() if (w % N != 0) or (h % N != 0): orig_w, orig_h = w, h w = max(1, w // N) * N h = max(1, h // N) * N logger.info( "Scaling %r to %dx%d (was: %dx%d)", filename, w, h, orig_w, orig_h, ) pixbuf = pixbuf.scale_simple( dest_width=w, dest_height=h, interp_type=GdkPixbuf.InterpType.BILINEAR, ) assert (w % N == 0) and (h % N == 0) if load_errors: pixbuf = None return pixbuf, load_errors
repeats_x, repeats_y, ) pixbuf = _tile_pixbuf(pixbuf, repeats_x, repeats_y) w, h = pixbuf.get_width(), pixbuf.get_height() if (w % N != 0) or (h % N != 0): orig_w, orig_h = w, h w = max(1, w//N) * N h = max(1, h//N) * N logger.info( "Scaling %r to %dx%d (was: %dx%d)", filename, w, h, orig_w, orig_h, ) pixbuf = pixbuf.scale_simple( dest_width=w, dest_height=h, interp_type=GdkPixbuf.InterpType.BILINEAR, ) assert (w % N == 0) and (h % N == 0) if load_errors: pixbuf = None return pixbuf, load_errors def _tile_pixbuf(pixbuf, repeats_x, repeats_y): """Make a repeated tiled image of a pixbuf""" w, h = pixbuf.get_width(), pixbuf.get_height() result = new_blank_pixbuf((0, 0, 0), repeats_x * w, repeats_y * h) for xi in xrange(repeats_x): for yi in xrange(repeats_y): pixbuf.copy_area(0, 0, w, h, result, w*xi, h*yi) return result