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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: bfbe455a6a95a6fc5161b65c03450e66e3e7165794556abd90cfef22da5a1f32
4
- data.tar.gz: 1ced66bb5a143f23d55dd1496fc79cf29d58b529138405741f93ba6379e62ba5
2
+ SHA1:
3
+ metadata.gz: 035d5f7341e71cdc9b5ba70de6e116acd8d8459e
4
+ data.tar.gz: 3119e97a26295db8f3931821d6ae835aaa6166ee
5
5
  SHA512:
6
- metadata.gz: 007c4d61f001ec86a6be7db860a68edf61f1eac63dfbf1b55afc24e51aa4033248b9224a3706b53c2887555812c0eeaac1d7e62ef66c40e0dd0544c8d67642a3
7
- data.tar.gz: ef41050b95a085ba6d1fd87c26596e3387a9eb6b248e1d0f741c079dcabaa381912e9c07f28916ff8739df29c903513c0cf78e22384396f5a16384428f6b8014
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) [![Build Status](https://travis-ci.org/mtgrosser/rszr.svg)](https://travis-ci.org/mtgrosser/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 and libexif
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 libexif
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 libexif libexif-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 libexif libexif-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
- # sharpen image by pixel radius
83
- image.sharpen!(1)
93
+ # flip vertically
94
+ image.flip
84
95
 
85
- # blur image by pixel radius
86
- image.blur!(1)
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
- ### Image info
106
+ ### Filters
107
+
108
+ Filters also support bang! and non-bang methods.
109
+
96
110
  ```ruby
97
- image.width => 400
98
- image.height => 300
99
- image.dimensions => [400, 300]
100
- image.format => "jpeg"
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
- int error_index = (int) error - 1;
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
- VALUE rb_error = rb_exc_new2(rb_error_class, sRszrErrorMessages[error_index]);
52
+
53
+ rb_error = rb_exc_new2(rb_error_class, sRszrErrorMessages[error_index]);
49
54
  rb_exc_raise(rb_error);
50
55
  }
51
56
 
data/ext/rszr/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, VALUE autorotate)
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 = imlib_load_image_with_error_return(path, &error);
63
+ image = imlib_load_image_without_cache(path);
101
64
 
102
65
  if (!image) {
103
- rszr_raise_load_error(error);
104
- return Qnil;
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
- if (RTEST(autorotate)) rszr_image_autorotate(image, path);
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 rszr_image_format_get(VALUE self)
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, 2);
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", rszr_image__rotate, 2);
498
- rb_define_private_method(cImage, "_sharpen!", rszr_image__sharpen_bang, 1);
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, autorotate)
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 options[:skew]
188
+ if skew
152
189
  new_width, new_height = box_width, box_height
153
- elsif options[:crop]
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Rszr
2
- VERSION = '0.7.1'
2
+ VERSION = '0.8.0'
3
3
  end
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.7.1
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-05-10 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '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
- - libexif
226
- rubygems_version: 3.1.4
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