devil 0.1.9.1 → 0.1.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,542 +1,547 @@
1
- # (C) John Mair 2009, under the MIT licence
2
-
3
- require 'rbconfig'
4
-
5
- direc = File.dirname(__FILE__)
6
- dlext = Config::CONFIG['DLEXT']
7
-
8
- begin
9
- if RUBY_VERSION && RUBY_VERSION =~ /1.9/
10
- require "#{direc}/1.9/devil.#{dlext}"
11
- else
12
- require "#{direc}/1.8/devil.#{dlext}"
13
- end
14
- rescue LoadError => e
15
- require "#{direc}/devil.#{dlext}"
16
- end
17
- require "#{direc}/devil/version"
18
-
19
- # Provides a high level wrapper for the low-level DevIL Ruby bindings
20
- module Devil
21
- include IL
22
- include ILU
23
-
24
- class << self
25
-
26
- # loads +file+ and returns a new image
27
- # Optionally accepts a block and yields the newly created image to the block.
28
- def load_image(file, options={}, &block)
29
- name = prepare_image
30
- attach_image_from_file(file)
31
-
32
- out_profile = options[:out_profile]
33
- in_profile = options[:in_profile]
34
-
35
- # apply a color profile if one is provided
36
- IL.ApplyProfile(in_profile, out_profile) if out_profile
37
-
38
- check_and_run_hook(:load_image_hook)
39
- error_check
40
- wrap_and_yield(name, file, block)
41
- end
42
-
43
- alias_method :with_image, :load_image
44
- alias_method :load, :load_image
45
-
46
- # returns a blank image of +width+ and +height+.
47
- # Optionally accepts the :color hash param that fills the new image with a color
48
- # (see: Devil.set_options :clear_color)
49
- # Optionally accepts a block and yields the newly created image to the block.
50
- def create_image(width, height, options={}, &block)
51
- name = prepare_image
52
- out_profile = options[:out_profile]
53
- in_profile = options[:in_profile]
54
-
55
- clear_color = options[:color]
56
-
57
- # created image is formatted RGBA8
58
- IL.TexImage(width, height, 1, 4, IL::RGBA, IL::UNSIGNED_BYTE, nil)
59
-
60
- # apply a color profile if one is provided
61
- IL.ApplyProfile(in_profile, out_profile) if out_profile
62
-
63
- IL.ClearColour(*clear_color) if clear_color
64
- IL.ClearImage
65
- IL.ClearColour(*Devil.get_options[:clear_color]) if clear_color
66
-
67
- check_and_run_hook(:create_image_hook)
68
- error_check
69
- wrap_and_yield(name, nil, block)
70
- end
71
-
72
- alias_method :create_blank_image, :create_image
73
-
74
- # load multiple images and yield them to the block
75
- # e.g Devil.with_group("hello.png", "friend.png") { |img1, img2| ... }
76
- # all yielded images are cleaned up at end of block so you do not need to
77
- # explictly call img1.free
78
- def with_group(*files, &block)
79
- images = files.map do |file|
80
- name = prepare_image
81
- attach_image_from_file(file)
82
- check_and_run_hook(:load_image_hook)
83
- error_check
84
-
85
- Image.new(name, file)
86
- end
87
-
88
- if block
89
- begin
90
- block.call(*images)
91
- ensure
92
- images.each { |img| img.free if img.name }
93
- end
94
- else
95
- raise RuntimeError, "a block must be provided."
96
- end
97
- end
98
-
99
- alias_method :with_images, :with_group
100
-
101
- # convert an image +blob+ with +width+ and +height+
102
- # to a bona fide image
103
- def from_blob(blob, width, height)
104
- Image.new(IL.FromBlob(blob, width, height), nil)
105
- end
106
-
107
- # configure Devil.
108
- # accepts hash parameters: :scale_filter, :placement, :clear_color,
109
- # :window_size, :edge_filter.
110
- #
111
- # :scale_filter accepts a valid scaling algorithm: (default is LANCZOS3).
112
- # Devil::NEAREST, Devil::LINEAR, Devil::BILINEAR, Devil::SCALE_BOX,
113
- # Devil::SCALE_TRIANGLE, Devil::SCALE_BELL, Devil::SCALE_BSPLINE,
114
- # Devil::SCALE_LANCZOS3, Devil::SCALE_MITCHELL
115
- #
116
- # :placement determines where in the canvas the image will be placed after
117
- # the canvas has been enlarged using the 'enlarge_canvas' method.
118
- # Valid parameters are: Devil::CENTER, Devil::LOWER_LEFT, Devil::UPPER_RIGHT, etc
119
- #
120
- # :clear_color sets the current clearing colour to be used by future
121
- # calls to clear. rotate and enlarge_canvas both use these values to
122
- # clear blank space in images, too.
123
- # e.g Devil.set_options(:clear_color => [255, 255, 0, 255])
124
- # Above sets the clear color to yellow with full opacity.
125
- #
126
- # :window_size sets the display window size Gosu will use when displaying images
127
- # that invoked the 'show' method. (default is 1024 x 768)
128
- # e.g Devil.set_options(:window_size => [2000, 768])
129
- # Example above sets a window size of 2000x768.
130
- # Note: :window_size is only relevant when require 'devil/gosu' is used.
131
- #
132
- # :edge_filter sets the edge detection algorithm to use when invoking
133
- # the 'edge_detect' method. (defaults to :prewitt)
134
- # Allowed values are :prewitt and :sobel
135
- #
136
- # hooks:
137
- # :prepare_image_hook, :create_image_hook, :load_image_hook
138
- # e.g Devil.set_options :load_image_hook => proc { IL::ConvertImage(IL::RGBA, IL::UNSIGNED_BYTE) }
139
- def set_options(options={})
140
- @options.merge!(options)
141
-
142
- # update the config. options
143
- ILU.ImageParameter(ILU::FILTER, @options[:scale_filter])
144
- ILU.ImageParameter(ILU::PLACEMENT, @options[:placement])
145
- IL.SetInteger(IL::JPG_QUALITY, @options[:jpg_quality])
146
- IL.ClearColour(*@options[:clear_color])
147
- end
148
-
149
- # return the current Devil configuration.
150
- def get_options
151
- @options
152
- end
153
-
154
- # initializes Devil and sets defaults.
155
- # This method should never need to be called directly.
156
- def init
157
- # initialize DevIL
158
- IL.Init
159
- ILU.Init
160
-
161
- set_defaults
162
- end
163
-
164
- # restore Devil's default configuration.
165
- def set_defaults
166
- @options = {
167
- :scale_filter => ILU::SCALE_LANCZOS3,
168
- :edge_filter => :prewitt,
169
- :window_size => [1024, 768],
170
- :clear_color => [255, 248, 230, 0],
171
- :placement => ILU::CENTER,
172
- :prepare_image_hook => nil,
173
- :load_image_hook => nil,
174
- :create_image_hook => nil,
175
- :jpg_quality => 99
176
- }
177
-
178
- # configurable options
179
- ILU.ImageParameter(ILU::FILTER, @options[:scale_filter])
180
- ILU.ImageParameter(ILU::PLACEMENT, @options[:placement])
181
- IL.SetInteger(IL::JPG_QUALITY, @options[:jpg_quality])
182
- IL.ClearColour(*@options[:clear_color])
183
-
184
- # fixed options
185
- IL.Enable(IL::FILE_OVERWRITE)
186
- IL.Enable(IL::ORIGIN_SET)
187
- IL.OriginFunc(IL::ORIGIN_LOWER_LEFT)
188
- end
189
-
190
- alias_method :restore_defaults, :set_defaults
191
-
192
- private
193
-
194
- def prepare_image
195
- name = IL.GenImages(1).first
196
- IL.BindImage(name)
197
-
198
- check_and_run_hook(:prepare_image_hook)
199
-
200
- name
201
- end
202
-
203
- def attach_image_from_file(file)
204
- IL.LoadImage(file)
205
-
206
- # ensure all images are formatted RGBA8
207
- IL.ConvertImage(IL::RGBA, IL::UNSIGNED_BYTE)
208
- end
209
-
210
- def wrap_and_yield(name, file, block)
211
- img = Image.new(name, file)
212
- if block
213
- begin
214
- block.call(img)
215
- ensure
216
- img.free if img.name
217
- end
218
- else
219
- img
220
- end
221
- end
222
-
223
- def check_and_run_hook(hook_name)
224
- Devil.get_options[hook_name].call if Devil.get_options[hook_name]
225
- end
226
-
227
- def error_check
228
- if (error_code = IL.GetError) != IL::NO_ERROR
229
- raise RuntimeError, "An error occured. #{ILU.ErrorString(error_code)}"
230
- end
231
- end
232
- end
233
- end
234
- ## end of Devil module
235
-
236
- # wraps a DevIL image
237
- class Devil::Image
238
- attr_reader :name, :file
239
-
240
- def initialize(name, file)
241
- @name = name
242
- @file = file
243
- end
244
-
245
- # Frees the memory associated with the image.
246
- # Must be called explictly if load_image or create_image is invoked without a block.
247
- def free
248
- raise "Error: calling 'free' on already freed image! #{self}" if !@name
249
- IL.DeleteImages([@name])
250
- error_check
251
- @name = nil
252
- end
253
-
254
- alias_method :close, :free
255
- alias_method :delete, :free
256
-
257
- # returns the width of the image.
258
- def width
259
- action { IL.GetInteger(IL::IMAGE_WIDTH) }
260
- end
261
-
262
- alias_method :columns, :width
263
-
264
- # returns the height of the image.
265
- def height
266
- action { IL.GetInteger(IL::IMAGE_HEIGHT) }
267
- end
268
-
269
- alias_method :rows, :height
270
-
271
- # saves the image to +file+. If no +file+ is provided default to the opened file.
272
- # Optional :quality hash parameter (only applies to JPEG images).
273
- # Valid values are in the 0-99 range, with 99 being the best quality.
274
- def save(file = @file, options = {})
275
- quality = options[:quality]
276
-
277
- raise "This image does not have an associated file. Please provide an explicit file name when saving." if !file
278
-
279
- action do
280
- IL.SetInteger(IL::JPG_QUALITY, quality) if quality
281
- IL.SaveImage(file)
282
- IL.SetInteger(IL::JPG_QUALITY, Devil.get_options[:jpg_quality]) if quality
283
- end
284
- self
285
- end
286
-
287
- # resize the image to +width+ and +height+. Aspect ratios of the image do not have to be the same.
288
- # Optional :filter hash parameter that maps to a valid scale filter
289
- # (see: Devil.set_options :scale_filter)
290
- def resize(width, height, options = {})
291
- filter = options[:filter]
292
-
293
- action do
294
- ILU.ImageParameter(ILU::FILTER, filter) if filter
295
- ILU.Scale(width, height, 1)
296
- ILU.ImageParameter(ILU::FILTER, Devil.get_options[:scale_filter]) if filter
297
- end
298
-
299
- self
300
- end
301
-
302
- # resize the image to +width+ and +height+. Aspect ratios of the image do not have to be the same.
303
- # Applies a gaussian filter before rescaling to reduce aliasing.
304
- # Optional :filter hash parameter that maps to a valid scale filter
305
- # Optional :gauss parameter - number of times to apply gaussian filter before resizing (default is 2)
306
- # (see: Devil.set_options :scale_filter)
307
- def resize2(width, height, options = {})
308
- gauss = options[:gauss] ||= 2
309
- blur(gauss) if (width < self.width && height < self.height)
310
- resize(width, height, options)
311
- self
312
- end
313
-
314
- # Creates a proportional thumbnail of the image scaled so its longest
315
- # edge is resized to +size+.
316
- # Optional :filter hash parameter that maps to a valid scale filter
317
- # Optional :gauss parameter - number of times to apply gaussian filter before resizing (default is 2)
318
- # (see: Devil.set_options :scale_filter)
319
- def thumbnail(size, options = {})
320
-
321
- # this thumbnail code from image_science.rb
322
- w, h = width, height
323
- scale = size.to_f / (w > h ? w : h)
324
- resize((w * scale).to_i, (h * scale).to_i, options)
325
- self
326
- end
327
-
328
- # Creates a proportional thumbnail of the image scaled so its longest
329
- # edge is resized to +size+.
330
- # Applies a gaussian filter before rescaling to reduce aliasing.
331
- # Optional :filter hash parameter that maps to a valid scale filter
332
- # (see: Devil.set_options :scale_filter)
333
- def thumbnail2(size, options = {})
334
- gauss = options[:gauss] ||= 2
335
- blur(gauss)
336
- thumbnail(size, options)
337
- self
338
- end
339
-
340
- # return a deep copy of the current image.
341
- def dup
342
- new_image_name = action { IL.CloneCurImage }
343
- Devil::Image.new(new_image_name, nil)
344
- end
345
-
346
- alias_method :clone, :dup
347
-
348
- # crop the current image.
349
- # +xoff+ number of pixels to skip in x direction.
350
- # +yoff+ number of pixels to skip in y direction.
351
- # +width+ number of pixels to preserve in x direction.
352
- # +height+ number of pixels to preserve in y direction.
353
- def crop(xoff, yoff, width, height)
354
- action { ILU.Crop(xoff, yoff, 1, width, height, 1) }
355
- self
356
- end
357
-
358
- # enlarge the canvas of current image to +width+ and +height+.
359
- def enlarge_canvas(width, height)
360
- if width < self.width || height < self.height
361
- raise "width and height parameters must be larger than current image width and height"
362
- end
363
-
364
- action { ILU.EnlargeCanvas(width, height, 1) }
365
- self
366
- end
367
-
368
- # splice the +source+ image into current image at position +x+ and +y+.
369
- # Takes an optional +:crop+ hash parameter that has the following format: +:crop => [sx, sy, width, height]+
370
- # +sx+, +sy+, +width, +height+ crop the source image to be spliced.
371
- # +sx+ is how many pixels to skip in x direction of source image.
372
- # +sy+ is how many pixels to skip in y direction of source image.
373
- # +width+ number of pixels to preserve in x direction of source image.
374
- # +height+ number of pixels to preserve in y direction of source image.
375
- # if no +:crop+ parameter is provided then the whole image is spliced in.
376
- def blit(source, x, y, options = {})
377
- options = {
378
- :crop => [0, 0, source.width, source.height]
379
- }.merge!(options)
380
-
381
- action do
382
- IL.Blit(source.name, x, y, 0, options[:crop][0], options[:crop][1], 0,
383
- options[:crop][2], options[:crop][3], 1)
384
- end
385
-
386
- self
387
- end
388
-
389
- alias_method :composite, :blit
390
-
391
- # reflect image about its y axis.
392
- def mirror
393
- action { ILU.Mirror }
394
- self
395
- end
396
-
397
- # use prewitt or sobel filters to detect the edges in the current image.
398
- # Optional :filter hash parameter selects filter to use (:prewitt or :sobel).
399
- # (see: Devil.set_options :edge_filter)
400
- def edge_detect(options={})
401
- options = {
402
- :filter => Devil.get_options[:edge_filter]
403
- }.merge!(options)
404
-
405
- case options[:filter]
406
- when :prewitt
407
- action { ILU.EdgeDetectP }
408
- when :sobel
409
- action { ILU.EdgeDetectS }
410
- else
411
- raise "No such edge filter #{options[:filter]}. Use :prewitt or :sobel"
412
- end
413
- self
414
- end
415
-
416
- # embosses an image, causing it to have a "relief" feel to it using a convolution filter.
417
- def emboss
418
- action { ILU.Emboss }
419
- self
420
- end
421
-
422
- # applies a strange color distortion effect to the image giving a preternatural feel
423
- def alienify
424
- action { ILU.Alienify }
425
- self
426
- end
427
-
428
- # performs a gaussian blur on the image. The blur is performed +iter+ times.
429
- def blur(iter)
430
- action { ILU.BlurGaussian(iter) }
431
- self
432
- end
433
-
434
- # 'pixelize' the image using a pixel size of +pixel_size+.
435
- def pixelize(pixel_size)
436
- action { ILU.Pixelize(pixel_size) }
437
- self
438
- end
439
-
440
- # add random noise to the image. +factor+ is the tolerance to use.
441
- # accepeted values range from 0.0 - 1.0.
442
- def noisify(factor)
443
- action { ILU.Noisify(factor) }
444
- self
445
- end
446
-
447
- # The sharpening +factor+ must be in the range of 0.0 - 2.5. A value of 1.0 for the sharpening.
448
- # factor will have no effect on the image. Values in the range 1.0 - 2.5 will sharpen the
449
- # image, with 2.5 having the most pronounced sharpening effect. Values from 0.0 to 1.0 do
450
- # a type of reverse sharpening, blurring the image. Values outside of the 0.0 - 2.5 range
451
- # produce undefined results.
452
- #
453
- # The number of +iter+ (iterations) to perform will usually be 1, but to achieve more sharpening,
454
- # increase the number of iterations.
455
- def sharpen(factor, iter)
456
- action { ILU.Sharpen(factor, iter) }
457
- self
458
- end
459
-
460
- # applies gamma correction to an image using an exponential curve.
461
- # +factor+ is gamma correction factor to use.
462
- # A value of 1.0 leaves the image unmodified.
463
- # Values in the range 0.0 - 1.0 darken the image
464
- # Values above 1.0 brighten the image.
465
- def gamma_correct(factor)
466
- action { ILU.GammaCorrect(factor) }
467
- self
468
- end
469
-
470
- # invert the color of every pixel in the image.
471
- def negate
472
- action { ILU.Negative }
473
- self
474
- end
475
-
476
- alias_method :negative, :negate
477
-
478
- # +factor+ describes desired contrast to use
479
- # A value of 1.0 has no effect on the image.
480
- # Values between 1.0 and 1.7 increase the amount of contrast (values above 1.7 have no effect)
481
- # Valid range of +factor+ is 0.0 - 1.7.
482
- def contrast(factor)
483
- action { ILU.Contrast(factor) }
484
- self
485
- end
486
-
487
- # darkens the bright colours and lightens the dark
488
- # colours, reducing the contrast in an image or 'equalizing' it.
489
- def equalize
490
- action { ILU.Equalize }
491
- self
492
- end
493
-
494
- # returns the image data in the form of a ruby string.
495
- # The image data is formatted to RGBA / UNSIGNED BYTE.
496
- def to_blob
497
- action { IL.ToBlob }
498
- end
499
-
500
- # flip the image about its x axis.
501
- def flip
502
- action { ILU.FlipImage }
503
- self
504
- end
505
-
506
- # rotate an image about its central point by +angle+ degrees (counter clockwise).
507
- def rotate(angle)
508
- action { ILU.Rotate(angle) }
509
- self
510
- end
511
-
512
- # simply clears the image to the 'clear color' (specified using Devil.set_options(:clear_color => [r, g, b, a])
513
- def clear
514
- action { IL.ClearImage }
515
- self
516
- end
517
-
518
- private
519
-
520
- def set_binding
521
- raise "Error: trying to use image that has already been freed! #{self}" if !@name
522
- IL.BindImage(@name)
523
- end
524
-
525
- def error_check
526
- if (error_code = IL.GetError) != IL::NO_ERROR
527
- raise RuntimeError, "An error occured. #{ILU.ErrorString(error_code)}"
528
- end
529
- end
530
-
531
- def action
532
- set_binding
533
- result = yield
534
- error_check
535
-
536
- result
537
- end
538
- end
539
- ## end of Devil::Image
540
-
541
- # initialize Devil and set default config
542
- Devil.init
1
+ # (C) John Mair 2009, under the MIT licence
2
+
3
+ require 'rbconfig'
4
+
5
+ direc = File.dirname(__FILE__)
6
+ dlext = Config::CONFIG['DLEXT']
7
+
8
+ begin
9
+ if RUBY_VERSION && RUBY_VERSION =~ /1.9/
10
+ require "#{direc}/1.9/devil.#{dlext}"
11
+ else
12
+ require "#{direc}/1.8/devil.#{dlext}"
13
+ end
14
+ rescue LoadError => e
15
+ require "#{direc}/devil.#{dlext}"
16
+ end
17
+ require "#{direc}/devil/version"
18
+
19
+ # Provides a high level wrapper for the low-level DevIL Ruby bindings
20
+ module Devil
21
+ include IL
22
+ include ILU
23
+
24
+ class << self
25
+
26
+ # loads +file+ and returns a new image
27
+ # Optionally accepts a block and yields the newly created image to the block.
28
+ def load_image(file, options={}, &block)
29
+ name = prepare_image
30
+ attach_image_from_file(file)
31
+
32
+ out_profile = options[:out_profile]
33
+ in_profile = options[:in_profile]
34
+
35
+ # apply a color profile if one is provided
36
+ IL.ApplyProfile(in_profile, out_profile) if out_profile
37
+
38
+ check_and_run_hook(:load_image_hook)
39
+ error_check
40
+ wrap_and_yield(name, file, block)
41
+ end
42
+
43
+ alias_method :with_image, :load_image
44
+ alias_method :load, :load_image
45
+
46
+ # returns a blank image of +width+ and +height+.
47
+ # Optionally accepts the :color hash param that fills the new image with a color
48
+ # (see: Devil.set_options :clear_color)
49
+ # Optionally accepts a block and yields the newly created image to the block.
50
+ def create_image(width, height, options={}, &block)
51
+ name = prepare_image
52
+ out_profile = options[:out_profile]
53
+ in_profile = options[:in_profile]
54
+
55
+ clear_color = options[:color]
56
+
57
+ # created image is formatted RGBA8
58
+ IL.TexImage(width, height, 1, 4, IL::RGBA, IL::UNSIGNED_BYTE, nil)
59
+
60
+ # apply a color profile if one is provided
61
+ IL.ApplyProfile(in_profile, out_profile) if out_profile
62
+
63
+ IL.ClearColour(*clear_color) if clear_color
64
+ IL.ClearImage
65
+ IL.ClearColour(*Devil.get_options[:clear_color]) if clear_color
66
+
67
+ check_and_run_hook(:create_image_hook)
68
+ error_check
69
+ wrap_and_yield(name, nil, block)
70
+ end
71
+
72
+ alias_method :create_blank_image, :create_image
73
+
74
+ # load multiple images and yield them to the block
75
+ # e.g Devil.with_group("hello.png", "friend.png") { |img1, img2| ... }
76
+ # all yielded images are cleaned up at end of block so you do not need to
77
+ # explictly call img1.free
78
+ def with_group(*files, &block)
79
+ images = files.map do |file|
80
+ name = prepare_image
81
+ attach_image_from_file(file)
82
+ check_and_run_hook(:load_image_hook)
83
+ error_check
84
+
85
+ Image.new(name, file)
86
+ end
87
+
88
+ if block
89
+ begin
90
+ block.call(*images)
91
+ ensure
92
+ images.each { |img| img.free if img.name }
93
+ end
94
+ else
95
+ raise RuntimeError, "a block must be provided."
96
+ end
97
+ end
98
+
99
+ alias_method :with_images, :with_group
100
+
101
+ # convert an image +blob+ with +width+ and +height+
102
+ # to a bona fide image
103
+ def from_blob(blob, width, height)
104
+
105
+ # try to convert automatically from array to packed string
106
+ # if passed an array
107
+ blob = blob.pack("C*") if blob.instance_of?(Array)
108
+
109
+ Image.new(IL.FromBlob(blob, width, height), nil)
110
+ end
111
+
112
+ # configure Devil.
113
+ # accepts hash parameters: :scale_filter, :placement, :clear_color,
114
+ # :window_size, :edge_filter.
115
+ #
116
+ # :scale_filter accepts a valid scaling algorithm: (default is LANCZOS3).
117
+ # Devil::NEAREST, Devil::LINEAR, Devil::BILINEAR, Devil::SCALE_BOX,
118
+ # Devil::SCALE_TRIANGLE, Devil::SCALE_BELL, Devil::SCALE_BSPLINE,
119
+ # Devil::SCALE_LANCZOS3, Devil::SCALE_MITCHELL
120
+ #
121
+ # :placement determines where in the canvas the image will be placed after
122
+ # the canvas has been enlarged using the 'enlarge_canvas' method.
123
+ # Valid parameters are: Devil::CENTER, Devil::LOWER_LEFT, Devil::UPPER_RIGHT, etc
124
+ #
125
+ # :clear_color sets the current clearing colour to be used by future
126
+ # calls to clear. rotate and enlarge_canvas both use these values to
127
+ # clear blank space in images, too.
128
+ # e.g Devil.set_options(:clear_color => [255, 255, 0, 255])
129
+ # Above sets the clear color to yellow with full opacity.
130
+ #
131
+ # :window_size sets the display window size Gosu will use when displaying images
132
+ # that invoked the 'show' method. (default is 1024 x 768)
133
+ # e.g Devil.set_options(:window_size => [2000, 768])
134
+ # Example above sets a window size of 2000x768.
135
+ # Note: :window_size is only relevant when require 'devil/gosu' is used.
136
+ #
137
+ # :edge_filter sets the edge detection algorithm to use when invoking
138
+ # the 'edge_detect' method. (defaults to :prewitt)
139
+ # Allowed values are :prewitt and :sobel
140
+ #
141
+ # hooks:
142
+ # :prepare_image_hook, :create_image_hook, :load_image_hook
143
+ # e.g Devil.set_options :load_image_hook => proc { IL::ConvertImage(IL::RGBA, IL::UNSIGNED_BYTE) }
144
+ def set_options(options={})
145
+ @options.merge!(options)
146
+
147
+ # update the config. options
148
+ ILU.ImageParameter(ILU::FILTER, @options[:scale_filter])
149
+ ILU.ImageParameter(ILU::PLACEMENT, @options[:placement])
150
+ IL.SetInteger(IL::JPG_QUALITY, @options[:jpg_quality])
151
+ IL.ClearColour(*@options[:clear_color])
152
+ end
153
+
154
+ # return the current Devil configuration.
155
+ def get_options
156
+ @options
157
+ end
158
+
159
+ # initializes Devil and sets defaults.
160
+ # This method should never need to be called directly.
161
+ def init
162
+ # initialize DevIL
163
+ IL.Init
164
+ ILU.Init
165
+
166
+ set_defaults
167
+ end
168
+
169
+ # restore Devil's default configuration.
170
+ def set_defaults
171
+ @options = {
172
+ :scale_filter => ILU::SCALE_LANCZOS3,
173
+ :edge_filter => :prewitt,
174
+ :window_size => [1024, 768],
175
+ :clear_color => [255, 248, 230, 0],
176
+ :placement => ILU::CENTER,
177
+ :prepare_image_hook => nil,
178
+ :load_image_hook => nil,
179
+ :create_image_hook => nil,
180
+ :jpg_quality => 99
181
+ }
182
+
183
+ # configurable options
184
+ ILU.ImageParameter(ILU::FILTER, @options[:scale_filter])
185
+ ILU.ImageParameter(ILU::PLACEMENT, @options[:placement])
186
+ IL.SetInteger(IL::JPG_QUALITY, @options[:jpg_quality])
187
+ IL.ClearColour(*@options[:clear_color])
188
+
189
+ # fixed options
190
+ IL.Enable(IL::FILE_OVERWRITE)
191
+ IL.Enable(IL::ORIGIN_SET)
192
+ IL.OriginFunc(IL::ORIGIN_LOWER_LEFT)
193
+ end
194
+
195
+ alias_method :restore_defaults, :set_defaults
196
+
197
+ private
198
+
199
+ def prepare_image
200
+ name = IL.GenImages(1).first
201
+ IL.BindImage(name)
202
+
203
+ check_and_run_hook(:prepare_image_hook)
204
+
205
+ name
206
+ end
207
+
208
+ def attach_image_from_file(file)
209
+ IL.LoadImage(file)
210
+
211
+ # ensure all images are formatted RGBA8
212
+ IL.ConvertImage(IL::RGBA, IL::UNSIGNED_BYTE)
213
+ end
214
+
215
+ def wrap_and_yield(name, file, block)
216
+ img = Image.new(name, file)
217
+ if block
218
+ begin
219
+ block.call(img)
220
+ ensure
221
+ img.free if img.name
222
+ end
223
+ else
224
+ img
225
+ end
226
+ end
227
+
228
+ def check_and_run_hook(hook_name)
229
+ Devil.get_options[hook_name].call if Devil.get_options[hook_name]
230
+ end
231
+
232
+ def error_check
233
+ if (error_code = IL.GetError) != IL::NO_ERROR
234
+ raise RuntimeError, "An error occured. #{ILU.ErrorString(error_code)}"
235
+ end
236
+ end
237
+ end
238
+ end
239
+ ## end of Devil module
240
+
241
+ # wraps a DevIL image
242
+ class Devil::Image
243
+ attr_reader :name, :file
244
+
245
+ def initialize(name, file)
246
+ @name = name
247
+ @file = file
248
+ end
249
+
250
+ # Frees the memory associated with the image.
251
+ # Must be called explictly if load_image or create_image is invoked without a block.
252
+ def free
253
+ raise "Error: calling 'free' on already freed image! #{self}" if !@name
254
+ IL.DeleteImages([@name])
255
+ error_check
256
+ @name = nil
257
+ end
258
+
259
+ alias_method :close, :free
260
+ alias_method :delete, :free
261
+
262
+ # returns the width of the image.
263
+ def width
264
+ action { IL.GetInteger(IL::IMAGE_WIDTH) }
265
+ end
266
+
267
+ alias_method :columns, :width
268
+
269
+ # returns the height of the image.
270
+ def height
271
+ action { IL.GetInteger(IL::IMAGE_HEIGHT) }
272
+ end
273
+
274
+ alias_method :rows, :height
275
+
276
+ # saves the image to +file+. If no +file+ is provided default to the opened file.
277
+ # Optional :quality hash parameter (only applies to JPEG images).
278
+ # Valid values are in the 0-99 range, with 99 being the best quality.
279
+ def save(file = @file, options = {})
280
+ quality = options[:quality]
281
+
282
+ raise "This image does not have an associated file. Please provide an explicit file name when saving." if !file
283
+
284
+ action do
285
+ IL.SetInteger(IL::JPG_QUALITY, quality) if quality
286
+ IL.SaveImage(file)
287
+ IL.SetInteger(IL::JPG_QUALITY, Devil.get_options[:jpg_quality]) if quality
288
+ end
289
+ self
290
+ end
291
+
292
+ # resize the image to +width+ and +height+. Aspect ratios of the image do not have to be the same.
293
+ # Optional :filter hash parameter that maps to a valid scale filter
294
+ # (see: Devil.set_options :scale_filter)
295
+ def resize(width, height, options = {})
296
+ filter = options[:filter]
297
+
298
+ action do
299
+ ILU.ImageParameter(ILU::FILTER, filter) if filter
300
+ ILU.Scale(width, height, 1)
301
+ ILU.ImageParameter(ILU::FILTER, Devil.get_options[:scale_filter]) if filter
302
+ end
303
+
304
+ self
305
+ end
306
+
307
+ # resize the image to +width+ and +height+. Aspect ratios of the image do not have to be the same.
308
+ # Applies a gaussian filter before rescaling to reduce aliasing.
309
+ # Optional :gauss parameter - number of times to apply gaussian filter before resizing (default is 2)
310
+ # Optional :filter hash parameter that maps to a valid scale filter
311
+ # (see: Devil.set_options :scale_filter)
312
+ def resize2(width, height, options = {})
313
+ gauss = options[:gauss] ||= 2
314
+ blur(gauss) if (width < self.width && height < self.height)
315
+ resize(width, height, options)
316
+ self
317
+ end
318
+
319
+ # Creates a proportional thumbnail of the image scaled so its longest
320
+ # edge is resized to +size+.
321
+ # Optional :filter hash parameter that maps to a valid scale filter
322
+ # (see: Devil.set_options :scale_filter)
323
+ def thumbnail(size, options = {})
324
+
325
+ # this thumbnail code from image_science.rb
326
+ w, h = width, height
327
+ scale = size.to_f / (w > h ? w : h)
328
+ resize((w * scale).to_i, (h * scale).to_i, options)
329
+ self
330
+ end
331
+
332
+ # Creates a proportional thumbnail of the image scaled so its longest
333
+ # edge is resized to +size+.
334
+ # Applies a gaussian filter before rescaling to reduce aliasing.
335
+ # Optional :gauss parameter - number of times to apply gaussian filter before resizing (default is 2)
336
+ # Optional :filter hash parameter that maps to a valid scale filter
337
+ # (see: Devil.set_options :scale_filter)
338
+ def thumbnail2(size, options = {})
339
+ gauss = options[:gauss] ||= 2
340
+ blur(gauss)
341
+ thumbnail(size, options)
342
+ self
343
+ end
344
+
345
+ # return a deep copy of the current image.
346
+ def dup
347
+ new_image_name = action { IL.CloneCurImage }
348
+ Devil::Image.new(new_image_name, nil)
349
+ end
350
+
351
+ alias_method :clone, :dup
352
+
353
+ # crop the current image.
354
+ # +xoff+ number of pixels to skip in x direction.
355
+ # +yoff+ number of pixels to skip in y direction.
356
+ # +width+ number of pixels to preserve in x direction.
357
+ # +height+ number of pixels to preserve in y direction.
358
+ def crop(xoff, yoff, width, height)
359
+ action { ILU.Crop(xoff, yoff, 1, width, height, 1) }
360
+ self
361
+ end
362
+
363
+ # enlarge the canvas of current image to +width+ and +height+.
364
+ def enlarge_canvas(width, height)
365
+ if width < self.width || height < self.height
366
+ raise "width and height parameters must be larger than current image width and height"
367
+ end
368
+
369
+ action { ILU.EnlargeCanvas(width, height, 1) }
370
+ self
371
+ end
372
+
373
+ # splice the +source+ image into current image at position +x+ and +y+.
374
+ # Takes an optional +:crop+ hash parameter that has the following format: +:crop => [sx, sy, width, height]+
375
+ # +sx+, +sy+, +width, +height+ crop the source image to be spliced.
376
+ # +sx+ is how many pixels to skip in x direction of source image.
377
+ # +sy+ is how many pixels to skip in y direction of source image.
378
+ # +width+ number of pixels to preserve in x direction of source image.
379
+ # +height+ number of pixels to preserve in y direction of source image.
380
+ # if no +:crop+ parameter is provided then the whole image is spliced in.
381
+ def blit(source, x, y, options = {})
382
+ options = {
383
+ :crop => [0, 0, source.width, source.height]
384
+ }.merge!(options)
385
+
386
+ action do
387
+ IL.Blit(source.name, x, y, 0, options[:crop][0], options[:crop][1], 0,
388
+ options[:crop][2], options[:crop][3], 1)
389
+ end
390
+
391
+ self
392
+ end
393
+
394
+ alias_method :composite, :blit
395
+
396
+ # reflect image about its y axis.
397
+ def mirror
398
+ action { ILU.Mirror }
399
+ self
400
+ end
401
+
402
+ # use prewitt or sobel filters to detect the edges in the current image.
403
+ # Optional :filter hash parameter selects filter to use (:prewitt or :sobel).
404
+ # (see: Devil.set_options :edge_filter)
405
+ def edge_detect(options={})
406
+ options = {
407
+ :filter => Devil.get_options[:edge_filter]
408
+ }.merge!(options)
409
+
410
+ case options[:filter]
411
+ when :prewitt
412
+ action { ILU.EdgeDetectP }
413
+ when :sobel
414
+ action { ILU.EdgeDetectS }
415
+ else
416
+ raise "No such edge filter #{options[:filter]}. Use :prewitt or :sobel"
417
+ end
418
+ self
419
+ end
420
+
421
+ # embosses an image, causing it to have a "relief" feel to it using a convolution filter.
422
+ def emboss
423
+ action { ILU.Emboss }
424
+ self
425
+ end
426
+
427
+ # applies a strange color distortion effect to the image giving a preternatural feel
428
+ def alienify
429
+ action { ILU.Alienify }
430
+ self
431
+ end
432
+
433
+ # performs a gaussian blur on the image. The blur is performed +iter+ times.
434
+ def blur(iter)
435
+ action { ILU.BlurGaussian(iter) }
436
+ self
437
+ end
438
+
439
+ # 'pixelize' the image using a pixel size of +pixel_size+.
440
+ def pixelize(pixel_size)
441
+ action { ILU.Pixelize(pixel_size) }
442
+ self
443
+ end
444
+
445
+ # add random noise to the image. +factor+ is the tolerance to use.
446
+ # accepeted values range from 0.0 - 1.0.
447
+ def noisify(factor)
448
+ action { ILU.Noisify(factor) }
449
+ self
450
+ end
451
+
452
+ # The sharpening +factor+ must be in the range of 0.0 - 2.5. A value of 1.0 for the sharpening.
453
+ # factor will have no effect on the image. Values in the range 1.0 - 2.5 will sharpen the
454
+ # image, with 2.5 having the most pronounced sharpening effect. Values from 0.0 to 1.0 do
455
+ # a type of reverse sharpening, blurring the image. Values outside of the 0.0 - 2.5 range
456
+ # produce undefined results.
457
+ #
458
+ # The number of +iter+ (iterations) to perform will usually be 1, but to achieve more sharpening,
459
+ # increase the number of iterations.
460
+ def sharpen(factor, iter)
461
+ action { ILU.Sharpen(factor, iter) }
462
+ self
463
+ end
464
+
465
+ # applies gamma correction to an image using an exponential curve.
466
+ # +factor+ is gamma correction factor to use.
467
+ # A value of 1.0 leaves the image unmodified.
468
+ # Values in the range 0.0 - 1.0 darken the image
469
+ # Values above 1.0 brighten the image.
470
+ def gamma_correct(factor)
471
+ action { ILU.GammaCorrect(factor) }
472
+ self
473
+ end
474
+
475
+ # invert the color of every pixel in the image.
476
+ def negate
477
+ action { ILU.Negative }
478
+ self
479
+ end
480
+
481
+ alias_method :negative, :negate
482
+
483
+ # +factor+ describes desired contrast to use
484
+ # A value of 1.0 has no effect on the image.
485
+ # Values between 1.0 and 1.7 increase the amount of contrast (values above 1.7 have no effect)
486
+ # Valid range of +factor+ is 0.0 - 1.7.
487
+ def contrast(factor)
488
+ action { ILU.Contrast(factor) }
489
+ self
490
+ end
491
+
492
+ # darkens the bright colours and lightens the dark
493
+ # colours, reducing the contrast in an image or 'equalizing' it.
494
+ def equalize
495
+ action { ILU.Equalize }
496
+ self
497
+ end
498
+
499
+ # returns the image data in the form of a ruby string.
500
+ # The image data is formatted to RGBA / UNSIGNED BYTE.
501
+ def to_blob
502
+ action { IL.ToBlob }
503
+ end
504
+
505
+ # flip the image about its x axis.
506
+ def flip
507
+ action { ILU.FlipImage }
508
+ self
509
+ end
510
+
511
+ # rotate an image about its central point by +angle+ degrees (counter clockwise).
512
+ def rotate(angle)
513
+ action { ILU.Rotate(angle) }
514
+ self
515
+ end
516
+
517
+ # simply clears the image to the 'clear color' (specified using Devil.set_options(:clear_color => [r, g, b, a])
518
+ def clear
519
+ action { IL.ClearImage }
520
+ self
521
+ end
522
+
523
+ private
524
+
525
+ def set_binding
526
+ raise "Error: trying to use image that has already been freed! #{self}" if !@name
527
+ IL.BindImage(@name)
528
+ end
529
+
530
+ def error_check
531
+ if (error_code = IL.GetError) != IL::NO_ERROR
532
+ raise RuntimeError, "An error occured. #{ILU.ErrorString(error_code)}"
533
+ end
534
+ end
535
+
536
+ def action
537
+ set_binding
538
+ result = yield
539
+ error_check
540
+
541
+ result
542
+ end
543
+ end
544
+ ## end of Devil::Image
545
+
546
+ # initialize Devil and set default config
547
+ Devil.init