readImageInfo function
Dimensions and transparency of a PNG or JPEG, or null if bytes is neither.
Hand-rolled rather than a dependency, on the same reasoning as the copy in
cux_ship_play: this reads a handful of integers out of a header, and
package:image is a decoder for a dozen formats. The check it enables is
worth having because Apple validates screenshots at submission — after they
have already been uploaded one at a time.
Implementation
ImageInfo? readImageInfo(List<int> bytes) {
// PNG: IHDR is required to be the first chunk, so everything needed sits at
// a fixed offset — 8 signature, 4 length, 4 type, width, height, bit depth,
// colour type.
if (bytes.length >= 26 &&
bytes[0] == 0x89 &&
bytes[1] == 0x50 &&
bytes[2] == 0x4E &&
bytes[3] == 0x47) {
final colourType = bytes[25];
// 4 is greyscale+alpha and 6 is RGBA. A tRNS chunk makes types 0 and 2
// transparent too, so it counts as alpha even though the colour type
// alone does not say so.
final hasAlphaChannel = colourType == 4 || colourType == 6;
return ImageInfo(
width: _be32(bytes, 16),
height: _be32(bytes, 20),
hasAlpha: hasAlphaChannel || _hasTrnsChunk(bytes),
);
}
// JPEG: walk the marker segments to the start-of-frame, the only one that
// carries the dimensions. JPEG has no alpha channel at all.
if (bytes.length >= 4 && bytes[0] == 0xFF && bytes[1] == 0xD8) {
var i = 2;
while (i + 9 < bytes.length) {
if (bytes[i] != 0xFF) {
i++;
continue;
}
final marker = bytes[i + 1];
// Padding, and the standalone markers that carry no length field.
if (marker == 0xFF ||
marker == 0x01 ||
(marker >= 0xD0 && marker <= 0xD9)) {
i += 2;
continue;
}
// Every SOFn except the three that are not frame headers at all: DHT
// (C4), JPG (C8) and DAC (CC).
final isFrameHeader =
marker >= 0xC0 &&
marker <= 0xCF &&
marker != 0xC4 &&
marker != 0xC8 &&
marker != 0xCC;
if (isFrameHeader) {
return ImageInfo(
width: _be16(bytes, i + 7),
height: _be16(bytes, i + 5),
hasAlpha: false,
);
}
final length = _be16(bytes, i + 2);
// A segment shorter than its own length field means the file is corrupt;
// stop rather than loop forever on it.
if (length < 2) {
return null;
}
i += 2 + length;
}
}
return null;
}