Как получить растровую информацию, а затем декодировать растровое изображение из интернет-inputStream?

фон

Предположим, у меня есть inputStream, который был создан из Интернета определенного файла изображения.

Я хочу получить информацию о файле изображения и только потом расшифровать его.

это полезно для нескольких целей, таких как понижающая дискретизация, а также предварительный просмотр информации перед показом изображения.

эта проблема

я попытался пометить и сбросить inputStream, обернув inputStream с BufferedInputStream, но это не сработало:

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

для получения inputStream из URL, я использую:

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;
  }

вопрос

Как я могу сделать код обрабатывать маркировку сброса?

он отлично работает с ресурсами (на самом деле мне даже не пришлось создавать новый BufferedInputStream для этого, чтобы работать), но не с inputStream из Интернета...


РЕДАКТИРОВАТЬ:

кажется, мой код просто отлично, вроде...

на некоторых веб-сайтах (таких как этот и этот) не удается декодировать файл изображения даже после сброса.

если вы декодируете растровое изображение (и используете inSampleSize), он может прекрасно его декодировать (это занимает много времени).

теперь вопрос в том, почему это происходит, и как я могу это исправить.

4 ответа

Я считаю, что проблема заключается в том, что вызов mark () с большим значением перезаписывается вызовом mark(1024). Как описано в документации:

До KITKAT, если is.markSupported() возвращает true, вызывается is.mark(1024). Что касается KITKAT, это уже не так.

Это может привести к ошибке reset (), если выполняется чтение больше, чем это значение.

(Вот решение той же проблемы, но при чтении с диска. Сначала я не осознавал, что ваш вопрос был задан именно из сетевого потока.)

Проблема с mark & ​​reset в общем случае заключается в том, что BitmapFactory.decodeStream() иногда сбрасывает ваши оценки. Таким образом, сброс для того, чтобы сделать фактическое чтение, нарушен.

Но есть и вторая проблема с BufferedInputStream: это может привести к тому, что все изображение будет помещено в буфер вместе с тем местом, где вы действительно его читаете. В зависимости от вашего варианта использования, это действительно может снизить производительность. (Много распределения означает много GC)

Здесь есть действительно отличное решение: /questions/43381717/vvod-java-fajla-s-vozmozhnostyu-peremotki-sbrosa/43381732#43381732

Я немного изменил его для этого конкретного случая использования, чтобы решить проблему с меткой и сбросом:

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;
        }
    }
}

Это не выделит никакой дополнительной памяти во время чтения и может быть сброшено, даже если метки были очищены.

Вот простой метод, который всегда работает для меня:)

 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;
}

Можно ли пометить / сбросить поток, зависит от реализации потока. это необязательные операции, которые обычно не поддерживаются. Вы можете прочитать поток в буфер и затем прочитать его 2 раза, или просто установить сетевое соединение 2 раза.

проще всего, наверное, написать в ByteArrayOutputStream,

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

Теперь либо использовать результат baos.toByteArray() напрямую или создать ByteArrayInputStream и использовать это неоднократно, вызывая reset() после употребления его каждый раз.

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

это может звучать глупо, но в этом нет магии. Вы либо буферизуете данные в памяти, либо читаете их 2 раза из источника. если бы поток поддерживал mark / reset, он должен был сделать то же самое в своей реализации.

Другие вопросы по тегам