When you have some images and you just want to throw them together as
layers in an ora file, one of the easiest ways is to hit “open as
layers” in the gimp
image program and then export to .ora. But if you
do this a lot you might want a fast command line program to do this, and
that’s what ora_stack is.
It’s nothing very polished, it’s just something I threw together tonight, but it’s a start.
It’s made in Chicken Scheme which means it’s a bit of a hassle to compile and run if you don’t usually use it. But it’s what it is.
sudo apt-get install chicken
sudo chicken-install imlib2 s sxml-serializer srfi-42 shell
git clone https://idiomdrottning.org/ora_stack
cd ora_stack
csc -O5 ora_stack.scm
sudo cp ora_stack /usr/local/bin
For example, type
ora_stack top_image.png middle-file2.png bottom-file3.png output.ora
in your shell. I’m probably going to call this from shell scripts and aliases if there are some common images or templates I like to stick in there.
(require-extension imlib2 s sxml-serializer (srfi 1 42) shell posix)
Just some libraries we use. I love srfi-42 and tend to use it for
everything kinda gratuitously, even when a simple for-each
or map
would do the trick.
Then we parse the calling command line:
(define output-stem (s-chop-suffix ".ora" (last (argv))))
(define input-filenames (drop-right (cdr (argv)) 1))
Then some guard clauses. This version doesn’t check if the input filenames refer to real image files, let alone make sure they’re png:s. But it does attempt to try to avoid clobbering what you’ve got. If you do want clobbering (maybe you update your images often), just remove the clause and recompile.
(unless (< 2 (length (argv))) (error "Usage: ora_stack input-filenames... output-file"))
(unless (null? (glob output-stem)) (error "Exiting to prevent clobbering working directory"))
(unless (null? (glob (string-append output-stem ".ora"))) (error "Exiting to prevent clobbering output file"))
I’ll set the frame according to the first (top) layer. While mypaint
,
gimp
and krita
can all change the frame size easily enough, it’s
even easier to reorder layers, so a tip is to make sure the layer you
want to match the frame size is top layer, i.e. first filename on the
command line.
(define w 0)
(define h 0)
(let* ((pic (image-load (first input-filenames))))
(set! w (image-width pic))
(set! h (image-height pic))
(image-destroy pic))
Then I’ll define a convenience function to get the new paths for the image layers.
(define (newp str)
(s-prepend "data/" (last (s-split "/" str))))
Here is where I create a working directory for the ora’s contents, and add a stack file to it.
(let ((dir (string-append output-stem "/data"))
(stack `(*TOP* (*PI* xml "version='1.0' encoding='UTF-8'")
(image (@ (http://mypaint.org/ns/openraster:frame-active "true")
(w ,w)
(h ,h))
(version "0.0.5")
(stack (@ (visibility "visible")
(opacity "1.0")
(isolation "isolate")
(composite-op "svg:src-over"))
,@(list-ec (: imname (index num) input-filenames)
`(layer (@
(composite-op "svg:src-over")
(opacity "1.0")
(src ,(newp imname))
,@(if (zero? num)
'((selected "true"))
'())
(name ,(s-chop-suffix ".png" (last (s-split "/" imname))))
(x 0)
(y 0)))))))))
(create-directory dir #t)
(serialize-sxml stack output: (string-append output-stem "/stack.xml")
ns-prefixes: '((mypaint . "http://mypaint.org/ns/openraster"))))
Adding #t
to create-directory
creates the parents so I can create
the /data subdir along with creating the working directory.
I’ll just comment on a couple of excerpts from that larger block of code:
,@(if (zero? num)
'((selected "true"))
'())
Unquote-splicing a null list looks like a weird hack, but it’s a useful pattern for when you conditionally want to include something in a list. I sometimes quasiquote just so I can do this.
(x 0)
(y 0)
This means that each layer will be left-justified and top-justified.
mypaint
, gimp
and krita
all have easy tools for moving the layers
where you want them. If you change this, remember to unquote:
(x ,(your-special-function arg arg))
(y 0)
OK, now we’re done with creating directory and the stack file, the rest
is just pretty much Chicken’s equivalent of shell script basics.
file-link
looks weird but the posix unit doesn’t really have a
“copy-file” as far as I know. But it saves on disk space and then the
link is deleted as the corresponding image data is zipped up into the
.ora file.
(with-output-to-file (string-append output-stem "/mimetype") (lambda () (display "image/openraster")))
(do-ec (: imname input-filenames)
(file-link imname (string-append output-stem "/" (newp imname))))
(change-directory output-stem)
(print (current-directory))
(run ,(string-append "zip -r ../" output-stem ".ora *"))
(change-directory "..")
(delete-directory output-stem #t)