In Image::from_pgm, the test for a valid pgm file is wrong.
image_data = string.gsub(/^(P5)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s/, '')
if $1 != 'P5'
raise ArgumentError, "input must be a PGM file"
end
The PGM spec says that any string starting with # is a comment. The test file I tried, failed to load, as it had a comment line between the P5 line, and the width line.
i.e. it looked like
P5
# test_qrcode.pgm
300 300
255
...
image_data = pgm.gsub(/^(P5)\s(#.*\n)?([0-9]+)\s([0-9]+)\s([0-9]+)\s/, '')
Would match this file, but shift $2 to $3, $3 to $4 and $4 to $5, even if there was no comment line. Of course, it is possible to have multiple comment lines, at any point, but having a comment after the P5 seems to be standard.