dimension 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/examples/sinatra.rb +17 -1
- data/lib/dimension.rb +19 -9
- data/lib/dimension/image.rb +15 -4
- data/lib/dimension/processors/image_magick.rb +3 -2
- data/lib/dimension/processors/imlib2.rb +0 -11
- data/lib/dimension/processors/vips.rb +142 -0
- data/lib/dimension/version.rb +2 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 193039197b19c73705300522aaf23f6ae3b07aee
|
4
|
+
data.tar.gz: c741743c6dfd4ab233832a46cc97e2b77f8b3901
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6660d273e74e9772aac18cf4d29c585b517ec4e117f933015c5d0b81c82f9986af371c1c8d10c9c25cced1624db8ea2eeae519d5694ea6b50d0d47ababab371b
|
7
|
+
data.tar.gz: a3b939d1c3d5d10b022d71cbfc1ab04549e278ba396b9424448e058e77a5a69f814d5e1920b7e77634f6a5cb524a8a62c40615a80727e4a00943cbf5489e8228
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@ Fast, simplified image resizing for Ruby. No ImageMagick.
|
|
5
5
|
|
6
6
|
``` rb
|
7
7
|
require 'dimension'
|
8
|
-
|
8
|
+
|
9
9
|
thumb = Dimension.open('tux.png')
|
10
10
|
thumb.generate('100x100') # => { :width => 100, :height => 100 }
|
11
11
|
thumb.save('resized.png')
|
@@ -33,7 +33,7 @@ You can also pass a block, which will ensure the original image is closed after
|
|
33
33
|
``` rb
|
34
34
|
get '/resize/:file' do
|
35
35
|
thumb = Dimension.open(params[:file])
|
36
|
-
thumb.generate('200x300
|
36
|
+
thumb.generate('200x300@') do
|
37
37
|
thumb.to_response
|
38
38
|
end
|
39
39
|
end
|
data/examples/sinatra.rb
CHANGED
@@ -3,11 +3,27 @@ ROOT = File.expand_path(File.dirname(__FILE__))
|
|
3
3
|
require 'sinatra'
|
4
4
|
require ROOT + '/../lib/dimension'
|
5
5
|
|
6
|
+
if processor = ARGV[0]
|
7
|
+
puts "Using #{processor} as processor"
|
8
|
+
Dimension.processor = processor
|
9
|
+
end
|
10
|
+
|
11
|
+
geometries = [
|
12
|
+
'200x200',
|
13
|
+
'300x200!',
|
14
|
+
'200x300#',
|
15
|
+
'20x50:ne'
|
16
|
+
]
|
17
|
+
|
6
18
|
get '/' do
|
7
19
|
images = Dir.glob(File.join(ROOT, 'assets') + '/*')
|
20
|
+
index = 0
|
8
21
|
links = images.map do |i|
|
9
22
|
name = File.basename(i)
|
10
|
-
|
23
|
+
geom = geometries[index]
|
24
|
+
index += 1
|
25
|
+
index = 0 unless geometries[index]
|
26
|
+
"<a href='/images/#{name}?geometry=#{geom}'>#{name}</a>"
|
11
27
|
end
|
12
28
|
'<ul><li>' + links.join('</li><li>') + '</li></ul>'
|
13
29
|
end
|
data/lib/dimension.rb
CHANGED
@@ -6,6 +6,7 @@ module Dimension
|
|
6
6
|
ROOT = File.expand_path(File.dirname(__FILE__))
|
7
7
|
|
8
8
|
PROCESSORS = {
|
9
|
+
'vips' => 'VipsProcessor',
|
9
10
|
'imlib2' => 'Imlib2Processor',
|
10
11
|
'image_magick' => 'ImageMagickProcessor'
|
11
12
|
}
|
@@ -27,14 +28,23 @@ module Dimension
|
|
27
28
|
|
28
29
|
end
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
def load_processor(name)
|
32
|
+
# puts "Loading #{name}"
|
33
|
+
require name
|
34
|
+
Dimension.processor = name
|
35
|
+
true
|
36
|
+
rescue LoadError => e
|
37
|
+
# puts "#{name} not found."
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
unless load_processor 'vips'
|
42
|
+
unless load_processor 'imlib2'
|
43
|
+
out = `which convert`
|
44
|
+
if $?.success?
|
45
|
+
Dimension.processor = 'image_magick'
|
46
|
+
else
|
47
|
+
puts "No available processors found. Please install ruby-vips, ruby-imlib2 or ImageMagick."
|
48
|
+
end
|
39
49
|
end
|
40
50
|
end
|
data/lib/dimension/image.rb
CHANGED
@@ -18,7 +18,7 @@ class Image
|
|
18
18
|
|
19
19
|
# Geometry string patterns
|
20
20
|
RESIZE_GEOMETRY = /^(\d+)?x(\d+)?[><%^!]?$|^\d+@$/ # e.g. '300x200!'
|
21
|
-
CROPPED_RESIZE_GEOMETRY = /^(\d+)x(\d+)[
|
21
|
+
CROPPED_RESIZE_GEOMETRY = /^(\d+)x(\d+)[:|@](\w{1,2})?$/ # e.g. '20x50:ne'
|
22
22
|
CROP_GEOMETRY = /^(\d+)x(\d+)([+-]\d+)?([+-]\d+)?(\w{1,2})?$/ # e.g. '30x30+10+10'
|
23
23
|
|
24
24
|
attr_reader :file
|
@@ -46,13 +46,14 @@ class Image
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def generate!(geometry, output_file = nil)
|
49
|
+
@geometry_string = geometry # for saving as [file]-[geometry].[ext]
|
49
50
|
resize_to(geometry)
|
50
51
|
save(output_file) && self
|
51
52
|
end
|
52
53
|
|
53
54
|
def resize_to(geometry)
|
54
55
|
case geometry
|
55
|
-
when RESIZE_GEOMETRY
|
56
|
+
when RESIZE_GEOMETRY
|
56
57
|
log "Resize -- #{$1}x#{$2}"
|
57
58
|
resize($1, $2)
|
58
59
|
when CROPPED_RESIZE_GEOMETRY
|
@@ -71,7 +72,6 @@ class Image
|
|
71
72
|
|
72
73
|
def save(out_file)
|
73
74
|
new_geometry = get_new_geometry
|
74
|
-
|
75
75
|
path = @path
|
76
76
|
|
77
77
|
if out_file and File.directory?(out_file)
|
@@ -80,7 +80,7 @@ class Image
|
|
80
80
|
end
|
81
81
|
|
82
82
|
if out_file.nil?
|
83
|
-
out_file = File.join(path, @name.sub(File.extname(file), '-' +
|
83
|
+
out_file = File.join(path, @name.sub(File.extname(file), '-' + @geometry_string + File.extname(file)))
|
84
84
|
end
|
85
85
|
|
86
86
|
log "Writing file: #{out_file}"
|
@@ -98,6 +98,17 @@ class Image
|
|
98
98
|
to_response
|
99
99
|
end
|
100
100
|
|
101
|
+
def to_rgba
|
102
|
+
bytes = image_data.bytes
|
103
|
+
(1..bytes.length).step(4).map { |i| bytes[i..i+2] << bytes[i-1] }.flatten
|
104
|
+
end
|
105
|
+
|
106
|
+
# transforms data (RGBA buffer) into a array of RGB values
|
107
|
+
def to_rgb
|
108
|
+
bytes = image_data.bytes
|
109
|
+
(1..bytes.length).step(4).map { |i| [bytes[i-1],bytes[i],bytes[i+1]] }.flatten
|
110
|
+
end
|
111
|
+
|
101
112
|
def inspect
|
102
113
|
geometry = get_new_geometry
|
103
114
|
"#<Dimension::Image:#{object_id} @width=#{geometry[0]}, @height=#{geometry[1]}>"
|
@@ -9,13 +9,14 @@ module ImageMagickProcessor
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def image_data
|
12
|
-
|
12
|
+
@current_path = path if @current_path.nil?
|
13
|
+
IO.read(@current_path)
|
13
14
|
end
|
14
15
|
|
15
16
|
def save_as(new_file_path)
|
16
17
|
return if new_file_path == @temp_file
|
17
18
|
FileUtils.mv(@temp_file, new_file_path)
|
18
|
-
@
|
19
|
+
@current_path = new_file_path
|
19
20
|
end
|
20
21
|
|
21
22
|
def get_new_geometry
|
@@ -16,17 +16,6 @@ module Imlib2Processor
|
|
16
16
|
File.extname(file).sub('.', '')
|
17
17
|
end
|
18
18
|
|
19
|
-
def to_rgba
|
20
|
-
bytes = data.bytes
|
21
|
-
(1..bytes.length).step(4).map { |i| bytes[i..i+2] << bytes[i-1] }.flatten
|
22
|
-
end
|
23
|
-
|
24
|
-
# transforms data (RGBA buffer) into a array of RGB values
|
25
|
-
def to_rgb
|
26
|
-
bytes = data.bytes
|
27
|
-
(1..bytes.length).step(4).map { |i| [bytes[i-1],bytes[i],bytes[i+1]] }.flatten
|
28
|
-
end
|
29
|
-
|
30
19
|
def image_data
|
31
20
|
unless @temp_file
|
32
21
|
@temp_file = "/tmp/#{$$}.#{File.basename(file)}"
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# from examples at
|
2
|
+
# - https://github.com/eltiare/carrierwave-vips/blob/master/lib/carrierwave/vips.rb
|
3
|
+
# - https://github.com/jcupitt/ruby-vips/tree/master/examples
|
4
|
+
|
5
|
+
require 'vips'
|
6
|
+
|
7
|
+
module VipsProcessor
|
8
|
+
|
9
|
+
FORMAT_OPTS = {
|
10
|
+
'jpeg' => { :quality => 0.9 },
|
11
|
+
'png' => { :compression => 6, :interlace => false }
|
12
|
+
}
|
13
|
+
|
14
|
+
SHARPEN_MASK = begin
|
15
|
+
conv_mask = [
|
16
|
+
[ -1, -1, -1 ],
|
17
|
+
[ -1, 24, -1 ],
|
18
|
+
[ -1, -1, -1 ]
|
19
|
+
]
|
20
|
+
::VIPS::Mask.new conv_mask, 16
|
21
|
+
end
|
22
|
+
|
23
|
+
def image
|
24
|
+
@image ||= if format == 'jpeg'
|
25
|
+
VIPS::Image.jpeg(@file, :sequential => true)
|
26
|
+
elsif format == 'png'
|
27
|
+
VIPS::Image.png(@file, :sequential => true)
|
28
|
+
else
|
29
|
+
VIPS::Image.new(@file)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def geometry
|
34
|
+
[image.x_size, image.y_size]
|
35
|
+
end
|
36
|
+
|
37
|
+
def format
|
38
|
+
file[/.png$/] ? 'png' : file[/jpe?g$/] ? 'jpeg' : File.extname(file).sub('.', '')
|
39
|
+
end
|
40
|
+
|
41
|
+
def image_data
|
42
|
+
writer.to_memory
|
43
|
+
end
|
44
|
+
|
45
|
+
def save_as(new_file_path)
|
46
|
+
save(new_file_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def save!
|
50
|
+
save(file)
|
51
|
+
end
|
52
|
+
|
53
|
+
def close
|
54
|
+
log "Closing image and cutting thread..."
|
55
|
+
@image = nil
|
56
|
+
VIPS::thread_shutdown
|
57
|
+
# image.delete!(true) # free image, and de-cache it too
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_new_geometry
|
61
|
+
geometry
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def resize(width, height, min_or_max = :min)
|
67
|
+
ratio = get_ratio(width, height, min_or_max)
|
68
|
+
if ratio == 1
|
69
|
+
return log "Same ratio as original."
|
70
|
+
end
|
71
|
+
if ratio > 1
|
72
|
+
@image = image.affinei_resize :nearest, ratio
|
73
|
+
else
|
74
|
+
if ratio <= 0.5
|
75
|
+
factor = (1.0 / ratio).floor
|
76
|
+
@image = image.shrink(factor)
|
77
|
+
@image = image.tile_cache(image.x_size, 1, 30)
|
78
|
+
ratio = get_ratio(width, height, min_or_max)
|
79
|
+
end
|
80
|
+
@image = image.affinei_resize :bicubic, ratio
|
81
|
+
@image = image.conv SHARPEN_MASK
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def resize_and_crop(new_width, new_height, gravity)
|
86
|
+
resize(new_width, new_height, :max)
|
87
|
+
|
88
|
+
if image.x_size > new_width.to_i
|
89
|
+
top = 0
|
90
|
+
left = (image.x_size - new_width.to_i) / 2
|
91
|
+
elsif image.y_size > new_height.to_i
|
92
|
+
left = 0
|
93
|
+
top = (image.y_size - new_height.to_i) / 2
|
94
|
+
else
|
95
|
+
left = 0
|
96
|
+
top = 0
|
97
|
+
end
|
98
|
+
|
99
|
+
@image = image.extract_area(left, top, new_width.to_i, new_height.to_i)
|
100
|
+
end
|
101
|
+
|
102
|
+
def crop(width, height, x, y, gravity)
|
103
|
+
raise "TODO"
|
104
|
+
end
|
105
|
+
|
106
|
+
def crop_scaled(x, y, new_w, new_h)
|
107
|
+
raise "TODO"
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_ratio(width, height, min_or_max = :min)
|
111
|
+
width_ratio = width.to_f / image.x_size
|
112
|
+
height_ratio = height.to_f / image.y_size
|
113
|
+
if height.nil?
|
114
|
+
width_ratio
|
115
|
+
elsif width.nil?
|
116
|
+
height_ratio
|
117
|
+
else
|
118
|
+
[width_ratio, height_ratio].send(min_or_max)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def write(filename, strip = false)
|
123
|
+
if strip
|
124
|
+
writer.remove_exif
|
125
|
+
writer.remove_icc
|
126
|
+
end
|
127
|
+
writer.write(filename)
|
128
|
+
end
|
129
|
+
|
130
|
+
def writer
|
131
|
+
@writer ||= writer_class.send(:new, @image, FORMAT_OPTS[format] || {})
|
132
|
+
end
|
133
|
+
|
134
|
+
def writer_class
|
135
|
+
case format
|
136
|
+
when 'jpeg' then VIPS::JPEGWriter
|
137
|
+
when 'png' then VIPS::PNGWriter
|
138
|
+
else VIPS::Writer
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
data/lib/dimension/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dimension
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tomás Pollak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack-throttle
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- lib/dimension/middleware.rb
|
50
50
|
- lib/dimension/processors/image_magick.rb
|
51
51
|
- lib/dimension/processors/imlib2.rb
|
52
|
+
- lib/dimension/processors/vips.rb
|
52
53
|
- lib/dimension/version.rb
|
53
54
|
homepage: https://github.com/tomas/dimension
|
54
55
|
licenses: []
|