Làm thế nào để lấy chiều cao và chiều rộng hình ảnh bằng java?


106

Có cách nào khác ngoài việc sử dụng ImageIO.read để lấy chiều cao và chiều rộng hình ảnh không?

Bởi vì tôi gặp phải sự cố khóa luồng.

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)

Lỗi này chỉ xảy ra trên máy chủ ứng dụng Sun và do đó tôi nghi ngờ rằng đó là lỗi của Sun.


Lỗi gì? Bạn chỉ hiển thị một phần của dấu vết ngăn xếp (dường như đến từ jstack).
Joachim Sauer

Bạn đã tìm ra nguyên nhân hoặc cách khắc phục sự cố này chưa? Tôi đang gặp vấn đề tương tự khi nó khóa luồng trên cùng một phương pháp.
Timothy Chen

Có một lỗi mà có thể liên quan: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6791502
Adam Schmideg

Câu trả lời:


288

Đây là một cái gì đó rất đơn giản và tiện dụng.

BufferedImage bimg = ImageIO.read(new File(filename));
int width          = bimg.getWidth();
int height         = bimg.getHeight();

7
Đây là câu trả lời hay nhất bởi một chặng đường rất dài, và bạn đã bị lừa mất phiếu bầu bởi câu trả lời tương tự do người khác đăng 17 ngày sau khi bạn đăng bài. Đây phải là câu trả lời trên cùng chứ không phải dưới cùng.
Oversteer

34
Từ tất cả những gì tôi đang đọc, điều này đọc toàn bộ hình ảnh vào bộ nhớ. Mà là cực chỉ để có được chiều rộng và chiều cao.
Marc

7
cách tồi tệ: bạn sẽ cần phải tải toàn bộ raster hình ảnh vào bộ nhớ, điều này gây ra OOM với những hình ảnh rất lớn
yeta

4
Câu hỏi đặc biệt yêu cầu một cách khác ngoài việc sử dụng ImageIO.read. Câu trả lời của bạn là "sử dụng ImageIO.read". Tại sao đây được coi là một câu trả lời tốt?
ssimm

5
Đây là cách tồi tệ nhất để làm điều đó và tôi không hiểu tại sao điều này lại được ủng hộ rất nhiều. Nó tải toàn bộ hình ảnh vào heap chậm và tiêu tốn nhiều bộ nhớ không cần thiết.
Patrick Favre

72

Đây là phần viết lại bài đăng tuyệt vời của @Kay, bài đăng này ném IOException và cung cấp lối ra sớm:

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

Tôi đoán rằng đại diện của tôi không đủ cao để đầu vào của tôi được coi là xứng đáng như một câu trả lời.


Cảm ơn vì điều đó! và xem xét so sánh hiệu suất được thực hiện bởi user194715, tôi sẽ xem xét đề xuất của bạn về hiệu suất và png! Cảm ơn bạn!
ah-shiang han

Bạn cũng không thể sử dụng probeContentType từ gói nio.Files kết hợp với javax.imageio.ImageIO.getImageReadersByMIMEType(mimeType)nếu bạn muốn chắc chắn về loại tệp?
EdgeCaseBerg

3
Ngoài ra, bạn không nên gọi điện .close()trên luồng trước khi trở về? nếu không bạn rời khỏi dòng mở
EdgeCaseBerg

1
@ php_coder_3809625 nhật ký nằm trong trình lặp, vì vậy có thể xảy ra trường hợp một ImageReader không thành công, nhưng một sau đó thành công. Nếu tất cả chúng đều không thành công, thì IOException sẽ được nâng lên.
Andrew Taylor

1
Bạn có thể cân nhắc sử dụng org.apache.commons.io.FilenameUtils#getExtensionđể phát hiện phần mở rộng tên tệp.
Patrick Bergner

52

Tôi đã tìm thấy một cách khác để đọc kích thước hình ảnh (chung chung hơn). Bạn có thể sử dụng lớp ImageIO hợp tác với ImageReaders. Đây là mã mẫu:

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

Lưu ý rằng getFileSuffix là phương thức trả về phần mở rộng của đường dẫn không có "." ví dụ như: png, jpg, v.v. Ví dụ triển khai là:

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

Giải pháp này rất nhanh chóng vì chỉ kích thước hình ảnh được đọc từ tệp chứ không phải toàn bộ hình ảnh. Tôi đã thử nghiệm nó và không có sự so sánh nào với hiệu suất của ImageIO.read. Tôi hy vọng ai đó sẽ thấy điều này hữu ích.


getFileSuffix()chứa ifs không cần thiết và khởi tạo bằng nullkhông phải là ý kiến ​​hay trong trường hợp này.
Jimmy T.

2
Đúng là cái này "về cơ bản là rất nhanh"! Tôi nghĩ rằng bạn đã đủ điều kiện cho giải thưởng 'bài phát biểu của năm' với giải thưởng đó. Thổi ImageIO.read()hoàn toàn khỏi nước, cả về thời gian sử dụng CPU và bộ nhớ.
aroth

1
public static String getFileSuffix (final String path) {if (path! = null && path.lastIndexOf ('.')! = -1) {return path.substring (path.lastIndexOf ('.')). substring (1) ; } trả về null; }
Nilanchal

47

Tôi đã cố gắng kiểm tra hiệu suất bằng một số cách tiếp cận khác nhau được liệt kê. Thật khó để thực hiện một bài kiểm tra nghiêm ngặt vì nhiều yếu tố ảnh hưởng đến kết quả. Tôi đã chuẩn bị hai thư mục, một thư mục chứa 330 tệp jpg và một thư mục khác có 330 tệp png. Kích thước tệp trung bình là 4Mb trong cả hai trường hợp. Sau đó, tôi gọi getDimension cho mỗi tệp. Mỗi lần triển khai phương thức getDimension và mỗi loại hình ảnh được thử nghiệm riêng biệt (chạy riêng). Đây là thời gian thực thi mà tôi nhận được (số đầu tiên cho jpg, số thứ hai cho png):

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

Rõ ràng là một số phương pháp tải toàn bộ tệp để có được kích thước trong khi những phương pháp khác tải chỉ bằng cách đọc một số thông tin tiêu đề từ hình ảnh. Tôi nghĩ rằng những con số này có thể hữu ích khi hiệu suất ứng dụng là quan trọng.

Cảm ơn mọi người đã đóng góp cho chủ đề này - rất hữu ích.


2
Bây giờ đó là một câu trả lời. Làm tốt lắm!
ssimm

1
Bạn cũng đã phân tích việc sử dụng dung lượng heap khi tải hình ảnh lên? Ngoài ra, bạn có gặp bất kỳ lỗi OOM nào khi chạy các thử nghiệm này trên bất kỳ phương pháp nào không?
saibharath

Cảm ơn câu trả lời của bạn đã giúp tôi rất nhiều. Tôi có (50k hình HD)
SüniÚr

17

Bạn có thể tải dữ liệu nhị phân jpeg dưới dạng tệp và tự phân tích cú pháp các tiêu đề jpeg. Cái bạn đang tìm là tiêu đề 0xFFC0 hoặc 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.

Để biết thêm thông tin về tiêu đề, hãy xem mục nhập jpeg của wikipedia hoặc tôi lấy thông tin ở trên ở đây .

Tôi đã sử dụng một phương pháp tương tự như mã bên dưới mà tôi nhận được từ bài đăng này tại diễn đàn mặt trời:

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

}


Tốt. Nhưng tôi nghĩ rằng thay vì ==192nó nên kiểm tra số 192-207, ngoại trừ 196, 200 và 204.
vortexwolf

Hoặc bạn có thể sử dụng com.drewnoakes.metadata-extractorthư viện để dễ dàng trích xuất các tiêu đề này
Victor Petit

9

Cách đơn giản:

BufferedImage readImage = null;

try {
    readImage = ImageIO.read(new File(your path);
    int h = readImage.getHeight();
    int w = readImage.getWidth();
} catch (Exception e) {
    readImage = null;
}

3
điều này chỉ cần đọc toàn bộ hình ảnh trong bộ nhớ khi biết chiều rộng và chiều cao. Có, đó là đơn giản, nhưng sẽ thực hiện tồi tệ cho cả hai nhiều hình ảnh hoặc những người khổng lồ ...
Clint Eastwood

4

Vấn đề với ImageIO.read là nó thực sự chậm. Tất cả những gì bạn cần làm là đọc tiêu đề hình ảnh để biết kích thước. ImageIO.getImageReaderlà ứng cử viên hoàn hảo.

Đây là ví dụ về Groovy, nhưng điều tương tự cũng áp dụng cho 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)"

Hiệu suất giống như sử dụng thư viện java SimpleImageInfo.

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


getReaderByFormatgì?
Koray Tugay

3

Bạn có thể sử dụng Bộ công cụ, không cần ImageIO

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

Nếu bạn không muốn xử lý việc tải hình ảnh, hãy làm

ImageIcon imageIcon = new ImageIcon(file.getAbsolutePath());
int height = imageIcon.getIconHeight();
int width = imageIcon.getIconWidth();

1
Không cần ImageIO nhưng cần Toolkit. Sự khác biệt là gì?
ăn kiêng,

ImageIO là một phụ thuộc bên ngoài. Bộ công cụ không
Karussell

ImageIO là một phần của Java từ 1,4 docs.oracle.com/javase/7/docs/api/javax/imageio/...
ăn kiêng

Vâng, có thể điều này sẽ hoạt động, xin lỗi nếu điều này gây nhầm lẫn sau đó. Khi tôi thử nó tôi cần cho một số bộ phận điều JAI (có thể đọc các định dạng khác?): Stackoverflow.com/questions/1209583/...
Karussell

3

Bạn có thể lấy chiều rộng và chiều cao của hình ảnh với đối tượng BufferedImage bằng 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();
    }

điều này sẽ tải toàn bộ hình ảnh trong bộ nhớ, rất lãng phí và rất chậm.
argoden

1

Để có được Hình ảnh được đệm bằng ImageIO.read là một phương pháp rất nặng nề, vì nó tạo ra một bản sao hoàn chỉnh không nén của hình ảnh trong bộ nhớ. Đối với png, bạn cũng có thể sử dụng pngj và mã:

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

0

Đã phải vật lộn với ImageIOrất nhiều thứ trong những năm qua, tôi nghĩ giải pháp của Andrew Taylor cho đến nay là giải pháp tốt nhất (nhanh chóng: không sử dụng ImageIO#readvà linh hoạt). Cảm ơn anh bạn !!

Nhưng tôi hơi bực bội khi bị bắt buộc phải sử dụng một tệp cục bộ (Tệp / Chuỗi), đặc biệt là trong trường hợp bạn muốn kiểm tra kích thước hình ảnh đến từ một yêu cầu đa phần / biểu mẫu-dữ liệu mà bạn thường truy xuất InputPart/ InputStream. Vì vậy, tôi nhanh chóng tạo ra một biến thể chấp nhận File, InputStreamRandomAccessFiledựa trên khả năng ImageIO#createImageInputStreamlàm như vậy.

Tất nhiên, một phương thức như vậy với Object input, có thể chỉ là riêng tư và bạn sẽ tạo nhiều phương thức đa hình nếu cần, gọi phương thức này. Bạn cũng có thể chấp nhận Pathvới Path#toFile()URLvới URL#openStream()trước khi đi đến phương pháp này:

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

-1

Vì vậy, thật không may, sau khi thử tất cả các câu trả lời từ trên, tôi đã không làm cho chúng hoạt động sau thời gian cố gắng không mệt mỏi. Vì vậy, tôi quyết định tự mình thực hiện hack thực sự và tôi làm việc này cho tôi. Tôi tin rằng nó cũng sẽ hoạt động hoàn hảo cho bạn.

Tôi đang sử dụng phương pháp đơn giản này để lấy chiều rộng của hình ảnh do ứng dụng tạo ra và chưa được tải lên sau này để xác minh:

Xin vui lòng. lưu ý: bạn sẽ phải bật quyền trong tệp kê khai để truy cập bộ nhớ.

/ Tôi đã đặt nó ở trạng thái tĩnh và đặt trong lớp Toàn cầu của mình để tôi có thể tham khảo hoặc truy cập nó chỉ từ một nguồn và nếu có bất kỳ sửa đổi nào, tất cả sẽ phải được thực hiện tại một nơi. Chỉ cần duy trì một khái niệm KHÔ trong java. (dù sao) :) /

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;

}


Xin chào @gpasch, làm thế nào vậy? Bạn đã thử chưa ? Điều này đang hoạt động hoàn hảo đối với tôi. Không có ví dụ nào khác phù hợp với tôi. Một số yêu cầu tôi thực hiện một số nhập các lớp kỳ lạ và những thứ khác gây ra sự cố. Tôi thực sự muốn tìm hiểu thêm từ (các) thử thách của bạn. Đang đứng cạnh . . .
AppEmmanuel

-2

Để có được kích thước của tệp emf mà không có Trình đọc hình ảnh EMF, bạn có thể sử dụng mã:

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.