Copyright notice: This article is the blogger’s original article, shall not be reproduced without the permission of the blogger

Series blog: source code reading series

Source: GifDecoder

If you see any mistakes or have any good suggestions, please leave a comment

prefaceReading good source code can greatly improve our level of development, thus opening a new pitDocument the analysis and interpretation of good source code (Android source code, various open source libraries, etc.)Learn how others have implemented a feature. Our hero in this episode isGIF decoding, we will decode the source code from GIFGifDecoderStart with, analyze the realization of the principle and process, I hope to help you ~ (GifDecoderSource code (bloggers have annotated the source code of each method and parameters, please rest assured to eat ~) link has been posted above, the source code for referenceGlide open source libraryParse the GIF part of the code, but because it is a long time ago to see, the specific source has not been verified, there are friends who know can leave a message to tell me)

directory

  • GIF structure brief description
  • Initialization of GifDecoder
  • Determine the format of the incoming file
  • Read GIF size, color depth and other global properties
  • Extract each frame picture

GIF structure brief description

Related blog links

GIF format image detailed analysis

Before analyzing the source code, we have to have a preliminary understanding of the composition of GIF images (see the link above for detailed analysis), see the following figure

The bold part of the figure is the place to save the image we need to extract (one frame image corresponds to one image block). Although we know where each frame of image information is stored, we can’t just pull the image out of it because on a computer, all files are stored in binary form, and Java reads files byte by byte in sequence. So the GIF decoding process, in effect, starts with the File Header, goes through each byte sequentially, and extracts the information we need when we read it (image data). Below we begin to analyze GifDecoder is how to achieve GIF decoding


Initialization of GifDecoder

Take a look at the GifDecoder initialization and use example, the code is as follows

try {
	InputStream is = getContentResolver().openInputStream(uri);
	GifDecoder gifDecoder = new GifDecoder();
	int code = gifDecoder.read(is);
	
	if (code == GifDecoder.STATUS_OK) {// Decoding succeeded
		GifDecoder.GifFrame[] frameList = gifDecoder.getFrames();
		
	} else if (code == gifDecoder.STATUS_FORMAT_ERROR) {// The image format is not GIF

	} else {// Image reading failed}}catch (FileNotFoundException e){
	e.printStackTrace();
}
Copy the code

The parameter URI is the URI path of the GIF image, and frameList is the decoding result, that is, the set of each frame in the GIF image, including the static image Bitmap and delay time of each frame. GifFrame is the object that holds each frame. The implementation and internal properties are as follows

/** * each frame object */
public static class GifFrame {
	public Bitmap image;// Static Bitmap
	public int delay;// Image delay time

	public GifFrame(Bitmap im, int del) { image = im; delay = del; }}Copy the code

GifDecoder defines three decoder states

public static final int STATUS_OK = 0;// Decoding succeeded
public static final int STATUS_FORMAT_ERROR = 1;// The image format is wrong
public static final int STATUS_OPEN_ERROR = 2;// Failed to open image
Copy the code

From the usage example of GifDecoder, we can see that the entrance of GifDecoder decoding GIF image is read(InputStream is) method, and the specific implementation is as follows

protected int status;// Decode state
protected Vector<GifFrame> frames;// An array of frames
protected int frameCount;/ / frames
protected int[] gct; // Global color list
protected int[] lct; // Local color list

/** * decode entry, read GIF image input stream *@param is
 * @return* /
public int read(InputStream is) {
	init();
	if(is ! =null) {
		in = is;
		readHeader();
		if(! err()) { readContents();if (frameCount < 0) { status = STATUS_FORMAT_ERROR; }}}else {
		status = STATUS_OPEN_ERROR;
	}
	try {
		is.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
	return status;
}

/** * Initialization parameters */
protected void init(a) {
	status = STATUS_OK;
	frameCount = 0;
	frames = new Vector<GifFrame>();
	gct = null;
	lct = null;
}

/** * Determine whether the current decoding process is wrong *@return* /
protected boolean err(a) {
	returnstatus ! = STATUS_OK; }Copy the code

It can be seen that the read(InputStream is) method embodies the complete decoding process and state judgment, and its call readHeader() and readContents() are the specific GIF internal data reading methods. In the next section we’ll dive into the readHeader() method and see how the GifDecoder handles GIF headers


Determine the format of the incoming file

Before decoding, it is necessary to determine whether the decoded object is a GIF image. ReadHeader () implements this judgment process, and the code to determine the file format is as follows

/** * Reads GIF headers, logical screen identifiers, global color lists */
protected void readHeader(a) {
	// Determine if the file is a GIF based on the file header
	String id = "";
	for (int i = 0; i < 6; i++) {
		id += (char) read();
	}
	if(! id.toUpperCase().startsWith("GIF")) {
		status = STATUS_FORMAT_ERROR;
		return;
	}
	
	// Parse GIF logical screen identifiers and global color lists. }/** * Read the input stream bytes one by one, failing to set the read failure status code *@return* /
protected int read(a) {
	int curByte = 0;
	try {
		curByte = in.read();
	} catch (Exception e) {
		status = STATUS_FORMAT_ERROR;
	}
	return curByte;
}
Copy the code

What do you make of this code? The File Header contains the GIF File signature and version number. The first three bytes contain the GIF File signature, namely G, I, and F. The format of the file is determined according to whether the header string read from the file starts with G, I, or F


Read GIF size, color depth and other global properties

There is also a section of code in the readHeader method, as follows

protected boolean gctFlag;// Whether the global color list is used
protected int bgIndex; // Background color index
protected int gctSize; // Global color list size
protected int bgColor; // Background color

protected void readHeader(a) {
	// Determine if the file is a GIF based on the file header.// Reads the GIF logical screen identifier
	readLSD();
	
	// Read the global color list
	if(gctFlag && ! err()) { gct = readColorTable(gctSize); bgColor = gct[bgIndex];// Get the background color in the global color list according to the index}}Copy the code

It corresponds to the parsing of the first two parts of the GIF Data Stream, the Logical Screen descriptors and the Global Color Table. ReadHeader () reads and parses all global properties and configuration information before reading the GIF image data. How does the readLSD() method parse the Logical Screen Descriptor

protected int width;// Full GIF image width
protected int height;// Full GIF image height
protected int pixelAspect; // Pixel Aspect Radio

/** * Reads the Logical Screen Descriptor and the Global Color Table */
protected void readLSD(a) {
	// Get the GIF image width and height
	width = readShort();
	height = readShort();

	/ * * * parsing Global Color list (Global Color Table) configuration information * configuration information of a byte, a specific deposit every Bit of data is as follows * 7 6 5 4 3 2 1 0 Bit * | | m cr | s | pixel | * /
	int packed = read();
	gctFlag = (packed & 0x80) != 0;// Check whether there is a global color list (m,0x80 is represented internally as 1000 0000)
	gctSize = 2 << (packed & 7);// Read the global color list size (pixel)

	// Read background color index and Pixel Aspect Radio
	bgIndex = read();
	pixelAspect = read();
}

/** * Reads two bytes of data *@return* /
protected int readShort(a) {
	return read() | (read() << 8);
}
Copy the code

According to readLSD(), we know whether the GIF contains a Global ColorTable(see below). If so, we call readColorTable(int ncolors) to obtain the Global Color list

/** * Reads the color list *@paramNcolors List size, i.e. number of colors *@return* /
protected int[] readColorTable(int ncolors) {
	int nbytes = 3 * ncolors;// A color takes up 3 bytes (r g b each takes up 1 byte), so the space taken up is the number of colors *3 bytes
	int[] tab = null;
	byte[] c = new byte[nbytes];
	int n = 0;
	try {
		n = in.read(c);
	} catch (Exception e) {
		e.printStackTrace();
	}
	if (n < nbytes) {
		status = STATUS_FORMAT_ERROR;
	} else {// Start parsing the color list
		tab = new int[256];// Set the maximum size to avoid boundary checking
		int i = 0;
		int j = 0;
		while (i < ncolors) {
			int r = ((int) c[j++]) & 0xff;
			int g = ((int) c[j++]) & 0xff;
			int b = ((int) c[j++]) & 0xff;
			tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b; }}return tab;
}
Copy the code

Now that we’re done with the readHeader method, how does the readContents method extract each frame of a GIF image


Extract each frame picture

So let’s just look inside the readContents method and see how it works, okay

/** * read the image block contents */
protected void readContents(a) {
	boolean done = false;
	while(! (done || err())) {int code = read();
		switch (code) {
			// The Image Descriptor starts
			case 0x2C:
				readImage();
				break;
			// Start the extension block
			case 0x21: // Extension block id, fixed value 0x21
				code = read();
				switch (code) {
					case 0xf9: //图形控制扩展块标识(Graphic Control Label),固定值0xf9
						readGraphicControlExt();
						break;

					case 0xff: //应用程序扩展块标识(Application Extension Label),固定值0xFF
						readBlock();
						String app = "";
						for (int i = 0; i < 11; i++) {
							app += (char) block[i];
						}
						if (app.equals("NETSCAPE2.0")) {
							readNetscapeExt();
						} else {
							skip(); // don't care
						}
						break;
					default: // All other extensions are skipped
						skip();
				}
				break;

			case 0x3b:// Mark the end of the GIF file, fixed value 0x3B
				done = true;
				break;

			case 0x00: // Bad bytes may appear, you can write bad byte analysis and other related content here as required
				break;
			default: status = STATUS_FORMAT_ERROR; }}}Copy the code

The core flow of readContents() is to judge the current decoding position according to the block identifier and call the corresponding method to decode the data block. If the GIF version is 89A, the data block may contain extension blocks (optional). The image delay time is stored in Graphic Control Extension, so we focus on analyzing how to read Graphic Control Extension (see the picture below). Other extension block decoding you can compare with the code comments and GIF structure of the relevant knowledge of research, here will not be described

The method to decode a GraphicControl Extension is readGraphicControlExt(), which is easy to understand with the byte description shown above

/** * Reads the graphics control extension block */
protected void readGraphicControlExt(a) {
	read();// In read order, here is the block size

	int packed = read();// Read the processing method, user input flags, etc
	dispose = (packed & 0x1c) > >2; // Disposal Method from the packed
	if (dispose == 0) {
		dispose = 1; //elect to keep old image if discretionary
	}
	transparency = (packed & 1) != 0;// The transparent color is resolved from the Packed

	delay = readShort() * 10;// Read latency (ms)
	transIndex = read();// Read transparent index
	read();// Block Terminator
}
Copy the code

GIF may contain multiple image blocks, Image blocks contain Image identifiers (see figure below), Local Color tables (Based on Local Color list flags), and table-based Image Data Based on Color list.

The readContents() method iterates through all image blocks and calls the readImage method for decoding. The code and comments are as follows

protected boolean lctFlag;// Local Color Table Flag
protected boolean interlace;// Interlace Flag
protected int lctSize;// Size of Local Color Table

/** * Read Image block Data sequentially: * Image Descriptor * Local Color Table (if available) * table-based Image Data */
protected void readImage(a) {
	/** ** Starts reading Image identifiers */
	ix = readShort();// Offset in x direction
	iy = readShort();// Offset in y direction
	iw = readShort();// Image width
	ih = readShort();// Image height

	int packed = read();
	lctFlag = (packed & 0x80) != 0;// Local Color Table Flag
	interlace = (packed & 0x40) != 0;// Interlace Flag
	// 3 - sort flag
	// 4-5 - reserved
	lctSize = 2 << (packed & 7);// Size of Local Color Table

	/** * start reading Local Color Table */
	if (lctFlag) {
		lct = readColorTable(lctSize);// Decodes the list of local colors
		act = lct;// If there is a local color list, the image data is based on the local color list
	} else {
		act = gct; // Otherwise, use the global color list
		if (bgIndex == transIndex) {
			bgColor = 0; }}int save = 0;
	if (transparency) {
		save = act[transIndex];// Save the original color of the transparent index position
		act[transIndex] = 0;// Set the transparent color according to the index position
	}
	if (act == null) {
		status = STATUS_FORMAT_ERROR;// Decoding error if no color list is available
	}
	if (err()) {
		return;
	}

	/** * start decoding image data */
	decodeImageData();
	skip();
	if (err()) {
		return;
	}
	frameCount++;
	image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
	setPixels(); // Convert pixel data to image Bitmap
	frames.addElement(new GifFrame(image, delay));// Add to the frame image collection
	// list
	if (transparency) {
		act[transIndex] = save;// Reset to the original color
	}
	resetFrame();
}
Copy the code

ReadImage method is divided into three steps: readImage Descriptor, read Local Color Table and decode Image data. DecodeImageData (), setPixels() decodeImageData(), Pixels()

At this point, the basic analysis of GifDecoder is finished. If there is any explanation that is not in place, welcome to comment and correct. If you feel good, please give me a thumbs up, your support is my biggest motivation ~