rszr 0.7.1 → 0.8.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 +5 -5
- data/README.md +62 -15
- data/ext/rszr/errors.c +7 -2
- data/ext/rszr/extconf.rb +0 -7
- data/ext/rszr/image.c +43 -65
- data/lib/rszr/buffered.rb +25 -0
- data/lib/rszr/identification.rb +60 -0
- data/lib/rszr/image.rb +62 -16
- 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 +6 -0
- metadata +22 -186
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 035d5f7341e71cdc9b5ba70de6e116acd8d8459e
|
4
|
+
data.tar.gz: 3119e97a26295db8f3931821d6ae835aaa6166ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 003305cff18cc4678a13f238e17d709351a36efea86b5ca2de751d15f933211c0d107601f2be6aa70dfc92a30a87615fdf3372dae26552190d21911451d33445
|
7
|
+
data.tar.gz: 732e05777cf4adc9c87515e043a226ed42bd56d0c76e3a28bf645fa22b492fed2cb2a473dbdccd4980b76a23b2d7bf579ec45c68a305a07c77f890697ff82e66
|
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,6 +54,14 @@ 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
|
+
```
|
64
|
+
|
54
65
|
### Transformations
|
55
66
|
|
56
67
|
For each transformation, there is a bang! and non-bang method.
|
@@ -79,11 +90,11 @@ image.turn!(-1)
|
|
79
90
|
# rotate by arbitrary angle
|
80
91
|
image.rotate(45)
|
81
92
|
|
82
|
-
#
|
83
|
-
image.
|
93
|
+
# flip vertically
|
94
|
+
image.flip
|
84
95
|
|
85
|
-
#
|
86
|
-
image.
|
96
|
+
# flop horizontally
|
97
|
+
image.flop
|
87
98
|
|
88
99
|
# initialize copy
|
89
100
|
image.dup
|
@@ -92,20 +103,43 @@ image.dup
|
|
92
103
|
image.resize!(400, :auto)
|
93
104
|
```
|
94
105
|
|
95
|
-
###
|
106
|
+
### Filters
|
107
|
+
|
108
|
+
Filters also support bang! and non-bang methods.
|
109
|
+
|
96
110
|
```ruby
|
97
|
-
image
|
98
|
-
image.
|
99
|
-
|
100
|
-
image
|
111
|
+
# sharpen image by pixel radius
|
112
|
+
image.sharpen!(1)
|
113
|
+
|
114
|
+
# blur image by pixel radius
|
115
|
+
image.blur!(1)
|
116
|
+
|
117
|
+
# brighten
|
118
|
+
image.brighten(0.1)
|
119
|
+
|
120
|
+
# darken
|
121
|
+
image.brighten(-0.1)
|
122
|
+
|
123
|
+
# contrast
|
124
|
+
image.contrast(0.5)
|
125
|
+
|
126
|
+
# gamma
|
127
|
+
image.gamma(1.1)
|
101
128
|
```
|
102
129
|
|
103
130
|
### Image auto orientation
|
104
131
|
|
132
|
+
Auto-rotation is supported for JPEG and TIFF files that include the necessary
|
133
|
+
EXIF metadata.
|
134
|
+
|
105
135
|
```ruby
|
106
136
|
# load and autorotate
|
107
137
|
image = Rszr::Image.load('image.jpg', autorotate: true)
|
138
|
+
```
|
139
|
+
|
140
|
+
To enable autorotation by default:
|
108
141
|
|
142
|
+
```ruby
|
109
143
|
# auto-rotate by default, for Rails apps put this into an initializer
|
110
144
|
Rszr.autorotate = true
|
111
145
|
```
|
@@ -133,6 +167,19 @@ When creating image variants, you can use all of Rszr's transformation methods:
|
|
133
167
|
<%= image_tag user.avatar.variant(resize_to_fit: [300, 200]) %>
|
134
168
|
```
|
135
169
|
|
170
|
+
## Loading from and saving to memory
|
171
|
+
|
172
|
+
The `Imlib2` library is mainly file-oriented and doesn't provide a way of loading
|
173
|
+
the undecoded image from a memory buffer. Therefore, the functionality is
|
174
|
+
implemented on the Ruby side of the gem, writing the memory buffer to a Tempfile.
|
175
|
+
Currently, this local write cannot be avoided.
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
image = Rszr::Image.load_data(binary_data)
|
179
|
+
|
180
|
+
data = image.save_data(format: :jpeg)
|
181
|
+
```
|
182
|
+
|
136
183
|
## Thread safety
|
137
184
|
|
138
185
|
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
@@ -15,11 +15,4 @@ unless find_library('Imlib2', 'imlib_set_cache_size')
|
|
15
15
|
abort 'Imlib2 is missing'
|
16
16
|
end
|
17
17
|
|
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
18
|
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;
|
@@ -249,6 +217,32 @@ static VALUE rszr_image__turn_bang(VALUE self, VALUE orientation)
|
|
249
217
|
}
|
250
218
|
|
251
219
|
|
220
|
+
static VALUE rszr_image_flop_bang(VALUE self)
|
221
|
+
{
|
222
|
+
rszr_image_handle * handle;
|
223
|
+
|
224
|
+
Data_Get_Struct(self, rszr_image_handle, handle);
|
225
|
+
|
226
|
+
imlib_context_set_image(handle->image);
|
227
|
+
imlib_image_flip_horizontal();
|
228
|
+
|
229
|
+
return self;
|
230
|
+
}
|
231
|
+
|
232
|
+
|
233
|
+
static VALUE rszr_image_flip_bang(VALUE self)
|
234
|
+
{
|
235
|
+
rszr_image_handle * handle;
|
236
|
+
|
237
|
+
Data_Get_Struct(self, rszr_image_handle, handle);
|
238
|
+
|
239
|
+
imlib_context_set_image(handle->image);
|
240
|
+
imlib_image_flip_vertical();
|
241
|
+
|
242
|
+
return self;
|
243
|
+
}
|
244
|
+
|
245
|
+
|
252
246
|
static VALUE rszr_image__rotate(VALUE self, VALUE bang, VALUE rb_angle)
|
253
247
|
{
|
254
248
|
rszr_image_handle * handle;
|
@@ -288,7 +282,6 @@ static VALUE rszr_image__rotate(VALUE self, VALUE bang, VALUE rb_angle)
|
|
288
282
|
static VALUE rszr_image_filter_bang(VALUE self, VALUE rb_filter_expr)
|
289
283
|
{
|
290
284
|
rszr_image_handle * handle;
|
291
|
-
Imlib_Image image;
|
292
285
|
char * filter_expr;
|
293
286
|
|
294
287
|
filter_expr = StringValueCStr(rb_filter_expr);
|
@@ -302,22 +295,6 @@ static VALUE rszr_image_filter_bang(VALUE self, VALUE rb_filter_expr)
|
|
302
295
|
}
|
303
296
|
|
304
297
|
|
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
298
|
static VALUE rszr_image__sharpen_bang(VALUE self, VALUE rb_radius)
|
322
299
|
{
|
323
300
|
rszr_image_handle * handle;
|
@@ -476,27 +453,28 @@ void Init_rszr_image()
|
|
476
453
|
rb_define_alloc_func(cImage, rszr_image_s_allocate);
|
477
454
|
|
478
455
|
// Class methods
|
479
|
-
rb_define_private_method(rb_singleton_class(cImage), "_load", rszr_image_s__load,
|
456
|
+
rb_define_private_method(rb_singleton_class(cImage), "_load", rszr_image_s__load, 1);
|
480
457
|
|
481
458
|
// Instance methods
|
482
459
|
rb_define_method(cImage, "initialize", rszr_image_initialize, 2);
|
483
460
|
rb_define_method(cImage, "width", rszr_image_width, 0);
|
484
461
|
rb_define_method(cImage, "height", rszr_image_height, 0);
|
485
|
-
rb_define_method(cImage, "format", rszr_image_format_get, 0);
|
486
462
|
rb_define_method(cImage, "dup", rszr_image_dup, 0);
|
487
463
|
rb_define_method(cImage, "filter!", rszr_image_filter_bang, 1);
|
464
|
+
rb_define_method(cImage, "flop!", rszr_image_flop_bang, 0);
|
465
|
+
rb_define_method(cImage, "flip!", rszr_image_flip_bang, 0);
|
488
466
|
|
489
467
|
// rb_define_method(cImage, "quality", rszr_image_get_quality, 0);
|
490
468
|
// rb_define_method(cImage, "quality=", rszr_image_set_quality, 1);
|
491
469
|
|
470
|
+
rb_define_protected_method(cImage, "_format", rszr_image__format_get, 0);
|
492
471
|
rb_define_protected_method(cImage, "_format=", rszr_image__format_set, 1);
|
493
472
|
|
494
473
|
rb_define_private_method(cImage, "_resize", rszr_image__resize, 7);
|
495
474
|
rb_define_private_method(cImage, "_crop", rszr_image__crop, 5);
|
496
475
|
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, "_brighten!", rszr_image__brighten_bang, 1);
|
476
|
+
rb_define_private_method(cImage, "_rotate", rszr_image__rotate, 2);
|
477
|
+
rb_define_private_method(cImage, "_sharpen!", rszr_image__sharpen_bang, 1);
|
500
478
|
|
501
479
|
rb_define_private_method(cImage, "_save", rszr_image__save, 3);
|
502
480
|
}
|
@@ -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
|
@@ -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,21 +1,38 @@
|
|
1
1
|
module Rszr
|
2
2
|
class Image
|
3
|
+
extend Identification
|
4
|
+
include Buffered
|
5
|
+
include Orientation
|
3
6
|
|
4
7
|
class << self
|
5
|
-
|
8
|
+
|
6
9
|
def load(path, autorotate: Rszr.autorotate, **opts)
|
7
10
|
path = path.to_s
|
8
11
|
raise FileNotFound unless File.exist?(path)
|
9
|
-
_load(path
|
12
|
+
image = _load(path)
|
13
|
+
autorotate(image, path) if autorotate
|
14
|
+
image
|
10
15
|
end
|
11
16
|
alias :open :load
|
12
17
|
|
18
|
+
def load_data(data, autorotate: Rszr.autorotate, **opts)
|
19
|
+
raise LoadError, 'Unknown format' unless format = identify(data)
|
20
|
+
with_tempfile(format, data) do |file|
|
21
|
+
load(file.path, autorotate: autorotate, **opts)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
13
25
|
end
|
14
26
|
|
15
27
|
def dimensions
|
16
28
|
[width, height]
|
17
29
|
end
|
18
30
|
|
31
|
+
def format
|
32
|
+
fmt = _format
|
33
|
+
fmt == 'jpg' ? 'jpeg' : fmt
|
34
|
+
end
|
35
|
+
|
19
36
|
def format=(fmt)
|
20
37
|
fmt = fmt.to_s if fmt.is_a?(Symbol)
|
21
38
|
self._format = fmt
|
@@ -28,12 +45,12 @@ module Rszr
|
|
28
45
|
end
|
29
46
|
|
30
47
|
module Transformations
|
31
|
-
def resize(*args)
|
32
|
-
_resize(false, *calculate_size(*args))
|
48
|
+
def resize(*args, **opts)
|
49
|
+
_resize(false, *calculate_size(*args, **opts))
|
33
50
|
end
|
34
51
|
|
35
|
-
def resize!(*args)
|
36
|
-
_resize(true, *calculate_size(*args))
|
52
|
+
def resize!(*args, **opts)
|
53
|
+
_resize(true, *calculate_size(*args, **opts))
|
37
54
|
end
|
38
55
|
|
39
56
|
def crop(x, y, width, height)
|
@@ -60,6 +77,16 @@ module Rszr
|
|
60
77
|
def rotate!(deg)
|
61
78
|
_rotate(true, deg.to_f * Math::PI / 180.0)
|
62
79
|
end
|
80
|
+
|
81
|
+
# horizontal
|
82
|
+
def flop
|
83
|
+
dup.flop!
|
84
|
+
end
|
85
|
+
|
86
|
+
# vertical
|
87
|
+
def flip
|
88
|
+
dup.flip!
|
89
|
+
end
|
63
90
|
|
64
91
|
def sharpen(radius)
|
65
92
|
dup.sharpen!(radius)
|
@@ -88,8 +115,8 @@ module Rszr
|
|
88
115
|
filter!("colormod(brightness=#{value.to_f});")
|
89
116
|
end
|
90
117
|
|
91
|
-
def brighten(*args)
|
92
|
-
dup.brighten!(*args)
|
118
|
+
def brighten(*args, **opts)
|
119
|
+
dup.brighten!(*args, **opts)
|
93
120
|
end
|
94
121
|
|
95
122
|
def contrast!(value, r: nil, g: nil, b: nil, a: nil)
|
@@ -97,8 +124,8 @@ module Rszr
|
|
97
124
|
filter!("colormod(contrast=#{value.to_f});")
|
98
125
|
end
|
99
126
|
|
100
|
-
def contrast(*args)
|
101
|
-
dup.contrast!(*args)
|
127
|
+
def contrast(*args, **opts)
|
128
|
+
dup.contrast!(*args, **opts)
|
102
129
|
end
|
103
130
|
|
104
131
|
def gamma!(value, r: nil, g: nil, b: nil, a: nil)
|
@@ -106,8 +133,8 @@ module Rszr
|
|
106
133
|
filter!("colormod(gamma=#{value.to_f});")
|
107
134
|
end
|
108
135
|
|
109
|
-
def gamma(*args)
|
110
|
-
dup.gamma!(*args)
|
136
|
+
def gamma(*args, **opts)
|
137
|
+
dup.gamma!(*args, **opts)
|
111
138
|
end
|
112
139
|
end
|
113
140
|
|
@@ -116,8 +143,18 @@ module Rszr
|
|
116
143
|
def save(path, format: nil, quality: nil)
|
117
144
|
format ||= format_from_filename(path) || self.format || 'jpg'
|
118
145
|
raise ArgumentError, "invalid quality #{quality.inspect}" if quality && !(0..100).cover?(quality)
|
146
|
+
ensure_path_is_writable(path)
|
119
147
|
_save(path.to_s, format.to_s, quality)
|
120
148
|
end
|
149
|
+
|
150
|
+
def save_data(format: nil, quality: nil)
|
151
|
+
format ||= self.format || 'jpg'
|
152
|
+
with_tempfile(format) do |file|
|
153
|
+
save(file.path, format: format, quality: quality)
|
154
|
+
file.rewind
|
155
|
+
file.read
|
156
|
+
end
|
157
|
+
end
|
121
158
|
|
122
159
|
private
|
123
160
|
|
@@ -129,9 +166,9 @@ module Rszr
|
|
129
166
|
# 400, 300, background: rgba
|
130
167
|
# 400, 300, skew: true
|
131
168
|
|
132
|
-
def calculate_size(*args)
|
169
|
+
def calculate_size(*args, crop: nil, skew: nil)
|
133
170
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
134
|
-
assert_valid_keys options, :crop, :background, :skew #:extend, :width, :height, :max_width, :max_height, :box
|
171
|
+
#assert_valid_keys options, :crop, :background, :skew #:extend, :width, :height, :max_width, :max_height, :box
|
135
172
|
original_width, original_height = width, height
|
136
173
|
x, y, = 0, 0
|
137
174
|
if args.size == 1
|
@@ -148,9 +185,9 @@ module Rszr
|
|
148
185
|
new_width = box_width
|
149
186
|
new_height = box_width.to_f / original_width.to_f * original_height.to_f
|
150
187
|
elsif box_width.is_a?(Numeric) && box_height.is_a?(Numeric)
|
151
|
-
if
|
188
|
+
if skew
|
152
189
|
new_width, new_height = box_width, box_height
|
153
|
-
elsif
|
190
|
+
elsif crop
|
154
191
|
# TODO: calculate x, y offset if crop
|
155
192
|
else
|
156
193
|
scale = original_width.to_f / original_height.to_f
|
@@ -175,6 +212,15 @@ module Rszr
|
|
175
212
|
def format_from_filename(path)
|
176
213
|
File.extname(path)[1..-1].to_s.downcase
|
177
214
|
end
|
215
|
+
|
216
|
+
def ensure_path_is_writable(path)
|
217
|
+
path = Pathname.new(path)
|
218
|
+
path.dirname.realpath.writable?
|
219
|
+
rescue Errno::ENOENT => e
|
220
|
+
raise SaveError, 'Non-existant path component'
|
221
|
+
rescue SystemCallError => e
|
222
|
+
raise SaveError, e.message
|
223
|
+
end
|
178
224
|
|
179
225
|
def assert_valid_keys(hsh, *valid_keys)
|
180
226
|
if unknown_key = (hsh.keys - valid_keys).first
|
@@ -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, IO::SEEK_CUR)
|
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, 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,14 @@
|
|
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'
|
6
12
|
require 'rszr/image'
|
7
13
|
|
8
14
|
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: 0.
|
4
|
+
version: 0.8.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: 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: '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: 2021-10-08 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
|
@@ -186,21 +18,25 @@ extensions:
|
|
186
18
|
- ext/rszr/extconf.rb
|
187
19
|
extra_rdoc_files: []
|
188
20
|
files:
|
189
|
-
- LICENSE
|
190
|
-
- README.md
|
191
|
-
- Rakefile
|
192
|
-
- ext/rszr/errors.c
|
193
|
-
- ext/rszr/errors.h
|
194
|
-
- ext/rszr/extconf.rb
|
195
|
-
- ext/rszr/image.c
|
196
|
-
- ext/rszr/image.h
|
197
|
-
- ext/rszr/rszr.c
|
198
|
-
- ext/rszr/rszr.h
|
199
|
-
- lib/rszr.rb
|
200
21
|
- lib/rszr/batch_transformation.rb
|
22
|
+
- lib/rszr/buffered.rb
|
23
|
+
- lib/rszr/identification.rb
|
201
24
|
- lib/rszr/image.rb
|
202
25
|
- lib/rszr/image_processing.rb
|
26
|
+
- lib/rszr/orientation.rb
|
27
|
+
- lib/rszr/stream.rb
|
203
28
|
- lib/rszr/version.rb
|
29
|
+
- lib/rszr.rb
|
30
|
+
- ext/rszr/extconf.rb
|
31
|
+
- ext/rszr/errors.h
|
32
|
+
- ext/rszr/image.h
|
33
|
+
- ext/rszr/rszr.h
|
34
|
+
- ext/rszr/errors.c
|
35
|
+
- ext/rszr/image.c
|
36
|
+
- ext/rszr/rszr.c
|
37
|
+
- LICENSE
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
204
40
|
homepage: https://github.com/mtgrosser/rszr
|
205
41
|
licenses:
|
206
42
|
- MIT
|
@@ -212,18 +48,18 @@ require_paths:
|
|
212
48
|
- ext
|
213
49
|
required_ruby_version: !ruby/object:Gem::Requirement
|
214
50
|
requirements:
|
215
|
-
- -
|
51
|
+
- - '>='
|
216
52
|
- !ruby/object:Gem::Version
|
217
53
|
version: '0'
|
218
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
219
55
|
requirements:
|
220
|
-
- -
|
56
|
+
- - '>='
|
221
57
|
- !ruby/object:Gem::Version
|
222
58
|
version: '0'
|
223
59
|
requirements:
|
224
60
|
- imlib2
|
225
|
-
|
226
|
-
rubygems_version:
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 2.0.14.1
|
227
63
|
signing_key:
|
228
64
|
specification_version: 4
|
229
65
|
summary: Fast image resizer
|