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.
- checksums.yaml +5 -5
- data/.gitignore +4 -0
- data/.rubocop.yml +165 -0
- data/.travis.yml +17 -0
- data/Gemfile +5 -0
- data/Jars.lock +3 -0
- data/LICENSE-2.0.txt +202 -0
- data/README.exif +18 -0
- data/README.md +1 -0
- data/Rakefile +21 -0
- data/bin/image_voodoo +81 -72
- data/image_voodoo.gemspec +14 -7
- data/lib/image_science.rb +3 -1
- data/lib/image_voodoo/awt/core_ext/buffered_image.rb +15 -0
- data/lib/image_voodoo/awt/core_ext/graphics2d.rb +15 -0
- data/lib/image_voodoo/awt/shapes.rb +41 -3
- data/lib/image_voodoo/awt.rb +177 -124
- data/lib/image_voodoo/gae.rb +12 -6
- data/lib/image_voodoo/metadata.rb +1689 -0
- data/lib/image_voodoo/needs_head.rb +3 -0
- data/lib/image_voodoo/version.rb +3 -1
- data/lib/image_voodoo.rb +77 -90
- data/samples/bench.rb +30 -36
- data/samples/file_greyscale.rb +2 -0
- data/samples/file_thumbnail.rb +2 -0
- data/samples/file_view.rb +4 -1
- data/samples/{in-memory.rb → in_memory.rb} +2 -0
- data/samples/lossy.rb +2 -0
- data/test/test_image_science.rb +34 -74
- data/test/test_image_voodoo.rb +84 -0
- data/test/test_metadata.rb +65 -0
- data/test/test_shapes.rb +23 -0
- data/tools/gen.rb +68 -0
- metadata +94 -7
data/bin/image_voodoo
CHANGED
@@ -1,86 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#!/usr/bin/env ruby
|
2
4
|
|
3
5
|
require 'optparse'
|
4
6
|
|
7
|
+
headless = true
|
5
8
|
actions = []
|
6
9
|
images = []
|
7
10
|
original_image = nil
|
8
11
|
|
9
|
-
opts = OptionParser.new do |
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
12
|
+
opts = OptionParser.new do |o|
|
13
|
+
o.banner = 'Usage: image_voodoo [actions] image_file'
|
14
|
+
o.separator 'Perform actions/transformations on an image.'
|
15
|
+
o.separator ''
|
16
|
+
o.separator 'Examples:'
|
17
|
+
o.separator ' image_voodoo --dim small.jpg # Print the dimensions'
|
18
|
+
o.separator ''
|
19
|
+
o.separator ' # Make a thumbnail, preview it, and then save it.'
|
20
|
+
o.separator ' image_voodoo --thumbnail 50 --preview --save thumb.png large.jpg'
|
21
|
+
o.separator ''
|
22
|
+
o.separator ' # Make 2 thumbnails, showing dimensions and previewing them'
|
23
|
+
o.separator ' image_voodoo --dim --resize 50x50 --dim --preview --save t1.jpg'
|
24
|
+
o.separator ' --pop --resize 40x40 --dim --preview --save t2.jpg image.jpg'
|
25
|
+
o.separator ''
|
26
|
+
o.separator 'Actions:'
|
27
|
+
|
28
|
+
o.on('-a', '--alpha rrggbb', 'Make color transparent in image') do |c|
|
29
|
+
o.usage 'rrggbb is in hexidecimal format' if c !~ /[[:xdigit:]]{6,6}/
|
30
|
+
actions << ->(img) { img.alpha(c) }
|
31
|
+
end
|
32
|
+
|
33
|
+
o.on('-b', '--brightness SCALE,OFFSET', 'Adjust brightness') do |args|
|
34
|
+
scale, offset = args.split(/,/).map(&:to_f)
|
35
|
+
o.usage 'You need to specify proper scale and offset' unless scale && offset
|
36
|
+
actions << ->(img) { img.adjust_brightness(scale, offset) }
|
37
|
+
end
|
38
|
+
|
39
|
+
o.on('-B', '--border WIDTH,COLOR,STYLE', 'Add a simple border') do |args|
|
40
|
+
width, color, style = args.split(/,/)
|
41
|
+
options = { width: width, color: color, style: style }
|
42
|
+
actions << ->(img) { img.add_border(options) }
|
35
43
|
end
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
opts.usage "You need to specify proper scale and offset" unless scale && offset
|
40
|
-
actions << lambda {|img| img.adjust_brightness(scale, offset) }
|
45
|
+
o.on('-d', '--dimensions', 'Print the image dimensions') do
|
46
|
+
actions << ->(img) { img.tap { puts "#{img.width}x#{img.height}" } }
|
41
47
|
end
|
42
48
|
|
43
|
-
|
44
|
-
|
45
|
-
|
49
|
+
o.on('-g', '--greyscale', 'Convert image to greyscale') do
|
50
|
+
actions << ->(img) { img.greyscale }
|
51
|
+
end
|
46
52
|
|
47
|
-
|
53
|
+
o.on('-h', '--flip_horizontally') do
|
54
|
+
actions << ->(img) { img.flip_horizontally }
|
48
55
|
end
|
49
56
|
|
50
|
-
|
51
|
-
actions <<
|
57
|
+
o.on('-m', '--metadata') do
|
58
|
+
actions << ->(img) { img.tap { puts img.metadata } }
|
52
59
|
end
|
53
60
|
|
54
|
-
|
55
|
-
actions <<
|
61
|
+
o.on('-n', '--negative', 'Make a negative out of the image') do
|
62
|
+
actions << ->(img) { img.negative }
|
56
63
|
end
|
57
64
|
|
58
|
-
|
59
|
-
actions <<
|
65
|
+
o.on('-o', '--orient', 'Rotate image to orient it based on metadata') do
|
66
|
+
actions << ->(img) { img.correct_orientation }
|
60
67
|
end
|
61
68
|
|
62
|
-
|
63
|
-
actions <<
|
69
|
+
o.on('-q', '--quality 0..1', Float, 'Set % of quality for lossy compression') do |quality|
|
70
|
+
actions << ->(img) { img.quality(quality) }
|
64
71
|
end
|
65
72
|
|
66
|
-
|
67
|
-
actions <<
|
73
|
+
o.on('-R', '--rotate 0..360', Float, 'Set angle to rotate image') do |angle|
|
74
|
+
actions << ->(img) { img.rotate(angle.to_f) }
|
68
75
|
end
|
69
76
|
|
70
|
-
|
71
|
-
|
77
|
+
o.on('-r', '--resize WIDTHxHEIGHT', 'Make a new resized image') do |dim|
|
78
|
+
width, height = dim.split(/x/i).map(&:to_i)
|
79
|
+
o.usage 'You need to specify proper dimensions' unless width && width > 0 && height && height > 0
|
80
|
+
actions << ->(img) { img.resize(width, height) }
|
72
81
|
end
|
73
82
|
|
74
|
-
|
75
|
-
actions <<
|
83
|
+
o.on('-s', '--save FILENAME', 'Save the results to a new file') do |f|
|
84
|
+
actions << ->(img) { img.tap { img.save(f) } }
|
76
85
|
end
|
77
86
|
|
78
|
-
|
79
|
-
actions <<
|
87
|
+
o.on('-t', '--thumbnail SIZE', Integer, 'Create a thumbnail of the given size') do |size|
|
88
|
+
actions << ->(img) { img.thumbnail(size) }
|
80
89
|
end
|
81
90
|
|
82
|
-
|
83
|
-
|
91
|
+
o.on('-v', '--flip_vertically') { actions << ->(img) { img.flip_vertically } }
|
92
|
+
|
93
|
+
o.on('-p', '--preview',
|
94
|
+
'Preview the image. Close the frame window',
|
95
|
+
'to continue, or quit the application to',
|
96
|
+
'abort the action pipeline') do
|
97
|
+
headless = false
|
84
98
|
actions << lambda do |img|
|
85
99
|
done = false
|
86
100
|
img.preview { done = true }
|
@@ -89,38 +103,33 @@ opts = OptionParser.new do |opts|
|
|
89
103
|
end
|
90
104
|
end
|
91
105
|
|
92
|
-
|
93
|
-
actions <<
|
106
|
+
o.on('--push', 'Save the current image to be popped later') do
|
107
|
+
actions << ->(img) { img.tap { images << img } }
|
94
108
|
end
|
95
109
|
|
96
|
-
|
97
|
-
actions <<
|
110
|
+
o.on('--pop', 'Revert back to the previous image') do
|
111
|
+
actions << -> { images.pop || original_image }
|
98
112
|
end
|
99
113
|
|
100
|
-
|
101
|
-
|
102
|
-
opts.usage "You need to specify proper dimensions" unless width && width > 0 && height && height > 0
|
103
|
-
actions << lambda {|img| img.resize(width,height) }
|
114
|
+
o.on('-f', '--format', 'Print the image format') do
|
115
|
+
actions << ->(img) { img.tap { img.format } }
|
104
116
|
end
|
105
117
|
|
106
|
-
|
107
|
-
actions << lambda {|img| puts img.format; img }
|
108
|
-
end
|
118
|
+
o.on_tail('-h', '--help', 'Show this message') { o.usage }
|
109
119
|
|
110
|
-
|
111
|
-
puts
|
112
|
-
exit 0
|
113
|
-
end
|
114
|
-
|
115
|
-
def opts.usage(msg)
|
116
|
-
puts msg
|
120
|
+
def o.usage(msg=nil)
|
121
|
+
puts msg if msg
|
117
122
|
puts self
|
118
123
|
exit 1
|
119
124
|
end
|
120
125
|
end
|
121
126
|
opts.parse!(ARGV)
|
122
|
-
opts.usage(
|
123
|
-
opts.usage(
|
127
|
+
opts.usage('You need to supply a source image filename.') unless ARGV.first
|
128
|
+
opts.usage('You need to supply one or more actions.') if actions.empty?
|
129
|
+
|
130
|
+
# For this binstub we only want to load non-headless if we are using
|
131
|
+
# the preview feature. top of See lib/image_voodoo.rb for more info...
|
132
|
+
require 'image_voodoo/needs_head' unless headless
|
124
133
|
|
125
134
|
require 'image_voodoo'
|
126
135
|
file_name = ARGV.first
|
data/image_voodoo.gemspec
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
5
|
+
require 'image_voodoo/version'
|
4
6
|
|
5
7
|
Gem::Specification.new do |s|
|
6
8
|
s.name = 'image_voodoo'
|
@@ -12,11 +14,16 @@ Gem::Specification.new do |s|
|
|
12
14
|
s.summary = 'Image manipulation in JRuby with ImageScience compatible API'
|
13
15
|
s.description = 'Image manipulation in JRuby with ImageScience compatible API'
|
14
16
|
|
15
|
-
s.rubyforge_project = "image_voodoo"
|
16
|
-
|
17
17
|
s.files = `git ls-files`.split("\n")
|
18
18
|
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 = [
|
21
|
-
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
20
|
+
s.require_paths = %w[lib vendor]
|
21
|
+
|
22
|
+
s.add_development_dependency 'jar-dependencies'
|
23
|
+
s.add_development_dependency 'rake'
|
24
|
+
s.add_development_dependency 'rubocop'
|
25
|
+
s.add_development_dependency 'ruby-maven'
|
26
|
+
s.add_development_dependency 'test-unit'
|
27
|
+
|
28
|
+
s.requirements << 'jar com.drewnoakes, metadata-extractor, 2.11.0'
|
22
29
|
end
|
data/lib/image_science.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'java'
|
4
|
+
|
5
|
+
# Hide in ImageVoodoo so awt.rb can see this and we will not polute global
|
6
|
+
class ImageVoodoo
|
7
|
+
java_import java.awt.image.BufferedImage
|
8
|
+
|
9
|
+
# Re-open to add convenience methods.
|
10
|
+
class BufferedImage
|
11
|
+
def each
|
12
|
+
height.times { |j| width.times { |i| yield i, j } }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'java'
|
4
|
+
|
5
|
+
# Hide in ImageVoodoo so awt.rb can see this and we will not polute global
|
6
|
+
class ImageVoodoo
|
7
|
+
java_import java.awt.Graphics2D
|
8
|
+
|
9
|
+
# Re-open to add convenience methods.
|
10
|
+
class Graphics2D
|
11
|
+
def draw_this_image(image)
|
12
|
+
draw_image image, 0, 0, nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,5 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ImageVoodoo
|
4
|
+
# (Experimental) An attempt at some primitive drawing in images.
|
2
5
|
module Shapes
|
6
|
+
# FIXME: if image has alpha values the border shows through since it is
|
7
|
+
# a solid fill.
|
8
|
+
##
|
9
|
+
# *AWT* (experimental) Add a border to the image and yield/return a new
|
10
|
+
# image. The following options are supported:
|
11
|
+
# - width: How thick is the border (default: 3)
|
12
|
+
# - color: Which color is the border (in rrggbb hex value)
|
13
|
+
# - style: etched, raised, plain (default: plain)
|
14
|
+
#
|
15
|
+
def add_border(options = {})
|
16
|
+
border_width = options[:width].to_i || 2
|
17
|
+
new_width, new_height = width + 2*border_width, height + 2*border_width
|
18
|
+
target = paint(BufferedImage.new(new_width, new_height, color_type)) do |g|
|
19
|
+
paint_border(g, new_width, new_height, options)
|
20
|
+
g.draw_image(@src, nil, border_width, border_width)
|
21
|
+
end
|
22
|
+
block_given? ? yield(target) : target
|
23
|
+
end
|
24
|
+
|
25
|
+
def paint_border(g, new_width, new_height, options)
|
26
|
+
g.color = hex_to_color(options[:color])
|
27
|
+
fill_method, *args = border_style(options)
|
28
|
+
g.send fill_method, 0, 0, new_width, new_height, *args
|
29
|
+
end
|
30
|
+
|
31
|
+
def border_style(options)
|
32
|
+
case (options[:style] || "").to_s
|
33
|
+
when "raised" then
|
34
|
+
[:fill3DRect, true]
|
35
|
+
when "etched" then
|
36
|
+
[:fill3DRect, false]
|
37
|
+
else
|
38
|
+
[:fill_rect]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
3
42
|
##
|
4
43
|
# *AWT* Draw a square
|
5
44
|
#
|
@@ -18,14 +57,14 @@ class ImageVoodoo
|
|
18
57
|
# *AWT* Draw a rounded square
|
19
58
|
#
|
20
59
|
def square_rounded(x, y, dim, rgb, arc_width=0, fill=true)
|
21
|
-
rect_rounded(x,y, dim, dim, rgb, arc_width, arc_width, fill)
|
60
|
+
rect_rounded(x, y, dim, dim, rgb, arc_width, arc_width, fill)
|
22
61
|
end
|
23
62
|
|
24
63
|
##
|
25
64
|
# *AWT* Draw a rounded rectangle
|
26
65
|
#
|
27
66
|
def rect_rounded(x, y, width, height, rgb, arc_width=0, arc_height=0, fill=true)
|
28
|
-
as_color(
|
67
|
+
as_color(hex_to_color(rgb)) do |g|
|
29
68
|
if fill
|
30
69
|
g.fill_round_rect x, y, width, height, arc_width, arc_height
|
31
70
|
else
|
@@ -44,4 +83,3 @@ class ImageVoodoo
|
|
44
83
|
end
|
45
84
|
end
|
46
85
|
end
|
47
|
-
|