qoi_decoder.sf 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #!/usr/bin/ruby
  2. # Implementation of the QOI decoder (generating a PNG file).
  3. # See also:
  4. # https://qoiformat.org/
  5. # https://github.com/phoboslab/qoi
  6. require('Imager')
  7. func qoi_decoder(bytes) {
  8. func decode32(a,b,c,d) {
  9. (a << 8*3) | (b << 8*2) | (c << 8*1) | (d << 8*0)
  10. }
  11. func invalid() {
  12. die "Not a QOIF image"
  13. }
  14. var index = 0
  15. [bytes[index..index+3]] == %b'qoif' || invalid()
  16. index += 4
  17. var width = decode32(bytes[index..index+3])
  18. index += 4
  19. var height = decode32(bytes[index..index+3])
  20. index += 4
  21. var channels = bytes[index++]
  22. var colorspace = bytes[index++]
  23. width>0 && height>0 || invalid()
  24. channels ~~ 1..4 || invalid()
  25. colorspace ~~ [0,1] || invalid()
  26. bytes.pop == 0x01 || invalid()
  27. 7.times { bytes.pop == 0x00 || invalid() }
  28. say [width, height, channels, colorspace]
  29. var img = %O<Imager>.new(
  30. xsize => width,
  31. ysize => height,
  32. channels => channels,
  33. )
  34. var run = 0
  35. var (R, G, B, A) = (0, 0, 0, 255)
  36. var pixels = []
  37. var colors = 64.of { [0,0,0,0] }
  38. loop {
  39. if (run > 0) {
  40. --run
  41. }
  42. else {
  43. var byte = (bytes[index++] \\ break)
  44. if (byte == 0b_11_11_11_10) { # OP RGB
  45. R = bytes[index++]
  46. G = bytes[index++]
  47. B = bytes[index++]
  48. }
  49. elsif (byte == 0b_11_11_11_11) { # OP RGBA
  50. R = bytes[index++]
  51. G = bytes[index++]
  52. B = bytes[index++]
  53. A = bytes[index++]
  54. }
  55. elsif (byte >> 6 == 0b00) { # OP INDEX
  56. (R, G, B, A) = colors[byte]...
  57. }
  58. elsif (byte >> 6 == 0b01) { # OP DIFF
  59. var dr = (byte & 0b00_11_00_00 >> 4)-2
  60. var dg = (byte & 0b00_00_11_00 >> 2)-2
  61. var db = (byte & 0b00_00_00_11 >> 0)-2
  62. R.addmod!(dr, 256)
  63. G.addmod!(dg, 256)
  64. B.addmod!(db, 256)
  65. }
  66. elsif (byte >> 6 == 0b10) { # OP LUMA
  67. var byte2 = bytes[index++]
  68. var dg = (byte & 0b00_111_111)-32
  69. var dr_dg = (byte2 >> 4)-8
  70. var db_dg = (byte2 & 0b0000_1111)-8
  71. var dr = (dr_dg+dg)
  72. var db = (db_dg+dg)
  73. R.addmod!(dr, 256)
  74. G.addmod!(dg, 256)
  75. B.addmod!(db, 256)
  76. }
  77. elsif (byte >> 6 == 0b11) { # OP RUN
  78. run = (byte & 0b00_111_111)
  79. }
  80. colors[sum(3*R, 5*G, 7*B, 11*A)%64] = [R, G, B, A]
  81. }
  82. pixels << (R, G, B, A)
  83. }
  84. for row in (^height) {
  85. var line = pixels.splice(0, 4*width)
  86. img.setscanline(y => row, pixels => line.pack('C*'))
  87. }
  88. return img
  89. }
  90. ARGV || do {
  91. STDERR << "usage: #{File(__MAIN__).basename} [input.qoi] [output.png]\n"
  92. Sys.exit(2)
  93. }
  94. var in_file = File(ARGV[0])
  95. var out_file = File(ARGV[1] \\ (in_file - /\.qoi\z/i + '.png'))
  96. var bytes = in_file.read(:raw).bytes
  97. var img = qoi_decoder(bytes)
  98. img.write(file => out_file, type => 'png')