大家好,我是烤鸭:
最近忙着体验优化,差点酿出大问题。本来打算做图片转存,目的是为了方便切图,更好地控制图片大小。当使用 imageio 下载图片后再上传发现比原来的图要大,懵圈了,越优化图片越大了?
下载图片的几种方式和大小对比
发现使用http方式是一样大小的,使用imageio的流比其他两种要大。
代码实现
@Test
public void compareSize() {
// 图片上传
String remotePicUrl = "https://img2.baidu.com/it/u=3202947311,1179654885&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500";
byte[] remoteBytes = getFileByteRemoteByImageIO(remotePicUrl);
byte[] remoteConnBytes = getFileByteRemoteByConnection(remotePicUrl);
byte[] downloadBytes = HttpUtil.downloadBytes(remotePicUrl);
System.out.println("[imageIO] remoteBytes =>" + remoteBytes.length);
System.out.println("[connection] remoteBytes =>" + remoteConnBytes.length);
System.out.println("[http] remoteBytes =>" + downloadBytes.length);
}
private byte[] getFileByte(String path){
byte[] data = null;
try {
File file = new File(path);//待处理的图片
InputStream is = null;
//读取图片字节数组
is = new FileInputStream(file);
data = new byte[is.available()];
is.read(data);
is.close();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
private byte[] getFileByteRemoteByImageIO(String picUrl){
byte[] data = null;
try (InputStream is = new URL(picUrl).openStream()) {
BufferedImage sourceImg = ImageIO.read(is);
data = bufferedImageToByteArray(sourceImg);
} catch (Exception e) {
log.error("imageToDsf error={}", ExceptionUtil.stacktraceToString(e));
}
return data;
}
public static byte[] getFileByteRemoteByConnection(String strUrl) {
try {
URL url = new URL(strUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
InputStream inStream = conn.getInputStream();// 通过输入流获取图片数据
byte[] btImg = readInputStream(inStream);// 得到图片的二进制数据
return btImg;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 从输入流中获取数据
*
* @param inStream 输入流
*/
public static byte[] readInputStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[10240];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
inStream.close();
return outStream.toByteArray();
}
ImageIO介绍
这多出来的1KB多是干啥的。
一起看下 ImageIO.read 的源码是咋写的。
返回的是 BufferedImage 是使用 自动注册的ImageReader 对流进行了解码。
/**
* Returns a {@code BufferedImage} as the result of decoding
* a supplied {@code InputStream} with an {@code ImageReader}
* chosen automatically from among those currently registered.
* The {@code InputStream} is wrapped in an
* {@code ImageInputStream}. If no registered
* {@code ImageReader} claims to be able to read the
* resulting stream, {@code null} is returned.
* ...
*/
public static BufferedImage read(InputStream input) throws IOException {
if (input == null) {
throw new IllegalArgumentException("input == null!");
}
ImageInputStream stream = createImageInputStream(input);
if (stream == null) {
throw new IIOException("Can't create an ImageInputStream!");
}
BufferedImage bi = read(stream);
if (bi == null) {
stream.close();
}
return bi;
}
后边又跟了一些源码,不得不说,sun包下的着实有些复杂。
为了能使filter正常解析,申请内存空间创建 Raster (光栅),不是简单的流读取生成文件了,而是读取原始图片的信息,比如帧、像素、色彩,重新生成一幅图。
JPEGImageReader.read
private Raster readInternal(int imageIndex,
ImageReadParam param,
boolean wantRaster) throws IOException {
readHeader(imageIndex, false);
WritableRaster imRas = null;
int numImageBands = 0;
if (!wantRaster){
// Can we read this image?
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
if (imageTypes.hasNext() == false) {
throw new IIOException("Unsupported Image Type");
}
image = getDestination(param, imageTypes, width, height);
imRas = image.getRaster();
// The destination may still be incompatible.
numImageBands = image.getSampleModel().getNumBands();
// Check whether we can handle any implied color conversion
// Throws IIOException if the stream and the image are
// incompatible, and sets convert if a java conversion
// is necessary
checkColorConversion(image, param);
// Check the source and destination bands in the param
checkReadParamBandSettings(param, numComponents, numImageBands);
} else {
// Set the output color space equal to the input colorspace
// This disables all conversions
setOutColorSpace(structPointer, colorSpaceCode);
image = null;
}
// Create an intermediate 1-line Raster that will hold the decoded,
// subsampled, clipped, band-selected image data in a single
// byte-interleaved buffer. The above transformations
// will occur in C for performance. Every time this Raster
// is filled we will call back to acceptPixels below to copy
// this to whatever kind of buffer our image has.
int [] srcBands = JPEG.bandOffsets[numComponents-1];
int numRasterBands = (wantRaster ? numComponents : numImageBands);
destinationBands = null;
Rectangle srcROI = new Rectangle(0, 0, 0, 0);
destROI = new Rectangle(0, 0, 0, 0);
computeRegions(param, width, height, image, srcROI, destROI);
int periodX = 1;
int periodY = 1;
minProgressivePass = 0;
maxProgressivePass = Integer.MAX_VALUE;
if (param != null) {
periodX = param.getSourceXSubsampling();
periodY = param.getSourceYSubsampling();
int[] sBands = param.getSourceBands();
if (sBands != null) {
srcBands = sBands;
numRasterBands = srcBands.length;
}
if (!wantRaster) { // ignore dest bands for Raster
destinationBands = param.getDestinationBands();
}
minProgressivePass = param.getSourceMinProgressivePass();
maxProgressivePass = param.getSourceMaxProgressivePass();
if (param instanceof JPEGImageReadParam) {
JPEGImageReadParam jparam = (JPEGImageReadParam) param;
if (jparam.areTablesSet()) {
abbrevQTables = jparam.getQTables();
abbrevDCHuffmanTables = jparam.getDCHuffmanTables();
abbrevACHuffmanTables = jparam.getACHuffmanTables();
}
}
}
int lineSize = destROI.width*numRasterBands;
buffer = new DataBufferByte(lineSize);
int [] bandOffs = JPEG.bandOffsets[numRasterBands-1];
raster = Raster.createInterleavedRaster(buffer,
destROI.width, 1,
lineSize,
numRasterBands,
bandOffs,
null);
// Now that we have the Raster we'll decode to, get a view of the
// target Raster that will permit a simple setRect for each scanline
if (wantRaster) {
target = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
destROI.width,
destROI.height,
lineSize,
numRasterBands,
bandOffs,
null);
} else {
target = imRas;
}
int [] bandSizes = target.getSampleModel().getSampleSize();
for (int i = 0; i < bandSizes.length; i++) {
if (bandSizes[i] <= 0 || bandSizes[i] > 8) {
throw new IIOException("Illegal band size: should be 0 < size <= 8");
}
}
/*
* If the process is sequential, and we have restart markers,
* we could skip to the correct restart marker, if the library
* lets us. That's an optimization to investigate later.
*/
// Check for update listeners (don't call back if none)
boolean callbackUpdates = ((updateListeners != null)
|| (progressListeners != null));
// Set up progression data
initProgressData();
// if we have a metadata object, we can count the scans
// and set knownPassCount
if (imageIndex == imageMetadataIndex) { // We have metadata
knownPassCount = 0;
for (Iterator<MarkerSegment> iter =
imageMetadata.markerSequence.iterator(); iter.hasNext();) {
if (iter.next() instanceof SOSMarkerSegment) {
knownPassCount++;
}
}
}
progInterval = Math.max((target.getHeight()-1) / 20, 1);
if (knownPassCount > 0) {
progInterval *= knownPassCount;
} else if (maxProgressivePass != Integer.MAX_VALUE) {
progInterval *= (maxProgressivePass - minProgressivePass + 1);
}
if (debug) {
System.out.println("**** Read Data *****");
System.out.println("numRasterBands is " + numRasterBands);
System.out.print("srcBands:");
for (int i = 0; i<srcBands.length;i++)
System.out.print(" " + srcBands[i]);
System.out.println();
System.out.println("destination bands is " + destinationBands);
if (destinationBands != null) {
for (int i = 0; i < destinationBands.length; i++) {
System.out.print(" " + destinationBands[i]);
}
System.out.println();
}
System.out.println("sourceROI is " + srcROI);
System.out.println("destROI is " + destROI);
System.out.println("periodX is " + periodX);
System.out.println("periodY is " + periodY);
System.out.println("minProgressivePass is " + minProgressivePass);
System.out.println("maxProgressivePass is " + maxProgressivePass);
System.out.println("callbackUpdates is " + callbackUpdates);
}
/*
* All the Jpeg processing happens in native, we should clear
* abortFlag of imageIODataStruct in imageioJPEG.c. And we need to
* clear abortFlag because if in previous read() if we had called
* reader.abort() that will continue to be valid for present call also.
*/
clearNativeReadAbortFlag(structPointer);
processImageStarted(currentImage);
/*
* Note that getData disables acceleration on buffer, but it is
* just a 1-line intermediate data transfer buffer that will not
* affect the acceleration of the resulting image.
*/
boolean aborted = readImage(imageIndex,
structPointer,
buffer.getData(),
numRasterBands,
srcBands,
bandSizes,
srcROI.x, srcROI.y,
srcROI.width, srcROI.height,
periodX, periodY,
abbrevQTables,
abbrevDCHuffmanTables,
abbrevACHuffmanTables,
minProgressivePass, maxProgressivePass,
callbackUpdates);
if (aborted) {
processReadAborted();
} else {
processImageComplete();
}
return target;
}
用beyond compare对比看一下效果。确实有很多不同。
总结
imageio的功能很强大,包含图片剪裁,操作等等。不过如果只是像我一样下载图片,就没啥必要了,差点出了大问题。
合适的类库要用在合适的场景,各种搜索简直是耽误事啊。
参考
http://jszx-jxpt.cuit.edu.cn/JavaAPI/javax/imageio/ImageIO.html
https://blog.csdn.net/Zhangsama1/article/details/122557334
https://www.javatpoint.com/java-imageio-class
https://docs.oracle.com/javase/8/docs/api/javax/imageio/package-summary.html
https://www.apiref.com/java11-zh/java.desktop/javax/imageio/ImageIO.html