rszr 1.2.1 → 1.3.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: 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