rszr 0.7.1 → 1.1.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 +84 -19
- data/ext/rszr/errors.c +7 -2
- data/ext/rszr/extconf.rb +2 -10
- data/ext/rszr/image.c +71 -66
- data/ext/rszr/rszr.h +0 -1
- 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 +150 -43
- data/lib/rszr/image_processing.rb +3 -3
- 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 +7 -0
- metadata +8 -172
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 453bbac498bb376737ac50b776a9d1824cbb0f2338753b82c09c36ef08bc152b
|
4
|
+
data.tar.gz: 66941eca0be0d49922627be9329ca6c623686809094be55b80fc490f8dd59290
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed7559dfe4f2a754f9f4b46fe0cf3b88f1003d8c055dc38d5eb9335f49ff8656689464fbb8f0c0756466ab9dd6af775e9bb12add63f29d167a20e832354d3adc
|
7
|
+
data.tar.gz: 49b195f7d27005a81175ae467e0f7f05d974b020db07a90a1f34999df2dd69cd96591d2a96d7ae07872886074dbc937e8d53bf99380fa0bf5d94181065461682
|
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.
|
@@ -12,29 +12,32 @@ In your Gemfile:
|
|
12
12
|
gem 'rszr'
|
13
13
|
```
|
14
14
|
|
15
|
-
### Imlib2
|
15
|
+
### Imlib2
|
16
16
|
|
17
17
|
Rszr requires the `Imlib2` library to do the heavy lifting.
|
18
|
-
`libexif` is required for EXIF auto orientation.
|
19
18
|
|
20
19
|
#### OS X
|
21
20
|
|
22
21
|
Using homebrew:
|
23
22
|
|
24
23
|
```bash
|
25
|
-
brew install imlib2
|
24
|
+
brew install imlib2
|
26
25
|
```
|
27
26
|
|
28
27
|
#### Linux
|
29
28
|
|
30
29
|
Using your favourite package manager:
|
31
30
|
|
31
|
+
##### RedHat-based
|
32
|
+
|
32
33
|
```bash
|
33
|
-
yum install imlib2 imlib2-devel
|
34
|
+
yum install imlib2 imlib2-devel
|
34
35
|
```
|
35
36
|
|
37
|
+
##### Debian-based
|
38
|
+
|
36
39
|
```bash
|
37
|
-
apt-get install libimlib2 libimlib2-dev
|
40
|
+
apt-get install libimlib2 libimlib2-dev
|
38
41
|
```
|
39
42
|
|
40
43
|
## Usage
|
@@ -51,12 +54,25 @@ image.save('resized.jpg')
|
|
51
54
|
image.save('resized.png')
|
52
55
|
```
|
53
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
|
+
|
54
68
|
### Transformations
|
55
69
|
|
56
70
|
For each transformation, there is a bang! and non-bang method.
|
57
71
|
The bang method changes the image in place, while the non-bang method
|
58
72
|
creates a copy of the image in memory.
|
59
73
|
|
74
|
+
#### Resizing
|
75
|
+
|
60
76
|
```ruby
|
61
77
|
# auto height
|
62
78
|
image.resize(400, :auto)
|
@@ -67,6 +83,22 @@ image.resize(:auto, 300)
|
|
67
83
|
# scale factor
|
68
84
|
image.resize(0.5)
|
69
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
|
70
102
|
# crop
|
71
103
|
image.crop(200, 200, 100, 100)
|
72
104
|
|
@@ -79,45 +111,65 @@ image.turn!(-1)
|
|
79
111
|
# rotate by arbitrary angle
|
80
112
|
image.rotate(45)
|
81
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
|
82
129
|
# sharpen image by pixel radius
|
83
130
|
image.sharpen!(1)
|
84
131
|
|
85
132
|
# blur image by pixel radius
|
86
133
|
image.blur!(1)
|
87
134
|
|
88
|
-
#
|
89
|
-
image.
|
135
|
+
# brighten
|
136
|
+
image.brighten(0.1)
|
90
137
|
|
91
|
-
#
|
92
|
-
image.
|
93
|
-
```
|
138
|
+
# darken
|
139
|
+
image.brighten(-0.1)
|
94
140
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
image.
|
100
|
-
image.format => "jpeg"
|
141
|
+
# contrast
|
142
|
+
image.contrast(0.5)
|
143
|
+
|
144
|
+
# gamma
|
145
|
+
image.gamma(1.1)
|
101
146
|
```
|
102
147
|
|
103
148
|
### Image auto orientation
|
104
149
|
|
150
|
+
Auto-rotation is supported for JPEG and TIFF files that include the necessary
|
151
|
+
EXIF metadata.
|
152
|
+
|
105
153
|
```ruby
|
106
154
|
# load and autorotate
|
107
155
|
image = Rszr::Image.load('image.jpg', autorotate: true)
|
156
|
+
```
|
157
|
+
|
158
|
+
To enable autorotation by default:
|
108
159
|
|
160
|
+
```ruby
|
109
161
|
# auto-rotate by default, for Rails apps put this into an initializer
|
110
162
|
Rszr.autorotate = true
|
111
163
|
```
|
112
164
|
|
113
165
|
## Rails / ActiveStorage interface
|
114
166
|
|
115
|
-
Rszr provides a drop-in interface to the `
|
167
|
+
Rszr provides a drop-in interface to the `image_processing` gem.
|
116
168
|
It is faster than both `mini_magick` and `vips` and way easier to install than the latter.
|
117
169
|
|
118
170
|
```ruby
|
119
171
|
# Gemfile
|
120
|
-
gem '
|
172
|
+
gem 'image_processing'
|
121
173
|
gem 'rszr'
|
122
174
|
|
123
175
|
# config/initializers/rszr.rb
|
@@ -133,6 +185,19 @@ When creating image variants, you can use all of Rszr's transformation methods:
|
|
133
185
|
<%= image_tag user.avatar.variant(resize_to_fit: [300, 200]) %>
|
134
186
|
```
|
135
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
|
+
|
136
201
|
## Thread safety
|
137
202
|
|
138
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
51
|
error_index = RSZR_MAX_ERROR_INDEX;
|
48
|
-
|
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/extconf.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
require 'rbconfig'
|
3
3
|
|
4
|
-
|
4
|
+
pkg_config('imlib2')
|
5
5
|
|
6
|
-
$CFLAGS << ' -DX_DISPLAY_MISSING
|
7
|
-
$LDFLAGS << ' ' << `#{imlib2_config} --libs`.chomp
|
6
|
+
$CFLAGS << ' -DX_DISPLAY_MISSING'
|
8
7
|
$LDFLAGS.gsub!(/\ -lX11\ -lXext/, '') if RUBY_PLATFORM =~ /darwin/
|
9
8
|
|
10
9
|
unless find_header('Imlib2.h')
|
@@ -15,11 +14,4 @@ unless find_library('Imlib2', 'imlib_set_cache_size')
|
|
15
14
|
abort 'Imlib2 is missing'
|
16
15
|
end
|
17
16
|
|
18
|
-
unless find_library('exif', 'exif_data_new_from_file')
|
19
|
-
abort 'libexif is missing'
|
20
|
-
end
|
21
|
-
|
22
|
-
have_library('exif')
|
23
|
-
have_header('libexif/exif-data.h')
|
24
|
-
|
25
17
|
create_makefile 'rszr/rszr'
|
data/ext/rszr/image.c
CHANGED
@@ -27,43 +27,6 @@ static void rszr_image_deallocate(rszr_image_handle * handle)
|
|
27
27
|
// fprintf(stderr, "\n");
|
28
28
|
}
|
29
29
|
|
30
|
-
|
31
|
-
static void rszr_image_autorotate(Imlib_Image image, char * path)
|
32
|
-
{
|
33
|
-
ExifData * exifData;
|
34
|
-
ExifByteOrder byteOrder;
|
35
|
-
ExifEntry * exifEntry;
|
36
|
-
int orientation = 0;
|
37
|
-
int turns = 0;
|
38
|
-
|
39
|
-
exifData = exif_data_new_from_file(path);
|
40
|
-
|
41
|
-
if (exifData) {
|
42
|
-
byteOrder = exif_data_get_byte_order(exifData);
|
43
|
-
exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
|
44
|
-
if (exifEntry) {
|
45
|
-
orientation = exif_get_short(exifEntry->data, byteOrder);
|
46
|
-
}
|
47
|
-
}
|
48
|
-
|
49
|
-
if (orientation < 2 || orientation > 8) return;
|
50
|
-
|
51
|
-
imlib_context_set_image(image);
|
52
|
-
|
53
|
-
if (orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7) {
|
54
|
-
imlib_image_flip_horizontal();
|
55
|
-
}
|
56
|
-
|
57
|
-
if (orientation == 5 || orientation == 6) {
|
58
|
-
imlib_image_orientate(1);
|
59
|
-
} else if (orientation == 3 || orientation == 4) {
|
60
|
-
imlib_image_orientate(2);
|
61
|
-
} else if (orientation == 7 || orientation == 8) {
|
62
|
-
imlib_image_orientate(3);
|
63
|
-
}
|
64
|
-
}
|
65
|
-
|
66
|
-
|
67
30
|
static VALUE rszr_image_s_allocate(VALUE klass)
|
68
31
|
{
|
69
32
|
rszr_image_handle * handle = calloc(1, sizeof(rszr_image_handle));
|
@@ -86,7 +49,7 @@ static VALUE rszr_image_initialize(VALUE self, VALUE rb_width, VALUE rb_height)
|
|
86
49
|
}
|
87
50
|
|
88
51
|
|
89
|
-
static VALUE rszr_image_s__load(VALUE klass, VALUE rb_path
|
52
|
+
static VALUE rszr_image_s__load(VALUE klass, VALUE rb_path)
|
90
53
|
{
|
91
54
|
rszr_image_handle * handle;
|
92
55
|
Imlib_Image image;
|
@@ -97,14 +60,19 @@ static VALUE rszr_image_s__load(VALUE klass, VALUE rb_path, VALUE autorotate)
|
|
97
60
|
path = StringValueCStr(rb_path);
|
98
61
|
|
99
62
|
imlib_set_cache_size(0);
|
100
|
-
image =
|
63
|
+
image = imlib_load_image_without_cache(path);
|
101
64
|
|
102
65
|
if (!image) {
|
103
|
-
|
104
|
-
|
66
|
+
image = imlib_load_image_with_error_return(path, &error);
|
67
|
+
|
68
|
+
if (!image) {
|
69
|
+
rszr_raise_load_error(error);
|
70
|
+
return Qnil;
|
71
|
+
}
|
105
72
|
}
|
106
73
|
|
107
|
-
|
74
|
+
imlib_context_set_image(image);
|
75
|
+
imlib_image_set_irrelevant_format(0);
|
108
76
|
|
109
77
|
oImage = rszr_image_s_allocate(cImage);
|
110
78
|
Data_Get_Struct(oImage, rszr_image_handle, handle);
|
@@ -113,7 +81,7 @@ static VALUE rszr_image_s__load(VALUE klass, VALUE rb_path, VALUE autorotate)
|
|
113
81
|
}
|
114
82
|
|
115
83
|
|
116
|
-
static VALUE
|
84
|
+
static VALUE rszr_image__format_get(VALUE self)
|
117
85
|
{
|
118
86
|
rszr_image_handle * handle;
|
119
87
|
char * format;
|
@@ -172,6 +140,32 @@ static VALUE rszr_image_height(VALUE self)
|
|
172
140
|
return INT2NUM(height);
|
173
141
|
}
|
174
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
|
+
|
175
169
|
/*
|
176
170
|
static VALUE rszr_image_get_quality(VALUE self)
|
177
171
|
{
|
@@ -249,6 +243,32 @@ static VALUE rszr_image__turn_bang(VALUE self, VALUE orientation)
|
|
249
243
|
}
|
250
244
|
|
251
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
|
+
|
252
272
|
static VALUE rszr_image__rotate(VALUE self, VALUE bang, VALUE rb_angle)
|
253
273
|
{
|
254
274
|
rszr_image_handle * handle;
|
@@ -288,7 +308,6 @@ static VALUE rszr_image__rotate(VALUE self, VALUE bang, VALUE rb_angle)
|
|
288
308
|
static VALUE rszr_image_filter_bang(VALUE self, VALUE rb_filter_expr)
|
289
309
|
{
|
290
310
|
rszr_image_handle * handle;
|
291
|
-
Imlib_Image image;
|
292
311
|
char * filter_expr;
|
293
312
|
|
294
313
|
filter_expr = StringValueCStr(rb_filter_expr);
|
@@ -302,22 +321,6 @@ static VALUE rszr_image_filter_bang(VALUE self, VALUE rb_filter_expr)
|
|
302
321
|
}
|
303
322
|
|
304
323
|
|
305
|
-
static VALUE rszr_image__brighten_bang(VALUE self, VALUE rb_brightness)
|
306
|
-
{
|
307
|
-
rszr_image_handle * handle;
|
308
|
-
double brightness;
|
309
|
-
|
310
|
-
brightness = NUM2DBL(rb_brightness);
|
311
|
-
|
312
|
-
Data_Get_Struct(self, rszr_image_handle, handle);
|
313
|
-
|
314
|
-
imlib_context_set_image(handle->image);
|
315
|
-
imlib_apply_filter("brightness(10);");
|
316
|
-
|
317
|
-
return self;
|
318
|
-
}
|
319
|
-
|
320
|
-
|
321
324
|
static VALUE rszr_image__sharpen_bang(VALUE self, VALUE rb_radius)
|
322
325
|
{
|
323
326
|
rszr_image_handle * handle;
|
@@ -332,7 +335,7 @@ static VALUE rszr_image__sharpen_bang(VALUE self, VALUE rb_radius)
|
|
332
335
|
if (radius >= 0) {
|
333
336
|
imlib_image_sharpen(radius);
|
334
337
|
} else {
|
335
|
-
imlib_image_blur(radius);
|
338
|
+
imlib_image_blur(-radius);
|
336
339
|
}
|
337
340
|
|
338
341
|
return self;
|
@@ -476,27 +479,29 @@ void Init_rszr_image()
|
|
476
479
|
rb_define_alloc_func(cImage, rszr_image_s_allocate);
|
477
480
|
|
478
481
|
// Class methods
|
479
|
-
rb_define_private_method(rb_singleton_class(cImage), "_load", rszr_image_s__load,
|
482
|
+
rb_define_private_method(rb_singleton_class(cImage), "_load", rszr_image_s__load, 1);
|
480
483
|
|
481
484
|
// Instance methods
|
482
485
|
rb_define_method(cImage, "initialize", rszr_image_initialize, 2);
|
483
486
|
rb_define_method(cImage, "width", rszr_image_width, 0);
|
484
487
|
rb_define_method(cImage, "height", rszr_image_height, 0);
|
485
|
-
rb_define_method(cImage, "format", rszr_image_format_get, 0);
|
486
488
|
rb_define_method(cImage, "dup", rszr_image_dup, 0);
|
487
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);
|
488
492
|
|
489
493
|
// rb_define_method(cImage, "quality", rszr_image_get_quality, 0);
|
490
494
|
// rb_define_method(cImage, "quality=", rszr_image_set_quality, 1);
|
491
495
|
|
496
|
+
rb_define_protected_method(cImage, "_format", rszr_image__format_get, 0);
|
492
497
|
rb_define_protected_method(cImage, "_format=", rszr_image__format_set, 1);
|
493
498
|
|
494
499
|
rb_define_private_method(cImage, "_resize", rszr_image__resize, 7);
|
495
500
|
rb_define_private_method(cImage, "_crop", rszr_image__crop, 5);
|
496
501
|
rb_define_private_method(cImage, "_turn!", rszr_image__turn_bang, 1);
|
497
|
-
rb_define_private_method(cImage, "_rotate",
|
498
|
-
rb_define_private_method(cImage, "_sharpen!",
|
499
|
-
rb_define_private_method(cImage, "
|
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);
|
500
505
|
|
501
506
|
rb_define_private_method(cImage, "_save", rszr_image__save, 3);
|
502
507
|
}
|
data/ext/rszr/rszr.h
CHANGED
@@ -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
|
+
GRAVITIES = [true, :center, :n, :nw, :w, :sw, :s, :se, :e, :ne].freeze
|
4
|
+
|
5
|
+
extend Identification
|
6
|
+
include Buffered
|
7
|
+
include Orientation
|
3
8
|
|
4
9
|
class << self
|
5
|
-
|
10
|
+
|
6
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)
|
@@ -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)
|
@@ -88,8 +123,8 @@ module Rszr
|
|
88
123
|
filter!("colormod(brightness=#{value.to_f});")
|
89
124
|
end
|
90
125
|
|
91
|
-
def brighten(*args)
|
92
|
-
dup.brighten!(*args)
|
126
|
+
def brighten(*args, **opts)
|
127
|
+
dup.brighten!(*args, **opts)
|
93
128
|
end
|
94
129
|
|
95
130
|
def contrast!(value, r: nil, g: nil, b: nil, a: nil)
|
@@ -97,8 +132,8 @@ module Rszr
|
|
97
132
|
filter!("colormod(contrast=#{value.to_f});")
|
98
133
|
end
|
99
134
|
|
100
|
-
def contrast(*args)
|
101
|
-
dup.contrast!(*args)
|
135
|
+
def contrast(*args, **opts)
|
136
|
+
dup.contrast!(*args, **opts)
|
102
137
|
end
|
103
138
|
|
104
139
|
def gamma!(value, r: nil, g: nil, b: nil, a: nil)
|
@@ -106,8 +141,8 @@ module Rszr
|
|
106
141
|
filter!("colormod(gamma=#{value.to_f});")
|
107
142
|
end
|
108
143
|
|
109
|
-
def gamma(*args)
|
110
|
-
dup.gamma!(*args)
|
144
|
+
def gamma(*args, **opts)
|
145
|
+
dup.gamma!(*args, **opts)
|
111
146
|
end
|
112
147
|
end
|
113
148
|
|
@@ -116,8 +151,18 @@ module Rszr
|
|
116
151
|
def save(path, format: nil, quality: nil)
|
117
152
|
format ||= format_from_filename(path) || self.format || 'jpg'
|
118
153
|
raise ArgumentError, "invalid quality #{quality.inspect}" if quality && !(0..100).cover?(quality)
|
154
|
+
ensure_path_is_writable(path)
|
119
155
|
_save(path.to_s, format.to_s, quality)
|
120
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
|
121
166
|
|
122
167
|
private
|
123
168
|
|
@@ -129,52 +174,114 @@ module Rszr
|
|
129
174
|
# 400, 300, background: rgba
|
130
175
|
# 400, 300, skew: true
|
131
176
|
|
132
|
-
def calculate_size(*args)
|
133
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
134
|
-
assert_valid_keys options, :crop, :background, :skew #:extend, :width, :height, :max_width, :max_height, :box
|
135
|
-
original_width, original_height = width, height
|
136
|
-
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
|
137
180
|
if args.size == 1
|
138
|
-
|
139
|
-
raise ArgumentError, "scale factor #{scale.inspect} out of range" unless scale > 0 && scale < 1
|
140
|
-
new_width = original_width.to_f * scale
|
141
|
-
new_height = original_height.to_f * scale
|
181
|
+
calculate_size_for_scale(args.first)
|
142
182
|
elsif args.size == 2
|
143
183
|
box_width, box_height = args
|
144
|
-
if
|
145
|
-
|
146
|
-
new_width = box_height.to_f / original_height.to_f * original_width.to_f
|
147
|
-
elsif box_width.is_a?(Numeric) && :auto == box_height
|
148
|
-
new_width = box_width
|
149
|
-
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)
|
150
186
|
elsif box_width.is_a?(Numeric) && box_height.is_a?(Numeric)
|
151
|
-
if
|
152
|
-
|
153
|
-
elsif
|
154
|
-
|
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)
|
155
193
|
else
|
156
|
-
|
157
|
-
box_scale = box_width.to_f / box_height.to_f
|
158
|
-
if scale >= box_scale # wider
|
159
|
-
new_width = box_width
|
160
|
-
new_height = original_height.to_f * box_width.to_f / original_width.to_f
|
161
|
-
else # narrower
|
162
|
-
new_height = box_height
|
163
|
-
new_width = original_width.to_f * box_height.to_f / original_height.to_f
|
164
|
-
end
|
194
|
+
calculate_size_for_limit(box_width, box_height)
|
165
195
|
end
|
166
|
-
else
|
167
|
-
raise ArgumentError, "unconclusive arguments #{args.inspect} #{options.inspect}"
|
168
196
|
end
|
169
197
|
else
|
170
198
|
raise ArgumentError, "wrong number of arguments (#{args.size} for 1..2)"
|
171
199
|
end
|
172
|
-
|
200
|
+
end
|
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]
|
173
271
|
end
|
174
272
|
|
175
273
|
def format_from_filename(path)
|
176
274
|
File.extname(path)[1..-1].to_s.downcase
|
177
275
|
end
|
276
|
+
|
277
|
+
def ensure_path_is_writable(path)
|
278
|
+
path = Pathname.new(path)
|
279
|
+
path.dirname.realpath.writable?
|
280
|
+
rescue Errno::ENOENT => e
|
281
|
+
raise SaveError, 'Non-existant path component'
|
282
|
+
rescue SystemCallError => e
|
283
|
+
raise SaveError, e.message
|
284
|
+
end
|
178
285
|
|
179
286
|
def assert_valid_keys(hsh, *valid_keys)
|
180
287
|
if unknown_key = (hsh.keys - valid_keys).first
|
@@ -51,7 +51,7 @@ module ImageProcessing
|
|
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,8 +1,15 @@
|
|
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'
|
7
14
|
|
8
15
|
module Rszr
|
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:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthias Grosser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
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: '13.0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '13.0'
|
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: 2022-02-09 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
|
@@ -198,8 +30,13 @@ files:
|
|
198
30
|
- ext/rszr/rszr.h
|
199
31
|
- lib/rszr.rb
|
200
32
|
- lib/rszr/batch_transformation.rb
|
33
|
+
- lib/rszr/buffered.rb
|
34
|
+
- lib/rszr/color.rb
|
35
|
+
- lib/rszr/identification.rb
|
201
36
|
- lib/rszr/image.rb
|
202
37
|
- lib/rszr/image_processing.rb
|
38
|
+
- lib/rszr/orientation.rb
|
39
|
+
- lib/rszr/stream.rb
|
203
40
|
- lib/rszr/version.rb
|
204
41
|
homepage: https://github.com/mtgrosser/rszr
|
205
42
|
licenses:
|
@@ -222,7 +59,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
222
59
|
version: '0'
|
223
60
|
requirements:
|
224
61
|
- imlib2
|
225
|
-
- libexif
|
226
62
|
rubygems_version: 3.1.4
|
227
63
|
signing_key:
|
228
64
|
specification_version: 4
|