rmagick 1.13.0 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rmagick might be problematic. Click here for more details.

Files changed (48) hide show
  1. data/ChangeLog +34 -0
  2. data/README.html +12 -29
  3. data/README.txt +10 -26
  4. data/configure +768 -73
  5. data/configure.ac +29 -26
  6. data/doc/comtasks.html +3 -4
  7. data/doc/constants.html +85 -67
  8. data/doc/draw.html +22 -0
  9. data/doc/ex/dissolve.rb +13 -0
  10. data/doc/ex/edge.rb +1 -1
  11. data/doc/ex/mask.rb +37 -0
  12. data/doc/ex/sketch.rb +25 -0
  13. data/doc/ex/watermark.rb +23 -0
  14. data/doc/ilist.html +11 -13
  15. data/doc/image1.html +601 -52
  16. data/doc/image2.html +637 -28
  17. data/doc/image3.html +339 -54
  18. data/doc/imageattrs.html +211 -41
  19. data/doc/imusage.html +41 -2
  20. data/doc/index.html +8 -6
  21. data/doc/info.html +57 -42
  22. data/doc/optequiv.html +1919 -0
  23. data/doc/rvg.html +45 -42
  24. data/doc/scripts/doc.js +14 -1
  25. data/doc/scripts/stripeTables.js +23 -0
  26. data/doc/usage.html +66 -15
  27. data/{doc/ex → examples}/demo.rb +0 -0
  28. data/examples/find_similar_region.rb +34 -0
  29. data/examples/import_export.rb +1 -1
  30. data/examples/pattern_fill.rb +2 -2
  31. data/examples/rotating_text.rb +2 -4
  32. data/examples/thumbnail.rb +1 -1
  33. data/ext/RMagick/MANIFEST +9 -4
  34. data/ext/RMagick/extconf.rb.in +1 -1
  35. data/ext/RMagick/rmagick.h +47 -10
  36. data/ext/RMagick/rmagick_config.h.in +24 -0
  37. data/ext/RMagick/rmdraw.c +32 -7
  38. data/ext/RMagick/rmilist.c +55 -37
  39. data/ext/RMagick/rmimage.c +1588 -447
  40. data/ext/RMagick/rminfo.c +94 -3
  41. data/ext/RMagick/rmmain.c +68 -7
  42. data/ext/RMagick/rmutil.c +67 -9
  43. data/lib/RMagick.rb +190 -53
  44. data/lib/rvg/stretchable.rb +17 -13
  45. data/rmagick.gemspec +1 -1
  46. metadata +11 -6
  47. data/doc/ex/level_channel.rb +0 -33
  48. data/doc/ex/opaque.rb +0 -14
@@ -1,4 +1,4 @@
1
- /* $Id: rmimage.c,v 1.153 2006/06/28 23:07:16 rmagick Exp $ */
1
+ /* $Id: rmimage.c,v 1.183 2006/09/27 21:26:35 rmagick Exp $ */
2
2
  /*============================================================================\
3
3
  | Copyright (C) 2006 by Timothy P. Hunter
4
4
  | Name: rmimage.c
@@ -28,27 +28,30 @@ static VALUE cropper(int, int, VALUE *, VALUE);
28
28
  static VALUE effect_image(VALUE, int, VALUE *, effector_t *);
29
29
  static VALUE flipflop(int, VALUE, flipper_t);
30
30
  static VALUE rd_image(VALUE, VALUE, reader_t);
31
- static VALUE rotate(int, VALUE, VALUE);
31
+ static VALUE rotate(int, int, VALUE *, VALUE);
32
32
  static VALUE scale(int, int, VALUE *, VALUE, scaler_t *);
33
33
  static VALUE threshold_image(int, VALUE *, VALUE, thresholder_t);
34
34
  static VALUE xform_image(int, VALUE, VALUE, VALUE, VALUE, VALUE, xformer_t);
35
35
  static VALUE array_from_images(Image *);
36
- static ChannelType extract_channels(int *, VALUE *);
37
- static void raise_ChannelType_error(VALUE);
38
36
 
39
37
  static ImageAttribute *Next_Attribute;
40
38
 
39
+ static const char *BlackPointCompensationKey = "PROFILE:black-point-compensation";
40
+
41
41
 
42
42
 
43
43
 
44
44
  /*
45
- Method: Image#adaptive_sharpen(radius=0.0, sigma=1.0)
46
- Purpose: call AdaptiveSharpenImage
45
+ Static: adaptive_method
46
+ Purpose: call Adaptive(Blur|Sharpen)Image
47
47
  */
48
- VALUE
49
- Image_adaptive_sharpen(int argc, VALUE *argv, VALUE self)
48
+ #if defined(HAVE_ADAPTIVEBLURIMAGECHANNEL) || defined(HAVE_ADAPTIVESHARPENIMAGE)
49
+ static VALUE adaptive_method(
50
+ int argc,
51
+ VALUE *argv,
52
+ VALUE self,
53
+ Image *fp(const Image *, const double, const double, ExceptionInfo *))
50
54
  {
51
- #if defined(HAVE_ADAPTIVESHARPENIMAGE)
52
55
  Image *image, *new_image;
53
56
  double radius = 0.0;
54
57
  double sigma = 1.0;
@@ -71,7 +74,7 @@ Image_adaptive_sharpen(int argc, VALUE *argv, VALUE self)
71
74
 
72
75
  GetExceptionInfo(&exception);
73
76
 
74
- new_image = AdaptiveSharpenImage(image, radius, sigma, &exception);
77
+ new_image = (fp)(image, radius, sigma, &exception);
75
78
  rm_check_exception(&exception, new_image, DestroyOnError);
76
79
 
77
80
  DestroyExceptionInfo(&exception);
@@ -79,24 +82,20 @@ Image_adaptive_sharpen(int argc, VALUE *argv, VALUE self)
79
82
  rm_ensure_result(new_image);
80
83
 
81
84
  return rm_image_new(new_image);
82
-
83
- #else
84
-
85
- rm_not_implemented();
86
- return (VALUE)0;
87
- #endif
88
85
  }
89
86
 
90
87
 
88
+
91
89
  /*
92
- Method: Image#adaptive_sharpen_channel(radius=0.0, sigma=1.0[, channel...])
93
- Purpose: Call AdaptiveSharpenImageChannel
90
+ Static: adaptive_channel_method
91
+ Purpose: call Adaptive(Blur|Sharpen)ImageChannel
94
92
  */
95
- VALUE
96
- Image_adaptive_sharpen_channel(int argc, VALUE *argv, VALUE self)
93
+ static VALUE adaptive_channel_method(
94
+ int argc,
95
+ VALUE *argv,
96
+ VALUE self,
97
+ Image *fp(const Image *, const ChannelType, const double, const double, ExceptionInfo *))
97
98
  {
98
- #if defined(HAVE_ADAPTIVESHARPENIMAGE)
99
-
100
99
  Image *image, *new_image;
101
100
  double radius = 0.0;
102
101
  double sigma = 1.0;
@@ -122,7 +121,7 @@ Image_adaptive_sharpen_channel(int argc, VALUE *argv, VALUE self)
122
121
 
123
122
  GetExceptionInfo(&exception);
124
123
 
125
- new_image = AdaptiveSharpenImageChannel(image, channels, radius, sigma, &exception);
124
+ new_image = (fp)(image, channels, radius, sigma, &exception);
126
125
  rm_check_exception(&exception, new_image, DestroyOnError);
127
126
 
128
127
  DestroyExceptionInfo(&exception);
@@ -130,6 +129,130 @@ Image_adaptive_sharpen_channel(int argc, VALUE *argv, VALUE self)
130
129
  rm_ensure_result(new_image);
131
130
 
132
131
  return rm_image_new(new_image);
132
+ }
133
+ #endif
134
+
135
+
136
+ /*
137
+ Method: Image#adaptive_blur(radius=0.0, sigma=1.0)
138
+ Purpose: call AdaptiveBlurImage
139
+ */
140
+ VALUE
141
+ Image_adaptive_blur(int argc, VALUE *argv, VALUE self)
142
+ {
143
+ #if defined(HAVE_ADAPTIVEBLURIMAGECHANNEL)
144
+ return adaptive_method(argc, argv, self, AdaptiveBlurImage);
145
+ #else
146
+ rm_not_implemented();
147
+ return (VALUE)0;
148
+ #endif
149
+ }
150
+
151
+
152
+
153
+ /*
154
+ Method: Image#adaptive_blur_channel(radius=0.0, sigma=1.0[ , channel...])
155
+ Purpose: call AdaptiveBlurImageChannel
156
+ */
157
+ VALUE
158
+ Image_adaptive_blur_channel(int argc, VALUE *argv, VALUE self)
159
+ {
160
+ #if defined(HAVE_ADAPTIVEBLURIMAGECHANNEL)
161
+ return adaptive_channel_method(argc, argv, self, AdaptiveBlurImageChannel);
162
+ #else
163
+ rm_not_implemented();
164
+ return (VALUE)0;
165
+ #endif
166
+ }
167
+
168
+
169
+ /*
170
+ Method: Image#adaptive_resize(scale)
171
+ Image#adaptive_resize(cols, rows)
172
+ Purpose: Call AdaptiveResizeImage
173
+ */
174
+ VALUE
175
+ Image_adaptive_resize(int argc, VALUE *argv, VALUE self)
176
+ {
177
+ #if defined(HAVE_ADAPTIVERESIZEIMAGE)
178
+
179
+ Image *image, *new_image;
180
+ unsigned long rows, columns;
181
+ double scale, drows, dcols;
182
+ ExceptionInfo exception;
183
+
184
+ Data_Get_Struct(self, Image, image);
185
+
186
+ switch (argc)
187
+ {
188
+ case 2:
189
+ rows = NUM2ULONG(argv[1]);
190
+ columns = NUM2ULONG(argv[0]);
191
+ break;
192
+ case 1:
193
+ scale = NUM2DBL(argv[0]);
194
+ if (scale < 0.0)
195
+ {
196
+ rb_raise(rb_eArgError, "invalid scale value (%g given)", scale);
197
+ }
198
+ drows = scale * image->rows + 0.5;
199
+ dcols = scale * image->columns + 0.5;
200
+ if (drows > ULONG_MAX || dcols > ULONG_MAX)
201
+ {
202
+ rb_raise(rb_eRangeError, "resized image too big");
203
+ }
204
+ rows = (unsigned long) drows;
205
+ columns = (unsigned long) dcols;
206
+ break;
207
+ default:
208
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc);
209
+ break;
210
+ }
211
+
212
+ GetExceptionInfo(&exception);
213
+ new_image = AdaptiveResizeImage(image, columns, rows, &exception);
214
+ rm_check_exception(&exception, new_image, DestroyOnError);
215
+
216
+ DestroyExceptionInfo(&exception);
217
+ rm_ensure_result(new_image);
218
+
219
+ return rm_image_new(new_image);
220
+
221
+ #else
222
+ rm_not_implemented();
223
+ return (VALUE)0;
224
+ #endif
225
+ }
226
+
227
+
228
+
229
+ /*
230
+ Method: Image#adaptive_sharpen(radius=0.0, sigma=1.0)
231
+ Purpose: call AdaptiveSharpenImage
232
+ */
233
+ VALUE
234
+ Image_adaptive_sharpen(int argc, VALUE *argv, VALUE self)
235
+ {
236
+ #if defined(HAVE_ADAPTIVESHARPENIMAGE)
237
+ return adaptive_method(argc, argv, self, AdaptiveSharpenImage);
238
+ #else
239
+
240
+ rm_not_implemented();
241
+ return (VALUE)0;
242
+ #endif
243
+ }
244
+
245
+
246
+
247
+ /*
248
+ Method: Image#adaptive_sharpen_channel(radius=0.0, sigma=1.0[, channel...])
249
+ Purpose: Call AdaptiveSharpenImageChannel
250
+ */
251
+ VALUE
252
+ Image_adaptive_sharpen_channel(int argc, VALUE *argv, VALUE self)
253
+ {
254
+ #if defined(HAVE_ADAPTIVESHARPENIMAGE)
255
+ return adaptive_channel_method(argc, argv, self, AdaptiveSharpenImageChannel);
133
256
  #else
134
257
 
135
258
  rm_not_implemented();
@@ -255,6 +378,137 @@ Image_add_noise_channel(int argc, VALUE *argv, VALUE self)
255
378
  #endif
256
379
  }
257
380
 
381
+
382
+ /*
383
+ Method: Image#add_profile(name)
384
+ Purpose: adds all the profiles in the specified file
385
+ Notes: `name' is the profile filename
386
+ */
387
+ VALUE
388
+ Image_add_profile(VALUE self, VALUE name)
389
+ {
390
+ #if defined(HAVE_GETNEXTIMAGEPROFILE)
391
+ // ImageMagick code based on the code for the "-profile" option in mogrify.c
392
+ Image *image, *profile_image;
393
+ ImageInfo *info;
394
+ ExceptionInfo exception;
395
+ char *profile_name;
396
+ char *profile_filename = NULL;
397
+ long profile_filename_l = 0;
398
+ const StringInfo *profile;
399
+
400
+ rm_check_frozen(self);
401
+ Data_Get_Struct(self, Image, image);
402
+
403
+ // ProfileImage issues a warning if something goes wrong.
404
+ profile_filename = STRING_PTR_LEN(name, profile_filename_l);
405
+
406
+ info = CloneImageInfo(NULL);
407
+ info->client_data= (void *)GetImageProfile(image,"8bim");
408
+
409
+ strncpy(info->filename, profile_filename, min(profile_filename_l, sizeof(info->filename)));
410
+ info->filename[MaxTextExtent-1] = '\0';
411
+
412
+ GetExceptionInfo(&exception);
413
+ profile_image = ReadImage(info, &exception);
414
+ DestroyImageInfo(info);
415
+ rm_check_exception(&exception, profile_image, DestroyOnError);
416
+ DestroyExceptionInfo(&exception);
417
+ rm_ensure_result(profile_image);
418
+
419
+ ResetImageProfileIterator(profile_image);
420
+ profile_name = GetNextImageProfile(profile_image);
421
+ while (profile_name)
422
+ {
423
+ profile = GetImageProfile(profile_image, profile_name);
424
+ if (profile)
425
+ {
426
+ (void)ProfileImage(image, profile_name, profile->datum, (unsigned long)profile->length, False);
427
+ if (image->exception.severity >= ErrorException)
428
+ {
429
+ break;
430
+ }
431
+ }
432
+ profile_name = GetNextImageProfile(profile_image);
433
+ }
434
+
435
+ DestroyImage(profile_image);
436
+ rm_check_image_exception(image, RetainOnError);
437
+
438
+ #else
439
+
440
+ // GraphicsMagick code based on the code for the "-profile" option in command.c
441
+ Image *image, *profile_image;
442
+ ImageInfo *info;
443
+ ExceptionInfo exception;
444
+ char *profile_filename = NULL;
445
+ long profile_filename_l = 0;
446
+ ProfileInfo *generic;
447
+ const unsigned char *profile;
448
+ size_t profile_l;
449
+ long x;
450
+
451
+ rm_check_frozen(self);
452
+ Data_Get_Struct(self, Image, image);
453
+
454
+ // ProfileImage issues a warning if something goes wrong.
455
+ profile_filename = STRING_PTR_LEN(name, profile_filename_l);
456
+
457
+ info = CloneImageInfo(NULL);
458
+ info->client_data= (void *) &image->iptc_profile;
459
+ strncpy(info->filename, profile_filename, min(profile_filename_l, sizeof(info->filename)));
460
+ info->filename[MaxTextExtent-1] = '\0';
461
+
462
+ GetExceptionInfo(&exception);
463
+ profile_image = ReadImage(info, &exception);
464
+ DestroyImageInfo(info);
465
+ rm_check_exception(&exception, profile_image, DestroyOnError);
466
+ DestroyExceptionInfo(&exception);
467
+ rm_ensure_result(profile_image);
468
+
469
+ /* IPTC NewsPhoto Profile */
470
+ profile = GetImageProfile(profile_image, "IPTC", &profile_l);
471
+ if (profile)
472
+ {
473
+ (void)SetImageProfile(image, "IPTC", profile, profile_l);
474
+ if (image->exception.severity >= ErrorException)
475
+ {
476
+ goto done;
477
+ }
478
+ }
479
+
480
+ /* ICC ICM Profile */
481
+ profile = GetImageProfile(profile_image, "ICM", &profile_l);
482
+ if (profile)
483
+ {
484
+ (void)SetImageProfile(image, "ICM", profile, profile_l);
485
+ if (image->exception.severity >= ErrorException)
486
+ {
487
+ goto done;
488
+ }
489
+ }
490
+
491
+ /* Generic Profiles */
492
+ for (x = 0; x < (long)profile_image->generic_profiles; x++)
493
+ {
494
+ generic = profile_image->generic_profile + x;
495
+ (void)SetImageProfile(image, generic->name, generic->info, generic->length);
496
+ if (image->exception.severity >= ErrorException)
497
+ {
498
+ break;
499
+ }
500
+ }
501
+
502
+ done:
503
+ DestroyImage(profile_image);
504
+ rm_check_image_exception(image, RetainOnError);
505
+
506
+ #endif
507
+
508
+ return self;
509
+ }
510
+
511
+
258
512
  /*
259
513
  Method: Image#affine_transform(affine_matrix)
260
514
  Purpose: transforms an image as dictated by the affine matrix argument
@@ -409,7 +663,7 @@ static VALUE
409
663
  crisscross(
410
664
  int bang,
411
665
  VALUE self,
412
- Image *(fp)(const Image *, ExceptionInfo *))
666
+ Image *fp(const Image *, ExceptionInfo *))
413
667
  {
414
668
  Image *image, *new_image;
415
669
  ExceptionInfo exception;
@@ -449,6 +703,7 @@ static VALUE auto_orient(int bang, VALUE self)
449
703
  #if defined(HAVE_TRANSPOSEIMAGE) || defined(HAVE_TRANSVERSEIMAGE)
450
704
  Image *image;
451
705
  volatile VALUE new_image;
706
+ VALUE degrees[1];
452
707
 
453
708
  Data_Get_Struct(self, Image, image);
454
709
 
@@ -459,7 +714,8 @@ static VALUE auto_orient(int bang, VALUE self)
459
714
  break;
460
715
 
461
716
  case BottomRightOrientation:
462
- new_image = rotate(bang, self, rb_float_new(180.0));
717
+ degrees[0] = rb_float_new(180.0);
718
+ new_image = rotate(bang, 1, degrees, self);
463
719
  break;
464
720
 
465
721
  case BottomLeftOrientation:
@@ -471,7 +727,8 @@ static VALUE auto_orient(int bang, VALUE self)
471
727
  break;
472
728
 
473
729
  case RightTopOrientation:
474
- new_image = rotate(bang, self, rb_float_new(90.0));
730
+ degrees[0] = rb_float_new(90.0);
731
+ new_image = rotate(bang, 1, degrees, self);
475
732
  break;
476
733
 
477
734
  case RightBottomOrientation:
@@ -479,7 +736,8 @@ static VALUE auto_orient(int bang, VALUE self)
479
736
  break;
480
737
 
481
738
  case LeftBottomOrientation:
482
- new_image = rotate(bang, self, rb_float_new(270.0));
739
+ degrees[0] = rb_float_new(270.0);
740
+ new_image = rotate(bang, 1, degrees, self);
483
741
  break;
484
742
 
485
743
  default: // Return IMMEDIATELY
@@ -517,7 +775,6 @@ Image_auto_orient_bang(VALUE self)
517
775
  }
518
776
 
519
777
 
520
-
521
778
  /*
522
779
  Method: Image#background_color
523
780
  Purpose: Return the name of the background color as a String.
@@ -531,6 +788,7 @@ Image_background_color(VALUE self)
531
788
  return PixelPacket_to_Color_Name(image, &image->background_color);
532
789
  }
533
790
 
791
+
534
792
  /*
535
793
  Method: Image#background_color=
536
794
  Purpose: Set the the background color to the specified color spec.
@@ -546,6 +804,7 @@ Image_background_color_eq(VALUE self, VALUE color)
546
804
  return self;
547
805
  }
548
806
 
807
+
549
808
  /*
550
809
  Method: Image#base_columns
551
810
  Purpose: Return the number of rows (before transformations)
@@ -591,6 +850,44 @@ VALUE Image_base_rows(VALUE self)
591
850
  }
592
851
 
593
852
 
853
+ /*
854
+ Method: Image#bias -> bias
855
+ Image#bias = a number between 0.0 and 1.0 or "NN%"
856
+ Purpose: Get/set image bias (used when convolving an image)
857
+ */
858
+ VALUE Image_bias(VALUE self)
859
+ {
860
+ #if defined(HAVE_IMAGE_BIAS)
861
+ Image *image;
862
+
863
+ Data_Get_Struct(self, Image, image);
864
+ return rb_float_new(image->bias);
865
+ #else
866
+ rm_not_implemented();
867
+ return (VALUE)0;
868
+ #endif
869
+ }
870
+
871
+
872
+ VALUE Image_bias_eq(VALUE self, VALUE pct)
873
+ {
874
+ #if defined(HAVE_IMAGE_BIAS)
875
+ Image *image;
876
+ double bias;
877
+
878
+ rm_check_frozen(self);
879
+ Data_Get_Struct(self, Image, image);
880
+ bias = rm_percentage(pct);
881
+ image->bias = bias * MaxRGB;
882
+
883
+ return self;
884
+
885
+ #else
886
+ rm_not_implemented();
887
+ return (VALUE)0;
888
+ #endif
889
+
890
+ }
594
891
 
595
892
  /*
596
893
  * Method: Image#bilevel_channel(threshold, channel=AllChannels)
@@ -622,23 +919,388 @@ Image_bilevel_channel(int argc, VALUE *argv, VALUE self)
622
919
 
623
920
  return rm_image_new(new_image);
624
921
 
625
- #else
626
- rm_not_implemented();
627
- return (VALUE)0;
628
- #endif
629
- }
922
+ #else
923
+ rm_not_implemented();
924
+ return (VALUE)0;
925
+ #endif
926
+ }
927
+
928
+
929
+ /*
930
+ Method: Image#black_point_compensation
931
+ Purpose: Return current value
932
+ */
933
+ VALUE
934
+ Image_black_point_compensation(VALUE self)
935
+ {
936
+ Image *image;
937
+ const ImageAttribute *attr;
938
+ volatile VALUE value;
939
+
940
+ Data_Get_Struct(self, Image, image);
941
+
942
+ attr = GetImageAttribute(image, BlackPointCompensationKey);
943
+ if (attr && rm_strcasecmp(attr->value, "true") == 0)
944
+ {
945
+ value = Qtrue;
946
+ }
947
+ else
948
+ {
949
+ value = Qfalse;
950
+ }
951
+ return value;
952
+ }
953
+
954
+
955
+ /*
956
+ Method: Image#black_point_compensation=true or false
957
+ Purpose: Set black point compensation attribute
958
+ */
959
+ VALUE
960
+ Image_black_point_compensation_eq(VALUE self, VALUE arg)
961
+ {
962
+ Image *image;
963
+ char *value;
964
+
965
+ rm_check_frozen(self);
966
+ Data_Get_Struct(self, Image, image);
967
+
968
+ (void) SetImageAttribute(image, BlackPointCompensationKey, NULL);
969
+ value = RTEST(arg) ? "true" : "false";
970
+ (void) SetImageAttribute(image, BlackPointCompensationKey, value);
971
+
972
+ return self;
973
+ }
974
+
975
+
976
+ /*
977
+ * Method: Image#black_threshold(red_channel [, green_channel
978
+ * [, blue_channel [, opacity_channel]]]);
979
+ * Purpose: Call BlackThresholdImage
980
+ */
981
+ VALUE
982
+ Image_black_threshold(int argc, VALUE *argv, VALUE self)
983
+ {
984
+ #if defined(HAVE_BLACKTHRESHOLDIMAGE)
985
+ return threshold_image(argc, argv, self, BlackThresholdImage);
986
+ #else
987
+ rm_not_implemented();
988
+ return (VALUE)0;
989
+ #endif
990
+ }
991
+
992
+
993
+ /*
994
+ Static: get_relative_offsets
995
+ Purpose: compute offsets using the gravity to determine what the
996
+ offsets are relative to
997
+ */
998
+ static void
999
+ get_relative_offsets(
1000
+ VALUE grav,
1001
+ Image *image,
1002
+ Image *mark,
1003
+ long *x_offset,
1004
+ long *y_offset)
1005
+ {
1006
+ MagickEnum *magick_enum;
1007
+ GravityType gravity;
1008
+
1009
+ VALUE_TO_ENUM(grav, gravity, GravityType);
1010
+
1011
+ switch(gravity)
1012
+ {
1013
+ case NorthEastGravity:
1014
+ case EastGravity:
1015
+ *x_offset = (long)(image->columns) - (long)(mark->columns) - *x_offset;
1016
+ break;
1017
+ case SouthWestGravity:
1018
+ case SouthGravity:
1019
+ *y_offset = (long)(image->rows) - (long)(mark->rows) - *y_offset;
1020
+ break;
1021
+ case SouthEastGravity:
1022
+ *x_offset = (long)(image->columns) - (long)(mark->columns) - *x_offset;
1023
+ *y_offset = (long)(image->rows) - (long)(mark->rows) - *y_offset;
1024
+ break;
1025
+ default:
1026
+ Data_Get_Struct(grav, MagickEnum, magick_enum);
1027
+ rb_warning("gravity type `%s' has no effect", rb_id2name(magick_enum->id));
1028
+ break;
1029
+ }
1030
+
1031
+ }
1032
+
1033
+
1034
+ /*
1035
+ Static: get_offsets_from_gravity
1036
+ Purpose: compute watermark offsets from gravity type
1037
+ */
1038
+ static void
1039
+ get_offsets_from_gravity(
1040
+ GravityType gravity,
1041
+ Image *image,
1042
+ Image *mark,
1043
+ long *x_offset,
1044
+ long *y_offset)
1045
+ {
1046
+
1047
+ switch (gravity)
1048
+ {
1049
+ case ForgetGravity:
1050
+ case NorthWestGravity:
1051
+ *x_offset = 0;
1052
+ *y_offset = 0;
1053
+ break;
1054
+ case NorthGravity:
1055
+ *x_offset = ((long)(image->columns) - (long)(mark->columns)) / 2;
1056
+ *y_offset = 0;
1057
+ break;
1058
+ case NorthEastGravity:
1059
+ *x_offset = (long)(image->columns) - (long)(mark->columns);
1060
+ *y_offset = 0;
1061
+ break;
1062
+ case WestGravity:
1063
+ *x_offset = 0;
1064
+ *y_offset = ((long)(image->rows) - (long)(mark->rows)) / 2;
1065
+ break;
1066
+ case StaticGravity:
1067
+ case CenterGravity:
1068
+ default:
1069
+ *x_offset = ((long)(image->columns) - (long)(mark->columns)) / 2;
1070
+ *y_offset = ((long)(image->rows) - (long)(mark->rows)) / 2;
1071
+ break;
1072
+ case EastGravity:
1073
+ *x_offset = (long)(image->columns) - (long)(mark->columns);
1074
+ *y_offset = ((long)(image->rows) - (long)(mark->rows)) / 2;
1075
+ break;
1076
+ case SouthWestGravity:
1077
+ *x_offset = 0;
1078
+ *y_offset = (long)(image->rows) - (long)(mark->rows);
1079
+ break;
1080
+ case SouthGravity:
1081
+ *x_offset = ((long)(image->columns) - (long)(mark->columns)) / 2;
1082
+ *y_offset = (long)(image->rows) - (long)(mark->rows);
1083
+ break;
1084
+ case SouthEastGravity:
1085
+ *x_offset = (long)(image->columns) - (long)(mark->columns);
1086
+ *y_offset = (long)(image->rows) - (long)(mark->rows);
1087
+ break;
1088
+ }
1089
+ }
1090
+
1091
+
1092
+ /*
1093
+ Static: check_for_long_value
1094
+ Purpose: called from rb_protect, returns the number if obj is really
1095
+ a numeric value.
1096
+ */
1097
+ static VALUE check_for_long_value(VALUE obj)
1098
+ {
1099
+ long t;
1100
+ t = NUM2LONG(obj);
1101
+ t = t; // placate gcc
1102
+ return (VALUE)0;
1103
+ }
1104
+
1105
+
1106
+ /*
1107
+ Static: get_composite_offsets
1108
+ Purpose: compute x- and y-offset of source image for a compositing method
1109
+ */
1110
+ static void get_composite_offsets(
1111
+ int argc,
1112
+ VALUE *argv,
1113
+ Image *dest,
1114
+ Image *src,
1115
+ long *x_offset,
1116
+ long *y_offset)
1117
+ {
1118
+ GravityType gravity;
1119
+ int exc = 0;
1120
+
1121
+ if (CLASS_OF(argv[0]) == Class_GravityType)
1122
+ {
1123
+ VALUE_TO_ENUM(argv[0], gravity, GravityType);
1124
+
1125
+ switch (argc)
1126
+ {
1127
+ // Gravity + offset(s). Offsets are relative to the image edges
1128
+ // as specified by the gravity.
1129
+ case 3:
1130
+ *y_offset = NUM2LONG(argv[2]);
1131
+ case 2:
1132
+ *x_offset = NUM2LONG(argv[1]);
1133
+ get_relative_offsets(argv[0], dest, src, x_offset, y_offset);
1134
+ break;
1135
+ case 1:
1136
+ // No offsets specified. Compute offset based on the gravity alone.
1137
+ get_offsets_from_gravity(gravity, dest, src, x_offset, y_offset);
1138
+ break;
1139
+ }
1140
+ }
1141
+ // Gravity not specified at all. Offsets are measured from the
1142
+ // NorthWest corner. The arguments must be numbers.
1143
+ else
1144
+ {
1145
+ *x_offset = rb_protect(check_for_long_value, argv[0], &exc);
1146
+ if (exc)
1147
+ {
1148
+ rb_raise(rb_eArgError, "expected GravityType, got %s", rb_obj_classname(argv[0]));
1149
+ }
1150
+ *x_offset = NUM2LONG(argv[0]);
1151
+ if (argc > 1)
1152
+ {
1153
+ *y_offset = NUM2LONG(argv[1]);
1154
+ }
1155
+ }
1156
+
1157
+ }
1158
+
1159
+
1160
+ /*
1161
+ Static: blend_geometry
1162
+ Purpose: Convert 2 doubles to a blend or dissolve geometry string.
1163
+ Notes: the geometry buffer needs to be at least 16 characters long.
1164
+ For safety's sake this function asserts that it is at least
1165
+ 20 characters long.
1166
+ The percentages must be in the range -1000 < n < 1000. This
1167
+ is far in excess of what xMagick will allow.
1168
+ */
1169
+ static void
1170
+ blend_geometry(
1171
+ char *geometry,
1172
+ size_t geometry_l,
1173
+ double src_percent,
1174
+ double dst_percent)
1175
+ {
1176
+ int sz = 0;
1177
+ int fw, prec;
1178
+
1179
+ if (fabs(src_percent) >= 1000.0 || fabs(dst_percent) >= 1000.0)
1180
+ {
1181
+ if (fabs(src_percent) < 1000.0)
1182
+ {
1183
+ src_percent = dst_percent;
1184
+ }
1185
+ rb_raise(rb_eArgError, "%g is out of range +/-999.99", src_percent);
1186
+ }
1187
+
1188
+ assert(geometry_l >= 20);
1189
+ memset(geometry, 0xdf, geometry_l);
1190
+
1191
+ fw = 4;
1192
+ prec = 0;
1193
+ if (src_percent != (int)(src_percent))
1194
+ {
1195
+ prec = 2;
1196
+ fw += 3;
1197
+ }
1198
+
1199
+ sz = sprintf(geometry, "%*.*f", -fw, prec, src_percent);
1200
+ assert(sz < geometry_l);
1201
+
1202
+ sz = strcspn(geometry, " ");
1203
+
1204
+ // if dst_percent was nil don't add to the geometry
1205
+ if (dst_percent != -1.0)
1206
+ {
1207
+ fw = 4;
1208
+ prec = 0;
1209
+ if (dst_percent != (int)(dst_percent))
1210
+ {
1211
+ prec = 2;
1212
+ fw += 3;
1213
+ }
1214
+
1215
+
1216
+ sz += sprintf(geometry+sz, "x%*.*f", -fw, prec, dst_percent);
1217
+ assert(sz < geometry_l);
1218
+ sz = strcspn(geometry, " ");
1219
+ }
1220
+
1221
+ if (sz < geometry_l)
1222
+ {
1223
+ memset(geometry+sz, 0x00, geometry_l-sz);
1224
+ }
1225
+
1226
+ }
1227
+
1228
+
1229
+ static VALUE
1230
+ special_composite(
1231
+ Image *image,
1232
+ Image *overlay,
1233
+ double image_pct,
1234
+ double overlay_pct,
1235
+ long x_off,
1236
+ long y_off,
1237
+ CompositeOperator op)
1238
+ {
1239
+ Image *new_image;
1240
+ char geometry[20];
1241
+
1242
+ blend_geometry(geometry, sizeof(geometry), image_pct, overlay_pct);
1243
+ CloneString(&overlay->geometry, geometry);
1244
+
1245
+ new_image = rm_clone_image(image);
1246
+ (void) CompositeImage(new_image, op, overlay, x_off, y_off);
1247
+
1248
+ rm_check_image_exception(new_image, DestroyOnError);
1249
+
1250
+ return rm_image_new(new_image);
1251
+ }
1252
+
1253
+
1254
+ /*
1255
+ Method: Image#blend(overlay, src_percent, dst_percent, x_offset=0, y_offset=0)
1256
+ Image#dissolve(overlay, src_percent, dst_percent, gravity, x_offset=0, y_offset=0)
1257
+ Purpose: Corresponds to the composite -blend operation
1258
+ Notes: `percent' can be a number or a string in the form "NN%"
1259
+ The default value for dst_percent is 100.0-src_percent
1260
+ */
1261
+ VALUE
1262
+ Image_blend(int argc, VALUE *argv, VALUE self)
1263
+ {
1264
+ #if defined(HAVE_COLORDODGECOMPOSITEOP)
1265
+ Image *image, *overlay;
1266
+ double src_percent, dst_percent;
1267
+ long x_offset = 0L, y_offset = 0L;
1268
+
1269
+ Data_Get_Struct(self, Image, image);
1270
+
1271
+ if (argc < 1)
1272
+ {
1273
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
1274
+ }
1275
+
1276
+ if (argc > 3)
1277
+ {
1278
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
1279
+ get_composite_offsets(argc-3, &argv[3], image, overlay, &x_offset, &y_offset);
1280
+ // There must be 3 arguments left
1281
+ argc = 3;
1282
+ }
1283
+
1284
+ switch (argc)
1285
+ {
1286
+ case 3:
1287
+ dst_percent = rm_percentage(argv[2]) * 100.0;
1288
+ src_percent = rm_percentage(argv[1]) * 100.0;
1289
+ break;
1290
+ case 2:
1291
+ src_percent = rm_percentage(argv[1]) * 100.0;
1292
+ dst_percent = FMAX(100.0 - src_percent, 0);
1293
+ break;
1294
+ default:
1295
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
1296
+ break;
1297
+ }
1298
+
1299
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
630
1300
 
1301
+ return special_composite(image, overlay, src_percent, dst_percent
1302
+ , x_offset, y_offset, BlendCompositeOp);
631
1303
 
632
- /*
633
- * Method: Image#black_threshold(red_channel [, green_channel
634
- * [, blue_channel [, opacity_channel]]]);
635
- * Purpose: Call BlackThresholdImage
636
- */
637
- VALUE
638
- Image_black_threshold(int argc, VALUE *argv, VALUE self)
639
- {
640
- #if defined(HAVE_BLACKTHRESHOLDIMAGE)
641
- return threshold_image(argc, argv, self, BlackThresholdImage);
642
1304
  #else
643
1305
  rm_not_implemented();
644
1306
  return (VALUE)0;
@@ -1338,35 +2000,6 @@ Image_chromaticity_eq(VALUE self, VALUE chroma)
1338
2000
  return self;
1339
2001
  }
1340
2002
 
1341
- /*
1342
- Method: Image#clip_mask=(mask-image)
1343
- Purpose: associates a clip mask with the image
1344
- Notes: pass "nil" for the mask-image to remove the current clip mask.
1345
- The two images must have the same dimensions.
1346
- */
1347
- VALUE
1348
- Image_clip_mask_eq(VALUE self, VALUE mask)
1349
- {
1350
- Image *image, *mask_image;
1351
- Image *clip_mask;
1352
-
1353
- rm_check_frozen(self);
1354
- Data_Get_Struct(self, Image, image);
1355
-
1356
- if (mask != Qnil)
1357
- {
1358
- Data_Get_Struct(ImageList_cur_image(mask), Image, mask_image);
1359
- clip_mask = rm_clone_image(mask_image);
1360
-
1361
- (void) SetImageClipMask(image, clip_mask);
1362
- }
1363
- else
1364
- {
1365
- (void) SetImageClipMask(image, NULL);
1366
- }
1367
-
1368
- return self;
1369
- }
1370
2003
 
1371
2004
  /*
1372
2005
  Method: Image#clone
@@ -1489,179 +2122,186 @@ Image_color_histogram(VALUE self)
1489
2122
  #endif
1490
2123
  }
1491
2124
 
2125
+
2126
+
1492
2127
  /*
1493
- Method: Image#color_profile
1494
- Purpose: Return the ICC color profile as a String.
1495
- Notes: If there is no profile, returns ""
2128
+ Static: set_profile(target_image, name, profile_image)
2129
+ Purpose: The `profile_image' argument is an IPTC or ICC profile. Store
2130
+ all the profiles in the profile in the target image.
2131
+ Called from Image_color_profile_eq and Image_iptc_profile_eq
1496
2132
  */
1497
- VALUE
1498
- Image_color_profile(VALUE self)
2133
+ static VALUE set_profile(VALUE self, const char *name, VALUE profile)
1499
2134
  {
1500
- Image *image;
1501
- volatile VALUE profile;
2135
+ #if defined(HAVE_GETNEXTIMAGEPROFILE)
2136
+ Image *image, *profile_image;
2137
+ ImageInfo *info;
2138
+ const MagickInfo *m;
2139
+ ExceptionInfo exception;
2140
+ char *profile_name;
2141
+ char *profile_blob;
2142
+ long profile_length;
2143
+ const StringInfo *profile_data;
1502
2144
 
1503
- #if defined(HAVE_GETIMAGEPROFILE)
1504
- /* Both IM 6.0.0 and GM 1.1. define GetImageProfile */
1505
- /* but the implementations are different. IM 6.0.0 */
1506
- /* uses a StringInfo type. That's our feature test. */
2145
+ rm_check_frozen(self);
2146
+ Data_Get_Struct(self, Image, image);
1507
2147
 
1508
- #if defined(HAVE_ACQUIRESTRINGINFO)
1509
- char *str;
1510
- StringInfo *str_info;
2148
+ profile_blob = STRING_PTR_LEN(profile, profile_length);
1511
2149
 
1512
- Data_Get_Struct(self, Image, image);
2150
+ GetExceptionInfo(&exception);
2151
+ m = GetMagickInfo(name, &exception);
2152
+ CHECK_EXCEPTION()
1513
2153
 
1514
- str_info = (StringInfo *)GetImageProfile(image, "icc");
1515
- if (!str_info)
2154
+ info = CloneImageInfo(NULL);
2155
+ if (!info)
1516
2156
  {
1517
- profile = Qnil;
2157
+ rb_raise(rb_eNoMemError, "not enough memory to continue");
1518
2158
  }
1519
- else
2159
+
2160
+ strncpy(info->magick, m->name, MaxTextExtent);
2161
+ info->magick[MaxTextExtent-1] = '\0';
2162
+
2163
+ profile_image = BlobToImage(info, profile_blob, profile_length, &exception);
2164
+ DestroyImageInfo(info);
2165
+ CHECK_EXCEPTION()
2166
+ DestroyExceptionInfo(&exception);
2167
+
2168
+ ResetImageProfileIterator(profile_image);
2169
+ profile_name = GetNextImageProfile(profile_image);
2170
+ while (profile_name)
1520
2171
  {
1521
- str = StringInfoToString(str_info);
1522
- profile = rb_str_new2(str);
1523
- DestroyString(str);
2172
+ if (rm_strcasecmp(profile_name, name) == 0)
2173
+ {
2174
+ profile_data = GetImageProfile(profile_image, profile_name);
2175
+ if (profile)
2176
+ {
2177
+ (void)ProfileImage(image, profile_name, profile_data->datum
2178
+ , (unsigned long)profile_data->length, False);
2179
+ if (image->exception.severity >= ErrorException)
2180
+ {
2181
+ break;
2182
+ }
2183
+ }
2184
+ }
2185
+ profile_name = GetNextImageProfile(profile_image);
1524
2186
  }
1525
2187
 
1526
- #else /* !defined(HAVE_ACQUIRESTRINGINFO) */
1527
- const unsigned char *str;
1528
- size_t length;
2188
+ DestroyImage(profile_image);
2189
+ rm_check_image_exception(image, RetainOnError);
1529
2190
 
2191
+ #else
2192
+
2193
+ Image *image, *profile_image;
2194
+ ImageInfo *info;
2195
+ ExceptionInfo exception;
2196
+ const MagickInfo *m;
2197
+ char *profile_blob;
2198
+ long profile_length;
2199
+ const unsigned char *profile_data;
2200
+ size_t profile_data_l;
2201
+
2202
+ rm_check_frozen(self);
1530
2203
  Data_Get_Struct(self, Image, image);
1531
2204
 
1532
- profile = Qnil; /* Assume no profile defined */
1533
- length = 0;
1534
- str = GetImageProfile(image, "icc", &length);
1535
- if (str)
2205
+ profile_blob = STRING_PTR_LEN(profile, profile_length);
2206
+
2207
+ GetExceptionInfo(&exception);
2208
+ m = GetMagickInfo(name, &exception);
2209
+ CHECK_EXCEPTION()
2210
+
2211
+ info = CloneImageInfo(NULL);
2212
+ if (!info)
1536
2213
  {
1537
- profile = rb_str_new((char *)str, length);
2214
+ rb_raise(rb_eNoMemError, "not enough memory to continue");
1538
2215
  }
1539
2216
 
1540
- #endif
2217
+ strncpy(info->magick, m->name, MaxTextExtent);
2218
+ info->magick[MaxTextExtent-1] = '\0';
1541
2219
 
1542
- #else
1543
- Data_Get_Struct(self, Image, image);
2220
+ profile_image = BlobToImage(info, profile_blob, profile_length, &exception);
2221
+ DestroyImageInfo(info);
2222
+ CHECK_EXCEPTION()
2223
+ DestroyExceptionInfo(&exception);
1544
2224
 
1545
- // Ensure consistency between the data field and the length field. If
1546
- // one field indicates that there is no profile, make the other agree.
1547
- if (image->color_profile.info == NULL)
2225
+ // GraphicsMagick uses "ICM" to refer to the ICC profile.
2226
+ if (rm_strcasecmp(name, "ICC") == 0)
1548
2227
  {
1549
- image->color_profile.length = 0;
2228
+ profile_data = GetImageProfile(profile_image, "ICM", &profile_data_l);
1550
2229
  }
1551
- else if (image->color_profile.length == 0
1552
- && image->color_profile.info)
2230
+ else
1553
2231
  {
1554
- magick_free(image->color_profile.info);
1555
- image->color_profile.info = NULL;
2232
+ profile_data = GetImageProfile(profile_image, name, &profile_data_l);
1556
2233
  }
1557
- if (image->color_profile.length == 0)
2234
+ if (profile_data)
1558
2235
  {
1559
- profile = Qnil;
2236
+ (void)SetImageProfile(image, name, profile_data, profile_data_l);
1560
2237
  }
1561
- profile = rb_str_new((const char *)image->color_profile.info
1562
- , image->color_profile.length);
2238
+
2239
+ DestroyImage(profile_image);
2240
+ rm_check_image_exception(image, RetainOnError);
2241
+
1563
2242
  #endif
1564
2243
 
1565
- return profile;
2244
+ return self;
1566
2245
  }
1567
2246
 
2247
+
1568
2248
  /*
1569
- Method: Image#color_profile=(String)
1570
- Purpose: Set the ICC color profile. The argument is a string.
1571
- Notes: Pass nil to remove any existing profile
2249
+ Method: Image#color_profile
2250
+ Purpose: Return the ICC color profile as a String.
2251
+ Notes: If there is no profile, returns ""
2252
+ This method has no real use but is retained for compatibility
2253
+ with earlier releases of RMagick, where it had no real use either.
1572
2254
  */
1573
2255
  VALUE
1574
- Image_color_profile_eq(VALUE self, VALUE profile)
2256
+ Image_color_profile(VALUE self)
1575
2257
  {
1576
2258
  Image *image;
1577
2259
 
1578
- #if defined(HAVE_GETIMAGEPROFILE)
1579
-
1580
- /* Both IM 6.0.0 and GM 1.1. define SetImageProfile */
1581
- /* but the implementations are different. IM 6.0.0 */
1582
- /* uses a StringInfo type. That's our feature test. */
1583
-
1584
2260
  #if defined(HAVE_ACQUIRESTRINGINFO)
1585
2261
 
1586
- StringInfo *str_info;
1587
- unsigned int status = True;
2262
+ const StringInfo *profile;
1588
2263
 
1589
- rm_check_frozen(self);
1590
2264
  Data_Get_Struct(self, Image, image);
1591
-
1592
- if (profile == Qnil)
2265
+ profile = GetImageProfile(image, "icc");
2266
+ if (!profile)
1593
2267
  {
1594
- #if defined(HAVE_NEW_REMOVEIMAGEPROFILE)
1595
- (void)RemoveImageProfile(image, "icc");
1596
- #else
1597
- str_info = RemoveImageProfile(image, "icc");
1598
- if(str_info)
1599
- {
1600
- DestroyStringInfo(str_info);
1601
- }
1602
- #endif
2268
+ return Qnil;
1603
2269
  }
1604
- else
1605
- {
1606
- str_info = StringToStringInfo(STRING_PTR(profile));
1607
- if (str_info)
1608
- {
1609
- if (str_info->length > 0)
1610
- {
1611
- status = SetImageProfile(image, "icc", str_info);
1612
- }
1613
2270
 
1614
- DestroyStringInfo(str_info);
2271
+ return rb_str_new((char *)profile->datum, (long)profile->length);
1615
2272
 
1616
- if(!status)
1617
- {
1618
- rb_raise(rb_eNoMemError, "not enough memory to continue");
1619
- }
1620
- }
1621
- }
2273
+ #else
1622
2274
 
1623
- #else /* !defined(HAVE_ACQUIRESTRINGINFO) */
1624
- unsigned char *prof = NULL;
1625
- long prof_l = 0;
2275
+ const unsigned char *profile;
2276
+ size_t length;
1626
2277
 
1627
- rm_check_frozen(self);
1628
2278
  Data_Get_Struct(self, Image, image);
1629
2279
 
1630
- if (profile == Qnil)
2280
+ profile = GetImageProfile(image, "ICM", &length);
2281
+ if (!profile)
1631
2282
  {
1632
- (void) SetImageProfile(image, "icc", NULL, 0);
1633
- }
1634
- else
1635
- {
1636
- prof = (unsigned char *)STRING_PTR_LEN(profile, prof_l);
1637
- (void) SetImageProfile(image, "icc", prof, (size_t)prof_l);
2283
+ return Qnil;
1638
2284
  }
1639
- #endif /* defined(HAVE_SETIMAGEPROFILE) */
1640
2285
 
1641
- #else
1642
-
1643
- char *prof = NULL;
1644
- long prof_l = 0;
2286
+ return rb_str_new((char *)profile, (long)length);
1645
2287
 
1646
- rm_check_frozen(self);
1647
- Data_Get_Struct(self, Image, image);
2288
+ #endif
2289
+ }
1648
2290
 
2291
+ /*
2292
+ Method: Image#color_profile=(String)
2293
+ Purpose: Set the ICC color profile. The argument is a string.
2294
+ Notes: Pass nil to remove any existing profile.
2295
+ Removes any existing profile before adding the new one.
2296
+ */
2297
+ VALUE
2298
+ Image_color_profile_eq(VALUE self, VALUE profile)
2299
+ {
2300
+ (void) Image_delete_profile(self, rb_str_new2("ICC"));
1649
2301
  if (profile != Qnil)
1650
2302
  {
1651
- prof = STRING_PTR_LEN(profile, prof_l);
1652
- }
1653
-
1654
- magick_free(image->color_profile.info);
1655
- image->color_profile.info = NULL;
1656
-
1657
- if (prof_l > 0)
1658
- {
1659
- image->color_profile.info = magick_malloc((size_t)prof_l);
1660
- memcpy(image->color_profile.info, prof, (size_t)prof_l);
1661
- image->color_profile.length = prof_l;
2303
+ (void) set_profile(self, "ICC", profile);
1662
2304
  }
1663
-
1664
- #endif
1665
2305
  return self;
1666
2306
  }
1667
2307
 
@@ -2856,6 +3496,26 @@ Image_depth(VALUE self)
2856
3496
  DEF_ATTR_ACCESSOR(Image, delay, ulong)
2857
3497
 
2858
3498
 
3499
+ /*
3500
+ Method: Image#delete_profile(name)
3501
+ Purpose: call ProfileImage
3502
+ Notes: name is the name of the profile to be deleted
3503
+ */
3504
+ VALUE
3505
+ Image_delete_profile(VALUE self, VALUE name)
3506
+ {
3507
+ Image *image;
3508
+
3509
+ rm_check_frozen(self);
3510
+ Data_Get_Struct(self, Image, image);
3511
+
3512
+ (void) ProfileImage(image, STRING_PTR(name), NULL, 0, True);
3513
+ rm_check_image_exception(image, RetainOnError);
3514
+
3515
+ return self;
3516
+ }
3517
+
3518
+
2859
3519
  /*
2860
3520
  Method: Image#despeckle
2861
3521
  Purpose: reduces the speckle noise in an image while preserving the
@@ -2912,6 +3572,53 @@ VALUE Image_difference(VALUE self, VALUE other)
2912
3572
  DEF_ATTR_READER(Image, directory, str)
2913
3573
 
2914
3574
 
3575
+ /*
3576
+ Method: Image#displace(displacement_map, x_amp, y_amp, x_offset=0, y_offset=0)
3577
+ Image#displace(displacement_map, x_amp, y_amp, gravity, x_offset=0, y_offset=0)
3578
+ Purpose: Implement the -displace option of xMagick's composite command
3579
+ Notes: If y_amp is omitted the default is x_amp.
3580
+ */
3581
+ VALUE
3582
+ Image_displace(int argc, VALUE *argv, VALUE self)
3583
+ {
3584
+
3585
+ Image *image, *displacement_map;
3586
+ double x_amplitude, y_amplitude;
3587
+ long x_offset = 0L, y_offset = 0L;
3588
+
3589
+ Data_Get_Struct(self, Image, image);
3590
+
3591
+ if (argc < 2)
3592
+ {
3593
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
3594
+ }
3595
+
3596
+ if (argc > 3)
3597
+ {
3598
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, displacement_map);
3599
+ get_composite_offsets(argc-3, &argv[3], image, displacement_map, &x_offset, &y_offset);
3600
+ // There must be 3 arguments left
3601
+ argc = 3;
3602
+ }
3603
+
3604
+ switch (argc)
3605
+ {
3606
+ case 3:
3607
+ y_amplitude = NUM2DBL(argv[2]);
3608
+ x_amplitude = NUM2DBL(argv[1]);
3609
+ break;
3610
+ case 2:
3611
+ x_amplitude = NUM2DBL(argv[1]);
3612
+ y_amplitude = x_amplitude;
3613
+ break;
3614
+ }
3615
+
3616
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, displacement_map);
3617
+ return special_composite(image, displacement_map, x_amplitude, y_amplitude
3618
+ , x_offset, y_offset, DisplaceCompositeOp);
3619
+ }
3620
+
3621
+
2915
3622
  /*
2916
3623
  Method: Image#dispatch(x, y, columns, rows, map <, float>)
2917
3624
  Purpose: Extracts pixel data from the image and returns it as an
@@ -3044,23 +3751,127 @@ Image_dispose(VALUE self)
3044
3751
  {
3045
3752
  Image *image;
3046
3753
 
3047
- Data_Get_Struct(self, Image, image);
3048
- return DisposeType_new(image->dispose);
3049
- }
3754
+ Data_Get_Struct(self, Image, image);
3755
+ return DisposeType_new(image->dispose);
3756
+ }
3757
+
3758
+ /*
3759
+ Method: Image#dispose=
3760
+ Purpose: Set the dispose attribute
3761
+ */
3762
+ VALUE
3763
+ Image_dispose_eq(VALUE self, VALUE dispose)
3764
+ {
3765
+ Image *image;
3766
+
3767
+ rm_check_frozen(self);
3768
+ Data_Get_Struct(self, Image, image);
3769
+ VALUE_TO_ENUM(dispose, image->dispose, DisposeType);
3770
+ return self;
3771
+ }
3772
+
3773
+
3774
+
3775
+ #if defined(GRAPHICSMAGICK)
3776
+ /*
3777
+ Static: create_mattes
3778
+ Purpose: GraphicsMagick establishes the source image mattes in
3779
+ command.c, before calling CompositeImage. This function does
3780
+ that step for Image_dissolve when we're built for GraphicsMagick.
3781
+ */
3782
+ static void
3783
+ create_mattes(Image *image, double src_percent)
3784
+ {
3785
+ long x, y;
3786
+ PixelPacket *q;
3787
+
3788
+ if (!image->matte)
3789
+ {
3790
+ SetImageOpacity(image,OpaqueOpacity);
3791
+ }
3792
+
3793
+ for (y = 0; y < (long) image->rows; y++)
3794
+ {
3795
+ q = GetImagePixels(image, 0, y, image->columns, 1);
3796
+
3797
+ if (q == NULL)
3798
+ {
3799
+ break;
3800
+ }
3801
+
3802
+ for (x = 0; x < (long) image->columns; x++)
3803
+ {
3804
+ q->opacity = (Quantum) (((MaxRGB - q->opacity) * src_percent) / 100.0);
3805
+ q += 1;
3806
+ }
3807
+
3808
+ if (!SyncImagePixels(image))
3809
+ {
3810
+ break;
3811
+ }
3812
+ }
3813
+ }
3814
+ #endif
3815
+
3816
+ /*
3817
+ Method: Image#dissolve(overlay, src_percent, dst_percent, x_offset=0, y_offset=0)
3818
+ Image#dissolve(overlay, src_percent, dst_percent, gravity, x_offset=0, y_offset=0)
3819
+ Purpose: Corresponds to the composite -dissolve operation
3820
+ Notes: `percent' can be a number or a string in the form "NN%"
3821
+ The "default" value of dst_percent is -1.0, which tells
3822
+ blend_geometry to leave it out of the geometry string.
3823
+ */
3824
+ VALUE
3825
+ Image_dissolve(int argc, VALUE *argv, VALUE self)
3826
+ {
3827
+ Image *image, *overlay;
3828
+ double src_percent, dst_percent = -1.0;
3829
+ long x_offset = 0L, y_offset = 0L;
3830
+ volatile VALUE composite;
3831
+
3832
+ Data_Get_Struct(self, Image, image);
3833
+
3834
+ if (argc < 1)
3835
+ {
3836
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
3837
+ }
3838
+
3839
+ if (argc > 3)
3840
+ {
3841
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
3842
+ get_composite_offsets(argc-3, &argv[3], image, overlay, &x_offset, &y_offset);
3843
+ // There must be 3 arguments left
3844
+ argc = 3;
3845
+ }
3846
+
3847
+ switch (argc)
3848
+ {
3849
+ case 3:
3850
+ dst_percent = rm_percentage(argv[2]) * 100.0;
3851
+ case 2:
3852
+ src_percent = rm_percentage(argv[1]) * 100.0;
3853
+ break;
3854
+ default:
3855
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
3856
+ break;
3857
+ }
3858
+
3859
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
3050
3860
 
3051
- /*
3052
- Method: Image#dispose=
3053
- Purpose: Set the dispose attribute
3054
- */
3055
- VALUE
3056
- Image_dispose_eq(VALUE self, VALUE dispose)
3057
- {
3058
- Image *image;
3861
+ // GraphicsMagick needs an extra step (ref: GM's command.c)
3862
+ #if defined(GRAPHICSMAGICK)
3863
+ overlay = rm_clone_image(overlay);
3864
+ create_mattes(overlay, src_percent);
3865
+ #endif
3059
3866
 
3060
- rm_check_frozen(self);
3061
- Data_Get_Struct(self, Image, image);
3062
- VALUE_TO_ENUM(dispose, image->dispose, DisposeType);
3063
- return self;
3867
+ composite = special_composite(image, overlay, src_percent, dst_percent
3868
+ , x_offset, y_offset, DissolveCompositeOp);
3869
+
3870
+ #if defined(GRAPHICSMAGICK)
3871
+ DestroyImage(overlay);
3872
+ #endif
3873
+
3874
+ return composite;
3064
3875
  }
3065
3876
 
3066
3877
 
@@ -3129,6 +3940,10 @@ Image__dump(VALUE self, VALUE depth)
3129
3940
  Data_Get_Struct(self, Image, image);
3130
3941
 
3131
3942
  info = CloneImageInfo(NULL);
3943
+ if (!info)
3944
+ {
3945
+ rb_raise(rb_eNoMemError, "not enough memory to continue");
3946
+ }
3132
3947
  strcpy(info->magick, image->magick);
3133
3948
 
3134
3949
  GetExceptionInfo(&exception);
@@ -3181,7 +3996,7 @@ Image_dup(VALUE self)
3181
3996
  /*
3182
3997
  Method: Image#each_profile
3183
3998
  Purpose: Iterate over image profiles
3184
- Notes: 5.5.8 and later
3999
+ Notes: ImageMagick only
3185
4000
  */
3186
4001
  VALUE
3187
4002
  Image_each_profile(VALUE self)
@@ -3189,8 +4004,7 @@ Image_each_profile(VALUE self)
3189
4004
  #if defined(HAVE_GETNEXTIMAGEPROFILE)
3190
4005
  Image *image;
3191
4006
  volatile VALUE ary, val;
3192
- char *str, *name;
3193
- StringInfo *str_info;
4007
+ char *name;
3194
4008
 
3195
4009
  Data_Get_Struct(self, Image, image);
3196
4010
 
@@ -3202,18 +4016,36 @@ Image_each_profile(VALUE self)
3202
4016
  while (name)
3203
4017
  {
3204
4018
  rb_ary_store(ary, 0, rb_str_new2(name));
3205
-
3206
- str_info = (StringInfo *)GetImageProfile(image, name);
3207
- if (str_info)
4019
+ #if defined(HAVE_ACQUIRESTRINGINFO)
3208
4020
  {
3209
- str = StringInfoToString(str_info);
3210
- rb_ary_store(ary, 1, rb_str_new2(str));
3211
- DestroyString(str);
4021
+ const StringInfo *profile;
4022
+
4023
+ profile = GetImageProfile(image, name);
4024
+ if (!profile)
4025
+ {
4026
+ rb_ary_store(ary, 1, Qnil);
4027
+ }
4028
+ else
4029
+ {
4030
+ rb_ary_store(ary, 1, rb_str_new((char *)profile->datum, (long)profile->length));
4031
+ }
3212
4032
  }
3213
- else
4033
+ #else
3214
4034
  {
3215
- rb_ary_store(ary, 1, Qnil);
4035
+ unsigned char *profile;
4036
+ size_t length;
4037
+
4038
+ profile = GetImageProfile(image, "iptc", &length);
4039
+ if (!profile)
4040
+ {
4041
+ rb_ary_store(ary, 1, Qnil);
4042
+ }
4043
+ else
4044
+ {
4045
+ rb_ary_store(ary, 1, rb_string_new((char *)profile, (long)length));
4046
+ }
3216
4047
  }
4048
+ #endif
3217
4049
  val = rb_yield(ary);
3218
4050
  name = GetNextImageProfile(image);
3219
4051
  }
@@ -3482,7 +4314,7 @@ Image_export_pixels(int argc, VALUE *argv, VALUE self)
3482
4314
  CHECK_EXCEPTION()
3483
4315
 
3484
4316
  // Should never get here...
3485
- rb_raise(rb_eStandardError, "ExportImagePixels failed with no explanation.");
4317
+ rm_magick_error("ExportImagePixels failed with no explanation.", NULL);
3486
4318
  }
3487
4319
 
3488
4320
  DestroyExceptionInfo(&exception);
@@ -3606,7 +4438,7 @@ Image_export_pixels_to_str(int argc, VALUE *argv, VALUE self)
3606
4438
  CHECK_EXCEPTION()
3607
4439
 
3608
4440
  // Should never get here...
3609
- rb_raise(rb_eStandardError, "ExportImagePixels failed with no explanation.");
4441
+ rm_magick_error("ExportImagePixels failed with no explanation.", NULL);
3610
4442
  }
3611
4443
 
3612
4444
  DestroyExceptionInfo(&exception);
@@ -3695,6 +4527,60 @@ Image_filter_eq(VALUE self, VALUE filter)
3695
4527
  }
3696
4528
 
3697
4529
 
4530
+ /*
4531
+ * Method: Image#find_similar_region(target, x=0, y=0)
4532
+ * Purpose: Search for a region in the image that is "similar" to the
4533
+ * target image.
4534
+ */
4535
+ VALUE
4536
+ Image_find_similar_region(int argc, VALUE *argv, VALUE self)
4537
+ {
4538
+ #if defined(HAVE_ISIMAGESIMILAR)
4539
+ Image *image, *target;
4540
+ volatile VALUE region;
4541
+ long x = 0L, y = 0L;
4542
+ ExceptionInfo exception;
4543
+ unsigned int okay;
4544
+
4545
+ Data_Get_Struct(self, Image, image);
4546
+
4547
+ switch (argc)
4548
+ {
4549
+ case 3:
4550
+ y = NUM2LONG(argv[2]);
4551
+ case 2:
4552
+ x = NUM2LONG(argv[1]);
4553
+ case 1:
4554
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, target);
4555
+ break;
4556
+ default:
4557
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 to 3)", argc);
4558
+ break;
4559
+ }
4560
+
4561
+ GetExceptionInfo(&exception);
4562
+ okay = IsImageSimilar(image, target, &x, &y, &exception);
4563
+ CHECK_EXCEPTION();
4564
+ DestroyExceptionInfo(&exception);
4565
+
4566
+ if (!okay)
4567
+ {
4568
+ return Qnil;
4569
+ }
4570
+
4571
+ region = rb_ary_new2(2);
4572
+ rb_ary_store(region, 0L, LONG2NUM(x));
4573
+ rb_ary_store(region, 1L, LONG2NUM(y));
4574
+
4575
+ return region;
4576
+
4577
+ #else
4578
+ rm_not_implemented();
4579
+ return (VALUE)0;
4580
+ #endif
4581
+ }
4582
+
4583
+
3698
4584
  /*
3699
4585
  Method: Image#flip
3700
4586
  Image#flip!
@@ -3995,17 +4881,17 @@ Image_gamma_channel(int argc, VALUE *argv, VALUE self)
3995
4881
 
3996
4882
 
3997
4883
  /*
3998
- Method: Image#gamma_correct(red_gamma<, green_gamma<, blue_gamma
3999
- <, opacity_gamma>>>)
4884
+ Method: Image#gamma_correct(red_gamma<, green_gamma<, blue_gamma>>>)
4000
4885
  Purpose: gamma-correct an image
4001
4886
  Notes: At least red_gamma must be specified. If one or more levels are
4002
4887
  omitted, the last specified number is used as the default.
4888
+ For backward compatibility accept a 4th argument but ignore it.
4003
4889
  */
4004
4890
  VALUE
4005
4891
  Image_gamma_correct(int argc, VALUE *argv, VALUE self)
4006
4892
  {
4007
4893
  Image *image, *new_image;
4008
- double red_gamma, green_gamma, blue_gamma, opacity_gamma;
4894
+ double red_gamma, green_gamma, blue_gamma;
4009
4895
  char gamma[50];
4010
4896
 
4011
4897
  switch(argc)
@@ -4019,31 +4905,25 @@ Image_gamma_correct(int argc, VALUE *argv, VALUE self)
4019
4905
  {
4020
4906
  rb_raise(rb_eArgError, "invalid gamma value (%f)", red_gamma);
4021
4907
  }
4022
- green_gamma = blue_gamma = opacity_gamma = red_gamma;
4908
+ green_gamma = blue_gamma = red_gamma;
4023
4909
  break;
4024
4910
  case 2:
4025
4911
  red_gamma = NUM2DBL(argv[0]);
4026
4912
  green_gamma = NUM2DBL(argv[1]);
4027
- blue_gamma = opacity_gamma = green_gamma;
4913
+ blue_gamma = green_gamma;
4028
4914
  break;
4029
4915
  case 3:
4030
- red_gamma = NUM2DBL(argv[0]);
4031
- green_gamma = NUM2DBL(argv[1]);
4032
- blue_gamma = NUM2DBL(argv[2]);
4033
- opacity_gamma = blue_gamma;
4034
- break;
4035
4916
  case 4:
4036
4917
  red_gamma = NUM2DBL(argv[0]);
4037
4918
  green_gamma = NUM2DBL(argv[1]);
4038
4919
  blue_gamma = NUM2DBL(argv[2]);
4039
- opacity_gamma = NUM2DBL(argv[3]);
4040
4920
  break;
4041
4921
  default:
4042
- rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 to 4)", argc);
4922
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 to 3)", argc);
4043
4923
  break;
4044
4924
  }
4045
4925
 
4046
- sprintf(gamma, "%f,%f,%f,%f", red_gamma, green_gamma, blue_gamma, opacity_gamma);
4926
+ sprintf(gamma, "%f,%f,%f", red_gamma, green_gamma, blue_gamma);
4047
4927
  Data_Get_Struct(self, Image, image);
4048
4928
 
4049
4929
  new_image = rm_clone_image(image);
@@ -4501,7 +5381,7 @@ Image_import_pixels(int argc, VALUE *argv, VALUE self)
4501
5381
  {
4502
5382
  rm_check_image_exception(image, RetainOnError);
4503
5383
  // Shouldn't get here...
4504
- rb_raise(rb_eStandardError, "ImportImagePixels failed with no explanation.");
5384
+ rm_magick_error("ImportImagePixels failed with no explanation.", NULL);
4505
5385
  }
4506
5386
 
4507
5387
  return self;
@@ -4643,6 +5523,7 @@ Image_inspect(VALUE self)
4643
5523
  return rb_str_new2(buffer);
4644
5524
  }
4645
5525
 
5526
+
4646
5527
  /*
4647
5528
  Method: Image#interlace
4648
5529
  Purpose: get the interlace attribute
@@ -4657,6 +5538,7 @@ Image_interlace(VALUE self)
4657
5538
  return InterlaceType_new(image->interlace);
4658
5539
  }
4659
5540
 
5541
+
4660
5542
  /*
4661
5543
  Method: Image#interlace=
4662
5544
  Purpose: set the interlace attribute
@@ -4672,81 +5554,50 @@ Image_interlace_eq(VALUE self, VALUE interlace)
4672
5554
  return self;
4673
5555
  }
4674
5556
 
5557
+
4675
5558
  /*
4676
5559
  Method: Image#iptc_profile
4677
5560
  Purpose: Return the IPTC profile as a String.
4678
- Notes: If there is no profile, returns ""
5561
+ Notes: If there is no profile, returns Qnil
4679
5562
  */
4680
5563
  VALUE
4681
5564
  Image_iptc_profile(VALUE self)
4682
5565
  {
4683
5566
  Image *image;
4684
- volatile VALUE profile;
4685
-
4686
- #if defined(HAVE_GETIMAGEPROFILE)
4687
- /* Both IM 6.0.0 and GM 1.1. define GetImageProfile */
4688
- /* but the implementations are different. IM 6.0.0 */
4689
- /* uses a StringInfo type. That's our feature test. */
4690
5567
 
4691
5568
  #if defined(HAVE_ACQUIRESTRINGINFO)
4692
- StringInfo *str_info;
4693
- char *str;
5569
+ const StringInfo *profile;
4694
5570
 
4695
5571
  Data_Get_Struct(self, Image, image);
4696
5572
 
4697
- profile = Qnil;
4698
-
4699
- str_info = (StringInfo *)GetImageProfile(image, "iptc");
4700
- if (str_info)
5573
+ profile = GetImageProfile(image, "iptc");
5574
+ if (!profile)
4701
5575
  {
4702
- str = StringInfoToString(str_info);
4703
- profile = rb_str_new2(str);
4704
- DestroyString(str);
5576
+ return Qnil;
4705
5577
  }
4706
5578
 
4707
- #else /* !defined(HAVE_ACQUIRESTRINGINFO) */
4708
- const unsigned char *prof;
4709
- size_t length;
4710
-
4711
- Data_Get_Struct(self, Image, image);
4712
-
4713
- profile = Qnil; /* Assume no profile defined */
4714
-
4715
- prof = GetImageProfile(image, "iptc", &length);
4716
- if (prof)
4717
- {
4718
- profile = rb_str_new((char *)prof, (long) length);
4719
- }
4720
- #endif
5579
+ return rb_str_new((char *)profile->datum, (long)profile->length);
4721
5580
 
4722
5581
  #else
4723
5582
 
5583
+ const unsigned char *profile;
5584
+ size_t length;
5585
+
4724
5586
  Data_Get_Struct(self, Image, image);
4725
5587
 
4726
- // Ensure consistency between the data field and the length field. If
4727
- // one field indicates that there is no profile, make the other agree.
4728
- if (image->iptc_profile.info == NULL)
4729
- {
4730
- image->iptc_profile.length = 0;
4731
- }
4732
- else if (image->iptc_profile.length == 0
4733
- && image->iptc_profile.info)
5588
+ profile = GetImageProfile(image, "iptc", &length);
5589
+ if (!profile)
4734
5590
  {
4735
- magick_free(image->iptc_profile.info);
4736
- image->iptc_profile.info = NULL;
5591
+ return Qnil;
4737
5592
  }
4738
5593
 
4739
- if (image->iptc_profile.length == 0)
4740
- {
4741
- profile = Qnil;
4742
- }
4743
- profile = rb_str_new((const char *)image->iptc_profile.info
4744
- , image->iptc_profile.length);
4745
- #endif
5594
+ return rb_str_new((char *)profile, (long)length);
4746
5595
 
4747
- return profile;
5596
+ #endif
4748
5597
  }
4749
5598
 
5599
+
5600
+
4750
5601
  /*
4751
5602
  Method: Image#iptc_profile=(String)
4752
5603
  Purpose: Set the IPTC profile. The argument is a string.
@@ -4755,90 +5606,11 @@ Image_iptc_profile(VALUE self)
4755
5606
  VALUE
4756
5607
  Image_iptc_profile_eq(VALUE self, VALUE profile)
4757
5608
  {
4758
- Image *image;
4759
-
4760
- #if defined(HAVE_GETIMAGEPROFILE)
4761
- /* Both IM 6.0.0 and GM 1.1. define GetImageProfile */
4762
- /* but the implementations are different. IM 6.0.0 */
4763
- /* uses a StringInfo type. That's our feature test. */
4764
-
4765
- #if defined(HAVE_ACQUIRESTRINGINFO)
4766
- StringInfo *str_info;
4767
- unsigned int status = True;
4768
-
4769
- rm_check_frozen(self);
4770
- Data_Get_Struct(self, Image, image);
4771
-
4772
- if (profile == Qnil)
4773
- {
4774
- #if defined(HAVE_NEW_REMOVEIMAGEPROFILE)
4775
- (void)RemoveImageProfile(image, "iptc");
4776
- #else
4777
- str_info = RemoveImageProfile(image, "iptc");
4778
- if(str_info)
4779
- {
4780
- DestroyStringInfo(str_info);
4781
- }
4782
- #endif
4783
- }
4784
- else
4785
- {
4786
- str_info = StringToStringInfo(STRING_PTR(profile));
4787
- if (str_info)
4788
- {
4789
- if (str_info->length > 0)
4790
- {
4791
- status = SetImageProfile(image, "iptc", str_info);
4792
- }
4793
-
4794
- DestroyStringInfo(str_info);
4795
-
4796
- if(!status)
4797
- {
4798
- rb_raise(rb_eNoMemError, "not enough memory to continue");
4799
- }
4800
- }
4801
- }
4802
- #else /* !defined(HAVE_ACQUIRESTRINGINFO) */
4803
- const unsigned char *prof = NULL;
4804
- long prof_l = 0;
4805
-
4806
- rm_check_frozen(self);
4807
- Data_Get_Struct(self, Image, image);
4808
-
4809
- if (profile == Qnil)
4810
- {
4811
- (void) SetImageProfile(image, "iptc", NULL, 0);
4812
- }
4813
- else
4814
- {
4815
- prof = (unsigned char *)STRING_PTR_LEN(profile, prof_l);
4816
- (void) SetImageProfile(image, "iptc", prof, (size_t)prof_l);
4817
- }
4818
- #endif
4819
-
4820
- #else
4821
-
4822
- char *prof = NULL;
4823
- long prof_l = 0;
4824
-
4825
- rm_check_frozen(self);
4826
- Data_Get_Struct(self, Image, image);
4827
-
5609
+ (void) Image_delete_profile(self, rb_str_new2("IPTC"));
4828
5610
  if (profile != Qnil)
4829
5611
  {
4830
- prof = STRING_PTR_LEN(profile, prof_l);
5612
+ (void) set_profile(self, "IPTC", profile);
4831
5613
  }
4832
- magick_free(image->iptc_profile.info);
4833
- image->iptc_profile.info = NULL;
4834
- if (prof_l > 0)
4835
- {
4836
- image->iptc_profile.info = magick_malloc((size_t)prof_l);
4837
- memcpy(image->iptc_profile.info, prof, (size_t)prof_l);
4838
- image->iptc_profile.length = (size_t) prof_l;
4839
- }
4840
-
4841
- #endif
4842
5614
  return self;
4843
5615
  }
4844
5616
 
@@ -5060,44 +5832,172 @@ Image_magnify_bang(VALUE self)
5060
5832
  return magnify(True, self, MagnifyImage);
5061
5833
  }
5062
5834
 
5063
- /*
5064
- Method: Image#map(map_image, dither=false)
5065
- Purpose: Call MapImage
5066
- Returns: a new image
5067
- */
5835
+ /*
5836
+ Method: Image#map(map_image, dither=false)
5837
+ Purpose: Call MapImage
5838
+ Returns: a new image
5839
+ */
5840
+
5841
+ VALUE
5842
+ Image_map(int argc, VALUE *argv, VALUE self)
5843
+ {
5844
+ Image *image, *new_image;
5845
+ Image *map;
5846
+ volatile VALUE map_obj, map_arg;
5847
+ unsigned int dither = False;
5848
+
5849
+ switch (argc)
5850
+ {
5851
+ case 2:
5852
+ dither = RTEST(argv[1]);
5853
+ case 1:
5854
+ map_arg = argv[0];
5855
+ break;
5856
+ default:
5857
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc);
5858
+ break;
5859
+ }
5860
+
5861
+
5862
+ Data_Get_Struct(self, Image, image);
5863
+ new_image = rm_clone_image(image);
5864
+
5865
+ map_obj = ImageList_cur_image(map_arg);
5866
+ Data_Get_Struct(map_obj, Image, map);
5867
+ (void) MapImage(new_image, map, dither);
5868
+ rm_check_image_exception(new_image, DestroyOnError);
5869
+
5870
+ return rm_image_new(new_image);
5871
+ }
5872
+
5873
+
5874
+ /*
5875
+ Method: Image#mask
5876
+ Purpose: Return the image's clip mask, or nil if it doesn't have a clip
5877
+ mask.
5878
+ Notes: Distinguish from Image#clip_mask
5879
+ */
5880
+ VALUE
5881
+ Image_mask(VALUE self)
5882
+ {
5883
+ Image *image, *mask;
5884
+ ExceptionInfo exception;
5885
+
5886
+ Data_Get_Struct(self, Image, image);
5887
+
5888
+ GetExceptionInfo(&exception);
5889
+
5890
+ #if defined(HAVE_GETIMAGECLIPMASK)
5891
+
5892
+ // The returned clip mask is a clone, ours to keep.
5893
+ mask = GetImageClipMask(image, &exception);
5894
+ rm_check_exception(&exception, mask, DestroyOnError);
5895
+
5896
+ #else
5897
+ mask = image->clip_mask;
5898
+ #endif
5899
+
5900
+ DestroyExceptionInfo(&exception);
5901
+
5902
+ return mask ? rm_image_new(mask) : Qnil;
5903
+ }
5904
+
5905
+
5906
+ /*
5907
+ Method: Image#mask=(mask-image)
5908
+ Purpose: associates a clip mask with the image
5909
+ Notes: pass "nil" for the mask-image to remove the current clip mask.
5910
+ If the clip mask is not the same size as the target image,
5911
+ resizes the clip mask to match the target.
5912
+ Notes: Distinguish from Image#clip_mask=
5913
+ */
5914
+ VALUE
5915
+ Image_mask_eq(VALUE self, VALUE mask)
5916
+ {
5917
+ Image *image, *mask_image, *resized_image;
5918
+ Image *clip_mask;
5919
+ long x, y;
5920
+ PixelPacket *q;
5921
+ ExceptionInfo exception;
5922
+
5923
+ rm_check_frozen(self);
5924
+ Data_Get_Struct(self, Image, image);
5925
+
5926
+ if (mask != Qnil)
5927
+ {
5928
+ Data_Get_Struct(ImageList_cur_image(mask), Image, mask_image);
5929
+ clip_mask = rm_clone_image(mask_image);
5930
+
5931
+ // Resize if necessary
5932
+ if (clip_mask->columns != image->columns || clip_mask->rows != image->rows)
5933
+ {
5934
+ GetExceptionInfo(&exception);
5935
+ resized_image = ResizeImage(clip_mask, image->columns, image->rows
5936
+ , UndefinedFilter, 0.0, &exception);
5937
+ rm_check_exception(&exception, resized_image, DestroyOnError);
5938
+ DestroyExceptionInfo(&exception);
5939
+ rm_ensure_result(resized_image);
5940
+ (void) DestroyImage(clip_mask);
5941
+ clip_mask = resized_image;
5942
+ }
5943
+
5944
+ // The following section is copied from mogrify.c (6.2.8-8)
5945
+ for (y = 0; y < (long) clip_mask->rows; y++)
5946
+ {
5947
+ q = GetImagePixels(clip_mask, 0, y, clip_mask->columns, 1);
5948
+ if (!q)
5949
+ {
5950
+ break;
5951
+ }
5952
+ for (x = 0; x < (long) clip_mask->columns; x++)
5953
+ {
5954
+ if (clip_mask->matte == False)
5955
+ {
5956
+ q->opacity = PIXEL_INTENSITY(q);
5957
+ }
5958
+ q->red = q->opacity;
5959
+ q->green = q->opacity;
5960
+ q->blue = q->opacity;
5961
+ q += 1;
5962
+ }
5963
+ if (SyncImagePixels(clip_mask) == False)
5964
+ {
5965
+ (void) DestroyImage(clip_mask);
5966
+ rm_magick_error("SyncImagePixels failed", NULL);
5967
+ }
5968
+ }
5969
+
5970
+ #if defined(HAVE_SETIMAGESTORAGECLASS)
5971
+ if (SetImageStorageClass(clip_mask, DirectClass) == False)
5972
+ {
5973
+ (void) DestroyImage(clip_mask);
5974
+ rm_magick_error("SetImageStorageClass failed", NULL);
5975
+ }
5976
+ #else
5977
+ if (clip_mask->storage_class == PseudoClass)
5978
+ {
5979
+ SyncImage(image);
5980
+ clip_mask->storage_class = DirectClass;
5981
+ }
5982
+ #endif
5983
+
5984
+ clip_mask->matte = True;
5068
5985
 
5069
- VALUE
5070
- Image_map(int argc, VALUE *argv, VALUE self)
5071
- {
5072
- Image *image, *new_image;
5073
- Image *map;
5074
- volatile VALUE map_obj, map_arg;
5075
- unsigned int dither = False;
5986
+ // SetImageClipMask clones the clip_mask image. We can
5987
+ // destroy our copy after SetImageClipMask is done with it.
5076
5988
 
5077
- switch (argc)
5989
+ (void) SetImageClipMask(image, clip_mask);
5990
+ (void) DestroyImage(clip_mask);
5991
+ }
5992
+ else
5078
5993
  {
5079
- case 2:
5080
- dither = RTEST(argv[1]);
5081
- case 1:
5082
- map_arg = argv[0];
5083
- break;
5084
- default:
5085
- rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc);
5086
- break;
5994
+ (void) SetImageClipMask(image, NULL);
5087
5995
  }
5088
5996
 
5089
-
5090
- Data_Get_Struct(self, Image, image);
5091
- new_image = rm_clone_image(image);
5092
-
5093
- map_obj = ImageList_cur_image(map_arg);
5094
- Data_Get_Struct(map_obj, Image, map);
5095
- (void) MapImage(new_image, map, dither);
5096
- rm_check_image_exception(new_image, DestroyOnError);
5097
-
5098
- return rm_image_new(new_image);
5997
+ return self;
5099
5998
  }
5100
5999
 
6000
+
5101
6001
  DEF_ATTR_ACCESSOR(Image, matte, bool)
5102
6002
 
5103
6003
  /*
@@ -5400,37 +6300,48 @@ Image_montage_eq(
5400
6300
  return self;
5401
6301
  }
5402
6302
 
6303
+
5403
6304
  /*
5404
- Method: Image#motion_blur(radius, sigma, angle)
5405
- Purpose: simulates motion blur. Convolves the image with a Gaussian
5406
- operator of the given radius and standard deviation (sigma).
5407
- For reasonable results, radius should be larger than sigma.
5408
- Use a radius of 0 and motion_blur selects a suitable radius
5409
- for you. Angle gives the angle of the blurring motion.
6305
+ Static: motion_blur(int argc, VALUE *argv, VALUE self, magick_api)
6306
+ Purpose: called from Image_motion_blur and Image_sketch
5410
6307
  */
5411
- VALUE
5412
- Image_motion_blur(
6308
+ static VALUE
6309
+ motion_blur(
6310
+ int argc,
6311
+ VALUE *argv,
5413
6312
  VALUE self,
5414
- VALUE radius_arg,
5415
- VALUE sigma_arg,
5416
- VALUE angle_arg)
6313
+ Image *fp(const Image *, const double, const double, const double, ExceptionInfo *))
5417
6314
  {
5418
6315
  Image *image, *new_image;
5419
- double radius, sigma, angle;
6316
+ double radius = 0.0;
6317
+ double sigma = 1.0;
6318
+ double angle = 0.0;
5420
6319
  ExceptionInfo exception;
5421
6320
 
5422
- Data_Get_Struct(self, Image, image);
5423
- radius = NUM2DBL(radius_arg);
5424
- sigma = NUM2DBL(sigma_arg);
5425
- angle = NUM2DBL(angle_arg);
6321
+ switch (argc)
6322
+ {
6323
+ case 3:
6324
+ angle = NUM2DBL(argv[2]);
6325
+ case 2:
6326
+ sigma = NUM2DBL(argv[1]);
6327
+ case 1:
6328
+ radius = NUM2DBL(argv[0]);
6329
+ case 0:
6330
+ break;
6331
+ default:
6332
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 to 3)", argc);
6333
+ break;
6334
+ }
5426
6335
 
5427
6336
  if (sigma == 0.0)
5428
6337
  {
5429
6338
  rb_raise(rb_eArgError, "sigma must be != 0.0");
5430
6339
  }
5431
6340
 
6341
+ Data_Get_Struct(self, Image, image);
6342
+
5432
6343
  GetExceptionInfo(&exception);
5433
- new_image = MotionBlurImage(image, radius, sigma, angle, &exception);
6344
+ new_image = (fp)(image, radius, sigma, angle, &exception);
5434
6345
  rm_check_exception(&exception, new_image, DestroyOnError);
5435
6346
 
5436
6347
  DestroyExceptionInfo(&exception);
@@ -5440,6 +6351,22 @@ Image_motion_blur(
5440
6351
  return rm_image_new(new_image);
5441
6352
  }
5442
6353
 
6354
+
6355
+ /*
6356
+ Method: Image#motion_blur(radius=0.0, sigma=1.0, angle=0.0)
6357
+ Purpose: simulates motion blur. Convolves the image with a Gaussian
6358
+ operator of the given radius and standard deviation (sigma).
6359
+ For reasonable results, radius should be larger than sigma.
6360
+ Use a radius of 0 and motion_blur selects a suitable radius
6361
+ for you. Angle gives the angle of the blurring motion.
6362
+ */
6363
+ VALUE
6364
+ Image_motion_blur(int argc, VALUE *argv, VALUE self)
6365
+ {
6366
+ return motion_blur(argc, argv, self, MotionBlurImage);
6367
+ }
6368
+
6369
+
5443
6370
  /*
5444
6371
  Method: Image#negate(grayscale=false)
5445
6372
  Purpose: negates the colors in the reference image. The grayscale option
@@ -5517,8 +6444,7 @@ Image_negate_channel(int argc, VALUE *argv, VALUE self)
5517
6444
  /*
5518
6445
  Method: Image.new(cols, rows<, fill>) <{info block}>
5519
6446
  Purpose: Create a new Image with "cols" columns and "rows" rows.
5520
- If the fill argument is omitted, create a SolidFill object
5521
- using the background color
6447
+ If the fill argument is omitted, fill with the background color
5522
6448
  Returns: A new Image
5523
6449
  Note: This routine creates an Info structure to use when allocating
5524
6450
  the Image structure. The caller can supply an info parm block to
@@ -6116,6 +7042,47 @@ Image_pixel_color(
6116
7042
  return Pixel_from_PixelPacket(&old_color);
6117
7043
  }
6118
7044
 
7045
+
7046
+ /*
7047
+ Method: Image.pixel_interpolation_method
7048
+ Image.pixel_interpolation_method=method
7049
+ Purpose: Get/set the "interpolate" field in the Image structure.
7050
+ Ref: Image.interpolate_pixel_color
7051
+ */
7052
+ VALUE
7053
+ Image_pixel_interpolation_method(VALUE self)
7054
+ {
7055
+ #if defined(HAVE_INTERPOLATEPIXELCOLOR)
7056
+ Image *image;
7057
+
7058
+ Data_Get_Struct(self, Image, image);
7059
+ return InterpolatePixelMethod_new(image->interpolate);
7060
+
7061
+ #else
7062
+ rm_not_implemented();
7063
+ return (VALUE)0;
7064
+ #endif
7065
+ }
7066
+
7067
+
7068
+ VALUE
7069
+ Image_pixel_interpolation_method_eq(VALUE self, VALUE method)
7070
+ {
7071
+ #if defined(HAVE_INTERPOLATEPIXELCOLOR)
7072
+ Image *image;
7073
+
7074
+ rm_check_frozen(self);
7075
+ Data_Get_Struct(self, Image, image);
7076
+ VALUE_TO_ENUM(method, image->interpolate, InterpolatePixelMethod);
7077
+ return self;
7078
+
7079
+ #else
7080
+ rm_not_implemented();
7081
+ return (VALUE)0;
7082
+ #endif
7083
+ }
7084
+
7085
+
6119
7086
  #if 0
6120
7087
  /*
6121
7088
  Method: Image.plasma(x1, y1, x2, y2, attenuate, depth)
@@ -6234,36 +7201,26 @@ Image_preview(VALUE self, VALUE preview)
6234
7201
 
6235
7202
  /*
6236
7203
  Method: Image#profile!(name, profile)
6237
- Purpose: call ProfileImage
6238
- Notes: modifies current image
6239
- History: added 'True' value for 'clone' argument for IM 5.4.7
7204
+ Purpose: If "profile" is nil, deletes the profile. Otherwise "profile"
7205
+ must be a string containing the specified profile.
6240
7206
  */
6241
7207
  VALUE
6242
- Image_profile_bang(
6243
- VALUE self,
6244
- VALUE name,
6245
- VALUE profile)
7208
+ Image_profile_bang(VALUE self, VALUE name, VALUE profile)
6246
7209
  {
6247
- Image *image;
6248
- char *prof = NULL;
6249
- long prof_l = 0;
6250
-
6251
- rm_check_frozen(self);
6252
- Data_Get_Struct(self, Image, image);
6253
7210
 
6254
- // ProfileImage issues a warning if something goes wrong.
6255
- if (profile != Qnil)
7211
+ if (profile == Qnil)
6256
7212
  {
6257
- prof = STRING_PTR_LEN(profile, prof_l);
7213
+ return Image_delete_profile(self, name);
7214
+ }
7215
+ else
7216
+ {
7217
+ return set_profile(self, STRING_PTR(name), profile);
6258
7218
  }
6259
- (void) ProfileImage(image, STRING_PTR(name), (const unsigned char *)prof
6260
- , (size_t)prof_l, True);
6261
- rm_check_image_exception(image, RetainOnError);
6262
7219
 
6263
- return self;
6264
7220
  }
6265
7221
 
6266
7222
 
7223
+
6267
7224
  #if defined(HAVE_IMAGE_QUALITY)
6268
7225
  DEF_ATTR_READER(Image, quality, ulong)
6269
7226
  #endif
@@ -6440,6 +7397,14 @@ Image_quantum_operator(int argc, VALUE *argv, VALUE self)
6440
7397
  case LShiftQuantumOperator:
6441
7398
  qop = LeftShiftEvaluateOperator;
6442
7399
  break;
7400
+ #if defined(HAVE_MAXEVALUATEOPERATOR)
7401
+ case MaxQuantumOperator:
7402
+ qop = MaxEvaluateOperator;
7403
+ break;
7404
+ case MinQuantumOperator:
7405
+ qop = MinEvaluateOperator;
7406
+ break;
7407
+ #endif
6443
7408
  case MultiplyQuantumOperator:
6444
7409
  qop = MultiplyEvaluateOperator;
6445
7410
  break;
@@ -6458,7 +7423,7 @@ Image_quantum_operator(int argc, VALUE *argv, VALUE self)
6458
7423
  }
6459
7424
 
6460
7425
  GetExceptionInfo(&exception);
6461
- (void) EvaluateImageChannel(image, channel, operator, rvalue, &exception);
7426
+ (void) EvaluateImageChannel(image, channel, qop, rvalue, &exception);
6462
7427
  CHECK_EXCEPTION()
6463
7428
 
6464
7429
  DestroyExceptionInfo(&exception);
@@ -7005,7 +7970,7 @@ resize(int bang, int argc, VALUE *argv, VALUE self)
7005
7970
  dcols = scale * image->columns + 0.5;
7006
7971
  if (drows > ULONG_MAX || dcols > ULONG_MAX)
7007
7972
  {
7008
- rb_raise(rb_eRangeError, "resulting image too big");
7973
+ rb_raise(rb_eRangeError, "resized image too big");
7009
7974
  }
7010
7975
  rows = (unsigned long) drows;
7011
7976
  columns = (unsigned long) dcols;
@@ -7071,21 +8036,51 @@ Image_roll(VALUE self, VALUE x_offset, VALUE y_offset)
7071
8036
 
7072
8037
 
7073
8038
  /*
7074
- Method: Image#rotate(degrees)
8039
+ Method: Image#rotate(degrees [,'<' | '>'])
7075
8040
  Purpose: creates a new image that is a rotated copy of an existing one
7076
8041
  Image#rotate!(degrees)
7077
8042
  Purpose: rotates the image by the specified number of degrees
8043
+ Note: If the 2nd argument is '<' rotate only if width < height.
8044
+ If the 2nd argument is '>' rotate only if width > height.
7078
8045
  */
7079
8046
  static VALUE
7080
- rotate(int bang, VALUE self, VALUE degrees)
8047
+ rotate(int bang, int argc, VALUE *argv, VALUE self)
7081
8048
  {
7082
8049
  Image *image, *new_image;
8050
+ double degrees;
8051
+ char *arrow;
8052
+ long arrow_l;
7083
8053
  ExceptionInfo exception;
7084
8054
 
7085
8055
  Data_Get_Struct(self, Image, image);
8056
+
8057
+ switch (argc)
8058
+ {
8059
+ case 2:
8060
+ arrow = STRING_PTR_LEN(argv[1], arrow_l);
8061
+ if (arrow_l != 1 || (*arrow != '<' && *arrow != '>'))
8062
+ {
8063
+ rb_raise(rb_eArgError, "second argument must be '<' or '>', '%s' given", arrow);
8064
+ }
8065
+ if (*arrow == '>' && image->columns <= image->rows)
8066
+ {
8067
+ return Qnil;
8068
+ }
8069
+ if (*arrow == '<' && image->columns >= image->rows)
8070
+ {
8071
+ return Qnil;
8072
+ }
8073
+ case 1:
8074
+ degrees = NUM2DBL(argv[0]);
8075
+ break;
8076
+ default:
8077
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc);
8078
+ break;
8079
+ }
8080
+
7086
8081
  GetExceptionInfo(&exception);
7087
8082
 
7088
- new_image = RotateImage(image, NUM2DBL(degrees), &exception);
8083
+ new_image = RotateImage(image, degrees, &exception);
7089
8084
  rm_check_exception(&exception, new_image, DestroyOnError);
7090
8085
 
7091
8086
  DestroyExceptionInfo(&exception);
@@ -7102,16 +8097,16 @@ rotate(int bang, VALUE self, VALUE degrees)
7102
8097
  }
7103
8098
 
7104
8099
  VALUE
7105
- Image_rotate(VALUE self, VALUE degrees)
8100
+ Image_rotate(int argc, VALUE *argv, VALUE self)
7106
8101
  {
7107
- return rotate(False, self, degrees);
8102
+ return rotate(False, argc, argv, self);
7108
8103
  }
7109
8104
 
7110
8105
  VALUE
7111
- Image_rotate_bang(VALUE self, VALUE degrees)
8106
+ Image_rotate_bang(int argc, VALUE *argv, VALUE self)
7112
8107
  {
7113
8108
  rm_check_frozen(self);
7114
- return rotate(True, self, degrees);
8109
+ return rotate(True, argc, argv, self);
7115
8110
  }
7116
8111
 
7117
8112
  DEF_ATTR_READER(Image, rows, int)
@@ -7192,7 +8187,7 @@ scale(int bang, int argc, VALUE *argv, VALUE self, scaler_t *scaler)
7192
8187
  dcols = scale * image->columns + 0.5;
7193
8188
  if (drows > ULONG_MAX || dcols > ULONG_MAX)
7194
8189
  {
7195
- rb_raise(rb_eRangeError, "resulting image too big");
8190
+ rb_raise(rb_eRangeError, "resized image too big");
7196
8191
  }
7197
8192
  rows = (unsigned long) drows;
7198
8193
  columns = (unsigned long) dcols;
@@ -7721,6 +8716,24 @@ Image_signature(VALUE self)
7721
8716
  return rb_str_new(signature->value, 64);
7722
8717
  }
7723
8718
 
8719
+
8720
+
8721
+ /*
8722
+ Method: Image#sketch(radius=0.0, sigma=1.0, angle=0.0)
8723
+ Purpose: Call SketchImage
8724
+ */
8725
+ VALUE
8726
+ Image_sketch(int argc, VALUE *argv, VALUE self)
8727
+ {
8728
+ #if defined(HAVE_SKETCHIMAGE)
8729
+ return motion_blur(argc, argv, self, SketchImage);
8730
+ #else
8731
+ rm_not_implemented();
8732
+ return (VALUE)0;
8733
+ #endif
8734
+ }
8735
+
8736
+
7724
8737
  /*
7725
8738
  Method: Image#solarize(threshold=50.0)
7726
8739
  Purpose: applies a special effect to the image, similar to the effect
@@ -8351,7 +9364,7 @@ thumbnail(int bang, int argc, VALUE *argv, VALUE self)
8351
9364
  dcols = scale * image->columns + 0.5;
8352
9365
  if (drows > ULONG_MAX || dcols > ULONG_MAX)
8353
9366
  {
8354
- rb_raise(rb_eRangeError, "resulting image too big");
9367
+ rb_raise(rb_eRangeError, "resized image too big");
8355
9368
  }
8356
9369
  rows = (unsigned long) drows;
8357
9370
  columns = (unsigned long) dcols;
@@ -8711,6 +9724,46 @@ Image_transparent(int argc, VALUE *argv, VALUE self)
8711
9724
  }
8712
9725
 
8713
9726
 
9727
+ /*
9728
+ Method: Image#transparent_color
9729
+ Purpose: Return the name of the transparent color as a String.
9730
+ */
9731
+ VALUE
9732
+ Image_transparent_color(VALUE self)
9733
+ {
9734
+ #if defined(HAVE_IMAGE_TRANSPARENT_COLOR)
9735
+ Image *image;
9736
+
9737
+ Data_Get_Struct(self, Image, image);
9738
+ return PixelPacket_to_Color_Name(image, &image->transparent_color);
9739
+ #else
9740
+ rm_not_implemented();
9741
+ return (VALUE)0;
9742
+ #endif
9743
+ }
9744
+
9745
+
9746
+ /*
9747
+ Method: Image#transparent_color=
9748
+ Purpose: Set the the transparent color to the specified color spec.
9749
+ */
9750
+ VALUE
9751
+ Image_transparent_color_eq(VALUE self, VALUE color)
9752
+ {
9753
+ #if defined(HAVE_IMAGE_TRANSPARENT_COLOR)
9754
+ Image *image;
9755
+
9756
+ rm_check_frozen(self);
9757
+ Data_Get_Struct(self, Image, image);
9758
+ Color_to_PixelPacket(&image->transparent_color, color);
9759
+ return self;
9760
+ #else
9761
+ rm_not_implemented();
9762
+ return (VALUE)0;
9763
+ #endif
9764
+ }
9765
+
9766
+
8714
9767
  /*
8715
9768
  * Method: Image#transpose
8716
9769
  * Image#transpose!
@@ -8863,6 +9916,34 @@ VALUE Image_image_type(VALUE self)
8863
9916
  }
8864
9917
 
8865
9918
 
9919
+ /*
9920
+ Method: Image#unique_colors
9921
+ Purpose: Call UniqueImageColors
9922
+ */
9923
+ VALUE
9924
+ Image_unique_colors(VALUE self)
9925
+ {
9926
+ #if defined(HAVE_UNIQUEIMAGECOLORS)
9927
+ Image *image, *new_image;
9928
+ ExceptionInfo exception;
9929
+
9930
+ Data_Get_Struct(self, Image, image);
9931
+ GetExceptionInfo(&exception);
9932
+
9933
+ new_image = UniqueImageColors(image, &exception);
9934
+ rm_check_exception(&exception, new_image, DestroyOnError);
9935
+ DestroyExceptionInfo(&exception);
9936
+
9937
+ rm_ensure_result(new_image);
9938
+
9939
+ return rm_image_new(new_image);
9940
+ #else
9941
+ rm_not_implemented();
9942
+ return (VALUE)0;
9943
+ #endif
9944
+ }
9945
+
9946
+
8866
9947
  /*
8867
9948
  Method: Image#units
8868
9949
  Purpose: Get the resolution type field
@@ -9097,6 +10178,64 @@ Image_virtual_pixel_method_eq(VALUE self, VALUE method)
9097
10178
  return self;
9098
10179
  }
9099
10180
 
10181
+
10182
+
10183
+
10184
+ /*
10185
+ Method: Image#watermark(mark, brightness=100.0, saturation=100.0
10186
+ , [gravity,] x_off=0, y_off=0)
10187
+ Purpose: add a watermark to an image
10188
+ Notes: x_off and y_off can be negative, which means measure from the right/bottom
10189
+ of the target image.
10190
+ */
10191
+ VALUE
10192
+ Image_watermark(int argc, VALUE *argv, VALUE self)
10193
+ {
10194
+ Image *image, *overlay, *new_image;
10195
+ double src_percent = 100.0, dst_percent = 100.0;
10196
+ long x_offset = 0L, y_offset = 0L;
10197
+ char geometry[20];
10198
+
10199
+ Data_Get_Struct(self, Image, image);
10200
+
10201
+ if (argc < 1)
10202
+ {
10203
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
10204
+ }
10205
+
10206
+ if (argc > 3)
10207
+ {
10208
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
10209
+ get_composite_offsets(argc-3, &argv[3], image, overlay, &x_offset, &y_offset);
10210
+ // There must be 3 arguments left
10211
+ argc = 3;
10212
+ }
10213
+
10214
+ switch (argc)
10215
+ {
10216
+ case 3:
10217
+ dst_percent = rm_percentage(argv[2]) * 100.0;
10218
+ case 2:
10219
+ src_percent = rm_percentage(argv[1]) * 100.0;
10220
+ case 1:
10221
+ Data_Get_Struct(ImageList_cur_image(argv[0]), Image, overlay);
10222
+ break;
10223
+ default:
10224
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 to 6)", argc);
10225
+ break;
10226
+ }
10227
+
10228
+ blend_geometry(geometry, sizeof(geometry), src_percent, dst_percent);
10229
+ CloneString(&overlay->geometry, geometry);
10230
+
10231
+ new_image = rm_clone_image(image);
10232
+ (void) CompositeImage(new_image, ModulateCompositeOp, overlay, x_offset, y_offset);
10233
+
10234
+ rm_check_image_exception(new_image, DestroyOnError);
10235
+
10236
+ return rm_image_new(new_image);
10237
+ }
10238
+
9100
10239
  /*
9101
10240
  Method: Image#wave(amplitude=25.0, wavelength=150.0)
9102
10241
  Purpose: creates a "ripple" effect in the image by shifting the pixels
@@ -9218,8 +10357,12 @@ Image_write(VALUE self, VALUE file)
9218
10357
  return self;
9219
10358
  }
9220
10359
 
10360
+
9221
10361
  DEF_ATTR_ACCESSOR(Image, x_resolution, dbl)
9222
10362
 
10363
+ DEF_ATTR_ACCESSOR(Image, y_resolution, dbl)
10364
+
10365
+
9223
10366
  /*
9224
10367
  Static: cropper
9225
10368
  Purpose: determine if the argument list is
@@ -9439,11 +10582,9 @@ xform_image(
9439
10582
 
9440
10583
  }
9441
10584
 
9442
- DEF_ATTR_ACCESSOR(Image, y_resolution, dbl)
9443
-
9444
10585
 
9445
10586
  /*
9446
- Static: extract_channels
10587
+ Extern: extract_channels
9447
10588
  Purpose: Remove all the ChannelType arguments from the
9448
10589
  end of the argument list.
9449
10590
  Returns: A ChannelType value suitable for passing into
@@ -9451,7 +10592,7 @@ DEF_ATTR_ACCESSOR(Image, y_resolution, dbl)
9451
10592
  no channel arguments were found. Returns the
9452
10593
  number of remaining arguments.
9453
10594
  */
9454
- static ChannelType extract_channels(
10595
+ ChannelType extract_channels(
9455
10596
  int *argc,
9456
10597
  VALUE *argv)
9457
10598
  {
@@ -9487,11 +10628,11 @@ static ChannelType extract_channels(
9487
10628
 
9488
10629
 
9489
10630
  /*
9490
- Static: raise_ChannelType_error
10631
+ Extern: raise_ChannelType_error
9491
10632
  Purpose: raise TypeError when an non-ChannelType object
9492
10633
  is unexpectedly encountered
9493
10634
  */
9494
- static void
10635
+ void
9495
10636
  raise_ChannelType_error(VALUE arg)
9496
10637
  {
9497
10638
  rb_raise(rb_eTypeError, "argument needs to be a ChannelType (%s given)"