// .pxc file structure:
// [4 bytes] magic: "PXC\0"
// [4 bytes] format version (uint32)
// [4 bytes] metadata length (uint32)
// [N bytes] metadata (JSON, gzipped)
// [remaining] image data (gzipped)
interface PXCMetadata {
version: number; // format version
appVersion: string;
createdAt: string;
updatedAt: string;
canvas: {
width: number;
height: number;
};
filters: {
brightness: number;
contrast: number;
saturation: number;
blur: number;
};
history: {
label: string;
timestamp: number;
}[];
customData: Record<string, unknown>;
// ↑ extensible for future features
}
async function saveProject(
metadata: PXCMetadata,
imageData: ImageData
): Promise<Blob> {
// 1. Compress metadata
const metaJson = JSON.stringify(
metadata);
const metaBytes =
await compress(
new TextEncoder()
.encode(metaJson));
// 2. Compress image data
const imageBytes =
await compress(
imageData.data.buffer);
// 3. Build file
const header = new ArrayBuffer(12);
const view = new DataView(header);
// Magic bytes: "PXC\0"
view.setUint8(0, 0x50); // P
view.setUint8(1, 0x58); // X
view.setUint8(2, 0x43); // C
view.setUint8(3, 0x00); // \0
// Version
view.setUint32(4, 1); // v1
// Metadata length
view.setUint32(8,
metaBytes.byteLength);
return new Blob([
header, metaBytes, imageBytes
], { type:
'application/x-pixelcraft' });
}
async function compress(
data: ArrayBuffer
): Promise<ArrayBuffer> {
const stream = new Blob([data])
.stream()
.pipeThrough(
new CompressionStream('gzip'));
return new Response(stream)
.arrayBuffer();
}
async function loadProject(
file: File
): Promise<{
metadata: PXCMetadata;
imageData: ImageData;
}> {
const buffer =
await file.arrayBuffer();
const view = new DataView(buffer);
// 1. Verify magic bytes
const magic = String.fromCharCode(
view.getUint8(0),
view.getUint8(1),
view.getUint8(2));
if (magic !== 'PXC')
throw new Error(
'Not a PixelCraft file');
// 2. Read version
const version = view.getUint32(4);
// 3. Read metadata
const metaLen = view.getUint32(8);
const metaBytes = buffer.slice(
12, 12 + metaLen);
const metaJson =
new TextDecoder().decode(
await decompress(metaBytes));
let metadata: PXCMetadata =
JSON.parse(metaJson);
// 4. Migrate if needed
metadata = migrateMetadata(
metadata, version);
// 5. Read image data
const imgBytes = buffer.slice(
12 + metaLen);
const pixels = new Uint8ClampedArray(
await decompress(imgBytes));
const imageData = new ImageData(
pixels,
metadata.canvas.width,
metadata.canvas.height);
return { metadata, imageData };
}
const CURRENT_VERSION = 1;
function migrateMetadata(
meta: any,
fromVersion: number
): PXCMetadata {
let data = { ...meta };
// v1 → v2 (future example):
// if (fromVersion < 2) {
// data.layers = [{
// id: 'base',
// name: 'Background',
// visible: true,
// }];
// data.version = 2;
// }
// v2 → v3 (future example):
// if (fromVersion < 3) {
// data.colorProfile = 'srgb';
// data.version = 3;
// }
// Each migration is a pure function:
// old shape → new shape.
// Chain them: v1 → v2 → v3.
// Old files always open correctly.
if (fromVersion > CURRENT_VERSION) {
console.warn(
`File is v${fromVersion},`
+ ` app supports v${
CURRENT_VERSION}.`
+ ` Some features may not load.`
);
}
return data as PXCMetadata;
}
// Save button handler
async function handleSave() {
const blob = await saveProject(
getMetadata(), getCurrentImageData()
);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `project-${
Date.now()}.pxc`;
a.click();
URL.revokeObjectURL(url);
}
// Open: file input with accept
<input
type="file"
accept=".pxc"
onChange={async (e) => {
const file = e.target.files?.[0];
if (!file) return;
const { metadata, imageData } =
await loadProject(file);
restoreState(metadata, imageData);
}}
/>
git switch -c feature/PIXELCRAFT-099-pxc-format
git add src/lib/fileFormat.ts
git commit -m "Add .pxc project file format (PIXELCRAFT-099)"
git push origin feature/PIXELCRAFT-099-pxc-format
# PR → Review → Merge → Close ticket ✅
File format design is protocol design.
The same principles apply to file formats, network protocols, and APIs: magic numbers, versioning, backward compatibility, and extensibility.