devil 0.1.9.1 → 0.1.9.5

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.
@@ -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