Bitmap優(yōu)化

網(wǎng)絡的優(yōu)化

1.請求的緩存。
Http請求是可以做緩存的。

三級緩存
內(nèi)存緩存、外部緩存
算法LruCache
+自定的 一周過期等等。
Sqlite緩存+加密


BitmapFactory.Options:參數(shù):
inDensity:bitmap的像素密度
inTargetDensity:bitmap最終的像素密度
DensityDpi(屏幕的像素密度) 分辨率 Density
160dpi 320x533 1
px = dp*Density

手機像素表

圖片存在的幾種形式:

  • File
  • 流的形式
  • Bitmap的形式---內(nèi)存

圖片壓縮方式

1.質(zhì)量壓縮

AB
CD
壓縮后
AA
AA
原理:通過算法摳掉(同化)了圖片中的一些某個些點附近相近的像素,達到降低質(zhì)量介紹文件大小的目的。
減小了圖片質(zhì)量
** 注意:*它其實只能實現(xiàn)對file的影響,對加載這個圖片出來的bitmap內(nèi)存是無法節(jié)省的,還是那么大。 因為bitmap在內(nèi)存中的大小是按照像素計算的,也就是widthheight,對于質(zhì)量壓縮,并不會改變圖片的真實的像素(像素大小不會變)。

** 使用場景:**
將圖片壓縮后保存到本地,或者將圖片上傳到服務器。根據(jù)實際需求來。

public static void compressImageToFile(Bitmap bmp,File file){
    //0~100
    int quality = 50;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bmp.compress(Bitmap.CompressFormat.JPEG, quality , baos );
    try {
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(baos.toByteArray());
        fos.flush();
        fos.close();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
2.尺寸壓縮

通過減少單位尺寸的像素值,真正意義上的降低像素。1020*8880

** 使用場景:** 緩存縮略圖的時候(頭像處理)

public static void compressBitmapToFileBySize(Bitmap bmp,File file){
    //壓縮尺寸倍數(shù),值越大,圖片的尺寸就越小
    int ratio = 4;
    Bitmap result = Bitmap.createBitmap(bmp.getWidth()/ratio, 
              bmp.getHeight()/ratio, Bitmap.Config.ARGB_8888);
    
    Canvas canvas = new Canvas(result);
    RectF rect = new RectF(0, 0, bmp.getWidth()/ratio, bmp.getHeight()/ratio);
    canvas.drawBitmap(bmp, null, rect , null);
    
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    result.compress(Bitmap.CompressFormat.JPEG, 100, baos);
    try {
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(baos.toByteArray());
        fos.flush();
        fos.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    
}
3.采樣率壓縮
4.終極壓縮(NDK層壓縮)

1.IOS拍照1M的圖片要比安卓拍照排出來的5M的圖片還要清晰。
都是在同一個環(huán)境下,保存的都是JPEG。為什么?

2.圖像處理引擎
95年 JPEG處理引擎,用于最初的在PC上面處理圖片的引擎。
05年 skia開源的引擎, 開發(fā)了一套基于JPEG處理引擎的第二次開發(fā)。便于瀏覽器的使用。

07年, 安卓上面用的什么引擎?
skia引擎,閹割版
谷歌拿了skia 思考了半天做了一個決定,去掉一個編碼算法---哈夫曼算法。采用定長編碼算法。但是解碼還是保留了哈夫曼算法。導致了圖片處理后文件變大了。

理由:當時由于CPU和內(nèi)存在手機上都非常吃緊 性能差,而哈夫曼算法非常吃CPU,被迫用了其他的算法。

我們的優(yōu)化:
繞過安卓Bitmap API層,來自己編碼實現(xiàn)----修復使用哈夫曼算法。

argb
一個像素點包涵四個信息:alpha,red,green,blue

a b c d e

abcde acdbe bacde ……

101010100011100
a:001
b:010
c:011
d:100
e:101
用3位來表示一個字符信息,屬于定長編碼的最優(yōu)。

abcde
001 010 011 100 101
加權信息編碼

a:80%
b:10%
c:10%
d:0%
e:0%
這種情況,編碼就可以優(yōu)化了
a:01
b:10
c:11
優(yōu)化后的abc:01 10 11
優(yōu)化前的abc:001 010 011

通過加權可以知道那個信息為0(即沒有),那么就表示可以省略“一位”,這樣數(shù)據(jù)量信息就會減少,從而使圖片變得更小。

那么如何得到每一個字母出現(xiàn)的權重?
哈夫曼編碼:需要去掃描每個信息(圖片信息--每一個像素包括ARGB),需要大量計算,很吃CPU。
1280800像素4

如何實現(xiàn)?

下載JPEG引擎使用的庫---libjpeg庫
http://www.ijg.org/

基于該引擎來做一定的開發(fā)----自己實現(xiàn)編碼。

1.導入庫文件libjpegbither.so
2.導入頭文件
3.寫mk文件
Android.mk
Applicatoin.mk
4.寫代碼
C++: XX.cpp
C: XX.c

操作步驟:
1.將android的bitmap解碼,并轉(zhuǎn)換成RGB數(shù)據(jù),一個圖片信息---像素點(argb),把alpha去掉。
2.JPEG對象分配空間以及初始化
3.指定壓縮數(shù)據(jù)源
4.獲取文件信息
5.為壓縮設置參數(shù),比如圖像大小、類型、顏色空間
boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */
6.開始壓縮
jpeg_start_compress()
7.壓縮結(jié)束
jpeg_finish_compress()
8.釋放資源


/*
 * Copyright 2014 http://Bither.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.bither.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.Log;

public class NativeUtil {
     private static int DEFAULT_QUALITY = 95;

        /**
         * @param bit      bitmap對象
         * @param fileName 指定保存目錄名
         * @param optimize 是否采用哈弗曼表數(shù)據(jù)計算 品質(zhì)相差5-10倍
         * @Description: JNI基本壓縮
         */
        public static void compressBitmap(Bitmap bit, String fileName, boolean optimize) {
            saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize);
        }

        /**
         * @param image    bitmap對象
         * @param filePath 要保存的指定目錄
         * @Description: 通過JNI圖片壓縮把Bitmap保存到指定目錄
         */
        public static void compressBitmap(Bitmap image, String filePath) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 質(zhì)量壓縮方法,這里100表示不壓縮,把壓縮后的數(shù)據(jù)存放到baos中
            int options = 20;
            // JNI調(diào)用保存圖片到SD卡 這個關鍵
            NativeUtil.saveBitmap(image, options, filePath, true);
        }

        /**
         * 計算縮放比
         *
         * @param bitWidth  當前圖片寬度
         * @param bitHeight 當前圖片高度
         * @return
         * @Description:函數(shù)描述
         */
        public static int getRatioSize(int bitWidth, int bitHeight) {
            // 圖片最大分辨率
            int imageHeight = 1920;
            int imageWidth = 1080;
            // 縮放比
            int ratio = 1;
            // 縮放比,由于是固定比例縮放,只用高或者寬其中一個數(shù)據(jù)進行計算即可
            if (bitWidth > bitHeight && bitWidth > imageWidth) {
                // 如果圖片寬度比高度大,以寬度為基準
                ratio = bitWidth / imageHeight;
            } else if (bitWidth < bitHeight && bitHeight > imageHeight) {
                // 如果圖片高度比寬度大,以高度為基準
                ratio = bitHeight / imageHeight;
            }
            // 最小比率為1
            if (ratio <= 0)
                ratio = 1;
            return ratio;
        }

        /**
         * 調(diào)用native方法
         *
         * @param bit
         * @param quality
         * @param fileName
         * @param optimize
         * @Description:函數(shù)描述
         */
        public static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) {
            compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize);
        }

        /**
         * 調(diào)用底層 bitherlibjni.c中的方法
         *
         * @param bit
         * @param w
         * @param h
         * @param quality
         * @param fileNameBytes
         * @param optimize
         * @return
         * @Description:函數(shù)描述
         */
        public static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
                                                    boolean optimize);

        /**
         * 加載lib下兩個so文件
         */
        static {
            System.loadLibrary("jpegbither");
            System.loadLibrary("bitherjni");
        }


        /**
         * 1. 質(zhì)量壓縮
                 設置bitmap options屬性,降低圖片的質(zhì)量,像素不會減少
                 第一個參數(shù)為需要壓縮的bitmap圖片對象,第二個參數(shù)為壓縮后圖片保存的位置
                 設置options 屬性0-100,來實現(xiàn)壓縮
         * @param bmp
         * @param file
         */
        public static void compressImageToFile(Bitmap bmp,File file) {
            // 0-100 100為不壓縮
            int options = 20;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 把壓縮后的數(shù)據(jù)存放到baos中
            bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
            try {
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        /**
         *
         * 2. 尺寸壓縮
         通過縮放圖片像素來減少圖片占用內(nèi)存大小
         * @param bmp
         * @param file
         */

        public static void compressBitmapToFile(Bitmap bmp, File file){
            // 尺寸壓縮倍數(shù),值越大,圖片尺寸越小
            int ratio = 8;
            // 壓縮Bitmap到對應尺寸
            Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888);
            Canvas canvas = new Canvas(result);
            Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
            canvas.drawBitmap(bmp, null, rect, null);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 把壓縮后的數(shù)據(jù)存放到baos中
            result.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
            try {
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        /**
         * 設置圖片的采樣率,降低圖片像素
         * @param filePath
         * @param file
         */
        public static void compressBitmap(String filePath, File file){
            // 數(shù)值越高,圖片像素越低
            int inSampleSize = 8;
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = false;
//          options.inJustDecodeBounds = true;//為true的時候不會真正加載圖片,而是得到圖片的寬高信息。
            //采樣率
            options.inSampleSize = inSampleSize;
            Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 把壓縮后的數(shù)據(jù)存放到baos中
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
            try {
                if(file.exists())
                {
                    file.delete();
                }
                else {
                    file.createNewFile();
                }
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
}

#include "bitherlibjni.h"
#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>

//統(tǒng)一編譯方式
extern "C" {
#include "jpeg/jpeglib.h"
#include "jpeg/cdjpeg.h"        /* Common decls for cjpeg/djpeg applications */
#include "jpeg/jversion.h"      /* for version message */
#include "jpeg/android/config.h"
}


#define LOG_TAG "jni"
#define LOGW(...)  __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#define true 1
#define false 0

typedef uint8_t BYTE;

char *error;
struct my_error_mgr {
  struct jpeg_error_mgr pub;
  jmp_buf setjmp_buffer;
};

typedef struct my_error_mgr * my_error_ptr;

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  (*cinfo->err->output_message) (cinfo);
  error=(char*)myerr->pub.jpeg_message_table[myerr->pub.msg_code];
  LOGE("jpeg_message_table[%d]:%s", myerr->pub.msg_code,myerr->pub.jpeg_message_table[myerr->pub.msg_code]);
 // LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
//  LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
//  LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
  longjmp(myerr->setjmp_buffer, 1);
}

int generateJPEG(BYTE* data, int w, int h, int quality,
        const char* outfilename, jboolean optimize) {

    //jpeg的結(jié)構體,保存的比如寬、高、位深、圖片格式等信息,相當于java的類
    struct jpeg_compress_struct jcs;

    //當讀完整個文件的時候就會回調(diào)my_error_exit這個退出方法。setjmp是一個系統(tǒng)級函數(shù),是一個回調(diào)。
    struct my_error_mgr jem;
    jcs.err = jpeg_std_error(&jem.pub);
    jem.pub.error_exit = my_error_exit;
    if (setjmp(jem.setjmp_buffer)) {
        return 0;
    }

    //初始化jsc結(jié)構體
    jpeg_create_compress(&jcs);
    //打開輸出文件 wb:可寫byte
    FILE* f = fopen(outfilename, "wb");
    if (f == NULL) {
        return 0;
    }
    //設置結(jié)構體的文件路徑
    jpeg_stdio_dest(&jcs, f);
    jcs.image_width = w;//設置寬高
    jcs.image_height = h;
//  if (optimize) {
//      LOGI("optimize==ture");
//  } else {
//      LOGI("optimize==false");
//  }

    //看源碼注釋,設置哈夫曼編碼:/* TRUE=arithmetic coding, FALSE=Huffman */
    jcs.arith_code = false;
    int nComponent = 3;
    /* 顏色的組成 rgb,三個 # of color components in input image */
    jcs.input_components = nComponent;
    //設置結(jié)構體的顏色空間為rgb
    jcs.in_color_space = JCS_RGB;
//  if (nComponent == 1)
//      jcs.in_color_space = JCS_GRAYSCALE;
//  else
//      jcs.in_color_space = JCS_RGB;

    //全部設置默認參數(shù)/* Default parameter setup for compression */
    jpeg_set_defaults(&jcs);
    //是否采用哈弗曼表數(shù)據(jù)計算 品質(zhì)相差5-10倍
    jcs.optimize_coding = optimize;
    //設置質(zhì)量
    jpeg_set_quality(&jcs, quality, true);
    //開始壓縮,(是否寫入全部像素)
    jpeg_start_compress(&jcs, TRUE);

    JSAMPROW row_pointer[1];
    int row_stride;
    //一行的rgb數(shù)量
    row_stride = jcs.image_width * nComponent;
    //一行一行遍歷
    while (jcs.next_scanline < jcs.image_height) {
        //得到一行的首地址
        row_pointer[0] = &data[jcs.next_scanline * row_stride];

        //此方法會將jcs.next_scanline加1
        jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:寫入的行數(shù)
    }
    jpeg_finish_compress(&jcs);//結(jié)束
    jpeg_destroy_compress(&jcs);//銷毀 回收內(nèi)存
    fclose(f);//關閉文件

    return 1;
}

/**
 * byte數(shù)組轉(zhuǎn)C的字符串
 */
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
    char* rtn = NULL;
    jsize alen = env->GetArrayLength( barr);
    jbyte* ba = env->GetByteArrayElements( barr, 0);
    if (alen > 0) {
        rtn = (char*) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements( barr, ba, 0);
    return rtn;
}

jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
        jclass thiz, jobject bitmapcolor, int w, int h, int quality,
        jbyteArray fileNameStr, jboolean optimize) {
    BYTE *pixelscolor;
    //1.將bitmap里面的所有像素信息讀取出來,并轉(zhuǎn)換成RGB數(shù)據(jù),保存到二維byte數(shù)組里面
    //處理bitmap圖形信息方法1 鎖定畫布
    AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor);

    //2.解析每一個像素點里面的rgb值(去掉alpha值),保存到一維數(shù)組data里面
    BYTE *data;
    BYTE r,g,b;
    data = (BYTE*)malloc(w*h*3);//每一個像素都有三個信息RGB
    BYTE *tmpdata;
    tmpdata = data;//臨時保存data的首地址
    int i=0,j=0;
    int color;
    for (i = 0; i < h; ++i) {
        for (j = 0; j < w; ++j) {
            //解決掉alpha
            //獲取二維數(shù)組的每一個像素信息(四個部分a/r/g/b)的首地址
            color = *((int *)pixelscolor);//通過地址取值
            //0~255:
//          a = ((color & 0xFF000000) >> 24);
            r = ((color & 0x00FF0000) >> 16);
            g = ((color & 0x0000FF00) >> 8);
            b = ((color & 0x000000FF));
            //改值?。。?---保存到data數(shù)據(jù)里面
            *data = b;
            *(data+1) = g;
            *(data+2) = r;
            data = data + 3;
            //一個像素包括argb四個值,每+4就是取下一個像素點
            pixelscolor += 4;
        }
    }
    //處理bitmap圖形信息方法2 解鎖
    AndroidBitmap_unlockPixels(env,bitmapcolor);
    char* fileName = jstrinTostring(env,fileNameStr);
    //調(diào)用libjpeg核心方法實現(xiàn)壓縮
    int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
    if(resultCode ==0){
        jstring result = env->NewStringUTF("-1");
        return result;
    }
    return env->NewStringUTF("1");
}

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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