Como obter altura e largura da imagem usando java?

106

Existe alguma outra maneira além de usar ImageIO.read para obter a altura e largura da imagem?

Porque encontro um problema que bloqueia o thread.

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)

Esse erro ocorre apenas em um servidor de aplicativo Sun e, portanto, suspeito que seja um bug da Sun.

prata est
fonte
Que erro? Você mostra apenas uma parte de um rastreamento de pilha (que parece vir de jstack).
Joachim Sauer
Você encontrou a causa ou solução para este problema? Estou tendo o mesmo problema de bloqueio do thread no mesmo método.
Timothy Chen
Há um bug que pode estar relacionado: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6791502
Adam Schmideg

Respostas:

288

Aqui está algo muito simples e prático.

BufferedImage bimg = ImageIO.read(new File(filename));
int width          = bimg.getWidth();
int height         = bimg.getHeight();
Apurv
fonte
7
Esta é, de longe, a melhor resposta, e você foi roubado em votos pela mesma resposta postada por outra pessoa 17 dias após sua postagem. Esta deve ser a primeira resposta, não a inferior.
Oversteer
34
De tudo o que estou lendo, isso lê a imagem inteira na memória. O que é extremo apenas para obter largura e altura.
Marc
7
maneira ruim: você precisará carregar todo o raster da imagem para a memória, o que causa OOM com imagens muito grandes
codificador
4
A questão pede especificamente uma maneira diferente de usar ImageIO.read. Sua resposta é "use ImageIO.read". Por que isso é considerado uma boa resposta?
ssimm
5
Esta é a pior maneira de fazer isso e eu não entendo por que isso é tão votado. Ele carrega toda a imagem no heap, que é lento e consome muita memória desnecessária.
Patrick Favre
72

Esta é uma reescrita da excelente postagem de @Kay, que lança IOException e fornece uma saída antecipada:

/**
 * 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());
}

Acho que minha representação não é alta o suficiente para que minha opinião seja considerada válida como resposta.

Andrew Taylor
fonte
Obrigado por isso! e considerando a comparação de desempenho feita pelo usuário 194715, vou levar sua sugestão para o desempenho e consideração do PNG! Obrigado!
ah-shiang han
Você também não poderia usar probeContentType do pacote nio.Files combinado com javax.imageio.ImageIO.getImageReadersByMIMEType(mimeType)se quisesse ter certeza do tipo de arquivo?
EdgeCaseBerg
3
Além disso, você não deveria ligar .close()para a transmissão antes de retornar? caso contrário, você deixa o stream aberto
EdgeCaseBerg
1
@ php_coder_3809625 o log está no iterador, portanto, pode ser que um ImageReader falhe, mas um subsequente seja bem-sucedido. Se todos eles falharem, uma IOException será gerada.
Andrew Taylor
1
Você pode considerar o uso org.apache.commons.io.FilenameUtils#getExtensionpara detectar a extensão do nome do arquivo.
Patrick Bergner
52

Encontrei outra maneira de ler o tamanho da imagem (mais genérico). Você pode usar a classe ImageIO em cooperação com ImageReaders. Aqui está o código de exemplo:

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

Observe que getFileSuffix é um método que retorna a extensão do caminho sem "." por exemplo: png, jpg etc. O exemplo de implementação é:

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 solução é muito rápida, pois apenas o tamanho da imagem é lido do arquivo e não da imagem inteira. Eu testei e não há comparação com o desempenho de ImageIO.read. Espero que alguém ache isso útil.

user350756
fonte
getFileSuffix()contém ifs desnecessários e inicializar com nullnão é uma boa ideia neste caso.
Jimmy T.
2
Inferno, sim, isso é "basicamente muito rápido"! Acho que você se qualificou para o prêmio de 'eufemismo do ano' com esse. Golpes ImageIO.read()completamente fora da água, tanto em termos de tempo de CPU e memória.
aroth
1
public static String getFileSuffix (caminho final da String) {if (path! = null && path.lastIndexOf ('.')! = -1) {return path.substring (path.lastIndexOf ('.')). substring (1) ; } return null; }
Nilanchal
47

Tentei testar o desempenho usando algumas das várias abordagens listadas. É difícil fazer um teste rigoroso, pois muitos fatores afetam o resultado. Preparei duas pastas, uma com 330 arquivos jpg e outra com 330 arquivos png. O tamanho médio do arquivo foi de 4 Mb em ambos os casos. Em seguida, chamei getDimension para cada arquivo. Cada implementação do método getDimension e cada tipo de imagem foi testado separadamente (execução separada). Aqui estão os tempos de execução que obtive (primeiro 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

É óbvio que alguns métodos carregam o arquivo inteiro para obter as dimensões, enquanto outros obtêm apenas lendo algumas informações do cabeçalho da imagem. Acho que esses números podem ser úteis quando o desempenho do aplicativo é crítico.

Obrigado a todos pela contribuição para este tópico - muito útil.

mp31415
fonte
3
Agora isso é uma resposta. Bom trabalho!
ssimm
1
Você também analisou o uso do espaço de heap ao fazer upload de imagens? Além disso, você obteve algum erro OOM ao executar esses testes em qualquer um dos métodos?
saibharath,
Obrigado sua resposta me ajudou muito. Eu tenho (50k HD pic)
SüniÚr
17

Você pode carregar dados binários jpeg como um arquivo e analisar os cabeçalhos jpeg você mesmo. O que você está procurando é o cabeçalho 0xFFC0 ou 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 obter mais informações sobre os cabeçalhos, verifique a entrada jpeg da wikipedia ou eu tenho as informações acima aqui .

Usei um método semelhante ao código a seguir, obtido nesta postagem nos fóruns da 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
fonte
Boa. Mas eu acho que em vez ==192disso deveria verificar os números 192-207, exceto 196, 200 e 204.
vortexwolf
Ou você pode usar a com.drewnoakes.metadata-extractorbiblioteca para extrair facilmente esses cabeçalhos
Victor Petit
9

Maneira simples:

BufferedImage readImage = null;

try {
    readImage = ImageIO.read(new File(your path);
    int h = readImage.getHeight();
    int w = readImage.getWidth();
} catch (Exception e) {
    readImage = null;
}
user1215499
fonte
3
isso precisa ler toda a imagem na memória apenas para saber a largura e a altura. Sim, é simples, mas terá um desempenho ruim tanto para muitas imagens quanto para imagens enormes ...
Clint Eastwood
4

O problema com ImageIO.read é que ele é muito lento. Tudo que você precisa fazer é ler o cabeçalho da imagem para obter o tamanho. ImageIO.getImageReaderé o candidato perfeito.

Aqui está o exemplo do Groovy, mas a mesma coisa se aplica ao 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)"

O desempenho foi o mesmo que usar a biblioteca Java SimpleImageInfo.

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

argoden
fonte
O que é getReaderByFormat?
Koray Tugay
3

Você pode usar o Toolkit, sem necessidade de ImageIO

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

Se você não quiser lidar com o carregamento da imagem, faça

ImageIcon imageIcon = new ImageIcon(file.getAbsolutePath());
int height = imageIcon.getIconHeight();
int width = imageIcon.getIconWidth();
Karussell
fonte
1
Não há necessidade de ImageIO, mas precisa de Toolkit. Qual é a diferença?
dieter
ImageIO é uma dependência externa. Toolkit not
Karussell
ImageIO faz parte do Java desde 1.4 docs.oracle.com/javase/7/docs/api/javax/imageio/…
dieter
Sim, talvez isso funcione, desculpe se isso foi confuso então. Quando eu tentei eu precisava para algumas partes a coisa JAI (talvez para ler outros formatos?): Stackoverflow.com/questions/1209583/...
Karussell
3

Você pode obter a largura e a altura da imagem com o 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
fonte
isso vai carregar a imagem inteira na memória, muito desperdício e muito lento.
argoden
1

Obter uma imagem com buffer com ImageIO.read é um método muito pesado, pois cria uma cópia descompactada completa da imagem na memória. Para png's, você também pode usar pngj e o código:

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

Tendo lutado ImageIOmuito nos últimos anos, acho que a solução de Andrew Taylor é de longe o melhor meio-termo (rápido: não usar e versátil). Obrigado cara !!ImageIO#read

Mas fiquei um pouco frustrado por ser obrigado a usar um arquivo local (Arquivo / String), especialmente nos casos em que você deseja verificar os tamanhos de imagem provenientes de, digamos, uma solicitação multipart / form-data onde você normalmente recupera InputPart/ InputStream's. Então eu rapidamente fez uma variante que aceita File, InputStreame RandomAccessFile, com base na capacidade de ImageIO#createImageInputStreamfazê-lo.

Claro, tal método com Object input, pode permanecer privado e você deve criar tantos métodos polimórficos quantos forem necessários, chamando este. Você também pode aceitar Pathcom Path#toFile()e URLcom URL#openStream()antes de passar para 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
fonte
-1

Então, infelizmente, depois de tentar todas as respostas acima, eu não consegui que funcionassem depois de tentativas incansáveis. Então eu decidi fazer o hack de verdade sozinho e vou trabalhar para mim. Acredito que funcionaria perfeitamente para você também.

Estou usando este método simples para obter a largura de uma imagem gerada pelo aplicativo e ainda para ser enviada posteriormente para verificação:

Pls. tome nota: você teria que habilitar as permissões no manifesto para acessar o armazenamento.

/ Eu o tornei estático e coloquei minha classe Global para que eu possa referenciá-lo ou acessá-lo de apenas uma fonte e se houver alguma modificação, tudo teria que ser feito em apenas um lugar. Apenas mantendo um conceito DRY em java. (de qualquer maneira) :) /

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
fonte
Olá @gpasch como assim? tentaste ? Isso está funcionando perfeitamente para mim. Nenhum dos outros exemplos funcionou para mim. Alguns exigiam que eu fizesse algumas importações estranhas de classes e outros que causaram problemas. Eu realmente quero aprender mais sobre quais foram seus desafios. Em espera . . .
AppEmmanuel
-2

Para obter o tamanho do arquivo emf sem o EMF Image Reader, você pode usar o 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
fonte