rszr 0.5.2 → 1.0.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
2
  SHA256:
3
- metadata.gz: c4fc21afb848256445e310a0feebab86b2b10ee6baf464bd7e67a44b282b4d07
4
- data.tar.gz: bfe92bbc780dbf03135b4b3d7296df141bee74279412b6bd3b6d2ba70362a140
3
+ metadata.gz: ea3437991dd4694e35653020632c3fc360f787e1d391834780fdf118991a90ce
4
+ data.tar.gz: 37ccc3f8693e4fe9d41939cfc2d3786ef72ed24e344059f1dd4e4492efcae862
5
5
  SHA512:
6
- metadata.gz: 26d46f7b4d594da5e871d75975d99cd54358bb6585d8b18fbddcba1b4383970099891f7af6d16b51b8dcf19f5ed179b275d001b15f546c3baa58a3b6573613f9
7
- data.tar.gz: 0f5866b917277c7afb518fb15ba83451fc7b448681fec26dad4321d17cfa464854e9182cff88008f7ee25b1c6df135f6a14f3980b64a145db9ef93e035de1d9e
6
+ metadata.gz: 35b8219e81c0f89e4b51719617c13edf71421d7e3ed4e1998f8e26fc017545da76c5164c0bbd63c16e5c617cfdd4f5cb1f07a8e0bbdc83b7bb90ea7fbbd71907
7
+ data.tar.gz: fd4361d89e0ff577032da1dea39686c035604d98e5b6b935ca8de8f1289bdfe6cf5678c8842406c0b9c5829cfab2c03af8350670638824a99deeb176e4675428
data/README.md CHANGED
@@ -1,7 +1,8 @@
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
- Rszr is an image resizer for Ruby based on the Imlib2 library. It is faster and consumes less memory than MiniMagick, rmagick and GD2.
4
+ Rszr is an image resizer for Ruby based on the Imlib2 library.
5
+ It is faster and consumes less memory than MiniMagick, GD2 and VIPS, and comes with an optional drop-in interface for Rails ActiveStorage image processing.
5
6
 
6
7
  ## Installation
7
8
 
@@ -13,7 +14,7 @@ gem 'rszr'
13
14
 
14
15
  ### Imlib2
15
16
 
16
- Rszr requires the Imlib2 library to do the heavy lifting.
17
+ Rszr requires the `Imlib2` library to do the heavy lifting.
17
18
 
18
19
  #### OS X
19
20
 
@@ -27,10 +28,14 @@ brew install imlib2
27
28
 
28
29
  Using your favourite package manager:
29
30
 
31
+ ##### RedHat-based
32
+
30
33
  ```bash
31
34
  yum install imlib2 imlib2-devel
32
35
  ```
33
36
 
37
+ ##### Debian-based
38
+
34
39
  ```bash
35
40
  apt-get install libimlib2 libimlib2-dev
36
41
  ```
@@ -47,7 +52,28 @@ image.save('resized.jpg')
47
52
 
48
53
  # save it as PNG
49
54
  image.save('resized.png')
55
+ ```
50
56
 
57
+ ### Image info
58
+ ```ruby
59
+ image.width => 400
60
+ image.height => 300
61
+ image.dimensions => [400, 300]
62
+ image.format => "jpeg"
63
+ image[0, 0] => <Rszr::Color::RGBA @red=38, @green=115, @blue=141, @alpha=255>
64
+ image[0, 0].to_hex => "26738dff"
65
+ image[0, 0].to_hex(rgb: true) => "26738d"
66
+ ```
67
+
68
+ ### Transformations
69
+
70
+ For each transformation, there is a bang! and non-bang method.
71
+ The bang method changes the image in place, while the non-bang method
72
+ creates a copy of the image in memory.
73
+
74
+ #### Resizing
75
+
76
+ ```ruby
51
77
  # auto height
52
78
  image.resize(400, :auto)
53
79
 
@@ -57,6 +83,22 @@ image.resize(:auto, 300)
57
83
  # scale factor
58
84
  image.resize(0.5)
59
85
 
86
+ # resize to fill
87
+ image.resize(400, 300, crop: true)
88
+
89
+ # resize to fill with gravity
90
+ # where gravity in [:n, :nw, :w, :sw, :w, :se, :e, :ne, :center]
91
+ image.resize(400, 300, crop: gravity)
92
+
93
+ # save memory, do not duplicate instance
94
+ image.resize!(400, :auto)
95
+ ```
96
+
97
+ Check out the [full list and demo of resize options](https://mtgrosser.github.io/rszr/resizing.html)!
98
+
99
+ #### Other transformations
100
+
101
+ ```ruby
60
102
  # crop
61
103
  image.crop(200, 200, 100, 100)
62
104
 
@@ -66,30 +108,110 @@ image.turn!(3)
66
108
  # rotate one time 90 deg counterclockwise
67
109
  image.turn!(-1)
68
110
 
111
+ # rotate by arbitrary angle
112
+ image.rotate(45)
113
+
114
+ # flip vertically
115
+ image.flip
116
+
117
+ # flop horizontally
118
+ image.flop
119
+
69
120
  # initialize copy
70
121
  image.dup
122
+ ```
71
123
 
72
- # save memory, do not duplicate instance
73
- image.resize!(400, :auto)
124
+ ### Filters
74
125
 
75
- # image info
76
- image.width => 400
77
- image.height => 300
78
- image.dimensions => [400, 300]
79
- image.format => "jpeg"
126
+ Filters also support bang! and non-bang methods.
127
+
128
+ ```ruby
129
+ # sharpen image by pixel radius
130
+ image.sharpen!(1)
131
+
132
+ # blur image by pixel radius
133
+ image.blur!(1)
134
+
135
+ # brighten
136
+ image.brighten(0.1)
137
+
138
+ # darken
139
+ image.brighten(-0.1)
140
+
141
+ # contrast
142
+ image.contrast(0.5)
143
+
144
+ # gamma
145
+ image.gamma(1.1)
146
+ ```
147
+
148
+ ### Image auto orientation
149
+
150
+ Auto-rotation is supported for JPEG and TIFF files that include the necessary
151
+ EXIF metadata.
152
+
153
+ ```ruby
154
+ # load and autorotate
155
+ image = Rszr::Image.load('image.jpg', autorotate: true)
156
+ ```
157
+
158
+ To enable autorotation by default:
159
+
160
+ ```ruby
161
+ # auto-rotate by default, for Rails apps put this into an initializer
162
+ Rszr.autorotate = true
163
+ ```
164
+
165
+ ## Rails / ActiveStorage interface
166
+
167
+ Rszr provides a drop-in interface to the `image_processing` gem.
168
+ It is faster than both `mini_magick` and `vips` and way easier to install than the latter.
169
+
170
+ ```ruby
171
+ # Gemfile
172
+ gem 'image_processing'
173
+ gem 'rszr'
174
+
175
+ # config/initializers/rszr.rb
176
+ require 'rszr/image_processing'
177
+
178
+ # config/application.rb
179
+ config.active_storage.variant_processor = :rszr
180
+ ```
181
+
182
+ When creating image variants, you can use all of Rszr's transformation methods:
183
+
184
+ ```erb
185
+ <%= image_tag user.avatar.variant(resize_to_fit: [300, 200]) %>
186
+ ```
187
+
188
+ ## Loading from and saving to memory
189
+
190
+ The `Imlib2` library is mainly file-oriented and doesn't provide a way of loading
191
+ the undecoded image from a memory buffer. Therefore, the functionality is
192
+ implemented on the Ruby side of the gem, writing the memory buffer to a Tempfile.
193
+ Currently, this local write cannot be avoided.
194
+
195
+ ```ruby
196
+ image = Rszr::Image.load_data(binary_data)
197
+
198
+ data = image.save_data(format: :jpeg)
80
199
  ```
81
200
 
82
201
  ## Thread safety
83
202
 
84
- As of version 0.4.0, Rszr is thread safe through the Ruby GIL.
203
+ As of version 0.5.0, Rszr is thread safe through Ruby's global VM lock.
85
204
  Use of any previous versions in a threaded environment is discouraged.
86
205
 
87
206
  ## Speed
88
207
 
89
- Resizing an 1500x997 JPEG image to 800x532, 100 times:
208
+ Resizing a 1500x997 JPEG image to 800x532, 500 times:
209
+ ![Speed](https://github.com/mtgrosser/rszr/blob/master/benchmark/speed.png)
210
+
90
211
 
91
212
  Library | Time
92
213
  ----------------|-----------
93
- MiniMagick | 12.9 s
94
- GD2 | 7.5 s
95
- Rszr | 2.8 s
214
+ MiniMagick | 27.0 s
215
+ GD2 | 28.2 s
216
+ VIPS | 13.6 s
217
+ Rszr | 7.9 s
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
- error_index = 13;
48
- VALUE rb_error = rb_exc_new2(rb_error_class, sRszrErrorMessages[error_index]);
51
+ error_index = RSZR_MAX_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/image.c CHANGED
@@ -27,7 +27,6 @@ static void rszr_image_deallocate(rszr_image_handle * handle)
27
27
  // fprintf(stderr, "\n");
28
28
  }
29
29
 
30
-
31
30
  static VALUE rszr_image_s_allocate(VALUE klass)
32
31
  {
33
32
  rszr_image_handle * handle = calloc(1, sizeof(rszr_image_handle));
@@ -61,13 +60,20 @@ static VALUE rszr_image_s__load(VALUE klass, VALUE rb_path)
61
60
  path = StringValueCStr(rb_path);
62
61
 
63
62
  imlib_set_cache_size(0);
64
- image = imlib_load_image_with_error_return(path, &error);
63
+ image = imlib_load_image_without_cache(path);
65
64
 
66
65
  if (!image) {
67
- rszr_raise_load_error(error);
68
- 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
+ }
69
72
  }
70
73
 
74
+ imlib_context_set_image(image);
75
+ imlib_image_set_irrelevant_format(0);
76
+
71
77
  oImage = rszr_image_s_allocate(cImage);
72
78
  Data_Get_Struct(oImage, rszr_image_handle, handle);
73
79
  handle->image = image;
@@ -75,7 +81,7 @@ static VALUE rszr_image_s__load(VALUE klass, VALUE rb_path)
75
81
  }
76
82
 
77
83
 
78
- static VALUE rszr_image_format(VALUE self)
84
+ static VALUE rszr_image__format_get(VALUE self)
79
85
  {
80
86
  rszr_image_handle * handle;
81
87
  char * format;
@@ -93,6 +99,20 @@ static VALUE rszr_image_format(VALUE self)
93
99
  }
94
100
 
95
101
 
102
+ static VALUE rszr_image__format_set(VALUE self, VALUE rb_format)
103
+ {
104
+ rszr_image_handle * handle;
105
+ char * format = StringValueCStr(rb_format);
106
+
107
+ Data_Get_Struct(self, rszr_image_handle, handle);
108
+
109
+ imlib_context_set_image(handle->image);
110
+ imlib_image_set_format(format);
111
+
112
+ return self;
113
+ }
114
+
115
+
96
116
  static VALUE rszr_image_width(VALUE self)
97
117
  {
98
118
  rszr_image_handle * handle;
@@ -121,6 +141,70 @@ static VALUE rszr_image_height(VALUE self)
121
141
  }
122
142
 
123
143
 
144
+ static VALUE rszr_image__pixel_get(VALUE self, VALUE rb_x, VALUE rb_y)
145
+ {
146
+ rszr_image_handle * handle;
147
+ Imlib_Color color_return;
148
+ VALUE rb_rgba;
149
+ int x, y;
150
+
151
+ Check_Type(rb_x, T_FIXNUM);
152
+ x = FIX2INT(rb_x);
153
+ Check_Type(rb_y, T_FIXNUM);
154
+ y = FIX2INT(rb_y);
155
+
156
+ Data_Get_Struct(self, rszr_image_handle, handle);
157
+
158
+ imlib_context_set_image(handle->image);
159
+ imlib_image_query_pixel(x, y, &color_return);
160
+
161
+ rb_rgba = rb_ary_new3(4, INT2NUM(color_return.red),
162
+ INT2NUM(color_return.green),
163
+ INT2NUM(color_return.blue),
164
+ INT2NUM(color_return.alpha));
165
+ return rb_rgba;
166
+ }
167
+
168
+
169
+ /*
170
+ static VALUE rszr_image_get_quality(VALUE self)
171
+ {
172
+ rszr_image_handle * handle;
173
+ int quality;
174
+
175
+ Data_Get_Struct(self, rszr_image_handle, handle);
176
+
177
+ imlib_context_set_image(handle->image);
178
+ quality = imlib_image_get_attached_value("quality");
179
+
180
+ if (quality) {
181
+ return INT2NUM(quality);
182
+ } else {
183
+ return Qnil;
184
+ }
185
+ }
186
+
187
+ static VALUE rszr_image_set_quality(VALUE self, VALUE rb_quality)
188
+ {
189
+ rszr_image_handle * handle;
190
+ int quality;
191
+
192
+ Check_Type(rb_quality, T_FIXNUM);
193
+ quality = FIX2INT(rb_quality);
194
+ if (quality <= 0) {
195
+ rb_raise(rb_eArgError, "quality must be >= 0");
196
+ return Qnil;
197
+ }
198
+
199
+ Data_Get_Struct(self, rszr_image_handle, handle);
200
+
201
+ imlib_context_set_image(handle->image);
202
+ imlib_image_attach_data_value("quality", NULL, quality, NULL);
203
+
204
+ return INT2NUM(quality);
205
+ }
206
+ */
207
+
124
208
  static VALUE rszr_image_dup(VALUE self)
125
209
  {
126
210
  rszr_image_handle * handle;
@@ -159,6 +243,105 @@ static VALUE rszr_image__turn_bang(VALUE self, VALUE orientation)
159
243
  }
160
244
 
161
245
 
246
+ static VALUE rszr_image_flop_bang(VALUE self)
247
+ {
248
+ rszr_image_handle * handle;
249
+
250
+ Data_Get_Struct(self, rszr_image_handle, handle);
251
+
252
+ imlib_context_set_image(handle->image);
253
+ imlib_image_flip_horizontal();
254
+
255
+ return self;
256
+ }
257
+
258
+
259
+ static VALUE rszr_image_flip_bang(VALUE self)
260
+ {
261
+ rszr_image_handle * handle;
262
+
263
+ Data_Get_Struct(self, rszr_image_handle, handle);
264
+
265
+ imlib_context_set_image(handle->image);
266
+ imlib_image_flip_vertical();
267
+
268
+ return self;
269
+ }
270
+
271
+
272
+ static VALUE rszr_image__rotate(VALUE self, VALUE bang, VALUE rb_angle)
273
+ {
274
+ rszr_image_handle * handle;
275
+ rszr_image_handle * rotated_handle;
276
+ Imlib_Image rotated_image;
277
+ VALUE oRotatedImage;
278
+ double angle;
279
+
280
+ angle = NUM2DBL(rb_angle);
281
+
282
+ Data_Get_Struct(self, rszr_image_handle, handle);
283
+
284
+ imlib_context_set_image(handle->image);
285
+ rotated_image = imlib_create_rotated_image(angle);
286
+
287
+ if (!rotated_image) {
288
+ rb_raise(eRszrTransformationError, "error rotating image");
289
+ return Qnil;
290
+ }
291
+
292
+ if (RTEST(bang)) {
293
+ rszr_free_image(handle->image);
294
+ handle->image = rotated_image;
295
+
296
+ return self;
297
+ }
298
+ else {
299
+ oRotatedImage = rszr_image_s_allocate(cImage);
300
+ Data_Get_Struct(oRotatedImage, rszr_image_handle, rotated_handle);
301
+ rotated_handle->image = rotated_image;
302
+
303
+ return oRotatedImage;
304
+ }
305
+ }
306
+
307
+
308
+ static VALUE rszr_image_filter_bang(VALUE self, VALUE rb_filter_expr)
309
+ {
310
+ rszr_image_handle * handle;
311
+ char * filter_expr;
312
+
313
+ filter_expr = StringValueCStr(rb_filter_expr);
314
+
315
+ Data_Get_Struct(self, rszr_image_handle, handle);
316
+
317
+ imlib_context_set_image(handle->image);
318
+ imlib_apply_filter(filter_expr);
319
+
320
+ return self;
321
+ }
322
+
323
+
324
+ static VALUE rszr_image__sharpen_bang(VALUE self, VALUE rb_radius)
325
+ {
326
+ rszr_image_handle * handle;
327
+ int radius;
328
+
329
+ radius = NUM2INT(rb_radius);
330
+
331
+ Data_Get_Struct(self, rszr_image_handle, handle);
332
+
333
+ imlib_context_set_image(handle->image);
334
+
335
+ if (radius >= 0) {
336
+ imlib_image_sharpen(radius);
337
+ } else {
338
+ imlib_image_blur(-radius);
339
+ }
340
+
341
+ return self;
342
+ }
343
+
344
+
162
345
  static Imlib_Image rszr_create_cropped_scaled_image(const Imlib_Image image, VALUE rb_src_x, VALUE rb_src_y, VALUE rb_src_w, VALUE rb_src_h, VALUE rb_dst_w, VALUE rb_dst_h)
163
346
  {
164
347
  Imlib_Image resized_image;
@@ -262,20 +445,24 @@ static VALUE rszr_image__crop(VALUE self, VALUE bang, VALUE rb_x, VALUE rb_y, VA
262
445
  }
263
446
 
264
447
 
265
- static VALUE rszr_image__save(VALUE self, VALUE rb_path, VALUE rb_format)
448
+ static VALUE rszr_image__save(VALUE self, VALUE rb_path, VALUE rb_format, VALUE rb_quality)
266
449
  {
267
450
  rszr_image_handle * handle;
268
451
  char * path;
269
452
  char * format;
453
+ int quality;
270
454
  Imlib_Load_Error save_error;
271
455
 
272
456
  path = StringValueCStr(rb_path);
273
457
  format = StringValueCStr(rb_format);
274
-
458
+ quality = (NIL_P(rb_quality)) ? 0 : FIX2INT(rb_quality);
459
+
275
460
  Data_Get_Struct(self, rszr_image_handle, handle);
276
461
 
277
462
  imlib_context_set_image(handle->image);
278
463
  imlib_image_set_format(format);
464
+ if (quality)
465
+ imlib_image_attach_data_value("quality", NULL, quality, NULL);
279
466
  imlib_save_image_with_error_return(path, &save_error);
280
467
 
281
468
  if (save_error) {
@@ -298,12 +485,25 @@ void Init_rszr_image()
298
485
  rb_define_method(cImage, "initialize", rszr_image_initialize, 2);
299
486
  rb_define_method(cImage, "width", rszr_image_width, 0);
300
487
  rb_define_method(cImage, "height", rszr_image_height, 0);
301
- rb_define_method(cImage, "format", rszr_image_format, 0);
302
488
  rb_define_method(cImage, "dup", rszr_image_dup, 0);
489
+ rb_define_method(cImage, "filter!", rszr_image_filter_bang, 1);
490
+ rb_define_method(cImage, "flop!", rszr_image_flop_bang, 0);
491
+ rb_define_method(cImage, "flip!", rszr_image_flip_bang, 0);
492
+
493
+ // rb_define_method(cImage, "quality", rszr_image_get_quality, 0);
494
+ // rb_define_method(cImage, "quality=", rszr_image_set_quality, 1);
495
+
496
+ rb_define_protected_method(cImage, "_format", rszr_image__format_get, 0);
497
+ rb_define_protected_method(cImage, "_format=", rszr_image__format_set, 1);
498
+
303
499
  rb_define_private_method(cImage, "_resize", rszr_image__resize, 7);
304
500
  rb_define_private_method(cImage, "_crop", rszr_image__crop, 5);
305
501
  rb_define_private_method(cImage, "_turn!", rszr_image__turn_bang, 1);
306
- rb_define_private_method(cImage, "_save", rszr_image__save, 2);
502
+ rb_define_private_method(cImage, "_rotate", rszr_image__rotate, 2);
503
+ rb_define_private_method(cImage, "_sharpen!", rszr_image__sharpen_bang, 1);
504
+ rb_define_private_method(cImage, "_pixel", rszr_image__pixel_get, 2);
505
+
506
+ rb_define_private_method(cImage, "_save", rszr_image__save, 3);
307
507
  }
308
508
 
309
509
  #endif
data/ext/rszr/rszr.h CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  #include "ruby.h"
5
5
  #include <Imlib2.h>
6
+ #include <libexif/exif-data.h>
6
7
 
7
8
  extern VALUE mRszr;
8
9
  void Init_rszr();
@@ -0,0 +1,24 @@
1
+ module Rszr
2
+ class BatchTransformation
3
+ attr_reader :transformations, :image
4
+
5
+ def initialize(path, **opts)
6
+ puts "INITIALIZED BATCH for #{path}"
7
+ @image = path.is_a?(Image) ? path : Image.load(path, **opts)
8
+ @transformations = []
9
+ end
10
+
11
+ Image::Transformations.instance_methods.grep(/\w\z/) do |method|
12
+ define_method method do |*args|
13
+ transformations << [method, args]
14
+ self
15
+ end
16
+ end
17
+
18
+ def call
19
+ transformations.each { |method, args| image.public_send("#{method}!", *args) }
20
+ image
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module Rszr
2
+ module Buffered
3
+ def self.included(base)
4
+ base.extend Buffered
5
+ end
6
+
7
+ private
8
+
9
+ def with_tempfile(format, data = nil)
10
+ raise ArgumentError, 'format is required' unless format
11
+ result = nil
12
+ Tempfile.create(['rszr-buffer', ".#{format}"], encoding: 'BINARY') do |file|
13
+ if data
14
+ file.binmode
15
+ file << data
16
+ file.fsync
17
+ file.rewind
18
+ end
19
+ result = yield(file)
20
+ end
21
+ result
22
+ end
23
+
24
+ end
25
+ end
data/lib/rszr/color.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Rszr
2
+ module Color
3
+
4
+ class RGBA
5
+ attr_reader :red, :green, :blue, :alpha
6
+
7
+ def initialize(red, green, blue, alpha = 255)
8
+ if red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255 || alpha < 0 || alpha > 255
9
+ raise ArgumentError, 'color out of range'
10
+ end
11
+ @red, @green, @blue, @alpha = red, green, blue, alpha
12
+ end
13
+
14
+ def to_i(rgb: false)
15
+ i = red.to_i << 24 | green.to_i << 16 | blue.to_i << 8 | alpha.to_i
16
+ rgb ? i >> 8 : i
17
+ end
18
+
19
+ def to_hex(rgb: false)
20
+ "%0#{rgb ? 6 : 8}x" % to_i(rgb: rgb)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,60 @@
1
+ # Type reader adapted from fastimage
2
+ # https://github.com/sdsykes/fastimage/
3
+
4
+ module Rszr
5
+ module Identification
6
+
7
+ private
8
+
9
+ def identify(data)
10
+ case data[0, 2]
11
+ when 'BM'
12
+ :bmp
13
+ when 'GI'
14
+ :gif
15
+ when 0xff.chr + 0xd8.chr
16
+ :jpeg
17
+ when 0x89.chr + 'P'
18
+ :png
19
+ when 'II', 'MM'
20
+ case data[0, 11][8..10] # @stream.peek(11)[8..10]
21
+ when 'APC', "CR\002"
22
+ nil # do not recognise CRW or CR2 as tiff
23
+ else
24
+ :tiff
25
+ end
26
+ when '8B'
27
+ :psd
28
+ when "\0\0"
29
+ case data[0, 3].bytes.last #@stream.peek(3).bytes.to_a.last
30
+ when 0
31
+ # http://www.ftyps.com/what.html
32
+ # HEIC is composed of nested "boxes". Each box has a header composed of
33
+ # - Size (32 bit integer)
34
+ # - Box type (4 chars)
35
+ # - Extended size: only if size === 1, the type field is followed by 64 bit integer of extended size
36
+ # - Payload: Type-dependent
37
+ case data[0, 12][4..-1] #@stream.peek(12)[4..-1]
38
+ when 'ftypheic'
39
+ :heic
40
+ when 'ftypmif1'
41
+ :heif
42
+ end
43
+ # ico has either a 1 (for ico format) or 2 (for cursor) at offset 3
44
+ when 1 then :ico
45
+ when 2 then :cur
46
+ end
47
+ when 'RI'
48
+ :webp if data[0, 12][8..11] == 'WEBP' #@stream.peek(12)[8..11] == "WEBP"
49
+ when "<s"
50
+ :svg if data[0, 4] == '<svg' #@stream.peek(4) == "<svg"
51
+ when /\s\s|\s<|<[?!]/, 0xef.chr + 0xbb.chr
52
+ # Peek 10 more chars each time, and if end of file is reached just raise
53
+ # unknown. We assume the <svg tag cannot be within 10 chars of the end of
54
+ # the file, and is within the first 250 chars.
55
+ :svg if (1..25).detect { |n| data[0, 10 * n]&.include?('<svg') }
56
+ end
57
+ end
58
+
59
+ end
60
+ end