2010-07-20 14 views
12

Tengo el programa java que lee un archivo jpeg del disco duro y lo usa como imagen de fondo para varias otras cosas. La imagen en sí se almacena en un objeto BufferImage así:¿Cómo hacer una imagen almacenada? ¿Usa menos memoria RAM?

BufferedImage background 
background = ImageIO.read(file) 

Esto funciona muy bien - el problema es que el objeto BufferedImage en sí es enorme. Por ejemplo, un archivo jpeg de 215k se convierte en un objeto BufferedImag que tiene 4 megas y cambia. La aplicación en cuestión puede tener algunas imágenes de fondo bastante grandes cargadas, pero mientras que los jpegs nunca son más que un meg o dos, la memoria utilizada para almacenar el BufferedImage puede superar rápidamente los 100 s de megabytes.

Supongo que todo esto se debe a que la imagen se está almacenando en RAM como datos RGB sin procesar, no comprimidos u optimizados de ninguna manera.

¿Hay alguna manera de almacenar la imagen en RAM en un formato más pequeño? Estoy en una situación en la que tengo más holgura en el lado de la CPU que en la RAM, por lo que un pequeño golpe de rendimiento para recuperar el tamaño del objeto de imagen hacia la compresión de jpeg valdría la pena.

Respuesta

1

Tamaño de la JPG en el disco es completamente irrelevante .
Las dimensiones en píxeles del archivo son. Si su imagen es de 15 megapíxeles, se espera que requiera una gran cantidad de RAM para cargar una versión sin comprimir.
Cambie el tamaño de las dimensiones de su imagen para que sea exactamente lo que necesita y es lo mejor que puede hacer sin tener que recurrir a una representación del espacio de color menos rica.

+1

piruleta: Lo siento, pero * a) * no está respondiendo (de ahí el -1) y * b) * Su comentario * que es "irrelevante" * es ALTAMENTE ** ** engañosa (lástima No puedo dar -2). El problema de la OP es, obviamente, que la compresión JPEG es ** ** muy eficiente en algún tipo de imagen y, por tanto, que está sorprendido de ver la diferencia sin comprimir comprimido JPEG/(A) RGB Tamaño. No estás ayudando de ninguna manera. – SyntaxT3rr0r

+3

Es irrelevante qué tan grande es el archivo fuente. Al igual que cuando se intenta leer un archivo TXT de 1GB que se ha comprimido, es irrelevante el tamaño del archivo zip. El tamaño del archivo de un disco __ no tiene nada que ver con la cantidad de memoria que se necesita para mostrar la imagen descomprimida. –

+1

la respuesta es no, cambiar el tamaño de la imagen a ser menores dimensiones –

0

Puede copiar los píxeles de la imagen en otro búfer y ver si eso ocupa menos memoria que el objeto BufferedImage. Probablemente algo como esto:

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

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

gran ejemplo de código. ¿Te importaría agregar algunos descansos más para evitar el desplazamiento? – Dinah

3

Asumo todo esto se debe a que la imagen se almacena en la memoria RAM como RGB prima datos, no comprimido u optimizados en ninguna manera.

Exactamente ... dicen que un JPG 1920x1200 puede caber en, digamos, 300 KB, mientras que en la memoria, en un (típico) RGB + alfa, 8 bits por componente (por lo tanto, 32 bits por píxel) se ocuparán, en la memoria:

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

por lo que su archivo de KB 300 se convierte en una imagen necesitan cerca de 9 MB de RAM (tenga en cuenta que, dependiendo del tipo de imágenes que está utilizando desde Java y en función de la JVM y el sistema operativo puede ser a veces GFX-card RAM).

Si desea utilizar una imagen como fondo de una computadora de escritorio de 1920x1200, probablemente no necesite una imagen más grande que la de la memoria (a menos que desee algún efecto especial, como decimación/color sub-rgb anti-aliasing/etc.).

lo que tiene que elegir:

  1. hace que sus archivos de menos de ancho y menos de altura (en píxeles) en el disco
  2. reducir el tamaño de la imagen sobre la marcha

normalmente voy con número 2 porque reducir el tamaño del archivo en el disco duro significa que está perdiendo detalles (una imagen de 1920x1200 es menos detallada que la "misma" en 3940x2400: estaría "perdiendo información" reduciéndola).

Ahora, Java chupa un poco grandes momentos en la manipulación de imágenes tan grande (tanto desde el punto de vista del rendimiento, un punto de vista el uso de memoria, y un punto de vista de la calidad [*]). En los días en que yo llamaría a ImageMagick de Java para cambiar el tamaño de la imagen en el disco primero, y luego cargar la imagen redimensionada (por ejemplo, ajustar el tamaño de mi pantalla).

Hoy en día existen puentes/API de Java para interactuar directamente con ImageMagick.

[*] Hay NO WAY está reduciendo el tamaño de una imagen usando la API incorporada de Java tan rápido y con una calidad tan buena como la proporcionada por ImageMagick, para empezar.

2

¿Tienes que usar BufferedImage? ¿Podría escribir su propia implementación Image que almacena los bytes jpg en la memoria y se convierte en una Imagen Buffered según sea necesario y luego se descarta?

Aplicado con lógica de visualización (reescalar la imagen usando JAI antes de almacenar en su matriz de bytes como jpg), lo hará más rápido que decodificar el jpg grande cada vez, y una huella más pequeña que la que tiene actualmente (procesamiento requisitos de memoria exceptuados).

9

Uno de mis proyectos que acabo de muestrear la imagen, ya que se está leyendo desde un ImageStream sobre la marcha. El muestreo descendente reduce las dimensiones de la imagen a un ancho requerido de & de altura sin requerir costosos cálculos de cambio de tamaño o modificación de la imagen en el disco.

Como hago un down-sample de la imagen a un tamaño más pequeño, también reduce significativamente la potencia de procesamiento y la RAM requeridas para mostrarla. Para una optimización adicional, también renderizo la imagen en búfer en mosaicos ... Pero eso está un poco fuera del alcance de esta discusión. Pruebe lo siguiente:

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

Para obtener el ImageInputStream de un archivo, utilice:

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

Como se puede ver, esta aplicación respeta la relación de aspecto original de las imágenes también. Opcionalmente, puede registrar un IIOReadProgressListener para que pueda realizar un seguimiento de la cantidad de imágenes que se han leído hasta el momento. Esto es útil para mostrar una barra de progreso si la imagen se lee en una red, por ejemplo ... No es necesario, solo puede especificar nulo.

¿Por qué es esto de particular relevancia para su situación? Nunca lee la imagen completa en la memoria, tanto como lo necesita para que se pueda mostrar con la resolución deseada. Funciona muy bien para imágenes enormes, incluso aquellas que son 10 de MB en disco.

+0

Esto es útil y una buena solución rápida. Hay algunas desventajas: sólo funciona cuando la imagen tiene que ser reducido por 2x o mayor (debido al algoritmo simplista utilizado para el muestreo como máximo al otro cada columna de origen) y también he notado cierta degradación de la calidad en un pequeño% de ejemplo imágenes. –

2

Uso imgscalr:

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

¿Por qué?

  1. sigue las mejores prácticas
  2. estúpida sencilla
  3. interpolación, el soporte anti-aliasing
  4. Por lo que no está rodando su propia biblioteca de escalamiento

Código:

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

Asimismo, el uso image.flush() en su imagen más grande después de la conversión para ayudar con la utilización de la memoria.

+4

No requiere Scalr la imagen original para ser cargado en un BufferedImage (la variable 'image' allí) en su totalidad por primera vez? En realidad estaba en el medio de usar imgscalr, pero mi original es demasiado grande para comenzar. – Kelly

Cuestiones relacionadas