qoi_decoder_gd.sf 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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('GD')
  7. %O<GD::Image>.trueColor(1)
  8. func qoi_decoder(bytes) {
  9. func decode32(a,b,c,d) {
  10. (a << 8*3) | (b << 8*2) | (c << 8*1) | (d << 8*0)
  11. }
  12. func invalid() {
  13. die "Not a QOIF image"
  14. }
  15. var index = 0
  16. [bytes[index..index+3]] == %b'qoif' || invalid()
  17. index += 4
  18. var width = decode32(bytes[index..index+3])
  19. index += 4
  20. var height = decode32(bytes[index..index+3])
  21. index += 4
  22. var channels = bytes[index++]
  23. var colorspace = bytes[index++]
  24. width>0 && height>0 || invalid()
  25. channels ~~ 1..4 || invalid()
  26. colorspace ~~ [0,1] || invalid()
  27. bytes.pop == 0x01 || invalid()
  28. 7.times { bytes.pop == 0x00 || invalid() }
  29. say [width, height, channels, colorspace]
  30. var img = %O<GD::Image>.new(width, height)
  31. # FIXME: alpha does not really work...
  32. if (channels == 4) {
  33. img.saveAlpha(1)
  34. img.alphaBlending(1)
  35. }
  36. var w = 0
  37. var run = 0
  38. var px = [0, 0, 0, 255]
  39. var color = 0
  40. var colors = 64.of { [0,0,0,0] }
  41. loop {
  42. if (run > 0) {
  43. --run
  44. }
  45. else {
  46. var byte = (bytes[index++] \\ break)
  47. if (byte == 0b_11_11_11_10) { # OP RGB
  48. px[0] = bytes[index++]
  49. px[1] = bytes[index++]
  50. px[2] = bytes[index++]
  51. }
  52. elsif (byte == 0b_11_11_11_11) { # OP RGBA
  53. px[0] = bytes[index++]
  54. px[1] = bytes[index++]
  55. px[2] = bytes[index++]
  56. px[3] = bytes[index++]
  57. }
  58. elsif (byte >> 6 == 0b00) { # OP INDEX
  59. px = colors[byte].clone
  60. }
  61. elsif (byte >> 6 == 0b01) { # OP DIFF
  62. var dr = (byte & 0b00_11_00_00 >> 4)-2
  63. var dg = (byte & 0b00_00_11_00 >> 2)-2
  64. var db = (byte & 0b00_00_00_11 >> 0)-2
  65. px[0].addmod!(dr, 256)
  66. px[1].addmod!(dg, 256)
  67. px[2].addmod!(db, 256)
  68. }
  69. elsif (byte >> 6 == 0b10) { # OP LUMA
  70. var byte2 = bytes[index++]
  71. var dg = (byte & 0b00_111_111)-32
  72. var dr_dg = (byte2 >> 4)-8
  73. var db_dg = (byte2 & 0b0000_1111)-8
  74. var dr = (dr_dg+dg)
  75. var db = (db_dg+dg)
  76. px[0].addmod!(dr, 256)
  77. px[1].addmod!(dg, 256)
  78. px[2].addmod!(db, 256)
  79. }
  80. elsif (byte >> 6 == 0b11) { # OP RUN
  81. run = (byte & 0b00_111_111)
  82. }
  83. colors[(px[0]*3 + px[1]*5 + px[2]*7 + px[3]*11)%64] = px.clone
  84. if (channels == 4) {
  85. color = img.colorResolveAlpha(px[0], px[1], px[2], 127-Math.map(px[3], 0, 255, 0, 127).int) # FIXME
  86. }
  87. else {
  88. color = img.colorResolve(px[0], px[1], px[2])
  89. }
  90. }
  91. img.setPixel(w%width, idiv(w, width), color)
  92. ++w
  93. }
  94. return img
  95. }
  96. ARGV || do {
  97. STDERR << "usage: #{File(__MAIN__).basename} [input.qoi] [output.png]\n"
  98. Sys.exit(2)
  99. }
  100. var in_file = File(ARGV[0])
  101. var out_file = (ARGV[1] \\ (in_file - /\.qoi\z/i + '.png'))
  102. var bytes = in_file.read(:raw).bytes
  103. var img = qoi_decoder(bytes)
  104. File(out_file).write(img.png, :raw)