lilimg 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Rakefile +1 -0
- data/example/build.sh +2 -0
- data/example/label.jpg +0 -0
- data/example/test.c +86 -0
- data/example/test.rb +7 -0
- data/ext/extconf.rb +7 -0
- data/ext/jo_gif.h +427 -0
- data/ext/lilimg.c +71 -0
- data/ext/nanojpeg.h +928 -0
- data/lilimg.gemspec +17 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fa73b9d4febb1427ef8da91517ccc7df809ae7df820525dee2c4abdeda75729a
|
4
|
+
data.tar.gz: c5a44effd659b3321be9694f6deab2b680e2c09b6bff7120995b257b3ea91839
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 93f8cf6b722e6cd944c6f3129af3e66bb23734e0c761897a69c566b6df9daa0657ba9196b72e44719314bf759b9f22ba393d5046b03d7e8dfe0f968551bf3473
|
7
|
+
data.tar.gz: c827a20106b4f6f00f9d5653fa9304affcf58c3bce510af57a82ca13634f720dcdb420b9992233620ab08d2190e2c8a2c8cf435a06efe125b57535082ea7bbf9
|
data/.gitignore
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/example/build.sh
ADDED
data/example/label.jpg
ADDED
Binary file
|
data/example/test.c
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <string.h>
|
4
|
+
#include "nanojpeg.h"
|
5
|
+
#include "jo_gif.h"
|
6
|
+
|
7
|
+
unsigned char * rgb_to_rgba(unsigned char * buf, int w, int h) {
|
8
|
+
int newsize = 4 * w * h;
|
9
|
+
unsigned char * dest;
|
10
|
+
dest = malloc(newsize);
|
11
|
+
int s = 0, d = 0;
|
12
|
+
while (d < newsize) {
|
13
|
+
dest[d++] = buf[s++];
|
14
|
+
dest[d++] = buf[s++];
|
15
|
+
dest[d++] = buf[s++];
|
16
|
+
dest[d++] = 255; // fully opaque
|
17
|
+
}
|
18
|
+
|
19
|
+
return dest;
|
20
|
+
}
|
21
|
+
|
22
|
+
int write_file(const char * name, char * buf, int size) {
|
23
|
+
FILE * fp = fopen(name, "wb");
|
24
|
+
if (!fp) {
|
25
|
+
return -1;
|
26
|
+
}
|
27
|
+
|
28
|
+
fwrite(buf, size, 1, fp);
|
29
|
+
fclose(fp);
|
30
|
+
return 0;
|
31
|
+
}
|
32
|
+
|
33
|
+
int main(int argc, char* argv[]) {
|
34
|
+
int size;
|
35
|
+
char *buf;
|
36
|
+
FILE *f;
|
37
|
+
|
38
|
+
if (argc < 2) {
|
39
|
+
printf("Usage: %s <input.jpg> [<output.gif>]\n", argv[0]);
|
40
|
+
return 2;
|
41
|
+
}
|
42
|
+
f = fopen(argv[1], "rb");
|
43
|
+
if (!f) {
|
44
|
+
printf("Error opening the input file.\n");
|
45
|
+
return 1;
|
46
|
+
}
|
47
|
+
|
48
|
+
fseek(f, 0, SEEK_END);
|
49
|
+
size = (int) ftell(f);
|
50
|
+
buf = (char*) malloc(size);
|
51
|
+
fseek(f, 0, SEEK_SET);
|
52
|
+
size = (int) fread(buf, 1, size, f);
|
53
|
+
fclose(f);
|
54
|
+
|
55
|
+
printf("size: %d\n", size);
|
56
|
+
|
57
|
+
njInit();
|
58
|
+
if (njDecode(buf, size)) {
|
59
|
+
free((void*)buf);
|
60
|
+
printf("Error decoding the input file.\n");
|
61
|
+
return 1;
|
62
|
+
}
|
63
|
+
free((void*)buf);
|
64
|
+
|
65
|
+
unsigned char * pixels = njGetImage();
|
66
|
+
unsigned char * rgba = rgb_to_rgba(pixels, njGetWidth(), njGetHeight());
|
67
|
+
int maxColors = 8;
|
68
|
+
|
69
|
+
char * out;
|
70
|
+
size_t bufsize;
|
71
|
+
FILE * fp = open_memstream(&out, &bufsize);
|
72
|
+
// FILE * fp = fopen("output.gif", "wb");
|
73
|
+
|
74
|
+
jo_gif_t gif = jo_gif_start(fp, njGetWidth(), njGetHeight(), 0, maxColors);
|
75
|
+
jo_gif_frame(&gif, rgba, 0, 0);
|
76
|
+
jo_gif_end(&gif);
|
77
|
+
|
78
|
+
// fclose(fp);
|
79
|
+
printf(" --> %d %d\n", bufsize, strlen(rgba));
|
80
|
+
write_file("output.gif", out, bufsize);
|
81
|
+
|
82
|
+
free(rgba);
|
83
|
+
free(out);
|
84
|
+
njDone();
|
85
|
+
return 0;
|
86
|
+
}
|
data/example/test.rb
ADDED
data/ext/extconf.rb
ADDED
data/ext/jo_gif.h
ADDED
@@ -0,0 +1,427 @@
|
|
1
|
+
/* public domain, Simple, Minimalistic GIF writer - http://jonolick.com
|
2
|
+
*
|
3
|
+
* Quick Notes:
|
4
|
+
* Supports only 4 component input, alpha is currently ignored. (RGBX)
|
5
|
+
*
|
6
|
+
* Latest revisions:
|
7
|
+
* 1.00 (2015-11-03) initial release
|
8
|
+
*
|
9
|
+
* Basic usage:
|
10
|
+
* char *frame = new char[128*128*4]; // 4 component. RGBX format, where X is unused
|
11
|
+
* jo_gif_t gif = jo_gif_start("foo.gif", 128, 128, 0, 32);
|
12
|
+
* jo_gif_frame(&gif, frame, 4, false); // frame 1
|
13
|
+
* jo_gif_frame(&gif, frame, 4, false); // frame 2
|
14
|
+
* jo_gif_frame(&gif, frame, 4, false); // frame 3, ...
|
15
|
+
* jo_gif_end(&gif);
|
16
|
+
* */
|
17
|
+
|
18
|
+
#ifndef JO_INCLUDE_GIF_H
|
19
|
+
#define JO_INCLUDE_GIF_H
|
20
|
+
|
21
|
+
// typedef uint8_t bool;
|
22
|
+
|
23
|
+
#include <stdio.h>
|
24
|
+
|
25
|
+
// To get a header file for this, either cut and paste the header,
|
26
|
+
// or create jo_gif.h, #define JO_GIF_HEADER_FILE_ONLY, and
|
27
|
+
// then include jo_gif.cpp from it.
|
28
|
+
|
29
|
+
typedef struct {
|
30
|
+
FILE *fp;
|
31
|
+
char * buf;
|
32
|
+
size_t bufsize;
|
33
|
+
unsigned char palette[0x300];
|
34
|
+
short width, height, repeat;
|
35
|
+
int numColors, palSize;
|
36
|
+
int frame;
|
37
|
+
} jo_gif_t;
|
38
|
+
|
39
|
+
// width/height | the same for every frame
|
40
|
+
// repeat | 0 = loop forever, 1 = loop once, etc...
|
41
|
+
// palSize | must be power of 2 - 1. so, 255 not 256.
|
42
|
+
extern jo_gif_t jo_gif_start(FILE * fp, short width, short height, short repeat, int numColors);
|
43
|
+
|
44
|
+
// gif | the state (returned from jo_gif_start)
|
45
|
+
// rgba | the pixels
|
46
|
+
// delayCsec | amount of time in between frames (in centiseconds)
|
47
|
+
// localPalette | true if you want a unique palette generated for this frame (does not effect future frames)
|
48
|
+
extern void jo_gif_frame(jo_gif_t *gif, unsigned char *rgba, short delayCsec, int localPalette);
|
49
|
+
|
50
|
+
// gif | the state (returned from jo_gif_start)
|
51
|
+
extern void jo_gif_end(jo_gif_t *gif);
|
52
|
+
|
53
|
+
#endif
|
54
|
+
|
55
|
+
#ifndef JO_GIF_HEADER_FILE_ONLY
|
56
|
+
|
57
|
+
#if defined(_MSC_VER) && _MSC_VER >= 0x1400
|
58
|
+
#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen()
|
59
|
+
#endif
|
60
|
+
|
61
|
+
#include <stdlib.h>
|
62
|
+
#include <memory.h>
|
63
|
+
#include <math.h>
|
64
|
+
|
65
|
+
// Based on NeuQuant algorithm
|
66
|
+
static void jo_gif_quantize(unsigned char *rgba, int rgbaSize, int sample, unsigned char *map, int numColors) {
|
67
|
+
// defs for freq and bias
|
68
|
+
const int intbiasshift = 16; /* bias for fractions */
|
69
|
+
const int intbias = (((int) 1) << intbiasshift);
|
70
|
+
const int gammashift = 10; /* gamma = 1024 */
|
71
|
+
const int betashift = 10;
|
72
|
+
const int beta = (intbias >> betashift); /* beta = 1/1024 */
|
73
|
+
const int betagamma = (intbias << (gammashift - betashift));
|
74
|
+
|
75
|
+
// defs for decreasing radius factor
|
76
|
+
const int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
|
77
|
+
const int radiusbias = (((int) 1) << radiusbiasshift);
|
78
|
+
const int radiusdec = 30; /* factor of 1/30 each cycle */
|
79
|
+
|
80
|
+
// defs for decreasing alpha factor
|
81
|
+
const int alphabiasshift = 10; /* alpha starts at 1.0 */
|
82
|
+
const int initalpha = (((int) 1) << alphabiasshift);
|
83
|
+
|
84
|
+
// radbias and alpharadbias used for radpower calculation
|
85
|
+
const int radbiasshift = 8;
|
86
|
+
const int radbias = (((int) 1) << radbiasshift);
|
87
|
+
const int alpharadbshift = (alphabiasshift + radbiasshift);
|
88
|
+
const int alpharadbias = (((int) 1) << alpharadbshift);
|
89
|
+
|
90
|
+
sample = sample < 1 ? 1 : sample > 30 ? 30 : sample;
|
91
|
+
int network[256][3];
|
92
|
+
int bias[256] = {}, freq[256];
|
93
|
+
int i;
|
94
|
+
for(i = 0; i < numColors; ++i) {
|
95
|
+
// Put nurons evenly through the luminance spectrum.
|
96
|
+
network[i][0] = network[i][1] = network[i][2] = (i << 12) / numColors;
|
97
|
+
freq[i] = intbias / numColors;
|
98
|
+
}
|
99
|
+
// Learn
|
100
|
+
{
|
101
|
+
const int primes[5] = {499, 491, 487, 503};
|
102
|
+
int step = 4;
|
103
|
+
for(i = 0; i < 4; ++i) {
|
104
|
+
if(rgbaSize > primes[i] * 4 && (rgbaSize % primes[i])) { // TODO/Error? primes[i]*4?
|
105
|
+
step = primes[i] * 4;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
sample = step == 4 ? 1 : sample;
|
109
|
+
|
110
|
+
int alphadec = 30 + ((sample - 1) / 3);
|
111
|
+
int samplepixels = rgbaSize / (4 * sample);
|
112
|
+
int delta = samplepixels / 100;
|
113
|
+
int alpha = initalpha;
|
114
|
+
delta = delta == 0 ? 1 : delta;
|
115
|
+
|
116
|
+
int radius = (numColors >> 3) * radiusbias;
|
117
|
+
int rad = radius >> radiusbiasshift;
|
118
|
+
rad = rad <= 1 ? 0 : rad;
|
119
|
+
int radSq = rad*rad;
|
120
|
+
int radpower[32];
|
121
|
+
for (i = 0; i < rad; i++) {
|
122
|
+
radpower[i] = alpha * (((radSq - i * i) * radbias) / radSq);
|
123
|
+
}
|
124
|
+
|
125
|
+
// Randomly walk through the pixels and relax neurons to the "optimal" target.
|
126
|
+
int pix;
|
127
|
+
for(i = 0, pix = 0; i < samplepixels;) {
|
128
|
+
int r = rgba[pix + 0] << 4;
|
129
|
+
int g = rgba[pix + 1] << 4;
|
130
|
+
int b = rgba[pix + 2] << 4;
|
131
|
+
int j = -1;
|
132
|
+
{
|
133
|
+
// finds closest neuron (min dist) and updates freq
|
134
|
+
// finds best neuron (min dist-bias) and returns position
|
135
|
+
// for frequently chosen neurons, freq[k] is high and bias[k] is negative
|
136
|
+
// bias[k] = gamma*((1/numColors)-freq[k])
|
137
|
+
|
138
|
+
int bestd = 0x7FFFFFFF, bestbiasd = 0x7FFFFFFF, bestpos = -1;
|
139
|
+
int k;
|
140
|
+
for (k = 0; k < numColors; k++) {
|
141
|
+
int *n = network[k];
|
142
|
+
int dist = abs(n[0] - r) + abs(n[1] - g) + abs(n[2] - b);
|
143
|
+
if (dist < bestd) {
|
144
|
+
bestd = dist;
|
145
|
+
bestpos = k;
|
146
|
+
}
|
147
|
+
int biasdist = dist - ((bias[k]) >> (intbiasshift - 4));
|
148
|
+
if (biasdist < bestbiasd) {
|
149
|
+
bestbiasd = biasdist;
|
150
|
+
j = k;
|
151
|
+
}
|
152
|
+
int betafreq = freq[k] >> betashift;
|
153
|
+
freq[k] -= betafreq;
|
154
|
+
bias[k] += betafreq << gammashift;
|
155
|
+
}
|
156
|
+
freq[bestpos] += beta;
|
157
|
+
bias[bestpos] -= betagamma;
|
158
|
+
}
|
159
|
+
|
160
|
+
// Move neuron j towards biased (b,g,r) by factor alpha
|
161
|
+
network[j][0] -= (network[j][0] - r) * alpha / initalpha;
|
162
|
+
network[j][1] -= (network[j][1] - g) * alpha / initalpha;
|
163
|
+
network[j][2] -= (network[j][2] - b) * alpha / initalpha;
|
164
|
+
if (rad != 0) {
|
165
|
+
// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
|
166
|
+
int lo = j - rad;
|
167
|
+
lo = lo < -1 ? -1 : lo;
|
168
|
+
int hi = j + rad;
|
169
|
+
hi = hi > numColors ? numColors : hi;
|
170
|
+
int jj, m, k;
|
171
|
+
for(jj = j+1, m=1; jj < hi; ++jj) {
|
172
|
+
int a = radpower[m++];
|
173
|
+
network[jj][0] -= (network[jj][0] - r) * a / alpharadbias;
|
174
|
+
network[jj][1] -= (network[jj][1] - g) * a / alpharadbias;
|
175
|
+
network[jj][2] -= (network[jj][2] - b) * a / alpharadbias;
|
176
|
+
}
|
177
|
+
for(k = j-1, m=1; k > lo; --k) {
|
178
|
+
int a = radpower[m++];
|
179
|
+
network[k][0] -= (network[k][0] - r) * a / alpharadbias;
|
180
|
+
network[k][1] -= (network[k][1] - g) * a / alpharadbias;
|
181
|
+
network[k][2] -= (network[k][2] - b) * a / alpharadbias;
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
pix += step;
|
186
|
+
pix = pix >= rgbaSize ? pix - rgbaSize : pix;
|
187
|
+
|
188
|
+
// every 1% of the image, move less over the following iterations.
|
189
|
+
if(++i % delta == 0) {
|
190
|
+
alpha -= alpha / alphadec;
|
191
|
+
radius -= radius / radiusdec;
|
192
|
+
rad = radius >> radiusbiasshift;
|
193
|
+
rad = rad <= 1 ? 0 : rad;
|
194
|
+
radSq = rad*rad;
|
195
|
+
for (j = 0; j < rad; j++) {
|
196
|
+
radpower[j] = alpha * ((radSq - j * j) * radbias / radSq);
|
197
|
+
}
|
198
|
+
}
|
199
|
+
}
|
200
|
+
}
|
201
|
+
// Unbias network to give byte values 0..255
|
202
|
+
for (i = 0; i < numColors; i++) {
|
203
|
+
map[i*3+0] = network[i][0] >>= 4;
|
204
|
+
map[i*3+1] = network[i][1] >>= 4;
|
205
|
+
map[i*3+2] = network[i][2] >>= 4;
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
typedef struct {
|
210
|
+
FILE *fp;
|
211
|
+
int numBits;
|
212
|
+
unsigned char buf[256];
|
213
|
+
unsigned char idx;
|
214
|
+
unsigned tmp;
|
215
|
+
int outBits;
|
216
|
+
int curBits;
|
217
|
+
} jo_gif_lzw_t;
|
218
|
+
|
219
|
+
static void jo_gif_lzw_write(jo_gif_lzw_t *s, int code) {
|
220
|
+
s->outBits |= code << s->curBits;
|
221
|
+
s->curBits += s->numBits;
|
222
|
+
while(s->curBits >= 8) {
|
223
|
+
s->buf[s->idx++] = s->outBits & 255;
|
224
|
+
s->outBits >>= 8;
|
225
|
+
s->curBits -= 8;
|
226
|
+
if (s->idx >= 255) {
|
227
|
+
putc(s->idx, s->fp);
|
228
|
+
fwrite(s->buf, s->idx, 1, s->fp);
|
229
|
+
s->idx = 0;
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
static void jo_gif_lzw_encode(unsigned char *in, int len, FILE *fp) {
|
235
|
+
jo_gif_lzw_t state = {fp, 9};
|
236
|
+
int maxcode = 511;
|
237
|
+
|
238
|
+
// Note: 30k stack space for dictionary =|
|
239
|
+
const int hashSize = 5003;
|
240
|
+
short codetab[hashSize];
|
241
|
+
int hashTbl[hashSize];
|
242
|
+
memset(hashTbl, 0xFF, sizeof(hashTbl));
|
243
|
+
|
244
|
+
jo_gif_lzw_write(&state, 0x100);
|
245
|
+
|
246
|
+
int free_ent = 0x102;
|
247
|
+
int ent = *in++;
|
248
|
+
CONTINUE:
|
249
|
+
while (--len) {
|
250
|
+
int c = *in++;
|
251
|
+
int fcode = (c << 12) + ent;
|
252
|
+
int key = (c << 4) ^ ent; // xor hashing
|
253
|
+
while(hashTbl[key] >= 0) {
|
254
|
+
if(hashTbl[key] == fcode) {
|
255
|
+
ent = codetab[key];
|
256
|
+
goto CONTINUE;
|
257
|
+
}
|
258
|
+
++key;
|
259
|
+
key = key >= hashSize ? key - hashSize : key;
|
260
|
+
}
|
261
|
+
jo_gif_lzw_write(&state, ent);
|
262
|
+
ent = c;
|
263
|
+
if(free_ent < 4096) {
|
264
|
+
if(free_ent > maxcode) {
|
265
|
+
++state.numBits;
|
266
|
+
if(state.numBits == 12) {
|
267
|
+
maxcode = 4096;
|
268
|
+
} else {
|
269
|
+
maxcode = (1<<state.numBits)-1;
|
270
|
+
}
|
271
|
+
}
|
272
|
+
codetab[key] = free_ent++;
|
273
|
+
hashTbl[key] = fcode;
|
274
|
+
} else {
|
275
|
+
memset(hashTbl, 0xFF, sizeof(hashTbl));
|
276
|
+
free_ent = 0x102;
|
277
|
+
jo_gif_lzw_write(&state, 0x100);
|
278
|
+
state.numBits = 9;
|
279
|
+
maxcode = 511;
|
280
|
+
}
|
281
|
+
}
|
282
|
+
jo_gif_lzw_write(&state, ent);
|
283
|
+
jo_gif_lzw_write(&state, 0x101);
|
284
|
+
jo_gif_lzw_write(&state, 0);
|
285
|
+
if(state.idx) {
|
286
|
+
putc(state.idx, fp);
|
287
|
+
fwrite(state.buf, state.idx, 1, fp);
|
288
|
+
}
|
289
|
+
}
|
290
|
+
|
291
|
+
static int jo_gif_clamp(int a, int b, int c) { return a < b ? b : a > c ? c : a; }
|
292
|
+
|
293
|
+
/*
|
294
|
+
|
295
|
+
|
296
|
+
jo_gif_t * jo_gif_start(FILE * fp, short width, short height, short repeat, int numColors) {
|
297
|
+
numColors = numColors > 255 ? 255 : numColors < 2 ? 2 : numColors;
|
298
|
+
jo_gif_t * gif; // = {};
|
299
|
+
gif->width = width;
|
300
|
+
gif->height = height;
|
301
|
+
gif->repeat = repeat;
|
302
|
+
gif->numColors = numColors;
|
303
|
+
gif->palSize = log2(numColors);
|
304
|
+
|
305
|
+
gif->fp = fp;
|
306
|
+
fwrite("GIF89a", 6, 1, gif->fp);
|
307
|
+
// Logical Screen Descriptor
|
308
|
+
fwrite(&gif->width, 2, 1, gif->fp);
|
309
|
+
fwrite(&gif->height, 2, 1, gif->fp);
|
310
|
+
putc(0xF0 | gif->palSize, gif->fp);
|
311
|
+
fwrite("\x00\x00", 2, 1, gif->fp); // bg color index (unused), aspect ratio
|
312
|
+
return gif;
|
313
|
+
}
|
314
|
+
|
315
|
+
*/
|
316
|
+
|
317
|
+
jo_gif_t jo_gif_start(FILE * fp, short width, short height, short repeat, int numColors) {
|
318
|
+
numColors = numColors > 255 ? 255 : numColors < 2 ? 2 : numColors;
|
319
|
+
jo_gif_t gif = {};
|
320
|
+
gif.width = width;
|
321
|
+
gif.height = height;
|
322
|
+
gif.repeat = repeat;
|
323
|
+
gif.numColors = numColors;
|
324
|
+
gif.palSize = log2(numColors);
|
325
|
+
|
326
|
+
gif.fp = fp;
|
327
|
+
fwrite("GIF89a", 6, 1, gif.fp);
|
328
|
+
// Logical Screen Descriptor
|
329
|
+
fwrite(&gif.width, 2, 1, gif.fp);
|
330
|
+
fwrite(&gif.height, 2, 1, gif.fp);
|
331
|
+
putc(0xF0 | gif.palSize, gif.fp);
|
332
|
+
fwrite("\x00\x00", 2, 1, gif.fp); // bg color index (unused), aspect ratio
|
333
|
+
return gif;
|
334
|
+
}
|
335
|
+
|
336
|
+
void jo_gif_frame(jo_gif_t *gif, unsigned char * rgba, short delayCsec, int localPalette) {
|
337
|
+
if(!gif->fp) {
|
338
|
+
printf("Output not initialized.\n");
|
339
|
+
return;
|
340
|
+
}
|
341
|
+
short width = gif->width;
|
342
|
+
short height = gif->height;
|
343
|
+
int size = width * height;
|
344
|
+
|
345
|
+
unsigned char localPalTbl[0x300];
|
346
|
+
unsigned char *palette = gif->frame == 0 || !localPalette ? gif->palette : localPalTbl;
|
347
|
+
if(gif->frame == 0 || localPalette) {
|
348
|
+
jo_gif_quantize(rgba, size*4, 1, palette, gif->numColors);
|
349
|
+
}
|
350
|
+
|
351
|
+
unsigned char *indexedPixels = (unsigned char *)malloc(size);
|
352
|
+
{
|
353
|
+
unsigned char *ditheredPixels = (unsigned char*)malloc(size*4);
|
354
|
+
memcpy(ditheredPixels, rgba, size*4);
|
355
|
+
int i, k;
|
356
|
+
for(k = 0; k < size*4; k+=4) {
|
357
|
+
int rgb[3] = { ditheredPixels[k+0], ditheredPixels[k+1], ditheredPixels[k+2] };
|
358
|
+
int bestd = 0x7FFFFFFF, best = -1;
|
359
|
+
// TODO: exhaustive search. do something better.
|
360
|
+
for(i = 0; i < gif->numColors; ++i) {
|
361
|
+
int bb = palette[i*3+0]-rgb[0];
|
362
|
+
int gg = palette[i*3+1]-rgb[1];
|
363
|
+
int rr = palette[i*3+2]-rgb[2];
|
364
|
+
int d = bb*bb + gg*gg + rr*rr;
|
365
|
+
if(d < bestd) {
|
366
|
+
bestd = d;
|
367
|
+
best = i;
|
368
|
+
}
|
369
|
+
}
|
370
|
+
indexedPixels[k/4] = best;
|
371
|
+
int diff[3] = { ditheredPixels[k+0] - palette[indexedPixels[k/4]*3+0], ditheredPixels[k+1] - palette[indexedPixels[k/4]*3+1], ditheredPixels[k+2] - palette[indexedPixels[k/4]*3+2] };
|
372
|
+
// Floyd-Steinberg Error Diffusion
|
373
|
+
// TODO: Use something better -- http://caca.zoy.org/study/part3.html
|
374
|
+
if(k+4 < size*4) {
|
375
|
+
ditheredPixels[k+4+0] = (unsigned char)jo_gif_clamp(ditheredPixels[k+4+0]+(diff[0]*7/16), 0, 255);
|
376
|
+
ditheredPixels[k+4+1] = (unsigned char)jo_gif_clamp(ditheredPixels[k+4+1]+(diff[1]*7/16), 0, 255);
|
377
|
+
ditheredPixels[k+4+2] = (unsigned char)jo_gif_clamp(ditheredPixels[k+4+2]+(diff[2]*7/16), 0, 255);
|
378
|
+
}
|
379
|
+
if(k+width*4+4 < size*4) {
|
380
|
+
for(i = 0; i < 3; ++i) {
|
381
|
+
ditheredPixels[k-4+width*4+i] = (unsigned char)jo_gif_clamp(ditheredPixels[k-4+width*4+i]+(diff[i]*3/16), 0, 255);
|
382
|
+
ditheredPixels[k+width*4+i] = (unsigned char)jo_gif_clamp(ditheredPixels[k+width*4+i]+(diff[i]*5/16), 0, 255);
|
383
|
+
ditheredPixels[k+width*4+4+i] = (unsigned char)jo_gif_clamp(ditheredPixels[k+width*4+4+i]+(diff[i]*1/16), 0, 255);
|
384
|
+
}
|
385
|
+
}
|
386
|
+
}
|
387
|
+
free(ditheredPixels);
|
388
|
+
}
|
389
|
+
if(gif->frame == 0) {
|
390
|
+
// Global Color Table
|
391
|
+
fwrite(palette, 3*(1<<(gif->palSize+1)), 1, gif->fp);
|
392
|
+
if(gif->repeat >= 0) {
|
393
|
+
// Netscape Extension
|
394
|
+
fwrite("\x21\xff\x0bNETSCAPE2.0\x03\x01", 16, 1, gif->fp);
|
395
|
+
fwrite(&gif->repeat, 2, 1, gif->fp); // loop count (extra iterations, 0=repeat forever)
|
396
|
+
putc(0, gif->fp); // block terminator
|
397
|
+
}
|
398
|
+
}
|
399
|
+
// Graphic Control Extension
|
400
|
+
fwrite("\x21\xf9\x04\x00", 4, 1, gif->fp);
|
401
|
+
fwrite(&delayCsec, 2, 1, gif->fp); // delayCsec x 1/100 sec
|
402
|
+
fwrite("\x00\x00", 2, 1, gif->fp); // transparent color index (first byte), currently unused
|
403
|
+
// Image Descriptor
|
404
|
+
fwrite("\x2c\x00\x00\x00\x00", 5, 1, gif->fp); // header, x,y
|
405
|
+
fwrite(&width, 2, 1, gif->fp);
|
406
|
+
fwrite(&height, 2, 1, gif->fp);
|
407
|
+
if (gif->frame == 0 || !localPalette) {
|
408
|
+
putc(0, gif->fp);
|
409
|
+
} else {
|
410
|
+
putc(0x80|gif->palSize, gif->fp );
|
411
|
+
fwrite(palette, 3*(1<<(gif->palSize+1)), 1, gif->fp);
|
412
|
+
}
|
413
|
+
putc(8, gif->fp); // block terminator
|
414
|
+
jo_gif_lzw_encode(indexedPixels, size, gif->fp);
|
415
|
+
putc(0, gif->fp); // block terminator
|
416
|
+
++gif->frame;
|
417
|
+
free(indexedPixels);
|
418
|
+
}
|
419
|
+
|
420
|
+
void jo_gif_end(jo_gif_t *gif) {
|
421
|
+
if(!gif->fp) {
|
422
|
+
return;
|
423
|
+
}
|
424
|
+
putc(0x3b, gif->fp); // gif trailer
|
425
|
+
fclose(gif->fp);
|
426
|
+
}
|
427
|
+
#endif
|
data/ext/lilimg.c
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <stdlib.h>
|
5
|
+
#include <string.h>
|
6
|
+
#include "nanojpeg.h"
|
7
|
+
#include "jo_gif.h"
|
8
|
+
|
9
|
+
unsigned char * rgb_to_rgba(unsigned char * buf, int w, int h) {
|
10
|
+
int newsize = 4 * w * h;
|
11
|
+
unsigned char * dest;
|
12
|
+
dest = malloc(newsize);
|
13
|
+
int s = 0, d = 0;
|
14
|
+
while (d < newsize) {
|
15
|
+
dest[d++] = buf[s++];
|
16
|
+
dest[d++] = buf[s++];
|
17
|
+
dest[d++] = buf[s++];
|
18
|
+
dest[d++] = 255; // fully opaque
|
19
|
+
}
|
20
|
+
|
21
|
+
return dest;
|
22
|
+
}
|
23
|
+
|
24
|
+
static VALUE jpeg_to_gif(VALUE self, VALUE buf) {
|
25
|
+
rb_check_type(buf, T_STRING);
|
26
|
+
int size = RSTRING_LEN(buf);
|
27
|
+
char * jpeg = StringValuePtr(buf);
|
28
|
+
|
29
|
+
njInit();
|
30
|
+
if (njDecode(jpeg, size)) {
|
31
|
+
free((void*)jpeg);
|
32
|
+
rb_raise(rb_eRuntimeError, "Error decoding input file (size %d)", size);
|
33
|
+
return Qnil;
|
34
|
+
}
|
35
|
+
|
36
|
+
free((void*)jpeg);
|
37
|
+
|
38
|
+
char * out;
|
39
|
+
size_t bufsize;
|
40
|
+
FILE * fp = open_memstream(&out, &bufsize);
|
41
|
+
// FILE * fp = fopen("output.gif", "wb");
|
42
|
+
|
43
|
+
if (!fp) {
|
44
|
+
rb_raise(rb_eRuntimeError, "Cannot initialize memstream");
|
45
|
+
return Qnil;
|
46
|
+
}
|
47
|
+
|
48
|
+
unsigned char * pixels = njGetImage();
|
49
|
+
unsigned char * rgba = rgb_to_rgba(pixels, njGetWidth(), njGetHeight());
|
50
|
+
int maxColors = 8;
|
51
|
+
|
52
|
+
jo_gif_t gif = jo_gif_start(fp, njGetWidth(), njGetHeight(), 0, maxColors);
|
53
|
+
jo_gif_frame(&gif, rgba, 0, 0);
|
54
|
+
jo_gif_end(&gif);
|
55
|
+
|
56
|
+
VALUE rstr;
|
57
|
+
rstr = rb_str_new(out, bufsize);
|
58
|
+
|
59
|
+
free(rgba);
|
60
|
+
free(out);
|
61
|
+
njDone();
|
62
|
+
|
63
|
+
return rstr;
|
64
|
+
}
|
65
|
+
|
66
|
+
VALUE Lilimg = Qnil;
|
67
|
+
|
68
|
+
void Init_lilimg() {
|
69
|
+
Lilimg = rb_define_module("Lilimg");
|
70
|
+
rb_define_module_function(Lilimg, "jpeg_to_gif", jpeg_to_gif, 1);
|
71
|
+
}
|