rszr 1.1.0 → 1.3.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
2
  SHA256:
3
- metadata.gz: 453bbac498bb376737ac50b776a9d1824cbb0f2338753b82c09c36ef08bc152b
4
- data.tar.gz: 66941eca0be0d49922627be9329ca6c623686809094be55b80fc490f8dd59290
3
+ metadata.gz: ce6596f9a3f8e4d355c5d974da7e918dd574517b1ab87aaf6f66e35c8c26c600
4
+ data.tar.gz: 21905d628d567cbc06de0b0db56dc1515c5da7f533acc3917aedb8d91962b9cc
5
5
  SHA512:
6
- metadata.gz: ed7559dfe4f2a754f9f4b46fe0cf3b88f1003d8c055dc38d5eb9335f49ff8656689464fbb8f0c0756466ab9dd6af775e9bb12add63f29d167a20e832354d3adc
7
- data.tar.gz: 49b195f7d27005a81175ae467e0f7f05d974b020db07a90a1f34999df2dd69cd96591d2a96d7ae07872886074dbc937e8d53bf99380fa0bf5d94181065461682
6
+ metadata.gz: 262f3cda5bc4ff4c6a86a5e7bb2ab4de2414f2652648d09e0d8d2a327c62e37eaf83d091fff515853f2d853754d6d5dd56c8a97e51a851c46c427bd69207213d
7
+ data.tar.gz: 1bd9c512ccbd0b95cb665d8a439c2ffa24456671d8165c555ad190058a16bad70913dfada98fbe6a4280a843ccfd83db02ea203746764ef8050586abcfd414fa
data/README.md CHANGED
@@ -52,6 +52,9 @@ image.save('resized.jpg')
52
52
 
53
53
  # save it as PNG
54
54
  image.save('resized.png')
55
+
56
+ # save without extension in given format
57
+ image.save('resized', format: 'png')
55
58
  ```
56
59
 
57
60
  ### Image info
@@ -60,9 +63,10 @@ image.width => 400
60
63
  image.height => 300
61
64
  image.dimensions => [400, 300]
62
65
  image.format => "jpeg"
66
+ image.alpha? => false
63
67
  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"
68
+ image[0, 0].to_hex => "#26738dff"
69
+ image[0, 0].to_hex(alpha: false) => "#26738d"
66
70
  ```
67
71
 
68
72
  ### Transformations
@@ -121,6 +125,97 @@ image.flop
121
125
  image.dup
122
126
  ```
123
127
 
128
+ ### Image generation
129
+
130
+ ```ruby
131
+ # generate new image with transparent background
132
+ image = Rszr::Image.new(500, 500, alpha: true, background: Rszr::Color::Transparent)
133
+
134
+ # fill image with 50% opacity
135
+ image.fill!(Rszr::Color::RGBA.new(0, 206, 209, 50))
136
+
137
+ # define a color gradient
138
+ gradient = Rszr::Color::Gradient.new do |g|
139
+ g.point 0, 255, 250, 205, 50
140
+ g.point 0.5, 135, 206, 250
141
+ g.point 1, Rszr::Color::White
142
+ end
143
+
144
+ # draw a rectangle and fill it using the gradient with 45°
145
+ image.rectangle!(gradient.to_fill(45), 100, 100, 300, 300)
146
+ ```
147
+
148
+ ### Colors
149
+
150
+ ```ruby
151
+ # pre-defined colors
152
+ Rszr::Color::White
153
+ Rszr::Color::Black
154
+ Rszr::Color::Transparent
155
+
156
+ # RGB
157
+ color = Rszr::Color.rgba(255, 250, 50)
158
+ color.red => 255
159
+ color.green => 250
160
+ color.blue => 50
161
+ color.alpha => 255
162
+ color.cyan => 0
163
+ color.magenta => 5
164
+ color.yellow => 205
165
+
166
+ # RGBA
167
+ Rszr::Color.rgba(255, 250, 50, 255)
168
+
169
+ # CMY
170
+ Rszr::Color.cmya(0, 5, 205)
171
+
172
+ # CMYA
173
+ Rszr::Color.cmya(0, 5, 205, 255)
174
+ ```
175
+
176
+ ### Color gradients
177
+
178
+ ```ruby
179
+ # three-color linear gradient with changing opacity
180
+ gradient = Rszr::Color::Gradient.new do |g|
181
+ g.point 0, 255, 250, 205, 50
182
+ g.point 0.5, 135, 206, 250
183
+ g.point 1, Rszr::Color::White
184
+ end
185
+
186
+ # alternative syntax
187
+ gradient = Rszr::Color::Gradient.new(0 => "#fffacd32", 0.5 => "#87cefa", 1 => "#fff")
188
+
189
+ # generate fill with 45° angle
190
+ fill = gradient.to_fill(45)
191
+
192
+ # use as image background
193
+ image = Rszr::Image.new(500, 500, background: fill)
194
+ ```
195
+
196
+ ### Watermarking and image blending
197
+
198
+ ```ruby
199
+ # load logo
200
+ logo = Rszr::Image.load('logo.png')
201
+
202
+ # load image
203
+ image = Rszr::Image.load('image.jpg')
204
+
205
+ # enable alpha channel
206
+ image.alpha = true
207
+
208
+ # blend it onto the image at position (10, 10)
209
+ image.blend!(logo, 10, 10)
210
+
211
+ # blending modes:
212
+ # - copy (default)
213
+ # - add
214
+ # - subtract
215
+ # - reshade
216
+ image.blend(logo, 10, 10, mode: :subtract)
217
+ ```
218
+
124
219
  ### Filters
125
220
 
126
221
  Filters also support bang! and non-bang methods.
@@ -162,9 +257,21 @@ To enable autorotation by default:
162
257
  Rszr.autorotate = true
163
258
  ```
164
259
 
260
+ ### Creating interlaced PNG and progressive JPEG images
261
+
262
+ In order to save interlaced PNGs and progressive JPEGs, set the `interlace` option to `true`:
263
+
264
+ ```ruby
265
+ image.save('interlaced.png', interlace: true)
266
+ ```
267
+
268
+ Saving progressive JPEG images requires `imlib2` >= 1.8.1.
269
+
270
+ For EL8, there are pre-built RPMs provided by the [onrooby repo](http://downloads.onrooby.com/repo/el/8/x86_64/).
271
+
165
272
  ## Rails / ActiveStorage interface
166
273
 
167
- Rszr provides a drop-in interface to the `image_processing` gem.
274
+ Rszr provides a drop-in interface to the [image_processing](https://github.com/janko/image_processing) gem.
168
275
  It is faster than both `mini_magick` and `vips` and way easier to install than the latter.
169
276
 
170
277
  ```ruby
data/ext/rszr/errors.c CHANGED
@@ -5,6 +5,7 @@
5
5
  #include "errors.h"
6
6
 
7
7
  VALUE eRszrError = Qnil;
8
+ VALUE eRszrInternalError = Qnil;
8
9
  VALUE eRszrFileNotFound = Qnil;
9
10
  VALUE eRszrTransformationError = Qnil;
10
11
  VALUE eRszrErrorWithMessage = Qnil;
@@ -33,11 +34,12 @@ const int RSZR_MAX_ERROR_INDEX = 13;
33
34
  void Init_rszr_errors()
34
35
  {
35
36
  eRszrError = rb_define_class_under(mRszr, "Error", rb_eStandardError);
37
+ eRszrInternalError = rb_define_class_under(mRszr, "InternalError", eRszrError);
36
38
  eRszrFileNotFound = rb_define_class_under(mRszr, "FileNotFound", eRszrError);
37
39
  eRszrTransformationError = rb_define_class_under(mRszr, "TransformationError", eRszrError);
38
40
  eRszrErrorWithMessage = rb_define_class_under(mRszr, "ErrorWithMessage", eRszrError);
39
41
  eRszrLoadError = rb_define_class_under(mRszr, "LoadError", eRszrErrorWithMessage);
40
- eRszrSaveError = rb_define_class_under(mRszr, "SaveError", eRszrErrorWithMessage);
42
+ eRszrSaveError = rb_define_class_under(mRszr, "SaveError", eRszrErrorWithMessage);
41
43
  }
42
44
 
43
45
  static void rszr_raise_error_with_message(VALUE rb_error_class, Imlib_Load_Error error)
data/ext/rszr/image.c CHANGED
@@ -6,6 +6,10 @@
6
6
  #include "errors.h"
7
7
 
8
8
  VALUE cImage = Qnil;
9
+ VALUE cColorBase = Qnil;
10
+ VALUE cColorGradient = Qnil;
11
+ VALUE cColorPoint = Qnil;
12
+ VALUE cFill = Qnil;
9
13
 
10
14
 
11
15
  static void rszr_free_image(Imlib_Image image)
@@ -34,7 +38,7 @@ static VALUE rszr_image_s_allocate(VALUE klass)
34
38
  }
35
39
 
36
40
 
37
- static VALUE rszr_image_initialize(VALUE self, VALUE rb_width, VALUE rb_height)
41
+ static VALUE rszr_image__initialize(VALUE self, VALUE rb_width, VALUE rb_height)
38
42
  {
39
43
  rszr_image_handle * handle;
40
44
 
@@ -98,7 +102,6 @@ static VALUE rszr_image__format_get(VALUE self)
98
102
  }
99
103
  }
100
104
 
101
-
102
105
  static VALUE rszr_image__format_set(VALUE self, VALUE rb_format)
103
106
  {
104
107
  rszr_image_handle * handle;
@@ -113,6 +116,51 @@ static VALUE rszr_image__format_set(VALUE self, VALUE rb_format)
113
116
  }
114
117
 
115
118
 
119
+ static void rszr_image_color_set(VALUE rb_color)
120
+ {
121
+ int r, g, b, a;
122
+
123
+ if(!rb_obj_is_kind_of(rb_color, cColorBase) || RBASIC_CLASS(rb_color) == cColorBase) {
124
+ rb_raise(rb_eArgError, "color must descend from Rszr::Color::Base");
125
+ }
126
+
127
+ r = FIX2INT(rb_funcall(rb_color, rb_intern("red"), 0));
128
+ g = FIX2INT(rb_funcall(rb_color, rb_intern("green"), 0));
129
+ b = FIX2INT(rb_funcall(rb_color, rb_intern("blue"), 0));
130
+ a = FIX2INT(rb_funcall(rb_color, rb_intern("alpha"), 0));
131
+
132
+ // TODO: use color model specific setter function
133
+ imlib_context_set_color(r, g, b, a);
134
+ }
135
+
136
+
137
+ static VALUE rszr_image_alpha_get(VALUE self)
138
+ {
139
+ rszr_image_handle * handle;
140
+
141
+ Data_Get_Struct(self, rszr_image_handle, handle);
142
+
143
+ imlib_context_set_image(handle->image);
144
+ if (imlib_image_has_alpha()) {
145
+ return Qtrue;
146
+ }
147
+
148
+ return Qfalse;
149
+ }
150
+
151
+ static VALUE rszr_image_alpha_set(VALUE self, VALUE rb_alpha)
152
+ {
153
+ rszr_image_handle * handle;
154
+
155
+ Data_Get_Struct(self, rszr_image_handle, handle);
156
+
157
+ imlib_context_set_image(handle->image);
158
+ imlib_image_set_has_alpha(RTEST(rb_alpha) ? 1 : 0);
159
+
160
+ return Qnil;
161
+ }
162
+
163
+
116
164
  static VALUE rszr_image_width(VALUE self)
117
165
  {
118
166
  rszr_image_handle * handle;
@@ -165,7 +213,6 @@ static VALUE rszr_image__pixel_get(VALUE self, VALUE rb_x, VALUE rb_y)
165
213
  return rb_rgba;
166
214
  }
167
215
 
168
-
169
216
  /*
170
217
  static VALUE rszr_image_get_quality(VALUE self)
171
218
  {
@@ -205,6 +252,7 @@ static VALUE rszr_image_set_quality(VALUE self, VALUE rb_quality)
205
252
  }
206
253
  */
207
254
 
255
+
208
256
  static VALUE rszr_image_dup(VALUE self)
209
257
  {
210
258
  rszr_image_handle * handle;
@@ -357,6 +405,7 @@ static Imlib_Image rszr_create_cropped_scaled_image(const Imlib_Image image, VAL
357
405
 
358
406
  imlib_context_set_image(image);
359
407
  imlib_context_set_anti_alias(1);
408
+ imlib_context_set_dither(1);
360
409
  resized_image = imlib_create_cropped_scaled_image(src_x, src_y, src_w, src_h, dst_w, dst_h);
361
410
 
362
411
  if (!resized_image) {
@@ -400,6 +449,11 @@ static Imlib_Image rszr_create_cropped_image(const Imlib_Image image, VALUE rb_x
400
449
  {
401
450
  Imlib_Image cropped_image;
402
451
 
452
+ Check_Type(rb_x, T_FIXNUM);
453
+ Check_Type(rb_y, T_FIXNUM);
454
+ Check_Type(rb_w, T_FIXNUM);
455
+ Check_Type(rb_h, T_FIXNUM);
456
+
403
457
  int x = NUM2INT(rb_x);
404
458
  int y = NUM2INT(rb_y);
405
459
  int w = NUM2INT(rb_w);
@@ -445,7 +499,130 @@ static VALUE rszr_image__crop(VALUE self, VALUE bang, VALUE rb_x, VALUE rb_y, VA
445
499
  }
446
500
 
447
501
 
448
- static VALUE rszr_image__save(VALUE self, VALUE rb_path, VALUE rb_format, VALUE rb_quality)
502
+ static VALUE rszr_image__blend(VALUE self, VALUE other, VALUE rb_merge_alpha, VALUE rb_mode,
503
+ VALUE rb_src_x, VALUE rb_src_y, VALUE rb_src_w, VALUE rb_src_h,
504
+ VALUE rb_dst_x, VALUE rb_dst_y, VALUE rb_dst_w, VALUE rb_dst_h)
505
+ {
506
+ rszr_image_handle * handle;
507
+ rszr_image_handle * other_handle;
508
+ Imlib_Operation operation;
509
+
510
+ Check_Type(rb_mode, T_FIXNUM);
511
+ Check_Type(rb_src_x, T_FIXNUM);
512
+ Check_Type(rb_src_y, T_FIXNUM);
513
+ Check_Type(rb_src_w, T_FIXNUM);
514
+ Check_Type(rb_src_h, T_FIXNUM);
515
+ Check_Type(rb_dst_x, T_FIXNUM);
516
+ Check_Type(rb_dst_y, T_FIXNUM);
517
+ Check_Type(rb_dst_w, T_FIXNUM);
518
+ Check_Type(rb_dst_h, T_FIXNUM);
519
+
520
+ operation = (Imlib_Operation) NUM2INT(rb_mode);
521
+ int src_x = NUM2INT(rb_src_x);
522
+ int src_y = NUM2INT(rb_src_y);
523
+ int src_w = NUM2INT(rb_src_w);
524
+ int src_h = NUM2INT(rb_src_h);
525
+ int dst_x = NUM2INT(rb_dst_x);
526
+ int dst_y = NUM2INT(rb_dst_y);
527
+ int dst_w = NUM2INT(rb_dst_w);
528
+ int dst_h = NUM2INT(rb_dst_h);
529
+
530
+ char merge_alpha = RTEST(rb_merge_alpha) ? 1 : 0;
531
+
532
+ Data_Get_Struct(self, rszr_image_handle, handle);
533
+ Data_Get_Struct(other, rszr_image_handle, other_handle);
534
+
535
+ imlib_context_set_image(handle->image);
536
+ imlib_context_set_operation(operation);
537
+ imlib_blend_image_onto_image(other_handle->image, merge_alpha, src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h);
538
+
539
+ return self;
540
+ }
541
+
542
+
543
+ static Imlib_Color_Range rszr_image_init_color_range(VALUE rb_gradient)
544
+ {
545
+ Imlib_Color_Range range;
546
+ VALUE rb_points;
547
+ VALUE rb_point;
548
+ VALUE rb_color;
549
+ int size, i;
550
+ double position;
551
+ int red, green, blue, alpha;
552
+
553
+ if(!rb_obj_is_kind_of(rb_gradient, cColorGradient)) {
554
+ rb_raise(rb_eArgError, "color must be a Rszr::Color::Gradient");
555
+ }
556
+
557
+ rb_points = rb_funcall(rb_gradient, rb_intern("points"), 0);
558
+ Check_Type(rb_points, T_ARRAY);
559
+
560
+ imlib_context_get_color(&red, &green, &blue, &alpha);
561
+
562
+ range = imlib_create_color_range();
563
+ imlib_context_set_color_range(range);
564
+
565
+ size = RARRAY_LEN(rb_points);
566
+ for (i = 0; i < size; i++) {
567
+ rb_point = rb_ary_entry(rb_points, i);
568
+ if(!rb_obj_is_kind_of(rb_point, cColorPoint))
569
+ rb_raise(rb_eArgError, "point must be a Rszr::Color::Point");
570
+
571
+ rb_color = rb_funcall(rb_point, rb_intern("color"), 0);
572
+ if(!rb_obj_is_kind_of(rb_color, cColorBase) || RBASIC_CLASS(rb_color) == cColorBase)
573
+ rb_raise(rb_eArgError, "color must descend from Rszr::Color::Base");
574
+
575
+ position = NUM2DBL(rb_funcall(rb_point, rb_intern("position"), 0));
576
+
577
+ rszr_image_color_set(rb_color);
578
+ imlib_add_color_to_color_range(position * 255);
579
+ }
580
+
581
+ imlib_context_set_color(red, green, blue, alpha);
582
+
583
+ return range;
584
+ }
585
+
586
+
587
+ static VALUE rszr_image__rectangle_bang(VALUE self, VALUE rb_fill, VALUE rb_x, VALUE rb_y, VALUE rb_w, VALUE rb_h)
588
+ {
589
+ rszr_image_handle * handle;
590
+ VALUE rb_gradient;
591
+ VALUE rb_color;
592
+ Imlib_Color_Range range;
593
+ double angle;
594
+
595
+ Check_Type(rb_x, T_FIXNUM);
596
+ Check_Type(rb_y, T_FIXNUM);
597
+ Check_Type(rb_w, T_FIXNUM);
598
+ Check_Type(rb_h, T_FIXNUM);
599
+
600
+ int x = NUM2INT(rb_x);
601
+ int y = NUM2INT(rb_y);
602
+ int w = NUM2INT(rb_w);
603
+ int h = NUM2INT(rb_h);
604
+
605
+ rb_gradient = rb_funcall(rb_fill, rb_intern("gradient"), 0);
606
+ rb_color = rb_funcall(rb_fill, rb_intern("color"), 0);
607
+
608
+ Data_Get_Struct(self, rszr_image_handle, handle);
609
+ imlib_context_set_image(handle->image);
610
+
611
+ if (!NIL_P(rb_gradient)) {
612
+ angle = NUM2DBL(rb_funcall(rb_fill, rb_intern("angle"), 0));
613
+ range = rszr_image_init_color_range(rb_gradient);
614
+ imlib_image_fill_color_range_rectangle(x, y, w, h, angle);
615
+ imlib_free_color_range();
616
+ } else if (!NIL_P(rb_color)) {
617
+ rszr_image_color_set(rb_color);
618
+ imlib_image_fill_rectangle(x, y, w, h);
619
+ }
620
+
621
+ return self;
622
+ }
623
+
624
+
625
+ static VALUE rszr_image__save(VALUE self, VALUE rb_path, VALUE rb_format, VALUE rb_quality, VALUE rb_interlace)
449
626
  {
450
627
  rszr_image_handle * handle;
451
628
  char * path;
@@ -461,8 +638,14 @@ static VALUE rszr_image__save(VALUE self, VALUE rb_path, VALUE rb_format, VALUE
461
638
 
462
639
  imlib_context_set_image(handle->image);
463
640
  imlib_image_set_format(format);
641
+
464
642
  if (quality)
465
643
  imlib_image_attach_data_value("quality", NULL, quality, NULL);
644
+
645
+ imlib_image_remove_attached_data_value("interlacing");
646
+ if (RTEST(rb_interlace))
647
+ imlib_image_attach_data_value("interlacing", NULL, 1, NULL);
648
+
466
649
  imlib_save_image_with_error_return(path, &save_error);
467
650
 
468
651
  if (save_error) {
@@ -476,13 +659,18 @@ static VALUE rszr_image__save(VALUE self, VALUE rb_path, VALUE rb_format, VALUE
476
659
  void Init_rszr_image()
477
660
  {
478
661
  cImage = rb_define_class_under(mRszr, "Image", rb_cObject);
662
+
663
+ cColorBase = rb_path2class("Rszr::Color::Base");
664
+ cColorGradient = rb_path2class("Rszr::Color::Gradient");
665
+ cColorPoint = rb_path2class("Rszr::Color::Point");
666
+ cFill = rb_path2class("Rszr::Fill");
667
+
479
668
  rb_define_alloc_func(cImage, rszr_image_s_allocate);
480
669
 
481
670
  // Class methods
482
671
  rb_define_private_method(rb_singleton_class(cImage), "_load", rszr_image_s__load, 1);
483
672
 
484
673
  // Instance methods
485
- rb_define_method(cImage, "initialize", rszr_image_initialize, 2);
486
674
  rb_define_method(cImage, "width", rszr_image_width, 0);
487
675
  rb_define_method(cImage, "height", rszr_image_height, 0);
488
676
  rb_define_method(cImage, "dup", rszr_image_dup, 0);
@@ -490,20 +678,23 @@ void Init_rszr_image()
490
678
  rb_define_method(cImage, "flop!", rszr_image_flop_bang, 0);
491
679
  rb_define_method(cImage, "flip!", rszr_image_flip_bang, 0);
492
680
 
493
- // rb_define_method(cImage, "quality", rszr_image_get_quality, 0);
494
- // rb_define_method(cImage, "quality=", rszr_image_set_quality, 1);
681
+ rb_define_method(cImage, "alpha", rszr_image_alpha_get, 0);
682
+ rb_define_method(cImage, "alpha=", rszr_image_alpha_set, 1);
495
683
 
496
684
  rb_define_protected_method(cImage, "_format", rszr_image__format_get, 0);
497
685
  rb_define_protected_method(cImage, "_format=", rszr_image__format_set, 1);
498
-
499
- rb_define_private_method(cImage, "_resize", rszr_image__resize, 7);
500
- rb_define_private_method(cImage, "_crop", rszr_image__crop, 5);
501
- rb_define_private_method(cImage, "_turn!", rszr_image__turn_bang, 1);
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);
686
+
687
+ rb_define_private_method(cImage, "_initialize", rszr_image__initialize, 2);
688
+ rb_define_private_method(cImage, "_resize", rszr_image__resize, 7);
689
+ rb_define_private_method(cImage, "_crop", rszr_image__crop, 5);
690
+ rb_define_private_method(cImage, "_turn!", rszr_image__turn_bang, 1);
691
+ rb_define_private_method(cImage, "_rotate", rszr_image__rotate, 2);
692
+ rb_define_private_method(cImage, "_sharpen!", rszr_image__sharpen_bang, 1);
693
+ rb_define_private_method(cImage, "_pixel", rszr_image__pixel_get, 2);
694
+ rb_define_private_method(cImage, "_blend", rszr_image__blend, 11);
695
+ rb_define_private_method(cImage, "_rectangle!", rszr_image__rectangle_bang, 5);
696
+
697
+ rb_define_private_method(cImage, "_save", rszr_image__save, 4);
507
698
  }
508
699
 
509
700
  #endif
@@ -0,0 +1,25 @@
1
+ module Rszr
2
+ module Color
3
+
4
+ class Base
5
+ attr_reader :alpha
6
+
7
+ def rgba
8
+ [red, green, blue, alpha]
9
+ end
10
+
11
+ def cmya
12
+ [cyan, magenta, yellow, alpha]
13
+ end
14
+
15
+ def ==(other)
16
+ other.is_a?(Base) && rgba == other.rgba
17
+ end
18
+
19
+ def to_fill(*)
20
+ Fill.new(color: self)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module Rszr
2
+ module Color
3
+
4
+ class CMYA < Base
5
+ attr_reader :cyan, :magenta, :yellow
6
+
7
+ def initialize(cyan, magenta, yellow, alpha = 255)
8
+ if cyan < 0 || cyan > 255 || magenta < 0 || magenta > 255 || yellow < 0 || yellow > 255 || alpha < 0 || alpha > 255
9
+ raise ArgumentError, 'color out of range'
10
+ end
11
+ @cyan, @magenta, @yellow = cyan, magenta, yellow
12
+ end
13
+
14
+ def red
15
+ 255 - cyan
16
+ end
17
+
18
+ def green
19
+ 255 - magenta
20
+ end
21
+
22
+ def blue
23
+ 255 - yellow
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ module Rszr
2
+ module Color
3
+
4
+ class Gradient
5
+ attr_reader :points
6
+
7
+ def initialize(*args)
8
+ @points = []
9
+ points = args.last.is_a?(Hash) ? args.pop.dup : {}
10
+ args.each { |point| self << point }
11
+ points.each { |pos, color| point(pos, color) }
12
+ yield self if block_given?
13
+ end
14
+
15
+ def initialize_dup(other) # :nodoc:
16
+ @points = other.points.map(&:dup)
17
+ end
18
+
19
+ def <<(position, red = nil, green = nil, blue= nil, alpha = 255)
20
+ point = if red.is_a?(Point)
21
+ red
22
+ elsif red.is_a?(Color::Base)
23
+ Point.new(position, red)
24
+ elsif red.is_a?(String) && red.start_with?('#')
25
+ Point.new(position, Color.hex(red))
26
+ else
27
+ Point.new(position, RGBA.new(red, green, blue, alpha))
28
+ end
29
+ points << point
30
+ points.sort!
31
+ end
32
+
33
+ alias_method :point, :<<
34
+
35
+ def to_fill(angle = 0)
36
+ Fill.new(gradient: self, angle: angle)
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ module Rszr
2
+ module Color
3
+
4
+ class Point
5
+ attr_reader :position, :color
6
+
7
+ class << self
8
+ def prgba(position, red, green, blue, alpha = 255)
9
+ new(position, RGBA.new(red, green, blue, alpha))
10
+ end
11
+ end
12
+
13
+ def initialize(position, color)
14
+ raise ArgumentError, 'position must be within 0..1' unless (0..1).cover?(position)
15
+ raise ArgumentError, 'color must be a Rszr::Color::Base' unless color.is_a?(Rszr::Color::Base)
16
+ @position, @color = position, color
17
+ end
18
+
19
+ def <=>(other)
20
+ position <=> other.position
21
+ end
22
+
23
+ def prgba
24
+ [position, *color.rgba]
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,56 @@
1
+ module Rszr
2
+ module Color
3
+
4
+ class << self
5
+ def rgba(red, green, blue, alpha = 255)
6
+ RGBA.new(red, green, blue, alpha)
7
+ end
8
+
9
+ def hex(str)
10
+ str = str[1..-1] if str.start_with?('#')
11
+ case str.size
12
+ when 3, 4 then hex(str.chars.map { |c| c * 2 }.join)
13
+ when 6 then hex("#{str}ff")
14
+ when 8
15
+ rgba(*str.scan(/../).map(&:hex))
16
+ else
17
+ raise ArgumentError, 'invalid color code'
18
+ end
19
+ end
20
+ end
21
+
22
+ class RGBA < Base
23
+ attr_reader :red, :green, :blue
24
+
25
+ def initialize(red, green, blue, alpha = 255)
26
+ if red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255 || alpha < 0 || alpha > 255
27
+ raise ArgumentError, 'color out of range'
28
+ end
29
+ @red, @green, @blue, @alpha = red, green, blue, alpha
30
+ end
31
+
32
+ def cyan
33
+ 255 - red
34
+ end
35
+
36
+ def magenta
37
+ 255 - green
38
+ end
39
+
40
+ def yellow
41
+ 255 - blue
42
+ end
43
+
44
+ def to_i(alpha: true)
45
+ i = red.to_i << 24 | green.to_i << 16 | blue.to_i << 8 | self.alpha.to_i
46
+ alpha ? i : i >> 8
47
+ end
48
+
49
+ def to_hex(alpha: true)
50
+ "#%0#{alpha ? 8 : 6}x" % to_i(alpha: alpha)
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+ end
data/lib/rszr/color.rb CHANGED
@@ -1,25 +1,15 @@
1
+ require_relative 'color/base'
2
+ require_relative 'color/rgba'
3
+ require_relative 'color/cmya'
4
+ require_relative 'color/point'
5
+ require_relative 'color/gradient'
6
+
1
7
  module Rszr
2
8
  module Color
3
9
 
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
10
+ Transparent = RGBA.new(0, 0, 0, 0)
11
+ White = RGBA.new(255,255,255)
12
+ Black = RGBA.new(0, 0, 0)
23
13
 
24
14
  end
25
15
  end
data/lib/rszr/fill.rb ADDED
@@ -0,0 +1,21 @@
1
+ module Rszr
2
+ class Fill
3
+ attr_reader :color, :gradient, :angle
4
+
5
+ def initialize(color: nil, gradient: nil, angle: 0)
6
+ if gradient
7
+ @gradient = gradient
8
+ @angle = angle || 0
9
+ elsif color
10
+ @color = color
11
+ else
12
+ raise ArgumentError, 'incomplete fill definition'
13
+ end
14
+ end
15
+
16
+ def to_fill(*)
17
+ self
18
+ end
19
+
20
+ end
21
+ end
data/lib/rszr/image.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module Rszr
2
2
  class Image
3
3
  GRAVITIES = [true, :center, :n, :nw, :w, :sw, :s, :se, :e, :ne].freeze
4
+ BLENDING_MODES = %i[copy add subtract reshade].freeze
4
5
 
5
6
  extend Identification
6
7
  include Buffered
@@ -40,6 +41,8 @@ module Rszr
40
41
  self._format = fmt
41
42
  end
42
43
 
44
+ alias_method :alpha?, :alpha
45
+
43
46
  def [](x, y)
44
47
  if x >= 0 && x <= width - 1 && y >= 0 && y <= height - 1
45
48
  Color::RGBA.new(*_pixel(x, y))
@@ -49,7 +52,7 @@ module Rszr
49
52
  def inspect
50
53
  fmt = format
51
54
  fmt = " #{fmt.upcase}" if fmt
52
- "#<#{self.class.name}:0x#{object_id.to_s(16)} #{width}x#{height}#{fmt}>"
55
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} #{width}x#{height}x#{alpha? ? 32 : 24}#{fmt}>"
53
56
  end
54
57
 
55
58
  module Transformations
@@ -144,15 +147,51 @@ module Rszr
144
147
  def gamma(*args, **opts)
145
148
  dup.gamma!(*args, **opts)
146
149
  end
150
+
151
+ def blend!(image, x, y, mode: :copy)
152
+ raise ArgumentError, "mode must be one of #{BLENDING_MODES.map(&:to_s).join(', ')}" unless BLENDING_MODES.include?(mode)
153
+ _blend(image, true, BLENDING_MODES.index(mode), 0, 0, image.width, image.height, x, y, image.width, image.height)
154
+ end
155
+
156
+ def blend(*args, **opts)
157
+ dup.blend!(*args, **opts)
158
+ end
159
+
160
+ def rectangle!(coloring, x, y, w, h)
161
+ raise ArgumentError, "coloring must respond to to_fill" unless coloring.respond_to?(:to_fill)
162
+ _rectangle!(coloring.to_fill, x, y, w, h)
163
+ end
164
+
165
+ def rectangle(*args, **opts)
166
+ dup.rectangle!(*args, **opts)
167
+ end
168
+
169
+ def fill!(coloring)
170
+ raise ArgumentError, "coloring must respond to to_fill" unless coloring.respond_to?(:to_fill)
171
+ rectangle!(coloring, 0, 0, width, height)
172
+ end
173
+
174
+ def fill(*args, **opts)
175
+ dup.fill(*args, **opts)
176
+ end
147
177
  end
148
178
 
149
179
  include Transformations
150
180
 
151
- def save(path, format: nil, quality: nil)
181
+ def initialize(width, height, alpha: false, background: nil)
182
+ raise ArgumentError, 'illegal image dimensions' if width < 1 || width > 32766 || height < 1 || height > 32766
183
+ raise ArgumentError, 'background must respond to to_fill' if background && !(background.respond_to?(:to_fill))
184
+ _initialize(width, height).tap do |image|
185
+ image.alpha = alpha
186
+ image.fill!(background) if background
187
+ end
188
+ end
189
+
190
+ def save(path, format: nil, quality: nil, interlace: false)
152
191
  format ||= format_from_filename(path) || self.format || 'jpg'
153
192
  raise ArgumentError, "invalid quality #{quality.inspect}" if quality && !(0..100).cover?(quality)
154
193
  ensure_path_is_writable(path)
155
- _save(path.to_s, format.to_s, quality)
194
+ _save(path.to_s, format.to_s, quality, interlace)
156
195
  end
157
196
 
158
197
  def save_data(format: nil, quality: nil)
@@ -271,7 +310,9 @@ module Rszr
271
310
  end
272
311
 
273
312
  def format_from_filename(path)
274
- File.extname(path)[1..-1].to_s.downcase
313
+ if extension = File.extname(path)[1..-1]
314
+ extension.downcase
315
+ end
275
316
  end
276
317
 
277
318
  def ensure_path_is_writable(path)
data/lib/rszr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rszr
2
- VERSION = '1.1.0'
2
+ VERSION = '1.3.0'
3
3
  end
data/lib/rszr.rb CHANGED
@@ -3,13 +3,14 @@ require 'pathname'
3
3
  require 'tempfile'
4
4
  require 'stringio'
5
5
 
6
- require 'rszr/rszr'
7
6
  require 'rszr/version'
8
7
  require 'rszr/stream'
9
8
  require 'rszr/identification'
10
9
  require 'rszr/orientation'
11
10
  require 'rszr/buffered'
12
11
  require 'rszr/color'
12
+ require 'rszr/fill'
13
+ require 'rszr/rszr'
13
14
  require 'rszr/image'
14
15
 
15
16
  module Rszr
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rszr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.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: 2022-02-09 00:00:00.000000000 Z
11
+ date: 2022-08-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Fast image resizer - do one thing and do it fast.
14
14
  email:
@@ -32,6 +32,12 @@ files:
32
32
  - lib/rszr/batch_transformation.rb
33
33
  - lib/rszr/buffered.rb
34
34
  - lib/rszr/color.rb
35
+ - lib/rszr/color/base.rb
36
+ - lib/rszr/color/cmya.rb
37
+ - lib/rszr/color/gradient.rb
38
+ - lib/rszr/color/point.rb
39
+ - lib/rszr/color/rgba.rb
40
+ - lib/rszr/fill.rb
35
41
  - lib/rszr/identification.rb
36
42
  - lib/rszr/image.rb
37
43
  - lib/rszr/image_processing.rb