1. 程式人生 > >使用BitmapFactory壓縮圖片遇到的問題總結

使用BitmapFactory壓縮圖片遇到的問題總結

壓縮前先搞明白原理:Bitmap佔用的記憶體大小:

bytes = 原始圖片寬*(options.inTargetDensity/options.inDensity)*原始圖片長*(options.inTargetDensity/options.inDensity)*每個畫素點位數

inTargetDensity指的是當前手機的密度,inDensity是圖片的所在drawable目錄生成的密度

使用sample取樣率來對Bitmap進行壓縮到指定的寬和高(原理不在贅述)
方法相信大家都熟悉,網上一搜都大體類似,下面是具體的方法

// 使用decodeRes方法對資源進行轉換,使用InJustDecodeBounds屬性置為true,先獲圖
//片高,native層先不返回Bitmap
//進行計算sampleSize,然後設為false,再次decode返回Bitmap
public Bitmap compressBitmapIntoThumbnailPic(Context context, int res) {
        BitmapFactory.Options
options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmap bitmap1 = BitmapFactory.decodeResource(context.getResources(), res, options); Log.d("tag", "first bitmap == " + bitmap1); int sampleBitmap = calculateInSampleSize(options, 40, 40); options.inSampleSize
= sampleBitmap; options.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), res, options); Log.d("tag", "final == " + bitmap.getByteCount() + " target real density " + options.inTargetDensity + " folder density " + options.inDensity
); return bitmap; }
// 對要求的寬高和當前圖片寬高對比,取一個最小的比例作為sampleSize
 private int calculateInSampleSize(BitmapFactory.Options options, int requireW, int requereH) {
        int sampleSize = 1;
        int outHeight = options.outHeight;
        int outWidth = options.outWidth;
        int rationHeight = Math.round(outHeight / requereH);
        int rationWidth = Math.round(outWidth / requireW);
        if (rationHeight > 1 || rationWidth > 1) {
            sampleSize = rationHeight > rationWidth ? rationWidth : rationHeight;
        }
        Log.d("tag", "outHeight = " + outHeight + "outWidth = " + outWidth + " -------- " + sampleSize);
        return sampleSize;
    }

執行上述的程式碼能正常的將一個大圖片進行壓縮展示,然後自己看了下原始碼,發現BitmapFactory最終呼叫的都是decodeStream(…) 這個方法來處理的,於是想試一下直接處理流來對圖片壓縮也應該可以,程式碼見下:

/**
     *  用流的形式生成Bitmap
     **/
    public Bitmap compressBitmapIntoThumbnailPic(InputStream is) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        // 最終返回的Bitmap
        Bitmap finalBitmap = null;
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){
            //設定inJustDecodeBounds在native decode時候只返回尺寸等,Bitmap為空
            // 或者將inputStream -> BufferedInputStream 保證某些情況reset不支援
            BitmapFactory.decodeStream(is, null, options);
            int sampleBitmap = calculateInSampleSize(options, 80, 80);
            options.inSampleSize = sampleBitmap;
            options.inJustDecodeBounds = false;
            try {
                is.reset();
            } catch (IOException e) {
                e.printStackTrace();
            }
            finalBitmap = BitmapFactory.decodeStream(is, null, options);
        }else {// 4.4包含以後就沒有這個is.mark(1024)的大小限制問題了。不會出現OOM
            if (is.markSupported()) {
                try {
                    BitmapFactory.decodeStream(is,null,options);
                    int sampleBitmap = calculateInSampleSize(options, 40, 40);
                    options.inSampleSize = sampleBitmap;
                    options.inJustDecodeBounds = false;
                    is.reset();
                    finalBitmap = BitmapFactory.decodeStream(is, null, options);
                    Log.d("tag", "final == " + finalBitmap.getByteCount() +
                            " target real density  " + options.inTargetDensity + " folder density " + options.inDensity);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return finalBitmap;
    }

這裡對版本做了一個判斷,只是為了調式方便。先說下原因,和上面方法相比而言,多了一個 is.reset , 不加的話直接就返回null 了,即使我們已近設定了inJustDecodeBounds為false, 這就奇怪了,這時候還是看下原始碼來找答案吧,圈紅的部分意思是 : 我們在options裡設定要求只返回大小或者流不能被decode的時候就返回null,那就說明是流不能被decode了,然後想到了流被使用過後的位置是會改變的,我們第一次decode的時候流執行了mark方法,下次再執行這個流的時候就必須要使用reset,返回上次標記的內容,這樣才能正常使用流。有點恍然大悟~,被自己感動了。
這裡寫圖片描述
然後我們去看下InputStream的原始碼是怎麼說的,重點是readLimit這個關鍵字,大體意思是在流支援mark的情況下,呼叫reset方法會返回給記錄的位元組數,但是如果超過了這個
閥值,呼叫reset不能全部的返回,

/**
     * Marks the current position in this input stream. A subsequent call to
     * the <code>reset</code> method repositions this stream at the last marked
     * position so that subsequent reads re-read the same bytes.
     *
     * <p> The <code>readlimit</code> arguments tells this input stream to
     * allow that many bytes to be read before the mark position gets
     * invalidated.
     *
     * <p> The general contract of <code>mark</code> is that, if the method
     * <code>markSupported</code> returns <code>true</code>, the stream somehow
     * remembers all the bytes read after the call to <code>mark</code> and
     * stands ready to supply those same bytes again if and whenever the method
     * <code>reset</code> is called.  However, the stream is not required to
     * remember any data at all if more than <code>readlimit</code> bytes are
     * read from the stream before <code>reset</code> is called.
     *
     * <p> Marking a closed stream should not have any effect on the stream.
     *
     * <p> The <code>mark</code> method of <code>InputStream</code> does
     * nothing.
     *
     * @param   readlimit   the maximum limit of bytes that can be read before
     *                      the mark position becomes invalid.
     * @see     java.io.InputStream#reset()
     */

在reset的時候容易發生OOM,原因是在流在建立時沒有呼叫mark的話,mark記錄的大小肯定是小於整體的,這時候你去呼叫reset請求的大於mark記錄的大小,就會OOM 。參考此條解決方式

/**
     * Repositions this stream to the position at the time the
     * <code>mark</code> method was last called on this input stream.
     *
     * <p> The general contract of <code>reset</code> is:
     *
     * <ul>
     * <li> If the method <code>markSupported</code> returns
     * <code>true</code>, then:
     *
     *     <ul><li> If the method <code>mark</code> has not been called since
     *     the stream was created, or the number of bytes read from the stream
     *     since <code>mark</code> was last called is larger than the argument
     *     to <code>mark</code> at that last call, then an
     *     <code>IOException</code> might be thrown.
     *
     *     <li> If such an <code>IOException</code> is not thrown, then the
     *     stream is reset to a state such that all the bytes read since the
     *     most recent call to <code>mark</code> (or since the start of the
     *     file, if <code>mark</code> has not been called) will be resupplied
     *     to subsequent callers of the <code>read</code> method, followed by
     *     any bytes that otherwise would have been the next input data as of
     *     the time of the call to <code>reset</code>. </ul>
     *
     * <li> If the method <code>markSupported</code> returns
     * <code>false</code>, then:
     *
     *     <ul><li> The call to <code>reset</code> may throw an
     *     <code>IOException</code>.
     *
     *     <li> If an <code>IOException</code> is not thrown, then the stream
     *     is reset to a fixed state that depends on the particular type of the
     *     input stream and how it was created. The bytes that will be supplied
     *     to subsequent callers of the <code>read</code> method depend on the
     *     particular type of the input stream. </ul></ul>
     *
     * <p>The method <code>reset</code> for class <code>InputStream</code>
     * does nothing except throw an <code>IOException</code>.
     *
     * @exception  IOException  if this stream has not been marked or if the
     *               mark has been invalidated.
     * @see     java.io.InputStream#mark(int)
     * @see     java.io.IOException
     */
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

上面程式碼中有區分4.4版本的判斷,這裡解釋下原因:如下圖所示,在4.3原始碼中預設mark的閥值大小為1024K,這樣的話對大圖片很容易引起OOM ; 在4.4(包含)之後,去掉了這個case,沒有具體的1024限制了。這裡只是對原理做了簡單的研究,實際上我們還是使用Glide之類的框架去做載入圖片的工作。但是對原理有些瞭解還是不錯的。
這裡寫圖片描述

這裡寫圖片描述

*擴充套件*
這裡還有倆點疑問,
1. 為啥設定了inJustDecodeBounds = true 就返回空的Bitmap ;
2.第一種用decodeResource的方法為啥能成功的返回Bitmap ???
還是要去原始碼中找答案:

這裡寫圖片描述

這裡寫圖片描述
上面這倆張顯示了在native層對Bitmap的操作 ;
下面這張圖解釋了疑問 2 ,因為decodeRes不是直接操作流的,是根據資源Id去載入流,內部做了處理,每次返回的是可用的流,這樣就沒問題了。
這裡寫圖片描述

增加一處decodeFileDescriptor

使用decodeStream的時候由於流的位置變化導致二次返回的Bitmap為空,其實可以用下面的方法完美代替,如標識,這個FileDescriptor是不會變的,所以用起來很放心

這裡寫圖片描述

結尾

有疑問還是要去看原始碼啊,知其然也要知其所以然·,水平有限,錯誤之處請幫忙斧正

相關推薦

使用BitmapFactory壓縮圖片遇到的問題總結

壓縮前先搞明白原理:Bitmap佔用的記憶體大小: bytes = 原始圖片寬*(options.inTargetDensity/options.inDensity)*原始圖片長*(options.inTargetDensity/options.inDen

BitmapFactory.Options壓縮圖片

BitmapFactory.Options opts = new BitmapFactory.Options(); //只請求圖片寬高,不解析圖片畫素 opts.inJustDecodeBounds

Android使用BitmapFactory.Options壓縮圖片解決載入大圖片記憶體溢位

由於Android對圖片使用記憶體有限制,若是載入幾兆的大圖片便記憶體溢位。Bitmap會將圖片的所有畫素(即長x寬)載入到記憶體中,如果圖片解析度過大,會直接導致記憶體溢位(java.lang.OutOfMemoryError),只有在BitmapFactory載入圖片時使

c# 無損高質量壓縮圖片代碼

++ osi name source 新路 public rip erp lan 最近,項目上涉及到了圖像壓縮,發現原有的圖像壓縮功能,雖然保證了圖像的大小300K以內,但是壓縮後的圖像看的不在清晰,並且,限定了圖片的Height或者是Width。 在CSDN上看到了一個

使用 gulp 壓縮圖片

mage 可能 true 所有 fault ssi 4.2 cnblogs 提高 請務必理解如下章節後閱讀此章節: 安裝 Node 和 gulp 使用 gulp 壓縮 JS 壓縮 圖片文件可降低文件大小,提高圖片加載速度。 找到規律轉換為 gulp 代碼 規律 找到 i

android 通過bitmapfactory得到圖片真實像素的方法,以及沒有得到真實像素的原因

hdp hive eight bit font style 大小 () ica 原文來自:原文地址 由於這個錯誤導致浪費非常長時間找原因,所以要趕緊記錄下來。 過程是這種,在使用android讀取圖片的時候,就是使用BitmapFactory.dec

Nodejs前端服務器壓縮圖片

res back gem callback nbsp 承擔 能力 true bsp Nodejs作為前端服務器,自然能承擔處理圖片的能力, 使用GM for nodejs 作為圖片處理器,調用ImageMagick處理圖片 使用ImageMagick var image

canvas前端壓縮圖片

參考 read ble element pre dev 轉換 制圖 status 參考網上的用法,下面是利用canvas進行的圖片壓縮 <!DOCTYPE html> <html> <head> <meta charset

最簡單的方法來壓縮圖片,改變圖片大小

com post 分享 圖片大小 壓縮圖片 bubuko log OS 技術分享 1. 2. 3.按照百分比或像素 調整即可 最簡單的方法來壓縮圖片,改變圖片大小

如何有效實現前端壓縮圖片並上傳功能

res 滿足 utf boot ade 賦值 als 多次 and   隨著現在手機的像素越來越高,很多照片動輒幾兆甚至十幾兆,上傳後在服務器端壓縮已經越來越不能滿足當今的需求。這對於許多技術人員來說,處理起來這樣的問題往往不知道該怎麽下手,那麽下面就跟大家講解一下如何在前

Xamarin.Android 壓縮圖片並上傳到WebServices

越來越大 () exists jpeg color 文件寫入 data pen map   隨著手機的拍照像素越來越高,導致圖片贊的容量越來越大,如果上傳多張圖片不進行壓縮、質量處理很容易出現OOM內存泄漏問題。   最近做了一個項目,向webservices上傳多張照片,

js壓縮圖片

ID 數據轉換 ascii碼 獲得 bsp put base ase eat /** * 獲得base64 * @param {Object} obj * @param {Number} [obj.width] 圖片需要壓縮的寬度,高度會跟隨調整

壓縮圖片,直接使用大圖,在小控件上會有毛邊

pre current OS option phi osi begin CA send 使用UIImage+YYAdd.h的: - (UIImage *)imageByResizeToSize:(CGSize)size { if (size.width <=

Java 後端壓縮圖片

平滑 warn 正常 GC 繪制 java eight new wid import java.io.*;import java.util.Date;import java.awt.*;import java.awt.image.*;import javax.imageio

上傳圖片壓縮圖片 - 前端(canvas)做法

als ase java use reac ice efi 壓縮圖片 basic HTML前端代碼: <?php $this->layout(‘head‘); ?> <?php $this->layout(‘sidebar‘); ?>

上傳圖片壓縮圖片 - 後端做法

rep create 路徑 creat 彩色 images rom sim 不同 /** * 函數:調整圖片尺寸或生成縮略圖 v 1.1 * @param $Image 需要調整的圖片(含路徑) * @param $Dw 調整時最大寬

壓縮圖片大小的軟件怎麽使用,怎麽壓縮圖片文件

分享 ali 通過 安裝 使用 介紹 res 幫助 直接 圖片的壓縮軟件怎麽使用呢?很多人不知道圖片壓縮軟件的使用方法,很多的圖片文件就會進行擱置,對於壓縮軟件來講,找到比較好的圖片壓縮軟件也是對自己的圖片文件負責,下面就為大家介紹一款圖片壓縮軟件的使用方法。 1:首先要將

批量壓縮圖片

dir var eal require 壓縮圖片 add () explorer read var images = require("images"); var fs = require("fs"); var path = "./images"; async func

megapix-image外掛 使用Canvas壓縮圖片上傳 MegaPixImage.js下載

MegaPixImage.js下載地址 <!DOCTYPE html > <html> <head> <title>通過Canvas及File API縮放並上傳圖片</title> <script

怎樣壓縮圖片大小

現在從事自媒體工作的人是越來越多了,大多數自媒體人在寫文章的時候都會配圖,但是有的平臺只給上傳多少k以內的圖片,很多時候我們上傳的數碼裝置拍攝的圖片就會遇到經常上傳不了的情況,這是因為在高畫素下拍攝的圖片大小遠超過平臺可以容忍的大小,這個時候我們就要選擇去壓縮圖片,那如何高效率的壓縮圖片大小呢,今天小編就給大