优化体验差点优化出大问题,imageio真的会用么?

大家好,我是烤鸭:

   最近忙着体验优化,差点酿出大问题。本来打算做图片转存,目的是为了方便切图,更好地控制图片大小。当使用 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

http://www.javased.com/index.php?api=javax.imageio.ImageIO

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烤鸭的世界我们不懂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值