Как получить высоту и ширину изображения с помощью java?

107

Есть ли другой способ, кроме использования ImageIO.read, для получения высоты и ширины изображения?

Потому что я столкнулся с проблемой, которая блокирует поток.

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)

Эта ошибка возникает только на сервере приложений Sun, поэтому я подозреваю, что это ошибка Sun.

серебро эст
источник
Какая ошибка? Вы показываете только часть трассировки стека (которая, кажется, исходит от jstack).
Joachim Sauer
Вы нашли причину или решение этой проблемы? У меня такая же проблема, когда поток блокируется тем же методом.
Тимоти Чен
Ошибка, которая может быть связана: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6791502
Адам Шмидег,

Ответы:

290

Вот что-то очень простое и удобное.

BufferedImage bimg = ImageIO.read(new File(filename));
int width          = bimg.getWidth();
int height         = bimg.getHeight();
Апурв
источник
7
Это лучший ответ на очень долгий путь, и вас обманул из-за того же ответа, который отправил кто-то еще через 17 дней после вашего сообщения. Это должен быть верхний ответ, а не нижний.
Oversteer
34
Из всего, что я читаю, это считывает все изображение в память. Что крайне важно, чтобы получить ширину и высоту.
Марк
7
плохой способ: вам нужно загрузить весь растр изображения в память, что приводит к OOM с очень большими изображениями
другой кодер
4
Вопрос, в частности, требует другого способа, кроме использования ImageIO.read. Ваш ответ - «используйте ImageIO.read». Почему это считается хорошим ответом?
ssimm
5
Это худший способ сделать это, и я не понимаю, почему за него так много голосов. Он загружает все изображение в кучу, что медленно и потребляет много ненужной памяти.
Патрик Фавр
74

Это переписывание отличного поста @Kay, которое выдает исключение IOException и обеспечивает ранний выход:

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

Думаю, моя репутация недостаточно высока, чтобы мой вклад можно было считать достойным ответа.

Эндрю Тейлор
источник
Спасибо за это! и учитывая сравнение производительности, выполненное user194715, я приму ваше предложение по производительности и рассмотрению png! Спасибо!
а-шианг хан
Не могли бы вы также использовать probeContentType из пакета nio.Files в сочетании с, javax.imageio.ImageIO.getImageReadersByMIMEType(mimeType)если хотите быть уверенным в типе файла?
EdgeCaseBerg
3
Кроме того, не следует ли вам позвонить .close()на стрим перед возвращением? в противном случае вы оставите поток открытым
EdgeCaseBerg
1
@ php_coder_3809625 журнал находится в итераторе, поэтому может случиться так, что один ImageReader выйдет из строя, но последующий завершится успешно. Если все они терпят неудачу, возникает исключение IOException.
Эндрю Тейлор
1
Вы можете рассмотреть возможность использования org.apache.commons.io.FilenameUtils#getExtensionдля определения расширения имени файла.
Патрик Бергнер
52

Я нашел другой способ прочитать размер изображения (более общий). Вы можете использовать класс ImageIO совместно с ImageReaders. Вот пример кода:

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

Обратите внимание, что getFileSuffix - это метод, который возвращает расширение пути без "." например, png, jpg и т. д. Пример реализации:

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

Это решение очень быстрое, поскольку из файла считывается только размер изображения, а не все изображение. Я тестировал его, и нет никакого сравнения с производительностью ImageIO.read. Надеюсь, кому-то это пригодится.

user350756
источник
getFileSuffix()содержит ненужные if, и инициализация с помощью nullв этом случае не лучшая идея.
Джимми Т.
2
Черт, да, это "в основном очень быстро"! Думаю, с этой наградой вы попали в категорию «Преуменьшение года». ImageIO.read()Полностью улетучивается , как с точки зрения использования процессорного времени, так и памяти.
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

Я попытался проверить производительность, используя некоторые из перечисленных подходов. Трудно провести тщательный тест, так как на результат влияет множество факторов. Я подготовил две папки, одну с 330 файлами jpg и другую с 330 файлами png. Средний размер файла в обоих случаях составлял 4 МБ. Затем я вызвал getDimension для каждого файла. Каждая реализация метода getDimension и каждый тип изображения тестировались отдельно (отдельный прогон). Вот время выполнения, которое я получил (первое число для jpg, второе число для png):

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

Очевидно, что некоторые методы загружают весь файл, чтобы получить размеры, в то время как другие получают, просто считывая некоторую информацию заголовка с изображения. Я думаю, что эти числа могут быть полезны, когда производительность приложений критична.

Спасибо всем за вклад в эту ветку - очень полезно.

mp31415
источник
3
Вот и ответ. Хорошая работа!
ssimm
1
Вы также анализировали использование места в куче при загрузке изображений? Кроме того, возникали ли у вас какие-либо ошибки OOM при выполнении этих тестов для любого из методов?
saibharath
Спасибо, ваш ответ мне очень помог. У меня есть (50k HD фото)
SüniÚr
17

Вы можете загрузить двоичные данные jpeg в виде файла и самостоятельно проанализировать заголовки jpeg. Тот, который вы ищете, - это заголовок 0xFFC0 или 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.

Для получения дополнительной информации о заголовках ознакомьтесь с записью jpeg в Википедии, или я получил указанную выше информацию здесь .

Я использовал метод, аналогичный приведенному ниже коду, который я получил из этого сообщения на форумах 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
источник
Хорошо. Но я думаю, что вместо ==192этого следует проверять номера 192-207, кроме 196, 200 и 204.
vortexwolf 02
Или вы можете использовать com.drewnoakes.metadata-extractorбиблиотеку, чтобы легко извлечь эти заголовки
Виктор Пети
9

Простой способ:

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
источник
3
для этого необходимо прочитать все изображение в памяти только для того, чтобы знать ширину и высоту. Да, это просто, но плохо подойдет как для многих изображений, так и для огромных ...
Клинт Иствуд
4

Проблема с ImageIO.read в том, что он очень медленный. Все, что вам нужно сделать, это прочитать заголовок изображения, чтобы узнать его размер. ImageIO.getImageReaderидеальный кандидат.

Вот пример Groovy, но то же самое относится и к 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)"

Производительность была такой же, как при использовании java-библиотеки SimpleImageInfo.

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

аргоден
источник
Что есть getReaderByFormat?
Корай Тугай
3

Вы можете использовать Toolkit, без ImageIO

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

Если вы не хотите обрабатывать загрузку изображения, сделайте

ImageIcon imageIcon = new ImageIcon(file.getAbsolutePath());
int height = imageIcon.getIconHeight();
int width = imageIcon.getIconWidth();
Karussell
источник
1
Нет необходимости в ImageIO, но нужен Toolkit. В чем разница?
dieter
ImageIO - это внешняя зависимость. Toolkit not
Каруселл
ImageIO является частью Java, начиная с версии
dieter
Да, возможно, это сработает, извините, если это тогда сбивало с толку. Когда я попробовал это, мне нужно было для некоторых частей вещь JAI (может быть, чтобы читать другие форматы?): Stackoverflow.com/questions/1209583/…
Karussell
3

Вы можете получить ширину и высоту изображения с помощью объекта BufferedImage, используя 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();
    }
КИБОУ Хасан
источник
это загрузит в память все изображение, очень расточительно и очень медленно.
argoden 07
1

Получение буферизованного изображения с помощью ImageIO.read - очень сложный метод, поскольку он создает полную несжатую копию изображения в памяти. Для png вы также можете использовать pngj и код:

if (png)
    PngReader pngr = new PngReader(file);
    width = pngr.imgInfo.cols;
    height = pngr.imgInfo.rows;
    pngr.close();
}
Джесси
источник
0

После боролся с ImageIOмного в последние годы, я думаю , что Эндрю Тейлор «S решение , безусловно , является лучшим компромиссом (быстрый: не используется ImageIO#read, и универсальный). Спасибо чувак!!

Но я был немного разочарован тем, что вынужден был использовать локальный файл (File / String), особенно в тех случаях, когда вы хотите проверить размеры изображений, исходящие, скажем, из запроса multipart / form-data, из которого вы обычно получаете InputPart/ InputStream. Так что я быстро сделал вариант, который принимает File, InputStreamи RandomAccessFile, исходя из его возможностей ImageIO#createImageInputStream.

Конечно, такой метод с Object input, может оставаться только частным, и вы должны создать столько полиморфных методов, сколько необходимо, вызывая этот. Вы также можете принять Pathwith Path#toFile()и URLwith URL#openStream()до перехода к этому методу:

  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
источник
-1

Поэтому, к сожалению, после того, как я попробовал все ответы сверху, я не смог заставить их работать после неустанных попыток. Так что я решил сделать настоящий хакер сам и пошел на это на работу. Я верю, что это сработает и для вас.

Я использую этот простой метод, чтобы получить ширину изображения, созданного приложением, которое еще предстоит загрузить для проверки:

Пожалуйста. обратите внимание: вам нужно будет включить разрешения в манифесте для доступа к хранилищу.

/ Я сделал его статическим и поместил в свой глобальный класс, чтобы я мог ссылаться на него или получать к нему доступ только из одного источника, и если есть какие-либо изменения, все это должно быть сделано только в одном месте. Просто поддержание концепции DRY в java. (в любом случае) :) /

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
источник
Привет @gpasch, как так? Ты это пробовал ? У меня это отлично работает. Ни один из других примеров у меня не работал. Некоторые требовали от меня странного импорта классов, а другие вызывали проблемы. Я действительно хочу узнать больше из ваших задач. Будучи на готове . . .
AppEmmanuel 01
-2

Чтобы получить размер файла emf без EMF Image Reader, вы можете использовать код:

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);
}
Виктор Сиротин
источник