Изменение размера большого растрового файла до масштабированного выходного файла на Android - PullRequest
208 голосов
/ 26 июля 2010

У меня есть большое растровое изображение (скажем, 3888x2592) в файле.Теперь я хочу изменить размер этого растрового изображения до 800x533 и сохранить его в другом файле.Обычно я масштабирую растровое изображение, вызывая метод Bitmap.createBitmap, но для него требуется исходное растровое изображение в качестве первого аргумента, который я не могу предоставить, поскольку загрузка исходного изображения в объект растрового изображения, конечно, будет превышать объем памяти (см. здесь, например).

Я также не могу прочитать растровое изображение, например, с BitmapFactory.decodeFile(file, options), предоставив BitmapFactory.Options.inSampleSize, потому что я хочу изменить его размеры до точных ширины и высоты.Использование inSampleSize изменило бы размер растрового изображения до 972x648 (если я использую inSampleSize=4) или до 778x518 (если я использую inSampleSize=5, что даже не равно степени 2).

Я также хотел бычтобы избежать чтения изображения, используя inSampleSize, например, с 972x648 на первом этапе, а затем изменив его размер точно до 800x533 на втором этапе, поскольку качество будет плохим по сравнению с прямым изменением размера исходного изображения.

Подводя итог моему вопросу: есть ли способ прочитать большой файл изображения с разрешением 10 Мп или более и сохранить его в новом файле изображения с изменением размера до определенной новой ширины и высоты, без получения исключения OutOfMemory?

Я также попытался BitmapFactory.decodeFile(file, options) и вручную установил значения Options.outHeight и Options.outWidth равными 800 и 533, но это не сработало.

Ответы [ 21 ]

2 голосов
/ 30 ноября 2011

Я использовал такой код:

  String filePath=Environment.getExternalStorageDirectory()+"/test_image.jpg";
  BitmapFactory.Options options=new BitmapFactory.Options();
  InputStream is=new FileInputStream(filePath);
  BitmapFactory.decodeStream(is, null, options);
  is.close();
  is=new FileInputStream(filePath);
  // here w and h are the desired width and height
  options.inSampleSize=Math.max(options.outWidth/460, options.outHeight/288); //Max 460 x 288 is my desired...
  // bmp is the resized bitmap
  Bitmap bmp=BitmapFactory.decodeStream(is, null, options);
  is.close();
  Log.d(Constants.TAG, "Scaled bitmap bytes, "+bmp.getRowBytes()+", width:"+bmp.getWidth()+", height:"+bmp.getHeight());

Я пробовал исходное изображение 1230 x 1230, и полученное растровое изображение говорит о 330 x 330.
А если попробуете 2590 x 3849, я получу ошибку OutOfMemoryError.

Я проследил это, он по-прежнему генерирует OutOfMemoryError в строке "BitmapFactory.decodeStream (is, null, options);", если исходное растровое изображение слишком большое ...

2 голосов
/ 23 сентября 2010

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

  1. Узнайте размер измененного изображения с помощью вызоваBitmapFactory.decodeFile и предоставление checkSizeOptions.inJustDecodeBounds
  2. Рассчитайте максимум возможных значений inSampleSize, которые вы можете использовать на своем устройстве, чтобы не превышать объем памяти.bitmapSizeInBytes = 2 * ширина * высота;Как правило, для вашей картинки отлично подходит inSampleSize = 2, так как вам потребуется только 2 * 1944x1296) = 4,8 МБ, что должно занимать места в памяти
  3. Используйте BitmapFactory.decodeFile с inSampleSize для загрузки растрового изображения
  4. Scaleточечный рисунок до точного размера.

Мотивация: многошаговое масштабирование может обеспечить более высокое качество изображения, однако нет гарантии, что оно будет работать лучше, чем использование высокого inSampleSize.На самом деле, я думаю, что вы также можете использовать inSampleSize как 5 (не pow из 2), чтобы иметь прямое масштабирование в одной операции.Или просто используйте 4, а затем вы можете просто использовать это изображение в пользовательском интерфейсе.если вы отправляете его на сервер - чем вы можете выполнить масштабирование до точного размера на стороне сервера, что позволит вам использовать расширенные методы масштабирования.

Примечания: если растровое изображение, загруженное на шаге 3, как минимум, в 4 раза больше (так что 4 * targetWidth http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html

1 голос
/ 23 мая 2013

Если вы абсолютно хотите сделать один шаг изменения размера, вы можете загрузить весь растровый файл, если android: largeHeap = true, но, как вы можете видеть, это не очень рекомендуется.

Из документов: андроид: largeHeap Должны ли процессы вашего приложения создаваться с большой кучей Dalvik. Это относится ко всем процессам, созданным для приложения. Это относится только к первому приложению, загруженному в процесс; если вы используете общий идентификатор пользователя, чтобы разрешить нескольким приложениям использовать процесс, все они должны использовать эту опцию последовательно, иначе они будут иметь непредсказуемые результаты. Большинству приложений это не нужно, и вместо этого следует сосредоточиться на снижении общего использования памяти для повышения производительности. Включение этого также не гарантирует фиксированного увеличения доступной памяти, поскольку некоторые устройства ограничены их общей доступной памятью.

1 голос
/ 04 августа 2012

Чтобы масштабировать изображение «правильным» способом, не пропуская ни одного пикселя, вам нужно подключиться к декодеру изображения, чтобы выполнить пост-дискретизацию построчно. Android (и лежащая в его основе библиотека Skia) не предоставляет таких хуков, поэтому вам придется свернуть свои собственные. Предполагая, что вы говорите с изображениями в формате jpeg, вам лучше всего использовать libjpeg напрямую, в C.

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

1 голос
/ 09 декабря 2012

Вот статья, которая использует другой подход к изменению размера.Он попытается загрузить максимально возможное растровое изображение в память на основе доступной памяти в процессе, а затем выполнить преобразования.

http://bricolsoftconsulting.com/2012/12/07/handling-large-images-on-android/

0 голосов
/ 06 августа 2014
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

или

 resized = Bitmap.createScaledBitmap(yourBitmap,(int)(yourBitmap.getWidth()*0.8), (int)(yourBitmap.getHeight()*0.8), true);
0 голосов
/ 29 июля 2013

Измените размер растрового изображения, используя следующий код

    public static Bitmap decodeFile(File file, int reqWidth, int reqHeight){

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;        
    BitmapFactory.decodeFile(file.getPath(), options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(file.getPath(), options);
   }

    private 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) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
     }

     return inSampleSize;
   }    

То же самое объясняется и в следующем совете / трюке

http://www.codeproject.com/Tips/625810/Android-Image-Operations-Using-BitmapFactory

0 голосов
/ 08 июля 2014

Вот код, который я использую, у которого нет проблем с декодированием больших изображений в памяти на Android. Я смог декодировать изображения размером более 20 МБ, если мои входные параметры около 1024x1024. Вы можете сохранить возвращенное растровое изображение в другой файл. Ниже этого метода есть еще один метод, который я также использую для масштабирования изображений до нового растрового изображения. Не стесняйтесь использовать этот код по своему усмотрению.

/*****************************************************************************
 * public decode - decode the image into a Bitmap
 * 
 * @param xyDimension
 *            - The max XY Dimension before the image is scaled down - XY =
 *            1080x1080 and Image = 2000x2000 image will be scaled down to a
 *            value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image - a value of "null" if there is an issue decoding
 *         image dimension
 * 
 * @throws FileNotFoundException
 *             - If the image has been removed while this operation is
 *             taking place
 */
public Bitmap decode( int xyDimension, Bitmap.Config bitmapConfig ) throws FileNotFoundException
{
    // The Bitmap to return given a Uri to a file
    Bitmap bitmap = null;
    File file = null;
    FileInputStream fis = null;
    InputStream in = null;

    // Try to decode the Uri
    try
    {
        // Initialize scale to no real scaling factor
        double scale = 1;

        // Get FileInputStream to get a FileDescriptor
        file = new File( this.imageUri.getPath() );

        fis = new FileInputStream( file );
        FileDescriptor fd = fis.getFD();

        // Get a BitmapFactory Options object
        BitmapFactory.Options o = new BitmapFactory.Options();

        // Decode only the image size
        o.inJustDecodeBounds = true;
        o.inPreferredConfig = bitmapConfig;

        // Decode to get Width & Height of image only
        BitmapFactory.decodeFileDescriptor( fd, null, o );
        BitmapFactory.decodeStream( null );

        if( o.outHeight > xyDimension || o.outWidth > xyDimension )
        {
            // Change the scale if the image is larger then desired image
            // max size
            scale = Math.pow( 2, (int) Math.round( Math.log( xyDimension / (double) Math.max( o.outHeight, o.outWidth ) ) / Math.log( 0.5 ) ) );
        }

        // Decode with inSampleSize scale will either be 1 or calculated value
        o.inJustDecodeBounds = false;
        o.inSampleSize = (int) scale;

        // Decode the Uri for real with the inSampleSize
        in = new BufferedInputStream( fis );
        bitmap = BitmapFactory.decodeStream( in, null, o );
    }
    catch( OutOfMemoryError e )
    {
        Log.e( DEBUG_TAG, "decode : OutOfMemoryError" );
        e.printStackTrace();
    }
    catch( NullPointerException e )
    {
        Log.e( DEBUG_TAG, "decode : NullPointerException" );
        e.printStackTrace();
    }
    catch( RuntimeException e )
    {
        Log.e( DEBUG_TAG, "decode : RuntimeException" );
        e.printStackTrace();
    }
    catch( FileNotFoundException e )
    {
        Log.e( DEBUG_TAG, "decode : FileNotFoundException" );
        e.printStackTrace();
    }
    catch( IOException e )
    {
        Log.e( DEBUG_TAG, "decode : IOException" );
        e.printStackTrace();
    }

    // Save memory
    file = null;
    fis = null;
    in = null;

    return bitmap;

} // decode

ПРИМЕЧАНИЕ. Методы не имеют ничего общего друг с другом, кроме createScaledBitmap, вызывающего метод decode выше. Примечание ширина и высота могут отличаться от исходного изображения.

/*****************************************************************************
 * public createScaledBitmap - Creates a new bitmap, scaled from an existing
 * bitmap.
 * 
 * @param dstWidth
 *            - Scale the width to this dimension
 * @param dstHeight
 *            - Scale the height to this dimension
 * @param xyDimension
 *            - The max XY Dimension before the original image is scaled
 *            down - XY = 1080x1080 and Image = 2000x2000 image will be
 *            scaled down to a value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image scaled - a value of "null" if there is an issue
 * 
 */
public Bitmap createScaledBitmap( int dstWidth, int dstHeight, int xyDimension, Bitmap.Config bitmapConfig )
{
    Bitmap scaledBitmap = null;

    try
    {
        Bitmap bitmap = this.decode( xyDimension, bitmapConfig );

        // Create an empty Bitmap which will contain the new scaled bitmap
        // This scaled bitmap should be the size we want to scale the
        // original bitmap too
        scaledBitmap = Bitmap.createBitmap( dstWidth, dstHeight, bitmapConfig );

        float ratioX = dstWidth / (float) bitmap.getWidth();
        float ratioY = dstHeight / (float) bitmap.getHeight();
        float middleX = dstWidth / 2.0f;
        float middleY = dstHeight / 2.0f;

        // Used to for scaling the image
        Matrix scaleMatrix = new Matrix();
        scaleMatrix.setScale( ratioX, ratioY, middleX, middleY );

        // Used to do the work of scaling
        Canvas canvas = new Canvas( scaledBitmap );
        canvas.setMatrix( scaleMatrix );
        canvas.drawBitmap( bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint( Paint.FILTER_BITMAP_FLAG ) );
    }
    catch( IllegalArgumentException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : IllegalArgumentException" );
        e.printStackTrace();
    }
    catch( NullPointerException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : NullPointerException" );
        e.printStackTrace();
    }
    catch( FileNotFoundException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : FileNotFoundException" );
        e.printStackTrace();
    }

    return scaledBitmap;
} // End createScaledBitmap
0 голосов
/ 12 февраля 2014

Это сработало для меня.Функция получает путь к файлу на SD-карте и возвращает растровое изображение в максимальном отображаемом размере.Код от Ofir с некоторыми изменениями, такими как файл изображения на sd, вместо Ressource, а witthth и heigth получаются из экранного объекта.

private Bitmap makeBitmap(String path) {

    try {
        final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
        //resource = getResources();

        // Decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        int scale = 1;
        while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) >
                IMAGE_MAX_SIZE) {
            scale++;
        }
        Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);

        Bitmap pic = null;
        if (scale > 1) {
            scale--;
            // scale to max possible inSampleSize that still yields an image
            // larger than target
            options = new BitmapFactory.Options();
            options.inSampleSize = scale;
            pic = BitmapFactory.decodeFile(path, options);

            // resize to desired dimensions

            Display display = getWindowManager().getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            int width = size.y;
            int height = size.x;

            //int height = imageView.getHeight();
            //int width = imageView.getWidth();
            Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);

            double y = Math.sqrt(IMAGE_MAX_SIZE
                    / (((double) width) / height));
            double x = (y / height) * width;

            Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
            pic.recycle();
            pic = scaledBitmap;

            System.gc();
        } else {
            pic = BitmapFactory.decodeFile(path);
        }

        Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
        return pic;

    } catch (Exception e) {
        Log.e("TAG", e.getMessage(),e);
        return null;
    }

}
0 голосов
/ 29 января 2019

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

Полный код в котлине:

@Throws(IOException::class)
fun File.decodeBitmap(options: BitmapFactory.Options): Bitmap? {
    return inputStream().use {
        BitmapFactory.decodeStream(it, null, options)
    }
}

@Throws(IOException::class)
fun File.decodeBitmapAtLeast(
        @androidx.annotation.IntRange(from = 1) width: Int,
        @androidx.annotation.IntRange(from = 1) height: Int
): Bitmap? {
    val options = BitmapFactory.Options()

    options.inJustDecodeBounds = true
    decodeBitmap(options)

    val ow = options.outWidth
    val oh = options.outHeight

    if (ow == -1 || oh == -1) return null

    val w = ow / width
    val h = oh / height

    if (w > 1 && h > 1) {
        val p = 31 - maxOf(Integer.numberOfLeadingZeros(w), Integer.numberOfLeadingZeros(h))
        options.inSampleSize = 1 shl maxOf(0, p)
    }
    options.inJustDecodeBounds = false
    return decodeBitmap(options)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...