Can ImageMagick's convert append white or black bars to maintain aspect ratio after specifying just the aspect ratio?
More concretely
Suppose I have a 2000x1000 widescope image and I would like to compute a new image that has an aspect ratio of 4:3 to fit, say a TV. I can do
convert input.png -background black -extent 2000x1500 -gravity center output.jpg
But here I have manually chosen 2000x1500 to produce an extra 250x2 pixels of blakc. Can I ask convert to:
change the aspect ratio to 4:3
not lose any pixels; not interpolate any pixels
center the image
?
If it's also possible to chose the background color as the dominant color in the image (as in iTunes 11), do mention how.
Convert does not have the built-in capability to pad an image out to a given aspect ratio, so you will need to script this. Here is how this might be done in bash:
#!/bin/bash -e
im="$1"
targetaspect="$2"
read W H <<< $(identify -ping -format "%w %h" "$im")
curaspect=$(bc <<< "scale=10; $W / $H")
echo "current-aspect: $curaspect; target-aspect: $targetaspect"
comparison=$(bc <<< "$curaspect > $targetaspect")
if [[ "$comparison" = "1" ]]; then
targeth=$(bc <<< "scale=10; $W / $targetaspect")
targetextent="${W}x${targeth%.*}"
else
targetw=$(bc <<< "scale=10; $H * $targetaspect")
targetextent="${targetw%.*}x$H"
fi
echo convert "$im" -background black \
-gravity center -extent "$targetextent" \
output.jpg
Call this script with the input image and the target aspect ratio given as a floating point number (for example, 4/3 = 1.333):
$ do-aspect input.png 1.333
Notes:
bc is used for floating point math, because bash itself has only integer arithmetic.
Note that -gravity center is on the final command line before -extent. This is because gravity is a setting while extent is an operator. Settings should always precede the operators that they affect, or else convert will do unexpected things when your commands start to get more complicated.
When you're happy with the results of the program, you can either copy and execute its output, or just remove the echo from before the final convert.
To your question about finding the dominant color of an image, there are different ways of doing that, but one common method is to resize the image to 1x1 and output the color of the resultant pixel - it will be the average color of the whole image:
convert input.png -resize 1x1 -format '%[pixel:p[0,0]]' info:
You can do other processing on the image before resizing to get a different heuristic.
Related
I do have thousands of images in different sizes; now I would like to get the biggest square out of it that's possible without transparent/black background. Of course, the ratio should be kept, if it's e.g. a landscape image all height should be in the destination image but the left and right should be cropped; for portrait images, the other way round.
How's that possible?
I think you mean this. If you start with a landscape image bean.jpg:
magick bean.jpg -gravity center -extent "%[fx:h<w?h:w]x%[fx:h<w?h:w]" result.jpg
If you start with a portrait image, scooby.jpg:
magick scooby.jpg -gravity center -extent "%[fx:h<w?h:w]x%[fx:h<w?h:w]" result2.jpg
The part inside the double-quotes is the interesting part. It is basically setting the extent of the image, like:
-extent 100x100
where 100 is the width and the height. Rather than that though, I am using a calculated expression which tests whether the height (h) is less than the width (w) using a ternary operator. That results in taking whatever is smaller out of the current height and width as the new height and width, So there are really two calculated expressions in there, with an x between them, similar to 100x100.
Note that this method requires ImageMagick v7 or better - i.e. it uses the magick command rather than v6's convert command. If you have v6, you need to use more steps. First, get the width and the height of the image, then choose the smaller of the two and then issue a convert command with the gravity and extent both set. In bash:
# Get width and height
read w h < <(identify -format "%w %h" scooby.jpg)
# Check them
echo $w,$h
272,391
# Set `n` to lesser of width and height
n=$w
[ $h -lt $n ] && n=$h
# Now do actual crop
convert scooby.jpg -gravity center -extent "${n}x${n}" result.jpg
If you have thousands to do, I would suggest using GNU Parallel if you are on macOS or Linux. If you are on Windows, sorry, you'll need a loop and be unable to easily use all your CPU cores.
I have not tested the following, so only try it out on a small, COPIED, sample of a few files:
# Create output directory
mkdir output
# Crop all JPEG files, in parallel, storing result in output directory
parallel --dry-run magick {} -gravity center -extent "%[fx:h<w?h:w]x%[fx:h<w?h:w]" output/{} ::: *.jpg
If the commands look good, remove the --dry-run part to do it for real.
If you're using ImageMagick v7, Mark Setchell has provided a simple method above (or below). If you're using IMv6, you can crop the largest center square from any image using a command lke this...
convert input.png -set option:distort:viewport "%[fx:min(w,h)]x%[fx:min(w,h)]" \
-distort affine "%[fx:w>h?(w-h)/2:0],%[fx:w<h?(h-w)/2:0] 0,0" output.png
That sets the output viewport size to the largest square you can crop from the input image. Then it adjusts the position of the input image so it is centered within that square viewport.
This command should work from a command prompt or script on most *nix systems. If you're using Windows, replace that continued line backslash "\" with a caret "^". If you're using a BAT script in Windows you'll also have to make all the single percent signs "%" into doubles "%%".
You can also simply change "convert" to "magick" to run this command using IMv7.
I find this easier to remember:
convert in.png -gravity Center -extent 1:1 out.png
I have an overlay image, which is like a watermark/logo, which needs to be overlayed on top of the source image (while preserving alpha channel, etc)
When overlay is the same or smaller dimension as the source image - things are easy:
composite.exe -alpha on -gravity center logo.png in_image.jpg out_image.jpg
However, when logo.png is larger than in_image.jpg - above call truncates the logo, and out_image.jpg has the same dimensions as in_image.jpg
I would like the resulting image to be the largest of either the logo.png or in_image.jpg so I can do things like artistic frames around the photos.
Below image demonstrates the end result I want to be able to get this:
Desired Result
Note, here, the png with skulls has larger dims than the kiddo's image. The alpha channel needs to be preserved.
Edit: more clarity through examples
Here is another desired result
Here, the png file is opaque on the sides, has a clear window in the middle, and half-translucent bubbles. the JPG file is just a regular JPG from a camera.
Would love to add original and logo files that result in it, but lack reputation to add more than 2 links (or to add images)
Updated Answer
If you have v7 of ImageMagick, you can get it to do the maths for you all in one line using -fx to determine the dimensions of the larger of the two images:
magick background.jpg overlay.png -background none -gravity center -extent '%[fx:u.w>v.w?u.w:v.w]x%[fx:u.h>v.h?u.h:v.h]' -composite result.png
That basically says... "Extend the two images as follows. If the width of the first image (u.w) is greater than that of the second image (v.w), then use the width of the first, else use that of the second. Same for height.".
Original Answer
I believe you want this. Get width of whatever is wider of background and overlay. Get height of whatever is taller of background and overlay. Extend both background and overlay with transparent pixels to new dimensions. Overlay.
So, if we start with this as background (300x50):
And this as overlay (122x242) - which is a tall blue rectangle surrounded with transparency then that is bordered in black to show the extent of it:
You would run this, which is actually very simple but it is full of comments and debug output so you can see what is going on:
#!/bin/bash
# Get background width and height
read wb hb < <(convert background.jpg -format "%w %h" info: )
echo "Background: " $wb $hb
# Get overlay width and height
read wo ho < <(convert overlay.png -format "%w %h" info: )
echo "Overlay: " $wo $ho
# Get wider of the two
w=$wb
[ $wo -gt $w ] && w=$wo
# Get taller of the two
h=$hb
[ $ho -gt $h ] && h=$ho
echo "New dimensions: " $w $h
convert background.jpg overlay.png -background none -gravity center -extent ${w}x${h} -composite result.png
Here is how it looks running:
Background: 300 50
Overlay: 122 242
New dimensions: 300 242
Presumably, when you have done your overlay, you would add -trim as the final part of the command line to remove any extraneous stuff that has been added.
Try using convert rather than composite. It is more flexible than composite.
convert logo.png in_image.jpg -gravity center -compose over -composite out_image.jpg
But if you insist on using composite, then
composite in_image.jpg logo.png -gravity center -compose src_over out_image.jpg
Note sure exactly what you mean by preserving the alpha channel, since jpg does not support transparency. Perhaps you can post a pair of inputs and your desired output, if what I have posted does not do what you want.
I have images of unknown size and ratios. I wish to take a centred 16:9 crop.
If all the images were known 4:3, then it's easy: 9/16 รท 3/4 = 0.75 so I simply set the height to 75% of the original like this:
convert photo.jpg -gravity center -crop '100%x75%' +repage photo16x9.jpg
However, if the photo is already 16:9 (or even wider), I don't want to crop it, and if it is 'taller' than 16:9 I want to crop it only by the amount necessary to achieve a 16:9 crop.
This would be also easier if I wanted to scale to a known width or height (example question for that). But I'm looking to leave as much of the original image data in place as poss.
Therefore I'm looking for a way to crop the height to a factor of the image's width, with a cut-off.
I hoped this might work
convert photo.jpg -gravity center \
-crop '100%x[fx:9/16*w]' +repage photo16x9.jpg
but alas it seems the [fx:...] is not allowed in a -crop argument.
Hacking a bit I found somewhere that I could not for the life of me understand(!) also failed, but I'll list it here to show research effort!
convert photo.jpg -set option:distort:viewport \
'[fx: w]x[fx: w*9/16 ]+0+0' -filter point \
-distort SRT 0 +repage photo16x9.jpg
(I realise that neither attempts above cover the case when the image is already wider than 16:9, but if the [fx:...] thing worked I could achieve that by using the ternary operator, so I kept the examples simple.)
Maybe you can just calculate the aspect ratio and use that to make a decision. Create two test images, one 15x9 and one 17x9:
convert -size 15x9 xc:black 15x9.png
convert -size 17x9 xc:black 17x9.png
Now ask ImageMagick to tell you if the aspect ratio is wider than 16:9 or less than or equal to 16:9:
convert 15x9.png -format "%[fx:(w/h)>16/9]" info:
0
convert 17x9.png -format "%[fx:(w/h)>16/9]" info:
1
I'm using ImageMagick to prepare a set of ~20,000 photos for a timelapse video. A common problem in timelapse videos is flickering, due to changing lighting conditions, passing clouds, hue changes, etc.
I've used IM to convert each image to greyscale and -auto-gamma, which is a drastic improvement in lighting "stability". Very good, but not yet perfect.
I would now like to do the following, but can't figure out how.
1. determine ideal auto gamma based only on the lower 30% of the image
2. apply that ideal gamma to the entire image
Each of my images has sky above and buildings below. The sky changes dramatically as clouds pass by, but the buildings' lighting is fairly stable.
I tried -region, but as expected, it only applies the gamma to the region specified. Is it possible to do what I'm hoping for? Thanks for any advice!
Yes, I think so.
You can crop the bottom 30% of the image like this:
convert image.jpg -gravity south -crop x30%+0+0 bottom.jpg
so that means you can get the mean of the bottom 30% of the image like this:
convert image.jpg -gravity south -crop x30%+0+0 -format "%[mean]" info:
and you can also get the quantum range as well all in one go if you add that in:
convert image.jpg -gravity south -crop x30%+0+0 -format "%[mean] %[fx:quantumrange]" info:
Now, the gamma is defined as the logarithm of the mean divided by the logarithm of the midpoint of the dynamic range, but we can normalize both these numbers to the range [0-1] as follows:
log(mean/quantumrange) / log(0.5)
so we'll let bc work that out for us like this:
echo "scale=4; l($mean30/$qr)/l(0.5)" | bc -l
and we can use the result of that to apply a gamma correction to the entire image. So, I have put all that together in a single script, which I call b30gamma. You save it under that name and then type:
chmod +x b30gamma
to make it executable. Then you can run it on an image like this and the result will be saved as out.jpg so as not to destroy the input image:
./b30gamma input.jpg
Here is the script:
#!/bin/bash
# Pick up image name as parameter
image=$1
# Get mean of bottom 30% of image, and quantum range (65535 for Q16, or 255 for Q8)
read mean30 qr < <(convert "$image" -gravity south -crop x30%+0+0 -format "%[mean] %[fx:quantumrange]" info:)
# Gamma = log(mean)/log(dynamic range centre point)
gcorr=$(echo "scale=4;l($mean30/$qr)/l(0.5)" | bc -l)
# Debug
echo Image: $image, Mean: $mean30, quantum range: $qr, Gamma: $gcorr
# Now apply this gamma to entire image
convert "$image" -gamma $gcorr out.jpg
I'm given an image of unknown size. I want to shrink it such that it will fit in either 640x480 or 480x640, preserving aspect ratio. I want the image to shrink the minimum amount (e.g. the result should be the maximum size which fits in either 640x480 or 480x640).
For example, if I have a 800x800 image, it should shrink to 480x480. If I have a 500x1000 image, it should shrink to 320x640. Similarly, 1000x500 should become 640x320.
Can I do this in ImageMagick in one command? No cropping should occur, and the aspect ratio of the original image should be preserved. Thanks!
It's easy to resize an image without cropping and preserving the aspect ratio, but I don't think that you'll be able to achieve your either/or in a single command.
From the geometry specification docs, resizing an overlarge image to 640x480 is easy:
convert input.png -resize 640x480> output.png
That will only resize if necessary, won't crop, and will preserve the aspect ratio.
Depending on your input images, you might be able to use the area operator:
convert input.png -resize $((640*480))# output.png
But that will only work if all the input images already have either a 4/3 or 3/4 aspect ratio.
I think your best bet is a short shell script:
wider_than_tall=`identify -format %w/%h input.png`
if (( $wider_than_tall )); then
convert input.png -resize 640x480> output.png
else
convert input.png -resize 480x640> output.png
fi