aseldawy-quick_magick 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ README
2
+ Rakefile
3
+ lib/quick_magick/image.rb
4
+ lib/quick_magick/image_list.rb
5
+ lib/quick_magick.rb
6
+ Manifest
7
+ quick_magick.gemspec
8
+ test/image_test.rb
9
+ test/badfile.xxx
10
+ test/multipage.tif
11
+ test/image_list_test.rb
12
+ test/test_magick.rb
13
+ test/9.gif
data/README ADDED
@@ -0,0 +1,186 @@
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 images 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 and has an advantage of allowing you to access single pixels 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 a bit 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 generate 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
+ (new) 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
+ For more information on drawing API visit:
183
+ http://www.imagemagick.org/Usage/draw/
184
+
185
+ Check all command line options of ImageMagick at:
186
+ http://www.imagemagick.org/script/convert.php
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('quick_magick', '0.6.0') do |p|
6
+ p.description = "QuickMagick allows you to access ImageMagick command line functions using Ruby interface."
7
+ p.url = "http://quickmagick.rubyforge.org/"
8
+ p.author = "Ahmed ElDawy"
9
+ p.email = "ahmed.eldawy@badrit.com"
10
+ p.project = "quickmagick"
11
+ end
12
+
13
+ #Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
14
+
@@ -0,0 +1,176 @@
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
+
59
+ class << self
60
+ # Generate a random string of specified length.
61
+ # Used to generate random names for temp files
62
+ def random_string(length=10)
63
+ @@CHARS ||= ("a".."z").to_a + ("1".."9").to_a
64
+ Array.new(length, '').collect{@@CHARS[rand(@@CHARS.size)]}.join
65
+ end
66
+
67
+ # Encodes a geometry string with the given options
68
+ def geometry(width, height=nil, x=nil, y=nil, flag=nil)
69
+ geometry_string = ""
70
+ geometry_string << width.to_s if width
71
+ geometry_string << 'x' << height.to_s if height
72
+ geometry_string << '+' << x.to_s if x
73
+ geometry_string << '+' << y.to_s if y
74
+ geometry_string << flag if flag
75
+ geometry_string
76
+ end
77
+
78
+ # Returns a formatted string for the color with the given components
79
+ # each component could take one of the following values
80
+ # * an integer from 0 to 255
81
+ # * a float from 0.0 to 1.0
82
+ # * a string showing percentage from "0%" to "100%"
83
+ def rgba_color(red, green, blue, alpha=255)
84
+ "#%02x%02x%02x%02x" % [red, green, blue, alpha].collect do |component|
85
+ case component
86
+ when Integer then component
87
+ when Float then Integer(component*255)
88
+ when String then Integer(component.sub('%', '')) * 255 / 100
89
+ end
90
+ end
91
+ end
92
+
93
+ alias rgb_color rgba_color
94
+
95
+ # Returns a formatted string for a gray color with the given level and alpha.
96
+ # level and alpha could take one of the following values
97
+ # * an integer from 0 to 255
98
+ # * a float from 0.0 to 1.0
99
+ # * a string showing percentage from "0%" to "100%"
100
+ def graya_color(level, alpha=255)
101
+ rgba_color(level, level, level, alpha)
102
+ end
103
+
104
+ alias gray_color graya_color
105
+
106
+ # HSL colors are encoding as a triple (hue, saturation, lightness).
107
+ # Hue is represented as an angle of the color circle (i.e. the rainbow represented in a circle).
108
+ # This angle is so typically measured in degrees that the unit is implicit in CSS;
109
+ # syntactically, only a number is given. By definition red=0=360,
110
+ # and the other colors are spread around the circle, so green=120, blue=240, etc.
111
+ # As an angle, it implicitly wraps around such that -120=240 and 480=120, for instance.
112
+ # (Students of trigonometry would say that "coterminal angles are equivalent" here;
113
+ # an angle (theta) can be standardized by computing the equivalent angle, (theta) mod 360.)
114
+ #
115
+ # Saturation and lightness are represented as percentages.
116
+ # 100% is full saturation, and 0% is a shade of grey.
117
+ # 0% lightness is black, 100% lightness is white, and 50% lightness is 'normal'.
118
+ #
119
+ # Hue can take one of the following values:
120
+ # * an integer from 0...360 representing angle in degrees
121
+ # * a float value from 0...2*PI represeting angle in radians
122
+ #
123
+ # saturation, lightness and alpha can take one of the following values:
124
+ # * an integer from 0 to 255
125
+ # * a float from 0.0 to 1.0
126
+ # * a string showing percentage from "0%" to "100%"
127
+ def hsla_color(hue, saturation, lightness, alpha=1.0)
128
+ components = [case hue
129
+ when Integer then hue
130
+ when Float then Integer(hue * 360 / 2 / Math::PI)
131
+ end]
132
+ components += [saturation, lightness].collect do |component|
133
+ case component
134
+ when Integer then (component * 100.0 / 255).round
135
+ when Float then Integer(component*100)
136
+ when String then Integer(component.sub('%', ''))
137
+ end
138
+ end
139
+ components << case alpha
140
+ when Integer then alpha * 100.0 / 255
141
+ when Float then alpha
142
+ when String then Float(alpha.sub('%', '')) / 100.0
143
+ end
144
+ "hsla(%d,%d%%,%d%%,%g)" % components
145
+ end
146
+
147
+ alias hsl_color hsla_color
148
+
149
+ # Escapes possible special chracters in command line by surrounding it with double quotes
150
+ def escape_commandline(str)
151
+ str =~ /^(\w|\s|\.)*$/ ? str : "\"#{str}\""
152
+ end
153
+
154
+ alias c escape_commandline
155
+ end
156
+ end
157
+
158
+ # For backward compatibility with ruby < 1.8.7
159
+ unless "".respond_to? :start_with?
160
+ class String
161
+ def start_with?(x)
162
+ self.index(x) == 0
163
+ end
164
+ end
165
+ end
166
+
167
+ unless "".respond_to? :end_with?
168
+ class String
169
+ def end_with?(x)
170
+ self.index(x) == self.length - 1
171
+ end
172
+ end
173
+ end
174
+
175
+ require 'quick_magick/image'
176
+ require 'quick_magick/image_list'
@@ -0,0 +1,458 @@
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(%Q<"#{filename}">)
20
+ info_lines = info.split(/[\r\n]/)
21
+ images = []
22
+ if info_lines.size == 1
23
+ images << Image.new(filename, info_lines.first)
24
+ else
25
+ info_lines.each_with_index do |info_line, i|
26
+ images << Image.new("#{filename}[#{i.to_s}]", info_line)
27
+ end
28
+ end
29
+ images.each(&proc) if block_given?
30
+ return images
31
+ end
32
+
33
+ alias open read
34
+
35
+ # Creates a new image initially set to gradient
36
+ # Default gradient is linear gradient from black to white
37
+ def gradient(width, height, type=QuickMagick::LinearGradient, color1=nil, color2=nil)
38
+ template_name = type + ":"
39
+ template_name << color1.to_s if color1
40
+ template_name << '-' << color2.to_s if color2
41
+ i = self.new(template_name, nil, true)
42
+ i.size = QuickMagick::geometry(width, height)
43
+ i
44
+ end
45
+
46
+ # Creates an image with solid color
47
+ def solid(width, height, color=nil)
48
+ template_name = QuickMagick::SolidColor+":"
49
+ template_name << color.to_s if color
50
+ i = self.new(template_name, nil, true)
51
+ i.size = QuickMagick::geometry(width, height)
52
+ i
53
+ end
54
+
55
+ # Creates an image from pattern
56
+ def pattern(width, height, pattern)
57
+ raise QuickMagick::QuickMagickError, "Invalid pattern '#{pattern.to_s}'" unless QuickMagick::Patterns.include?(pattern.to_s)
58
+ template_name = "pattern:#{pattern.to_s}"
59
+ i = self.new(template_name, nil, true)
60
+ i.size = QuickMagick::geometry(width, height)
61
+ i
62
+ end
63
+
64
+ # returns info for an image using <code>identify</code> command
65
+ def identify(filename)
66
+ result = `identify #{filename} 2>&1`
67
+ unless $?.success?
68
+ raise QuickMagick::QuickMagickError, "Illegal file \"#{filename}\""
69
+ end
70
+ result
71
+ end
72
+
73
+ end
74
+
75
+ # append the given option, value pair to the settings of the current image
76
+ def append_to_settings(arg, value=nil)
77
+ @arguments << %Q<-#{arg} #{QuickMagick::c value} >
78
+ @last_is_draw = false
79
+ self
80
+ end
81
+
82
+ # Image settings supported by ImageMagick
83
+ IMAGE_SETTINGS_METHODS = %w{
84
+ adjoin affine alpha antialias 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 antialias 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 floodfill 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
+ IMAGE_SETTINGS_METHODS.each do |method|
150
+ if WITH_EQUAL_METHODS.include?(method)
151
+ define_method((method+'=').to_sym) do |arg|
152
+ append_to_settings(method, arg)
153
+ end
154
+ elsif WITH_GEOMETRY_METHODS.include?(method)
155
+ define_method((method).to_sym) do |*args|
156
+ append_to_settings(method, QuickMagick::geometry(*args) )
157
+ end
158
+ else
159
+ define_method(method.to_sym) do |*args|
160
+ append_to_settings(method, args.join(" "))
161
+ end
162
+ end
163
+ end
164
+
165
+ IMAGE_OPERATORS_METHODS.each do |method|
166
+ if WITH_EQUAL_METHODS.include?(method)
167
+ define_method((method+'=').to_sym) do |arg|
168
+ append_to_operators(method, arg )
169
+ end
170
+ elsif WITH_GEOMETRY_METHODS.include?(method)
171
+ define_method((method).to_sym) do |*args|
172
+ append_to_operators(method, QuickMagick::geometry(*args) )
173
+ end
174
+ else
175
+ define_method(method.to_sym) do |*args|
176
+ append_to_operators(method, args.join(" "))
177
+ end
178
+ end
179
+ end
180
+
181
+ # Fills a rectangle with a solid color
182
+ def floodfill(width, height=nil, x=nil, y=nil, flag=nil, color=nil)
183
+ append_to_operators "floodfill", QuickMagick::geometry(width, height, x, y, flag), color
184
+ end
185
+
186
+ # define attribute readers (getters)
187
+ attr_reader :image_filename
188
+ alias original_filename image_filename
189
+
190
+ # constructor
191
+ def initialize(filename, info_line=nil, pseudo_image=false)
192
+ @image_filename = filename
193
+ @pseudo_image = pseudo_image
194
+ if info_line
195
+ @image_infoline = info_line.split
196
+ @image_infoline[0..1] = @image_infoline[0..1].join(' ') while @image_infoline.size > 1 && !@image_infoline[0].start_with?(image_filename)
197
+ end
198
+ @arguments = ""
199
+ end
200
+
201
+ # The command line so far that will be used to convert or save the image
202
+ def command_line
203
+ %Q< "(" #{@arguments} #{QuickMagick::c image_filename} ")" >
204
+ end
205
+
206
+ # An information line about the image obtained using 'identify' command line
207
+ def image_infoline
208
+ return nil if @pseudo_image
209
+ unless @image_infoline
210
+ @image_infoline = QuickMagick::Image::identify(command_line).split
211
+ @image_infoline[0..1] = @image_infoline[0..1].join(' ') while @image_infoline.size > 1 && !@image_infoline[0].start_with?(image_filename)
212
+ end
213
+ @image_infoline
214
+ end
215
+
216
+ # converts options passed to any primitive to a string that can be passed to ImageMagick
217
+ # options allowed are:
218
+ # * rotate degrees
219
+ # * translate dx,dy
220
+ # * scale sx,sy
221
+ # * skewX degrees
222
+ # * skewY degrees
223
+ # * gravity NorthWest, North, NorthEast, West, Center, East, SouthWest, South, or SouthEast
224
+ # * stroke color
225
+ # * fill color
226
+ # The rotate primitive rotates subsequent shape primitives and text primitives about the origin of the main image.
227
+ # If you set the region before the draw command, the origin for transformations is the upper left corner of the region.
228
+ # The translate primitive translates subsequent shape and text primitives.
229
+ # The scale primitive scales them.
230
+ # The skewX and skewY primitives skew them with respect to the origin of the main image or the region.
231
+ # The text gravity primitive only affects the placement of text and does not interact with the other primitives.
232
+ # It is equivalent to using the gravity method, except that it is limited in scope to the draw_text option in which it appears.
233
+ def options_to_str(options)
234
+ options.to_a.flatten.join " "
235
+ end
236
+
237
+ # Converts an array of coordinates to a string that can be passed to polygon, polyline and bezier
238
+ def points_to_str(points)
239
+ raise QuickMagick::QuickMagickError, "Points must be an even number of coordinates" if points.size.odd?
240
+ points_str = ""
241
+ points.each_slice(2) do |point|
242
+ points_str << point.join(",") << " "
243
+ end
244
+ points_str
245
+ end
246
+
247
+ # The shape primitives are drawn in the color specified by the preceding -fill setting.
248
+ # For unfilled shapes, use -fill none.
249
+ # You can optionally control the stroke (the "outline" of a shape) with the -stroke and -strokewidth settings.
250
+
251
+ # draws a point at the given location in pixels
252
+ # A point primitive is specified by a single point in the pixel plane, that is, by an ordered pair
253
+ # of integer coordinates, x,y.
254
+ # (As it involves only a single pixel, a point primitive is not affected by -stroke or -strokewidth.)
255
+ def draw_point(x, y, options={})
256
+ append_to_operators("draw", "#{options_to_str(options)} point #{x},#{y}")
257
+ end
258
+
259
+ # draws a line between the given two points
260
+ # A line primitive requires a start point and end point.
261
+ def draw_line(x0, y0, x1, y1, options={})
262
+ append_to_operators("draw", "#{options_to_str(options)} line #{x0},#{y0} #{x1},#{y1}")
263
+ end
264
+
265
+ # draw a rectangle with the given two corners
266
+ # A rectangle primitive is specified by the pair of points at the upper left and lower right corners.
267
+ def draw_rectangle(x0, y0, x1, y1, options={})
268
+ append_to_operators("draw", "#{options_to_str(options)} rectangle #{x0},#{y0} #{x1},#{y1}")
269
+ end
270
+
271
+ # draw a rounded rectangle with the given two corners
272
+ # wc and hc are the width and height of the arc
273
+ # A roundRectangle primitive takes the same corner points as a rectangle
274
+ # followed by the width and height of the rounded corners to be removed.
275
+ def draw_round_rectangle(x0, y0, x1, y1, wc, hc, options={})
276
+ append_to_operators("draw", "#{options_to_str(options)} roundRectangle #{x0},#{y0} #{x1},#{y1} #{wc},#{hc}")
277
+ end
278
+
279
+ # The arc primitive is used to inscribe an elliptical segment in to a given rectangle.
280
+ # An arc requires the two corners used for rectangle (see above) followed by
281
+ # the start and end angles of the arc of the segment segment (e.g. 130,30 200,100 45,90).
282
+ # The start and end points produced are then joined with a line segment and the resulting segment of an ellipse is filled.
283
+ def draw_arc(x0, y0, x1, y1, a0, a1, options={})
284
+ append_to_operators("draw", "#{options_to_str(options)} arc #{x0},#{y0} #{x1},#{y1} #{a0},#{a1}")
285
+ end
286
+
287
+ # Use ellipse to draw a partial (or whole) ellipse.
288
+ # Give the center point, the horizontal and vertical "radii"
289
+ # (the semi-axes of the ellipse) and start and end angles in degrees (e.g. 100,100 100,150 0,360).
290
+ def draw_ellipse(x0, y0, rx, ry, a0, a1, options={})
291
+ append_to_operators("draw", "#{options_to_str(options)} ellipse #{x0},#{y0} #{rx},#{ry} #{a0},#{a1}")
292
+ end
293
+
294
+ # The circle primitive makes a disk (filled) or circle (unfilled). Give the center and any point on the perimeter (boundary).
295
+ def draw_circle(x0, y0, x1, y1, options={})
296
+ append_to_operators("draw", "#{options_to_str(options)} circle #{x0},#{y0} #{x1},#{y1}")
297
+ end
298
+
299
+ # The polyline primitive requires three or more points to define their perimeters.
300
+ # A polyline is simply a polygon in which the final point is not stroked to the start point.
301
+ # When unfilled, this is a polygonal line. If the -stroke setting is none (the default), then a polyline is identical to a polygon.
302
+ # points - A single array with each pair forming a coordinate in the form (x, y). e.g. [0,0,100,100,100,0] will draw a polyline between points (0,0)-(100,100)-(100,0)
303
+ def draw_polyline(points, options={})
304
+ append_to_operators("draw", "#{options_to_str(options)} polyline #{points_to_str(points)}")
305
+ end
306
+
307
+ # The polygon primitive requires three or more points to define their perimeters.
308
+ # A polyline is simply a polygon in which the final point is not stroked to the start point.
309
+ # When unfilled, this is a polygonal line. If the -stroke setting is none (the default), then a polyline is identical to a polygon.
310
+ # points - A single array with each pair forming a coordinate in the form (x, y). e.g. [0,0,100,100,100,0] will draw a polygon between points (0,0)-(100,100)-(100,0)
311
+ def draw_polygon(points, options={})
312
+ append_to_operators("draw", "#{options_to_str(options)} polygon #{points_to_str(points)}")
313
+ end
314
+
315
+ # The Bezier primitive creates a spline curve and requires three or points to define its shape.
316
+ # The first and last points are the knots and these points are attained by the curve,
317
+ # while any intermediate coordinates are control points.
318
+ # If two control points are specified, the line between each end knot and its sequentially
319
+ # respective control point determines the tangent direction of the curve at that end.
320
+ # If one control point is specified, the lines from the end knots to the one control point
321
+ # determines the tangent directions of the curve at each end.
322
+ # If more than two control points are specified, then the additional control points
323
+ # act in combination to determine the intermediate shape of the curve.
324
+ # In order to draw complex curves, it is highly recommended either to use the path primitive
325
+ # or to draw multiple four-point bezier segments with the start and end knots of each successive segment repeated.
326
+ def draw_bezier(points, options={})
327
+ append_to_operators("draw", "#{options_to_str(options)} bezier #{points_to_str(points)}")
328
+ end
329
+
330
+ # A path represents an outline of an object, defined in terms of moveto
331
+ # (set a new current point), lineto (draw a straight line), curveto (draw a Bezier curve),
332
+ # arc (elliptical or circular arc) and closepath (close the current shape by drawing a
333
+ # line to the last moveto) elements.
334
+ # Compound paths (i.e., a path with subpaths, each consisting of a single moveto followed by
335
+ # one or more line or curve operations) are possible to allow effects such as donut holes in objects.
336
+ # (See http://www.w3.org/TR/SVG/paths.html)
337
+ def draw_path(path_spec, options={})
338
+ append_to_operators("draw", "#{options_to_str(options)} path #{path_spec}")
339
+ end
340
+
341
+ # Use image to composite an image with another image. Follow the image keyword
342
+ # with the composite operator, image location, image size, and filename
343
+ # You can use 0,0 for the image size, which means to use the actual dimensions found in the image header.
344
+ # Otherwise, it is scaled to the given dimensions. See -compose for a description of the composite operators.
345
+ def draw_image(operator, x0, y0, w, h, image_filename, options={})
346
+ append_to_operators("draw", "#{options_to_str(options)} image #{operator} #{x0},#{y0} #{w},#{h} \"#{image_filename}\"")
347
+ end
348
+
349
+ # Use text to annotate an image with text. Follow the text coordinates with a string.
350
+ def draw_text(x0, y0, text, options={})
351
+ append_to_operators("draw", "#{options_to_str(options)} text #{x0},#{y0} '#{text}'")
352
+ end
353
+
354
+ # saves the current image to the given filename
355
+ def save(output_filename)
356
+ result = `convert #{command_line} "#{output_filename}" 2>&1`
357
+ if $?.success?
358
+ if @pseudo_image
359
+ # since it's been saved, convert it to normal image (not pseudo)
360
+ initialize(output_filename)
361
+ revert!
362
+ end
363
+ return result
364
+ else
365
+ error_message = <<-ERROR
366
+ Error executing command: convert #{command_line} "#{output_filename}"
367
+ Result is: #{result}
368
+ ERROR
369
+ raise QuickMagick::QuickMagickError, error_message
370
+ end
371
+ end
372
+
373
+ alias write save
374
+ alias convert save
375
+
376
+ # saves the current image overwriting the original image file
377
+ def save!
378
+ raise QuickMagick::QuickMagickError, "Cannot mogrify a pseudo image" if @pseudo_image
379
+ result = `mogrify #{command_line}`
380
+ if $?.success?
381
+ # remove all operations to avoid duplicate operations
382
+ revert!
383
+ return result
384
+ else
385
+ error_message = <<-ERRORMSG
386
+ Error executing command: mogrify #{command_line}
387
+ Result is: #{result}
388
+ ERRORMSG
389
+ raise QuickMagick::QuickMagickError, error_message
390
+ end
391
+ end
392
+
393
+ alias write! save!
394
+ alias mogrify! save!
395
+
396
+ def to_blob
397
+ tmp_file = Tempfile.new(QuickMagick::random_string)
398
+ if command_line =~ /-format\s(\S+)\s/
399
+ # use format set up by user
400
+ blob_format = $1
401
+ elsif !@pseudo_image
402
+ # use original image format
403
+ blob_format = self.format
404
+ else
405
+ # default format is jpg
406
+ blob_format = 'jpg'
407
+ end
408
+ save "#{blob_format}:#{tmp_file.path}"
409
+ blob = nil
410
+ File.open(tmp_file.path, 'rb') { |f| blob = f.read}
411
+ blob
412
+ end
413
+
414
+ # image file format
415
+ def format
416
+ image_infoline[1]
417
+ end
418
+
419
+ # columns of image in pixels
420
+ def columns
421
+ image_infoline[2].split('x').first.to_i
422
+ end
423
+
424
+ alias width columns
425
+
426
+ # rows of image in pixels
427
+ def rows
428
+ image_infoline[2].split('x').last.to_i
429
+ end
430
+
431
+ alias height rows
432
+
433
+ # Bit depth
434
+ def bit_depth
435
+ image_infoline[4].to_i
436
+ end
437
+
438
+ # Number of different colors used in this image
439
+ def colors
440
+ image_infoline[6].to_i
441
+ end
442
+
443
+ # returns size of image in bytes
444
+ def size
445
+ File.size?(image_filename)
446
+ end
447
+
448
+ # displays the current image as animated image
449
+ def animate
450
+ `animate #{command_line}`
451
+ end
452
+
453
+ # displays the current image to the x-windowing system
454
+ def display
455
+ `display #{command_line}`
456
+ end
457
+ end
458
+ end