Android性能優(yōu)化之Bitmap圖片優(yōu)化詳解
前言
在Android開(kāi)發(fā)過(guò)程中,Bitmap往往會(huì)給開(kāi)發(fā)者帶來(lái)一些困擾,因?yàn)閷?duì)Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError
- 內(nèi)存溢出),本篇博客,我們將一起探討B(tài)itmap的性能優(yōu)化。
為什么Bitmap會(huì)導(dǎo)致OOM?
1.每個(gè)機(jī)型在編譯ROM時(shí)都設(shè)置了一個(gè)應(yīng)用堆內(nèi)存VM值上限dalvik.vm.heapgrowthlimit
,用來(lái)限定每個(gè)應(yīng)用可用的最大內(nèi)存,超出這個(gè)最大值將會(huì)報(bào)OOM。這個(gè)閥值,一般根據(jù)手機(jī)屏幕dpi大小遞增,dpi越小的手機(jī),每個(gè)應(yīng)用可用最大內(nèi)存就越低。所以當(dāng)加載圖片的數(shù)量很多時(shí),就很容易超過(guò)這個(gè)閥值,造成OOM。
2.圖片分辨率越高,消耗的內(nèi)存越大,當(dāng)加載高分辨率圖片的時(shí)候,將會(huì)非常占用內(nèi)存,一旦處理不當(dāng)就會(huì)OOM。例如,一張分辨率為:1920x1080的圖片。如果Bitmap使用 ARGB_8888 32位來(lái)平鋪顯示的話,占用的內(nèi)存是1920x1080x4個(gè)字節(jié),占用將近8M內(nèi)存,可想而知,如果不對(duì)圖片進(jìn)行處理的話,就會(huì)OOM。
3.在使用ListView, GridView等這些大量加載view的組件時(shí),如果沒(méi)有合理的處理緩存,大量加載Bitmap的時(shí)候,也將容易引發(fā)OOM
Bitmap基礎(chǔ)知識(shí)
一張圖片Bitmap所占用的內(nèi)存 = 圖片長(zhǎng)度 x 圖片寬度 x 一個(gè)像素點(diǎn)占用的字節(jié)數(shù)
而Bitmap.Config
,正是指定單位像素占用的字節(jié)數(shù)的重要參數(shù)。
其中,A代表透明度;R代表紅色;G代表綠色;B代表藍(lán)色。
ALPHA_8
表示8位Alpha位圖,即A=8,一個(gè)像素點(diǎn)占用1個(gè)字節(jié),它沒(méi)有顏色,只有透明度
ARGB_4444
表示16位ARGB位圖,即A=4,R=4,G=4,B=4,一個(gè)像素點(diǎn)占4+4+4+4=16位,2個(gè)字節(jié)
ARGB_8888
表示32位ARGB位圖,即A=8,R=8,G=8,B=8,一個(gè)像素點(diǎn)占8+8+8+8=32位,4個(gè)字節(jié)
RGB_565
表示16位RGB位圖,即R=5,G=6,B=5,它沒(méi)有透明度,一個(gè)像素點(diǎn)占5+6+5=16位,2個(gè)字節(jié)
一張圖片Bitmap所占用的內(nèi)存 = 圖片長(zhǎng)度 x 圖片寬度 x 一個(gè)像素點(diǎn)占用的字節(jié)數(shù)
根據(jù)以上的算法,可以計(jì)算出圖片占用的內(nèi)存,以100*100像素的圖片為例
BitmapFactory解析Bitmap的原理
BitmapFactory提供的解析Bitmap的靜態(tài)工廠方法有以下五種:
Bitmap decodeFile(...) Bitmap decodeResource(...) Bitmap decodeByteArray(...) Bitmap decodeStream(...) Bitmap decodeFileDescriptor(...)
其中常用的三個(gè):decodeFile、decodeResource、decodeStream。
decodeFile和decodeResource其實(shí)最終都是調(diào)用decodeStream方法來(lái)解析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的代碼最終會(huì)調(diào)用以下兩個(gè)native方法之一
nativeDecodeAsset() nativeDecodeStream()
這兩個(gè)native方法只是對(duì)應(yīng)decodeFile和decodeResource、decodeStream來(lái)解析的,像decodeByteArray、decodeFileDescriptor也有專門(mén)的native方法負(fù)責(zé)解析Bitmap。
decodeFile、decodeResource的區(qū)別在于他們方法的調(diào)用路徑不同:
decodeFile->decodeStream decodeResource->decodeResourceStream->decodeStream
decodeResource在解析時(shí)多調(diào)用了一個(gè)decodeResourceStream方法,而這個(gè)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); }
其中對(duì)Options進(jìn)行處理了,在得到opts.inDensity
屬性的前提下,如果我們沒(méi)有對(duì)該屬性設(shè)定值,那么將opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
賦定這個(gè)默認(rèn)的Density值,這個(gè)默認(rèn)值為160,為標(biāo)準(zhǔn)的dpi比例,即在Density=160
的設(shè)備上1dp=1px,這個(gè)方法中還有這么一行
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
對(duì)opts.inTargetDensity
進(jìn)行了賦值,該值為當(dāng)前設(shè)備的densityDpi值,所以說(shuō)在decodeResourceStream方法中主要做了兩件事:
1.對(duì)opts.inDensity
賦值,沒(méi)有則賦默認(rèn)值160
2.對(duì)opts.inTargetDensity
賦值,沒(méi)有則賦當(dāng)前設(shè)備的densityDpi值
之后參數(shù)將傳入decodeStream方法,該方法中在調(diào)用native方法進(jìn)行解析Bitmap后會(huì)調(diào)用這個(gè)方法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()); } }
主要就是把剛剛賦值過(guò)的兩個(gè)屬性inDensity和inTargetDensity給Bitmap進(jìn)行賦值,不過(guò)并不是直接賦給Bitmap就完了,中間有個(gè)判斷,當(dāng)inDensity的值與inTargetDensity或與設(shè)備的屏幕Density不相等時(shí),則將應(yīng)用inTargetDensity的值,如果相等則應(yīng)用inDensity的值。
所以總結(jié)來(lái)說(shuō),setDensityFromOptions方法就是把inTargetDensity的值賦給Bitmap,不過(guò)前提是opts.inScaled = true
;
進(jìn)過(guò)上面的分析,結(jié)論如下:
在不配置Options的情況下:
1.decodeFile、decodeStream在解析時(shí)不會(huì)對(duì)Bitmap進(jìn)行一系列的屏幕適配,解析出來(lái)的將是原始大小的圖
2.decodeResource在解析時(shí)會(huì)對(duì)Bitmap根據(jù)當(dāng)前設(shè)備屏幕像素密度densityDpi的值進(jìn)行縮放適配操作,使得解析出來(lái)的Bitmap與當(dāng)前設(shè)備的分辨率匹配,達(dá)到一個(gè)最佳的顯示效果,并且Bitmap的大小將比原始的大
Bitmap的優(yōu)化策略
經(jīng)過(guò)上面的分析,我們可以得出Bitmap優(yōu)化的思路:
1、BitmapConfig的配置
2、使用decodeFile、decodeResource、decodeStream進(jìn)行解析Bitmap時(shí),配置inDensity和inTargetDensity,兩者應(yīng)該相等,值可以等于屏幕像素密度*0.75f
3、使用inJustDecodeBounds預(yù)判斷Bitmap的大小及使用inSampleSize進(jìn)行壓縮
4、對(duì)Density>240的設(shè)備進(jìn)行Bitmap的適配(縮放Density)
5、2.3版本inNativeAlloc的使用
6、4.4以下版本inPurgeable、inInputShareable的使用
7、Bitmap的回收
所以我們根據(jù)以上的思路,我們將Bitmap優(yōu)化的策略總結(jié)為以下3種:
1.對(duì)圖片質(zhì)量進(jìn)行壓縮
2.對(duì)圖片尺寸進(jìn)行壓縮
3.使用libjpeg.so庫(kù)進(jìn)行壓縮
對(duì)圖片質(zhì)量進(jìn)行壓縮
public static Bitmap compressImage(Bitmap bitmap){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); //質(zhì)量壓縮方法,這里100表示不壓縮,把壓縮后的數(shù)據(jù)存放到baos中 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); int options = 100; //循環(huán)判斷如果壓縮后圖片是否大于50kb,大于繼續(xù)壓縮 while ( baos.toByteArray().length / 1024>50) { //清空baos baos.reset(); bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos); options -= 10;//每次都減少10 } //把壓縮后的數(shù)據(jù)baos存放到ByteArrayInputStream中 ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray()); //把ByteArrayInputStream數(shù)據(jù)生成圖片 Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null); return newBitmap; }
對(duì)圖片尺寸進(jìn)行壓縮
/** * 按圖片尺寸壓縮 參數(shù)是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,進(jìn)行壓縮避免在生成圖片(BitmapFactory.decodeStream)時(shí)溢出 os.reset(); bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);//這里壓縮50%,把壓縮后的數(shù)據(jù)存放到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; } /** * 動(dòng)態(tài)計(jì)算出圖片的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庫(kù)進(jìn)行壓縮
除了通過(guò)設(shè)置simpleSize根據(jù)圖片尺寸壓縮圖片和通過(guò)Bitmap.compress
方法通過(guò)壓縮圖片質(zhì)量?jī)煞N方法外,我們還可以使用libjpeg.so這個(gè)庫(kù)來(lái)進(jìn)行壓縮。
libjpeg是廣泛使用的開(kāi)源JPEG圖像庫(kù),Android所用的是skia的壓縮算法,而Skia對(duì)libjpeg進(jìn)行了的封裝。
libjpeg在壓縮圖像時(shí),有一個(gè)參數(shù)叫optimize_coding,關(guān)于這個(gè)參數(shù),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.
如果設(shè)置optimize_coding為T(mén)RUE,將會(huì)使得壓縮圖像過(guò)程中基于圖像數(shù)據(jù)計(jì)算哈弗曼表,由于這個(gè)計(jì)算會(huì)顯著消耗空間和時(shí)間,默認(rèn)值被設(shè)置為FALSE。
谷歌的Skia項(xiàng)目工程師們最終沒(méi)有設(shè)置這個(gè)參數(shù),optimize_coding在Skia中默認(rèn)的等于了FALSE,但是問(wèn)題就隨之出現(xiàn)了,如果我們想在FALSE和TRUE時(shí)壓縮成相同大小的JPEG 圖片,F(xiàn)ALSE的品質(zhì)將大大遜色于TRUE的,盡管谷歌工程師沒(méi)有將該值設(shè)置為true,但是我們可以自己編譯libjpeg進(jìn)行圖片的壓縮。
libjpeg的官網(wǎng)下載地址:http://www.ijg.org/
從官網(wǎng)下載之后,我們必須自己對(duì)其進(jìn)行編譯。
編譯libjpeg
下載最新的源碼,解壓后將所有文件放到j(luò)ni目錄中,準(zhǔn)備用ndk編譯
1、新建config.sh,將ndk中的交叉編譯工具加入其中,內(nèi)容如下:
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、執(zhí)行此腳本
$ 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進(jìn)行編譯;其次,它生成了重要的頭文件,jconfig.h.
但是這個(gè)Makefile是編譯static庫(kù)而不是共享庫(kù)的。
此時(shí),我們可以執(zhí)行構(gòu)建命令進(jìn)行編譯:
jni$ make install-libLTLIBRARIES libtool: install: ranlib /home/linc/jpeg-9b/jni/dist/lib/libjpeg.a
3、Android.mk
使用ndk-build指令編譯,需要手動(dòng)編寫(xiě)Android.mk文件,內(nèi)容如下:
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目錄上一級(jí)使用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項(xiàng)目引入編譯好的libjpeg
首先把so庫(kù)加載到libs中,然后將編譯好的頭文件拷貝到項(xiàng)目的jni文件夾下,就可以使用Android的具體函數(shù)了,具體使用分為如下幾步:
1、將Android的bitmap解碼并轉(zhuǎn)換為RGB數(shù)據(jù)
2、為JPEG對(duì)象分配空間并初始化
3、指定壓縮數(shù)據(jù)源
4、獲取文件信息
5、為壓縮設(shè)定參數(shù),包括圖像大小,顏色空間
6、開(kāi)始?jí)嚎s
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對(duì)象分配空間并初始化 jpeg_create_compress(&jcs); //獲取文件信息 FILE* f = fopen(outfilename, "wb"); if (f == NULL) { return 0; } //指定壓縮數(shù)據(jù)源 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; //為壓縮設(shè)定參數(shù),包括圖像大小,顏色空間 jpeg_set_quality(&jcs, quality, true); //開(kāi)始?jí)嚎s 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]; //寫(xiě)入數(shù)據(jù) 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轉(zhuǎn)換為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信息,并存儲(chǔ)值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轉(zhuǎn)換為rgb數(shù)據(jù) 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); //進(jìn)行壓縮 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,生成可執(zhí)行文件:
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)
總結(jié)
本篇博客總結(jié)了3種圖片壓縮的方法,大家可以根據(jù)自己的情況進(jìn)行相應(yīng)的使用,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Android 百度地圖marker中圖片不顯示的解決方法(推薦)
下面小編就為大家分享一篇Android 百度地圖marker中圖片不顯示的解決方法(推薦),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01Android實(shí)現(xiàn)濾鏡效果ColorMatrix
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)濾鏡效果ColorMatrix,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05Android 中ViewPager重排序與更新實(shí)例詳解
這篇文章主要介紹了Android 中ViewPager重排序與更新實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-07-07Android實(shí)現(xiàn)H5與Native交互的兩種方式
Android實(shí)現(xiàn)H5頁(yè)面和Native頁(yè)面交互的方法有兩種,一種是Url攔截的方法,另一種是JavaScript注入,下面來(lái)通過(guò)這篇文章分別講解。有需要的朋友們可以參考借鑒,下面來(lái)一起看看吧。2016-12-12Android?Studio實(shí)現(xiàn)登錄界面功能
這篇文章主要為大家詳細(xì)介紹了Android?Studio實(shí)現(xiàn)登錄界面功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Retrofit實(shí)現(xiàn)圖文上傳至服務(wù)器
本文主要介紹了Retrofit實(shí)現(xiàn)圖文上傳至服務(wù)器的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-03-03簡(jiǎn)單談?wù)刟ndroid studio 的單元測(cè)試
昨天在完善項(xiàng)目的時(shí)候,需要進(jìn)行單元測(cè)試,在Eclipse環(huán)境中進(jìn)行是很簡(jiǎn)單的,但是在Android Studio環(huán)境中進(jìn)行單元測(cè)試,在國(guó)內(nèi)找了很多資料,大都是人云亦云,本文發(fā)布出來(lái)供大家學(xué)習(xí)參考。2016-08-08Flutter?Widget之FutureBuilder使用示例詳解
這篇文章主要為大家介紹了Flutter?Widget之FutureBuilder使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11