Bitmap那些事

Author:楊空明 Date:2018-11-15

1.何為bitmap窖壕?

2.開發(fā)中bitmap遇到的那些問題

3.bitmap幾種壓縮方式

4.android中加載大圖片的正確方式

5.Android Skia 圖像引擎

1.何為bitmap匙铡?

我們可以稱之為位圖,是一種存儲像素的數(shù)據(jù)結(jié)構(gòu)攀例,通過這個對象我們可以獲取到一系列和圖片相關(guān)的屬性脑蠕, 并且可以對圖像進(jìn)行處理,比如切割蔬将,放大等等,相關(guān)操作央星。

1.1怎么計算一張圖片占用的內(nèi)存大邢蓟场?

bitmap 在內(nèi)存空間中所占用的內(nèi)存計算是這樣的:

bitmap 的寬 x 高 x 每個像素所占的字節(jié)

其中每個像素占用的字節(jié)可以通過Bitmap.Config動態(tài)配置

Config 每個像素占用的字節(jié) 說明
ALPHA_8 1 bytes 每個像素僅僅儲存透明度通道
RGB_565 2 bytes 每個像素的RGB通道會保存莉给,透明度不會保存毙石,紅色通道5位,有2^5 =32種表現(xiàn)形式颓遏,綠色通道6位徐矩,有2^6 =64種表現(xiàn)形式;藍(lán)色通道5位叁幢,有2^5=32種表現(xiàn)形式
ARGB_4444 2 bytes 每個像素的ARGB通道都會保存滤灯,透明度/紅色/綠色/藍(lán)色通道4位,有2^4=16種表現(xiàn)形式
ARGB_8888 4 bytes 每個像素的ARGB通道都會保存曼玩,透明度/紅色/綠色/藍(lán)色通道8位鳞骤,有2^8=256種表現(xiàn)形式

有什么區(qū)別呢?最簡單的黍判,當(dāng)一個顏色表現(xiàn)形式越多豫尽,那么畫面整體的色彩就會更豐富,圖片質(zhì)量就會越高顷帖,當(dāng)然拂募,圖片占用的儲存空間也越大。

1.2圖片存在形式

1.文件形式(即以二進(jìn)制形式存在于硬盤上)

2.流的形式(即以二進(jìn)制形式存在于內(nèi)存中)

3.Bitmap形式

這三種形式的區(qū)別: 文件形式和流的形式對圖片體積大小并沒有影響,也就是說,如果你手機SD卡上的如果是100K,那么通過流的形式讀到內(nèi)存中,也一定是占100K的內(nèi)存,注意是流的形式,不是Bitmap的形式,當(dāng)圖片以Bitmap的形式存在時,其占用的內(nèi)存會瞬間變大, 我做分享的時候一個9.9M的圖片保存在手機相冊中顯示是238KB窟她,80M的圖片在手機相冊中顯示是1.24M。

2.開發(fā)中bitmap遇到的問題

在Android的開發(fā)中蔼水,我們經(jīng)痴鹛牵回去處理一些圖片相關(guān)的問題,比如當(dāng)加載圖片到內(nèi)存中產(chǎn)生的OOM(OutOfMemory)異常趴腋、圖片太大壓縮造成失真吊说,圖片不顯示论咏,圖片壓縮之后出現(xiàn)黑色,分享圖片漸變色出現(xiàn)色塊等颁井。

3.bitmap幾種壓縮方式

bitmap 的寬 x 高 x 每個像素所占的字節(jié) 從這個公式可以看出想要壓縮圖片大小有兩種方式:

1.減小圖片的長寬
2.減小圖片每個像素占用的字節(jié)數(shù)

3.1質(zhì)量壓縮

bitmap.compress(CompressFormat format, int quality, OutputStream stream);

圖片格式 說明
PNG 它是一種無損數(shù)據(jù)壓縮位圖圖形文件格式厅贪。這也就是說PNG 只支持無損壓縮。對于PNG 格式是有8 位雅宾、24位养涮、32位的三種形式的。區(qū)別就是對透明度的支持眉抬。
JPG 其實就是 JPEG的另一種叫法
JPEG 它是一種有損壓縮的圖片格式
WEBP Google 開發(fā)出的一種支持alpha 通道的有損壓縮和無損壓縮贯吓。同等質(zhì)量情況下比 JPEG和PNG小 25%~45%.WebP格式圖像的編碼時間“比JPEG格式圖像長8倍”(占用cpu,節(jié)省內(nèi)存
GIF 它是動態(tài)圖片的一種格式蜀变,和PNG 一樣是一種無損壓縮悄谐。
SVG 是一種無損、矢量圖(放大不失真)
WEBP的體積對比

圖片的格式有很多種库北,除了我們熟知的 JPG爬舰、PNG、GIF寒瓦,還有Webp情屹,BMP,TIFF孵构,CDR 等等幾十種屁商,用于不同的場景或平臺。

圖片常見格式

這些格式可以分為兩大類:有損壓縮和無損壓縮颈墅。

1.有損壓縮:是對圖像數(shù)據(jù)進(jìn)行處理蜡镶,去掉那些圖像上會被人眼忽略的細(xì)節(jié),然后使用附近的顏色通過漸變或其他形式進(jìn)行填充恤筛。這樣既能大大降低圖像信息的數(shù)據(jù)量官还,又不會影響圖像的還原效果。最典型的有損壓縮格式是 jpg毒坛。

2.無損壓縮:先判斷圖像上哪些區(qū)域的顏色是相同的望伦,哪些是不同的,然后把這些相同的數(shù)據(jù)信息進(jìn)行壓縮記錄煎殷,(例如一片藍(lán)色的天空之需要記錄起點和終點的位置就可以了)屯伞,而把不同的數(shù)據(jù)另外保存(例如天空上的白云和漸變等數(shù)據(jù))。常見的無損壓縮格式是 png豪直,gif劣摇。

Android 原生支持的格式只有 JPEG、PNG弓乙、GIF末融、WEBP(android 4.0 加入)钧惧、BMP。而在android層代碼中我們只能調(diào)用的編碼方式只有PNG勾习、JPEG浓瞪、和WEBP 三種。不過目前來說android 還不支持對GIF 這種的動態(tài)編碼巧婶。

注意 :我們?nèi)粘K械?.png乾颁、.jpg、.jpeg 等等指的是圖像數(shù)據(jù)經(jīng)過壓縮編碼后在媒體上的封存形式粹舵,是不能和PNG 钮孵、JPG、JEPG 混為一談的眼滤。

quality=100
quality=50

通過此種方式巴席,圖片的大小是沒有變的,因為質(zhì)量壓縮不會減少圖片的像素诅需,它是在保持像素的前提下改變圖片的位深及透明度等漾唉,來達(dá)到壓縮圖片的目的,這也是為什么該方法叫質(zhì)量壓縮方法堰塌。圖片的長赵刑,寬,像素都不變场刑,那么bitmap所占內(nèi)存大小是不會變的般此。

3.2尺寸壓縮

3.2.1鄰近采樣

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; //inSampleSize 為壓縮比 此處為1/2

bm = BitmapFactory.decodeFile("/DCIM/Camera/test.jpg", options);

采樣前:

采樣前

采樣后:

采樣后

接著我們來看看 inSampleSzie 的官方描述:

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.

從官方的inSampleSzie描述看我們可以看到 x(x 為 2 的倍數(shù))個像素最后對應(yīng)一個像素,由于采樣率設(shè)置為 1/2牵现,所以是兩個像素生成一個像素铐懊。鄰近采樣的方式比較粗暴,直接選擇其中的一個像素作為生成像素瞎疼,另一個像素直接拋棄科乎,這樣就造成了圖片變成了純綠色,也就是紅色像素被拋棄贼急。

3.2.2雙線性采樣

1.Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),bit.getHeight(), matrix, true);

2.Bitmap.createScaledBitmap(bitmapOld, 150, 150, true);

采樣前:

采樣前

采樣后:

采樣后

可以看到處理之后的圖片不是像鄰近采樣一樣純粹的一種顏色茅茂,而是兩種顏色的混合。雙線性采樣使用的是雙線性內(nèi)插值算法太抓,這個算法不像鄰近點插值算法一樣空闲,直接粗暴的選擇一個像素,而是參考了源像素相應(yīng)位置周圍 2x2 個點的值走敌,根據(jù)相對位置取對應(yīng)的權(quán)重进副,經(jīng)過計算之后得到目標(biāo)圖像。

3.2.3鄰近采樣和雙線性采樣對比

鄰近采樣的方式是最快的,因為它直接選擇其中一個像素作為生成像素影斑,但是生成的圖片可能會相對比較失真,產(chǎn)生比較明顯的鋸齒机打,最具有代表性的就是處理文字比較多的圖片在展示效果上的差別矫户,對比:

原圖
鄰近采樣
雙線性采樣對比

3.3像素壓縮

BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565; //將格式設(shè)置成RGB_565
bm = BitmapFactory.decodeFile( "/DCIM/Camera/test.jpg", options);

 注意:由于ARGB_4444的畫質(zhì)慘不忍睹,一般假如對圖片沒有透明度要求的話残邀,可以改成RGB_565皆辽,相比ARGB_8888將節(jié)省一半的內(nèi)存開銷。

4.Android中如何加載大圖片和長圖片

如果我們要加載的圖片遠(yuǎn)遠(yuǎn)大于ImageView的大小芥挣,直接用ImageView去展示的話驱闷,就會帶來不好的視覺效果,也會占用太多的內(nèi)存和性能開銷空免。甚至這張圖片足夠大到導(dǎo)致程序oom崩潰

1.壓縮

2.局部展示

有時候我們通過壓縮可以取得很好的效果空另,但有時候效果就不那么美好了,例如長圖像清明上河圖蹋砚,像這類的長圖扼菠,如果我們直接壓縮展示的話匈棘,這張圖完全看不清射富,很影響體驗。這時我們就可以采用局部展示查刻,然后滑動查看的方式去展示圖片墨坚。

public class LargeImageView extends View implements GestureDetector.OnGestureListener {

int bitmapLeft;
Paint paint;
private BitmapRegionDecoder mDecoder;

/**
 * 繪制的區(qū)域
 */
public volatile Rect mRect = new Rect();

//    private int mScaledTouchSlop;

// 分別記錄上次滑動的坐標(biāo)
private int mLastX = 0;
private int mLastY = 0;
/**
 * 圖片的寬度和高度
 */
public int mImageWidth, mImageHeight;
private GestureDetector mGestureDetector;
private BitmapFactory.Options options;


public LargeImageView(Context context) {
    this(context, null);
}

public LargeImageView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}

public LargeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
}

private void init(Context context) {
    options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.ARGB_4444;


 //        mScaledTouchSlop = ViewConfiguration.get(getContext())
//                .getScaledTouchSlop();
    //初始化手勢控制器
    mGestureDetector = new GestureDetector(context, this);
    paint = new Paint();
    paint.setColor(Color.TRANSPARENT);
    paint.setAntiAlias(true);
}

/**
 * setInputStream里面去獲得圖片的真實的寬度和高度秧饮,以及初始化我們的mDecoder。
 *
 * @param is
 */
public void setInputStream(InputStream is) {
    try {
        mDecoder = BitmapRegionDecoder.newInstance(is, false);
        BitmapFactory.Options bfOptions = new BitmapFactory.Options();
        //設(shè)置為true后泽篮。不加載到內(nèi)存就能獲取Bitmap尺寸大小盗尸。
        bfOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, bfOptions);

        mImageWidth = mDecoder.getWidth();
        mImageHeight = mDecoder.getHeight();

        requestLayout();
        invalidate();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (is != null) {
                is.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


@Override
public boolean onTouchEvent(MotionEvent ev) {
    //把觸摸事件交給手勢控制器處理
    return mGestureDetector.onTouchEvent(ev);
}


@Override
public boolean onDown(MotionEvent e) {
    mLastX = (int) e.getRawX();
    mLastY = (int) e.getRawY();
    return true;
}

@Override
public void onShowPress(MotionEvent e) {

}

@Override
public boolean onSingleTapUp(MotionEvent e) {

    return false;
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    int x = (int) e2.getRawX();
    int y = (int) e2.getRawY();
    move(x, y);

    return true;
}

/**
 * 移動的時候更新圖片顯示的區(qū)域
 *
 * @param x
 * @param y
 */
private void move(int x, int y) {

    boolean isInvalidate = false;

    int deltaX = x - mLastX;
    int deltaY = y - mLastY;
    //如果圖片寬度大于屏幕寬度
    if (mImageWidth > getWidth()) {
        //移動rect區(qū)域
        mRect.offset(-deltaX, 0);
        //檢查是否到達(dá)圖片最右端
        if (mRect.right > mImageWidth) {
            mRect.right = mImageWidth;
            mRect.left = mImageWidth - getWidth();
        }

        //檢查左端
        if (mRect.left < 0) {
            mRect.left = 0;
            mRect.right = getWidth();
        }
        isInvalidate = true;

    }
    //如果圖片高度大于屏幕高度
    if (mImageHeight > getHeight()) {
        mRect.offset(0, -deltaY);

        //是否到達(dá)最底部
        if (mRect.bottom > mImageHeight) {
            mRect.bottom = mImageHeight;
            mRect.top = mImageHeight - getHeight();
        }

        if (mRect.top < 0) {
            mRect.top = 0;
            mRect.bottom = getHeight();
        }
        isInvalidate = true;

    }

    if (isInvalidate) {
        invalidate();
    }

    mLastX = x;
    mLastY = y;
}

@Override
public void onLongPress(MotionEvent e) {
    mLastX = (int) e.getRawX();
    mLastY = (int) e.getRawY();
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    int x = (int) e2.getRawX();
    int y = (int) e2.getRawY();
    move(x, y);
    return true;
}


@SuppressLint("CheckResult")
@Override
protected void onDraw(final Canvas canvas) {
    //顯示圖片
    if (null != mDecoder) {
        Bitmap bm = mDecoder.decodeRegion(mRect, options);
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        canvas.drawBitmap(bm, bitmapLeft, 0, null);
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width = getMeasuredWidth();
    int height = getMeasuredHeight();

    bitmapLeft = width / 2 - mImageWidth / 2;//圖片距離左邊大小
    mRect.left = 0;
    mRect.top = 0;
    mRect.right = mImageWidth;
    mRect.bottom = mRect.top + height;
}
}

根據(jù)上述源碼:

在setInputStream方法里面初始BitmapRegionDecoder,獲取圖片的實際寬高;
onMeasure方法里面給Rect賦初始化值咪辱,控制開始顯示的圖片區(qū)域;
onTouchEvent監(jiān)聽用戶手勢振劳,修改Rect參數(shù)來修改圖片展示區(qū)域,并且進(jìn)行邊界檢測油狂,最后invalidate;
在onDraw里面根據(jù)Rect獲取Bitmap并且繪制历恐。

5、Android Skia 圖像引擎

Skia 是一個 Google 自己維護(hù)的 c++ 實現(xiàn)的圖像引擎专筷,實現(xiàn)了各種圖像處理功能弱贼,并且廣泛地應(yīng)用于谷歌自己和其它公司的產(chǎn)品中(如:Chrome、Firefox磷蛹、 Android等)吮旅,基于它可以很方便為操作系統(tǒng)、瀏覽器等開發(fā)圖像處理功能。

Skia 在 Android 中提供了基本的畫圖和簡單的編解碼功能庇勃,可以掛接其他的第三方編碼解碼庫或者硬件編解碼庫檬嘀,例如 libpng 和 libjpeg,libgif 等等责嚷。因此鸳兽,這個函數(shù)調(diào)用bitmap.compress(Bitmap.CompressFormat.JPEG...),實際會調(diào)用 libjpeg.so 動態(tài)庫進(jìn)行編碼壓縮罕拂。

最終 Android 編碼保存圖片的邏輯是 Java 層函數(shù)→Native 函數(shù)→Skia函數(shù)→對應(yīng)第三庫函數(shù)(例如 libjpeg)揍异。所以 skia 就像一個膠水層,用來鏈接各種第三方編解碼庫爆班,不過 Android 也會對這些庫做一些修改衷掷,比如修改內(nèi)存管理的方式等等。

Android 在之前從某種程度來說使用的算是 libjpeg 的功能閹割版柿菩,壓縮圖片默認(rèn)使用的是 standard huffman戚嗅,而不是 optimized huffman,也就是說使用的是默認(rèn)的哈夫曼表碗旅,并沒有根據(jù)實際圖片去計算相對應(yīng)的哈夫曼表渡处,Google 在初期考慮到手機的性能瓶頸,計算圖片權(quán)重這個階段非常占用 CPU 資源的同時也非常耗時祟辟,因為此時需要計算圖片所有像素 argb 的權(quán)重医瘫,這也是 Android 的圖片壓縮率對比 iOS 來說差了一些的原因之一。

圖片格式

阿里圖片庫

壓縮圖片工具類

圖好快

在線摳圖

為什么Android的圖片質(zhì)量會比iPhone的差旧困?

Android中的圖片壓縮技術(shù)詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末醇份,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吼具,更是在濱河造成了極大的恐慌僚纷,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拗盒,死亡現(xiàn)場離奇詭異怖竭,居然都是意外死亡,警方通過查閱死者的電腦和手機陡蝇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門痊臭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人登夫,你說我怎么就攤上這事广匙。” “怎么了恼策?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵鸦致,是天一觀的道長。 經(jīng)常有香客問我,道長分唾,這世上最難降的妖魔是什么抗碰? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮绽乔,結(jié)果婚禮上改含,老公的妹妹穿的比我還像新娘。我一直安慰自己迄汛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布骤视。 她就那樣靜靜地躺著鞍爱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪专酗。 梳的紋絲不亂的頭發(fā)上睹逃,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音祷肯,去河邊找鬼沉填。 笑死,一個胖子當(dāng)著我的面吹牛佑笋,可吹牛的內(nèi)容都是我干的翼闹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蒋纬,長吁一口氣:“原來是場噩夢啊……” “哼猎荠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜀备,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤关摇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后碾阁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體输虱,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年脂凶,在試婚紗的時候發(fā)現(xiàn)自己被綠了宪睹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡艰猬,死狀恐怖横堡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情冠桃,我是刑警寧澤命贴,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響胸蛛,放射性物質(zhì)發(fā)生泄漏污茵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一葬项、第九天 我趴在偏房一處隱蔽的房頂上張望泞当。 院中可真熱鬧,春花似錦民珍、人聲如沸襟士。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陋桂。三九已至,卻和暖如春蝶溶,著一層夾襖步出監(jiān)牢的瞬間嗜历,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工抖所, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梨州,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓田轧,卻偏偏與公主長得像暴匠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子涯鲁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容