image_voodoo 0.8.8 → 0.9.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/lib/image_voodoo.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Before we load image_voodoo we can specify whether we want it to load full
2
4
  # AWT ala http://www.oracle.com/technetwork/articles/javase/headless-136834.html
3
5
  # Most users are using image_voodoo as a library for manipulation and do not
@@ -5,10 +7,7 @@
5
7
  unless defined? ImageVoodoo::NEEDS_HEAD
6
8
  java.lang.System.set_property 'java.awt.headless', 'true'
7
9
  end
8
-
9
10
 
10
- ##
11
- #
12
11
  # = ImageVoodoo
13
12
  # == Description
14
13
  #
@@ -33,24 +32,22 @@ end
33
32
  #
34
33
  # img = ImageVoodoo.with_image(ARGV[0])
35
34
  # negative_img = img.negative
36
- #
37
35
  class ImageVoodoo
38
- attr_accessor :quality
36
+ attr_writer :quality # used by quality(value)
39
37
 
40
38
  include Java
41
39
 
42
40
  JFile = java.io.File
43
41
 
44
- ##
45
42
  # FIXME: This has an issue when used in test/unit where the classcastexception
46
43
  # is throwing the stack trace to output. This does not happen when used
47
44
  # directly. Not sure....
48
45
  # gae and awt define the technology-specific methods and more importantly
49
46
  # all the *_impl methods which you will see referenced in this file.
50
47
  begin
51
- require 'image_voodoo/gae'
48
+ require 'image_voodoo/gae'
52
49
  rescue
53
- require 'image_voodoo/awt'
50
+ require 'image_voodoo/awt'
54
51
  end
55
52
 
56
53
  def initialize(io, src, format=nil)
@@ -58,263 +55,217 @@ class ImageVoodoo
58
55
  @quality = nil # nil means no specific quality ever specified
59
56
  end
60
57
 
61
- ##
62
- #
58
+ # Gets RGB value within the source image at [x, y]. If using AWT backend
59
+ # then consider using color_at as this is a Java signed int value of an
60
+ # unsigned value.
61
+ def pixel(x, y)
62
+ @src.getRGB(x, y)
63
+ end
64
+
63
65
  # Adjusts the brightness of each pixel in image by the following formula:
64
66
  # new_pixel = pixel * scale + offset
65
- #
66
67
  def adjust_brightness(scale, offset)
67
68
  image = guard { adjust_brightness_impl(scale, offset) }
68
69
  block_given? ? yield(image) : image
69
70
  end
70
71
 
71
- ##
72
- #
73
72
  # Converts rgb hex color value to an alpha value an yields/returns the new
74
73
  # image.
75
- #
76
74
  def alpha(rgb)
77
75
  target = guard { alpha_impl(rgb) }
78
76
  block_given? ? yield(target) : target
79
77
  end
80
78
 
81
- ##
82
- #
83
79
  # Get current image bytes as a String using provided format. Format parameter
84
80
  # is the informal name of an image type - for instance,
85
81
  # "bmp" or "jpg". If the backend is AWT the types available are listed in
86
82
  # javax.imageio.ImageIO.getWriterFormatNames()
87
- #
88
83
  def bytes(format)
89
84
  java_bytes = guard { bytes_impl(format) }
90
85
  String.from_java_bytes java_bytes
91
86
  end
92
87
 
93
- ##
94
88
  # If current image was taken by a phone it might save the orientation
95
89
  # in format it was physically taken and added IFD0 Orientation information
96
90
  # instead of rotating it. This method will perform that rotation based
97
91
  # on Orientation metadata.
98
- #
99
92
  def correct_orientation
100
93
  target = guard { correct_orientation_impl }
101
94
  block_given? ? yield(target) : target
102
95
  end
103
96
 
104
- ##
105
- #
106
97
  # Creates a square thumbnail of the image cropping the longest edge to
107
98
  # match the shortest edge, resizes to size, and yields/returns the new image.
108
- #
109
99
  def cropped_thumbnail(size)
110
- l, t, r, b, half = 0, 0, width, height, (width - height).abs / 2
111
- l, r = half, half + height if width > height
112
- t, b = half, half + width if height > width
113
-
100
+ l, t, r, b = calculate_thumbnail_dimensions
114
101
  target = with_crop(l, t, r, b).thumbnail(size)
115
102
  block_given? ? yield(target) : target
116
103
  end
117
104
 
118
- ##
119
- #
105
+ private def calculate_thumbnail_dimensions
106
+ half = (width - height).abs / 2
107
+ if width > height
108
+ [half, 0, half + height, height]
109
+ else
110
+ [0, half, width, half + width]
111
+ end
112
+ end
113
+
120
114
  # Flips the image horizontally and yields/returns the new image.
121
- #
122
115
  def flip_horizontally
123
116
  target = guard { flip_horizontally_impl }
124
117
  block_given? ? yield(target) : target
125
118
  end
126
119
 
127
- ##
128
- #
129
120
  # Flips the image vertically and yields/returns the new image.
130
- #
131
121
  def flip_vertically
132
122
  target = guard { flip_vertically_impl }
133
123
  block_given? ? yield(target) : target
134
124
  end
135
125
 
136
- ##
137
- #
138
126
  # Creates a grayscale version of image and yields/returns the new image.
139
- #
140
127
  def greyscale
141
128
  target = guard { greyscale_impl }
142
129
  block_given? ? yield(target) : target
143
130
  end
144
- alias_method :grayscale, :greyscale
131
+ alias grayscale greyscale
145
132
 
146
- ##
147
- #
148
133
  # Extracts metadata from an image.
149
- #
150
134
  def metadata
151
135
  guard { metadata_impl }
152
136
  end
153
137
 
154
- ##
155
- #
156
138
  # Creates a negative and yields/returns the new image.
157
- #
158
139
  def negative
159
140
  target = guard { negative_impl }
160
141
  block_given? ? yield(target) : target
161
142
  end
162
143
 
163
- ##
164
- #
165
144
  # Set quality you want resulting image to be once you save or extract
166
145
  # bytes for the image. Note: This will only work for lossy image
167
146
  # formats like PNG of JPEG. For others it will be ignored.
168
147
  def quality(amount)
169
148
  if amount < 0.0 || amount > 1.0
170
- raise ArgumentError.new "Quality must be between 0.0 and 1.0"
149
+ raise ArgumentError, 'Quality must be between 0.0 and 1.0'
171
150
  end
172
151
 
173
- target = self.dup
152
+ target = dup
174
153
  target.quality = amount
175
154
  block_given? ? yield(target) : target
176
155
  end
177
156
 
178
- ##
179
- #
180
157
  # Resizes the image to width and height and yields/returns the new image.
181
- #
182
158
  def resize(width, height)
183
159
  target = guard { resize_impl(width, height) }
184
160
  block_given? ? yield(target) : target
185
- rescue NativeException => ne
186
- raise ArgumentError, ne.message
161
+ rescue java.lang.Exception => e # figure out why this is here at all?
162
+ raise ArgumentError, e.message
187
163
  end
188
164
 
189
- ##
190
- #
191
165
  # Rotates the image by angle (specified in degrees).
192
- #
193
166
  def rotate(angle)
194
- target = guard { rotate_impl(angle) }
167
+ target = guard { rotate_impl(to_radians(angle)) }
195
168
  block_given? ? yield(target) : target
196
169
  end
197
170
 
198
- ##
199
- #
200
171
  # Saves the image out to path. Changing the file extension will convert
201
172
  # the file type to the appropriate format.
202
- #
203
173
  def save(file)
204
174
  format = File.extname(file)
205
- return false if format == ""
175
+ return false if format == ''
176
+
206
177
  format = format[1..-1].downcase
207
178
  guard { save_impl(format, JFile.new(file)) }
208
179
  true
209
180
  end
210
181
 
211
- ##
212
- #
213
182
  # Resize (scale) the current image by the provided ratio and yield/return
214
183
  # the new image.
215
- #
216
184
  def scale(ratio)
217
185
  new_width, new_height = (width * ratio).to_i, (height * ratio).to_i
218
186
  target = resize(new_width, new_height)
219
187
  block_given? ? yield(target) : target
220
188
  end
221
189
 
222
- ##
223
- #
224
190
  # Creates a proportional thumbnail of the image scaled so its longest
225
191
  # edge is resized to size and yields/returns the new image.
226
- #
227
192
  def thumbnail(size)
228
193
  target = scale(size.to_f / (width > height ? width : height))
229
194
  block_given? ? yield(target) : target
230
195
  end
231
196
 
232
- ##
233
- #
234
197
  # Crops an image to left, top, right, and bottom and then yields/returns the
235
198
  # new image.
236
- #
237
199
  def with_crop(left, top, right, bottom)
238
200
  image = guard { with_crop_impl(left, top, right, bottom) }
239
201
  block_given? ? yield(image) : image
240
202
  end
241
203
 
242
- ##
243
- #
204
+ # Creates a new (empty) image with a file name specified.
205
+ def self.new_image(width, height, file_name)
206
+ image = guard { new_image_impl(width, height, file_name) }
207
+ block_given? ? yield(image) : image
208
+ end
209
+
244
210
  # A top-level image loader opens path and then yields/returns the image.
245
- #
246
211
  def self.with_image(path)
247
212
  raise ArgumentError, "file does not exist: #{path}" unless File.file?(path)
213
+
248
214
  image = guard { with_image_impl(JFile.new(path)) }
249
215
  image && block_given? ? yield(image) : image
250
216
  end
251
217
 
252
- ##
253
- #
254
218
  # A top-level image loader reads bytes and then yields/returns the image.
255
- #
256
219
  def self.with_bytes(bytes)
257
- bytes = bytes.to_java_bytes if String === bytes
220
+ bytes = bytes.to_java_bytes if bytes.is_a? String
258
221
  image = guard { with_bytes_impl(bytes) }
259
222
  block_given? ? yield(image) : image
260
223
  end
261
224
 
262
225
  class << self
263
- alias_method :with_image_from_memory, :with_bytes
226
+ alias with_image_from_memory with_bytes
264
227
  end
265
228
 
266
- ##
267
- #
268
229
  # *_impl providers only need provide the implementation if it can
269
230
  # support it. Otherwise, this method will detect that the method is
270
231
  # missing.
271
- #
272
232
  def self.guard(&block)
273
- begin
274
- return block.call
275
- rescue NoMethodError => e
276
- "Unimplemented Feature: #{e}"
277
- end
233
+ block.call
234
+ rescue NoMethodError => e
235
+ "Unimplemented Feature: #{e}"
278
236
  end
237
+
279
238
  def guard(&block)
280
239
  ImageVoodoo.guard(&block)
281
240
  end
282
241
 
283
- ##
284
- #
285
242
  # Returns the height of the image, in pixels.
286
- #
287
243
  def height
288
244
  @src.height
289
245
  end
290
246
 
291
- ##
292
- #
293
247
  # Returns the width of the image, in pixels.
294
- #
295
248
  def width
296
249
  @src.width
297
250
  end
298
251
 
299
- ##
300
- #
301
252
  # Returns the underlying Java class associated with this object. Note:
302
253
  # Depending on whether you are using AWT or GAE/J you will get a totally
303
254
  # different Java class. So caveat emptor!
304
- #
305
255
  def to_java
306
256
  @src
307
257
  end
308
258
 
309
- ##
310
- #
311
259
  # Returns detected image format from binary representation of input data
312
260
  # as upper case string. Eg. JPEG, BMP, PNG. For GWT image representation
313
261
  # compatibility method name is :format. It also accepts block and returns
314
262
  # format as first block argument. When format not detected or not set it
315
263
  # returns nil
316
- #
317
264
  def format
318
265
  @format && block_given? ? yield(@format) : @format
319
266
  end
267
+
268
+ def to_radians(degrees)
269
+ degrees * Math::PI / 180
270
+ end
320
271
  end
data/samples/bench.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/local/bin/ruby -w
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'benchmark'
4
5
  require 'rbconfig'
@@ -6,59 +7,52 @@ require 'rubygems'
6
7
  require 'image_science'
7
8
 
8
9
  max = (ARGV.shift || 100).to_i
9
- ext = ARGV.shift || "png"
10
-
10
+ ext = ARGV.shift || 'png'
11
11
  file = "blah_big.#{ext}"
12
12
 
13
- if Config::CONFIG['host_os'] =~ /darwin/ then
14
- # how fucking cool is this???
15
- puts "taking screenshot for thumbnailing benchmarks"
16
- system "screencapture -SC #{file}"
17
- else
18
- abort "You need to plonk down #{file} or buy a mac"
19
- end unless test ?f, "#{file}"
13
+ unless File.exist?(file)
14
+ if RbConfig::CONFIG['host_os'] =~ /darwin/i
15
+ puts 'taking screenshot for thumbnailing benchmarks'
16
+ system "screencapture -SC #{file}"
17
+ elsif RbConfig::CONFIG['host_os'] =~ /linux/i
18
+ puts 'taking screenshot for thumbnailing benchmarks'
19
+ system "gnome-screenshot -f #{file}"
20
+ else
21
+ abort "You need to save an image to #{file} since we cannot generate one"
22
+ end
23
+ end
20
24
 
21
- ImageScience.with_image(file.sub(/#{ext}$/, 'png')) do |img|
22
- img.save(file)
23
- end if ext != "png"
25
+ if ext != 'png'
26
+ ImageScience.with_image(file.sub(/#{ext}$/, 'png')) { |img| img.save(file) }
27
+ end
24
28
 
25
29
  puts "# of iterations = #{max}"
26
- Benchmark::bm(20) do |x|
27
- x.report("null_time") {
28
- for i in 0..max do
29
- # do nothing
30
- end
31
- }
30
+ Benchmark.bm(20) do |x|
31
+ x.report('null_time') { max.times {} }
32
32
 
33
- x.report("cropped") {
34
- for i in 0..max do
33
+ x.report('cropped') do
34
+ max.times do
35
35
  ImageScience.with_image(file) do |img|
36
- img.cropped_thumbnail(100) do |thumb|
37
- thumb.save("blah_cropped.#{ext}")
38
- end
36
+ img.cropped_thumbnail(100) { |thumb| thumb.save("blah_cropped.#{ext}") }
39
37
  end
40
38
  end
41
- }
39
+ end
42
40
 
43
- x.report("proportional") {
44
- for i in 0..max do
41
+ x.report('proportional') do
42
+ max.times do
45
43
  ImageScience.with_image(file) do |img|
46
- img.thumbnail(100) do |thumb|
47
- thumb.save("blah_thumb.#{ext}")
48
- end
44
+ img.thumbnail(100) { |thumb| thumb.save("blah_thumb.#{ext}") }
49
45
  end
50
46
  end
51
- }
47
+ end
52
48
 
53
- x.report("resize") {
54
- for i in 0..max do
49
+ x.report('resize') do
50
+ max.times do
55
51
  ImageScience.with_image(file) do |img|
56
- img.resize(200, 200) do |resize|
57
- resize.save("blah_resize.#{ext}")
58
- end
52
+ img.resize(200, 200) { |resize| resize.save("blah_resize.#{ext}") }
59
53
  end
60
54
  end
61
- }
55
+ end
62
56
  end
63
57
 
64
58
  # File.unlink(*Dir["blah*#{ext}"])
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_voodoo'
2
4
 
3
5
  # reads in the file specified by ARGV[0], transforming to greyscale and
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_science'
2
4
 
3
5
  # reads in the file specified by ARGV[0], transforms it into a 32-pixel thumbnail,
data/samples/file_view.rb CHANGED
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'image_voodoo/needs_head'
1
4
  require 'image_voodoo'
2
5
 
3
- ImageVoodoo.with_image(ARGV[0]) { |img| img.preview }
6
+ ImageVoodoo.with_image(ARGV[0], &:preview)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_voodoo'
2
4
 
3
5
  # reads in the image at ARGV[0], transforms it into a 32-pixel thumbnail in-memory,
data/samples/lossy.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_voodoo'
2
4
 
3
5
  ImageVoodoo.with_image(ARGV[0]) do |img|
@@ -1,9 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test/unit/testcase'
2
- require 'test/unit' if $0 == __FILE__
4
+ require 'test/unit' if $PROGRAM_NAME == __FILE__
3
5
  require 'image_science'
4
6
 
5
7
  class TestImageScience < Test::Unit::TestCase
6
- def deny x; assert ! x; end
8
+ def deny(x)
9
+ assert !x
10
+ end
11
+
12
+ def similar_image?(w, h, img)
13
+ assert_kind_of ImageScience, img
14
+ assert_equal h, img.height
15
+ assert_equal w, img.width
16
+ end
7
17
 
8
18
  def setup
9
19
  @path = 'test/pix.png'
@@ -17,118 +27,68 @@ class TestImageScience < Test::Unit::TestCase
17
27
 
18
28
  def test_class_with_image
19
29
  ImageScience.with_image @path do |img|
20
- assert_kind_of ImageScience, img
21
- assert_equal @h, img.height
22
- assert_equal @w, img.width
30
+ similar_image? @w, @h, img
23
31
  assert img.save(@tmppath)
24
32
  end
25
33
 
26
- assert File.exists?(@tmppath)
27
-
28
- ImageScience.with_image @tmppath do |img|
29
- assert_kind_of ImageScience, img
30
- assert_equal @h, img.height
31
- assert_equal @w, img.width
32
- end
34
+ ImageScience.with_image(@tmppath) { |img| similar_image? @w, @h, img }
33
35
  end
34
36
 
35
37
  def test_class_with_image_missing
36
38
  assert_raises ArgumentError do
37
- ImageScience.with_image @path + "nope" do |img|
38
- flunk
39
- end
39
+ ImageScience.with_image("#{@path}nope") { flunk }
40
40
  end
41
41
  end
42
42
 
43
43
  def test_class_with_image_missing_with_img_extension
44
44
  assert_raises ArgumentError do
45
- ImageScience.with_image("nope#{@path}") do |img|
46
- flunk
47
- end
45
+ ImageScience.with_image("nope#{@path}") { flunk }
48
46
  end
49
47
  end
50
48
 
51
49
  def test_class_with_image_return_nil_on_bogus_image
52
- File.open(@tmppath, "w") {|f| f << "bogus image file"}
53
- assert_nil ImageScience.with_image(@tmppath) do |img|
54
- flunk
55
- end
50
+ File.open(@tmppath, 'w') { |f| f << 'bogus image file' }
51
+ assert_nil ImageScience.with_image(@tmppath) { flunk }
56
52
  end
57
53
 
58
54
  def test_resize
59
55
  ImageScience.with_image @path do |img|
60
- img.resize(25, 25) do |thumb|
61
- assert thumb.save(@tmppath)
62
- end
56
+ img.resize(25, 25) { |thumb| assert thumb.save(@tmppath) }
63
57
  end
64
58
 
65
- assert File.exists?(@tmppath)
66
-
67
- ImageScience.with_image @tmppath do |img|
68
- assert_kind_of ImageScience, img
69
- assert_equal 25, img.height
70
- assert_equal 25, img.width
71
- end
59
+ ImageScience.with_image(@tmppath) { |img| similar_image? 25, 25, img }
72
60
  end
73
61
 
74
62
  def test_resize_floats
75
63
  ImageScience.with_image @path do |img|
76
- img.resize(25.2, 25.7) do |thumb|
77
- assert thumb.save(@tmppath)
78
- end
64
+ img.resize(25.2, 25.7) { |thumb| assert thumb.save(@tmppath) }
79
65
  end
80
66
 
81
- assert File.exists?(@tmppath)
82
-
83
- ImageScience.with_image @tmppath do |img|
84
- assert_kind_of ImageScience, img
85
- assert_equal 25, img.height
86
- assert_equal 25, img.width
87
- end
67
+ ImageScience.with_image(@tmppath) { |img| similar_image? 25, 25, img }
88
68
  end
89
69
 
90
70
  def test_resize_zero
91
- assert_raises ArgumentError do
92
- ImageScience.with_image @path do |img|
93
- img.resize(0, 25) do |thumb|
94
- assert thumb.save(@tmppath)
71
+ [[0, 25], [25, 0], [0, 0]].each do |w, h|
72
+ assert_raises ArgumentError do
73
+ ImageScience.with_image @path do |img|
74
+ img.resize(w, h) { |thumb| assert thumb.save(@tmppath) }
95
75
  end
96
76
  end
97
- end
98
77
 
99
- deny File.exists?(@tmppath)
100
-
101
- assert_raises ArgumentError do
102
- ImageScience.with_image @path do |img|
103
- img.resize(25, 0) do |thumb|
104
- assert thumb.save(@tmppath)
105
- end
106
- end
78
+ deny File.exist?(@tmppath)
107
79
  end
108
-
109
- deny File.exists?(@tmppath)
110
80
  end
111
81
 
112
82
  def test_resize_negative
113
- assert_raises ArgumentError do
114
- ImageScience.with_image @path do |img|
115
- img.resize(-25, 25) do |thumb|
116
- assert thumb.save(@tmppath)
83
+ [[-25, 25], [25, -25], [-25, -25]].each do |w, h|
84
+ assert_raises ArgumentError do
85
+ ImageScience.with_image @path do |img|
86
+ img.resize(w, h) { |thumb| assert thumb.save(@tmppath) }
117
87
  end
118
88
  end
119
- end
120
89
 
121
- deny File.exists?(@tmppath)
122
-
123
- assert_raises ArgumentError do
124
- ImageScience.with_image @path do |img|
125
- img.resize(25, -25) do |thumb|
126
- assert thumb.save(@tmppath)
127
- end
128
- end
90
+ deny File.exist?(@tmppath)
129
91
  end
130
-
131
- deny File.exists?(@tmppath)
132
92
  end
133
93
 
134
94
  def test_image_format_retrieval
@@ -147,7 +107,7 @@ class TestImageScience < Test::Unit::TestCase
147
107
  end
148
108
 
149
109
  def test_image_format_retrieval_fail_when_invalid_bytes
150
- image = ImageScience.with_bytes("some invalid image bytes")
110
+ image = ImageScience.with_bytes('some invalid image bytes')
151
111
  assert_equal nil, image.format
152
112
  end
153
113
 
@@ -166,6 +126,6 @@ class TestImageScience < Test::Unit::TestCase
166
126
  assert new_img.save(@tmppath)
167
127
  end
168
128
 
169
- assert File.exists?(@tmppath)
129
+ assert File.exist?(@tmppath)
170
130
  end
171
131
  end