quick_magick 0.4.0 → 0.5.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.

Potentially problematic release.


This version of quick_magick might be problematic. Click here for more details.

data/Manifest CHANGED
@@ -6,8 +6,8 @@ lib/quick_magick.rb
6
6
  Manifest
7
7
  quick_magick.gemspec
8
8
  test/image_test.rb
9
- test/logo-small.jpg
10
9
  test/badfile.xxx
11
10
  test/multipage.tif
12
11
  test/image_list_test.rb
13
- test/imagemagick-logo.png
12
+ test/test_magick.rb
13
+ test/9.gif
data/README CHANGED
@@ -1,72 +1,125 @@
1
- == QuickMagick
1
+ = Quick Magick
2
2
 
3
+ == What is QuickMagick
3
4
  QuickMagick is a gem for easily accessing ImageMagick command line tools from Ruby programs.
4
5
 
5
- == What is different?
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
+ These are some situation when QuickMagick will be useful for you:
10
+ * Suppose you built an image gallery site that allows users to upload images. You can use QuickMagick to check uploaded images dimensions and make thumbnails for them.
11
+ * Generate captchas to check whether the user is a robot or a human.
12
+ * Generate graphical reports and charts to the user based on some calculations.
13
+ * Convert uploaded images to a format usable by your application. For example, you can transform .pdf files to .jpg so that you can display them at your web site.
14
+
15
+ == Features
16
+ * Open an existing image from disk and determine basic info like width, height.
17
+ * Open an image from blob. For example, an image read from an upload form.
18
+ * Do basic and advanced operations on images like resize, rotate, shear, motion blur and other.
19
+ * Create an image from scratch and draw basic elements on it like line, circle and text. This allows making captchas for example.
20
+ * Combine images using ImageList to make a multipage or animated images.
21
+ * API is very simple and powerful.
22
+ * Minimizes disk access by calling command line tools only when required.
23
+
24
+ == How to install
25
+ First, you should install ImageMagick (http://www.imagemagick.org/) on your machine.
26
+ Command lines of ImageMagick must be in your system path.
27
+ You can check this by running the command:
28
+ identify --version
29
+ Now to install QuickMagick just type at your command line:
30
+ gem install quick_magick
31
+ ... and it's done.
32
+ You don't have to install any libraries or compile code from source.
6
33
 
34
+ == What is different?
7
35
  But what's different from other gems like RMagick and mini-magick?
8
- RMagick is a very good gem and QuickMagick is not a replacement of it.
9
- RMagick mainpulates images in memory.
10
- This is sometimes preferable but not in all cases.
11
- It uses a huge amonut of memory when manipulating images of large sizes.
12
- QuickMagick allows you to access all the powerful commands of ImageMagick that are accessible from command line.
13
- When you need more advanced options like reading pixel values, you should go to RMagick.
14
- Another good point in QuickMagick is that it's very easy to install.
15
- It doesn't require any Magick libraries or compile something from source to be installed.
16
- A running copy of ImageMagick is enough.
17
-
18
- The idea of this gem came from MiniMagick.
19
- I used MiniMagick for a little time but I found that some advanced options are missing.
20
- For example, you cannot manipulate multipage images.
21
- Also, it uses "mogrify" and creates temporary files to simulate the "convert" command which makes it slower as it accesses the disk multiple times.
22
- Another small stuff is that it relies on method_missing to set command line arguments which is slow and not preferable.
23
-
24
- In QuickMagick I tried to solve the above problems while keeping the API as easy as possible.
25
36
 
26
- == Comparison
37
+ The story begins when I was working on a project at BadrIT (http://www.badrit.com) using Ruby on Rails.
38
+ In this projects users were uploading images in many formats like pdf, tiff and png.
39
+ We were using Flex as a front end to display and annotate these images.
40
+ Flex has a limitation in that it can open images up to 8192x8192.
41
+ Unfortunately, users were uploading images much larger than this.
42
+ Another issue is that Flex can only open .jpg, .png and .gif files.
43
+ The solution was to convert all images uploaded to one of these formats and resizing them down to at most 8192x8192.
44
+
45
+ First, I used ImageMagick as a command line tool and was calling it using system calls.
46
+ This accomplished the work perfectly buy my source code was a rubbish.
47
+ It has many lines of code to handle creating temporary files and accessing multipage tiff and pdf files.
48
+ I found RMagick at that time and decided to use it.
49
+ It caused the code to be much simple without affecting performance notably.
50
+ It worked with me well while I was using my application with test images.
51
+ Once I decided to test it with real images it failed.
52
+ 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).
53
+ When I tried to make the same operation from command line using (convert) it was working much better.
54
+ It did not finish in a second but at least it worked correctly.
55
+ The solution was to return back to command line.
56
+
57
+ I searched for alternatives and found MiniMagick.
58
+ MiniMagick is a gem that allows you to perform basic operations using ImageMagick through command line tools.
59
+ I tried to use it but it was not good enough.
60
+ First, it writes temporary images and files as it is working which makes it slow for nothing.
61
+ Second, it doesn't handle multipage images.
62
+ I tried it with a .pdf file and I found that it handled the first page only.
63
+ Third, it doesn't give an API to draw images from scratch.
64
+ Actually, I didn't need this feature, but I may need it in the future.
65
+
66
+ At this point I decided to make my own gem and QuickMagick was born.
67
+ I addressed the problems of MiniMagick while using the same main idea.
68
+ First, QuickMagick doesn't write any temporary images to disk.
69
+ It doesn't issue any command line commands till the very end when you are saving the image.
70
+ Second, I made an API similar to RMagick which allows for accessing multipage images.
71
+ Third, I added API commands to create images from scratch and drawing simple primitives on images.
72
+ I tested my gem, compared it to MiniMagick and RMagick and it pleased me.
27
73
 
74
+ == Comparison
28
75
  I've made some test benches to compare the speed of QuickMagick, MiniMagick and RMagick.
76
+ All denoted numbers are in seconds.
29
77
  Here are the results:
30
78
 
31
- Test 1: resize a normal image (20 times)
79
+ ===Test 1: resize a normal image
32
80
  user system total real
33
- mini 0.010000 0.070000 3.730000 ( 3.693978)
34
- quick 0.010000 0.040000 3.270000 ( 3.124558)
35
- rmagick 1.740000 1.610000 3.350000 ( 3.283624)
81
+ mini 0.030000 0.040000 3.640000 ( 3.585617)
82
+ quick 0.010000 0.030000 3.330000 ( 3.295369)
83
+ rmagick 1.680000 1.660000 3.340000 ( 3.150202)
36
84
 
37
- It's clear that QuickMagick is faster that MiniMagick.
38
- In this run RMagick was a little bit slower than QuickMagick.
39
- Actually, this is not always the case.
40
- Sometimes It's faster than QuickMagick.
41
- On average, we can say that they both take the same time.
85
+ It's clear that QuickMagick is faster than MiniMagick.
86
+ RMagick was the fastest as it accesses the requested operations directly without the need to load an executable file or parse a command line.
42
87
 
43
- Test 2: resize a large image
88
+ ===Test 2: resize a large image
44
89
  user system total real
45
- mini 0.000000 0.030000 58.090000 ( 33.852697)
46
- quick 0.000000 0.000000 55.820000 ( 31.492870)
90
+ mini 0.000000 0.040000 57.150000 (130.609229)
91
+ quick 0.010000 0.010000 56.510000 ( 58.426361)
47
92
 
48
93
  Again QuickMagick is faster than MiniMagick.
49
94
  However, RMagick has failed to pass this test.
50
95
  It kept working and eating memory, cpu and harddisk till I had to unplug my computer to stop it.
51
96
  So, I removed it from this test bench.
52
97
 
53
- Test 3: generate random captchas (20)
98
+ ===Test 3: generate random captchas
54
99
  user system total real
55
- quick 0.010000 0.020000 3.610000 ( 4.952026)
56
- rmagick 1.320000 1.640000 2.960000 ( 3.058445)
100
+ quick 0.000000 0.000000 0.290000 ( 3.623418)
101
+ rmagick 0.150000 0.120000 0.270000 ( 3.171975)
57
102
 
58
- In this last test, RMagick was 38% faster than QuickMagick.
103
+ In this last test, RMagick was about 12% faster than QuickMagick.
59
104
  This is normal because it works in memory and doesn't have to parse a command line string to know what to draw.
60
105
  I couldn't test MiniMagick for this because it doesn't support an API for drawing functions.
61
106
 
62
- == Examples
107
+ == Conclusion
108
+ QuickMagick is very easy to install, very easy to use and allows you to access most features of ImageMagick.
109
+ RMagick is a bit faster and has an advantage of allowing you to access single pixels but it's a bit hard to install.
110
+ Also RMagick sometimes fail when it tries to handle large images.
111
+ MiniMagick is proved to be a bit slower than QuickMagick with no advantage.
112
+ So, it's better to use QuickMagick when your application is not image-centric.
113
+ This means, you're not going to build an image manipulation tool or something like this.
114
+ For normal operations like resize, rotate, generating captchas ... etc, QuickMagick will be a good friend of you.
63
115
 
116
+ == Examples
64
117
  Determine image information
65
118
  i = QuickMagick::Image.read('test.jpg').first
66
119
  i.width # Retrieves width in pixels
67
120
  i.height # Retrieves height in pixels
68
121
 
69
- Resize and image
122
+ Resize an image
70
123
  i = QuickMagick::Image.read('test.jpg').first
71
124
  i.resize "300x300!"
72
125
  i.save "resized_image.jpg"
@@ -80,7 +133,7 @@ Resize and image
80
133
 
81
134
  Access multipage image
82
135
  i = QuickMagick::Image.read("multipage.pdf") {|image| image.density = 300}
83
- i.size % number of pages
136
+ i.size # number of pages
84
137
  i.each_with_index do |page, i|
85
138
  i.save "page_#{i}.jpg"
86
139
  end
@@ -99,14 +152,15 @@ You can also display an image to Xserver
99
152
  i = QuickMagick::Image.read('test.jpg')
100
153
  i.display
101
154
 
102
- QuickMagick supports also ImageLists
155
+ QuickMagick supports also ImageList s
156
+ # Batch generate a list of jpg files to gif while resizing them
103
157
  il = QuickMagick::ImageList.new('test.jpg', 'test2.jpg')
104
158
  il << QuickMagick::Image.read('test3.jpg')
105
159
  il.format = 'gif'
106
160
  il.resize "300x300>"
107
161
  il.save!
108
162
 
109
- You can also create images from scratch
163
+ (new) You can also create images from scratch
110
164
  # Create a 300x300 gradient image from yellow to red
111
165
  i1 = QuickMagick::Image::gradient(300, 300, QuickMagick::RadialGradient, :yellow, :red)
112
166
  i1.save 'gradient.png'
@@ -121,5 +175,8 @@ You can also create images from scratch
121
175
  i.draw_text(30, 30, "Hello world!", :rotate=>45)
122
176
  i.save 'hello.jpg'
123
177
 
124
- Check all command line options of ImageMagick at
178
+ For more information on drawing API visit:
179
+ http://www.imagemagick.org/Usage/draw/
180
+
181
+ Check all command line options of ImageMagick at:
125
182
  http://www.imagemagick.org/script/convert.php
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('quick_magick', '0.4.0') do |p|
5
+ Echoe.new('quick_magick', '0.5.0') do |p|
6
6
  p.description = "QuickMagick allows you to access ImageMagick command line functions using Ruby interface."
7
7
  p.url = "http://quickmagick.rubyforge.org/"
8
8
  p.author = "Ahmed ElDawy"
@@ -42,7 +42,7 @@ module QuickMagick
42
42
  template_name << color1.to_s if color1
43
43
  template_name << '-' << color2.to_s if color2
44
44
  i = self.new(template_name, nil, true)
45
- i.size = QuickMagick::Image::retrieve_geometry(width, height)
45
+ i.size = QuickMagick::geometry(width, height)
46
46
  i
47
47
  end
48
48
 
@@ -51,7 +51,7 @@ module QuickMagick
51
51
  template_name = QuickMagick::SolidColor+":"
52
52
  template_name << color.to_s if color
53
53
  i = self.new(template_name, nil, true)
54
- i.size = QuickMagick::Image::retrieve_geometry(width, height)
54
+ i.size = QuickMagick::geometry(width, height)
55
55
  i
56
56
  end
57
57
 
@@ -60,7 +60,7 @@ module QuickMagick
60
60
  raise QuickMagick::QuickMagickError, "Invalid pattern '#{pattern.to_s}'" unless QuickMagick::Patterns.include?(pattern.to_s)
61
61
  template_name = "pattern:#{pattern.to_s}"
62
62
  i = self.new(template_name, nil, true)
63
- i.size = QuickMagick::Image::retrieve_geometry(width, height)
63
+ i.size = QuickMagick::geometry(width, height)
64
64
  i
65
65
  end
66
66
 
@@ -69,26 +69,12 @@ module QuickMagick
69
69
  `identify #{filename} 2>&1`
70
70
  end
71
71
 
72
- # Encodes a geometry string with the given options
73
- def retrieve_geometry(width, height=nil, x=nil, y=nil, flag=nil)
74
- geometry_string = ""
75
- geometry_string << width.to_s if width
76
- geometry_string << 'x' << height.to_s if height
77
- geometry_string << '+' << x.to_s if x
78
- geometry_string << '+' << y.to_s if y
79
- geometry_string << flag if flag
80
- geometry_string
81
- end
82
- end
83
-
84
- # append the given option, value pair to the args for the current image
85
- def append_to_operators(arg, value="")
86
- @operators << %Q<-#{arg} "#{value}" >
87
72
  end
88
73
 
89
74
  # append the given option, value pair to the settings of the current image
90
75
  def append_to_settings(arg, value="")
91
- @settings << %Q<-#{arg} "#{value}" >
76
+ @arguments << %Q<-#{arg} "#{value}" >
77
+ self
92
78
  end
93
79
 
94
80
  # Image settings supported by ImageMagick
@@ -104,10 +90,28 @@ module QuickMagick
104
90
  density page sampling-factor size tile-offset
105
91
  }
106
92
 
93
+ # append the given option, value pair to the args for the current image
94
+ def append_to_operators(arg, value="")
95
+ if @last_is_draw
96
+ @arguments.insert(@arguments.rindex('"'), " #{value}")
97
+ else
98
+ @arguments << %Q<-#{arg} "#{value}" >
99
+ end
100
+ @last_is_draw = arg == 'draw'
101
+ self
102
+ end
103
+
104
+ # Reverts this image to its last saved state.
105
+ # Note that you cannot revert an image created from scratch.
106
+ def revert!
107
+ raise QuickMagick::QuickMagickError, "Cannot revert a pseudo image" if @pseudo_image
108
+ @arguments = ""
109
+ end
110
+
107
111
  # Image operators supported by ImageMagick
108
112
  IMAGE_OPERATORS_METHODS = %w{
109
113
  alpha auto-orient bench black-threshold bordercolor charcoal clip clip-mask clip-path colorize
110
- contrast convolve cycle decipher deskew despeckle distort draw edge encipher emboss enhance equalize
114
+ contrast convolve cycle decipher deskew despeckle distort edge encipher emboss enhance equalize
111
115
  evaluate flip flop function gamma identify implode layers level level-colors median modulate monochrome
112
116
  negate noise normalize opaque ordered-dither NxN paint polaroid posterize print profile quantize
113
117
  radial-blur Raise random-threshold recolor render rotate segment sepia-tone set shade solarize
@@ -123,11 +127,6 @@ module QuickMagick
123
127
  crop
124
128
  }
125
129
 
126
- # fills a rectangle with a solid color
127
- def floodfill(width, height=nil, x=nil, y=nil, flag=nil, color=nil)
128
- append_to_operators "floodfill", QuickMagick::Image.retrieve_geometry(width, height, x, y, flag), color
129
- end
130
-
131
130
  # methods that are called with (=)
132
131
  WITH_EQUAL_METHODS =
133
132
  %w{alpha antialias background bias black-point-compensation blue-primary border bordercolor caption
@@ -151,7 +150,7 @@ module QuickMagick
151
150
  end
152
151
  elsif WITH_GEOMETRY_METHODS.include?(method)
153
152
  define_method((method).to_sym) do |*args|
154
- append_to_settings(method, QuickMagick::Image.retrieve_geometry(*args) )
153
+ append_to_settings(method, QuickMagick::geometry(*args) )
155
154
  end
156
155
  else
157
156
  define_method(method.to_sym) do |*args|
@@ -167,7 +166,7 @@ module QuickMagick
167
166
  end
168
167
  elsif WITH_GEOMETRY_METHODS.include?(method)
169
168
  define_method((method).to_sym) do |*args|
170
- append_to_operators(method, QuickMagick::Image.retrieve_geometry(*args) )
169
+ append_to_operators(method, QuickMagick::geometry(*args) )
171
170
  end
172
171
  else
173
172
  define_method(method.to_sym) do |*args|
@@ -175,6 +174,11 @@ module QuickMagick
175
174
  end
176
175
  end
177
176
  end
177
+
178
+ # Fills a rectangle with a solid color
179
+ def floodfill(width, height=nil, x=nil, y=nil, flag=nil, color=nil)
180
+ append_to_operators "floodfill", QuickMagick::geometry(width, height, x, y, flag), color
181
+ end
178
182
 
179
183
  # define attribute readers (getters)
180
184
  attr_reader :image_filename
@@ -188,13 +192,12 @@ module QuickMagick
188
192
  @image_infoline = info_line.split
189
193
  @image_infoline[0..1] = @image_infoline[0..1].join(' ') while @image_infoline.size > 1 && !@image_infoline[0].start_with?(image_filename)
190
194
  end
191
- @operators = ""
192
- @settings = ""
195
+ @arguments = ""
193
196
  end
194
197
 
195
198
  # The command line so far that will be used to convert or save the image
196
199
  def command_line
197
- %Q<#{@settings} "#{image_filename}" #{@operators}>
200
+ %Q< "(" #{@arguments} "#{image_filename}" ")" >
198
201
  end
199
202
 
200
203
  # An information line about the image obtained using 'identify' command line
@@ -214,6 +217,8 @@ module QuickMagick
214
217
  # * skewX degrees
215
218
  # * skewY degrees
216
219
  # * gravity NorthWest, North, NorthEast, West, Center, East, SouthWest, South, or SouthEast
220
+ # * stroke color
221
+ # * fill color
217
222
  # The rotate primitive rotates subsequent shape primitives and text primitives about the origin of the main image.
218
223
  # If you set the region before the draw command, the origin for transformations is the upper left corner of the region.
219
224
  # The translate primitive translates subsequent shape and text primitives.
@@ -290,6 +295,7 @@ module QuickMagick
290
295
  # The polyline primitive requires three or more points to define their perimeters.
291
296
  # A polyline is simply a polygon in which the final point is not stroked to the start point.
292
297
  # When unfilled, this is a polygonal line. If the -stroke setting is none (the default), then a polyline is identical to a polygon.
298
+ # 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)
293
299
  def draw_polyline(points, options={})
294
300
  append_to_operators("draw", "#{options_to_str(options)} polyline #{points_to_str(points)}")
295
301
  end
@@ -297,6 +303,7 @@ module QuickMagick
297
303
  # The polygon primitive requires three or more points to define their perimeters.
298
304
  # A polyline is simply a polygon in which the final point is not stroked to the start point.
299
305
  # When unfilled, this is a polygonal line. If the -stroke setting is none (the default), then a polyline is identical to a polygon.
306
+ # 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)
300
307
  def draw_polygon(points, options={})
301
308
  append_to_operators("draw", "#{options_to_str(options)} polygon #{points_to_str(points)}")
302
309
  end
@@ -362,6 +369,8 @@ module QuickMagick
362
369
  raise QuickMagick::QuickMagickError, "Cannot mogrify a pseudo image" if @pseudo_image
363
370
  result = `mogrify #{command_line}`
364
371
  if $?.success?
372
+ # remove all operations to avoid duplicate operations
373
+ revert!
365
374
  return result
366
375
  else
367
376
  error_message = <<-ERRORMSG
@@ -419,4 +428,4 @@ module QuickMagick
419
428
  `display #{command_line}`
420
429
  end
421
430
  end
422
- end
431
+ end
data/lib/quick_magick.rb CHANGED
@@ -13,12 +13,32 @@ rescue Errno::ENOENT
13
13
  end
14
14
 
15
15
  module QuickMagick
16
- # Different geometry flags
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.
17
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.
18
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.
19
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.
20
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.
21
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.
22
42
  MinimumGeometry = "^"
23
43
 
24
44
  # Command for solid color
@@ -35,11 +55,96 @@ module QuickMagick
35
55
  hs_vertical left30 left45 leftshingle octagons right30 right45 rightshingle smallfishscales
36
56
  vertical verticalbricks verticalleftshingle verticalrightshingle verticalsaw}
37
57
 
38
- # Generate a random string of specified length.
39
- # Used to generate random names for temp files
40
- def self.random_string(length=10)
41
- @@CHARS ||= ("a".."z").to_a + ("1".."9").to_a
42
- Array.new(length, '').collect{@@CHARS[rand(@@CHARS.size)]}.join
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
43
148
  end
44
149
  end
45
150
 
@@ -52,5 +157,13 @@ unless "".respond_to? :start_with?
52
157
  end
53
158
  end
54
159
 
160
+ unless "".respond_to? :end_with?
161
+ class String
162
+ def end_with?(x)
163
+ self.index(x) == self.length - 1
164
+ end
165
+ end
166
+ end
167
+
55
168
  require 'quick_magick/image'
56
169
  require 'quick_magick/image_list'
data/quick_magick.gemspec CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{quick_magick}
5
- s.version = "0.4.0"
5
+ s.version = "0.5.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Ahmed ElDawy"]
9
- s.date = %q{2009-08-05}
9
+ s.date = %q{2009-08-12}
10
10
  s.description = %q{QuickMagick allows you to access ImageMagick command line functions using Ruby interface.}
11
11
  s.email = %q{ahmed.eldawy@badrit.com}
12
12
  s.extra_rdoc_files = ["README", "lib/quick_magick/image.rb", "lib/quick_magick/image_list.rb", "lib/quick_magick.rb"]
13
- s.files = ["README", "Rakefile", "lib/quick_magick/image.rb", "lib/quick_magick/image_list.rb", "lib/quick_magick.rb", "Manifest", "quick_magick.gemspec", "test/image_test.rb", "test/logo-small.jpg", "test/badfile.xxx", "test/multipage.tif", "test/image_list_test.rb", "test/imagemagick-logo.png"]
13
+ s.files = ["README", "Rakefile", "lib/quick_magick/image.rb", "lib/quick_magick/image_list.rb", "lib/quick_magick.rb", "Manifest", "quick_magick.gemspec", "test/image_test.rb", "test/badfile.xxx", "test/multipage.tif", "test/image_list_test.rb", "test/test_magick.rb", "test/9.gif"]
14
14
  s.has_rdoc = true
15
15
  s.homepage = %q{http://quickmagick.rubyforge.org/}
16
16
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Quick_magick", "--main", "README"]
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.rubyforge_project = %q{quickmagick}
19
19
  s.rubygems_version = %q{1.3.1}
20
20
  s.summary = %q{QuickMagick allows you to access ImageMagick command line functions using Ruby interface.}
21
- s.test_files = ["test/image_test.rb", "test/image_list_test.rb"]
21
+ s.test_files = ["test/image_test.rb", "test/image_list_test.rb", "test/test_magick.rb"]
22
22
 
23
23
  if s.respond_to? :specification_version then
24
24
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
data/test/9.gif ADDED
Binary file
@@ -5,16 +5,25 @@ require 'ftools'
5
5
  $base_dir = File.dirname(File.expand_path(__FILE__))
6
6
 
7
7
  class ImageTest < Test::Unit::TestCase
8
+ def setup
9
+ @logo_filename = File.join($base_dir, "imagemagick-logo.png")
10
+ `convert magick:logo #{@logo_filename}`
11
+ @small_logo_filename = File.join($base_dir, "logo-small.jpg")
12
+ `convert magick:logo -resize 100x100! #{@small_logo_filename}`
13
+ end
14
+
15
+ def teardown
16
+ File.delete(@logo_filename) if File.exists?(@logo_filename)
17
+ File.delete(@small_logo_filename) if File.exists?(@small_logo_filename)
18
+ end
19
+
8
20
  def test_open_file
9
- image_filename = File.join($base_dir, "imagemagick-logo.png")
10
- i = QuickMagick::ImageList.new(image_filename)
21
+ i = QuickMagick::ImageList.new(@logo_filename)
11
22
  assert_equal 1, i.length
12
23
  end
13
24
 
14
25
  def test_save_multipage
15
- image_filename = File.join($base_dir, "imagemagick-logo.png")
16
- image_filename2 = File.join($base_dir, "logo-small.jpg")
17
- i = QuickMagick::ImageList.new(image_filename, image_filename2)
26
+ i = QuickMagick::ImageList.new(@logo_filename, @small_logo_filename)
18
27
  assert_equal 2, i.length
19
28
  out_filename = File.join($base_dir, "out.tif")
20
29
  i.save out_filename
@@ -27,9 +36,7 @@ class ImageTest < Test::Unit::TestCase
27
36
  end
28
37
 
29
38
  def test_bulk_resize
30
- image_filename1 = File.join($base_dir, "imagemagick-logo.png")
31
- image_filename2 = File.join($base_dir, "logo-small.jpg")
32
- i = QuickMagick::ImageList.new(image_filename1, image_filename2)
39
+ i = QuickMagick::ImageList.new(@logo_filename, @small_logo_filename)
33
40
  i.resize "50x50!"
34
41
  out_filename = File.join($base_dir, "out.tif")
35
42
  i.save out_filename
@@ -43,11 +50,9 @@ class ImageTest < Test::Unit::TestCase
43
50
  end
44
51
 
45
52
  def test_append_image
46
- image_filename1 = File.join($base_dir, "imagemagick-logo.png")
47
- image_filename2 = File.join($base_dir, "logo-small.jpg")
48
53
  i = QuickMagick::ImageList.new
49
- i << QuickMagick::Image.read(image_filename1)
50
- i << QuickMagick::Image.read(image_filename2)
54
+ i << QuickMagick::Image.read(@logo_filename)
55
+ i << QuickMagick::Image.read(@small_logo_filename)
51
56
  i.resize "50x50!"
52
57
  out_filename = File.join($base_dir, "out.tif")
53
58
  i.save out_filename
@@ -60,24 +65,49 @@ class ImageTest < Test::Unit::TestCase
60
65
  File.delete(out_filename) if out_filename && File.exists?(out_filename)
61
66
  end
62
67
 
68
+ def test_different_operators
69
+ i = QuickMagick::ImageList.new
70
+ i << QuickMagick::Image.read(@logo_filename)
71
+ i << QuickMagick::Image.read(@small_logo_filename)
72
+ i[0].resize "50x50!"
73
+ i[1].resize "75x75!"
74
+ out_filename = File.join($base_dir, "out.tif")
75
+ i.save out_filename
76
+
77
+ i = QuickMagick::Image.read(out_filename)
78
+ assert_equal 2, i.length
79
+ assert_equal 50, i[0].width
80
+ assert_equal 75, i[1].width
81
+ ensure
82
+ File.delete(out_filename) if out_filename && File.exists?(out_filename)
83
+ end
84
+
85
+ def test_resize_one_image_in_a_list
86
+ i = QuickMagick::ImageList.new
87
+ i << QuickMagick::Image.read(@logo_filename)
88
+ i << QuickMagick::Image.read(@small_logo_filename)
89
+ i[0].resize "50x50!"
90
+ out_filename = File.join($base_dir, "out.tif")
91
+ i.save out_filename
92
+
93
+ i = QuickMagick::Image.read(out_filename)
94
+ assert_equal 2, i.length
95
+ assert_equal 50, i[0].width
96
+ assert_equal 100, i[1].width
97
+ ensure
98
+ File.delete(out_filename) if out_filename && File.exists?(out_filename)
99
+ end
100
+
63
101
  def test_bulk_convert
64
- image_filename1 = File.join($base_dir, "imagemagick-logo.png")
65
- image_filename2 = File.join($base_dir, "logo-small.jpg")
66
- new_image_filename1 = File.join($base_dir, "temp1.png")
67
- new_image_filename2 = File.join($base_dir, "temp2.jpg")
68
- File.copy(image_filename1, new_image_filename1)
69
- File.copy(image_filename2, new_image_filename2)
70
- i = QuickMagick::ImageList.new(new_image_filename1, new_image_filename2)
102
+ i = QuickMagick::ImageList.new(@logo_filename, @small_logo_filename)
71
103
  i.format = 'gif'
72
104
  i.save!
73
105
 
74
- out_filename1 = new_image_filename1.sub('.png', '.gif')
75
- out_filename2 = new_image_filename2.sub('.jpg', '.gif')
106
+ out_filename1 = @logo_filename.sub('.png', '.gif')
107
+ out_filename2 = @small_logo_filename.sub('.jpg', '.gif')
76
108
  assert File.exists?(out_filename1)
77
109
  assert File.exists?(out_filename2)
78
110
  ensure
79
- File.delete(new_image_filename1) if new_image_filename1 && File.exists?(new_image_filename1)
80
- File.delete(new_image_filename2) if new_image_filename2 && File.exists?(new_image_filename2)
81
111
  File.delete(out_filename1) if out_filename1 && File.exists?(out_filename1)
82
112
  File.delete(out_filename2) if out_filename2 && File.exists?(out_filename2)
83
113
  end
data/test/image_test.rb CHANGED
@@ -4,16 +4,27 @@ require 'quick_magick'
4
4
  $base_dir = File.dirname(File.expand_path(__FILE__))
5
5
 
6
6
  class ImageTest < Test::Unit::TestCase
7
+
8
+ def setup
9
+ @logo_filename = File.join($base_dir, "imagemagick-logo.png")
10
+ `convert magick:logo #{@logo_filename}`
11
+ @small_logo_filename = File.join($base_dir, "logo-small.jpg")
12
+ `convert magick:logo -resize 100x100! #{@small_logo_filename}`
13
+ end
14
+
15
+ def teardown
16
+ File.delete(@logo_filename) if File.exists?(@logo_filename)
17
+ File.delete(@small_logo_filename) if File.exists?(@small_logo_filename)
18
+ end
19
+
7
20
  def test_open_existing_image
8
- image_filename = File.join($base_dir, "imagemagick-logo.png")
9
- i = QuickMagick::Image.read(image_filename)
21
+ i = QuickMagick::Image.read(@logo_filename)
10
22
  assert_equal 1, i.size
11
23
  end
12
24
 
13
25
  def test_create_from_blob
14
- image_filename = File.join($base_dir, "imagemagick-logo.png")
15
26
  blob = nil
16
- File.open(image_filename, "rb") do |f|
27
+ File.open(@logo_filename, "rb") do |f|
17
28
  blob = f.read
18
29
  end
19
30
  i = QuickMagick::Image.from_blob(blob)
@@ -21,10 +32,9 @@ class ImageTest < Test::Unit::TestCase
21
32
  end
22
33
 
23
34
  def test_image_info
24
- image_filename = File.join($base_dir, "imagemagick-logo.png")
25
- i = QuickMagick::Image.read(image_filename).first
26
- assert_equal 464, i.width
27
- assert_equal 479, i.height
35
+ i = QuickMagick::Image.read(@logo_filename).first
36
+ assert_equal 640, i.width
37
+ assert_equal 480, i.height
28
38
  end
29
39
 
30
40
  def test_open_non_existing_file
@@ -50,8 +60,7 @@ class ImageTest < Test::Unit::TestCase
50
60
  end
51
61
 
52
62
  def test_resize_image
53
- image_filename = File.join($base_dir, "imagemagick-logo.png")
54
- i = QuickMagick::Image.read(image_filename).first
63
+ i = QuickMagick::Image.read(@logo_filename).first
55
64
  i.resize("300x300!")
56
65
  out_filename = File.join($base_dir, "imagemagick-resized.png")
57
66
  File.delete out_filename if File.exists?(out_filename)
@@ -66,8 +75,7 @@ class ImageTest < Test::Unit::TestCase
66
75
  end
67
76
 
68
77
  def test_crop_image
69
- image_filename = File.join($base_dir, "imagemagick-logo.png")
70
- i = QuickMagick::Image.read(image_filename).first
78
+ i = QuickMagick::Image.read(@logo_filename).first
71
79
  i.crop("300x200+0+0")
72
80
  out_filename = File.join($base_dir, "imagemagick-cropped.png")
73
81
  File.delete out_filename if File.exists?(out_filename)
@@ -82,8 +90,7 @@ class ImageTest < Test::Unit::TestCase
82
90
  end
83
91
 
84
92
  def test_resize_with_geometry_options
85
- image_filename = File.join($base_dir, "imagemagick-logo.png")
86
- i = QuickMagick::Image.read(image_filename).first
93
+ i = QuickMagick::Image.read(@logo_filename).first
87
94
  i.resize(300, 300, nil, nil, QuickMagick::AspectGeometry)
88
95
  out_filename = File.join($base_dir, "imagemagick-resized.png")
89
96
  File.delete out_filename if File.exists?(out_filename)
@@ -98,8 +105,7 @@ class ImageTest < Test::Unit::TestCase
98
105
  end
99
106
 
100
107
  def test_resize_with_append_to_operators
101
- image_filename = File.join($base_dir, "imagemagick-logo.png")
102
- i = QuickMagick::Image.read(image_filename).first
108
+ i = QuickMagick::Image.read(@logo_filename).first
103
109
  i.append_to_operators 'resize', '300x300!'
104
110
  out_filename = File.join($base_dir, "imagemagick-resized.png")
105
111
  File.delete out_filename if File.exists?(out_filename)
@@ -142,4 +148,41 @@ class ImageTest < Test::Unit::TestCase
142
148
  # clean up
143
149
  File.delete(out_filename) if out_filename && File.exists?(out_filename)
144
150
  end
151
+
152
+ def test_line_and_circle
153
+ i = QuickMagick::Image.solid(100, 100, :white)
154
+ i.draw_line(0,0,20,20)
155
+ i.draw_circle(30,30,20,20)
156
+ out_filename = File.join($base_dir, "draw_test.gif")
157
+ i.save out_filename
158
+ ensure
159
+ # clean up
160
+ File.delete(out_filename) if out_filename && File.exists?(out_filename)
161
+ end
162
+
163
+ def test_colors
164
+ assert_equal "#00ff00ff", QuickMagick::rgb_color(0, 255, 0)
165
+ assert_equal "#00007fff", QuickMagick::rgb_color(0, 0, 0.5)
166
+ assert_equal "#3f0000ff", QuickMagick::rgb_color("25%" ,0 , 0)
167
+
168
+ assert_equal "#ff00007f", QuickMagick::rgba_color(1.0, 0, 0, 0.5)
169
+
170
+ assert_equal "#7f7f7f7f", QuickMagick::graya_color(0.5, 0.5)
171
+
172
+ assert_equal "hsla(30,50%,50%,1)", QuickMagick::hsl_color(30, 127, 127)
173
+ assert_equal "hsla(180,50%,50%,0.5)", QuickMagick::hsla_color(Math::PI, 0.5, "50%", 0.5)
174
+ end
175
+
176
+ def test_revert
177
+ i = QuickMagick::Image.read(@logo_filename).first
178
+ i.resize("300x300!")
179
+ i.revert!
180
+ out_filename = File.join($base_dir, "imagemagick-resized.png")
181
+ File.delete out_filename if File.exists?(out_filename)
182
+ i.save(out_filename)
183
+ assert File.exists?(out_filename)
184
+ i2 = QuickMagick::Image.read(out_filename).first
185
+ assert_equal 640, i2.width
186
+ assert_equal 480, i2.height
187
+ end
145
188
  end
@@ -0,0 +1,169 @@
1
+ if __FILE__ == $0
2
+ require 'rubygems'
3
+ require 'mini_magick'
4
+ require 'quick_magick'
5
+ require 'RMagick'
6
+ require 'benchmark'
7
+ require 'ftools'
8
+
9
+ begin
10
+ include Benchmark
11
+
12
+ $base_dir = File.dirname(File.expand_path(__FILE__))
13
+
14
+ # Generate a test file
15
+ in_file = File.join($base_dir, 'logo.png')
16
+ out_file = File.join($base_dir, 'testout.gif')
17
+
18
+ `convert magick:logo #{in_file}`
19
+
20
+ test_quick = lambda {
21
+ image_filename =
22
+ 20.times {
23
+ i = QuickMagick::Image.read(in_file).first
24
+ i.resize "100x100!"
25
+ i.save out_file
26
+ }
27
+ }
28
+
29
+ test_mini = lambda {
30
+ 20.times {
31
+ i = MiniMagick::Image.from_file(in_file)
32
+ i.combine_options do |c|
33
+ c.resize "100x100!"
34
+ c.format "gif"
35
+ end
36
+ i.write out_file
37
+ }
38
+ }
39
+
40
+ test_r = lambda {
41
+ 20.times {
42
+ i = Magick::Image.read(in_file).first
43
+ i = i.change_geometry!('100x100!') { |cols, rows, img|
44
+ img.resize!(cols, rows)
45
+ }
46
+ i.write out_file
47
+ }
48
+ }
49
+
50
+ puts "Test 1: resize a normal image"
51
+ bm(12) { |x|
52
+ x.report("mini", &test_mini)
53
+ x.report("quick", &test_quick)
54
+ x.report("rmagick", &test_r)
55
+ }
56
+
57
+ ##################################################
58
+
59
+ in_file = File.join($base_dir, '9.gif')
60
+ out_file = File.join($base_dir, 'testout.gif')
61
+
62
+ test_quick = lambda {
63
+ i = QuickMagick::Image.read(in_file).first
64
+ i.resize "8190x8190>"
65
+ i.save out_file
66
+ }
67
+
68
+ test_mini = lambda {
69
+ i = MiniMagick::Image.from_file(in_file)
70
+ i.combine_options do |c|
71
+ c.resize "8190x8190>"
72
+ c.format "gif"
73
+ end
74
+ i.write out_file
75
+ }
76
+
77
+ test_r = lambda {
78
+ i = Magick::Image.read(in_file).first
79
+ i = i.change_geometry!('8190x8190>') { |cols, rows, img|
80
+ img.resize!(cols, rows)
81
+ }
82
+ i.write out_file
83
+ }
84
+
85
+ puts "Test 2: resize a large image"
86
+ bm(12) { |x|
87
+ x.report("mini", &test_mini)
88
+ x.report("quick", &test_quick)
89
+ # Don't try RMagick! You'll regret that ... a lot :'(
90
+ # x.report("rmagick", &test_r)
91
+ }
92
+
93
+ ##################################################
94
+
95
+ class String
96
+ CHARS = ("a".."z").to_a + ("1".."9").to_a
97
+ def self.random(length)
98
+ Array.new(length, '').collect{CHARS[rand(CHARS.size)]}.join
99
+ end
100
+ end
101
+
102
+ def generate_captcha(length, width, height)
103
+ captcha = []
104
+ String.random(6).chars.each_with_index do |c, i|
105
+ letter = {}
106
+ letter[:char] = c
107
+ letter[:x] = width * i / length + (rand - 0.5) * width / length / 8
108
+ letter[:y] = height / 5 + (rand - 0.5) * height / 2
109
+ letter[:sx] = rand * 90 - 45
110
+ letter[:bx] = width * i / length + (rand - 0.5) * width / length / 2
111
+ letter[:by] = height / 2 + (rand - 0.5) * height
112
+ captcha << letter
113
+ end
114
+ captcha
115
+ end
116
+
117
+ out_file = File.join($base_dir, 'captcha.gif')
118
+
119
+ test_quick = lambda {
120
+ i = QuickMagick::Image.solid(290, 70, :white)
121
+ i.bordercolor = :black
122
+ i.border = 5
123
+ i.fill = :black
124
+ i.stroke = :black
125
+ i.strokewidth = 1
126
+ i.pointsize = 40
127
+ i.gravity = :northwest
128
+ captcha = generate_captcha(6, 290, 70)
129
+ captcha.each do |letter|
130
+ i.draw_text letter[:x], letter[:y], letter[:char], :skewx=>letter[:sx]
131
+ end
132
+ i.save out_file
133
+ }
134
+
135
+ test_mini = lambda {
136
+ }
137
+
138
+ test_r = lambda {
139
+ i = Magick::Image.new(290, 70)
140
+ gc = Magick::Draw.new
141
+ i.border! 5, 5, "black"
142
+ gc.stroke "black"
143
+ gc.stroke_width 1
144
+ gc.pointsize 40
145
+ gc.gravity = Magick::NorthWestGravity
146
+ captcha = generate_captcha(6, 290, 70)
147
+ captcha.each do |letter|
148
+ gc.skewx letter[:sx]
149
+ gc.text letter[:x], letter[:y], letter[:char]
150
+ end
151
+ gc.fill "none"
152
+ gc.draw i
153
+ i.write out_file
154
+ }
155
+
156
+ puts "Test 3: generate random captchas"
157
+ bm(12) { |x|
158
+ # x.report("mini", &test_mini)
159
+ x.report("quick", &test_quick)
160
+ x.report("rmagick", &test_r)
161
+ }
162
+ # Cleanup temp files
163
+ ensure
164
+ %w{captcha.gif testout.gif logo.png}.each do |filename|
165
+ fullname = File.join($base_dir, filename)
166
+ File.delete(fullname) if File.exists?(fullname)
167
+ end
168
+ end
169
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quick_magick
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmed ElDawy
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-05 00:00:00 +03:00
12
+ date: 2009-08-12 00:00:00 +03:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -33,11 +33,11 @@ files:
33
33
  - Manifest
34
34
  - quick_magick.gemspec
35
35
  - test/image_test.rb
36
- - test/logo-small.jpg
37
36
  - test/badfile.xxx
38
37
  - test/multipage.tif
39
38
  - test/image_list_test.rb
40
- - test/imagemagick-logo.png
39
+ - test/test_magick.rb
40
+ - test/9.gif
41
41
  has_rdoc: true
42
42
  homepage: http://quickmagick.rubyforge.org/
43
43
  post_install_message:
@@ -72,3 +72,4 @@ summary: QuickMagick allows you to access ImageMagick command line functions usi
72
72
  test_files:
73
73
  - test/image_test.rb
74
74
  - test/image_list_test.rb
75
+ - test/test_magick.rb
Binary file
data/test/logo-small.jpg DELETED
Binary file