image_voodoo 0.8.7 → 0.9.1

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,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ImageVoodoo; NEEDS_HEAD = true; end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageVoodoo
2
- VERSION = "0.8.7"
4
+ VERSION = '0.9.1'
3
5
  end
data/lib/image_voodoo.rb CHANGED
@@ -1,5 +1,13 @@
1
- ##
2
- #
1
+ # frozen_string_literal: true
2
+
3
+ # Before we load image_voodoo we can specify whether we want it to load full
4
+ # AWT ala http://www.oracle.com/technetwork/articles/javase/headless-136834.html
5
+ # Most users are using image_voodoo as a library for manipulation and do not
6
+ # want a full peer window popping up.
7
+ unless defined? ImageVoodoo::NEEDS_HEAD
8
+ java.lang.System.set_property 'java.awt.headless', 'true'
9
+ end
10
+
3
11
  # = ImageVoodoo
4
12
  # == Description
5
13
  #
@@ -24,261 +32,240 @@
24
32
  #
25
33
  # img = ImageVoodoo.with_image(ARGV[0])
26
34
  # negative_img = img.negative
27
- #
28
35
  class ImageVoodoo
29
- attr_accessor :quality
36
+ attr_writer :quality # used by quality(value)
30
37
 
31
38
  include Java
32
39
 
33
40
  JFile = java.io.File
34
41
 
35
- ##
36
42
  # FIXME: This has an issue when used in test/unit where the classcastexception
37
43
  # is throwing the stack trace to output. This does not happen when used
38
44
  # directly. Not sure....
39
45
  # gae and awt define the technology-specific methods and more importantly
40
46
  # all the *_impl methods which you will see referenced in this file.
41
47
  begin
42
- require 'image_voodoo/gae'
48
+ require 'image_voodoo/gae'
43
49
  rescue
44
- require 'image_voodoo/awt'
50
+ require 'image_voodoo/awt'
45
51
  end
46
52
 
47
- def initialize(src, format=nil)
48
- @src = src
49
- @format = format
53
+ def initialize(io, src, format=nil)
54
+ @io, @src, @format = io, src, format
50
55
  @quality = nil # nil means no specific quality ever specified
51
56
  end
52
57
 
53
- ##
54
- #
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
+
55
65
  # Adjusts the brightness of each pixel in image by the following formula:
56
66
  # new_pixel = pixel * scale + offset
57
- #
58
67
  def adjust_brightness(scale, offset)
59
68
  image = guard { adjust_brightness_impl(scale, offset) }
60
69
  block_given? ? yield(image) : image
61
70
  end
62
71
 
63
- ##
64
- #
65
72
  # Converts rgb hex color value to an alpha value an yields/returns the new
66
73
  # image.
67
- #
68
74
  def alpha(rgb)
69
75
  target = guard { alpha_impl(rgb) }
70
76
  block_given? ? yield(target) : target
71
77
  end
72
78
 
73
- ##
74
- #
75
79
  # Get current image bytes as a String using provided format. Format parameter
76
80
  # is the informal name of an image type - for instance,
77
81
  # "bmp" or "jpg". If the backend is AWT the types available are listed in
78
82
  # javax.imageio.ImageIO.getWriterFormatNames()
79
- #
80
83
  def bytes(format)
81
84
  java_bytes = guard { bytes_impl(format) }
82
85
  String.from_java_bytes java_bytes
83
86
  end
84
87
 
85
- ##
86
- #
88
+ # If current image was taken by a phone it might save the orientation
89
+ # in format it was physically taken and added IFD0 Orientation information
90
+ # instead of rotating it. This method will perform that rotation based
91
+ # on Orientation metadata.
92
+ def correct_orientation
93
+ target = guard { correct_orientation_impl }
94
+ block_given? ? yield(target) : target
95
+ end
96
+
87
97
  # Creates a square thumbnail of the image cropping the longest edge to
88
98
  # match the shortest edge, resizes to size, and yields/returns the new image.
89
- #
90
99
  def cropped_thumbnail(size)
91
- l, t, r, b, half = 0, 0, width, height, (width - height).abs / 2
92
- l, r = half, half + height if width > height
93
- t, b = half, half + width if height > width
94
-
100
+ l, t, r, b = calculate_thumbnail_dimensions
95
101
  target = with_crop(l, t, r, b).thumbnail(size)
96
102
  block_given? ? yield(target) : target
97
103
  end
98
104
 
99
- ##
100
- #
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
+
101
114
  # Flips the image horizontally and yields/returns the new image.
102
- #
103
115
  def flip_horizontally
104
116
  target = guard { flip_horizontally_impl }
105
117
  block_given? ? yield(target) : target
106
118
  end
107
119
 
108
- ##
109
- #
110
120
  # Flips the image vertically and yields/returns the new image.
111
- #
112
121
  def flip_vertically
113
122
  target = guard { flip_vertically_impl }
114
123
  block_given? ? yield(target) : target
115
124
  end
116
125
 
117
- ##
118
- #
119
126
  # Creates a grayscale version of image and yields/returns the new image.
120
- #
121
127
  def greyscale
122
128
  target = guard { greyscale_impl }
123
129
  block_given? ? yield(target) : target
124
130
  end
125
- alias_method :grayscale, :greyscale
131
+ alias grayscale greyscale
132
+
133
+ # Extracts metadata from an image.
134
+ def metadata
135
+ guard { metadata_impl }
136
+ end
126
137
 
127
- ##
128
- #
129
138
  # Creates a negative and yields/returns the new image.
130
- #
131
139
  def negative
132
140
  target = guard { negative_impl }
133
141
  block_given? ? yield(target) : target
134
142
  end
135
143
 
136
- ##
137
- #
138
144
  # Set quality you want resulting image to be once you save or extract
139
145
  # bytes for the image. Note: This will only work for lossy image
140
146
  # formats like PNG of JPEG. For others it will be ignored.
141
147
  def quality(amount)
142
148
  if amount < 0.0 || amount > 1.0
143
- raise ArgumentError.new "Quality must be between 0.0 and 1.0"
149
+ raise ArgumentError, 'Quality must be between 0.0 and 1.0'
144
150
  end
145
151
 
146
- target = self.dup
152
+ target = dup
147
153
  target.quality = amount
148
154
  block_given? ? yield(target) : target
149
155
  end
150
156
 
151
- ##
152
- #
153
157
  # Resizes the image to width and height and yields/returns the new image.
154
- #
155
158
  def resize(width, height)
156
159
  target = guard { resize_impl(width, height) }
157
160
  block_given? ? yield(target) : target
158
- rescue NativeException => ne
159
- raise ArgumentError, ne.message
161
+ rescue java.lang.Exception => e # figure out why this is here at all?
162
+ raise ArgumentError, e.message
163
+ end
164
+
165
+ # Rotates the image by angle (specified in degrees).
166
+ def rotate(angle)
167
+ target = guard { rotate_impl(to_radians(angle)) }
168
+ block_given? ? yield(target) : target
160
169
  end
161
170
 
162
- ##
163
- #
164
171
  # Saves the image out to path. Changing the file extension will convert
165
172
  # the file type to the appropriate format.
166
- #
167
173
  def save(file)
168
174
  format = File.extname(file)
169
- return false if format == ""
175
+ return false if format == ''
176
+
170
177
  format = format[1..-1].downcase
171
178
  guard { save_impl(format, JFile.new(file)) }
172
179
  true
173
180
  end
174
181
 
175
- ##
176
- #
177
182
  # Resize (scale) the current image by the provided ratio and yield/return
178
183
  # the new image.
179
- #
180
184
  def scale(ratio)
181
185
  new_width, new_height = (width * ratio).to_i, (height * ratio).to_i
182
186
  target = resize(new_width, new_height)
183
187
  block_given? ? yield(target) : target
184
188
  end
185
189
 
186
- ##
187
- #
188
190
  # Creates a proportional thumbnail of the image scaled so its longest
189
191
  # edge is resized to size and yields/returns the new image.
190
- #
191
192
  def thumbnail(size)
192
193
  target = scale(size.to_f / (width > height ? width : height))
193
194
  block_given? ? yield(target) : target
194
195
  end
195
196
 
196
- ##
197
- #
198
197
  # Crops an image to left, top, right, and bottom and then yields/returns the
199
198
  # new image.
200
- #
201
199
  def with_crop(left, top, right, bottom)
202
200
  image = guard { with_crop_impl(left, top, right, bottom) }
203
201
  block_given? ? yield(image) : image
204
202
  end
205
203
 
206
- ##
207
- #
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
+
208
210
  # A top-level image loader opens path and then yields/returns the image.
209
- #
210
211
  def self.with_image(path)
211
- raise ArgumentError, "file does not exist" unless File.file?(path)
212
+ raise ArgumentError, "file does not exist: #{path}" unless File.file?(path)
213
+
212
214
  image = guard { with_image_impl(JFile.new(path)) }
213
215
  image && block_given? ? yield(image) : image
214
216
  end
215
217
 
216
- ##
217
- #
218
218
  # A top-level image loader reads bytes and then yields/returns the image.
219
- #
220
219
  def self.with_bytes(bytes)
221
- bytes = bytes.to_java_bytes if String === bytes
220
+ bytes = bytes.to_java_bytes if bytes.is_a? String
222
221
  image = guard { with_bytes_impl(bytes) }
223
222
  block_given? ? yield(image) : image
224
223
  end
225
224
 
226
225
  class << self
227
- alias_method :with_image_from_memory, :with_bytes
226
+ alias with_image_from_memory with_bytes
228
227
  end
229
228
 
230
- ##
231
- #
232
229
  # *_impl providers only need provide the implementation if it can
233
230
  # support it. Otherwise, this method will detect that the method is
234
231
  # missing.
235
- #
236
232
  def self.guard(&block)
237
- begin
238
- return block.call
239
- rescue NoMethodError => e
240
- "Unimplemented Feature: #{e}"
241
- end
233
+ block.call
234
+ rescue NoMethodError => e
235
+ "Unimplemented Feature: #{e}"
242
236
  end
237
+
243
238
  def guard(&block)
244
239
  ImageVoodoo.guard(&block)
245
240
  end
246
241
 
247
- ##
248
- #
249
242
  # Returns the height of the image, in pixels.
250
- #
251
243
  def height
252
244
  @src.height
253
245
  end
254
246
 
255
- ##
256
- #
257
247
  # Returns the width of the image, in pixels.
258
- #
259
248
  def width
260
249
  @src.width
261
250
  end
262
251
 
263
- ##
264
- #
265
252
  # Returns the underlying Java class associated with this object. Note:
266
253
  # Depending on whether you are using AWT or GAE/J you will get a totally
267
254
  # different Java class. So caveat emptor!
268
- #
269
255
  def to_java
270
256
  @src
271
257
  end
272
258
 
273
- ##
274
- #
275
259
  # Returns detected image format from binary representation of input data
276
260
  # as upper case string. Eg. JPEG, BMP, PNG. For GWT image representation
277
261
  # compatibility method name is :format. It also accepts block and returns
278
262
  # format as first block argument. When format not detected or not set it
279
263
  # returns nil
280
- #
281
264
  def format
282
265
  @format && block_given? ? yield(@format) : @format
283
266
  end
267
+
268
+ def to_radians(degrees)
269
+ degrees * Math::PI / 180
270
+ end
284
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