Ubah ukuran file bitmap besar menjadi file output yang diskalakan di Android

218

Saya memiliki bitmap besar (katakanlah 3888x2592) dalam sebuah file. Sekarang, saya ingin mengubah ukuran bitmap menjadi 800x533 dan menyimpannya ke file lain. Saya biasanya akan skala bitmap dengan memanggil Bitmap.createBitmapmetode tetapi membutuhkan bitmap sumber sebagai argumen pertama, yang saya tidak bisa berikan karena memuat gambar asli ke objek Bitmap tentu saja akan melebihi memori (lihat di sini , misalnya).

Saya juga tidak dapat membaca bitmap dengan, misalnya BitmapFactory.decodeFile(file, options), memberikan BitmapFactory.Options.inSampleSize, karena saya ingin mengubah ukurannya menjadi lebar dan tinggi yang tepat. Menggunakan inSampleSizeakan mengubah ukuran bitmap ke 972x648 (jika saya menggunakan inSampleSize=4) atau ke 778x518 (jika saya menggunakan inSampleSize=5, yang bahkan bukan kekuatan 2).

Saya juga ingin menghindari membaca gambar menggunakan inSampleSize dengan, misalnya, 972x648 pada langkah pertama dan kemudian mengubah ukurannya menjadi 800x533 pada langkah kedua, karena kualitasnya akan buruk dibandingkan dengan mengubah ukuran langsung dari gambar asli.

Untuk meringkas pertanyaan saya: Apakah ada cara untuk membaca file gambar besar dengan 10MP atau lebih dan menyimpannya ke file gambar baru, diubah ukurannya menjadi lebar dan tinggi baru tertentu, tanpa mendapatkan pengecualian OutOfMemory?

Saya juga mencoba BitmapFactory.decodeFile(file, options)dan mengatur nilai Options.outHeight dan Options.outWidth secara manual ke 800 dan 533, tetapi tidak berfungsi seperti itu.

Manuel
sumber
Tidak, outHeight dan outWidth keluar parameter dari metode decode. Yang sedang berkata, saya memiliki masalah yang sama dari Anda, dan saya tidak terlalu puas dengan pendekatan 2 langkah sebelumnya.
rds
sering, syukurlah, Anda dapat menggunakan satu baris kode .. stackoverflow.com/a/17733530/294884
Fattie
Pembaca, harap perhatikan QA yang benar-benar kritis ini !!! stackoverflow.com/a/24135522/294884
Fattie
1
Harap dicatat bahwa pertanyaan ini sekarang berusia 5 tahun, dan solusi lengkapnya adalah .. stackoverflow.com/a/24135522/294884 Cheers!
Fattie
2
Sekarang ada dokumentasi resmi tentang topik itu: developer.android.com/training/displaying-bitmaps/…
Vince

Jawaban:

146

Tidak. Saya ingin seseorang mengoreksi saya, tetapi saya menerima pendekatan load / resize yang Anda coba sebagai kompromi.

Berikut langkah-langkah untuk siapa saja yang menjelajah:

  1. Hitung kemungkinan maksimum inSampleSizeyang masih menghasilkan gambar lebih besar dari target Anda.
  2. Muat gambar menggunakan BitmapFactory.decodeFile(file, options), lewat inSampleSize sebagai opsi.
  3. Ubah ukuran ke dimensi yang diinginkan menggunakan Bitmap.createScaledBitmap().
Justin
sumber
Saya mencoba menghindarinya. Jadi tidak ada cara untuk secara langsung mengubah ukuran gambar besar hanya dalam satu langkah?
Manuel
2
Tidak sepengetahuan saya, tapi jangan biarkan itu menghentikan Anda menjelajahi ini lebih jauh.
Justin
Baiklah, saya akan mengambil ini untuk jawaban yang saya terima sejauh ini. Jika saya menemukan metode lain, saya akan memberi tahu Anda.
Manuel
Seperti yang PSIXO sebutkan dalam sebuah jawaban, Anda mungkin juga ingin menggunakan android: largeHeap jika Anda masih memiliki masalah setelah menggunakan inSampleSize.
user276648
variabel bitmap semakin kosong
Prasad
99

Jawaban Justin diterjemahkan ke kode (berfungsi sempurna untuk saya):

private Bitmap getBitmap(String path) {

Uri uri = getImageUri(path);
InputStream in = null;
try {
    final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
    in = mContentResolver.openInputStream(uri);

    // Decode image size
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();



    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 resultBitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) {
        scale--;
        // scale to max possible inSampleSize that still yields an image
        // larger than target
        options = new BitmapFactory.Options();
        options.inSampleSize = scale;
        resultBitmap = BitmapFactory.decodeStream(in, null, options);

        // resize to desired dimensions
        int height = resultBitmap.getHeight();
        int width = resultBitmap.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(resultBitmap, (int) x, 
           (int) y, true);
        resultBitmap.recycle();
        resultBitmap = scaledBitmap;

        System.gc();
    } else {
        resultBitmap = BitmapFactory.decodeStream(in);
    }
    in.close();

    Log.d(TAG, "bitmap size - width: " +resultBitmap.getWidth() + ", height: " + 
       resultBitmap.getHeight());
    return resultBitmap;
} catch (IOException e) {
    Log.e(TAG, e.getMessage(),e);
    return null;
}
Ofir
sumber
15
Membuatnya sulit dibaca ketika Anda menggunakan variabel seperti "b" tetapi jawaban yang baik tidak kurang.
Oliver Dixon
@Ofir: getImageUri (path); apa yang harus saya lewati dalam metode ini?
Biginner
1
Alih-alih (w h) /Math.pow (skala, 2) lebih efisien untuk menggunakan skala (w h) >>.
david.perez
2
Jangan panggil System.gc()tolong
gw0
Terima kasih @Ofir tetapi transformasi ini tidak menghemat orientasi gambar: - /
JoeCoolman
43

Ini adalah solusi 'Mojo Risin dan' Ofir "digabungkan". Ini akan memberi Anda ukuran gambar yang proporsional dengan batas lebar maks dan tinggi maks.

  1. Itu hanya membaca meta data untuk mendapatkan ukuran asli (options.inJustDecodeBounds)
  2. Ini menggunakan ukuran rought untuk menyimpan memori (itmap.createScaledBitmap)
  3. Ini menggunakan gambar yang diubah ukurannya secara tepat berdasarkan pada Bitamp kasar yang dibuat sebelumnya.

Bagi saya itu telah bekerja dengan baik pada gambar 5 MegaPixel di bawah ini.

try
{
    int inWidth = 0;
    int inHeight = 0;

    InputStream in = new FileInputStream(pathOfInputImage);

    // decode image size (decode metadata only, not the whole image)
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();
    in = null;

    // save width and height
    inWidth = options.outWidth;
    inHeight = options.outHeight;

    // decode full image pre-resized
    in = new FileInputStream(pathOfInputImage);
    options = new BitmapFactory.Options();
    // calc rought re-size (this is no exact resize)
    options.inSampleSize = Math.max(inWidth/dstWidth, inHeight/dstHeight);
    // decode full image
    Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);

    // calc exact destination size
    Matrix m = new Matrix();
    RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
    RectF outRect = new RectF(0, 0, dstWidth, dstHeight);
    m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
    float[] values = new float[9];
    m.getValues(values);

    // resize bitmap
    Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

    // save image
    try
    {
        FileOutputStream out = new FileOutputStream(pathOfOutputImage);
        resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
    }
    catch (Exception e)
    {
        Log.e("Image", e.getMessage(), e);
    }
}
catch (IOException e)
{
    Log.e("Image", e.getMessage(), e);
}
blubl
sumber
23

Mengapa tidak menggunakan API?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);
Bostone
sumber
21
Karena itu tidak akan menyelesaikan masalah saya. Yaitu: "... itu membutuhkan bitmap sumber sebagai argumen pertama, yang saya tidak bisa berikan karena memuat gambar asli ke objek Bitmap tentu saja akan melebihi memori." Jadi, saya tidak bisa melewatkan Bitmap ke metode .createScaledBitmap, karena saya masih perlu memuat gambar besar ke objek Bitmap terlebih dahulu.
Manuel
2
Baik. Saya membaca kembali pertanyaan Anda dan pada dasarnya (jika saya memahaminya dengan benar) itu muncul menjadi "bisakah saya mengubah ukuran gambar ke dimensi yang tepat tanpa memuat file asli ke dalam memori?" Jika demikian - saya tidak cukup tahu tentang seluk-beluk pemrosesan gambar untuk menjawabnya, tetapi ada sesuatu yang memberitahu saya bahwa 1. tidak tersedia dari API, 2. tidak akan menjadi 1-liner. Saya akan menandai ini sebagai favorit - akan menarik untuk melihat apakah Anda (atau orang lain) akan menyelesaikan ini.
Bostone
itu berhasil bagi saya karena saya mendapatkan uri dan mengonversi ke bitmap sehingga penskalaan mereka mudah bagi saya 1+ untuk yang paling sederhana.
Hamza
22

Mengakui jawaban luar biasa lainnya sejauh ini, kode terbaik yang pernah saya lihat untuk ini adalah dalam dokumentasi untuk alat pengambilan foto.

Lihat bagian berjudul "Decode a Scaled Image".

http://developer.android.com/training/camera/photobasics.html

Solusi yang diajukannya adalah solusi pengubahan ukuran kemudian skala seperti yang lain di sini, tetapi cukup rapi.

Saya telah menyalin kode di bawah ini sebagai fungsi siap pakai untuk kenyamanan.

private void setPic(String imagePath, ImageView destination) {
    int targetW = destination.getWidth();
    int targetH = destination.getHeight();
    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    destination.setImageBitmap(bitmap);
}
Alex
sumber
1
Pertama Anda membagi bilangan bulat apa yang akan menjadi dasar hasilnya. Kedua kode crash dengan targetW atau targetH menjadi 0 (meskipun ini tidak masuk akal saya tahu). InSampleSize ketiga harus menjadi kekuatan 2.
cybergen
Jangan salah sangka. Ini akan secara definit memuat gambar, tetapi jika lantai int indend, tidak terlihat begitu. Dan ini juga jelas bukan jawaban yang tepat karena gambar tidak akan diskalakan seperti yang diharapkan. Ini tidak akan melakukan apa pun sampai tampilan gambar adalah setengah dari ukuran gambar atau lebih kecil. Maka tidak ada yang terjadi sampai tampilan gambar adalah 1/4 ukuran gambar. Dan seterusnya dengan kekuatan dua!
cybergen
18

Setelah membaca jawaban ini dan dokumentasi android berikut ini adalah kode untuk mengubah ukuran bitmap tanpa memuatnya ke dalam memori:

public Bitmap getResizedBitmap(int targetW, int targetH,  String imagePath) {

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    //inJustDecodeBounds = true <-- will not load the bitmap into memory
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    return(bitmap);
}
penduDev
sumber
Harap perhatikan bahwa bmOptions.inPurgeable = true; sudah ditinggalkan.
Ravit
6

Ketika saya memiliki bitmap besar dan saya ingin men-decode ukurannya saya menggunakan yang berikut ini

BitmapFactory.Options options = new BitmapFactory.Options();
InputStream is = null;
is = new FileInputStream(path_to_file);
BitmapFactory.decodeStream(is,null,options);
is.close();
is = new FileInputStream(path_to_file);
// here w and h are the desired width and height
options.inSampleSize = Math.max(options.outWidth/w, options.outHeight/h);
// bitmap is the resized bitmap
Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);
Mojo Risin
sumber
1
Karena inSampleSize adalah Integer, Anda hanya akan sangat jarang mendapatkan lebar dan tinggi piksel yang tepat yang ingin Anda dapatkan. Anda mungkin kadang-kadang dekat, tetapi Anda juga mungkin jauh dari itu, tergantung pada desimal.
Manuel
Pagi, saya sudah mencoba kode Anda (posting di atas di utas ini), tetapi tampaknya tidak berfungsi, di mana saya melakukan kesalahan? Ada saran yang diterima :-)
RRTW
5

Ini mungkin berguna untuk orang lain yang melihat pertanyaan ini. Saya menulis ulang kode Justin untuk memungkinkan metode menerima objek ukuran target yang diperlukan juga. Ini bekerja sangat baik ketika menggunakan Kanvas. Semua kredit harus diberikan kepada JUSTIN untuk kode awal yang bagus.

    private Bitmap getBitmap(int path, Canvas canvas) {

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

            // Decode image size
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(resource, 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.decodeResource(resource, path, options);

                // resize to desired dimensions
                int height = canvas.getHeight();
                int width = canvas.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.decodeResource(resource, 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;
        }
    }

Kode Justin SANGAT efektif dalam mengurangi overhead bekerja dengan Bitmap besar.

Musik Monyet
sumber
4

Saya tidak tahu apakah solusi saya adalah praktik terbaik, tetapi saya mencapai pemuatan bitmap dengan penskalaan yang saya inginkan dengan menggunakan opsi inDensitydan inTargetDensity. inDensityadalah0 awalnya ketika tidak memuat sumber daya ditarik, sehingga pendekatan ini adalah untuk gambar sumber daya non pemuatan.

Variabel imageUri, maxImageSideLengthdan contextmerupakan parameter dari metode saya. Saya hanya memposting implementasi metode tanpa membungkus AsyncTask untuk kejelasan.

            ContentResolver resolver = context.getContentResolver();
            InputStream is;
            try {
                is = resolver.openInputStream(imageUri);
            } catch (FileNotFoundException e) {
                Log.e(TAG, "Image not found.", e);
                return null;
            }
            Options opts = new Options();
            opts.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(is, null, opts);

            // scale the image
            float maxSideLength = maxImageSideLength;
            float scaleFactor = Math.min(maxSideLength / opts.outWidth, maxSideLength / opts.outHeight);
            // do not upscale!
            if (scaleFactor < 1) {
                opts.inDensity = 10000;
                opts.inTargetDensity = (int) ((float) opts.inDensity * scaleFactor);
            }
            opts.inJustDecodeBounds = false;

            try {
                is.close();
            } catch (IOException e) {
                // ignore
            }
            try {
                is = resolver.openInputStream(imageUri);
            } catch (FileNotFoundException e) {
                Log.e(TAG, "Image not found.", e);
                return null;
            }
            Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);
            try {
                is.close();
            } catch (IOException e) {
                // ignore
            }

            return bitmap;
cybergen
sumber
2
Sangat bagus! Menggunakan inDensity alih-alih Bitmap.createScaledBitmap menyelamatkan saya banyak tumpukan memori. Bahkan lebih baik dikombinasikan dengan inSamplesize.
Ostkontentitan
2

Mempertimbangkan bahwa Anda ingin mengubah ukuran ke ukuran yang tepat dan ingin menjaga kualitas sebanyak yang diperlukan, saya pikir Anda harus mencoba ini.

  1. Cari tahu ukuran gambar yang diubah ukurannya dengan panggilan BitmapFactory.decodeFile dan berikan checkSizeOptions.inJustDecodeBounds
  2. Hitung kemungkinan maksimum dalam ukuran sampel yang dapat Anda gunakan pada perangkat Anda untuk tidak melebihi memori. bitmapSizeInBytes = 2 * lebar * tinggi; Umumnya untuk gambar Anda diSampleSize = 2 akan baik-baik saja karena Anda hanya perlu 2 * 1944x1296) = 4,8Mbб yang seharusnya berada di memori
  3. Gunakan BitmapFactory.decodeFile dengan inSampleSize untuk memuat Bitmap
  4. Skala bitmap ke ukuran yang tepat.

Motivasi: penskalaan beberapa langkah dapat memberikan gambar berkualitas lebih tinggi, namun tidak ada jaminan bahwa itu akan bekerja lebih baik daripada menggunakan inSampleSize yang tinggi. Sebenarnya, saya pikir Anda juga dapat menggunakan inSampleSize seperti 5 (bukan pow 2) untuk melakukan penskalaan langsung dalam satu operasi. Atau cukup gunakan 4 dan kemudian Anda bisa menggunakan gambar itu di UI. jika Anda mengirimnya ke server - maka Anda dapat melakukan penskalaan ke ukuran tepat di sisi server yang memungkinkan Anda menggunakan teknik penskalaan tingkat lanjut.

Catatan: jika Bitmap yang dimuat di langkah-3 setidaknya 4 kali lebih besar (sehingga lebar target 4 * <Lebar) Anda mungkin dapat menggunakan beberapa ukuran untuk mencapai kualitas yang lebih baik. setidaknya itu berfungsi di generik java, di android Anda tidak memiliki opsi untuk menentukan interpolasi yang digunakan untuk penskalaan http://today.java.net/pub/a/today/2007/04/03/perils-of- image-getscaledinstance.html

Andrey Chorniy
sumber
2

Saya menggunakan kode seperti ini:

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

Saya mencoba gambar asli 1230 x 1230, dan mendapat bitmap mengatakan 330 x 330.
Dan jika mencoba 2590 x 3849, saya akan mendapat OutOfMemoryError.

Saya menelusurinya, masih membuang OutOfMemoryError on line "BitmapFactory.decodeStream (is, null, options);", jika bitmap asli terlalu besar ...

RRTW
sumber
2

Kode di atas sedikit lebih bersih. InputStreams akhirnya menutup pembungkus untuk memastikan mereka ditutup juga:

* Catatan
Input: InputStream adalah, int w, int h
Output: Bitmap

    try
    {

        final int inWidth;
        final int inHeight;

        final File tempFile = new File(temp, System.currentTimeMillis() + is.toString() + ".temp");

        {

            final FileOutputStream tempOut = new FileOutputStream(tempFile);

            StreamUtil.copyTo(is, tempOut);

            tempOut.close();

        }



        {

            final InputStream in = new FileInputStream(tempFile);
            final BitmapFactory.Options options = new BitmapFactory.Options();

            try {

                // decode image size (decode metadata only, not the whole image)
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(in, null, options);

            }
            finally {
                in.close();
            }

            // save width and height
            inWidth = options.outWidth;
            inHeight = options.outHeight;

        }

        final Bitmap roughBitmap;

        {

            // decode full image pre-resized
            final InputStream in = new FileInputStream(tempFile);

            try {

                final BitmapFactory.Options options = new BitmapFactory.Options();
                // calc rought re-size (this is no exact resize)
                options.inSampleSize = Math.max(inWidth/w, inHeight/h);
                // decode full image
                roughBitmap = BitmapFactory.decodeStream(in, null, options);

            }
            finally {
                in.close();
            }

            tempFile.delete();

        }

        float[] values = new float[9];

        {

            // calc exact destination size
            Matrix m = new Matrix();
            RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
            RectF outRect = new RectF(0, 0, w, h);
            m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
            m.getValues(values);

        }

        // resize bitmap
        final Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

        return resizedBitmap;

    }
    catch (IOException e) {

        logger.error("Error:" , e);
        throw new ResourceException("could not create bitmap");

    }
pengguna1327738
sumber
1

Untuk skala gambar dengan cara "benar", tanpa melewatkan piksel, Anda harus menghubungkan ke decoder gambar untuk melakukan pengambilan sampel baris demi baris. Android (dan pustaka Skia yang melandasinya) tidak menyediakan kait seperti itu, jadi Anda harus menggulirkannya sendiri. Dengan asumsi Anda berbicara gambar jpeg, taruhan terbaik Anda adalah menggunakan libjpeg secara langsung, dalam C.

Mengingat kompleksitas yang terlibat, menggunakan dua langkah subsampel-lalu-skala ulang mungkin yang terbaik untuk aplikasi jenis pratinjau gambar.

D0SBoots
sumber
1

Jika Anda benar-benar ingin melakukan perubahan ukuran satu langkah, Anda mungkin dapat memuat seluruh bitmap jika android: largeHeap = true tetapi seperti yang Anda lihat ini tidak benar-benar disarankan.

Dari docs: android: largeHeap Apakah proses aplikasi Anda harus dibuat dengan tumpukan Dalvik besar. Ini berlaku untuk semua proses yang dibuat untuk aplikasi. Ini hanya berlaku untuk aplikasi pertama yang dimuat ke dalam suatu proses; jika Anda menggunakan ID pengguna bersama untuk memungkinkan beberapa aplikasi menggunakan proses, mereka semua harus menggunakan opsi ini secara konsisten atau mereka akan memiliki hasil yang tidak dapat diprediksi. Sebagian besar aplikasi seharusnya tidak memerlukan ini dan sebaliknya harus fokus pada pengurangan penggunaan memori secara keseluruhan untuk peningkatan kinerja. Mengaktifkan ini juga tidak menjamin peningkatan yang tetap dalam memori yang tersedia, karena beberapa perangkat dibatasi oleh total memori yang tersedia.

Igor Čordaš
sumber
0

Ini berhasil untuk saya. Fungsi mendapatkan path ke file pada kartu sd dan mengembalikan Bitmap dalam ukuran maksimum yang dapat ditampilkan. Kode ini dari Ofir dengan beberapa perubahan seperti file gambar di sd bukan Ressource dan witdth dan heigth dapatkan dari Display Object.

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

}
Panca
sumber
0

Berikut adalah kode yang saya gunakan yang tidak memiliki masalah mendekode gambar besar dalam memori di Android. Saya telah dapat mendekode gambar yang lebih besar dari 20MB selama parameter input saya sekitar 1024x1024. Anda dapat menyimpan bitmap yang dikembalikan ke file lain. Di bawah metode ini adalah metode lain yang juga saya gunakan untuk skala gambar ke bitmap baru. Jangan ragu untuk menggunakan kode ini seperti yang Anda inginkan.

/*****************************************************************************
 * 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

CATATAN: Metode tidak ada hubungannya dengan satu sama lain kecuali metode decode panggilan createScaledBitmap di atas. Catatan lebar dan tinggi dapat berubah dari gambar asli.

/*****************************************************************************
 * 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
pengguna560663
sumber
perhitungan daya untuk skala sama sekali salah di sini; cukup gunakan perhitungan di halaman doco android.
Fattie
0
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

atau:

 resized = Bitmap.createScaledBitmap(yourBitmap,(int)(yourBitmap.getWidth()*0.8), (int)(yourBitmap.getHeight()*0.8), true);
Vaishali Sutariya
sumber
0

Saya gunakan Integer.numberOfLeadingZerosuntuk menghitung ukuran sampel terbaik, kinerja lebih baik.

Kode lengkap dalam kotlin:

@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)
}
lymoge
sumber
-2

Ubah ukuran bitmap menggunakan kode berikut

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

Hal yang sama juga dijelaskan dalam tip / trik berikut

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

Amol
sumber