BitmapFactory.decodeStream возвращает значение null при установке параметров

90

У меня проблемы с BitmapFactory.decodeStream(inputStream). При использовании без параметров он вернет изображение. Но когда я использую его с параметрами, .decodeStream(inputStream, null, options)он никогда не возвращает растровые изображения.

Что я пытаюсь сделать, так это уменьшить разрешение растрового изображения перед его загрузкой для экономии памяти. Я читал несколько хороших руководств, но ни один из них не использовал .decodeStream.

РАБОТАЕТ ПРОСТО ОТЛИЧНО

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

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

НЕ РАБОТАЕТ

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);
Роберт Фосс
источник
1
Что дает ваш оператор System.out.println ("Samplesize:" ...)? Указывает, что options.inSampleSize является приемлемым значением?
Стив Хейли,
Да, каждый раз возвращает приемлемое значение.
Роберт Фосс,
Оператор удален из-за отладки.
Роберт Фосс,
1
Спасибо, что опубликовали свое решение, но есть еще кое-что, что нужно сделать. Этот вопрос по-прежнему отображается в списках «нерешенных вопросов», поскольку вы не отметили ответ как «принятый». Вы можете сделать это, щелкнув значок галочки рядом с ответом. Вы можете принять ответ Самуха, если считаете, что он помог вам найти решение, или вы можете опубликовать свой ответ и принять его. (Обычно вы включаете свое решение в свой ответ, но поскольку вы уже включили его, отредактировав свой вопрос, вы можете просто направить их к вопросу.)
Стив Хейли,
Спасибо за помощь новому пользователю в интеграции в сообщество :)
Роберт Фосс

Ответы:

114

Проблема заключалась в том, что после того, как вы использовали InputStream из HttpUrlConnection для получения метаданных изображения, вы не можете перемотать назад и снова использовать тот же InputStream.

Поэтому вам необходимо создать новый InputStream для фактической выборки изображения.

  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();
Роберт Фосс
источник
17
Означает ли это, что изображение нужно скачивать дважды? Один раз для получения размера и один раз для получения данных о пикселях?
user123321
1
@Robert, вам, вероятно, следует объяснить это конкретное поведение, чтобы другие пользователи получили четкое представление об этом
Мухаммад Бабар
1
Мне было интересно, почему он сам не работает с тем же
входным потоком
1
вам не нужно воссоздавать его, просто сбросив его, можно решить задачу. Смотрите мой ответ
Шашанк Томар
5
Я должен сказать, что класс Bitmap в Android - отстой. Это так запутывает и разочаровывает в использовании.
Neon Warge
30

Попробуйте обернуть InputStream с помощью BufferedInputStream.

InputStream is = new BufferedInputStream(conn.getInputStream());
is.mark(is.available());
// Do the bound decoding
// inJustDecodeBounds =true
is.reset();  
// Do the actual decoding
Джетт Хси
источник
2
у тебя это всегда работало? по какой-то причине я получаю null в некоторых очень конкретных случаях, используя этот метод. Я написал об этом сообщение здесь: stackoverflow.com/questions/17774442/…
разработчик Android
1
он работал, поэтому я проголосовал за него, но документ is.available () содержит предупреждение, что его следует использовать только для проверки, пуст ли поток или нет, а не для расчета размера, поскольку это ненадежно.
Abhishek Chauhan
1
проголосовали против, но рассматриваемое соединение входящего потока - это HTTP-соединение, и reset () не будет работать ....
Джонни Ву
3

Я думаю, что проблема в логике «вычислить масштабный коэффициент», потому что остальная часть кода мне кажется правильной (при условии, конечно, что входной поток не равен нулю).

Было бы лучше, если бы вы могли вынести всю логику расчета размера из этой процедуры в метод (назовите его calculateScaleFactor () или что-то еще) и сначала независимо протестировать этот метод.

Что-то вроде:

// 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);

и протестируйте getScaleFactor (...) независимо.

Это также поможет окружить весь код блоком try..catch {}, если это еще не сделано.

Самух
источник
Большое спасибо за ответ! Я попытался установить окончательное значение int, например options.inSampleSize = 2. Но это приводит к тем же проблемам. Logcat читает «SkImageDecoder :: Factory вернул null» для каждого изображения, которое я пытался декодировать. Запуск кода внутри блока try / catch мне не поможет, так как он ничего не бросает, верно? Однако BitmapFactory.decodeStream возвращает значение null, если не может создать img, чего не может быть, когда я пытаюсь использовать sampleSize.
Роберт Фосс,
Это странно. Можете ли вы попробовать изменить размер растрового изображения, включенного в ваш ресурс? Например, откройте файл ресурсов и попробуйте его декодировать. Если вы можете это сделать, возможно, возникла проблема с удаленным потоком, из-за которой декодирование не удалось.
Samuh
BitmapFactory.decodeResource (this.getResources (), R.drawable.icon, options) == null) отлично работает с повторной выборкой. Первый BitmapFactory.decodeStream с options.inJustDecodeBounds = true работает и отлично возвращает параметры. Но следующий BitmapFactory.decodeStream с options.inJustDecodeBounds = false каждый раз терпит неудачу.
Роберт Фосс,
Боюсь, это выше меня ... Мне было бы интересно узнать, что здесь может пойти не так, потому что я использую аналогичный код, и он отлично работает для меня.
Samuh
4
Хорошо. Я решил это. Проблема заключается в http-соединении. Когда вы прочитали из входного потока, предоставленного HttpUrlConnection один раз, вы не можете читать из него снова, и вам придется повторно подключиться, чтобы выполнить второй decodeStream ().
Роберт Фосс,
2

Вы можете преобразовать InputStream в массив байтов и использовать decodeByteArray (). Например,

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;
}
Джимми Сан
источник