rszr 0.3.1

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 47015c365c2eaa3d2fb3178bd8c68b67dcd33428
4
+ data.tar.gz: 11440490c24628076aa8f42592640c055126e302
5
+ SHA512:
6
+ metadata.gz: e2353ad80438886866c5246d9483ba09000c3729142324e8e650b00d17415076017cfc0260986a647f3da0305d087fe193ff9fb60095cd0d2d82ab590a35fb53
7
+ data.tar.gz: ce0fefb4820593aa643a96f3c33d667c2a37c441f132541666cf60c3853866055ef5e1d4e44f7ee32f0d333ac70e095409c0a6835e2a2bb4f4a6b80187eff008
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Matthias Grosser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,74 @@
1
+ # Rszr
2
+
3
+ Rszr is a fast image resizer.
4
+
5
+ ## Installation
6
+
7
+ In your Gemfile:
8
+
9
+ ```ruby
10
+ gem 'rszr'
11
+ ```
12
+
13
+ ### Imlib2
14
+
15
+ Rszr requires the Imlib2 library to do the heavy lifting.
16
+
17
+ #### OS X
18
+
19
+ Using homebrew:
20
+
21
+ ```bash
22
+ brew install imlib2
23
+ ```
24
+
25
+ ### Linux
26
+
27
+ Using your favourite package manager:
28
+
29
+ ```bash
30
+ yum install imlib2
31
+ ```
32
+
33
+ ```bash
34
+ apt-get install libimlib2
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ```ruby
40
+ # bounding box 400x300
41
+ image = Rszr::Image.load('image.jpg')
42
+ image.resize(400, 300)
43
+ image.save('resized.jpg')
44
+
45
+ # auto height
46
+ image.resize(400, :auto)
47
+
48
+ # auto width
49
+ image.resize(:auto, 300)
50
+
51
+ # scale factor
52
+ image.resize(0.5)
53
+
54
+ # crop
55
+ image.crop(200, 200, 100, 100)
56
+
57
+ # save memory, do not duplicate instance
58
+ image.resize!(400, :auto)
59
+
60
+ # image dimensions
61
+ image.width => 400
62
+ image.height => 300
63
+ image.dimensions => [400, 300]
64
+ ```
65
+
66
+ ## Speed
67
+
68
+ Resizing an 1500x997 JPEG image to 800x532, 100 times:
69
+
70
+ Library | Time
71
+ ----------------|-----------
72
+ MiniMagick | 11.4 s
73
+ GD2 | 7.2 s
74
+ Rszr | 3.2 s
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,11 @@
1
+ require 'fiddle'
2
+ require 'fiddle/import'
3
+ require 'rbconfig'
4
+ require 'pathname'
5
+
6
+ require 'rszr/version'
7
+ require 'rszr/errors'
8
+ require 'rszr/lib'
9
+ require 'rszr/handle'
10
+ require 'rszr/base'
11
+ require 'rszr/image'
@@ -0,0 +1,27 @@
1
+ module Rszr
2
+ module Base
3
+
4
+ def self.included(base)
5
+ Rszr::Lib.delegate(base)
6
+ Rszr::Lib.delegate(base.singleton_class)
7
+ end
8
+
9
+ protected
10
+
11
+ def handle
12
+ @handle
13
+ end
14
+
15
+ def ptr
16
+ @handle.ptr
17
+ end
18
+
19
+ private
20
+
21
+ def assert_valid_keys(hsh, *valid_keys)
22
+ if unknown_key = (hsh.keys - valid_keys).first
23
+ raise ArgumentError.new("Unknown key: #{unknown_key.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ module Rszr
2
+ class Error < StandardError; end
3
+
4
+ class ErrorWithMessage < Error
5
+ MESSAGES = [nil,
6
+ 'File does not exist',
7
+ 'File is a directory',
8
+ 'Read permission denied',
9
+ 'Unsupported format',
10
+ 'Path too long',
11
+ 'Non-existant path component',
12
+ 'Path component is not a directory',
13
+ 'Path outside address space',
14
+ 'Too many symbolic links',
15
+ 'Out of memory',
16
+ 'Out of file descriptors',
17
+ 'Write permission denied',
18
+ 'Out of disk space',
19
+ 'Unknown error']
20
+
21
+ attr_reader :ptr
22
+
23
+ def initialize
24
+ @ptr = Fiddle::Pointer.malloc(Rszr::Lib.sizeof('Imlib_Load_Error'))
25
+ end
26
+
27
+ def error?
28
+ return if ptr.null?
29
+ 0 != ptr[0]
30
+ end
31
+
32
+ def message
33
+ MESSAGES[ptr[0]] unless ptr.null?
34
+ end
35
+ end
36
+
37
+
38
+ class FileNotFound < Error; end
39
+ class LoadError < ErrorWithMessage; end
40
+ class TransformationError < Error; end
41
+ class SaveError < ErrorWithMessage; end
42
+ end
@@ -0,0 +1,37 @@
1
+ module Rszr
2
+ class Handle
3
+ attr_reader :ptr, :klass
4
+
5
+ def initialize(obj, ptr)
6
+ @ptr = ptr
7
+ raise ArgumentError, "#{obj.class.inspect} does not define the finalize class method" unless obj.class.respond_to?(:finalize, true)
8
+ @klass = obj.class
9
+ ObjectSpace.define_finalizer(obj, self)
10
+ end
11
+
12
+ def replace!(other_ptr)
13
+ raise ArgumentError, "Cannot replace pointer with itself" if ptr == other_ptr
14
+ finalize!
15
+ @ptr = other_ptr
16
+ end
17
+
18
+ def finalize!(*args)
19
+ #puts "Finalizing #{args.inspect} #{ptr.inspect}"
20
+ if !ptr.null? && klass
21
+ #puts " calling #{args.inspect}"
22
+ klass.send(:finalize, ptr)
23
+ ptr = Fiddle::Pointer.new(0)
24
+ #else
25
+ #puts " skipping #{args.inspect}"
26
+ end
27
+ end
28
+
29
+ def release!
30
+ replace! Fiddle::Pointer.new(0)
31
+ end
32
+
33
+ def call(object_id)
34
+ finalize!(object_id)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,183 @@
1
+ module Rszr
2
+ class Image
3
+ include Base
4
+
5
+ class << self
6
+
7
+ alias_method :instantiate, :new
8
+ protected :instantiate
9
+
10
+ def new(width, height)
11
+ ptr = imlib_create_image(width, height)
12
+ raise Error, 'Could not instantiate image' if ptr.null?
13
+ instantiate(ptr)
14
+ end
15
+
16
+ def load(path, options = {})
17
+ path = path.to_s
18
+ raise FileNotFound unless File.exist?(path)
19
+ load_error = LoadError.new
20
+ imlib_set_cache_size(0)
21
+ ptr = imlib_load_image_with_error_return(path, load_error.ptr)
22
+ raise load_error, load_error.message if ptr.null?
23
+ return instantiate(ptr)
24
+ end
25
+ alias :open :load
26
+
27
+ protected
28
+
29
+ def finalize(ptr)
30
+ imlib_context_set_image(ptr)
31
+ imlib_free_image
32
+ end
33
+
34
+ end
35
+
36
+ def initialize(ptr)
37
+ @handle = Handle.new(self, ptr)
38
+ end
39
+
40
+ def width
41
+ context_set_image
42
+ imlib_image_get_width
43
+ end
44
+
45
+ def height
46
+ context_set_image
47
+ imlib_image_get_height
48
+ end
49
+
50
+ def dimensions
51
+ [width, height]
52
+ end
53
+
54
+ def format
55
+ context_set_image
56
+ str_ptr = imlib_image_format
57
+ return if str_ptr.null?
58
+ str_ptr.to_s
59
+ end
60
+
61
+ def resize(*args)
62
+ instantiate(create_resized_image(*args))
63
+ end
64
+
65
+ def resize!(*args)
66
+ handle.replace!(create_resized_image(*args))
67
+ self
68
+ end
69
+
70
+ def crop(*args)
71
+ instantiate(create_cropped_image(*args))
72
+ end
73
+
74
+ def crop!(*args)
75
+ handle.replace!(create_cropped_image(*args))
76
+ self
77
+ end
78
+
79
+ def save(path, format = nil)
80
+ context_set_image
81
+ format ||= format_from_filename(path) || 'jpg'
82
+ imlib_image_set_format(format)
83
+ save_error = SaveError.new
84
+ imlib_save_image_with_error_return(path, save_error.ptr)
85
+ raise save_error, save_error.message if save_error.error?
86
+ true
87
+ end
88
+
89
+ def inspect
90
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} width=#{width} height=#{height} format=#{format.inspect}>"
91
+ end
92
+
93
+ private
94
+
95
+ # 0.5 0 < scale < 1
96
+ # 400, 300 fit box
97
+ # 400, :auto fit width, auto height
98
+ # :auto, 300 auto width, fit height
99
+ # 400, 300, crop: :center_middle
100
+ # 400, 300, background: rgba
101
+ # 400, 300, aspect: false
102
+
103
+ def create_resized_image(*args)
104
+ options = args.last.is_a?(Hash) ? args.pop : {}
105
+ assert_valid_keys options, :crop, :background, :skew #:extend, :width, :height, :max_width, :max_height, :box
106
+ context_set_image
107
+ x, y, = 0, 0
108
+ if args.size == 1
109
+ scale = args.first
110
+ raise ArgumentError, "scale #{scale.inspect} out of range" unless scale > 0 && scale < 1
111
+ new_width = width.to_f * scale
112
+ new_height = height.to_f * scale
113
+ elsif args.size == 2
114
+ box_width, box_height = args
115
+ if :auto == box_width && box_height.is_a?(Numeric)
116
+ new_height = box_height
117
+ new_width = box_height.to_f / height.to_f * width.to_f
118
+ elsif box_width.is_a?(Numeric) && :auto == box_height
119
+ new_width = box_width
120
+ new_height = box_width.to_f / width.to_f * height.to_f
121
+ elsif box_width.is_a?(Numeric) && box_height.is_a?(Numeric)
122
+ if options[:skew]
123
+ new_width, new_height = box_width, box_height
124
+ elsif options[:crop]
125
+ # TODO: calculate x, y offset if crop
126
+ else
127
+ scale = width.to_f / height.to_f
128
+ box_scale = box_width.to_f / box_height.to_f
129
+ if scale >= box_scale # wider
130
+ new_width = box_width
131
+ new_height = height.to_f * box_width.to_f / width.to_f
132
+ else # narrower
133
+ new_height = box_height
134
+ new_width = width.to_f * box_height.to_f / height.to_f
135
+ end
136
+ end
137
+ else
138
+ raise ArgumentError, "unconclusive arguments #{args.inspect} #{options.inspect}"
139
+ end
140
+ else
141
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 1..2)"
142
+ end
143
+
144
+ #new_width = options[:width] || imlib_image_get_width
145
+ #new_height = options[:height] || imlib_image_get_height
146
+ #if max_width = options[:max_width] and new_width > max_width
147
+ # scale = max_width.to_f / new_width.to_f
148
+ # width = max_width
149
+ # new_height = (scale * new_height).to_i
150
+ #end
151
+ #if max_height = options[:max_height] and new_height > max_height
152
+ # scale = max_height.to_f / new_height.to_f
153
+ # new_height = max_height
154
+ # new_width = (scale * new_width).to_i
155
+ #end
156
+
157
+ imlib_context_set_anti_alias(1)
158
+ resized_ptr = imlib_create_cropped_scaled_image(x, y, imlib_image_get_width, imlib_image_get_height, new_width.round, new_height.round)
159
+ raise TransformationError, "error resizing image" if resized_ptr.null?
160
+ resized_ptr
161
+ end
162
+
163
+ def create_cropped_image(x, y, width, height)
164
+ context_set_image
165
+ cropped_ptr = imlib_create_cropped_image(x, y, width, height)
166
+ raise TransformationError, 'error cropping image' if cropped_ptr.null?
167
+ cropped_ptr
168
+ end
169
+
170
+ def format_from_filename(path)
171
+ File.extname(path)[1..-1]
172
+ end
173
+
174
+ def context_set_image
175
+ imlib_context_set_image(ptr)
176
+ end
177
+
178
+ def instantiate(ptr)
179
+ self.class.send(:instantiate, ptr)
180
+ end
181
+
182
+ end
183
+ end
@@ -0,0 +1,64 @@
1
+ module Rszr
2
+
3
+ module Lib
4
+ extend Fiddle::Importer
5
+
6
+ dlload case RbConfig::CONFIG['arch']
7
+ when /darwin/ then 'libImlib2.dylib'
8
+ when /mswin/, /cygwin/ then 'imlib2.dll'
9
+ else
10
+ 'libImlib2.so'
11
+ end
12
+
13
+ typealias 'Imlib_Image', 'void *'
14
+ typealias 'Imlib_Context', 'void *'
15
+ typealias 'enum', 'int'
16
+ typealias 'Imlib_Load_Error', 'enum'
17
+
18
+ #typedef void *Imlib_Color_Modifier;
19
+ #typedef void *Imlib_Updates;
20
+ #typedef void *Imlib_Font;
21
+ #typedef void *Imlib_Color_Range;
22
+ #typedef void *Imlib_Filter;
23
+ #typedef struct _imlib_border Imlib_Border;
24
+ #typedef struct _imlib_color Imlib_Color;
25
+ #typedef void *ImlibPolygon;
26
+
27
+ extern 'int imlib_get_cache_size()'
28
+ extern 'void imlib_set_cache_size(int)'
29
+
30
+ extern 'Imlib_Image imlib_load_image(const char *)'
31
+ extern 'Imlib_Image imlib_load_image_without_cache(const char *)'
32
+ extern 'Imlib_Image imlib_load_image_with_error_return(const char *, Imlib_Load_Error *)'
33
+ extern 'Imlib_Image imlib_create_image(int, int)'
34
+ extern 'void imlib_context_set_image(Imlib_Image)'
35
+ extern 'int imlib_image_get_width()'
36
+ extern 'int imlib_image_get_height()'
37
+ extern 'void imlib_context_set_anti_alias(char)'
38
+
39
+ # source_x, source_y, source_width, source_height, destination_width, destination_height
40
+ extern 'Imlib_Image imlib_create_cropped_scaled_image(int, int, int, int, int, int)'
41
+ # x, y, width, height
42
+ extern 'Imlib_Image imlib_create_cropped_image(int, int, int, int)'
43
+
44
+ extern 'char * imlib_image_format()'
45
+ extern 'void imlib_image_set_format(const char *)'
46
+ extern 'void imlib_save_image(const char *)'
47
+ extern 'void imlib_save_image_with_error_return(const char *, Imlib_Load_Error *)'
48
+
49
+ extern 'void imlib_free_image()'
50
+ extern 'void imlib_free_image_and_decache()'
51
+
52
+ def self.delegate(base)
53
+ Lib.methods.grep(/\Aimlib_/).each do |method|
54
+ line_no = __LINE__; str = %Q{
55
+ def #{method}(*args, &block)
56
+ Lib.#{method}(*args, &block)
57
+ end
58
+ }
59
+ base.class_eval(str, __FILE__, line_no)
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module Rszr
2
+ VERSION = "0.3.1"
3
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rszr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-09-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: gd2-ffij
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: mini_magick
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Fast image resizer - do one thing and do it fast.
112
+ email:
113
+ - mtgrosser@gmx.net
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - LICENSE
119
+ - README.md
120
+ - Rakefile
121
+ - lib/rszr.rb
122
+ - lib/rszr/base.rb
123
+ - lib/rszr/errors.rb
124
+ - lib/rszr/handle.rb
125
+ - lib/rszr/image.rb
126
+ - lib/rszr/lib.rb
127
+ - lib/rszr/version.rb
128
+ homepage: https://github.com/mtgrosser/rszr
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.4.5
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Fast image resizer
152
+ test_files: []