96

I'm having issues with BitmapFactory.decodeStream(inputStream). When using it without options, it will return an image. But when I use it with options as in .decodeStream(inputStream, null, options) it never returns Bitmaps.

What I'm trying to do is to downsample a Bitmap before I actually load it to save memory. I've read some good guides, but none using .decodeStream.

WORKS JUST FINE

URL url = new URL(sUrl);
HttpURLConnection connection  = (HttpURLConnection) url.openConnection();

InputStream is = connection.getInputStream();
Bitmap img = BitmapFactory.decodeStream(is, null, options);

DOESN'T WORK

InputStream is = connection.getInputStream();
Bitmap img = BitmapFactory.decodeStream(is, null, options);

InputStream is = connection.getInputStream();

Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;

BitmapFactory.decodeStream(is, null, options);

Boolean scaleByHeight = Math.abs(options.outHeight - TARGET_HEIGHT) >= Math.abs(options.outWidth - TARGET_WIDTH);

if (options.outHeight * options.outWidth * 2 >= 200*100*2){
    // Load, scaling to smallest power of 2 that'll get it <= desired dimensions
    double sampleSize = scaleByHeight
    ? options.outHeight / TARGET_HEIGHT
    : options.outWidth / TARGET_WIDTH;
    options.inSampleSize =
        (int)Math.pow(2d, Math.floor(
        Math.log(sampleSize)/Math.log(2d)));
}

// Do the actual decoding
options.inJustDecodeBounds = false;
Bitmap img = BitmapFactory.decodeStream(is, null, options);
anticafe
  • 6,816
  • 9
  • 43
  • 74
Robert Foss
  • 2,497
  • 4
  • 19
  • 18
  • 1
    What's the output from your System.out.println("Samplesize: " ...) statement? Is indicating that options.inSampleSize is an acceptable value? – Steve Haley Mar 23 '10 at 22:16
  • Yes, it returns an acceptable value every time. – Robert Foss Mar 24 '10 at 15:25
  • Removed the statement due to it being debug. – Robert Foss Mar 24 '10 at 15:26
  • 1
    Thanks for posting your solution, but there's just one more thing to do. This question still appears in the "unsolved questions" lists because you haven't marked a response as "accepted". You can do that by clicking the tickmark icon next to an answer. You could accept Samuh's answer if you feel it helped you find the solution, or you could post an answer of your own and accept it. (Normally you'd put your solution in your answer, but since you've already included that by editing your question, you could just refer them to the question.) – Steve Haley Mar 24 '10 at 20:48
  • Thanks for helping a new user to integrate into the community :) – Robert Foss Mar 28 '10 at 16:17

4 Answers4

115

The problem was that once you've used an InputStream from a HttpUrlConnection to fetch image metadata, you can't rewind and use the same InputStream again.

Therefore you have to create a new InputStream for the actual sampling of the image.

  Options options = new BitmapFactory.Options();
  options.inJustDecodeBounds = true;

  BitmapFactory.decodeStream(is, null, options);

  Boolean scaleByHeight = Math.abs(options.outHeight - TARGET_HEIGHT) >= Math.abs(options.outWidth - TARGET_WIDTH);

  if(options.outHeight * options.outWidth * 2 >= 200*200*2){
         // Load, scaling to smallest power of 2 that'll get it <= desired dimensions
        double sampleSize = scaleByHeight
              ? options.outHeight / TARGET_HEIGHT
              : options.outWidth / TARGET_WIDTH;
        options.inSampleSize = 
              (int)Math.pow(2d, Math.floor(
              Math.log(sampleSize)/Math.log(2d)));
     }

        // Do the actual decoding
        options.inJustDecodeBounds = false;

        is.close();
        is = getHTTPConnectionInputStream(sUrl);
        Bitmap img = BitmapFactory.decodeStream(is, null, options);
        is.close();
Robert Foss
  • 2,497
  • 4
  • 19
  • 18
31

Try wrap InputStream with BufferedInputStream.

InputStream is = new BufferedInputStream(conn.getInputStream());
is.mark(is.available());
// Do the bound decoding
// inJustDecodeBounds =true
is.reset();  
// Do the actual decoding
Jett Hsieh
  • 3,159
  • 27
  • 33
  • 2
    did it always work for you? for some reason, i get null on some very specific cases using this method. i've written a post about it here: http://stackoverflow.com/questions/17774442/how-to-get-bitmap-information-and-then-decode-bitmap-from-internet-inputstream – android developer Jul 21 '13 at 20:56
  • 1
    it worked so i upvoted it but is.available() doc comes with warning that it should be only used to check if stream is empty or not and not for calculating size as this is unreliable . – Abhishek Chauhan Jun 29 '14 at 15:17
  • 1
    down-voted, but the inputstream connection in question is a HTTP connection and reset() won't work.... – Johnny Wu Apr 21 '16 at 02:07
3

I think the problem is with the "calculate-scale-factor" logic because rest of the code looks correct to me (assuming of course that inputstream is not null).

It would be better if you can factor out all the size calculation logic from this routine into a method(call it calculateScaleFactor() or whatever) and test that method independently first.

Something like:

// Get the stream 
InputStream is = mUrl.openStream();

// get the Image bounds
BitmapFactory.Options options=new BitmapFactory.Options(); 
options.inJustDecodeBounds = true;

bitmap = BitmapFactory.decodeStream(is,null,options);

//get actual width x height of the image and calculate the scale factor
options.inSampleSize = getScaleFactor(options.outWidth,options.outHeight,
                view.getWidth(),view.getHeight());

options.inJustDecodeBounds = false;
bitmap=BitmapFactory.decodeStream(mUrl.openStream(),null,options);

and test getScaleFactor(...) independently.

It will also help to surround the entire code with try..catch{} block, if not done already.

Samuh
  • 36,316
  • 26
  • 109
  • 116
  • Thanks alot for the answer! I tried setting a final int value like 'options.inSampleSize = 2'. But it results in the same issues. Logcat reads 'SkImageDecoder::Factory returned null', for every image that i tried to decode. Running the code inside a try/catch block wouldn't help me as it isn't throwing anything, right? However BitmapFactory.decodeStream does return null if it can't create an img, which it cant when i try to use a sampleSize. – Robert Foss Mar 24 '10 at 13:48
  • This is strange. Can you try to resize some Bitmap bundled in your resource? Like open a resource file and try to decode it. If you can do that maybe there is some problem with the remote stream which is causing the decode to fail. – Samuh Mar 24 '10 at 14:15
  • BitmapFactory.decodeResource(this.getResources(), R.drawable.icon, options) == null) works fine with re-sampling. The first BitmapFactory.decodeStream with options.inJustDecodeBounds = true works and returns options just fine. But the following BitmapFactory.decodeStream with options.inJustDecodeBounds = false fails every time. – Robert Foss Mar 24 '10 at 14:52
  • I am afraid this is beyond me ... I would be interested to know what could possibly go wrong here because I am using similar code and it works just fine for me. – Samuh Mar 24 '10 at 14:56
  • 4
    Ok. I've solved it. The issue lies in the http-connection. When you've read from the input-stream provided by the HttpUrlConnection once, you can't read from it again, and have to reconnect to do the second decodeStream(). – Robert Foss Mar 24 '10 at 15:06
  • btw, I just edited the issue to reflect that i use a HttpUrlConnection. I should've posted that in the issue. Sorry about that. And thanks alot for your help! Greets from Sweden. – Robert Foss Mar 24 '10 at 15:07
  • Glad that you found an answer; thank you for sharing your findings... cheers! – Samuh Mar 25 '10 at 03:22
3

You can convert the InputStream to a byte array, and use the decodeByteArray(). For example,

public static Bitmap decodeSampledBitmapFromStream(InputStream inputStream, int reqWidth, int reqHeight) {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try {
        int n;
        byte[] buffer = new byte[1024];
        while ((n = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, n);
        }
        return decodeSampledBitmapFromByteArray(outputStream.toByteArray(), reqWidth, reqHeight);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}

public static Bitmap decodeSampledBitmapFromByteArray(byte[] data, int reqWidth, int reqHeight) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeByteArray(data, 0, data.length, options);
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}

private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int
        reqHeight) {
    int width = options.outWidth;
    int height = options.outHeight;
    int inSampleSize = 1;
    if (width > reqWidth || height > reqHeight) {
        int halfWidth = width / 2;
        int halfHeight = height / 2;
        while (halfWidth / inSampleSize >= reqWidth && halfHeight / inSampleSize >= reqHeight) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}
Jimmy Sun
  • 31
  • 3