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

被折叠的 条评论
为什么被折叠?



