web-dev-qa-db-fra.com

Comment mettre à l'échelle une BufferedImage

Suite aux javadocs, j'ai essayé de mettre à l'échelle un BufferedImage sans succès voici mon code:

BufferedImage image = MatrixToImageWriter.getBufferedImage(encoded);
Graphics2D grph = image.createGraphics();
grph.scale(2.0, 2.0);
grph.dispose();

Je ne comprends pas pourquoi cela ne fonctionne pas, une aide?

40
Thiago Diniz

AffineTransformOp offre la flexibilité supplémentaire de choisir le type d'interpolation.

BufferedImage before = getBufferedImage(encoded);
int w = before.getWidth();
int h = before.getHeight();
BufferedImage after = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
AffineTransform at = new AffineTransform();
at.scale(2.0, 2.0);
AffineTransformOp scaleOp = 
   new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
after = scaleOp.filter(before, after);

Le fragment montré illustre rééchantillonnage , pas recadrage ; ce lien réponse résout le problème ; quelques exemples connexes sont examinés ici .

61
trashgod

Malheureusement, les performances de getScaledInstance () sont très médiocres sinon problématiques.

L'approche alternative consiste à créer une nouvelle BufferedImage et à dessiner une version à l'échelle de l'original sur la nouvelle.

BufferedImage resized = new BufferedImage(newWidth, newHeight, original.getType());
Graphics2D g = resized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(original, 0, 0, newWidth, newHeight, 0, 0, original.getWidth(),
    original.getHeight(), null);
g.dispose();

newWidth, newHeight indiquent la nouvelle taille de BufferedImage et doivent être correctement calculés. En cas de mise à l'échelle des facteurs:

int newWidth = new Double(original.getWidth() * widthFactor).intValue();
int newHeight = new Double(original.getHeight() * heightFactor).intValue();

[~ # ~] modifier [~ # ~] : a trouvé l'article illustrant le problème de performances: The Perils of Image.getScaledInstance ()

33
charisis

Comme le dit @Bozho, vous voudrez probablement utiliser getScaledInstance.

Cependant, pour comprendre le fonctionnement de grph.scale(2.0, 2.0), vous pouvez consulter ce code:

import Java.awt.*;
import Java.awt.image.BufferedImage;
import Java.io.*;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;


class Main {
    public static void main(String[] args) throws IOException {

        final int SCALE = 2;

        Image img = new ImageIcon("duke.png").getImage();

        BufferedImage bi = new BufferedImage(SCALE * img.getWidth(null),
                                             SCALE * img.getHeight(null),
                                             BufferedImage.TYPE_INT_ARGB);

        Graphics2D grph = (Graphics2D) bi.getGraphics();
        grph.scale(SCALE, SCALE);

        // everything drawn with grph from now on will get scaled.

        grph.drawImage(img, 0, 0, null);
        grph.dispose();

        ImageIO.write(bi, "png", new File("duke_double_size.png"));
    }
}

Étant donné duke.png:
enter image description here

il produit duke_double_size.png:
enter image description here

12
aioobe

Utilisation de imgscalr - Java Image Scaling Library :

BufferedImage image =
     Scalr.resize(originalImage, Scalr.Method.BALANCED, newWidth, newHeight);

C'est assez rapide pour moi.

9
ceklock

Si cela ne vous dérange pas d'utiliser une bibliothèque externe, Thumbnailator peut effectuer une mise à l'échelle de BufferedImage s.

Thumbnailator se chargera de gérer le traitement Java 2D (comme l'utilisation de Graphics2D et le réglage approprié conseils de rend ) afin qu'un simple appel d'API fluide puisse être utilisé pour redimensionner les images:

BufferedImage image = Thumbnails.of(originalImage).scale(2.0).asBufferedImage();

Bien que Thumbnailator, comme son nom l'indique, vise à réduire les images, il fera également un travail d'agrandissement décent, en utilisant l'interpolation bilinéaire dans son implémentation de redimensionnement par défaut.


Clause de non-responsabilité: je suis le responsable de la bibliothèque Thumbnailator .

6
coobird

Pour mettre une image à l'échelle, vous devez créer une nouvelle image et y dessiner. Une façon consiste à utiliser la méthode filter() d'un AffineTransferOp, comme suggéré ici . Cela vous permet de choisir la technique d'interpolation.

private static BufferedImage scale1(BufferedImage before, double scale) {
    int w = before.getWidth();
    int h = before.getHeight();
    // Create a new image of the proper size
    int w2 = (int) (w * scale);
    int h2 = (int) (h * scale);
    BufferedImage after = new BufferedImage(w2, h2, BufferedImage.TYPE_INT_ARGB);
    AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
    AffineTransformOp scaleOp 
        = new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);

    scaleOp.filter(before, after);
    return after;
}

Une autre façon consiste à simplement dessiner l'image d'origine dans la nouvelle image, en utilisant une opération de mise à l'échelle pour effectuer la mise à l'échelle. Cette méthode est très similaire, mais elle illustre également comment vous pouvez dessiner tout ce que vous voulez dans l'image finale. (J'ai mis une ligne vierge où les deux méthodes commencent à différer.)

private static BufferedImage scale2(BufferedImage before, double scale) {
    int w = before.getWidth();
    int h = before.getHeight();
    // Create a new image of the proper size
    int w2 = (int) (w * scale);
    int h2 = (int) (h * scale);
    BufferedImage after = new BufferedImage(w2, h2, BufferedImage.TYPE_INT_ARGB);
    AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
    AffineTransformOp scaleOp 
        = new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);

    Graphics2D g2 = (Graphics2D) after.getGraphics();
    // Here, you may draw anything you want into the new image, but we're
    // drawing a scaled version of the original image.
    g2.drawImage(before, scaleOp, 0, 0);
    g2.dispose();
    return after;
}

Addendum: Résultats

Pour illustrer les différences, j'ai comparé les résultats des cinq méthodes ci-dessous. Voici à quoi ressemblent les résultats, augmentés et diminués, ainsi que les données de performances. (Les performances varient d'une exécution à l'autre, alors ne prenez ces chiffres qu'à titre indicatif.) L'image du haut est l'original. Je le redimensionne en double et en demi-taille.

Comme vous pouvez le voir, AffineTransformOp.filter(), utilisée dans scaleBilinear(), est plus rapide que la méthode de dessin standard de Graphics2D.drawImage() dans scale2(). L'interpolation BiCubic est également la plus lente, mais donne les meilleurs résultats lors du développement de l'image. (Pour les performances, il ne doit être comparé qu'avec scaleBilinear() et scaleNearest().) Bilinéaire semble être préférable pour réduire l'image, bien que ce soit un appel difficile. Et NearestNeighbors est le plus rapide, avec les pires résultats. Bilinéaire semble être le meilleur compromis entre vitesse et qualité. La Image.getScaledInstance(), appelée dans la méthode questionable(), fonctionnait très mal et renvoyait la même qualité médiocre que NearestNeighbor. (Les chiffres de performance ne sont donnés que pour agrandir l'image.)

enter image description here

public static BufferedImage scaleBilinear(BufferedImage before, double scale) {
    final int interpolation = AffineTransformOp.TYPE_BILINEAR;
    return scale(before, scale, interpolation);
}

public static BufferedImage scaleBicubic(BufferedImage before, double scale) {
    final int interpolation = AffineTransformOp.TYPE_BICUBIC;
    return scale(before, scale, interpolation);
}

public static BufferedImage scaleNearest(BufferedImage before, double scale) {
    final int interpolation = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
    return scale(before, scale, interpolation);
}

@NotNull
private static 
BufferedImage scale(final BufferedImage before, final double scale, final int type) {
    int w = before.getWidth();
    int h = before.getHeight();
    int w2 = (int) (w * scale);
    int h2 = (int) (h * scale);
    BufferedImage after = new BufferedImage(w2, h2, before.getType());
    AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
    AffineTransformOp scaleOp = new AffineTransformOp(scaleInstance, type);
    scaleOp.filter(before, after);
    return after;
}

/**
 * This is a more generic solution. It produces the same result, but it shows how you 
 * can draw anything you want into the newly created image. It's slower 
 * than scaleBilinear().
 * @param before The original image
 * @param scale The scale factor
 * @return A scaled version of the original image
 */
private static BufferedImage scale2(BufferedImage before, double scale) {
    int w = before.getWidth();
    int h = before.getHeight();
    // Create a new image of the proper size
    int w2 = (int) (w * scale);
    int h2 = (int) (h * scale);
    BufferedImage after = new BufferedImage(w2, h2, before.getType());
    AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
    AffineTransformOp scaleOp
            = new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);

    Graphics2D g2 = (Graphics2D) after.getGraphics();
    // Here, you may draw anything you want into the new image, but we're just drawing
    // a scaled version of the original image. This is slower than 
    // calling scaleOp.filter().
    g2.drawImage(before, scaleOp, 0, 0);
    g2.dispose();
    return after;
}

/**
 * I call this one "questionable" because it uses the questionable getScaledImage() 
 * method. This method is no longer favored because it's slow, as my tests confirm.
 * @param before The original image
 * @param scale The scale factor
 * @return The scaled image.
 */
private static Image questionable(final BufferedImage before, double scale) {
    int w2 = (int) (before.getWidth() * scale);
    int h2 = (int) (before.getHeight() * scale);
    return before.getScaledInstance(w2, h2, Image.SCALE_FAST);
}
3
MiguelMunoz

scale(..) fonctionne un peu différemment. Vous pouvez utiliser bufferedImage.getScaledInstance(..)

3
Bozho