Ändern Sie die Größe einer großen Bitmap-Datei in eine skalierte Ausgabedatei unter Android

218

Ich habe eine große Bitmap (sagen wir 3888x2592) in einer Datei. Jetzt möchte ich die Größe dieser Bitmap auf 800 x 533 ändern und sie in einer anderen Datei speichern. Normalerweise würde ich die Bitmap durch Aufrufen der Bitmap.createBitmapMethode skalieren, aber als erstes Argument wird eine Quell-Bitmap benötigt, die ich nicht angeben kann, da das Laden des Originalbilds in ein Bitmap-Objekt natürlich den Speicher überschreiten würde (siehe hier zum Beispiel).

Ich kann die Bitmap auch nicht lesen, wenn ich beispielsweise a BitmapFactory.decodeFile(file, options)bereitstelle BitmapFactory.Options.inSampleSize, da ich die Größe auf eine genaue Breite und Höhe ändern möchte. Bei Verwendung inSampleSizewürde die Größe der Bitmap auf 972 x 648 (wenn ich sie verwende inSampleSize=4) oder auf 778 x 518 (wenn ich sie verwende inSampleSize=5, was nicht einmal eine Zweierpotenz ist) geändert .

Ich möchte auch vermeiden, das Bild mit inSampleSize mit beispielsweise 972 x 648 in einem ersten Schritt zu lesen und es dann in einem zweiten Schritt auf genau 800 x 533 zu ändern, da die Qualität im Vergleich zu einer direkten Größenänderung des Originalbilds schlecht wäre.

Um meine Frage zusammenzufassen: Gibt es eine Möglichkeit, eine große Bilddatei mit 10 MP oder mehr zu lesen und in einer neuen Bilddatei zu speichern, deren Größe auf eine bestimmte neue Breite und Höhe angepasst wurde, ohne eine OutOfMemory-Ausnahme zu erhalten?

Ich habe auch versucht BitmapFactory.decodeFile(file, options), die Werte Options.outHeight und Options.outWidth manuell auf 800 und 533 zu setzen, aber das funktioniert nicht so.

Manuel
quelle
Nein, outHeight und outWidth sind out- Parameter der Decodierungsmethode. Davon abgesehen habe ich das gleiche Problem wie Sie und bin mit dem 2-Schritte-Ansatz nie sehr zufrieden.
rds
Gott sei Dank können Sie oft eine Codezeile verwenden. stackoverflow.com/a/17733530/294884
Fattie
Leser, bitte beachten Sie diese absolut kritische Qualitätssicherung !!! stackoverflow.com/a/24135522/294884
Fattie
1
Bitte beachten Sie, dass diese Frage jetzt 5 Jahre alt ist und die vollständige Lösung lautet .. stackoverflow.com/a/24135522/294884 Prost!
Fattie
2
Es gibt jetzt eine offizielle Dokumentation zu diesem Thema: developer.android.com/training/displaying-bitmaps/…
Vince

Antworten:

146

Nein. Ich würde es lieben, wenn mich jemand korrigiert, aber ich habe den von Ihnen versuchten Ansatz zum Laden / Ändern der Größe als Kompromiss akzeptiert.

Hier sind die Schritte für alle, die surfen:

  1. Berechnen Sie das Maximum, inSampleSizedas immer noch ein Bild ergibt, das größer als Ihr Ziel ist.
  2. Laden Sie das Bild mit BitmapFactory.decodeFile(file, options)und übergeben Sie inSampleSize als Option.
  3. Ändern Sie die Größe mit auf die gewünschten Abmessungen Bitmap.createScaledBitmap().
Justin
quelle
Ich habe versucht, das zu vermeiden. Es gibt also keine Möglichkeit, die Größe eines großen Bildes in nur einem Schritt direkt zu ändern.
Manuel
2
Meines Wissens nicht, aber lassen Sie sich nicht davon abhalten, dies weiter zu untersuchen.
Justin
Okay, ich werde dies für meine bisher akzeptierte Antwort nehmen. Wenn ich andere Methoden herausfinde, werde ich Sie wissen lassen.
Manuel
Wie PSIXO in einer Antwort erwähnt hat, möchten Sie möglicherweise auch android: largeHeap verwenden, wenn Sie nach der Verwendung von inSampleSize immer noch Probleme haben.
user276648
Bitmap-Variable wurde leer
Prasad
99

Justin Antwort in Code übersetzt (funktioniert perfekt für mich):

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
quelle
15
Das Lesen ist schwierig, wenn Sie Variablen wie "b" verwenden, aber trotzdem eine gute Antwort.
Oliver Dixon
@Ofir: getImageUri (Pfad); Was muss ich bei dieser Methode übergeben?
Biginner
1
Anstelle von (w h) /Math.pow (scale, 2) ist es effizienter, (w h) >> scale zu verwenden.
david.perez
2
Rufen System.gc()Sie bitte nicht an
gw0
Danke @Ofir, aber diese Transformation bewahrt nicht die
Bildorientierung
43

Dies sind die "kombinierten" Lösungen von 'Mojo Risin' und 'Ofir'. Dadurch erhalten Sie ein Bild mit proportionaler Größe und den Grenzen für maximale Breite und maximale Höhe.

  1. Es werden nur Metadaten gelesen, um die ursprüngliche Größe zu erhalten (options.inJustDecodeBounds).
  2. Es wird eine Größenänderung verwendet, um Speicherplatz zu sparen (itmap.createScaledBitmap).
  3. Es wird ein genau angepasstes Bild verwendet, das auf dem zuvor erstellten groben Bitamp basiert.

Für mich hat es bei 5 MegaPixel-Bildern eine gute Leistung erbracht.

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
quelle
23

Warum nicht die API verwenden?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);
Bostone
quelle
21
Weil es mein Problem nicht lösen würde. Das heißt: "... es benötigt eine Quell-Bitmap als erstes Argument, das ich nicht angeben kann, da das Laden des Originalbilds in ein Bitmap-Objekt natürlich den Speicher überschreiten würde." Daher kann ich auch keine Bitmap an die .createScaledBitmap-Methode übergeben, da ich immer noch zuerst ein großes Bild in ein Bitmap-Objekt laden müsste.
Manuel
2
Richtig. Ich habe Ihre Frage erneut gelesen und im Grunde genommen (wenn ich sie richtig verstehe) lautet sie: "Kann ich die Größe des Bilds auf exakte Abmessungen ändern, ohne die Originaldatei in den Speicher zu laden?" Wenn ja - ich weiß nicht genug über die Feinheiten der Bildverarbeitung, um darauf zu antworten, aber irgendetwas sagt mir, dass 1. es nicht über die API verfügbar ist, 2. es kein 1-Liner sein wird. Ich werde dies als Favorit markieren - es wäre interessant zu sehen, ob Sie (oder jemand anderes) dies lösen werden.
Bostone
Es hat bei mir funktioniert, weil ich Uri bekomme und in Bitmap konvertiere, so dass das Skalieren für mich 1+ für die einfachste einfach ist.
Hamza
22

Der beste Code, den ich bisher gesehen habe, ist die Dokumentation für das Foto-Tool.

Siehe den Abschnitt "Dekodieren eines skalierten Bildes".

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

Die vorgeschlagene Lösung ist eine Größenänderung und dann eine Skalierungslösung wie die anderen hier, aber sie ist recht ordentlich.

Ich habe den folgenden Code der Einfachheit halber als sofort einsatzbereite Funktion kopiert.

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
quelle
1
Zuerst teilen Sie ganze Zahlen auf, was das Ergebnis beeinflusst. Zweitens stürzt der Code ab, wobei targetW oder targetH 0 sind (obwohl dies meines Wissens nicht viel Sinn macht). Dritte inSampleSize sollte eine Potenz von 2 sein.
Cybergen
Versteh mich nicht falsch. Dadurch wird definitiv ein Bild geladen, aber wenn der Boden der Ints eingerückt ist, sieht es nicht so aus. Und dies ist definitiv auch nicht die richtige Antwort, da das Bild nicht wie erwartet skaliert wird. Es wird nichts getan, bis die Bildansicht halb so groß wie das Bild oder kleiner ist. Dann passiert nichts, bis die Bildansicht 1/4 der Größe des Bildes ist. Und so weiter mit Zweierpotenzen!
Cybergen
18

Nachdem Sie diese Antworten und die Android-Dokumentation gelesen haben, finden Sie hier den Code zum Ändern der Bitmap-Größe, ohne sie in den Speicher zu laden:

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
quelle
Bitte beachten Sie, dass bmOptions.inPurgeable = true; ist veraltet.
Ravit
6

Wenn ich große Bitmaps habe und deren Größe dekodieren möchte, verwende ich Folgendes

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
quelle
1
Da inSampleSize eine Ganzzahl ist, erhalten Sie nur sehr selten die genaue Pixelbreite und -höhe, die Sie erhalten möchten. Sie können manchmal nahe kommen, aber Sie können auch weit davon entfernt sein, abhängig von den Dezimalstellen.
Manuel
Morgen habe ich Ihren Code ausprobiert (Beitrag oben in diesem Thread), aber er scheint nicht zu funktionieren. Wo habe ich etwas falsch gemacht?
Vorschläge
5

Dies kann für andere Personen nützlich sein, die sich mit dieser Frage befassen. Ich habe Justins Code neu geschrieben, damit die Methode auch das gewünschte Zielgrößenobjekt empfangen kann. Dies funktioniert sehr gut bei der Verwendung von Canvas. Alle Gutschriften sollten an JUSTIN für seinen großartigen Anfangscode gehen.

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

Justins Code ist SEHR effektiv, um den Aufwand für die Arbeit mit großen Bitmaps zu reduzieren.

Musik Affe
quelle
4

Ich weiß nicht, ob meine Lösung die beste Vorgehensweise ist, aber ich habe mithilfe der Optionen inDensityund eine Bitmap mit der gewünschten Skalierung inTargetDensitygeladen. inDensityist0 zunächst, wenn keine zeichnbare Ressource geladen wird, daher dient dieser Ansatz zum Laden von Nicht-Ressourcen-Bildern.

Die Variablen imageUri, maxImageSideLengthund contextsind Parameter meiner Methode. Aus Gründen der Übersichtlichkeit habe ich nur die Methodenimplementierung ohne das Umschließen von AsyncTask veröffentlicht.

            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
quelle
2
Sehr schön! Die Verwendung von inDensity anstelle von Bitmap.createScaledBitmap hat mir viel Speicherplatz gespart. Noch besser kombiniert mit inSamplesize.
Ostkontentitan
2

In Anbetracht dessen, dass Sie die Größe auf die exakte Größe anpassen und so viel Qualität wie nötig beibehalten möchten, sollten Sie dies versuchen.

  1. Ermitteln Sie die Größe des Bilds mit geänderter Größe, indem Sie BitmapFactory.decodeFile aufrufen und checkSizeOptions.inJustDecodeBounds bereitstellen
  2. Berechnen Sie das maximal mögliche inSampleSize, das Sie auf Ihrem Gerät verwenden können, um den Speicher nicht zu überschreiten. bitmapSizeInBytes = 2 * width * height; Im Allgemeinen wäre für Ihr Bild inSampleSize = 2 in Ordnung, da Sie nur 2 * 1944x1296) = 4,8 MBit benötigen, was im Speicher bleiben sollte
  3. Verwenden Sie BitmapFactory.decodeFile mit inSampleSize, um die Bitmap zu laden
  4. Skalieren Sie die Bitmap auf die exakte Größe.

Motivation: Eine mehrstufige Skalierung kann zu einer höheren Bildqualität führen. Es gibt jedoch keine Garantie dafür, dass sie besser funktioniert als die Verwendung von High inSampleSize. Eigentlich denke ich, dass Sie auch inSampleSize wie 5 (nicht pow of 2) verwenden können, um eine direkte Skalierung in einem Vorgang zu erzielen. Oder verwenden Sie einfach 4 und dann können Sie dieses Bild einfach in der Benutzeroberfläche verwenden. Wenn Sie es an den Server senden, können Sie es auf der Serverseite auf die exakte Größe skalieren, sodass Sie erweiterte Skalierungstechniken verwenden können.

Hinweise: Wenn die in Schritt 3 geladene Bitmap mindestens viermal größer ist (also die 4 * Zielbreite <Breite), können Sie wahrscheinlich mehrere Größenänderungen verwenden, um eine bessere Qualität zu erzielen. Zumindest funktioniert das in generischem Java. In Android haben Sie nicht die Möglichkeit, die für die Skalierung verwendete Interpolation anzugeben. http://today.java.net/pub/a/today/2007/04/03/perils-of- image-getscaledinstance.html

Andrey Chorniy
quelle
2

Ich habe folgenden Code verwendet:

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

Ich habe versucht, das Originalbild ist 1230 x 1230, und die Bitmap lautet 330 x 330.
Und wenn ich 2590 x 3849 ausprobiert habe, habe ich OutOfMemoryError.

Ich habe es verfolgt, es wirft immer noch OutOfMemoryError in die Zeile "BitmapFactory.decodeStream (is, null, options);", wenn die ursprüngliche Bitmap zu groß ist ...

RRTW
quelle
2

Der obige Code wurde etwas sauberer. InputStreams haben endlich eine enge Umhüllung, um sicherzustellen, dass sie auch geschlossen werden:

* Hinweis
Eingabe: InputStream ist, int w, int h
Ausgabe: 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");

    }
user1327738
quelle
1

Um das Bild auf die "richtige" Weise zu skalieren, ohne Pixel zu überspringen, müssten Sie sich in den Bilddecoder einhängen, um das Downsampling zeilenweise durchzuführen. Android (und die zugrunde liegende Skia-Bibliothek) bietet keine solchen Hooks, sodass Sie Ihre eigenen rollen müssten. Angenommen, Sie sprechen von JPEG-Bildern, ist es am besten, libjpeg direkt in C zu verwenden.

Angesichts der Komplexität ist die Verwendung der zweistufigen Teilstichprobe und dann der Neuskalierung wahrscheinlich am besten für Apps vom Typ Bildvorschau geeignet.

D0SBoots
quelle
1

Wenn Sie unbedingt eine Schrittgröße ändern möchten, können Sie wahrscheinlich die gesamte Bitmap laden, wenn android: largeHeap = true, aber wie Sie sehen, ist dies nicht wirklich ratsam.

Aus docs: android: largeHeap Gibt an, ob die Prozesse Ihrer Anwendung mit einem großen Dalvik-Heap erstellt werden sollen. Dies gilt für alle für die Anwendung erstellten Prozesse. Dies gilt nur für die erste in einen Prozess geladene Anwendung. Wenn Sie eine gemeinsam genutzte Benutzer-ID verwenden, um mehreren Anwendungen die Verwendung eines Prozesses zu ermöglichen, müssen alle diese Option konsistent verwenden, da sonst unvorhersehbare Ergebnisse erzielt werden. Die meisten Apps sollten dies nicht benötigen und sich stattdessen darauf konzentrieren, die Gesamtspeicherauslastung zu reduzieren, um die Leistung zu verbessern. Das Aktivieren dieser Option garantiert auch keine feste Erhöhung des verfügbaren Speichers, da einige Geräte durch ihren insgesamt verfügbaren Speicher eingeschränkt sind.

Igor Čordaš
quelle
0

Das hat bei mir funktioniert. Die Funktion erhält einen Pfad zu einer Datei auf der SD-Karte und gibt eine Bitmap in der maximal anzeigbaren Größe zurück. Der Code stammt von Ofir mit einigen Änderungen wie Bilddatei auf SD statt einer Ressource und die Breite und Höhe werden vom Anzeigeobjekt abgerufen.

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

}
Penta
quelle
0

Hier ist der Code, den ich verwende und der keine Probleme beim Dekodieren großer Bilder im Speicher von Android hat. Ich konnte Bilder mit einer Größe von mehr als 20 MB dekodieren, solange meine Eingabeparameter bei 1024 x 1024 liegen. Sie können die zurückgegebene Bitmap in einer anderen Datei speichern. Unter dieser Methode befindet sich eine weitere Methode, mit der ich Bilder auch auf eine neue Bitmap skaliere. Fühlen Sie sich frei, diesen Code zu verwenden, wie Sie möchten.

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

HINWEIS: Methoden haben nichts miteinander zu tun, außer die oben beschriebene Dekodierungsmethode für createScaledBitmap-Aufrufe. Beachten Sie, dass sich Breite und Höhe vom Originalbild ändern können.

/*****************************************************************************
 * 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
user560663
quelle
Die Leistungsberechnung für die Skala ist hier einfach falsch. Verwenden Sie einfach die Berechnung auf der Android Doco Seite.
Fattie
0
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

oder:

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

Ich verwende Integer.numberOfLeadingZeros, um die beste Stichprobengröße und bessere Leistung zu berechnen.

Vollständiger Code in 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
quelle
-2

Ändern Sie die Größe der Bitmap mithilfe des folgenden Codes

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

Das gleiche wird auch im folgenden Tipp / Trick erklärt

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

Amol
quelle