¿Cómo obtener la altura y el ancho de la imagen usando java?

106

¿Hay alguna otra forma además de usar ImageIO.read para obtener la altura y el ancho de la imagen?

Porque encuentro un problema que bloquea el hilo.

at com.sun.medialib.codec.jpeg.Decoder.njpeg_decode(Native Method)      
at com.sun.medialib.codec.jpeg.Decoder.decode(Decoder.java:87)      
at com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader.decode(CLibJPEGImageReader.java:73)     
 - locked <0xd96fb668> (a com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader)      
at com.sun.media.imageioimpl.plugins.clib.CLibImageReader.getImage(CLibImageReader.java:320)    
 - locked <0xd96fb668> (a com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader)     
 at com.sun.media.imageioimpl.plugins.clib.CLibImageReader.read(CLibImageReader.java:384)   
 - locked <0xd96fb668> (a com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader)      
at javax.imageio.ImageIO.read(ImageIO.java:1400)      
at javax.imageio.ImageIO.read(ImageIO.java:1322)

Este error solo ocurre en un servidor de aplicaciones de Sun y, por lo tanto, sospecho que es un error de Sun.

plata est
fuente
¿Qué error? Solo muestra una parte del seguimiento de la pila (que parece provenir de jstack).
Joachim Sauer
¿Ha encontrado la causa o solución a este problema? Tengo el mismo problema en el que está bloqueando el hilo en ese mismo método.
Timothy Chen
Hay un error que podría estar relacionado: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6791502
Adam Schmideg

Respuestas:

288

Aquí hay algo muy simple y útil.

BufferedImage bimg = ImageIO.read(new File(filename));
int width          = bimg.getWidth();
int height         = bimg.getHeight();
Apurv
fuente
7
Esta es la mejor respuesta con mucho, y la misma respuesta publicada por otra persona 17 días después de tu publicación te ha quitado los votos. Esta debería ser la respuesta superior, no la inferior.
Oversteer
34
De todo lo que estoy leyendo, esto lee la imagen completa en la memoria. Lo cual es extremo solo para obtener ancho y alto.
Marc
7
mala manera: necesitará cargar todo el ráster de la imagen en la memoria, lo que causa OOM con imágenes muy grandes
yetanothercoder
4
La pregunta pide específicamente una forma distinta de usar ImageIO.read. Su respuesta es "use ImageIO.read". ¿Por qué se considera esto una buena respuesta?
ssimm
5
Esta es la peor manera de hacerlo y no entiendo por qué se vota tanto. Carga toda la imagen en el montón, lo que es lento y consume mucha memoria innecesaria.
Patrick Favre
72

Esta es una reescritura de la gran publicación de @Kay, que arroja IOException y proporciona una salida anticipada:

/**
 * Gets image dimensions for given file 
 * @param imgFile image file
 * @return dimensions of image
 * @throws IOException if the file is not a known image
 */
public static Dimension getImageDimension(File imgFile) throws IOException {
  int pos = imgFile.getName().lastIndexOf(".");
  if (pos == -1)
    throw new IOException("No extension for file: " + imgFile.getAbsolutePath());
  String suffix = imgFile.getName().substring(pos + 1);
  Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix);
  while(iter.hasNext()) {
    ImageReader reader = iter.next();
    try {
      ImageInputStream stream = new FileImageInputStream(imgFile);
      reader.setInput(stream);
      int width = reader.getWidth(reader.getMinIndex());
      int height = reader.getHeight(reader.getMinIndex());
      return new Dimension(width, height);
    } catch (IOException e) {
      log.warn("Error reading: " + imgFile.getAbsolutePath(), e);
    } finally {
      reader.dispose();
    }
  }

  throw new IOException("Not a known image file: " + imgFile.getAbsolutePath());
}

Supongo que mi reputación no es lo suficientemente alta como para que mi entrada se considere digna de respuesta.

Andrew Taylor
fuente
¡Gracias por esto! y teniendo en cuenta la comparación de rendimiento realizada por user194715, tomaré su sugerencia para el rendimiento y consideración de png. ¡Gracias!
ah-shiang han
¿No podría usar probeContentType del paquete nio.Files combinado con javax.imageio.ImageIO.getImageReadersByMIMEType(mimeType)si quisiera estar seguro del tipo de archivo?
EdgeCaseBerg
3
Además, ¿no debería llamar .close()en la transmisión antes de regresar? de lo contrario, deja la transmisión abierta
EdgeCaseBerg
1
@ php_coder_3809625 el registro está en el iterador, por lo que podría darse el caso de que un ImageReader falle, pero el siguiente tenga éxito. Si todos fallan, se genera una IOException.
Andrew Taylor
1
Podría considerar usar org.apache.commons.io.FilenameUtils#getExtensionpara detectar la extensión del nombre de archivo.
Patrick Bergner
52

Encontré otra forma de leer el tamaño de una imagen (más genérica). Puede utilizar la clase ImageIO en cooperación con ImageReaders. Aquí está el código de ejemplo:

private Dimension getImageDim(final String path) {
    Dimension result = null;
    String suffix = this.getFileSuffix(path);
    Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix);
    if (iter.hasNext()) {
        ImageReader reader = iter.next();
        try {
            ImageInputStream stream = new FileImageInputStream(new File(path));
            reader.setInput(stream);
            int width = reader.getWidth(reader.getMinIndex());
            int height = reader.getHeight(reader.getMinIndex());
            result = new Dimension(width, height);
        } catch (IOException e) {
            log(e.getMessage());
        } finally {
            reader.dispose();
        }
    } else {
        log("No reader found for given format: " + suffix));
    }
    return result;
}

Tenga en cuenta que getFileSuffix es un método que devuelve la extensión de la ruta sin "." por ejemplo: png, jpg, etc. La implementación de ejemplo es:

private String getFileSuffix(final String path) {
    String result = null;
    if (path != null) {
        result = "";
        if (path.lastIndexOf('.') != -1) {
            result = path.substring(path.lastIndexOf('.'));
            if (result.startsWith(".")) {
                result = result.substring(1);
            }
        }
    }
    return result;
}

Esta solución es muy rápida ya que solo se lee el tamaño de la imagen del archivo y no la imagen completa. Lo probé y no hay comparación con el rendimiento de ImageIO.read. Espero que alguien lo encuentre útil.

user350756
fuente
getFileSuffix()contiene ifs innecesarios y poner sus iniciales con nullno es una buena idea en este caso.
Jimmy T.
2
¡Demonios, sí, esto es "básicamente muy rápido"! Creo que te has clasificado para el premio a la "subestimación del año" con ese. Sopla ImageIO.read()completamente fuera del agua, tanto en términos de tiempo de CPU como de uso de memoria.
aroth
1
public static String getFileSuffix (ruta de cadena final) {if (ruta! = nula && ruta.lastIndexOf ('.')! = -1) {retorno ruta.substring (ruta.lastIndexOf ('.')). substring (1) ; } return null; }
Nilanchal
47

Intenté probar el rendimiento utilizando algunos de los diversos enfoques enumerados. Es difícil hacer una prueba rigurosa ya que muchos factores afectan el resultado. Preparé dos carpetas, una con 330 archivos jpg y otra con 330 archivos png. El tamaño medio del archivo fue de 4 Mb en ambos casos. Luego llamé a getDimension para cada archivo. Cada implementación del método getDimension y cada tipo de imagen se probaron por separado (ejecución separada). Aquí están los tiempos de ejecución que obtuve (primer número para jpg, segundo número para png):

1(Apurv) - 101454ms, 84611ms
2(joinJpegs) - 471ms, N/A
3(Andrew Taylor) - 707ms, 68ms
4(Karussell, ImageIcon) - 106655ms, 100898ms
5(user350756) - 2649ms, 68ms

Es obvio que algunos métodos cargan todo el archivo para obtener dimensiones, mientras que otros lo hacen simplemente leyendo algo de información del encabezado de la imagen. Creo que estos números pueden ser útiles cuando el rendimiento de la aplicación es crítico.

Gracias a todos por la contribución a este hilo, muy útil.

mp31415
fuente
3
Ahora esa es una respuesta. ¡Buen trabajo!
ssimm
1
¿También analizó el uso del espacio en la pila cuando estaba cargando imágenes? Además, ¿obtuvo algún error OOM al ejecutar estas pruebas en alguno de los métodos?
saibharath
Gracias tu respuesta me ayudó mucho. Tengo (50k imagen HD)
SüniÚr
17

Puede cargar datos binarios jpeg como un archivo y analizar los encabezados jpeg usted mismo. El que está buscando es el encabezado 0xFFC0 o Start of Frame:

Start of frame marker (FFC0)

* the first two bytes, the length, after the marker indicate the number of bytes, including the two length bytes, that this header contains
* P -- one byte: sample precision in bits (usually 8, for baseline JPEG)
* Y -- two bytes
* X -- two bytes
* Nf -- one byte: the number of components in the image
      o 3 for color baseline JPEG images
      o 1 for grayscale baseline JPEG images

* Nf times:
      o Component ID -- one byte
      o H and V sampling factors -- one byte: H is first four bits and V is second four bits
      o Quantization table number-- one byte

The H and V sampling factors dictate the final size of the component they are associated with. For instance, the color space defaults to YCbCr and the H and V sampling factors for each component, Y, Cb, and Cr, default to 2, 1, and 1, respectively (2 for both H and V of the Y component, etc.) in the Jpeg-6a library by the Independent Jpeg Group. While this does mean that the Y component will be twice the size of the other two components--giving it a higher resolution, the lower resolution components are quartered in size during compression in order to achieve this difference. Thus, the Cb and Cr components must be quadrupled in size during decompression.

Para obtener más información sobre los encabezados, consulte la entrada jpeg de wikipedia o obtuve la información anterior aquí .

Usé un método similar al código a continuación que obtuve de esta publicación en los foros de Sun:

import java.awt.Dimension;
import java.io.*;

public class JPEGDim {

public static Dimension getJPEGDimension(File f) throws IOException {
    FileInputStream fis = new FileInputStream(f);

    // check for SOI marker
    if (fis.read() != 255 || fis.read() != 216)
        throw new RuntimeException("SOI (Start Of Image) marker 0xff 0xd8 missing");

    Dimension d = null;

    while (fis.read() == 255) {
        int marker = fis.read();
        int len = fis.read() << 8 | fis.read();

        if (marker == 192) {
            fis.skip(1);

            int height = fis.read() << 8 | fis.read();
            int width = fis.read() << 8 | fis.read();

            d = new Dimension(width, height);
            break;
        }

        fis.skip(len - 2);
    }

    fis.close();

    return d;
}

public static void main(String[] args) throws IOException {
    System.out.println(getJPEGDimension(new File(args[0])));
}

}

joinJpegs
fuente
Bueno. Pero creo que en lugar de ==192eso debería marcar los números 192-207, excepto 196, 200 y 204.
vortexwolf
O puede usar la com.drewnoakes.metadata-extractorbiblioteca para extraer fácilmente estos encabezados
Victor Petit
9

Manera simple:

BufferedImage readImage = null;

try {
    readImage = ImageIO.read(new File(your path);
    int h = readImage.getHeight();
    int w = readImage.getWidth();
} catch (Exception e) {
    readImage = null;
}
usuario1215499
fuente
3
esto necesita leer la imagen completa en la memoria solo para conocer el ancho y el alto. Sí, es simple, pero funcionará mal para muchas imágenes o para imágenes grandes ...
Clint Eastwood
4

El problema con ImageIO.read es que es muy lento. Todo lo que necesita hacer es leer el encabezado de la imagen para obtener el tamaño. ImageIO.getImageReaderes candidato perfecto.

Aquí está el ejemplo de Groovy, pero lo mismo se aplica a Java

def stream = ImageIO.createImageInputStream(newByteArrayInputStream(inputStream))
def formatReader = ImageIO.getImageWritersByFormatName(format).next() 
def reader = ImageIO.getImageReader(formatReader)
reader.setInput(stream, true)

println "width:reader.getWidth(0) -> height: reader.getHeight(0)"

El rendimiento fue el mismo que con la biblioteca java SimpleImageInfo.

https://github.com/cbeust/personal/blob/master/src/main/java/com/beust/SimpleImageInfo.java

Argoden
fuente
¿Qué es getReaderByFormat?
Koray Tugay
3

Puede usar el kit de herramientas, sin necesidad de ImageIO

Image image = Toolkit.getDefaultToolkit().getImage(file.getAbsolutePath());
int width = image.getWidth(null);
int height = image.getHeight(null);

Si no desea manejar la carga de la imagen, hágalo

ImageIcon imageIcon = new ImageIcon(file.getAbsolutePath());
int height = imageIcon.getIconHeight();
int width = imageIcon.getIconWidth();
Karussell
fuente
1
No se necesita ImageIO pero sí se necesita Toolkit. ¿Cuál es la diferencia?
persona que hace dieta
ImageIO es una dependencia externa. Kit de herramientas no
Karussell
ImageIO es parte de Java desde 1.4 docs.oracle.com/javase/7/docs/api/javax/imageio/…
dieter
Sí, tal vez esto funcione, lo siento si esto fue confuso entonces. Cuando lo probé, necesitaba para algunas partes lo de JAI (¿tal vez para leer otros formatos?): Stackoverflow.com/questions/1209583/…
Karussell
3

Puede obtener el ancho y el alto de la imagen con el objeto BufferedImage usando java.

public void setWidthAndHeightImage(FileUploadEvent event){
    byte[] imageTest = event.getFile().getContents();
                baiStream = new ByteArrayInputStream(imageTest );
                BufferedImage bi = ImageIO.read(baiStream);
                //get width and height of image
                int imageWidth = bi.getWidth();
                int imageHeight = bi.getHeight();
    }
KIBOU Hassan
fuente
esto cargará la imagen completa en la memoria, muy derrochadora y muy lenta.
argoden
1

Obtener una imagen almacenada en búfer con ImageIO.read es un método muy pesado, ya que crea una copia completa sin comprimir de la imagen en la memoria. Para png, también puede usar pngj y el código:

if (png)
    PngReader pngr = new PngReader(file);
    width = pngr.imgInfo.cols;
    height = pngr.imgInfo.rows;
    pngr.close();
}
Jessi
fuente
0

Habiendo luchado con ImageIOmuchas cosas en los últimos años, creo que la solución de Andrew Taylor es, con mucho, el mejor compromiso (rápido: sin uso y versátil). ¡¡Gracias hombre!!ImageIO#read

Pero me sentí un poco frustrado al verme obligado a usar un archivo local (Archivo / Cadena), especialmente en los casos en los que desea verificar los tamaños de imagen que provienen, por ejemplo, de una solicitud de datos de formularios / múltiples partes donde generalmente recupera InputPart/ InputStream. Así que rápidamente hizo una variante que acepta File, InputStreamy RandomAccessFile, en base a la capacidad de ImageIO#createImageInputStreamhacerlo.

Por supuesto, tal método con Object input, solo puede permanecer privado y deberá crear tantos métodos polimórficos como sea necesario, llamando a este. También puede aceptar Pathcon Path#toFile()y URLcon URL#openStream()antes de pasar a este método:

  private static Dimension getImageDimensions(Object input) throws IOException {

    try (ImageInputStream stream = ImageIO.createImageInputStream(input)) { // accepts File, InputStream, RandomAccessFile
      if(stream != null) {
        IIORegistry iioRegistry = IIORegistry.getDefaultInstance();
        Iterator<ImageReaderSpi> iter = iioRegistry.getServiceProviders(ImageReaderSpi.class, true);
        while (iter.hasNext()) {
          ImageReaderSpi readerSpi = iter.next();
          if (readerSpi.canDecodeInput(stream)) {
            ImageReader reader = readerSpi.createReaderInstance();
            try {
              reader.setInput(stream);
              int width = reader.getWidth(reader.getMinIndex());
              int height = reader.getHeight(reader.getMinIndex());
              return new Dimension(width, height);
            } finally {
              reader.dispose();
            }
          }
        }
        throw new IllegalArgumentException("Can't find decoder for this image");
      } else {
        throw new IllegalArgumentException("Can't open stream for this image");
      }
    }
  }
Fabien M
fuente
-1

Entonces, desafortunadamente, después de probar todas las respuestas anteriores, no logré que funcionen después de incansables intentos. Así que decidí hacer el truco real yo mismo y lo hice funcionar para mí. Confío en que también funcionará perfectamente para ti.

Estoy usando este método simple para obtener el ancho de una imagen generada por la aplicación y aún no cargarla más tarde para su verificación:

Por favor. tome nota: tendría que habilitar los permisos en el manifiesto para el almacenamiento de acceso.

/ Lo hice estático y lo puse en mi clase global para poder hacer referencia o acceder a él desde una sola fuente y, si hay alguna modificación, todo tendría que hacerse en un solo lugar. Simplemente manteniendo un concepto DRY en java. (de todos modos) :) /

public static int getImageWidthOrHeight(String imgFilePath) {

            Log.d("img path : "+imgFilePath);

            // Decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(imgFilePath, o);

            int width_tmp = o.outWidth, height_tmp = o.outHeight;

            Log.d("Image width : ", Integer.toString(width_tmp) );

            //you can decide to rather return height_tmp to get the height.

            return width_tmp;

}

AppEmmanuel
fuente
Hola @gpasch, ¿cómo es eso? Lo has probado ? Esto está funcionando perfectamente para mí. Ninguno de los otros ejemplos funcionó para mí. Algunos me pidieron que hiciera algunas importaciones extrañas de clases y otros que causaron problemas. Realmente quiero aprender más de cuáles fueron sus desafíos. En espera . . .
AppEmmanuel
-2

Para obtener el tamaño del archivo emf sin el lector de imágenes EMF, puede usar el código:

Dimension getImageDimForEmf(final String path) throws IOException {

    ImageInputStream inputStream = new FileImageInputStream(new File(path));

    inputStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);

    // Skip magic number and file size
    inputStream.skipBytes(6*4);

    int left   = inputStream.readInt();
    int top    = inputStream.readInt();
    int right  = inputStream.readInt();
    int bottom = inputStream.readInt();

    // Skip other headers
    inputStream.skipBytes(30);

    int deviceSizeInPixelX = inputStream.readInt();
    int deviceSizeInPixelY = inputStream.readInt();

    int deviceSizeInMlmX = inputStream.readInt();
    int deviceSizeInMlmY = inputStream.readInt();

    int widthInPixel = (int) Math.round(0.5 + ((right - left + 1.0) * deviceSizeInPixelX / deviceSizeInMlmX) / 100.0);
    int heightInPixel = (int) Math.round(0.5 + ((bottom-top + 1.0) * deviceSizeInPixelY / deviceSizeInMlmY) / 100.0);

    inputStream.close();

    return new Dimension(widthInPixel, heightInPixel);
}
Viktor Sirotin
fuente