Bitmap總結

相信大家在平時的android開發中肯定用過Bitmap,而且用的還不少,那麼你對它有足夠的瞭解嗎?你的使用高效嗎?對於超大圖片是如何處理的呢?下面就讓我們一起來從基礎學習一下Bitmap。

一、Bitmap是什麼?

位圖(Bitmap),又稱柵格圖或點陣圖,是使用像素陣列來表示的圖像。

位圖(Bitmap)也可指:

  • 一種數據結構,代表了有限域中的稠集,每一個元素至少出現一次,沒有其他的數據和元素相關聯。在索引,數據壓縮等方面有廣泛應用。

位圖的像素都分配有特定的位置和顏色值。每個像素的顏色信息由RGB組合或者灰度值表示。

根據位深度,可將位圖分爲1、4、8、16、24及32位圖像等。每個像素使用的信息位數越多,可用的顏色就越多,顏色表現就越逼真,相應的數據量越大。例如,位深度爲 1 的像素位圖只有兩個可能的值(黑色和白色),所以又稱爲二值位圖。位深度爲 8 的圖像有 2^8(即 256)個可能的值。位深度爲 8 的灰度模式圖像有 256 個可能的灰色值。

二、格式

1.Config

表示圖片像素類型,包括ALPHA_8、RGB_565、ARGB_4444、ARGB_8888 A:透明度;RGB分別是Red、Green、Blue,三種原色

ARGB_8888:四個通道都是8位,每個像素佔用4個字節,圖片質量是最高的,但是佔用的內存也是最大的;

ARGB_4444:四個通道都是4位,每個像素佔用2個字節,圖片的失真比較嚴重;

RGB_565:沒有A通道,每個像素佔用2個字節,圖片失真小,但是沒有透明度;

ALPHA_8:只有A通道,每個像素佔用1個字節大大小,只有透明度,沒有顏色值。

使用場景總結:ARGB_4444失真嚴重,基本不用;ALPHA_8使用場景特殊,比如設置遮蓋效果等;不需要設置透明度,RGB_565是個不錯的選擇;既要設置透明度,對圖片質量要求又高,就用ARGB_8888。

2.CompressFormat

Bitmap.CompressFormat.JPEG、Bitmap.CompressFormat.PNG、Bitmap.CompressFormat.WEBP三種壓縮格式

JPEG:一種有損壓縮(JPEG2000既可以有損也可以無損),".jpg"或者".jpeg"; 優點:採用了直接色,有豐富的色彩,適合存儲照片和生動圖像效果;缺點:有損,不適合用來存儲logo、線框類圖。

PNG: 一種無損壓縮,".png"; 優點:支持透明、無損,主要用於小圖標,透明背景等;缺點:若色彩複雜,則圖片生成後文件很大;

WEBP:以WebP算法進行壓縮;Google開發的新的圖片格式,同時支持無損和有損壓縮,使用直接色。無損壓縮,相同質量的webp比PNG小大約26%;有損壓縮,相同質量的webp比JPEG小25%-34% 支持動圖,基本取代gif。

三、創建方法

1.根據現有的Bitmap來創建新的Bitmap

/**
* 通過矩陣的方式,返回原始 Bitmap 中的一個不可變子集。新 Bitmap 可能返回的就是原始的 Bitmap,也可能還是複製出來的。
* 新 Bitmap 與原始 Bitmap 具有相同的密度(density)和顏色空間;
*
* @param source   原始 Bitmap
* @param x        在原始 Bitmap 中 x方向的其起始座標(你可能只需要原始 Bitmap x方向上的一部分)
* @param y        在原始 Bitmap 中 y方向的其起始座標(你可能只需要原始 Bitmap y方向上的一部分)
* @param width    需要返回 Bitmap 的寬度(px)(如果超過原始Bitmap寬度會報錯)
* @param height   需要返回 Bitmap 的高度(px)(如果超過原始Bitmap高度會報錯)
* @param m        Matrix類型,表示需要做的變換操作
* @param filter   是否需要過濾,只有 matrix 變換不只有平移操作纔有效
*/ public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height, @Nullable Matrix m, boolean filter) 

2.通過像素點數組創建空的Bitmap

/**
     * 
     * 返回具有指定寬度和高度的不可變位圖,每個像素值設置爲colors數組中的對應值。
     * 其初始密度由給定的確定DisplayMetrics。新創建的位圖位於sRGB 顏色空間中。
     * @param display  顯示將顯示此位圖的顯示的度量標準
     * @param colors   用於初始化像素的sRGB數組
     * @param offset   顏色數組中第一個顏色之前要跳過的值的數量
     * @param stride   行之間數組中的顏色數(必須> = width或<= -width)
     * @param width    位圖的寬度
     * @param height   位圖的高度
     * @param config   要創建的位圖配置。如果配置不支持每像素alpha(例如RGB_565),
     * 那麼colors []中的alpha字節將被忽略(假設爲FF)
     */ public static Bitmap createBitmap(@NonNull DisplayMetrics display, @NonNull @ColorInt int[] colors, int offset, int stride, int width, int height, @NonNull Config config) 

3.創建縮放的Bitmap

/**
* 對Bitmap進行縮放,縮放成寬 dstWidth、高 dstHeight 的新Bitmap
*/ public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,boolean filter)

四、常用方法

public void recycle()——回收位圖佔用的內存空間,把位圖標記爲Dead 
public final boolean isRecycled() ——判斷位圖內存是否已釋放 
public final int getWidth()——獲取位圖的寬度 
public final int getHeight()——獲取位圖的高度 
public final boolean isMutable()——圖片是否可修改 
public int getScaledWidth(Canvas canvas)——獲取指定密度轉換後的圖像的寬度 
public int getScaledHeight(Canvas canvas)——獲取指定密度轉換後的圖像的高度 
public boolean compress(CompressFormat format, int quality, OutputStream stream)——按指定的圖片格式以及畫質,將圖片轉換爲輸出流。 
format:Bitmap.CompressFormat.PNG或Bitmap.CompressFormat.JPEG 
quality:畫質,0-100.0表示最低畫質壓縮,100以最高畫質壓縮。對於PNG等無損格式的圖片,會忽略此項設置。 

常用的靜態方法: 
public static Bitmap createBitmap(Bitmap src) ——以src爲原圖生成不可變得新圖像 
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, 
            int dstHeight, boolean filter)——以src爲原圖,創建新的圖像,指定新圖像的高寬以及是否可變。 
public static Bitmap createBitmap(int width, int height, Config config)——創建指定格式、大小的位圖 
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height)——以source爲原圖,創建新的圖片,指定起始座標以及新圖像的高寬。 

五、BitmapFactory

Option 參數類: 
public boolean inJustDecodeBounds——如果設置爲true,不獲取圖片,不分配內存,但會返回圖片的高度寬度信息。 
public int inSampleSize——圖片縮放的倍數。如果設爲4,則寬和高都爲原來的1/4,則圖是原來的1/16。 
public int outWidth——獲取圖片的寬度值 
public int outHeight——獲取圖片的高度值 
public int inDensity——用於位圖的像素壓縮比 
public int inTargetDensity——用於目標位圖的像素壓縮比(要生成的位圖) 
public boolean inScaled——設置爲true時進行圖片壓縮,從inDensity到inTargetDensity。

使用BitmapFactory  可從資源files, streams, and byte-arrays中解碼生成Bitmap對象。
讀取一個文件路徑得到一個位圖。如果指定文件爲空或者不能解碼成文件,則返回NULL。 
public static Bitmap decodeFile(String pathName, Options opts) 
public static Bitmap decodeFile(String pathName)

從輸入流中解碼位圖
public static Bitmap decodeStream(InputStream is) 

示例:

try {
    FileInputStream in = new FileInputStream("/sdcard/Download/sample.png");
} catch (FileNotFoundException e) {
    e.printStackTrace();
}

Bitmap bitmap = BitmapFactory.decodeStream(in);
Bitmap bm = BitmapFactory.decodeFile(sd_path); // 間接調用 BitmapFactory.decodeStream

讀取一個資源文件得到一個位圖。如果位圖數據不能被解碼,或者opts參數只請求大小信息時,則返回NuLL。 
(即當Options.inJustDecodeBounds=true,只請求圖片的大小信息。) 
public static Bitmap decodeResource(Resources res, int id) 
public static Bitmap decodeResource(Resources res, int id, Options opts) 
示例:

Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.sample); // 間接調用 BitmapFactory.decodeStream

從字節數組中解碼生成不可變的位圖 
public static Bitmap decodeByteArray(byte[] data, int offset, int length) 

// InputStream轉換成byte[]
Bitmap bm = BitmapFactory.decodeByteArray(myByte,0,myByte.length);

六、高效加載

核心是利用BitmapFactory加載一個圖片時(從文件系統、資源、輸入流以及字節數組)中加載一個Bitmap對象的時候,選擇合適的採樣率進行加載,即Options參數的採樣率參數對要加載的圖片進行縮放,變成合適ImageView的大小的圖片。縮放率是1/採樣率的平方。具體做法如下:

第一:首先設置Options的inJustDecodeBounds爲true並加載圖片,這樣只會獲取圖片的參數(長寬)

第二:根據需要的長寬對圖片的長寬不停做/2操作,計算合適的採樣率

第三:根據新的採樣率,重新加載圖片

示例:

public static Bitmap decodeSampledBitmapFromResource(Resourcesres, int resId, int reqWidth, int reqHeight){
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    //加載圖片
    BitmapFactory.decodeResource(res,resId,options);
    //計算縮放比
    options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
    //重新加載圖片
    options.inJustDecodeBounds =false;
    return BitmapFactory.decodeResource(res,resId,options);
} 

private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
    int height = options.outHeight;
    int width = options.outWidth;
    int inSampleSize = 1;
    if(height>reqHeight||width>reqWidth){
        int halfHeight = height/2;
        int halfWidth = width/2;
        //計算縮放比, 是2的指數
        while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
            inSampleSize*=2;
        }
    }
    return inSampleSize;
}

七、巨圖加載

使用BitmapRegionDecoder來加載,顯示指定矩形部分的內容,可以和手勢監聽相配合實現滑動查看

BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
bitmapRegionDecoder.decodeRegion(rect, options);

八、常見操作

摘自:http://www.jb51.net/article/32366.htm

//方法:
//1 生成圓角Bitmap圖片
//2 生成Bitmap縮量圖
//3 壓縮圖片場長寬以及kB
//注意:
//以上代碼,測試其中一個方法時最好註釋掉其餘的代碼
public class MainActivity extends Activity {
    private ImageView imageView;
    private Bitmap copyRawBitmap1;
    private Bitmap copyRawBitmap2;
    private Bitmap copyRawBitmap3;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        imageView = (ImageView) findViewById(R.id.imageView);
        //第一種方式:從資源文件中得到圖片
        Bitmap rawBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.haha);
        copyRawBitmap1 = rawBitmap;
        copyRawBitmap2 = rawBitmap;
        copyRawBitmap3 = rawBitmap;
        //第二種方式:從SD卡中得到圖片(方法1)
        String SDCarePath = Environment.getExternalStorageDirectory().toString();
        String filePath = SDCarePath + "/" + "haha.jpg";
        Bitmap rawBitmap1 = BitmapFactory.decodeFile(filePath, null);
        //第二種方式:從SD卡中得到圖片(方法2)
        InputStream inputStream = getBitmapInputStreamFromSDCard("haha.jpg");
        Bitmap rawBitmap2 = BitmapFactory.decodeStream(inputStream);

        //————>以下爲將設置圖片的圓角
        Bitmap roundCornerBitmap = this.toRoundCorner(rawBitmap, 40);
        imageView.setImageBitmap(roundCornerBitmap);
        //————>以上爲將設置圖片的圓角
        
        //————>以下爲將圖片高寬和的大小kB壓縮
        // 得到圖片原始的高寬
        int rawHeight = rawBitmap.getHeight();
        int rawWidth = rawBitmap.getWidth();
        // 設定圖片新的高寬
        int newHeight = 500;
        int newWidth = 500;
        // 計算縮放因子
        float heightScale = ((float) newHeight) / rawHeight;
        float widthScale = ((float) newWidth) / rawWidth;
        // 新建立矩陣
        Matrix matrix = new Matrix();
        matrix.postScale(heightScale, widthScale);
        // 設置圖片的旋轉角度
        //matrix.postRotate(-30);
        // 設置圖片的傾斜
        //matrix.postSkew(0.1f, 0.1f);
        //將圖片大小壓縮
        //壓縮後圖片的寬和高以及kB大小均會變化
        Bitmap newBitmap = Bitmap.createBitmap(rawBitmap, 0, 0, rawWidth, rawWidth, matrix, true);
        // 將Bitmap轉換爲Drawable
        Drawable newBitmapDrawable = new BitmapDrawable(newBitmap);
        imageView.setImageDrawable(newBitmapDrawable);
        //然後將Bitmap保存到SDCard中,方便於原圖片的比較
        this.compressAndSaveBitmapToSDCard(newBitmap, "xx100.jpg", 80);
        //問題:
        //原圖大小爲625x690 90.2kB
        //如果設置圖片500x500 壓縮後大小爲171kB.即壓縮後kB反而變大了.
        //原因是將:compress(Bitmap.CompressFormat.JPEG, quality, fileOutputStream);
        //第二個參數quality設置得有些大了(比如100).
        //常用的是80,剛設100太大了造成的.
        //————>以上爲將圖片高寬和的大小kB壓縮


        //————>以下爲將圖片的kB壓縮,寬高不變
        this.compressAndSaveBitmapToSDCard(copyRawBitmap1, "0011fa.jpg", 80);
        //————>以上爲將圖片的kB壓縮,寬高不變

        //————>以下爲獲取SD卡圖片的縮略圖方法1
        String SDCarePath1 = Environment.getExternalStorageDirectory().toString();
        String filePath1 = SDCarePath1 + "/" + "haha.jpg";
        Bitmap bitmapThumbnail1 = this.getBitmapThumbnail(filePath1);
        imageView.setImageBitmap(bitmapThumbnail1);
        //————>以上爲獲取SD卡圖片的縮略圖方法1

        //————>以下爲獲取SD卡圖片的縮略圖方法2
        String SDCarePath2 = Environment.getExternalStorageDirectory().toString();
        String filePath2 = SDCarePath2 + "/" + "haha.jpg";
        Bitmap tempBitmap = BitmapFactory.decodeFile(filePath2);
        Bitmap bitmapThumbnail2 = ThumbnailUtils.extractThumbnail(tempBitmap, 100, 100);
        imageView.setImageBitmap(bitmapThumbnail2);
        //————>以上爲獲取SD卡圖片的縮略圖方法2

    }

    //讀取SD卡下的圖片
    private InputStream getBitmapInputStreamFromSDCard(String fileName) {
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            String SDCarePath = Environment.getExternalStorageDirectory().toString();
            String filePath = SDCarePath + File.separator + fileName;
            File file = new File(filePath);
            try {
                FileInputStream fileInputStream = new FileInputStream(file);
                return fileInputStream;
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return null;
    }


    //獲取SDCard的目錄路徑功能
    private String getSDCardPath() {
        String SDCardPath = null;
        // 判斷SDCard是否存在
        boolean IsSDcardExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
        if (IsSDcardExist) {
            SDCardPath = Environment.getExternalStorageDirectory().toString();
        }
        return SDCardPath;
    }

    //壓縮且保存圖片到SDCard
    private void compressAndSaveBitmapToSDCard(Bitmap rawBitmap, String fileName, int quality) {
        String saveFilePaht = this.getSDCardPath() + File.separator + fileName;
        File saveFile = new File(saveFilePaht);
        if (!saveFile.exists()) {
            try {
                saveFile.createNewFile();
                FileOutputStream fileOutputStream = new FileOutputStream(saveFile);
                if (fileOutputStream != null) {
                    //imageBitmap.compress(format, quality, stream);
                    //把位圖的壓縮信息寫入到一個指定的輸出流中
                    //第一個參數format爲壓縮的格式
                    //第二個參數quality爲圖像壓縮比的值,0-100.0 意味着小尺寸壓縮,100意味着高質量壓縮
                    //第三個參數stream爲輸出流
                    rawBitmap.compress(Bitmap.CompressFormat.JPEG, quality, fileOutputStream);
                }
                fileOutputStream.flush();
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();

            }
        }
    }

    //獲取圖片的縮略圖
    private Bitmap getBitmapThumbnail(String filePath) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        //true那麼將不返回實際的bitmap對象,不給其分配內存空間但是可以得到一些解碼邊界信息即圖片大小等信息
        options.inJustDecodeBounds = true;
        //此時rawBitmap爲null
        Bitmap rawBitmap = BitmapFactory.decodeFile(filePath, options);
        if (rawBitmap == null) {
            System.out.println("此時rawBitmap爲null");
        }
        //inSampleSize表示縮略圖大小爲原始圖片大小的幾分之一,若該值爲3
        //則取出的縮略圖的寬和高都是原始圖片的1/3,圖片大小就爲原始大小的1/9
        //計算sampleSize
        int sampleSize = computeSampleSize(options, 150, 200 * 200);
        //爲了讀到圖片,必須把options.inJustDecodeBounds設回false
        options.inJustDecodeBounds = false;
        options.inSampleSize = sampleSize;
        //原圖大小爲625x690 90.2kB
        //測試調用computeSampleSize(options, 100, 200*100);
        //得到sampleSize=8
        //得到寬和高位79和87
        //79*8=632 87*8=696
        Bitmap thumbnailBitmap = BitmapFactory.decodeFile(filePath, options);
        //保存到SD卡方便比較
        this.compressAndSaveBitmapToSDCard(thumbnailBitmap, "15.jpg", 80);
        return thumbnailBitmap;
    }

    //參考資料:
    //http://my.csdn.net/zljk000/code/detail/18212
    //第一個參數:原本Bitmap的options
    //第二個參數:希望生成的縮略圖的寬高中的較小的值
    //第三個參數:希望生成的縮量圖的總像素
    public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }
        return roundedSize;
    }

    private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        //原始圖片的寬
        double w = options.outWidth;
        //原始圖片的高
        double h = options.outHeight;
        System.out.println("========== w=" + w + ",h=" + h);
        int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math
                .sqrt(w * h / maxNumOfPixels));
        int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(
                Math.floor(w / minSideLength), Math.floor(h / minSideLength));
        if (upperBound < lowerBound) {
            // return the larger one when there is no overlapping zone.
            return lowerBound;
        }
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

    /**
     * @param bitmap 需要修改的圖片
     * @param pixels 圓角的弧度
     * @return 圓角圖片
     */
    //參考資料:
    //http://blog.csdn.net/c8822882/article/details/6906768
    public Bitmap toRoundCorner(Bitmap bitmap, int pixels) {
        Bitmap roundCornerBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(roundCornerBitmap);
        int color = 0xff424242;//int color = 0xff424242;
        Paint paint = new Paint();
        paint.setColor(color);
        //防止鋸齒
        paint.setAntiAlias(true);
        Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        RectF rectF = new RectF(rect);
        float roundPx = pixels;
        //相當於清屏
        canvas.drawARGB(0, 0, 0, 0);
        //先畫了一個帶圓角的矩形
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        //再把原來的bitmap畫到現在的bitmap!!!注意這個理解
        canvas.drawBitmap(bitmap, rect, rect, paint);
        return roundCornerBitmap;
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章