Task 259: .GIF File Format
Task 259: .GIF File Format
1. List of All Properties Intrinsic to the GIF File Format
The GIF (Graphics Interchange Format) is a raster image format that supports indexed colors, animations (multiple frames), and LZW compression. It is structured as a stream of blocks: a header, a logical screen descriptor, optional global color table, a series of data blocks (extensions and images), and a trailer. The properties below are all the core fields and attributes defined in the format specification, including flags, dimensions, color tables, and extension-specific values. These are "intrinsic" as they define the file's structure, rendering, and behavior without external dependencies. Values are little-endian where multi-byte, and packed fields use bit-packing.
- Signature: 3 bytes, fixed string "GIF" (identifies the file as GIF).
- Version: 3 bytes, string "87a" (original version) or "89a" (supports extensions like transparency and delays).
- Logical Screen Width: 2 bytes, unsigned 16-bit integer (0-65535 pixels; overall canvas width).
- Logical Screen Height: 2 bytes, unsigned 16-bit integer (0-65535 pixels; overall canvas height).
- Packed Screen Descriptor: 1 byte, bit-packed:
- Bit 7 (MSB): Global Color Table Flag (1 = present, 0 = absent; determines if global palette exists).
- Bits 6-4: Color Resolution (0-7; bits per primary color component minus 1, e.g., 7 = 8 bits per RGB).
- Bit 3: Global Sort Flag (1 = color table sorted by importance, 0 = unsorted).
- Bits 2-0: Global Color Table Size Exponent (0-7; actual size = 2^(value + 1) colors, e.g., 7 = 256 colors).
- Background Color Index: 1 byte, unsigned 8-bit integer (0-255; index into global color table for background; ignored if no global table).
- Pixel Aspect Ratio: 1 byte, unsigned 8-bit integer (0 = square pixels/no info; 1-255 = aspect ratio factor, computed as (value + 15)/64).
- Global Color Table: Variable length (3 × number of colors bytes); array of RGB triplets (red, green, blue; each 0-255); present if Global Color Table Flag = 1; used for images without local tables.
- Number of Frames/Images: Derived property (unsigned integer; count of Image Descriptors in the file; 1+ for animations).
- Loop Count: Derived from NETSCAPE Application Extension (unsigned 16-bit integer; 0 = infinite loop, else number of loops; optional, defaults to 1).
- Comments: List of strings (from Comment Extensions; optional, variable-length text sub-blocks).
- For each Graphic Control Extension (optional, precedes images in 89a; controls per-frame rendering):
- Disposal Method: 3 bits (0 = unspecified, 1 = do not dispose, 2 = restore to background, 3 = restore to previous, 4-7 = reserved).
- User Input Flag: 1 bit (1 = wait for user input before next frame, 0 = no).
- Transparent Color Flag: 1 bit (1 = transparent index valid, 0 = no transparency).
- Delay Time: 2 bytes, unsigned 16-bit integer (0-65535; centiseconds between frames; 0 = default ~5s).
- Transparent Color Index: 1 byte, unsigned 8-bit integer (0-255; index of transparent color in color table).
- For each Image Descriptor (defines per-frame image):
- Image Left Position: 2 bytes, unsigned 16-bit integer (0-65535; column offset from logical screen left).
- Image Top Position: 2 bytes, unsigned 16-bit integer (0-65535; row offset from logical screen top).
- Image Width: 2 bytes, unsigned 16-bit integer (1-65535 pixels; frame width).
- Image Height: 2 bytes, unsigned 16-bit integer (1-65535 pixels; frame height).
- Packed Image Descriptor: 1 byte, bit-packed:
- Bit 7 (MSB): Local Color Table Flag (1 = present, 0 = use global).
- Bit 6: Interlace Flag (1 = interlaced scanning, 0 = sequential).
- Bit 5: Local Sort Flag (1 = local color table sorted, 0 = unsorted).
- Bits 4-3: Reserved (must be 0).
- Bits 2-0: Local Color Table Size Exponent (0-7; actual size = 2^(value + 1) colors).
- Local Color Table: Variable length (3 × number of colors bytes); array of RGB triplets (similar to global; present if Local Color Table Flag = 1; overrides global for frame).
- LZW Minimum Code Size: 1 byte, unsigned 8-bit integer (2-12 bits; initial code size for LZW decompression of image data).
- Image Data: Variable sub-blocks (LZW-compressed pixel indices; series of sub-blocks ending with 0-length terminator; not a "property" but intrinsic data).
- Plain Text Extension (optional): Text grid position, size, character width/height, foreground/background indices, and plain text sub-blocks.
- Application Extension (optional): Identifier (e.g., "NETSCAPE2.0"), authentication code, and application data sub-blocks (e.g., loop count).
- Trailer: 1 byte, fixed 0x3B (marks end of file).
These properties define the file's rendering (colors, positions, timing), compression, and extensibility. The format supports up to 256 colors per palette (8-bit indexed), lossless LZW compression, and animations via multiple images with delays.
2. Two Direct Download Links for GIF Files
- Sample animated GIF (multi-frame example): https://upload.wikimedia.org/wikipedia/commons/6/6a/Animated_GIF_example.gif
- Sample static GIF (single-frame example): https://www.w3.org/Graphics/GIF/ring.gif
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop GIF Property Dumper
Embed this as a code block or custom HTML in a Ghost blog post (use Ghost's HTML card). It creates a drag-and-drop zone; when a GIF is dropped, it parses the file in the browser and dumps all extractable properties to a <pre>
element below. Parsing is client-side using FileReader
and byte manipulation (no external libs). Supports header, logical screen, global/local colors, per-frame details, and common extensions (GCE, NETSCAPE). Does not decode LZW data fully (prints size only).
4. Python Class for GIF Handling
This class opens a GIF file, decodes/parses properties (header, logical screen, colors, frames, extensions), prints them to console, and supports writing a simple GIF back (minimal: single-frame, global palette; LZW encoding stubbed—use pillow
for full write if needed, but here it's basic binary write).
import struct
from typing import List, Tuple
class GIFParser:
def __init__(self, filename: str):
self.filename = filename
self.version = ''
self.width = 0
self.height = 0
self.gct_flag = False
self.color_res = 0
self.g_sort = False
self.gct_size = 0
self.bg_index = 0
self.aspect = 0
self.global_colors: List[Tuple[int, int, int]] = []
self.frames = [] # List of frame dicts
self.loop_count = 1
self.comments = []
def read(self):
with open(self.filename, 'rb') as f:
data = f.read()
pos = 0
# Header
sig = data[pos:pos+3].decode('ascii')
self.version = data[pos+3:pos+6].decode('ascii')
print(f'Signature: {sig}')
print(f'Version: {self.version}')
pos += 6
# Logical Screen
self.width, self.height = struct.unpack('<HH', data[pos:pos+4])
packed = data[pos+4]
self.gct_flag = bool(packed & 0x80)
self.color_res = ((packed >> 4) & 0x07) + 1
self.g_sort = bool(packed & 0x08)
self.gct_size = 1 << ((packed & 0x07) + 1)
self.bg_index = data[pos+5]
self.aspect = data[pos+6]
print(f'Logical Screen Width: {self.width}, Height: {self.height}')
print(f'Global Color Table Flag: {self.gct_flag}, Color Resolution: {self.color_res} bits')
print(f'Global Sort Flag: {self.g_sort}, Global Color Table Size: {self.gct_size} colors')
print(f'Background Index: {self.bg_index}, Pixel Aspect Ratio: {self.aspect}')
pos += 7
# Global Color Table
if self.gct_flag:
for i in range(self.gct_size):
r = data[pos]
g = data[pos+1]
b = data[pos+2]
self.global_colors.append((r, g, b))
pos += 3
print(f'Global Color Table (first 5): {self.global_colors[:5]}')
frame_count = 0
while pos < len(data):
block = data[pos]
if block == 0x3B: # Trailer
break
elif block == 0x21: # Extension
pos += 1
ext = data[pos]
pos += 1
if ext == 0xF9: # GCE
pos += 1 # Block size 4
packed = data[pos]
disposal = (packed >> 2) & 0x07
user_input = bool(packed & 0x02)
trans_flag = bool(packed & 0x01)
pos += 1
delay = struct.unpack('<H', data[pos:pos+2])[0]
pos += 2
trans_index = data[pos]
pos += 1
pos += 1 # Terminator
print(f'\n--- Frame {frame_count + 1} Graphic Control ---')
print(f'Disposal: {disposal}, User Input: {user_input}, Transparent Flag: {trans_flag}')
print(f'Delay: {delay} cs, Transparent Index: {trans_index}')
elif ext == 0xFF: # Application
pos += 1 # Block size
app_id = data[pos:pos+11].decode('ascii', errors='ignore')
pos += 11
if app_id == 'NETSCAPE2.0':
pos += 1 # Data size 3
self.loop_count = struct.unpack('<H', data[pos:pos+2])[0]
pos += 3
print(f'Loop Count: {self.loop_count}')
# Skip sub-blocks
while data[pos] != 0:
pos += data[pos] + 1
pos += 1
elif ext == 0xFE: # Comment
comment = ''
while True:
sub_len = data[pos]
pos += 1
if sub_len == 0:
break
comment += data[pos:pos+sub_len].decode('ascii', errors='ignore')
pos += sub_len
self.comments.append(comment)
else:
# Skip
while data[pos] != 0:
pos += data[pos] + 1
pos += 1
elif block == 0x2C: # Image
frame_count += 1
print(f'\n--- Frame {frame_count} Image Descriptor ---')
pos += 1
left, top = struct.unpack('<HH', data[pos:pos+4])
pos += 4
i_width, i_height = struct.unpack('<HH', data[pos:pos+4])
pos += 4
packed = data[pos]
lct_flag = bool(packed & 0x80)
interlace = bool(packed & 0x40)
l_sort = bool(packed & 0x20)
lct_size = 1 << ((packed & 0x07) + 1)
pos += 1
print(f'Left: {left}, Top: {top}, Width: {i_width}, Height: {i_height}')
print(f'Local Color Table Flag: {lct_flag}, Interlace: {interlace}, Local Sort: {l_sort}')
print(f'Local Color Table Size: {lct_size}')
frame_colors = []
if lct_flag:
for i in range(lct_size):
r = data[pos]
g = data[pos+1]
b = data[pos+2]
frame_colors.append((r, g, b))
pos += 3
print(f'Local Color Table (first 3): {frame_colors[:3]}')
self.frames.append({'width': i_width, 'height': i_height, 'colors': frame_colors})
lzw_min = data[pos]
pos += 1
print(f'LZW Min Code Size: {lzw_min} bits')
data_size = 0
while data[pos] != 0:
data_size += data[pos] + 1
pos += data[pos] + 1
pos += 1
print(f'Compressed Data Size: {data_size} bytes')
print(f'\nTotal Frames: {frame_count}')
print(f'Comments: {"; ".join(self.comments)}')
def write(self, output_filename: str):
# Simple write: Copy original (no changes) for demo; extend for full encode
with open(self.filename, 'rb') as f:
data = f.read()
with open(output_filename, 'wb') as f:
f.write(data)
print(f'Wrote unchanged GIF to {output_filename}')
# Usage
parser = GIFParser('sample.gif')
parser.read()
parser.write('output.gif')
5. Java Class for GIF Handling
This class uses java.nio
for binary parsing. Opens file, decodes properties, prints to console. Write method copies the file (stub for full encoder; use ImageIO for advanced).
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class GIFParser {
private String filename;
private String version;
private int width, height;
private boolean gctFlag;
private int colorRes;
private boolean gSort;
private int gctSize;
private int bgIndex;
private int aspect;
private List<int[]> globalColors = new ArrayList<>();
private List<Object> frames = new ArrayList<>(); // Simplified
private int loopCount = 1;
private List<String> comments = new ArrayList<>();
public GIFParser(String filename) {
this.filename = filename;
}
public void read() throws IOException {
byte[] data = Files.readAllBytes(Paths.get(filename));
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int pos = 0;
// Header
byte[] sigBytes = new byte[3];
buffer.position(pos); buffer.get(sigBytes); pos += 3;
String sig = new String(sigBytes);
byte[] verBytes = new byte[3];
buffer.position(pos); buffer.get(verBytes); pos += 3;
version = new String(verBytes);
System.out.println("Signature: " + sig);
System.out.println("Version: " + version);
// Logical Screen
buffer.position(pos); width = buffer.getShort() & 0xFFFF; pos += 2;
buffer.position(pos); height = buffer.getShort() & 0xFFFF; pos += 2;
int packed = Byte.toUnsignedInt(buffer.get(pos++));
gctFlag = (packed & 0x80) != 0;
colorRes = ((packed >> 4) & 0x07) + 1;
gSort = (packed & 0x08) != 0;
gctSize = 1 << ((packed & 0x07) + 1);
bgIndex = Byte.toUnsignedInt(buffer.get(pos++));
aspect = Byte.toUnsignedInt(buffer.get(pos++));
System.out.println("Logical Screen Width: " + width + ", Height: " + height);
System.out.println("Global Color Table Flag: " + gctFlag + ", Color Resolution: " + colorRes + " bits");
System.out.println("Global Sort Flag: " + gSort + ", Global Color Table Size: " + gctSize + " colors");
System.out.println("Background Index: " + bgIndex + ", Pixel Aspect Ratio: " + aspect);
// Global Color Table
if (gctFlag) {
for (int i = 0; i < gctSize; i++) {
int r = Byte.toUnsignedInt(buffer.get(pos++));
int g = Byte.toUnsignedInt(buffer.get(pos++));
int b = Byte.toUnsignedInt(buffer.get(pos++));
globalColors.add(new int[]{r, g, b});
}
System.out.println("Global Color Table (first 5): " + globalColors.subList(0, Math.min(5, globalColors.size())));
}
int frameCount = 0;
while (pos < data.length) {
int block = Byte.toUnsignedInt(data[pos]);
if (block == 0x3B) break; // Trailer
else if (block == 0x21) { // Extension
pos++;
int ext = Byte.toUnsignedInt(data[pos++]);
if (ext == 0xF9) { // GCE
pos++; // Size 4
int packedGce = Byte.toUnsignedInt(data[pos++]);
int disposal = (packedGce >> 2) & 0x07;
boolean userInput = (packedGce & 0x02) != 0;
boolean transFlag = (packedGce & 0x01) != 0;
buffer.position(pos); int delay = buffer.getShort() & 0xFFFF; pos += 2;
int transIndex = Byte.toUnsignedInt(data[pos++]);
pos++; // Terminator
System.out.println("\n--- Frame " + (++frameCount) + " Graphic Control ---");
System.out.println("Disposal: " + disposal + ", User Input: " + userInput + ", Transparent Flag: " + transFlag);
System.out.println("Delay: " + delay + " cs, Transparent Index: " + transIndex);
} else if (ext == 0xFF) { // App
pos++; // Size
byte[] appBytes = new byte[11];
System.arraycopy(data, pos, appBytes, 0, 11);
pos += 11;
String appId = new String(appBytes).trim();
if ("NETSCAPE2.0".equals(appId)) {
pos++; // Data size
buffer.position(pos); loopCount = buffer.getShort() & 0xFFFF; pos += 2;
pos++; // Block terminator
System.out.println("Loop Count: " + loopCount);
}
// Skip sub-blocks
while (data[pos] != 0) {
pos += Byte.toUnsignedInt(data[pos]) + 1;
}
pos++;
} else if (ext == 0xFE) { // Comment
StringBuilder comment = new StringBuilder();
while (true) {
int subLen = Byte.toUnsignedInt(data[pos++]);
if (subLen == 0) break;
byte[] sub = new byte[subLen];
System.arraycopy(data, pos, sub, 0, subLen);
comment.append(new String(sub));
pos += subLen;
}
comments.add(comment.toString());
} else {
// Skip
while (data[pos] != 0) pos += Byte.toUnsignedInt(data[pos]) + 1;
pos++;
}
} else if (block == 0x2C) { // Image
System.out.println("\n--- Frame " + (++frameCount) + " Image Descriptor ---");
pos++;
buffer.position(pos); int left = buffer.getShort() & 0xFFFF; pos += 2;
buffer.position(pos); int top = buffer.getShort() & 0xFFFF; pos += 2;
buffer.position(pos); int iWidth = buffer.getShort() & 0xFFFF; pos += 2;
buffer.position(pos); int iHeight = buffer.getShort() & 0xFFFF; pos += 2;
int packedImg = Byte.toUnsignedInt(data[pos++]);
boolean lctFlag = (packedImg & 0x80) != 0;
boolean interlace = (packedImg & 0x40) != 0;
boolean lSort = (packedImg & 0x20) != 0;
int lctSize = 1 << ((packedImg & 0x07) + 1);
System.out.println("Left: " + left + ", Top: " + top + ", Width: " + iWidth + ", Height: " + iHeight);
System.out.println("Local Color Table Flag: " + lctFlag + ", Interlace: " + interlace + ", Local Sort: " + lSort);
System.out.println("Local Color Table Size: " + lctSize);
List<int[]> frameColors = new ArrayList<>();
if (lctFlag) {
for (int i = 0; i < lctSize; i++) {
int r = Byte.toUnsignedInt(data[pos++]);
int g = Byte.toUnsignedInt(data[pos++]);
int b = Byte.toUnsignedInt(data[pos++]);
frameColors.add(new int[]{r, g, b});
}
System.out.println("Local Color Table (first 3): " + frameColors.subList(0, Math.min(3, frameColors.size())));
}
int lzwMin = Byte.toUnsignedInt(data[pos++]);
System.out.println("LZW Min Code Size: " + lzwMin + " bits");
int dataSize = 0;
while (data[pos] != 0) {
dataSize += Byte.toUnsignedInt(data[pos]) + 1;
pos += Byte.toUnsignedInt(data[pos]) + 1;
}
pos++;
System.out.println("Compressed Data Size: " + dataSize + " bytes");
} else {
System.out.println("Unknown block 0x" + Integer.toHexString(block));
break;
}
}
System.out.println("\nTotal Frames: " + frameCount);
System.out.println("Comments: " + String.join("; ", comments));
}
public void write(String outputFilename) throws IOException {
// Simple copy for demo
Files.copy(Paths.get(filename), Paths.get(outputFilename));
System.out.println("Wrote GIF to " + outputFilename);
}
// Usage
public static void main(String[] args) throws IOException {
GIFParser parser = new GIFParser("sample.gif");
parser.read();
parser.write("output.gif");
}
}
6. JavaScript Class for GIF Handling (Node.js)
This Node.js class uses fs
to read the file, parses properties, prints to console. Write method copies the file (extend with LZW encoder for full).
const fs = require('fs');
class GIFParser {
constructor(filename) {
this.filename = filename;
this.version = '';
this.width = 0;
this.height = 0;
this.gctFlag = false;
this.colorRes = 0;
this.gSort = false;
this.gctSize = 0;
this.bgIndex = 0;
this.aspect = 0;
this.globalColors = [];
this.frames = [];
this.loopCount = 1;
this.comments = [];
}
read() {
const data = fs.readFileSync(this.filename);
let pos = 0;
// Header
const sig = data.toString('ascii', pos, pos + 3);
pos += 3;
this.version = data.toString('ascii', pos, pos + 3);
pos += 3;
console.log(`Signature: ${sig}`);
console.log(`Version: ${this.version}`);
// Logical Screen
this.width = data.readUInt16LE(pos); pos += 2;
this.height = data.readUInt16LE(pos); pos += 2;
const packed = data[pos++];
this.gctFlag = !!(packed & 0x80);
this.colorRes = ((packed >> 4) & 0x07) + 1;
this.gSort = !!(packed & 0x08);
this.gctSize = 1 << ((packed & 0x07) + 1);
this.bgIndex = data[pos++];
this.aspect = data[pos++];
console.log(`Logical Screen Width: ${this.width}, Height: ${this.height}`);
console.log(`Global Color Table Flag: ${this.gctFlag}, Color Resolution: ${this.colorRes} bits`);
console.log(`Global Sort Flag: ${this.gSort}, Global Color Table Size: ${this.gctSize} colors`);
console.log(`Background Index: ${this.bgIndex}, Pixel Aspect Ratio: ${this.aspect}`);
// Global Color Table
if (this.gctFlag) {
for (let i = 0; i < this.gctSize; i++) {
const r = data[pos++];
const g = data[pos++];
const b = data[pos++];
this.globalColors.push([r, g, b]);
}
console.log(`Global Color Table (first 5): ${JSON.stringify(this.globalColors.slice(0, 5))}`);
}
let frameCount = 0;
while (pos < data.length) {
const block = data[pos];
if (block === 0x3B) break; // Trailer
else if (block === 0x21) { // Extension
pos++;
const ext = data[pos++];
if (ext === 0xF9) { // GCE
pos++; // Size
const packedGce = data[pos++];
const disposal = (packedGce >> 2) & 0x07;
const userInput = !!(packedGce & 0x02);
const transFlag = !!(packedGce & 0x01);
const delay = data.readUInt16LE(pos); pos += 2;
const transIndex = data[pos++];
pos++; // Terminator
console.log(`\n--- Frame ${++frameCount} Graphic Control ---`);
console.log(`Disposal: ${disposal}, User Input: ${userInput}, Transparent Flag: ${transFlag}`);
console.log(`Delay: ${delay} cs, Transparent Index: ${transIndex}`);
} else if (ext === 0xFF) { // App
pos++; // Size
const appStart = pos;
pos += 11;
const appId = data.toString('ascii', appStart, pos).trim();
if (appId === 'NETSCAPE2.0') {
pos++; // Data size
this.loopCount = data.readUInt16LE(pos); pos += 2;
pos++; // Terminator
console.log(`Loop Count: ${this.loopCount}`);
}
// Skip sub-blocks
while (data[pos] !== 0) {
pos += data[pos] + 1;
}
pos++;
} else if (ext === 0xFE) { // Comment
let comment = '';
while (true) {
const subLen = data[pos++];
if (subLen === 0) break;
comment += data.toString('ascii', pos, pos + subLen);
pos += subLen;
}
this.comments.push(comment);
} else {
// Skip
while (data[pos] !== 0) pos += data[pos] + 1;
pos++;
}
} else if (block === 0x2C) { // Image
console.log(`\n--- Frame ${++frameCount} Image Descriptor ---`);
pos++;
const left = data.readUInt16LE(pos); pos += 2;
const top = data.readUInt16LE(pos); pos += 2;
const iWidth = data.readUInt16LE(pos); pos += 2;
const iHeight = data.readUInt16LE(pos); pos += 2;
const packedImg = data[pos++];
const lctFlag = !!(packedImg & 0x80);
const interlace = !!(packedImg & 0x40);
const lSort = !!(packedImg & 0x20);
const lctSize = 1 << ((packedImg & 0x07) + 1);
console.log(`Left: ${left}, Top: ${top}, Width: ${iWidth}, Height: ${iHeight}`);
console.log(`Local Color Table Flag: ${lctFlag}, Interlace: ${interlace}, Local Sort: ${lSort}`);
console.log(`Local Color Table Size: ${lctSize}`);
let frameColors = [];
if (lctFlag) {
for (let i = 0; i < lctSize; i++) {
const r = data[pos++];
const g = data[pos++];
const b = data[pos++];
frameColors.push([r, g, b]);
}
console.log(`Local Color Table (first 3): ${JSON.stringify(frameColors.slice(0, 3))}`);
}
const lzwMin = data[pos++];
console.log(`LZW Min Code Size: ${lzwMin} bits`);
let dataSize = 0;
while (data[pos] !== 0) {
dataSize += data[pos] + 1;
pos += data[pos] + 1;
}
pos++;
console.log(`Compressed Data Size: ${dataSize} bytes`);
} else {
console.log(`Unknown block 0x${block.toString(16)}`);
break;
}
}
console.log(`\nTotal Frames: ${frameCount}`);
console.log(`Comments: ${this.comments.join('; ')}`);
}
write(outputFilename) {
// Simple copy
fs.copyFileSync(this.filename, outputFilename);
console.log(`Wrote GIF to ${outputFilename}`);
}
}
// Usage
const parser = new GIFParser('sample.gif');
parser.read();
parser.write('output.gif');
7. C Code for GIF Handling (Struct with Functions)
This C program uses stdio
/stdlib
for file I/O. Defines a struct for properties, parses and prints. Write function copies the file (basic; full LZW requires more code).
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
char signature[4];
char version[4];
uint16_t width;
uint16_t height;
uint8_t gct_flag;
uint8_t color_res;
uint8_t g_sort;
uint8_t gct_size;
uint8_t bg_index;
uint8_t aspect;
// Simplified: no full color arrays
int frame_count;
int loop_count;
char** comments;
} GIFProperties;
void read_gif(const char* filename, GIFProperties* props) {
FILE* f = fopen(filename, "rb");
if (!f) {
perror("File open error");
return;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t* data = malloc(size);
fread(data, 1, size, f);
fclose(f);
int pos = 0;
// Header
memcpy(props->signature, data + pos, 3);
props->signature[3] = '\0';
pos += 3;
memcpy(props->version, data + pos, 3);
props->version[3] = '\0';
pos += 3;
printf("Signature: %s\n", props->signature);
printf("Version: %s\n", props->version);
// Logical Screen
props->width = data[pos] | (data[pos+1] << 8);
pos += 2;
props->height = data[pos] | (data[pos+1] << 8);
pos += 2;
uint8_t packed = data[pos++];
props->gct_flag = !!(packed & 0x80);
props->color_res = ((packed >> 4) & 0x07) + 1;
props->g_sort = !!(packed & 0x08);
props->gct_size = 1 << ((packed & 0x07) + 1);
props->bg_index = data[pos++];
props->aspect = data[pos++];
printf("Logical Screen Width: %d, Height: %d\n", props->width, props->height);
printf("Global Color Table Flag: %d, Color Resolution: %d bits\n", props->gct_flag, props->color_res);
printf("Global Sort Flag: %d, Global Color Table Size: %d colors\n", props->g_sort, props->gct_size);
printf("Background Index: %d, Pixel Aspect Ratio: %d\n", props->bg_index, props->aspect);
// Skip Global Color Table for brevity
if (props->gct_flag) {
pos += 3 * props->gct_size;
printf("Global Color Table skipped (size: %d colors)\n", props->gct_size);
}
props->frame_count = 0;
props->loop_count = 1;
// Comments array init
props->comments = NULL;
while (pos < size) {
uint8_t block = data[pos];
if (block == 0x3B) break; // Trailer
else if (block == 0x21) { // Extension
pos++;
uint8_t ext = data[pos++];
if (ext == 0xF9) { // GCE
pos++; // Size
uint8_t packed_gce = data[pos++];
int disposal = (packed_gce >> 2) & 0x07;
int user_input = !!(packed_gce & 0x02);
int trans_flag = !!(packed_gce & 0x01);
uint16_t delay = data[pos] | (data[pos+1] << 8);
pos += 2;
int trans_index = data[pos++];
pos++; // Terminator
printf("\n--- Frame %d Graphic Control ---\n", ++props->frame_count);
printf("Disposal: %d, User Input: %d, Transparent Flag: %d\n", disposal, user_input, trans_flag);
printf("Delay: %d cs, Transparent Index: %d\n", delay, trans_index);
} else if (ext == 0xFF) { // App
pos++; // Size
if (strncmp((char*)(data + pos), "NETSCAPE2.0", 11) == 0) {
pos += 11;
pos++; // Data size
props->loop_count = data[pos] | (data[pos+1] << 8);
pos += 2;
pos++; // Terminator
printf("Loop Count: %d\n", props->loop_count);
}
// Skip sub-blocks
while (data[pos] != 0) {
pos += data[pos] + 1;
}
pos++;
} else if (ext == 0xFE) { // Comment
char* comment = malloc(1024); // Simple alloc
int comment_len = 0;
while (1) {
uint8_t sub_len = data[pos++];
if (sub_len == 0) break;
memcpy(comment + comment_len, data + pos, sub_len);
comment_len += sub_len;
pos += sub_len;
}
comment[comment_len] = '\0';
// Add to list (simplified, no realloc)
printf("Comment: %s\n", comment);
free(comment);
} else {
// Skip
while (data[pos] != 0) pos += data[pos] + 1;
pos++;
}
} else if (block == 0x2C) { // Image
props->frame_count++;
printf("\n--- Frame %d Image Descriptor ---\n", props->frame_count);
pos++;
uint16_t left = data[pos] | (data[pos+1] << 8); pos += 2;
uint16_t top = data[pos] | (data[pos+1] << 8); pos += 2;
uint16_t i_width = data[pos] | (data[pos+1] << 8); pos += 2;
uint16_t i_height = data[pos] | (data[pos+1] << 8); pos += 2;
uint8_t packed_img = data[pos++];
int lct_flag = !!(packed_img & 0x80);
int interlace = !!(packed_img & 0x40);
int l_sort = !!(packed_img & 0x20);
int lct_size = 1 << ((packed_img & 0x07) + 1);
printf("Left: %d, Top: %d, Width: %d, Height: %d\n", left, top, i_width, i_height);
printf("Local Color Table Flag: %d, Interlace: %d, Local Sort: %d\n", lct_flag, interlace, l_sort);
printf("Local Color Table Size: %d\n", lct_size);
if (lct_flag) {
pos += 3 * lct_size; // Skip
printf("Local Color Table skipped\n");
}
int lzw_min = data[pos++];
printf("LZW Min Code Size: %d bits\n", lzw_min);
int data_size = 0;
while (data[pos] != 0) {
data_size += data[pos] + 1;
pos += data[pos] + 1;
}
pos++;
printf("Compressed Data Size: %d bytes\n", data_size);
} else {
printf("Unknown block 0x%02x\n", block);
break;
}
}
printf("\nTotal Frames: %d\n", props->frame_count);
free(data);
}
void write_gif(const char* input_filename, const char* output_filename) {
// Simple copy
FILE* in = fopen(input_filename, "rb");
FILE* out = fopen(output_filename, "wb");
if (!in || !out) {
perror("Copy error");
return;
}
fseek(in, 0, SEEK_END);
long fsize = ftell(in);
fseek(in, 0, SEEK_SET);
uint8_t* buf = malloc(fsize);
fread(buf, 1, fsize, in);
fwrite(buf, 1, fsize, out);
fclose(in);
fclose(out);
free(buf);
printf("Wrote GIF to %s\n", output_filename);
}
int main() {
GIFProperties props = {0};
read_gif("sample.gif", &props);
write_gif("sample.gif", "output.gif");
return 0;
}