rszr 0.5.3 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +89 -13
- data/ext/rszr/errors.c +8 -3
- data/ext/rszr/image.c +80 -16
- data/lib/rszr/batch_transformation.rb +24 -0
- data/lib/rszr/buffered.rb +25 -0
- data/lib/rszr/color.rb +25 -0
- data/lib/rszr/identification.rb +60 -0
- data/lib/rszr/image.rb +168 -46
- data/lib/rszr/image_processing.rb +4 -4
- data/lib/rszr/orientation.rb +107 -0
- data/lib/rszr/stream.rb +61 -0
- data/lib/rszr/version.rb +1 -1
- data/lib/rszr.rb +21 -0
- metadata +13 -175
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51f96b1754949daff45bf2d18b32d2857d5b82f33c285b85bbaf5fdb25d12f0b
|
4
|
+
data.tar.gz: 956217311a71053dcf0a781afa306b4f27c49ff4b00487e7d470f9290b91a0c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fd6b4f8cf30f5f0a811b802aed1ab6dcfd709bdde3c803689d700276f6c0a996d5a93ecaee2587679097cb6faddea689296788c773215ff19ac4033191ee2ea
|
7
|
+
data.tar.gz: 7fc2f31485281ed5ccfc2b7c679fbda4cb03baa261aee18e661331dbc71adb5f98db8015aae46bbea6f27f016bd2f641ca7e46b12f6d5cebe975e09240b25d0b
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[](http://badge.fury.io/rb/rszr) [](http://badge.fury.io/rb/rszr) [](https://github.com/mtgrosser/rszr/actions/workflows/build.yml)
|
2
2
|
# Rszr - fast image resizer for Ruby
|
3
3
|
|
4
4
|
Rszr is an image resizer for Ruby based on the Imlib2 library.
|
@@ -14,7 +14,7 @@ gem 'rszr'
|
|
14
14
|
|
15
15
|
### Imlib2
|
16
16
|
|
17
|
-
Rszr requires the Imlib2 library to do the heavy lifting.
|
17
|
+
Rszr requires the `Imlib2` library to do the heavy lifting.
|
18
18
|
|
19
19
|
#### OS X
|
20
20
|
|
@@ -28,10 +28,14 @@ brew install imlib2
|
|
28
28
|
|
29
29
|
Using your favourite package manager:
|
30
30
|
|
31
|
+
##### RedHat-based
|
32
|
+
|
31
33
|
```bash
|
32
34
|
yum install imlib2 imlib2-devel
|
33
35
|
```
|
34
36
|
|
37
|
+
##### Debian-based
|
38
|
+
|
35
39
|
```bash
|
36
40
|
apt-get install libimlib2 libimlib2-dev
|
37
41
|
```
|
@@ -50,12 +54,25 @@ image.save('resized.jpg')
|
|
50
54
|
image.save('resized.png')
|
51
55
|
```
|
52
56
|
|
57
|
+
### Image info
|
58
|
+
```ruby
|
59
|
+
image.width => 400
|
60
|
+
image.height => 300
|
61
|
+
image.dimensions => [400, 300]
|
62
|
+
image.format => "jpeg"
|
63
|
+
image[0, 0] => <Rszr::Color::RGBA @red=38, @green=115, @blue=141, @alpha=255>
|
64
|
+
image[0, 0].to_hex => "26738dff"
|
65
|
+
image[0, 0].to_hex(rgb: true) => "26738d"
|
66
|
+
```
|
67
|
+
|
53
68
|
### Transformations
|
54
69
|
|
55
70
|
For each transformation, there is a bang! and non-bang method.
|
56
71
|
The bang method changes the image in place, while the non-bang method
|
57
72
|
creates a copy of the image in memory.
|
58
73
|
|
74
|
+
#### Resizing
|
75
|
+
|
59
76
|
```ruby
|
60
77
|
# auto height
|
61
78
|
image.resize(400, :auto)
|
@@ -66,6 +83,22 @@ image.resize(:auto, 300)
|
|
66
83
|
# scale factor
|
67
84
|
image.resize(0.5)
|
68
85
|
|
86
|
+
# resize to fill
|
87
|
+
image.resize(400, 300, crop: true)
|
88
|
+
|
89
|
+
# resize to fill with gravity
|
90
|
+
# where gravity in [:n, :nw, :w, :sw, :w, :se, :e, :ne, :center]
|
91
|
+
image.resize(400, 300, crop: gravity)
|
92
|
+
|
93
|
+
# save memory, do not duplicate instance
|
94
|
+
image.resize!(400, :auto)
|
95
|
+
```
|
96
|
+
|
97
|
+
Check out the [full list and demo of resize options](https://mtgrosser.github.io/rszr/resizing.html)!
|
98
|
+
|
99
|
+
#### Other transformations
|
100
|
+
|
101
|
+
```ruby
|
69
102
|
# crop
|
70
103
|
image.crop(200, 200, 100, 100)
|
71
104
|
|
@@ -78,35 +111,65 @@ image.turn!(-1)
|
|
78
111
|
# rotate by arbitrary angle
|
79
112
|
image.rotate(45)
|
80
113
|
|
114
|
+
# flip vertically
|
115
|
+
image.flip
|
116
|
+
|
117
|
+
# flop horizontally
|
118
|
+
image.flop
|
119
|
+
|
120
|
+
# initialize copy
|
121
|
+
image.dup
|
122
|
+
```
|
123
|
+
|
124
|
+
### Filters
|
125
|
+
|
126
|
+
Filters also support bang! and non-bang methods.
|
127
|
+
|
128
|
+
```ruby
|
81
129
|
# sharpen image by pixel radius
|
82
130
|
image.sharpen!(1)
|
83
131
|
|
84
132
|
# blur image by pixel radius
|
85
133
|
image.blur!(1)
|
86
134
|
|
87
|
-
#
|
88
|
-
image.
|
135
|
+
# brighten
|
136
|
+
image.brighten(0.1)
|
89
137
|
|
90
|
-
#
|
91
|
-
image.
|
138
|
+
# darken
|
139
|
+
image.brighten(-0.1)
|
140
|
+
|
141
|
+
# contrast
|
142
|
+
image.contrast(0.5)
|
143
|
+
|
144
|
+
# gamma
|
145
|
+
image.gamma(1.1)
|
92
146
|
```
|
93
147
|
|
94
|
-
### Image
|
148
|
+
### Image auto orientation
|
149
|
+
|
150
|
+
Auto-rotation is supported for JPEG and TIFF files that include the necessary
|
151
|
+
EXIF metadata.
|
152
|
+
|
95
153
|
```ruby
|
96
|
-
|
97
|
-
image.
|
98
|
-
|
99
|
-
|
154
|
+
# load and autorotate
|
155
|
+
image = Rszr::Image.load('image.jpg', autorotate: true)
|
156
|
+
```
|
157
|
+
|
158
|
+
To enable autorotation by default:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
# auto-rotate by default, for Rails apps put this into an initializer
|
162
|
+
Rszr.autorotate = true
|
100
163
|
```
|
101
164
|
|
102
165
|
## Rails / ActiveStorage interface
|
103
166
|
|
104
|
-
Rszr provides a drop-in interface to the `
|
167
|
+
Rszr provides a drop-in interface to the `image_processing` gem.
|
105
168
|
It is faster than both `mini_magick` and `vips` and way easier to install than the latter.
|
106
169
|
|
107
170
|
```ruby
|
108
171
|
# Gemfile
|
109
|
-
gem '
|
172
|
+
gem 'image_processing'
|
110
173
|
gem 'rszr'
|
111
174
|
|
112
175
|
# config/initializers/rszr.rb
|
@@ -122,6 +185,19 @@ When creating image variants, you can use all of Rszr's transformation methods:
|
|
122
185
|
<%= image_tag user.avatar.variant(resize_to_fit: [300, 200]) %>
|
123
186
|
```
|
124
187
|
|
188
|
+
## Loading from and saving to memory
|
189
|
+
|
190
|
+
The `Imlib2` library is mainly file-oriented and doesn't provide a way of loading
|
191
|
+
the undecoded image from a memory buffer. Therefore, the functionality is
|
192
|
+
implemented on the Ruby side of the gem, writing the memory buffer to a Tempfile.
|
193
|
+
Currently, this local write cannot be avoided.
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
image = Rszr::Image.load_data(binary_data)
|
197
|
+
|
198
|
+
data = image.save_data(format: :jpeg)
|
199
|
+
```
|
200
|
+
|
125
201
|
## Thread safety
|
126
202
|
|
127
203
|
As of version 0.5.0, Rszr is thread safe through Ruby's global VM lock.
|
data/ext/rszr/errors.c
CHANGED
@@ -42,10 +42,15 @@ void Init_rszr_errors()
|
|
42
42
|
|
43
43
|
static void rszr_raise_error_with_message(VALUE rb_error_class, Imlib_Load_Error error)
|
44
44
|
{
|
45
|
-
|
45
|
+
VALUE rb_error;
|
46
|
+
int error_index;
|
47
|
+
|
48
|
+
error_index = (int) error - 1;
|
49
|
+
|
46
50
|
if (error_index < 1 || error_index > RSZR_MAX_ERROR_INDEX)
|
47
|
-
error_index =
|
48
|
-
|
51
|
+
error_index = RSZR_MAX_ERROR_INDEX;
|
52
|
+
|
53
|
+
rb_error = rb_exc_new2(rb_error_class, sRszrErrorMessages[error_index]);
|
49
54
|
rb_exc_raise(rb_error);
|
50
55
|
}
|
51
56
|
|
data/ext/rszr/image.c
CHANGED
@@ -27,7 +27,6 @@ static void rszr_image_deallocate(rszr_image_handle * handle)
|
|
27
27
|
// fprintf(stderr, "\n");
|
28
28
|
}
|
29
29
|
|
30
|
-
|
31
30
|
static VALUE rszr_image_s_allocate(VALUE klass)
|
32
31
|
{
|
33
32
|
rszr_image_handle * handle = calloc(1, sizeof(rszr_image_handle));
|
@@ -61,13 +60,20 @@ static VALUE rszr_image_s__load(VALUE klass, VALUE rb_path)
|
|
61
60
|
path = StringValueCStr(rb_path);
|
62
61
|
|
63
62
|
imlib_set_cache_size(0);
|
64
|
-
image =
|
63
|
+
image = imlib_load_image_without_cache(path);
|
65
64
|
|
66
65
|
if (!image) {
|
67
|
-
|
68
|
-
|
66
|
+
image = imlib_load_image_with_error_return(path, &error);
|
67
|
+
|
68
|
+
if (!image) {
|
69
|
+
rszr_raise_load_error(error);
|
70
|
+
return Qnil;
|
71
|
+
}
|
69
72
|
}
|
70
73
|
|
74
|
+
imlib_context_set_image(image);
|
75
|
+
imlib_image_set_irrelevant_format(0);
|
76
|
+
|
71
77
|
oImage = rszr_image_s_allocate(cImage);
|
72
78
|
Data_Get_Struct(oImage, rszr_image_handle, handle);
|
73
79
|
handle->image = image;
|
@@ -75,7 +81,7 @@ static VALUE rszr_image_s__load(VALUE klass, VALUE rb_path)
|
|
75
81
|
}
|
76
82
|
|
77
83
|
|
78
|
-
static VALUE
|
84
|
+
static VALUE rszr_image__format_get(VALUE self)
|
79
85
|
{
|
80
86
|
rszr_image_handle * handle;
|
81
87
|
char * format;
|
@@ -134,6 +140,32 @@ static VALUE rszr_image_height(VALUE self)
|
|
134
140
|
return INT2NUM(height);
|
135
141
|
}
|
136
142
|
|
143
|
+
|
144
|
+
static VALUE rszr_image__pixel_get(VALUE self, VALUE rb_x, VALUE rb_y)
|
145
|
+
{
|
146
|
+
rszr_image_handle * handle;
|
147
|
+
Imlib_Color color_return;
|
148
|
+
VALUE rb_rgba;
|
149
|
+
int x, y;
|
150
|
+
|
151
|
+
Check_Type(rb_x, T_FIXNUM);
|
152
|
+
x = FIX2INT(rb_x);
|
153
|
+
Check_Type(rb_y, T_FIXNUM);
|
154
|
+
y = FIX2INT(rb_y);
|
155
|
+
|
156
|
+
Data_Get_Struct(self, rszr_image_handle, handle);
|
157
|
+
|
158
|
+
imlib_context_set_image(handle->image);
|
159
|
+
imlib_image_query_pixel(x, y, &color_return);
|
160
|
+
|
161
|
+
rb_rgba = rb_ary_new3(4, INT2NUM(color_return.red),
|
162
|
+
INT2NUM(color_return.green),
|
163
|
+
INT2NUM(color_return.blue),
|
164
|
+
INT2NUM(color_return.alpha));
|
165
|
+
return rb_rgba;
|
166
|
+
}
|
167
|
+
|
168
|
+
|
137
169
|
/*
|
138
170
|
static VALUE rszr_image_get_quality(VALUE self)
|
139
171
|
{
|
@@ -211,6 +243,32 @@ static VALUE rszr_image__turn_bang(VALUE self, VALUE orientation)
|
|
211
243
|
}
|
212
244
|
|
213
245
|
|
246
|
+
static VALUE rszr_image_flop_bang(VALUE self)
|
247
|
+
{
|
248
|
+
rszr_image_handle * handle;
|
249
|
+
|
250
|
+
Data_Get_Struct(self, rszr_image_handle, handle);
|
251
|
+
|
252
|
+
imlib_context_set_image(handle->image);
|
253
|
+
imlib_image_flip_horizontal();
|
254
|
+
|
255
|
+
return self;
|
256
|
+
}
|
257
|
+
|
258
|
+
|
259
|
+
static VALUE rszr_image_flip_bang(VALUE self)
|
260
|
+
{
|
261
|
+
rszr_image_handle * handle;
|
262
|
+
|
263
|
+
Data_Get_Struct(self, rszr_image_handle, handle);
|
264
|
+
|
265
|
+
imlib_context_set_image(handle->image);
|
266
|
+
imlib_image_flip_vertical();
|
267
|
+
|
268
|
+
return self;
|
269
|
+
}
|
270
|
+
|
271
|
+
|
214
272
|
static VALUE rszr_image__rotate(VALUE self, VALUE bang, VALUE rb_angle)
|
215
273
|
{
|
216
274
|
rszr_image_handle * handle;
|
@@ -246,20 +304,22 @@ static VALUE rszr_image__rotate(VALUE self, VALUE bang, VALUE rb_angle)
|
|
246
304
|
}
|
247
305
|
}
|
248
306
|
|
249
|
-
|
250
|
-
static VALUE
|
307
|
+
|
308
|
+
static VALUE rszr_image_filter_bang(VALUE self, VALUE rb_filter_expr)
|
251
309
|
{
|
252
310
|
rszr_image_handle * handle;
|
253
|
-
|
311
|
+
char * filter_expr;
|
254
312
|
|
255
|
-
|
313
|
+
filter_expr = StringValueCStr(rb_filter_expr);
|
314
|
+
|
315
|
+
Data_Get_Struct(self, rszr_image_handle, handle);
|
256
316
|
|
257
317
|
imlib_context_set_image(handle->image);
|
258
|
-
|
318
|
+
imlib_apply_filter(filter_expr);
|
259
319
|
|
260
320
|
return self;
|
261
321
|
}
|
262
|
-
|
322
|
+
|
263
323
|
|
264
324
|
static VALUE rszr_image__sharpen_bang(VALUE self, VALUE rb_radius)
|
265
325
|
{
|
@@ -275,7 +335,7 @@ static VALUE rszr_image__sharpen_bang(VALUE self, VALUE rb_radius)
|
|
275
335
|
if (radius >= 0) {
|
276
336
|
imlib_image_sharpen(radius);
|
277
337
|
} else {
|
278
|
-
imlib_image_blur(radius);
|
338
|
+
imlib_image_blur(-radius);
|
279
339
|
}
|
280
340
|
|
281
341
|
return self;
|
@@ -425,19 +485,23 @@ void Init_rszr_image()
|
|
425
485
|
rb_define_method(cImage, "initialize", rszr_image_initialize, 2);
|
426
486
|
rb_define_method(cImage, "width", rszr_image_width, 0);
|
427
487
|
rb_define_method(cImage, "height", rszr_image_height, 0);
|
428
|
-
rb_define_method(cImage, "format", rszr_image_format_get, 0);
|
429
488
|
rb_define_method(cImage, "dup", rszr_image_dup, 0);
|
489
|
+
rb_define_method(cImage, "filter!", rszr_image_filter_bang, 1);
|
490
|
+
rb_define_method(cImage, "flop!", rszr_image_flop_bang, 0);
|
491
|
+
rb_define_method(cImage, "flip!", rszr_image_flip_bang, 0);
|
492
|
+
|
430
493
|
// rb_define_method(cImage, "quality", rszr_image_get_quality, 0);
|
431
494
|
// rb_define_method(cImage, "quality=", rszr_image_set_quality, 1);
|
432
495
|
|
496
|
+
rb_define_protected_method(cImage, "_format", rszr_image__format_get, 0);
|
433
497
|
rb_define_protected_method(cImage, "_format=", rszr_image__format_set, 1);
|
434
498
|
|
435
499
|
rb_define_private_method(cImage, "_resize", rszr_image__resize, 7);
|
436
500
|
rb_define_private_method(cImage, "_crop", rszr_image__crop, 5);
|
437
501
|
rb_define_private_method(cImage, "_turn!", rszr_image__turn_bang, 1);
|
438
|
-
rb_define_private_method(cImage, "_rotate",
|
439
|
-
rb_define_private_method(cImage, "_sharpen!",
|
440
|
-
|
502
|
+
rb_define_private_method(cImage, "_rotate", rszr_image__rotate, 2);
|
503
|
+
rb_define_private_method(cImage, "_sharpen!", rszr_image__sharpen_bang, 1);
|
504
|
+
rb_define_private_method(cImage, "_pixel", rszr_image__pixel_get, 2);
|
441
505
|
|
442
506
|
rb_define_private_method(cImage, "_save", rszr_image__save, 3);
|
443
507
|
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rszr
|
2
|
+
class BatchTransformation
|
3
|
+
attr_reader :transformations, :image
|
4
|
+
|
5
|
+
def initialize(path, **opts)
|
6
|
+
puts "INITIALIZED BATCH for #{path}"
|
7
|
+
@image = path.is_a?(Image) ? path : Image.load(path, **opts)
|
8
|
+
@transformations = []
|
9
|
+
end
|
10
|
+
|
11
|
+
Image::Transformations.instance_methods.grep(/\w\z/) do |method|
|
12
|
+
define_method method do |*args|
|
13
|
+
transformations << [method, args]
|
14
|
+
self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
transformations.each { |method, args| image.public_send("#{method}!", *args) }
|
20
|
+
image
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rszr
|
2
|
+
module Buffered
|
3
|
+
def self.included(base)
|
4
|
+
base.extend Buffered
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def with_tempfile(format, data = nil)
|
10
|
+
raise ArgumentError, 'format is required' unless format
|
11
|
+
result = nil
|
12
|
+
Tempfile.create(['rszr-buffer', ".#{format}"], encoding: 'BINARY') do |file|
|
13
|
+
if data
|
14
|
+
file.binmode
|
15
|
+
file << data
|
16
|
+
file.fsync
|
17
|
+
file.rewind
|
18
|
+
end
|
19
|
+
result = yield(file)
|
20
|
+
end
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/rszr/color.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rszr
|
2
|
+
module Color
|
3
|
+
|
4
|
+
class RGBA
|
5
|
+
attr_reader :red, :green, :blue, :alpha
|
6
|
+
|
7
|
+
def initialize(red, green, blue, alpha = 255)
|
8
|
+
if red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255 || alpha < 0 || alpha > 255
|
9
|
+
raise ArgumentError, 'color out of range'
|
10
|
+
end
|
11
|
+
@red, @green, @blue, @alpha = red, green, blue, alpha
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_i(rgb: false)
|
15
|
+
i = red.to_i << 24 | green.to_i << 16 | blue.to_i << 8 | alpha.to_i
|
16
|
+
rgb ? i >> 8 : i
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hex(rgb: false)
|
20
|
+
"%0#{rgb ? 6 : 8}x" % to_i(rgb: rgb)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Type reader adapted from fastimage
|
2
|
+
# https://github.com/sdsykes/fastimage/
|
3
|
+
|
4
|
+
module Rszr
|
5
|
+
module Identification
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def identify(data)
|
10
|
+
case data[0, 2]
|
11
|
+
when 'BM'
|
12
|
+
:bmp
|
13
|
+
when 'GI'
|
14
|
+
:gif
|
15
|
+
when 0xff.chr + 0xd8.chr
|
16
|
+
:jpeg
|
17
|
+
when 0x89.chr + 'P'
|
18
|
+
:png
|
19
|
+
when 'II', 'MM'
|
20
|
+
case data[0, 11][8..10] # @stream.peek(11)[8..10]
|
21
|
+
when 'APC', "CR\002"
|
22
|
+
nil # do not recognise CRW or CR2 as tiff
|
23
|
+
else
|
24
|
+
:tiff
|
25
|
+
end
|
26
|
+
when '8B'
|
27
|
+
:psd
|
28
|
+
when "\0\0"
|
29
|
+
case data[0, 3].bytes.last #@stream.peek(3).bytes.to_a.last
|
30
|
+
when 0
|
31
|
+
# http://www.ftyps.com/what.html
|
32
|
+
# HEIC is composed of nested "boxes". Each box has a header composed of
|
33
|
+
# - Size (32 bit integer)
|
34
|
+
# - Box type (4 chars)
|
35
|
+
# - Extended size: only if size === 1, the type field is followed by 64 bit integer of extended size
|
36
|
+
# - Payload: Type-dependent
|
37
|
+
case data[0, 12][4..-1] #@stream.peek(12)[4..-1]
|
38
|
+
when 'ftypheic'
|
39
|
+
:heic
|
40
|
+
when 'ftypmif1'
|
41
|
+
:heif
|
42
|
+
end
|
43
|
+
# ico has either a 1 (for ico format) or 2 (for cursor) at offset 3
|
44
|
+
when 1 then :ico
|
45
|
+
when 2 then :cur
|
46
|
+
end
|
47
|
+
when 'RI'
|
48
|
+
:webp if data[0, 12][8..11] == 'WEBP' #@stream.peek(12)[8..11] == "WEBP"
|
49
|
+
when "<s"
|
50
|
+
:svg if data[0, 4] == '<svg' #@stream.peek(4) == "<svg"
|
51
|
+
when /\s\s|\s<|<[?!]/, 0xef.chr + 0xbb.chr
|
52
|
+
# Peek 10 more chars each time, and if end of file is reached just raise
|
53
|
+
# unknown. We assume the <svg tag cannot be within 10 chars of the end of
|
54
|
+
# the file, and is within the first 250 chars.
|
55
|
+
:svg if (1..25).detect { |n| data[0, 10 * n]&.include?('<svg') }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/lib/rszr/image.rb
CHANGED
@@ -1,25 +1,50 @@
|
|
1
1
|
module Rszr
|
2
2
|
class Image
|
3
|
-
|
3
|
+
GRAVITIES = [true, :center, :n, :nw, :w, :sw, :s, :se, :e, :ne].freeze
|
4
|
+
|
5
|
+
extend Identification
|
6
|
+
include Buffered
|
7
|
+
include Orientation
|
8
|
+
|
4
9
|
class << self
|
5
|
-
|
6
|
-
def load(path, **opts)
|
10
|
+
|
11
|
+
def load(path, autorotate: Rszr.autorotate, **opts)
|
7
12
|
path = path.to_s
|
8
13
|
raise FileNotFound unless File.exist?(path)
|
9
|
-
_load(path)
|
14
|
+
image = _load(path)
|
15
|
+
autorotate(image, path) if autorotate
|
16
|
+
image
|
10
17
|
end
|
11
18
|
alias :open :load
|
12
19
|
|
20
|
+
def load_data(data, autorotate: Rszr.autorotate, **opts)
|
21
|
+
raise LoadError, 'Unknown format' unless format = identify(data)
|
22
|
+
with_tempfile(format, data) do |file|
|
23
|
+
load(file.path, autorotate: autorotate, **opts)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
13
27
|
end
|
14
28
|
|
15
29
|
def dimensions
|
16
30
|
[width, height]
|
17
31
|
end
|
18
32
|
|
33
|
+
def format
|
34
|
+
fmt = _format
|
35
|
+
fmt == 'jpg' ? 'jpeg' : fmt
|
36
|
+
end
|
37
|
+
|
19
38
|
def format=(fmt)
|
20
39
|
fmt = fmt.to_s if fmt.is_a?(Symbol)
|
21
40
|
self._format = fmt
|
22
41
|
end
|
42
|
+
|
43
|
+
def [](x, y)
|
44
|
+
if x >= 0 && x <= width - 1 && y >= 0 && y <= height - 1
|
45
|
+
Color::RGBA.new(*_pixel(x, y))
|
46
|
+
end
|
47
|
+
end
|
23
48
|
|
24
49
|
def inspect
|
25
50
|
fmt = format
|
@@ -28,12 +53,12 @@ module Rszr
|
|
28
53
|
end
|
29
54
|
|
30
55
|
module Transformations
|
31
|
-
def resize(*args)
|
32
|
-
_resize(false, *calculate_size(*args))
|
56
|
+
def resize(*args, **opts)
|
57
|
+
_resize(false, *calculate_size(*args, **opts))
|
33
58
|
end
|
34
59
|
|
35
|
-
def resize!(*args)
|
36
|
-
_resize(true, *calculate_size(*args))
|
60
|
+
def resize!(*args, **opts)
|
61
|
+
_resize(true, *calculate_size(*args, **opts))
|
37
62
|
end
|
38
63
|
|
39
64
|
def crop(x, y, width, height)
|
@@ -43,7 +68,7 @@ module Rszr
|
|
43
68
|
def crop!(x, y, width, height)
|
44
69
|
_crop(true, x, y, width, height)
|
45
70
|
end
|
46
|
-
|
71
|
+
|
47
72
|
def turn(orientation)
|
48
73
|
dup.turn!(orientation)
|
49
74
|
end
|
@@ -60,6 +85,16 @@ module Rszr
|
|
60
85
|
def rotate!(deg)
|
61
86
|
_rotate(true, deg.to_f * Math::PI / 180.0)
|
62
87
|
end
|
88
|
+
|
89
|
+
# horizontal
|
90
|
+
def flop
|
91
|
+
dup.flop!
|
92
|
+
end
|
93
|
+
|
94
|
+
# vertical
|
95
|
+
def flip
|
96
|
+
dup.flip!
|
97
|
+
end
|
63
98
|
|
64
99
|
def sharpen(radius)
|
65
100
|
dup.sharpen!(radius)
|
@@ -79,11 +114,36 @@ module Rszr
|
|
79
114
|
_sharpen!(-radius)
|
80
115
|
end
|
81
116
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
117
|
+
def filter(filter_expr)
|
118
|
+
dup.filter!(filter_expr)
|
119
|
+
end
|
120
|
+
|
121
|
+
def brighten!(value, r: nil, g: nil, b: nil, a: nil)
|
122
|
+
raise ArgumentError, 'illegal brightness' if value > 1 || value < -1
|
123
|
+
filter!("colormod(brightness=#{value.to_f});")
|
124
|
+
end
|
125
|
+
|
126
|
+
def brighten(*args, **opts)
|
127
|
+
dup.brighten!(*args, **opts)
|
128
|
+
end
|
129
|
+
|
130
|
+
def contrast!(value, r: nil, g: nil, b: nil, a: nil)
|
131
|
+
raise ArgumentError, 'illegal contrast (must be > 0)' if value < 0
|
132
|
+
filter!("colormod(contrast=#{value.to_f});")
|
133
|
+
end
|
134
|
+
|
135
|
+
def contrast(*args, **opts)
|
136
|
+
dup.contrast!(*args, **opts)
|
137
|
+
end
|
138
|
+
|
139
|
+
def gamma!(value, r: nil, g: nil, b: nil, a: nil)
|
140
|
+
#raise ArgumentError, 'illegal gamma (must be > 0)' if value < 0
|
141
|
+
filter!("colormod(gamma=#{value.to_f});")
|
142
|
+
end
|
143
|
+
|
144
|
+
def gamma(*args, **opts)
|
145
|
+
dup.gamma!(*args, **opts)
|
146
|
+
end
|
87
147
|
end
|
88
148
|
|
89
149
|
include Transformations
|
@@ -94,6 +154,15 @@ module Rszr
|
|
94
154
|
ensure_path_is_writable(path)
|
95
155
|
_save(path.to_s, format.to_s, quality)
|
96
156
|
end
|
157
|
+
|
158
|
+
def save_data(format: nil, quality: nil)
|
159
|
+
format ||= self.format || 'jpg'
|
160
|
+
with_tempfile(format) do |file|
|
161
|
+
save(file.path, format: format, quality: quality)
|
162
|
+
file.rewind
|
163
|
+
file.read
|
164
|
+
end
|
165
|
+
end
|
97
166
|
|
98
167
|
private
|
99
168
|
|
@@ -105,51 +174,104 @@ module Rszr
|
|
105
174
|
# 400, 300, background: rgba
|
106
175
|
# 400, 300, skew: true
|
107
176
|
|
108
|
-
def calculate_size(*args)
|
109
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
110
|
-
assert_valid_keys options, :crop, :background, :skew #:extend, :width, :height, :max_width, :max_height, :box
|
111
|
-
original_width, original_height = width, height
|
112
|
-
x, y, = 0, 0
|
177
|
+
def calculate_size(*args, crop: nil, skew: nil, inflate: true)
|
178
|
+
#options = args.last.is_a?(Hash) ? args.pop : {}
|
179
|
+
#assert_valid_keys options, :crop, :background, :skew #:extend, :width, :height, :max_width, :max_height, :box
|
113
180
|
if args.size == 1
|
114
|
-
|
115
|
-
raise ArgumentError, "scale #{scale.inspect} out of range" unless scale > 0 && scale < 1
|
116
|
-
new_width = original_width.to_f * scale
|
117
|
-
new_height = original_height.to_f * scale
|
181
|
+
calculate_size_for_scale(args.first)
|
118
182
|
elsif args.size == 2
|
119
183
|
box_width, box_height = args
|
120
|
-
if
|
121
|
-
|
122
|
-
new_width = box_height.to_f / original_height.to_f * original_width.to_f
|
123
|
-
elsif box_width.is_a?(Numeric) && :auto == box_height
|
124
|
-
new_width = box_width
|
125
|
-
new_height = box_width.to_f / original_width.to_f * original_height.to_f
|
184
|
+
if args.include?(:auto)
|
185
|
+
calculate_size_for_auto(box_width, box_height)
|
126
186
|
elsif box_width.is_a?(Numeric) && box_height.is_a?(Numeric)
|
127
|
-
if
|
128
|
-
|
129
|
-
elsif
|
130
|
-
|
187
|
+
if not inflate and width <= box_width and height <= box_height
|
188
|
+
[0, 0, width, height, width, height]
|
189
|
+
elsif skew
|
190
|
+
calculate_size_for_skew(box_width, box_height)
|
191
|
+
elsif crop
|
192
|
+
calculate_size_for_crop(box_width, box_height, crop)
|
131
193
|
else
|
132
|
-
|
133
|
-
box_scale = box_width.to_f / box_height.to_f
|
134
|
-
if scale >= box_scale # wider
|
135
|
-
new_width = box_width
|
136
|
-
new_height = original_height.to_f * box_width.to_f / original_width.to_f
|
137
|
-
else # narrower
|
138
|
-
new_height = box_height
|
139
|
-
new_width = original_width.to_f * box_height.to_f / original_height.to_f
|
140
|
-
end
|
194
|
+
calculate_size_for_limit(box_width, box_height)
|
141
195
|
end
|
142
|
-
else
|
143
|
-
raise ArgumentError, "unconclusive arguments #{args.inspect} #{options.inspect}"
|
144
196
|
end
|
145
197
|
else
|
146
198
|
raise ArgumentError, "wrong number of arguments (#{args.size} for 1..2)"
|
147
199
|
end
|
148
|
-
[x, y, original_width, original_height, new_width.round, new_height.round]
|
149
200
|
end
|
150
201
|
|
202
|
+
def calculate_size_for_scale(factor)
|
203
|
+
raise ArgumentError, "scale factor #{factor.inspect} out of range" unless factor > 0 && factor < 1
|
204
|
+
[0, 0, width, height, (width.to_f * factor).round, (height.to_f * factor).round]
|
205
|
+
end
|
206
|
+
|
207
|
+
def calculate_size_for_skew(box_width, box_height)
|
208
|
+
[0, 0, width, height, box_width, box_height]
|
209
|
+
end
|
210
|
+
|
211
|
+
def calculate_size_for_auto(box_width, box_height)
|
212
|
+
if :auto == box_width && box_height.is_a?(Numeric)
|
213
|
+
new_height = box_height
|
214
|
+
new_width = (box_height.to_f / height.to_f * width.to_f).round
|
215
|
+
elsif box_width.is_a?(Numeric) && :auto == box_height
|
216
|
+
new_width = box_width
|
217
|
+
new_height = (box_width.to_f / width.to_f * height.to_f).round
|
218
|
+
else
|
219
|
+
raise ArgumentError, "unconclusive arguments #{box_width.inspect}, #{box_height.inspect}"
|
220
|
+
end
|
221
|
+
[0, 0, width, height, new_width, new_height]
|
222
|
+
end
|
223
|
+
|
224
|
+
def calculate_size_for_crop(box_width, box_height, crop)
|
225
|
+
raise ArgumentError, "invalid crop gravity" unless GRAVITIES.include?(crop)
|
226
|
+
aspect = width.to_f / height.to_f
|
227
|
+
box_aspect = box_width.to_f / box_height.to_f
|
228
|
+
if aspect >= box_aspect # wider than box
|
229
|
+
src_width = (box_width.to_f * height.to_f / box_height.to_f).round
|
230
|
+
src_height = height
|
231
|
+
x = crop_horizontally(src_width, crop)
|
232
|
+
y = 0
|
233
|
+
else # narrower than box
|
234
|
+
src_width = width
|
235
|
+
src_height = (box_height.to_f * width.to_f / box_width.to_f).round
|
236
|
+
x = 0
|
237
|
+
y = crop_vertically(src_height, crop)
|
238
|
+
end
|
239
|
+
[x, y, src_width, src_height, box_width, box_height]
|
240
|
+
end
|
241
|
+
|
242
|
+
def crop_horizontally(src_width, crop)
|
243
|
+
case crop
|
244
|
+
when :nw, :w, :sw then 0
|
245
|
+
when :ne, :e, :se then width - src_width
|
246
|
+
else
|
247
|
+
((width - src_width).to_f / 2.to_f).round
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def crop_vertically(src_height, crop)
|
252
|
+
case crop
|
253
|
+
when :nw, :n, :ne then 0
|
254
|
+
when :sw, :s, :se then height - src_height
|
255
|
+
else
|
256
|
+
((height - src_height).to_f / 2.to_f).round
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def calculate_size_for_limit(box_width, box_height)
|
261
|
+
scale = width.to_f / height.to_f
|
262
|
+
box_scale = box_width.to_f / box_height.to_f
|
263
|
+
if scale >= box_scale # wider
|
264
|
+
new_width = box_width
|
265
|
+
new_height = (height.to_f * box_width.to_f / width.to_f).round
|
266
|
+
else # narrower
|
267
|
+
new_height = box_height
|
268
|
+
new_width = (width.to_f * box_height.to_f / height.to_f).round
|
269
|
+
end
|
270
|
+
[0, 0, width, height, new_width, new_height]
|
271
|
+
end
|
272
|
+
|
151
273
|
def format_from_filename(path)
|
152
|
-
File.extname(path)[1..-1]
|
274
|
+
File.extname(path)[1..-1].to_s.downcase
|
153
275
|
end
|
154
276
|
|
155
277
|
def ensure_path_is_writable(path)
|
@@ -47,11 +47,11 @@ module ImageProcessing
|
|
47
47
|
end
|
48
48
|
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
# Resizes the image to not be larger than the specified dimensions.
|
52
52
|
def resize_to_limit(width, height, **options)
|
53
53
|
width, height = default_dimensions(width, height)
|
54
|
-
thumbnail(width, height, **options)
|
54
|
+
thumbnail(width, height, inflate: false, **options)
|
55
55
|
end
|
56
56
|
|
57
57
|
# Resizes the image to fit within the specified dimensions.
|
@@ -62,8 +62,8 @@ module ImageProcessing
|
|
62
62
|
|
63
63
|
# Resizes the image to fill the specified dimensions, applying any
|
64
64
|
# necessary cropping.
|
65
|
-
def resize_to_fill(width, height, **options)
|
66
|
-
thumbnail(width, height, crop:
|
65
|
+
def resize_to_fill(width, height, gravity: :center, **options)
|
66
|
+
thumbnail(width, height, crop: gravity, **options)
|
67
67
|
end
|
68
68
|
|
69
69
|
private
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Rszr
|
2
|
+
module Orientation
|
3
|
+
ROTATIONS = { 5 => 1, 6 => 1, 3 => 2, 4 => 2, 7 => 3, 8 => 3 }
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
base.attr_reader :original_orientation
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def autorotate(image, path)
|
15
|
+
return unless %w[jpeg tiff].include?(image.format)
|
16
|
+
File.open(path) do |file|
|
17
|
+
if orientation = send("parse_#{image.format}_orientation", file) and (1..8).member?(orientation)
|
18
|
+
image.instance_variable_set :@original_orientation, orientation
|
19
|
+
image.flop! if [2, 4, 5, 7].include?(orientation)
|
20
|
+
image.turn!(ROTATIONS[orientation]) if ROTATIONS.key?(orientation)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_tiff_orientation(data)
|
26
|
+
exif_parse_orientation(Stream.new(data))
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_jpeg_orientation(data)
|
30
|
+
stream = Stream.new(data)
|
31
|
+
exif = nil
|
32
|
+
state = nil
|
33
|
+
loop do
|
34
|
+
state = case state
|
35
|
+
when nil
|
36
|
+
stream.skip(2)
|
37
|
+
:started
|
38
|
+
when :started
|
39
|
+
stream.read_byte == 0xFF ? :sof : :started
|
40
|
+
when :sof
|
41
|
+
case stream.read_byte
|
42
|
+
when 0xe1 # APP1
|
43
|
+
skip_chars = stream.read_int - 2
|
44
|
+
app1 = Stream.new(stream.read(skip_chars))
|
45
|
+
if app1.read(4) == 'Exif'
|
46
|
+
app1.skip(2)
|
47
|
+
orientation = exif_parse_orientation(app1.fast_forward)# rescue nil
|
48
|
+
return orientation
|
49
|
+
end
|
50
|
+
:started
|
51
|
+
when 0xe0..0xef
|
52
|
+
:skipframe
|
53
|
+
when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF
|
54
|
+
:readsize
|
55
|
+
when 0xFF
|
56
|
+
:sof
|
57
|
+
else
|
58
|
+
:skipframe
|
59
|
+
end
|
60
|
+
when :skipframe
|
61
|
+
skip_chars = stream.read_int - 2
|
62
|
+
stream.skip(skip_chars)
|
63
|
+
:started
|
64
|
+
when :readsize
|
65
|
+
# stream.skip(3)
|
66
|
+
# height = stream.read_int
|
67
|
+
# width = stream.read_int
|
68
|
+
return exif&.orientation
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def exif_byte_order(stream)
|
74
|
+
byte_order = stream.read(2)
|
75
|
+
case byte_order
|
76
|
+
when 'II'
|
77
|
+
%w[v V]
|
78
|
+
when 'MM'
|
79
|
+
%w[n N]
|
80
|
+
else
|
81
|
+
raise LoadError
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def exif_parse_ifd(stream, short)
|
86
|
+
tag_count = stream.read(2).unpack(short)[0]
|
87
|
+
tag_count.downto(1) do
|
88
|
+
type = stream.read(2).unpack(short)[0]
|
89
|
+
stream.read(6)
|
90
|
+
data = stream.read(2).unpack(short)[0]
|
91
|
+
return data if 0x0112 == type
|
92
|
+
stream.read(2)
|
93
|
+
end
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def exif_parse_orientation(stream)
|
98
|
+
short, long = exif_byte_order(stream)
|
99
|
+
stream.read(2) # 42
|
100
|
+
offset = stream.read(4).unpack(long)[0]
|
101
|
+
stream.skip(offset - 8)
|
102
|
+
exif_parse_ifd(stream, short)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
data/lib/rszr/stream.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Rszr
|
2
|
+
class Stream
|
3
|
+
attr_reader :pos, :data
|
4
|
+
protected :data
|
5
|
+
|
6
|
+
def initialize(data, start: 0)
|
7
|
+
raise ArgumentError, 'start must be > 0' if start < 0
|
8
|
+
@data = case data
|
9
|
+
when IO then data
|
10
|
+
when String then StringIO.new(data)
|
11
|
+
when Stream then data.data
|
12
|
+
else
|
13
|
+
raise ArgumentError, "data must be File or String, got #{data.class}"
|
14
|
+
end
|
15
|
+
@data.binmode
|
16
|
+
@data.seek(start)
|
17
|
+
@pos = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def read(n)
|
21
|
+
@data.read(n).tap { @pos += n }
|
22
|
+
end
|
23
|
+
|
24
|
+
def peek(n)
|
25
|
+
old_pos = @data.pos
|
26
|
+
@data.read(n)
|
27
|
+
ensure
|
28
|
+
@data.pos = old_pos
|
29
|
+
end
|
30
|
+
|
31
|
+
def skip(n)
|
32
|
+
@data.seek(n, IO::SEEK_CUR).tap { @pos += n }
|
33
|
+
end
|
34
|
+
|
35
|
+
def substream
|
36
|
+
self.class.new(self, @data.pos)
|
37
|
+
end
|
38
|
+
|
39
|
+
def fast_forward
|
40
|
+
@pos = 0
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_byte
|
45
|
+
read(1)[0].ord
|
46
|
+
end
|
47
|
+
|
48
|
+
def read_int
|
49
|
+
read(2).unpack('n')[0]
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_string_int
|
53
|
+
value = []
|
54
|
+
while read(1) =~ /(\d)/
|
55
|
+
value << $1
|
56
|
+
end
|
57
|
+
value.join.to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/rszr/version.rb
CHANGED
data/lib/rszr.rb
CHANGED
@@ -1,6 +1,27 @@
|
|
1
1
|
require 'rbconfig'
|
2
2
|
require 'pathname'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'stringio'
|
3
5
|
|
4
6
|
require 'rszr/rszr'
|
5
7
|
require 'rszr/version'
|
8
|
+
require 'rszr/stream'
|
9
|
+
require 'rszr/identification'
|
10
|
+
require 'rszr/orientation'
|
11
|
+
require 'rszr/buffered'
|
12
|
+
require 'rszr/color'
|
6
13
|
require 'rszr/image'
|
14
|
+
|
15
|
+
module Rszr
|
16
|
+
class << self
|
17
|
+
@@autorotate = nil
|
18
|
+
|
19
|
+
def autorotate
|
20
|
+
@@autorotate
|
21
|
+
end
|
22
|
+
|
23
|
+
def autorotate=(value)
|
24
|
+
@@autorotate = !!value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
CHANGED
@@ -1,183 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rszr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthias Grosser
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
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: '0'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 12.3.3
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 12.3.3
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rake-compiler
|
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: byebug
|
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: rspec
|
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: minitest
|
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: simplecov
|
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
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: image_processing
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ">="
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: gd2-ffij
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '0'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '0'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: mini_magick
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: ruby-vips
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '0'
|
160
|
-
type: :development
|
161
|
-
prerelease: false
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - ">="
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: memory_profiler
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ">="
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - ">="
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
11
|
+
date: 2021-11-10 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
181
13
|
description: Fast image resizer - do one thing and do it fast.
|
182
14
|
email:
|
183
15
|
- mtgrosser@gmx.net
|
@@ -197,14 +29,20 @@ files:
|
|
197
29
|
- ext/rszr/rszr.c
|
198
30
|
- ext/rszr/rszr.h
|
199
31
|
- lib/rszr.rb
|
32
|
+
- lib/rszr/batch_transformation.rb
|
33
|
+
- lib/rszr/buffered.rb
|
34
|
+
- lib/rszr/color.rb
|
35
|
+
- lib/rszr/identification.rb
|
200
36
|
- lib/rszr/image.rb
|
201
37
|
- lib/rszr/image_processing.rb
|
38
|
+
- lib/rszr/orientation.rb
|
39
|
+
- lib/rszr/stream.rb
|
202
40
|
- lib/rszr/version.rb
|
203
41
|
homepage: https://github.com/mtgrosser/rszr
|
204
42
|
licenses:
|
205
43
|
- MIT
|
206
44
|
metadata: {}
|
207
|
-
post_install_message:
|
45
|
+
post_install_message:
|
208
46
|
rdoc_options: []
|
209
47
|
require_paths:
|
210
48
|
- lib
|
@@ -220,9 +58,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
58
|
- !ruby/object:Gem::Version
|
221
59
|
version: '0'
|
222
60
|
requirements:
|
223
|
-
-
|
61
|
+
- imlib2
|
224
62
|
rubygems_version: 3.1.4
|
225
|
-
signing_key:
|
63
|
+
signing_key:
|
226
64
|
specification_version: 4
|
227
65
|
summary: Fast image resizer
|
228
66
|
test_files: []
|