[关闭]
@946898963 2022-11-10T10:50:25.000000Z 字数 17005 阅读 1377

BitmapFactory.Options中的inDensity和inTargetDensity

Bitmap


影响BitmapFactory decode出的图片的最终大小的不只有inSampleSize属性,还有inDensity和inTargetDensity这两个属性。

  • Options中的inDensity属性会根据drawable文件夹的分辨率来赋值。
  • Options中的inTartgetDensity会根据屏幕的像素密度来赋值。

在从资源目录加载图片时,这个时候调用的是BitmapFactory#decodeResource方法,内部调用的是decodeResourceStream方法:

#decodeResource

  1. public static Bitmap decodeResource(Resources res, int id, Options opts) {
  2. validate(opts);
  3. Bitmap bm = null;
  4. InputStream is = null;
  5. try {
  6. //openRawResource解析资源,获取相关信息,这里是获取desinity,drawable文件夹下是//0,mdpi===160,hdpi====240 以此类推DisplayMetrics.DENSITY_DEFAULT=160
  7. final TypedValue value = new TypedValue();
  8. is = res.openRawResource(id, value);
  9. bm = decodeResourceStream(res, value, is, null, opts);
  10. } catch (Exception e) {
  11. /* do nothing.
  12. If the exception happened on open, bm will be null.
  13. If it happened on close, bm is still valid.
  14. */
  15. } finally {
  16. try {
  17. if (is != null) is.close();
  18. } catch (IOException e) {
  19. // Ignore
  20. }
  21. }
  22. if (bm == null && opts != null && opts.inBitmap != null) {
  23. throw new IllegalArgumentException("Problem decoding into existing bitmap");
  24. }
  25. return bm;
  26. }

#decodeResourceStream

  1. public static Bitmap decodeResourceStream(Resources res, TypedValue value,
  2. InputStream is, Rect pad, Options opts) {
  3. if (opts == null) {
  4. opts = new Options();
  5. }
  6. if (opts.inDensity == 0 && value != null) {
  7. final int density = value.density;
  8. if (density == TypedValue.DENSITY_DEFAULT) {//图片在drawable目录下
  9. opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
  10. } else if (density != TypedValue.DENSITY_NONE) {//图片在非drawable目录下
  11. opts.inDensity = density;
  12. }
  13. }
  14. if (opts.inTargetDensity == 0 && res != null) {
  15. opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
  16. }
  17. return decodeStream(is, pad, opts);
  18. }
  1. final TypedValue value = new TypedValue();
  2. is = res.openRawResource(id, value);

在读取资源时,会使用openRawResource方法,这个方法内部会对TypedValue进行赋值,其中包含了原始资源的density等信息,也即是文件夹代表的density;
各文件夹和density的对应关系如下:

  1. drawable -----> 0
  2. ldpi -----> 120
  3. mdpi -----> 160
  4. hdpi -----> 240
  5. xhdpi -----> 320
  6. xxhdpi -----> 480
  7. xxxhdpi -----> 640

drawable文件夹下是0,ldpi==120,mdpi===160,hdpi====240,xhdpi==320以此类推,如果将图片放入默认drawable文件夹(不指定分辨率,即文件夹名后不跟分辨率),则最终会使用默认的Density(DisplayMetrics.DENSITY_DEFAULT=160)。

  1. DisplayMetrics metrics = new DisplayMetrics();
  2. WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  3. wm.getDefaultDisplay().getMetrics(metrics);
  4. int inTargetDensity = metircs.densityDpi;

屏幕的density可由此代码获得,这个就是inTargetDensity。

根据设备屏幕像素密度,到对应drawable目录去寻找图片,如果能在对应目录找到图片,这时候inTargetDensity/inDensity=1,图片不会做缩放,宽度和高度就是图片原始的像素规格;如果没有在对应的drawable目录找到图片,会到其他规格的drawable目录去找,然后会根据inTargetDensity/inDensity的比例对图片的宽度和高度进行缩放。

输出图片的宽高= 原图片的宽高 / inSampleSize * (inTargetDensity / inDensity)

这个计算仅针对于drawable文件夹的图片来说,而对于一个file或者stream那么inDensity和inTargetDensity是不考虑的!他们默认就是0。

调用decodeResourceStream对原始资源进行解码和适配,实际是原始资源density到设备屏幕density的映射。

注意:inSampleSize只能是2的幂,如不是2的幂下转到最大的2的幂,而且inSampleSize>=1。

接下来,具体看下缩放的实现代码:

Android 4.4之前#decodeStream

  1. public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
  2. // we don't throw in this case, thus allowing the caller to only check
  3. // the cache, and not force the image to be decoded.
  4. if (is == null) {
  5. return null;
  6. }
  7. // we need mark/reset to work properly
  8. if (!is.markSupported()) {
  9. is = new BufferedInputStream(is, DECODE_BUFFER_SIZE);
  10. }
  11. // so we can call reset() if a given codec gives up after reading up to
  12. // this many bytes. FIXME: need to find out from the codecs what this
  13. // value should be.
  14. is.mark(1024);
  15. Bitmap bm;
  16. boolean finish = true;
  17. if (is instanceof AssetManager.AssetInputStream) {
  18. final int asset = ((AssetManager.AssetInputStream) is).getAssetInt();
  19. if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
  20. float scale = 1.0f;
  21. int targetDensity = 0;
  22. if (opts != null) {
  23. final int density = opts.inDensity;
  24. targetDensity = opts.inTargetDensity;
  25. if (density != 0 && targetDensity != 0) {
  26. scale = targetDensity / (float) density;
  27. }
  28. }
  29. bm = nativeDecodeAsset(asset, outPadding, opts, true, scale);
  30. if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);
  31. finish = false;
  32. } else {
  33. bm = nativeDecodeAsset(asset, outPadding, opts);
  34. }
  35. } else {
  36. // pass some temp storage down to the native code. 1024 is made up,
  37. // but should be large enough to avoid too many small calls back
  38. // into is.read(...) This number is not related to the value passed
  39. // to mark(...) above.
  40. byte [] tempStorage = null;
  41. if (opts != null) tempStorage = opts.inTempStorage;
  42. if (tempStorage == null) tempStorage = new byte[16 * 1024];
  43. if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
  44. float scale = 1.0f;
  45. int targetDensity = 0;
  46. if (opts != null) {
  47. final int density = opts.inDensity;
  48. targetDensity = opts.inTargetDensity;
  49. if (density != 0 && targetDensity != 0) {
  50. scale = targetDensity / (float) density;
  51. }
  52. }
  53. bm = nativeDecodeStream(is, tempStorage, outPadding, opts, true, scale);
  54. if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);
  55. finish = false;
  56. } else {
  57. bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
  58. }
  59. }
  60. if (bm == null && opts != null && opts.inBitmap != null) {
  61. throw new IllegalArgumentException("Problem decoding into existing bitmap");
  62. }
  63. return finish ? finishDecode(bm, outPadding, opts) : bm;
  64. }

#finishDecode

  1. private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
  2. if (bm == null || opts == null) {
  3. return bm;
  4. }
  5. final int density = opts.inDensity;
  6. if (density == 0) {
  7. return bm;
  8. }
  9. bm.setDensity(density);
  10. final int targetDensity = opts.inTargetDensity;
  11. if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
  12. return bm;
  13. }
  14. byte[] np = bm.getNinePatchChunk();
  15. int[] lb = bm.getLayoutBounds();
  16. final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
  17. if (opts.inScaled || isNinePatch) {
  18. float scale = targetDensity / (float) density;
  19. if (scale != 1.0f) {
  20. final Bitmap oldBitmap = bm;
  21. bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
  22. (int) (bm.getHeight() * scale + 0.5f), true);
  23. if (bm != oldBitmap) oldBitmap.recycle();
  24. if (isNinePatch) {
  25. np = nativeScaleNinePatch(np, scale, outPadding);
  26. bm.setNinePatchChunk(np);
  27. }
  28. if (lb != null) {
  29. int[] newLb = new int[lb.length];
  30. for (int i=0; i<lb.length; i++) {
  31. newLb[i] = (int)((lb[i]*scale)+.5f);
  32. }
  33. bm.setLayoutBounds(newLb);
  34. }
  35. }
  36. bm.setDensity(targetDensity);
  37. }
  38. return bm;
  39. }
  1. ....
  2. float scale = targetDensity / (float) density;
  3. if (scale != 1.0f) {
  4. ....
  5. final Bitmap oldBitmap = bm;
  6. bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
  7. (int) (bm.getHeight() * scale + 0.5f), true);
  8. if (bm != oldBitmap) oldBitmap.recycle();
  9. ....
  10. }
  11. ....

在Android 4.4之前,当decodeStream完成后,在设备密度不为0且不等于资源密度时,会执行finishDecode,在finishDecode中会调用createScaledBitmap重新创建bitmap并回收旧的bitmap,也就是说在java层有一个调整(缩放)bitmap的逻辑。

Android 4.4之后#decodeStream

  1. public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
  2. // we don't throw in this case, thus allowing the caller to only check
  3. // the cache, and not force the image to be decoded.
  4. if (is == null) {
  5. return null;
  6. }
  7. validate(opts);
  8. Bitmap bm = null;
  9. Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
  10. try {
  11. if (is instanceof AssetManager.AssetInputStream) {
  12. final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
  13. bm = nativeDecodeAsset(asset, outPadding, opts);
  14. } else {
  15. bm = decodeStreamInternal(is, outPadding, opts);
  16. }
  17. if (bm == null && opts != null && opts.inBitmap != null) {
  18. throw new IllegalArgumentException("Problem decoding into existing bitmap");
  19. }
  20. setDensityFromOptions(bm, opts);
  21. } finally {
  22. Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
  23. }
  24. return bm;
  25. }

#setDensityFromOptions

  1. private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
  2. if (outputBitmap == null || opts == null) return;
  3. final int density = opts.inDensity;
  4. if (density != 0) {
  5. outputBitmap.setDensity(density);
  6. final int targetDensity = opts.inTargetDensity;
  7. if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
  8. return;
  9. }
  10. byte[] np = outputBitmap.getNinePatchChunk();
  11. final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
  12. if (opts.inScaled || isNinePatch) {
  13. outputBitmap.setDensity(targetDensity);
  14. }
  15. } else if (opts.inBitmap != null) {
  16. // bitmap was reused, ensure density is reset
  17. outputBitmap.setDensity(Bitmap.getDefaultDensity());
  18. }
  19. }

可以看到,decodeStream方法在执行完成后,会调用setDensityFromOptions方法,该方法主要就是把前面赋值过的两个属性inDensity和inTargetDensity给Bitmap进行赋值,不过并不是直接赋给Bitmap就完了,中间有个判断,当inDensity的值与inTargetDensity或与设备的屏幕Density不相等时,则将应用inTargetDensity的值,如果相等则应用inDensity的值。

在setDensityFromOptions方法中,我们没有看到根据密度调整Bitmap的操作,这是因为Android 4.4以后,为了节省内存,将这个操作放在了native方法中,也就是C层去做Bitmap的缩放。接下来我们看下相关的缩放的代码:

  1. if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
  2. const int density = env->GetIntField(options, gOptions_densityFieldID);
  3. const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
  4. const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
  5. if (density != 0 && targetDensity != 0 && density != screenDensity) {
  6. scale = (float) targetDensity / density;
  7. }
  8. }

更详细的方法代码如下:

  1. #define BYTES_TO_BUFFER 64
  2. static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
  3. jobject padding, jobject options) {
  4. jobject bitmap = NULL;
  5. SkAutoTDelete<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));
  6. if (stream.get()) {
  7. SkAutoTDelete<SkStreamRewindable> bufferedStream(
  8. SkFrontBufferedStream::Create(stream.detach(), BYTES_TO_BUFFER));
  9. SkASSERT(bufferedStream.get() != NULL);
  10. bitmap = doDecode(env, bufferedStream, padding, options);
  11. }
  12. return bitmap;
  13. }
  1. static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
  2. int sampleSize = 1;
  3. SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodePixels_Mode;
  4. SkColorType prefColorType = kN32_SkColorType;
  5. bool doDither = true;
  6. bool isMutable = false;
  7. float scale = 1.0f;
  8. bool preferQualityOverSpeed = false;
  9. bool requireUnpremultiplied = false;
  10. jobject javaBitmap = NULL;
  11. if (options != NULL) {
  12. sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
  13. if (optionsJustBounds(env, options)) {
  14. decodeMode = SkImageDecoder::kDecodeBounds_Mode;
  15. }
  16. // initialize these, in case we fail later on
  17. env->SetIntField(options, gOptions_widthFieldID, -1);
  18. env->SetIntField(options, gOptions_heightFieldID, -1);
  19. env->SetObjectField(options, gOptions_mimeFieldID, 0);
  20. jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
  21. prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
  22. isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
  23. doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
  24. preferQualityOverSpeed = env->GetBooleanField(options,
  25. gOptions_preferQualityOverSpeedFieldID);
  26. requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
  27. javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
  28. if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
  29. const int density = env->GetIntField(options, gOptions_densityFieldID);
  30. const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
  31. const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
  32. if (density != 0 && targetDensity != 0 && density != screenDensity) {
  33. scale = (float) targetDensity / density;
  34. }
  35. }
  36. }
  37. const bool willScale = scale != 1.0f;
  38. SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
  39. if (decoder == NULL) {
  40. return nullObjectReturn("SkImageDecoder::Factory returned null");
  41. }
  42. decoder->setSampleSize(sampleSize);
  43. decoder->setDitherImage(doDither);
  44. decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
  45. decoder->setRequireUnpremultipliedColors(requireUnpremultiplied);
  46. android::Bitmap* reuseBitmap = nullptr;
  47. unsigned int existingBufferSize = 0;
  48. if (javaBitmap != NULL) {
  49. reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
  50. if (reuseBitmap->peekAtPixelRef()->isImmutable()) {
  51. ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
  52. javaBitmap = NULL;
  53. reuseBitmap = nullptr;
  54. } else {
  55. existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
  56. }
  57. }
  58. NinePatchPeeker peeker(decoder);
  59. decoder->setPeeker(&peeker);
  60. JavaPixelAllocator javaAllocator(env);
  61. RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
  62. ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
  63. SkBitmap::Allocator* outputAllocator = (javaBitmap != NULL) ?
  64. (SkBitmap::Allocator*)&recyclingAllocator : (SkBitmap::Allocator*)&javaAllocator;
  65. if (decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
  66. if (!willScale) {
  67. // If the java allocator is being used to allocate the pixel memory, the decoder
  68. // need not write zeroes, since the memory is initialized to 0.
  69. decoder->setSkipWritingZeroes(outputAllocator == &javaAllocator);
  70. decoder->setAllocator(outputAllocator);
  71. } else if (javaBitmap != NULL) {
  72. // check for eventual scaled bounds at allocation time, so we don't decode the bitmap
  73. // only to find the scaled result too large to fit in the allocation
  74. decoder->setAllocator(&scaleCheckingAllocator);
  75. }
  76. }
  77. // Only setup the decoder to be deleted after its stack-based, refcounted
  78. // components (allocators, peekers, etc) are declared. This prevents RefCnt
  79. // asserts from firing due to the order objects are deleted from the stack.
  80. SkAutoTDelete<SkImageDecoder> add(decoder);
  81. AutoDecoderCancel adc(options, decoder);
  82. // To fix the race condition in case "requestCancelDecode"
  83. // happens earlier than AutoDecoderCancel object is added
  84. // to the gAutoDecoderCancelMutex linked list.
  85. if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) {
  86. return nullObjectReturn("gOptions_mCancelID");
  87. }
  88. SkBitmap decodingBitmap;
  89. if (decoder->decode(stream, &decodingBitmap, prefColorType, decodeMode)
  90. != SkImageDecoder::kSuccess) {
  91. return nullObjectReturn("decoder->decode returned false");
  92. }
  93. int scaledWidth = decodingBitmap.width();
  94. int scaledHeight = decodingBitmap.height();
  95. if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
  96. scaledWidth = int(scaledWidth * scale + 0.5f);
  97. scaledHeight = int(scaledHeight * scale + 0.5f);
  98. }
  99. // update options (if any)
  100. if (options != NULL) {
  101. jstring mimeType = getMimeTypeString(env, decoder->getFormat());
  102. if (env->ExceptionCheck()) {
  103. return nullObjectReturn("OOM in getMimeTypeString()");
  104. }
  105. env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
  106. env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
  107. env->SetObjectField(options, gOptions_mimeFieldID, mimeType);
  108. }
  109. // if we're in justBounds mode, return now (skip the java bitmap)
  110. if (decodeMode == SkImageDecoder::kDecodeBounds_Mode) {
  111. return NULL;
  112. }
  113. jbyteArray ninePatchChunk = NULL;
  114. if (peeker.mPatch != NULL) {
  115. if (willScale) {
  116. scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
  117. }
  118. size_t ninePatchArraySize = peeker.mPatch->serializedSize();
  119. ninePatchChunk = env->NewByteArray(ninePatchArraySize);
  120. if (ninePatchChunk == NULL) {
  121. return nullObjectReturn("ninePatchChunk == null");
  122. }
  123. jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL);
  124. if (array == NULL) {
  125. return nullObjectReturn("primitive array == null");
  126. }
  127. memcpy(array, peeker.mPatch, peeker.mPatchSize);
  128. env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
  129. }
  130. jobject ninePatchInsets = NULL;
  131. if (peeker.mHasInsets) {
  132. ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
  133. peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
  134. peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
  135. peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
  136. if (ninePatchInsets == NULL) {
  137. return nullObjectReturn("nine patch insets == null");
  138. }
  139. if (javaBitmap != NULL) {
  140. env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
  141. }
  142. }
  143. SkBitmap outputBitmap;
  144. if (willScale) {
  145. // This is weird so let me explain: we could use the scale parameter
  146. // directly, but for historical reasons this is how the corresponding
  147. // Dalvik code has always behaved. We simply recreate the behavior here.
  148. // The result is slightly different from simply using scale because of
  149. // the 0.5f rounding bias applied when computing the target image size
  150. const float sx = scaledWidth / float(decodingBitmap.width());
  151. const float sy = scaledHeight / float(decodingBitmap.height());
  152. // TODO: avoid copying when scaled size equals decodingBitmap size
  153. SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
  154. // FIXME: If the alphaType is kUnpremul and the image has alpha, the
  155. // colors may not be correct, since Skia does not yet support drawing
  156. // to/from unpremultiplied bitmaps.
  157. outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
  158. colorType, decodingBitmap.alphaType()));
  159. if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {
  160. return nullObjectReturn("allocation failed for scaled bitmap");
  161. }
  162. // If outputBitmap's pixels are newly allocated by Java, there is no need
  163. // to erase to 0, since the pixels were initialized to 0.
  164. if (outputAllocator != &javaAllocator) {
  165. outputBitmap.eraseColor(0);
  166. }
  167. SkPaint paint;
  168. paint.setFilterQuality(kLow_SkFilterQuality);
  169. SkCanvas canvas(outputBitmap);
  170. canvas.scale(sx, sy);
  171. canvas.drawARGB(0x00, 0x00, 0x00, 0x00);
  172. canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
  173. } else {
  174. outputBitmap.swap(decodingBitmap);
  175. }
  176. if (padding) {
  177. if (peeker.mPatch != NULL) {
  178. GraphicsJNI::set_jrect(env, padding,
  179. peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
  180. peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
  181. } else {
  182. GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
  183. }
  184. }
  185. // if we get here, we're in kDecodePixels_Mode and will therefore
  186. // already have a pixelref installed.
  187. if (outputBitmap.pixelRef() == NULL) {
  188. return nullObjectReturn("Got null SkPixelRef");
  189. }
  190. if (!isMutable && javaBitmap == NULL) {
  191. // promise we will never change our pixels (great for sharing and pictures)
  192. outputBitmap.setImmutable();
  193. }
  194. if (javaBitmap != NULL) {
  195. bool isPremultiplied = !requireUnpremultiplied;
  196. GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
  197. outputBitmap.notifyPixelsChanged();
  198. // If a java bitmap was passed in for reuse, pass it back
  199. return javaBitmap;
  200. }
  201. int bitmapCreateFlags = 0x0;
  202. if (isMutable) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Mutable;
  203. if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
  204. // now create the java bitmap
  205. return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
  206. bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
  207. }

BitmapFactory.cpp

BitmapFactory.Options中的inDensity和inTargetDensity

Android Bitmap 浅析

Android性能优化:Bitmap详解&你的Bitmap占多大内存?

android解析图片资源缩小放大问题

android使用inSampleSize、inScaled、inDensity、inTargetDensity对图片进行缩放(有关于使用inScaled、inDensity、inTargetDensity的代码)

在Android源码中查找Java代码中native函数对应的C++实现

inDensity,inTargetDensity,inScreenDensity关系详解

深入理解Android Bitmap的各种操作

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注