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 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
@@ -0,0 +1,5 @@
1
+ ext/Makefile
2
+ ext/*.o
3
+ ext/*.so
4
+ other
5
+ example
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/example/build.sh ADDED
@@ -0,0 +1,2 @@
1
+ # gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c -lm
2
+ gcc -O3 -o test test.c -I ../ext -lm
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
@@ -0,0 +1,7 @@
1
+ require('./lili')
2
+
3
+ jpeg = IO.read('../label.jpg')
4
+ gif = Lili.jpeg_to_gif(jpeg)
5
+ # puts gif.length
6
+
7
+ File.open('out.gif', 'wb') { |f| f.write(gif) })
data/ext/extconf.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'mkmf'
2
+ extension_name = 'lilimg'
3
+ dir_config(extension_name)
4
+
5
+
6
+ CONFIG['warnflags'].slice!(/ -Wdeclaration-after-statement/)
7
+ create_makefile(extension_name)
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
+ }