extract static method
String?
extract({
- required Uint8List imageBytes,
- required String key,
- ForensicConfig config = const ForensicConfig(),
Extracts a previously embedded payload from imageBytes using key.
Returns the payload string, or null if the key is wrong, no watermark
is found, or the data is corrupted.
Implementation
static String? extract({
required Uint8List imageBytes,
required String key,
ForensicConfig config = const ForensicConfig(),
}) {
if (key.isEmpty) return null;
final image = img.decodePng(imageBytes);
if (image == null) return null;
final pixelCount = image.width * image.height;
final seed = _djb2(key);
// Phase 1: read header (16-bit magic + 32-bit length = 48 bits) per copy.
const headerBits = 48;
final headerTotalBits = headerBits * config.redundancy;
if (headerTotalBits > (pixelCount * _kMaxCapacityFraction).floor()) {
return null;
}
final rng = Random(seed);
final used = <int>{};
final headerPositions =
_generatePositions(rng, pixelCount, headerTotalBits, used);
final headerBitValues = <int>[];
for (final pixelIndex in headerPositions) {
final x = pixelIndex % image.width;
final y = pixelIndex ~/ image.width;
final pixel = image.getPixel(x, y);
headerBitValues.add(pixel.b.toInt() & 1);
}
final headerVoted =
_majorityVote(headerBitValues, headerBits, config.redundancy);
// Verify magic number.
var magic = 0;
for (var i = 0; i < 16; i++) {
magic = (magic << 1) | headerVoted[i];
}
if (magic != _kMagic) return null;
// Read byte count.
var byteCount = 0;
for (var i = 16; i < 48; i++) {
byteCount = (byteCount << 1) | headerVoted[i];
}
if (byteCount <= 0 || byteCount > 10 * 1024 * 1024) return null;
// Phase 2: read payload bits.
final payloadBitCount = byteCount * 8;
final payloadTotalBits = payloadBitCount * config.redundancy;
final totalBitsNeeded = headerTotalBits + payloadTotalBits;
if (totalBitsNeeded > (pixelCount * _kMaxCapacityFraction).floor()) {
return null;
}
final payloadPositions =
_generatePositions(rng, pixelCount, payloadTotalBits, used);
final payloadBitValues = <int>[];
for (final pixelIndex in payloadPositions) {
final x = pixelIndex % image.width;
final y = pixelIndex ~/ image.width;
final pixel = image.getPixel(x, y);
payloadBitValues.add(pixel.b.toInt() & 1);
}
final payloadVoted =
_majorityVote(payloadBitValues, payloadBitCount, config.redundancy);
// Decode bits → bytes → UTF-8.
final bytes = Uint8List(byteCount);
for (var i = 0; i < byteCount; i++) {
var byte = 0;
for (var b = 0; b < 8; b++) {
byte = (byte << 1) | payloadVoted[i * 8 + b];
}
bytes[i] = byte;
}
try {
return utf8.decode(bytes);
} catch (_) {
return null;
}
}