oil 0.0.1 → 0.0.2
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.
- 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
|