-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathqoi_decoder_magick.sf
126 lines (98 loc) · 3.25 KB
/
qoi_decoder_magick.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
#!/usr/bin/ruby
# Implementation of the QOI decoder (generating a PNG file).
# See also:
# https://qoiformat.org/
# https://github.com/phoboslab/qoi
require('Image::Magick')
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<Image::Magick>.new(join('x', width, height))
if (channels == 4) {
img.ReadImage('canvas:transparent')
img.Set(alpha => 'Transparent')
}
else {
img.ReadImage('canvas:white')
}
var w = 0
var run = 0
var px = [0, 0, 0, 255]
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
}
img.Set('pixel[' + join(',', w%width, idiv(w, width)) + ']', sprintf("#%02x%02x%02x%02x", px...))
++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)
img.Write(out_file) && die "Error: couldn't save file!"