123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- #!/usr/bin/ruby
- # Implementation of the QOI decoder (generating a PNG file).
- # See also:
- # https://qoiformat.org/
- # https://github.com/phoboslab/qoi
- require('Imager')
- func qoi_decoder(bytes) {
- func decode32(a,b,c,d) {
- (a << 8*3) | (b << 8*2) | (c << 8*1) | (d << 8*0)
- }
- func invalid() {
- die "Not a QOIF image"
- }
- var index = 0
- [bytes[index..index+3]] == %b'qoif' || invalid()
- index += 4
- var width = decode32(bytes[index..index+3])
- index += 4
- var height = decode32(bytes[index..index+3])
- index += 4
- var channels = bytes[index++]
- var colorspace = bytes[index++]
- width>0 && height>0 || invalid()
- channels ~~ 1..4 || invalid()
- colorspace ~~ [0,1] || invalid()
- bytes.pop == 0x01 || invalid()
- 7.times { bytes.pop == 0x00 || invalid() }
- say [width, height, channels, colorspace]
- var img = %O<Imager>.new(
- xsize => width,
- ysize => height,
- channels => channels,
- )
- var run = 0
- var (R, G, B, A) = (0, 0, 0, 255)
- var pixels = []
- var colors = 64.of { [0,0,0,0] }
- loop {
- if (run > 0) {
- --run
- }
- else {
- var byte = (bytes[index++] \\ break)
- if (byte == 0b_11_11_11_10) { # OP RGB
- R = bytes[index++]
- G = bytes[index++]
- B = bytes[index++]
- }
- elsif (byte == 0b_11_11_11_11) { # OP RGBA
- R = bytes[index++]
- G = bytes[index++]
- B = bytes[index++]
- A = bytes[index++]
- }
- elsif (byte >> 6 == 0b00) { # OP INDEX
- (R, G, B, A) = colors[byte]...
- }
- elsif (byte >> 6 == 0b01) { # OP DIFF
- var dr = (byte & 0b00_11_00_00 >> 4)-2
- var dg = (byte & 0b00_00_11_00 >> 2)-2
- var db = (byte & 0b00_00_00_11 >> 0)-2
- R.addmod!(dr, 256)
- G.addmod!(dg, 256)
- B.addmod!(db, 256)
- }
- elsif (byte >> 6 == 0b10) { # OP LUMA
- var byte2 = bytes[index++]
- var dg = (byte & 0b00_111_111)-32
- var dr_dg = (byte2 >> 4)-8
- var db_dg = (byte2 & 0b0000_1111)-8
- var dr = (dr_dg+dg)
- var db = (db_dg+dg)
- R.addmod!(dr, 256)
- G.addmod!(dg, 256)
- B.addmod!(db, 256)
- }
- elsif (byte >> 6 == 0b11) { # OP RUN
- run = (byte & 0b00_111_111)
- }
- colors[sum(3*R, 5*G, 7*B, 11*A)%64] = [R, G, B, A]
- }
- pixels << (R, G, B, A)
- }
- for row in (^height) {
- var line = pixels.splice(0, 4*width)
- img.setscanline(y => row, pixels => line.pack('C*'))
- }
- return img
- }
- ARGV || do {
- STDERR << "usage: #{File(__MAIN__).basename} [input.qoi] [output.png]\n"
- Sys.exit(2)
- }
- var in_file = File(ARGV[0])
- var out_file = File(ARGV[1] \\ (in_file - /\.qoi\z/i + '.png'))
- var bytes = in_file.read(:raw).bytes
- var img = qoi_decoder(bytes)
- img.write(file => out_file, type => 'png')
|