Android性能優化系列之Bitmap圖片優化

閆佳麗 7年前發布 | 20K 次閱讀 Bitmap 性能優化 安卓開發 Java開發

在Android開發過程中,Bitmap往往會給開發者帶來一些困擾,因為對Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError - 內存溢出),本篇博客,我們將一起探討Bitmap的性能優化。

為什么Bitmap會導致OOM?

1.每個機型在編譯ROM時都設置了一個應用堆內存VM值上限dalvik.vm.heapgrowthlimit,用來限定每個應用可用的最大內存,超出這個最大值將會報OOM。這個閥值,一般根據手機屏幕dpi大小遞增,dpi越小的手機,每個應用可用最大內存就越低。所以當加載圖片的數量很多時,就很容易超過這個閥值,造成OOM。

2.圖片分辨率越高,消耗的內存越大,當加載高分辨率圖片的時候,將會非常占用內存,一旦處理不當就會OOM。例如,一張分辨率為:1920x1080的圖片。如果Bitmap使用 ARGB_8888 32位來平鋪顯示的話,占用的內存是1920x1080x4個字節,占用將近8M內存,可想而知,如果不對圖片進行處理的話,就會OOM。

3.在使用ListView, GridView等這些大量加載view的組件時,如果沒有合理的處理緩存,大量加載Bitmap的時候,也將容易引發OOM

Bitmap基礎知識

一張圖片Bitmap所占用的內存 = 圖片長度 x 圖片寬度 x 一個像素點占用的字節數

而Bitmap.Config,正是指定單位像素占用的字節數的重要參數。

其中,A代表透明度;R代表紅色;G代表綠色;B代表藍色。

ALPHA_8

表示8位Alpha位圖,即A=8,一個像素點占用1個字節,它沒有顏色,只有透明度

ARGB_4444

表示16位ARGB位圖,即A=4,R=4,G=4,B=4,一個像素點占4+4+4+4=16位,2個字節

ARGB_8888

表示32位ARGB位圖,即A=8,R=8,G=8,B=8,一個像素點占8+8+8+8=32位,4個字節

RGB_565表示16位RGB位圖,即R=5,G=6,B=5,它沒有透明度,一個像素點占5+6+5=16位,2個字節

一張圖片Bitmap所占用的內存 = 圖片長度 x 圖片寬度 x 一個像素點占用的字節數

根據以上的算法,可以計算出圖片占用的內存,以100*100像素的圖片為例

BitmapFactory解析Bitmap的原理

BitmapFactory提供的解析Bitmap的靜態工廠方法有以下五種:

Bitmap decodeFile(...)
Bitmap decodeResource(...)
Bitmap decodeByteArray(...)
Bitmap decodeStream(...)
Bitmap decodeFileDescriptor(...)

其中常用的三個:decodeFile、decodeResource、decodeStream。

decodeFile和decodeResource其實最終都是調用decodeStream方法來解析Bitmap

decodeFile方法代碼:

public static Bitmap decodeFile(String pathName, Options opts) {
        Bitmap bm = null;
        InputStream stream = null;
        try {
            stream = new FileInputStream(pathName);
            bm = decodeStream(stream, null, opts);
        } catch (Exception e) {
            /*  do nothing.
                If the exception happened on open, bm will be null.
            */
            Log.e("BitmapFactory", "Unable to decode stream: " + e);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // do nothing here
                }
            }
        }

decodeResource方法的代碼:

public static Bitmap decodeResource(Resources res, int id, Options opts) {
        Bitmap bm = null;
        InputStream is = null; 

        try {
            final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts);
        } catch (Exception e) {
            /*  do nothing.
                If the exception happened on open, bm will be null.
                If it happened on close, bm is still valid.
            */
        } finally {
            try {
                if (is != null) is.close();
            } catch (IOException e) {
                // Ignore
            }
        }

        if (bm == null && opts != null && opts.inBitmap != null) {
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        }

        return bm;
    }

decodeStream的邏輯如下:

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
        // we don't throw in this case, thus allowing the caller to only check
        // the cache, and not force the image to be decoded.
        if (is == null) {
            return null;
        }

        Bitmap bm = null;

        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
        try {
            if (is instanceof AssetManager.AssetInputStream) {
                final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
                bm = nativeDecodeAsset(asset, outPadding, opts);
            } else {
                bm = decodeStreamInternal(is, outPadding, opts);
            }

            if (bm == null && opts != null && opts.inBitmap != null) {
                throw new IllegalArgumentException("Problem decoding into existing bitmap");
            }

            setDensityFromOptions(bm, opts);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
        }

        return bm;
    }
private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
        // ASSERT(is != null);
        byte [] tempStorage = null;
        if (opts != null) tempStorage = opts.inTempStorage;
        if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
        return nativeDecodeStream(is, tempStorage, outPadding, opts);
    }

從上面的代碼可以看出,decodeStream的代碼最終會調用以下兩個native方法之一

nativeDecodeAsset()
nativeDecodeStream()

這兩個native方法只是對應decodeFile和decodeResource、decodeStream來解析的,像decodeByteArray、decodeFileDescriptor也有專門的native方法負責解析Bitmap。

decodeFile、decodeResource的區別在于他們方法的調用路徑不同:

decodeFile->decodeStream
decodeResource->decodeResourceStream->decodeStream

decodeResource在解析時多調用了一個decodeResourceStream方法,而這個decodeResourceStream方法代碼如下:

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {

        if (opts == null) {
            opts = new Options();
        }

        if (opts.inDensity == 0 && value != null) {
            final int density = value.density;
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }

        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }

        return decodeStream(is, pad, opts);
    }

其中對Options進行處理了,在得到opts.inDensity屬性的前提下,如果我們沒有對該屬性設定值,那么將opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;賦定這個默認的Density值,這個默認值為160,為標準的dpi比例,即在Density=160的設備上1dp=1px,這個方法中還有這么一行

opts.inTargetDensity = res.getDisplayMetrics().densityDpi;

對opts.inTargetDensity進行了賦值,該值為當前設備的densityDpi值,所以說在decodeResourceStream方法中主要做了兩件事:

1.對opts.inDensity賦值,沒有則賦默認值160

2.對opts.inTargetDensity賦值,沒有則賦當前設備的densityDpi值

之后參數將傳入decodeStream方法,該方法中在調用native方法進行解析Bitmap后會調用這個方法setDensityFromOptions(bm, opts);:

private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
        if (outputBitmap == null || opts == null) return;

        final int density = opts.inDensity;
        if (density != 0) {
            outputBitmap.setDensity(density);
            final int targetDensity = opts.inTargetDensity;
            if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
                return;
            }

            byte[] np = outputBitmap.getNinePatchChunk();
            final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
            if (opts.inScaled || isNinePatch) {
                outputBitmap.setDensity(targetDensity);
            }
        } else if (opts.inBitmap != null) {
            // bitmap was reused, ensure density is reset
            outputBitmap.setDensity(Bitmap.getDefaultDensity());
        }
    }

主要就是把剛剛賦值過的兩個屬性inDensity和inTargetDensity給Bitmap進行賦值,不過并不是直接賦給Bitmap就完了,中間有個判斷,當inDensity的值與inTargetDensity或與設備的屏幕Density不相等時,則將應用inTargetDensity的值,如果相等則應用inDensity的值。

所以總結來說,setDensityFromOptions方法就是把inTargetDensity的值賦給Bitmap,不過前提是opts.inScaled = true;

進過上面的分析,結論如下:

在不配置Options的情況下:

1.decodeFile、decodeStream在解析時不會對Bitmap進行一系列的屏幕適配,解析出來的將是原始大小的圖

2.decodeResource在解析時會對Bitmap根據當前設備屏幕像素密度densityDpi的值進行縮放適配操作,使得解析出來的Bitmap與當前設備的分辨率匹配,達到一個最佳的顯示效果,并且Bitmap的大小將比原始的大

Bitmap的優化策略

經過上面的分析,我們可以得出Bitmap優化的思路:

1、BitmapConfig的配置

2、使用decodeFile、decodeResource、decodeStream進行解析Bitmap時,配置inDensity和inTargetDensity,兩者應該相等,值可以等于屏幕像素密度*0.75f

3、使用inJustDecodeBounds預判斷Bitmap的大小及使用inSampleSize進行壓縮

4、對Density>240的設備進行Bitmap的適配(縮放Density)

5、2.3版本inNativeAlloc的使用

6、4.4以下版本inPurgeable、inInputShareable的使用

7、Bitmap的回收

所以我們根據以上的思路,我們將Bitmap優化的策略總結為以下3種:

1.對圖片質量進行壓縮 2.對圖片尺寸進行壓縮 3.使用libjpeg.so庫進行壓縮

對圖片質量進行壓縮

public static Bitmap compressImage(Bitmap bitmap){  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            //質量壓縮方法,這里100表示不壓縮,把壓縮后的數據存放到baos中  
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);  
            int options = 100;  
            //循環判斷如果壓縮后圖片是否大于50kb,大于繼續壓縮  
            while ( baos.toByteArray().length / 1024>50) {  
                //清空baos  
                baos.reset();  
                bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);  
                options -= 10;//每次都減少10  
            }  
            //把壓縮后的數據baos存放到ByteArrayInputStream中  
            ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());  
            //把ByteArrayInputStream數據生成圖片  
            Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);  
            return newBitmap;  
        }

對圖片尺寸進行壓縮

/**
     * 按圖片尺寸壓縮 參數是bitmap
     * @param bitmap
     * @param pixelW
     * @param pixelH
     * @return
     */
    public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
        if( os.toByteArray().length / 1024>512) {//判斷如果圖片大于0.5M,進行壓縮避免在生成圖片(BitmapFactory.decodeStream)時溢出
            os.reset();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);//這里壓縮50%,把壓縮后的數據存放到baos中
        }
        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        BitmapFactory.decodeStream(is, null, options);
        options.inJustDecodeBounds = false;
        options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelW : pixelH ,pixelW * pixelH );
        is = new ByteArrayInputStream(os.toByteArray());
        Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);
        return newBitmap;
    }


    /**
     * 動態計算出圖片的inSampleSize
     * @param options
     * @param minSideLength
     * @param maxNumOfPixels
     * @return
     */
    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;
        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 lowerBound;
        }
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

使用libjpeg.so庫進行壓縮

除了通過設置simpleSize根據圖片尺寸壓縮圖片和通過Bitmap.compress方法通過壓縮圖片質量兩種方法外,我們還可以使用libjpeg.so這個庫來進行壓縮。

libjpeg是廣泛使用的開源JPEG圖像庫,Android所用的是skia的壓縮算法,而Skia對libjpeg進行了的封裝。

libjpeg在壓縮圖像時,有一個參數叫optimize_coding,關于這個參數,libjpeg.doc有如下解釋:

boolean optimize_coding 
TRUE causes the compressor to compute optimal Huffman coding tables 
for the image. This requires an extra pass over the data and 
therefore costs a good deal of space and time. The default is 
FALSE, which tells the compressor to use the supplied or default 
Huffman tables. In most cases optimal tables save only a few percent 
of file size compared to the default tables. Note that when this is 
TRUE, you need not supply Huffman tables at all, and any you do 
supply will be overwritten.

如果設置optimize_coding為TRUE,將會使得壓縮圖像過程中基于圖像數據計算哈弗曼表,由于這個計算會顯著消耗空間和時間,默認值被設置為FALSE。

谷歌的Skia項目工程師們最終沒有設置這個參數,optimize_coding在Skia中默認的等于了FALSE,但是問題就隨之出現了,如果我們想在FALSE和TRUE時壓縮成相同大小的JPEG 圖片,FALSE的品質將大大遜色于TRUE的,盡管谷歌工程師沒有將該值設置為true,但是我們可以自己編譯libjpeg進行圖片的壓縮。

libjpeg的官網下載地址: http://www.ijg.org/

從官網下載之后,我們必須自己對其進行編譯。

編譯libjpeg

下載最新的源碼,解壓后將所有文件放到jni目錄中,準備用ndk編譯

1、新建config.sh,將ndk中的交叉編譯工具加入其中,內容如下:

NDK=/opt/ndk/android-ndk-r10e/
PLATFORM=$NDK/platforms/android-9/arch-arm/
PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86/
CC=$PREBUILT/bin/arm-linux-androideabi-gcc
./configure --prefix=/home/linc/jpeg-9b/jni/dist --host=arm CC="$CC --sysroot=$PLATFORM"

2、執行此腳本

$ sh config.sh 
...
checking whether to build shared libraries... no
checking whether to build static libraries... yes
...
config.status: creating Makefile
config.status: creating jconfig.h

首先,它生成了Makefile,我們可以直接使用此Makefile進行編譯;其次,它生成了重要的頭文件,jconfig.h.

但是這個Makefile是編譯static庫而不是共享庫的。

此時,我們可以執行構建命令進行編譯:

jni$ make install-libLTLIBRARIES
libtool: install: ranlib /home/linc/jpeg-9b/jni/dist/lib/libjpeg.a

3、Android.mk

使用ndk-build指令編譯,需要手動編寫Android.mk文件,內容如下:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm

LOCAL_SRC_FILES :=jaricom.c jcapimin.c jcapistd.c jcarith.c jccoefct.c jccolor.c \
        jcdctmgr.c jchuff.c jcinit.c jcmainct.c jcmarker.c jcmaster.c \
        jcomapi.c jcparam.c jcprepct.c jcsample.c jctrans.c jdapimin.c \
        jdapistd.c jdarith.c jdatadst.c jdatasrc.c jdcoefct.c jdcolor.c \
        jddctmgr.c jdhuff.c jdinput.c jdmainct.c jdmarker.c jdmaster.c \
        jdmerge.c jdpostct.c jdsample.c jdtrans.c jerror.c jfdctflt.c \
        jfdctfst.c jfdctint.c jidctflt.c jidctfst.c jidctint.c jquant1.c \
        jquant2.c jutils.c jmemmgr.c jmemnobs.c

LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays \
    -DANDROID -DANDROID_TILE_BASED_DECODE -DENABLE_ANDROID_NULL_CONVERT


LOCAL_MODULE := libjpeg

LOCAL_MODULE_TAGS := optional

# unbundled branch, built against NDK.
LOCAL_SDK_VERSION := 17

include $(BUILD_SHARED_LIBRARY)

其中LOCAL_SRC_FILES后面的源文件可以參考剛剛生成的Makefile。

在jni目錄上一級使用ndk-build編譯即可。

$ ndk-build
[armeabi] Compile arm    : jpeg <= jaricom.c
...
[armeabi] Compile arm    : jpeg <= jmemnobs.c
[armeabi] SharedLibrary  : libjpeg.so
[armeabi] Install        : libjpeg.so => libs/armeabi/libjpeg.so

在Android項目引入編譯好的libjpeg首先把so庫加載到libs中,然后將編譯好的頭文件拷貝到項目的jni文件夾下,就可以使用Android的具體函數了,具體使用分為如下幾步:

1、將Android的bitmap解碼并轉換為RGB數據

2、為JPEG對象分配空間并初始化

3、指定壓縮數據源

4、獲取文件信息

5、為壓縮設定參數,包括圖像大小,顏色空間

6、開始壓縮

7、壓縮完畢

8、釋放資源

#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <jni.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>
#include "jpeglib.h"
#include "cdjpeg.h"     /* Common decls for cjpeg/djpeg applications */
#include "jversion.h"       /* for version message */
#include "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=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) {
    int nComponent = 3;

    struct jpeg_compress_struct jcs;

    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;
         }
     //為JPEG對象分配空間并初始化
    jpeg_create_compress(&jcs);
    //獲取文件信息
    FILE* f = fopen(outfilename, "wb");
    if (f == NULL) {
        return 0;
    }
    //指定壓縮數據源
    jpeg_stdio_dest(&jcs, f);
    jcs.image_width = w;
    jcs.image_height = h;
    if (optimize) {
        LOGI("optimize==ture");
    } else {
        LOGI("optimize==false");
    }

    jcs.arith_code = false;
    jcs.input_components = nComponent;
    if (nComponent == 1)
        jcs.in_color_space = JCS_GRAYSCALE;
    else
        jcs.in_color_space = JCS_RGB;

    jpeg_set_defaults(&jcs);
    jcs.optimize_coding = optimize;
    //為壓縮設定參數,包括圖像大小,顏色空間
    jpeg_set_quality(&jcs, quality, true);
    //開始壓縮
    jpeg_start_compress(&jcs, TRUE);

    JSAMPROW row_pointer[1];
    int row_stride;
    row_stride = jcs.image_width * nComponent;
    while (jcs.next_scanline < jcs.image_height) {
        row_pointer[0] = &data[jcs.next_scanline * row_stride];
        //寫入數據
        jpeg_write_scanlines(&jcs, row_pointer, 1);
    }

    if (jcs.optimize_coding) {
            LOGI("optimize==ture");
        } else {
            LOGI("optimize==false");
        }
    //壓縮完畢
    jpeg_finish_compress(&jcs);
    //釋放資源
    jpeg_destroy_compress(&jcs);
    fclose(f);

    return 1;
}

typedef struct {
    uint8_t r;
    uint8_t g;
    uint8_t b;
} rgb;

//將java string轉換為char*
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
    char* rtn = NULL;
    jsize alen = (*env)->GetArrayLength(env, barr);
    jbyte* ba = (*env)->GetByteArrayElements(env, barr, 0);
    if (alen > 0) {
        rtn = (char*) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
    return rtn;
}
//jni方法入口
jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
        jobject thiz, jobject bitmapcolor, int w, int h, int quality,
        jbyteArray fileNameStr, jboolean optimize) {

    AndroidBitmapInfo infocolor;
    BYTE* pixelscolor;
    int ret;
    BYTE * data;
    BYTE *tmpdata;
    char * fileName = jstrinTostring(env, fileNameStr);
    //解碼Android bitmap信息,并存儲值infocolor中
    if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return (*env)->NewStringUTF(env, "0");;
    }
    if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, &pixelscolor)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }

    BYTE r, g, b;
    data = NULL;
    data = malloc(w * h * 3);
    tmpdata = data;
    int j = 0, i = 0;
    int color;
    //將bitmap轉換為rgb數據
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            color = *((int *) pixelscolor);
            r = ((color & 0x00FF0000) >> 16);
            g = ((color & 0x0000FF00) >> 8);
            b = color & 0x000000FF;
            *data = b;
            *(data + 1) = g;
            *(data + 2) = r;
            data = data + 3;
            pixelscolor += 4;

        }

    }
    AndroidBitmap_unlockPixels(env, bitmapcolor);
    //進行壓縮
    int resultCode= generateJPEG(tmpdata, w, h, quality, fileName, optimize);
    free(tmpdata);
    if(resultCode==0){
        jstring result=(*env)->NewStringUTF(env, error);
        error=NULL;
        return result;
    }
    return (*env)->NewStringUTF(env, "1"); //success
}

新建Android.mk,生成可執行文件:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= jpeg_compress.cpp

LOCAL_MODULE:= jtest

LOCAL_LDLIBS :=-llog
LOCAL_LDLIBS += $(LOCAL_PATH)/libjpeg.so
LOCAL_C_INCLUDES := $(LOCAL_PATH)

LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE_TAGS := debug 

include $(BUILD_EXECUTABLE)

本篇博客總結了3種圖片壓縮的方法,大家可以根據自己的情況進行相應的使用,如有錯漏,歡迎留言。

參考文章:

http://blog.csdn.net/lincyang/article/details/51085737

 

來自:http://blog.csdn.net/u012124438/article/details/66087785

 

 本文由用戶 閆佳麗 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!