java - 如何将 BufferedImage 保存为低于特定大小

标签 java bufferedimage java-2d java-8

(使用 java 8)鉴于图像用户需要能够指定最小/最大图像大小(以像素为单位)以及保存图像的最大大小(以 kbs 为单位),图像将保存为 jpg。

所以我开始工作了,通过调整缓冲图像的大小:

public static BufferedImage resizeUsingImageIO(Image srcImage, int size)
    {
        int w = srcImage.getWidth(null);
        int h = srcImage.getHeight(null);

        // Determine the scaling required to get desired result.
        float scaleW = (float) size / (float) w;
        float scaleH = (float) size / (float) h;

        MainWindow.logger.finest("Image Resizing to size:" + size + " w:" + w + ":h:" + h + ":scaleW:" + scaleW + ":scaleH" + scaleH);

        //Create an image buffer in which to paint on, create as an opaque Rgb type image, it doesn't matter what type
        //the original image is we want to convert to the best type for displaying on screen regardless
        BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);

        // Set the scale.
        AffineTransform tx = new AffineTransform();
        tx.scale(scaleW, scaleH);

        // Paint image.
        Graphics2D g2d = bi.createGraphics();
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, size, size);
        g2d.setComposite(AlphaComposite.SrcOver);
        g2d.drawImage(srcImage, tx, null);
        g2d.dispose();
        return bi;
    }

图片最终输出为jpg如下

public static byte[] convertToByteArray(BufferedImage bi) throws Exception
{
    final ByteArrayOutputStream output = new ByteArrayOutputStream();
    //Convert JPEG and then a byte array
    if (ImageIO.write(bi, FILE_SUFFIX_JPG, new DataOutputStream(output)))
    {
        final byte[] imageData = output.toByteArray();
        return imageData;
    }
}

但有没有一种方法可以指定最大图像大小,并使其根据需要执行更多压缩以低于该大小。

我是否应该在第一阶段根据所需的总尺寸设置宽度和高度的限制,即如果总尺寸太小,如果压缩到太小的尺寸就不可能获得好的图像

最佳答案

我不知道执行此操作的“简单”或“优雅”方法。

然而,1.5 年前,我写了这个代码片段:这是一个小实用程序,允许选择图像分辨率、压缩质量和生成的 JPG 文件大小,并显示生成图像的预览。

图像质量的 slider 和 JPG 文件大小的微调器是“关联的”:当您更改质量时,生成的文件大小也会更新。当您更改文件大小时,将调整质量以使生成的图像不大于给定的文件大小(如果可能)。

质量调整是使用某种“二进制搜索”(参见 computeQuality 方法)完成的,因为根据压缩预测文件大小很难(甚至不可能)。当然,这意味着一些计算成本,但我想没有那么多替代方案。 (您可以定义另一个停止标准。目前,它会努力为给定的文件大小限制找到“完美”的质量)。无论如何,此实用程序中的一种或另一种方法可能对您有所帮助。

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ImageLimiterTest
{
    public static void main(String[] args) throws IOException
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    createAndShowGUI();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        });
    }

    private static void createAndShowGUI() throws IOException
    {
        JFrame f = new JFrame("ImageLimiter");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        ImageLimiterPanel imageLimiterPanel = new ImageLimiterPanel(new ImageLimiter());
        BufferedImage inputImage = ImageIO.read(new File("lena512color.png"));
        imageLimiterPanel.setInputImage(inputImage);
        f.getContentPane().add(imageLimiterPanel);
        f.setSize(800,600);
        f.setVisible(true);
    }
}

class ImageLimiter
{
    private BufferedImage inputImage;
    private BufferedImage scaledImage;
    private BufferedImage outputImage;

    private int maxResolution;
    private float quality;

    private int fileSizeBytes;

    public void setInputImage(BufferedImage inputImage)
    {
        this.inputImage = inputImage;
        this.maxResolution = Math.max(inputImage.getWidth(), inputImage.getHeight());
        this.quality = 1.0f;
        this.scaledImage = computeScaledImage(inputImage, maxResolution);
        updateOutputImage();
    }

    public BufferedImage getOutputImage()
    {
        return outputImage;
    }

    public int getFileSizeBytes()
    {
        return fileSizeBytes;
    }

    public void setMaxResolution(int maxResolution)
    {
        this.maxResolution = maxResolution;
        this.scaledImage = computeScaledImage(inputImage, maxResolution);
        updateOutputImage();
    }

    public void setQuality(float quality)
    {
        this.quality = quality;
        updateOutputImage();
    }

    public float getQuality()
    {
        return quality;
    }

    public void setMaxFileSize(int maxFileSizeBytes)
    {
        this.quality = computeQuality(scaledImage, maxFileSizeBytes);
        updateOutputImage();
    }

    private void updateOutputImage()
    {
        try
        {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            writeJPG(scaledImage, baos, quality);
            baos.close();
            byte data[] = baos.toByteArray();
            fileSizeBytes = data.length;
            ByteArrayInputStream bais = new ByteArrayInputStream(data);
            outputImage = ImageIO.read(bais);
            bais.close();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }


    static float computeQuality(BufferedImage image, int sizeLimitBytes)
    {
        int minSizeBytes = computeSizeBytes(image, 0.0f);
        if (sizeLimitBytes < minSizeBytes)
        {
            return 0.0f;
        }
        int maxSizeBytes = computeSizeBytes(image, 1.0f);
        if (sizeLimitBytes > maxSizeBytes)
        {
            return 1.0f;
        }
        float intervalSize = 0.5f;
        float quality = 0.5f;
        float lastSmaller = 0;
        while (true)
        {
            int sizeBytes = computeSizeBytes(image, quality);
            if (sizeBytes >= sizeLimitBytes)
            {
                //System.out.println("For "+quality+" have size "+sizeBytes+", decrease quality by "+intervalSize);
                quality -= intervalSize;
                intervalSize /= 2;
            }
            else if (sizeBytes < sizeLimitBytes)
            {
                //System.out.println("For "+quality+" have size "+sizeBytes+", increase quality by "+intervalSize);
                lastSmaller = quality;
                quality += intervalSize;
                intervalSize /= 2;
            }
            if (intervalSize < 0.01f)
            {
                break;
            }
        }
        return lastSmaller;
    }

    private static int computeSizeBytes(BufferedImage image, float quality)
    {
        quality = Math.min(1, Math.max(0, quality));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try
        {
            writeJPG(image, baos, quality);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            try
            {
                baos.close();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
        byte data[] = baos.toByteArray();
        return data.length;
    }

    private static BufferedImage computeScaledImage(BufferedImage input, int limit)
    {
        int w = input.getWidth();
        int h  = input.getHeight();
        float aspect = (float)w / h;
        if (aspect > 1)
        {
            w = limit;
            h = (int)(w / aspect);
        }
        else
        {
            h = limit;
            w = (int)(h * aspect);
        }
        BufferedImage output = new BufferedImage(
            w, h, BufferedImage.TYPE_INT_ARGB);    

        Graphics2D g = output.createGraphics();
        g.setRenderingHint(
            RenderingHints.KEY_INTERPOLATION,
            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(input, 0, 0, w, h, null);
        g.dispose();

        return output;
    }

    /**
     * Write the given RenderedImage as a JPEG to the given outputStream,
     * using the given quality. The quality must be a value between
     * 0 (lowest quality, maximum compression) and 1 (highest
     * quality, minimum compression). The caller is responsible for
     * closing the given stream.
     *  
     * @param renderedImage The image to write
     * @param outputStream The stream to write to
     * @param quality The quality, between 0 and 1
     * @throws IOException If an IO error occurs
     */
    public static void writeJPG(RenderedImage renderedImage,
        OutputStream outputStream, float quality) throws IOException
    {
        Iterator<ImageWriter> imageWriters =
            ImageIO.getImageWritersByFormatName("jpeg");
        ImageWriter imageWriter = imageWriters.next();
        ImageOutputStream imageOutputStream =
            ImageIO.createImageOutputStream(outputStream);
        imageWriter.setOutput(imageOutputStream);
        ImageWriteParam param = imageWriter.getDefaultWriteParam();
        param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        param.setCompressionQuality(quality);
        IIOImage iioImage = new IIOImage(renderedImage, null, null);
        imageWriter.write(null, iioImage, param);
    }   
}


class ImageLimiterPanel extends JPanel
{
    private ImageLimiter imageLimiter;

    private ImageIcon inputImageIcon;
    private ImageIcon outputImageIcon;

    private JScrollPane inputScrollPane;
    private JScrollPane outputScrollPane;

    private JSlider qualitySlider;
    private JLabel qualityLabel;

    private JSlider resolutionLimitSlider;
    private JLabel resolutionLimitLabel;

    private JSpinner sizeLimitSpinner;
    private JLabel sizeLimitLabel;

    private boolean updating = false;

    public ImageLimiterPanel(ImageLimiter imageLimiter)
    {
        this.imageLimiter = imageLimiter;

        setLayout(new BorderLayout());

        final JSplitPane splitPane = new JSplitPane();

        inputImageIcon = new ImageIcon();
        JLabel inputImageLabel = new JLabel(inputImageIcon);
        inputScrollPane = new JScrollPane(inputImageLabel);
        inputScrollPane.setBorder(BorderFactory.createTitledBorder("Input"));
        splitPane.setLeftComponent(inputScrollPane);

        outputImageIcon = new ImageIcon();
        JLabel outputImageLabel = new JLabel(outputImageIcon);
        outputScrollPane = new JScrollPane(outputImageLabel);
        outputScrollPane.setBorder(BorderFactory.createTitledBorder("Output"));
        splitPane.setRightComponent(outputScrollPane);
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                splitPane.setDividerLocation(0.5);
            }
        });

        add(splitPane, BorderLayout.CENTER);


        JPanel controlPanel = new JPanel(new GridLayout(0,1));

        JPanel resolutionLimitPanel = createResolutionLimitPanel();
        controlPanel.add(resolutionLimitPanel);

        JPanel qualityPanel = createQualityPanel();
        controlPanel.add(qualityPanel);

        JPanel sizePanel = createSizeLimitPanel();
        controlPanel.add(sizePanel);

        add(controlPanel, BorderLayout.NORTH);

    }

    public void setInputImage(BufferedImage inputImage)
    {
        imageLimiter.setInputImage(inputImage);
        inputImageIcon.setImage(inputImage);
        int max = Math.max(inputImage.getWidth(), inputImage.getHeight());
        resolutionLimitSlider.setMaximum(max);        
        resolutionLimitSlider.setValue(max);        
    }

    private JPanel createResolutionLimitPanel()
    {
        JPanel resolutionLimitPanel = new JPanel(new BorderLayout());
        resolutionLimitLabel = new JLabel("Resolution: ");
        resolutionLimitLabel.setPreferredSize(new Dimension(300, 10));
        resolutionLimitPanel.add(resolutionLimitLabel, BorderLayout.WEST);
        resolutionLimitSlider = new JSlider(0,100,80);
        resolutionLimitSlider.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent arg0)
            {
                if (!updating)
                {
                    updating = true;

                    int maxResolution = resolutionLimitSlider.getValue();
                    imageLimiter.setMaxResolution(maxResolution);
                    updateOutputImage(imageLimiter.getOutputImage());

                    sizeLimitLabel.setText("Size: "+imageLimiter.getFileSizeBytes());
                    sizeLimitSpinner.setValue(imageLimiter.getFileSizeBytes());

                    updating = false;
                }
            }
        });
        resolutionLimitPanel.add(resolutionLimitSlider, BorderLayout.CENTER);
        return resolutionLimitPanel;
    }

    private JPanel createQualityPanel()
    {
        JPanel qualityPanel = new JPanel(new BorderLayout());
        qualityLabel = new JLabel("Quality: ");
        qualityLabel.setPreferredSize(new Dimension(300, 10));
        qualityPanel.add(qualityLabel, BorderLayout.WEST);
        qualitySlider = new JSlider(0,100,80);
        qualitySlider.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent arg0)
            {
                if (!updating)
                {
                    updating = true;

                    float quality = qualitySlider.getValue()/100.0f;
                    imageLimiter.setQuality(quality);
                    updateOutputImage(imageLimiter.getOutputImage());

                    qualityLabel.setText("Quality: "+String.format("%.2f", quality));
                    sizeLimitLabel.setText("Size: "+imageLimiter.getFileSizeBytes());
                    sizeLimitSpinner.setValue(imageLimiter.getFileSizeBytes());

                    updating = false;
                }
            }
        });
        qualityPanel.add(qualitySlider, BorderLayout.CENTER);
        return qualityPanel;
    }

    private JPanel createSizeLimitPanel()
    {
        JPanel sizeLimitPanel = new JPanel(new BorderLayout());
        sizeLimitLabel = new JLabel("Size: ");
        sizeLimitLabel.setPreferredSize(new Dimension(300, 10));
        sizeLimitPanel.add(sizeLimitLabel, BorderLayout.WEST);
        sizeLimitSpinner = new JSpinner(new SpinnerNumberModel(10000, 0, 1000000000, 1000));
        sizeLimitSpinner.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent arg0)
            {
                if (!updating)
                {
                    updating = true;

                    int sizeLimit = (Integer)sizeLimitSpinner.getValue();
                    imageLimiter.setMaxFileSize(sizeLimit);
                    updateOutputImage(imageLimiter.getOutputImage());

                    qualityLabel.setText("Quality: "+String.format("%.2f", imageLimiter.getQuality()));
                    qualitySlider.setValue((int)(imageLimiter.getQuality()*100));

                    sizeLimitLabel.setText("Size: "+imageLimiter.getFileSizeBytes());
                    sizeLimitSpinner.setValue(imageLimiter.getFileSizeBytes());

                    updating = false;
                }

            }
        });
        sizeLimitPanel.add(sizeLimitSpinner, BorderLayout.CENTER);
        return sizeLimitPanel;
    }

    private void updateOutputImage(BufferedImage outputImage)
    {
        outputImageIcon.setImage(outputImage);
        outputScrollPane.invalidate();
        revalidate();
        outputScrollPane.repaint();
    }

}

关于java - 如何将 BufferedImage 保存为低于特定大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22016091/

相关文章:

java - 如何从时间戳获取 MM/DD/YY 格式的日期

java - 坐标超出 .setRGB 范围

java - Java2D 视频游戏的最佳计时器设计

scala - Actor 是在简单的多人游戏之间实现消息传递的正确工具吗?

java - java.net.SocketTimeoutException : Connect timed out 的奇怪情况

java - 如何在 hibernate 中使用属性文件读取数据库配置参数

java - BufferedImage 实例数组 : IIOException

java - 绘制二维图形

java - 如何分隔 jtextfield 上的多个输入

java - 如何缩放 BufferedImage