使用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");}}
六、总结
- 使用Java程序读取JPEG文件
- 内存jpeg文件读取程序
- 使用jpeg库读取jpg文件
- Java 读取 JPEG 文件的 exif 信息
- 在程序中使用JPEG等非BMP资源文件
- java程序读取资源文件
- codec 直接读取jpeg文件的尺寸
- java读取JPEG中exif中信息
- 使用Java读取Excel文件
- 使用JAVA读取GZ文件
- 使用java读取xml文件
- java使用通道读取文件
- 使用C#WinForms程序读取代码行数(如.txt文件,.cs,.java,.c等文件)
- 使用脚本运行程序读取.properties文件
- Spring使用程序方式读取properties文件
- Spring使用程序方式读取properties文件
- dbf文件使用python读取程序
- Java 读取xml文件内容程序小结
- C语言sscanf函数的总结
- CMD查看端口使用
- 我是一个纯洁的好孩子
- Oracle EBS API使用说明
- Activity 与 Service 实现Parcelable接口传递 ArrayList 集合
- 使用Java程序读取JPEG文件
- jquery validate 自定义验证方法 日期验证
- 从零开始node.js入门项目(五)后台管理
- Maven的安装
- arcgis server创建站点失败,提示连接不到arcgis server时,可试下关闭ipv6
- XMPP 配置开发
- js定时器setInterval、setTimeout的使用
- MySQL中SQL优化和架构设计
- Java Dom API中getNodeValue和getTextContent的区别