Search code examples
randomimagemagick

Randomly compose images by layering pngs


I am trying to use Imagemagick v7 to batch create images by randomly combining layers which are transparent pngs, all at the same size.

I am a total newbie so I copied the code I found here: https://stackoverflow.com/a/27621140/17835805

#!/bin/bash

# Number of output files - edit freely :-)
NFILES=10

# Build arrays of filenames in each layer, assume directories are "Layer0", "Layer1" etc
IFS=$'\n' L0files=($(find "Layer 0" -name "*.png"))
IFS=$'\n' L1files=($(find "Layer 1" -name "*.png"))
IFS=$'\n' L2files=($(find "Layer 2" -name "*.png"))
IFS=$'\n' L3files=($(find "Layer 3" -name "*.png"))

# Produce NFILES output files
for i in `seq 1 $NFILES`; do

   # Choose random index into each array of filenames
   index0=$( jot -r 1  0 $((${#L0files[@]} - 1)) )
   index1=$( jot -r 1  0 $((${#L1files[@]} - 1)) )
   index2=$( jot -r 1  0 $((${#L2files[@]} - 1)) )
   index3=$( jot -r 1  0 $((${#L3files[@]} - 1)) )

   # Pick up files as specified by the random index
   f0=${L0files[index0]}
   f1=${L1files[index1]}
   f2=${L2files[index2]}
   f3=${L3files[index3]}

   # Generate output filename, "output-nnn.png" 
   # ... where nnn starts at 0 and goes up till no clash
   i=0
   while :; do
      out="output-$i.png"
      [ ! -f "$out" ] && break
      ((i++))
   done

   echo $f0, $f1, $f2, $f3 "=> $out"
   convert "$f0" "$f1" -composite "$f2" -composite "$f3" -composite "$out"
done

I managed to make it randomly pick my layers but:

  1. No files are created in the output folder.
  2. No matter what I put for NFILES, it always stops at the first one ("output-o.png").

Would be great if I could also make sure that, once a layer has been randomly picked and used, it is not picked again. Perhaps deleting the file with ephemeral?


Solution

  • ok, thanks to the great help of Mark Setchell, finally figured out a solution.

    #!/bin/bash
    
    # Number of output files
    NFILES=20
    BASE="$HOME/foldername"
    
    
    
    # Function to read a random line from file, deleting it if required
    
    randomLine () {
       filename=$1; delete=$2
    
       # Check file is not empty
       declare -i nlines
       nlines=$(wc -l < "$filename")
       if [ $nlines -eq 0 ] ; then
          2>&1 echo "ERROR: $filename is empty"
          exit 1
       fi
       # Read lines into array
       IFS=$'\n' read -d '' -r -a lines < "$filename"
    
       # Choose a random line number
       ((sel = RANDOM % nlines))
    
       # Output result for caller in global variable
       retval="${lines[sel]}"
    
       # Delete the line if required
       if [ $delete -eq 1 ] ; then
          lines[sel]="JUNK"
          printf "%s\n" "${lines[@]}" | grep -v "JUNK" > "$filename"
       fi
    }
    
    
    # Main program
    
    
    # Create output directory
    mkdir -p "${BASE}/export"
    
    # Build lists of filenames in each layer - write lists in /tmp
    for ((layer=1; layer<=6; layer++)) ; do
       find "${BASE}/parts/p${layer}" -name "*.png" > /tmp/L${layer}
    done
    
    # Produce NFILES output files
    for ((n=0; n<$NFILES; n++)) ; do
    
       # Pick up files randomly, deleting 2, 4 and 6, reusing others
       randomLine "/tmp/L1" 0; f1="$retval"
       randomLine "/tmp/L2" 1; f2="$retval"
       randomLine "/tmp/L3" 0; f3="$retval"
       randomLine "/tmp/L4" 1; f4="$retval"
       randomLine "/tmp/L5" 0; f5="$retval"
       randomLine "/tmp/L6" 1; f6="$retval"
    
       # Generate output filename
       for ((i=0;;i++)) ; do
          out="${BASE}/output-$i.png"
          [ ! -f "$out" ] && break
       done
    
       echo $f1, $f2, $f3, $f4, $f5, $f6 "=> $out"
       convert "$f1" "$f2" -composite "$f3" -composite "$f4" -composite "$f5" -composite "$f6" -composite "$out"
    
    done