Combining multiple images in ImageMagick with relative (not absolute) offsets - imagemagick

I'm looking for the most efficient way to stitch multiple images together in ImageMagick, on top of a background image, such that the spacing / padding between the overlaid images is consistent?
I've investigated use of +append, convert -composite, and convert with -page and -layers merge.
The following command (convert -composite) works, but requires precalculation of image dimensions in order to specify absolute offsets. Really, I want a 10 pixel gap between the end of the FIRST layered image and the start of the second layered image, but the only way I can see to achieve that is by specifying the absolute offset from the top-left corner of the canvas.
convert \
background.jpg \
first.jpg -gravity Northwest -geometry +10+10 -composite \
second.jpg -geometry +300+10 -composite \
third.jpg -geometry +590+10 -composite \
output.jpg
I am looking for some sort of operator so that the horizontal offset can be interpreted relative to the "last" image in the layering, so instead of specifying +300+10 for the second image and +590+10 for the third, I can somehow specify a +10+10 offset.
I thought gravity would allow me to achieve that (-gravity Northwest), in the same way that float: left; works in CSS positioning, but that is not the case.
I have also had some success with the following:
convert \
-page +10+10 first.jpg \
-page +300+10 second.jpg \
-page +590+10 third.jpg \
-background transparent \
-layers merge \
layered.png
convert background.jpg layered.png -gravity Center -composite output.jpg
Both the techniques described require pre-calculation of absolute offsets, which is a bit of a pain. Is there a better way to do this?

You've overlooked the montage command.
The most simple command to add the wanted spacing with it would be to set a -frame 5 option with -mattecolor none. This works with images of different width values and spaces them all apart with a distance of 10 pixels:
montage \
-alpha on \
-background none \
-mode concatenate \
-tile x1 \
-frame 5 \
-mattecolor none \
*.jpg \
output1.png
You'll easily notice however, that the resulting image's border is only 5 pixels wide on top, right, bottom and left. To remove these 5 pixels all around use:
convert output1.png -shave 5 output2.png
To overlay this result on your background.jpg, use:
convert \
background.jpg \
output2.png \
-gravity Northwest \
-geometry +10+10 \
-composite \
final.jpg

You can also use Kurt's transparent frame trick with append. Using append instead of montage has the advantage that you can use gravity settings to align your images top (north) bottom (south) or center.
Here's an example of how to append images horizontally with a 10 pixel gap between them, and with the images top aligned:
convert \
-frame 5 \
-mattecolor none \
-background none \
-gravity north \
first.jpg second.jpg third.jpg \
+append \
png:- | convert - -shave 5
output.png
To append images vertically use -append instead of +append. I've used a pipe | to shave off the outside frame in the same command.

Related

How to pass Imagemagick montage output and image properties to the convert command without using any temporary files?

I have a set of images, and I can use the Imagemagick montage command on them to produce a montage image file with transparent background (let's call this fgimg). Now I have another existing image (let's call this bgimg) that I'd like to use (after some special processing with the convert command) as the background for fgimg, which can be achieved within the same convert command. At this point it seems trivial to avoid writing the temporary fgimg to disk, simply by piping the standard output of montage to the standard input of convert.
My problem is that the special processing I'm applying to bgimg will require some knowledge of the image properties of fgimg (e.g., resizing bgimg to have the same size as fgimg), which I don't know in advance. How can this information be retrieved and used in the convert command?
Note: I'm using Imagemagick version 6.9.7-4 on Linux.
I'll include some commands below to further illustrate the problem in detail.
The following command produces the montage image fgimg from a set of input images. The output is in the 'special' miff format (which seems best for temporary output to be worked on later), and has transparent background so that the actual background can be applied later. Most of the other options here are not important, but the point is that the output size (dimensions) cannot be determined in advance.
montage input_*.jpg -tile 5x -border 2 -geometry '200x200>+20+20' \
-gravity center -set label '%f\n%G' -background none -fill white \
-title 'Sample Title' miff:fgimg
Next, I have another input image bgimg.jpg. I want to perform some processing on it before using it as background to fgimg. The processing can be quite complex in general, but in this example, I want to:
resize bgimg.jpg to fit inside the dimensions of fgimg without any cropping;
apply a fade-to-black effect around the edges;
make it the same size as fgimg, with a black background;
combine this with fgimg to produce the final output.
Notice that I need the size of fgimg in two places. I can first extract this into a shell variable:
size=$(identify -format '%G' miff:fgimg)
Then I can do all the steps above in one convert command (note that $size is used twice):
convert "bgimg.jpg[$size]" -gravity center \
\( +clone -fill white -colorize 100% -bordercolor black \
-shave 20 -border 20 -blur 0x20 \) -compose multiply -composite \
-background black -compose copy -extent $size \
miff:fgimg -compose over -composite final_out.jpg
Now here is the problem: I want to avoid writing the temporary file fgimg to disk.
I could replace miff:fgimg with miff:- in both the montage and convert commands and then just pipe one to the other: montage ... | convert .... But how do I deal with the $size?
I tried to use file descriptors (miff:fd:3) but this does not seem to work, which is confirmed by the comments to this question.
Is there a way to do this (in Imagemagick v6) without creating a temporary file?
This example command uses ImageMagick v6 on a bash shell. Instead of "montage" it starts by using "convert" to create a "logo:", one of IM's built-in sample images, then pipes it out as a MIFF and into the "convert" command that follows. You can pipe the output of "montage" just as easily. And it uses another IM built-in image "rose:" as your "bgimg.jpg"...
convert logo: miff:- | convert - rose: \
+distort SRT "%[fx:t?min(u.w/v.w,u.h/v.h):1] 0" \
-shave 1 +repage -gravity center -bordercolor black \
\( -clone 1 -fill white -colorize 100 -shave 6 -border 6 \
-blur 0x6 -clone 1 -compose multiply -composite \) -delete 1 \
\( -clone 0 -alpha off -fill black -colorize 100 \
-clone 1 -compose over -composite \) -delete 1 \
+swap -composite final_out.jpg
That reads the piped image "-" and the background image "rose:".
Then it uses "+distort" with an FX expression to scale "rose:" to the maximum dimensions that still fit within the original piped input image. That operation adds a pixel all around so we use "-shave 1" to get rid of that.
Next inside parentheses it clones that re-scaled background image, makes an edge blur mask, and composites them to make the fade-to-black edge on the background image. Right after the parentheses it deletes the non-edged background image.
In the next parentheses it clones the input image, makes it black, clones the modified background image, and composites it centered over the black one. Again the non-extended background image is discarded after the parentheses with "-delete 1".
Finally the modified background and the input image are put in the proper order with "+swap" and composited for the final output. Run this command without the last "-composite" to see the two images that result from the prior parts of the command.
Both the main input image and background image can be any size, any dimensions, and any aspect ratio. This works for me on v6.8.9 on a bash shell. It should work on any ImageMagick newer. It should work on Windows by removing all the backslashes that escape parentheses and changing the continued-line backslashes "\" to carets "^".
EDITED TO ADD:
You can use that FX expression to find the scaling amount, save it as a variable, then isolate the background image inside parentheses and use that variable to do the scaling and shaving there. That way it only affects the background image. There may be a rounding error with that, but the main image, which determines the exact final output dimensions, will be unaffected. Note the difference in the first few lines of this command...
convert logo: miff:- | convert - rose: \
-set option:v1 "%[fx:min(u.w/v.w,u.h/v.h)]" \
\( -clone 1 +distort SRT "%[v1] 0" -shave 1 \) -delete 1 \
+repage -gravity center -bordercolor black \
\( -clone 1 -fill white -colorize 100 -shave 6 -border 6 \
-blur 0x6 -clone 1 -compose multiply -composite \) -delete 1 \
\( -clone 0 -alpha off -fill black -colorize 100 \
-clone 1 -compose over -composite \) -delete 1 \
+swap final_out.jpg

ImageMagick gradient ratio

How do I adjust the color ratio of a gradient?
I currently use the following to create my gradient.
convert -size 200x600 gradient:none-black output.png
Although at least one acceptable solution has been provided, here are a couple other ideas...
Example 1: This command creates a red-blue gradient of the finished dimensions, crops it into a top and bottom half, resizes them to 40 and 60 percent of the input height, and appends them back to make a single image. What started as the color at the exact vertical center is now at 40% down from the top with clean gradients going up and down from there.
convert -size 200x600 gradient:red-blue -crop 1x2# \
\( -clone 0 -resize 100x40% \) \( -clone 1 -resize 100x60% \) \
-delete 0,1 -append result.png
That splits the gradient image into a top and bottom half, then inside parentheses it resizes each to the required proportion. After that it deletes the 50/50 crops from before the parentheses, appends the two resized remaining images, and writes the output.
Example 2: This next example starts by creating the red-blue gradient in the final dimensions, then sets variables to hold the top color, the exact middle color, and the bottom color.
Then inside the first parentheses it clones and crops the image to 60% its original height. It uses "-sparse-color" to fill that with a gradient from "color1" to "color2".
Inside the second parentheses it clones and crops the image to 40% its original height, and using "-sparse-color" again it fills it with a gradient from "color2" to "color3".
After creating those two gradients, delete the original, append the other two together, and write the output.
convert -size 200x600 gradient:red-blue \
-set option:color1 "%[pixel:p{0,0}]" \
-set option:color2 "%[pixel:p{0,h/2}]" \
-set option:color3 "%[pixel:p{0,h}]" \
\( -clone 0 -extent 100x60% \
-sparse-color barycentric "0,0 %[color1] 0,%[h] %[color2]" \) \
\( -clone 0 -extent 100x40% \
-sparse-color barycentric "0,0 %[color2] 0,%[h] %[color3]" \) \
-delete 0 -append result.png
Maybe you want this, where you get to the half-red/half-blue colour just 20% of the way down the height of the image. It is done by creating two gradients of different lengths and putting them back-to-back:
midcolour="rgb(127,0,127)"
convert -size 100x20 gradient:red-"$midcolour" \
-size 100x80 gradient:"$midcolour"-blue \
-append result.png
Another way is to put 3 single pixels together in a row and then resize that up to what you want. I know you want the middle to be 40% red and 60% blue, but, for ease of viewing, I'll make it lime green:
convert -size 1x1 xc:red xc:lime xc:blue -append -resize 100x100\! result.png
You would change lime to something like "rgb(100,0,155)".
I am not quite sure I understand. But if you want to start with 90% transparent (10% opaque black) and end with black. You can do:
convert -size 200x600 gradient:"graya(0,0.1)-black" output.png
graya means gray with alpha. So graya(0,0.1) is gray(0) or black with 0.1 fraction opacity, so 90% transparent.
Perhaps this is what you want:
Normal 50-50:
convert -size 200x600 gradient:red-blue red_blue1.png
60-40:
rr=`convert xc: -format "%[fx:0.6*255]" info:`
bb=`convert xc: -format "%[fx:0.4*255]" info:`
convert -size 200x600 gradient:"rgb($rr,0,$bb)-rgb(0,0,255)" red_blue2.png
Or perhaps this is what you want:
bb=`convert xc: -format "%[fx:0.1*255]" info:`
convert -size 200x600 gradient:"rgb(255,0,$bb)-rgb(0,0,255)" red_blue3.png
I have an Imagemagick bash shell script called, multigradient, which may do what you want. It allows you to create gradients of many colors each with stops to set where they start. For example:
multigradient -w 200 -h 600 -s "blue 0 red 80" -d to-top result.png
The first color must start at 0, but the direction can be many options. Here I go from bottom to top with pure blue at the bottom and pure red starting at 80 up from the bottom going to the top
(See)

Position another image relatively to text with ImageMagick

convert inputImage.jpeg -gravity South -size x32 label:"Morning in paradise" -geometry +0+40 -composite starImage.png -composite finalImage.png
With this command, I can add text at the bottom of inputImage and another image on this text. But how can I set (or prefix) the starImage image to the left of the text that has a dynamic width and fixed height. I have attached some images below to explain what I want to do.
Obtained result
Expected result
You can read the star image, create the text label, and append them together inside parentheses. Then composite that assembled star-text image over the main input image. A command like this should get pretty near what you described.
convert inputImage.jpeg -gravity center -size x32 \
\( starImage.png label:"Morning in paradise" +append \) \
-geometry +0+40 -gravity South -composite finalImage.png
If you want a star on both sides of the line of text, you can read the "starImage.png" in once more after creating the label and before appending.
I think what you should do using Imagemagick is to set the width you want for the text so that there is room for the star image to be append on each side and have some padding as well. Here is how I would do it. Since you did not provide your input or star image, I have simulated the image as a blue image and taken some graphic image that I had around to simulate your star or logo. I first measure the desired width. The width is 70% of the difference is width between the large blue image and twice the width of the logo. I append the logo on each side of the text image, then composite that near the bottom of the blue image to create your final image. If this were in Imagemagick 7, it could be done in one command. This is Unix syntax.
Image:
Logo:
width=`convert background.jpg logo.png -format "%[fx:0.70*(u.w-2*v.w)]\n" info: | head -n1`
convert background.jpg \
\( logo.png \
-size ${width}x -background none -fill black -font Arial -gravity center label:"THIS IS A TEST" \
logo.png \
+append \) \
-gravity south -geometry +0+50 \
-compose over -composite \
result.jpg

Montage 3 images in a 2x2 grid, first in top center (like "triforce")?

Not sure how to put this question otherwise - but let's assume I have three square images. I'd like to arrange them in a sort of a square 2x2 grid, such that image 2 is bottom left, image 3 is bottom right - and image 1 is top center (so image 1 is not in the cells of the grid on top; neither left cell, nor right cell, but in center of the row delimited by them).
Closest I could get was with this test, done on Ubuntu 14.04, montage --version ImageMagick 6.7.7-10 2017-07-31 Q16:
montage \
<(convert -size 100x100 xc:green bmp:-) \
<(montage \
<(convert -size 100x100 xc:blue bmp:-) \
<(convert -size 100x100 xc:red bmp:-) \
-geometry +5+5 bmp:- \
) \
-geometry +5+5 -tile 1x2 bmp3:- | display
... or as one-liner:
montage <(convert -size 100x100 xc:green bmp:-) <(montage <(convert -size 100x100 xc:blue bmp:-) <(convert -size 100x100 xc:red bmp:-) -geometry +5+5 bmp:- ) -geometry +5+5 -tile 1x2 bmp3:- | display
The image produced is:
What I want instead is something like this (I edited this manually in an image editor):
... that is, somewhat like that old meme Triforce (Wikipedia)
How could I achieve that with ImageMagick's montage?
This might be a case where ImageMagick's "convert" command would serve you better than "montage". Here is an example that should get you pretty much the same result...
convert -size 100x100 xc:green xc:blue xc:red -bordercolor white -border 5 \
\( -clone 1,2 +append \) -delete 1,2 -gravity center -append -border 5 out.bmp
Using "convert" can give you more freedom to arrange the images using "+append" and "-append" to attach them, "-gravity" for alignment, and "-border" for spacing.

RMagick torn edges

I'm trying to create a torn edge effect in RMagick. Is there a filter similar to photoshop's crystallize?
Also, I found this ImageMagick code that does it here http://www.imagemagick.org/Usage/thumbnails/#torn:
convert thumbnail.gif \
\( +clone -alpha extract -virtual-pixel black \
-spread 10 -blur 0x3 -threshold 50% -spread 1 -blur 0x.7 \) \
-alpha off -compose Copy_Opacity -composite torn_paper.png
However, I don't understand any of it. Can anyone provide some advice?
This command does two main things: create a mask with that torn-paper effect, apply the mask to the image. It does them, fancily, in one line, by using +clone and the parentheses. It's less confusing to do it as two commmands though:
convert thumbnail.gif \
-alpha extract \
-virtual-pixel black \
-spread 10 \
-blur 0x3 \
-threshold 50% \
-spread 1 \
-blur 0x.7 \
mask.png
convert thumbnail.gif mask.png \
-alpha off \
-compose Copy_Opacity \
-composite torn_paper.png
The first command is fairly complex. But you can find decent explanations of each of the component commands in the ImageMagick docs:
http://www.imagemagick.org/Usage/masking/#alpha_extract
http://www.imagemagick.org/Usage/misc/#virtual_examples
http://www.imagemagick.org/script/command-line-options.php#blur
Also, by splitting the command into these two pieces, you can see what the mask looks like on its own. It's basically the inverse of the paper effect. White throughout the middle of the images, fading to black around the "torn edges".
The second command is a lot more straightforward. Copy_Opacity, as described in the ImageMagick docs, is a way of making parts of an image transparent or not. Anything that's black in the mask will be made transparent in the resulting image. In effect, the second command uses the mask to "erase" the edges of the original thumbnail in a stylistically interesting way.

Resources