dynamic_images 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ README.rdoc
2
+ README_USAGE.rdoc
3
+ Rakefile
4
+ examples/gtk_window.rb
5
+ examples/named_colors_table.rb
6
+ init.rb
7
+ lib/dynamic_image.rb
8
+ lib/elements/block_element.rb
9
+ lib/elements/element_interface.rb
10
+ lib/elements/image_element.rb
11
+ lib/elements/table_cell_element.rb
12
+ lib/elements/table_element.rb
13
+ lib/elements/text_element.rb
14
+ lib/parsers/xml.dtd
15
+ lib/parsers/xml_parser.rb
16
+ lib/render_image.rb
17
+ lib/sources/color_source.rb
18
+ lib/sources/gradient_source.rb
19
+ lib/sources/source_factory.rb
20
+ test/asym.rb
21
+ test/performance.1.rb
22
+ test/performance.2.rb
23
+ test/performance.3.rb
24
+ test/performance.rb
25
+ test/units1.rb
26
+ test/units2.rb
27
+ Manifest
@@ -0,0 +1,104 @@
1
+ = Dynamic Images
2
+
3
+ Ruby library providing image rendering described by dynamic templates
4
+
5
+ == Dependencies
6
+
7
+ Library is using these libraries:
8
+ * cairo
9
+ * pango
10
+ * rexml
11
+
12
+ == How to install & use
13
+
14
+ Download this repository and place it into your folder.
15
+
16
+ Require init.rb file from library's folder.
17
+
18
+ Optionaly you can load gtk2 libraty too. It's supporting another image formats.
19
+
20
+ For more information how to use or about elements read documentation.
21
+
22
+
23
+
24
+
25
+ = Usage of Library
26
+
27
+ == In general about options
28
+ Options should be in base given as hash. Format of keys in hash is not fixed. You can use +Symbol+ as good as +String+. There is also no difference between "-" and "_" chars.
29
+
30
+ If option accepts more arguments you can specify they in +Array+ and also in +String+. In case you choose +String+ it's necessary to seperate arguments by space char.
31
+
32
+ === Example
33
+ These <tt>Hash</tt>es are considered as absolutelly same.
34
+ * <tt>{:vertical_align => :middle}</tt>, <tt>{'vertical_align' => 'middle'}</tt>, <tt>{"vertical-align" => :middle}</tt>, <tt>{:vertical_align => "middle"}</tt>, <tt>{:vertical_align => :middle}</tt>, etc.
35
+ * <tt>{:to_fit => [:crop, :sentences, 3, :resize]}</tt>, <tt>{:to_fit => "crop sentences 3 resize"}</tt>, <tt>{'to-fit' => "crop sentences 3 resize"}</tt>, etc.
36
+
37
+ == Passing a Block
38
+ In block you can accept object to call methods on it or if you don't accept any argument, block is called in object instance.
39
+
40
+ === Example
41
+ These examples are considered as same.
42
+ DynamicImage.new do |img|
43
+ img.text "<b>Warning</b>"
44
+ img.save! "warning.png"
45
+ end
46
+
47
+ DynamicImage.new do
48
+ text "<b>Warning</b>"
49
+ save! "warning.png"
50
+ end
51
+
52
+ == Image formats
53
+ In base you can save and load all as PNG images. You can enable more formats by loading gtk library. DynamicImage will automatically detect it's loaded.
54
+
55
+ require 'gtk2'
56
+
57
+ == Using with Rails
58
+ To use within Rails application just download this library as plugin.
59
+
60
+ rails plugin install git://github.com/malis/dynamic_images.git
61
+
62
+ You need to add <tt>cairo</tt> and <tt>pango</tt> gems to your Gemfile or environment.rb file.
63
+
64
+ Then just update your controller like this:
65
+
66
+ def show
67
+ @article = Article.find(params[:id])
68
+ respond_to do |format|
69
+ format.html
70
+ format.png { render_image } #or render_image("show.jpg"), find more in doc
71
+ end
72
+ end
73
+
74
+ Do not forgot to add mime types for used image formats to your environment file.
75
+
76
+ Mime::Type.register "image/png", :png
77
+
78
+ Create view articles/show.png.xml.erb like this:
79
+
80
+ <?xml version="1.0" encoding="UTF-8" ?>
81
+ <!DOCTYPE dynamic_images PUBLIC "-//malis//dynamic_images//EN" "https://raw.github.com/malis/dynamic_images/master/lib/parsers/xml.dtd">
82
+ <dynamic_images>
83
+ <dynamic_image width="500" align="center" background="blue 0.5">
84
+ <text font="Arial bold 20"><%= @article.title %></text>
85
+ <text indent="30"><%= @article.text %></text>
86
+ </dynamic_image>
87
+ </dynamic_images>
88
+
89
+ That's all!
90
+
91
+ Library is tested under Rails 2.3.11 (ruby 1.8.7) and 3.1.1 (ruby 1.9.2)
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ == Copying
101
+ Copyright (c) 2012 Dominik Mališ
102
+
103
+ This program is free software.
104
+ You can distribute/modify this program under the terms of the GNU LESSER GENERAL PUBLIC LICENSE.
@@ -0,0 +1,67 @@
1
+ = Usage of Library
2
+
3
+ == In general about options
4
+ Options should be in base given as hash. Format of keys in hash is not fixed. You can use +Symbol+ as good as +String+. There is also no difference between "-" and "_" chars.
5
+
6
+ If option accepts more arguments you can specify they in +Array+ and also in +String+. In case you choose +String+ it's necessary to seperate arguments by space char.
7
+
8
+ === Example
9
+ These <tt>Hash</tt>es are considered as absolutelly same.
10
+ * <tt>{:vertical_align => :middle}</tt>, <tt>{'vertical_align' => 'middle'}</tt>, <tt>{"vertical-align" => :middle}</tt>, <tt>{:vertical_align => "middle"}</tt>, <tt>{:vertical_align => :middle}</tt>, etc.
11
+ * <tt>{:to_fit => [:crop, :sentences, 3, :resize]}</tt>, <tt>{:to_fit => "crop sentences 3 resize"}</tt>, <tt>{'to-fit' => "crop sentences 3 resize"}</tt>, etc.
12
+
13
+ == Passing a Block
14
+ In block you can accept object to call methods on it or if you don't accept any argument, block is called in object instance.
15
+
16
+ === Example
17
+ These examples are considered as same.
18
+ DynamicImage.new do |img|
19
+ img.text "<b>Warning</b>"
20
+ img.save! "warning.png"
21
+ end
22
+
23
+ DynamicImage.new do
24
+ text "<b>Warning</b>"
25
+ save! "warning.png"
26
+ end
27
+
28
+ == Image formats
29
+ In base you can save and load all as PNG images. You can enable more formats by loading gtk library. DynamicImage will automatically detect it's loaded.
30
+
31
+ require 'gtk2'
32
+
33
+ == Using with Rails
34
+ To use within Rails application just download this library as plugin.
35
+
36
+ rails plugin install git://github.com/malis/dynamic_images.git
37
+
38
+ You need to add <tt>cairo</tt> and <tt>pango</tt> gems to your Gemfile or environment.rb file.
39
+
40
+ Then just update your controller like this:
41
+
42
+ def show
43
+ @article = Article.find(params[:id])
44
+ respond_to do |format|
45
+ format.html
46
+ format.png { render_image } #or render_image("show.jpg"), find more in doc
47
+ end
48
+ end
49
+
50
+ Do not forgot to add mime types for used image formats to your environment file.
51
+
52
+ Mime::Type.register "image/png", :png
53
+
54
+ Create view articles/show.png.xml.erb like this:
55
+
56
+ <?xml version="1.0" encoding="UTF-8" ?>
57
+ <!DOCTYPE dynamic_images PUBLIC "-//malis//dynamic_images//EN" "https://raw.github.com/malis/dynamic_images/master/lib/parsers/xml.dtd">
58
+ <dynamic_images>
59
+ <dynamic_image width="500" align="center" background="blue 0.5">
60
+ <text font="Arial bold 20"><%= @article.title %></text>
61
+ <text indent="30"><%= @article.text %></text>
62
+ </dynamic_image>
63
+ </dynamic_images>
64
+
65
+ That's all!
66
+
67
+ Library is tested under Rails 2.3.11 (ruby 1.8.7) and 3.1.1 (ruby 1.9.2)
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('dynamic_images', '1.0.0') do |p|
6
+ p.description = "Ruby library providing image rendering described by dynamic templates"
7
+ p.url = "http://github.com/malis/dynamic_images"
8
+ p.author = "Dominik Malis"
9
+ p.email = "dominik.malis@gmail.com"
10
+ p.ignore_pattern = ["tmp/*", "script/*"]
11
+ p.runtime_dependencies = ["cairo", "pango"]
12
+ end
13
+
14
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "dynamic_images"
5
+ s.version = "1.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Dominik Malis"]
9
+ s.date = "2012-06-19"
10
+ s.description = "Ruby library providing image rendering described by dynamic templates"
11
+ s.email = "dominik.malis@gmail.com"
12
+ s.extra_rdoc_files = ["README.rdoc", "README_USAGE.rdoc", "lib/dynamic_image.rb", "lib/elements/block_element.rb", "lib/elements/element_interface.rb", "lib/elements/image_element.rb", "lib/elements/table_cell_element.rb", "lib/elements/table_element.rb", "lib/elements/text_element.rb", "lib/parsers/xml.dtd", "lib/parsers/xml_parser.rb", "lib/render_image.rb", "lib/sources/color_source.rb", "lib/sources/gradient_source.rb", "lib/sources/source_factory.rb"]
13
+ s.files = ["README.rdoc", "README_USAGE.rdoc", "Rakefile", "examples/gtk_window.rb", "examples/named_colors_table.rb", "init.rb", "lib/dynamic_image.rb", "lib/elements/block_element.rb", "lib/elements/element_interface.rb", "lib/elements/image_element.rb", "lib/elements/table_cell_element.rb", "lib/elements/table_element.rb", "lib/elements/text_element.rb", "lib/parsers/xml.dtd", "lib/parsers/xml_parser.rb", "lib/render_image.rb", "lib/sources/color_source.rb", "lib/sources/gradient_source.rb", "lib/sources/source_factory.rb", "test/asym.rb", "test/performance.1.rb", "test/performance.2.rb", "test/performance.3.rb", "test/performance.rb", "test/units1.rb", "test/units2.rb", "Manifest", "dynamic_images.gemspec"]
14
+ s.homepage = "http://github.com/malis/dynamic_images"
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Dynamic_images", "--main", "README_USAGE.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = "dynamic_images"
18
+ s.rubygems_version = "1.8.10"
19
+ s.summary = "Ruby library providing image rendering described by dynamic templates"
20
+
21
+ if s.respond_to? :specification_version then
22
+ s.specification_version = 3
23
+
24
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
25
+ s.add_runtime_dependency(%q<cairo>, [">= 0"])
26
+ s.add_runtime_dependency(%q<pango>, [">= 0"])
27
+ else
28
+ s.add_dependency(%q<cairo>, [">= 0"])
29
+ s.add_dependency(%q<pango>, [">= 0"])
30
+ end
31
+ else
32
+ s.add_dependency(%q<cairo>, [">= 0"])
33
+ s.add_dependency(%q<pango>, [">= 0"])
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ require 'rubygems'
2
+ require 'gtk2'
3
+ require File.dirname(__FILE__) + '/../init.rb'
4
+
5
+ class Win < Gtk::Window
6
+ def initialize
7
+ super
8
+ set_title "DynamicImage Gtk Test"
9
+ signal_connect "destroy" do
10
+ Gtk.main_quit
11
+ end
12
+ resize 300, 300
13
+
14
+ @darea = Gtk::DrawingArea.new
15
+ @darea.signal_connect "expose-event" do
16
+ expose
17
+ end
18
+ add @darea
19
+
20
+ show_all
21
+ end
22
+
23
+ private
24
+ def expose
25
+ w = allocation.width
26
+ h = allocation.height
27
+
28
+ DynamicImage.from @darea.window.create_cairo_context do
29
+ block :w => w, :h => h, :bg => [:gradient_radial_repeat, "225deg", "50%", "0%", :lime, "50%", :red, "100%", :orange], :align => :center, :valign => :middle do
30
+ text "Try to resize me!", :font => "Arial bold 48", :color => [:gradient_repeat, "0%", :blue, "100%", :yellow]
31
+ end
32
+ save!
33
+ end
34
+ end
35
+ end
36
+
37
+ Gtk.init
38
+ window = Win.new
39
+ Gtk.main
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../init.rb'
3
+
4
+ DynamicImage.new do
5
+ table :cols => 9 do
6
+ for color in DynamicImageSources::ColorSource.named_colors
7
+ cell :padding => 5, :margin => 2, :background => color do
8
+ text color
9
+ end
10
+ end
11
+ end
12
+ save! "named_colors_table.png"
13
+ end
data/init.rb ADDED
@@ -0,0 +1,6 @@
1
+ require File.dirname(__FILE__) + '/lib/dynamic_image.rb'
2
+
3
+ if defined? ActionController::Base
4
+ require File.dirname(__FILE__) + '/lib/render_image.rb'
5
+ ActionController::Base.send :include, RenderImage
6
+ end
@@ -0,0 +1,240 @@
1
+ require 'cairo'
2
+ require 'pango'
3
+ require File.dirname(__FILE__) + '/elements/block_element.rb'
4
+ require File.dirname(__FILE__) + '/parsers/xml_parser.rb'
5
+
6
+ # DynamicImage provides interface to create an image in ruby code.
7
+ #
8
+ # :include:../README_USAGE.rdoc
9
+ class DynamicImage < DynamicImageElements::BlockElement
10
+ # Gets original Cairo::Context of Cairo::ImageSurface object if width and height was given in options or it's created from existing source.
11
+ attr_reader :context
12
+
13
+ # DynamicImage accepts options +Hash+. If block is given destroy method is called automatically after a block if source of image isn't Cairo object. Otherwise you have to call destroy manually.
14
+ #
15
+ # === Options
16
+ # Image accepts also all options like BlockElement. See it first.
17
+ #
18
+ # [:auto_destroy]
19
+ # Sets whether to automatically destroy surface if you are using block. Default is true.
20
+ #
21
+ # [:format]
22
+ # Sets the memory format of image data.
23
+ #
24
+ # Valid values are :a1, :a8, :rgb24 and :argb32. See http://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t for details.
25
+ #
26
+ # [:from_source]
27
+ # Creates new DynamicImage from given source if it's supported. It has same behavior as DynamicImage.from.
28
+ #
29
+ def initialize(options = {}, &block) # :yields: block_element
30
+ treat_options options
31
+ @options = options
32
+ use_options :margin
33
+ use_options :padding
34
+ if options[:width] && options[:height] || options[:from_source]
35
+ [:width, :height].each {|key| @options[key] = nil } if options[:from_source] #remove forbidden options
36
+ create_surface
37
+ end
38
+ super options, &block
39
+ destroy_by_block if block
40
+ end
41
+
42
+ private
43
+ def create_surface(use_from_source = true)
44
+ if @options[:from_source] && use_from_source
45
+ if @options[:from_source].is_a? Cairo::Surface
46
+ set_surface_and_context @options[:from_source]
47
+ @options[:auto_destroy] = false
48
+ elsif @options[:from_source].is_a? Cairo::Context
49
+ set_surface_and_context nil, @options[:from_source]
50
+ @options[:auto_destroy] = false
51
+ elsif @options[:from_source].to_s =~ /\.png$/i
52
+ image = Cairo::ImageSurface.from_png @options[:from_source]
53
+ set_surface_and_context image
54
+ set_width image.width, false
55
+ set_height image.height, false
56
+ else
57
+ if defined? Gdk
58
+ pixbuf = Gdk::Pixbuf.new @options[:from_source]
59
+ set_width pixbuf.width, false
60
+ set_height pixbuf.height, false
61
+ create_surface false
62
+ context.save
63
+ context.set_source_pixbuf pixbuf, 0, 0
64
+ context.rectangle 0, 0, pixbuf.width, pixbuf.height
65
+ context.clip
66
+ context.paint
67
+ context.restore
68
+ else
69
+ raise "Unsupported source format of: #{@options[:from_source]}"
70
+ end
71
+ end
72
+ else
73
+ w, h = @options[:width].to_i, @options[:height].to_i
74
+ if @padding
75
+ w += @padding[1] + @padding[3]
76
+ h += @padding[0] + @padding[2]
77
+ end
78
+ if @margin
79
+ w += @margin[1] + @margin[3]
80
+ h += @margin[0] + @margin[2]
81
+ end
82
+ if @border && !@border.empty?
83
+ w += @border[:left][0].to_i if @border[:left]
84
+ w += @border[:right][0].to_i if @border[:right]
85
+ h += @border[:top][0].to_i if @border[:top]
86
+ h += @border[:bottom][0].to_i if @border[:bottom]
87
+ end
88
+ surface_args = [w, h]
89
+ surface_args.unshift({
90
+ :a1 => Cairo::Format::A1,
91
+ :a8 => Cairo::Format::A8,
92
+ :rgb24 => Cairo::Format::RGB24,
93
+ :argb32 => Cairo::Format::ARGB32
94
+ }[@options[:format].to_sym]) if @options[:format]
95
+ @surface = Cairo::ImageSurface.new *surface_args
96
+ @context = Cairo::Context.new @surface
97
+ @context.set_antialias({
98
+ :default => Cairo::ANTIALIAS_DEFAULT,
99
+ :gray => Cairo::ANTIALIAS_GRAY,
100
+ :none => Cairo::ANTIALIAS_NONE,
101
+ :subpixel => Cairo::ANTIALIAS_SUBPIXEL
102
+ }[@options[:antialias].to_sym]) if @options[:antialias]
103
+ end
104
+ end
105
+
106
+ def set_surface_and_context(surface, context = nil)
107
+ @surface = surface
108
+ @context = context || Cairo::Context.new(surface)
109
+ end
110
+
111
+ # Call this if block is given to destroy surface
112
+ def destroy_by_block
113
+ self.destroy if @options[:auto_destroy] != false
114
+ end
115
+
116
+ public
117
+ # Gets left and bottom borders of drawing canvas
118
+ def canvas_border
119
+ x, y = [@options[:width], @options[:height]]
120
+ if @padding
121
+ x += @padding[3]
122
+ y += @padding[0]
123
+ end
124
+ if @margin
125
+ x += @margin[3]
126
+ y += @margin[0]
127
+ end
128
+ if @border && !@border.empty?
129
+ x += @border[:left][0].to_i if @border[:left]
130
+ y += @border[:top][0].to_i if @border[:top]
131
+ end
132
+ [x, y]
133
+ end
134
+
135
+ # Creates new DynamicImage from given source if it's supported. Use it in same way as DynamicImage.new.
136
+ #
137
+ # PNG is always supported as source.
138
+ #
139
+ # If there is +Gdk+ loaded you can use any from <tt>Gdk::Pixbuf.formats</tt> as source. By default, "jpeg", "png" and "ico" are possible file formats to load from, but more formats may be installed.
140
+ #
141
+ def self.from(source, options = {}, &block) # :yields: block_element
142
+ options[:from_source] = source
143
+ DynamicImage.new options, &block
144
+ end
145
+
146
+ # Saves image into file or given IO object (you have to speficify :format option like file extension, f.e. <tt>png</tt>). Image will be drawed only if no file is given. It's usable when you drawing into prepared Cairo object.
147
+ #
148
+ # Block can be given to draw into context directly.
149
+ #
150
+ # PNG format is always supported.
151
+ #
152
+ # If there is +Gdk+ loaded you can use any from <tt>Gdk::Pixbuf.formats</tt> as source. By default, "jpeg", "png" and "ico" are possible file formats to save in, but more formats may be installed.
153
+ #
154
+ # When saving into JPEG format you can pass :quality into options. Valid values are in 0 - 100.
155
+ #
156
+ def save!(file = nil, options = {}, &block) # :yields: context
157
+ treat_options options
158
+ unless context
159
+ canvas_size = final_size
160
+ canvas_size[0] = @options[:width] if @options[:width]
161
+ canvas_size[1] = @options[:height] if @options[:height]
162
+ set_width canvas_size[0], false
163
+ set_height canvas_size[1], false
164
+ create_surface
165
+ end
166
+ draw!
167
+ block.call context if block
168
+ write_to file, options if file
169
+ end
170
+
171
+ private
172
+ def write_to(file, options)
173
+ ext = options[:format]
174
+ ext ||= file.scan(/\.([a-z]+)$/i).flatten.first.downcase
175
+ if ext.to_s == "png"
176
+ context.target.write_to_png file
177
+ else
178
+ raise "Unsupported file type #{ext}" unless defined? Gdk
179
+ w, h = @options[:width], @options[:height]
180
+ pixmap = Gdk::Pixmap.new nil, w, h, 24
181
+ context = pixmap.create_cairo_context
182
+ context.set_source @context.target, 0, 0
183
+ context.paint
184
+ #pixbuf = Gdk::Pixbuf.new gtk.gdk.COLORSPACE_RGB, True, 8, w, h
185
+ pixbuf = Gdk::Pixbuf.from_drawable Gdk::Colormap.system, pixmap, 0, 0, w, h
186
+ begin
187
+ format = Gdk::Pixbuf.formats.select{|f| f.extensions.include? ext.to_s}.first.name
188
+ rescue
189
+ raise "Unsupported file type #{ext}"
190
+ end
191
+ pixbuf.save file, format, (format == "jpeg" && options[:quality] ? {'quality' => options[:quality]} : {})
192
+ end
193
+ end
194
+
195
+ public
196
+ # Saves image content into more images if content is bigger than given image size.
197
+ # Image is cut between elements in first level of elements hierarchy. In case of table it's cut between rows of table.
198
+ #
199
+ # Method accepts limit of pages to be rendered. If no number is given or 0 is passed it's not limited.
200
+ # Give a block returning filename or given IO object (you have to speficify :format option like file extension, f.e. <tt>png</tt>) to saving in it. Block provides index of page which is currently rendered. Index starting at 0.
201
+ #
202
+ # PNG format is always supported.
203
+ #
204
+ # If there is +Gdk+ loaded you can use any from <tt>Gdk::Pixbuf.formats</tt> as source. By default, "jpeg", "png" and "ico" are possible file formats to save in, but more formats may be installed.
205
+ #
206
+ # When saving into JPEG format you can pass :quality into options. Valid values are in 0 - 100.
207
+ #
208
+ # === Example
209
+ # save_endless 4 do |index|
210
+ # "image-#{index}.png"
211
+ # end
212
+ #
213
+ def save_endless!(limit = 0, options = {}, &block) # :yields: index
214
+ raise "Width and height must be set when you saving endless" unless @options[:width] || @options[:height]
215
+ treat_options options
216
+ @drawing_endless = index = 0
217
+ loop do
218
+ draw! 0, 0, index
219
+ write_to block.call(index), options
220
+ destroy
221
+ create_surface
222
+ index += 1
223
+ @drawing_endless = index
224
+ break if index == limit || is_drawed?
225
+ end
226
+ end
227
+
228
+ # Gets index of drawing endless from canvas
229
+ def drawing_endless #:nodoc:
230
+ @drawing_endless
231
+ end
232
+
233
+ # Destroys source objects to free a memory. It's important to call this method when it's finished to avoid a memory leaks.
234
+ #
235
+ # If you passed a block to DynamicImage.new or DynamicImage.from it's called automatically after block is finished.
236
+ def destroy
237
+ @surface.destroy if @surface
238
+ @context.destroy if @context
239
+ end
240
+ end