image_voodoo 0.8.8 → 0.8.9
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +115 -0
- data/.travis.yml +7 -0
- data/Rakefile +2 -2
- data/bin/image_voodoo +71 -84
- data/image_voodoo.gemspec +6 -5
- data/lib/image_science.rb +1 -1
- data/lib/image_voodoo.rb +41 -93
- data/lib/image_voodoo/awt.rb +156 -153
- data/lib/image_voodoo/awt/core_ext/buffered_image.rb +13 -0
- data/lib/image_voodoo/awt/core_ext/graphics2d.rb +17 -0
- data/lib/image_voodoo/awt/shapes.rb +39 -3
- data/lib/image_voodoo/gae.rb +10 -6
- data/lib/image_voodoo/metadata.rb +115 -89
- data/lib/image_voodoo/needs_head.rb +1 -0
- data/lib/image_voodoo/version.rb +1 -1
- data/samples/bench.rb +29 -36
- data/samples/file_view.rb +2 -1
- data/samples/{in-memory.rb → in_memory.rb} +0 -0
- data/test/test_image_science.rb +32 -74
- data/test/test_image_voodoo.rb +82 -0
- data/test/test_metadata.rb +23 -16
- data/test/test_shapes.rb +21 -0
- data/tools/gen.rb +31 -27
- metadata +15 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 525eaa36345ce66a917c101be73299b04e2800cb
|
4
|
+
data.tar.gz: e851fe90c983c411388d7b1e37ef0c2e323f677b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02453c98b8e64606ecb01834cebd6d04ba96212377308ca3f4a36f2ab91ae79db59dee0ca1b8056d912b3e66eea71da20d120fb81935d98f6d3a1550e3e57b17
|
7
|
+
data.tar.gz: 204639d91890c6525a6bae30fd1db9fd61f999187576e5213ac064bc1b8b29e57d3b7412b4ae691e6c1caa9922bdf0659f5535c49a0ebfed9344af5721bdb4db
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Experiment to tweak source based on what rubocop wanted me to. I basically
|
2
|
+
# gave up and made the following exceptions. Personally, I find the tool did
|
3
|
+
# find many interesting/questionable code Qs in the project but it is soooo
|
4
|
+
# opinionated in pretty unimportant ways (like who cares how I write my
|
5
|
+
# comments, or why can't I use kind_of? or why give me a fail for a less
|
6
|
+
# readable but more performant pattern).
|
7
|
+
#
|
8
|
+
# I can obviously tweak rules (like below) but I keep hearing of people pulling
|
9
|
+
# their hair out over all the unimportant rules; I question why this project is
|
10
|
+
# "all-in" on it's rules versus leaving most of the style rules as some preset
|
11
|
+
# profiles people can opt in to.
|
12
|
+
#
|
13
|
+
# So I am committing this and I will try and run this every now and then to
|
14
|
+
# see what it thinks about what I wrote but you will not see this as a
|
15
|
+
# requirement for commits since I think by the time I configured it to my
|
16
|
+
# liking I could have finished implementing this library. Perhaps if I
|
17
|
+
# continue using this here and there I will end up with a profile I find
|
18
|
+
# acceptable? I did find value but all the piddly shit just made me angry.
|
19
|
+
# I will reflect on it some more...
|
20
|
+
|
21
|
+
# This is a Java AWT call. It is not what the tool thinks it is...which
|
22
|
+
# might be an issue for users interacting with Java from JRuby.
|
23
|
+
Lint/UselessSetterCall:
|
24
|
+
Exclude:
|
25
|
+
- 'lib/image_voodoo/awt.rb'
|
26
|
+
|
27
|
+
# This is just impossible for them to call properly. I am doing something
|
28
|
+
# like rotate_impl and it is a bunch of reasonably simple math. If you look
|
29
|
+
# for this transformation in a text book it will look like this code. Putting
|
30
|
+
# a bunch of math within different methods would get the score down but
|
31
|
+
# it would not help readability. (addendum: I did try and break it apart more
|
32
|
+
# and never reached 15 but got closer. I felt this rule is neat but the
|
33
|
+
# score really depends on what it is.
|
34
|
+
Metrics/AbcSize:
|
35
|
+
Max: 16.5
|
36
|
+
|
37
|
+
# This is triggered for correct_orientation_impl. I could put these transforms
|
38
|
+
# in a table and then execute lambdas for each orientation but this is much
|
39
|
+
# more clear.
|
40
|
+
Metrics/CyclomaticComplexity:
|
41
|
+
Max: 9
|
42
|
+
|
43
|
+
# A bunch of fields in a hash are basically generated data. Correcting them
|
44
|
+
# for column seems much too pedantic. I guess exclude is the right thing for
|
45
|
+
# an unusual file?
|
46
|
+
Metrics/LineLength:
|
47
|
+
Max: 132
|
48
|
+
Exclude:
|
49
|
+
- 'lib/image_voodoo/metadata.rb'
|
50
|
+
|
51
|
+
# Metadata classes have data in them. awt.rb is big and perhaps could be
|
52
|
+
# shrunk but it is not a hard file to navigate and the cleaving points are
|
53
|
+
# not super obvious. These sorts of rules feel very arbitrary and if I have
|
54
|
+
# n highly related things and they do not fit into a more restrictive
|
55
|
+
# taxonomy why would the file being longer matter? I do understand the
|
56
|
+
# motivation here but as a default rule this feels wrong to me.
|
57
|
+
Metrics/ClassLength:
|
58
|
+
Max: 250
|
59
|
+
|
60
|
+
# I do not find this very useful. There is no performance difference and
|
61
|
+
# sometimes I want to highlight this string does not involve interpolation.
|
62
|
+
# Other times it is not worth pointing out.
|
63
|
+
Style/StringLiterals:
|
64
|
+
Enabled: false
|
65
|
+
|
66
|
+
# require 'english' is a step too far for $! which is so baked into my
|
67
|
+
# head I do not want to change :)
|
68
|
+
Style/SpecialGlobalVars:
|
69
|
+
Enabled: false
|
70
|
+
|
71
|
+
# I am grouping math and using lack of whitespace for separation.
|
72
|
+
Style/SpaceAroundOperators:
|
73
|
+
Enabled: false
|
74
|
+
|
75
|
+
# I prefer tight assignment for opt args.
|
76
|
+
Style/SpaceAroundEqualsInParameterDefault:
|
77
|
+
Enabled: false
|
78
|
+
|
79
|
+
# Java methods which override or implement Java method names cannot be switched
|
80
|
+
# to snake case. Do we really need this as a rule anyways? I have never seen
|
81
|
+
# a Rubyist do this as a preferred style?
|
82
|
+
Style/MethodName:
|
83
|
+
Enabled: false
|
84
|
+
|
85
|
+
# bin/image_voodoo main options block.
|
86
|
+
Metrics/BlockLength:
|
87
|
+
Exclude:
|
88
|
+
- 'bin/image_voodoo'
|
89
|
+
|
90
|
+
# casecmp for this case seems like it is much less readable in a place where
|
91
|
+
# performance could never matter (a 3-4 char downcase before processing an
|
92
|
+
# image :) ). This could end up being important somewhere but as a default
|
93
|
+
# on it feels weird since I find it less readable.
|
94
|
+
Performance/Casecmp:
|
95
|
+
Exclude:
|
96
|
+
- 'lib/image_voodoo/awt.rb'
|
97
|
+
|
98
|
+
# Hash rocket looks much more natural in a rakefile for its deps.
|
99
|
+
Style/HashSyntax:
|
100
|
+
Exclude:
|
101
|
+
- 'Rakefile'
|
102
|
+
|
103
|
+
# Forget it. I do parallel assignment and you will have to peel it out of
|
104
|
+
# my cold dead hands.
|
105
|
+
Style/ParallelAssignment:
|
106
|
+
Enabled: false
|
107
|
+
|
108
|
+
# FIXME: consider keywords for shapes.
|
109
|
+
# I might switch these to keyword args if I ever revisit shapes support.
|
110
|
+
# In general lots of params do suck and are hard to remember. This library
|
111
|
+
# still is supposed to work in 1.8 but I can probably soon major rev this
|
112
|
+
# and switch over to keywords.
|
113
|
+
Metrics/ParameterLists:
|
114
|
+
Exclude:
|
115
|
+
- 'lib/image_voodoo/awt/shapes.rb'
|
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
data/bin/image_voodoo
CHANGED
@@ -7,101 +7,91 @@ actions = []
|
|
7
7
|
images = []
|
8
8
|
original_image = nil
|
9
9
|
|
10
|
-
opts = OptionParser.new do |
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
opts.on("-b", "--brightness SCALE,OFFSET", "Adjust brightness") do |args|
|
39
|
-
scale, offset = args.split(/,/).map {|v| v.to_f}
|
40
|
-
opts.usage "You need to specify proper scale and offset" unless scale && offset
|
41
|
-
actions << lambda {|img| img.adjust_brightness(scale, offset) }
|
42
|
-
end
|
43
|
-
|
44
|
-
opts.on("-B", "--border WIDTH,COLOR,STYLE", "Add a simple border") do |args|
|
10
|
+
opts = OptionParser.new do |o|
|
11
|
+
o.banner = 'Usage: image_voodoo [actions] image_file'
|
12
|
+
o.separator 'Perform actions/transformations on an image.'
|
13
|
+
o.separator ''
|
14
|
+
o.separator 'Examples:'
|
15
|
+
o.separator ' image_voodoo --dim small.jpg # Print the dimensions'
|
16
|
+
o.separator ''
|
17
|
+
o.separator ' # Make a thumbnail, preview it, and then save it.'
|
18
|
+
o.separator ' image_voodoo --thumbnail 50 --preview --save thumb.png large.jpg'
|
19
|
+
o.separator ''
|
20
|
+
o.separator ' # Make 2 thumbnails, showing dimensions and previewing them'
|
21
|
+
o.separator ' image_voodoo --dim --resize 50x50 --dim --preview --save t1.jpg'
|
22
|
+
o.separator ' --pop --resize 40x40 --dim --preview --save t2.jpg image.jpg'
|
23
|
+
o.separator ''
|
24
|
+
o.separator 'Actions:'
|
25
|
+
|
26
|
+
o.on('-a', '--alpha rrggbb', 'Make color transparent in image') do |c|
|
27
|
+
o.usage 'rrggbb is in hexidecimal format' if c !~ /[[:xdigit:]]{6,6}/
|
28
|
+
actions << ->(img) { img.alpha(c) }
|
29
|
+
end
|
30
|
+
|
31
|
+
o.on('-b', '--brightness SCALE,OFFSET', 'Adjust brightness') do |args|
|
32
|
+
scale, offset = args.split(/,/).map(&:to_f)
|
33
|
+
o.usage 'You need to specify proper scale and offset' unless scale && offset
|
34
|
+
actions << ->(img) { img.adjust_brightness(scale, offset) }
|
35
|
+
end
|
36
|
+
|
37
|
+
o.on('-B', '--border WIDTH,COLOR,STYLE', 'Add a simple border') do |args|
|
45
38
|
width, color, style = args.split(/,/)
|
46
|
-
options = {:
|
47
|
-
|
48
|
-
actions << lambda {|img| img.add_border(options) }
|
39
|
+
options = { width: width, color: color, style: style }
|
40
|
+
actions << ->(img) { img.add_border(options) }
|
49
41
|
end
|
50
42
|
|
51
|
-
|
52
|
-
actions <<
|
43
|
+
o.on('-d', '--dimensions', 'Print the image dimensions') do
|
44
|
+
actions << ->(img) { img.tap { puts "#{img.width}x#{img.height}" } }
|
53
45
|
end
|
54
46
|
|
55
|
-
|
56
|
-
actions <<
|
47
|
+
o.on('-g', '--greyscale', 'Convert image to greyscale') do
|
48
|
+
actions << ->(img) { img.greyscale }
|
57
49
|
end
|
58
50
|
|
59
|
-
|
60
|
-
actions <<
|
51
|
+
o.on('-h', '--flip_horizontally') do
|
52
|
+
actions << ->(img) { img.flip_horizontally }
|
61
53
|
end
|
62
54
|
|
63
|
-
|
64
|
-
actions <<
|
55
|
+
o.on('-m', '--metadata') do
|
56
|
+
actions << ->(img) { img.tap { puts img.metadata } }
|
65
57
|
end
|
66
58
|
|
67
|
-
|
68
|
-
actions <<
|
59
|
+
o.on('-n', '--negative', 'Make a negative out of the image') do
|
60
|
+
actions << ->(img) { img.negative }
|
69
61
|
end
|
70
62
|
|
71
|
-
|
72
|
-
actions <<
|
63
|
+
o.on('-o', '--orient', 'Rotate image to orient it based on metadata') do
|
64
|
+
actions << ->(img) { img.correct_orientation }
|
73
65
|
end
|
74
66
|
|
75
|
-
|
76
|
-
actions <<
|
67
|
+
o.on('-q', '--quality 0..1', Float, 'Set % of quality for lossy compression') do |quality|
|
68
|
+
actions << ->(img) { img.quality(quality) }
|
77
69
|
end
|
78
70
|
|
79
|
-
|
80
|
-
actions <<
|
71
|
+
o.on('-R', '--rotate 0..360', Float, 'Set angle to rotate image') do |angle|
|
72
|
+
actions << ->(img) { img.rotate(angle.to_f) }
|
81
73
|
end
|
82
74
|
|
83
|
-
|
84
|
-
width, height = dim.split(/x/i).map
|
85
|
-
|
86
|
-
actions <<
|
75
|
+
o.on('-r', '--resize WIDTHxHEIGHT', 'Make a new resized image') do |dim|
|
76
|
+
width, height = dim.split(/x/i).map(&:to_i)
|
77
|
+
o.usage 'You need to specify proper dimensions' unless width && width > 0 && height && height > 0
|
78
|
+
actions << ->(img) { img.resize(width, height) }
|
87
79
|
end
|
88
80
|
|
89
|
-
|
90
|
-
|
91
|
-
actions << lambda {|img| img.save(f); img }
|
81
|
+
o.on('-s', '--save FILENAME', 'Save the results to a new file') do |f|
|
82
|
+
actions << ->(img) { img.tap { img.save(f) } }
|
92
83
|
end
|
93
84
|
|
94
|
-
|
95
|
-
actions <<
|
85
|
+
o.on('-t', '--thumbnail SIZE', Integer, 'Create a thumbnail of the given size') do |size|
|
86
|
+
actions << ->(img) { img.thumbnail(size) }
|
96
87
|
end
|
97
88
|
|
98
|
-
|
99
|
-
actions << lambda {|img| img.flip_vertically }
|
100
|
-
end
|
101
|
-
|
102
|
-
opts.on("-p", "--preview", "Preview the image. Close the frame window",
|
103
|
-
"to continue, or quit the application to", "abort the action pipeline") do
|
89
|
+
o.on('-v', '--flip_vertically') { actions << ->(img) { img.flip_vertically } }
|
104
90
|
|
91
|
+
o.on('-p', '--preview',
|
92
|
+
'Preview the image. Close the frame window',
|
93
|
+
'to continue, or quit the application to',
|
94
|
+
'abort the action pipeline') do
|
105
95
|
headless = false
|
106
96
|
actions << lambda do |img|
|
107
97
|
done = false
|
@@ -111,36 +101,33 @@ opts = OptionParser.new do |opts|
|
|
111
101
|
end
|
112
102
|
end
|
113
103
|
|
114
|
-
|
115
|
-
actions <<
|
104
|
+
o.on('--push', 'Save the current image to be popped later') do
|
105
|
+
actions << ->(img) { img.tap { images << img } }
|
116
106
|
end
|
117
107
|
|
118
|
-
|
119
|
-
actions <<
|
108
|
+
o.on('--pop', 'Revert back to the previous image') do
|
109
|
+
actions << ->() { images.pop || original_image }
|
120
110
|
end
|
121
111
|
|
122
|
-
|
123
|
-
actions <<
|
112
|
+
o.on('-f', '--format', 'Print the image format') do
|
113
|
+
actions << ->(img) { img.tap { img.format } }
|
124
114
|
end
|
125
115
|
|
126
|
-
|
127
|
-
puts opts
|
128
|
-
exit 0
|
129
|
-
end
|
116
|
+
o.on_tail('-h', '--help', 'Show this message') { o.usage }
|
130
117
|
|
131
|
-
def
|
132
|
-
puts msg
|
118
|
+
def o.usage(msg=nil)
|
119
|
+
puts msg if msg
|
133
120
|
puts self
|
134
121
|
exit 1
|
135
122
|
end
|
136
123
|
end
|
137
124
|
opts.parse!(ARGV)
|
138
|
-
opts.usage(
|
139
|
-
opts.usage(
|
125
|
+
opts.usage('You need to supply a source image filename.') unless ARGV.first
|
126
|
+
opts.usage('You need to supply one or more actions.') if actions.empty?
|
140
127
|
|
141
128
|
# For this binstub we only want to load non-headless if we are using
|
142
129
|
# the preview feature. top of See lib/image_voodoo.rb for more info...
|
143
|
-
|
130
|
+
require 'image_voodoo/needs_head' unless headless
|
144
131
|
|
145
132
|
require 'image_voodoo'
|
146
133
|
file_name = ARGV.first
|
data/image_voodoo.gemspec
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
4
|
+
require 'image_voodoo/version'
|
4
5
|
|
5
6
|
Gem::Specification.new do |s|
|
6
7
|
s.name = 'image_voodoo'
|
@@ -12,11 +13,11 @@ Gem::Specification.new do |s|
|
|
12
13
|
s.summary = 'Image manipulation in JRuby with ImageScience compatible API'
|
13
14
|
s.description = 'Image manipulation in JRuby with ImageScience compatible API'
|
14
15
|
|
15
|
-
s.rubyforge_project =
|
16
|
+
s.rubyforge_project = 'image_voodoo'
|
16
17
|
|
17
18
|
s.files = `git ls-files`.split("\n")
|
18
19
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
-
s.require_paths = [
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
21
|
+
s.require_paths = %w[lib vendor]
|
21
22
|
s.has_rdoc = true
|
22
23
|
end
|
data/lib/image_science.rb
CHANGED
data/lib/image_voodoo.rb
CHANGED
@@ -5,10 +5,7 @@
|
|
5
5
|
unless defined? ImageVoodoo::NEEDS_HEAD
|
6
6
|
java.lang.System.set_property 'java.awt.headless', 'true'
|
7
7
|
end
|
8
|
-
|
9
8
|
|
10
|
-
##
|
11
|
-
#
|
12
9
|
# = ImageVoodoo
|
13
10
|
# == Description
|
14
11
|
#
|
@@ -33,7 +30,6 @@ end
|
|
33
30
|
#
|
34
31
|
# img = ImageVoodoo.with_image(ARGV[0])
|
35
32
|
# negative_img = img.negative
|
36
|
-
#
|
37
33
|
class ImageVoodoo
|
38
34
|
attr_accessor :quality
|
39
35
|
|
@@ -41,16 +37,15 @@ class ImageVoodoo
|
|
41
37
|
|
42
38
|
JFile = java.io.File
|
43
39
|
|
44
|
-
##
|
45
40
|
# FIXME: This has an issue when used in test/unit where the classcastexception
|
46
41
|
# is throwing the stack trace to output. This does not happen when used
|
47
42
|
# directly. Not sure....
|
48
43
|
# gae and awt define the technology-specific methods and more importantly
|
49
44
|
# all the *_impl methods which you will see referenced in this file.
|
50
45
|
begin
|
51
|
-
|
46
|
+
require 'image_voodoo/gae'
|
52
47
|
rescue
|
53
|
-
|
48
|
+
require 'image_voodoo/awt'
|
54
49
|
end
|
55
50
|
|
56
51
|
def initialize(io, src, format=nil)
|
@@ -58,127 +53,107 @@ class ImageVoodoo
|
|
58
53
|
@quality = nil # nil means no specific quality ever specified
|
59
54
|
end
|
60
55
|
|
61
|
-
|
62
|
-
#
|
56
|
+
# Gets RGB value within the source image at [x, y]. If using AWT backend
|
57
|
+
# then consider using color_at as this is a Java signed int value of an
|
58
|
+
# unsigned value.
|
59
|
+
def pixel(x, y)
|
60
|
+
@src.getRGB(x, y)
|
61
|
+
end
|
62
|
+
|
63
63
|
# Adjusts the brightness of each pixel in image by the following formula:
|
64
64
|
# new_pixel = pixel * scale + offset
|
65
|
-
#
|
66
65
|
def adjust_brightness(scale, offset)
|
67
66
|
image = guard { adjust_brightness_impl(scale, offset) }
|
68
67
|
block_given? ? yield(image) : image
|
69
68
|
end
|
70
69
|
|
71
|
-
##
|
72
|
-
#
|
73
70
|
# Converts rgb hex color value to an alpha value an yields/returns the new
|
74
71
|
# image.
|
75
|
-
#
|
76
72
|
def alpha(rgb)
|
77
73
|
target = guard { alpha_impl(rgb) }
|
78
74
|
block_given? ? yield(target) : target
|
79
75
|
end
|
80
76
|
|
81
|
-
##
|
82
|
-
#
|
83
77
|
# Get current image bytes as a String using provided format. Format parameter
|
84
78
|
# is the informal name of an image type - for instance,
|
85
79
|
# "bmp" or "jpg". If the backend is AWT the types available are listed in
|
86
80
|
# javax.imageio.ImageIO.getWriterFormatNames()
|
87
|
-
#
|
88
81
|
def bytes(format)
|
89
82
|
java_bytes = guard { bytes_impl(format) }
|
90
83
|
String.from_java_bytes java_bytes
|
91
84
|
end
|
92
85
|
|
93
|
-
##
|
94
86
|
# If current image was taken by a phone it might save the orientation
|
95
87
|
# in format it was physically taken and added IFD0 Orientation information
|
96
88
|
# instead of rotating it. This method will perform that rotation based
|
97
89
|
# on Orientation metadata.
|
98
|
-
#
|
99
90
|
def correct_orientation
|
100
91
|
target = guard { correct_orientation_impl }
|
101
92
|
block_given? ? yield(target) : target
|
102
93
|
end
|
103
94
|
|
104
|
-
##
|
105
|
-
#
|
106
95
|
# Creates a square thumbnail of the image cropping the longest edge to
|
107
96
|
# match the shortest edge, resizes to size, and yields/returns the new image.
|
108
|
-
#
|
109
97
|
def cropped_thumbnail(size)
|
110
|
-
l, t, r, b
|
111
|
-
l, r = half, half + height if width > height
|
112
|
-
t, b = half, half + width if height > width
|
113
|
-
|
98
|
+
l, t, r, b = calculate_thumbnail_dimentions
|
114
99
|
target = with_crop(l, t, r, b).thumbnail(size)
|
115
100
|
block_given? ? yield(target) : target
|
116
101
|
end
|
117
102
|
|
118
|
-
|
119
|
-
|
103
|
+
def calculate_thumbnail_dimensions
|
104
|
+
half = (width - height).abs / 2
|
105
|
+
if width > height
|
106
|
+
[half, 0, half + height, height]
|
107
|
+
else
|
108
|
+
[0, half, width, half + width]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
private :calculate_thumbnail_dimensions
|
112
|
+
|
120
113
|
# Flips the image horizontally and yields/returns the new image.
|
121
|
-
#
|
122
114
|
def flip_horizontally
|
123
115
|
target = guard { flip_horizontally_impl }
|
124
116
|
block_given? ? yield(target) : target
|
125
117
|
end
|
126
118
|
|
127
|
-
##
|
128
|
-
#
|
129
119
|
# Flips the image vertically and yields/returns the new image.
|
130
|
-
#
|
131
120
|
def flip_vertically
|
132
121
|
target = guard { flip_vertically_impl }
|
133
122
|
block_given? ? yield(target) : target
|
134
123
|
end
|
135
124
|
|
136
|
-
##
|
137
|
-
#
|
138
125
|
# Creates a grayscale version of image and yields/returns the new image.
|
139
|
-
#
|
140
126
|
def greyscale
|
141
127
|
target = guard { greyscale_impl }
|
142
128
|
block_given? ? yield(target) : target
|
143
129
|
end
|
144
|
-
|
130
|
+
alias grayscale greyscale
|
145
131
|
|
146
|
-
##
|
147
|
-
#
|
148
132
|
# Extracts metadata from an image.
|
149
|
-
#
|
150
133
|
def metadata
|
151
134
|
guard { metadata_impl }
|
152
135
|
end
|
153
136
|
|
154
|
-
##
|
155
|
-
#
|
156
137
|
# Creates a negative and yields/returns the new image.
|
157
|
-
#
|
158
138
|
def negative
|
159
139
|
target = guard { negative_impl }
|
160
140
|
block_given? ? yield(target) : target
|
161
141
|
end
|
162
142
|
|
163
|
-
##
|
164
|
-
#
|
165
143
|
# Set quality you want resulting image to be once you save or extract
|
166
144
|
# bytes for the image. Note: This will only work for lossy image
|
167
145
|
# formats like PNG of JPEG. For others it will be ignored.
|
168
146
|
def quality(amount)
|
169
147
|
if amount < 0.0 || amount > 1.0
|
170
|
-
raise ArgumentError
|
148
|
+
raise ArgumentError, 'Quality must be between 0.0 and 1.0'
|
171
149
|
end
|
172
150
|
|
173
|
-
target =
|
151
|
+
target = dup
|
174
152
|
target.quality = amount
|
175
153
|
block_given? ? yield(target) : target
|
176
154
|
end
|
177
155
|
|
178
|
-
##
|
179
|
-
#
|
180
156
|
# Resizes the image to width and height and yields/returns the new image.
|
181
|
-
#
|
182
157
|
def resize(width, height)
|
183
158
|
target = guard { resize_impl(width, height) }
|
184
159
|
block_given? ? yield(target) : target
|
@@ -186,135 +161,108 @@ class ImageVoodoo
|
|
186
161
|
raise ArgumentError, ne.message
|
187
162
|
end
|
188
163
|
|
189
|
-
##
|
190
|
-
#
|
191
164
|
# Rotates the image by angle (specified in degrees).
|
192
|
-
#
|
193
165
|
def rotate(angle)
|
194
|
-
target = guard { rotate_impl(angle) }
|
166
|
+
target = guard { rotate_impl(to_radians(angle)) }
|
195
167
|
block_given? ? yield(target) : target
|
196
168
|
end
|
197
169
|
|
198
|
-
##
|
199
|
-
#
|
200
170
|
# Saves the image out to path. Changing the file extension will convert
|
201
171
|
# the file type to the appropriate format.
|
202
|
-
#
|
203
172
|
def save(file)
|
204
173
|
format = File.extname(file)
|
205
|
-
return false if format ==
|
174
|
+
return false if format == ''
|
206
175
|
format = format[1..-1].downcase
|
207
176
|
guard { save_impl(format, JFile.new(file)) }
|
208
177
|
true
|
209
178
|
end
|
210
179
|
|
211
|
-
##
|
212
|
-
#
|
213
180
|
# Resize (scale) the current image by the provided ratio and yield/return
|
214
181
|
# the new image.
|
215
|
-
#
|
216
182
|
def scale(ratio)
|
217
183
|
new_width, new_height = (width * ratio).to_i, (height * ratio).to_i
|
218
184
|
target = resize(new_width, new_height)
|
219
185
|
block_given? ? yield(target) : target
|
220
186
|
end
|
221
187
|
|
222
|
-
##
|
223
|
-
#
|
224
188
|
# Creates a proportional thumbnail of the image scaled so its longest
|
225
189
|
# edge is resized to size and yields/returns the new image.
|
226
|
-
#
|
227
190
|
def thumbnail(size)
|
228
191
|
target = scale(size.to_f / (width > height ? width : height))
|
229
192
|
block_given? ? yield(target) : target
|
230
193
|
end
|
231
194
|
|
232
|
-
##
|
233
|
-
#
|
234
195
|
# Crops an image to left, top, right, and bottom and then yields/returns the
|
235
196
|
# new image.
|
236
|
-
#
|
237
197
|
def with_crop(left, top, right, bottom)
|
238
198
|
image = guard { with_crop_impl(left, top, right, bottom) }
|
239
199
|
block_given? ? yield(image) : image
|
240
200
|
end
|
241
201
|
|
242
|
-
|
243
|
-
|
202
|
+
# Creates a new (empty) image with a file name specified.
|
203
|
+
def self.new_image(width, height, file_name)
|
204
|
+
image = guard { new_image_impl(width, height, file_name) }
|
205
|
+
block_given? ? yield(image) : image
|
206
|
+
end
|
207
|
+
|
244
208
|
# A top-level image loader opens path and then yields/returns the image.
|
245
|
-
#
|
246
209
|
def self.with_image(path)
|
247
210
|
raise ArgumentError, "file does not exist: #{path}" unless File.file?(path)
|
248
211
|
image = guard { with_image_impl(JFile.new(path)) }
|
249
212
|
image && block_given? ? yield(image) : image
|
250
213
|
end
|
251
214
|
|
252
|
-
##
|
253
|
-
#
|
254
215
|
# A top-level image loader reads bytes and then yields/returns the image.
|
255
|
-
#
|
256
216
|
def self.with_bytes(bytes)
|
257
|
-
bytes = bytes.to_java_bytes if String
|
217
|
+
bytes = bytes.to_java_bytes if bytes.is_a? String
|
258
218
|
image = guard { with_bytes_impl(bytes) }
|
259
219
|
block_given? ? yield(image) : image
|
260
220
|
end
|
261
221
|
|
262
222
|
class << self
|
263
|
-
|
223
|
+
alias with_image_from_memory with_bytes
|
264
224
|
end
|
265
225
|
|
266
|
-
##
|
267
|
-
#
|
268
226
|
# *_impl providers only need provide the implementation if it can
|
269
227
|
# support it. Otherwise, this method will detect that the method is
|
270
228
|
# missing.
|
271
|
-
#
|
272
229
|
def self.guard(&block)
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
"Unimplemented Feature: #{e}"
|
277
|
-
end
|
230
|
+
return block.call
|
231
|
+
rescue NoMethodError => e
|
232
|
+
"Unimplemented Feature: #{e}"
|
278
233
|
end
|
234
|
+
|
279
235
|
def guard(&block)
|
280
236
|
ImageVoodoo.guard(&block)
|
281
237
|
end
|
282
238
|
|
283
|
-
##
|
284
|
-
#
|
285
239
|
# Returns the height of the image, in pixels.
|
286
|
-
#
|
287
240
|
def height
|
288
241
|
@src.height
|
289
242
|
end
|
290
243
|
|
291
|
-
##
|
292
|
-
#
|
293
244
|
# Returns the width of the image, in pixels.
|
294
|
-
#
|
295
245
|
def width
|
296
246
|
@src.width
|
297
247
|
end
|
298
248
|
|
299
|
-
##
|
300
|
-
#
|
301
249
|
# Returns the underlying Java class associated with this object. Note:
|
302
250
|
# Depending on whether you are using AWT or GAE/J you will get a totally
|
303
251
|
# different Java class. So caveat emptor!
|
304
|
-
#
|
305
252
|
def to_java
|
306
253
|
@src
|
307
254
|
end
|
308
255
|
|
309
|
-
##
|
310
|
-
#
|
311
256
|
# Returns detected image format from binary representation of input data
|
312
257
|
# as upper case string. Eg. JPEG, BMP, PNG. For GWT image representation
|
313
258
|
# compatibility method name is :format. It also accepts block and returns
|
314
259
|
# format as first block argument. When format not detected or not set it
|
315
260
|
# returns nil
|
316
|
-
#
|
317
261
|
def format
|
318
262
|
@format && block_given? ? yield(@format) : @format
|
319
263
|
end
|
264
|
+
|
265
|
+
def to_radians(degrees)
|
266
|
+
degrees * Math::PI / 180
|
267
|
+
end
|
320
268
|
end
|