Reputation: 1929
I am working on a project that has multiple plugins, and one of the plugins needs to composite two animated images on top of one another. The problem is that one of these will be user-defined and will most likely not match up frame-wise to the other.
How I get around this currently is by loading both images, finding the lowest common multiple of their frame count, then extracting them over and over until I hit their LCM, then re-combining the extracted images into separate gif files, then compositing those two together. Here is the relevant code as far as the extending goes:
def self.extend_gifs(one, two)
img_one = Magick::ImageList.new(one).coalesce
img_two = Magick::ImageList.new(two).coalesce
lcm = img_one.length.lcm(img_two.length)
i = 0
while i < lcm
img_one[i % img_one.length].write("tmp/1_#{i.to_s.rjust(4, "0")}.png")
img_two[i % img_two.length].write("tmp/2_#{i.to_s.rjust(4, "0")}.png")
i += 1
end
%x{convert -limit memory 256MiB -dispose Background tmp/1_*.png tmp/extend_1.gif}
%x{convert -limit memory 256MiB -dispose Background tmp/2_*.png tmp/extend_2.gif}
Dir["tmp/*.png"].each { |f| File.delete f }
end
The problem is that this is deployed on a server with 1GB of memory, and running this code in ruby takes upwards of 500MB or more depending on how many frames or how big the user defined gif is, not including the memory that the convert process takes. Because of this, I want to move away from using rmagick and ruby and drop the process onto the external tools I have available which can limit memory usage to some degree.
This brings me to my question: How would I replicate this process using the imagemagick command line tools?
Edit:
By request, here are some example images and output. This should work for any two images though, assuming one has a transparent background.
Background image (3 frames):
Foreground image (11 frames):
Result (foreground repeated 3 times, background repeated 11 times).
Upvotes: 1
Views: 1112
Reputation: 1930
It's not the LCM of the frame counts you need to deal with, it's the LCM of the cycle times.
So long as the cycle times are the same, you can combine a 3-frame GIF with an 11-frame GIF to yield a single 11, 12, 13 or 14-frame GIF by interleaving their frames and splitting up the inter-frame delays where necessary.
Conversely, if you want to combine two simple 2-frame GIFs with cycle times of 1000ms and 1001ms respectively, your resulting combined GIF will have 1,001,000 frames!
The best way is to overlay the animations in the browser. I understand you have architectural limitations on positioning and overlaying two images. You can still combine them in the browser using a single IMG tag, like this:
<img src="foreground.gif" style="background-image: url(background.gif)" />
Then all you need to do on the server is the very simple task of resizing the user-selected foreground.gif so it is the same size as your predefined background.gif.
ps: If you do want to combine GIFs with ImageMagick or other tools, you can reduce your file size greatly by analyzing the original palettes and creating a single global palette to use for all frames, them optimize to remove redundant pixels that don't change between pairs of frames. Your 450KB combined animation cannot really be optimized because each frame has its own separate palette derived from the two palettes of the two original frames.
Upvotes: 0
Reputation: 1929
So after some messing around and viewing some other questions that were unrelated I found out that you can specify the same image multiple times to continue extraction. My updated code isn't cleaned yet, but here it is:
def self.extend_gifs(one, two)
img_one = Magick::ImageList.new(one)
img_two = Magick::ImageList.new(two)
# Get the lcm of the frame counts
lcm = img_one.length.lcm(img_two.length)
# Get how many times to extract for each
mult_one = lcm / img_one.length
mult_two = lcm / img_two.length
# Multiply the image + coalecence out that many times
string_one = (" #{one} -coalesce ") * mult_one
string_two = (" #{two} -coalesce ") * mult_two
# Split images up so their frames match
%x{convert -limit memory 256MiB #{string_one} tmp/1_%04d.png}
%x{convert -limit memory 256MiB #{string_two} tmp/2_%04d.png}
# Merge gifs back together
%x{convert -limit memory 256MiB -dispose Background tmp/1_*.png tmp/extend_1.gif}
%x{convert -limit memory 256MiB -dispose Background tmp/2_*.png tmp/extend_2.gif}
# Delete temp files
Dir["tmp/*.png"].each { |f| File.delete f }
end
Upvotes: 0
Reputation: 207660
Got started but gotta go out with the kids :-)
Will return later...
#!/bin/bash
# Get filenames from params or use defaults if none supplied
im1=${1:-http://i.imgur.com/C9IT9SY.gif}
im2=${2:-http://i.imgur.com/MlFOzAE.gif}
# Coalesce them
convert "$im1" -coalesce i1.gif
convert "$im2" -coalesce i2.gif
# Get number of frames in each sequence
nframes1=$(identify -format "%n" i1.gif)
nframes2=$(identify -format "%n" i2.gif)
echo DEBUG: nframes1:$nframes1
echo DEBUG: nframes2:$nframes2
# Calculate lcm using awk
lcm=$(awk -v n1=$nframes1 -v n2=$nframes2 '
function gcd(m,n,t) {
while (n != 0) {
t = m
m = n
n = t % n
}
return m
}
function lcm(m,n,r) {
if (m == 0 || n == 0)
return 0
r = m * n / gcd(m, n)
return r < 0 ? -r : r
}
BEGIN {print lcm(n1,n2)}')
echo $lcm
i=0
while [[ $i -lt $lcm ]]; do
f1=$((i % nframes1))
f2=$((i % nframes2))
n1=$(printf "1_#%04d" $i)
n2=$(printf "2_#%04d" $i)
echo $i,$f1,$f2,$n1,$n2
((i++))
done
Output
DEBUG: nframes1:3
DEBUG: nframes2:11
LCM:33
0,0,0,1_#0000,2_#0000
1,1,1,1_#0001,2_#0001
2,2,2,1_#0002,2_#0002
3,0,3,1_#0003,2_#0003
4,1,4,1_#0004,2_#0004
5,2,5,1_#0005,2_#0005
6,0,6,1_#0006,2_#0006
7,1,7,1_#0007,2_#0007
8,2,8,1_#0008,2_#0008
9,0,9,1_#0009,2_#0009
10,1,10,1_#0010,2_#0010
11,2,0,1_#0011,2_#0011
12,0,1,1_#0012,2_#0012
13,1,2,1_#0013,2_#0013
14,2,3,1_#0014,2_#0014
15,0,4,1_#0015,2_#0015
16,1,5,1_#0016,2_#0016
17,2,6,1_#0017,2_#0017
18,0,7,1_#0018,2_#0018
19,1,8,1_#0019,2_#0019
20,2,9,1_#0020,2_#0020
21,0,10,1_#0021,2_#0021
22,1,0,1_#0022,2_#0022
23,2,1,1_#0023,2_#0023
24,0,2,1_#0024,2_#0024
25,1,3,1_#0025,2_#0025
26,2,4,1_#0026,2_#0026
27,0,5,1_#0027,2_#0027
28,1,6,1_#0028,2_#0028
29,2,7,1_#0029,2_#0029
30,0,8,1_#0030,2_#0030
31,1,9,1_#0031,2_#0031
32,2,10,1_#0032,2_#0032
Upvotes: 1