ImageMagick: How to extract tiles in a grid from a single PNG? - imagemagick

I know questions like this get asked a lot, but I haven't been able to find an answer to my specific use-case.
I frequently have to extract rectangular tile images that are laid out in a grid in a single PNG. The image contains a grid of M x N tiles. The upper-left corner of the upper-left tile in the grid is at offset (X, Y) in pixels. Each tile is WxH pixels in size. In addition, each tile in a row is DX pixels from upper-left corner to upper-left corner (so that DX >= W), and each column is DY pixels below the one above it (DY >= H).
In this picture, M=3 and N=2
Give all these variables, could someone please tell me what command to use to extract the six tiles into their own PNG files? I'm assuming it's using the 'convert' command.
Thanks.

There is no simple crop command in Imagemagick that will do the offset tile cropping. But you can do that with a combination of 3 crop commands.
Crop the larger area that you want from the image
Tile crop with no skip
Crop each tile to remove the skip area
Input:
Here I make an animation simply for demonstration. Use PNG output or JPG output for separate tiles.
convert -delay 50 lena.png \
-crop 200x200+20+20 +repage \
-crop 100x100 +repage \
-crop 90x90+0+0 +repage \
-loop 0 lena_crop.gif

I was able to get this working using the following script:
#!/bin/bash
COLS=8
ROWS=4
WIDTH=263
HEIGHT=500
XOFF=154
YOFF=176
DX=267
DY=523
IMAGE=image
mkdir -p ${IMAGE}
for M in `seq 0 $(($COLS - 1))`
do
for N in `seq 0 $(($ROWS - 1))`
do
X=$(($XOFF + $M * $DX))
Y=$(($YOFF + $N * $DY))
convert ${IMAGE}.png[${WIDTH}x${HEIGHT}+${X}+${Y}] ${IMAGE}/${IMAGE}-${M}-${N}.png
done
done
I'm not sure how elegant this is.

Related

Splitting a region of an image into tiles

I have a set of images that are 4500px x 3000px in size.
Out of each of these images I need to extract a set of tiles in a 5 columns by 3 rows combination with each tile being 700px wide by 500px high.
What complicates the above is that the starting point for this needs to be at X:650 and Y:450 position from the top left corner of the image.
I have gotten as far as convert image.jpg -gravity NorthWest -chop 650x450 -crop 700x500 tile-%d.jpg
This gets me to a correct start position for creating the tile set but includes the rest of the blank area in the right and bottom edge of the image.
How do I go about solving this?
Within a single ImageMagick command you can crop the initial rectangle that contains all the tiles first, then crop that into the 15 output images. Try something like this...
convert image.jpg -crop 3500x1500+650+450 -crop 5x3# tile-%02d.jpg

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

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.

Understanding Perspective Projection Distortion ImageMagick

For a project I am trying to create a perspective distortion of an image to match a DVD case front template. So I want to automate this using ImageMagick (CLI) but I have a hard time understanding the mathematical aspects of this transformation.
convert \
-verbose mw2.png \
-alpha set \
-virtual-pixel transparent \
-distort Perspective-Projection '0,0 0,0 0,0 0,0' \
box.png
This code is en empty set of coordinates, I have read the documentation thoroughly but I can't seem to understand what parameter represents what point. The documentation gives me variables and names where I have no clue what they actually mean (more useful for a mathematical mastermind maybe). So if someone could explain me (visually prefered, or give me a link to useful information) on this subject because I have no clue on what I am doing. Just playing around with the parameters just wont do for this job and I need to calculate these points.
Here you will find an easy image of what I am trying to achieve (with CLI tools):
Update:
convert \
-virtual-pixel transparent \
-size 159x92 \
-verbose \
cd_empty.png \
\(mw2.png -distort Perspective '7,40 4,30 4,124 4,123 85,122 100,123 85,2 100,30'\) \
-geometry +3+20 \
-composite cover-after.png
Gives me as output:
cd_empty.png PNG 92x159 92x159+0+0 8-bit sRGB 16.1KB 0.000u 0:00.000
convert: unable to open image `(mw2.png': No such file or directory # error/blob.c/OpenBlob/2641.
convert: unable to open file `(mw2.png' # error/png.c/ReadPNGImage/3741.
convert: invalid argument for option Perspective : 'require at least 4 CPs' # error/distort.c/GenerateCoefficients/807.
convert: no images defined `cover-after.png' # error/convert.c/ConvertImageCommand/3044.
Correction by Kurt Pfeifle:
The command has a syntax error, because it does not surround the \( and \) delimiters by (at least one) blank on each side as required by ImageMagick!
Since there are no links to the source images provided, I cannot test the outcome of this corrected command:
convert \
-virtual-pixel transparent \
-size 159x92 \
-verbose \
cd_empty.png \
\( \
mw2.png -distort Perspective '7,40 4,30 4,124 4,123 85,122 100,123 85,2 100,30' \
\) \
-geometry +3+20 \
-composite \
cover-after.png
Did you see this very detailed explanation of ImageMagick's distortion algorithms? It comes with quite a few illustrations as well.
From looking at your example image, my guess is that you'll get there using a Four Point Distortion Method.
Of course, the example you gave with the 0,0 0,0 0,0 0,0 parameter does not do what you want.
Many of the distortion methods available in ImageMagick work like this:
The method uses a set of pairs of control points.
The values are numbers (may be floating point, not only integer).
Each pair of control points represents a pixel coordinate.
Each set of four values represent a source image coordinate, followed immediately by the destination image coordinate.
Transfer the coordinates for each source image control point into the respective destination image control point exactly as given by the respective parameters.
Transfer all the other pixel's coordinates according to the distortion method given.
Example:
Sx1,Sy1 Dx1,Dy1
Sx2,Sy2 Dx2,Dy2
Sx3,Sy3 Dx3,Dy3
...
Sxn,Syn Dxn,Dyn
x is used to represent an X coordinate.
y is used to represent an Y coordinate.
1, 2, 3, ... n is used to represent the 1st, 2nd, 3rd, ... nth pixel.
S is used here for the source pixel.
D is used here for the destination pixel.
First: method -distort perspective
The distortion method perspective will make sure that straight lines in the source image will remain straight lines in the destination image. Other methods, like barrel or bilinearforward do not: they will distort straight lines into curves.
The -distort perspective requires a set of at least 4 pre-calculated pairs of pixel coordinates (where the last one may be zero). More than 4 pairs of pixel coordinates provide for more accurate distortions. So if you used for example:
-distort perspective '1,2 3,4 5,6 7,8 9,10 11,12 13,14 15,16'
(for readability reasons using more {optional} blanks between the mapping pairs than required) would mean:
From the source image take pixel at coordinate (1,2) and paint it at coordinate (3,4) in the destination image.
From the source image take pixel at coordinate (5,6) and paint it at coordinate (7,8) in the destination image.
From the source image take pixel at coordinate (9,10) and paint it at coordinate (11,12) in the destination image.
From the source image take pixel at coordinate (13,14) and paint it at coordinate (15,16) in the destination image.
You may have seen photo images where the vertical lines (like the corners of building walls) do not look vertical at all (due to some tilting of the camera when taking the snap). The method -distort perspective can rectify this.
It can even achieve things like this, 'straightening' or 'rectifying' one face of a building that appears in the 'correct' perspective of the original photo:
==>
The control points used for this distortion are indicated by the corners of the red (source controls) and blue rectangles (destination controls) drawn over the original image:
==>
This particular distortion used
-distort perspective '7,40 4,30 4,124 4,123 85,122 100,123 85,2 100,30'
Complete command for your copy'n'paste pleasure:
convert \
-verbose \
http://i.stack.imgur.com/SN7sm.jpg \
-matte \
-virtual-pixel transparent \
-distort perspective '7,40 4,30 4,124 4,123 85,122 100,123 85,2 100,30' \
output.png
Second: method -distort perspective-projection
The method -distort perspective-projection is derived from the easier understandable perspective method. It achieves the exactly same distortion result as -distort perspective does, but doesn't use (at least) 4 pairs of coordinate values (at least 16 integers) as parameter, but 8 floating point coefficients.
It uses...
A set of exactly 8 pre-calculated coefficients;
Each of these coefficients is a floating point value (unlike with -distort perspective, where for values only integers are allowed);
These 8 values represent a matrix of the form
sx ry tx
rx sy ty
px py
which is used to calculate the destination pixels from the source pixels according to this formula:
X-of-destination = (sx*xs + ry+ys +tx) / (px*xs + py*ys +1)
Y-of-destination = (rx*xs + sy+ys +ty) / (px*xs + py*ys +1)
(TO BE DONE --
I've no time right now to find out how to
properly format + put formulas into the SO editor)
To avoid (the more difficult) calculating of the 8 required cooefficients for a re-usable -distort perspective-projection method, you can...
FIRST, (more easily) calculate the coordinates for a -distort perspective ,
SECOND, run this -distort perspective with a -verbose parameter added,
LAST, read the 8 coefficients from the output printed to stderr .
The (above quoted) complete command example would spit out this info:
Perspective Projection:
-distort PerspectiveProjection \
'1.945622, 0.071451, -12.187838, 0.799032,
1.276214, -24.470275, 0.006258, 0.000715'
Thanks to ImageMagick Distorting Images Documentation, I ended up with this clean-understandable code:
$points = array(
0,0, # Source Top Left
0,0, # Destination Top Left
0,490, # Source Bottom Left
2.2,512, # Destination Bottom Left
490,838, # Source Bottom Right
490,768, # Destination Bottom Right
838,0, # Source Top Right
838,50 # Destination Top Right
);
$imagick->distortImage(Imagick::DISTORTION_PERSPECTIVE, $points, false);
Please keep in mind that each set of coordinates are separated into two
parts. The first is the X axis and the second is the Y axis .. so when we say 838,0
at Destination Right Top, we mean the X axis of Destination Right Top
is 838 and the Y axis of it is zero (0).

How to insert one row spacing between tiles in a tiled image using Imagemagick?

I have a bunch of images that I want to tile together in one row. This can be done using Imagemagick montage like this
montage `ls tile*.png` -tile x1 -gravity west -geometry 1x1\<+0+0 out_file.png
However, now I want to insert one pixel of spacing after each tile. I played with -tile-offset -1+0 for a bit but this would not change anything. Similarly, I could use -geometry 1x1\<+1+0, but this would introduce two pixels of spacing instead of one.
How can I do one pixel of spacing between tiles?
If the images are a known size then you can simply do 1 pixel larger in the -geometry switch. So if the images are 256x256 then use -geometry 257x256

Resources