zpl.tools
Graphics and images

^GF - Graphic Field

Draws a bitmap sent inline with the label.

^GF carries a bitmap inside the label format itself, as a field like any other, and draws it at the current origin. It draws every logo on a shipping label that the printer does not hold already. The three byte counts are not interchangeable: the image height comes from c divided by d, and b gives only how many bytes travel on the wire. A wrong d shears the raster. A wrong c crops the image.

Syntax

^GFa,b,c,d,data

Support

Partially supportedparsed: full · state: full · rendered: partial

The command renders, with the caveat below.

Compression types A and B render, including the :Z64:/:B64: and ACS-repeat encodings of an A payload. The renderer rejects type C, as Labelary does. — not pinned by a test

Labelary renders this command.

Parameters

aCompression typeoptional · one of a set

How the payload in data is encoded.

Default
A
Invalid value
enough to stop the field being printed. The renderer reports ARGUMENT_INVALID for any other value and ignores the field. It accepts lower case, as Labelary does.
Test coverage
Values exercised: A, B, C. The invalid-value rule is pinned by 1 test, including the diagnostic it emits.
A
ASCII hexadecimal, optionally ACS run-length compressed or wrapped as :Z64:/:B64: (default)
B
Raw binary bytes
C
Compressed binary. The renderer rejects it; see the deviations — not honoured by zpl.tools
bBinary byte countrequired · integer

Number of bytes actually transmitted in data. For a :Z64: payload this counts the compressed bytes on the wire, not the decoded raster.

Accepted range
1 to 99999 bytes
Invalid value
enough to stop the field being printed
Test coverage
No test at the minimum, maximum or one step outside the range. The invalid-value rule is pinned by 1 test.
cGraphic field countrequired · integer

Total size of the uncompressed raster in bytes: bytes per row times number of rows. This, not b, determines the image height.

Accepted range
1 to 99999 bytes
Invalid value
discarded, and the current value is kept. When c is absent or zero, the renderer sizes the bitmap from b instead, as older writers frame an uncompressed ^GFA.
Test coverage
Bounds pinned: above the maximum (4294967295). No test at the minimum or maximum. The invalid-value rule is not pinned by a test.
dBytes per rowrequired · integer

Number of bytes in one row of the raster. Each byte carries 8 horizontal pixels, so the image is 8 × d dots wide.

Accepted range
1 to 99999 bytes
Invalid value
enough to stop the field being printed
Test coverage
No test at the minimum, maximum or one step outside the range. The invalid-value rule is not pinned by a test.
dataImage datarequired · text

The raster itself, in the encoding named by a. One bit per pixel, 1 meaning black.

Invalid value
enough to stop the field being printed. The renderer reports ARGUMENT_MISSING for an empty payload and ignores the field.
Test coverage
The invalid-value rule is pinned by 2 tests.

Behavior

The raster is one bit per pixel and is black where the bit is 1. ^GF has no magnification arguments and does not scale, so its dot dimensions are its printed dimensions. The width is 8 × d dots, always a multiple of eight, so a bitmap written for ^GF needs a pad to a byte boundary on the right.

The preceding ^FO or ^FT positions the field, and ^FS ends it. ^FO places the top-left corner of the raster, and ^FT places the bottom-left corner.

The renderer truncates a payload longer than the declared geometry needs, and does not reject it. A shorter payload is a decode error: the renderer ignores the field and reports a diagnostic, and draws no half image.

Compression and readability

A type A payload is text and can wrap across lines, because the decoder ignores a space, a carriage return and a line feed inside it. ASCII-hex is also why a ^GF field runs to kilobytes. Four run-length operators and two base64 wrappers shorten it, and the renderer accepts all six:

Written asMeans
,Fill the rest of this row with white
!Fill the rest of this row with black
:Repeat the previous row verbatim
GY, gzRepeat the next character 1 to 19, or 20 to 400 by twenties
:Z64:…:crcThe deflated raster, base64-encoded
:B64:…:crcThe raster base64-encoded, without the deflate step

The repeat letters accumulate, so vM asks for 320 + 7 copies. In front of : the two engines differ: the renderer expands the count, and Labelary ignores the count and repeats the row once. We measured this on 2026-07-29. ^GFA,48,48,16,!H: renders three rows here and no label at all on Labelary, which stays two rows short of the declared byte count. Write a run of repeated rows as a run of colons.

In a :Z64: or :B64: payload, b counts the encoded bytes, and c stays the size of the decoded raster. The :crc suffix is mandatory, but neither engine checks its value. Labelary does the same, and a stricter check would reject labels that print today.

A type B payload is raw binary, and the parser reads it by length. It takes the b bytes after the fourth comma verbatim, so a ^, a ~ and a , among them are image data and not the start of the next command. This holds only while b is correct. On a printer, ~DN or any caret or tilde that reaches the parser aborts a binary download. That is the other reason a wrong byte count prints rubbish rather than an error.

Tested behavior

Differences from the specification

Labelary against the ZPL II guide

Where Labelary — our compatibility target — behaves differently from the printed specification.

  • Labelary rejects compression type C, which the ZPL II guide defines. — not pinned by a test
  • Labelary accepts lower-case compression types (^GFa), which the guide writes only in upper case. — not pinned by a test

zpl.tools against the ZPL II guide

Where our renderer behaves differently from the specification.

  • The renderer rejects compression type C to match Labelary, and reports UNSUPPORTED_COMMAND instead. — pinned by test_gf_compressed_binary_type_c

zpl.tools against Labelary

Where our output differs from Labelary for the same input. These are the differences that break a migration.

  • The renderer expands an ACS repeat count before :; Labelary ignores it. We measured this on 2026-07-29. ^GFA,48,48,16,!H: asks for three rows and gets them here. Labelary draws two rows, falls short of the declared byte count and returns no label. Write one colon per row for a portable format. — not pinned by a test

Example

An 80 × 8 dot checkerboard as plain ASCII hex, one raster row per source line:

^XA
^FO50,50^GFA,80,80,10,
FFFFFFFFFFFFFFFFFFFF
8000FFFF0000FFFF0001
8000FFFF0000FFFF0001
8000FFFF0000FFFF0001
FFFF0000FFFF0000FFFF
FFFF0000FFFF0000FFFF
FFFF0000FFFF0000FFFF
FFFFFFFFFFFFFFFFFFFF^FS
^XZ

Open in viewer

The same command with the compression operators. This is a 64 × 16 dot hollow frame in twenty-one characters — a solid black row, a row carrying one black byte at each end and twelve zero bytes between them, thirteen repeats of that row, and a closing solid row:

^XA
^FO50,50^GFA,128,128,8,!FFR0FF:::::::::::::!^FS
^XZ

Open in viewer