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 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