rszr 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Gem Version](https://badge.fury.io/rb/rszr.svg)](http://badge.fury.io/rb/rszr) [![
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/rszr.svg)](http://badge.fury.io/rb/rszr) [![build](https://github.com/mtgrosser/rszr/actions/workflows/build.yml/badge.svg)](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
|