image_voodoo 0.8.8 → 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|