oil 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +36 -11
- data/Rakefile +0 -12
- data/ext/extconf.rb +18 -0
- data/ext/oil.c +344 -162
- data/test/test_jpeg.rb +19 -5
- data/test/test_png.rb +197 -0
- metadata +5 -3
data/README.rdoc
CHANGED
@@ -4,34 +4,59 @@ http://github.com/ender672/oil
|
|
4
4
|
|
5
5
|
== DESCRIPTION:
|
6
6
|
|
7
|
-
|
7
|
+
Oil resizes JPEG and PNG images.
|
8
|
+
|
9
|
+
== INSTALLATION:
|
10
|
+
|
11
|
+
$ gem install oil
|
8
12
|
|
9
13
|
== SYNOPSIS:
|
10
14
|
|
11
|
-
# Resize a JPEG image
|
12
15
|
require 'oil'
|
13
|
-
|
14
16
|
io = File.open('image.jpg', 'rb')
|
17
|
+
|
18
|
+
# prepare to resize JPEG to fit in a 200x300 box, preserving aspect ratio
|
15
19
|
jpeg = Oil::JPEG.new(io, 200, 300)
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
|
21
|
+
# yield successive parts of the resized and compressed JPEG file
|
22
|
+
jpeg.each { |data| }
|
19
23
|
|
20
24
|
== REQUIREMENTS:
|
21
25
|
|
22
|
-
* IJG JPEG Library
|
26
|
+
* IJG JPEG Library or libjpeg-turbo.
|
27
|
+
* libpng
|
23
28
|
|
24
29
|
Installing libjpeg headers (OSX):
|
25
30
|
|
26
31
|
$ brew install libjpeg # requires homebrew (http://mxcl.github.com/homebrew/)
|
27
32
|
|
28
|
-
Installing libjpeg headers (Debian/Ubuntu):
|
33
|
+
Installing libjpeg and libpng headers (Debian/Ubuntu):
|
29
34
|
|
30
|
-
$ sudo apt-get install libjpeg-dev
|
35
|
+
$ sudo apt-get install libjpeg-dev libpng-dev
|
31
36
|
|
32
|
-
==
|
37
|
+
== COMPILING & TESTING:
|
33
38
|
|
34
|
-
|
39
|
+
Compile & run unit tests. Should show no warnings and no failing tests:
|
40
|
+
|
41
|
+
$ rake compile
|
42
|
+
$ rake test
|
43
|
+
|
44
|
+
Valgrind should not complain (ruby-1.9.3p125, compiled with -O3):
|
45
|
+
|
46
|
+
$ valgrind /path/to/ruby -Iext test/test_jpeg.rb
|
47
|
+
$ valgrind --suppressions=test/valgrind_suppressions.txt /path/to/ruby -Iext test/test_png.rb
|
48
|
+
|
49
|
+
Tests should not leak memory:
|
50
|
+
|
51
|
+
$ ruby -Iext:test -e "require 'test_jpeg.rb'; require 'test_png.rb'; loop{ MiniTest::Unit.new.run }"
|
52
|
+
|
53
|
+
Changes to the interpolator should be analyzed using ResampleScope:
|
54
|
+
|
55
|
+
https://github.com/jsummers/resamplescope
|
56
|
+
|
57
|
+
== TODO:
|
58
|
+
|
59
|
+
* JRuby
|
35
60
|
|
36
61
|
== LICENSE:
|
37
62
|
|
data/Rakefile
CHANGED
@@ -13,13 +13,6 @@ file 'ext/oil.so' => FileList.new('ext/Makefile', 'ext/oil.c') do
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
desc 'Clean up Rubinius .rbc files.'
|
17
|
-
namespace :clean do
|
18
|
-
task :rbc do
|
19
|
-
system "find . -name *.rbc -delete"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
16
|
Rake::TestTask.new do |t|
|
24
17
|
t.libs = ['ext']
|
25
18
|
t.test_files = FileList['test/test*.rb']
|
@@ -28,11 +21,6 @@ end
|
|
28
21
|
CLEAN.add('ext/*{.o,.so,.log}', 'ext/Makefile')
|
29
22
|
CLOBBER.add('*.gem')
|
30
23
|
|
31
|
-
desc 'Build the gem'
|
32
|
-
task :gem do
|
33
|
-
system "gem build oil.gemspec"
|
34
|
-
end
|
35
|
-
|
36
24
|
desc 'Compile the extension'
|
37
25
|
task :compile => "ext/oil.so"
|
38
26
|
|
data/ext/extconf.rb
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
|
3
|
+
# OSX by default keeps libpng in the X11 dirs
|
4
|
+
if RUBY_PLATFORM =~ /darwin/
|
5
|
+
png_idefault = '/usr/X11/include'
|
6
|
+
png_ldefault = '/usr/X11/lib'
|
7
|
+
else
|
8
|
+
png_idefault = nil
|
9
|
+
png_ldefault = nil
|
10
|
+
end
|
11
|
+
|
3
12
|
dir_config('jpeg')
|
13
|
+
dir_config('png', png_idefault, png_ldefault)
|
4
14
|
|
5
15
|
unless have_header('jpeglib.h')
|
6
16
|
abort "libjpeg headers were not found."
|
@@ -10,4 +20,12 @@ unless have_library('jpeg')
|
|
10
20
|
abort "libjpeg was not found."
|
11
21
|
end
|
12
22
|
|
23
|
+
unless have_header('png.h')
|
24
|
+
abort "libpng headers were not found."
|
25
|
+
end
|
26
|
+
|
27
|
+
unless have_library('png')
|
28
|
+
abort "libpng was not found."
|
29
|
+
end
|
30
|
+
|
13
31
|
create_makefile('oil')
|
data/ext/oil.c
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#include <ruby.h>
|
2
2
|
#include <jpeglib.h>
|
3
|
+
#include <png.h>
|
3
4
|
|
4
5
|
#define BUF_LEN 8192
|
5
6
|
|
@@ -24,6 +25,22 @@ struct jpeg_src {
|
|
24
25
|
VALUE buffer;
|
25
26
|
};
|
26
27
|
|
28
|
+
struct png_src {
|
29
|
+
VALUE io;
|
30
|
+
VALUE buffer;
|
31
|
+
};
|
32
|
+
|
33
|
+
struct interpolation {
|
34
|
+
long sw;
|
35
|
+
long sh;
|
36
|
+
long dw;
|
37
|
+
long dh;
|
38
|
+
int cmp;
|
39
|
+
void (*read)(char *, void *);
|
40
|
+
void (*write)(char *, void *);
|
41
|
+
void *data;
|
42
|
+
};
|
43
|
+
|
27
44
|
/* jpeglib error callbacks */
|
28
45
|
|
29
46
|
static void output_message(j_common_ptr cinfo) {}
|
@@ -48,7 +65,7 @@ fill_input_buffer(j_decompress_ptr cinfo)
|
|
48
65
|
VALUE ret, string = src->buffer;
|
49
66
|
long len;
|
50
67
|
char *buf;
|
51
|
-
|
68
|
+
|
52
69
|
ret = rb_funcall(src->io, id_read, 2, INT2FIX(BUF_LEN), string);
|
53
70
|
len = RSTRING_LEN(string);
|
54
71
|
|
@@ -106,124 +123,173 @@ term_destination(j_compress_ptr cinfo)
|
|
106
123
|
}
|
107
124
|
}
|
108
125
|
|
109
|
-
/*
|
126
|
+
/* libpng error callbacks */
|
127
|
+
|
128
|
+
static void png_warning_fn(png_structp png_ptr, png_const_charp message) {}
|
110
129
|
|
111
130
|
static void
|
112
|
-
|
113
|
-
|
131
|
+
png_error_fn(png_structp png_ptr, png_const_charp message)
|
132
|
+
{
|
133
|
+
rb_raise(rb_eRuntimeError, "pnglib: %s", message);
|
134
|
+
}
|
135
|
+
|
136
|
+
/* libpng io callbacks */
|
137
|
+
|
138
|
+
void png_flush_data(png_structp png_ptr){}
|
139
|
+
|
140
|
+
void
|
141
|
+
png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
|
142
|
+
{
|
143
|
+
if (!png_ptr) return;
|
144
|
+
rb_yield(rb_str_new(data, length));
|
145
|
+
}
|
146
|
+
|
147
|
+
void
|
148
|
+
png_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
|
114
149
|
{
|
115
|
-
|
116
|
-
|
117
|
-
|
150
|
+
VALUE string;
|
151
|
+
png_size_t rlen;
|
152
|
+
struct png_src *io_ptr;
|
153
|
+
|
154
|
+
if (!png_ptr) return;
|
155
|
+
io_ptr = (struct png_src *)png_get_io_ptr(png_ptr);
|
156
|
+
string = io_ptr->buffer;
|
157
|
+
rb_funcall(io_ptr->io, id_read, 2, INT2FIX(length), string);
|
158
|
+
rlen = RSTRING_LEN(string);
|
159
|
+
if (rlen > length) rb_raise(rb_eRuntimeError, "IO return buffer is too big.");
|
160
|
+
memcpy(data, RSTRING_PTR(string), RSTRING_LEN(string));
|
161
|
+
}
|
162
|
+
|
163
|
+
/* png read/write callbacks */
|
164
|
+
|
165
|
+
static void
|
166
|
+
png_read(char *sl, void *data)
|
167
|
+
{
|
168
|
+
void **data_ary = (void **)data;
|
169
|
+
png_read_row((png_structp)data_ary[2], (png_bytep)sl, (png_bytep)NULL);
|
170
|
+
}
|
171
|
+
|
172
|
+
static void
|
173
|
+
png_write(char *sl, void *data)
|
174
|
+
{
|
175
|
+
void **data_ary = (void **)data;
|
176
|
+
png_write_row((png_structp)*data_ary, (png_bytep)sl);
|
177
|
+
}
|
178
|
+
|
179
|
+
/* Bilinear Interpolation */
|
180
|
+
|
181
|
+
static void
|
182
|
+
bilinear_get_scanlines(struct interpolation *bi, char **sl1, char **sl2,
|
183
|
+
size_t *next, double ysmp)
|
184
|
+
{
|
185
|
+
int i, j, n, len=bi->cmp * bi->sw - 1;
|
186
|
+
int line=ysmp;
|
187
|
+
char *tmp;
|
188
|
+
|
189
|
+
n = line + 1 - *next;
|
190
|
+
if (line < bi->sh - 1) n++;
|
191
|
+
*next += n;
|
192
|
+
|
193
|
+
for (i=0; i<n; i++) {
|
118
194
|
tmp = *sl1;
|
119
195
|
*sl1 = *sl2;
|
120
196
|
*sl2 = tmp;
|
121
|
-
|
197
|
+
bi->read(*sl2, bi->data);
|
198
|
+
|
199
|
+
for (j=0; j<bi->cmp; j++)
|
200
|
+
sl2[0][len + bi->cmp - j] = sl2[0][len - j];
|
122
201
|
}
|
202
|
+
|
203
|
+
if (line >= bi->sh - 1) *sl1 = *sl2;
|
123
204
|
}
|
124
205
|
|
125
206
|
static void
|
126
|
-
bilinear3(
|
127
|
-
|
207
|
+
bilinear3(char *sl1, char *sl2, char *sl_out, size_t sw, size_t dw, int cmp,
|
208
|
+
double ysmp)
|
128
209
|
{
|
129
|
-
double
|
130
|
-
unsigned char *
|
131
|
-
size_t
|
210
|
+
double xsmp, tx, _tx, p1, p2, p3, p4;
|
211
|
+
unsigned char *c1, *c2, *c3, *c4;
|
212
|
+
size_t xsmp_i, i, j;
|
213
|
+
double xscale_inv, xscale_ctr, ty;
|
132
214
|
|
133
|
-
|
134
|
-
|
135
|
-
|
215
|
+
xscale_inv = sw / (float)dw;
|
216
|
+
xscale_ctr = (xscale_inv - 1) / 2;
|
217
|
+
ty = ysmp - (int)ysmp;
|
136
218
|
|
137
|
-
|
219
|
+
for (i = 0; i < dw; i++) {
|
220
|
+
xsmp = i * xscale_inv + xscale_ctr;
|
221
|
+
xsmp_i = (int)xsmp;
|
222
|
+
|
223
|
+
tx = xsmp - xsmp_i;
|
138
224
|
_tx = 1 - tx;
|
139
225
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
226
|
+
p4 = tx * ty;
|
227
|
+
p3 = _tx * ty;
|
228
|
+
p2 = tx - p4;
|
229
|
+
p1 = _tx - p3;
|
144
230
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
231
|
+
c1 = sl1 + xsmp_i * cmp;
|
232
|
+
c2 = c1 + cmp;
|
233
|
+
c3 = sl2 + xsmp_i * cmp;
|
234
|
+
c4 = c3 + cmp;
|
149
235
|
|
150
236
|
for (j = 0; j < cmp; j++)
|
151
|
-
*sl_out++ =
|
152
|
-
p11 * c11[j];
|
237
|
+
*sl_out++ = p1 * c1[j] + p2 * c2[j] + p3 * c3[j] + p4 * c4[j];
|
153
238
|
}
|
154
239
|
}
|
155
240
|
|
156
241
|
static VALUE
|
157
|
-
bilinear2(VALUE
|
242
|
+
bilinear2(VALUE _args)
|
158
243
|
{
|
159
|
-
|
160
|
-
struct
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
cinfo = (struct jpeg_compress_struct *)args[0];
|
166
|
-
dinfo = (struct jpeg_decompress_struct *)args[1];
|
167
|
-
sl_len = *(size_t *)args[2];
|
168
|
-
sl1 = (JSAMPROW)args[3];
|
169
|
-
sl2 = (JSAMPROW)args[4];
|
170
|
-
sl_out = (JSAMPROW)args[5];
|
171
|
-
|
172
|
-
inv_scale_x = dinfo->output_width / (float)cinfo->image_width;
|
173
|
-
inv_scale_y = dinfo->output_height / (float)cinfo->image_height;
|
174
|
-
advance_scanlines(dinfo, &sl1, &sl2, 1);
|
175
|
-
|
176
|
-
for (i = 0; i < cinfo->image_height; i++) {
|
177
|
-
smpy = i * inv_scale_y;
|
178
|
-
smpy_i = (int)smpy;
|
179
|
-
ty = smpy - smpy_i;
|
180
|
-
|
181
|
-
advance_scanlines(dinfo, &sl1, &sl2, smpy_i - smpy_last);
|
182
|
-
smpy_last = smpy_i;
|
183
|
-
|
184
|
-
sl1[sl_len - 1] = sl1[sl_len - 2];
|
185
|
-
sl2[sl_len - 1] = sl2[sl_len - 2];
|
186
|
-
bilinear3(cinfo, smpy_i ? sl1 : sl2, sl2, sl_out, inv_scale_x, ty);
|
187
|
-
jpeg_write_scanlines(cinfo, &sl_out, 1);
|
188
|
-
}
|
244
|
+
VALUE *args = (VALUE *)_args;
|
245
|
+
struct interpolation *bi=(struct interpolation *)args[0];
|
246
|
+
char *sl1=(char *)args[1], *sl2=(char *)args[2], *sl_out=(char *)args[3];
|
247
|
+
size_t i, line=0;
|
248
|
+
double ysmp, yscale_inv, yscale_ctr;
|
189
249
|
|
190
|
-
|
191
|
-
|
250
|
+
yscale_inv = bi->sh / (float)bi->dh;
|
251
|
+
yscale_ctr = (yscale_inv - 1) / 2;
|
252
|
+
|
253
|
+
for (i = 0; i<bi->dh; i++) {
|
254
|
+
ysmp = i * yscale_inv + yscale_ctr;
|
255
|
+
|
256
|
+
bilinear_get_scanlines(bi, &sl1, &sl2, &line, ysmp);
|
257
|
+
bilinear3(sl1, sl2, sl_out, bi->sw, bi->dw, bi->cmp, ysmp);
|
258
|
+
bi->write(sl_out, bi->data);
|
259
|
+
}
|
192
260
|
|
193
|
-
static VALUE
|
194
|
-
free_sl_bufs(VALUE *args)
|
195
|
-
{
|
196
|
-
free((char *)args[3]);
|
197
|
-
free((char *)args[4]);
|
198
|
-
free((char *)args[5]);
|
199
261
|
return Qnil;
|
200
262
|
}
|
201
263
|
|
202
264
|
static void
|
203
|
-
bilinear(struct
|
204
|
-
struct jpeg_decompress_struct *dinfo)
|
265
|
+
bilinear(struct interpolation *bi)
|
205
266
|
{
|
206
|
-
VALUE
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
sl1 =
|
212
|
-
sl2 =
|
213
|
-
sl_out =
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
267
|
+
VALUE args[4];
|
268
|
+
int state, in_len;
|
269
|
+
char *sl1, *sl2, *sl_out;
|
270
|
+
|
271
|
+
in_len = (bi->sw + 1) * bi->cmp;
|
272
|
+
sl1 = malloc(in_len);
|
273
|
+
sl2 = malloc(in_len);
|
274
|
+
sl_out = malloc(bi->dw * bi->cmp);
|
275
|
+
|
276
|
+
args[0] = (VALUE)bi;
|
277
|
+
args[1] = (VALUE)sl1;
|
278
|
+
args[2] = (VALUE)sl2;
|
279
|
+
args[3] = (VALUE)sl_out;
|
280
|
+
rb_protect(bilinear2, (VALUE)args, &state);
|
281
|
+
|
282
|
+
free(sl1);
|
283
|
+
free(sl2);
|
284
|
+
free(sl_out);
|
285
|
+
|
286
|
+
if (state) rb_jump_tag(state);
|
222
287
|
}
|
223
288
|
|
289
|
+
/* JPEG helper functions */
|
290
|
+
|
224
291
|
static void
|
225
|
-
|
226
|
-
struct jpeg_decompress_struct *dinfo)
|
292
|
+
jpeg_fix_aspect_ratio(j_compress_ptr cinfo, j_decompress_ptr dinfo)
|
227
293
|
{
|
228
294
|
double x, y;
|
229
295
|
|
@@ -235,22 +301,15 @@ fix_aspect_ratio(struct jpeg_compress_struct *cinfo,
|
|
235
301
|
}
|
236
302
|
|
237
303
|
static void
|
238
|
-
|
239
|
-
struct jpeg_decompress_struct *dinfo)
|
304
|
+
jpeg_set_output_header(j_compress_ptr cinfo, j_decompress_ptr dinfo)
|
240
305
|
{
|
241
|
-
struct thumbdata *data;
|
242
|
-
Data_Get_Struct(self, struct thumbdata, data);
|
243
|
-
|
244
|
-
cinfo->image_width = data->width;
|
245
|
-
cinfo->image_height = data->height;
|
246
306
|
cinfo->input_components = dinfo->output_components;
|
247
307
|
cinfo->in_color_space = dinfo->out_color_space;
|
248
308
|
jpeg_set_defaults(cinfo);
|
249
309
|
}
|
250
310
|
|
251
311
|
static void
|
252
|
-
|
253
|
-
struct jpeg_decompress_struct *dinfo)
|
312
|
+
jpeg_pre_scale(j_compress_ptr cinfo, j_decompress_ptr dinfo)
|
254
313
|
{
|
255
314
|
int inv_scale = dinfo->output_width / cinfo->image_width;
|
256
315
|
|
@@ -260,6 +319,86 @@ pre_scale(struct jpeg_compress_struct *cinfo,
|
|
260
319
|
jpeg_calc_output_dimensions(dinfo);
|
261
320
|
}
|
262
321
|
|
322
|
+
static void
|
323
|
+
jpeg_read(char *sl, void *data)
|
324
|
+
{
|
325
|
+
void **data_ary = (void **)data;
|
326
|
+
jpeg_read_scanlines((j_decompress_ptr)*data_ary, (JSAMPROW *)&sl, 1);
|
327
|
+
}
|
328
|
+
|
329
|
+
static void
|
330
|
+
jpeg_write(char *sl, void *data)
|
331
|
+
{
|
332
|
+
void **data_ary = (void **)data;
|
333
|
+
jpeg_write_scanlines((j_compress_ptr)data_ary[1], (JSAMPROW *)&sl, 1);
|
334
|
+
}
|
335
|
+
|
336
|
+
static VALUE
|
337
|
+
jpeg_each3(VALUE _args)
|
338
|
+
{
|
339
|
+
VALUE *args = (VALUE *)_args;
|
340
|
+
j_decompress_ptr dinfo = (j_decompress_ptr)args[0];
|
341
|
+
j_compress_ptr cinfo = (j_compress_ptr)args[1];
|
342
|
+
struct interpolation intrp;
|
343
|
+
void *data[2];
|
344
|
+
|
345
|
+
jpeg_read_header(dinfo, TRUE);
|
346
|
+
jpeg_calc_output_dimensions(dinfo);
|
347
|
+
jpeg_set_output_header(cinfo, dinfo);
|
348
|
+
jpeg_fix_aspect_ratio(cinfo, dinfo);
|
349
|
+
jpeg_pre_scale(cinfo, dinfo);
|
350
|
+
|
351
|
+
jpeg_start_compress(cinfo, TRUE);
|
352
|
+
jpeg_start_decompress(dinfo);
|
353
|
+
|
354
|
+
intrp.sw = dinfo->output_width;
|
355
|
+
intrp.sh = dinfo->output_height;
|
356
|
+
intrp.dw = cinfo->image_width;
|
357
|
+
intrp.dh = cinfo->image_height;
|
358
|
+
intrp.cmp = dinfo->output_components;
|
359
|
+
intrp.read = jpeg_read;
|
360
|
+
intrp.write = jpeg_write;
|
361
|
+
data[0] = dinfo;
|
362
|
+
data[1] = cinfo;
|
363
|
+
intrp.data = (void *)data;
|
364
|
+
|
365
|
+
bilinear(&intrp);
|
366
|
+
//nearest(&intrp);
|
367
|
+
|
368
|
+
jpeg_abort_decompress(dinfo);
|
369
|
+
jpeg_finish_compress(cinfo);
|
370
|
+
|
371
|
+
return Qnil;
|
372
|
+
}
|
373
|
+
|
374
|
+
static void
|
375
|
+
jpeg_each2(struct thumbdata *data, struct jpeg_src *src, struct jpeg_dest *dest)
|
376
|
+
{
|
377
|
+
int state;
|
378
|
+
VALUE args[2];
|
379
|
+
struct jpeg_decompress_struct dinfo;
|
380
|
+
struct jpeg_compress_struct cinfo;
|
381
|
+
|
382
|
+
dinfo.err = cinfo.err = &jerr;
|
383
|
+
|
384
|
+
jpeg_create_compress(&cinfo);
|
385
|
+
cinfo.dest = &dest->mgr;
|
386
|
+
cinfo.image_width = data->width;
|
387
|
+
cinfo.image_height = data->height;
|
388
|
+
|
389
|
+
jpeg_create_decompress(&dinfo);
|
390
|
+
dinfo.src = &src->mgr;
|
391
|
+
|
392
|
+
args[0] = (VALUE)&dinfo;
|
393
|
+
args[1] = (VALUE)&cinfo;
|
394
|
+
rb_protect(jpeg_each3, (VALUE)args, &state);
|
395
|
+
|
396
|
+
jpeg_destroy_decompress(&dinfo);
|
397
|
+
jpeg_destroy_compress(&cinfo);
|
398
|
+
|
399
|
+
if (state) rb_jump_tag(state);
|
400
|
+
}
|
401
|
+
|
263
402
|
/* Ruby allocator, deallocator, mark, and methods */
|
264
403
|
|
265
404
|
static void
|
@@ -284,113 +423,151 @@ allocate(VALUE klass)
|
|
284
423
|
|
285
424
|
/*
|
286
425
|
* call-seq:
|
287
|
-
*
|
426
|
+
* Class.new(io, width, height) -> obj
|
288
427
|
*
|
289
|
-
* Creates a new
|
290
|
-
*
|
428
|
+
* Creates a new resizer. +io_in+ must be an IO-like object that responds to
|
429
|
+
* #read(size, buffer) and #seek(size).
|
291
430
|
*
|
292
431
|
* The resulting image will be scaled to fit in the box given by +width+ and
|
293
|
-
* +height+ while preserving the aspect ratio.
|
294
|
-
*
|
295
|
-
* If both box dimensions are larger than the source image, then the image will
|
296
|
-
* be unchanged. The thumbnailer will not enlarge images.
|
432
|
+
* +height+ while preserving the original aspect ratio.
|
297
433
|
*/
|
298
434
|
|
299
435
|
static VALUE
|
300
|
-
initialize(VALUE self, VALUE io, VALUE
|
436
|
+
initialize(VALUE self, VALUE io, VALUE rb_width, VALUE rb_height)
|
301
437
|
{
|
438
|
+
int width=FIX2INT(rb_width), height=FIX2INT(rb_height);
|
302
439
|
struct thumbdata *data;
|
440
|
+
|
441
|
+
if (width<1 || height<1) rb_raise(rb_eArgError, "Dimensions must be > 0");
|
303
442
|
|
304
443
|
Data_Get_Struct(self, struct thumbdata, data);
|
305
444
|
data->io = io;
|
306
|
-
data->width =
|
307
|
-
data->height =
|
445
|
+
data->width = width;
|
446
|
+
data->height = height;
|
308
447
|
|
309
448
|
return self;
|
310
449
|
}
|
311
450
|
|
312
|
-
static VALUE
|
313
|
-
each2(VALUE *args)
|
314
|
-
{
|
315
|
-
VALUE self = args[0];
|
316
|
-
struct jpeg_decompress_struct *dinfo;
|
317
|
-
struct jpeg_compress_struct *cinfo;
|
318
|
-
|
319
|
-
dinfo = (struct jpeg_decompress_struct *)args[1];
|
320
|
-
cinfo = (struct jpeg_compress_struct *)args[2];
|
321
|
-
|
322
|
-
jpeg_read_header(dinfo, TRUE);
|
323
|
-
jpeg_calc_output_dimensions(dinfo);
|
324
|
-
set_header(self, cinfo, dinfo);
|
325
|
-
fix_aspect_ratio(cinfo, dinfo);
|
326
|
-
pre_scale(cinfo, dinfo);
|
327
|
-
|
328
|
-
jpeg_start_compress(cinfo, TRUE);
|
329
|
-
jpeg_start_decompress(dinfo);
|
330
|
-
|
331
|
-
bilinear(cinfo, dinfo);
|
332
|
-
|
333
|
-
jpeg_abort_decompress(dinfo);
|
334
|
-
jpeg_finish_compress(cinfo);
|
335
|
-
|
336
|
-
return Qnil;
|
337
|
-
}
|
338
|
-
|
339
|
-
static VALUE
|
340
|
-
destroy_info(VALUE *args)
|
341
|
-
{
|
342
|
-
jpeg_destroy_decompress((struct jpeg_decompress_struct *)args[1]);
|
343
|
-
jpeg_destroy_compress((struct jpeg_compress_struct *)args[2]);
|
344
|
-
return Qnil;
|
345
|
-
}
|
346
|
-
|
347
451
|
/*
|
348
452
|
* call-seq:
|
349
|
-
*
|
453
|
+
* jpeg.each(&block) -> self
|
350
454
|
*
|
351
455
|
* Yields a series of binary strings that make up the resized JPEG image.
|
352
456
|
*/
|
353
457
|
|
354
458
|
static VALUE
|
355
|
-
|
459
|
+
jpeg_each(VALUE self)
|
356
460
|
{
|
357
|
-
VALUE ensure_args[3];
|
358
461
|
struct jpeg_src src;
|
359
|
-
struct jpeg_decompress_struct dinfo;
|
360
462
|
struct jpeg_dest dest;
|
361
|
-
struct jpeg_compress_struct cinfo;
|
362
463
|
struct thumbdata *data;
|
363
464
|
Data_Get_Struct(self, struct thumbdata, data);
|
364
465
|
|
365
|
-
dinfo.err = cinfo.err = &jerr;
|
366
|
-
jpeg_create_decompress(&dinfo);
|
367
|
-
jpeg_create_compress(&cinfo);
|
368
|
-
|
369
466
|
memset(&src, 0, sizeof(struct jpeg_src));
|
467
|
+
src.io = data->io;
|
468
|
+
src.buffer = rb_str_new(0, 0);
|
370
469
|
src.mgr.init_source = init_source;
|
371
470
|
src.mgr.fill_input_buffer = fill_input_buffer;
|
372
471
|
src.mgr.skip_input_data = skip_input_data;
|
373
472
|
src.mgr.resync_to_restart = jpeg_resync_to_restart;
|
374
473
|
src.mgr.term_source = term_source;
|
375
|
-
src.io = data->io;
|
376
|
-
src.buffer = rb_str_new(0, BUF_LEN);
|
377
474
|
|
378
475
|
memset(&dest, 0, sizeof(struct jpeg_dest));
|
476
|
+
dest.buffer = rb_str_new(0, BUF_LEN);
|
477
|
+
dest.mgr.next_output_byte = RSTRING_PTR(dest.buffer);
|
478
|
+
dest.mgr.free_in_buffer = BUF_LEN;
|
379
479
|
dest.mgr.init_destination = init_destination;
|
380
480
|
dest.mgr.empty_output_buffer = empty_output_buffer;
|
381
481
|
dest.mgr.term_destination = term_destination;
|
382
|
-
dest.buffer = rb_str_new(0, BUF_LEN);
|
383
|
-
dest.mgr.next_output_byte = RSTRING_PTR(dest.buffer);
|
384
|
-
dest.mgr.free_in_buffer = BUF_LEN;
|
385
482
|
|
386
|
-
|
387
|
-
|
483
|
+
jpeg_each2(data, &src, &dest);
|
484
|
+
|
485
|
+
return self;
|
486
|
+
}
|
487
|
+
|
488
|
+
static VALUE
|
489
|
+
png_each2(VALUE _args)
|
490
|
+
{
|
491
|
+
VALUE *args = (VALUE *)_args;
|
492
|
+
png_structp write_ptr=(png_structp)args[0];
|
493
|
+
png_infop write_i_ptr=(png_infop)args[1];
|
494
|
+
png_structp read_ptr=(png_structp)args[2];
|
495
|
+
png_infop read_i_ptr=(png_infop)args[3];
|
496
|
+
struct thumbdata *thumb=(struct thumbdata *)args[4];
|
497
|
+
void *data[4];
|
498
|
+
struct interpolation intrp;
|
499
|
+
|
500
|
+
png_read_info(read_ptr, read_i_ptr);
|
501
|
+
png_set_IHDR(write_ptr, write_i_ptr, thumb->width, thumb->height, 8,
|
502
|
+
PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
|
503
|
+
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
504
|
+
png_write_info(write_ptr, write_i_ptr);
|
505
|
+
|
506
|
+
intrp.sw = png_get_image_width(read_ptr, read_i_ptr);
|
507
|
+
intrp.sh = png_get_image_height(read_ptr, read_i_ptr);
|
508
|
+
intrp.dw = thumb->width;
|
509
|
+
intrp.dh = thumb->height;
|
510
|
+
intrp.cmp = 3;
|
511
|
+
intrp.read = png_read;
|
512
|
+
intrp.write = png_write;
|
513
|
+
data[0] = write_ptr;
|
514
|
+
data[1] = write_i_ptr;
|
515
|
+
data[2] = read_ptr;
|
516
|
+
data[3] = read_i_ptr;
|
517
|
+
intrp.data = (void *)data;
|
518
|
+
|
519
|
+
bilinear(&intrp);
|
520
|
+
|
521
|
+
png_read_end(read_ptr, (png_infop)NULL);
|
522
|
+
png_write_end(write_ptr, write_i_ptr);
|
523
|
+
|
524
|
+
return Qnil;
|
525
|
+
}
|
388
526
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
527
|
+
/*
|
528
|
+
* call-seq:
|
529
|
+
* png.each(&block) -> self
|
530
|
+
*
|
531
|
+
* Yields a series of binary strings that make up the resized PNG image.
|
532
|
+
*/
|
393
533
|
|
534
|
+
static VALUE
|
535
|
+
png_each(VALUE self)
|
536
|
+
{
|
537
|
+
int state;
|
538
|
+
VALUE args[5];
|
539
|
+
png_structp read_ptr, write_ptr;
|
540
|
+
png_infop read_i_ptr, write_i_ptr;
|
541
|
+
struct png_src src;
|
542
|
+
struct thumbdata *thumb;
|
543
|
+
Data_Get_Struct(self, struct thumbdata, thumb);
|
544
|
+
|
545
|
+
write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
|
546
|
+
(png_error_ptr)png_error_fn,
|
547
|
+
(png_error_ptr)png_warning_fn);
|
548
|
+
write_i_ptr = png_create_info_struct(write_ptr);
|
549
|
+
png_set_write_fn(write_ptr, NULL, png_write_data, png_flush_data);
|
550
|
+
|
551
|
+
src.io = thumb->io;
|
552
|
+
src.buffer = rb_str_new(0, 0);
|
553
|
+
|
554
|
+
read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
|
555
|
+
(png_error_ptr)png_error_fn,
|
556
|
+
(png_error_ptr)png_warning_fn);
|
557
|
+
read_i_ptr = png_create_info_struct(read_ptr);
|
558
|
+
png_set_read_fn(read_ptr, (void *)&src, png_read_data);
|
559
|
+
|
560
|
+
args[0] = (VALUE)write_ptr;
|
561
|
+
args[1] = (VALUE)write_i_ptr;
|
562
|
+
args[2] = (VALUE)read_ptr;
|
563
|
+
args[3] = (VALUE)read_i_ptr;
|
564
|
+
args[4] = (VALUE)thumb;
|
565
|
+
rb_protect(png_each2, (VALUE)args, &state);
|
566
|
+
|
567
|
+
png_destroy_read_struct(&read_ptr, &read_i_ptr, (png_info **)NULL);
|
568
|
+
png_destroy_write_struct(&write_ptr, &write_i_ptr);
|
569
|
+
|
570
|
+
if (state) rb_jump_tag(state);
|
394
571
|
return self;
|
395
572
|
}
|
396
573
|
|
@@ -398,10 +575,16 @@ void
|
|
398
575
|
Init_oil()
|
399
576
|
{
|
400
577
|
VALUE mOil = rb_define_module("Oil");
|
578
|
+
|
401
579
|
VALUE cJPEG = rb_define_class_under(mOil, "JPEG", rb_cObject);
|
402
580
|
rb_define_alloc_func(cJPEG, allocate);
|
403
581
|
rb_define_method(cJPEG, "initialize", initialize, 3);
|
404
|
-
rb_define_method(cJPEG, "each",
|
582
|
+
rb_define_method(cJPEG, "each", jpeg_each, 0);
|
583
|
+
|
584
|
+
VALUE cPNG = rb_define_class_under(mOil, "PNG", rb_cObject);
|
585
|
+
rb_define_alloc_func(cPNG, allocate);
|
586
|
+
rb_define_method(cPNG, "initialize", initialize, 3);
|
587
|
+
rb_define_method(cPNG, "each", png_each, 0);
|
405
588
|
|
406
589
|
jpeg_std_error(&jerr);
|
407
590
|
jerr.error_exit = error_exit;
|
@@ -410,4 +593,3 @@ Init_oil()
|
|
410
593
|
id_read = rb_intern("read");
|
411
594
|
id_seek = rb_intern("seek");
|
412
595
|
}
|
413
|
-
|
data/test/test_jpeg.rb
CHANGED
@@ -16,21 +16,21 @@ module Oil
|
|
16
16
|
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xDA\x00\x08\x01\x01\x00\x01\x3F\
|
17
17
|
\x10\xFF\xD9"
|
18
18
|
|
19
|
-
def
|
19
|
+
def test_valid
|
20
20
|
validate_jpeg resize_string(JPEG_DATA)
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def test_missing_eof
|
24
24
|
validate_jpeg resize_string(JPEG_DATA[0..-2])
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
27
|
+
def test_bogus_header_marker
|
28
28
|
str = JPEG_DATA.dup
|
29
29
|
str[3] = "\x10"
|
30
30
|
assert_raises(RuntimeError) { resize_string(str) }
|
31
31
|
end
|
32
32
|
|
33
|
-
def
|
33
|
+
def test_bogus_body_marker
|
34
34
|
str = JPEG_DATA.dup
|
35
35
|
str[-1] = "\x10"
|
36
36
|
assert_raises(RuntimeError) { resize_string(str) }
|
@@ -44,6 +44,21 @@ module Oil
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
+
def test_alloc_each
|
48
|
+
io = JPEG.allocate
|
49
|
+
assert_raises(NoMethodError){ io.each{ |f| } }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Test dimensions
|
53
|
+
|
54
|
+
def test_zero_dim
|
55
|
+
assert_raises(ArgumentError){ resize_string(JPEG_DATA, 0, 0) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_neg_dim
|
59
|
+
assert_raises(ArgumentError){ resize_string(JPEG_DATA, -1231, -123) }
|
60
|
+
end
|
61
|
+
|
47
62
|
# Test io source handler
|
48
63
|
|
49
64
|
def test_io_returns_too_much_data
|
@@ -52,7 +67,6 @@ module Oil
|
|
52
67
|
buf << (io.read(size)[0..-2] * 2)
|
53
68
|
end
|
54
69
|
assert_raises(RuntimeError) { custom_io proc, JPEG_DATA }
|
55
|
-
#custom_io proc, JPEG_DATA
|
56
70
|
end
|
57
71
|
|
58
72
|
def test_io_does_nothing
|
data/test/test_png.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'oil'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
module Oil
|
7
|
+
class TestPNG < MiniTest::Unit::TestCase
|
8
|
+
# http://garethrees.org/2007/11/14/pngcrush/
|
9
|
+
PNG_DATA = "\
|
10
|
+
\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\
|
11
|
+
\x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x37\x6E\xF9\x24\x00\x00\x00\x10\x49\
|
12
|
+
\x44\x41\x54\x78\x9C\x62\x60\x01\x00\x00\x00\xFF\xFF\x03\x00\x00\x06\x00\x05\
|
13
|
+
\x57\xBF\xAB\xD4\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82"
|
14
|
+
|
15
|
+
def test_valid
|
16
|
+
validate_png resize_string(PNG_DATA)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_bogus_header_chunk
|
20
|
+
str = PNG_DATA.dup
|
21
|
+
str[15] = "\x10"
|
22
|
+
assert_raises(RuntimeError) { resize_string(str) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_bogus_body_chunk
|
26
|
+
str = PNG_DATA.dup
|
27
|
+
str[37] = "\x10"
|
28
|
+
assert_raises(RuntimeError) { resize_string(str) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_bogus_end_chunk
|
32
|
+
str = PNG_DATA.dup
|
33
|
+
str[-6] = "\x10"
|
34
|
+
assert_raises(RuntimeError) { resize_string(str) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_calls_each_during_yield
|
38
|
+
io = StringIO.new(PNG_DATA)
|
39
|
+
j = PNG.new(io, 600, 600)
|
40
|
+
assert_raises(RuntimeError) do
|
41
|
+
j.each{ |d| j.each { |e| j.each { |f| } } }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_alloc_each
|
46
|
+
io = PNG.allocate
|
47
|
+
assert_raises(NoMethodError){ io.each{ |f| } }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Test dimensions
|
51
|
+
|
52
|
+
def test_zero_dim
|
53
|
+
assert_raises(ArgumentError){ resize_string(PNG_DATA, 0, 0) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_neg_dim
|
57
|
+
assert_raises(ArgumentError){ resize_string(PNG_DATA, -1231, -123) }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Test io source handler
|
61
|
+
|
62
|
+
def test_io_returns_too_much_data
|
63
|
+
proc = Proc.new do |io, size, buf|
|
64
|
+
buf.slice!(0,0)
|
65
|
+
buf << (io.read(size)[0..-2] * 2)
|
66
|
+
end
|
67
|
+
assert_raises(RuntimeError) { custom_io proc, PNG_DATA }
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_io_does_nothing
|
71
|
+
assert_raises(RuntimeError) { custom_io(nil) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_io_raises_exception_immediately
|
75
|
+
proc = Proc.new{ raise CustomError }
|
76
|
+
assert_raises(CustomError) { custom_io proc }
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_io_throws_immediately
|
80
|
+
proc = Proc.new{ throw(:foo) }
|
81
|
+
catch(:foo){ custom_io proc }
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_io_raises_exception_in_body
|
85
|
+
flag = false
|
86
|
+
proc = Proc.new do |parent, size, buf|
|
87
|
+
raise CustomError if flag
|
88
|
+
flag = true
|
89
|
+
parent.read(size, buf)
|
90
|
+
end
|
91
|
+
assert_raises(CustomError) { custom_io proc, big_png }
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_io_throws_in_body
|
95
|
+
flag = false
|
96
|
+
proc = Proc.new do |parent, size, buf|
|
97
|
+
throw :foo if flag
|
98
|
+
flag = true
|
99
|
+
parent.read(size, buf)
|
100
|
+
end
|
101
|
+
catch(:foo){ custom_io proc, big_png }
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_io_shrinks_buffer
|
105
|
+
proc = Proc.new do |parent, size, buf|
|
106
|
+
parent.read(size, buf)
|
107
|
+
buf.slice!(0, 10)
|
108
|
+
end
|
109
|
+
assert_raises(RuntimeError) { custom_io(proc, big_png) }
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_io_enlarges_buffer
|
113
|
+
proc = Proc.new do |parent, size, buf|
|
114
|
+
res = parent.read(size, buf)
|
115
|
+
buf << res
|
116
|
+
end
|
117
|
+
assert_raises(RuntimeError) { validate_jpeg custom_io(proc, big_png) }
|
118
|
+
end
|
119
|
+
|
120
|
+
# Test yielding
|
121
|
+
|
122
|
+
def test_raise_in_each
|
123
|
+
assert_raises(CustomError) do
|
124
|
+
io = StringIO.new(PNG_DATA)
|
125
|
+
PNG.new(io, 200, 200).each { raise CustomError }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_throw_in_each
|
130
|
+
catch(:foo) do
|
131
|
+
io = StringIO.new(PNG_DATA)
|
132
|
+
PNG.new(io, 200, 200).each { throw :foo }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_each_shrinks_buffer
|
137
|
+
io = StringIO.new(PNG_DATA)
|
138
|
+
io_out = binary_stringio
|
139
|
+
PNG.new(io, 200, 200).each { |d| io_out << d; d.slice!(0, 4) }
|
140
|
+
validate_png(io_out.string)
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_each_enlarges_buffer
|
144
|
+
io = StringIO.new(PNG_DATA)
|
145
|
+
io_out = binary_stringio
|
146
|
+
PNG.new(io, 200, 200).each { |d| io_out << d; d << "foobar" }
|
147
|
+
validate_png(io_out.string)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def io(io, width=nil, height=nil)
|
153
|
+
width ||= 100
|
154
|
+
height ||= 200
|
155
|
+
out = binary_stringio
|
156
|
+
PNG.new(io, width, height).each{ |d| out << d }
|
157
|
+
return out.string
|
158
|
+
end
|
159
|
+
|
160
|
+
def custom_io(*args)
|
161
|
+
io CustomIO.new(*args)
|
162
|
+
end
|
163
|
+
|
164
|
+
def resize_string(str, width=nil, height=nil)
|
165
|
+
io(StringIO.new(str), width, height)
|
166
|
+
end
|
167
|
+
|
168
|
+
def validate_png(data)
|
169
|
+
assert_equal "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", data[0,8]
|
170
|
+
assert_equal "\x49\x45\x4E\x44\xAE\x42\x60\x82", data[-8, 8]
|
171
|
+
end
|
172
|
+
|
173
|
+
def big_png
|
174
|
+
resize_string(PNG_DATA, 200, 200)
|
175
|
+
end
|
176
|
+
|
177
|
+
def binary_stringio
|
178
|
+
io = StringIO.new
|
179
|
+
io.set_encoding 'ASCII-8BIT' if RUBY_VERSION >= '1.9'
|
180
|
+
io
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class CustomError < RuntimeError; end
|
185
|
+
|
186
|
+
class CustomIO
|
187
|
+
def initialize(proc, *args)
|
188
|
+
@parent = StringIO.new(*args)
|
189
|
+
@proc = proc
|
190
|
+
end
|
191
|
+
|
192
|
+
def read(size, buf)
|
193
|
+
@proc.call(@parent, size, buf) if @proc
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oil
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-03-08 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description:
|
15
15
|
email: tle@holymonkey.com
|
@@ -24,6 +24,7 @@ files:
|
|
24
24
|
- ext/oil.c
|
25
25
|
- ext/extconf.rb
|
26
26
|
- test/test_jpeg.rb
|
27
|
+
- test/test_png.rb
|
27
28
|
homepage: http://github.com/ender672/oil
|
28
29
|
licenses: []
|
29
30
|
post_install_message:
|
@@ -47,6 +48,7 @@ rubyforge_project:
|
|
47
48
|
rubygems_version: 1.8.17
|
48
49
|
signing_key:
|
49
50
|
specification_version: 3
|
50
|
-
summary:
|
51
|
+
summary: Oil resizes JPEG and PNG images.
|
51
52
|
test_files:
|
52
53
|
- test/test_jpeg.rb
|
54
|
+
- test/test_png.rb
|