Home » Android » android – how to get bitmap information and then decode bitmap from internet-inputStream?

android – how to get bitmap information and then decode bitmap from internet-inputStream?

Posted by: admin June 15, 2020 Leave a comment

Questions:

background

suppose i have an inputStream that was originated from the internet of a certain image file.

i wish to get information about the image file and only then to decode it.

it’s useful for multiple purposes, such as downsampling and also previewing of information before the image is shown.

the problem

i’ve tried to mark&reset the inputStream by wrapping the inputStream with a BufferedInputStream , but it didn’t work:

inputStream=new BufferedInputStream(inputStream);
inputStream.mark(Integer.MAX_VALUE);
final BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeStream(inputStream,null,options);
//this works fine. i get the options filled just right.

inputStream.reset();
final Bitmap bitmap=BitmapFactory.decodeStream(inputStream,null,options);
//this returns null

for getting the inputStream out of a url, i use:

public static InputStream getInputStreamFromInternet(final String urlString)
  {
  try
    {
    final URL url=new URL(urlString);
    final HttpURLConnection urlConnection=(HttpURLConnection)url.openConnection();
    final InputStream in=urlConnection.getInputStream();
    return in;
    }
  catch(final Exception e)
    {
    e.printStackTrace();
    }
  return null;
  }

the question

how can i make the code handle the marking an resetting ?

it works perfectly with resources (in fact i didn’t even have to create a new BufferedInputStream for this to work) but not with inputStream from the internet…


EDIT:

it seems my code is just fine, sort of…

on some websites (like this one and this one), it fails to decode the image file even after reseting.

if you decode the bitmap (and use inSampleSize) , it can decode it fine (just takes a long time).

now the question is why it happens, and how can i fix it.

How to&Answers:

I believe the problem is that the call to mark() with the large value is overwritten by a call to mark(1024). As described in the documentation:

Prior to KITKAT, if is.markSupported() returns true, is.mark(1024) would be called. As of KITKAT, this is no longer the case.

This may be resulting in a reset() fail if reads larger than this value are being done.

Answer:

(Here is a solution for the same problem, but when reading from disk. I didn’t realize at first your question was specifically from a network stream.)

The problem with mark & reset in general here is that BitmapFactory.decodeStream() sometimes resets your marks. Thus resetting in order to do the actual read is broken.

But there is a second problem with BufferedInputStream: it can cause the entire image to be buffered in memory along side of where ever you are actually reading it into. Depending on your use case, this can really kill your performance. (Lots of allocation means lots of GC)

There is a really great solution here:
https://stackoverflow.com/a/18665678/1366

I modified it slightly for this particular use case to solve the mark & reset problem:

public class MarkableFileInputStream extends FilterInputStream
{
    private static final String TAG = MarkableFileInputStream.class.getSimpleName();

    private FileChannel m_fileChannel;
    private long m_mark = -1;

    public MarkableFileInputStream( FileInputStream fis )
    {
        super( fis );
        m_fileChannel = fis.getChannel();
    }

    @Override
    public boolean markSupported()
    {
        return true;
    }

    @Override
    public synchronized void mark( int readlimit )
    {
        try
        {
            m_mark = m_fileChannel.position();
        }
        catch( IOException ex )
        {
            Log.d( TAG, "Mark failed" );
            m_mark = -1;
        }
    }

    @Override
    public synchronized void reset() throws IOException
    {
        // Reset to beginning if mark has not been called or was reset
        // This is a little bit of custom functionality to solve problems
        // specific to Android's Bitmap decoding, and is slightly non-standard behavior
        if( m_mark == -1 )
        {
            m_fileChannel.position( 0 );
        }
        else
        {
            m_fileChannel.position( m_mark );
            m_mark = -1;
        }
    }
}

This won’t allocate any extra memory during reads, and can be reset even if the marks have been cleared.

Answer:

whether you can mark / reset a stream depends on the implementation of the stream. those are optional operations and aren’t typically supported. your options are to read the stream into a buffer and then read from that stream 2x, or just make the network connection 2x.

the easiest thing is probably to write into a ByteArrayOutputStream,

ByteArrayOutputStream baos = new ByteArrayOutputStream();
int count;
byte[] b = new byte[...];
while ((count = input.read(b) != -1) [
  baos.write(b, 0, count);
}

now either use the result of baos.toByteArray() directly, or create a ByteArrayInputStream and use that repeatedly, calling reset() after consuming it each time.

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

that might sound silly, but there’s no magic. you either buffer the data in memory, or you read it 2x from the source. if the stream did support mark / reset, it’d have to do the same thing in it’s implementation.

Answer:

Here is a simple method that always works for me 🙂

 private Bitmap downloadBitmap(String url) {
    // initilize the default HTTP client object
    final DefaultHttpClient client = new DefaultHttpClient();

    //forming a HttoGet request
    final HttpGet getRequest = new HttpGet(url);
    try {

        HttpResponse response = client.execute(getRequest);

        //check 200 OK for success
        final int statusCode = response.getStatusLine().getStatusCode();

        if (statusCode != HttpStatus.SC_OK) {
            Log.w("ImageDownloader", "Error " + statusCode +
                    " while retrieving bitmap from " + url);
            return null;

        }

        final HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream inputStream = null;
            try {
                // getting contents from the stream
                inputStream = entity.getContent();

                // decoding stream data back into image Bitmap that android understands
                image = BitmapFactory.decodeStream(inputStream);


            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }
                entity.consumeContent();
            }
        }
    } catch (Exception e) {
        // You Could provide a more explicit error message for IOException
        getRequest.abort();
        Log.e("ImageDownloader", "Something went wrong while" +
                " retrieving bitmap from " + url + e.toString());
    }

    return image;
}