Comment améliorer les performances de la méthode G. drawImage () pour redimensionner les images

J'ai une application où les utilisateurs peuvent télécharger des images dans des albums, mais naturellement les images téléchargées doivent être redimensionnées, donc il y a aussi des pouces disponibles et les images montrées tiennent également dans la page (par exemple. 800x600). La façon dont je fais le redimensionnement est comme ceci:

Image scaledImage = img.getScaledInstance((int)width, (int)height, Image.SCALE_SMOOTH);
BufferedImage imageBuff = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_RGB);
Graphics g = imageBuff.createGraphics();
g.drawImage(scaledImage, 0, 0, new Color(0,0,0), null);
g.dispose();

Et ça marche okayish. Mon seul problème est que la méthode g.drawImage() semble terriblement lente, et je ne peux pas imaginer que l'utilisateur soit assez patient pour attendre un téléchargement de 20 images 20 * 10 secondes ~ 3 minutes. Dans fait, sur mon ordinateur, il faut presque 40 secondes pour faire les 3 redimensionnements différents pour une seule image.

Ce n'est pas assez bon, et je cherche une solution plus rapide. Je me demande si quelqu'un pourrait me parler d'un meilleur en Java ou en appelant un script shell, une commande, quel que soit le hack que vous connaissez, il doit être plus rapide, tout le reste n'a pas d'importance cette fois.

45
demandé sur Braiam 2010-10-19 15:14:41

12 réponses

Vous pouvez utiliser ImageMagick à créer des vignettes.

convert -define jpeg:size=500x180  hatching_orig.jpg  -auto-orient \
        -thumbnail 250x90   -unsharp 0x.5  thumbnail.gif

Pour l'utiliser à partir de Java, vous pouvez essayer JMagick qui fournit une interface Java (JNI) à ImageMagick. Ou vous pouvez simplement appeler les commandes ImageMagick directement en utilisant Runtime.exec ou ProcessBuilder.

10
répondu dogbane 2010-10-19 11:26:15

J'utilise un code similaire à ce qui suit pour mettre à l'échelle les images, j'ai supprimé la partie qui traite de la préservation du rapport d'aspect. La performance était certainement meilleure que 10s par image, mais je ne me souviens pas de chiffres exacts. Pour archiver une meilleure qualité lors de la conversion, vous devriez échelle en plusieurs étapes si l'image originale est plus de deux fois la taille de la vignette, chaque étape doit échelle de l'image précédente à environ la moitié de sa taille.

public static BufferedImage getScaledImage(BufferedImage image, int width, int height) throws IOException {
    int imageWidth  = image.getWidth();
    int imageHeight = image.getHeight();

    double scaleX = (double)width/imageWidth;
    double scaleY = (double)height/imageHeight;
    AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
    AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR);

    return bilinearScaleOp.filter(
        image,
        new BufferedImage(width, height, image.getType()));
}
25
répondu Jörn Horstmann 2012-05-11 08:38:07

Avez-vous vraiment besoin de la qualité fournie en utilisant L'Image.SCALE_SMOOTH? Si vous ne le faites pas, vous pouvez essayer d'utiliser Image.SCALE_FAST . Vous pourriez trouver cet article utile si vous voulez vous en tenir à quelque chose fourni par Java.

14
répondu Klarth 2010-10-19 11:32:24

Eh bien, Jacob et moi voulions redimensionner une Image, pas un BufferedImage. Nous avons donc fini avec ce code:

/**
 * we want the x and o to be resized when the JFrame is resized
 *
 * @param originalImage an x or an o. Use cross or oh fields.
 *
 * @param biggerWidth
 * @param biggerHeight
 */
private Image resizeToBig(Image originalImage, int biggerWidth, int biggerHeight) {
    int type = BufferedImage.TYPE_INT_ARGB;


    BufferedImage resizedImage = new BufferedImage(biggerWidth, biggerHeight, type);
    Graphics2D g = resizedImage.createGraphics();

    g.setComposite(AlphaComposite.Src);
    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    g.drawImage(originalImage, 0, 0, biggerWidth, biggerHeight, this);
    g.dispose();


    return resizedImage;
}
11
répondu johnstosh 2012-07-07 00:47:48

Le point principal de la question était sur la performance de la mise à l'échelle des images en Java. Les autres réponses ont montré des approches différentes, sans les évaluer davantage. J'étais curieux à ce sujet aussi, alors j'ai essayé d'écrire un petit test de performance. Cependant, le test de l'image mise à l'échelle de performance fiable, sensiblement et objectivement est difficile. Il y a beaucoup trop de facteurs d'infusion à prendre en compte:

  • la taille de l'image d'entrée
  • La taille de l'image de sortie
  • l'interpolation (c'est-à-dire "qualité": voisin le plus proche, bilinéaire, bicubique)
  • Le BufferedImage.TYPE_* de l'image d'entrée
  • Le BufferedImage.TYPE_* de l'image de sortie
  • la version JVM et le système d'exploitation
  • enfin: la méthode qui est réellement utilisée pour effectuer l'opération.

J'ai essayé de couvrir ceux que je considérais comme les plus importants. La configuration était:

  • Le l'entrée est une photo simple, "Moyenne" (en particulier, cette "Image du jour" de Wikipedia, avec une taille de 2560x1706 pixels)

  • Les principaux types d'interpolation sont testés - à savoir, en utilisant RenderingHints, la INTERPOLATION clé a été définie pour les valeurs NEAREST_NEIGHBOR, BILINEAR et BICUBIC

  • L'image d'entrée a été convertie en différents types:

    • BufferedImage.TYPE_INT_RGB: Un type qui est couramment utilisé, car il "habituellement" affiche la meilleure performance caractéristiques

    • BufferedImage.TYPE_3BTE_BGR: C'est le type qu'il est en lecture par défaut, quand il suffit de la lire avec ImageIO

  • La Taille de l'image cible a varié entre une largeur de 10000 (donc, mise à l'échelle de l'image vers le haut) et 100 (ainsi, mise à l'échelle de l'image vers le bas à la taille des vignettes)

Les tests ont été exécutés sur un Win64 / AMD K10 avec 3.7 GHz et JDK 1. 8u31, avec -Xmx4000m -server.

Les méthodes testées sont:

  • en utilisant un AffineTransformOp, comme dans la réponse de Jörn Horstmann
  • en utilisant un Graphics, comme dans la réponse de johnstosh
  • en utilisant Image#getScaledInstance

Le code des tests est montré ici:

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Supplier;

import javax.imageio.ImageIO;
import javax.swing.JLabel;

public class ImageScalingPerformance
{
    private static int blackHole = 0;

    public static void main(String[] args) throws IOException
    {
        // Image with size 2560 x 1706, from https://upload.wikimedia.org/
        //   wikipedia/commons/4/41/Pitta_moluccensis_-_Kaeng_Krachan.jpg
        BufferedImage image = ImageIO.read(
            new File("Pitta_moluccensis_-_Kaeng_Krachan.jpg"));

        int types[] =
        {
            BufferedImage.TYPE_3BYTE_BGR,
            BufferedImage.TYPE_INT_RGB,
        };
        Object interpolationValues[] =
        {
            RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR,
            RenderingHints.VALUE_INTERPOLATION_BILINEAR,
            RenderingHints.VALUE_INTERPOLATION_BICUBIC,
        };
        int widths[] =
        {
            10000, 5000, 2500, 1000, 500, 100
        };


        System.out.printf("%10s%22s%6s%18s%10s\n",
            "Image type", "Interpolation", "Size", "Method", "Duration (ms)");

        for (int type : types)
        {
            BufferedImage currentImage = convert(image, type);
            for (Object interpolationValue : interpolationValues)
            {
                for (int width : widths)
                {
                    List<Supplier<Image>> tests = 
                        createTests(currentImage, interpolationValue, width);

                    for (Supplier<Image> test : tests)
                    {
                        double durationMs = computeMs(test);

                        System.out.printf("%10s%22s%6s%18s%10s\n",
                            stringForBufferedImageType(type),
                            stringForInterpolationValue(interpolationValue),
                            String.valueOf(width), 
                            String.valueOf(test),
                            String.format(Locale.ENGLISH, "%6.3f", durationMs));
                    }
                }
            }
        }
        System.out.println(blackHole);
    }

    private static List<Supplier<Image>> createTests(
        BufferedImage image, Object interpolationValue, int width)
    {
        RenderingHints renderingHints = new RenderingHints(null);
        renderingHints.put(
            RenderingHints.KEY_INTERPOLATION, 
            interpolationValue);
        double scale = (double) width / image.getWidth();
        int height = (int)(scale * image.getHeight());

        Supplier<Image> s0 = new Supplier<Image>()
        {
            @Override
            public BufferedImage get()
            {
                return scaleWithAffineTransformOp(
                    image, width, height, renderingHints);
            }

            @Override
            public String toString()
            {
                return "AffineTransformOp";
            }
        };

        Supplier<Image> s1 = new Supplier<Image>()
        {
            @Override
            public Image get()
            {
                return scaleWithGraphics(
                    image, width, height, renderingHints);
            }

            @Override
            public String toString()
            {
                return "Graphics";
            }
        };

        Supplier<Image> s2 = new Supplier<Image>()
        {
            @Override
            public Image get()
            {
                return scaleWithGetScaledInstance(
                    image, width, height, renderingHints);
            }

            @Override
            public String toString()
            {
                return "GetScaledInstance";
            }
        };

        List<Supplier<Image>> tests = new ArrayList<Supplier<Image>>();
        tests.add(s0);
        tests.add(s1);
        tests.add(s2);
        return tests;
    }

    private static double computeMs(Supplier<Image> supplier)
    {
        int runs = 5;
        long before = System.nanoTime();
        for (int i=0; i<runs; i++)
        {
            Image image0 = supplier.get();
            blackHole += image0.hashCode();
        }
        long after = System.nanoTime();
        double durationMs = (after-before) / 1e6 / runs;
        return durationMs;
    }

    private static BufferedImage convert(BufferedImage image, int type)
    {
        BufferedImage newImage = new BufferedImage(
            image.getWidth(), image.getHeight(), type);
        Graphics2D g = newImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return newImage;
    }        

    private static BufferedImage scaleWithAffineTransformOp(
        BufferedImage image, int w, int h,
        RenderingHints renderingHints)
    {
        BufferedImage scaledImage = new BufferedImage(w, h, image.getType());
        double scaleX = (double) w / image.getWidth();
        double scaleY = (double) h / image.getHeight();
        AffineTransform affineTransform = 
            AffineTransform.getScaleInstance(scaleX, scaleY);
        AffineTransformOp affineTransformOp = new AffineTransformOp(
            affineTransform, renderingHints);
        return affineTransformOp.filter(
            image, scaledImage);
    }

    private static BufferedImage scaleWithGraphics(
        BufferedImage image, int w, int h,
        RenderingHints renderingHints) 
    {
        BufferedImage scaledImage = new BufferedImage(w, h, image.getType());
        Graphics2D g = scaledImage.createGraphics();
        g.setRenderingHints(renderingHints);
        g.drawImage(image, 0, 0, w, h, null);
        g.dispose();
        return scaledImage;
    }

    private static Image scaleWithGetScaledInstance(
        BufferedImage image, int w, int h,
        RenderingHints renderingHints)
    {
        int hint = Image.SCALE_REPLICATE;
        if (renderingHints.get(RenderingHints.KEY_ALPHA_INTERPOLATION) != 
            RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
        {
            hint = Image.SCALE_AREA_AVERAGING;
        }
        Image scaledImage = image.getScaledInstance(w, h, hint);
        MediaTracker mediaTracker = new MediaTracker(new JLabel());
        mediaTracker.addImage(scaledImage, 0);
        try
        {
            mediaTracker.waitForAll();
        }
        catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
        }
        return scaledImage;
    }

    private static String stringForBufferedImageType(int type)
    {
        switch (type)
        {
            case BufferedImage.TYPE_INT_RGB : return "INT_RGB";
            case BufferedImage.TYPE_INT_ARGB : return "INT_ARGB";
            case BufferedImage.TYPE_INT_ARGB_PRE : return "INT_ARGB_PRE";
            case BufferedImage.TYPE_INT_BGR : return "INT_BGR";
            case BufferedImage.TYPE_3BYTE_BGR : return "3BYTE_BGR";
            case BufferedImage.TYPE_4BYTE_ABGR : return "4BYTE_ABGR";
            case BufferedImage.TYPE_4BYTE_ABGR_PRE : return "4BYTE_ABGR_PRE";
            case BufferedImage.TYPE_USHORT_565_RGB : return "USHORT_565_RGB";
            case BufferedImage.TYPE_USHORT_555_RGB : return "USHORT_555_RGB";
            case BufferedImage.TYPE_BYTE_GRAY : return "BYTE_GRAY";
            case BufferedImage.TYPE_USHORT_GRAY : return "USHORT_GRAY";
            case BufferedImage.TYPE_BYTE_BINARY : return "BYTE_BINARY";
            case BufferedImage.TYPE_BYTE_INDEXED : return "BYTE_INDEXED";
        }
        return "CUSTOM";
    }

    private static String stringForInterpolationValue(Object value)
    {
        if (value == RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
        {
            return "NEAREST/REPLICATE";
        }
        if (value == RenderingHints.VALUE_INTERPOLATION_BILINEAR)
        {
            return "BILINEAR/AREA_AVG";
        }
        if (value == RenderingHints.VALUE_INTERPOLATION_BICUBIC)
        {
            return "BICUBIC/AREA_AVG";
        }
        return "(unknown)";
    }


}

Tout d'abord, en ce qui concerne getScaledInstance: comme Chris Campbell l'a souligné dans son (célèbre) article sur les périls de l'Image.getScaledInstance () (qui était déjà lié à dans d'autres réponses), la méthode Image#getScaledInstance est quelque peu cassée et a une mauvaise performance pour la plupart configuration. De plus, il présente l'inconvénient de ne pas avoir un contrôle aussi fin sur le type d'interpolation. ceci doit être pris en compte dans la comparaison de performances suivante: la qualité {[33] } des images résultantes peut différer, ce qui n'est pas considéré ici. Par exemple, la méthode de "moyenne de surface" de getScaledInstance ne donne pas une bonne qualité d'image lorsque la taille de l'image est augmentée.

(l'inconvénient le plus grave de Image#getScaledInstance est À mon humble avis qu'il ne délivre Image, et pas un BufferedImage, mais si l'image est supposé être peint dans un Graphics, cela peut ne pas être important)

Je vais juste vider la sortie du programme ici pour référence, quelques détails suivront ci-dessous:

Image type         Interpolation  Size            MethodDuration (ms)
 3BYTE_BGR     NEAREST/REPLICATE 10000 AffineTransformOp   197.287
 3BYTE_BGR     NEAREST/REPLICATE 10000          Graphics   184.427
 3BYTE_BGR     NEAREST/REPLICATE 10000 GetScaledInstance  1869.759
 3BYTE_BGR     NEAREST/REPLICATE  5000 AffineTransformOp    38.354
 3BYTE_BGR     NEAREST/REPLICATE  5000          Graphics    40.220
 3BYTE_BGR     NEAREST/REPLICATE  5000 GetScaledInstance  1088.448
 3BYTE_BGR     NEAREST/REPLICATE  2500 AffineTransformOp    10.153
 3BYTE_BGR     NEAREST/REPLICATE  2500          Graphics     9.461
 3BYTE_BGR     NEAREST/REPLICATE  2500 GetScaledInstance   613.030
 3BYTE_BGR     NEAREST/REPLICATE  1000 AffineTransformOp     2.137
 3BYTE_BGR     NEAREST/REPLICATE  1000          Graphics     1.956
 3BYTE_BGR     NEAREST/REPLICATE  1000 GetScaledInstance   464.989
 3BYTE_BGR     NEAREST/REPLICATE   500 AffineTransformOp     0.861
 3BYTE_BGR     NEAREST/REPLICATE   500          Graphics     0.750
 3BYTE_BGR     NEAREST/REPLICATE   500 GetScaledInstance   407.751
 3BYTE_BGR     NEAREST/REPLICATE   100 AffineTransformOp     0.206
 3BYTE_BGR     NEAREST/REPLICATE   100          Graphics     0.153
 3BYTE_BGR     NEAREST/REPLICATE   100 GetScaledInstance   385.863
 3BYTE_BGR     BILINEAR/AREA_AVG 10000 AffineTransformOp   830.097
 3BYTE_BGR     BILINEAR/AREA_AVG 10000          Graphics  1501.290
 3BYTE_BGR     BILINEAR/AREA_AVG 10000 GetScaledInstance  1627.934
 3BYTE_BGR     BILINEAR/AREA_AVG  5000 AffineTransformOp   207.816
 3BYTE_BGR     BILINEAR/AREA_AVG  5000          Graphics   376.789
 3BYTE_BGR     BILINEAR/AREA_AVG  5000 GetScaledInstance  1063.942
 3BYTE_BGR     BILINEAR/AREA_AVG  2500 AffineTransformOp    52.362
 3BYTE_BGR     BILINEAR/AREA_AVG  2500          Graphics    95.041
 3BYTE_BGR     BILINEAR/AREA_AVG  2500 GetScaledInstance   612.660
 3BYTE_BGR     BILINEAR/AREA_AVG  1000 AffineTransformOp     9.121
 3BYTE_BGR     BILINEAR/AREA_AVG  1000          Graphics    15.749
 3BYTE_BGR     BILINEAR/AREA_AVG  1000 GetScaledInstance   452.578
 3BYTE_BGR     BILINEAR/AREA_AVG   500 AffineTransformOp     2.593
 3BYTE_BGR     BILINEAR/AREA_AVG   500          Graphics     4.237
 3BYTE_BGR     BILINEAR/AREA_AVG   500 GetScaledInstance   407.661
 3BYTE_BGR     BILINEAR/AREA_AVG   100 AffineTransformOp     0.275
 3BYTE_BGR     BILINEAR/AREA_AVG   100          Graphics     0.297
 3BYTE_BGR     BILINEAR/AREA_AVG   100 GetScaledInstance   381.835
 3BYTE_BGR      BICUBIC/AREA_AVG 10000 AffineTransformOp  3015.943
 3BYTE_BGR      BICUBIC/AREA_AVG 10000          Graphics  5431.703
 3BYTE_BGR      BICUBIC/AREA_AVG 10000 GetScaledInstance  1654.424
 3BYTE_BGR      BICUBIC/AREA_AVG  5000 AffineTransformOp   756.136
 3BYTE_BGR      BICUBIC/AREA_AVG  5000          Graphics  1359.288
 3BYTE_BGR      BICUBIC/AREA_AVG  5000 GetScaledInstance  1063.467
 3BYTE_BGR      BICUBIC/AREA_AVG  2500 AffineTransformOp   189.953
 3BYTE_BGR      BICUBIC/AREA_AVG  2500          Graphics   341.039
 3BYTE_BGR      BICUBIC/AREA_AVG  2500 GetScaledInstance   615.807
 3BYTE_BGR      BICUBIC/AREA_AVG  1000 AffineTransformOp    31.351
 3BYTE_BGR      BICUBIC/AREA_AVG  1000          Graphics    55.914
 3BYTE_BGR      BICUBIC/AREA_AVG  1000 GetScaledInstance   451.808
 3BYTE_BGR      BICUBIC/AREA_AVG   500 AffineTransformOp     8.422
 3BYTE_BGR      BICUBIC/AREA_AVG   500          Graphics    15.028
 3BYTE_BGR      BICUBIC/AREA_AVG   500 GetScaledInstance   408.626
 3BYTE_BGR      BICUBIC/AREA_AVG   100 AffineTransformOp     0.703
 3BYTE_BGR      BICUBIC/AREA_AVG   100          Graphics     0.825
 3BYTE_BGR      BICUBIC/AREA_AVG   100 GetScaledInstance   382.610
   INT_RGB     NEAREST/REPLICATE 10000 AffineTransformOp   330.445
   INT_RGB     NEAREST/REPLICATE 10000          Graphics   114.656
   INT_RGB     NEAREST/REPLICATE 10000 GetScaledInstance  2784.542
   INT_RGB     NEAREST/REPLICATE  5000 AffineTransformOp    83.081
   INT_RGB     NEAREST/REPLICATE  5000          Graphics    29.148
   INT_RGB     NEAREST/REPLICATE  5000 GetScaledInstance  1117.136
   INT_RGB     NEAREST/REPLICATE  2500 AffineTransformOp    22.296
   INT_RGB     NEAREST/REPLICATE  2500          Graphics     7.735
   INT_RGB     NEAREST/REPLICATE  2500 GetScaledInstance   436.779
   INT_RGB     NEAREST/REPLICATE  1000 AffineTransformOp     3.859
   INT_RGB     NEAREST/REPLICATE  1000          Graphics     2.542
   INT_RGB     NEAREST/REPLICATE  1000 GetScaledInstance   205.863
   INT_RGB     NEAREST/REPLICATE   500 AffineTransformOp     1.413
   INT_RGB     NEAREST/REPLICATE   500          Graphics     0.963
   INT_RGB     NEAREST/REPLICATE   500 GetScaledInstance   156.537
   INT_RGB     NEAREST/REPLICATE   100 AffineTransformOp     0.160
   INT_RGB     NEAREST/REPLICATE   100          Graphics     0.074
   INT_RGB     NEAREST/REPLICATE   100 GetScaledInstance   126.159
   INT_RGB     BILINEAR/AREA_AVG 10000 AffineTransformOp  1019.438
   INT_RGB     BILINEAR/AREA_AVG 10000          Graphics  1230.621
   INT_RGB     BILINEAR/AREA_AVG 10000 GetScaledInstance  2721.918
   INT_RGB     BILINEAR/AREA_AVG  5000 AffineTransformOp   254.616
   INT_RGB     BILINEAR/AREA_AVG  5000          Graphics   308.374
   INT_RGB     BILINEAR/AREA_AVG  5000 GetScaledInstance  1269.898
   INT_RGB     BILINEAR/AREA_AVG  2500 AffineTransformOp    68.137
   INT_RGB     BILINEAR/AREA_AVG  2500          Graphics    80.163
   INT_RGB     BILINEAR/AREA_AVG  2500 GetScaledInstance   444.968
   INT_RGB     BILINEAR/AREA_AVG  1000 AffineTransformOp    13.093
   INT_RGB     BILINEAR/AREA_AVG  1000          Graphics    15.396
   INT_RGB     BILINEAR/AREA_AVG  1000 GetScaledInstance   211.929
   INT_RGB     BILINEAR/AREA_AVG   500 AffineTransformOp     3.238
   INT_RGB     BILINEAR/AREA_AVG   500          Graphics     3.689
   INT_RGB     BILINEAR/AREA_AVG   500 GetScaledInstance   159.688
   INT_RGB     BILINEAR/AREA_AVG   100 AffineTransformOp     0.329
   INT_RGB     BILINEAR/AREA_AVG   100          Graphics     0.277
   INT_RGB     BILINEAR/AREA_AVG   100 GetScaledInstance   127.905
   INT_RGB      BICUBIC/AREA_AVG 10000 AffineTransformOp  4211.287
   INT_RGB      BICUBIC/AREA_AVG 10000          Graphics  4712.587
   INT_RGB      BICUBIC/AREA_AVG 10000 GetScaledInstance  2830.749
   INT_RGB      BICUBIC/AREA_AVG  5000 AffineTransformOp  1069.088
   INT_RGB      BICUBIC/AREA_AVG  5000          Graphics  1182.285
   INT_RGB      BICUBIC/AREA_AVG  5000 GetScaledInstance  1155.663
   INT_RGB      BICUBIC/AREA_AVG  2500 AffineTransformOp   263.003
   INT_RGB      BICUBIC/AREA_AVG  2500          Graphics   297.663
   INT_RGB      BICUBIC/AREA_AVG  2500 GetScaledInstance   444.497
   INT_RGB      BICUBIC/AREA_AVG  1000 AffineTransformOp    42.841
   INT_RGB      BICUBIC/AREA_AVG  1000          Graphics    48.605
   INT_RGB      BICUBIC/AREA_AVG  1000 GetScaledInstance   209.261
   INT_RGB      BICUBIC/AREA_AVG   500 AffineTransformOp    11.004
   INT_RGB      BICUBIC/AREA_AVG   500          Graphics    12.407
   INT_RGB      BICUBIC/AREA_AVG   500 GetScaledInstance   156.794
   INT_RGB      BICUBIC/AREA_AVG   100 AffineTransformOp     0.817
   INT_RGB      BICUBIC/AREA_AVG   100          Graphics     0.790
   INT_RGB      BICUBIC/AREA_AVG   100 GetScaledInstance   128.700

On peut voir que pour presque tous les cas, getScaledInstance fonctionne mal par rapport aux autres approches (et les quelques cas où il semble mieux fonctionner peuvent s'expliquer par la qualité inférieure lors de la mise à l'échelle jusqu').

L'approche basée sur AffineTransformOp semble fonctionner le mieux en moyenne, à la seule exception notable étant une mise à l'échelle NEAREST_NEIGHBOR des images TYPE_INT_RGB, où l'approche basée sur Graphics semble être constamment plus rapide.

L'essentiel est: la méthode utilisant AffineTransformOp, comme dans la réponse de Jörn Horstmann, semble être celle qui offre les meilleures performances pour la plupart des cas d'application.

9
répondu Marco13 2017-05-23 12:10:45

Cela fonctionne pour moi:

private BufferedImage getScaledImage(BufferedImage src, int w, int h){
    int original_width = src.getWidth();
    int original_height = src.getHeight();
    int bound_width = w;
    int bound_height = h;
    int new_width = original_width;
    int new_height = original_height;

    // first check if we need to scale width
    if (original_width > bound_width) {
        //scale width to fit
        new_width = bound_width;
        //scale height to maintain aspect ratio
        new_height = (new_width * original_height) / original_width;
    }

    // then check if we need to scale even with the new height
    if (new_height > bound_height) {
        //scale height to fit instead
        new_height = bound_height;
        //scale width to maintain aspect ratio
        new_width = (new_height * original_width) / original_height;
    }

    BufferedImage resizedImg = new BufferedImage(new_width, new_height, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2 = resizedImg.createGraphics();
    g2.setBackground(Color.WHITE);
    g2.clearRect(0,0,new_width, new_height);
    g2.drawImage(src, 0, 0, new_width, new_height, null);
    g2.dispose();
    return resizedImg;
}

J'ai aussi ajouté un fond blanc pour png

5
répondu dpineda 2014-09-09 21:57:30

Le moyen le plus rapide de mettre à l'échelle une image en java sans perdre la qualité d'image est d'utiliser la mise à l'échelle bilinéaire. Bilinéaire n'est bon que si vous redimensionnez l'image de 50% à la fois en raison de la façon dont il fonctionne. le code suivant provient de 'Filthy rich clients' de Chet Haase. Il explique plusieurs techniques dans le livre, mais celui-ci a la plus haute performance au compromis de qualité.

Il prend en charge tous les types de BufferedImages, alors ne vous inquiétez pas de la compatibilité. Il permet également au matériel java2D Accélérez votre image car les calculs sont effectués par Java2D. ne vous inquiétez pas si vous ne comprenez pas cette dernière partie. La chose la plus importante est que c'est le moyen le plus rapide de le faire.

public static BufferedImage getFasterScaledInstance(BufferedImage img, int targetWidth, int targetHeight, boolean progressiveBilinear)
{
    int type = (img.getTransparency() == Transparency.OPAQUE) ? 
            BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
    BufferedImage ret = (BufferedImage) img;
    BufferedImage scratchImage = null;
    Graphics2D g2 = null;
    int w, h;
    int prevW = ret.getWidth();
    int prevH = ret.getHeight();
    if(progressiveBilinear) {
        w = img.getWidth();
        h = img.getHeight();
    }else{
        w = targetWidth;
        h = targetHeight;
    }
    do {
        if (progressiveBilinear && w > targetWidth) {
            w /= 2;
            if(w < targetWidth) {
                w = targetWidth;
            }
        }

        if (progressiveBilinear && h > targetHeight) {
            h /= 2;
            if (h < targetHeight) {
                h = targetHeight;
            }
        }

        if(scratchImage == null) {
            scratchImage = new BufferedImage(w, h, type);
            g2 = scratchImage.createGraphics();
        }
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null);
        prevW = w;
        prevH = h;
        ret = scratchImage;
    } while (w != targetWidth || h != targetHeight);

    if (g2 != null) {
        g2.dispose();
    }

    if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) {
        scratchImage = new BufferedImage(targetWidth, targetHeight, type);
        g2 = scratchImage.createGraphics();
        g2.drawImage(ret, 0, 0, null);
        g2.dispose();
        ret = scratchImage;
    }
    System.out.println("ret is "+ret);
    return ret;
}
5
répondu nemo 2016-04-11 22:40:05

Vous aurez jamais un compromis entre la vitesse du redimensionnement et la qualité de l'image résultante. Vous pouvez essayer un autre algorithme de mise à l'échelle du JDK.

L'outil le meilleur et le plus flexible pour l'édition D'image AFAIK est ImageMagick .

Il existe deux interfaces pour le langage Java:

  • JMagick - est une Interface JNI pour ImageMagick. Voir les projets Wiki pour obtenir plus d'informations.
  • im4java - est une commande interface de ligne pour ImageMagick. Ce n'est pas, comme JMagick, basé sur JNI.

Vous devriez préférer im4java avant d'utiliser directement la ligne de commande pour appeler ImageMagick.

3
répondu echox 2014-02-07 15:45:35

Ancienne question mais au cas où quelqu'un d'autre rencontrerait ce problème: j'ai profilé votre code et votre plus grand goulot d'étranglement est l'appel à:

Image.getScaledInstance()

Cet appel est bien connu pour être horriblement lent. Soyez convaincu en lisant ce document:

Les périls de L'Image.getScaledInstance()

La solution la plus simple/la meilleure pour une amélioration spectaculaire des performances serait de remplacer cet appel. Vous pouvez utiliser la méthode de la réponse de dpineda (voir sa réponse / code ci-dessus):

private BufferedImage getScaledImage(BufferedImage src, int w, int h){

J'ai testé sa méthode et ça marche vraiment bien. Dans mes tests, son implémentation (ce qui évite l'Image lente.getScaledInstance()) Rasé 80% du temps de traitement!

2
répondu Pierre 2016-06-14 14:14:18

Une certaine amélioration des performances (peut-être petite, peut-être négligeable, peut-être au détriment de la qualité) peut être obtenue en peaufinant les indices de rendu. Par exemple

    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 
               RenderingHints.VALUE_INTERPOLATION_BILINEAR);
1
répondu leonbloy 2010-10-19 16:26:28

Si vous voulez quelque chose de rapide, vous êtes probablement mieux avec du code natif, si vous pouvez abandonner la portabilité.

Mais si vous voulez une solution Java pure, vous pouvez essayer d'autres solutions, comme Graphics2D. scale et Image.getScaledInstance . Je les ai utilisés dans le passé, mais je ne me souviens pas qui avait de meilleures performances ou de meilleurs résultats, désolé.

Essayez-les et voyez lequel correspond le mieux à vos besoins.

0
répondu mdrg 2010-10-19 11:38:22

J'ai utilisé im4java avec GraphicsMagick afin d'avoir vraiment vite des résultats (plus rapide que ImageIO).

Utilisé ce genre de code :

public static void createFilePreview(final File originalFile, final String originalFileMimeType, final File destinationPreviewFile, final Integer maxWidth, final Integer maxHeight) throws IOException, InterruptedException, IM4JavaException {
    runThumbnail(new ConvertCmd(), originalFile.getAbsolutePath(), originalFileMimeType, destinationPreviewFile.getAbsolutePath(), maxWidth, maxHeight);
}

public static void createFilePreview(final InputStream originalFileInputStream, final String originalFileMimeType, final File destinationPreviewFile, final Integer maxWidth, final Integer maxHeight) throws IOException, InterruptedException, IM4JavaException {
    final ConvertCmd cmd = new ConvertCmd();

    cmd.setInputProvider(new Pipe(originalFileInputStream, null));

    runThumbnail(cmd, "-", originalFileMimeType, destinationPreviewFile.getAbsolutePath(), maxWidth, maxHeight);
}

private static void runThumbnail(final ConvertCmd cmd, final String originalFile, final String originalFileMimeType, final String destinationPreviewFile, final Integer maxWidth, final Integer maxHeight) throws IOException, InterruptedException, IM4JavaException {
    final IMOperation operation = new IMOperation();
    // if it is a PDF, will add some optional parameters to get nicer results
    if (originalFileMimeType.startsWith("application/pdf")) {
        operation.define("pdf:use-trimbox=true");   // as it is said here http://www.prepressure.com/pdf/basics/page_boxes "The imposition programs and workflows that I know all use the TrimBox as the basis for positioning pages on a press sheet."
        operation.density(300, 300);    // augment the rendering from 75 (screen size) to 300 dpi in order to create big preview with good quality
    }
    operation.addImage("[0]");  // if it is a PDF or other multiple image source, will extract the first page / image, else it is ignored
    operation.autoOrient(); // Auto-orient the image if it contains some orientation information (typically JPEG with EXIF header)
    operation.thumbnail(maxWidth, maxHeight);
    operation.addImage();

    cmd.run(operation, originalFile, destinationPreviewFile);
}
0
répondu Anthony O. 2014-03-20 14:59:34