rszr 1.2.1 → 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: 3ce2686ea62d3e1a96bbf452e4c8d1198ea083bc9af870ca6e9d76c021eb732c
4
- data.tar.gz: '009b9bf5d73730b24c64a74611e4f06285a0cbf76e6bd0c769ea69f9df8c2582'
3
+ metadata.gz: ce6596f9a3f8e4d355c5d974da7e918dd574517b1ab87aaf6f66e35c8c26c600
4
+ data.tar.gz: 21905d628d567cbc06de0b0db56dc1515c5da7f533acc3917aedb8d91962b9cc
5
5
  SHA512:
6
- metadata.gz: 7d6354e6a43b7cb64529b9b98ea3bc451529a875985ccb3171c7f97eda57f90e536e67997b7cee1c4d4379e1968334621fcb122fecdbd4f57dfc64989a5f34f4
7
- data.tar.gz: 210253dd07258eaf5a1f43cd872b60481844a87a7fcd9e2a352351756560542892cb4769eaa7339f97c1cdc8d38eea4af8b9167779c7b5d4bae165300efd8556
6
+ metadata.gz: 262f3cda5bc4ff4c6a86a5e7bb2ab4de2414f2652648d09e0d8d2a327c62e37eaf83d091fff515853f2d853754d6d5dd56c8a97e51a851c46c427bd69207213d
7
+ data.tar.gz: 1bd9c512ccbd0b95cb665d8a439c2ffa24456671d8165c555ad190058a16bad70913dfada98fbe6a4280a843ccfd83db02ea203746764ef8050586abcfd414fa
data/README.md CHANGED
@@ -63,9 +63,10 @@ image.width => 400
63
63
  image.height => 300
64
64
  image.dimensions => [400, 300]
65
65
  image.format => "jpeg"
66
+ image.alpha? => false
66
67
  image[0, 0] => <Rszr::Color::RGBA @red=38, @green=115, @blue=141, @alpha=255>
67
- image[0, 0].to_hex => "26738dff"
68
- image[0, 0].to_hex(rgb: true) => "26738d"
68
+ image[0, 0].to_hex => "#26738dff"
69
+ image[0, 0].to_hex(alpha: false) => "#26738d"
69
70
  ```
70
71
 
71
72
  ### Transformations
@@ -124,6 +125,97 @@ image.flop
124
125
  image.dup
125
126
  ```
126
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
+
127
219
  ### Filters
128
220
 
129
221
  Filters also support bang! and non-bang methods.
@@ -173,8 +265,7 @@ In order to save interlaced PNGs and progressive JPEGs, set the `interlace` opti
173
265
  image.save('interlaced.png', interlace: true)
174
266
  ```
175
267
 
176
- As of v1.8.0, `imlib2` doesn't support saving progressive JPEG images yet,
177
- but a [patch](https://git.enlightenment.org/legacy/imlib2.git/commit/?id=37e8c9578897259211284d3590cc38b7f6a718dc) has been submitted.
268
+ Saving progressive JPEG images requires `imlib2` >= 1.8.1.
178
269
 
179
270
  For EL8, there are pre-built RPMs provided by the [onrooby repo](http://downloads.onrooby.com/repo/el/8/x86_64/).
180
271
 
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;
@@ -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,6 +499,129 @@ static VALUE rszr_image__crop(VALUE self, VALUE bang, VALUE rb_x, VALUE rb_y, VA
445
499
  }
446
500
 
447
501
 
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
+
448
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;
@@ -461,6 +638,7 @@ 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);
466
644
 
@@ -481,13 +659,18 @@ static VALUE rszr_image__save(VALUE self, VALUE rb_path, VALUE rb_format, VALUE
481
659
  void Init_rszr_image()
482
660
  {
483
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
+
484
668
  rb_define_alloc_func(cImage, rszr_image_s_allocate);
485
669
 
486
670
  // Class methods
487
671
  rb_define_private_method(rb_singleton_class(cImage), "_load", rszr_image_s__load, 1);
488
672
 
489
673
  // Instance methods
490
- rb_define_method(cImage, "initialize", rszr_image_initialize, 2);
491
674
  rb_define_method(cImage, "width", rszr_image_width, 0);
492
675
  rb_define_method(cImage, "height", rszr_image_height, 0);
493
676
  rb_define_method(cImage, "dup", rszr_image_dup, 0);
@@ -495,19 +678,22 @@ void Init_rszr_image()
495
678
  rb_define_method(cImage, "flop!", rszr_image_flop_bang, 0);
496
679
  rb_define_method(cImage, "flip!", rszr_image_flip_bang, 0);
497
680
 
498
- // rb_define_method(cImage, "quality", rszr_image_get_quality, 0);
499
- // 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);
500
683
 
501
684
  rb_define_protected_method(cImage, "_format", rszr_image__format_get, 0);
502
685
  rb_define_protected_method(cImage, "_format=", rszr_image__format_set, 1);
503
-
504
- rb_define_private_method(cImage, "_resize", rszr_image__resize, 7);
505
- rb_define_private_method(cImage, "_crop", rszr_image__crop, 5);
506
- rb_define_private_method(cImage, "_turn!", rszr_image__turn_bang, 1);
507
- rb_define_private_method(cImage, "_rotate", rszr_image__rotate, 2);
508
- rb_define_private_method(cImage, "_sharpen!", rszr_image__sharpen_bang, 1);
509
- rb_define_private_method(cImage, "_pixel", rszr_image__pixel_get, 2);
510
-
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
+
511
697
  rb_define_private_method(cImage, "_save", rszr_image__save, 4);
512
698
  }
513
699
 
@@ -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,10 +147,46 @@ 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
 
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
+
151
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)
data/lib/rszr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rszr
2
- VERSION = '1.2.1'
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.2.1
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-03-22 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