ETOOBUSY 🚀 minimal blogging for the impatient
QRate - decoding
TL;DR
The decoding part of QRate.
We already had a look at the decoding process, i.e. going from a PDF file with images back to the original data:
sub decode ($input, $output) {
my $data = assemble_data(slice_reader_it(pdf_reader_it($input)));
path($output)->spew_raw(uncompress($data));
return 0;
}
As before, there are two iterators at play (provided by pdf_reader_it
and slice_reader_it
), as well as a function that takes all the input
slices and reassembles them back into the desired data
(assemble_data
).
Let’s start from the PDF reader:
sub pdf_reader_it ($file) {
my $magick = Image::Magick->new();
die if $magick->Read($file); # returns 0 on success
my $n = 0;
return sub {
return if $n > $magick->$#*;
return $magick->[$n++];
};
}
As already observed in PerlMagick PDF pages, Image::Magick provides us back with a (blessed) reference to an array when there are multiple images (or, in our case, multiple pages). This means that our iterator just needs to provide each page back, in order.
A page from the PDF is consumed by a slice reader, which turns the page’s image back into “actionable” data thanks to Barcode::ZBar, which we already saw in the past (Reading QR Codes from Perl):
sub slice_reader_it ($it) {
my $scanner = Barcode::ZBar::ImageScanner->new();
$scanner->parse_config("enable");
return sub {
my $page = $it->() or return;
my $image = Barcode::ZBar::Image->new();
$image->set_format('Y800');
$image->set_size($page->Get(qw(columns rows)));
$image->set_data($page->ImageToBlob(magick => 'GRAY', depth => 8));
my $n = $scanner->scan_image($image);
return map {
my ($header, $data) = split m{\n}mxs, $_->get_data, 2;
my ($n, $more) = $header =~ m{\A (\d+) ([+.]) \z}mxs;
return {
n => $n,
last => ($more eq '.' ? 1 : 0),
data => $data,
};
} $image->get_symbols;
}
}
Here, we have to remember of our framing overhead, which added a
sequence number to each slice, as well as an indicator of whether more
slices are expected or this was the last one. This accounts for the
small processing of the data read from the QR code, which is split into
the sequence number (n
), the indicator for the last slice (last
) and
the payload data itself.
Last, let’s take a look at the function to reassemble all pieces. It receives the iterator for the slices:
sub assemble_data ($it) {
my @slices;
while (my $slice = $it->()) {
print {*STDERR} '.';
push @slices, $slice;
}
print {*STDERR} "\n";
@slices = sort { $a->{n} <=> $b->{n} } @slices;
for my $n (0 .. $#slices) {
die "missing slice $n\n" if $slices[$n]{n} != $n;
}
die "missing trailing slices\n" unless $slices[-1]{last};
my $data = join '', map { $_->{data} } @slices;
return decode_base64($data);
}
The initial part is just amassing all slices into array @slices
. Then
we sort them according to their sequence number and perform some sanity
check to see if there are missing pieces. If not, we can return the data
by decoding it from Base-64.
I hope you enjoyed the ride so far!