【问题标题】:Scale a BufferedImage the fastest and easiest way以最快和最简单的方式缩放 BufferedImage
【发布时间】:2023-03-25 07:30:01
【问题描述】:

任务: 我有一些图像,我将它们按比例缩小,然后将它们连接到一个图像中。但是我的实现有点小问题:

具体问题: 我想调整/缩放 BufferedImage。 getScaledInstance 方法返回一个 Image 对象,但我无法将其转换为 BufferedImage:

Exception in thread "main" java.lang.ClassCastException: sun.awt.image.ToolkitImage cannot be cast to java.awt.image.BufferedImage

(我不知道为什么它是 ToolkitImage 而不是 Image...)

我找到了解决办法:

Image tmp = bi.getScaledInstance(SMALL_SIZE, SMALL_SIZE, BufferedImage.SCALE_FAST);
BufferedImage buffered = new BufferedImage(SMALL_SIZE,SMALL_SIZE,BufferedImage.TYPE_INT_RGB);
buffered.getGraphics().drawImage(tmp, 0, 0, null);

但是速度很慢,我认为应该有更好的方法来做到这一点。

我需要 BufferedImage,因为我必须让像素加入小图像。

有更好(更好/更快)的方法吗?

编辑: 如果我首先将 Image 转换为 ToolkitImage,它有一个 getBufferedImage() 方法。但它总是返回 null。你知道为什么吗?

【问题讨论】:

标签: java image image-processing image-scaling


【解决方案1】:

Graphics 对象具有绘制Image 的方法,同时还执行调整大小操作:

Graphics.drawImage(Image, int, int, int, int, ImageObserver)方法可用于在绘图时指定位置以及图像的大小。

所以,我们可以使用这样的一段代码:

BufferedImage otherImage = // .. created somehow
BufferedImage newImage = new BufferedImage(SMALL_SIZE, SMALL_SIZE, BufferedImage.TYPE_INT_RGB);

Graphics g = newImage.createGraphics();
g.drawImage(otherImage, 0, 0, SMALL_SIZE, SMALL_SIZE, null);
g.dispose();

这将采用otherImage 并将其绘制在newImage 上,宽度和高度为SMALL_SIZE


或者,如果您不介意使用库,Thumbnailator 也可以这样做:

BufferedImage newImage = Thumbnails.of(otherImage)
                             .size(SMALL_SIZE, SMALL_SIZE)
                             .asBufferedImage();

Thumbnailator 还可以比使用 Image.getScaledInstance 更快地执行调整大小操作,同时执行比仅使用 Graphics.drawImage 更高质量的调整大小操作。

免责声明:我是 Thumbnailator 库的维护者。

【讨论】:

  • @coobird 首先,感谢库,我会尝试一下,但如果可能的话,我想用标准 java 解决这个问题。
  • 是否可以使用 Thumbnailator 获得更高质量的图像?图形会破坏图像,甚至在 300 像素后都不会显示..
  • 我需要在缩放的原始图像上多次绘制一些形状,这段代码非常适合!
  • @JFreeman 最初的问题是关于调整BufferedImage 的大小以得到BufferedImage ;)
【解决方案2】:

您还可以使用 OpenCV Java 库。它的resize操作比Imgscalr的快:

测试

图像 5184 x 3456 缩放到 150 x 100(这是较小的版本,因为原始文件大于 2mb):

Imgscalr

依赖:

<dependency>
    <groupId>org.imgscalr</groupId>
    <artifactId>imgscalr-lib</artifactId>
    <version>4.2</version>
</dependency>

代码:

BufferedImage thumbnail = Scalr.resize(img,
            Scalr.Method.SPEED,
            Scalr.Mode.AUTOMATIC,
            150,
            100);

结果图片:

平均时间:80 毫秒

OpenCV

依赖:

<dependency>
    <groupId>nu.pattern</groupId>
    <artifactId>opencv</artifactId>
    <version>2.4.9-4</version>
</dependency>

将 BufferedImage 转换为 Mat 对象(必须):

BufferedImage img = ImageIO.read(image); // load image
byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer())
            .getData();
Mat matImg = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC3);
matImg.put(0, 0, pixels);

代码:

Imgproc.resize(matImg, resizeimage, sz);

附加配置(适用于windows):

opencv_java249.dll 添加到 JDK 的 bin 目录中。

结果图片:

平均时间:13 毫秒

总体结果

在测试中,只计算“调整大小”函数的时间。 Imgscalr 以 80 毫秒调整给定图像的大小,而 OpenCV 以 13 毫秒完成相同的任务。你可以在下面找到整个项目来玩一下。

正如您所问的简单方法,如果 Imgscalr 库的性能对您有好处,那么它非常容易。因为要使用 OpenCV,您看到的库文件必须位于您的所有开发环境和服务器中。您还必须使用 Mat 对象。

整个项目

Pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.btasdemir</groupId>
    <artifactId>testapp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>testapp</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.imgscalr</groupId>
            <artifactId>imgscalr-lib</artifactId>
            <version>4.2</version>
        </dependency>

        <dependency>
            <groupId>nu.pattern</groupId>
            <artifactId>opencv</artifactId>
            <version>2.4.9-4</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin> 
                <groupId>org.bytedeco</groupId> 
                <artifactId>javacpp</artifactId> 
                <version>0.9</version> 
            </plugin>
        </plugins>
    </build>

</project>

App.java:

package com.btasdemir.testapp;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.imgscalr.Scalr;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;


/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args ) throws IOException
    {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

        File image = new File("C:\\your_dir\\test.jpg");
        BufferedImage img = ImageIO.read(image); // load image
        long startTime = System.currentTimeMillis();//imgscalr------------------------------------------------------
        //resize to 150 pixels max
        BufferedImage thumbnail = Scalr.resize(img,
                Scalr.Method.SPEED,
                Scalr.Mode.AUTOMATIC,
                150,
                100);
//      BufferedImage thumbnail = Scalr.resize(img,
//                Scalr.Method.SPEED,
//                Scalr.Mode.AUTOMATIC,
//                150,
//                100,
//                Scalr.OP_ANTIALIAS);
        System.out.println(calculateElapsedTime(startTime));//END-imgscalr------------------------------------------------------
        File outputfile = new File("C:\\your_dir\\imgscalr_result.jpg");
        ImageIO.write(thumbnail, "jpg", outputfile);


        img = ImageIO.read(image); // load image
        byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer())
                .getData();
        Mat matImg = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC3);
        matImg.put(0, 0, pixels);

        Mat resizeimage = new Mat();
        Size sz = new Size(150, 100);
        startTime = System.currentTimeMillis();//opencv------------------------------------------------------

        Imgproc.resize(matImg, resizeimage, sz);
//      Imgproc.resize(matImg, resizeimage, sz, 0.5, 0.5, Imgproc.INTER_CUBIC);

        System.out.println(calculateElapsedTime(startTime));//END-opencv------------------------------------------------------
        Highgui.imwrite("C:\\your_dir\\opencv_result.jpg", resizeimage);


    }

    protected static long calculateElapsedTime(long startTime) {
        long stopTime = System.currentTimeMillis();
        long elapsedTime = stopTime - startTime;
        return elapsedTime;
    }
}

【讨论】:

  • 非常感谢!很棒的帖子。我发现并正在使用 Image Scalr 库,很容易从谷歌下载 jar,但如果你发布一个链接会很好:)
【解决方案3】:

我用这种方法得到它,它会调整图像的大小并尝试保持比例:

/**
* Resizes an image using a Graphics2D object backed by a BufferedImage.
* @param srcImg - source image to scale
* @param w - desired width
* @param h - desired height
* @return - the new resized image
*/
private BufferedImage getScaledImage(BufferedImage src, int w, int h){
    int finalw = w;
    int finalh = h;
    double factor = 1.0d;
    if(src.getWidth() > src.getHeight()){
        factor = ((double)src.getHeight()/(double)src.getWidth());
        finalh = (int)(finalw * factor);                
    }else{
        factor = ((double)src.getWidth()/(double)src.getHeight());
        finalw = (int)(finalh * factor);
    }   

    BufferedImage resizedImg = new BufferedImage(finalw, finalh, BufferedImage.TRANSLUCENT);
    Graphics2D g2 = resizedImg.createGraphics();
    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g2.drawImage(src, 0, 0, finalw, finalh, null);
    g2.dispose();
    return resizedImg;
}

【讨论】:

    【解决方案4】:

    这些答案对我来说都不够快。所以我终于编写了自己的程序。

    static BufferedImage scale(BufferedImage src, int w, int h)
    {
      BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
      int x, y;
      int ww = src.getWidth();
      int hh = src.getHeight();
      for (x = 0; x < w; x++) {
        for (y = 0; y < h; y++) {
          int col = src.getRGB(x * ww / w, y * hh / h);
          img.setRGB(x, y, col);
        }
      }
      return img;
    }
    

    【讨论】:

    • 请注意,例如,如果src 图像是黑白条纹,而wh2w=2ww2h=2hh,则结果是黑色图像。发生这种情况是因为上面的算法每第二个像素取一个,所以它不取任何白色像素。为了避免它需要过滤图像的高频
    • 是的,这种方法存在混叠问题,如果速度高于质量,则可以。
    • 如果速度是您的唯一目标,是否值得在循环之前预先计算 ww/w 和 hh/h(循环中的值不会改变)?
    【解决方案5】:

    也许这种方法会有所帮助:

    public  BufferedImage resizeImage(BufferedImage image, int width, int height) {
             int type=0;
            type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
            BufferedImage resizedImage = new BufferedImage(width, height,type);
            Graphics2D g = resizedImage.createGraphics();
            g.drawImage(image, 0, 0, width, height, null);
            g.dispose();
            return resizedImage;
         }
    

    不要忘记那些“导入”行:

    import java.awt.Graphics2D;
    import java.awt.image.BufferedImage;
    

    关于选角:

    抽象类Image是所有表示图形图像的类的超类。 我们不能将Image 转换为BufferedImage,因为每个BufferedImage 都是Image,但反之亦然。

    Image im = new BufferedImage(width, height, imageType);//this is true
    
    BufferedImage img = new Image(){//.....}; //this is wrong
    

    【讨论】:

    • 感谢您的回答。如果可能的话,我想完全跳过绘图。是否可以从图像中获取像素颜色数据?
    • @tamas.pflanzner 你能看到this post,我认为这是关于从Image 获取像素颜色
    • 你确定吗?我认为这是关于从 BufferedImage 获取像素 :(
    【解决方案6】:
    public static double[] reduceQuality(int quality, int width, int height) {
        if(quality >= 1 && quality <= 100) {
            double[] dims = new double[2];
            dims[0] = width * (quality/100.0);
            dims[1] = height * (quality/100.0);
            return dims;
        } else if(quality > 100) {
            return new double[] { width, height };
        } else {
            return new double[] { 1, 1 };
        }
    }
    
    public static byte[] resizeImage(byte[] data, int width, int height) throws Exception {
        BufferedImage bi = ImageIO.read(new ByteArrayInputStream(data));
        BufferedImage bo = resizeImage(bi, width, height);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(bo, "jpg", bos);
        bos.close();
        return bos.toByteArray();
    }
    
    private static BufferedImage resizeImage(BufferedImage buf, int width, int height) {
        final BufferedImage bufImage = new BufferedImage(width, height, 
                (buf.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB
                : BufferedImage.TYPE_INT_ARGB));
        final Graphics2D g2 = bufImage.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        g2.drawImage(buf, 0, 0, width, height, null);
        g2.dispose();
        return bufImage;
    }
    

    这是直接取自 https://github.com/rkalla/imgscalr/blob/master/src/main/java/org/imgscalr/Scalr.java 的 imgscalr

    我降低 5152x3864 尺寸的 8mb 图像质量的平均时间约为 800 毫秒。

    没有依赖关系。我恨他们。有时。

    这仅适用于 jpg 图像。就我而言。

    例子:

    byte[] of = Files.readAllBytes(Paths.get("/home/user/Pictures/8mbsample.jpg"));
        double[] wh = ImageUtil.reduceQuality(2, 6600, 4950);
    
        long start = System.currentTimeMillis();
        byte[] sof = ImageUtil.resizeImage(of, (int)wh[0], (int)wh[1]);
        long end = System.currentTimeMillis();
    
        if(!Files.exists(Paths.get("/home/user/Pictures/8mbsample_scaled.jpg"))) {
            Files.createFile(Paths.get("/home/user/Pictures/8mbsample_scaled.jpg"), Util.getFullPermissions());
        }
    
        FileOutputStream fos = new FileOutputStream("/home/user/Pictures/8mbsample_scaled.jpg");
        fos.write(sof); fos.close();
    
        System.out.println("Process took: " + (end-start) + "ms");
    

    输出:

    Process took: 783ms
    

    【讨论】:

      猜你喜欢
      • 2019-12-02
      • 2019-11-29
      • 1970-01-01
      • 2018-01-15
      • 2015-08-13
      • 2011-06-21
      • 2011-01-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多