วิธีรับความสูงและความกว้างของภาพโดยใช้ java


108

มีวิธีอื่นนอกเหนือจากการใช้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

คุณพบสาเหตุหรือแก้ไขปัญหานี้แล้วหรือยัง? ฉันได้รับปัญหาเดียวกันกับการล็อคเธรดด้วยวิธีการเดียวกันนั้น
Timothy Chen

มีข้อบกพร่องที่อาจเกี่ยวข้อง: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6791502
Adam Schmideg

คำตอบ:


291

นี่คือสิ่งที่ง่ายและสะดวกมาก

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

7
นี่เป็นคำตอบที่ดีที่สุดโดยทางยาวมากและคุณถูกโกงคะแนนโหวตจากคำตอบเดียวกันที่โพสต์โดยคนอื่น 17 วันหลังจากโพสต์ของคุณ นี่ควรเป็นคำตอบด้านบนไม่ใช่คำตอบด้านล่าง
Oversteer

35
จากทั้งหมดที่ฉันอ่านสิ่งนี้จะอ่านภาพทั้งหมดลงในหน่วยความจำ ซึ่งมากเพียงเพื่อให้ได้ความกว้างและความสูง
Marc

7
วิธีที่ไม่ดี: คุณจะต้องโหลดภาพแรสเตอร์ทั้งหมดลงในหน่วยความจำซึ่งทำให้ OOM มีภาพขนาดใหญ่มาก
yetanothercoder

4
คำถามนี้ถามหาวิธีอื่นนอกเหนือจากการใช้ ImageIO.read คำตอบของคุณคือ "ใช้ ImageIO.read" เหตุใดจึงถือว่าเป็นคำตอบที่ดี
ssimm

5
นี่เป็นวิธีที่แย่ที่สุดที่จะทำและฉันไม่เข้าใจว่าทำไมสิ่งนี้ถึงได้รับการโหวตอย่างมาก มันโหลดภาพทั้งหมดลงในฮีปซึ่งช้าและใช้หน่วยความจำที่ไม่จำเป็นมาก
Patrick Favre

76

นี่คือการเขียนใหม่ของโพสต์ที่ยอดเยี่ยมโดย @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! ขอบคุณ!
ah-shiang han

คุณไม่สามารถใช้probeContentTypeจาก nio แพ็คเกจไฟล์รวมกับjavax.imageio.ImageIO.getImageReadersByMIMEType(mimeType)ถ้าคุณต้องการแน่ใจประเภทของไฟล์หรือไม่
EdgeCaseBerg

3
นอกจากนี้คุณไม่ควรโทรหา.close()สตรีมก่อนที่คุณจะกลับมา? มิฉะนั้นคุณจะเปิดสตรีมทิ้งไว้
EdgeCaseBerg

1
@ php_coder_3809625 บันทึกอยู่ในตัววนซ้ำดังนั้นอาจเป็นกรณีที่ ImageReader เครื่องหนึ่งล้มเหลว แต่จะประสบความสำเร็จในภายหลัง หากล้มเหลวทั้งหมดจะมีการเพิ่ม IOException
Andrew Taylor

1
คุณอาจพิจารณาใช้org.apache.commons.io.FilenameUtils#getExtensionเพื่อตรวจหานามสกุลไฟล์
Patrick Bergner

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 ฉันหวังว่าใครบางคนจะพบว่าสิ่งนี้มีประโยชน์


getFileSuffix()มี ifs ที่ไม่จำเป็นและการเริ่มต้นด้วยnullไม่ใช่ความคิดที่ดีในกรณีนี้
Jimmy T.

2
ใช่แล้วนี่คือ "โดยทั่วไปเร็วมาก"! ฉันคิดว่าคุณมีคุณสมบัติสำหรับรางวัล 'understatement of the year' ด้วยรางวัลนั้น เป่าImageIO.read()ออกจากน้ำอย่างสมบูรณ์ทั้งในแง่ของเวลาของ CPU และการใช้หน่วยความจำ
aroth

1
สาธารณะคง String getFileSuffix (เส้นทางสตริงสุดท้าย) {if (path! = null && path.lastIndexOf ('.')! = -1) {return path.substring (path.lastIndexOf ('.')). substring (1) ; } คืนค่าว่าง }
Nilanchal

47

ฉันพยายามทดสอบประสิทธิภาพโดยใช้วิธีการต่างๆที่ระบุไว้ เป็นการยากที่จะทำการทดสอบอย่างเข้มงวดเนื่องจากหลายปัจจัยมีผลต่อผลลัพธ์ ฉันเตรียมสองโฟลเดอร์หนึ่งไฟล์มีไฟล์ jpg 330 ไฟล์และอีกโฟลเดอร์หนึ่งมีไฟล์ 330 png ขนาดไฟล์เฉลี่ยคือ 4Mb ในทั้งสองกรณี จากนั้นฉันก็เรียก 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

เห็นได้ชัดว่าวิธีการบางอย่างโหลดไฟล์ทั้งหมดเพื่อให้ได้ขนาดในขณะที่วิธีอื่น ๆ ได้รับเพียงแค่อ่านข้อมูลส่วนหัวบางส่วนจากรูปภาพ ฉันคิดว่าตัวเลขเหล่านี้อาจมีประโยชน์เมื่อประสิทธิภาพของแอปพลิเคชันเป็นสิ่งสำคัญ

ขอบคุณทุกคนที่มาอุดหนุนกระทู้นี้ - มีประโยชน์มาก


3
ตอนนี้นั่นคือคำตอบ เก่งมาก!
ssimm

1
คุณวิเคราะห์การใช้พื้นที่ฮีปเมื่ออัปโหลดรูปภาพด้วยหรือไม่ นอกจากนี้คุณได้รับข้อผิดพลาด OOM ขณะเรียกใช้การทดสอบเหล่านี้ด้วยวิธีการใด ๆ หรือไม่
saibharath

ขอบคุณคำตอบของคุณช่วยฉันได้มาก ฉันมี (รูป HD 50k)
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])));
}

}


ดี. แต่ฉันคิดว่าแทนที่จะ==192ตรวจสอบหมายเลข 192-207 ยกเว้น 196, 200 และ 204
vortexwolf

หรือคุณสามารถใช้com.drewnoakes.metadata-extractorไลบรารีเพื่อแยกส่วนหัวเหล่านี้ได้อย่างง่ายดาย
Victor Petit

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

3
จำเป็นต้องอ่านภาพทั้งหมดในหน่วยความจำเพื่อทราบความกว้างและความสูงเท่านั้น ใช่มันง่าย แต่จะทำงานได้ไม่ดีสำหรับภาพจำนวนมากหรือภาพขนาดใหญ่ ...
Clint Eastwood

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

ประสิทธิภาพการทำงานเหมือนกับการใช้ไลบรารีจาวา SimpleImageInfo

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


คืออะไรgetReaderByFormat?
Koray Tugay

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();

1
ไม่จำเป็นต้องใช้ ImageIO แต่ต้องใช้ Toolkit อะไรคือความแตกต่าง?
dieter

ImageIO เป็นการพึ่งพาภายนอก Toolkit not
Karussell

ImageIO เป็นส่วนหนึ่งของ Java ตั้งแต่ 1.4 docs.oracle.com/javase/7/docs/api/javax/imageio/…
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

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, และหลากหลาย) ขอบคุณค่า !!

แต่ฉันรู้สึกหงุดหงิดเล็กน้อยที่ถูกบังคับให้ใช้ไฟล์ในเครื่อง (ไฟล์ / สตริง) โดยเฉพาะอย่างยิ่งในกรณีที่คุณต้องการตรวจสอบขนาดภาพที่มาจากเช่นคำขอข้อมูลหลายส่วน / แบบฟอร์มซึ่งคุณมักจะดึงข้อมูลInputPart/ InputStream's ดังนั้นผมอย่างรวดเร็วทำให้ตัวแปรที่ยอมรับFile, InputStreamและRandomAccessFileขึ้นอยู่กับความสามารถในการImageIO#createImageInputStreamที่จะทำเช่นนั้น

แน่นอนว่าวิธีการดังกล่าวObject inputอาจยังคงเป็นส่วนตัวและคุณจะต้องสร้างวิธีการหลายรูปแบบได้มากเท่าที่จำเป็นโดยเรียกวิธีนี้ นอกจากนี้คุณยังสามารถยอมรับPathด้วยPath#toFile()และURLด้วย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");
      }
    }
  }

-1

น่าเสียดายที่หลังจากลองทำตามคำตอบทั้งหมดจากด้านบนแล้วฉันก็ไม่ได้รับคำตอบหลังจากพยายามอย่างไม่รู้จักเหน็ดเหนื่อย ดังนั้นฉันจึงตัดสินใจที่จะแฮ็คจริงด้วยตัวเองและฉันก็ไปทำงานให้ฉัน ฉันเชื่อว่ามันจะทำงานได้อย่างสมบูรณ์แบบสำหรับคุณเช่นกัน

ฉันใช้วิธีง่ายๆนี้เพื่อรับความกว้างของรูปภาพที่สร้างโดยแอพและยังไม่ได้อัปโหลดเพื่อการตรวจสอบในภายหลัง:

กรุณา. โปรดทราบ: คุณจะต้องเปิดใช้งานการอนุญาตในรายการเพื่อเข้าถึงที่เก็บข้อมูล

/ ฉันทำให้มันคงที่และใส่ไว้ในคลาส Global ของฉันเพื่อให้ฉันสามารถอ้างอิงหรือเข้าถึงได้จากแหล่งที่มาเพียงแหล่งเดียวและหากมีการแก้ไขใด ๆ ก็จะต้องทำในที่เดียว เพียงแค่รักษาแนวคิด 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;

}


สวัสดี @gpasch ยังไง? คุณลองหรือยัง นี่ทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน ไม่มีตัวอย่างอื่นที่ใช้ได้ผลสำหรับฉัน บางคนต้องการให้ฉันนำเข้าคลาสแปลก ๆ และอื่น ๆ ที่ทำให้เกิดปัญหา ฉันต้องการเรียนรู้เพิ่มเติมจากความท้าทายของคุณ ยืนอยู่ข้าง. . .
AppEmmanuel

-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);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.