Zmień rozmiar dużego pliku bitmapy na skalowany plik wyjściowy w systemie Android


218

Mam dużą mapę bitową (powiedzmy 3888 x 2592) w pliku. Teraz chcę zmienić rozmiar tej mapy bitowej na 800 x 533 i zapisać ją w innym pliku. I normalnie skalowania bitmapy poprzez wywołanie Bitmap.createBitmapmetody, ale potrzebuje bitmapę źródłowego jako pierwszy argument, którego nie może zapewnić ponieważ ładuje obraz oryginalny do obiektu Bitmap byłoby oczywiście przekroczyć pamięć (patrz tutaj , na przykład).

Nie mogę również odczytać mapy bitowej za pomocą, na przykład, BitmapFactory.decodeFile(file, options)podając BitmapFactory.Options.inSampleSize, ponieważ chcę zmienić rozmiar do dokładnej szerokości i wysokości. Użycie inSampleSizespowoduje zmianę rozmiaru mapy bitowej na 972 x 648 (jeśli używam inSampleSize=4) lub na 778 x 518 (jeśli używam inSampleSize=5, co nie jest nawet potęgą 2).

Chciałbym również uniknąć czytania obrazu przy użyciu inSampleSize z, na przykład, 972 x 648 w pierwszym kroku, a następnie zmiany rozmiaru do dokładnie 800 x 533 w drugim kroku, ponieważ jakość byłaby słaba w porównaniu z bezpośrednią zmianą rozmiaru oryginalnego obrazu.

Podsumowując moje pytanie: Czy istnieje sposób na odczytanie dużego pliku obrazu o rozmiarze 10 MP lub większym i zapisanie go w nowym pliku obrazu, zmienionym na określoną nową szerokość i wysokość, bez uzyskania wyjątku OutOfMemory?

Próbowałem też BitmapFactory.decodeFile(file, options)ręcznie ustawić wartości Options.outHeight i Options.outWidth ręcznie na 800 i 533, ale to nie działa w ten sposób.


Nie, outHeight i outWidth są parametrami out z metody dekodowania. To powiedziawszy, mam ten sam problem co ty i nigdy nie jestem bardzo zadowolony z podejścia 2 kroków.
rds

często, dzięki Bogu, możesz użyć jednego wiersza kodu .. stackoverflow.com/a/17733530/294884
Fattie

Czytelnicy, proszę zwrócić uwagę na absolutnie krytyczną kontrolę jakości !!! stackoverflow.com/a/24135522/294884
Fattie

1
Pls zauważ, że to pytanie ma teraz 5 lat, a pełne rozwiązanie to ... stackoverflow.com/a/24135522/294884 Pozdrawiam!
Fattie,

2
Istnieje teraz oficjalna dokumentacja na ten temat: developer.android.com/training/displaying-bitmaps/…
Vince

Odpowiedzi:


146

Nie. Chciałbym, żeby ktoś mnie poprawił, ale zaakceptowałem podejście polegające na obciążeniu / zmianie rozmiaru, które próbowałaś jako kompromis.

Oto kroki dla każdego, kto przegląda:

  1. Oblicz maksimum, inSampleSizektóre wciąż daje obraz większy niż twój cel.
  2. Załaduj obraz za pomocą BitmapFactory.decodeFile(file, options), przekazując inSampleSize jako opcję.
  3. Zmień rozmiar do żądanych wymiarów za pomocą Bitmap.createScaledBitmap() .

Próbowałem tego uniknąć. Więc nie ma sposobu, aby bezpośrednio zmienić rozmiar dużego obrazu w jednym kroku?
Manuel

2
Nie, o ile wiem, ale nie pozwól, aby powstrzymało cię to od dalszego zgłębiania tego.
Justin

W porządku, wezmę to do tej pory za moją zaakceptowaną odpowiedź. Jeśli znajdę inne metody, dam ci znać.
Manuel

Jak wspomniano w odpowiedzi na PSIXO, możesz również chcieć użyć Androida: largeHeap, jeśli nadal masz problemy po użyciu inSampleSize.
user276648,

Zmienna bitmapy była pusta
Prasad

99

Odpowiedź Justina przetłumaczona na kod (działa dla mnie idealnie):

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

15
Utrudnia czytanie, kiedy używasz zmiennych takich jak „b”, ale dobra odpowiedź nie mniej.
Oliver Dixon

@Ofir: getImageUri (ścieżka); co muszę przekazać w tej metodzie?
Biginner

1
Zamiast (w h) /Math.pow (skala, 2) bardziej efektywne jest użycie (w h) >> skali.
david.perez

2
Nie dzwoń, System.gc()proszę
gw0

Dzięki @Ofir, ale ta transformacja nie zachowuje orientacji obrazu: - /
JoeCoolman

43

To są „połączone” rozwiązania Mojo Risina i Ofira. To da ci proporcjonalnie zmieniony rozmiar obrazu z granicami maksymalnej szerokości i maksymalnej wysokości.

  1. Czyta tylko metadane, aby uzyskać oryginalny rozmiar (options.inJustDecodeBounds)
  2. Używa przemyślanej zmiany rozmiaru do oszczędzania pamięci (itmap.createScaledBitmap)
  3. Wykorzystuje precyzyjnie zmieniony rozmiar obrazu na podstawie wcześniej utworzonego szorstkiego Bitampa.

Dla mnie działa dobrze na 5 obrazach MegaPixel i poniżej.

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

23

Dlaczego nie skorzystać z interfejsu API?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);

21
Ponieważ to nie rozwiązałoby mojego problemu. Co oznacza: „... potrzebuje źródłowej mapy bitowej jako pierwszego argumentu, czego nie mogę podać, ponieważ załadowanie oryginalnego obrazu do obiektu Bitmap oczywiście przekroczyłoby pamięć”. Dlatego nie mogę przekazać mapy bitowej do metody .createScaledBitmap, ponieważ nadal musiałbym najpierw załadować duży obraz do obiektu Bitmap.
Manuel

2
Dobrze. Ponownie przeczytałem twoje pytanie i w zasadzie (jeśli dobrze to rozumiem) sprowadza się ono do „czy mogę zmienić rozmiar obrazu do dokładnych wymiarów bez ładowania oryginalnego pliku do pamięci?” Jeśli tak - nie wiem wystarczająco dużo o zawiłościach przetwarzania obrazu, aby na nie odpowiedzieć, ale coś mi mówi, że 1. nie jest dostępny z API, 2. nie będzie to 1-liniowy. Oznaczę to jako ulubione - byłoby interesujące zobaczyć, czy ty (lub ktoś inny) to rozwiążesz.
Bostone

zadziałało to dla mnie, ponieważ otrzymuję URI i konwertuję na bitmapę, więc skalowanie ich jest dla mnie łatwe 1+ dla najprostszego.
Hamza

22

Biorąc pod uwagę drugą doskonałą do tej pory odpowiedź, najlepszy kod, jaki do tej pory widziałem, znajduje się w dokumentacji narzędzia do robienia zdjęć.

Zobacz sekcję „Dekodowanie skalowanego obrazu”.

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

Rozwiązaniem, które proponuje, jest rozwiązanie zmiany rozmiaru, a następnie skalowania, jak inne tutaj, ale jest całkiem fajne.

Dla wygody skopiowałem poniższy kod jako funkcję gotową do użycia.

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

1
Najpierw dzielimy liczby całkowite, co spowoduje powstanie wyniku. Po drugie, kod ulega awarii, gdy wartość docelowaW lub wartość docelowa wynosi 0 (choć wiem, że to nie ma sensu). Trzeci inSampleSize powinien być potęgą 2.
cybergen

Nie zrozum mnie źle. Spowoduje to definitywne załadowanie obrazu, ale jeśli wykładzina ints jest zmieniona, to nie wygląda. I to zdecydowanie nie jest właściwa odpowiedź, ponieważ obraz nie będzie skalowany zgodnie z oczekiwaniami. Nie zrobi nic, dopóki widok obrazu nie zmniejszy się o połowę. Wtedy nic się nie dzieje, dopóki widok obrazu nie będzie miał 1/4 rozmiaru obrazu. I tak dalej z potęgami dwóch osób!
cybergen

18

Po przeczytaniu tych odpowiedzi i dokumentacji systemu Android oto kod zmiany rozmiaru mapy bitowej bez ładowania jej do pamięci:

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

Zauważ, że bmOptions.inPurgeable = true; jest przestarzałe.
Ravit

6

Kiedy mam duże bitmapy i chcę je zdekodować, zmieniam ich rozmiar, używam następujących

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

1
Ponieważ inSampleSize jest liczbą całkowitą, bardzo rzadko otrzymujesz dokładnie tę szerokość i wysokość piksela, którą chcesz uzyskać. Czasami możesz się zbliżyć, ale możesz też być z dala od niego, w zależności od miejsc po przecinku.
Manuel

Rano wypróbowałem Twój kod (post powyżej w tym wątku), ale wydaje się, że nie działa, gdzie popełniłem błąd? Wszelkie sugestie są mile widziane :-)
RRTW

5

Może to być przydatne dla kogoś, kto patrzy na to pytanie. Przepisałem kod Justina, aby umożliwić metodzie otrzymanie wymaganego obiektu rozmiaru docelowego. Działa to bardzo dobrze podczas korzystania z Canvas. Wszystkie zasługi należy się JUSTINOWI za jego świetny kod początkowy.

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

Kod Justina BARDZO skutecznie zmniejsza obciążenie związane z pracą z dużymi mapami bitowymi.


4

Nie wiem, czy moje rozwiązanie jest najlepszą praktyką, ale udało mi się załadować bitmapę z pożądanym skalowaniem za pomocą opcji inDensityi inTargetDensity. inDensityjest0 początkowo, gdy nie ładuje zasobu, który można wyciągnąć, więc to podejście służy do ładowania obrazów innych niż zasoby.

Zmienne imageUri, maxImageSideLengthi contextsą parametry mojej metody. Dla przejrzystości opublikowałem tylko implementację metody bez owijania AsyncTask.

            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;

2
Bardzo dobrze! Użycie inDensity zamiast Bitmap.createScaledBitmap pozwoliło mi zaoszczędzić mnóstwo pamięci. Jeszcze lepiej w połączeniu z inSamplesize.
Ostkontentitan

2

Biorąc pod uwagę, że chcesz zmienić rozmiar do dokładnego rozmiaru i chcesz zachować tyle jakości, ile potrzebujesz, myślę, że powinieneś spróbować.

  1. Znajdź rozmiar obrazu o zmienionym rozmiarze za pomocą wywołania BitmapFactory.decodeFile i podając checkSizeOptions.inJustDecodeBounds
  2. Oblicz maksimum możliwą wartość parametru InSampleSize, której możesz użyć w swoim urządzeniu, aby nie przekroczyć pamięci. bitmapSizeInBytes = 2 * szerokość * wysokość; Zasadniczo dla twojego obrazu inSampleSize = 2 byłoby w porządku, ponieważ potrzebujesz tylko 2 * 1944x1296) = 4,8 Mb, co powinno mieć stopy w pamięci
  3. Użyj BitmapFactory.decodeFile z inSampleSize, aby załadować bitmapę
  4. Skaluj mapę bitową do dokładnego rozmiaru.

Motywacja: skalowanie wielostopniowe może dać obraz o wyższej jakości, jednak nie ma gwarancji, że będzie działał lepiej niż przy użyciu wysokiej inSampleSize. Właściwie myślę, że można również użyć inSampleSize jak 5 (nie pow 2), aby uzyskać bezpośrednie skalowanie w jednej operacji. Lub po prostu użyj 4, a następnie możesz po prostu użyć tego obrazu w interfejsie użytkownika. jeśli wyślesz go do serwera - możesz wykonać skalowanie do dokładnego rozmiaru po stronie serwera, co pozwala na użycie zaawansowanych technik skalowania.

Uwagi: jeśli bitmapa załadowana w kroku 3 jest co najmniej 4 razy większa (więc 4 * szerokość docelowa <szerokość) prawdopodobnie można użyć kilku zmian rozmiaru, aby uzyskać lepszą jakość. przynajmniej działa to w ogólnej Javie, w Androidzie nie masz opcji określania interpolacji używanej do skalowania http://today.java.net/pub/a/today/2007/04/03/perils-of- image-getscaledinstance.html


2

Użyłem takiego kodu:

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

Próbowałem oryginalnego obrazu to 1230 x 1230, a według mapy bitowej jest to 330 x 330.
A jeśli spróbuję 2590 x 3849, dostanę błąd OutOfMemoryError.

Prześledziłem go, nadal wyrzuca OutOfMemoryError w linii „BitmapFactory.decodeStream (is, null, options);”, jeśli oryginalna mapa bitowa jest zbyt duża ...


2

Powyższy kod uczynił trochę czystszym. Strumienie wejściowe w końcu są ściśle zamykane, aby zapewnić, że również zostaną zamknięte:

* Uwaga
Wejście: InputStream jest, int w, int h
Wyjście: Bitmapa

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

    }

1

Aby przeskalować obraz w „prawidłowy” sposób, bez pomijania pikseli, należy podłączyć się do dekodera obrazu, aby wykonać próbkowanie w dół rząd po rzędzie. Android (i leżąca u jego podstaw biblioteka Skia) nie oferuje takich haczyków, więc trzeba by rzucić własną. Zakładając, że mówisz o obrazach JPEG, najlepszym rozwiązaniem byłoby użycie libjpeg bezpośrednio w C.

Biorąc pod uwagę złożoność, korzystanie z dwuetapowej podpróbki, a następnie przeskalowania jest prawdopodobnie najlepsze dla aplikacji typu podgląd obrazu.



1

Jeśli absolutnie chcesz zmienić rozmiar o jeden krok, prawdopodobnie możesz załadować całą mapę bitową, jeśli Android: largeHeap = true, ale jak widać, nie jest to zalecane.

Z dokumentacji: android: largeHeap Określa, czy procesy aplikacji powinny być tworzone za pomocą dużej sterty Dalvik. Dotyczy to wszystkich procesów utworzonych dla aplikacji. Dotyczy to tylko pierwszej aplikacji załadowanej do procesu; jeśli używasz wspólnego identyfikatora użytkownika, aby umożliwić wielu aplikacjom korzystanie z procesu, wszystkie one muszą konsekwentnie korzystać z tej opcji lub będą miały nieprzewidywalne wyniki. Większość aplikacji nie powinna tego potrzebować i zamiast tego powinna skupić się na zmniejszeniu całkowitego zużycia pamięci w celu zwiększenia wydajności. Włączenie tego również nie gwarantuje stałego zwiększenia dostępnej pamięci, ponieważ niektóre urządzenia są ograniczone przez całkowitą dostępną pamięć.



0

To zadziałało dla mnie. Funkcja pobiera ścieżkę do pliku na karcie SD i zwraca mapę bitową w maksymalnym możliwym do wyświetlenia rozmiarze. Kod pochodzi z Ofir, z pewnymi zmianami, takimi jak plik obrazu na sd zamiast Ressource, a wysokość i wysokość są pobierane z obiektu wyświetlanego.

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

Oto kod, którego używam, który nie ma problemów z dekodowaniem dużych obrazów w pamięci na Androidzie. Byłem w stanie dekodować obrazy większe niż 20 MB, o ile moje parametry wejściowe wynoszą około 1024x1024. Możesz zapisać zwróconą mapę bitową w innym pliku. Poniżej tej metody znajduje się kolejna metoda, której używam również do skalowania obrazów do nowej mapy bitowej. Możesz użyć tego kodu, jak chcesz.

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

UWAGA: Metody nie mają ze sobą nic wspólnego oprócz metody dekodowania wywołań metody createScaledBitmap powyżej. Uwaga szerokość i wysokość mogą się różnić od oryginalnego obrazu.

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

obliczenia mocy dla skali są po prostu błędne; wystarczy skorzystać z obliczeń na stronie Android doco.
Fattie,

0
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

lub:

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

0

używam Integer.numberOfLeadingZeros do obliczania najlepszego rozmiaru próbki, lepszej wydajności.

Pełny kod w 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)
}

-2

Zmień rozmiar mapy bitowej za pomocą następującego kodu

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

To samo wyjaśniono w poniższej wskazówce / sztuczce

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.