quick_magick 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

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