關於做者java
郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。android
文章目錄c++
本篇文章用來介紹Android平臺的圖像壓縮方案以及圖像編解碼的通識性理解,事實上Android平臺對圖像的處理最終都交由底層實現,篇幅有限,咱們這裏不會去過多的分析底層的細節實現細節,可是
咱們會提一下底層的實現方案概覽,給向進一步擴展的同窗提供一些思路。git
在介紹圖像壓縮方案以前,咱們先要了解一下和壓縮相關的圖像的基本知識,這也能夠幫助咱們理解Bitmap.java裏定義的一些變量的含義。程序員
像素密度github
像素密度指的是每英寸像素數目,在Bitmap裏用mDensity/mTargetDensity,mDensity默認是設備屏幕的像素密度,mTargetDensity是圖片的目標像素密度,在加載圖片時就是 drawable 目錄的像素密度。算法
色彩模式canvas
色彩模式是數字世界中表示顏色的一種算法,在Bitmap裏用Config來表示。數組
另外提一點Bitmap計算大小的方法。promise
Bitamp 佔用內存大小 = 寬度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一個像素所佔的內存
在Bitmap裏有兩個獲取內存佔用大小的方法。
在不復用 Bitmap 時,getByteCount() 和 getAllocationByteCount 返回的結果是同樣的。在經過複用 Bitmap 來解碼圖片時,那麼 getByteCount() 表示新解碼圖片佔用內存的大
小,getAllocationByteCount() 表示被複用 Bitmap真實佔用的內存大小(即 mBuffer 的長度)。
除了以上這些概念,咱們再提一下Bitmap.java裏的一些成員變量,這些變量你們在可能也常常遇到,要理解清楚。
瞭解完基本的概念,咱們來分析壓縮圖像的方法。
Android平臺壓縮圖像的手段一般有兩種:
質量壓縮的關鍵在於Bitmap.compress()函數,該函數不會改變圖像的大小,可是能夠下降圖像的質量,從而下降存儲大小,進而達到壓縮的目的。
compress(CompressFormat format, int quality, OutputStream stream)複製代碼
它有三個參數
咱們來寫個例子驗證一下。
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
, "timo_compress_quality_100.jpg");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timo);
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(file));
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bitmap.recycle();
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
try {
if(bos != null){
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}複製代碼
quality = 100
1823x1076 1.16m
quality = 50
1823x1076 124.52k
quality = 0
1823x1076 35.80k
能夠看到隨着quality的下降,圖像質量發生了明顯的變化,可是圖像的尺寸沒有發生變化。
Android圖片的編碼是由Skia庫來完成的。
Skia是一個開源的二維圖形庫,提供各類經常使用的API,並可在多種軟硬件平臺上運行。谷歌Chrome瀏覽器、Chrome OS、安卓、火狐瀏覽器、火狐操做
系統以及其它許多產品都使用它做爲圖形引擎。
Skia在external/skia包中,咱們雖然在平時的開發中沒有直接用到Skia,但它對咱們過重要了,它
是Android系統的重要組成部分,不少重要操做例如圖像編解碼,Canvas繪製在底層都是經過Skia來完成的。它一樣被普遍用於Google的其餘產品中。
Skia在src/images包下定義了各類格式圖片的編解碼器。
kImageEncoder.cpp
Skia自己提供了基本的畫圖和編解碼功能,它同時還掛載了其餘第三方編解碼庫,例如:libpng.so、libjpeg.so、libgif.so、因此咱們上面想要編碼成jpeg圖像最終是由libjpeg來完成的。
上面也提到,咱們作圖像壓縮,通常選擇的JPEG,咱們重點來看看JPEG的編解碼。
libjpeg是一個徹底用C語言編寫的處理JPEG圖像數據格式的自由庫。它包含一個JPEG編解碼器的算法實現,以及用於處理JPEG數據的多種實用程序。
Android並不是採用原生的libjpeg,而是作了一些修改,具體說來:
libjpeg源碼在external/jpeg包下,接下來咱們具體看看JPEG壓縮的實現。
咱們再來從上到下看看整個源碼的實現流程。
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
if (stream == null) {
throw new NullPointerException();
}
if (quality < 0 || quality > 100) {
throw new IllegalArgumentException("quality must be 0..100");
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
boolean result = nativeCompress(mNativePtr, format.nativeInt,
quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return result;
}複製代碼
能夠看到它在內部調用的是一個native方法nativeCompress(),這是定義在Bitmap.java裏的一個函數,它的實如今Bitmap.cpp裏
它最終調用的是Bitmap.cpp裏的Bitmap_compress()函數,咱們來看看它的實現。
static bool Bitmap_compress(JNIEnv* env, jobject clazz, SkBitmap* bitmap, int format, int quality, jobject jstream, jbyteArray jstorage) {
SkImageEncoder::Type fm;
//根據編碼類型選擇SkImageEncoder
switch (format) {
case kJPEG_JavaEncodeFormat:
fm = SkImageEncoder::kJPEG_Type;
break;
case kPNG_JavaEncodeFormat:
fm = SkImageEncoder::kPNG_Type;
break;
case kWEBP_JavaEncodeFormat:
fm = SkImageEncoder::kWEBP_Type;
break;
default:
return false;
}
//判斷當前bitmap指針是否爲空
bool success = false;
if (NULL != bitmap) {
SkAutoLockPixels alp(*bitmap);
if (NULL == bitmap->getPixels()) {
return false;
}
//建立SkWStream,用於將壓縮數據輸出到輸出流
SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
if (NULL == strm) {
return false;
}
//根據編碼類型,建立對應的編碼器,對bitmap指針指向的圖像數據進行壓縮並輸出到輸出流
SkImageEncoder* encoder = SkImageEncoder::Create(fm);
if (NULL != encoder) {
//調用encodeStream進行編碼
success = encoder->encodeStream(strm, *bitmap, quality);
delete encoder;
}
delete strm;
}
return success;
}複製代碼
能夠看到該函數根據編碼格式選擇SkImageEncoder,從而建立對應的圖像編碼器,最後
調用encodeStream(strm, *bitmap, quality)方法來完成編碼。通
上面的代碼建立了SkJpegEncoder,並最終調用了它裏面的make()方法,以下所示:
std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst, const SkPixmap& src,
const Options& options) {
if (!SkPixmapIsValid(src, options.fBlendBehavior)) {
return nullptr;
}
std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst);
if (setjmp(encoderMgr->jmpBuf())) {
return nullptr;
}
if (!encoderMgr->setParams(src.info(), options)) {
return nullptr;
}
//設置壓縮質量
jpeg_set_quality(encoderMgr->cinfo(), options.fQuality, TRUE);
//開始壓縮
jpeg_start_compress(encoderMgr->cinfo(), TRUE);
sk_sp<SkData> icc = icc_from_color_space(src.info());
if (icc) {
// Create a contiguous block of memory with the icc signature followed by the profile.
sk_sp<SkData> markerData =
SkData::MakeUninitialized(kICCMarkerHeaderSize + icc->size());
uint8_t* ptr = (uint8_t*) markerData->writable_data();
memcpy(ptr, kICCSig, sizeof(kICCSig));
ptr += sizeof(kICCSig);
*ptr++ = 1; // This is the first marker.
*ptr++ = 1; // Out of one total markers.
memcpy(ptr, icc->data(), icc->size());
jpeg_write_marker(encoderMgr->cinfo(), kICCMarker, markerData->bytes(), markerData->size());
}
return std::unique_ptr<SkJpegEncoder>(new SkJpegEncoder(std::move(encoderMgr), src));
}複製代碼
上面就是整個圖像壓縮的流程。
通常狀況下,Android自帶的libjpeg就能夠知足平常的開發需求,若是業務對高質量和低存儲的需求比較大,能夠考慮一下如下兩個庫:
尺寸壓縮本質上就是一個從新採樣的過程,放大圖像稱爲上採樣,縮小圖像稱爲下采樣,Android提供了兩種圖像採樣方法,鄰近採樣和雙線性採樣。
鄰近採樣採用鄰近點插值算法,用一個像素點代替鄰近的像素點,
它的實現代碼你們也很是熟悉。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_red, options);
String savePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()
+ "/timo_BitmapFactory_1.png";
ImageUtils.save(bitmap, savePath, Bitmap.CompressFormat.PNG);複製代碼
inSampleSize = 1
inSampleSize = 32
能夠看到這種方式的關鍵在於inSampleSize的選擇,它決定了壓縮後圖像的大小。
inSampleSize表明了壓縮後的圖像一個像素點表明了原來的幾個像素點,例如inSampleSize爲4,則壓縮後的圖像的寬高是原來的1/4,像素點數是原來的1/16,inSampleSize
通常會選擇2的指數,若是不是2的指數,內部計算的時候也會像2的指數靠近。
關於inSampleSize的計算,Luban提供了很好的思路,做者也給出了算法思路。
算法思路
1. 判斷圖像比例值,是否處於如下區間內;
- [1, 0.5625) 即圖像處於 [1:1 ~ 9:16) 比例範圍內
- [0.5625, 0.5) 即圖像處於 [9:16 ~ 1:2) 比例範圍內
- [0.5, 0) 即圖像處於 [1:2 ~ 1:∞) 比例範圍內
2. 判斷圖像最長邊是否過邊界值;
- [1, 0.5625) 邊界值爲:1664 * n(n=1), 4990 * n(n=2), 1280 * pow(2, n-1)(n≥3)
- [0.5625, 0.5) 邊界值爲:1280 * pow(2, n-1)(n≥1)
- [0.5, 0) 邊界值爲:1280 * pow(2, n-1)(n≥1)
3. 計算壓縮圖像實際邊長值,以第2步計算結果爲準,超過某個邊界值則:width / pow(2, n-1),height/pow(2, n-1)
4. 計算壓縮圖像的實際文件大小,以第二、3步結果爲準,圖像比例越大則文件越大。
size = (newW * newH) / (width * height) * m;
- [1, 0.5625) 則 width & height 對應 1664,4990,1280 * n(n≥3),m 對應 150,300,300;
- [0.5625, 0.5) 則 width = 1440,height = 2560, m = 200;
- [0.5, 0) 則 width = 1280,height = 1280 / scale,m = 500;注:scale爲比例值
5. 判斷第4步的size是否太小
- [1, 0.5625) 則最小 size 對應 60,60,100
- [0.5625, 0.5) 則最小 size 都爲 100
- [0.5, 0) 則最小 size 都爲 100
6. 將前面求到的值壓縮圖像 width, height, size 傳入壓縮流程,壓縮圖像直到知足以上數值複製代碼
具體實現
private int computeSize() {
int mSampleSize;
mSourceWidth = mSourceWidth % 2 == 1 ? mSourceWidth + 1 : mSourceWidth;
mSourceHeight = mSourceHeight % 2 == 1 ? mSourceHeight + 1 : mSourceHeight;
mSourceWidth = mSourceWidth > mSourceHeight ? mSourceHeight : mSourceWidth;
mSourceHeight = mSourceWidth > mSourceHeight ? mSourceWidth : mSourceHeight;
double scale = ((double) mSourceWidth / mSourceHeight);
if (scale <= 1 && scale > 0.5625) {
if (mSourceHeight < 1664) {
mSampleSize = 1;
} else if (mSourceHeight >= 1664 && mSourceHeight < 4990) {
mSampleSize = 2;
} else if (mSourceHeight >= 4990 && mSourceHeight < 10240) {
mSampleSize = 4;
} else {
mSampleSize = mSourceHeight / 1280 == 0 ? 1 : mSourceHeight / 1280;
}
} else if (scale <= 0.5625 && scale > 0.5) {
mSampleSize = mSourceHeight / 1280 == 0 ? 1 : mSourceHeight / 1280;
} else {
mSampleSize = (int) Math.ceil(mSourceHeight / (1280.0 / scale));
}
return mSampleSize;
}複製代碼
核心思想就是經過對原圖寬高的比較計算出合適的採樣值。
一樣的咱們也來看看這種方式的底層實現原理,BitmapFactory裏有不少decode方法,它們最終調用的是native方法。
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts);
private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, Rect padding, Options opts);
private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts);複製代碼
這些native方法在BitmapFactory.cpp裏實現,這些方法最終調用的是doDecode()方法
static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, jobject options, bool allowPurgeable, bool forcePurgeable = false, bool applyScale = false, float scale = 1.0f) {
int sampleSize = 1;
//圖像解碼模式,這裏是像素點模式
SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
//參數初始化
SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config;
bool doDither = true;
bool isMutable = false;
bool willScale = applyScale && scale != 1.0f;
bool isPurgeable = !willScale &&
(forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)));
bool preferQualityOverSpeed = false;
//javaBitmap對象
jobject javaBitmap = NULL;
//對options裏的參數進行初始化
if (options != NULL) {
sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
if (optionsJustBounds(env, options)) {
mode = SkImageDecoder::kDecodeBounds_Mode;
}
// initialize these, in case we fail later on
env->SetIntField(options, gOptions_widthFieldID, -1);
env->SetIntField(options, gOptions_heightFieldID, -1);
env->SetObjectField(options, gOptions_mimeFieldID, 0);
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
preferQualityOverSpeed = env->GetBooleanField(options,
gOptions_preferQualityOverSpeedFieldID);
javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
}
if (willScale && javaBitmap != NULL) {
return nullObjectReturn("Cannot pre-scale a reused bitmap");
}
//建立圖像解碼器,並設置從Java層傳遞過來的參數,例如sampleSize、doDither等
SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
if (decoder == NULL) {
return nullObjectReturn("SkImageDecoder::Factory returned null");
}
decoder->setSampleSize(sampleSize);
decoder->setDitherImage(doDither);
decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
NinePatchPeeker peeker(decoder);
//Java的像素分配器
JavaPixelAllocator javaAllocator(env);
SkBitmap* bitmap;
if (javaBitmap == NULL) {
bitmap = new SkBitmap;
} else {
if (sampleSize != 1) {
return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1");
}
bitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
// config of supplied bitmap overrules config set in options
prefConfig = bitmap->getConfig();
}
SkAutoTDelete<SkImageDecoder> add(decoder);
SkAutoTDelete<SkBitmap> adb(bitmap, javaBitmap == NULL);
decoder->setPeeker(&peeker);
if (!isPurgeable) {
decoder->setAllocator(&javaAllocator);
}
AutoDecoderCancel adc(options, decoder);
// To fix the race condition in case "requestCancelDecode"
// happens earlier than AutoDecoderCancel object is added
// to the gAutoDecoderCancelMutex linked list.
if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) {
return nullObjectReturn("gOptions_mCancelID");
}
SkImageDecoder::Mode decodeMode = mode;
if (isPurgeable) {
decodeMode = SkImageDecoder::kDecodeBounds_Mode;
}
//解碼
SkBitmap* decoded;
if (willScale) {
decoded = new SkBitmap;
} else {
decoded = bitmap;
}
SkAutoTDelete<SkBitmap> adb2(willScale ? decoded : NULL);
if (!decoder->decode(stream, decoded, prefConfig, decodeMode, javaBitmap != NULL)) {
return nullObjectReturn("decoder->decode returned false");
}
//縮放操做
int scaledWidth = decoded->width();
int scaledHeight = decoded->height();
if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
scaledWidth = int(scaledWidth * scale + 0.5f);
scaledHeight = int(scaledHeight * scale + 0.5f);
}
// 更新選項參數
if (options != NULL) {
env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
env->SetObjectField(options, gOptions_mimeFieldID,
getMimeTypeString(env, decoder->getFormat()));
}
//處於justBounds模式,再也不建立Bitmap對象,直接返回,這個很熟悉吧,對應了
//options.inJustDecodeBounds = true,直解析大小,不實際加載圖像
if (mode == SkImageDecoder::kDecodeBounds_Mode) {
return NULL;
}
jbyteArray ninePatchChunk = NULL;
if (peeker.fPatchIsValid) {
if (willScale) {
scaleNinePatchChunk(peeker.fPatch, scale);
}
size_t ninePatchArraySize = peeker.fPatch->serializedSize();
ninePatchChunk = env->NewByteArray(ninePatchArraySize);
if (ninePatchChunk == NULL) {
return nullObjectReturn("ninePatchChunk == null");
}
jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL);
if (array == NULL) {
return nullObjectReturn("primitive array == null");
}
peeker.fPatch->serialize(array);
env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
}
// detach bitmap from its autodeleter, since we want to own it now
adb.detach();
//處理縮放
if (willScale) {
// This is weird so let me explain: we could use the scale parameter
// directly, but for historical reasons this is how the corresponding
// Dalvik code has always behaved. We simply recreate the behavior here.
// The result is slightly different from simply using scale because of
// the 0.5f rounding bias applied when computing the target image size
const float sx = scaledWidth / float(decoded->width());
const float sy = scaledHeight / float(decoded->height());
bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight);
bitmap->allocPixels(&javaAllocator, NULL);
bitmap->eraseColor(0);
SkPaint paint;
paint.setFilterBitmap(true);
SkCanvas canvas(*bitmap);
canvas.scale(sx, sy);
canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint);
}
//處理圖像的邊距
if (padding) {
if (peeker.fPatchIsValid) {
GraphicsJNI::set_jrect(env, padding,
peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
} else {
GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
}
}
SkPixelRef* pr;
if (isPurgeable) {
pr = installPixelRef(bitmap, stream, sampleSize, doDither);
} else {
// if we get here, we're in kDecodePixels_Mode and will therefore
// already have a pixelref installed.
pr = bitmap->pixelRef();
}
if (!isMutable) {
// promise we will never change our pixels (great for sharing and pictures)
pr->setImmutable();
}
if (javaBitmap != NULL) {
// If a java bitmap was passed in for reuse, pass it back
return javaBitmap;
}
// 建立Bitmap對象並返回
return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(),
isMutable, ninePatchChunk);
}複製代碼
咱們發如今最後調用了createBitmap()方法來建立Bitmap對象,這個方法在Graphics.cpp裏定義的,咱們來看看它是如何建立Bitmap的。
jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
bool isMutable, jbyteArray ninepatch, int density)
{
SkASSERT(bitmap);
SkASSERT(bitmap->pixelRef());
//調用Java方法,建立一個對象
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)),
buffer, isMutable, ninepatch, density);
hasException(env); // For the side effect of logging.
//返回Bitmap對象
return obj;
}複製代碼
能夠看到最終C++層調用JNI方法建立了Java層的Bitmap對象,至此,整個BitmapFactory的解碼流程咱們就分析完了。
雙線性採樣採用雙線性插值算法,相比鄰近採樣簡單粗暴的選擇一個像素點代替其餘像素點,雙線性採樣參考源像素相應位置周圍2x2個點的值,根據相對位置取對應的權重,通過計算獲得目標圖像。
它的實現方式也很簡單
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_red);
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
Bitmap sclaedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth()/2, bitmap.getHeight()/2, matrix, true);
String savePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + "/timo_BitmapFactory_1.png";
ImageUtils.save(bitmap, savePath, Bitmap.CompressFormat.PNG);複製代碼
這種方式的關鍵在於Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter)方法。
這個方法有七個參數:
咱們來看看它的實現。
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) {
//參數校驗
...
int neww = width;
int newh = height;
Canvas canvas = new Canvas();
Bitmap bitmap;
Paint paint;
Rect srcR = new Rect(x, y, x + width, y + height);
RectF dstR = new RectF(0, 0, width, height);
//選擇圖像的編碼格式,和源圖像保持一致
Config newConfig = Config.ARGB_8888;
final Config config = source.getConfig();
// GIF files generate null configs, assume ARGB_8888
if (config != null) {
switch (config) {
case RGB_565:
newConfig = Config.RGB_565;
break;
case ALPHA_8:
newConfig = Config.ALPHA_8;
break;
//noinspection deprecation
case ARGB_4444:
case ARGB_8888:
default:
newConfig = Config.ARGB_8888;
break;
}
}
if (m == null || m.isIdentity()) {
bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
paint = null; // not needed
} else {
final boolean transformed = !m.rectStaysRect();
//經過Matrix變換獲取新的圖像寬高
RectF deviceR = new RectF();
m.mapRect(deviceR, dstR);
neww = Math.round(deviceR.width());
newh = Math.round(deviceR.height());
//傳入圖像參數到底層,建立愛女Bitmap對象
bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
transformed || source.hasAlpha());
canvas.translate(-deviceR.left, -deviceR.top);
canvas.concat(m);
paint = new Paint();
paint.setFilterBitmap(filter);
if (transformed) {
paint.setAntiAlias(true);
}
}
// The new bitmap was created from a known bitmap source so assume that
// they use the same density
bitmap.mDensity = source.mDensity;
bitmap.setHasAlpha(source.hasAlpha());
bitmap.setPremultiplied(source.mRequestPremultiplied);
canvas.setBitmap(bitmap);
canvas.drawBitmap(source, srcR, dstR, paint);
canvas.setBitmap(null);
return bitmap;
}複製代碼
能夠看到這個方法又調用了它的同名方法createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,transformed || source.hasAlpha())
該方法固然也是藉由底層的native方法實現Bitmap的建立。
private static native Bitmap nativeCreate(int[] colors, int offset, int stride, int width, int height, int nativeConfig, boolean mutable);複製代碼
這個方法對應着Bitmap.cpp裏的Bitmap_creator()方法。
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, int offset, int stride, int width, int height, SkBitmap::Config config, jboolean isMutable) {
if (NULL != jColors) {
size_t n = env->GetArrayLength(jColors);
if (n < SkAbs32(stride) * (size_t)height) {
doThrowAIOOBE(env);
return NULL;
}
}
//SkBitmap對象
SkBitmap bitmap;
//設置圖像配置信息
bitmap.setConfig(config, width, height);
//建立圖像數組,這裏對應着Bitmap.java裏的mBuffers
jbyteArray buff = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
if (NULL == buff) {
return NULL;
}
if (jColors != NULL) {
GraphicsJNI::SetPixels(env, jColors, offset, stride,
0, 0, width, height, bitmap);
}
//建立Bitmap對象,並返回
return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL);
}複製代碼
能夠看到上面調用allocateJavaPixelRef()方法來建立圖像數組,該方法在Graphics.cpp裏定義的。
jbyteArray GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
SkColorTable* ctable) {
Sk64 size64 = bitmap->getSize64();
if (size64.isNeg() || !size64.is32()) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"bitmap size exceeds 32bits");
return NULL;
}
size_t size = size64.get32();
//調用Java層的方法建立一個Java數組
jbyteArray arrayObj = env->NewByteArray(size);
if (arrayObj) {
// TODO: make this work without jniGetNonMovableArrayElements
//獲取數組地址
jbyte* addr = jniGetNonMovableArrayElements(&env->functions, arrayObj);
if (addr) {
SkPixelRef* pr = new AndroidPixelRef(env, (void*) addr, size, arrayObj, ctable);
bitmap->setPixelRef(pr)->unref();
// since we're already allocated, we lockPixels right away
// HeapAllocator behaves this way too
bitmap->lockPixels();
}
}
return arrayObj;
}複製代碼
建立完成圖像數組後,就接着調用createBitmap()建立Java層的Bitmap對象,這個咱們在上面已經說過,自此Bitmap.createBitmap()方法的實現流程咱們也分析完了。
以上即是Android原生支持的兩種採樣方式,若是這些並不能知足你的業務需求,能夠考慮如下兩種方式。
好了,以上就是關於Android平臺處理圖像壓縮的所有內容,下一篇文章咱們來分析視頻壓縮的實現方案。另外phoenix項目完整的實現了圖片與視頻的壓縮,其中圖片的壓縮就是用的上文提到的Luban的算法實現,你們在作項目的時候能夠作個參考。