dball-zoomifier 1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ Zoomifier is a ruby library for creating directories of tiled images
2
+ suitable for viewing with the free Zoomify flash player:
3
+
4
+ http://www.zoomify.com/
@@ -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,182 @@
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
+ # @version 1.2
11
+ # @copyright (C) 2008 Donald A. Ball Jr.
12
+ #
13
+ # Licensed under the Apache License, Version 2.0 (the "License");
14
+ # you may not use this file except in compliance with the License.
15
+ # You may obtain a copy of the License at
16
+ #
17
+ # http://www.apache.org/licenses/LICENSE-2.0
18
+ #
19
+ # Unless required by applicable law or agreed to in writing, software
20
+ # distributed under the License is distributed on an "AS IS" BASIS,
21
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
+ # See the License for the specific language governing permissions and
23
+ # limitations under the License.
24
+
25
+ module Zoomifier
26
+
27
+ TILESIZE = 256
28
+
29
+ # Zoomifies the image file specified by filename. The zoomified directory
30
+ # name will be the filename without its extension, e.g. 5.jpg will be
31
+ # zoomified into a directory named 5. If there is already a directory with
32
+ # this name, it will be destroyed without mercy.
33
+ def self.zoomify(filename)
34
+ raise ArgumentError unless filename && File.file?(filename)
35
+ #filename = File.expand_path(filename)
36
+ outputdir = File.dirname(filename) + '/' + File.basename(filename, '.*')
37
+ raise ArgumentError unless filename != outputdir
38
+ FileUtils.rm_rf(outputdir) if File.exists?(outputdir)
39
+ Dir.mkdir(outputdir)
40
+ tmpdir = "#{outputdir}/tmp"
41
+ Dir.mkdir(tmpdir)
42
+ tilesdir = nil
43
+ image = Magick::Image.read(filename).first.strip!
44
+ # Each level of zoom is a factor of 2. Here we obtain the number of zooms
45
+ # allowed by the original file dimensions and the constant tile size.
46
+ levels = (Math.log([image.rows, image.columns].max.to_f / TILESIZE) / Math.log(2)).ceil
47
+ tiles = 0
48
+ (0..levels).each do |level|
49
+ n = levels - level
50
+ # Obtain the image to tile for this level. The 0th level should consist
51
+ # of one tile, while the highest level should be the original image.
52
+ level_image = image.resize(image.columns >> n, image.rows >> n)
53
+ tiles(tmpdir, level, level_image) do |filename|
54
+ # The tile images are chunked into directories named TileGroupN, N
55
+ # starting at 0 and increasing monotonically. Each directory contains
56
+ # at most 256 images. The images are sorted by level, tile row, and
57
+ # tile column.
58
+ div, mod = tiles.divmod(256)
59
+ if mod == 0
60
+ tilesdir = "#{outputdir}/TileGroup#{div}"
61
+ Dir.mkdir(tilesdir)
62
+ end
63
+ FileUtils.mv("#{tmpdir}/#{filename}", "#{tilesdir}/#{filename}")
64
+ tiles += 1
65
+ end
66
+ # Rmagick needs a bit of help freeing image memory.
67
+ level_image = nil
68
+ GC.start
69
+ end
70
+ File.open("#{outputdir}/ImageProperties.xml", 'w') do |f|
71
+ f.write("<IMAGE_PROPERTIES WIDTH=\"#{image.columns}\" HEIGHT=\"#{image.rows}\" NUMTILES=\"#{tiles}\" NUMIMAGES=\"1\" VERSION=\"1.8\" TILESIZE=\"#{TILESIZE}\" />")
72
+ end
73
+ Dir.rmdir(tmpdir)
74
+ outputdir
75
+ end
76
+
77
+ # Splits the given image up into images of TILESIZE, writes them to the
78
+ # given directory, and yields their names
79
+ def self.tiles(dir, level, image)
80
+ slice(image.rows).each_with_index do |y_slice, j|
81
+ slice(image.columns).each_with_index do |x_slice, i|
82
+ # The images are named "level-column-row.jpg"
83
+ filename = "#{level}-#{i}-#{j}.jpg"
84
+ tile_image = image.crop(x_slice[0], y_slice[0], x_slice[1], y_slice[1])
85
+ tile_image.write("#{dir}/#{filename}") do
86
+ # FIXME - the images end up being 4-5x larger than those produced
87
+ # by Zoomifier EZ and friends... no idea why just yet, except to note
88
+ # that the density of these tiles ends up being 400x400, while
89
+ # everybody else produces tiles at 72x72. Can't see why that would
90
+ # matter though...
91
+ self.quality = 80
92
+ end
93
+ # Rmagick needs a bit of help freeing image memory.
94
+ tile_image = nil
95
+ GC.start
96
+ yield filename
97
+ end
98
+ end
99
+ end
100
+
101
+ # Returns an array of slices ([offset, length]) obtained by slicing the
102
+ # given number by TILESIZE.
103
+ # E.g. 256 -> [[0, 256]], 257 -> [[0, 256], [256, 1]],
104
+ # 513 -> [[0, 256], [256, 256], [512, 1]]
105
+ def self.slice(n)
106
+ results = []
107
+ i = 0
108
+ while true
109
+ if i + TILESIZE >= n
110
+ results << [i, n-i]
111
+ break
112
+ else
113
+ results << [i, TILESIZE]
114
+ i += TILESIZE
115
+ end
116
+ end
117
+ results
118
+ end
119
+
120
+ def self.unzoomify(url)
121
+ tmpdir = 'tmp'
122
+ FileUtils.rm_rf(tmpdir) if File.exists?(tmpdir)
123
+ Dir.mkdir(tmpdir)
124
+ doc = nil
125
+ begin
126
+ open("#{url}/ImageProperties.xml") do |f|
127
+ doc = REXML::Document.new(f)
128
+ end
129
+ rescue OpenURI::HTTPError
130
+ return nil
131
+ end
132
+ attrs = doc.root.attributes
133
+ return nil unless attrs['TILESIZE'] == '256' && attrs['VERSION'] == '1.8'
134
+ width = attrs['WIDTH'].to_i
135
+ height = attrs['HEIGHT'].to_i
136
+ tiles = attrs['NUMTILES'].to_i
137
+ image_paths = (0 .. tiles/256).map {|n| "TileGroup#{n}"}
138
+ max_level = 0
139
+ while (get_tile(url, image_paths, tmpdir, "#{max_level}-0-0.jpg"))
140
+ max_level += 1
141
+ end
142
+ max_level -= 1
143
+ image = Magick::Image.new(width, height)
144
+ (0 .. width / TILESIZE).each do |column|
145
+ (0 .. height / TILESIZE).each do |row|
146
+ filename = "#{max_level}-#{column}-#{row}.jpg"
147
+ get_tile(url, image_paths, tmpdir, filename)
148
+ tile_image = Magick::Image.read("#{tmpdir}/#{filename}").first
149
+ image.composite!(tile_image, column*TILESIZE, row*TILESIZE, Magick::OverCompositeOp)
150
+ time_image = nil
151
+ GC.start
152
+ end
153
+ end
154
+ # FIXME - get filename from the url
155
+ image.write('file.jpg') { self.quality = 90 }
156
+ image = nil
157
+ GC.start
158
+ FileUtils.rm_rf(tmpdir)
159
+ end
160
+
161
+ # TODO - could reduce the miss rate by using heuristics to guess the
162
+ # proper path from which to download the file
163
+ def self.get_tile(url, image_paths, tmpdir, filename)
164
+ image_paths.each do |path|
165
+ begin
166
+ open("#{tmpdir}/#{filename}", 'wb') {|f| f.write(open("#{url}/#{path}/#{filename}").read)}
167
+ return filename
168
+ rescue OpenURI::HTTPError
169
+ end
170
+ end
171
+ nil
172
+ end
173
+
174
+ end
175
+
176
+ if __FILE__ == $0
177
+ if ARGV.length == 1
178
+ Zoomifier::zoomify(ARGV[0])
179
+ else
180
+ puts "Usage: zoomify filename"
181
+ end
182
+ end
Binary file
Binary file
@@ -0,0 +1 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib/')
@@ -0,0 +1,114 @@
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 "On a 1024x768 JPEG file" do
10
+ before(:all) do
11
+ @input = File.dirname(__FILE__) + '/data/1024x768.jpg'
12
+ @output = File.dirname(__FILE__) + '/data/1024x768/'
13
+ @tiles = %w[0-0-0.jpg 1-1-1.jpg 2-1-0.jpg 2-2-1.jpg 2-3-2.jpg
14
+ 1-0-0.jpg 2-0-0.jpg 2-1-1.jpg 2-2-2.jpg
15
+ 1-0-1.jpg 2-0-1.jpg 2-1-2.jpg 2-3-0.jpg
16
+ 1-1-0.jpg 2-0-2.jpg 2-2-0.jpg 2-3-1.jpg]
17
+ FileUtils.rm_rf(@output)
18
+ Zoomifier::zoomify(@input)
19
+ end
20
+
21
+ after(:all) do
22
+ FileUtils.rm_rf(@output)
23
+ end
24
+
25
+ it "should create the output directory" do
26
+ File.directory?(@output).should be_true
27
+ end
28
+
29
+ it "should create the image properties file" do
30
+ File.file?(@output + '/ImageProperties.xml').should be_true
31
+ end
32
+
33
+ it "should create a tile group directory" do
34
+ File.directory?(@output + '/TileGroup0/').should be_true
35
+ end
36
+
37
+ it "should create the tiled images" do
38
+ tile_images = Dir.entries(@output + '/TileGroup0/').reject {|f| f.match(/^\./)}
39
+ tile_images.sort.should == @tiles.sort
40
+ tile_images.each do |file|
41
+ image = Magick::Image.read(@output + '/TileGroup0/' + file).first
42
+ image.rows.should <= 256
43
+ image.columns.should <= 256
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "On a 2973x2159 JPEG file" do
49
+ before(:all) do
50
+ @input = File.dirname(__FILE__) + '/data/2973x2159.jpg'
51
+ @output = File.dirname(__FILE__) + '/data/2973x2159/'
52
+ @tiles = %w[
53
+ 0-0-0.jpg 3-3-2.jpg 4-10-0.jpg 4-3-4.jpg 4-6-8.jpg
54
+ 1-0-0.jpg 3-3-3.jpg 4-10-1.jpg 4-3-5.jpg 4-7-0.jpg
55
+ 1-0-1.jpg 3-3-4.jpg 4-10-2.jpg 4-3-6.jpg 4-7-1.jpg
56
+ 1-1-0.jpg 3-4-0.jpg 4-10-3.jpg 4-3-7.jpg 4-7-2.jpg
57
+ 1-1-1.jpg 3-4-1.jpg 4-10-4.jpg 4-3-8.jpg 4-7-3.jpg
58
+ 2-0-0.jpg 3-4-2.jpg 4-10-5.jpg 4-4-0.jpg 4-7-4.jpg
59
+ 2-0-1.jpg 3-4-3.jpg 4-10-6.jpg 4-4-1.jpg 4-7-5.jpg
60
+ 2-0-2.jpg 3-4-4.jpg 4-10-7.jpg 4-4-2.jpg 4-7-6.jpg
61
+ 2-1-0.jpg 3-5-0.jpg 4-10-8.jpg 4-4-3.jpg 4-7-7.jpg
62
+ 2-1-1.jpg 3-5-1.jpg 4-11-0.jpg 4-4-4.jpg 4-7-8.jpg
63
+ 2-1-2.jpg 3-5-2.jpg 4-11-1.jpg 4-4-5.jpg 4-8-0.jpg
64
+ 2-2-0.jpg 3-5-3.jpg 4-11-2.jpg 4-4-6.jpg 4-8-1.jpg
65
+ 2-2-1.jpg 3-5-4.jpg 4-11-3.jpg 4-4-7.jpg 4-8-2.jpg
66
+ 2-2-2.jpg 4-0-0.jpg 4-11-4.jpg 4-4-8.jpg 4-8-3.jpg
67
+ 3-0-0.jpg 4-0-1.jpg 4-11-5.jpg 4-5-0.jpg 4-8-4.jpg
68
+ 3-0-1.jpg 4-0-2.jpg 4-11-6.jpg 4-5-1.jpg 4-8-5.jpg
69
+ 3-0-2.jpg 4-0-3.jpg 4-11-7.jpg 4-5-2.jpg 4-8-6.jpg
70
+ 3-0-3.jpg 4-0-4.jpg 4-11-8.jpg 4-5-3.jpg 4-8-7.jpg
71
+ 3-0-4.jpg 4-0-5.jpg 4-2-0.jpg 4-5-4.jpg 4-8-8.jpg
72
+ 3-1-0.jpg 4-0-6.jpg 4-2-1.jpg 4-5-5.jpg 4-9-0.jpg
73
+ 3-1-1.jpg 4-0-7.jpg 4-2-2.jpg 4-5-6.jpg 4-9-1.jpg
74
+ 3-1-2.jpg 4-0-8.jpg 4-2-3.jpg 4-5-7.jpg 4-9-2.jpg
75
+ 3-1-3.jpg 4-1-0.jpg 4-2-4.jpg 4-5-8.jpg 4-9-3.jpg
76
+ 3-1-4.jpg 4-1-1.jpg 4-2-5.jpg 4-6-0.jpg 4-9-4.jpg
77
+ 3-2-0.jpg 4-1-2.jpg 4-2-6.jpg 4-6-1.jpg 4-9-5.jpg
78
+ 3-2-1.jpg 4-1-3.jpg 4-2-7.jpg 4-6-2.jpg 4-9-6.jpg
79
+ 3-2-2.jpg 4-1-4.jpg 4-2-8.jpg 4-6-3.jpg 4-9-7.jpg
80
+ 3-2-3.jpg 4-1-5.jpg 4-3-0.jpg 4-6-4.jpg 4-9-8.jpg
81
+ 3-2-4.jpg 4-1-6.jpg 4-3-1.jpg 4-6-5.jpg
82
+ 3-3-0.jpg 4-1-7.jpg 4-3-2.jpg 4-6-6.jpg
83
+ 3-3-1.jpg 4-1-8.jpg 4-3-3.jpg 4-6-7.jpg]
84
+ FileUtils.rm_rf(@output)
85
+ Zoomifier::zoomify(@input)
86
+ end
87
+
88
+ after(:all) do
89
+ FileUtils.rm_rf(@output)
90
+ end
91
+
92
+ it "should create the output directory" do
93
+ File.directory?(@output).should be_true
94
+ end
95
+
96
+ it "should create the image properties file" do
97
+ File.file?(@output + '/ImageProperties.xml').should be_true
98
+ end
99
+
100
+ it "should create a tile group directory" do
101
+ File.directory?(@output + '/TileGroup0/').should be_true
102
+ end
103
+
104
+ it "should create the tiled images" do
105
+ tile_images = Dir.entries(@output + '/TileGroup0/').reject {|f| f.match(/^\./)}
106
+ tile_images.sort.should == @tiles.sort
107
+ tile_images.each do |file|
108
+ image = Magick::Image.read(@output + '/TileGroup0/' + file).first
109
+ image.rows.should <= 256
110
+ image.columns.should <= 256
111
+ end
112
+ end
113
+ end
114
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dball-zoomifier
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.2"
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 -08:00
13
+ default_executable: zoomify
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rmagick
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description:
25
+ email: donald.ball@gmail.com
26
+ executables:
27
+ - zoomify
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README.txt
32
+ files:
33
+ - lib/zoomifier.rb
34
+ - bin/zoomify
35
+ - spec/zoomifier_spec.rb
36
+ - spec/spec_helper.rb
37
+ - spec/data/1024x768.jpg
38
+ - spec/data/2973x2159.jpg
39
+ - README.txt
40
+ has_rdoc: true
41
+ homepage:
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.2.0
63
+ signing_key:
64
+ specification_version: 2
65
+ summary: A library for zoomifying images
66
+ test_files:
67
+ - spec/zoomifier_spec.rb