Tile images of different aspect ratios using ImageMagick without gaps - bash

I want to be able to tile together images of different aspect ratios in a way that looks good and avoids as much whitespace between the images as possible.
What I've done so far is rename all the images using a script that changes the image name to the aspect ratio, which makes ImageMagick tile the narrowest images first:
for i in *.jpg;
do mv "$i" $(printf '%.4f' $(echo "scale=4;" $(identify -format "%w" "$i") "/" $(identify -format "%h" "$i") | bc))"$i";
done
Then I run ImageMagick:
montage -mode concatenate -tile 6x -geometry 250x+10+20 -background black *.jpg out.jpg
Which gives me something like this:
Unfortunately, I want something like this, where there isn't as much vertical space between the images with the smaller aspect ratios and bigger ones:
Anyone have any ideas?

Related

How to use imagemagick "convert" to create Google Earth pyramid files

I have a large image that I am using imagemagick to convert into tiles for use in a Google Earth KML as explained here
instructions on image pyramid construction
The idea is to chop up the images into 4 pieces, then 16, then 64, etc.
To keep things simple, I made the image canvas 4096x4096 so that dividing it in will produce equal size files. The basic command is very simple. For example:
convert large.png -crop 512x512 tiles.png
The issue is the convert command creates file names sequentially, while google needs a format of row column. For instance if there were four files output, the file names should be:
tiles00.png
tiles01.png
tiles10.png
tiles11.png
I brute forced renaming scripts for up to 64 files, but before doing the 256 file case, I'd like to know if there is a simpler way to generate the file names. I'm using linux.
Here is one way in Imagemagick 6 using for loops.
lena.png
The lena.png image is 256x256. I choose 128x128 size tiles. So there will be a total of 2 rows and 2 columns for four output images.
infile="lena.png"
tx=128
ty=128
ncols=`convert -ping "$infile" -format "%[fx:floor(w/$tx)]" info:`
nrows=`convert -ping "$infile" -format "%[fx:floor(h/$ty)]" info:`
for ((j=0; j<nrows; j++)); do
offy=$((j*ty))
for ((i=0; i<ncols; i++)); do
offx=$((i*tx))
convert lena.png -crop ${tx}x${ty}+${offx}+${offy} +repage lena_tile${j}${i}.png
done
done
lena_tile00
lena_tile01
lena_tile10
lena_tile11
An alternate, more compact way is to use -set filename command with fx calculations to name the files in the image chain.
infile="lena.png"
tx=128
ty=128
ncols=`convert -ping "$infile" -format "%[fx:floor(w/$tx)]" info:`
nrows=`convert -ping "$infile" -format "%[fx:floor(h/$ty)]" info:`
convert "$infile" -crop ${tx}x${ty} -set filename:row_col "%[fx:floor(t/$nrows)]%[fx:mod(t,$ncols)]" "lena_tile%[filename:row_col].png"
See:
https://imagemagick.org/Usage/basics/#set
https://imagemagick.org/script/fx.php
Are you trying to make your own in order to learn about the process? If not, existing tools like dzsave can build complete pyramids for you very quickly in a single command. For example:
$ vipsheader wtc.jpg
wtc.jpg: 10000x10000 uchar, 3 bands, srgb, jpegload
$ /usr/bin/time -f %M:%e vips dzsave wtc.jpg x --layout google
211224:1.52
$ ls -R x | wc
2404 2316 15186
So that's making a google-style pyramid of 2400 tiles in directory x from a 10,000 x 10,000 pixel JPG image. It takes about 1.5s and 210mb of ram.
There's a chapter in the manual introducing dzsave:
http://libvips.github.io/libvips/API/current/Making-image-pyramids.md.html

How to extract photos from JPG with white background?

I have a JPG file which contains multiple photos over a white background.
I'm looking for a CLI tool which can extract photos from the source JPG (without supplying coordinates) into separate JPG files maintaining quality and photo resolution.
From some research I suspect ImageMagick can achieve this though unsure of the correct CLI command.
If useful, I'm on OSX 10.13.2 and have ImageMagick 7.0.7-28 installed.
Here are two ways to do this in Unix using Imagemagick. I just cropped out your base image from your diagram, since I was not sure it that was part of your image or not. If it is part of the image, then you would have to trim that off first using -trim.
Input:
The first is my script, multicrop2:
(-f 10 is the fuzz factor for extracting the background)
(-u 3 means no attempt to unrotated the results)
multicrop2 -f 10 -u 3 image.jpg resulta.jpg
Processing Image 0
Initial Crop Box: 113x84+81+89
Processing Image 1
Initial Crop Box: 113x67+144+10
Processing Image 2
Initial Crop Box: 113x66+10+11
The second is using Imagemagick -connected-componets (which is what I use in my script)
What this does is:
1) fuzzy flood fill the background to transparent (since jpg is loss and does not preserve a uniform background.
2) change the color under the transparent to white and remove the transparency
3) change anything not white to black
4) apply -connected-components to throw out areas smaller than 400 pixel area and extract each bounding box and color
5) if the color is gray(0), i.e. black, then crop the original image to the bounding box and save to disk
OLDIFS=$IFS
IFS=$'\n'
arr=(`convert image.jpg -fuzz 10% -fill none -draw "matte 0,0 floodfill" \
-background white -alpha background -alpha off \
-fill black +opaque white -type bilevel \
-define connected-components:verbose=true \
-define connected-components:mean-color=true \
-define connected-components:area-threshold=400 \
-connected-components 4 null: | tail -n +2 | sed 's/^[ ]*//'`)
IFS=$OLDIFS
num=${#arr[*]}
j=0
for ((i=0; i<num; i++)); do
bbox=`echo "${arr[$i]}" | cut -d\ -f2`
color=`echo "${arr[$i]}" | cut -d\ -f5`
if [ "$color" = "gray(0)" ]; then
convert image.jpg -crop $bbox +repage resultb_$j.jpg
j=$((j+1))
fi
done
EDIT: Add processing of your actual image
Input:
The first thing to note is that your actual two images are right at the right side, but there is a black edge there. Also one at the top. That black edge connects the two image so that they cannot easily be separated by the multicrop2 script. So you need to shave off the right side by enough pixels to remove that edge. There is also the edge at the top and you can shave that off if you want. If you do, you can reduce the -d argument. The -d argument needs to be smaller that the area of the smallest image you want to extract and larger than any other minor noise or the stripe at the top in area. So I clip off 20 pixels from the right side and then use multicrop2 with a very large value for -d. I have chosen a value for -f of 8, which is seems to be in a rather narrow range due to the non-constant background. You can add -m save to look at the mask the script creates to see that you get a good separation between your two images. I seed the processing at -c 20,20 to avoid the black border at the top of your image, so that the script gets a good measure of the background color for the flood fill step.
convert test.jpeg -gravity east -chop 20x0 tmp.png
multicrop2 -c 20,20 -f 8 -d 100000 tmp.png result.jpg
Processing Image 0
Initial Crop Box: 2319x1627+968+2153
Processing Image 1
Initial Crop Box: 2293x1611+994+436

How to shrink and optimize images?

I'm currently using jpegoptim on CentOS 6. It lets you set a quality and file size benchmark. However, it doesn't let you resize the images.
I have 5000 images of all file sizes and dimensions that I want to shrink to a max width and max file size.
For example, I'd like to get all images down to a maximum width of 500 pixels and 50 KB.
How can I shrink and optimize all of these images?
You can do this with ImageMagick, but it is hard to say explicitly which way to do it as it depends on whether all the files are in the same directory and also whether you have or can use, GNU Parallel.
Generally, you can reduce the size of a single image to a specific width of 500 like this:
# Resize image to 500 pixels wide
convert input.jpg -resize 500x result.jpg
where input.jpg and result.jpg are permitted to be the same file. If you wanted to do the height, you would use:
# Resize image to 500 pixels high
convert input.jpg -resize x500 result.jpg
since dimensions are specified as width x height.
If you only want to reduce files that are larger than 500 pixels, and not do any up-resing (increasing resolution), you add > to the dimension:
# Resize images wider than 500 pixels down to 500 pixels wide
convert image.jpg -resize '500x>' image.jpg
If you want to reduce the file size of the result, you must use a -define to guide the JPEG encoder as follows:
# Resize image to no more than 500px wide and keep output file size below 50kB
convert image.jpg -resize '500x>' -define jpeg:extent=50KB result.jpg
So, now you need to put a loop around all your files:
#!/bin/bash
shopt -s nullglob
shopt -s nocaseglob
for f in *.jpg; do
convert "$f" -resize '500x>' -define jpeg:extent=50KB "$f"
done
If you like thrashing all your CPU cores, do that using GNU Parallel to get the job done faster.
Note that if you have a file that is smaller than 500px wide, ImageMagick will not process it so if it is smaller than 500 pixels wide and also larger than 50kB, it will not get reduced in terms of filesize. To catch that unlikely edge case, you may need to run another find afterwards to find any files over 50kB and then run them through convert but without the -resize, something like this:
find . -type f -iname "*.jpg" -size -51200c -exec convert {} -define jpeg:extent=50KB {} \;

ImageMagick and spritesheet

I'm working on a little script that generates a sprite sheet. I have 6 spritesheets and I need to re-organize them and put their content (once ordered) in a unique file.
I logically chose to use ImageMagick. But here I'm stuck.
Here is what I have so far :
convert '%d.png[0-5]' \( -crop 456x912+0+0 -crop 3x6+0+0# +append \) -append test.png
This command line takes my 6 files (0.png to 5.png) crop them, and split them into 18 sprites. Once splited, the 18 sprites are aligned horizontally and then aligned vertically with the 18 previous one.
The problem is this command seems to only aligned them horizontally. Instead of being composed of 18x6 sprites, test.png is composed by 108x1 sprites.
Any idea how to perform this in one command ?
You might try another imagemagick tool, "montage," for this one.
$ for i in `seq 1 18 `; do convert -background none -fill black -size 32x32 -pointsize 14 caption:"$i" $i.png; done
$ montage `ls ?.png` `ls ??.png` -tile 6x3 -geometry 32x32 tile.jpg

Generating x number of pictures

I need to generate pictures with a certain size (in pixels). Each picture will have an incrementing number in it. That is all that will be in the picture, a number. I've been thinking of using photoshop but I have no idea how the scripting works. Any suggestions or examples I could use?
Try using ImageMagick (http://www.imagemagick.org) and its text handling feautures (http://www.imagemagick.org/Usage/text/).
Here is a way of doing that using ImageMagick:
#!/bin/bash
for i in {0..3}; do
echo Generating $i...
convert -size 256x256 xc:black \
-gravity south -background orange -splice 0x20 -annotate +0+2 "$i" image-$i.png
done
And the result:

Resources