wake-up-neo.com

So skalieren Sie ein BufferedImage

Im Anschluss an die Javadocs habe ich versucht, ein BufferedImage zu skalieren, ohne Erfolg. Hier ist mein Code:

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

Ich kann nicht verstehen, warum es nicht funktioniert, irgendeine Hilfe?

40
Thiago Diniz

AffineTransformOp bietet die zusätzliche Flexibilität, den Interpolationstyp zu wählen.

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);

Das abgebildete Fragment zeigt Resampling , nicht Cropping ; Dieses verwandte Antwort behandelt das Problem ; einige verwandte Beispiele werden untersucht hier .

61
trashgod

Leider ist die Leistung von getScaledInstance () sehr schlecht, wenn nicht problematisch.

Der alternative Ansatz besteht darin, ein neues gepuffertes Bild zu erstellen und eine skalierte Version des Originals auf dem neuen zu zeichnen.

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 geben die neue BufferedImage-Größe an und müssen ordnungsgemäß berechnet werden. Im Falle einer Faktorskalierung:

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

[~ # ~] edit [~ # ~] : Den Artikel gefunden, der das Leistungsproblem veranschaulicht: The Perils of Image.getScaledInstance ()

33
charisis

Wie @Bozho sagt, möchten Sie wahrscheinlich getScaledInstance verwenden.

Um zu verstehen, wie grph.scale(2.0, 2.0) funktioniert, können Sie sich diesen Code ansehen:

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"));
    }
}

Gegeben duke.png:
enter image description here

es erzeugt duke_double_size.png:
enter image description here

12
aioobe

Verwenden von imgscalr - Java Image Scaling Library :

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

Das ist schnell genug für mich.

9
ceklock

Wenn es Ihnen nichts ausmacht, eine externe Bibliothek zu verwenden, können Sie mit ThumbnailatorBufferedImage s skalieren.

Thumbnailator kümmert sich um die Verarbeitung von Java 2D (z. B. mit Graphics2D und entsprechende Rendering-Hinweise ) festlegen, damit die Größe von Bildern mit einem einfachen, fließenden API-Aufruf geändert werden kann:

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

Obwohl Thumbnailator, wie der Name schon sagt, auf das Verkleinern von Bildern ausgerichtet ist, können Bilder auch in angemessener Weise vergrößert werden, indem die bilineare Interpolation in der Standard-Resizer-Implementierung verwendet wird.


Haftungsausschluss: Ich bin der Betreuer der Thumbnailator Bibliothek.

6
coobird

Um ein Bild zu skalieren, müssen Sie ein neues Bild erstellen und darin zeichnen. Eine Möglichkeit besteht darin, die filter() -Methode eines AffineTransferOp zu verwenden, wie vorgeschlagen hier . Auf diese Weise können Sie die Interpolationstechnik auswählen.

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;
}

Eine andere Möglichkeit besteht darin, das Originalbild einfach mit Hilfe einer Skalierungsoperation in das neue Bild zu zeichnen. Diese Methode ist sehr ähnlich, zeigt aber auch, wie Sie im endgültigen Bild alles zeichnen können, was Sie wollen. (Ich habe eine leere Zeile eingefügt, in der sich die beiden Methoden zu unterscheiden beginnen.)

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;
}

Nachtrag: Ergebnisse

Um die Unterschiede zu veranschaulichen, habe ich die Ergebnisse der folgenden fünf Methoden verglichen. So sehen die Ergebnisse aus, skaliert sowohl nach oben als auch nach unten, zusammen mit Leistungsdaten. (Die Leistung variiert von Lauf zu Lauf. Verwenden Sie diese Zahlen daher nur als grobe Richtlinie.) Das obere Bild ist das Original. Ich skaliere es doppelt und halb.

Wie Sie sehen können, ist AffineTransformOp.filter(), das in scaleBilinear() verwendet wird, schneller als die Standardzeichnungsmethode von Graphics2D.drawImage() in scale2(). Auch die BiCubic-Interpolation ist am langsamsten, liefert jedoch die besten Ergebnisse beim Erweitern des Bildes. (Für die Leistung sollte es nur mit scaleBilinear() und scaleNearest(). verglichen werden.) Bilinear scheint besser für das Verkleinern des Bildes zu sein, obwohl es eine schwierige Aufgabe ist. Und NearestNeighbor ist der Schnellste mit den schlechtesten Ergebnissen. Bilinear scheint der beste Kompromiss zwischen Geschwindigkeit und Qualität zu sein. Die Image.getScaledInstance(), die in der questionable() -Methode aufgerufen wurde, lief sehr schlecht und gab dieselbe niedrige Qualität zurück wie NearestNeighbor. (Performance-Nummern werden nur zum Erweitern des Bildes angegeben.)

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(..) funktioniert etwas anders. Sie können bufferedImage.getScaledInstance(..) verwenden

3
Bozho