ImageMagick convert resize by the smaller dimension? - imagemagick

The following command resizes the larger dimension to 256:
convert -resize 256x256 in.jpg out.jpg
For example, if in.jpg is 1024x512, it resizes it to 256x128.
Is it possible to resize the smaller dimension to 256 (while keeping the aspect ratio) with ImageMagick convert? (I need 512x256)
If not, is there any other easy command line solution?

The fill area flag ^ seems to do exactly what you want:
convert -resize 256x256^ in.jpg out.jpg
If you're on Windows:
The Fill Area Flag ('^' flag) is a special character in Window batch scripts and you will need to escape that character by doubling it. For example '^^', or it will not work.
This only works with ImageMagick 6.3.8-3 and above. For older versions, use this trick.

Maybe the command I suggested in my comment will work, namely
convert in.jpg -resize x256 out.jpg
Or, if you actually want to identify the smaller dimension and resize that explicitly, this should do the trick
#!/bin/bash
image=$1
cmd="x256"
[ $(identify -format "%[fx:w<h?1:0]" "$image") -eq 1 ] && cmd="256x"
convert "$image" -resize $cmd out.jpg
I preset the command to resize by height at line 3. Then I ask ImageMagick to output 1 if the image is taller than wide, and if it is, I change the resize command to resize by width. Then, finally, I do the actual resize. You can re-cast the script various ways to make it shorter, or leave it explicit.
E.g.
if [ $(identify -format "%[fx:w<h?1:0]" in.jpg) -eq 1 ]; then
convert in.jpg -resize x256 out.jpg;
else
convert in.jpg -resize 256x out.jpg;
fi

Related

Imagemagick: How to get image width, then use that to batch resize all images in folder to width x width / 2?

I have over 400 images I need resizing. They need to be their individual original widths, but the heights need to be exactly half the width of each image.
I've been trying for 3 hours to get something work in Imagemagick, with no luck.
Any ideas?
Thanks a lot!
magick 40inch_downbelow_001.jpg -ping -resize "x%w/2" 40inch_downbelow_0aaaaaaa01.jpg - This is my current command, which is just a test. This spits out an image of exactly the same size, despite telling it to use half the width.
Here are two ways to accomplish this in Windows, one using ImageMagick v6 and another using v7. These will run all the JPG images in a directory.
Using IMv6...
for %I in ( *.jpg ) do (
convert "%I" -set filename:0 "%[t]XXX.%[e]" ^
+distort SRT "0,0 1,%[fx:w/h/2] 0" -shave 1 "%[filename:0]"
)
Using IMv7...
for %I in ( *.jpg ) do (
magick "%I" -set filename:0 "%[t]XXX.%[e]" -resize "x%[fx:w/2]!" "%[filename:0]"
)
Substitute your file name modifier for the "XXX" I used in the examples. To use these commands in a BAT script you'll need to double the percent signs "%%".
I think you want this, for a single image:
magick input.png -resize "x%[fx:int(w/2)]\!" result.png
The fact there is nothing before the x means the width is left unchanged. The new height is calculated as half the width and the exclamation mark permits distortion of the original aspect ratio.
If you want to do all PNGs in the current directory, use:
#!/bin/bash
for f in *.png; do
magick "$f" -resize "x${h}\!" "$f"
done
If you only have old, v6 ImageMagick, go with:
#!/bin/bash
for f in *.png; do
# Get current width and halve it
w=$(identify -format "%w" "$f")
((h=w/2))
mogrify -resize x${h}\! "$f"
done

Imagemagick - how to get largest layer and compress?

Please help. Is there an easy way to take the largest layer of a tiff and zip compress it back as a single layer tiff again with imagemagick or similar?
Just a slightly easier version of Fred's answer. You can generate a list of the area (in pixels) of each layer in a TIF followed by the layer/scene number like this:
magick identify -format "%[fx:w*h] %s\n" image.tif
Sample Output
240000 0
560000 1
200000 2
So, if we do that again, sort it reverse numerically and take the second field of the first result, we will get the number of the layer with the largest area:
layer=$(magick identify -format "%[fx:w*h] %s\n" image.tif | sort -rn | awk 'NR==1{print $2}')
So, the complete solution would look like:
#!/bin/bash
# Get layer number of layer with largest area
layer=$(magick identify -format "%[fx:w*h] %s\n" image.tif | sort -rn | awk 'NR==1{print $2}')
# Extract that layer and recompress as single layer
magick image.tif[$layer] -compress lzw result.tif
If you are using ImageMagick v6 or older:
magick identify ... becomes identify ...
magick image.tif ... becomes convert image.tif ...
In concept, using ImageMagick this can be done in a single command. Here's an example...
magick input.tif -background none -virtual-pixel none ^
( -clone 0--1 +repage -layers merge ) ^
-distort affine "0,0 0,%[fx:s.w==u[-1].w&&s.h==u[-1].h?0:h]" ^
-delete -1 -layers merge output.tif
That starts by reading in the original TIF and setting the background and virtual-pixel settings to "none".
Then inside the parentheses it clones all the layers of the TIF, repages them, and merges them into a single image with the dimensions of the largest layer. That will become a gauge to measure with.
Next it uses "-distort affine" to slide each image out of the viewport and leave it transparent unless the image matches the width and height of that gauge. So after that distort, the largest image will remain unchanged, and all the others will be transparent.
Finish by deleting that gauge image and merging the rest. All the layers are transparent except the largest one, so merging them leaves just that visible one as a single layer.
The command is in Windows syntax using IM7. If you're using ImageMagick v6, use "convert" instead of "magick". To make it work in *nix, change the continued line carets "^" to backslashes "\" and escape the parentheses with backslashes "\(...\)". There may be other issues I've overlooked.
Obviously if there are two or more layers matching the largest dimensions, the output result will only be the first one from the original TIF.
Edited to add: This method will only work if both the greatest width and greatest height are on the same image.
How do you define largest? Width, Height, File size? If the largest dimension from width and height is used, then in Unix, you can do the following on a 3 layer tif file. Get the max dimension of each layer. Then find which layer is the largest. Then use just that layer when reading and writing the file.
Arr=(`identify -format "%[fx:max(w,h)]\n" img.tif`)
echo "${Arr[*]}"
500 1024 770
num=${#Arr[*]}
dim=0
for ((i=0; i<num; i++)); do
if [ ${Arr[$i]} > $dim ]; then
dim=${Arr[$i]}
index=$i
fi
done
echo "$index"
2
convert img.tif[$index] -compress zip newimg.tif
identify newimg.tif
newimg.tif[2] TIFF 770x768 770x768+0+0 8-bit sRGB 3662B 0.000u 0:00.000
I cannot think of any direct and simple method to find the largest layer and extract it in the same command line.

ImageMagick: get biggest square out of image

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

Extracting data digitally

I have 200 copies of this page(15 * 10 matrix) and i have to write all the numbers from 0 - 9 in each corresponding cell and then extract those digits digitally in a seperate image of (32*32 pixels) for each digit, after scanning each page once. How can i achieve this? This is required for my research purpose. I am a CS student so i can code too.
Update:
For mark : Here is one of the scanned image
This is for some local language ( 0 - 9) ..
Update 2:
The commands for the previous image are working fine but on new images,something is getting wrong(some kind of offsets)..
I am attaching the image below
What changes do u suggest ?
Updated Answer
I have taken your feedback and improved the algorithm to the following bash script now...
#!/bin/bash
################################################################################
# dice
#
# Trim borders off an image (twice) and then dice into 10x15 cells.
#
# Usage: ./dice image
################################################################################
# Pick up image name from first parameter
image="$1"
echo DEBUG: Processing image $image...
# Apply median filter to remove noisy black dots around image and then get the
# dimensions of the "trim box" - note we don't use the (degraded) median-filtered image in
# later steps.
trimbox=$(convert "$image" -median 9x9 -fuzz 50% -format %# info:)
echo DEBUG: trimbox $trimbox
# Now trim original unfiltered image into stage1-$$.png (for debug)
convert "$1" -crop $trimbox +repage stage1-$$.png
echo DEBUG: Trimmed outer: stage1-$$.png
# Now trim column headings
convert stage1-$$.png -crop 2000x2590+120+190 +repage stage2-$$.png
echo DEBUG: Trimmed inner: stage2-$$.png
# Now slice into 10x15 rectangles
echo DEBUG: Slicing and dicing
convert stage2-$$.png -crop 10x15# +repage rectangles-%03d.png
# Now trim the edges off the rectangles and resize all to a constant size
for f in rectangles*png; do
echo DEBUG: Trimming and resizing $f
trimbox=$(convert "$f" -median 9x9 -shave 15x15 -bordercolor black -border 15 -threshold 50% -floodfill +0+0 white -fuzz 50% -format %# info:)
echo DEBUG: Cell trimbox $trimbox
convert "$f" -crop $trimbox +repage -resize 32x32! "$f"
done
Here are the resulting cells - i.e. 150 separate image files. I have put a red border around the individual cells/files so you can see their extent:
Original Answer
I would do that with ImageMagick which is free and installed on most Linux distros and is available for OSX and Windows too. There are Perl, PHP, Java, node, .NET, Ruby, C/C++ bindings too if you prefer those languages. Here I am using the command line in Terminal.
First job is to get rid of noise and trim the outer edges:
convert scan.jpg -median 3x3 -fuzz 50% -trim +repage trimmed1.png
Now, trim again to get rid of outer frame and column titles across the top:
convert trimmed1.png -crop 2000x2590+120+190 +repage trimmed2.png
Now divide into 10 cells by 15 cells and save as rectangles-nnn.png
convert trimmed2.png -crop 10x15# rectangles-%03d.png
Check what we got - yes, 150 images:
ls -l rect*
rectangles-000.png rectangles-022.png rectangles-044.png rectangles-066.png rectangles-088.png rectangles-110.png rectangles-132.png
rectangles-001.png rectangles-023.png rectangles-045.png rectangles-067.png rectangles-089.png rectangles-111.png rectangles-133.png
rectangles-002.png rectangles-024.png rectangles-046.png rectangles-068.png rectangles-090.png rectangles-112.png rectangles-134.png
rectangles-003.png rectangles-025.png rectangles-047.png rectangles-069.png rectangles-091.png rectangles-113.png rectangles-135.png
rectangles-004.png rectangles-026.png rectangles-048.png rectangles-070.png rectangles-092.png rectangles-114.png rectangles-136.png
rectangles-005.png rectangles-027.png rectangles-049.png rectangles-071.png rectangles-093.png rectangles-115.png rectangles-137.png
rectangles-006.png rectangles-028.png rectangles-050.png rectangles-072.png rectangles-094.png rectangles-116.png rectangles-138.png
rectangles-007.png rectangles-029.png rectangles-051.png rectangles-073.png rectangles-095.png rectangles-117.png rectangles-139.png
rectangles-008.png rectangles-030.png rectangles-052.png rectangles-074.png rectangles-096.png rectangles-118.png rectangles-140.png
rectangles-009.png rectangles-031.png rectangles-053.png rectangles-075.png rectangles-097.png rectangles-119.png rectangles-141.png
rectangles-010.png rectangles-032.png rectangles-054.png rectangles-076.png rectangles-098.png rectangles-120.png rectangles-142.png
rectangles-011.png rectangles-033.png rectangles-055.png rectangles-077.png rectangles-099.png rectangles-121.png rectangles-143.png
rectangles-012.png rectangles-034.png rectangles-056.png rectangles-078.png rectangles-100.png rectangles-122.png rectangles-144.png
rectangles-013.png rectangles-035.png rectangles-057.png rectangles-079.png rectangles-101.png rectangles-123.png rectangles-145.png
rectangles-014.png rectangles-036.png rectangles-058.png rectangles-080.png rectangles-102.png rectangles-124.png rectangles-146.png
rectangles-015.png rectangles-037.png rectangles-059.png rectangles-081.png rectangles-103.png rectangles-125.png rectangles-147.png
rectangles-016.png rectangles-038.png rectangles-060.png rectangles-082.png rectangles-104.png rectangles-126.png rectangles-148.png
rectangles-017.png rectangles-039.png rectangles-061.png rectangles-083.png rectangles-105.png rectangles-127.png rectangles-149.png
rectangles-018.png rectangles-040.png rectangles-062.png rectangles-084.png rectangles-106.png rectangles-128.png
rectangles-019.png rectangles-041.png rectangles-063.png rectangles-085.png rectangles-107.png rectangles-129.png
rectangles-020.png rectangles-042.png rectangles-064.png rectangles-086.png rectangles-108.png rectangles-130.png
rectangles-021.png rectangles-043.png rectangles-065.png rectangles-087.png rectangles-109.png rectangles-131.png
Look at first couple:
You can do those 3 steps in 1 like this:
convert scan.jpg -median 3x3 -fuzz 50% -trim +repage \
-crop 2000x2590+120+190 +repage \
-crop 10x15# rectangles-%03d.png
You may want to shave a few pixels off each side of each image and resize to 32x32 with something like (untested):
mogrify -shave 3x3 -resize 32x32! rectangles*png

Fitting images by specifying the new aspect ratio

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.

Resources