2010-07-20 20 views
12

Ich habe Java-Programm, das eine JPEG-Datei von der Festplatte liest und es als Hintergrundbild für verschiedene andere Dinge verwendet. Das Bild selbst ist wie so in einem BufferImage Objekt gespeichert:Machen Sie ein BufferedImage verwenden weniger RAM?

BufferedImage background 
background = ImageIO.read(file) 

Dies funktioniert gut - das Problem ist, dass das BufferedImage Objekt selbst ist enorm. Zum Beispiel wird eine 215k-JPEG-Datei zu einem BufferedImag e-Objekt, das 4 MB groß ist und sich ändert. Die fragliche App kann einige ziemlich große Hintergrundbilder geladen haben, aber während die JPEGs nie mehr als ein oder zwei Megabyte sind, kann der Speicher, der zum Speichern der BufferedImage verwendet wird, 100 MB schnell überschreiten.

Ich nehme an, das alles ist, weil das Bild im RAM als rohe RGB-Daten gespeichert wird, nicht komprimiert oder in keiner Weise optimiert.

Gibt es eine Möglichkeit, das Bild im RAM in einem kleineren Format speichern zu lassen? Ich bin in einer Situation, in der ich auf der CPU-Seite mehr Kapazität als RAM habe, so dass ein leichter Performance-Hit, um die Größe des Bildobjekts in Richtung der JPEG-Komprimierung zurückzubekommen, es wert wäre.

Antwort

1

Dateigröße des JPG auf der Festplatte ist vollständig irrelevant.
Die Pixelmaße der Datei sind. Wenn Ihr Bild 15 Megapixel beträgt, wird erwartet, dass es eine RAM-Ladung benötigt, um eine unkomprimierte Rohversion zu laden.
Ändern Sie die Bildgröße so, dass sie genau das ist, was Sie brauchen, und das ist das Beste, was Sie tun können, ohne zu einer weniger reichen Farbraumdarstellung zu wechseln.

+1

lollipop: Tut mir leid, aber * a) * Sie antworten nicht (daher die -1) und * b) * Ihr Kommentar, dass es * irrelevant * ist ** HOCH ** irreführend (zu schlecht Ich kann nicht geben -2). Das OP-Problem besteht offensichtlich darin, dass die JPEG-Komprimierung bei einer Art von Bild ** SEHR effizient ist, und daher ist er überrascht, die komprimierte JPEG/unkomprimierte (A) RGB-Größendifferenz zu sehen. Sie helfen nicht in irgendeiner Weise. – SyntaxT3rr0r

+3

Es ist irrelevant, wie groß die Quelldatei ist. Genauso wie der Versuch, eine 1 GB TXT-Datei zu lesen, die gezippt wurde, ist irrelevant für die Größe der ZIP-Datei. Die Größe der Dateigröße der einzelnen Datenträger hat nichts mit der Speichermenge zu tun, die zum Anzeigen des unkomprimierten Bilds benötigt wird. –

+1

die Antwort ist da, Größe ändern Sie das Bild zu kleineren Abmessungen –

0

Sie könnten die Pixel des Bildes in einen anderen Puffer kopieren und sehen, ob das weniger Speicherplatz belegt als das BufferedImage-Objekt. Wahrscheinlich so etwas wie diese:

BufferedImage background= new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 

int[] pixels = background.getRaster().getPixels(0, 0, imageBuffer.getWidth(), imageBuffer.getHeight(), (int[]) null); 
+0

große Codebeispiel. Würden Sie etwas mehr Pausen hinzufügen, um das Scrollen zu vermeiden? – Dinah

3

ich das alles übernehmen, weil das Bild ist Daten im RAM als Ausgang RGB gespeichert wird, nicht komprimiert oder in irgendeine Weise optimieren.

Genau ... Sprich ein JPG 1920x1200 passen kann, sagen wir, 300 KB, während im Speicher, in einem (typisch) RGB + alpha, 8 Bit pro Komponente (daher 32 Bits pro Pixel) es soll belegen, Speicher:

1920 x 1200 x 32/8 = 9 216 000 bytes 

so Ihre 300 KB Datei wird ein Bild fast 9 MB RAM benötigt (beachten Sie, dass Sie verwenden von Java nach Art der Bilder und in Abhängigkeit von der JVM und OS dies manchmal sein kann, GFX-Karten-RAM).

Wenn Sie ein Bild als Hintergrund eines 1920x1200 Desktops verwenden möchten, brauchen Sie wahrscheinlich kein Bild größer als das im Speicher (es sei denn, Sie möchten einen Spezialeffekt wie Sub-RGB-Dezimierung/Farbe) Anti-Aliasing/etc.).

So haben Sie zur Auswahl:

  1. macht Ihre Dateien weniger breit und weniger hoch (in Pixeln) auf der Festplatte
  2. die Bildgröße auf dem
  3. fly reduzieren

ich in der Regel gehen mit Nummer 2, weil das Reduzieren der Dateigröße auf der Festplatte bedeutet, dass Sie Details verlieren (ein 1920x1200 Bild ist weniger detailliert als das "Gleiche" bei 3940x2400): Sie würden "Informationen verlieren", indem Sie es verkleinern.

Jetzt ist Java ein bisschen saugt große Zeit bei der Manipulation von Bildern so groß (sowohl aus Sicht der Leistung, eine Sicht der Speicherauslastung und eine Qualität Sicht [*]). Früher habe ich ImageMagick von Java angerufen, um das Bild zuerst auf der Festplatte zu skalieren und dann das Bild in der Größe zu laden (z. B. passend zu meiner Bildschirmgröße).

Heutzutage gibt es Java Bridges/APIs, um direkt mit ImageMagick zu verbinden.

[*] Es gibt KEIN WEG Sie verkleinern ein Bild mit der integrierten API von Java so schnell und mit einer Qualität, wie sie von ImageMagick zur Verfügung gestellt wird.

2

Müssen Sie BufferedImage verwenden? Könnten Sie Ihre eigene Image Implementierung schreiben, die die jpg Bytes im Speicher speichert und nach Bedarf zu einem BufferedImage konvertiert und dann ablegt?

Dies galt mit einigen Display bewusst Logik (skalieren Sie das Bild mit JAI vor dem Speichern in Ihrem Byte-Array als jpg), wird es schneller als die Decodierung der großen jpg jedes Mal, und eine kleinere Stellfläche als was Sie derzeit haben (Verarbeitung Speicheranforderungen ausgenommen).

9

Eines meiner Projekte Ich nehme das Bild herunter, während es gerade von einem ImageStream gelesen wird. Das Downsampling reduziert die Abmessungen des Bildes auf eine erforderliche Breite von &, während keine kostspieligen Größenänderungsberechnungen oder Modifikationen des Bildes auf der Platte erforderlich sind.

Da ich das Bild auf eine kleinere Größe verkleinere, reduziert es auch deutlich die Rechenleistung und den Arbeitsspeicher, die für die Anzeige benötigt werden. Für zusätzliche Optimierung rendere ich das gepufferte Bild in Kacheln auch ... Aber das ist ein wenig außerhalb des Rahmens dieser Diskussion. Versuchen Sie Folgendes:

public static BufferedImage subsampleImage(
    ImageInputStream inputStream, 
    int x, 
    int y, 
    IIOReadProgressListener progressListener) throws IOException { 
    BufferedImage resampledImage = null; 

    Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream); 

    if(!readers.hasNext()) { 
     throw new IOException("No reader available for supplied image stream."); 
    } 

    ImageReader reader = readers.next(); 

    ImageReadParam imageReaderParams = reader.getDefaultReadParam(); 
    reader.setInput(inputStream); 

    Dimension d1 = new Dimension(reader.getWidth(0), reader.getHeight(0)); 
    Dimension d2 = new Dimension(x, y); 
    int subsampling = (int)scaleSubsamplingMaintainAspectRatio(d1, d2); 
    imageReaderParams.setSourceSubsampling(subsampling, subsampling, 0, 0); 

    reader.addIIOReadProgressListener(progressListener); 
    resampledImage = reader.read(0, imageReaderParams); 
    reader.removeAllIIOReadProgressListeners(); 

    return resampledImage; 
    } 

public static long scaleSubsamplingMaintainAspectRatio(Dimension d1, Dimension d2) { 
    long subsampling = 1; 

    if(d1.getWidth() > d2.getWidth()) { 
     subsampling = Math.round(d1.getWidth()/d2.getWidth()); 
    } else if(d1.getHeight() > d2.getHeight()) { 
     subsampling = Math.round(d1.getHeight()/d2.getHeight()); 
    } 

    return subsampling; 
    } 

Um die ImageInputStream aus einer Datei zu erhalten, verwenden:

ImageIO.createImageInputStream(new File("C:\\image.jpeg")); 

Wie Sie sehen können, respektiert diese Implementierung die Bilder als auch Original-Seitenverhältnis. Sie können optional einen IIOReadProgressListener registrieren, damit Sie verfolgen können, wie viel von dem Bild bisher gelesen wurde. Dies ist nützlich, um einen Fortschrittsbalken anzuzeigen, wenn das Bild zum Beispiel über ein Netzwerk gelesen wird ... Nicht erforderlich, aber Sie können einfach null angeben.

Warum ist das für Ihre Situation besonders relevant? Es liest niemals das gesamte Bild in den Speicher, genauso wenig wie es benötigt wird, damit es in der gewünschten Auflösung angezeigt werden kann. Funktioniert wirklich gut für große Bilder, sogar solche, die 10 von MB auf der Festplatte sind.

+0

Dies ist nützlich und eine gute schnelle Lösung. Es gibt einige Nachteile: Es funktioniert nur, wenn das Bild um 2x oder größer geschrumpft werden muss (wegen des vereinfachten Algorithmus, der verwendet wird, um höchstens jede andere Quellenspalte zu proben) und ich habe auch eine Qualitätsverschlechterung in einem kleinen% -Beispiel bemerkt Bilder. –

2

Verwendung imgscalr:

http://www.thebuzzmedia.com/software/imgscalr-java-image-scaling-library/

Warum?

  1. Folgt Best Practices
  2. dumme einfache
  3. Interpolation, Anti-Aliasing-Unterstützung
  4. So sind Sie nicht Ihre eigene Skalierung Bibliothek

Rolling Code:

BufferedImage thumbnail = Scalr.resize(image, 150); 

or 

BufferedImage thumbnail = Scalr.resize(image, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_WIDTH, 150, 100, Scalr.OP_ANTIALIAS); 

Verwenden Sie auch image.flush() auf Ihrem größeres Bild nach der Konvertierung, um bei der Speichernutzung zu helfen.

+4

Muss Scalr das Originalbild nicht vollständig als BufferedImage (die Variable image) dorthin laden? Ich war gerade dabei, imgscalr zu benutzen, aber mein Original ist zu groß, um es zu starten. – Kelly

Verwandte Themen