1

I am trying to create multiple (two or more) small images with different texts/fonts/sizes and then overlay them over a single source image on different positions in a single step (without creating temporary named files by myself).

This is what I tried so far:

  • Create a single text and merge it on a position using a pipe and a miff stream (works, but I need multiple images):

    gm convert \
      -background transparent \
      -fill black \
      -font Calibri \
      -size 300x100 \
      -pointsize 36 \
      -gravity SouthEast \
      label:'large text' \
      miff:- \
    | gm composite -geometry +10+10 miff:- source.tif out.tif
    
  • Create and merge first image, store output temp file, load temp file and merge it to source (works, but two calls to gm needed, temp file must be written and must be deleted manually):

    gm convert \
      -background transparent \
      -fill black \
      -font Calibri \
      -size 300x100 \
      -pointsize 36 \
      -gravity SouthEast \
      label:'large text' \
      miff:- \
    | gm composite -geometry +10+10 miff:- source.tif tmp.tif ; \
    gm convert \
      -background transparent \
      -fill grey \
      -font Calibri \
      -size 200x50 \
      -pointsize 12 \
      -gravity SouthEast \
      label:'small text' \
      miff:- \
    | gm composite -geometry +300+100 miff:- tmp.tif out.tif
    
  • Create multiple texts and merge them onto a position, where only the last position is counted and applied to all files (does not work, only first text is overlaid):

    { gm convert \
      -background transparent \
      -fill black \
      -font Calibri \
      -size 300x100 \
      -pointsize 36 \
      -gravity SouthEast \
      label:'large text' \
      miff:- ; \
      gm convert \
      -background transparent \
      -fill grey \
      -font Calibri \
      -size 200x50 \
      -pointsize 12 \
      -gravity SouthEast \
      label:'small text' \
      miff:- ; } \
    | gm composite miff:- -geometry +10+10 miff:- -geometry +300+100 source.tif -geometry +0+0 out.tif
    
  • Use ( ... ) syntax, replacing "text1.tif" and "text2.tif" with the gm convert commands in braces (did not work in any cases, maybe I am using it wrong?)

  • Use flatten instead of composite (did work, but all files where placed on the same position rather than on different ones, also would destroy multi-page images)
  • Use append with multiple files through a single pipe (I need them overlaid, not side by side)

Is it possible to combine three or more images over each other, so that each image has a different position on the output image, without resorting to saving and loading temp files? Alternatively, how can I set the z-order of overlaid images before writing them? If possible on gm 1.3.25 and bash 4.3, but I am open to alternatives that work.

1
  • I think your 3rd case could be made to work with named pipes.
    – Olivier
    Dec 19, 2016 at 20:13

2 Answers 2

2

From the initial request I understand that you need to merge 3 images (1 from file and 2 generated) in one shot considering that you can only work with 2 at the time.

I also understand that you don't want temporary data stored on disk. The quickest solution would be to use "tmpfs" as suggested in the previous answer by @nominal-animal.

But taking it a step further it is possible to avoid creating temporary files and basically do all processing in one shot. The solution I found is using "named pipes", they are special files that will not store data to disk and will even put on hold any transfer over pipes until the receiving part is also ready. I will not use complete commands as you provided (only the command, inputs and output), feel free to fill the arguments you need.

# First it's necessary to have the pipes created, one time operation and they can be reused over and over.
mknod /tmp/pipe1 p
mknod /tmp/pipe2 p
mknod /tmp/pipe3 p

# generate the 2 images and send each to a pipe 
gm convert ... miff:- > pipe1 &
gm convert ... miff:- > pipe2 &
# merge the source image to the 1st generated image
gm composite ... source.tif pipe1 miff:- > pipe3 &
# finally merge the result to the 2nd generated image
gm composite ... pipe3 pipe2 out.tif

It looks like the operation is done in 4 steps, but in fact all will be done in one shot without anything being written to disk.

Here is how it works and why the commands are written as they are:

  • the command to generate the 1st image will start working on the request but the output has to go into the named pipe. Because at this moment nothing is reading from the pipe the output will stall waiting for a received. The process is not going to exit or display any error, so in order to continue it is put in background.
  • 2nd image same thing just different pipe.
  • merging of the source image and the data from the 1st pipe will cause it to read from pipe1 and release the 1st generating command, but because it's output goes to another pipe it will have to wait for the final step.
  • merging of the data from the pipe3 and pipe2 will finalize the operation and generate the desired output file and release the previous 2 commands.

You may consider the pipes like the temporary files that are not files, just remember to put some things in background, otherwise the script will wait for your CTRL+C. Each command must be put in background separately! There is no issue if you put them in a script like this once the output is generated the previous commands have finished to.

A nice feature is that you can start any 3 command first in background and the last one normally, since all 4 depend on data from pipes. Also it's easy to extend to multiple commands just by adding more pipes.

There is one limitation that I see, you can't use the same pipes in parallel, if you need that you have to use different sets of pipes for each instance running in parallel otherwise there will be surprises :) Sequential processing has no such issue.

From performance perspective I don't know if this is faster then creating temporary files on a "tmpfs", so you will need to find out by testing it in real scenario. For this to work you must have enough memory to accommodate all 4 processes with their data in memory. (it depends a lot on the size of the images which you process/generate).

1

This is not an answer to the stated question, but only an aside showing that the premise of the original question, that "temp file ... must be removed manually", is completely incorrect.

(I posted this "answer" in the likely case that noticing that this premise is false, lets the OP and others feel free to use temporary files. In many cases, using temporary files makes the scripts more robust and easier to maintain.)

If a Bash script needs to use one or more temporary files, I always use the following approach:

#!/bin/bash
work="$(mktemp -d)" || exit 1
trap "cd / ; rm -rf '$work'" EXIT

This uses the mktemp helper to create a temporary work directory (in /tmp/). If mktemp is not available, or the directory cannot be created, the script is aborted.

To automatically remove the work directory and any files it may contain, we set an EXIT trap. This trap is triggered -- that is, bash executes the specified command -- whenever the shell exits; be it a normal exit, or due to an error.

The EXIT trap command changes to the root directory (just to make sure current working directory is not within the temporary directory), then removes the entire subtree. Note that because the command is within double quotes, $work is evaluated when the trap is set; this means that even if you were to change the value of the work variable later in the script, the trap would still remove the original temporary directory.

Although the trap changes the current working directory, it will not have any effect outside the trap -- either in the script elsewhere, or to other processes --, because the current working directory is specific to each process, and the current bash process will exit right after the trap command is executed.

Therefore, any temporary files created under $work, for example "$work/1", "$work/result", and so on, are automatically removed, as is the temporary directory $work itself, when the script is done.

2
  • Thank you for your detailed explanation. Do you know how performance would differ when reading and writing from the same (slow) disk and for files that are quite large (about 1 to 150 MB for each file uncompressed)? I want to minimize the needed time, so I prefer whatever solution is faster. My (maybe naive) thought was that writing files must certainly be slower than keeping everything in memory - does this still hold true?
    – user121391
    Dec 14, 2016 at 12:43
  • @user121391: No, I do not believe temporary files are slower than the alternatives. In some cases, for example with certain image file formats, temporary files may even be faster, because the readers/writers can start processing the data before all of it is read into memory, without accessing the data sequentially. Furthermore, current operating systems agressively cache data. In Linux, the temporary files' data may never be written to disk, if there is sufficient RAM. I myself use a tmpfs (a RAM-only filesystem) for /tmp, which means temporary files only exist in RAM on my system. Dec 14, 2016 at 13:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .