BitmapFactory.decodeStream from Assets returns null on Android 7
Asked Answered
E

3

7

How to decode bitmaps from Asset directory in Android 7?

My App is running well on Android versions up to Marshmallow. With Android 7 it fails to load images from the Asset directory.

My Code:

private Bitmap getImage(String imagename) {
    // Log.dd(logger, "AsyncImageLoader: " + ORDNER_IMAGES + imagename);

    AssetManager asset = context.getAssets();
    InputStream is = null;
    try {
        is = asset.open(ORDNER_IMAGES + imagename);
    } catch (IOException e) {
        // Log.de(logger, "image konnte nicht gelesen werden: " + ORDNER_IMAGES + imagename);
        return null;
    }


    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(is, null, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, PW, PH);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;

    // Lesen des Bitmaps in der optimierten Groesse
    return BitmapFactory.decodeStream(is, null, options);

}

As a result (only Android 7) BitmapFactory.decodeStream is null. It works correctly an older Android APIs.

In debug mode I see the following Message:

09-04 10:10:50.384 6274-6610/myapp D/skia: --- SkAndroidCodec::NewFromStream returned null

Can someone tell me the reason and how to correct the coding?

Edit: Meanwhile i found, that removing of the first BitmapFactory.decodeStream with inJustDecodeBounds=true leads to a successful BitmapFactory.decodeStream afterwards with inJustDecodeBounds=false. Don't know the reason and don't know how to substitute the measurement of bitmap size.

Ecclesiasticus answered 4/9, 2016 at 10:42 Comment(1)
My guess is that your problem is particular to your asset. I just tested one of my book samples that loads images from assets, on a Nexus 9 running Android 7.0. It seems to work fine.Paregmenon
M
11

I think we are in the same boat. My team stuck in this problem for a while like you.

It seems be a problem in BitmapFactory.cpp (https://android.googlesource.com/platform/frameworks/base.git/+/master/core/jni/android/graphics/BitmapFactory.cpp) Some code was added in Android 7.0 and made the problem occurred.

// Create the codec.
NinePatchPeeker peeker;
std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(), &peeker));
if (!codec.get()) {
    return nullObjectReturn("SkAndroidCodec::NewFromStream returned null");
}

And I found out the BitmapFactory.decodeStream method didn't create the bitmap after we set inJustDecodeBounds=false but when I try to create bitmap without bound decoding. It's works! The problem is about BitmapOptions in that InputStream doesn't updated when we called BitmapFactory.decodeStream again.

So I reset that InputStream before decode again

private Bitmap getBitmapFromAssets(Context context, String fileName, int width, int height) {
    AssetManager asset = context.getAssets();
    InputStream is;
    try {
        is = asset.open(fileName);
    } catch (IOException e) {
        return null;
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(is, null, options);
    try {
        is.reset();
    } catch (IOException e) {
        return null;
    }
    options.inSampleSize = calculateInSampleSize(options, width, height);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeStream(is, null, options);
}

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        final int halfHeight = height / 2;
        final int halfWidth = width / 2;
        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

It's looks like we have to reset InputStream every time before reuse it.

Marcellusmarcelo answered 19/1, 2017 at 23:38 Comment(6)
This did the trick. Just resetting the InputStream. Thank you for your help.Ecclesiasticus
I tried this trick, but I keep getting "mark/reset not supported" IOException when calling rest. But I use FileInputStream, cause I try to load bitmap from SD card.Langston
Is there any other way but FileInputStream to open file from storage?Langston
Thanks, it helped me. Android 7, file was used from resources (png image). Adding inputStream.reset() is safe to use in previous versions so add it if you're re-using the same inputstream.Skirt
Android 7.1.2 and it's not working. To make it work i have to change reset() for close() and than create new stream before next decodeStream()Capitalization
Had the same problem with Android 5.1, Thanks to you it solved +1.Declinometer
M
1

I suspect the android default security config is not letting you download a file using protocol "http://" (no encryption).

Try finding an image or file with an "https://" protocol. See if that causes your file to load. Otherwise, set a Network Security Config that allows "http".

Mackle answered 5/1, 2019 at 6:30 Comment(0)
S
0

In case this helps anyone, I was bumping up against a similar issue updating older code that had previously worked for resizing images. My issue was further up the stack where I was reading data from the image file. I made use of IOUtils.toByteArray(Reader), which has been deprecated. I switched to converting to a byte array directly from the URI and now it is working well. See the first two lines of resizeImage() below for the example of that new method (The rest of the code allows me to resize the image.)

public static Bitmap resizeImage(Uri imageUri, int targetWidth, int targetHeight) {
    // Convert the image to a byte array
    java.net.URI tempUri = new URI(uri.toString());
    byte[] imageData = IOUtils.toByteArray(tempUri);

    // First decode with inJustDecodeBounds=true to check dimensions
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    Bitmap reducedBitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options);
    Bitmap resizedBitmap = Bitmap.createScaledBitmap(reducedBitmap, targetWidth, targetHeight, false);

    return resizedBitmap;
}

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a 
        // power of 2 and keeps both height and width larger
        // than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}
Superheterodyne answered 28/10, 2016 at 16:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.