zoomifier 1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ Zoomifier
2
+ =========
3
+ __Version:__ 1.3 (November 24, 2008)
4
+
5
+ __Authors:__ [Donald Ball](mailto:donald.ball@gmail.com)
6
+
7
+ __Copyright:__ Copyright (c) 2008, Donald Ball
8
+
9
+ __License:__ Apache Public License, v2.0
10
+
11
+ Zoomifier is a ruby library for creating directories of tiled images suitable for viewing with the free Zoomify flash player:
12
+
13
+ http://www.zoomify.com/
14
+
15
+ as well as a rails plugin that provides a helper method to make adding zoomified images to your rails app very easy.
16
+
17
+ ## Installation
18
+
19
+ To install the plugin:
20
+
21
+ script/plugin install git://github.com/dball/zoomifier.git
22
+
23
+ If you want the standalone library for whatever reason:
24
+
25
+ sudo gem install dball-zoomifier
26
+
27
+ all this gets you is the zoomify script installed in your PATH, though, and the free convert released by Zoomify is quite a bit faster.
28
+
29
+ I'm working on a GemPlugin version, but I can't seem to figure out how you're supposed to have assets installed; there doesn't seem to be any command which runs the GemPlugin's install.rb script.
30
+
31
+ ## Testing
32
+
33
+ Install the rspec gem, if you don't already have it, then run spec spec from the vendor/plugins/zoomifier directory.
34
+
35
+ ## Usage
36
+
37
+ In your views, wherever you want a zoomified image:
38
+
39
+ <%= zoomify_image_tag ('image.jpg', { :id => 'foo', :width => 400, :height => 300 }) %>
40
+
41
+ This will render a zoomified image with the specified dimensions using the swfobject Javascript library. An image tag with given attributes, except for the id (which is used to tag the div wrapper), is generated as a fallback for users without Javascript, so feel free to feed it alt and title and all that other good stuff as you see fit.
42
+
43
+ The directory of zoomified tiles is create in the same directory as the image, using its name without its extension, e.g. image/ in this example. The images will be automatically created if they do not exist already, or if the image file is newer than its tiles. Bear in mind this process can be fairly slow for large images, and of course, there's no point in zoomifying small images, so be patient on your first request. If there's sufficient, or, hell, any interest, I could write up some rake tasks to do this ahead of time.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
3
+ require 'zoomifier'
4
+ Zoomifier::zoomify(ARGV[0])
@@ -0,0 +1,184 @@
1
+ require 'fileutils'
2
+ require 'open-uri'
3
+ require 'rubygems'
4
+ require 'rmagick'
5
+
6
+ # Breaks up images into tiles suitable for viewing with Zoomify.
7
+ # See http://zoomify.com/ for more details.
8
+ #
9
+ # @author Donald A. Ball Jr. <donald.ball@gmail.com>
10
+ # @copyright (C) 2008 Donald A. Ball Jr.
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+
24
+ module Zoomifier
25
+
26
+ TILESIZE = 256
27
+
28
+ # Zoomifies the image file specified by filename. The zoomified directory
29
+ # name will be the filename without its extension, e.g. 5.jpg will be
30
+ # zoomified into a directory named 5. If there is already a directory with
31
+ # this name, it will be destroyed without mercy.
32
+ def self.zoomify(filename)
33
+ raise ArgumentError unless filename && File.file?(filename)
34
+ #filename = File.expand_path(filename)
35
+ outputdir = File.dirname(filename) + '/' + File.basename(filename, '.*')
36
+ raise ArgumentError unless filename != outputdir
37
+ if File.directory?(outputdir) && File.file?(outputdir + '/ImageProperties.xml') && File.mtime(filename) <= File.mtime(outputdir + '/ImageProperties.xml')
38
+ return
39
+ end
40
+ FileUtils.rm_rf(outputdir) if File.exists?(outputdir)
41
+ Dir.mkdir(outputdir)
42
+ tmpdir = "#{outputdir}/tmp"
43
+ Dir.mkdir(tmpdir)
44
+ tilesdir = nil
45
+ image = Magick::Image.read(filename).first.strip!
46
+ # Each level of zoom is a factor of 2. Here we obtain the number of zooms
47
+ # allowed by the original file dimensions and the constant tile size.
48
+ levels = (Math.log([image.rows, image.columns].max.to_f / TILESIZE) / Math.log(2)).ceil
49
+ tiles = 0
50
+ (0..levels).each do |level|
51
+ n = levels - level
52
+ # Obtain the image to tile for this level. The 0th level should consist
53
+ # of one tile, while the highest level should be the original image.
54
+ level_image = image.resize(image.columns >> n, image.rows >> n)
55
+ tiles(tmpdir, level, level_image) do |filename|
56
+ # The tile images are chunked into directories named TileGroupN, N
57
+ # starting at 0 and increasing monotonically. Each directory contains
58
+ # at most 256 images. The images are sorted by level, tile row, and
59
+ # tile column.
60
+ div, mod = tiles.divmod(256)
61
+ if mod == 0
62
+ tilesdir = "#{outputdir}/TileGroup#{div}"
63
+ Dir.mkdir(tilesdir)
64
+ end
65
+ FileUtils.mv("#{tmpdir}/#{filename}", "#{tilesdir}/#{filename}")
66
+ tiles += 1
67
+ end
68
+ # Rmagick needs a bit of help freeing image memory.
69
+ level_image = nil
70
+ GC.start
71
+ end
72
+ File.open("#{outputdir}/ImageProperties.xml", 'w') do |f|
73
+ f.write("<IMAGE_PROPERTIES WIDTH=\"#{image.columns}\" HEIGHT=\"#{image.rows}\" NUMTILES=\"#{tiles}\" NUMIMAGES=\"1\" VERSION=\"1.8\" TILESIZE=\"#{TILESIZE}\" />")
74
+ end
75
+ Dir.rmdir(tmpdir)
76
+ outputdir
77
+ end
78
+
79
+ # Splits the given image up into images of TILESIZE, writes them to the
80
+ # given directory, and yields their names
81
+ def self.tiles(dir, level, image)
82
+ slice(image.rows).each_with_index do |y_slice, j|
83
+ slice(image.columns).each_with_index do |x_slice, i|
84
+ # The images are named "level-column-row.jpg"
85
+ filename = "#{level}-#{i}-#{j}.jpg"
86
+ tile_image = image.crop(x_slice[0], y_slice[0], x_slice[1], y_slice[1])
87
+ tile_image.write("#{dir}/#{filename}") do
88
+ # FIXME - the images end up being 4-5x larger than those produced
89
+ # by Zoomifier EZ and friends... no idea why just yet, except to note
90
+ # that the density of these tiles ends up being 400x400, while
91
+ # everybody else produces tiles at 72x72. Can't see why that would
92
+ # matter though...
93
+ self.quality = 80
94
+ end
95
+ # Rmagick needs a bit of help freeing image memory.
96
+ tile_image = nil
97
+ GC.start
98
+ yield filename
99
+ end
100
+ end
101
+ end
102
+
103
+ # Returns an array of slices ([offset, length]) obtained by slicing the
104
+ # given number by TILESIZE.
105
+ # E.g. 256 -> [[0, 256]], 257 -> [[0, 256], [256, 1]],
106
+ # 513 -> [[0, 256], [256, 256], [512, 1]]
107
+ def self.slice(n)
108
+ results = []
109
+ i = 0
110
+ while true
111
+ if i + TILESIZE >= n
112
+ results << [i, n-i]
113
+ break
114
+ else
115
+ results << [i, TILESIZE]
116
+ i += TILESIZE
117
+ end
118
+ end
119
+ results
120
+ end
121
+
122
+ def self.unzoomify(url)
123
+ tmpdir = 'tmp'
124
+ FileUtils.rm_rf(tmpdir) if File.exists?(tmpdir)
125
+ Dir.mkdir(tmpdir)
126
+ doc = nil
127
+ begin
128
+ open("#{url}/ImageProperties.xml") do |f|
129
+ doc = REXML::Document.new(f)
130
+ end
131
+ rescue OpenURI::HTTPError
132
+ return nil
133
+ end
134
+ attrs = doc.root.attributes
135
+ return nil unless attrs['TILESIZE'] == '256' && attrs['VERSION'] == '1.8'
136
+ width = attrs['WIDTH'].to_i
137
+ height = attrs['HEIGHT'].to_i
138
+ tiles = attrs['NUMTILES'].to_i
139
+ image_paths = (0 .. tiles/256).map {|n| "TileGroup#{n}"}
140
+ max_level = 0
141
+ while (get_tile(url, image_paths, tmpdir, "#{max_level}-0-0.jpg"))
142
+ max_level += 1
143
+ end
144
+ max_level -= 1
145
+ image = Magick::Image.new(width, height)
146
+ (0 .. width / TILESIZE).each do |column|
147
+ (0 .. height / TILESIZE).each do |row|
148
+ filename = "#{max_level}-#{column}-#{row}.jpg"
149
+ get_tile(url, image_paths, tmpdir, filename)
150
+ tile_image = Magick::Image.read("#{tmpdir}/#{filename}").first
151
+ image.composite!(tile_image, column*TILESIZE, row*TILESIZE, Magick::OverCompositeOp)
152
+ time_image = nil
153
+ GC.start
154
+ end
155
+ end
156
+ # FIXME - get filename from the url
157
+ image.write('file.jpg') { self.quality = 90 }
158
+ image = nil
159
+ GC.start
160
+ FileUtils.rm_rf(tmpdir)
161
+ end
162
+
163
+ # TODO - could reduce the miss rate by using heuristics to guess the
164
+ # proper path from which to download the file
165
+ def self.get_tile(url, image_paths, tmpdir, filename)
166
+ image_paths.each do |path|
167
+ begin
168
+ open("#{tmpdir}/#{filename}", 'wb') {|f| f.write(open("#{url}/#{path}/#{filename}").read)}
169
+ return filename
170
+ rescue OpenURI::HTTPError
171
+ end
172
+ end
173
+ nil
174
+ end
175
+
176
+ end
177
+
178
+ if __FILE__ == $0
179
+ if ARGV.length == 1
180
+ Zoomifier::zoomify(ARGV[0])
181
+ else
182
+ puts "Usage: zoomify filename"
183
+ end
184
+ end
Binary file
Binary file
@@ -0,0 +1 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib/')
@@ -0,0 +1,115 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'zoomifier'
3
+
4
+ describe Zoomifier do
5
+ it "should respond to its main method" do
6
+ Zoomifier.should respond_to(:zoomify)
7
+ end
8
+
9
+ describe "all images", :shared => true do
10
+ before(:all) do
11
+ FileUtils.rm_rf(@output)
12
+ Zoomifier::zoomify(@input)
13
+ end
14
+
15
+ after(:all) do
16
+ FileUtils.rm_rf(@output)
17
+ end
18
+
19
+ it "should create the output directory" do
20
+ File.directory?(@output).should be_true
21
+ end
22
+
23
+ it "should create the image properties file" do
24
+ File.file?(@output + 'ImageProperties.xml').should be_true
25
+ end
26
+
27
+ it "should create the tiles" do
28
+ tiles = Dir.glob(@output + 'TileGroup*/*.jpg')
29
+ tiles.sort.should == @tiles.map {|file| @output + file }.sort
30
+ end
31
+
32
+ it "should create tiles of 256x256 or less" do
33
+ @tiles.each do |file|
34
+ image = Magick::Image.read(@output + file).first
35
+ image.rows.should <= 256
36
+ image.columns.should <= 256
37
+ end
38
+ end
39
+
40
+ it "should not recreate the tiles if the image date precedes them" do
41
+ old_timestamps = timestamps
42
+ sleep(1)
43
+ Zoomifier::zoomify(@input)
44
+ old_timestamps.should == timestamps
45
+ end
46
+
47
+ def timestamps
48
+ timestamps = {}
49
+ ['ImageProperties.xml', 'TileGroup*/*.jpg'].each do |pattern|
50
+ Dir.glob(@output + pattern) do |filename|
51
+ timestamps[filename] = File.mtime(filename)
52
+ end
53
+ end
54
+ timestamps
55
+ end
56
+ end
57
+
58
+ describe "On a 1024x768 JPEG file" do
59
+ before(:all) do
60
+ @input = File.dirname(__FILE__) + '/data/1024x768.jpg'
61
+ @output = File.dirname(__FILE__) + '/data/1024x768/'
62
+ @tiles = %w[0-0-0.jpg 1-1-1.jpg 2-1-0.jpg 2-2-1.jpg 2-3-2.jpg
63
+ 1-0-0.jpg 2-0-0.jpg 2-1-1.jpg 2-2-2.jpg
64
+ 1-0-1.jpg 2-0-1.jpg 2-1-2.jpg 2-3-0.jpg
65
+ 1-1-0.jpg 2-0-2.jpg 2-2-0.jpg 2-3-1.jpg].map do |file|
66
+ 'TileGroup0/' + file
67
+ end
68
+ end
69
+
70
+ it_should_behave_like "all images"
71
+ end
72
+
73
+ describe "On a 2973x2159 JPEG file" do
74
+ before(:all) do
75
+ @input = File.dirname(__FILE__) + '/data/2973x2159.jpg'
76
+ @output = File.dirname(__FILE__) + '/data/2973x2159/'
77
+ @tiles = %w[
78
+ 0-0-0.jpg 3-3-2.jpg 4-10-0.jpg 4-3-4.jpg 4-6-8.jpg
79
+ 1-0-0.jpg 3-3-3.jpg 4-10-1.jpg 4-3-5.jpg 4-7-0.jpg
80
+ 1-0-1.jpg 3-3-4.jpg 4-10-2.jpg 4-3-6.jpg 4-7-1.jpg
81
+ 1-1-0.jpg 3-4-0.jpg 4-10-3.jpg 4-3-7.jpg 4-7-2.jpg
82
+ 1-1-1.jpg 3-4-1.jpg 4-10-4.jpg 4-3-8.jpg 4-7-3.jpg
83
+ 2-0-0.jpg 3-4-2.jpg 4-10-5.jpg 4-4-0.jpg 4-7-4.jpg
84
+ 2-0-1.jpg 3-4-3.jpg 4-10-6.jpg 4-4-1.jpg 4-7-5.jpg
85
+ 2-0-2.jpg 3-4-4.jpg 4-10-7.jpg 4-4-2.jpg 4-7-6.jpg
86
+ 2-1-0.jpg 3-5-0.jpg 4-10-8.jpg 4-4-3.jpg 4-7-7.jpg
87
+ 2-1-1.jpg 3-5-1.jpg 4-11-0.jpg 4-4-4.jpg 4-7-8.jpg
88
+ 2-1-2.jpg 3-5-2.jpg 4-11-1.jpg 4-4-5.jpg 4-8-0.jpg
89
+ 2-2-0.jpg 3-5-3.jpg 4-11-2.jpg 4-4-6.jpg 4-8-1.jpg
90
+ 2-2-1.jpg 3-5-4.jpg 4-11-3.jpg 4-4-7.jpg 4-8-2.jpg
91
+ 2-2-2.jpg 4-0-0.jpg 4-11-4.jpg 4-4-8.jpg 4-8-3.jpg
92
+ 3-0-0.jpg 4-0-1.jpg 4-11-5.jpg 4-5-0.jpg 4-8-4.jpg
93
+ 3-0-1.jpg 4-0-2.jpg 4-11-6.jpg 4-5-1.jpg 4-8-5.jpg
94
+ 3-0-2.jpg 4-0-3.jpg 4-11-7.jpg 4-5-2.jpg 4-8-6.jpg
95
+ 3-0-3.jpg 4-0-4.jpg 4-11-8.jpg 4-5-3.jpg 4-8-7.jpg
96
+ 3-0-4.jpg 4-0-5.jpg 4-2-0.jpg 4-5-4.jpg 4-8-8.jpg
97
+ 3-1-0.jpg 4-0-6.jpg 4-2-1.jpg 4-5-5.jpg 4-9-0.jpg
98
+ 3-1-1.jpg 4-0-7.jpg 4-2-2.jpg 4-5-6.jpg 4-9-1.jpg
99
+ 3-1-2.jpg 4-0-8.jpg 4-2-3.jpg 4-5-7.jpg 4-9-2.jpg
100
+ 3-1-3.jpg 4-1-0.jpg 4-2-4.jpg 4-5-8.jpg 4-9-3.jpg
101
+ 3-1-4.jpg 4-1-1.jpg 4-2-5.jpg 4-6-0.jpg 4-9-4.jpg
102
+ 3-2-0.jpg 4-1-2.jpg 4-2-6.jpg 4-6-1.jpg 4-9-5.jpg
103
+ 3-2-1.jpg 4-1-3.jpg 4-2-7.jpg 4-6-2.jpg 4-9-6.jpg
104
+ 3-2-2.jpg 4-1-4.jpg 4-2-8.jpg 4-6-3.jpg 4-9-7.jpg
105
+ 3-2-3.jpg 4-1-5.jpg 4-3-0.jpg 4-6-4.jpg 4-9-8.jpg
106
+ 3-2-4.jpg 4-1-6.jpg 4-3-1.jpg 4-6-5.jpg
107
+ 3-3-0.jpg 4-1-7.jpg 4-3-2.jpg 4-6-6.jpg
108
+ 3-3-1.jpg 4-1-8.jpg 4-3-3.jpg 4-6-7.jpg].map do |file|
109
+ 'TileGroup0/' + file
110
+ end
111
+ end
112
+
113
+ it_should_behave_like "all images"
114
+ end
115
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zoomifier
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.3"
5
+ platform: ruby
6
+ authors:
7
+ - Donald Ball
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-21 00:00:00 -06:00
13
+ default_executable: zoomify
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rmagick
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description:
26
+ email: donald.ball@gmail.com
27
+ executables:
28
+ - zoomify
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.markdown
33
+ files:
34
+ - lib/zoomifier.rb
35
+ - bin/zoomify
36
+ - spec/zoomifier_spec.rb
37
+ - spec/spec_helper.rb
38
+ - spec/data/1024x768.jpg
39
+ - spec/data/2973x2159.jpg
40
+ - README.markdown
41
+ has_rdoc: true
42
+ homepage:
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.1
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: A library for zoomifying images
67
+ test_files:
68
+ - spec/zoomifier_spec.rb