Reputation: 21
I have a script that takes 4 pictures and duplicates them to produce 8 small pictures in one. The script also adds a background to the output.
Here is what I expect:
http://img11.hostingpics.net/pics/831624stack.png
I have a code that works well. But it needs to save multiple temporary images.
So I was looking if I could merge the commands of my script to get only one image saving operation. The goal is to make the script complete faster.
//Make image smaller:
convert /home/pi/images/*.png -resize 487x296 -flop \
-set filename:f "/home/pi/imagesResized/%t%p" '%[filename:f]'.png
//Make one column from 4 images:
montage /home/pi/imagesResized/*.png -tile 1x4 \
-geometry +0+15 -background none /home/pi/line.png
//Duplicate to two columns:
montage /home/pi/line.png /home/pi/line.png -tile 2x1 \
-geometry +45+0 -background none /home/pi/photos.png
//Add the background image:
suffix=$(date +%H%M%S)
montage /home/pi/photos.png -geometry +20+247 \
-texture /home/pi/data/background_photo.png \
/home/pi/photo_${suffix}.jpg
Upvotes: 1
Views: 2972
Reputation: 90253
Here is another answer. It took inspiration from Mark Setchell's approach. Here I come up with a new command pipeline. The difference is, that I...
montage
commands in a pipeline (Mark used 1 montage
and 2 convert
s in his pipeline), and IAs input I used the four images created by the Ghostscript command in my other answer, t{1,2,3,4}.png
.
To create a suitable background image I used the command from Mark, though modified: I made the image a tad bit smaller, so that the -texture
operator from the OP could by meaningfully used:
convert -size 200x200 xc:gray +noise gaussian background.png
Then I benchmarked all three commands/scripts,...
For benchmarking, I did run 100 repetitions each. When doing so, I started each script/command in a separate terminal window, roughly at the same time. The CPU on the test machine has 4 cores. So when the commands were running in parallel, they had to deal with the same CPU- and I/O-load each, competing with each other for resources. This is the most "fair" ad-hoc performance testing setup I could come up with.
I also confirmed that the output files created by the 3 tests (photo_test1.jpg
, ms+kp_test2.jpg
and kp_test3.jpg
) are pixelwise (almost) identical.
If the output is switched to PNG (instead of JPEG, how the OP asked for), then these small differences also disappear between the 3 approaches.
Here are the results:
mkdir ./imagesResized
time for i in {1..100}; do
convert t*.png -resize 487x296\! -flop \
-set filename:f "./imagesResized/%t%p" '%[filename:f]'.png
montage ./imagesResized/*.png -tile 1x4 -geometry +0+15 \
-background none line.png
montage line.png line.png -tile 2x1 -geometry +45+0 \
-background none photos.png
montage photos.png -geometry +20+247 \
-texture background.png \
photo_test1.jpg
done
Results:
real 2m13.471s
user 1m54.306s
sys 0m14.340s
Take this as 100% time consumption.
time for i in {1..100}; do
montage t[1234].png -resize 487x296\! -flop -background none \
-tile 1x4 -geometry +0+15 miff:- \
| montage :- -background none -clone 0 -tile 2x1 -geometry +45+0 miff:- \
| montage :- -geometry +20+247 -texture background.png ms+kp_test2.jpg ;
done
Results:
real 1m50.125s
user 1m32.453s
sys 0m16.578s
About 83% time consumption compared to original commands.
time for i in {1..100}; do
convert t*.png -flop -resize 487x296\! \
-background none \
-size 0x15 \
xc:white \
-duplicate 7 \
-insert 0 \
-insert 2 \
-insert 3 \
-insert 5 \
-insert 6 \
-insert 8 \
-insert 9 \
-append \
\( +clone -size 90x0 xc:white +swap \) \
+append \
-transparent white \
miff:- \
| montage :- -geometry +65+247 -texture background.png kp_test3.png
done
Results:
real 1m34.786s
user 1m20.595s
sys 0m13.026s
About 72% time consumption compared to original commands.
Upvotes: 0
Reputation: 90253
First, let me say this: you'll save only significant processing time by putting the separate commands into one single ImageMagick command chain if your input images are quite large. You may however save disk I/O time by skipping the need to write out and read in the intermediate result images.
Your code uses two different montage
commands plus one convert
command in order to achieve a first montage.
Finally you use one montage
to place the previous result on the background.
From the top of my head, I can quickly come up with a way to combine the first three commands into a single one. The last montage step to place the intermediate results onto the background is not so easy to get right, and very likely will not save much time either. Hence, I will leave that open for now.
Unfortunately you did not provide any link to your source images. I had to create my own ones in order to answer this question. They can also serve to demo the validity of my answer.
To create four 800x600 pixel sized PNGs, I used Ghostscript with a little bit of PostScript code on the command line:
for i in 1 2 3 4 ; do
gs -o t${i}.png \
-g800x600 \
-sDEVICE=pngalpha \
-c "0.5 setgray" \
-c "0 0 800 600 rectfill" \
-c "1 0 0 setrgbcolor" \
-c "3 setlinewidth" \
-c "10 10 780 580 rectstroke" \
-c "0 setgray" \
-c "/Helvetica-Bold findfont" \
-c "560 scalefont setfont" \
-c "230 60 moveto" \
-c "(${i}) show " \
-c "showpage" ;
done
Then I first tested your code with my images.
Here is the result from the OP's commands. The result is complete, including montage on a background image from my own stock (updated), created with a command inspired by Mark Setchell's answer:
convert -size 200x200 xc:gray +noise gaussian background.png
The following was my first shot in order to come up with a single command.
It should achieve the same result as your first two commands outputting line.png
.
I already knew it wouldn't work exactly as expected in some aspects, but I still tried.
I tried it in order to see if there are other places of the command that would show problems that I didn't expect.
No worries, the explanation of the complete, final code will be at the end of the answer.
You can try to figure out how the following command works once you read the complete answer:
_col1=blue ;
_col2=red ;
convert t*.png -flop -resize 487x296\! \
\( -size 15x15 \
-clone 0 xc:${_col1} \
-clone 1 xc:${_col1} \
-clone 2 xc:${_col1} \
-clone 3 \
-append \
+write f.png \
\) null:
Here is the result of my command (right) compared to the intermediate result after your second command (left):
So, one thing I had expected happened: there is a blue spacer between each image.
I colorized it for debugging reasons.
This can be fixed by setting the color variable to none
(transparent).
Things I hadn't expected and which I only discovered after opening the resulting image f.png
:
My background was white instead of transparent.
This can be fixed by adding -background none
at the right place.
My spacing of the individual images in the columns is too narraw, being 15 pixels only.
This is because in the intermediate file line.png
of the OP the spacing is not 15 pixels, but 30 pixels.
His parameter -geometry +0+15
for the montage
creating the columns does add the 15 pixels on top as well as on bottom of each image.
My command (using convert ... -append
instead of montage
) does not allow for additional -geometry
settings which would have the same effect.
But his can be fixed by adding more xc:{_col1}
spacers into my command.
So here is the next iteration.
It integrates the effect of the third command from the OP.
This is achieved by adding +duplicate
for duplicating the first created column.
Then it adds +append
to append the duplicate column horizontally (-append
would do so vertically):
_col1=blue ;
_col2=red ;
convert t*.png -flop -resize 487x296\! \
\( -size 15x15 \
-background none \
xc:${_col1} \
-clone 0 xc:${_col1} xc:${_col1} \
-clone 1 xc:${_col1} xc:${_col1} \
-clone 2 xc:${_col1} xc:${_col1} \
-clone 3 \
xc:${_col1} \
-append \
+duplicate \
-size 45x45 xc:${_col2} \
+append \
+write f2.png \
\) null:
Again one thing I had expected happened:
The red spacer between the two columns was on the right instead of sitting in between the columns.
We can fix that by swapping the last two images that get +append
-ed.
This can be done by adding the +swap
operator at the right place.
Also, the same thing as with the inter-image spacing in the first column will apply to the spacing in bewteen the columns: I have to double it.
I will not care at the moment that the same space (45 pixels) is not added to the +append
-ed columns left and right.
So here is one more iteration:
_col1=red ;
_col2=blue ;
convert t*.png -flop -resize 487x296\! \
\( -background none \
-size 15x15 \
xc:${_col1} \
-clone 0 xc:${_col1} xc:${_col1} \
-clone 1 xc:${_col1} xc:${_col1} \
-clone 2 xc:${_col1} xc:${_col1} \
-clone 3 \
xc:${_col1} \
-append \
+duplicate \
-size 90x90 xc:${_col2} \
+swap \
+append \
+write f3.png \
\) null:
Here is the result:
photos.png
created by the OP code after the third command.
What's missing now is an explanation with a breakdown of the individual operations I packed into a single command:
\(
:\)
:-size 15x15
:xc:${_col1}
:xc:
is just an alias to canvas:
, but it is faster to type.-clone 0
:t1.png
.
-clone 1
copies t2.png
, -clone 2
copies t3.png
, etc.
-clone
or +clone
work best inside sideway processing chains, hence the previously explained use of \(
and \)
.-append
:t1.png
, ... t4.png
.+duplicate
:+clone
.
It copies the last image in the currently loaded image stack.
In this case the last image (and only remaining one inside the sideways pipeline) is the result of the previous -append
operation.
This operation had created the first column of 4 images, spaced apart by the red spacers.+append
:-append
operation, its copy created by +duplicate
, and the 90x90
sized xc:
-canvas.+swap
:+write
:+write
operation is finished, the previously loaded images remain all on the stack.
These images remain unchanged, and processing can continue.
However, we are finished now and won't continue in this case.
Hence we close the side-way process with a \)
.null
:
Now that we closed the sideway process, ImageMagick puts the resulting image from the sideway into the main pipeline again.
Remember, +write
didn't finish the processing, it wrote an file to disk that is meant to be an intermediate result.
In the main pipeline, there are now still the original t1.png
... t4.png
plus the result from the sideway processing.
However we will not do anything with them.
We will take the intermediate result from +write
as our final one.
But the convert
command expects to now see an output filename.
If it doesn't see one, it will complain and show us an error message.
Hence we tell it to write off all it has loaded and discard all images from the stack.
To achieve this, we use null:
as output filename.
(If you feel adventurous, use out.png
as a filename instead of null:
.
You will see that ImageMagick actually creates multiple out-0.png
, out-1.png
,...out-3.png
filenames.
You will find that out-4.png
is the same as f.png
, and out-{0,1,2,3}.png
are the same as the input images. --
You could also replace null:
by -append output.jpg
and see what happens then...)
Now for the speed comparison...
For a first rough benchmark, I did run the OP's first three commands in a loop with 100 iterations. Then I did run my own command 100 times as well.
Here are the results:
So my single command saved roughly 20% time over the original commands from the OP.
Given that my disk I/O performance can be assumed to be pretty fast (the test system has an SSD) in comparison with a spinning harddisk, the speed gain from the merged command (which avoids too many temporary file write/reads) may be more distinct on a system with a slower disk.
To check if a little re-architecture of the command (where not so many loaded images are simply discarded at the end, as can be seen by the null:
output file name) would gain more improvements, I also tried this:
convert t*.png -flop -resize 487x296\! \
-background none \
-size 0x15 \
xc:red \
-duplicate 7 \
-insert 0 \
-insert 2 \
-insert 3 \
-insert 5 \
-insert 6 \
-insert 8 \
-insert 9 \
-append \
\( +clone -size 45x0 xc:blue +swap \) \
+append \
f4.png
The architecture of this command is a bit different.
First, it loads all the images. It -flop
s them and it -resize
s them.
Second, it creates a single 15x15 pixels canvas, which then is also placed on the image stack.
Third, it creates 7 additional copies of that canvas.
Now there are 12 images on the stack:
the 4 input files, 1 xc:
-canvas, and 7 copies of the canvas.
Then, it uses a series of -insert N
operations.
The -insert N
operation manipulates the order of the image stack.
It removes the last image from the stack and inserts it into image index position N
.
When the -insert
series of operations starts, there are 4 images (t1
, t2
, t3
, t4
) on the stack, plus 8 "spacers" (s
).
This is their original order:
index: 0 1 2 3 4 5 6 7 8 9 10 11
image: t1 t2 t3 t4 s s s s s s s s
I've picked the index numbers in a way so that from above original order the changed new order after all -insert N
operations are finished is:
index: 0 1 2 3 4 5 6 7 8 9 10 11
image: s t1 s s t2 s s t3 s s t4 s
Since all spacers s
are 15 pixels wide, this achieves the same spacing as my initial command.
Next, a similar sideway processing happens: this time to +clone
the result of the previous -append
operation and to create the horizontal spacer.
Last, the +append
(which operates on the result from the previous -append
and from the sideway-process) creates the final result, f4.png
.
When I benchmarked this last command, I got the following results for 100 repetitions of each command:
So the speed gain is not quite so notable, roughly 3% better if you want to trust these numbers.
(It should be noted, that I did run both loops in parallel on the same machine to create more fairness in the benchmarking competition. By running in parallel, they both have to deal with the same CPU and I/O load which may be caused by themselves as well as by other processes happening on the machine concurrently.)
Upvotes: 6
Reputation: 207465
As I don't have your images or your texture or their sizes, I will show you something similar for you to adapt...
Make some input images:
convert -size 500x400 xc:black 1.png
convert -size 500x400 xc:red 2.png
convert -size 500x400 xc:green 3.png
convert -size 500x400 xc:blue 4.png
and a background texture:
convert -size 2000x2000 xc:gray +noise gaussian background.png
Now do what you asked, but without intermediate files to disk:
montage [1234].png -background none -tile 1x4 -geometry +0+15 miff:- |
convert -background none :- -size 15 xc:none -clone 0 +append png: |
convert -gravity center background.png :- -composite z.png
The first line lays out the 4 images one above the other, and sends the combined result in a MIFF
(Mutiple Image File Format) through a pipe to the next command. The second command reads an image from the pipe and appends a 15 pixel wider "spacer" and then duplicates the first column of images (with clone
) and writes as a PNG
to the next command through another pipe. The final command reads the 8 small images combined and puts them on a background, centred.
If the right hand column of images is supposed to be the left column reflected - it's hard to tell from your poor example which shows flat black boxes - you may need to change the second line of the command from
convert -background none :- -size 15 xc:none -clone 0 +append png: |
to
convert -background none :- -size 15 xc:none \( -clone 0 -flop \) +append png: |
Upvotes: 1