如何将BufferedImage保存为低于特定大小 [英] How can I save a BufferedImage to be below a particular size

查看:175
本文介绍了如何将BufferedImage保存为低于特定大小的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

(Using java 8) Given a image user needs to be able to specify min/max image size in pixels and also maximum size of saved image in kbs, image is saved as jpg.

所以我通过调整缓冲图像大小来实现第一步:

So I have the first bit working, by resizing buffered image:

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格式输出如下

The image is eventually output as a jpg as follows

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

但有没有办法指定最大图像尺寸,并根据需要执行更多压缩以达到该尺寸。

but is there a way I specify a max image size , and make it perform more compression as required to get underneath that size.

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

And should I set limits to width and height in first stage based on total size required, i.e if the total size is too small it would be impossible to get a good image if compression to a size that is too small

推荐答案

我不知道这样做的简单或优雅方式。

I don't know an "easy" or "elegant" way to do this.

但是,1。5年前,我编写了这段代码片段:它是一个小实用程序,可以选择图像分辨率,压缩质量和生成的JPG文件大小,并显示预览生成的图像。

However, 1.5 years ago, I wrote this code snippet: It is a small utility that allows selecting the image resolution, compression quality and resulting JPG file size, and shows a preview of the resulting image.

图像质量的滑块和JPG文件大小的微调器是链接的:当您更改质量时,将更新生成的文件大小。更改文件大小时,将调整质量,使得生成的图像不大于给定的文件大小(如果可能)。

The slider for the image quality and the spinner for the JPG file size are "linked": When you change the quality, the resulting file size will be updated. When you change the file size, the quality will be adjusted so that the resulting image is not larger than the given file size (if possible).

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

The quality adjustment is done using some sort of a "binary search" (see the computeQuality method), because the predicting the file size depending on the compression is hard (or even impossible). Of course, this implies some computational cost, but there are not so many alternatives, I guess. (You could define another stopping criterion. At the moment, it tries hard to find the "perfect" quality for the given file size limit). Maybe one or another method from this utility may be helpful for you, anyhow.

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

}

这篇关于如何将BufferedImage保存为低于特定大小的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆