GrdKit package illustration

GrdKit

GrdKit is a pure Dart codec for Adobe Photoshop gradient libraries (.grd). It reads current descriptor-based libraries and legacy version 3 libraries, writes version 5 libraries, and preserves data it does not interpret. It has no Flutter or native-code dependency, and builds on the shared Photoshop primitives of pscore.

Supported data

  • 8BGR version 5 libraries: a version 16 Action Descriptor holding the GrdL gradient list.
  • Custom-stop gradients (CstS) with color stops, transparency stops, midpoints, and smoothness.
  • Color-noise gradients (ClNs) with their seed, roughness, color model, and channel ranges.
  • RGB, CMYK, grayscale, HSB, Lab, and color-book stop colors, plus foreground and background stops.
  • Legacy 8BGR version 3 libraries, converted to the same descriptor model.
  • Trailing tagged blocks, including phry group hierarchies, and unrecognized trailing bytes.
  • Configurable limits for file size, gradients, legacy stops, descriptors, hierarchy entries, and tagged blocks.

The complete descriptors remain accessible alongside the typed gradient views. Unknown descriptor keys are retained; exact reconstruction depends on the input structure and the preservation options described in docs/GRD.md.

Reading a file

import 'dart:io';
import 'dart:typed_data';

import 'package:grdkit/grdkit.dart';

final Uint8List bytes = await File('gradients.grd').readAsBytes();
final GrdFile library = GrdDecoder.decode(bytes);

for (int index = 0; index < library.gradients.length; index++) {
  final PsGradient gradient = library.gradients[index].gradient;
  print('${gradient.name}: ${gradient.colorStops.length} color stops');
  print('  groups: ${library.groupPathOf(index).join(' / ')}');
}

GrdGradient.gradient exposes a shared PsGradient view. Use GrdGradient.descriptor for the complete wrapper or GrdFile.rootDescriptor for library-level fields.

Writing and editing

final GrdFile created = GrdFile.create([
  PsGradient.custom(
    name: 'Sunset',
    colorStops: [
      PsGradientColorStop.create(
        color: PsColor.rgb(red: 255, green: 94, blue: 0),
        location: 0,
      ),
      PsGradientColorStop.create(
        color: PsColor.rgb(red: 90, green: 0, blue: 160),
        location: 4096,
      ),
    ],
  ),
]);

final Uint8List output = GrdEncoder.encode(created);
await File('sunset.grd').writeAsBytes(output, flush: true);

Encoding always produces a version 5 library, including when the input used the legacy version 3 layout. GrdGradient.withGradient replaces a gradient while retaining the surrounding descriptor fields. GrdFile.create authors a new library without a group hierarchy.

GrdEncodeOptions.includeTaggedBlocks and includeTrailingData control whether preserved trailer data is written; both default to true.

Reusable dart:convert API

GrdCodec implements Codec<GrdFile, List<int>> and keeps decoding and encoding policies together in one immutable value:

const GrdCodec codec = GrdCodec(
  decodeOptions: GrdDecodeOptions(mode: GrdDecodeMode.strict),
  encodeOptions: GrdEncodeOptions(),
);

final GrdFile library = codec.decode(bytes);
final Uint8List encoded = codec.encode(library);

The List<int> binary type allows composition with standard codecs such as base64; direct encode calls still return Uint8List. GrdEncoder and GrdDecoder are also configurable Converter implementations. Every conversion consumes or produces one complete in-memory GRD file rather than an incremental byte stream.

Hierarchy

Newer libraries can append a phry group hierarchy. GrdFile.hierarchy exposes its ordered group and preset entries, while groupPathOf(index) returns the enclosing group names for one gradient. Gradients without a hierarchy entry remain at the top level.

The complete hierarchy descriptors remain available in hierarchyDescriptors. Preserved phry blocks are written back unchanged; editing the typed hierarchy does not regenerate those blocks.

Strict, tolerant, and bounded decoding

GrdDecodeMode.tolerant, the default, records recoverable problems in GrdFile.warnings: unknown tagged blocks, trailing bytes, list entries holding no gradient, unknown gradient forms, and unsupported legacy color models. GrdDecodeMode.strict turns each of them into a GrdFormatException. Structural damage, such as a bad signature or a truncated descriptor, always throws.

final GrdFile library = GrdDecoder.decode(
  bytes,
  options: const GrdDecodeOptions(mode: GrdDecodeMode.strict),
);

Decode options bound input size, gradient and legacy-stop counts, descriptor complexity, hierarchy entries, and tagged-block payloads. Keep preserveTaggedBlockData enabled when the library must be saved with its original trailer blocks.

The bundled inspector accepts individual files, prints gradients and warnings, and compares re-encoded bytes with the source:

dart run tool/inspect_grd.dart gradients.grd

Current boundaries

GrdKit does not render gradients. Noise gradients are exposed as their parameters only, and dynamic foreground or background stops are left for the caller to resolve. PsColor.toRgb() provides a profile-free approximation of supported process colors for previews; color-managed rendering belongs to the application.

See docs/GRD.md for the binary layout, descriptor mapping, preservation rules, and integration guidance.

References

GrdKit is an independent implementation and is not affiliated with or endorsed by Adobe.


Built for Focale, an advanced local image editor. Discover what these packages make possible in a real creative workflow.

Libraries

grdkit
Reads and writes Adobe Photoshop GRD gradient libraries in pure Dart.