glide icon indicating copy to clipboard operation
glide copied to clipboard

DecodeFormat PREFER_RGB_565 doesn't work

Open runInFuture opened this issue 4 years ago • 5 comments

glide version: 4.11.0 integration library: none model: ANA-AN00 os version: EMUI 11.0.0 base on Android 10

Issue details:

When set DecodeFormat to PREFER_RGB_565,glide apply Config.ARGB_8888 to ANY decoded bitmap,even though when a bitmap doesn't has alpha channel. According to the document:

Bitmaps decoded from image formats that support and/or use alpha (some types of PNGs, GIFs etc) should return {@link android.graphics.Bitmap.Config#ARGB_8888} for {@link android.graphics.Bitmap#getConfig()}. Bitmaps decoded from formats that don't support or use alpha should return {@link android.graphics.Bitmap.Config#RGB_565} for {@link android.graphics.Bitmap#getConfig()}.

it seems something wrong happed.

Or put another way,how can i make sure glide apply RGB_565 config when decoded a bitmap which has no alpha channel?

The reason why I want to use RGB_565 is for memory consider. RGB_565 use less memory compared to ARGB_8888.

Code

MainActivity

public class MainActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ImageView imageView = findViewById(R.id.image);
        loadImage(imageView, "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3363295869,2467511306&fm=26&gp=0.jpg");
    }

    private void loadImage(ImageView imageView, String url) {
        Glide.with(imageView)
                .load(url)
                .transform(UnitTransformation.get()) // for skip centerCrop transformation
                .set(Downsampler.DECODE_FORMAT, DecodeFormat.PREFER_RGB_565)
                .listener(new RequestListener<Drawable>() {
                    @Override
                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                        return false;
                    }

                    @Override
                    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                        Bitmap bitmap = ((BitmapDrawable) resource).getBitmap();
                        Log.d("diagnosis", String.format("config: %s hasAlpha: %b bytePerPixel: %d",
                                bitmap.getConfig().name(),
                                bitmap.hasAlpha(),
                                bitmap.getAllocationByteCount() / bitmap.getHeight() / bitmap.getWidth()));
                        return false;
                    }
                })
                .into(imageView);
    }
}

Layout XML

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:scaleType="centerCrop"
        android:layout_width="300dp"
        android:layout_height="300dp" />
</FrameLayout>

With code list above,issue can be reproduce.

Stack trace / LogCat

LruBitmapPool: Missing bitmap=[4320000](ARGB_8888)
LruBitmapPool: Get bitmap=[4320000](ARGB_8888)
LruBitmapPool: Hits=0, misses=1, puts=0, evictions=0, currentSize=0, maxSize=9551520
    Strategy=SizeConfigStrategy{groupedMap=GroupedLinkedMap( {[4320000](ARGB_8888):0} ), sortedSizes=(null[{}], ARGB_8888[{}], RGBA_F16[{}])}
diagnosis: config: RGB_565 hasAlpha: false bytePerPixel: 4

As log message show,the final bitmap display by imageView's config is has no alpha channel, config is RGB_565. RGB_565 should 2 byte per pixel,but this bitmap is as big as ARGB_8888 actually.

Analysis

And then i dig in glide's source code,find some point:

  • Bitmap is decoded by Downsampler.Before a real decoded action,Downsampler call getDimensions to get bitmap's width and height,and then call calculateConfig to fill options.inPreferCofig filed,and then call setInBitmap to fill options.inBitmap

  • When call getDimensions,options.inPreferCofig is null

    private static int[] getDimensions(
        ImageReader imageReader,
        BitmapFactory.Options options,
        DecodeCallbacks decodeCallbacks,
        BitmapPool bitmapPool)
        throws IOException {
      options.inJustDecodeBounds = true;
      decodeStream(imageReader, options, decodeCallbacks, bitmapPool);
      options.inJustDecodeBounds = false;
      return new int[] {options.outWidth, options.outHeight};
    }
    

    Options.outConfig is ARGB_8888 when this method return. This is because of JNI code default behavior.

  • When call setInBitmap

      @TargetApi(Build.VERSION_CODES.O)
      private static void setInBitmap(
          BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
        @Nullable Bitmap.Config expectedConfig = null;
        // Avoid short circuiting, it appears to break on some devices.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
          if (options.inPreferredConfig == Config.HARDWARE) {
            return;
          }
          // On API 26 outConfig may be null for some images even if the image is valid, can be decoded
          // and outWidth/outHeight/outColorSpace are populated (see b/71513049).
          expectedConfig = options.outConfig;
        }
    
        if (expectedConfig == null) {
          // We're going to guess that BitmapFactory will return us the config we're requesting. This
          // isn't always the case, even though our guesses tend to be conservative and prefer configs
          // of larger sizes so that the Bitmap will fit our image anyway. If we're wrong here and the
          // config we choose is too small, our initial decode will fail, but we will retry with no
          // inBitmap which will succeed so if we're wrong here, we're less efficient but still correct.
          expectedConfig = options.inPreferredConfig;
        }
        // BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
        options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
      }
    

    expectedConfig is ARGB_8888 assign from options.outConfig. So there whill create a ARGB_8888 bitmap when decode a non-alpha bitmap.

There is my questions

  • Why glide use ARGB_8888 bitmap as inBitmap when decode a bitmap which is known should decode to RGB_565. Is this a bug? Or a feature for reuse consideration?
  • It seem's set(Downsampler.DECODE_FORMAT, DecodeFormat.PREFER_xxxx) does't effect the actual behavior of Downampler at all. Is this api totally useless?

runInFuture avatar Feb 01 '21 07:02 runInFuture

@sjudd any reply is appreciated

runInFuture avatar Feb 22 '21 12:02 runInFuture

I meet this issue too. I guess maybe it is caused by bitmap reuse issue.

kyze8439690 avatar Aug 22 '22 11:08 kyze8439690

Just wanted to add my +1 that this issue exists and makes it hard to optimize an app for low-memory scenarios, given that bitmaps are commonly one of the largest, if not the largest source of memory usage in apps. @sjudd

AlexanderGH avatar Sep 28 '22 13:09 AlexanderGH

+1

Highly appreciate if someone can refer to the duplicate links or reference related to this problem

mwshubham avatar Feb 16 '23 14:02 mwshubham

+1

ErgooLee avatar Feb 26 '24 02:02 ErgooLee