java压缩图片变色

在Java中压缩图片并转化为base64时,遇到图片变色的问题,影响用户体验。问题源于`ImageIO.read(inputStream)`未能正确处理图片的ICC信息,导致图片渲染错误。解决方法是通过自定义`toBufferedImage`方法,保持图片原始色彩信息不变。

java图片压缩导致图片变色以及解决

需求描述

对接服务中,由于需要传输图片的base64,同时对数据大小有要求,因此需要对云服务器上的图片需要读取并进行压缩,从而在传输时通过base64进行传输。

图片读取以及压缩转化为base64

   /**
     * 将url压缩为指定大小
     * @param imageUrl
     * @param sizeLimit
     * @return
     * @throws IOException
     */
    public static String convertImageToBase64(String imageUrl, Integer sizeLimit) throws IOException {
        String imgType=imageUrl.substring(imageUrl.lastIndexOf(".")+1);
        //默认上限为500k
        if (sizeLimit == null) {
            sizeLimit = 500;
        }
        sizeLimit = sizeLimit * 1024;
        String base64Image;
        DataInputStream dataInputStream = null;
        ByteArrayOutputStream outputStream = null;
        ByteArrayInputStream inputStream = null;
        try {
            //从远程读取图片
            URL url = new URL(imageUrl);
            dataInputStream = new DataInputStream(url.openStream());
            outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int length;
            while ((length = dataInputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, length);
            }
            byte[] context = outputStream.toByteArray();
            //将图片数据还原为图片
            inputStream = new ByteArrayInputStream(context);
            //注意:使用ImageIO.read()方法可能会导致图片数据丢失,导致图片变色
            BufferedImage image = ImageIO.read(inputStream);

            int imageSize = context.length;
            int type = image.getType();
            int height = image.getHeight();
            int width = image.getWidth();
            BufferedImage tempImage;
            //判断文件大小是否大于size,循环压缩,直到大小小于给定的值
            while (imageSize > sizeLimit) {
                //将图片长宽压缩到原来的90%
                height = new Double(height * 0.9).intValue();
                width = new Double(width * 0.9).intValue();
                tempImage = new BufferedImage(width, height, type);
                // 绘制缩小后的图
                tempImage.getGraphics().drawImage(image, 0, 0, width, height, null);
                //重新计算图片大小
                outputStream.reset();
                ImageIO.write(tempImage, imgType, outputStream);
                imageSize = outputStream.toByteArray().length;
            }

            //将图片转化为base64并返回
            byte[] data = outputStream.toByteArray();
            //此处一定要使用org.apache.tomcat.util.codec.binary.Base64,防止再linux上出现换行等特殊符号
            base64Image = Base64.encodeBase64String(data);
        } catch (Exception e) {
            //抛出异常
            throw e;
        } finally {
            if (dataInputStream != null) {
                try {
                    dataInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return base64Image;
    }

    private static String encode(byte[] image) {
        BASE64Encoder decoder = new BASE64Encoder();
        return replaceEnter(decoder.encode(image));
    }

    private static String replaceEnter(String str) {
        String reg = "[\n-\r]";
        Pattern p = Pattern.compile(reg);
        Matcher m = p.matcher(str);
        return m.replaceAll("");
    }

图片变色问题描述

图片转换过程中,偶尔会出现图片变色问题。导致图片无法使用,影响用户使用体验。
在这里插入图片描述
压缩后
在这里插入图片描述

问题排查

初步以为是 ImageIO.write(tempImage, imgType, outputStream); 中写入imgType的问题,因为最初写入时 imgType为固定写入类型为 jpeg。但是转换之前图片类型是不固定的,因此修改为动态写入和转换图片之前的图片类型一致。进行测试之后,发现仍然会出现图片压缩后变色的问题。

最终搜索发现原因为 BufferedImage image = ImageIO.read(inputStream); 在读取时不能正确处理图片ICC信息,ICC为JPEG图片格式中的一种头部信息,导致渲染图片前景色时蒙上一层红色。

解决办法

将代码

ImageIO.write(tempImage, imgType, outputStream);

修改为

Image src = Toolkit.getDefaultToolkit().getImage(upload.getPath());
BufferedImage originalImage = toBufferedImage(src);//Image to BufferedImage  

toBufferedImage具体为

public static BufferedImage toBufferedImage(Image image) {
    if (image instanceof BufferedImage) {
        return (BufferedImage) image;
    }
    // This code ensures that all the pixels in the image are loaded
    image = new ImageIcon(image).getImage();
    BufferedImage bimage = null;
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    try {
        int transparency = Transparency.OPAQUE;
        GraphicsDevice gs = ge.getDefaultScreenDevice();
        GraphicsConfiguration gc = gs.getDefaultConfiguration();
        bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
    } catch (HeadlessException e) {
        // The system does not have a screen
    }
    if (bimage == null) {
        // Create a buffered image using the default color model
        int type = BufferedImage.TYPE_INT_RGB;
        bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
    }
    // Copy image to buffered image
    Graphics g = bimage.createGraphics();
    // Paint the image onto the buffered image
    g.drawImage(image, 0, 0, null);
    g.dispose();
    return bimage;
}

最终修改后代码为

import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Image2Base64 {


    private static String image2Base64(String imgUrl) {
        URL url;
        HttpURLConnection urlConnection = null;
        InputStream inputStream = null;
        ByteArrayOutputStream baos = null;
        try {
            url = new URL(imgUrl);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.connect();
            inputStream = urlConnection.getInputStream();

            baos = new ByteArrayOutputStream();

            byte[] buffer = new byte[1024];
            int len = 0;
            //使用一个输入流从buffer里把数据读取出来
            while ((len = inputStream.read(buffer)) != -1) {
                //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度
                baos.write(buffer, 0, len);
            }
            // 对字节数组Base64编码
            return encode(baos.toByteArray());

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }

        return imgUrl;
    }

    private static String encode(byte[] image) {
        BASE64Encoder decoder = new BASE64Encoder();
        return replaceEnter(decoder.encode(image));
    }

    private static String replaceEnter(String str) {
        String reg = "[\n-\r]";
        Pattern p = Pattern.compile(reg);
        Matcher m = p.matcher(str);
        return m.replaceAll("");
    }

    /**
     * 字符串转图片
     *
     * @param base64Str
     * @return
     */
    private static byte[] decode(String base64Str) {
        byte[] b = null;
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            b = decoder.decodeBuffer(replaceEnter(base64Str));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return b;
    }

    private static ByteBuffer decodeBuffer(String base64Str) {
        ByteBuffer buffer = null;
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            buffer = decoder.decodeBufferToByteBuffer(base64Str);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buffer;
    }


    /**
     * 将url压缩为指定大小
     * @param imageUrl
     * @param sizeLimit
     * @return
     * @throws IOException
     */
    public static String convertImageToBase64(String imageUrl, Integer sizeLimit) throws IOException {
        String imgType=imageUrl.substring(imageUrl.lastIndexOf(".")+1);
        //默认上限为500k
        if (sizeLimit == null) {
            sizeLimit = 500;
        }
        sizeLimit = sizeLimit * 1024;
        String base64Image;
        DataInputStream dataInputStream = null;
        ByteArrayOutputStream outputStream = null;
        ByteArrayInputStream inputStream = null;
        try {
            //从远程读取图片
            URL url = new URL(imageUrl);
            dataInputStream = new DataInputStream(url.openStream());
            outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int length;
            while ((length = dataInputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, length);
            }
            byte[] context = outputStream.toByteArray();
            //将图片数据还原为图片
            //inputStream = new ByteArrayInputStream(context);
            //BufferedImage image = ImageIO.read(inputStream);

            //使用该方法解决 图片压缩变色问题  ImageIO.read()会存在图片变色问题
            Image src = Toolkit.getDefaultToolkit().getImage(url);
            BufferedImage image = toBufferedImage(src);
            int imageSize = context.length;
            int type = image.getType();
            int height = image.getHeight();
            int width = image.getWidth();
            BufferedImage tempImage;
            //判断文件大小是否大于size,循环压缩,直到大小小于给定的值
            while (imageSize > sizeLimit) {
                //将图片长宽压缩到原来的90%
                height = new Double(height * 0.9).intValue();
                width = new Double(width * 0.9).intValue();
                tempImage = new BufferedImage(width, height, type);
                // 绘制缩小后的图
                tempImage.getGraphics().drawImage(image, 0, 0, width, height, null);
                //重新计算图片大小
                outputStream.reset();
                ImageIO.write(tempImage, imgType, outputStream);
                imageSize = outputStream.toByteArray().length;
            }

            //将图片转化为base64并返回
            byte[] data = outputStream.toByteArray();
            //此处一定要使用org.apache.tomcat.util.codec.binary.Base64,防止再linux上出现换行等特殊符号
            base64Image = Base64.encodeBase64String(data);
        } catch (Exception e) {
            //抛出异常
            throw e;
        } finally {
            if (dataInputStream != null) {
                try {
                    dataInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return base64Image;
    }

    public static BufferedImage toBufferedImage(Image image) {
        if (image instanceof BufferedImage) {
            return (BufferedImage) image;
        }
        // This code ensures that all the pixels in the image are loaded
        image = new ImageIcon(image).getImage();
        BufferedImage bimage = null;
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        try {
            int transparency = Transparency.OPAQUE;
            GraphicsDevice gs = ge.getDefaultScreenDevice();
            GraphicsConfiguration gc = gs.getDefaultConfiguration();
            bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
        } catch (HeadlessException e) {
            // The system does not have a screen
        }
        if (bimage == null) {
            // Create a buffered image using the default color model
            int type = BufferedImage.TYPE_INT_RGB;
            bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
        }
        // Copy image to buffered image
        Graphics g = bimage.createGraphics();
        // Paint the image onto the buffered image
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return bimage;
    }

}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值