Подтвердить что ты не робот

Как повысить производительность метода g.drawImage() для изменения размеров изображений

У меня есть приложение, в котором пользователи могут загружать фотографии в альбомы, но, естественно, загружаемые изображения необходимо изменить, чтобы также были доступны большие пальцы, а показанные изображения также попали на страницу (например, 800x600). Способ изменения размера выглядит следующим образом:

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

И он работает нормально. Моя единственная проблема заключается в том, что метод g.drawImage() кажется ужасно медленным, и я просто не могу представить, чтобы пользователь был достаточно терпелив, чтобы дождаться загрузки 20 изображений 20 * 10 секунд ~ 3 минуты. Фактически, на моем компьютере требуется около 40 секунд для создания трех разных размеров для одной картинки.

Это не очень хорошо, и я ищу более быстрое решение. Мне интересно, может ли кто-нибудь рассказать мне о лучшем в Java OR, вызвав команду script, команду, какой бы хак вы не знали, это должно быть быстрее, все остальное не имеет значения на этот раз.

4b9b3361

Ответ 1

Вы можете использовать ImageMagick для создавать эскизы,

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

Чтобы использовать его с Java, вы можете попробовать JMagick, который предоставляет интерфейс Java (JNI) для ImageMagick. Или вы можете просто вызвать команды ImageMagick напрямую, используя Runtime.exec или ProcessBuilder.

Ответ 2

Я использую код, похожий на следующий, для масштабирования изображений, я удалил часть, которая имеет дело с сохранением соотношения сторон. Производительность была определенно лучше, чем 10 с на изображение, но я не помню никаких точных цифр. Чтобы архивировать лучшее качество при масштабировании, вы должны масштабироваться в несколько этапов, если исходное изображение более чем в два раза превышает размер требуемого эскиза, каждый шаг должен масштабировать предыдущее изображение примерно до половины его размера.

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

Ответ 3

Вам действительно нужно качество, предоставляемое с помощью Image.SCALE_SMOOTH? Если вы этого не сделаете, вы можете попробовать Image.SCALE_FAST. Вы можете найти эту статью полезной, если хотите придерживаться чего-то, предоставляемого Java.

Ответ 4

Ну, мы с Джейкобом хотели изменить размер изображения, а не BufferedImage. Итак, мы закончили с этим кодом:

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

Ответ 5

Основная проблема заключалась в производительности масштабирования изображений в Java. Другие ответы показали разные подходы, не оценивая их дальше. Мне также было интересно об этом, поэтому я попытался написать небольшой тест производительности. Тем не менее, тестирование масштабирования изображения надежно, разумно и объективно сложно. Слишком много факторов, которые необходимо учитывать:

  • Размер входного изображения
  • Размер выходного изображения
  • Интерполяция (т.е. "качество": ближайший сосед, билинейный, бикубический)
  • BufferedImage.TYPE_* входного изображения
  • BufferedImage.TYPE_* выходного изображения
  • Версия JVM и операционная система
  • Наконец: метод, который фактически используется для выполнения операции.

Я попытался охватить те, которые я считал самыми важными. Настройка была:

  • Вход представляет собой простую "среднюю" фотографию (в частности, это "изображение дня" из Википедии, с размером 2560x1706 пикселей)

  • Проверяются основные типы интерполяции, а именно, используя RenderingHints, где для ключа INTERPOLATION были установлены значения NEAREST_NEIGHBOR, BILINEAR и BICUBIC

  • Входное изображение было преобразовано в разные типы:

    • BufferedImage.TYPE_INT_RGB: тип, который обычно используется, поскольку он "обычно" показывает лучшие характеристики производительности

    • BufferedImage.TYPE_3BTE_BGR: Это тип, который он читает по умолчанию, когда просто читал его с помощью ImageIO

  • Размер целевого изображения варьировался между шириной 10000 (таким образом, масштабированием изображения вверх) и 100 (таким образом, масштабируя изображение до размера миниатюр)

Тесты выполнялись на Win64/AMD K10 с 3.7 ГГц и JDK 1.8u31, с -Xmx4000m -server.

Проверенные методы:

  • Использование AffineTransformOp, как в ответе Йорна Хорстмана
  • Используя Graphics, как в ответе от johnstosh
  • Использование Image#getScaledInstance

Код тестов показан здесь:

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


}

Во-первых, относительно getScaledInstance: Как сказал Крис Кэмпбелл в своей (известной) статье о The Perils of Image.getScaledInstance() (что уже было связано с другими ответами), метод Image#getScaledInstance несколько разбит и имеет большую неприятную производительность для большинства конфигураций. Кроме того, он имеет недостаток в том, что он не имеет такого мелкозернистого контроля над типом интерполяции. Это следует учитывать при следующем сравнении производительности. Качество полученных изображений может отличаться, что не рассматривается здесь. Например, метод "усреднения области" getScaledInstance не дает хорошего качества изображения при увеличении размера изображения.

(Самый серьезный недостаток Image#getScaledInstance - это ИМХО, что он поставляет только Image, а не a BufferedImage, но если изображение должно быть расписано только в Graphics, это может быть неважно)

Я просто выгружу вывод программы здесь для справки, некоторые подробности будут приведены ниже:

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

Можно видеть, что почти во всех случаях getScaledInstance работает плохо по сравнению с другими подходами (и несколько случаев, когда он кажется лучше, можно объяснить более низким качеством при масштабировании).

Подход, основанный на AffineTransformOp, как представляется, лучше всего работает в среднем, причем единственным заметным исключением является масштабирование NEAREST_NEIGHBOR изображений TYPE_INT_RGB, где подход на основе Graphics, как представляется, последовательно быстрее.

Суть в следующем: метод, использующий AffineTransformOp, как в ответе Йорна Хорстмана, кажется, тот, который предлагает лучшую производительность для большинства приложений случаев.

Ответ 6

Самый быстрый способ масштабирования изображения в java без потери качества изображения - использовать Bilinear scaling. Bilinear только хорош, если вы масштабируете изображение на 50% за раз из-за того, как он работает. Следующий код от "Filthy rich clients" от Чет Хаазе. Он объясняет несколько методов в книге, но у этого есть наивысшая эффективность для качественного компромисса.

Он поддерживает все типы BufferedImages, поэтому не беспокойтесь о совместимости. Он также позволяет аппарату java2D ускорить ваш образ, потому что вычисления выполняются с помощью Java2D. Не беспокойтесь, если вы не понимаете эту последнюю часть. Самое главное, что это самый быстрый способ сделать это.

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

Ответ 7

это работает для меня:

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

Также я добавил белый фон для png

Ответ 8

У вас всегда будет компромисс между скоростью изменения размера и качеством результирующего изображения. Вы можете попробовать другой алгоритм масштабирования JDK.

Лучший и самый гибкий инструмент для редактирования изображений AFAIK ImageMagick.

Для языка Java существуют два интерфейса:

  • JMagick - это интерфейс JNI для ImageMagick. Для получения дополнительной информации см. Проекты Wiki.
  • im4java - это интерфейс командной строки для ImageMagick. Это не так, как JMagick, основанный на JNI.

Вы должны предпочесть im4java, прежде чем использовать командную строку для вызова ImageMagick.

Ответ 9

Некоторое улучшение производительности (возможно, небольшое, возможно, незначительное, возможно, за счет качества) может быть достигнуто путем настройки подсказок рендеринга. Например.

    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 
               RenderingHints.VALUE_INTERPOLATION_BILINEAR);

Ответ 10

Старый вопрос, но в случае, если кто-то еще сталкивается с этой проблемой: я профилировал ваш код, а ваше самое узкое место - вызов:

Image.getScaledInstance()

Этот вызов, как известно, ужасно медленный. Пожалуйста, убедитесь, прочитав этот документ:

Опасности Image.getScaledInstance()

Простейшим/лучшим решением для резкого повышения производительности будет замена этого вызова. Вы можете использовать метод из ответа dpineda (см. Его ответ/код выше):

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

Я протестировал его метод, и он работает очень хорошо. В моем тестировании его реализация (которая позволяет избежать медленного Image.getScaledInstance()), побрит 80% времени обработки!

Ответ 11

Если вы хотите что-то быстро, вы, вероятно, лучше с каким-то собственным кодом, если можете отказаться от переносимости.

Но если вы хотите чистое решение Java, вы можете попробовать и другие решения, например Graphics2D.scale и Image.getScaledInstance. Я использовал их в прошлом, но не могу вспомнить, у кого была лучшая производительность или лучшие результаты, извините.

Попробуйте их и посмотрите, какой из них лучше всего соответствует вашим потребностям.

Ответ 12

Я использовал im4java с GraphicsMagick для того, чтобы иметь действительно более быстрые результаты (быстрее, чем ImageIO).

Используется этот код:

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