10
10
import android .util .Log ;
11
11
import com .bumptech .glide .load .DecodeFormat ;
12
12
import com .bumptech .glide .load .ImageHeaderParser ;
13
+ import com .bumptech .glide .load .ImageHeaderParser .ImageType ;
13
14
import com .bumptech .glide .load .ImageHeaderParserUtils ;
14
15
import com .bumptech .glide .load .Option ;
15
16
import com .bumptech .glide .load .Options ;
@@ -121,6 +122,9 @@ public void onDecodeComplete(BitmapPool bitmapPool, Bitmap downsampled) throws I
121
122
// 5MB. This is the max image header size we can handle, we preallocate a much smaller buffer
122
123
// but will resize up to this amount if necessary.
123
124
private static final int MARK_POSITION = 5 * 1024 * 1024 ;
125
+ // Defines the level of precision we get when using inDensity/inTargetDensity to calculate an
126
+ // arbitrary float scale factor.
127
+ private static final int DENSITY_PRECISION_MULTIPLIER = 1000000000 ;
124
128
125
129
private final BitmapPool bitmapPool ;
126
130
private final DisplayMetrics displayMetrics ;
@@ -231,8 +235,20 @@ private Bitmap decodeFromWrappedStreams(InputStream is,
231
235
int targetWidth = requestedWidth == Target .SIZE_ORIGINAL ? sourceWidth : requestedWidth ;
232
236
int targetHeight = requestedHeight == Target .SIZE_ORIGINAL ? sourceHeight : requestedHeight ;
233
237
234
- calculateScaling (downsampleStrategy , degreesToRotate , sourceWidth , sourceHeight , targetWidth ,
235
- targetHeight , options );
238
+ ImageType imageType = ImageHeaderParserUtils .getType (parsers , is , byteArrayPool );
239
+
240
+ calculateScaling (
241
+ imageType ,
242
+ is ,
243
+ callbacks ,
244
+ bitmapPool ,
245
+ downsampleStrategy ,
246
+ degreesToRotate ,
247
+ sourceWidth ,
248
+ sourceHeight ,
249
+ targetWidth ,
250
+ targetHeight ,
251
+ options );
236
252
calculateConfig (
237
253
is ,
238
254
decodeFormat ,
@@ -244,8 +260,7 @@ private Bitmap decodeFromWrappedStreams(InputStream is,
244
260
245
261
boolean isKitKatOrGreater = Build .VERSION .SDK_INT >= Build .VERSION_CODES .KITKAT ;
246
262
// Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
247
- if ((options .inSampleSize == 1 || isKitKatOrGreater )
248
- && shouldUsePool (is )) {
263
+ if ((options .inSampleSize == 1 || isKitKatOrGreater ) && shouldUsePool (imageType )) {
249
264
int expectedWidth ;
250
265
int expectedHeight ;
251
266
if (fixBitmapToRequestedDimensions && isKitKatOrGreater ) {
@@ -299,10 +314,18 @@ && shouldUsePool(is)) {
299
314
}
300
315
301
316
// Visible for testing.
302
- static void calculateScaling (DownsampleStrategy downsampleStrategy ,
317
+ static void calculateScaling (
318
+ ImageType imageType ,
319
+ InputStream is ,
320
+ DecodeCallbacks decodeCallbacks ,
321
+ BitmapPool bitmapPool ,
322
+ DownsampleStrategy downsampleStrategy ,
303
323
int degreesToRotate ,
304
- int sourceWidth , int sourceHeight , int targetWidth , int targetHeight ,
305
- BitmapFactory .Options options ) {
324
+ int sourceWidth ,
325
+ int sourceHeight ,
326
+ int targetWidth ,
327
+ int targetHeight ,
328
+ BitmapFactory .Options options ) throws IOException {
306
329
// We can't downsample source content if we can't determine its dimensions.
307
330
if (sourceWidth <= 0 || sourceHeight <= 0 ) {
308
331
return ;
@@ -323,16 +346,18 @@ static void calculateScaling(DownsampleStrategy downsampleStrategy,
323
346
324
347
if (exactScaleFactor <= 0f ) {
325
348
throw new IllegalArgumentException ("Cannot scale with factor: " + exactScaleFactor
326
- + " from: " + downsampleStrategy );
349
+ + " from: " + downsampleStrategy
350
+ + ", source: [" + sourceWidth + "x" + sourceHeight + "]"
351
+ + ", target: [" + targetWidth + "x" + targetHeight + "]" );
327
352
}
328
353
SampleSizeRounding rounding = downsampleStrategy .getSampleSizeRounding (sourceWidth ,
329
354
sourceHeight , targetWidth , targetHeight );
330
355
if (rounding == null ) {
331
356
throw new IllegalArgumentException ("Cannot round with null rounding" );
332
357
}
333
358
334
- int outWidth = ( int ) ( exactScaleFactor * sourceWidth + 0.5f );
335
- int outHeight = ( int ) ( exactScaleFactor * sourceHeight + 0.5f );
359
+ int outWidth = round ( exactScaleFactor * sourceWidth );
360
+ int outHeight = round ( exactScaleFactor * sourceHeight );
336
361
337
362
int widthScaleFactor = sourceWidth / outWidth ;
338
363
int heightScaleFactor = sourceHeight / outHeight ;
@@ -354,14 +379,53 @@ static void calculateScaling(DownsampleStrategy downsampleStrategy,
354
379
}
355
380
}
356
381
357
- float adjustedScaleFactor = powerOfTwoSampleSize * exactScaleFactor ;
358
-
382
+ // Here we mimic framework logic for determining how inSampleSize division is rounded on various
383
+ // versions of Android. The logic here has been tested on emulators for Android versions 15-26.
384
+ // PNG - Always uses floor
385
+ // JPEG - Always uses ceiling
386
+ // Webp - Prior to N, always uses floor. At and after N, always uses round.
359
387
options .inSampleSize = powerOfTwoSampleSize ;
388
+ final int powerOfTwoWidth ;
389
+ final int powerOfTwoHeight ;
390
+ // Jpeg rounds with ceiling on all API verisons.
391
+ if (imageType == ImageType .JPEG ) {
392
+ powerOfTwoWidth = (int ) Math .ceil (sourceWidth / (float ) powerOfTwoSampleSize );
393
+ powerOfTwoHeight = (int ) Math .ceil (sourceHeight / (float ) powerOfTwoSampleSize );
394
+ } else if (imageType == ImageType .PNG || imageType == ImageType .PNG_A ) {
395
+ powerOfTwoWidth = (int ) Math .floor (sourceWidth / (float ) powerOfTwoSampleSize );
396
+ powerOfTwoHeight = (int ) Math .floor (sourceHeight / (float ) powerOfTwoSampleSize );
397
+ } else if (imageType == ImageType .WEBP || imageType == ImageType .WEBP_A ) {
398
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N ) {
399
+ powerOfTwoWidth = Math .round (sourceWidth / (float ) powerOfTwoSampleSize );
400
+ powerOfTwoHeight = Math .round (sourceHeight / (float ) powerOfTwoSampleSize );
401
+ } else {
402
+ powerOfTwoWidth = (int ) Math .floor (sourceWidth / (float ) powerOfTwoSampleSize );
403
+ powerOfTwoHeight = (int ) Math .floor (sourceHeight / (float ) powerOfTwoSampleSize );
404
+ }
405
+ } else if (
406
+ sourceWidth % powerOfTwoSampleSize != 0 || sourceHeight % powerOfTwoSampleSize != 0 ) {
407
+ // If we're not confident the image is in one of our types, fall back to checking the
408
+ // dimensions again. inJustDecodeBounds decodes do obey inSampleSize.
409
+ int [] dimensions = getDimensions (is , options , decodeCallbacks , bitmapPool );
410
+ // Power of two downsampling in BitmapFactory uses a variety of random factors to determine
411
+ // rounding that we can't reliably replicate for all image formats. Use ceiling here to make
412
+ // sure that we at least provide a Bitmap that's large enough to fit the content we're going
413
+ // to load.
414
+ powerOfTwoWidth = dimensions [0 ];
415
+ powerOfTwoHeight = dimensions [1 ];
416
+ } else {
417
+ powerOfTwoWidth = sourceWidth / powerOfTwoSampleSize ;
418
+ powerOfTwoHeight = sourceHeight / powerOfTwoSampleSize ;
419
+ }
420
+
421
+ double adjustedScaleFactor = downsampleStrategy .getScaleFactor (
422
+ powerOfTwoWidth , powerOfTwoHeight , targetWidth , targetHeight );
423
+
360
424
// Density scaling is only supported if inBitmap is null prior to KitKat. Avoid setting
361
425
// densities here so we calculate the final Bitmap size correctly.
362
426
if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .KITKAT ) {
363
- options .inTargetDensity = ( int ) ( 1000 * adjustedScaleFactor + 0.5f );
364
- options .inDensity = 1000 ;
427
+ options .inTargetDensity = adjustTargetDensityForError ( adjustedScaleFactor );
428
+ options .inDensity = DENSITY_PRECISION_MULTIPLIER ;
365
429
}
366
430
if (isScaling (options )) {
367
431
options .inScaled = true ;
@@ -373,6 +437,7 @@ static void calculateScaling(DownsampleStrategy downsampleStrategy,
373
437
Log .v (TAG , "Calculate scaling"
374
438
+ ", source: [" + sourceWidth + "x" + sourceHeight + "]"
375
439
+ ", target: [" + targetWidth + "x" + targetHeight + "]"
440
+ + ", power of two scaled: [" + powerOfTwoWidth + "x" + powerOfTwoHeight + "]"
376
441
+ ", exact scale factor: " + exactScaleFactor
377
442
+ ", power of 2 sample size: " + powerOfTwoSampleSize
378
443
+ ", adjusted scale factor: " + adjustedScaleFactor
@@ -381,24 +446,34 @@ static void calculateScaling(DownsampleStrategy downsampleStrategy,
381
446
}
382
447
}
383
448
384
- private boolean shouldUsePool (InputStream is ) throws IOException {
449
+ /**
450
+ * BitmapFactory calculates the density scale factor as a float. This introduces some non-trivial
451
+ * error. This method attempts to account for that error by adjusting the inTargetDensity so that
452
+ * the final scale factor is as close to our target as possible.
453
+ */
454
+ private static int adjustTargetDensityForError (double adjustedScaleFactor ) {
455
+ int targetDensity = round (DENSITY_PRECISION_MULTIPLIER * adjustedScaleFactor );
456
+ float scaleFactorWithError = targetDensity / (float ) DENSITY_PRECISION_MULTIPLIER ;
457
+ double difference = adjustedScaleFactor / scaleFactorWithError ;
458
+ return round (difference * targetDensity );
459
+ }
460
+
461
+ // This is weird, but it matches the logic in a bunch of Android views/framework classes for
462
+ // rounding.
463
+ private static int round (double value ) {
464
+ return (int ) (value + 0.5d );
465
+ }
466
+
467
+ private boolean shouldUsePool (ImageType imageType ) throws IOException {
385
468
// On KitKat+, any bitmap (of a given config) can be used to decode any other bitmap
386
469
// (with the same config).
387
470
if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .KITKAT ) {
388
471
return true ;
389
472
}
390
473
391
- try {
392
- ImageHeaderParser .ImageType type = ImageHeaderParserUtils .getType (parsers , is , byteArrayPool );
393
- // We cannot reuse bitmaps when decoding images that are not PNG or JPG prior to KitKat.
394
- // See: https://groups.google.com/forum/#!msg/android-developers/Mp0MFVFi1Fo/e8ZQ9FGdWdEJ
395
- return TYPES_THAT_USE_POOL_PRE_KITKAT .contains (type );
396
- } catch (IOException e ) {
397
- if (Log .isLoggable (TAG , Log .DEBUG )) {
398
- Log .d (TAG , "Cannot determine the image type from header" , e );
399
- }
400
- }
401
- return false ;
474
+ // We cannot reuse bitmaps when decoding images that are not PNG or JPG prior to KitKat.
475
+ // See: https://groups.google.com/forum/#!msg/android-developers/Mp0MFVFi1Fo/e8ZQ9FGdWdEJ
476
+ return TYPES_THAT_USE_POOL_PRE_KITKAT .contains (imageType );
402
477
}
403
478
404
479
private void calculateConfig (
0 commit comments