rmagick 2.13.2 → 4.2.2

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 (380) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +63 -0
  3. data/.editorconfig +17 -0
  4. data/.github/ISSUE_TEMPLATE.md +17 -0
  5. data/.github/workflows/ci.yml +107 -0
  6. data/.gitignore +25 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +37 -0
  9. data/.rubocop_todo.yml +449 -0
  10. data/.yardopts +5 -0
  11. data/CHANGELOG.md +1277 -0
  12. data/CODE_OF_CONDUCT.md +128 -0
  13. data/CONTRIBUTING.md +81 -0
  14. data/Gemfile +4 -0
  15. data/LICENSE +20 -0
  16. data/README.md +324 -0
  17. data/Rakefile +190 -0
  18. data/before_install_linux.sh +69 -0
  19. data/before_install_osx.sh +57 -0
  20. data/deprecated/RMagick.rb +6 -0
  21. data/ext/RMagick/extconf.rb +334 -333
  22. data/ext/RMagick/rmagick.c +129 -127
  23. data/ext/RMagick/rmagick.h +221 -320
  24. data/ext/RMagick/rmdraw.c +458 -767
  25. data/ext/RMagick/rmenum.c +305 -752
  26. data/ext/RMagick/rmfill.c +231 -199
  27. data/ext/RMagick/rmilist.c +437 -478
  28. data/ext/RMagick/rmimage.c +6130 -5342
  29. data/ext/RMagick/rminfo.c +695 -833
  30. data/ext/RMagick/rmkinfo.c +198 -0
  31. data/ext/RMagick/rmmain.c +752 -506
  32. data/ext/RMagick/rmmontage.c +94 -152
  33. data/ext/RMagick/rmpixel.c +619 -425
  34. data/ext/RMagick/rmstruct.c +206 -309
  35. data/ext/RMagick/rmutil.c +514 -344
  36. data/lib/rmagick/version.rb +5 -0
  37. data/lib/rmagick.rb +1 -0
  38. data/lib/rmagick_internal.rb +1922 -0
  39. data/lib/rvg/clippath.rb +35 -39
  40. data/lib/rvg/container.rb +123 -124
  41. data/lib/rvg/deep_equal.rb +45 -49
  42. data/lib/rvg/describable.rb +41 -47
  43. data/lib/rvg/embellishable.rb +380 -411
  44. data/lib/rvg/misc.rb +691 -711
  45. data/lib/rvg/paint.rb +43 -47
  46. data/lib/rvg/pathdata.rb +119 -125
  47. data/lib/rvg/rvg.rb +214 -213
  48. data/lib/rvg/stretchable.rb +153 -162
  49. data/lib/rvg/stylable.rb +111 -117
  50. data/lib/rvg/text.rb +169 -180
  51. data/lib/rvg/transformable.rb +121 -127
  52. data/lib/rvg/units.rb +58 -61
  53. data/rmagick.gemspec +36 -16
  54. metadata +188 -365
  55. data/ChangeLog +0 -773
  56. data/Doxyfile +0 -1514
  57. data/README-Mac-OSX.txt +0 -1
  58. data/README.html +0 -10
  59. data/build_tarball.rake +0 -215
  60. data/doc/comtasks.html +0 -287
  61. data/doc/constants.html +0 -1581
  62. data/doc/css/doc.css +0 -299
  63. data/doc/css/popup.css +0 -34
  64. data/doc/css/ref.css +0 -67
  65. data/doc/draw.html +0 -3269
  66. data/doc/ex/InitialCoords.rb +0 -23
  67. data/doc/ex/NewCoordSys.rb +0 -32
  68. data/doc/ex/OrigCoordSys.rb +0 -18
  69. data/doc/ex/PreserveAspectRatio.rb +0 -205
  70. data/doc/ex/RotateScale.rb +0 -37
  71. data/doc/ex/Skew.rb +0 -38
  72. data/doc/ex/Use01.rb +0 -16
  73. data/doc/ex/Use02.rb +0 -21
  74. data/doc/ex/Use03.rb +0 -16
  75. data/doc/ex/ViewBox.rb +0 -33
  76. data/doc/ex/adaptive_threshold.rb +0 -10
  77. data/doc/ex/add_noise.rb +0 -17
  78. data/doc/ex/affine.rb +0 -48
  79. data/doc/ex/affine_transform.rb +0 -20
  80. data/doc/ex/arc.rb +0 -49
  81. data/doc/ex/arcpath.rb +0 -33
  82. data/doc/ex/arcs01.rb +0 -28
  83. data/doc/ex/arcs02.rb +0 -61
  84. data/doc/ex/average.rb +0 -15
  85. data/doc/ex/axes.rb +0 -64
  86. data/doc/ex/baseline_shift01.rb +0 -18
  87. data/doc/ex/bilevel_channel.rb +0 -9
  88. data/doc/ex/blur_image.rb +0 -12
  89. data/doc/ex/border.rb +0 -10
  90. data/doc/ex/bounding_box.rb +0 -44
  91. data/doc/ex/cbezier1.rb +0 -42
  92. data/doc/ex/cbezier2.rb +0 -42
  93. data/doc/ex/cbezier3.rb +0 -42
  94. data/doc/ex/cbezier4.rb +0 -43
  95. data/doc/ex/cbezier5.rb +0 -43
  96. data/doc/ex/cbezier6.rb +0 -53
  97. data/doc/ex/channel.rb +0 -26
  98. data/doc/ex/charcoal.rb +0 -12
  99. data/doc/ex/chop.rb +0 -29
  100. data/doc/ex/circle.rb +0 -33
  101. data/doc/ex/circle01.rb +0 -17
  102. data/doc/ex/clip_path.rb +0 -60
  103. data/doc/ex/coalesce.rb +0 -60
  104. data/doc/ex/color_fill_to_border.rb +0 -29
  105. data/doc/ex/color_floodfill.rb +0 -28
  106. data/doc/ex/color_histogram.rb +0 -48
  107. data/doc/ex/color_reset.rb +0 -11
  108. data/doc/ex/colorize.rb +0 -16
  109. data/doc/ex/colors.rb +0 -64
  110. data/doc/ex/compose_mask.rb +0 -23
  111. data/doc/ex/composite.rb +0 -135
  112. data/doc/ex/composite_layers.rb +0 -53
  113. data/doc/ex/composite_tiled.rb +0 -23
  114. data/doc/ex/contrast.rb +0 -36
  115. data/doc/ex/crop.rb +0 -31
  116. data/doc/ex/crop_with_gravity.rb +0 -46
  117. data/doc/ex/cubic01.rb +0 -45
  118. data/doc/ex/cubic02.rb +0 -94
  119. data/doc/ex/cycle_colormap.rb +0 -21
  120. data/doc/ex/dissolve.rb +0 -13
  121. data/doc/ex/drawcomp.rb +0 -42
  122. data/doc/ex/drop_shadow.rb +0 -60
  123. data/doc/ex/edge.rb +0 -11
  124. data/doc/ex/ellipse.rb +0 -45
  125. data/doc/ex/ellipse01.rb +0 -22
  126. data/doc/ex/emboss.rb +0 -11
  127. data/doc/ex/enhance.rb +0 -28
  128. data/doc/ex/equalize.rb +0 -11
  129. data/doc/ex/evenodd.rb +0 -43
  130. data/doc/ex/fill_pattern.rb +0 -26
  131. data/doc/ex/flatten_images.rb +0 -36
  132. data/doc/ex/flip.rb +0 -11
  133. data/doc/ex/flop.rb +0 -11
  134. data/doc/ex/font_styles.rb +0 -34
  135. data/doc/ex/fonts.rb +0 -20
  136. data/doc/ex/frame.rb +0 -12
  137. data/doc/ex/gaussian_blur.rb +0 -11
  138. data/doc/ex/get_multiline_type_metrics.rb +0 -42
  139. data/doc/ex/get_pixels.rb +0 -48
  140. data/doc/ex/get_type_metrics.rb +0 -146
  141. data/doc/ex/gradientfill.rb +0 -27
  142. data/doc/ex/grav.rb +0 -46
  143. data/doc/ex/gravity.rb +0 -79
  144. data/doc/ex/group.rb +0 -26
  145. data/doc/ex/hatchfill.rb +0 -27
  146. data/doc/ex/image.rb +0 -46
  147. data/doc/ex/images/Apple.miff +0 -0
  148. data/doc/ex/images/Ballerina.jpg +0 -0
  149. data/doc/ex/images/Ballerina3.jpg +0 -0
  150. data/doc/ex/images/Button_0.gif +0 -0
  151. data/doc/ex/images/Button_1.gif +0 -0
  152. data/doc/ex/images/Button_2.gif +0 -0
  153. data/doc/ex/images/Button_3.gif +0 -0
  154. data/doc/ex/images/Button_4.gif +0 -0
  155. data/doc/ex/images/Button_5.gif +0 -0
  156. data/doc/ex/images/Button_6.gif +0 -0
  157. data/doc/ex/images/Button_7.gif +0 -0
  158. data/doc/ex/images/Button_8.gif +0 -0
  159. data/doc/ex/images/Button_9.gif +0 -0
  160. data/doc/ex/images/Button_A.gif +0 -0
  161. data/doc/ex/images/Button_B.gif +0 -0
  162. data/doc/ex/images/Button_C.gif +0 -0
  163. data/doc/ex/images/Button_D.gif +0 -0
  164. data/doc/ex/images/Button_E.gif +0 -0
  165. data/doc/ex/images/Button_F.gif +0 -0
  166. data/doc/ex/images/Button_G.gif +0 -0
  167. data/doc/ex/images/Button_H.gif +0 -0
  168. data/doc/ex/images/Button_I.gif +0 -0
  169. data/doc/ex/images/Button_J.gif +0 -0
  170. data/doc/ex/images/Button_K.gif +0 -0
  171. data/doc/ex/images/Button_L.gif +0 -0
  172. data/doc/ex/images/Button_M.gif +0 -0
  173. data/doc/ex/images/Button_N.gif +0 -0
  174. data/doc/ex/images/Button_O.gif +0 -0
  175. data/doc/ex/images/Button_P.gif +0 -0
  176. data/doc/ex/images/Button_Q.gif +0 -0
  177. data/doc/ex/images/Button_R.gif +0 -0
  178. data/doc/ex/images/Button_S.gif +0 -0
  179. data/doc/ex/images/Button_T.gif +0 -0
  180. data/doc/ex/images/Button_U.gif +0 -0
  181. data/doc/ex/images/Button_V.gif +0 -0
  182. data/doc/ex/images/Button_W.gif +0 -0
  183. data/doc/ex/images/Button_X.gif +0 -0
  184. data/doc/ex/images/Button_Y.gif +0 -0
  185. data/doc/ex/images/Button_Z.gif +0 -0
  186. data/doc/ex/images/Cheetah.jpg +0 -0
  187. data/doc/ex/images/Coffee.wmf +0 -0
  188. data/doc/ex/images/Flower_Hat.jpg +0 -0
  189. data/doc/ex/images/Gold_Statue.jpg +0 -0
  190. data/doc/ex/images/Hot_Air_Balloons.jpg +0 -0
  191. data/doc/ex/images/Hot_Air_Balloons_H.jpg +0 -0
  192. data/doc/ex/images/Leaf.miff +0 -0
  193. data/doc/ex/images/No.wmf +0 -0
  194. data/doc/ex/images/Polynesia.jpg +0 -0
  195. data/doc/ex/images/Red_Rocks.jpg +0 -0
  196. data/doc/ex/images/Rocks_On_Beach.miff +0 -0
  197. data/doc/ex/images/Shorts.jpg +0 -0
  198. data/doc/ex/images/Snake.wmf +0 -0
  199. data/doc/ex/images/Violin.jpg +0 -0
  200. data/doc/ex/images/Yellow_Rose.miff +0 -0
  201. data/doc/ex/images/big-duck.gif +0 -0
  202. data/doc/ex/images/duck.gif +0 -0
  203. data/doc/ex/images/duck0.gif +0 -0
  204. data/doc/ex/images/duck1.gif +0 -0
  205. data/doc/ex/images/duck10.gif +0 -0
  206. data/doc/ex/images/duck11.gif +0 -0
  207. data/doc/ex/images/duck12.gif +0 -0
  208. data/doc/ex/images/duck13.gif +0 -0
  209. data/doc/ex/images/duck14.gif +0 -0
  210. data/doc/ex/images/duck15.gif +0 -0
  211. data/doc/ex/images/duck2.gif +0 -0
  212. data/doc/ex/images/duck3.gif +0 -0
  213. data/doc/ex/images/duck4.gif +0 -0
  214. data/doc/ex/images/duck5.gif +0 -0
  215. data/doc/ex/images/duck6.gif +0 -0
  216. data/doc/ex/images/duck7.gif +0 -0
  217. data/doc/ex/images/duck8.gif +0 -0
  218. data/doc/ex/images/duck9.gif +0 -0
  219. data/doc/ex/images/graydient230x6.gif +0 -0
  220. data/doc/ex/images/logo400x83.gif +0 -0
  221. data/doc/ex/images/model.miff +0 -0
  222. data/doc/ex/images/notimplemented.gif +0 -0
  223. data/doc/ex/images/smile.miff +0 -0
  224. data/doc/ex/images/spin.gif +0 -0
  225. data/doc/ex/implode.rb +0 -34
  226. data/doc/ex/level.rb +0 -11
  227. data/doc/ex/level_colors.rb +0 -11
  228. data/doc/ex/line.rb +0 -42
  229. data/doc/ex/line01.rb +0 -23
  230. data/doc/ex/mask.rb +0 -36
  231. data/doc/ex/matte_fill_to_border.rb +0 -40
  232. data/doc/ex/matte_floodfill.rb +0 -33
  233. data/doc/ex/matte_replace.rb +0 -40
  234. data/doc/ex/median_filter.rb +0 -28
  235. data/doc/ex/modulate.rb +0 -11
  236. data/doc/ex/mono.rb +0 -23
  237. data/doc/ex/morph.rb +0 -26
  238. data/doc/ex/mosaic.rb +0 -35
  239. data/doc/ex/motion_blur.rb +0 -11
  240. data/doc/ex/negate.rb +0 -11
  241. data/doc/ex/negate_channel.rb +0 -9
  242. data/doc/ex/nested_rvg.rb +0 -21
  243. data/doc/ex/nonzero.rb +0 -43
  244. data/doc/ex/normalize.rb +0 -11
  245. data/doc/ex/oil_paint.rb +0 -11
  246. data/doc/ex/opacity.rb +0 -37
  247. data/doc/ex/ordered_dither.rb +0 -11
  248. data/doc/ex/path.rb +0 -64
  249. data/doc/ex/pattern1.rb +0 -25
  250. data/doc/ex/pattern2.rb +0 -26
  251. data/doc/ex/polaroid.rb +0 -28
  252. data/doc/ex/polygon.rb +0 -24
  253. data/doc/ex/polygon01.rb +0 -23
  254. data/doc/ex/polyline.rb +0 -23
  255. data/doc/ex/polyline01.rb +0 -23
  256. data/doc/ex/posterize.rb +0 -8
  257. data/doc/ex/preview.rb +0 -9
  258. data/doc/ex/qbezierpath.rb +0 -52
  259. data/doc/ex/quad01.rb +0 -36
  260. data/doc/ex/quantize-m.rb +0 -25
  261. data/doc/ex/radial_blur.rb +0 -9
  262. data/doc/ex/raise.rb +0 -8
  263. data/doc/ex/random_threshold_channel.rb +0 -13
  264. data/doc/ex/rect01.rb +0 -15
  265. data/doc/ex/rect02.rb +0 -22
  266. data/doc/ex/rectangle.rb +0 -35
  267. data/doc/ex/reduce_noise.rb +0 -28
  268. data/doc/ex/remap.rb +0 -12
  269. data/doc/ex/remap_images.rb +0 -21
  270. data/doc/ex/resize_to_fill.rb +0 -10
  271. data/doc/ex/resize_to_fit.rb +0 -10
  272. data/doc/ex/roll.rb +0 -9
  273. data/doc/ex/rotate.rb +0 -45
  274. data/doc/ex/rotate_f.rb +0 -14
  275. data/doc/ex/roundrect.rb +0 -34
  276. data/doc/ex/rubyname.rb +0 -30
  277. data/doc/ex/rvg_clippath.rb +0 -14
  278. data/doc/ex/rvg_linecap.rb +0 -43
  279. data/doc/ex/rvg_linejoin.rb +0 -41
  280. data/doc/ex/rvg_opacity.rb +0 -19
  281. data/doc/ex/rvg_pattern.rb +0 -26
  282. data/doc/ex/rvg_stroke_dasharray.rb +0 -12
  283. data/doc/ex/segment.rb +0 -11
  284. data/doc/ex/sepiatone.rb +0 -8
  285. data/doc/ex/shade.rb +0 -11
  286. data/doc/ex/shadow.rb +0 -31
  287. data/doc/ex/shave.rb +0 -15
  288. data/doc/ex/shear.rb +0 -10
  289. data/doc/ex/sketch.rb +0 -18
  290. data/doc/ex/skewx.rb +0 -52
  291. data/doc/ex/skewy.rb +0 -47
  292. data/doc/ex/smile.rb +0 -125
  293. data/doc/ex/solarize.rb +0 -11
  294. data/doc/ex/sparse_color.rb +0 -55
  295. data/doc/ex/splice.rb +0 -9
  296. data/doc/ex/spread.rb +0 -11
  297. data/doc/ex/stegano.rb +0 -55
  298. data/doc/ex/stroke_dasharray.rb +0 -43
  299. data/doc/ex/stroke_fill.rb +0 -11
  300. data/doc/ex/stroke_linecap.rb +0 -44
  301. data/doc/ex/stroke_linejoin.rb +0 -48
  302. data/doc/ex/stroke_width.rb +0 -49
  303. data/doc/ex/swirl.rb +0 -17
  304. data/doc/ex/text.rb +0 -37
  305. data/doc/ex/text01.rb +0 -17
  306. data/doc/ex/text_align.rb +0 -36
  307. data/doc/ex/text_antialias.rb +0 -38
  308. data/doc/ex/text_styles.rb +0 -21
  309. data/doc/ex/text_undercolor.rb +0 -28
  310. data/doc/ex/texture_fill_to_border.rb +0 -34
  311. data/doc/ex/texture_floodfill.rb +0 -32
  312. data/doc/ex/texturefill.rb +0 -25
  313. data/doc/ex/threshold.rb +0 -13
  314. data/doc/ex/to_blob.rb +0 -14
  315. data/doc/ex/translate.rb +0 -39
  316. data/doc/ex/transparent.rb +0 -38
  317. data/doc/ex/transpose.rb +0 -9
  318. data/doc/ex/transverse.rb +0 -9
  319. data/doc/ex/tref01.rb +0 -25
  320. data/doc/ex/triangle01.rb +0 -16
  321. data/doc/ex/trim.rb +0 -24
  322. data/doc/ex/tspan01.rb +0 -18
  323. data/doc/ex/tspan02.rb +0 -19
  324. data/doc/ex/tspan03.rb +0 -21
  325. data/doc/ex/unsharp_mask.rb +0 -28
  326. data/doc/ex/viewex.rb +0 -35
  327. data/doc/ex/vignette.rb +0 -12
  328. data/doc/ex/watermark.rb +0 -28
  329. data/doc/ex/wave.rb +0 -9
  330. data/doc/ex/wet_floor.rb +0 -59
  331. data/doc/ex/writing_mode01.rb +0 -27
  332. data/doc/ex/writing_mode02.rb +0 -26
  333. data/doc/ilist.html +0 -2056
  334. data/doc/image1.html +0 -4680
  335. data/doc/image2.html +0 -3665
  336. data/doc/image3.html +0 -4522
  337. data/doc/imageattrs.html +0 -1638
  338. data/doc/imusage.html +0 -514
  339. data/doc/index.html +0 -416
  340. data/doc/info.html +0 -1499
  341. data/doc/magick.html +0 -565
  342. data/doc/optequiv.html +0 -2435
  343. data/doc/rvg.html +0 -975
  344. data/doc/rvgclip.html +0 -248
  345. data/doc/rvggroup.html +0 -305
  346. data/doc/rvgimage.html +0 -289
  347. data/doc/rvgpattern.html +0 -475
  348. data/doc/rvgshape.html +0 -406
  349. data/doc/rvgstyle.html +0 -270
  350. data/doc/rvgtext.html +0 -465
  351. data/doc/rvgtspan.html +0 -238
  352. data/doc/rvgtut.html +0 -530
  353. data/doc/rvguse.html +0 -145
  354. data/doc/rvgxform.html +0 -294
  355. data/doc/scripts/doc.js +0 -22
  356. data/doc/scripts/stripeTables.js +0 -23
  357. data/doc/struct.html +0 -1339
  358. data/doc/usage.html +0 -1621
  359. data/examples/constitute.rb +0 -7
  360. data/examples/crop_with_gravity.rb +0 -46
  361. data/examples/demo.rb +0 -324
  362. data/examples/describe.rb +0 -44
  363. data/examples/find_similar_region.rb +0 -34
  364. data/examples/histogram.rb +0 -325
  365. data/examples/identify.rb +0 -187
  366. data/examples/image_opacity.rb +0 -29
  367. data/examples/import_export.rb +0 -31
  368. data/examples/pattern_fill.rb +0 -38
  369. data/examples/rotating_text.rb +0 -45
  370. data/examples/spinner.rb +0 -50
  371. data/examples/thumbnail.rb +0 -65
  372. data/examples/vignette.rb +0 -79
  373. data/ext/RMagick/MANIFEST +0 -358
  374. data/lib/RMagick.rb +0 -1962
  375. data/metaconfig +0 -7
  376. data/post-clean.rb +0 -12
  377. data/post-install.rb +0 -50
  378. data/post-setup.rb +0 -254
  379. data/setup.rb +0 -1585
  380. data/uninstall.rb +0 -76
@@ -0,0 +1,1922 @@
1
+ # $Id: RMagick.rb,v 1.84 2009/09/15 22:08:41 rmagick Exp $
2
+ #==============================================================================
3
+ # Copyright (C) 2009 by Timothy P. Hunter
4
+ # Name: RMagick.rb
5
+ # Author: Tim Hunter
6
+ # Purpose: Extend Ruby to interface with ImageMagick.
7
+ # Notes: RMagick2.so defines the classes. The code below adds methods
8
+ # to the classes.
9
+ #==============================================================================
10
+
11
+ if RUBY_PLATFORM =~ /mingw/i
12
+ begin
13
+ require 'ruby_installer'
14
+ ENV['PATH'].split(File::PATH_SEPARATOR).grep(/ImageMagick/i).each do |path|
15
+ RubyInstaller::Runtime.add_dll_directory(path)
16
+ end
17
+ rescue LoadError
18
+ end
19
+ end
20
+
21
+ require 'English'
22
+ require 'observer'
23
+ require 'RMagick2.so'
24
+
25
+ module Magick
26
+ @formats = nil
27
+ @trace_proc = nil
28
+ @exit_block_set_up = nil
29
+ IMAGEMAGICK_VERSION = Magick::Magick_version.split[1].split('-').first
30
+
31
+ class << self
32
+ # Describes the image formats supported by ImageMagick.
33
+ # If the optional block is present, calls the block once for each image format.
34
+ # The first argument, +k+, is the format name. The second argument, +v+, is the
35
+ # properties string described below.
36
+ #
37
+ # - +B+ is "*" if the format has native blob support, or " " otherwise.
38
+ # - +R+ is "r" if ImageMagick can read that format, or "-" otherwise.
39
+ # - +W+ is "w" if ImageMagick can write that format, or "-" otherwise.
40
+ # - +A+ is "+" if the format supports multi-image files, or "-" otherwise.
41
+ #
42
+ # @overload formats
43
+ # @return [Hash] the formats hash
44
+ #
45
+ # @overload formats
46
+ # @yield [k, v]
47
+ # @yieldparam k [String] the format name
48
+ # @yieldparam v [String] the properties string
49
+ # @return [Magick]
50
+ #
51
+ # @example
52
+ # p Magick.formats
53
+ # => {"3FR"=>" r-+", "3G2"=>" r-+", "3GP"=>" r-+", "A"=>"*rw+",
54
+ # ...
55
+ def formats
56
+ @formats ||= init_formats
57
+
58
+ if block_given?
59
+ @formats.each { |k, v| yield k, v }
60
+ self
61
+ else
62
+ @formats
63
+ end
64
+ end
65
+
66
+ # If the Magick module attribute +trace_proc+ is set to a Proc object,
67
+ # RMagick calls the proc whenever an image is created or destroyed.
68
+ #
69
+ # You can use this proc to keep track of which images your program has created
70
+ # and which have been destroyed.
71
+ #
72
+ # @param p [Proc] The proc object.
73
+ # The following value will be passed into the proc object.
74
+ # - +which+ - A symbol that indicates which operation the proc is being called for.
75
+ # If the proc is called for an image creation, the value is +:c+.
76
+ # If called for an image destruction, the value is +:d+.
77
+ # - +description+ - A string describing the image. This is the same string that
78
+ # would be returned by calling the image's inspect method.
79
+ # - +id+ - A unique identifier for the image. This identifier is not the same as the object's +object_id+.
80
+ # - +method+ - The name of the method responsible for creating or destroying the image.
81
+ #
82
+ # @example
83
+ # Magick.trace_proc = proc do |which, description, id, method|
84
+ # ...
85
+ # end
86
+ def trace_proc=(p)
87
+ m = Mutex.new
88
+ m.synchronize do
89
+ if @trace_proc.nil? && !p.nil? && !@exit_block_set_up
90
+ at_exit { @trace_proc = nil }
91
+ @exit_block_set_up = true
92
+ end
93
+
94
+ @trace_proc = p
95
+ end
96
+ end
97
+ end
98
+
99
+ # Geometry class and related enum constants
100
+ class GeometryValue < Enum
101
+ # no methods
102
+ end
103
+
104
+ PercentGeometry = GeometryValue.new(:PercentGeometry, 1).freeze
105
+ AspectGeometry = GeometryValue.new(:AspectGeometry, 2).freeze
106
+ LessGeometry = GeometryValue.new(:LessGeometry, 3).freeze
107
+ GreaterGeometry = GeometryValue.new(:GreaterGeometry, 4).freeze
108
+ AreaGeometry = GeometryValue.new(:AreaGeometry, 5).freeze
109
+ MinimumGeometry = GeometryValue.new(:MinimumGeometry, 6).freeze
110
+
111
+ class Geometry
112
+ FLAGS = ['', '%', '!', '<', '>', '@', '^']
113
+ RFLAGS = {
114
+ '%' => PercentGeometry,
115
+ '!' => AspectGeometry,
116
+ '<' => LessGeometry,
117
+ '>' => GreaterGeometry,
118
+ '@' => AreaGeometry,
119
+ '^' => MinimumGeometry
120
+ }
121
+
122
+ attr_accessor :width, :height, :x, :y, :flag
123
+
124
+ def initialize(width = nil, height = nil, x = nil, y = nil, flag = nil)
125
+ raise(ArgumentError, "width set to #{width}") if width.is_a? GeometryValue
126
+ raise(ArgumentError, "height set to #{height}") if height.is_a? GeometryValue
127
+ raise(ArgumentError, "x set to #{x}") if x.is_a? GeometryValue
128
+ raise(ArgumentError, "y set to #{y}") if y.is_a? GeometryValue
129
+
130
+ # Support floating-point width and height arguments so Geometry
131
+ # objects can be used to specify Image#density= arguments.
132
+ if width.nil?
133
+ @width = 0
134
+ elsif width.to_f >= 0.0
135
+ @width = width.to_f
136
+ else
137
+ Kernel.raise ArgumentError, "width must be >= 0: #{width}"
138
+ end
139
+ if height.nil?
140
+ @height = 0
141
+ elsif height.to_f >= 0.0
142
+ @height = height.to_f
143
+ else
144
+ Kernel.raise ArgumentError, "height must be >= 0: #{height}"
145
+ end
146
+
147
+ @x = x.to_i
148
+ @y = y.to_i
149
+ @flag = flag
150
+ end
151
+
152
+ # Construct an object from a geometry string
153
+ W = /(\d+\.\d+%?)|(\d*%?)/
154
+ H = W
155
+ X = /(?:([-+]\d+))?/
156
+ Y = X
157
+ RE = /\A#{W}x?#{H}#{X}#{Y}([!<>@\^]?)\Z/
158
+
159
+ def self.from_s(str)
160
+ m = RE.match(str)
161
+ if m
162
+ width = (m[1] || m[2]).to_f
163
+ height = (m[3] || m[4]).to_f
164
+ x = m[5].to_i
165
+ y = m[6].to_i
166
+ flag = RFLAGS[m[7]]
167
+ else
168
+ Kernel.raise ArgumentError, 'invalid geometry format'
169
+ end
170
+ flag = PercentGeometry if str['%']
171
+ Geometry.new(width, height, x, y, flag)
172
+ end
173
+
174
+ # Convert object to a geometry string
175
+ def to_s
176
+ str = ''
177
+ if @width > 0
178
+ fmt = @width.truncate == @width ? '%d' : '%.2f'
179
+ str << sprintf(fmt, @width)
180
+ str << '%' if @flag == PercentGeometry
181
+ end
182
+
183
+ str << 'x' if (@width > 0 && @flag != PercentGeometry) || (@height > 0)
184
+
185
+ if @height > 0
186
+ fmt = @height.truncate == @height ? '%d' : '%.2f'
187
+ str << sprintf(fmt, @height)
188
+ str << '%' if @flag == PercentGeometry
189
+ end
190
+ str << sprintf('%+d%+d', @x, @y) if @x != 0 || @y != 0
191
+ str << FLAGS[@flag.to_i] if @flag != PercentGeometry
192
+ str
193
+ end
194
+ end
195
+
196
+ class Draw
197
+ # Thse hashes are used to map Magick constant
198
+ # values to the strings used in the primitives.
199
+ ALIGN_TYPE_NAMES = {
200
+ LeftAlign.to_i => 'left',
201
+ RightAlign.to_i => 'right',
202
+ CenterAlign.to_i => 'center'
203
+ }.freeze
204
+ ANCHOR_TYPE_NAMES = {
205
+ StartAnchor.to_i => 'start',
206
+ MiddleAnchor.to_i => 'middle',
207
+ EndAnchor.to_i => 'end'
208
+ }.freeze
209
+ DECORATION_TYPE_NAMES = {
210
+ NoDecoration.to_i => 'none',
211
+ UnderlineDecoration.to_i => 'underline',
212
+ OverlineDecoration.to_i => 'overline',
213
+ LineThroughDecoration.to_i => 'line-through'
214
+ }.freeze
215
+ FONT_WEIGHT_NAMES = {
216
+ AnyWeight.to_i => 'all',
217
+ NormalWeight.to_i => 'normal',
218
+ BoldWeight.to_i => 'bold',
219
+ BolderWeight.to_i => 'bolder',
220
+ LighterWeight.to_i => 'lighter'
221
+ }.freeze
222
+ GRAVITY_NAMES = {
223
+ NorthWestGravity.to_i => 'northwest',
224
+ NorthGravity.to_i => 'north',
225
+ NorthEastGravity.to_i => 'northeast',
226
+ WestGravity.to_i => 'west',
227
+ CenterGravity.to_i => 'center',
228
+ EastGravity.to_i => 'east',
229
+ SouthWestGravity.to_i => 'southwest',
230
+ SouthGravity.to_i => 'south',
231
+ SouthEastGravity.to_i => 'southeast'
232
+ }.freeze
233
+ PAINT_METHOD_NAMES = {
234
+ PointMethod.to_i => 'point',
235
+ ReplaceMethod.to_i => 'replace',
236
+ FloodfillMethod.to_i => 'floodfill',
237
+ FillToBorderMethod.to_i => 'filltoborder',
238
+ ResetMethod.to_i => 'reset'
239
+ }.freeze
240
+ STRETCH_TYPE_NAMES = {
241
+ NormalStretch.to_i => 'normal',
242
+ UltraCondensedStretch.to_i => 'ultra-condensed',
243
+ ExtraCondensedStretch.to_i => 'extra-condensed',
244
+ CondensedStretch.to_i => 'condensed',
245
+ SemiCondensedStretch.to_i => 'semi-condensed',
246
+ SemiExpandedStretch.to_i => 'semi-expanded',
247
+ ExpandedStretch.to_i => 'expanded',
248
+ ExtraExpandedStretch.to_i => 'extra-expanded',
249
+ UltraExpandedStretch.to_i => 'ultra-expanded',
250
+ AnyStretch.to_i => 'all'
251
+ }.freeze
252
+ STYLE_TYPE_NAMES = {
253
+ NormalStyle.to_i => 'normal',
254
+ ItalicStyle.to_i => 'italic',
255
+ ObliqueStyle.to_i => 'oblique',
256
+ AnyStyle.to_i => 'all'
257
+ }.freeze
258
+
259
+ private
260
+
261
+ def enquote(str)
262
+ if str.length > 2 && /\A(?:\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})\z/.match(str)
263
+ str
264
+ else
265
+ '"' + str + '"'
266
+ end
267
+ end
268
+
269
+ def check_opacity(opacity)
270
+ return if opacity.is_a?(String) && opacity['%']
271
+
272
+ value = Float(opacity)
273
+ Kernel.raise ArgumentError, 'opacity must be >= 0 and <= 1.0' if value < 0 || value > 1.0
274
+ end
275
+
276
+ public
277
+
278
+ # Apply coordinate transformations to support scaling (s), rotation (r),
279
+ # and translation (t). Angles are specified in radians.
280
+ def affine(sx, rx, ry, sy, tx, ty)
281
+ primitive 'affine ' + sprintf('%g,%g,%g,%g,%g,%g', sx, rx, ry, sy, tx, ty)
282
+ end
283
+
284
+ # Set alpha (make transparent) in image according to the specified
285
+ # colorization rule
286
+ def alpha(x, y, method)
287
+ Kernel.raise ArgumentError, 'Unknown paint method' unless PAINT_METHOD_NAMES.key?(method.to_i)
288
+ name = Gem::Version.new(Magick::IMAGEMAGICK_VERSION) > Gem::Version.new('7.0.0') ? 'alpha ' : 'matte '
289
+ primitive name + sprintf('%g,%g, %s', x, y, PAINT_METHOD_NAMES[method.to_i])
290
+ end
291
+
292
+ # Draw an arc.
293
+ def arc(start_x, start_y, end_x, end_y, start_degrees, end_degrees)
294
+ primitive 'arc ' + sprintf(
295
+ '%g,%g %g,%g %g,%g',
296
+ start_x, start_y, end_x, end_y, start_degrees, end_degrees
297
+ )
298
+ end
299
+
300
+ # Draw a bezier curve.
301
+ def bezier(*points)
302
+ if points.length.zero?
303
+ Kernel.raise ArgumentError, 'no points specified'
304
+ elsif points.length.odd?
305
+ Kernel.raise ArgumentError, 'odd number of arguments specified'
306
+ end
307
+ primitive 'bezier ' + points.map! { |x| sprintf('%g', x) }.join(',')
308
+ end
309
+
310
+ # Draw a circle
311
+ def circle(origin_x, origin_y, perim_x, perim_y)
312
+ primitive 'circle ' + sprintf('%g,%g %g,%g', origin_x, origin_y, perim_x, perim_y)
313
+ end
314
+
315
+ # Invoke a clip-path defined by def_clip_path.
316
+ def clip_path(name)
317
+ primitive "clip-path #{name}"
318
+ end
319
+
320
+ # Define the clipping rule.
321
+ def clip_rule(rule)
322
+ Kernel.raise ArgumentError, "Unknown clipping rule #{rule}" unless %w[evenodd nonzero].include?(rule.downcase)
323
+ primitive "clip-rule #{rule}"
324
+ end
325
+
326
+ # Define the clip units
327
+ def clip_units(unit)
328
+ Kernel.raise ArgumentError, "Unknown clip unit #{unit}" unless %w[userspace userspaceonuse objectboundingbox].include?(unit.downcase)
329
+ primitive "clip-units #{unit}"
330
+ end
331
+
332
+ # Set color in image according to specified colorization rule. Rule is one of
333
+ # point, replace, floodfill, filltoborder,reset
334
+ def color(x, y, method)
335
+ Kernel.raise ArgumentError, "Unknown PaintMethod: #{method}" unless PAINT_METHOD_NAMES.key?(method.to_i)
336
+ primitive 'color ' + sprintf('%g,%g,%s', x, y, PAINT_METHOD_NAMES[method.to_i])
337
+ end
338
+
339
+ # Specify EITHER the text decoration (none, underline, overline,
340
+ # line-through) OR the text solid background color (any color name or spec)
341
+ def decorate(decoration)
342
+ if DECORATION_TYPE_NAMES.key?(decoration.to_i)
343
+ primitive "decorate #{DECORATION_TYPE_NAMES[decoration.to_i]}"
344
+ else
345
+ primitive "decorate #{enquote(decoration)}"
346
+ end
347
+ end
348
+
349
+ # Define a clip-path. A clip-path is a sequence of primitives
350
+ # bracketed by the "push clip-path <name>" and "pop clip-path"
351
+ # primitives. Upon advice from the IM guys, we also bracket
352
+ # the clip-path primitives with "push(pop) defs" and "push
353
+ # (pop) graphic-context".
354
+ def define_clip_path(name)
355
+ push('defs')
356
+ push("clip-path \"#{name}\"")
357
+ push('graphic-context')
358
+ yield
359
+ ensure
360
+ pop('graphic-context')
361
+ pop('clip-path')
362
+ pop('defs')
363
+ end
364
+
365
+ # Draw an ellipse
366
+ def ellipse(origin_x, origin_y, width, height, arc_start, arc_end)
367
+ primitive 'ellipse ' + sprintf(
368
+ '%g,%g %g,%g %g,%g',
369
+ origin_x, origin_y, width, height, arc_start, arc_end
370
+ )
371
+ end
372
+
373
+ # Let anything through, but the only defined argument
374
+ # is "UTF-8". All others are apparently ignored.
375
+ def encoding(encoding)
376
+ primitive "encoding #{encoding}"
377
+ end
378
+
379
+ # Specify object fill, a color name or pattern name
380
+ def fill(colorspec)
381
+ primitive "fill #{enquote(colorspec)}"
382
+ end
383
+ alias fill_color fill
384
+ alias fill_pattern fill
385
+
386
+ # Specify fill opacity (use "xx%" to indicate percentage)
387
+ def fill_opacity(opacity)
388
+ check_opacity(opacity)
389
+ primitive "fill-opacity #{opacity}"
390
+ end
391
+
392
+ def fill_rule(rule)
393
+ Kernel.raise ArgumentError, "Unknown fill rule #{rule}" unless %w[evenodd nonzero].include?(rule.downcase)
394
+ primitive "fill-rule #{rule}"
395
+ end
396
+
397
+ # Specify text drawing font
398
+ def font(name)
399
+ primitive "font \'#{name}\'"
400
+ end
401
+
402
+ def font_family(name)
403
+ primitive "font-family \'#{name}\'"
404
+ end
405
+
406
+ def font_stretch(stretch)
407
+ Kernel.raise ArgumentError, 'Unknown stretch type' unless STRETCH_TYPE_NAMES.key?(stretch.to_i)
408
+ primitive "font-stretch #{STRETCH_TYPE_NAMES[stretch.to_i]}"
409
+ end
410
+
411
+ def font_style(style)
412
+ Kernel.raise ArgumentError, 'Unknown style type' unless STYLE_TYPE_NAMES.key?(style.to_i)
413
+ primitive "font-style #{STYLE_TYPE_NAMES[style.to_i]}"
414
+ end
415
+
416
+ # The font weight argument can be either a font weight
417
+ # constant or [100,200,...,900]
418
+ def font_weight(weight)
419
+ if weight.is_a?(WeightType)
420
+ primitive "font-weight #{FONT_WEIGHT_NAMES[weight.to_i]}"
421
+ else
422
+ primitive "font-weight #{Integer(weight)}"
423
+ end
424
+ end
425
+
426
+ # Specify the text positioning gravity, one of:
427
+ # NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast
428
+ def gravity(grav)
429
+ Kernel.raise ArgumentError, 'Unknown text positioning gravity' unless GRAVITY_NAMES.key?(grav.to_i)
430
+ primitive "gravity #{GRAVITY_NAMES[grav.to_i]}"
431
+ end
432
+
433
+ def image(composite, x, y, width, height, image_file_path)
434
+ Kernel.raise ArgumentError, 'Unknown composite' unless composite.is_a?(CompositeOperator)
435
+ composite_name = composite.to_s.sub!('CompositeOp', '')
436
+ primitive 'image ' + sprintf('%s %g,%g %g,%g %s', composite_name, x, y, width, height, enquote(image_file_path))
437
+ end
438
+
439
+ # IM 6.5.5-8 and later
440
+ def interline_spacing(space)
441
+ begin
442
+ Float(space)
443
+ rescue ArgumentError
444
+ Kernel.raise ArgumentError, 'invalid value for interline_spacing'
445
+ rescue TypeError
446
+ Kernel.raise TypeError, "can't convert #{space.class} into Float"
447
+ end
448
+ primitive "interline-spacing #{space}"
449
+ end
450
+
451
+ # IM 6.4.8-3 and later
452
+ def interword_spacing(space)
453
+ begin
454
+ Float(space)
455
+ rescue ArgumentError
456
+ Kernel.raise ArgumentError, 'invalid value for interword_spacing'
457
+ rescue TypeError
458
+ Kernel.raise TypeError, "can't convert #{space.class} into Float"
459
+ end
460
+ primitive "interword-spacing #{space}"
461
+ end
462
+
463
+ # IM 6.4.8-3 and later
464
+ def kerning(space)
465
+ begin
466
+ Float(space)
467
+ rescue ArgumentError
468
+ Kernel.raise ArgumentError, 'invalid value for kerning'
469
+ rescue TypeError
470
+ Kernel.raise TypeError, "can't convert #{space.class} into Float"
471
+ end
472
+ primitive "kerning #{space}"
473
+ end
474
+
475
+ # Draw a line
476
+ def line(start_x, start_y, end_x, end_y)
477
+ primitive 'line ' + sprintf('%g,%g %g,%g', start_x, start_y, end_x, end_y)
478
+ end
479
+
480
+ # Specify drawing fill and stroke opacities. If the value is a string
481
+ # ending with a %, the number will be multiplied by 0.01.
482
+ def opacity(opacity)
483
+ check_opacity(opacity)
484
+ primitive "opacity #{opacity}"
485
+ end
486
+
487
+ # Draw using SVG-compatible path drawing commands. Note that the
488
+ # primitive requires that the commands be surrounded by quotes or
489
+ # apostrophes. Here we simply use apostrophes.
490
+ def path(cmds)
491
+ primitive "path '" + cmds + "'"
492
+ end
493
+
494
+ # Define a pattern. In the block, call primitive methods to
495
+ # draw the pattern. Reference the pattern by using its name
496
+ # as the argument to the 'fill' or 'stroke' methods
497
+ def pattern(name, x, y, width, height)
498
+ push('defs')
499
+ push("pattern #{name} " + sprintf('%g %g %g %g', x, y, width, height))
500
+ push('graphic-context')
501
+ yield
502
+ ensure
503
+ pop('graphic-context')
504
+ pop('pattern')
505
+ pop('defs')
506
+ end
507
+
508
+ # Set point to fill color.
509
+ def point(x, y)
510
+ primitive 'point ' + sprintf('%g,%g', x, y)
511
+ end
512
+
513
+ # Specify the font size in points. Yes, the primitive is "font-size" but
514
+ # in other places this value is called the "pointsize". Give it both names.
515
+ def pointsize(points)
516
+ primitive 'font-size ' + sprintf('%g', points)
517
+ end
518
+ alias font_size pointsize
519
+
520
+ # Draw a polygon
521
+ def polygon(*points)
522
+ if points.length.zero?
523
+ Kernel.raise ArgumentError, 'no points specified'
524
+ elsif points.length.odd?
525
+ Kernel.raise ArgumentError, 'odd number of points specified'
526
+ end
527
+ primitive 'polygon ' + points.map! { |x| sprintf('%g', x) }.join(',')
528
+ end
529
+
530
+ # Draw a polyline
531
+ def polyline(*points)
532
+ if points.length.zero?
533
+ Kernel.raise ArgumentError, 'no points specified'
534
+ elsif points.length.odd?
535
+ Kernel.raise ArgumentError, 'odd number of points specified'
536
+ end
537
+ primitive 'polyline ' + points.map! { |x| sprintf('%g', x) }.join(',')
538
+ end
539
+
540
+ # Return to the previously-saved set of whatever
541
+ # pop('graphic-context') (the default if no arguments)
542
+ # pop('defs')
543
+ # pop('gradient')
544
+ # pop('pattern')
545
+
546
+ def pop(*what)
547
+ if what.length.zero?
548
+ primitive 'pop graphic-context'
549
+ else
550
+ # to_s allows a Symbol to be used instead of a String
551
+ primitive 'pop ' + what.map(&:to_s).join(' ')
552
+ end
553
+ end
554
+
555
+ # Push the current set of drawing options. Also you can use
556
+ # push('graphic-context') (the default if no arguments)
557
+ # push('defs')
558
+ # push('gradient')
559
+ # push('pattern')
560
+ def push(*what)
561
+ if what.length.zero?
562
+ primitive 'push graphic-context'
563
+ else
564
+ # to_s allows a Symbol to be used instead of a String
565
+ primitive 'push ' + what.map(&:to_s).join(' ')
566
+ end
567
+ end
568
+
569
+ # Draw a rectangle
570
+ def rectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
571
+ primitive 'rectangle ' + sprintf(
572
+ '%g,%g %g,%g',
573
+ upper_left_x, upper_left_y, lower_right_x, lower_right_y
574
+ )
575
+ end
576
+
577
+ # Specify coordinate space rotation. "angle" is measured in degrees
578
+ def rotate(angle)
579
+ primitive 'rotate ' + sprintf('%g', angle)
580
+ end
581
+
582
+ # Draw a rectangle with rounded corners
583
+ def roundrectangle(center_x, center_y, width, height, corner_width, corner_height)
584
+ primitive 'roundrectangle ' + sprintf(
585
+ '%g,%g,%g,%g,%g,%g',
586
+ center_x, center_y, width, height, corner_width, corner_height
587
+ )
588
+ end
589
+
590
+ # Specify scaling to be applied to coordinate space on subsequent drawing commands.
591
+ def scale(x, y)
592
+ primitive 'scale ' + sprintf('%g,%g', x, y)
593
+ end
594
+
595
+ def skewx(angle)
596
+ primitive 'skewX ' + sprintf('%g', angle)
597
+ end
598
+
599
+ def skewy(angle)
600
+ primitive 'skewY ' + sprintf('%g', angle)
601
+ end
602
+
603
+ # Specify the object stroke, a color name or pattern name.
604
+ def stroke(colorspec)
605
+ primitive "stroke #{enquote(colorspec)}"
606
+ end
607
+ alias stroke_color stroke
608
+ alias stroke_pattern stroke
609
+
610
+ # Specify if stroke should be antialiased or not
611
+ def stroke_antialias(bool)
612
+ bool = bool ? '1' : '0'
613
+ primitive "stroke-antialias #{bool}"
614
+ end
615
+
616
+ # Specify a stroke dash pattern
617
+ def stroke_dasharray(*list)
618
+ if list.length.zero?
619
+ primitive 'stroke-dasharray none'
620
+ else
621
+ list.each do |x|
622
+ Kernel.raise ArgumentError, "dash array elements must be > 0 (#{x} given)" if x <= 0
623
+ end
624
+ primitive "stroke-dasharray #{list.join(',')}"
625
+ end
626
+ end
627
+
628
+ # Specify the initial offset in the dash pattern
629
+ def stroke_dashoffset(value = 0)
630
+ primitive 'stroke-dashoffset ' + sprintf('%g', value)
631
+ end
632
+
633
+ def stroke_linecap(value)
634
+ Kernel.raise ArgumentError, "Unknown linecap type: #{value}" unless %w[butt round square].include?(value.downcase)
635
+ primitive "stroke-linecap #{value}"
636
+ end
637
+
638
+ def stroke_linejoin(value)
639
+ Kernel.raise ArgumentError, "Unknown linejoin type: #{value}" unless %w[round miter bevel].include?(value.downcase)
640
+ primitive "stroke-linejoin #{value}"
641
+ end
642
+
643
+ def stroke_miterlimit(value)
644
+ Kernel.raise ArgumentError, 'miterlimit must be >= 1' if value < 1
645
+ primitive "stroke-miterlimit #{value}"
646
+ end
647
+
648
+ # Specify opacity of stroke drawing color
649
+ # (use "xx%" to indicate percentage)
650
+ def stroke_opacity(opacity)
651
+ check_opacity(opacity)
652
+ primitive "stroke-opacity #{opacity}"
653
+ end
654
+
655
+ # Specify stroke (outline) width in pixels.
656
+ def stroke_width(pixels)
657
+ primitive 'stroke-width ' + sprintf('%g', pixels)
658
+ end
659
+
660
+ # Draw text at position x,y. Add quotes to text that is not already quoted.
661
+ def text(x, y, text)
662
+ Kernel.raise ArgumentError, 'missing text argument' if text.to_s.empty?
663
+ if text.length > 2 && /\A(?:\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})\z/.match(text)
664
+ # text already quoted
665
+ elsif !text['\'']
666
+ text = '\'' + text + '\''
667
+ elsif !text['"']
668
+ text = '"' + text + '"'
669
+ elsif !(text['{'] || text['}'])
670
+ text = '{' + text + '}'
671
+ else
672
+ # escape existing braces, surround with braces
673
+ text = '{' + text.gsub(/[}]/) { |b| '\\' + b } + '}'
674
+ end
675
+ primitive 'text ' + sprintf('%g,%g %s', x, y, text)
676
+ end
677
+
678
+ # Specify text alignment relative to a given point
679
+ def text_align(alignment)
680
+ Kernel.raise ArgumentError, "Unknown alignment constant: #{alignment}" unless ALIGN_TYPE_NAMES.key?(alignment.to_i)
681
+ primitive "text-align #{ALIGN_TYPE_NAMES[alignment.to_i]}"
682
+ end
683
+
684
+ # SVG-compatible version of text_align
685
+ def text_anchor(anchor)
686
+ Kernel.raise ArgumentError, "Unknown anchor constant: #{anchor}" unless ANCHOR_TYPE_NAMES.key?(anchor.to_i)
687
+ primitive "text-anchor #{ANCHOR_TYPE_NAMES[anchor.to_i]}"
688
+ end
689
+
690
+ # Specify if rendered text is to be antialiased.
691
+ def text_antialias(boolean)
692
+ boolean = boolean ? '1' : '0'
693
+ primitive "text-antialias #{boolean}"
694
+ end
695
+
696
+ # Specify color underneath text
697
+ def text_undercolor(color)
698
+ primitive "text-undercolor #{enquote(color)}"
699
+ end
700
+
701
+ # Specify center of coordinate space to use for subsequent drawing
702
+ # commands.
703
+ def translate(x, y)
704
+ primitive 'translate ' + sprintf('%g,%g', x, y)
705
+ end
706
+ end # class Magick::Draw
707
+
708
+ # Define IPTC record number:dataset tags for use with Image#get_iptc_dataset
709
+ module IPTC
710
+ module Envelope
711
+ Model_Version = '1:00'
712
+ Destination = '1:05'
713
+ File_Format = '1:20'
714
+ File_Format_Version = '1:22'
715
+ Service_Identifier = '1:30'
716
+ Envelope_Number = '1:40'
717
+ Product_ID = '1:50'
718
+ Envelope_Priority = '1:60'
719
+ Date_Sent = '1:70'
720
+ Time_Sent = '1:80'
721
+ Coded_Character_Set = '1:90'
722
+ UNO = '1:100'
723
+ Unique_Name_of_Object = '1:100'
724
+ ARM_Identifier = '1:120'
725
+ ARM_Version = '1:122'
726
+ end
727
+
728
+ module Application
729
+ Record_Version = '2:00'
730
+ Object_Type_Reference = '2:03'
731
+ Object_Name = '2:05'
732
+ Title = '2:05'
733
+ Edit_Status = '2:07'
734
+ Editorial_Update = '2:08'
735
+ Urgency = '2:10'
736
+ Subject_Reference = '2:12'
737
+ Category = '2:15'
738
+ Supplemental_Category = '2:20'
739
+ Fixture_Identifier = '2:22'
740
+ Keywords = '2:25'
741
+ Content_Location_Code = '2:26'
742
+ Content_Location_Name = '2:27'
743
+ Release_Date = '2:30'
744
+ Release_Time = '2:35'
745
+ Expiration_Date = '2:37'
746
+ Expiration_Time = '2:35'
747
+ Special_Instructions = '2:40'
748
+ Action_Advised = '2:42'
749
+ Reference_Service = '2:45'
750
+ Reference_Date = '2:47'
751
+ Reference_Number = '2:50'
752
+ Date_Created = '2:55'
753
+ Time_Created = '2:60'
754
+ Digital_Creation_Date = '2:62'
755
+ Digital_Creation_Time = '2:63'
756
+ Originating_Program = '2:65'
757
+ Program_Version = '2:70'
758
+ Object_Cycle = '2:75'
759
+ By_Line = '2:80'
760
+ Author = '2:80'
761
+ By_Line_Title = '2:85'
762
+ Author_Position = '2:85'
763
+ City = '2:90'
764
+ Sub_Location = '2:92'
765
+ Province = '2:95'
766
+ State = '2:95'
767
+ Country_Primary_Location_Code = '2:100'
768
+ Country_Primary_Location_Name = '2:101'
769
+ Original_Transmission_Reference = '2:103'
770
+ Headline = '2:105'
771
+ Credit = '2:110'
772
+ Source = '2:115'
773
+ Copyright_Notice = '2:116'
774
+ Contact = '2:118'
775
+ Abstract = '2:120'
776
+ Caption = '2:120'
777
+ Editor = '2:122'
778
+ Caption_Writer = '2:122'
779
+ Rasterized_Caption = '2:125'
780
+ Image_Type = '2:130'
781
+ Image_Orientation = '2:131'
782
+ Language_Identifier = '2:135'
783
+ Audio_Type = '2:150'
784
+ Audio_Sampling_Rate = '2:151'
785
+ Audio_Sampling_Resolution = '2:152'
786
+ Audio_Duration = '2:153'
787
+ Audio_Outcue = '2:154'
788
+ ObjectData_Preview_File_Format = '2:200'
789
+ ObjectData_Preview_File_Format_Version = '2:201'
790
+ ObjectData_Preview_Data = '2:202'
791
+ end
792
+
793
+ module Pre_ObjectData_Descriptor
794
+ Size_Mode = '7:10'
795
+ Max_Subfile_Size = '7:20'
796
+ ObjectData_Size_Announced = '7:90'
797
+ Maximum_ObjectData_Size = '7:95'
798
+ end
799
+
800
+ module ObjectData
801
+ Subfile = '8:10'
802
+ end
803
+
804
+ module Post_ObjectData_Descriptor
805
+ Confirmed_ObjectData_Size = '9:10'
806
+ end
807
+
808
+ # Make all constants above immutable
809
+ constants.each do |record|
810
+ rec = const_get(record)
811
+ rec.constants.each { |ds| rec.const_get(ds).freeze }
812
+ end
813
+ end # module Magick::IPTC
814
+
815
+ # Ruby-level Magick::Image methods
816
+ class Image
817
+ include Comparable
818
+
819
+ alias affinity remap
820
+
821
+ # Provide an alternate version of Draw#annotate, for folks who
822
+ # want to find it in this class.
823
+ def annotate(draw, width, height, x, y, text, &block)
824
+ check_destroyed
825
+ draw.annotate(self, width, height, x, y, text, &block)
826
+ self
827
+ end
828
+
829
+ # Set the color at x,y
830
+ def color_point(x, y, fill)
831
+ f = copy
832
+ f.pixel_color(x, y, fill)
833
+ f
834
+ end
835
+
836
+ # Set all pixels that have the same color as the pixel at x,y and
837
+ # are neighbors to the fill color
838
+ def color_floodfill(x, y, fill)
839
+ target = pixel_color(x, y)
840
+ color_flood_fill(target, fill, x, y, Magick::FloodfillMethod)
841
+ end
842
+
843
+ # Set all pixels that are neighbors of x,y and are not the border color
844
+ # to the fill color
845
+ def color_fill_to_border(x, y, fill)
846
+ color_flood_fill(border_color, fill, x, y, Magick::FillToBorderMethod)
847
+ end
848
+
849
+ # Set all pixels to the fill color. Very similar to Image#erase!
850
+ # Accepts either String or Pixel arguments
851
+ def color_reset!(fill)
852
+ save = background_color
853
+ # Change the background color _outside_ the begin block
854
+ # so that if this object is frozen the exeception will be
855
+ # raised before we have to handle it explicitly.
856
+ self.background_color = fill
857
+ begin
858
+ erase!
859
+ ensure
860
+ self.background_color = save
861
+ end
862
+ self
863
+ end
864
+
865
+ # Used by ImageList methods - see ImageList#cur_image
866
+ def cur_image
867
+ self
868
+ end
869
+
870
+ # Thanks to Russell Norris!
871
+ def each_pixel
872
+ get_pixels(0, 0, columns, rows).each_with_index do |p, n|
873
+ yield(p, n % columns, n / columns)
874
+ end
875
+ self
876
+ end
877
+
878
+ # Retrieve EXIF data by entry or all. If one or more entry names specified,
879
+ # return the values associated with the entries. If no entries specified,
880
+ # return all entries and values. The return value is an array of [name,value]
881
+ # arrays.
882
+ def get_exif_by_entry(*entry)
883
+ ary = []
884
+ if entry.length.zero?
885
+ exif_data = self['EXIF:*']
886
+ exif_data.split("\n").each { |exif| ary.push(exif.split('=')) } if exif_data
887
+ else
888
+ get_exif_by_entry # ensure properties is populated with exif data
889
+ entry.each do |name|
890
+ rval = self["EXIF:#{name}"]
891
+ ary.push([name, rval])
892
+ end
893
+ end
894
+ ary
895
+ end
896
+
897
+ # Retrieve EXIF data by tag number or all tag/value pairs. The return value is a hash.
898
+ def get_exif_by_number(*tag)
899
+ hash = {}
900
+ if tag.length.zero?
901
+ exif_data = self['EXIF:!']
902
+ if exif_data
903
+ exif_data.split("\n").each do |exif|
904
+ tag, value = exif.split('=')
905
+ tag = tag[1, 4].hex
906
+ hash[tag] = value
907
+ end
908
+ end
909
+ else
910
+ get_exif_by_number # ensure properties is populated with exif data
911
+ tag.each do |num|
912
+ rval = self[sprintf('#%04X', num.to_i)]
913
+ hash[num] = rval == 'unknown' ? nil : rval
914
+ end
915
+ end
916
+ hash
917
+ end
918
+
919
+ # Retrieve IPTC information by record number:dataset tag constant defined in
920
+ # Magick::IPTC, above.
921
+ def get_iptc_dataset(ds)
922
+ self['IPTC:' + ds]
923
+ end
924
+
925
+ # Iterate over IPTC record number:dataset tags, yield for each non-nil dataset
926
+ def each_iptc_dataset
927
+ Magick::IPTC.constants.each do |record|
928
+ rec = Magick::IPTC.const_get(record)
929
+ rec.constants.each do |dataset|
930
+ data_field = get_iptc_dataset(rec.const_get(dataset))
931
+ yield(dataset, data_field) unless data_field.nil?
932
+ end
933
+ end
934
+ nil
935
+ end
936
+
937
+ # Patches problematic change to the order of arguments in 1.11.0.
938
+ # Before this release, the order was
939
+ # black_point, gamma, white_point
940
+ # RMagick 1.11.0 changed this to
941
+ # black_point, white_point, gamma
942
+ # This fix tries to determine if the arguments are in the old order and
943
+ # if so, swaps the gamma and white_point arguments. Then it calls
944
+ # level2, which simply accepts the arguments as given.
945
+
946
+ # Inspect the gamma and white point values and swap them if they
947
+ # look like they're in the old order.
948
+
949
+ # (Thanks to Al Evans for the suggestion.)
950
+ def level(black_point = 0.0, white_point = nil, gamma = nil)
951
+ black_point = Float(black_point)
952
+
953
+ white_point ||= Magick::QuantumRange - black_point
954
+ white_point = Float(white_point)
955
+
956
+ gamma_arg = gamma
957
+ gamma ||= 1.0
958
+ gamma = Float(gamma)
959
+
960
+ if gamma.abs > 10.0 || white_point.abs <= 10.0 || white_point.abs < gamma.abs
961
+ gamma, white_point = white_point, gamma
962
+ white_point = Magick::QuantumRange - black_point unless gamma_arg
963
+ end
964
+
965
+ level2(black_point, white_point, gamma)
966
+ end
967
+
968
+ # These four methods are equivalent to the Draw#matte method
969
+ # with the "Point", "Replace", "Floodfill", "FilltoBorder", and
970
+ # "Replace" arguments, respectively.
971
+
972
+ # Make the pixel at (x,y) transparent.
973
+ def matte_point(x, y)
974
+ f = copy
975
+ f.alpha(OpaqueAlphaChannel) unless f.alpha?
976
+ pixel = f.pixel_color(x, y)
977
+ pixel.alpha = TransparentAlpha
978
+ f.pixel_color(x, y, pixel)
979
+ f
980
+ end
981
+
982
+ # Make transparent all pixels that are the same color as the
983
+ # pixel at (x, y).
984
+ def matte_replace(x, y)
985
+ f = copy
986
+ f.alpha(OpaqueAlphaChannel) unless f.alpha?
987
+ target = f.pixel_color(x, y)
988
+ f.transparent(target)
989
+ end
990
+
991
+ # Make transparent any pixel that matches the color of the pixel
992
+ # at (x,y) and is a neighbor.
993
+ def matte_floodfill(x, y)
994
+ f = copy
995
+ f.alpha(OpaqueAlphaChannel) unless f.alpha?
996
+ target = f.pixel_color(x, y)
997
+ f.matte_flood_fill(target, x, y, FloodfillMethod, alpha: TransparentAlpha)
998
+ end
999
+
1000
+ # Make transparent any neighbor pixel that is not the border color.
1001
+ def matte_fill_to_border(x, y)
1002
+ f = copy
1003
+ f.alpha(OpaqueAlphaChannel) unless f.alpha?
1004
+ f.matte_flood_fill(border_color, x, y, FillToBorderMethod, alpha: TransparentAlpha)
1005
+ end
1006
+
1007
+ # Make all pixels transparent.
1008
+ def matte_reset!
1009
+ alpha(TransparentAlphaChannel)
1010
+ self
1011
+ end
1012
+
1013
+ # Force an image to exact dimensions without changing the aspect ratio.
1014
+ # Resize and crop if necessary. (Thanks to Jerett Taylor!)
1015
+ def resize_to_fill(ncols, nrows = nil, gravity = CenterGravity)
1016
+ copy.resize_to_fill!(ncols, nrows, gravity)
1017
+ end
1018
+
1019
+ def resize_to_fill!(ncols, nrows = nil, gravity = CenterGravity)
1020
+ nrows ||= ncols
1021
+ if ncols != columns || nrows != rows
1022
+ scale = [ncols / columns.to_f, nrows / rows.to_f].max
1023
+ resize!(scale * columns + 0.5, scale * rows + 0.5)
1024
+ end
1025
+ crop!(gravity, ncols, nrows, true) if ncols != columns || nrows != rows
1026
+ self
1027
+ end
1028
+
1029
+ # Preserve aliases used < RMagick 2.0.1
1030
+ alias crop_resized resize_to_fill
1031
+ alias crop_resized! resize_to_fill!
1032
+
1033
+ # Convenience method to resize retaining the aspect ratio.
1034
+ # (Thanks to Robert Manni!)
1035
+ def resize_to_fit(cols, rows = nil)
1036
+ rows ||= cols
1037
+ change_geometry(Geometry.new(cols, rows)) do |ncols, nrows|
1038
+ resize(ncols, nrows)
1039
+ end
1040
+ end
1041
+
1042
+ def resize_to_fit!(cols, rows = nil)
1043
+ rows ||= cols
1044
+ change_geometry(Geometry.new(cols, rows)) do |ncols, nrows|
1045
+ resize!(ncols, nrows)
1046
+ end
1047
+ end
1048
+
1049
+ # Replace matching neighboring pixels with texture pixels
1050
+ def texture_floodfill(x, y, texture)
1051
+ target = pixel_color(x, y)
1052
+ texture_flood_fill(target, texture, x, y, FloodfillMethod)
1053
+ end
1054
+
1055
+ # Replace neighboring pixels to border color with texture pixels
1056
+ def texture_fill_to_border(x, y, texture)
1057
+ texture_flood_fill(border_color, texture, x, y, FillToBorderMethod)
1058
+ end
1059
+
1060
+ # Construct a view. If a block is present, yield and pass the view
1061
+ # object, otherwise return the view object.
1062
+ def view(x, y, width, height)
1063
+ view = View.new(self, x, y, width, height)
1064
+
1065
+ return view unless block_given?
1066
+
1067
+ begin
1068
+ yield(view)
1069
+ ensure
1070
+ view.sync
1071
+ end
1072
+ nil
1073
+ end
1074
+
1075
+ # Magick::Image::View class
1076
+ class View
1077
+ attr_reader :x, :y, :width, :height
1078
+ attr_accessor :dirty
1079
+
1080
+ def initialize(img, x, y, width, height)
1081
+ img.check_destroyed
1082
+ Kernel.raise ArgumentError, "invalid geometry (#{width}x#{height}+#{x}+#{y})" if width <= 0 || height <= 0
1083
+ Kernel.raise RangeError, "geometry (#{width}x#{height}+#{x}+#{y}) exceeds image boundary" if x < 0 || y < 0 || (x + width) > img.columns || (y + height) > img.rows
1084
+ @view = img.get_pixels(x, y, width, height)
1085
+ @img = img
1086
+ @x = x
1087
+ @y = y
1088
+ @width = width
1089
+ @height = height
1090
+ @dirty = false
1091
+ end
1092
+
1093
+ def [](*args)
1094
+ rows = Rows.new(@view, @width, @height, args)
1095
+ rows.add_observer(self)
1096
+ rows
1097
+ end
1098
+
1099
+ # Store changed pixels back to image
1100
+ def sync(force = false)
1101
+ @img.store_pixels(x, y, width, height, @view) if @dirty || force
1102
+ @dirty || force
1103
+ end
1104
+
1105
+ # Get update from Rows - if @dirty ever becomes
1106
+ # true, don't change it back to false!
1107
+ def update(rows)
1108
+ @dirty = true
1109
+ rows.delete_observer(self) # No need to tell us again.
1110
+ nil
1111
+ end
1112
+
1113
+ # Magick::Image::View::Pixels
1114
+ # Defines channel attribute getters/setters
1115
+ class Pixels < Array
1116
+ include Observable
1117
+
1118
+ # Define a getter and a setter for each channel.
1119
+ %i[red green blue opacity].each do |c|
1120
+ module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
1121
+ def #{c}
1122
+ return collect { |p| p.#{c} }
1123
+ end
1124
+ def #{c}=(v)
1125
+ each { |p| p.#{c} = v }
1126
+ changed
1127
+ notify_observers(self)
1128
+ nil
1129
+ end
1130
+ END_EVAL
1131
+ end
1132
+ end # class Magick::Image::View::Pixels
1133
+
1134
+ # Magick::Image::View::Rows
1135
+ class Rows
1136
+ include Observable
1137
+
1138
+ def initialize(view, width, height, rows)
1139
+ @view = view
1140
+ @width = width
1141
+ @height = height
1142
+ @rows = rows
1143
+ end
1144
+
1145
+ def [](*args)
1146
+ cols(args)
1147
+
1148
+ # Both View::Pixels and Magick::Pixel implement Observable
1149
+ if @unique
1150
+ pixels = @view[@rows[0] * @width + @cols[0]]
1151
+ pixels.add_observer(self)
1152
+ else
1153
+ pixels = View::Pixels.new
1154
+ each do |x|
1155
+ p = @view[x]
1156
+ p.add_observer(self)
1157
+ pixels << p
1158
+ end
1159
+ end
1160
+ pixels
1161
+ end
1162
+
1163
+ def []=(*args)
1164
+ rv = args.delete_at(-1) # get rvalue
1165
+ unless rv.is_a?(Pixel) # must be a Pixel or a color name
1166
+ begin
1167
+ rv = Pixel.from_color(rv)
1168
+ rescue TypeError
1169
+ Kernel.raise TypeError, "cannot convert #{rv.class} into Pixel"
1170
+ end
1171
+ end
1172
+ cols(args)
1173
+ each { |x| @view[x] = rv.dup }
1174
+ changed
1175
+ notify_observers(self)
1176
+ end
1177
+
1178
+ # A pixel has been modified. Tell the view.
1179
+ def update(pixel)
1180
+ changed
1181
+ notify_observers(self)
1182
+ pixel.delete_observer(self) # Don't need to hear again.
1183
+ nil
1184
+ end
1185
+
1186
+ private
1187
+
1188
+ def cols(*args)
1189
+ @cols = args[0] # remove the outermost array
1190
+ @unique = false
1191
+
1192
+ # Convert @rows to an Enumerable object
1193
+ case @rows.length
1194
+ when 0 # Create a Range for all the rows
1195
+ @rows = Range.new(0, @height, true)
1196
+ when 1 # Range, Array, or a single integer
1197
+ # if the single element is already an Enumerable
1198
+ # object, get it.
1199
+ if @rows.first.respond_to? :each
1200
+ @rows = @rows.first
1201
+ else
1202
+ @rows = Integer(@rows.first)
1203
+ @rows += @height if @rows < 0
1204
+ Kernel.raise IndexError, "index [#{@rows}] out of range" if @rows < 0 || @rows > @height - 1
1205
+ # Convert back to an array
1206
+ @rows = Array.new(1, @rows)
1207
+ @unique = true
1208
+ end
1209
+ when 2
1210
+ # A pair of integers representing the starting column and the number of columns
1211
+ start = Integer(@rows[0])
1212
+ length = Integer(@rows[1])
1213
+
1214
+ # Negative start -> start from last row
1215
+ start += @height if start < 0
1216
+
1217
+ if start > @height || start < 0 || length < 0
1218
+ Kernel.raise IndexError, "index [#{@rows.first}] out of range"
1219
+ elsif start + length > @height
1220
+ length = @height - length
1221
+ length = [length, 0].max
1222
+ end
1223
+ # Create a Range for the specified set of rows
1224
+ @rows = Range.new(start, start + length, true)
1225
+ end
1226
+
1227
+ case @cols.length
1228
+ when 0 # all rows
1229
+ @cols = Range.new(0, @width, true) # convert to range
1230
+ @unique = false
1231
+ when 1 # Range, Array, or a single integer
1232
+ # if the single element is already an Enumerable
1233
+ # object, get it.
1234
+ if @cols.first.respond_to? :each
1235
+ @cols = @cols.first
1236
+ @unique = false
1237
+ else
1238
+ @cols = Integer(@cols.first)
1239
+ @cols += @width if @cols < 0
1240
+ Kernel.raise IndexError, "index [#{@cols}] out of range" if @cols < 0 || @cols > @width - 1
1241
+ # Convert back to array
1242
+ @cols = Array.new(1, @cols)
1243
+ @unique &&= true
1244
+ end
1245
+ when 2
1246
+ # A pair of integers representing the starting column and the number of columns
1247
+ start = Integer(@cols[0])
1248
+ length = Integer(@cols[1])
1249
+
1250
+ # Negative start -> start from last row
1251
+ start += @width if start < 0
1252
+
1253
+ if start > @width || start < 0 || length < 0
1254
+ # nop
1255
+ elsif start + length > @width
1256
+ length = @width - length
1257
+ length = [length, 0].max
1258
+ end
1259
+ # Create a Range for the specified set of columns
1260
+ @cols = Range.new(start, start + length, true)
1261
+ @unique = false
1262
+ end
1263
+ end
1264
+
1265
+ # iterator called from subscript methods
1266
+ def each
1267
+ maxrows = @height - 1
1268
+ maxcols = @width - 1
1269
+
1270
+ @rows.each do |j|
1271
+ Kernel.raise IndexError, "index [#{j}] out of range" if j > maxrows
1272
+ @cols.each do |i|
1273
+ Kernel.raise IndexError, "index [#{i}] out of range" if i > maxcols
1274
+ yield j * @width + i
1275
+ end
1276
+ end
1277
+ nil # useless return value
1278
+ end
1279
+ end # class Magick::Image::View::Rows
1280
+ end # class Magick::Image::View
1281
+ end # class Magick::Image
1282
+
1283
+ class ImageList
1284
+ include Comparable
1285
+ include Enumerable
1286
+ attr_reader :scene
1287
+
1288
+ private
1289
+
1290
+ def get_current
1291
+ @images[@scene].__id__
1292
+ rescue StandardError
1293
+ nil
1294
+ end
1295
+
1296
+ protected
1297
+
1298
+ def assert_image(obj)
1299
+ Kernel.raise ArgumentError, "Magick::Image required (#{obj.class} given)" unless obj.is_a? Magick::Image
1300
+ end
1301
+
1302
+ # Ensure array is always an array of Magick::Image objects
1303
+ def assert_image_array(ary)
1304
+ Kernel.raise ArgumentError, "Magick::ImageList or array of Magick::Images required (#{ary.class} given)" unless ary.respond_to? :each
1305
+ ary.each { |obj| assert_image obj }
1306
+ end
1307
+
1308
+ # Find old current image, update scene number
1309
+ # current is the id of the old current image.
1310
+ def set_current(current)
1311
+ if length.zero?
1312
+ self.scene = nil
1313
+ return
1314
+ # Don't bother looking for current image
1315
+ elsif scene.nil? || scene >= length
1316
+ self.scene = length - 1
1317
+ return
1318
+ elsif !current.nil?
1319
+ # Find last instance of "current" in the list.
1320
+ # If "current" isn't in the list, set current to last image.
1321
+ self.scene = length - 1
1322
+ each_with_index do |f, i|
1323
+ self.scene = i if f.__id__ == current
1324
+ end
1325
+ return
1326
+ end
1327
+ self.scene = length - 1
1328
+ end
1329
+
1330
+ public
1331
+
1332
+ # Allow scene to be set to nil
1333
+ def scene=(n)
1334
+ if n.nil?
1335
+ Kernel.raise IndexError, 'scene number out of bounds' unless @images.length.zero?
1336
+ @scene = nil
1337
+ return
1338
+ elsif @images.length.zero?
1339
+ Kernel.raise IndexError, 'scene number out of bounds'
1340
+ end
1341
+
1342
+ n = Integer(n)
1343
+ Kernel.raise IndexError, 'scene number out of bounds' if n < 0 || n > length - 1
1344
+ @scene = n
1345
+ end
1346
+
1347
+ # All the binary operators work the same way.
1348
+ # 'other' should be either an ImageList or an Array
1349
+ %w[& + - |].each do |op|
1350
+ module_eval <<-END_BINOPS, __FILE__, __LINE__ + 1
1351
+ def #{op}(other)
1352
+ ilist = self.class.new
1353
+ begin
1354
+ a = other #{op} @images
1355
+ rescue TypeError
1356
+ Kernel.raise ArgumentError, "Magick::ImageList expected, got " + other.class.to_s
1357
+ end
1358
+ current = get_current()
1359
+ a.each do |image|
1360
+ assert_image image
1361
+ ilist << image
1362
+ end
1363
+ ilist.set_current current
1364
+ return ilist
1365
+ end
1366
+ END_BINOPS
1367
+ end
1368
+
1369
+ def *(other)
1370
+ Kernel.raise ArgumentError, "Integer required (#{other.class} given)" unless other.is_a? Integer
1371
+ current = get_current
1372
+ ilist = self.class.new
1373
+ (@images * other).each { |image| ilist << image }
1374
+ ilist.set_current current
1375
+ ilist
1376
+ end
1377
+
1378
+ def <<(obj)
1379
+ assert_image obj
1380
+ @images << obj
1381
+ @scene = @images.length - 1
1382
+ self
1383
+ end
1384
+
1385
+ # Compare ImageLists
1386
+ # Compare each image in turn until the result of a comparison
1387
+ # is not 0. If all comparisons return 0, then
1388
+ # return if A.scene != B.scene
1389
+ # return A.length <=> B.length
1390
+ def <=>(other)
1391
+ Kernel.raise TypeError, "#{self.class} required (#{other.class} given)" unless other.is_a? self.class
1392
+ size = [length, other.length].min
1393
+ size.times do |x|
1394
+ r = self[x] <=> other[x]
1395
+ return r unless r.zero?
1396
+ end
1397
+ return 0 if @scene.nil? && other.scene.nil?
1398
+
1399
+ Kernel.raise TypeError, "cannot convert nil into #{other.scene.class}" if @scene.nil? && !other.scene.nil?
1400
+ Kernel.raise TypeError, "cannot convert nil into #{scene.class}" if !@scene.nil? && other.scene.nil?
1401
+ r = scene <=> other.scene
1402
+ return r unless r.zero?
1403
+
1404
+ length <=> other.length
1405
+ end
1406
+
1407
+ def [](*args)
1408
+ a = @images[*args]
1409
+ if a.respond_to?(:each)
1410
+ ilist = self.class.new
1411
+ a.each { |image| ilist << image }
1412
+ a = ilist
1413
+ end
1414
+ a
1415
+ end
1416
+
1417
+ def []=(*args)
1418
+ obj = @images.[]=(*args)
1419
+ if obj && obj.respond_to?(:each)
1420
+ assert_image_array(obj)
1421
+ set_current obj.last.__id__
1422
+ elsif obj
1423
+ assert_image(obj)
1424
+ set_current obj.__id__
1425
+ else
1426
+ set_current nil
1427
+ end
1428
+ end
1429
+
1430
+ %i[
1431
+ at each each_index empty? fetch
1432
+ first hash include? index length rindex sort!
1433
+ ].each do |mth|
1434
+ module_eval <<-END_SIMPLE_DELEGATES, __FILE__, __LINE__ + 1
1435
+ def #{mth}(*args, &block)
1436
+ @images.#{mth}(*args, &block)
1437
+ end
1438
+ END_SIMPLE_DELEGATES
1439
+ end
1440
+ alias size length
1441
+
1442
+ def clear
1443
+ @scene = nil
1444
+ @images.clear
1445
+ end
1446
+
1447
+ def clone
1448
+ ditto = dup
1449
+ ditto.freeze if frozen?
1450
+ ditto
1451
+ end
1452
+
1453
+ # override Enumerable#collect
1454
+ def collect(&block)
1455
+ current = get_current
1456
+ a = @images.map(&block)
1457
+ ilist = self.class.new
1458
+ a.each { |image| ilist << image }
1459
+ ilist.set_current current
1460
+ ilist
1461
+ end
1462
+
1463
+ def collect!(&block)
1464
+ @images.map!(&block)
1465
+ assert_image_array @images
1466
+ self
1467
+ end
1468
+
1469
+ # Make a deep copy
1470
+ def copy
1471
+ ditto = self.class.new
1472
+ @images.each { |f| ditto << f.copy }
1473
+ ditto.scene = @scene
1474
+ ditto
1475
+ end
1476
+
1477
+ # Return the current image
1478
+ def cur_image
1479
+ Kernel.raise IndexError, 'no images in this list' unless @scene
1480
+ @images[@scene]
1481
+ end
1482
+
1483
+ # ImageList#map took over the "map" name. Use alternatives.
1484
+ alias map collect
1485
+ alias __map__ collect
1486
+ alias map! collect!
1487
+ alias __map__! collect!
1488
+
1489
+ # ImageMagic used affinity in 6.4.3, switch to remap in 6.4.4.
1490
+ alias affinity remap
1491
+
1492
+ def compact
1493
+ current = get_current
1494
+ ilist = self.class.new
1495
+ a = @images.compact
1496
+ a.each { |image| ilist << image }
1497
+ ilist.set_current current
1498
+ ilist
1499
+ end
1500
+
1501
+ def compact!
1502
+ current = get_current
1503
+ a = @images.compact! # returns nil if no changes were made
1504
+ set_current current
1505
+ a.nil? ? nil : self
1506
+ end
1507
+
1508
+ def concat(other)
1509
+ assert_image_array other
1510
+ other.each { |image| @images << image }
1511
+ @scene = length - 1
1512
+ self
1513
+ end
1514
+
1515
+ # Set same delay for all images
1516
+ def delay=(d)
1517
+ raise ArgumentError, 'delay must be greater than or equal to 0' if Integer(d) < 0
1518
+
1519
+ @images.each { |f| f.delay = Integer(d) }
1520
+ end
1521
+
1522
+ def delete(obj, &block)
1523
+ assert_image obj
1524
+ current = get_current
1525
+ a = @images.delete(obj, &block)
1526
+ set_current current
1527
+ a
1528
+ end
1529
+
1530
+ def delete_at(ndx)
1531
+ current = get_current
1532
+ a = @images.delete_at(ndx)
1533
+ set_current current
1534
+ a
1535
+ end
1536
+
1537
+ def delete_if(&block)
1538
+ current = get_current
1539
+ @images.delete_if(&block)
1540
+ set_current current
1541
+ self
1542
+ end
1543
+
1544
+ def dup
1545
+ ditto = self.class.new
1546
+ @images.each { |img| ditto << img }
1547
+ ditto.scene = @scene
1548
+ ditto
1549
+ end
1550
+
1551
+ def eql?(other)
1552
+ assert_image_array other
1553
+ eql = other.eql?(@images)
1554
+ begin # "other" is another ImageList
1555
+ eql &&= @scene == other.scene
1556
+ rescue NoMethodError
1557
+ # "other" is a plain Array
1558
+ end
1559
+ eql
1560
+ end
1561
+
1562
+ def fill(*args, &block)
1563
+ assert_image args[0] unless block_given?
1564
+ current = get_current
1565
+ @images.fill(*args, &block)
1566
+ assert_image_array self
1567
+ set_current current
1568
+ self
1569
+ end
1570
+
1571
+ # Override Enumerable's find_all
1572
+ def find_all(&block)
1573
+ current = get_current
1574
+ a = @images.select(&block)
1575
+ ilist = self.class.new
1576
+ a.each { |image| ilist << image }
1577
+ ilist.set_current current
1578
+ ilist
1579
+ end
1580
+ alias select find_all
1581
+
1582
+ def from_blob(*blobs, &block)
1583
+ Kernel.raise ArgumentError, 'no blobs given' if blobs.length.zero?
1584
+ blobs.each do |b|
1585
+ Magick::Image.from_blob(b, &block).each { |n| @images << n }
1586
+ end
1587
+ @scene = length - 1
1588
+ self
1589
+ end
1590
+
1591
+ # Initialize new instances
1592
+ def initialize(*filenames, &block)
1593
+ @images = []
1594
+ @scene = nil
1595
+ filenames.each do |f|
1596
+ Magick::Image.read(f, &block).each { |n| @images << n }
1597
+ end
1598
+
1599
+ @scene = length - 1 if length > 0 # last image in array
1600
+ end
1601
+
1602
+ def insert(index, *args)
1603
+ args.each { |image| assert_image image }
1604
+ current = get_current
1605
+ @images.insert(index, *args)
1606
+ set_current current
1607
+ self
1608
+ end
1609
+
1610
+ # Call inspect for all the images
1611
+ def inspect
1612
+ img = []
1613
+ @images.each { |image| img << image.inspect }
1614
+ img = '[' + img.join(",\n") + "]\nscene=#{@scene}"
1615
+ end
1616
+
1617
+ # Set the number of iterations of an animated GIF
1618
+ def iterations=(n)
1619
+ n = Integer(n)
1620
+ Kernel.raise ArgumentError, 'iterations must be between 0 and 65535' if n < 0 || n > 65_535
1621
+ @images.each { |f| f.iterations = n }
1622
+ end
1623
+
1624
+ def last(*args)
1625
+ if args.length.zero?
1626
+ a = @images.last
1627
+ else
1628
+ a = @images.last(*args)
1629
+ ilist = self.class.new
1630
+ a.each { |img| ilist << img }
1631
+ @scene = a.length - 1
1632
+ a = ilist
1633
+ end
1634
+ a
1635
+ end
1636
+
1637
+ # Custom marshal/unmarshal for Ruby 1.8.
1638
+ def marshal_dump
1639
+ ary = [@scene]
1640
+ @images.each { |i| ary << Marshal.dump(i) }
1641
+ ary
1642
+ end
1643
+
1644
+ def marshal_load(ary)
1645
+ @scene = ary.shift
1646
+ @images = []
1647
+ ary.each { |a| @images << Marshal.load(a) }
1648
+ end
1649
+
1650
+ # The ImageList class supports the Magick::Image class methods by simply sending
1651
+ # the method to the current image. If the method isn't explicitly supported,
1652
+ # send it to the current image in the array. If there are no images, send
1653
+ # it up the line. Catch a NameError and emit a useful message.
1654
+ def method_missing(meth_id, *args, &block)
1655
+ if @scene
1656
+ @images[@scene].send(meth_id, *args, &block)
1657
+ else
1658
+ super
1659
+ end
1660
+ rescue NoMethodError
1661
+ Kernel.raise NoMethodError, "undefined method `#{meth_id.id2name}' for #{self.class}"
1662
+ rescue Exception
1663
+ $ERROR_POSITION.delete_if { |s| /:in `send'$/.match(s) || /:in `method_missing'$/.match(s) }
1664
+ Kernel.raise
1665
+ end
1666
+
1667
+ # Create a new image and add it to the end
1668
+ def new_image(cols, rows, *fill, &info_blk)
1669
+ self << Magick::Image.new(cols, rows, *fill, &info_blk)
1670
+ end
1671
+
1672
+ def partition(&block)
1673
+ a = @images.partition(&block)
1674
+ t = self.class.new
1675
+ a[0].each { |img| t << img }
1676
+ t.set_current nil
1677
+ f = self.class.new
1678
+ a[1].each { |img| f << img }
1679
+ f.set_current nil
1680
+ [t, f]
1681
+ end
1682
+
1683
+ # Ping files and concatenate the new images
1684
+ def ping(*files, &block)
1685
+ Kernel.raise ArgumentError, 'no files given' if files.length.zero?
1686
+ files.each do |f|
1687
+ Magick::Image.ping(f, &block).each { |n| @images << n }
1688
+ end
1689
+ @scene = length - 1
1690
+ self
1691
+ end
1692
+
1693
+ def pop
1694
+ current = get_current
1695
+ a = @images.pop # can return nil
1696
+ set_current current
1697
+ a
1698
+ end
1699
+
1700
+ def push(*objs)
1701
+ objs.each do |image|
1702
+ assert_image image
1703
+ @images << image
1704
+ end
1705
+ @scene = length - 1
1706
+ self
1707
+ end
1708
+
1709
+ # Read files and concatenate the new images
1710
+ def read(*files, &block)
1711
+ Kernel.raise ArgumentError, 'no files given' if files.length.zero?
1712
+ files.each do |f|
1713
+ Magick::Image.read(f, &block).each { |n| @images << n }
1714
+ end
1715
+ @scene = length - 1
1716
+ self
1717
+ end
1718
+
1719
+ # override Enumerable's reject
1720
+ def reject(&block)
1721
+ current = get_current
1722
+ ilist = self.class.new
1723
+ a = @images.reject(&block)
1724
+ a.each { |image| ilist << image }
1725
+ ilist.set_current current
1726
+ ilist
1727
+ end
1728
+
1729
+ def reject!(&block)
1730
+ current = get_current
1731
+ a = @images.reject!(&block)
1732
+ @images = a unless a.nil?
1733
+ set_current current
1734
+ a.nil? ? nil : self
1735
+ end
1736
+
1737
+ def replace(other)
1738
+ assert_image_array other
1739
+ current = get_current
1740
+ @images.clear
1741
+ other.each { |image| @images << image }
1742
+ @scene = length.zero? ? nil : 0
1743
+ set_current current
1744
+ self
1745
+ end
1746
+
1747
+ # Ensure respond_to? answers correctly when we are delegating to Image
1748
+ alias __respond_to__? respond_to?
1749
+ def respond_to?(meth_id, priv = false)
1750
+ return true if __respond_to__?(meth_id, priv)
1751
+
1752
+ if @scene
1753
+ @images[@scene].respond_to?(meth_id, priv)
1754
+ else
1755
+ super
1756
+ end
1757
+ end
1758
+
1759
+ def reverse
1760
+ current = get_current
1761
+ a = self.class.new
1762
+ @images.reverse_each { |image| a << image }
1763
+ a.set_current current
1764
+ a
1765
+ end
1766
+
1767
+ def reverse!
1768
+ current = get_current
1769
+ @images.reverse!
1770
+ set_current current
1771
+ self
1772
+ end
1773
+
1774
+ def reverse_each
1775
+ @images.reverse_each { |image| yield(image) }
1776
+ self
1777
+ end
1778
+
1779
+ def shift
1780
+ current = get_current
1781
+ a = @images.shift
1782
+ set_current current
1783
+ a
1784
+ end
1785
+
1786
+ def slice(*args)
1787
+ slice = @images.slice(*args)
1788
+ if slice
1789
+ ilist = self.class.new
1790
+ if slice.respond_to?(:each)
1791
+ slice.each { |image| ilist << image }
1792
+ else
1793
+ ilist << slice
1794
+ end
1795
+ else
1796
+ ilist = nil
1797
+ end
1798
+ ilist
1799
+ end
1800
+
1801
+ def slice!(*args)
1802
+ current = get_current
1803
+ a = @images.slice!(*args)
1804
+ set_current current
1805
+ a
1806
+ end
1807
+
1808
+ def ticks_per_second=(t)
1809
+ Kernel.raise ArgumentError, 'ticks_per_second must be greater than or equal to 0' if Integer(t) < 0
1810
+ @images.each { |f| f.ticks_per_second = Integer(t) }
1811
+ end
1812
+
1813
+ def to_a
1814
+ a = []
1815
+ @images.each { |image| a << image }
1816
+ a
1817
+ end
1818
+
1819
+ def uniq
1820
+ current = get_current
1821
+ a = self.class.new
1822
+ @images.uniq.each { |image| a << image }
1823
+ a.set_current current
1824
+ a
1825
+ end
1826
+
1827
+ def uniq!(*_args)
1828
+ current = get_current
1829
+ a = @images.uniq!
1830
+ set_current current
1831
+ a.nil? ? nil : self
1832
+ end
1833
+
1834
+ # @scene -> new object
1835
+ def unshift(obj)
1836
+ assert_image obj
1837
+ @images.unshift(obj)
1838
+ @scene = 0
1839
+ self
1840
+ end
1841
+
1842
+ def values_at(*args)
1843
+ a = @images.values_at(*args)
1844
+ a = self.class.new
1845
+ @images.values_at(*args).each { |image| a << image }
1846
+ a.scene = a.length - 1
1847
+ a
1848
+ end
1849
+ alias indexes values_at
1850
+ alias indices values_at
1851
+ end # Magick::ImageList
1852
+
1853
+ class Pixel
1854
+ # include Observable for Image::View class
1855
+ include Observable
1856
+ end
1857
+
1858
+ # Collects non-specific optional method arguments
1859
+ class OptionalMethodArguments
1860
+ def initialize(img)
1861
+ @img = img
1862
+ end
1863
+
1864
+ # miscellaneous options like -verbose
1865
+ def method_missing(mth, val)
1866
+ @img.define(mth.to_s.tr('_', '-'), val)
1867
+ end
1868
+
1869
+ # set(key, val) corresponds to -set option:key val
1870
+ def define(key, val = nil)
1871
+ @img.define(key, val)
1872
+ end
1873
+
1874
+ # accepts Pixel object or color name
1875
+ def highlight_color=(color)
1876
+ color = @img.to_color(color) if color.respond_to?(:to_color)
1877
+ @img.define('highlight-color', color)
1878
+ end
1879
+
1880
+ # accepts Pixel object or color name
1881
+ def lowlight_color=(color)
1882
+ color = @img.to_color(color) if color.respond_to?(:to_color)
1883
+ @img.define('lowlight-color', color)
1884
+ end
1885
+ end
1886
+
1887
+ # Example fill class. Fills the image with the specified background
1888
+ # color, then crosshatches with the specified crosshatch color.
1889
+ # @dist is the number of pixels between hatch lines.
1890
+ # See Magick::Draw examples.
1891
+ class HatchFill
1892
+ def initialize(bgcolor, hatchcolor = 'white', dist = 10)
1893
+ @bgcolor = bgcolor
1894
+ @hatchpixel = Pixel.from_color(hatchcolor)
1895
+ @dist = dist
1896
+ end
1897
+
1898
+ def fill(img) # required
1899
+ img.background_color = @bgcolor
1900
+ img.erase! # sets image to background color
1901
+ pixels = Array.new([img.rows, img.columns].max, @hatchpixel)
1902
+ @dist.step((img.columns - 1) / @dist * @dist, @dist) do |x|
1903
+ img.store_pixels(x, 0, 1, img.rows, pixels)
1904
+ end
1905
+ @dist.step((img.rows - 1) / @dist * @dist, @dist) do |y|
1906
+ img.store_pixels(0, y, img.columns, 1, pixels)
1907
+ end
1908
+ end
1909
+ end
1910
+
1911
+ # Fill class with solid monochromatic color
1912
+ class SolidFill
1913
+ def initialize(bgcolor)
1914
+ @bgcolor = bgcolor
1915
+ end
1916
+
1917
+ def fill(img)
1918
+ img.background_color = @bgcolor
1919
+ img.erase!
1920
+ end
1921
+ end
1922
+ end # Magick