skeptick 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +584 -0
  5. data/Rakefile +10 -0
  6. data/foo.rb +1 -0
  7. data/lib/skeptick.rb +2 -0
  8. data/lib/skeptick/chain.rb +55 -0
  9. data/lib/skeptick/chain/dsl_context.rb +21 -0
  10. data/lib/skeptick/command.rb +46 -0
  11. data/lib/skeptick/convert.rb +109 -0
  12. data/lib/skeptick/convert/dsl_context.rb +27 -0
  13. data/lib/skeptick/core.rb +46 -0
  14. data/lib/skeptick/error.rb +4 -0
  15. data/lib/skeptick/helper.rb +26 -0
  16. data/lib/skeptick/image.rb +69 -0
  17. data/lib/skeptick/image/dsl_context.rb +29 -0
  18. data/lib/skeptick/railtie.rb +8 -0
  19. data/lib/skeptick/sugar.rb +8 -0
  20. data/lib/skeptick/sugar/composition.rb +55 -0
  21. data/lib/skeptick/sugar/debugging.rb +12 -0
  22. data/lib/skeptick/sugar/drawing.rb +32 -0
  23. data/lib/skeptick/sugar/edges.rb +70 -0
  24. data/lib/skeptick/sugar/formatting.rb +12 -0
  25. data/lib/skeptick/sugar/geometry.rb +38 -0
  26. data/lib/skeptick/sugar/resizing.rb +16 -0
  27. data/lib/skeptick/sugar/sequence_manipulation.rb +43 -0
  28. data/lib/skeptick/version.rb +3 -0
  29. data/logo.png +0 -0
  30. data/logo.rb +45 -0
  31. data/refresh_preview.scpt +2 -0
  32. data/skeptick.gemspec +21 -0
  33. data/test/chain_test.rb +94 -0
  34. data/test/convert_test.rb +177 -0
  35. data/test/image_test.rb +145 -0
  36. data/test/sugar/composition_test.rb +273 -0
  37. data/test/sugar/debugging_test.rb +24 -0
  38. data/test/sugar/drawing_test.rb +86 -0
  39. data/test/sugar/edges_test.rb +99 -0
  40. data/test/sugar/formatting_test.rb +19 -0
  41. data/test/sugar/geometry_test.rb +92 -0
  42. data/test/sugar/resizing_test.rb +25 -0
  43. data/test/sugar/sequence_manipulation_test.rb +98 -0
  44. data/test/test_helper.rb +11 -0
  45. metadata +117 -0
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in skeptick.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Maxim Chernyak
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,584 @@
1
+ # Skeptick
2
+
3
+ Skeptick is an all-purpose DSL for building and running ImageMagic commands.
4
+ It helps you build any transformations, from trivial resizes to complex mask
5
+ algorithms and free drawing. In a nutshell, Skeptick is nothing more than a
6
+ string manipulator and a process spawner. That's all it's meant to be. However,
7
+ with Skeptick you get quite a few advantages over using plain shell-out or other
8
+ libraries.
9
+
10
+ ## What you get
11
+
12
+ * Clean Ruby syntax to build ImageMagick commands
13
+ * Composable Image objects
14
+ * ImageMagick's `STDERR` output revealed in a Ruby exception
15
+ * Ability to save intermediate images for debugging
16
+ * Minimal memory consumption on shell-outs thanks to
17
+ [posix-spawn](https://github.com/rtomayko/posix-spawn)
18
+ * Emphasis on performing the whole transformation in a single command
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ gem 'skeptick'
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install skeptick
33
+
34
+ ## Usage
35
+
36
+ To use Skeptick, you simply require it and include the module into your class.
37
+
38
+ ```ruby
39
+ require 'skeptick'
40
+
41
+ class MyClass
42
+ include Skeptick
43
+
44
+ def convert_some_image
45
+ cmd = convert(to: 'result.png') do
46
+ # ...
47
+ end
48
+
49
+ cmd.build
50
+ end
51
+ end
52
+ ```
53
+
54
+ The `cmd` object seen in above example can be inspected to see the exact command
55
+ that Skeptick will run. Simply use `cmd.inspect` or `cmd.to_s`. Skeptick never
56
+ runs anything until you call `build` (except for one very special case), so you
57
+ can inspect commands all you want before executing them.
58
+
59
+ If you don't want to require all of Skeptick, you can just require the core, and
60
+ and select any specific sugar you want.
61
+
62
+ ```ruby
63
+ require 'skeptick/core'
64
+ require 'skeptick/sugar/resizing'
65
+ require 'skeptick/sugar/composition'
66
+ ```
67
+
68
+ See the `lib/skeptick/sugar` dir for all the goodies.
69
+
70
+ In Rails Skeptick will automatically use `Rails.logger` and `Rails.root` as
71
+ `cd_path`. You can also configure your own.
72
+
73
+ ```ruby
74
+ Skeptick.logger = MyLogger.new
75
+ Skeptick.cd_path = '/some/dir'
76
+ ```
77
+
78
+ You can enable `debug_mode` to display every executed command in the log.
79
+
80
+ ```ruby
81
+ Skeptick.debug_mode = true
82
+ ```
83
+
84
+ ## DSL
85
+
86
+ ![Skeptick Logo](https://raw.github.com/maxim/skeptick/master/logo.png)
87
+
88
+ This picture is produced with the following script
89
+
90
+ ```ruby
91
+ include Skeptick
92
+
93
+ image_size = '400x120'
94
+ left, top = 8, 80
95
+
96
+ # Build a picture with built-in tile:granite: texture and using the
97
+ # skeptick-provided sugar method `rounded_corners_image`
98
+ paper = rounded_corners_image(size: image_size, radius: 25) do
99
+ set :size, image_size
100
+ image 'tile:granite:'
101
+ apply '-brightness-contrast', '38x-33'
102
+ apply :blur, '0x0.5'
103
+ end
104
+
105
+ # Build a text image that says "Skeptick" using specified font, add gradient
106
+ text = image do
107
+ canvas :none, size: '395x110'
108
+ font 'Handwriting - Dakota Regular'
109
+ set :pointsize, 90
110
+ set :fill, 'gradient:#37e-#007'
111
+ write 'Skeptick', left: left, top: top
112
+ apply :blur, '0x0.7'
113
+ end
114
+
115
+ bezier = \
116
+ "#{left + 17 }, #{top + 17} #{left + 457}, #{top - 13} " +
117
+ "#{left + 377}, #{top + 27} #{left + 267}, #{top + 27}"
118
+
119
+ # Draw a curve that will appear underneath the text using bezier coordinates
120
+ curve = image do
121
+ canvas :none, size: '395x110'
122
+ set :strokewidth, 2
123
+ set :stroke, 'gradient:#37e-#007'
124
+ draw "fill none bezier #{bezier}"
125
+ end
126
+
127
+ # Combine text and curve using `:over` blending, multiply it with paper using
128
+ # `:multiply` blending, and add a torn effect using Skeptick-provided sugar
129
+ # method `torn_paper_image`
130
+ torn = torn_paper_image(
131
+ paper * (text + curve),
132
+ spread: 50,
133
+ blur: '3x10'
134
+ )
135
+
136
+ # Create a convert command with all of the above and run it
137
+ logo = convert(torn, to: "#{File.dirname(__FILE__)}/logo.png")
138
+ logo.build
139
+
140
+ # This is what the resulting command looks like
141
+ # You can see it by running `logo.to_s`
142
+ #
143
+ # convert (
144
+ # (
145
+ # (
146
+ # -size 400x120 tile:granite:
147
+ # -brightness-contrast 38x-33 -blur 0x0.5
148
+ # (
149
+ # +clone -alpha transparent -background none
150
+ # -draw roundrectangle 1,1 400,120 25,25
151
+ # )
152
+ # -alpha set -compose dstin -composite
153
+ # )
154
+ #
155
+ # (
156
+ # -size 395x110 canvas:none
157
+ # -font Handwriting---Dakota-Regular -pointsize 90
158
+ # -fill gradient:#37e-#007 -draw text 8,80 'Skeptick'
159
+ # -blur 0x0.7 -size 395x110 canvas:none -strokewidth 2
160
+ # -stroke gradient:#37e-#007
161
+ # -draw fill none
162
+ # bezier 25, 97 465, 67 385, 107 275, 107
163
+ # -compose over -composite
164
+ # )
165
+ #
166
+ # -compose multiply -composite
167
+ # )
168
+ #
169
+ # (
170
+ # +clone -alpha extract -virtual-pixel black -spread 50
171
+ # -blur 0x3 -threshold 50% -spread 1 -blur 0x.7
172
+ # )
173
+ #
174
+ # -alpha off -compose copy_opacity -composite
175
+ # ) logo.png
176
+
177
+ ```
178
+
179
+ ## All those little commands
180
+
181
+ A lot of things happened in the above script, no worries, it's just a showcase.
182
+ I bet the first thing you noticed is a shitstorm of little method names like
183
+ `apply`, `canvas`, `font`, `write`, `draw`, etc. Well, they are all sugar. We
184
+ will cover sugar later in teh given parchment.
185
+
186
+ There are actually only three real commands in all of Skeptick: `convert`,
187
+ `set`, and `image`.
188
+
189
+ ### Convert
190
+
191
+ `convert` can be used both outside and inside a transformation block. You could
192
+ say for example this.
193
+
194
+ ```ruby
195
+ command = convert('image1.png', to: 'image2.png') do
196
+ set '-resize', '200x200'
197
+ end
198
+
199
+ # OUTPUT:
200
+ # convert image1.png -resize 200x200 image2.png
201
+ ```
202
+
203
+ Or you could put it inside, and it will become a parenthesized subcommand.
204
+
205
+ ```ruby
206
+ command = convert('image1.png', to: 'image2.png') do
207
+ convert do
208
+ set '+clone' # pull in image1 into parentheses
209
+ set '-resize 100x100' # resize image1's clone in memory
210
+ end
211
+
212
+ set '-compose over'
213
+ set '-composite'
214
+ end
215
+
216
+ # OUTPUT:
217
+ # convert image1.png ( +clone -resize 100x100 )
218
+ # -compose over -composite image2.png
219
+ ```
220
+
221
+ If you love parentheses a lot, you could nest `convert` infinitely. However,
222
+ ImageMagick's `clone`, `delete`, and `swap` are your friends, learn them to
223
+ cure parenthethitis.
224
+
225
+ Oh, speaking of nesting — we can reuse that whole command inside another command
226
+ by passing it to `convert` in place of an image filepath.
227
+
228
+ ```ruby
229
+ new_command = convert(command, to: 'whatever.png') do
230
+ set '-resize 300x300'
231
+ end
232
+
233
+ # OUTPUT:
234
+ # convert
235
+ # ( image1.png ( +clone -resize 100x100 ) -compose over -composite )
236
+ # -resize 300x300 whatever.png
237
+ ```
238
+
239
+ See what I did there? It's composability. If you have a `convert` object in a
240
+ variable, you can use it inside another `convert` object down the line.
241
+
242
+ ### Set
243
+
244
+ `set` appends a string to your command. You can give it any arguments, it
245
+ doesn't care, it will just `to_s` and concatenate them.
246
+
247
+ ```ruby
248
+ # All same thing
249
+ set '-resize 100x100'
250
+ set '-resize', '100x100'
251
+ set :resize, '100x100'
252
+ ```
253
+
254
+ Yeah that last one is special convenience. If an argument to `set` is a symbol,
255
+ it will convert it to `"-#{symbol}"`. If you need `+resize` type of thing you'd
256
+ just have to use a string, or sugar, but later on that.
257
+
258
+ ### Image
259
+
260
+ `image` is very similar to `convert`. However, `convert` is a command object
261
+ that may contain many images, settings, operators, nested converts, etc. Image
262
+ is also a command object that can contain many settings and operators, but it
263
+ can only contain one image reference inside of it. The reference can be a path,
264
+ a nested convert, or a special string representing a built-in imagemagick image,
265
+ but it can be only one.
266
+
267
+ ```ruby
268
+ command = convert(to: '/path/to/result.png') do
269
+ image '/path/to/image.png'
270
+ set :resize, '200x200'
271
+ end
272
+
273
+ # OUTPUT:
274
+ # convert /path/to/image.png -resize 200x200 /path/to/result.png
275
+ ```
276
+
277
+ In this case we declared an image inside a `convert` which references a path.
278
+ Instead we could create an image that references a built-in image.
279
+
280
+ ```ruby
281
+ command = convert(to: '/path/to/result.png') do
282
+ image 'rose:'
283
+ end
284
+
285
+ # OUTPUT:
286
+ # convert rose: /path/to/result.png
287
+ ```
288
+
289
+ You can save image objects in variables, and pass them around, but unlike
290
+ `convert`, you cannot run them standalone.
291
+
292
+ ```ruby
293
+ rose_image = image('rose:')
294
+ command = convert(rose_image, to: '/path/to/result.png')
295
+
296
+ # OUTPUT:
297
+ # convert rose: /path/to/result.png
298
+ ```
299
+ See, we had to wrap it in a `convert` in order to use it. You could also append
300
+ this image at any point inside the convert block.
301
+
302
+ ```ruby
303
+ rose_image = image('rose:')
304
+ command = convert(to: '/path/to/result.png') do
305
+ image rose_image
306
+ end
307
+
308
+ # OUTPUT:
309
+ # convert rose: /path/to/result.png
310
+ ```
311
+
312
+ As mentioned above, an image can come with its own settings and operators.
313
+
314
+ ```ruby
315
+ rose_image = image do
316
+ set :background, 'transparent'
317
+ image 'rose:'
318
+ apply :resize, '200x200'
319
+ end
320
+
321
+ command = convert(to: '/path/to/result.png') do
322
+ image rose_image
323
+ end
324
+ ```
325
+
326
+ You could do all of this inline, the output will be the same.
327
+
328
+ ```ruby
329
+ command = convert(to: '/path/to/result.png') do
330
+ image do
331
+ set :background, 'transparent'
332
+ image 'rose:'
333
+ apply :resize, '200x200'
334
+ end
335
+ end
336
+
337
+ # OUTPUT:
338
+ # convert -background transparent rose: -resize 200x200 /path/to/result.png
339
+ ```
340
+
341
+ If you have a `convert` object you can pass it as an image too.
342
+
343
+ ```ruby
344
+ saved_convert = convert(to: 'foo.png') do
345
+ image 'rose:'
346
+ set :resize, '200x200'
347
+ end
348
+
349
+ another_convert = convert(to: 'bar.png') do
350
+ image saved_convert
351
+ apply :blur, '0x0.5'
352
+ end
353
+
354
+ # OUTPUT
355
+ # convert ( rose: -resize 200x200 ) -blur 0x0.5 bar.png
356
+ ```
357
+
358
+ Nesting possibilities are endless.
359
+
360
+ ## Sugar
361
+
362
+ Skeptick comes with a bunch of sugar. When you require Skeptick, you can simply
363
+ require everything. This includes all the sugar.
364
+
365
+ ```ruby
366
+ require 'skeptick'
367
+ ```
368
+
369
+ However, you can require just the core stuff described above, and select any
370
+ sugar you want.
371
+
372
+ ```ruby
373
+ require 'skeptick/core'
374
+ require 'skeptick/sugar/composition'
375
+ ```
376
+
377
+ ### Composition Sugar
378
+
379
+ Composition is sugar that adds `compose` shortcut to Skeptick's DSL.
380
+
381
+ ```ruby
382
+ command = compose(:multiply, 'a.png', 'b.png', to: 'out.png') do
383
+ with '-resize', '200x200'
384
+ end
385
+
386
+ # OUTPUT:
387
+ # convert a.png b.png -compose multiply -resize 200x200 -composite out.png
388
+ ```
389
+
390
+ It takes the blending type as the first argument, and injects some extra stuff
391
+ into the resulting command, but really it's just a wrapper around `convert` as
392
+ you could easily see in its implementation.
393
+
394
+ ```ruby
395
+ def compose(blending, *args, &blk)
396
+ convert(*args, &blk).tap do |c|
397
+ c.append :compose, blending.to_s
398
+ c.append :composite
399
+ end
400
+ end
401
+ ```
402
+
403
+ As usual, you don't have to list your images as method arguments like that.
404
+ Instead you could declare them inside the block using the `image` method. The
405
+ following command does the same thing.
406
+
407
+ ```ruby
408
+ command = compose(:multiply, to: 'out.png') do
409
+ image 'a.png'
410
+ image 'b.png'
411
+ with '-resize', '200x200'
412
+ end
413
+ ```
414
+
415
+ Since most of Skeptick's power comes from the ability to infinitely nest things,
416
+ here's a an example involving a nested `compose`.
417
+
418
+ ```ruby
419
+ command = convert('image1.png', to: 'result.png') do
420
+ compose(:multiply) do
421
+ image 'image3.png[200x200]'
422
+
423
+ convert 'image4.png' do
424
+ with '-unsharp', '0x5'
425
+ end
426
+
427
+ end
428
+ end
429
+
430
+ # OUTPUT:
431
+ # convert
432
+ # image1.png ( image3.png[200x200] ( image4.png -unsharp 0x5 ) -compose
433
+ # multiply -composite ) result.png"
434
+ ```
435
+
436
+ Notice how we nest `compose` inside of `convert`, and then `convert` inside of
437
+ `compose`. The output of each acts like any declared image. In other words,
438
+ wherever you would write `image "foo.png"` you could also write a nested
439
+ command.
440
+
441
+ ### Composition Operators
442
+
443
+ This is more of a gimmick than a real feature, but you can use math operators
444
+ like `+`, `-`, `*`, `/`, `&`, `|` to compose images. These are all based on
445
+ `compose` method. Here's a multiply example.
446
+
447
+ ```ruby
448
+ image1 = image('foo.png')
449
+ image2 = image('bar.png')
450
+ result = convert(image1 * image2, to: 'baz.png')
451
+
452
+ # OUTPUT:
453
+ # convert ( foo.png bar.png -compose multiply -composite ) baz.png
454
+ ```
455
+
456
+ As you can see, this is equivalent of simply using `compose`.
457
+
458
+ ```ruby
459
+ # Same thing
460
+ result = compose(:multiply, 'foo.png', 'bar.png', to: 'baz.png')
461
+ ```
462
+
463
+ Check out `lib/skeptick/sugar/composition.rb` for what these operators do.
464
+
465
+ ### Sequence Manipulation Sugar
466
+
467
+ Skeptick provides methods `clone`, `delete`, and `swap` to
468
+ manipulate declared images in a sequence, just like in ImageMagick CLI.
469
+
470
+ ```ruby
471
+ command = compose(:over, 'image1.png', to: 'result.png') do
472
+ # You could think of image sequence as a ruby array. Here's what it would
473
+ # look like right now.
474
+ # [ 'image1.png' ]
475
+
476
+ compose(:multiply) do
477
+ image 'mask.png' # loading another image for this operation
478
+ clone(0) # cloning image1.png from outside "into parentheses"
479
+ end
480
+
481
+ # Sequence at this point:
482
+ # [ 'image1.png', 'result of compose(:multiply)' ]
483
+
484
+ delete(0) # deleting image1.png from the sequence and from memory
485
+
486
+ # Sequence at this point:
487
+ # [ 'result of compose(:multiply)' ]
488
+
489
+ # At this point the only image loaded in memory is the one produced by the
490
+ # compose(:multiply) command above. Let's load another one.
491
+
492
+ image 'image2.png'
493
+
494
+ # Sequence at this point:
495
+ # [ 'result of compose(:multiply)', 'image2.png' ]
496
+
497
+ # Now we have two images in the sequence. We can swap them in case we need
498
+ # to change their order.
499
+
500
+ swap
501
+
502
+ # Sequence at this point:
503
+ # [ 'image2.png', 'result of compose(:multiply)' ]
504
+
505
+ # Now image2.png is first in the sequence, and the output of
506
+ # compose(:multiply) is second. Since our outermost command is compose(:over),
507
+ # at this point these 2 images will be composed over each other, and the
508
+ # result written to result.png.
509
+ end
510
+
511
+ # OUTPUT
512
+ # convert
513
+ # image1.png ( mask.png -clone 0 -compose multiply -composite )
514
+ # -delete 0 image2.png +swap -compose over -composite result.png
515
+ ```
516
+
517
+ You can use `clone` and `delete` to refer to multiple images at once by passing
518
+ mutliple indexes as arguments, like `clone(0,1,2)` or `delete(0,1)`. Ranges are
519
+ also accepted. Without any arguments `clone` and `delete` are translated to
520
+ ImageMagick's `+clone` and `+delete`. They then refer to the last image in the
521
+ sequence. Same with `swap` - you can provide two indexes in arguments like
522
+ `swap(1,3)` to swap any 2 images in the sequence, or without arguments it'll
523
+ act as `+swap` - which swaps last two images.
524
+
525
+ ### Debugging Sugar
526
+
527
+ Sometimes you might want to take a look at an intermediate image that's being
528
+ generated inside parentheses, nested somewhere in your command. You can do so
529
+ with the help of `write('/path/to/img.png')`.
530
+
531
+ ```ruby
532
+ command = convert(to: 'result.png') do
533
+ compose(:multiply, 'a.png', 'b.png') do
534
+ write('~/Desktop/debug.png')
535
+ end
536
+
537
+ set '-resize', '200x200'
538
+ end
539
+ ```
540
+
541
+ In this case the result of inner `compose` command will be written to desktop,
542
+ without affecting anything else. Again, this is a feature that already exists
543
+ in ImageMagick, as becomes apparent from the resulting command.
544
+
545
+ convert
546
+ ( a.png b.png -compose multiply -composite -write ~/Desktop/debug.png )
547
+ -resize 200x200 result.png
548
+
549
+ ## Chain
550
+
551
+ This is rarely (if ever) needed, but with Skeptick you could easily create
552
+ piped commands.
553
+
554
+ ```ruby
555
+ command = chain(to: 'result.png') do
556
+ compose(:hardlight, 'a.png', 'b.png') do
557
+ with '-brightness-contrast', '2x4'
558
+ end
559
+
560
+ compose(:atop, 'c.png', :pipe)
561
+ end
562
+
563
+ # OUTPUT:
564
+ convert
565
+ a.png b.png -compose hardlight -brightness-contrast 2x4 -composite miff:- |
566
+ convert
567
+ c.png miff:- -compose atop -composite result.png
568
+ ```
569
+
570
+ Two things to note here. First of all, commands that are declared in the `chain`
571
+ block will become piped together. Second, we use a special `:pipe` symbol in
572
+ the last `compose` command. This symbol indicates where the piped-in image
573
+ should appear in the image sequence. You can see this in the output string. The
574
+ `miff:-` appears after c.png, as expected.
575
+
576
+ Documentation is to be continued...
577
+
578
+ ## Contributing
579
+
580
+ 1. Fork it
581
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
582
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
583
+ 4. Push to the branch (`git push origin my-new-feature`)
584
+ 5. Create new Pull Request