-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathqoi_decoder_gd.sf
134 lines (104 loc) · 3.42 KB
/
qoi_decoder_gd.sf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/ruby
# Implementation of the QOI decoder (generating a PNG file).
# See also:
# https://qoiformat.org/
# https://github.com/phoboslab/qoi
require('GD')
%O<GD::Image>.trueColor(1)
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<GD::Image>.new(width, height)
# FIXME: alpha does not really work...
if (channels == 4) {
img.saveAlpha(1)
img.alphaBlending(1)
}
var w = 0
var run = 0
var px = [0, 0, 0, 255]
var color = 0
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
px[0] = bytes[index++]
px[1] = bytes[index++]
px[2] = bytes[index++]
}
elsif (byte == 0b_11_11_11_11) { # OP RGBA
px[0] = bytes[index++]
px[1] = bytes[index++]
px[2] = bytes[index++]
px[3] = bytes[index++]
}
elsif (byte >> 6 == 0b00) { # OP INDEX
px = colors[byte].clone
}
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
px[0].addmod!(dr, 256)
px[1].addmod!(dg, 256)
px[2].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)
px[0].addmod!(dr, 256)
px[1].addmod!(dg, 256)
px[2].addmod!(db, 256)
}
elsif (byte >> 6 == 0b11) { # OP RUN
run = (byte & 0b00_111_111)
}
colors[(px[0]*3 + px[1]*5 + px[2]*7 + px[3]*11)%64] = px.clone
if (channels == 4) {
color = img.colorResolveAlpha(px[0], px[1], px[2], 127-Math.map(px[3], 0, 255, 0, 127).int) # FIXME
}
else {
color = img.colorResolve(px[0], px[1], px[2])
}
}
img.setPixel(w%width, idiv(w, width), color)
++w
}
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 = (ARGV[1] \\ (in_file - /\.qoi\z/i + '.png'))
var bytes = in_file.read(:raw).bytes
var img = qoi_decoder(bytes)
File(out_file).write(img.png, :raw)