quick_magick_hooopo 0.8.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.
- data/Manifest +15 -0
- data/README.rdoc +195 -0
- data/Rakefile +15 -0
- data/lib/quick_magick.rb +208 -0
- data/lib/quick_magick/image.rb +495 -0
- data/lib/quick_magick/image_list.rb +60 -0
- data/quick_magick_hooopo.gemspec +30 -0
- data/test/9.gif +0 -0
- data/test/badfile.xxx +0 -0
- data/test/image_list_test.rb +114 -0
- data/test/image_test.rb +336 -0
- data/test/multipage.tif +0 -0
- data/test/test_magick.rb +169 -0
- data/test/test_quick_magick.rb +17 -0
- data/test/warnings.tiff +0 -0
- metadata +75 -0
data/Manifest
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Manifest
|
2
|
+
README.rdoc
|
3
|
+
Rakefile
|
4
|
+
lib/quick_magick.rb
|
5
|
+
lib/quick_magick/image.rb
|
6
|
+
lib/quick_magick/image_list.rb
|
7
|
+
quick_magick_hooopo.gemspec
|
8
|
+
test/9.gif
|
9
|
+
test/badfile.xxx
|
10
|
+
test/image_list_test.rb
|
11
|
+
test/image_test.rb
|
12
|
+
test/multipage.tif
|
13
|
+
test/test_magick.rb
|
14
|
+
test/test_quick_magick.rb
|
15
|
+
test/warnings.tiff
|
data/README.rdoc
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
= Quick Magick
|
2
|
+
|
3
|
+
== What is QuickMagick
|
4
|
+
QuickMagick is a gem built by BadrIT (http://www.badrit.com) for easily accessing ImageMagick command line tools from Ruby programs.
|
5
|
+
|
6
|
+
== When to use QuickMagick
|
7
|
+
QuickMagick is a library that allows you to create and manipulate images.
|
8
|
+
When you are faced with a problem that requires high quality manipulation of images you can use QuickMagick.
|
9
|
+
Use QuickMagick to:
|
10
|
+
* Check uploaded images dimensions.
|
11
|
+
* Generate captchas.
|
12
|
+
* Generate graphical reports and charts.
|
13
|
+
* Convert uploaded images formats.
|
14
|
+
* Display pdfs as images.
|
15
|
+
|
16
|
+
== Features
|
17
|
+
* Open an existing image from disk and determine basic info like width, height.
|
18
|
+
* Open an image from blob. For example, an image read from an upload form.
|
19
|
+
* Do basic and advanced operations on images like resize, rotate, shear, motion blur and other.
|
20
|
+
* Create an image from scratch and draw basic elements on it like line, circle and text. This allows making captchas for example.
|
21
|
+
* Combine images using ImageList to make a multipage or animated images.
|
22
|
+
* API is very simple and powerful.
|
23
|
+
* Minimizes disk access by calling command line tools only when required.
|
24
|
+
|
25
|
+
== How to install
|
26
|
+
First, you should install ImageMagick (http://www.imagemagick.org/) on your machine.
|
27
|
+
Command line tools of ImageMagick must be in your system path.
|
28
|
+
You can check this by running the command:
|
29
|
+
identify -version
|
30
|
+
Now to install QuickMagick just type at your command line:
|
31
|
+
gem install quick_magick
|
32
|
+
... and it's done.
|
33
|
+
You don't have to install any libraries or compile code from source.
|
34
|
+
|
35
|
+
== What is different?
|
36
|
+
But what's different from other gems like RMagick and mini-magick?
|
37
|
+
|
38
|
+
The story begins when I was working on a project at BadrIT (http://www.badrit.com) using Ruby on Rails.
|
39
|
+
In this projects users were uploading images in many formats like pdf, tiff and png.
|
40
|
+
We were using Flex as a front end to display and annotate these images.
|
41
|
+
Flex has a limitation in that it can open images up to 8192x8192.
|
42
|
+
Unfortunately, users were uploading images much larger than this.
|
43
|
+
Another issue is that Flex can only open .jpg, .png and .gif files.
|
44
|
+
The solution was to convert all images uploaded to one of these formats and resizing them down to at most 8192x8192.
|
45
|
+
|
46
|
+
First, I used ImageMagick as a command line tool and was calling it using system calls.
|
47
|
+
This accomplished the work perfectly but my source code was a rubbish.
|
48
|
+
It has many lines of code to handle creating temporary files and accessing multipage tiff and pdf files.
|
49
|
+
I found RMagick at that time and decided to use it.
|
50
|
+
It caused the code to be much simple without affecting performance notably.
|
51
|
+
It worked with me well while I was using my application with test images.
|
52
|
+
Once I decided to test it with real images it failed.
|
53
|
+
For example, when I transform a tiff image from 14400x9600 to 8192x8192 while transforming it to gif, my machine runs out of memory (2GB RAM).
|
54
|
+
When I tried to make the same operation from command line using (convert) it was working much better.
|
55
|
+
It did not finish in a second but at least it worked correctly.
|
56
|
+
The solution was to return back to command line.
|
57
|
+
|
58
|
+
I searched for alternatives and found MiniMagick.
|
59
|
+
MiniMagick is a gem that allows you to perform basic operations using ImageMagick through command line tools.
|
60
|
+
I tried to use it but it was not good enough.
|
61
|
+
First, it writes temporary images and files as it is working which makes it slow for nothing.
|
62
|
+
Second, it doesn't handle multipage images.
|
63
|
+
I tried it with a .pdf file and I found that it handled the first page only.
|
64
|
+
Third, it doesn't give an API to draw images from scratch.
|
65
|
+
Actually, I didn't need this feature, but I may need it in the future.
|
66
|
+
|
67
|
+
At this point I decided to make my own gem and QuickMagick was born.
|
68
|
+
I addressed the problems of MiniMagick while using the same main idea.
|
69
|
+
First, QuickMagick doesn't write any temporary image files to disk.
|
70
|
+
It doesn't issue any command line commands till the very end when you are saving the image.
|
71
|
+
Second, I made an API similar to RMagick which allows for accessing multipage images.
|
72
|
+
Third, I added API commands to create images from scratch and drawing simple primitives on images.
|
73
|
+
I tested my gem, compared it to MiniMagick and RMagick and it pleased me.
|
74
|
+
|
75
|
+
== Comparison
|
76
|
+
I've made some test benches to compare the speed of QuickMagick, MiniMagick and RMagick.
|
77
|
+
All denoted numbers are in seconds.
|
78
|
+
Here are the results:
|
79
|
+
|
80
|
+
===Test 1: resize a normal image
|
81
|
+
user system total real
|
82
|
+
mini 0.030000 0.040000 3.640000 ( 3.585617)
|
83
|
+
quick 0.010000 0.030000 3.330000 ( 3.295369)
|
84
|
+
rmagick 1.680000 1.660000 3.340000 ( 3.150202)
|
85
|
+
|
86
|
+
It's clear that QuickMagick is faster than MiniMagick.
|
87
|
+
RMagick was the fastest as it accesses the requested operations directly without the need to load an executable file or parse a command line.
|
88
|
+
|
89
|
+
===Test 2: resize a large image
|
90
|
+
user system total real
|
91
|
+
mini 0.000000 0.040000 57.150000 (130.609229)
|
92
|
+
quick 0.010000 0.010000 56.510000 ( 58.426361)
|
93
|
+
|
94
|
+
Again QuickMagick is faster than MiniMagick.
|
95
|
+
However, RMagick has failed to pass this test.
|
96
|
+
It kept working and eating memory, cpu and harddisk till I had to unplug my computer to stop it.
|
97
|
+
So, I removed it from this test bench.
|
98
|
+
|
99
|
+
===Test 3: generate random captchas
|
100
|
+
user system total real
|
101
|
+
quick 0.000000 0.000000 0.290000 ( 3.623418)
|
102
|
+
rmagick 0.150000 0.120000 0.270000 ( 3.171975)
|
103
|
+
|
104
|
+
In this last test, RMagick was about 12% faster than QuickMagick.
|
105
|
+
This is normal because it works in memory and doesn't have to parse a command line string to know what to draw.
|
106
|
+
I couldn't test MiniMagick for this because it doesn't support an API for drawing functions.
|
107
|
+
|
108
|
+
== Conclusion
|
109
|
+
QuickMagick is very easy to install, very easy to use and allows you to access most features of ImageMagick.
|
110
|
+
RMagick is a bit faster but it's a bit hard to install.
|
111
|
+
Also RMagick sometimes fail when it tries to handle large images.
|
112
|
+
MiniMagick is proved to be slower than QuickMagick with no advantage.
|
113
|
+
So, it's better to use QuickMagick when your application is not image-centric.
|
114
|
+
This means, you're not going to build an image manipulation tool or something like this.
|
115
|
+
For normal operations like resize, rotate, generating captchas ... etc, QuickMagick will be a good friend of you.
|
116
|
+
|
117
|
+
== Examples
|
118
|
+
Determine image information
|
119
|
+
i = QuickMagick::Image.read('test.jpg').first
|
120
|
+
i.width # Retrieves width in pixels
|
121
|
+
i.height # Retrieves height in pixels
|
122
|
+
|
123
|
+
Resize an image
|
124
|
+
i = QuickMagick::Image.read('test.jpg').first
|
125
|
+
i.resize "300x300!"
|
126
|
+
i.save "resized_image.jpg"
|
127
|
+
|
128
|
+
or
|
129
|
+
i.append_to_operators 'resize', "300x300!"
|
130
|
+
|
131
|
+
or
|
132
|
+
i.resize 300, 300, nil, nil, QuickMagick::AspectGeometry
|
133
|
+
|
134
|
+
|
135
|
+
Access multipage image
|
136
|
+
i = QuickMagick::Image.read("multipage.pdf") {|image| image.density = 300}
|
137
|
+
i.size # number of pages
|
138
|
+
i.each_with_index do |page, i|
|
139
|
+
i.save "page_#{i}.jpg"
|
140
|
+
end
|
141
|
+
|
142
|
+
From blob
|
143
|
+
i = QuickMagick::Image.from_blob(blob_data).first
|
144
|
+
i.sample "100x100!"
|
145
|
+
i.save
|
146
|
+
|
147
|
+
Modify a file (mogrify)
|
148
|
+
i = QuickMagick::Image.read('test.jpg')
|
149
|
+
i.resize "100x100!"
|
150
|
+
i.save!
|
151
|
+
|
152
|
+
You can also display an image to Xserver
|
153
|
+
i = QuickMagick::Image.read('test.jpg')
|
154
|
+
i.display
|
155
|
+
|
156
|
+
QuickMagick supports also ImageList s
|
157
|
+
# Batch convert a list of jpg files to gif while resizing them
|
158
|
+
il = QuickMagick::ImageList.new('test.jpg', 'test2.jpg')
|
159
|
+
il << QuickMagick::Image.read('test3.jpg')
|
160
|
+
il.format = 'gif'
|
161
|
+
il.resize "300x300>"
|
162
|
+
il.save!
|
163
|
+
|
164
|
+
You can also create images from scratch
|
165
|
+
# Create a 300x300 gradient image from yellow to red
|
166
|
+
i1 = QuickMagick::Image::gradient(300, 300, QuickMagick::RadialGradient, :yellow, :red)
|
167
|
+
i1.save 'gradient.png'
|
168
|
+
|
169
|
+
# Create a 100x200 CheckerBoard image
|
170
|
+
i1 = QuickMagick::Image::pattern(100, 200, :checkerboard)
|
171
|
+
i1.display
|
172
|
+
|
173
|
+
... and draw primitives on them
|
174
|
+
i = QuickMagick::Image::solid(100, 100, :white)
|
175
|
+
i.draw_line(0,0,50,50)
|
176
|
+
i.draw_text(30, 30, "Hello world!", :rotate=>45)
|
177
|
+
i.save 'hello.jpg'
|
178
|
+
|
179
|
+
... you can then convert it to blob using
|
180
|
+
i.to_blob
|
181
|
+
|
182
|
+
(new) You can now access single pixels using QuickMagick
|
183
|
+
# Create a 300x300 gradient image from yellow to red
|
184
|
+
i1 = QuickMagick::Image::gradient(300, 300, QuickMagick::RadialGradient, :yellow, :red)
|
185
|
+
i1.save 'gradient.png'
|
186
|
+
|
187
|
+
# Now you can access single pixel values
|
188
|
+
i1.get_pixel(10,10) # [255, 0, 0]
|
189
|
+
i1.get_pixel(50,50) # [255, 15, 0]
|
190
|
+
|
191
|
+
For more information on drawing API visit:
|
192
|
+
http://www.imagemagick.org/Usage/draw/
|
193
|
+
|
194
|
+
Check all command line options of ImageMagick at:
|
195
|
+
http://www.imagemagick.org/script/convert.php
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('quick_magick_hooopo', '0.8.1') do |p|
|
6
|
+
p.description = "QuickMagick allows you to access ImageMagick command line functions using Ruby interface."
|
7
|
+
p.summary = "A gem build by BadrIT to access ImageMagick command line functions easily and quickly"
|
8
|
+
p.url = "http://quickmagick.rubyforge.org/"
|
9
|
+
p.author = "Ahmed ElDawy"
|
10
|
+
p.email = "ahmed.eldawy@badrit.com"
|
11
|
+
p.project = "quickmagick"
|
12
|
+
end
|
13
|
+
|
14
|
+
#Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
15
|
+
|
data/lib/quick_magick.rb
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
# Define quick magick error
|
2
|
+
module QuickMagick
|
3
|
+
class QuickMagickError < RuntimeError; end
|
4
|
+
end
|
5
|
+
|
6
|
+
# check if ImageMagick is installed
|
7
|
+
begin
|
8
|
+
x = `identify -version 2>&1`
|
9
|
+
raise(QuickMagick::QuickMagickError, "ImageMagick not installed") unless x.index('ImageMagick')
|
10
|
+
rescue Errno::ENOENT
|
11
|
+
# For Windows machines
|
12
|
+
raise(QuickMagick::QuickMagickError, "ImageMagick not installed")
|
13
|
+
end
|
14
|
+
|
15
|
+
module QuickMagick
|
16
|
+
# Normally the attributes are treated as pixels.
|
17
|
+
# Use this flag when the width and height attributes represent percentages.
|
18
|
+
# For example, 125x75 means 125% of the height and 75% of the width.
|
19
|
+
# The x and y attributes are not affected by this flag.
|
20
|
+
PercentGeometry = "%"
|
21
|
+
|
22
|
+
# Use this flag when you want to force the new image to have exactly the size specified by the the width and height attributes.
|
23
|
+
AspectGeometry = "!"
|
24
|
+
|
25
|
+
# Use this flag when you want to change the size of the image only if both its width and height
|
26
|
+
# are smaller the values specified by those attributes. The image size is changed proportionally.
|
27
|
+
LessGeometry = "<"
|
28
|
+
|
29
|
+
# Use this flag when you want to change the size of the image if either its width and height
|
30
|
+
# exceed the values specified by those attributes. The image size is changed proportionally.
|
31
|
+
GreaterGeometry = ">"
|
32
|
+
|
33
|
+
# This flag is useful only with a single width attribute.
|
34
|
+
# When present, it means the width attribute represents the total area of the image in pixels.
|
35
|
+
AreaGeometry = "@"
|
36
|
+
|
37
|
+
# Use ^ to set a minimum image size limit.
|
38
|
+
# The geometry 640x480^, for example, means the image width will not be less than 640 and
|
39
|
+
# the image height will not be less than 480 pixels after the resize.
|
40
|
+
# One of those dimensions will match the requested size,
|
41
|
+
# but the image will likely overflow the space requested to preserve its aspect ratio.
|
42
|
+
MinimumGeometry = "^"
|
43
|
+
|
44
|
+
# Command for solid color
|
45
|
+
SolidColor = "xc"
|
46
|
+
# Command for linear gradient
|
47
|
+
LinearGradient = "gradient"
|
48
|
+
# Command for radial gradient
|
49
|
+
RadialGradient = "radial-gradient"
|
50
|
+
|
51
|
+
# Different possible patterns
|
52
|
+
Patterns = %w{bricks checkerboard circles crosshatch crosshatch30 crosshatch45 fishscales} +
|
53
|
+
(0..20).collect {|level| "gray#{level}" } +
|
54
|
+
%w{hexagons horizontal horizontalsaw hs_bdiagonal hs_cross hs_diagcross hs_fdiagonal hs_horizontal
|
55
|
+
hs_vertical left30 left45 leftshingle octagons right30 right45 rightshingle smallfishscales
|
56
|
+
vertical verticalbricks verticalleftshingle verticalrightshingle verticalsaw}
|
57
|
+
|
58
|
+
%w{NorthWest North NorthEast West Center East SouthWest South SouthEast}.each do |gravity_type|
|
59
|
+
const_set gravity_type+"Gravity", gravity_type
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class << self
|
64
|
+
# Generate a random string of specified length.
|
65
|
+
# Used to generate random names for temp files
|
66
|
+
def random_string(length=10)
|
67
|
+
@@CHARS ||= ("a".."z").to_a + ("1".."9").to_a
|
68
|
+
Array.new(length, '').collect{@@CHARS[rand(@@CHARS.size)]}.join
|
69
|
+
end
|
70
|
+
|
71
|
+
def verbose
|
72
|
+
defined?(@verbose) && @verbose
|
73
|
+
end
|
74
|
+
|
75
|
+
def verbose=(flag)
|
76
|
+
@verbose = flag
|
77
|
+
end
|
78
|
+
|
79
|
+
# Encodes a geometry string with the given options
|
80
|
+
def geometry(width, height=nil, x=nil, y=nil, flag=nil)
|
81
|
+
geometry_string = ""
|
82
|
+
geometry_string << width.to_s if width
|
83
|
+
geometry_string << 'x' << height.to_s if height
|
84
|
+
geometry_string << '+' << x.to_s if x
|
85
|
+
geometry_string << '+' << y.to_s if y
|
86
|
+
geometry_string << flag if flag
|
87
|
+
geometry_string
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns a formatted string for the color with the given components
|
91
|
+
# each component could take one of the following values
|
92
|
+
# * an integer from 0 to 255
|
93
|
+
# * a float from 0.0 to 1.0
|
94
|
+
# * a string showing percentage from "0%" to "100%"
|
95
|
+
def rgba_color(red, green, blue, alpha=255)
|
96
|
+
"#%02x%02x%02x%02x" % [red, green, blue, alpha].collect do |component|
|
97
|
+
case component
|
98
|
+
when Integer then component
|
99
|
+
when Float then Integer(component*255)
|
100
|
+
when String then Integer(component.sub('%', '')) * 255 / 100
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
alias rgb_color rgba_color
|
106
|
+
|
107
|
+
# Returns a formatted string for a gray color with the given level and alpha.
|
108
|
+
# level and alpha could take one of the following values
|
109
|
+
# * an integer from 0 to 255
|
110
|
+
# * a float from 0.0 to 1.0
|
111
|
+
# * a string showing percentage from "0%" to "100%"
|
112
|
+
def graya_color(level, alpha=255)
|
113
|
+
rgba_color(level, level, level, alpha)
|
114
|
+
end
|
115
|
+
|
116
|
+
alias gray_color graya_color
|
117
|
+
|
118
|
+
# HSL colors are encoding as a triple (hue, saturation, lightness).
|
119
|
+
# Hue is represented as an angle of the color circle (i.e. the rainbow represented in a circle).
|
120
|
+
# This angle is so typically measured in degrees that the unit is implicit in CSS;
|
121
|
+
# syntactically, only a number is given. By definition red=0=360,
|
122
|
+
# and the other colors are spread around the circle, so green=120, blue=240, etc.
|
123
|
+
# As an angle, it implicitly wraps around such that -120=240 and 480=120, for instance.
|
124
|
+
# (Students of trigonometry would say that "coterminal angles are equivalent" here;
|
125
|
+
# an angle (theta) can be standardized by computing the equivalent angle, (theta) mod 360.)
|
126
|
+
#
|
127
|
+
# Saturation and lightness are represented as percentages.
|
128
|
+
# 100% is full saturation, and 0% is a shade of grey.
|
129
|
+
# 0% lightness is black, 100% lightness is white, and 50% lightness is 'normal'.
|
130
|
+
#
|
131
|
+
# Hue can take one of the following values:
|
132
|
+
# * an integer from 0...360 representing angle in degrees
|
133
|
+
# * a float value from 0...2*PI represeting angle in radians
|
134
|
+
#
|
135
|
+
# saturation, lightness and alpha can take one of the following values:
|
136
|
+
# * an integer from 0 to 255
|
137
|
+
# * a float from 0.0 to 1.0
|
138
|
+
# * a string showing percentage from "0%" to "100%"
|
139
|
+
def hsla_color(hue, saturation, lightness, alpha=1.0)
|
140
|
+
components = [case hue
|
141
|
+
when Integer then hue
|
142
|
+
when Float then Integer(hue * 360 / 2 / Math::PI)
|
143
|
+
end]
|
144
|
+
components += [saturation, lightness].collect do |component|
|
145
|
+
case component
|
146
|
+
when Integer then (component * 100.0 / 255).round
|
147
|
+
when Float then Integer(component*100)
|
148
|
+
when String then Integer(component.sub('%', ''))
|
149
|
+
end
|
150
|
+
end
|
151
|
+
components << case alpha
|
152
|
+
when Integer then alpha * 100.0 / 255
|
153
|
+
when Float then alpha
|
154
|
+
when String then Float(alpha.sub('%', '')) / 100.0
|
155
|
+
end
|
156
|
+
"hsla(%d,%d%%,%d%%,%g)" % components
|
157
|
+
end
|
158
|
+
|
159
|
+
alias hsl_color hsla_color
|
160
|
+
|
161
|
+
# Escapes possible special chracters in command line
|
162
|
+
# by surrounding it with double quotes
|
163
|
+
def escape_commandline(str)
|
164
|
+
str =~ /^(\w|\.)*$/ ? str : "\"#{str}\""
|
165
|
+
end
|
166
|
+
|
167
|
+
alias c escape_commandline
|
168
|
+
|
169
|
+
# Execute a command line and returns its results when it suceeds
|
170
|
+
# When the command fails, it throws QuickMagick::QuickMagickError
|
171
|
+
def exec3(command)
|
172
|
+
error_file = Tempfile.new('error')
|
173
|
+
error_filepath = error_file.path
|
174
|
+
error_file.close
|
175
|
+
puts "Run Command -> #{command}" if verbose
|
176
|
+
result = `#{command} 2>"#{error_filepath}"`
|
177
|
+
unless $?.success?
|
178
|
+
error_message = <<-ERROR
|
179
|
+
Error executing command: command
|
180
|
+
Result is: #{result}
|
181
|
+
Error is: #{File.read(error_filepath)}
|
182
|
+
ERROR
|
183
|
+
raise QuickMagick::QuickMagickError, error_message
|
184
|
+
end
|
185
|
+
result
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# For backward compatibility with ruby < 1.8.7
|
191
|
+
unless "".respond_to? :start_with?
|
192
|
+
class String
|
193
|
+
def start_with?(x)
|
194
|
+
self.index(x) == 0
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
unless "".respond_to? :end_with?
|
200
|
+
class String
|
201
|
+
def end_with?(x)
|
202
|
+
self.index(x) == self.length - 1
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
require 'quick_magick/image'
|
208
|
+
require 'quick_magick/image_list'
|
@@ -0,0 +1,495 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
|
3
|
+
module QuickMagick
|
4
|
+
|
5
|
+
class Image
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# create an array of images from the given blob data
|
9
|
+
def from_blob(blob, &proc)
|
10
|
+
file = Tempfile.new(QuickMagick::random_string)
|
11
|
+
file.binmode
|
12
|
+
file.write(blob)
|
13
|
+
file.close
|
14
|
+
self.read(file.path, &proc)
|
15
|
+
end
|
16
|
+
|
17
|
+
# create an array of images from the given file
|
18
|
+
def read(filename, &proc)
|
19
|
+
info = identify(filename)
|
20
|
+
info_lines = info.split(/[\r\n]/)
|
21
|
+
images = []
|
22
|
+
info_lines.each_with_index do |info_line, i|
|
23
|
+
images << Image.new(filename, i, info_line)
|
24
|
+
end
|
25
|
+
images.each(&proc) if block_given?
|
26
|
+
return images
|
27
|
+
end
|
28
|
+
|
29
|
+
alias open read
|
30
|
+
|
31
|
+
# Creates a new image initially set to gradient
|
32
|
+
# Default gradient is linear gradient from black to white
|
33
|
+
def gradient(width, height, type=QuickMagick::LinearGradient, color1=nil, color2=nil)
|
34
|
+
template_name = type + ":"
|
35
|
+
template_name << color1.to_s if color1
|
36
|
+
template_name << '-' << color2.to_s if color2
|
37
|
+
i = self.new(template_name, 0, nil, true)
|
38
|
+
i.size = QuickMagick::geometry(width, height)
|
39
|
+
yield(i) if block_given?
|
40
|
+
i
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates an image with solid color
|
44
|
+
def solid(width, height, color=nil)
|
45
|
+
template_name = QuickMagick::SolidColor+":"
|
46
|
+
template_name << color.to_s if color
|
47
|
+
i = self.new(template_name, 0, nil, true)
|
48
|
+
i.size = QuickMagick::geometry(width, height)
|
49
|
+
yield(i) if block_given?
|
50
|
+
i
|
51
|
+
end
|
52
|
+
|
53
|
+
# Creates an image from pattern
|
54
|
+
def pattern(width, height, pattern)
|
55
|
+
raise QuickMagick::QuickMagickError, "Invalid pattern '#{pattern.to_s}'" unless QuickMagick::Patterns.include?(pattern.to_s)
|
56
|
+
template_name = "pattern:#{pattern.to_s}"
|
57
|
+
i = self.new(template_name, 0, nil, true)
|
58
|
+
i.size = QuickMagick::geometry(width, height)
|
59
|
+
yield(i) if block_given?
|
60
|
+
i
|
61
|
+
end
|
62
|
+
|
63
|
+
# returns info for an image using <code>identify</code> command
|
64
|
+
def identify(filename)
|
65
|
+
QuickMagick.exec3 "identify #{QuickMagick.c filename}"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# append the given option, value pair to the settings of the current image
|
71
|
+
def append_to_settings(arg, value=nil)
|
72
|
+
@arguments << "-#{arg} #{QuickMagick.c value} "
|
73
|
+
@last_is_draw = false
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# append the given string as is. Used to append special arguments like +antialias or +debug
|
78
|
+
def append_basic(arg)
|
79
|
+
@arguments << arg << ' '
|
80
|
+
end
|
81
|
+
|
82
|
+
# Image settings supported by ImageMagick
|
83
|
+
IMAGE_SETTINGS_METHODS = %w{
|
84
|
+
adjoin affine alpha authenticate attenuate background bias black-point-compensation
|
85
|
+
blue-primary bordercolor caption channel colors colorspace comment compose compress define
|
86
|
+
delay depth display dispose dither encoding endian family fill filter font format fuzz gravity
|
87
|
+
green-primary intent interlace interpolate interword-spacing kerning label limit loop mask
|
88
|
+
mattecolor monitor orient ping pointsize preview quality quiet red-primary regard-warnings
|
89
|
+
remap respect-parentheses scene seed stretch stroke strokewidth style taint texture treedepth
|
90
|
+
transparent-color undercolor units verbose view virtual-pixel weight white-point
|
91
|
+
|
92
|
+
density page sampling-factor size tile-offset
|
93
|
+
}
|
94
|
+
|
95
|
+
# append the given option, value pair to the args for the current image
|
96
|
+
def append_to_operators(arg, value=nil)
|
97
|
+
is_draw = (arg == 'draw')
|
98
|
+
if @last_is_draw && is_draw
|
99
|
+
@arguments.insert(@arguments.rindex('"'), " #{value}")
|
100
|
+
else
|
101
|
+
@arguments << %Q<-#{arg} #{QuickMagick.c value} >
|
102
|
+
end
|
103
|
+
@last_is_draw = is_draw
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# Reverts this image to its last saved state.
|
108
|
+
# Note that you cannot revert an image created from scratch.
|
109
|
+
def revert!
|
110
|
+
raise QuickMagick::QuickMagickError, "Cannot revert a pseudo image" if @pseudo_image
|
111
|
+
@arguments = ""
|
112
|
+
end
|
113
|
+
|
114
|
+
# Image operators supported by ImageMagick
|
115
|
+
IMAGE_OPERATORS_METHODS = %w{
|
116
|
+
alpha auto-orient bench black-threshold bordercolor charcoal clip clip-mask clip-path colorize
|
117
|
+
contrast convolve cycle decipher deskew despeckle distort edge encipher emboss enhance equalize
|
118
|
+
evaluate flip flop function gamma identify implode layers level level-colors median modulate monochrome
|
119
|
+
negate noise normalize opaque ordered-dither NxN paint polaroid posterize print profile quantize
|
120
|
+
radial-blur Raise random-threshold recolor render rotate segment sepia-tone set shade solarize
|
121
|
+
sparse-color spread strip swirl threshold tile tint transform transparent transpose transverse trim
|
122
|
+
type unique-colors white-threshold
|
123
|
+
|
124
|
+
adaptive-blur adaptive-resize adaptive-sharpen annotate blur border chop contrast-stretch extent
|
125
|
+
extract frame gaussian-blur geometry lat linear-stretch liquid-rescale motion-blur region repage
|
126
|
+
resample resize roll sample scale selective-blur shadow sharpen shave shear sigmoidal-contrast
|
127
|
+
sketch splice thumbnail unsharp vignette wave
|
128
|
+
|
129
|
+
append average clut coalesce combine composite deconstruct flatten fx hald-clut morph mosaic process reverse separate write
|
130
|
+
crop
|
131
|
+
}
|
132
|
+
|
133
|
+
# methods that are called with (=)
|
134
|
+
WITH_EQUAL_METHODS =
|
135
|
+
%w{alpha background bias black-point-compensation blue-primary border bordercolor caption
|
136
|
+
cahnnel colors colorspace comment compose compress depth density encoding endian family fill filter
|
137
|
+
font format frame fuzz geometry gravity label mattecolor page pointsize quality stroke strokewidth
|
138
|
+
undercolor units weight
|
139
|
+
brodercolor transparent type size}
|
140
|
+
|
141
|
+
# methods that takes geometry options
|
142
|
+
WITH_GEOMETRY_METHODS =
|
143
|
+
%w{density page sampling-factor size tile-offset adaptive-blur adaptive-resize adaptive-sharpen
|
144
|
+
annotate blur border chop contrast-stretch extent extract frame gaussian-blur
|
145
|
+
geometry lat linear-stretch liquid-rescale motion-blur region repage resample resize roll
|
146
|
+
sample scale selective-blur shadow sharpen shave shear sigmoidal-contrast sketch
|
147
|
+
splice thumbnail unsharp vignette wave crop}
|
148
|
+
|
149
|
+
# Methods that need special treatment. This array is used just to keep track of them.
|
150
|
+
SPECIAL_COMMANDS =
|
151
|
+
%w{floodfill antialias draw}
|
152
|
+
|
153
|
+
IMAGE_SETTINGS_METHODS.each do |method|
|
154
|
+
if WITH_EQUAL_METHODS.include?(method)
|
155
|
+
define_method((method+'=').to_sym) do |arg|
|
156
|
+
append_to_settings(method, arg)
|
157
|
+
end
|
158
|
+
elsif WITH_GEOMETRY_METHODS.include?(method)
|
159
|
+
define_method((method).to_sym) do |*args|
|
160
|
+
append_to_settings(method, QuickMagick::geometry(*args) )
|
161
|
+
end
|
162
|
+
else
|
163
|
+
define_method(method.to_sym) do |*args|
|
164
|
+
append_to_settings(method, args.join(" "))
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
IMAGE_OPERATORS_METHODS.each do |method|
|
170
|
+
if WITH_EQUAL_METHODS.include?(method)
|
171
|
+
define_method((method+'=').to_sym) do |arg|
|
172
|
+
append_to_operators(method, arg )
|
173
|
+
end
|
174
|
+
elsif WITH_GEOMETRY_METHODS.include?(method)
|
175
|
+
define_method((method).to_sym) do |*args|
|
176
|
+
append_to_operators(method, QuickMagick::geometry(*args) )
|
177
|
+
end
|
178
|
+
else
|
179
|
+
define_method(method.to_sym) do |*args|
|
180
|
+
append_to_operators(method, args.join(" "))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Fills a rectangle with a solid color
|
186
|
+
def floodfill(width, height=nil, x=nil, y=nil, flag=nil, color=nil)
|
187
|
+
append_to_operators "floodfill", QuickMagick::geometry(width, height, x, y, flag), color
|
188
|
+
end
|
189
|
+
|
190
|
+
# Enables/Disables flood fill. Pass a boolean argument.
|
191
|
+
def antialias=(flag)
|
192
|
+
append_basic flag ? '-antialias' : '+antialias'
|
193
|
+
end
|
194
|
+
|
195
|
+
# define attribute readers (getters)
|
196
|
+
attr_reader :image_filename
|
197
|
+
alias original_filename image_filename
|
198
|
+
|
199
|
+
# constructor
|
200
|
+
def initialize(filename, index=0, info_line=nil, pseudo_image=false)
|
201
|
+
@image_filename = filename
|
202
|
+
@index = index
|
203
|
+
@pseudo_image = pseudo_image
|
204
|
+
if info_line
|
205
|
+
@image_infoline = info_line.split
|
206
|
+
@image_infoline[0..1] = @image_infoline[0..1].join(' ') while @image_infoline.size > 1 && !@image_infoline[0].start_with?(image_filename)
|
207
|
+
end
|
208
|
+
@arguments = ""
|
209
|
+
end
|
210
|
+
|
211
|
+
# The command line so far that will be used to convert or save the image
|
212
|
+
def command_line
|
213
|
+
%Q< "(" #{@arguments} #{QuickMagick.c(image_filename + (@pseudo_image ? "" : "[#{@index}]"))} ")" >
|
214
|
+
end
|
215
|
+
|
216
|
+
# An information line about the image obtained using 'identify' command line
|
217
|
+
def image_infoline
|
218
|
+
return nil if @pseudo_image
|
219
|
+
unless @image_infoline
|
220
|
+
@image_infoline = QuickMagick::Image::identify(command_line).split
|
221
|
+
@image_infoline[0..1] = @image_infoline[0..1].join(' ') while @image_infoline.size > 1 && !@image_infoline[0].start_with?(image_filename)
|
222
|
+
end
|
223
|
+
@image_infoline
|
224
|
+
end
|
225
|
+
|
226
|
+
# converts options passed to any primitive to a string that can be passed to ImageMagick
|
227
|
+
# options allowed are:
|
228
|
+
# * rotate degrees
|
229
|
+
# * translate dx,dy
|
230
|
+
# * scale sx,sy
|
231
|
+
# * skewX degrees
|
232
|
+
# * skewY degrees
|
233
|
+
# * gravity NorthWest, North, NorthEast, West, Center, East, SouthWest, South, or SouthEast
|
234
|
+
# * stroke color
|
235
|
+
# * fill color
|
236
|
+
# The rotate primitive rotates subsequent shape primitives and text primitives about the origin of the main image.
|
237
|
+
# If you set the region before the draw command, the origin for transformations is the upper left corner of the region.
|
238
|
+
# The translate primitive translates subsequent shape and text primitives.
|
239
|
+
# The scale primitive scales them.
|
240
|
+
# The skewX and skewY primitives skew them with respect to the origin of the main image or the region.
|
241
|
+
# The text gravity primitive only affects the placement of text and does not interact with the other primitives.
|
242
|
+
# It is equivalent to using the gravity method, except that it is limited in scope to the draw_text option in which it appears.
|
243
|
+
def options_to_str(options)
|
244
|
+
options.to_a.flatten.join " "
|
245
|
+
end
|
246
|
+
|
247
|
+
# Converts an array of coordinates to a string that can be passed to polygon, polyline and bezier
|
248
|
+
def points_to_str(points)
|
249
|
+
raise QuickMagick::QuickMagickError, "Points must be an even number of coordinates" if points.size.odd?
|
250
|
+
points_str = ""
|
251
|
+
points.each_slice(2) do |point|
|
252
|
+
points_str << point.join(",") << " "
|
253
|
+
end
|
254
|
+
points_str
|
255
|
+
end
|
256
|
+
|
257
|
+
# The shape primitives are drawn in the color specified by the preceding -fill setting.
|
258
|
+
# For unfilled shapes, use -fill none.
|
259
|
+
# You can optionally control the stroke (the "outline" of a shape) with the -stroke and -strokewidth settings.
|
260
|
+
|
261
|
+
# draws a point at the given location in pixels
|
262
|
+
# A point primitive is specified by a single point in the pixel plane, that is, by an ordered pair
|
263
|
+
# of integer coordinates, x,y.
|
264
|
+
# (As it involves only a single pixel, a point primitive is not affected by -stroke or -strokewidth.)
|
265
|
+
def draw_point(x, y, options={})
|
266
|
+
append_to_operators("draw", "#{options_to_str(options)} point #{x},#{y}")
|
267
|
+
end
|
268
|
+
|
269
|
+
# draws a line between the given two points
|
270
|
+
# A line primitive requires a start point and end point.
|
271
|
+
def draw_line(x0, y0, x1, y1, options={})
|
272
|
+
append_to_operators("draw", "#{options_to_str(options)} line #{x0},#{y0} #{x1},#{y1}")
|
273
|
+
end
|
274
|
+
|
275
|
+
# draw a rectangle with the given two corners
|
276
|
+
# A rectangle primitive is specified by the pair of points at the upper left and lower right corners.
|
277
|
+
def draw_rectangle(x0, y0, x1, y1, options={})
|
278
|
+
append_to_operators("draw", "#{options_to_str(options)} rectangle #{x0},#{y0} #{x1},#{y1}")
|
279
|
+
end
|
280
|
+
|
281
|
+
# draw a rounded rectangle with the given two corners
|
282
|
+
# wc and hc are the width and height of the arc
|
283
|
+
# A roundRectangle primitive takes the same corner points as a rectangle
|
284
|
+
# followed by the width and height of the rounded corners to be removed.
|
285
|
+
def draw_round_rectangle(x0, y0, x1, y1, wc, hc, options={})
|
286
|
+
append_to_operators("draw", "#{options_to_str(options)} roundRectangle #{x0},#{y0} #{x1},#{y1} #{wc},#{hc}")
|
287
|
+
end
|
288
|
+
|
289
|
+
# The arc primitive is used to inscribe an elliptical segment in to a given rectangle.
|
290
|
+
# An arc requires the two corners used for rectangle (see above) followed by
|
291
|
+
# the start and end angles of the arc of the segment segment (e.g. 130,30 200,100 45,90).
|
292
|
+
# The start and end points produced are then joined with a line segment and the resulting segment of an ellipse is filled.
|
293
|
+
def draw_arc(x0, y0, x1, y1, a0, a1, options={})
|
294
|
+
append_to_operators("draw", "#{options_to_str(options)} arc #{x0},#{y0} #{x1},#{y1} #{a0},#{a1}")
|
295
|
+
end
|
296
|
+
|
297
|
+
# Use ellipse to draw a partial (or whole) ellipse.
|
298
|
+
# Give the center point, the horizontal and vertical "radii"
|
299
|
+
# (the semi-axes of the ellipse) and start and end angles in degrees (e.g. 100,100 100,150 0,360).
|
300
|
+
def draw_ellipse(x0, y0, rx, ry, a0, a1, options={})
|
301
|
+
append_to_operators("draw", "#{options_to_str(options)} ellipse #{x0},#{y0} #{rx},#{ry} #{a0},#{a1}")
|
302
|
+
end
|
303
|
+
|
304
|
+
# The circle primitive makes a disk (filled) or circle (unfilled). Give the center and any point on the perimeter (boundary).
|
305
|
+
def draw_circle(x0, y0, x1, y1, options={})
|
306
|
+
append_to_operators("draw", "#{options_to_str(options)} circle #{x0},#{y0} #{x1},#{y1}")
|
307
|
+
end
|
308
|
+
|
309
|
+
# The polyline primitive requires three or more points to define their perimeters.
|
310
|
+
# A polyline is simply a polygon in which the final point is not stroked to the start point.
|
311
|
+
# When unfilled, this is a polygonal line. If the -stroke setting is none (the default), then a polyline is identical to a polygon.
|
312
|
+
# points - A single array with each pair forming a coordinate in the form (x, y).
|
313
|
+
# e.g. [0,0,100,100,100,0] will draw a polyline between points (0,0)-(100,100)-(100,0)
|
314
|
+
def draw_polyline(points, options={})
|
315
|
+
append_to_operators("draw", "#{options_to_str(options)} polyline #{points_to_str(points)}")
|
316
|
+
end
|
317
|
+
|
318
|
+
# The polygon primitive requires three or more points to define their perimeters.
|
319
|
+
# A polyline is simply a polygon in which the final point is not stroked to the start point.
|
320
|
+
# When unfilled, this is a polygonal line. If the -stroke setting is none (the default), then a polyline is identical to a polygon.
|
321
|
+
# points - A single array with each pair forming a coordinate in the form (x, y).
|
322
|
+
# e.g. [0,0,100,100,100,0] will draw a polygon between points (0,0)-(100,100)-(100,0)
|
323
|
+
def draw_polygon(points, options={})
|
324
|
+
append_to_operators("draw", "#{options_to_str(options)} polygon #{points_to_str(points)}")
|
325
|
+
end
|
326
|
+
|
327
|
+
# The Bezier primitive creates a spline curve and requires three or points to define its shape.
|
328
|
+
# The first and last points are the knots and these points are attained by the curve,
|
329
|
+
# while any intermediate coordinates are control points.
|
330
|
+
# If two control points are specified, the line between each end knot and its sequentially
|
331
|
+
# respective control point determines the tangent direction of the curve at that end.
|
332
|
+
# If one control point is specified, the lines from the end knots to the one control point
|
333
|
+
# determines the tangent directions of the curve at each end.
|
334
|
+
# If more than two control points are specified, then the additional control points
|
335
|
+
# act in combination to determine the intermediate shape of the curve.
|
336
|
+
# In order to draw complex curves, it is highly recommended either to use the path primitive
|
337
|
+
# or to draw multiple four-point bezier segments with the start and end knots of each successive segment repeated.
|
338
|
+
def draw_bezier(points, options={})
|
339
|
+
append_to_operators("draw", "#{options_to_str(options)} bezier #{points_to_str(points)}")
|
340
|
+
end
|
341
|
+
|
342
|
+
# A path represents an outline of an object, defined in terms of moveto
|
343
|
+
# (set a new current point), lineto (draw a straight line), curveto (draw a Bezier curve),
|
344
|
+
# arc (elliptical or circular arc) and closepath (close the current shape by drawing a
|
345
|
+
# line to the last moveto) elements.
|
346
|
+
# Compound paths (i.e., a path with subpaths, each consisting of a single moveto followed by
|
347
|
+
# one or more line or curve operations) are possible to allow effects such as donut holes in objects.
|
348
|
+
# (See http://www.w3.org/TR/SVG/paths.html)
|
349
|
+
def draw_path(path_spec, options={})
|
350
|
+
append_to_operators("draw", "#{options_to_str(options)} path #{path_spec}")
|
351
|
+
end
|
352
|
+
|
353
|
+
# Use image to composite an image with another image. Follow the image keyword
|
354
|
+
# with the composite operator, image location, image size, and filename
|
355
|
+
# You can use 0,0 for the image size, which means to use the actual dimensions found in the image header.
|
356
|
+
# Otherwise, it is scaled to the given dimensions. See -compose for a description of the composite operators.
|
357
|
+
def draw_image(operator, x0, y0, w, h, image_filename, options={})
|
358
|
+
append_to_operators("draw", "#{options_to_str(options)} image #{operator} #{x0},#{y0} #{w},#{h} \"#{image_filename}\"")
|
359
|
+
end
|
360
|
+
|
361
|
+
# Use text to annotate an image with text. Follow the text coordinates with a string.
|
362
|
+
def draw_text(x0, y0, text, options={})
|
363
|
+
append_to_operators("draw", "#{options_to_str(options)} text #{x0},#{y0} '#{text.gsub('"', '\"').gsub("'", "\\\\'")}'")
|
364
|
+
end
|
365
|
+
|
366
|
+
# saves the current image to the given filename
|
367
|
+
def save(output_filename)
|
368
|
+
result = QuickMagick.exec3 "convert #{command_line} #{QuickMagick.c output_filename}"
|
369
|
+
if @pseudo_image
|
370
|
+
# since it's been saved, convert it to normal image (not pseudo)
|
371
|
+
initialize(output_filename)
|
372
|
+
revert!
|
373
|
+
end
|
374
|
+
return result
|
375
|
+
end
|
376
|
+
|
377
|
+
alias write save
|
378
|
+
alias convert save
|
379
|
+
|
380
|
+
# saves the current image overwriting the original image file
|
381
|
+
def save!
|
382
|
+
raise QuickMagick::QuickMagickError, "Cannot mogrify a pseudo image" if @pseudo_image
|
383
|
+
result = QuickMagick.exec3 "mogrify #{command_line}"
|
384
|
+
# remove all operations to avoid duplicate operations
|
385
|
+
revert!
|
386
|
+
return result
|
387
|
+
end
|
388
|
+
|
389
|
+
alias write! save!
|
390
|
+
alias mogrify! save!
|
391
|
+
|
392
|
+
def to_blob
|
393
|
+
tmp_file = Tempfile.new(QuickMagick::random_string)
|
394
|
+
if command_line =~ /-format\s(\S+)\s/
|
395
|
+
# use format set up by user
|
396
|
+
blob_format = $1
|
397
|
+
elsif !@pseudo_image
|
398
|
+
# use original image format
|
399
|
+
blob_format = self.format
|
400
|
+
else
|
401
|
+
# default format is jpg
|
402
|
+
blob_format = 'jpg'
|
403
|
+
end
|
404
|
+
save "#{blob_format}:#{tmp_file.path}"
|
405
|
+
blob = nil
|
406
|
+
File.open(tmp_file.path, 'rb') { |f| blob = f.read}
|
407
|
+
blob
|
408
|
+
end
|
409
|
+
|
410
|
+
# image file format
|
411
|
+
def format
|
412
|
+
image_infoline[1]
|
413
|
+
end
|
414
|
+
|
415
|
+
# columns of image in pixels
|
416
|
+
def columns
|
417
|
+
image_infoline[2].split('x').first.to_i
|
418
|
+
end
|
419
|
+
|
420
|
+
alias width columns
|
421
|
+
|
422
|
+
# rows of image in pixels
|
423
|
+
def rows
|
424
|
+
image_infoline[2].split('x').last.to_i
|
425
|
+
end
|
426
|
+
|
427
|
+
alias height rows
|
428
|
+
|
429
|
+
# Bit depth
|
430
|
+
def bit_depth
|
431
|
+
image_infoline[4].to_i
|
432
|
+
end
|
433
|
+
|
434
|
+
# Number of different colors used in this image
|
435
|
+
def colors
|
436
|
+
image_infoline[6].to_i
|
437
|
+
end
|
438
|
+
|
439
|
+
# returns size of image in bytes
|
440
|
+
def size
|
441
|
+
File.size?(image_filename)
|
442
|
+
end
|
443
|
+
|
444
|
+
# Reads a pixel from the image.
|
445
|
+
# WARNING: This is done through command line which is very slow.
|
446
|
+
# It is not recommended at all to use this method for image processing for example.
|
447
|
+
def get_pixel(x, y)
|
448
|
+
result = QuickMagick.exec3("identify -verbose -crop #{QuickMagick::geometry(1,1,x,y)} #{QuickMagick.c image_filename}[#{@index}]")
|
449
|
+
result =~ /Histogram:\s*\d+:\s*\(\s*(\d+),\s*(\d+),\s*(\d+)\)/
|
450
|
+
return [$1.to_i, $2.to_i, $3.to_i]
|
451
|
+
end
|
452
|
+
|
453
|
+
# Returns details of the image as found by "identify -verbose" command
|
454
|
+
def details
|
455
|
+
str_details = QuickMagick.exec3("identify -verbose #{QuickMagick.c image_filename}[#@index]")
|
456
|
+
# This is something like breadcrumb for hashes visited at any time
|
457
|
+
hash_stack = []
|
458
|
+
# Current indentation. Used to infer nesting using indentation
|
459
|
+
current_indent = ""
|
460
|
+
# Last key inserted in the hash. Used to determine which key is the parent of a nested hash
|
461
|
+
last_key = nil
|
462
|
+
# Start with top level empty hash
|
463
|
+
hash_stack.push({})
|
464
|
+
str_details.each_line do |line|
|
465
|
+
key, value = line.split(":", 2)
|
466
|
+
key_indent = key[/^\s*/]
|
467
|
+
if key_indent.length < current_indent.length
|
468
|
+
# Must go one level up
|
469
|
+
hash_stack.pop
|
470
|
+
elsif key_indent.length > current_indent.length
|
471
|
+
# Must go one level deeper using last key
|
472
|
+
hash_stack.last[last_key] = {}
|
473
|
+
hash_stack.push hash_stack.last[last_key]
|
474
|
+
end
|
475
|
+
current_indent = key_indent
|
476
|
+
|
477
|
+
key = key.strip
|
478
|
+
value = value.strip
|
479
|
+
hash_stack.last[key] = value
|
480
|
+
last_key = key
|
481
|
+
end
|
482
|
+
hash_stack.first
|
483
|
+
end
|
484
|
+
|
485
|
+
# displays the current image as animated image
|
486
|
+
def animate
|
487
|
+
`animate #{command_line}`
|
488
|
+
end
|
489
|
+
|
490
|
+
# displays the current image to the x-windowing system
|
491
|
+
def display
|
492
|
+
`display #{command_line}`
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|