Copilot / AI Agent Instructions for xcfreader

February 11, 2026 · View on GitHub

Summary

  • Small Node.js library that parses GIMP .xcf files and exposes a TypeScript/JS API to read metadata, layers and render images.
  • Source is in src/ (TypeScript). dist/ contains compiled output (built with TypeScript compiler tsc) and is the package target during runtime.

Quick workflow

  • Install dev deps: npm install.
  • Build TypeScript: npm run build (runs tsc to compile src/**/*.tsdist/).
  • Run examples (uses node + nodemon): npm run single, npm run multi, npm run map, npm run text, npm run empty.
    • These commands now build TypeScript first, then execute the compiled JS.
  • Run tests: npm test (builds and runs dist/tests/runner.js).
  • Watch mode: npm run watch (continuously recompiles TypeScript as you edit).

Where to look (key files)

Architecture & implementation notes

  • Binary parsing: Uses custom BinaryReader class (in lib/binary-reader.ts) for efficient XCF parsing.
    • Lightweight implementation (~1.5 KB minified) with only XCF-specific operations
    • Functional parsers in lib/xcf-parsers.ts for all XCF structures
    • XCF_PropType enum in types/index.ts defines property types.
    • XCF v011 support: Separate parser functions for v010 (32-bit) and v011 (64-bit pointers):
      • parseGimpHeaderV10/parseGimpHeaderV11 - header with layer/channel pointer lists
      • parseLayerV10/parseLayerV11 - layer with hptr/mptr pointers
      • parseHierarchyV10/parseHierarchyV11 - hierarchy with lptr pointer
      • parseLevelV10/parseLevelV11 - level with tptr tile pointer array
    • XCFParser.isV11 getter detects version; GimpLayer selects correct parser at runtime.
    • Perfect TypeScript types for all parsed structures
  • Buffer management: XCF is a single binary Buffer; offsets/pointers index into it. Use XCFParser.getBufferForPointer(offset) to slice.
  • Tiled rendering: Layers use 64×64 tile blocks. GimpLayer.uncompress() decompresses tile data; copyTile() writes pixels to image.
  • Compositing: XCFCompositer.makeCompositer(mode, opacity) returns compositing logic (blend modes); used in GimpLayer.makeImage().
  • Image classes: Two implementations of IXCFImage interface:
    • XCFPNGImage (Node.js) - wraps pngjs, has writeImage() for file output
    • XCFDataImage (Browser) - uses Uint8ClampedArray, has imageData getter for canvas
  • Entry points: Separate modules for different environments:
    • gimpparser.ts - base module with parser, no image classes
    • node.ts - re-exports everything plus XCFPNGImage
    • browser.ts - re-exports everything plus XCFDataImage
  • Type safety: Full TypeScript with strict mode; interfaces for ColorRGBA, IXCFImage, Parser result types.

Conventions & patterns

  • Edit source in src/ only. TypeScript files (.ts) are compiled to dist/ by npm run build.
  • Do not edit dist/ except to inspect compiled output for debugging transpilation.
  • Prefer small, focused parser changes; add validation checks for expected field lengths and values.
  • Use native Array methods (filter, map, forEach, find, slice, reverse) for array flows.
  • Async API: XCFParser.parseFileAsync(file) returns Promise<XCFParser>; tests and examples use async/await.
  • When adding API features, update readme.md and examples in src/examples/.

Building & running

Build TypeScript:

npm run build

Run examples (auto-builds first):

npm run single    # parse and render single.xcf with live reload (nodemon)
npm run multi     # parse and render multi.xcf
npm run map       # parse and render specific layers from map1.xcf
npm run text      # parse text.xcf with parasite inspection
npm run empty     # test parsing empty.xcf
npm run grey      # parse grayscale v011 file (64-bit pointers)

Run tests (auto-builds first):

npm test

Watch mode (recompile on file changes):

npm run watch

Testing tips

  • Tests are in ../src/tests/ and run via npm test.
  • Test files are numbered (01-parse-single.ts, etc.) and auto-imported by runner.ts.
  • Each test imports from ../node.js (for XCFPNGImage) or ../gimpparser.js (parser only).
  • Add new tests as src/tests/NN-description.ts and export a testNNFunction with signature: async function testNN(): Promise<void>.
  • Add the test to the imports in runner.ts.

Common edits

Adding a new layer property:

  1. Add PROP_MY_THING = N to XCF_PropType enum in types/index.ts.
  2. Create a parser function in lib/xcf-parsers.ts (see existing patterns).
  3. Add a case in parseProperty() switch statement for XCF_PropType.PROP_MY_THING.
  4. Access via layer.getProps(XCF_PropType.PROP_MY_THING) in parsing code.

Updating compositing logic:

  • Blending math lives in XCFCompositer and subclasses in src/lib/xcfcompositer.ts.
  • Each blend mode is a case in GeneralCompositer.chooseFunction().
  • Update constants (PROP_MODE_*) and switch logic; tests verify against test images.

Adding an example:

  1. Create ../src/examples/myexample.ts.
  2. Import { XCFParser as GimpParser, XCFPNGImage } from '../node.js'.
  3. Use GimpParser.parseFileAsync(path), create new XCFPNGImage(w, h), and call parser.createImage(image).
  4. Add script to package.json scripts: "myexample": "npm run build && nodemon --exec node dist/examples/myexample.js".
  5. Export or log results.

Type system

  • Color type: { red: number; green: number; blue: number; alpha?: number } (0–255 range).
  • ColorRGBA type: { red; green; blue; alpha: number } (always includes alpha).
  • XCF_BaseType enum: RGB = 0, GRAYSCALE = 1, INDEXED = 2 - image color mode.
  • Parser result types: Strongly typed interfaces for all XCF structures (e.g., ParsedGimpHeaderV10, ParsedLayerV11).
  • Full strict: true in tsconfig.json; be explicit with types or use as unknown as T for unavoidable type conversions.

Debugging

  • Compile errors: Check tsconfig.json; ensure types match (especially Parser result types and null-coalescing).
  • Runtime errors: Add breakpoints in src/gimpparser.ts and run examples with Node debugger: node --inspect dist/examples/single.js.
  • Binary parsing issues: Log parser output at suspicious offsets; use getBufferForPointer() and inspect raw bytes.
  • Image rendering bugs: Add debug logs in GimpLayer.copyTile() or compositing logic in xcfcompositer.ts.

Further reading