Task 036: .ART File Format
Task 036: .ART File Format
- The properties of the .ART file format intrinsic to its structure are as follows:
- Signature: 2 bytes ('J' 'G')
- Version: 1 byte (typically 0x03 or 0x04)
- Unknown field 1: 1 byte (typically 0x0E)
- Padding: 9 bytes (typically 0x00)
- Width: 2 bytes (little-endian unsigned integer)
- Height: 2 bytes (little-endian unsigned integer)
- Compressed image data: Remainder of the file (proprietary Johnson-Grace compression algorithm)
- Two direct download links for .ART files:
- https://samples.ffmpeg.org/image-samples/ART/6-BOATS.ART
- https://samples.ffmpeg.org/image-samples/ART/BOAT6.ART
- The following is an embedded HTML and JavaScript code snippet suitable for a Ghost blog post. It enables drag-and-drop functionality for .ART files and displays the extracted properties on the screen:
Drag and drop a .ART file here
- The following is a Python class for handling .ART files:
import struct
class ArtFileHandler:
def __init__(self, filename):
self.filename = filename
self.signature = None
self.version = None
self.unknown1 = None
self.padding = None
self.width = None
self.height = None
self.compressed_data = None
def open_and_decode(self):
with open(self.filename, 'rb') as f:
data = f.read()
if len(data) < 17:
raise ValueError("File too small for .ART header")
self.signature = data[0:2].decode('ascii', errors='ignore')
self.version = hex(data[2])
self.unknown1 = hex(data[3])
self.padding = ' '.join(hex(b) for b in data[4:13])
self.width = struct.unpack('<H', data[13:15])[0]
self.height = struct.unpack('<H', data[15:17])[0]
self.compressed_data = data[17:]
def print_properties(self):
print(f"Signature: {self.signature}")
print(f"Version: {self.version}")
print(f"Unknown field 1: {self.unknown1}")
print(f"Padding: {self.padding}")
print(f"Width: {self.width}")
print(f"Height: {self.height}")
print(f"Compressed image data size: {len(self.compressed_data)} bytes")
def write(self, new_filename):
if self.signature is None:
raise ValueError("No data decoded yet")
header = (
self.signature.encode('ascii') +
bytes([int(self.version, 0)]) +
bytes([int(self.unknown1, 0)]) +
bytes([int(h, 0) for h in self.padding.split()]) +
struct.pack('<H', self.width) +
struct.pack('<H', self.height)
)
with open(new_filename, 'wb') as f:
f.write(header + self.compressed_data)
# Example usage:
# handler = ArtFileHandler('example.ART')
# handler.open_and_decode()
# handler.print_properties()
# handler.write('new.ART')
- The following is a Java class for handling .ART files:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class ArtFileHandler {
private String filename;
private String signature;
private String version;
private String unknown1;
private String padding;
private int width;
private int height;
private byte[] compressedData;
public ArtFileHandler(String filename) {
this.filename = filename;
}
public void openAndDecode() throws IOException {
byte[] data = readFileToByteArray(filename);
if (data.length < 17) {
throw new IOException("File too small for .ART header");
}
signature = new String(data, 0, 2);
version = String.format("0x%02X", data[2]);
unknown1 = String.format("0x%02X", data[3]);
StringBuilder padBuilder = new StringBuilder();
for (int i = 4; i < 13; i++) {
padBuilder.append(String.format("%02X ", data[i]));
}
padding = padBuilder.toString().trim();
ByteBuffer buffer = ByteBuffer.wrap(data, 13, 4).order(ByteOrder.LITTLE_ENDIAN);
width = buffer.getShort();
height = buffer.getShort();
compressedData = new byte[data.length - 17];
System.arraycopy(data, 17, compressedData, 0, compressedData.length);
}
public void printProperties() {
System.out.println("Signature: " + signature);
System.out.println("Version: " + version);
System.out.println("Unknown field 1: " + unknown1);
System.out.println("Padding: " + padding);
System.out.println("Width: " + width);
System.out.println("Height: " + height);
System.out.println("Compressed image data size: " + compressedData.length + " bytes");
}
public void write(String newFilename) throws IOException {
if (signature == null) {
throw new IOException("No data decoded yet");
}
ByteArrayOutputStream header = new ByteArrayOutputStream();
header.write(signature.getBytes());
header.write((byte) Integer.parseInt(version.substring(2), 16));
header.write((byte) Integer.parseInt(unknown1.substring(2), 16));
String[] padBytes = padding.split(" ");
for (String pad : padBytes) {
header.write((byte) Integer.parseInt(pad, 16));
}
ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
buffer.putShort((short) width);
buffer.putShort((short) height);
header.write(buffer.array());
try (FileOutputStream fos = new FileOutputStream(newFilename)) {
fos.write(header.toByteArray());
fos.write(compressedData);
}
}
private byte[] readFileToByteArray(String filePath) throws IOException {
try (FileInputStream fis = new FileInputStream(filePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// ArtFileHandler handler = new ArtFileHandler("example.ART");
// handler.openAndDecode();
// handler.printProperties();
// handler.write("new.ART");
// }
}
- The following is a JavaScript class for handling .ART files (node.js compatible, using fs module):
const fs = require('fs');
class ArtFileHandler {
constructor(filename) {
this.filename = filename;
this.signature = null;
this.version = null;
this.unknown1 = null;
this.padding = null;
this.width = null;
this.height = null;
this.compressedData = null;
}
openAndDecode() {
const data = fs.readFileSync(this.filename);
if (data.length < 17) {
throw new Error('File too small for .ART header');
}
this.signature = data.toString('ascii', 0, 2);
this.version = '0x' + data[2].toString(16).padStart(2, '0');
this.unknown1 = '0x' + data[3].toString(16).padStart(2, '0');
this.padding = Array.from(data.slice(4, 13)).map(b => b.toString(16).padStart(2, '0')).join(' ');
const view = new DataView(data.buffer);
this.width = view.getUint16(13, true);
this.height = view.getUint16(15, true);
this.compressedData = data.slice(17);
}
printProperties() {
console.log(`Signature: ${this.signature}`);
console.log(`Version: ${this.version}`);
console.log(`Unknown field 1: ${this.unknown1}`);
console.log(`Padding: ${this.padding}`);
console.log(`Width: ${this.width}`);
console.log(`Height: ${this.height}`);
console.log(`Compressed image data size: ${this.compressedData.length} bytes`);
}
write(newFilename) {
if (this.signature === null) {
throw new Error('No data decoded yet');
}
const header = Buffer.alloc(17);
header.write(this.signature, 0, 2, 'ascii');
header[2] = parseInt(this.version, 16);
header[3] = parseInt(this.unknown1, 16);
this.padding.split(' ').forEach((hex, i) => {
header[4 + i] = parseInt(hex, 16);
});
const view = new DataView(header.buffer);
view.setUint16(13, this.width, true);
view.setUint16(15, this.height, true);
const fullBuffer = Buffer.concat([header, this.compressedData]);
fs.writeFileSync(newFilename, fullBuffer);
}
}
// Example usage:
// const handler = new ArtFileHandler('example.ART');
// handler.openAndDecode();
// handler.printProperties();
// handler.write('new.ART');
- The following is a C++ class for handling .ART files:
#include <iostream>
#include <fstream>
#include <vector>
#include <iomanip>
#include <cstring>
class ArtFileHandler {
private:
std::string filename;
char signature[3]; // Null-terminated
unsigned char version;
unsigned char unknown1;
unsigned char padding[9];
unsigned short width;
unsigned short height;
std::vector<unsigned char> compressedData;
public:
ArtFileHandler(const std::string& fn) : filename(fn), version(0), unknown1(0), width(0), height(0) {
std::memset(signature, 0, sizeof(signature));
std::memset(padding, 0, sizeof(padding));
}
void openAndDecode() {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
throw std::runtime_error("Unable to open file");
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
if (size < 17) {
throw std::runtime_error("File too small for .ART header");
}
std::vector<unsigned char> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
signature[0] = data[0];
signature[1] = data[1];
signature[2] = '\0';
version = data[2];
unknown1 = data[3];
std::memcpy(padding, &data[4], 9);
width = static_cast<unsigned short>(data[13]) | (static_cast<unsigned short>(data[14]) << 8);
height = static_cast<unsigned short>(data[15]) | (static_cast<unsigned short>(data[16]) << 8);
compressedData.assign(data.begin() + 17, data.end());
}
void printProperties() {
std::cout << "Signature: " << signature << std::endl;
std::cout << "Version: 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(version) << std::dec << std::endl;
std::cout << "Unknown field 1: 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(unknown1) << std::dec << std::endl;
std::cout << "Padding: ";
for (int i = 0; i < 9; ++i) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(padding[i]) << " ";
}
std::cout << std::dec << std::endl;
std::cout << "Width: " << width << std::endl;
std::cout << "Height: " << height << std::endl;
std::cout << "Compressed image data size: " << compressedData.size() << " bytes" << std::endl;
}
void write(const std::string& newFilename) {
if (std::strlen(signature) == 0) {
throw std::runtime_error("No data decoded yet");
}
std::ofstream file(newFilename, std::ios::binary);
if (!file.is_open()) {
throw std::runtime_error("Unable to write file");
}
file.write(signature, 2);
file.write(reinterpret_cast<const char*>(&version), 1);
file.write(reinterpret_cast<const char*>(&unknown1), 1);
file.write(reinterpret_cast<const char*>(padding), 9);
unsigned char widthBytes[2] = { static_cast<unsigned char>(width & 0xFF), static_cast<unsigned char>(width >> 8) };
file.write(reinterpret_cast<const char*>(widthBytes), 2);
unsigned char heightBytes[2] = { static_cast<unsigned char>(height & 0xFF), static_cast<unsigned char>(height >> 8) };
file.write(reinterpret_cast<const char*>(heightBytes), 2);
file.write(reinterpret_cast<const char*>(compressedData.data()), compressedData.size());
}
};
// Example usage:
// int main() {
// try {
// ArtFileHandler handler("example.ART");
// handler.openAndDecode();
// handler.printProperties();
// handler.write("new.ART");
// } catch (const std::exception& e) {
// std::cerr << e.what() << std::endl;
// }
// return 0;
// }