溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么在Android應(yīng)用中利用Bitmap對圖片進(jìn)行優(yōu)化

發(fā)布時間:2020-12-02 15:26:34 來源:億速云 閱讀:148 作者:Leah 欄目:移動開發(fā)

這篇文章給大家介紹怎么在Android應(yīng)用中利用Bitmap對圖片進(jìn)行優(yōu)化,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

前言

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

為什么Bitmap會導(dǎo)致OOM?

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

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

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

Bitmap基礎(chǔ)知識

一張圖片Bitmap所占用的內(nèi)存 = 圖片長度 x 圖片寬度 x 一個像素點占用的字節(jié)數(shù)

Bitmap.Config,正是指定單位像素占用的字節(jié)數(shù)的重要參數(shù)。

怎么在Android應(yīng)用中利用Bitmap對圖片進(jìn)行優(yōu)化

其中,A代表透明度;R代表紅色;G代表綠色;B代表藍(lán)色。

ALPHA_8

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

ARGB_4444

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

ARGB_8888

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

RGB_565

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

一張圖片Bitmap所占用的內(nèi)存 = 圖片長度 x 圖片寬度 x 一個像素點占用的字節(jié)數(shù)

根據(jù)以上的算法,可以計算出圖片占用的內(nèi)存,以100*100像素的圖片為例

怎么在Android應(yīng)用中利用Bitmap對圖片進(jìn)行優(yōu)化

BitmapFactory解析Bitmap的原理

BitmapFactory提供的解析Bitmap的靜態(tài)工廠方法有以下五種:

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

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

decodeFile和decodeResource其實最終都是調(diào)用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的代碼最終會調(diào)用以下兩個native方法之一

nativeDecodeAsset()
nativeDecodeStream()

這兩個native方法只是對應(yīng)decodeFile和decodeResource、decodeStream來解析的,像decodeByteArray、decodeFileDescriptor也有專門的native方法負(fù)責(zé)解析Bitmap。

decodeFile、decodeResource的區(qū)別在于他們方法的調(diào)用路徑不同:

decodeFile->decodeStream
decodeResource->decodeResourceStream->decodeStream

decodeResource在解析時多調(diào)用了一個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進(jìn)行處理了,在得到opts.inDensity屬性的前提下,如果我們沒有對該屬性設(shè)定值,那么將opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;賦定這個默認(rèn)的Density值,這個默認(rèn)值為160,為標(biāo)準(zhǔn)的dpi比例,即在Density=160的設(shè)備上1dp=1px,這個方法中還有這么一行

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

opts.inTargetDensity進(jìn)行了賦值,該值為當(dāng)前設(shè)備的densityDpi值,所以說在decodeResourceStream方法中主要做了兩件事:

     1.對opts.inDensity賦值,沒有則賦默認(rèn)值160

     2.對opts.inTargetDensity賦值,沒有則賦當(dāng)前設(shè)備的densityDpi值

之后參數(shù)將傳入decodeStream方法,該方法中在調(diào)用native方法進(jìn)行解析Bitmap后會調(diào)用這個方法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進(jìn)行賦值,不過并不是直接賦給Bitmap就完了,中間有個判斷,當(dāng)inDensity的值與inTargetDensity或與設(shè)備的屏幕Density不相等時,則將應(yīng)用inTargetDensity的值,如果相等則應(yīng)用inDensity的值。

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

進(jìn)過上面的分析,結(jié)論如下:

在不配置Options的情況下:

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

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

Bitmap的優(yōu)化策略

經(jīng)過上面的分析,我們可以得出Bitmap優(yōu)化的思路:

      1、BitmapConfig的配置

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

      3、使用inJustDecodeBounds預(yù)判斷Bitmap的大小及使用inSampleSize進(jìn)行壓縮

      4、對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.對圖片質(zhì)量進(jìn)行壓縮

      2.對圖片尺寸進(jìn)行壓縮

      3.使用libjpeg.so庫進(jìn)行壓縮

對圖片質(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; 
 } 

對圖片尺寸進(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)時溢出
   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;
 }


 /**
  * 動態(tài)計算出圖片的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) &#63; 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
  int upperBound = (minSideLength == -1) &#63; 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庫進(jìn)行壓縮

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

libjpeg是廣泛使用的開源JPEG圖像庫,Android所用的是skia的壓縮算法,而Skia對libjpeg進(jìn)行了的封裝。
libjpeg在壓縮圖像時,有一個參數(shù)叫optimize_coding,關(guān)于這個參數(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為TRUE,將會使得壓縮圖像過程中基于圖像數(shù)據(jù)計算哈弗曼表,由于這個計算會顯著消耗空間和時間,默認(rèn)值被設(shè)置為FALSE。

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

libjpeg的官網(wǎng)下載地址:http://www.ijg.org/

從官網(wǎng)下載之后,我們必須自己對其進(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.
但是這個Makefile是編譯static庫而不是共享庫的。

此時,我們可以執(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指令編譯,需要手動編寫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目錄上一級使用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的具體函數(shù)了,具體使用分為如下幾步:

     1、將Android的bitmap解碼并轉(zhuǎn)換為RGB數(shù)據(jù)

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

     3、指定壓縮數(shù)據(jù)源

     4、獲取文件信息

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

     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;
 }
 //指定壓縮數(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);
 //開始壓縮
 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];
  //寫入數(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信息,并存儲值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)

關(guān)于怎么在Android應(yīng)用中利用Bitmap對圖片進(jìn)行優(yōu)化就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI