使用Java程序读取JPEG文件

来源:互联网 发布:北京云计算公司排名 编辑:程序博客网 时间:2024/05/21 10:46

一、JPEG文件格式简介

(此段文字摘自云风的《JPEG简易文档》,我觉得写得简单明了,有兴趣的同学可以直接度娘)

附:JPEG 文件格式

~~~~~~~~~~~~~~~~


  - 文件头 (2 bytes):  $ff, $d8 (SOI) (JPEG 文件标识)
  - 任意数量的段 , 见后面
  - 文件结束 (2 bytes): $ff, $d9 (EOI)


段的格式:
~~~~~~~~~


  - header (4 bytes):
       $ff     段标识
        n      段的类型 (1 byte)
       sh, sl  该段长度, 包括这两个字节, 但是不包括前面的 $ff 和 n.
               注意: 长度不是 intel 次序, 而是 Motorola 的, 高字节在前,
      低字节在后!
  - 该段的内容, 最多 65533 字节


 注意:
  - 有一些无参数的段 (下面那些前面注明星号的)
    这些段没有长度描述 (而且没有内容), 只有 $ff 和类型字节.
  - 段之间无论有多少 $ff 都是合法的, 必须被忽略掉.


段的类型:
~~~~~~~~~


   *TEM   = $01   可以忽略掉


    SOF0  = $c0   帧开始 (baseline JPEG), 细节附后
    SOF1  = $c1   dito
    SOF2  = $c2   通常不支持
    SOF3  = $c3   通常不支持


    SOF5  = $c5   通常不支持
    SOF6  = $c6   通常不支持
    SOF7  = $c7   通常不支持


    SOF9  = $c9   arithmetic 编码(Huffman 的一种扩展算法), 通常不支持
    SOF10 = $ca   通常不支持
    SOF11 = $cb   通常不支持


    SOF13 = $cd   通常不支持
    SOF14 = $ce   通常不支持
    SOF14 = $ce   通常不支持
    SOF15 = $cf   通常不支持


    DHT   = $c4   定义 Huffman Table,  细节附后
    JPG   = $c8   未定义/保留 (引起解码错误)
    DAC   = $cc   定义 Arithmetic Table, 通常不支持


   *RST0  = $d0   RSTn 用于 resync, 通常被忽略
   *RST1  = $d1
   *RST2  = $d2
   *RST3  = $d3
   *RST4  = $d4
   *RST5  = $d5
   *RST6  = $d6
   *RST7  = $d7


    SOI   = $d8   图片开始
    EOI   = $d9   图片结束
    SOS   = $da   扫描行开始, 细节附后
    DQT   = $db   定义 Quantization Table, 细节附后
    DNL   = $dc   通常不支持, 忽略
    DRI   = $dd   定义重新开始间隔, 细节附后
    DHP   = $de   忽略 (跳过)
    EXP   = $df   忽略 (跳过)


    APP0  = $e0   JFIF APP0 segment marker (细节略)
    APP15 = $ef   忽略


    JPG0  = $f0   忽略 (跳过)
    JPG13 = $fd   忽略 (跳过)
    COM   = $fe   注释, 细节附后


 其它的段类型都保留必须跳过


SOF0: Start Of Frame 0:
~~~~~~~~~~~~~~~~~~~~~~~


  - $ff, $c0 (SOF0)
  - 长度 (高字节, 低字节), 8+components*3
  - 数据精度 (1 byte) 每个样本位数, 通常是 8 (大多数软件不支持 12 和 16)
  - 图片高度 (高字节, 低字节), 如果不支持 DNL 就必须 >0
  - 图片宽度 (高字节, 低字节), 如果不支持 DNL 就必须 >0
  - components 数量(1 byte), 灰度图是 1, YCbCr/YIQ 彩色图是 3, CMYK 彩色图
    是 4
  - 每个 component: 3 bytes
     - component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q)
     - 采样系数 (bit 0-3 vert., 4-7 hor.)
     - quantization table 号


DRI: Define Restart Interval:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


  - $ff, $dd (DRI)
  - 长度 (高字节, 低字节), 必须是 4
  - MCU 块的单元中的重新开始间隔 (高字节, 低字节),
    意思是说, 每 n 个 MCU 块就有一个 RSTn 标记.
    第一个标记是 RST0, 然后是 RST1 等, RST7 后再从 RST0 重复 


DQT: Define Quantization Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


  - $ff, $db (DQT)
  - 长度 (高字节, 低字节)
  - QT 信息 (1 byte):
     bit 0..3: QT 号(0..3, 否则错误)
     bit 4..7: QT 精度, 0 = 8 bit, 否则 16 bit
  - n 字节的 QT, n = 64*(精度+1)


 备注:
  - 一个单独的 DQT 段可以包含多个 QT, 每个都有自己的信息字节
  - 当精度=1 (16 bit), 每个字都是高位在前低位在后


DAC: Define Arithmetic Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 法律原因, 现在的软件不支持 arithmetic 编码.
 不能生产使用 arithmetic 编码的 JPEG 文件


DHT: Define Huffman Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~


  - $ff, $c4 (DHT)
  - 长度 (高字节, 低字节)
  - HT 信息 (1 byte):
     bit 0..3: HT 号 (0..3, 否则错误)
     bit 4   : HT 类型, 0 = DC table, 1 = AC table
     bit 5..7: 必须是 0
  - 16 bytes: 长度是 1..16 代码的符号数. 这 16 个数的和应该 <=256
  - n bytes: 一个包含了按递增次序代码长度排列的符号表
    (n = 代码总数)


 备注:
  - 一个单独的 DHT 段可以包含多个 HT, 每个都有自己的信息字节


COM: 注释:
~~~~~~~~~~


  - $ff, $fe (COM)
  - 注释长度 (高字节, 低字节) = L+2
  - 注释为长度为 L 的字符流


SOS: Start Of Scan:
~~~~~~~~~~~~~~~~~~~


  - $ff, $da (SOS)
  - 长度 (高字节, 低字节), 必须是 6+2*(扫描行内组件的数量)
  - 扫描行内组件的数量 (1 byte), 必须 >= 1 , <=4 (否则是错的) 通常是 3
  - 每个组件: 2 bytes
     - component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q), 见 SOF0
     - 使用的 Huffman 表:
- bit 0..3: AC table (0..3)
- bit 4..7: DC table (0..3)
  - 忽略 3 bytes (???)


 备注:
  - 图片数据 (一个个扫描行) 紧接着 SOS 段.


二、Java读取二进制文件

首先,要能够读取二进制文件,因为JPEG文件其实上也就是二进制编码的格式文件,只是有它特定的编码方式而已。

class BinaryFile {static byte[] read(File bFile) throws IOException {BufferedInputStream bf = new BufferedInputStream(new FileInputStream(bFile));try {byte[] data = new byte[bf.available()];bf.read(data);return data;} finally {bf.close();}}static byte[] read(String bFile) throws IOException {return read(new File(bFile).getAbsoluteFile());}}

此类将文件以二进制形式读取到内存中,保存为字节数组。


三、使用Java定义JPEG格式的类

定义一个Java类,来表示JPEG文件。以下只展示了部分的类定义,其余部分在后面章节展示

class JPEGFile {enum SectionType{TEM,SOF0, SOF1, SOF2, SOF3, SOF5, SOF6, SOF7, SOF9, SOF10, SOF11, SOF13, SOF14, SOF15, DHT, JPG, DAC, RST0, RST1, RST2, RST3, RST4, RST5, RST6, RST7,SOI, EOI, SOS, DQT, DNL, DRI, DHP, EXP, APP0, APP15, JPG0, JPG13, COM, NOP,};static HashMap<Integer, SectionType> m_mapSectionType = new HashMap<Integer, SectionType>();static {m_mapSectionType.put(0x01, SectionType.TEM);m_mapSectionType.put(0xc0, SectionType.SOF0);m_mapSectionType.put(0xc1, SectionType.SOF1);m_mapSectionType.put(0xc2, SectionType.SOF2);m_mapSectionType.put(0xc3, SectionType.SOF3);m_mapSectionType.put(0xc5, SectionType.SOF5);m_mapSectionType.put(0xc6, SectionType.SOF6);m_mapSectionType.put(0xc7, SectionType.SOF7);m_mapSectionType.put(0xc9, SectionType.SOF9);m_mapSectionType.put(0xca, SectionType.SOF10);m_mapSectionType.put(0xcb, SectionType.SOF11);m_mapSectionType.put(0xcd, SectionType.SOF13);m_mapSectionType.put(0xce, SectionType.SOF14);m_mapSectionType.put(0xcf, SectionType.SOF15);m_mapSectionType.put(0xc4, SectionType.DHT);m_mapSectionType.put(0xc8, SectionType.JPG);m_mapSectionType.put(0xcc, SectionType.DAC);m_mapSectionType.put(0xd0, SectionType.RST0);m_mapSectionType.put(0xd1, SectionType.RST1);m_mapSectionType.put(0xd2, SectionType.RST2);m_mapSectionType.put(0xd3, SectionType.RST3);m_mapSectionType.put(0xd4, SectionType.RST4);m_mapSectionType.put(0xd5, SectionType.RST5);m_mapSectionType.put(0xd6, SectionType.RST6);m_mapSectionType.put(0xd7, SectionType.RST7);m_mapSectionType.put(0xd8, SectionType.SOI);m_mapSectionType.put(0xd9, SectionType.EOI);m_mapSectionType.put(0xda, SectionType.SOS);m_mapSectionType.put(0xdb, SectionType.DQT);m_mapSectionType.put(0xdc, SectionType.DNL);m_mapSectionType.put(0xdd, SectionType.DRI);m_mapSectionType.put(0xde, SectionType.DHP);m_mapSectionType.put(0xdf, SectionType.EXP);m_mapSectionType.put(0xe0, SectionType.APP0);m_mapSectionType.put(0xef, SectionType.APP15);m_mapSectionType.put(0xf0, SectionType.JPG0);m_mapSectionType.put(0xfd, SectionType.JPG13);m_mapSectionType.put(0xfe, SectionType.COM);m_mapSectionType.put(0xff, SectionType.NOP);};enum ERROR_CODE{ERR_OK,ERR_NOT_JEPG_FILE,};//SOF0: Start Of Frame 0:class JSOF0 {byte m_byPrecision;byte m_byHeight;byte m_byWidth;byte m_byComponentNum;class JComponent {byte m_byId;byte m_byFactor;byte m_byQTId;}ArrayList<JComponent> m_arrComponents = new ArrayList<JComponent>();}JSOF0 m_oSOF0 = new JSOF0();//DQT: Define Quantization Table:class JDQT {byte m_byQTInfo;class JQT {byte [] m_arrByte = new byte[64];}ArrayList<JQT> m_arrQT = new ArrayList<JQT>();void ReadQTArray(ByteArrayInputStream bais, int n) throws IOException{for (int i = 0; i < n + 1; ++i) {JQT qt = new JQT();bais.read(qt.m_arrByte);m_arrQT.add(qt);}}}ArrayList<JDQT> m_arrDQT = new ArrayList<JDQT>();......}

类的开始部分定义了各个段的标示符,接下来对SOF0和DQT段分别定义了内部类,这两个段是我们需要读取数据的段,其他的段我们暂时不分析,直接跳过,当然,也可以增加其他段的内部类,来读取段数据。


四、Java的JPEG类读取数据

  接下来是JPEGFile类的读取JPEG数据的部分。


class JPEG{  ......  SectionType GetSectionType(int iFirstByte, int iSecondByte) {iFirstByte = iFirstByte & 0xff;iSecondByte = iSecondByte & 0xff;System.out.println("GetSectionType(" + iFirstByte + ", " + iSecondByte + ")");if (0xff != iFirstByte) return SectionType.NOP;if (!m_mapSectionType.containsKey(iSecondByte)) return SectionType.NOP;return m_mapSectionType.get(iSecondByte);}int GetSectionLen(int iByteHigh, int iByteLow) {System.out.println("GetSectionLen(" + iByteHigh + ", " + iByteLow + ")");int iBlockLen = ((iByteHigh << 8) + iByteLow) & 0xffff;System.out.println("Section length: " + iBlockLen + " bytes");return iBlockLen;}void JumpOverSection(ByteArrayInputStream bais, SectionType eSectionType) {System.out.println("Section[" + eSectionType + "] jump over");int iBlockLen = GetSectionLen(bais.read(), bais.read());bais.skip(iBlockLen - 2);}void ReadDQT(ByteArrayInputStream bais) throws IOException{System.out.println("----------Section[DQT]----------");System.out.println("Section[DQT] reading ...");int iBlockLen = GetSectionLen(bais.read(), bais.read());JDQT dqt = new JDQT();dqt.m_byQTInfo = (byte)bais.read();//check validation//QTint n = (dqt.m_byQTInfo >> 4) & 0xf;dqt.ReadQTArray(bais, n);m_arrDQT.add(dqt);System.out.println("Section[DQT] read " + (64 * (n + 1) + 1) + " bytes");System.out.println("");}//ERROR_CODE readFromFile(String sFilename) {try {byte[] byteArr = BinaryFile.read(sFilename);ByteArrayInputStream bais = new ByteArrayInputStream(byteArr);if (bais.available() < 2) {System.out.println("File length is too small");return ERROR_CODE.ERR_OK;}//JPEG headerSectionType eSectionType = GetSectionType(bais.read(), bais.read());if (eSectionType != SectionType.SOI) {System.out.println("File is not JPEG File");return ERROR_CODE.ERR_NOT_JEPG_FILE;}//Iterate SectionSystem.out.println("File is JPEGFile");System.out.println("Start iterating sections ...");Boolean bStartScanning = false;while (bais.available() > 0) {int iByteHigh = bais.read();int iByteLow = bais.read();if (bStartScanning) {continue;}eSectionType = GetSectionType(iByteHigh, iByteLow);switch (eSectionType) {case NOP:continue;case SOF0:JumpOverSection(bais, eSectionType);break;case SOF2:JumpOverSection(bais, eSectionType);break;case DRI:JumpOverSection(bais, eSectionType);break;case DQT:ReadDQT(bais);break;case DHT:JumpOverSection(bais, eSectionType);break;case COM:JumpOverSection(bais, eSectionType);break;case SOS:bStartScanning = true;JumpOverSection(bais, eSectionType);break;default:JumpOverSection(bais, eSectionType);break;}}} catch (IOException e) {System.out.println("IOException: " + e);}return ERROR_CODE.ERR_OK;}}



GetSectionType获取段的类型,GetSectionLen获取段的长度,JumpOverSection跳过当前段的数据,ReadDQT读取DQT段的数据。

readFromFile利用BinaryFile读取文件到内存的字节数组,判断JPEG头。如果是JPEG文件,那么逐段的读入数据。我们这里只处理了DQT段,其他的段我们都跳过了;在SOS段我们设置了bStartScanning标示,这个SOS段标示之后就是具体的图像数据,可以开始扫描了。

五、类的使用方法

如下使用此类
public class Hello {public static void main(String[] args) {JPEGFile jpg = new JPEGFile();jpg.readFromFile("JUC801.jpg");}}

六、总结

本文提供了最简单的读取JPEG各段的方法,在此基础上可以继续分析JPEG的各个数据段和具体图像数据。


2 0
原创粉丝点击