Jak uzyskać wysokość i szerokość obrazu za pomocą Java?

106

Czy istnieje inny sposób niż użycie ImageIO.read, aby uzyskać wysokość i szerokość obrazu?

Ponieważ napotykam problem, który blokuje wątek.

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)

Ten błąd występuje tylko na serwerze aplikacji Sun i dlatego podejrzewam, że jest to błąd Sun.

srebrny est
źródło
Jaki błąd? Pokazujesz tylko część śladu stosu (który wydaje się pochodzić z jstack).
Joachim Sauer
Czy znalazłeś przyczynę lub rozwiązałeś ten problem? Otrzymuję ten sam problem, w którym blokuje wątek tą samą metodą.
Timothy Chen
Istnieje błąd, który może być powiązany: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6791502
Adam Schmideg

Odpowiedzi:

288

Oto coś bardzo prostego i poręcznego.

BufferedImage bimg = ImageIO.read(new File(filename));
int width          = bimg.getWidth();
int height         = bimg.getHeight();
Apurv
źródło
7
To najlepsza odpowiedź z bardzo daleka i zostałeś oszukany z głosów przez tę samą odpowiedź opublikowaną przez kogoś innego 17 dni po twoim wpisie. To powinna być górna odpowiedź, a nie dolna.
Nadsterowność,
34
Ze wszystkiego, co czytam, wczytuje to cały obraz do pamięci. Co jest ekstremalne tylko po to, aby uzyskać szerokość i wysokość.
Marc
7
zły sposób: musisz załadować cały raster obrazu do pamięci, co powoduje OOM z bardzo dużymi obrazami
yetanothercoder
4
Pytanie dotyczy konkretnie innego sposobu niż użycie ImageIO.read. Twoja odpowiedź brzmi „użyj ImageIO.read”. Dlaczego jest to uważane za dobrą odpowiedź?
ssimm
5
To jest najgorszy sposób, aby to zrobić i nie rozumiem, dlaczego jest to tak mocno przegłosowane. Ładuje cały obraz do stosu, który jest powolny i zużywa dużo niepotrzebnej pamięci.
Patrick Favre,
73

To jest przepisanie świetnego posta autorstwa @Kay, który rzuca IOException i zapewnia wczesne wyjście:

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

Wydaje mi się, że moja reprezentacja nie jest wystarczająco wysoka, aby mój wkład został uznany za warty odpowiedzi.

Andrew Taylor
źródło
Dzięki za to! i biorąc pod uwagę porównanie wydajności wykonane przez użytkownika 194715, wezmę pod uwagę twoją sugestię dotyczącą wydajności i png! Dziękuję Ci!
ah-shiang han
Czy nie mógłbyś również użyć probeContentType z pakietu nio.Files w połączeniu z javax.imageio.ImageIO.getImageReadersByMIMEType(mimeType), gdybyś chciał mieć pewność co do typu pliku?
EdgeCaseBerg
3
Nie powinieneś też zadzwonić .close()na stream przed powrotem? w przeciwnym razie zostawisz strumień otwarty
EdgeCaseBerg
1
@ php_coder_3809625 dziennik znajduje się w iteratorze, więc może się zdarzyć, że jeden ImageReader zawiedzie, ale kolejny się powiedzie. Jeśli wszystkie zawiodą, zostanie zgłoszony wyjątek IOException.
Andrew Taylor
1
Możesz rozważyć użycie org.apache.commons.io.FilenameUtils#getExtensiondo wykrywania rozszerzenia nazwy pliku.
Patrick Bergner,
52

Znalazłem inny sposób odczytywania rozmiaru obrazu (bardziej ogólny). Z klasy ImageIO można korzystać we współpracy z ImageReaders. Oto przykładowy kod:

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

Zauważ, że getFileSuffix to metoda, która zwraca rozszerzenie ścieżki bez "." czyli np .: png, jpg itp. Przykładowa implementacja to:

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

To rozwiązanie jest bardzo szybkie, ponieważ z pliku odczytywany jest tylko rozmiar obrazu, a nie cały obraz. Przetestowałem to i nie ma porównania do wydajności ImageIO.read. Mam nadzieję, że komuś to się przyda.

user350756
źródło
getFileSuffix()zawiera niepotrzebne „if”, a inicjowanie przy użyciu nullnie jest dobrym pomysłem w tym przypadku.
Jimmy T.
2
Do diabła, tak, to jest „w zasadzie bardzo szybkie”! Myślę, że dzięki temu zakwalifikowałeś się do nagrody „niedopowiedzenie roku”. Wieje ImageIO.read()całkowicie z wody, zarówno pod względem czasu procesora i pamięci.
aroth
1
public static String getFileSuffix (final String path) {if (path! = null && path.lastIndexOf ('.')! = -1) {return path.substring (path.lastIndexOf ('.')). substring (1) ; } return null; }
Nilanchal
47

Próbowałem przetestować wydajność, korzystając z różnych wymienionych podejść. Trudno jest przeprowadzić rygorystyczny test, ponieważ na wynik wpływa wiele czynników. Przygotowałem dwa foldery, jeden zawierający 330 plików jpg, a drugi 330 plików png. W obu przypadkach średni rozmiar pliku wynosił 4 MB. Następnie zadzwoniłem do getDimension dla każdego pliku. Każda implementacja metody getDimension i każdy typ obrazu były testowane osobno (osobne uruchomienie). Oto czasy wykonania, które otrzymałem (pierwsza liczba dla jpg, druga liczba dla png):

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

Jest oczywiste, że niektóre metody ładują cały plik w celu uzyskania wymiarów, podczas gdy inne uzyskują po prostu odczytywanie niektórych informacji nagłówka z obrazu. Myślę, że te liczby mogą być przydatne, gdy wydajność aplikacji jest krytyczna.

Dziękuję wszystkim za wkład w ten wątek - bardzo pomocny.

mp31415
źródło
3
Teraz to jest odpowiedź. Dobra robota!
ssimm
1
Czy przeanalizowałeś również wykorzystanie miejsca na sterty podczas przesyłania zdjęć? Czy wystąpiły jakieś błędy OOM podczas wykonywania tych testów na którejkolwiek z metod?
saibharath
Dziękuję, twoja odpowiedź bardzo mi pomogła. Mam (50k HD pic)
SüniÚr
17

Możesz załadować dane binarne jpeg jako plik i samodzielnie przeanalizować nagłówki jpeg. Ten, którego szukasz, to nagłówek 0xFFC0 lub 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.

Aby uzyskać więcej informacji na temat nagłówków, sprawdź wpis JPEG na Wikipedii lub mam powyższe informacje tutaj .

Użyłem metody podobnej do poniższego kodu, który dostałem z tego posta na forach 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
źródło
Dobry. Ale myślę, że zamiast ==192tego warto sprawdzić numery 192-207, poza 196, 200 i 204.
vortexwolf
Lub możesz skorzystać z com.drewnoakes.metadata-extractorbiblioteki, aby łatwo wyodrębnić te nagłówki
Victor Petit
9

Prosta droga:

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
źródło
3
wymaga to odczytania całego obrazu z pamięci tylko po to, aby poznać szerokość i wysokość. Tak, to proste, ale będzie źle działać w przypadku wielu obrazów lub dużych ...
Clint Eastwood
4

Problem z ImageIO.read polega na tym, że działa bardzo wolno. Wszystko, co musisz zrobić, to przeczytać nagłówek obrazu, aby uzyskać rozmiar. ImageIO.getImageReaderjest idealnym kandydatem.

Oto przykład Groovy, ale to samo dotyczy Javy

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

Wydajność była taka sama, jak przy użyciu biblioteki Java SimpleImageInfo.

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

argoden
źródło
Co to jest getReaderByFormat?
Koray Tugay
3

Możesz użyć zestawu narzędzi, nie potrzebujesz ImageIO

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

Jeśli nie chcesz zajmować się ładowaniem obrazu, zrób to

ImageIcon imageIcon = new ImageIcon(file.getAbsolutePath());
int height = imageIcon.getIconHeight();
int width = imageIcon.getIconWidth();
Karussell
źródło
1
Nie potrzebujesz ImageIO, ale potrzebujesz Toolkit. Jaka jest różnica?
dieter
ImageIO to zależność zewnętrzna. Zestaw narzędzi nie
Karussell
ImageIO jest częścią Javy od 1.4 docs.oracle.com/javase/7/docs/api/javax/imageio/…
dieter
Tak, może to zadziała, przepraszam, jeśli to było wtedy mylące. Kiedy próbowałem go potrzebowałem dla niektórych częściach rzecz JAI (może czytać inne formaty): stackoverflow.com/questions/1209583/...
Karussell
3

Możesz uzyskać szerokość i wysokość obrazu za pomocą obiektu BufferedImage używając 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
źródło
spowoduje to załadowanie całego obrazu do pamięci, bardzo marnotrawne i bardzo wolne.
argoden
1

Uzyskanie buforowanego obrazu za pomocą ImageIO.read jest bardzo ciężką metodą, ponieważ tworzy pełną nieskompresowaną kopię obrazu w pamięci. W przypadku png możesz również użyć pngj i kodu:

if (png)
    PngReader pngr = new PngReader(file);
    width = pngr.imgInfo.cols;
    height = pngr.imgInfo.rows;
    pngr.close();
}
Jessi
źródło
0

Po zmagał się z ImageIOwielu w ostatnich latach, myślę, Andrew Taylor „s rozwiązanie jest zdecydowanie najlepszym kompromisem (szybki: nie używa ImageIO#read, a uniwersalny). Dzięki!!

Ale byłem trochę sfrustrowany, że zmuszono mnie do korzystania z lokalnego pliku (Plik / Ciąg), szczególnie w przypadkach, gdy chcesz sprawdzić rozmiary obrazów pochodzące, powiedzmy, z żądania wieloczęściowego / formularza-danych, z którego zwykle pobierasz InputPart/ InputStream. Więc szybko się wariant, który akceptuje File, InputStreami RandomAccessFile, na podstawie zdolności ImageIO#createImageInputStreamdo zrobić.

Oczywiście taka metoda z Object input, może pozostać tylko prywatna i powinieneś utworzyć tyle metod polimorficznych, ile potrzeba, nazywając tę. Można również przyjąć Pathz Path#toFile()i URLz URL#openStream()przed przejściem do tej metody:

  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
źródło
-1

Więc niestety, po wypróbowaniu wszystkich odpowiedzi z góry, nie udało mi się ich uruchomić po niestrudzonych próbach. Więc zdecydowałem się zrobić prawdziwy hack sam i robię to dla mnie. Ufam, że dla ciebie też będzie działać idealnie.

Używam tej prostej metody, aby uzyskać szerokość obrazu wygenerowanego przez aplikację, który ma zostać przesłany później do weryfikacji:

Pls. uwaga: aby uzyskać dostęp do pamięci, musisz włączyć uprawnienia w manifeście.

/ Zrobiłem to statycznie i umieściłem w mojej klasie Global, aby móc odwoływać się do niego lub uzyskiwać do niego dostęp z jednego źródła, a jeśli jest jakakolwiek modyfikacja, wszystko musiałoby być zrobione w jednym miejscu. Po prostu utrzymanie koncepcji DRY w java. (zresztą) :) /

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
źródło
Witaj @gpasch, jak to? próbowałeś tego ? To działa idealnie dla mnie. Żaden z innych przykładów nie zadziałał dla mnie. Niektóre wymagały ode mnie dziwnego importu klas, a inne powodowały problemy. Naprawdę chcę dowiedzieć się więcej z tego, jakie były Twoje wyzwania. Być w gotowości . . .
AppEmmanuel
-2

Aby uzyskać rozmiar pliku emf bez EMF Image Reader, możesz użyć kodu:

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
źródło