sobakasu-image_science 1.1.3

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.
@@ -0,0 +1,157 @@
1
+ require File.dirname(__FILE__) + '/../ext/image_science_ext'
2
+
3
+ class ImageScience
4
+
5
+ VERSION = "1.1.3"
6
+
7
+ ##
8
+ # Returns the type of the image as a string.
9
+
10
+ def self.image_type(path)
11
+ fif_to_string(file_type(path))
12
+ end
13
+
14
+ ##
15
+ # Returns the type of the image as a string.
16
+
17
+ def image_type
18
+ ImageScience.fif_to_string(@file_type)
19
+ end
20
+
21
+ ##
22
+ # Returns the colorspace of the image as a string
23
+
24
+ def colorspace
25
+ case colortype
26
+ when 0 then depth == 1 ? 'InvertedMonochrome' : 'InvertedGrayscale'
27
+ when 1 then depth == 1 ? 'Monochrome' : 'Grayscale'
28
+ when 2 then 'RGB'
29
+ when 3 then 'Indexed'
30
+ when 4 then 'RGBA'
31
+ when 5 then 'CMYK'
32
+ end
33
+ end
34
+
35
+ def colourspace # :nodoc:
36
+ colorspace
37
+ end
38
+
39
+ ##
40
+ # alias for buffer()
41
+
42
+ def data(*args)
43
+ buffer(*args)
44
+ end
45
+
46
+ ##
47
+ # call-seq:
48
+ # thumbnail(size)
49
+ # thumbnail(size) { |image| ... }
50
+ #
51
+ # Creates a proportional thumbnail of the image scaled so its
52
+ # longest edge is resized to +size+. If a block is given, yields
53
+ # the new image, else returns true on success.
54
+
55
+ def thumbnail(size)
56
+ w, h = width, height
57
+ scale = size.to_f / (w > h ? w : h)
58
+ w = (w * scale).to_i
59
+ h = (h * scale).to_i
60
+
61
+ if block_given?
62
+ self.resize(w, h) do |image|
63
+ yield image
64
+ end
65
+ else
66
+ self.resize(w, h)
67
+ end
68
+ end
69
+
70
+ ##
71
+ # call-seq:
72
+ # cropped_thumbnail(size)
73
+ # cropped_thumbnail(size) { |image| ... }
74
+ #
75
+ # Creates a square thumbnail of the image cropping the longest edge
76
+ # to match the shortest edge, resizes to +size+. If a block is given,
77
+ # yields the new image, else returns true on success.
78
+
79
+ def cropped_thumbnail(size) # :yields: image
80
+ w, h = width, height
81
+ l, t, r, b, half = 0, 0, w, h, (w - h).abs / 2
82
+
83
+ l, r = half, half + h if w > h
84
+ t, b = half, half + w if h > w
85
+
86
+ if block_given?
87
+ with_crop(l, t, r, b) do |img|
88
+ img.thumbnail(size) do |thumb|
89
+ yield thumb
90
+ end
91
+ end
92
+ else
93
+ crop(l, t, r, b) && thumbnail(size)
94
+ end
95
+ end
96
+
97
+ ##
98
+ # resize the image to fit within the max_w and max_h passed in without
99
+ # changing the aspect ratio of the original image. If a block is given,
100
+ # yields the new image, else returns true on success.
101
+
102
+ def fit_within(max_w, max_h)
103
+ w, h = width, height
104
+
105
+ if w > max_w.to_i or h > max_h.to_i
106
+
107
+ w_ratio = max_w.quo(w)
108
+ h_ratio = max_h.quo(h)
109
+
110
+ if (w_ratio < h_ratio)
111
+ h = (h * w_ratio)
112
+ w = (w * w_ratio)
113
+ else
114
+ h = (h * h_ratio)
115
+ w = (w * h_ratio)
116
+ end
117
+ end
118
+
119
+ if block_given?
120
+ self.resize(w, h) do |image|
121
+ yield image
122
+ end
123
+ else
124
+ self.resize(w, h)
125
+ end
126
+ end
127
+
128
+ # call-seq:
129
+ # img[x, y] -> index
130
+ # img[x, y] -> [red, green, blue]
131
+ #
132
+ # alias for get_pixel_color
133
+
134
+ def [](x, y)
135
+ get_pixel_color(x, y)
136
+ end
137
+
138
+ # call-seq:
139
+ # img[x, y] = index
140
+ # img[x, y] = [red, green, blue]
141
+ #
142
+ # alias for set_pixel_color
143
+
144
+ def []=(x, y, *args)
145
+ set_pixel_color(x, y, *args)
146
+ end
147
+
148
+ private
149
+
150
+ def self.fif_to_string(fif)
151
+ file_types = %W{BMP ICO JPEG JNG KOALA IFF MNG PBM PBMRAW PCD PCX PGM
152
+ PGMRAW PNG PPM PPMRAW RAS TARGA TIFF WBMP PSD CUT XBM
153
+ XPM DDS GIF HDR FAXG3 SGI EXR J2K JP2}
154
+ file_types[fif]
155
+ end
156
+
157
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,531 @@
1
+ require File.dirname(__FILE__) + '/../lib/image_science'
2
+
3
+ include ImageScience::ColorChannels
4
+ include ImageScience::ColorTypes
5
+ include ImageScience::ImageFilters
6
+ include ImageScience::ImageFormats
7
+
8
+ describe ImageScience do
9
+
10
+ FILE_TYPES = %W{png jpg gif bmp tif xpm}
11
+
12
+ before(:each) do
13
+ @path = 'spec/fixtures'
14
+ @h = @w = 50
15
+ end
16
+
17
+ after(:each) do
18
+ FILE_TYPES.each do |ext|
19
+ File.unlink tmp_image_path(ext) if File.exist? tmp_image_path(ext)
20
+ end
21
+ end
22
+
23
+ describe "get_version" do
24
+ it "should return the image science version" do
25
+ ImageScience.get_version.should_not be_nil
26
+ end
27
+ end
28
+
29
+ FILE_TYPES.each do |ext|
30
+
31
+ describe "#{ext}" do
32
+
33
+ describe "new" do
34
+ it "should create a new ImageScience object by reading a file" do
35
+ img = ImageScience.new(image_path(ext))
36
+ img.should be_kind_of(ImageScience)
37
+ [img.width, img.height].should == [@w, @h]
38
+ end
39
+
40
+ it "should create a new ImageScience object by reading image data" do
41
+ data = File.read(image_path(ext))
42
+ img = ImageScience.new(data)
43
+ img.should be_kind_of(ImageScience)
44
+ [img.width, img.height].should == [@w, @h]
45
+ end
46
+ end
47
+
48
+ describe "with_image" do
49
+ it "should raise an error when a file does not exist" do
50
+ lambda {
51
+ ImageScience.with_image(image_path(ext) + "nope") {}
52
+ }.should raise_error
53
+ end
54
+
55
+ it "should fetch image dimensions" do
56
+ ImageScience.with_image image_path(ext) do |img|
57
+ img.should be_kind_of(ImageScience)
58
+ img.height.should == @h
59
+ img.width.should == @w
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "with_image_from_memory" do
65
+ it "should raise an error when an empty string is given" do
66
+ lambda {
67
+ ImageScience.with_image_from_memory("") {}
68
+ }.should raise_error
69
+ end
70
+ end
71
+
72
+ describe "with_image_from_memory" do
73
+ it "should fetch image dimensions" do
74
+ data = File.new(image_path(ext)).binmode.read
75
+ ImageScience.with_image_from_memory data do |img|
76
+ img.should be_kind_of(ImageScience)
77
+ img.height.should == @h
78
+ img.width.should == @w
79
+ end
80
+ end
81
+ end
82
+
83
+ describe "save" do
84
+ it "should save a new copy of an image" do
85
+ ImageScience.with_image image_path(ext) do |img|
86
+ img.save(tmp_image_path(ext)).should be_true
87
+ end
88
+ File.exists?(tmp_image_path(ext)).should be_true
89
+
90
+ ImageScience.with_image tmp_image_path(ext) do |img|
91
+ img.should be_kind_of(ImageScience)
92
+ img.height.should == @h
93
+ img.width.should == @w
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "resize" do
99
+
100
+ it "should resize an image" do
101
+ ImageScience.with_image image_path(ext) do |img|
102
+ img.resize(25, 25) do |thumb|
103
+ thumb.save(tmp_image_path(ext)).should be_true
104
+ end
105
+ end
106
+
107
+ File.exists?(tmp_image_path(ext)).should be_true
108
+
109
+ ImageScience.with_image tmp_image_path(ext) do |img|
110
+ img.should be_kind_of(ImageScience)
111
+ img.height.should == 25
112
+ img.width.should == 25
113
+ end
114
+ end
115
+
116
+ it "should resize an image given floating point dimensions" do
117
+ ImageScience.with_image image_path(ext) do |img|
118
+ img.resize(25.2, 25.7) do |thumb|
119
+ thumb.save(tmp_image_path(ext)).should be_true
120
+ end
121
+ end
122
+
123
+ File.exists?(tmp_image_path(ext)).should be_true
124
+
125
+ ImageScience.with_image tmp_image_path(ext) do |img|
126
+ img.should be_kind_of(ImageScience)
127
+ img.height.should == 25
128
+ img.width.should == 25
129
+ end
130
+ end
131
+
132
+ # do not accept negative or zero values for width/height
133
+ it "should raise an error if given invalid width or height" do
134
+ [ [0, 25], [25, 0], [-25, 25], [25, -25] ].each do |width, height|
135
+ lambda {
136
+ ImageScience.with_image image_path(ext) do |img|
137
+ img.resize(width, height) do |thumb|
138
+ thumb.save(tmp_image_path(ext))
139
+ end
140
+ end
141
+ }.should raise_error
142
+
143
+ File.exists?(tmp_image_path(ext)).should be_false
144
+ end
145
+ end
146
+
147
+ it "should resize the image in-place if no block given" do
148
+ ImageScience.with_image image_path(ext) do |img|
149
+ img.resize(20, 20).should be_true
150
+ img.width.should == 20
151
+ img.height.should == 20
152
+ end
153
+ end
154
+
155
+ it "should resize the image with the given filter" do
156
+ ImageScience.with_image image_path(ext) do |img|
157
+ img.resize(20, 20, FILTER_BILINEAR).should be_true
158
+ img.width.should == 20
159
+ img.height.should == 20
160
+ end
161
+ end
162
+
163
+ end
164
+
165
+ describe "buffer" do
166
+ it "should return image data" do
167
+ ImageScience.with_image image_path(ext) do |img|
168
+ expected = File.size(image_path(ext))
169
+ tolerance = expected * 0.05
170
+
171
+ data = img.buffer
172
+ data.should_not be_nil
173
+ #data.length.should be_close(expected, tolerance)
174
+ end
175
+ end
176
+
177
+ it "should yield image data" do
178
+ ImageScience.with_image image_path(ext) do |img|
179
+ expected = File.size(image_path(ext))
180
+ tolerance = expected * 0.05
181
+
182
+ img.buffer do |data|
183
+ data.should_not be_nil
184
+ #data.length.should be_close(expected, tolerance)
185
+ end
186
+ end
187
+ end
188
+
189
+ it "should return image data in the given format" do
190
+ ImageScience.with_image image_path(ext) do |img|
191
+ [FIF_BMP, FIF_GIF, FIF_JPEG, FIF_PNG].each do |target_format|
192
+ data = img.buffer(target_format)
193
+ data.should_not be_nil
194
+ end
195
+ end
196
+ end
197
+
198
+ # TODO: convert to use constants
199
+ it "should accept save flags" do
200
+ ImageScience.with_image image_path(ext) do |img|
201
+ jpg_large = img.buffer(FIF_JPEG, 0x80) # jpeg quality superb
202
+ jpg_small = img.buffer(FIF_JPEG, 0x08) # jpeg quality bad
203
+ jpg_small.length.should < jpg_large.length
204
+ end
205
+ end
206
+
207
+ end
208
+
209
+ describe "fit_within" do
210
+
211
+ it "should resize image to fit within given dimensions (yield)" do
212
+ # 50 x 50 image -> shrink to 20, 50 -> 20, 20
213
+ ImageScience.with_image image_path(ext) do |img|
214
+ img.fit_within(20, 50) do |thumb|
215
+ thumb.width.should == 20
216
+ thumb.height.should == 20
217
+ end
218
+ end
219
+ end
220
+
221
+ it "should resize image to fit within given dimensions (inline)" do
222
+ # 50 x 50 image -> shrink to 20, 50 -> 20, 20
223
+ ImageScience.with_image image_path(ext) do |img|
224
+ img.fit_within(20, 50)
225
+ img.width.should == 20
226
+ img.height.should == 20
227
+ end
228
+
229
+ # 100 x 50 image -> shrink to 50, 100 -> 50, 25
230
+ ImageScience.with_image image_path(ext, "pix2") do |img|
231
+ img.fit_within(50, 100)
232
+ img.width.should == 50
233
+ img.height.should == 25
234
+ end
235
+
236
+ # 100 x 50 image -> shrink to 150, 25 -> 50, 25
237
+ ImageScience.with_image image_path(ext, "pix2") do |img|
238
+ img.fit_within(150, 25)
239
+ img.width.should == 50
240
+ img.height.should == 25
241
+ end
242
+ end
243
+
244
+ end
245
+
246
+ describe "get_pixel_color" do
247
+ expected = {
248
+ :jpg => [[62, 134, 122, 0], [0, 14, 7, 0]],
249
+ :png => [[62, 134, 121, 0], [1, 2, 2, 0]],
250
+ :gif => [[59, 135, 119, 0], [0, 2, 0, 0]],
251
+ :bmp => [[62, 134, 121, 0], [1, 2, 2, 0]],
252
+ :tif => [[62, 134, 121, 0], [1, 2, 2, 0]],
253
+ :xpm => [[255, 255, 255, 0], [0, 0, 0, 0]]
254
+ }
255
+
256
+ it "should get pixel color" do
257
+ ImageScience.with_image image_path(ext) do |img|
258
+ rgb = img.get_pixel_color(10,7)
259
+ rgb.should_not be_nil
260
+ rgb.should == expected[ext.to_sym][0]
261
+
262
+ rgb = img.get_pixel_color(24,0)
263
+ rgb.should_not be_nil
264
+ rgb.should == expected[ext.to_sym][1]
265
+ end
266
+ end
267
+
268
+ it "should get pixel color with []" do
269
+ ImageScience.with_image image_path(ext) do |img|
270
+ rgb = img[10,7]
271
+ rgb.should_not be_nil
272
+ rgb.should == expected[ext.to_sym][0]
273
+
274
+ rgb = img[24,0]
275
+ rgb.should_not be_nil
276
+ rgb.should == expected[ext.to_sym][1]
277
+ end
278
+ end
279
+ end
280
+
281
+ describe "set_pixel_color" do
282
+ it "should set pixel color" do
283
+ ImageScience.with_image image_path(ext) do |img|
284
+ if img.colortype == FIC_PALETTE
285
+ img.set_pixel_color(10, 7, 3).should be_true
286
+ img[25, 25] = 4
287
+ else
288
+ img.set_pixel_color(10, 7, [100, 20, 50]).should be_true
289
+ img.set_pixel_color(25, 25, 100, 20, 50).should be_true
290
+ img.set_pixel_color(25, 25, [100, 20, 50, 0]).should be_true
291
+ img.set_pixel_color(25, 25, 100, 20, 50, 0).should be_true
292
+ img[25, 25] = [100, 20, 50, 0];
293
+ end
294
+ end
295
+ end
296
+ end
297
+
298
+ describe "thumbnail" do
299
+ # Note: pix2 is 100x50
300
+ it "should create a proportional thumbnail" do
301
+ thumbnail_created = false
302
+ ImageScience.with_image image_path(ext, "pix2") do |img|
303
+ img.thumbnail(30) do |thumb|
304
+ thumb.should_not be_nil
305
+ thumb.width.should == 30
306
+ thumb.height.should == thumb.width / 2 # half of width
307
+ thumbnail_created = true
308
+ end
309
+ end
310
+ thumbnail_created.should be_true
311
+ end
312
+
313
+ it "should create a proportional thumbnail in-place if no block given" do
314
+ thumbnail_created = false
315
+ ImageScience.with_image image_path(ext, "pix2") do |img|
316
+ img.thumbnail(30)
317
+ img.width.should == 30
318
+ img.height.should == img.width / 2 # half of width
319
+ thumbnail_created = true
320
+ end
321
+ thumbnail_created.should be_true
322
+ end
323
+ end
324
+
325
+ describe "cropped_thumbnail" do
326
+ # Note: pix2 is 100x50
327
+ it "should create a square thumbnail" do
328
+ thumbnail_created = false
329
+ ImageScience.with_image image_path(ext, "pix2") do |img|
330
+ img.cropped_thumbnail(30) do |thumb|
331
+ thumb.should_not be_nil
332
+ thumb.width.should == 30
333
+ thumb.height.should == 30 # same as width
334
+ thumbnail_created = true
335
+ end
336
+ end
337
+ thumbnail_created.should be_true
338
+ end
339
+
340
+ it "should create a square thumbnail in-place if no block given" do
341
+ thumbnail_created = false
342
+ ImageScience.with_image image_path(ext, "pix2") do |img|
343
+ img.cropped_thumbnail(30)
344
+ img.width.should == 30
345
+ img.height.should == 30 # same as width
346
+ thumbnail_created = true
347
+ end
348
+ thumbnail_created.should be_true
349
+ end
350
+ end
351
+
352
+ describe "crop" do
353
+ it "should crop the image in-place if no block given" do
354
+ ImageScience.with_image image_path(ext) do |img|
355
+ img.crop(0, 0, 25, 20).should be_true
356
+ img.width.should == 25
357
+ img.height.should == 20
358
+ end
359
+ end
360
+ end
361
+
362
+ # image_type calls ImageScience.file_type, converts to string.
363
+ # allow calling as a class or instance method
364
+ describe "image_type" do
365
+ expected = {
366
+ 'gif' => 'GIF',
367
+ 'jpg' => 'JPEG',
368
+ 'png' => 'PNG',
369
+ 'bmp' => 'BMP',
370
+ 'tif' => 'TIFF',
371
+ 'xpm' => 'XPM'
372
+ }
373
+ it "should return the image type (class method)" do
374
+ ImageScience.image_type(image_path(ext)).should == expected[ext]
375
+ end
376
+
377
+ it "should return the image type (instance method)" do
378
+ ImageScience.with_image image_path(ext) do |img|
379
+ img.image_type.should == expected[ext]
380
+ end
381
+ end
382
+ end
383
+
384
+ # colorspace calls img.colortype & img.depth, converts to string
385
+ describe "colorspace" do
386
+ it "should return the color space" do
387
+ expected = {
388
+ 'gif' => 'Indexed',
389
+ 'jpg' => 'RGB',
390
+ 'png' => 'RGB',
391
+ 'bmp' => 'RGB',
392
+ 'tif' => 'RGB',
393
+ 'xpm' => 'Indexed'
394
+ }
395
+ ImageScience.with_image image_path(ext) do |img|
396
+ img.colorspace.should == expected[ext]
397
+ end
398
+ end
399
+ end
400
+
401
+ describe "depth" do
402
+ it "should return the BPP of the image" do
403
+ expected = {
404
+ 'gif' => 8,
405
+ 'jpg' => 24,
406
+ 'png' => 24,
407
+ 'bmp' => 24,
408
+ 'tif' => 24,
409
+ 'xpm' => 8
410
+ }
411
+ ImageScience.with_image image_path(ext) do |img|
412
+ img.depth.should == expected[ext]
413
+ end
414
+ end
415
+ end
416
+
417
+ describe "adjust_gamma" do
418
+ it "should perform gamma correction" do
419
+ ImageScience.with_image image_path(ext) do |img|
420
+ # darken image
421
+ img.adjust_gamma(0.5).should be_true
422
+ end
423
+ end
424
+ end
425
+
426
+ describe "adjust_brightness" do
427
+ it "should adjust brightness" do
428
+ ImageScience.with_image image_path(ext) do |img|
429
+ # 50% brighter
430
+ img.adjust_brightness(50).should be_true
431
+ end
432
+ end
433
+ end
434
+
435
+ describe "adjust_contrast" do
436
+ it "should adjust contrast" do
437
+ ImageScience.with_image image_path(ext) do |img|
438
+ # 50% less contrast
439
+ img.adjust_contrast(-50).should be_true
440
+ end
441
+ end
442
+ end
443
+
444
+ describe "invert" do
445
+ it "should invert pixel data" do
446
+ ImageScience.with_image image_path(ext) do |img|
447
+ # 50% less contrast
448
+ img.invert.should be_true
449
+ end
450
+ end
451
+ end
452
+
453
+ describe "histogram" do
454
+ it "should compute the image histogram" do
455
+ ImageScience.with_image image_path(ext) do |img|
456
+ h = img.histogram
457
+ h.should be_kind_of(Array)
458
+ h.length.should == 256
459
+ end
460
+ end
461
+
462
+ it "should compute the image histogram for a given channel" do
463
+ ImageScience.with_image image_path(ext) do |img|
464
+ [FICC_RED, FICC_GREEN, FICC_BLUE].each do |channel|
465
+ h = img.histogram(channel)
466
+ h.should be_kind_of(Array)
467
+ h.length.should == 256
468
+ end
469
+ end
470
+ end
471
+ end
472
+
473
+ describe "rotate" do
474
+
475
+ # test Rotate
476
+ it "should rotate the image 90 degrees" do
477
+ ImageScience.with_image image_path(ext, "pix2") do |img|
478
+ w, h = img.width, img.height
479
+ img.rotate(90)
480
+ # width & height reversed
481
+ img.width.should == h
482
+ img.height.should == w
483
+ end
484
+ end
485
+
486
+ # test RotateEx
487
+ it "should rotate the image 120 degrees about an origin" do
488
+ ImageScience.with_image image_path(ext, "pix2") do |img|
489
+ w, h = img.width, img.height
490
+ img.rotate(120, 0, 0, 20, 20)
491
+ # width & height unchanged
492
+ img.width.should == w
493
+ img.height.should == h
494
+ end
495
+ end
496
+
497
+ end
498
+
499
+ describe "flip_horizontal" do
500
+ it "should flip the image horizontally" do
501
+ ImageScience.with_image image_path(ext) do |img|
502
+ img.flip_horizontal.should be_true
503
+ end
504
+ end
505
+ end
506
+
507
+ describe "flip_vertical" do
508
+ it "should flip the image vertically" do
509
+ ImageScience.with_image image_path(ext) do |img|
510
+ img.flip_vertical.should be_true
511
+ end
512
+ end
513
+ end
514
+
515
+ end
516
+ end
517
+
518
+ private
519
+
520
+ def image_path(extension, basename = "pix")
521
+ raise "extension required" unless extension
522
+ File.join(@path, "#{basename}.#{extension}")
523
+ end
524
+
525
+ def tmp_image_path(extension, basename = "pix")
526
+ raise "extension required" unless extension
527
+ File.join(@path, "#{basename}-tmp.#{extension}")
528
+ end
529
+
530
+ end
531
+
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require 'cp-deployment'