1. JPEG file format Overview
(This text is taken from Yun Feng's "JPEG simple document". I think it is easy to write clearly. If you are interested, you can write it directly)
Appendix: JPEG file format
~~~~~~~~~~~~~~~~
-File Header (2 bytes): $ ff, $ d8 (SOI) (JPEG file ID)
-For any number of segments, see the following section.
-End of file (2 bytes): $ ff, $ d9 (EOI)
Segment format:
~~~~~~~~~
-Header (4 bytes ):
$ Ff segment ID
Type of n segments (1 byte)
Sh, sl length, including the two bytes, but not the previous $ ff and n.
Note: The length is not in intel order, but Motorola's, and the high byte is in front,
The lower byte is behind!
-The content of this segment, up to 65533 bytes
Note:
-There are some parameter-free segments (the asterisks are listed below)
These segments have no length description (and no content), only $ ff and type bytes.
-The $ ff between segments is valid and must be ignored.
Segment type:
~~~~~~~~~
* TEM = $01 can be ignored
SOF0 = $ c0 frame start (baseline JPEG), details attached
SOF1 = $ c1 dito
SOF2 = $ c2 is generally not supported
SOF3 = $ c3 is generally not supported
SOF5 = $ c5 is generally not supported
SOF6 = $ c6 is generally not supported
SOF7 = $ c7 is generally not supported
SOF9 = $ c9 arithmetic encoding (an Extended Huffman algorithm), usually not supported
SOF10 = $ ca generally does not support
SOF11 = $ cb is generally not supported
SOF13 = $ cd is generally not supported
SOF14 = $ ce is generally not supported
SOF14 = $ ce is generally not supported
SOF15 = $ cf is generally not supported
DHT = $ c4 defines the Huffman Table.
JPG = $ c8 undefined/retained (causing a decoding error)
DAC = $ cc defines the Arithmetic Table, which is usually not supported
* RST0 = $ d0 RSTn is used for resync, which is usually ignored.
* RST1 = $ d1
* RST2 = $ d2
* RST3 = $ d3
* RST4 = $ d4
* RST5 = $ d5
* RST6 = $ d6
* RST7 = $ d7
SOI = $ d8 image start
EOI = $ d9 picture ended
SOS = $ da scanning line starts, details are attached
DQT = $ db defines the Quantization Table.
DNL = $ dc is generally not supported. Ignore
DRI = $ dd defines the restart interval. The details are attached.
DHP = $ de ignore (skip)
EXP = $ df ignore (skip)
APP0 = $ e0 JFIF APP0 segment marker (details)
APP15 = $ ef ignore
JPG0 = $ f0 ignore (skip)
JPG13 = $ fd ignore (skip)
COM = $ fe comments, details attached
Other segment types must be retained.
SOF0: Start Of Frame 0:
~~~~~~~~~~~~~~~~~~~~~~~
-$ Ff, $ c0 (SOF0)
-Length (high byte, low byte), 8 + components * 3
-Data Precision (1 byte): number of digits per sample, usually 8 (most software does not support 12 or 16)
-The Image Height (high byte, low byte) must be greater than 0 if DNL is not supported
-The image width (high byte, low byte) must be greater than 0 if DNL is not supported
-The number of components (1 byte). The gray scale is 1, The YCbCr/YIQ color is 3, and the CMYK color is
Yes 4
-Each component: 3 bytes
-Component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q)
-Sampling coefficient (bit 0-3 vert., 4-7 hor .)
-Quantization table No.
DRI: Define Restart Interval:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-$ Ff, $ dd (DRI)
-Length (high byte, low byte), must be 4
-Re-start interval (high byte, low byte) in the unit of the MCU block ),
This means that each n MCU blocks has an RSTn mark.
The first mark is RST0, then RST1, and RST7 and then repeat from RST0.
DQT: Define Quantization Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-$ Ff, $ db (DQT)
-Length (high byte, low byte)
-QT Information (1 byte ):
Bit 0 .. 3: QT (0 .. 3, otherwise error)
Bit 4 .. 7: QT precision, 0 = 8 bit, otherwise 16 bit
-N-byte QT, n = 64 * (precision + 1)
Note:
-A single DQT segment can contain multiple QT segments, each of which has its own information bytes.
-When the precision is 1 (16 bit), each word is in the top position and in the back position.
DAC: Define Arithmetic Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Due to legal reasons, the current software does not support arithmetic encoding.
JPEG files encoded with arithmetic cannot be produced.
DHT: Define Huffman Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~
-$ Ff, $ c4 (DHT)
-Length (high byte, low byte)
-HT Information (1 byte ):
Bit 0 .. 3: HT No. (0 .. 3, otherwise it is incorrect)
Bit 4: HT type, 0 = DC table, 1 = AC table
Bit 5 .. 7: Must be 0
-16 bytes: The length is the number of characters in the 1 .. 16 code. The sum of the 16 characters should be <= 256
-N bytes: A symbol table containing code lengths in ascending order
(N = Total number of codes)
Note:
-A separate DHT segment can contain multiple HT, each of which has its own information bytes.
COM: Note:
~~~~~~~~~~
-$ Ff, $ fe (COM)
-Annotation length (high byte, low byte) = L + 2
-The comment is a batch stream with a length of L.
SOS: Start Of Scan:
~~~~~~~~~~~~~~~~~~~
-$ Ff, $ da (SOS)
-Length (high-byte, low-byte), must be 6 + 2 * (number of components in the scan row)
-The number of components in the scan row (1 byte), must be greater than or equal to 1, <= 4 (otherwise it is wrong), usually 3
-Each component: 2 bytes
-Component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q), see SOF0
-Huffman table used:
-Bit 0 .. 3: AC table (0 .. 3)
-Bit 4 .. 7: DC table (0 .. 3)
-Ignore 3 bytes (???)
Note:
-Image data (SCAN rows one by one) followed by the SOS segment.
Ii. Reading binary files using Java
First, you must be able to read binary files, because JPEG files are actually binary-encoded files, but they only have their specific encoding methods.
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());}}
This class reads files to the memory in binary format and saves them as byte arrays.
Iii. Use Java to define the class in JPEG format
Defines a Java class to represent JPEG files. The following sections only show some class definitions, and the rest are described in the following sections.
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
m_mapSectionType = new HashMap
();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
m_arrComponents = new ArrayList
();}JSOF0 m_oSOF0 = new JSOF0();//DQT: Define Quantization Table:class JDQT {byte m_byQTInfo;class JQT {byte [] m_arrByte = new byte[64];}ArrayList
m_arrQT = new ArrayList
();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
m_arrDQT = new ArrayList
();......}
The start part of the class defines the identifier of each segment, and then defines the internal class for the SOF0 and DQT segments respectively. These two segments are the segments for reading data, we do not analyze other segments for the time being and skip them directly. Of course, we can also add internal classes of other segments to read segment data.
Iv. Java JPEG class Data Reading
Next is the part of the JPEG data read by the sort File class.
class JPEGFile{
......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: Get the segment type. GetSectionLen gets the segment length. JumpOverSection skips the data of the current segment and ReadDQT reads data of the DQT segment.
ReadFromFile uses BinaryFile to read the byte array of the file to the memory and determine the JPEG header. For JPEG files, data is read in segments one by one. We only process the DQT segment here, And we skip the other segments. In the SOS segment, we set the bStartScanning flag. After the SOS segment is marked, it is the specific image data, you can start scanning.
5. Use this class as follows:
public class Hello {public static void main(String[] args) {JPEGFile jpg = new JPEGFile();jpg.readFromFile("JUC801.jpg");}}
6. Summary This article provides the simplest method to read each segment of JPEG. On this basis, you can continue to analyze each segment of JPEG and specific image data.