lilimg 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|