I'm trying to write a bash script to trim the scanner white space around some old photos that were scanned in ages ago. I've got hundreds of photos so I'm not doing it manually.
Fred's imagemagick scripts don't manage to select the appropriate area.
I am no programmer so please dont be too offended by my terrible attempts at scripting!
I've found a combination of commands using imagemagick that does it.
first I use a blurring filter to confuse imagemagick into correctly selecting the photo size:
convert input -virtual-pixel edge -blur 0x15 -fuzz 15% -trim info:
This spits out data as follows:
0001.jpeg JPEG 3439x2437 4960x6874+1521+115 8-bit DirectClass 0.070u 0:00.009
I then use the numbers to do a crop which has been very accurate on my scans. The following is an example using the numbers from above.
convert inputfile -crop 3439x2437+1521+115 +repage outputfile
My problem is in writing the bash file to go through a directory of pictures and automate the process.
Here's what I have so far:
#!/bin/bash
ls *.jpeg > list
cat list | while read line; do
convert $line -virtual-pixel edge -blur 0x15 -fuzz 15% -trim info: > blurtrim.txt
#need a line to manipulate the output of the above to spit out the crop coordinates for the next command
crop=$(<crop.txt)
convert $line -crop $crop +repage trim$line.jpeg
rm blurtext.txt
rm crop.txt
done
rm list
The key bit I can't do is changing the string output of the first imagemagick command.
the file goes along the lines of:
input fileformat 1111x2222 3333x4444+5555+666 and then a load of crap i dont care about
the numbers I need in my script are:
1111x2222+5555+666
the cherry on the top is that while most of the numbers are four digits long not all of them are so I cant rely on that.
any ideas on how to use sed or preferably something else less demonic to get the above numbers in my script?
an explanation of the syntax would be nice (but i understand if the explantion is the size of a book then its best left out).
thanks in advance!
You don't need to parse anything! ImageMagick can tell you the trim box directly itself, using the %# format:
convert image.jpg -virtual-pixel edge -blur 0x15 -fuzz 15% -format "%#" info:
1111x2222+5555+666
So, you can say:
trimbox=$(convert image.jpg -virtual-pixel edge -blur 0x15 -fuzz 15% -format "%#" info:)
convert image.jpg -crop $trimbox ...
Benefits include the fact that this approach works on Windows too, where there is no sed.
So, the full solution would be something like:
#!/bin/bash
shopt -s nullglob
for f in *.jpeg; do
trimbox=$(convert "$f" -virtual-pixel edge -blur 0x15 -fuzz 15% -format "%#" info:)
convert "$f" -crop "$trimbox" +repage "trimmed-$f"
done
Solution
This will parse your file line by line, extract the desired parameters, concatenate them together, and use it as the argument value to 'crop' for the convert program:
regex='([0-9]+x[0-9]+) [0-9]+x[0-9]+\+([0-9]+\+[0-9]+)'
while read line
do
if [[ $line =~ $regex ]]
then
cropParam="${BASH_REMATCH[1]}+${BASH_REMATCH[2]}"
convert inputfile -crop $cropParam +repage outputfile
else
echo "ERROR: Line was not in the expected format ($line)"
exit 1;
fi
done < blurtrim.txt
Explanation
The regex variable holds a regular expression (brief introduction to regular expressions in bash here: http://www.tldp.org/LDP/abs/html/x17129.html) which describes the format of the numbers you describe in your question. The () around parts of the pattern denotes something called a capture group. If the pattern matches, the part that is in the first () is captured in a bash variable BASH_REMATCH[1], and the second () is captured in BASH_REMATCH[2]. BASH_REMATCH[0] contains the whole match, in case you're wondering why we start at index 1.
The line [[ $line =~ $regex ]] is what actually executes the pattern matching algorithm for us. In Bash [[ is called the extended test command, and the operator =~ is called the regular expression matching operator. This article explains the operator in more detail: http://www.linuxjournal.com/content/bash-regular-expressions.
I would propose a similar solution to Jonathan:
re='([0-9x]+) [0-9x]+(\+[0-9+]+)'
for file in *.jpeg; do
output=$(convert "$file" -virtual-pixel edge -blur 0x15 -fuzz 15% -trim info:)
if [[ $output =~ $re ]]; then
crop="${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
convert "$file" -crop "$crop" +repage "trim$file.jpeg"
fi
done
The regular expression captures any group containing characters within the range 0-9 or x and then a + followed by numbers and + characters. It is a less strict pattern as it includes the x and + inside the bracket expressions, so technically would allow things like 0x9x9x0 but I can't imagine that this would present a problem based on the output you've shown us.
The other differences between this and your original attempt are that no temporary files are created and the loop is run over the list of files, rather than using ls, the parsing of which should generally be avoided in scripts.
Related
A pretty basic question but I'm new to
Imagemagick (and bash) and I'm having trouble batch cropping images in a folder. I've tried using a loop:
for image in '/home/donald/Desktop/New Folder'*.jpg; do
convert "$image" -gravity center -crop 95X95% "${image%.jpg}"-modified.jpg
done
but it returns:
convert.im6: unable to open image `/home/donald/Desktop/New Folder/*.jpg': No such file or directory # error/blob.c/OpenBlob/2638.
convert.im6: no images defined `/home/donald/Desktop/New Folder/*-modified.jpg' # error/convert.c/ConvertImageCommand/3044."
What would be the proper way of doing this?
Edit: Apparently a space in the folder name was causing problems I deleted it and things seem to be working.Apparently if you want to use a folder with a space name in bash you need to escape the space.
I believe you have no jpg files in the /home/donald/Desktop/New Folder/ directory. The shell will interpret it as the literal string /home/donald/Desktop/New Folder/*.jpg if there are no files matching the wildcard-ed string.
See this example:
$ for f in *.jpg*; do echo $f; done
file.jpg
file2.jpg
$ for f in *.jpgg; do echo $f; done
*.jpgg
See how that last one is the literal string and not a real file? It should have been displayed the first time too if it was (notice the trailing asterix symbol in *.jpg*).
You can fix this by checking if the file exists before executing the command, using [ -f "${file}" ]. For instance:
for image in '/home/donald/Desktop/New Folder'*.jpg; do
[ -f "${image}" ] && convert "$image" -gravity center -crop 95X95% "${image%.jpg}"-modified.jpg
done
This will check if the file image exists (-f) and execute the following statement only if true is returned &&. Had you written || instead of && then the following statement would be executed when false was returned.
Note that bash doesn't return true or false but it's the easiest way to explain and comprehend the notation.
Whenever I try to execute the following shell command , it works properly .
convert maanavulu_GIST-TLOTKrishna.tif -alpha set -matte -virtual-pixel transparent -set option:distort:viewport 1000x1000 -distort perspective-projection '1.06,0.5,0,0,1.2,0,0,0' -trim 1.jpg
But , whenever I try assign the command to a variable and then execute it , it reports the following error .
convert.im6: invalid argument for option PerspectiveProjection : 'Needs 8 coefficient values' # error/distort.c/GenerateCoefficients/873.
The short of it: it's best to:
store your arguments in an array
not including the command itself, for safety (preferable to an eval solution)
then invoke the command with the array
# Store options in array - note that the filenames are excluded here, too,
# for modularity
opts=(-alpha set -matte -virtual-pixel transparent -set option:distort:viewport \
1000x1000 -distort perspective-projection '1.06,0.5,0,0,1.2,0,0,0' -trim)
# Invoke command with filenames and saved options
convert maanavulu_GIST-TLOTKrishna.tif "${opts[#]}" 1.jpg
Afterthought: As #konsolebox and #chepner point out: using a function is probably the best choice (clear separation between fixed and variable parts, encapsulation, full range of shell commands available).
The proper way to assign-and-execute a command is to use an array:
COMMAND=(convert maanavulu_GIST-TLOTKrishna.tif -alpha set -matte -virtual-pixel transparent -set option:distort:viewport 1000x1000 -distort perspective-projection '1.06,0.5,0,0,1.2,0,0,0' -trim 1.jpg)
Then execute it:
"${COMMAND[#]}"
I like eval but it's definitely not the solution this time.
And just a tip: If you can use a function, use a function. And quote your arguments properly.
Quotes are not processed after expanding a variable. The only processing that occurs is word splitting and wildcard expansion. If you need to perform all the normal steps of command execution, you have to use eval:
eval "$variable"
#!/bin/bash
dir="/posix/path/to/folder"
cd "$dir"
color1 = "#816c51"
color2 = "#5a4a3b"
color3 = "#1c110f"
for file in *.tiff
do
base=${file%*.tif}
convert -unsharp 5 "$base" "$base.ppm"
convert -opaque white -fill white -fuzz 10% "$base.ppm" "${base}_step1.tif"
convert -fuzz 5% -fill "$color1" -opaque "$color1" "${base}_step1.tif" "${base}_step2.tif"
convert -fuzz 1.5% -fill "$color1" -opaque "$color2" "${base}_step2.tif" "${base}_step3.tif"
convert -fuzz 12% -fill "black" -opaque "$color3" "${base}_step3.tif" "${base}_step4.tif"
convert "${base}_step4.tif" "${base}_final.tif"
done
I get a couple of error:
convert: unable to open image ...
convert: missing an image filename
test.sh: line 6: color1: command not found
I'd really appreciate some help! Thanks!
I'm a bit unclear on what this is trying to do (I don't have much experience with ImageMagick), but from a Bash standpoint, I can tell you that this bit:
for file in *.tiff
do
base=${file%*.tif}
does not make sense: it tries to strip off a final .tif (one F) from a file-name that ends in .tiff (two F's). You presumably meant either this:
for file in *.tiff
do
base="${file%.tiff}"
(which strips off the final .tiff) or this:
for file in *.tif
do
base="${file%.tif}"
(which finds files ending in .tif); or, perhaps, this:
for file in *.tiff *.tif
do
base="${file%.tiff}"
base="${base%.tif}"
(which handles both cases).
You also probably want to explicitly check for the case that "$base" is '*' (which will happen, for example, if you try to use *.tif in a directory that doesn't contain any files matching that name).
I'm not sure entirely if I can fully help because the full extent of the problem isn't relevant, but I see you have ".tiff" (with two f) and then later everything else is ".tif" (with one f). So maybe that is why it can't find the filenames.
Also get rid of the spaces in color1 = "blah" (e.g. color1="blah") and that line will work.
I have a folder of images over 4MB - let's call this folder dsc_big/. I'd like to use convert -define jpeg:extent=2MB to convert them to under 2MB and copy dsc_big/* to a folder dsc_small/ that already exists.
I tried convert dsc_big/* -define jpeg:extent=2MB dsc_small/ but that produces images called -0, -1, and so on.
What do I do?
convert is designed to handle a single input file as far as I can tell, although I have to admit I don't understand the output you're getting. mogrify is better suited for batch processing in the following style:
mogrify -path ../dsc_small -define jpeg:extent=2MB dsc_big/*
But honestly I consider it dangerous for general usage (it'll overwrite the original images if you forget that -path) so I always use convert coupled with a for loop for this:
for file in dsc_big/*; do convert $file -define jpeg:extent=2MB dsc_small/`basename $file`; done
The basename call isn't necessary if you're processing files in the current directory.
This was the command which helped me after a long try.
I wanted to make same sized thumbnails from a big list of large images which have variable width and height . It was for creating a gallery page.
convert -define jpeg:size=250x200 *.jpg -thumbnail 250x200^ -gravity center -extent 250x200 crop/thumbnail-%d.jpeg
I got re-sized thumbnails which all having same width and height. :) thanks to ImageMagick.
Here's a solution without using for loops on the console
convert *.jpeg -define jpeg:extent=2MB -set filename:f '../dsc_small/%t_small.%e' +adjoin '%[filename:f]'
Although this is an old question, but I'm adding this response for the benefit of anyone else that stumbles upon this.
I had the same exact issue, and being discouraged by the use of mogrify, I wrote a small Python based utility called easymagick to make this process easier while internally using the convert command.
Please note, this is still a work in progress. I'll appreciate any kind of feedback I can get.
I found that cd-ing into the desired folder, and then using the bash global variable $PWD made my convert not throw any errors. I'm utilizing ImageMagick's recently implemented caption: http://www.imagemagick.org/Usage/text/#caption function to label my images with the base filename and place them in another directory within the first.
cd ~/person/photos
mkdir labeled
for f in $PWD/*.JPG; do
width=$(identify -format %w $f)
filename=$(basename $f .JPG)
convert -background '#0008' -colorspace transparent -fill white -gravity center -size ${width}x100 caption:"${filename}" ${f} +swap -gravity south -composite "$PWD/labeled/${filename}.jpg";
done
This works for me
convert -rotate 90 *.png rotate/image.jpg
produces image-0.jpg, image-1.jpg, image-2.jpg ..... in the 'rotate' folder. Don't know of a way to preserve the original filenames though.
let's have a look at the following image:
I have a horizontal grid and i want to place a text in this grid. The above example is wrong, because what i would like to have is that each character is placed exactly in one of the cells of the grid.
I wonder, if i can adjust the text-output in imagemagick to achieve this, without having to place each of the characters with it's own command.
Some additional facts:
i am using imagemagick from some shell script
i am doing rather complex drawings with imagemagick's MVG -- so it would be nice if the text could be still placed with the MVG commands
i am able to adjust the width of the grid by a few pixel, if this would be required with your solution, but all cells of course need to have the same width
i am always using the same fixed-width font (Courier) for this
i am able parse the font-metrics in my shell script and use this information to apply values to my text-commands
i only care about horizontal placement, vertical placement is not important because i render each row individual
With all this in mind -- is there any solution for my problem?
Thanks a lot!
You can use the kerning option - setting inter-character spacing.
e.g.
for i in 0 3 6 9 12 15
do
convert -kerning $i -font Courier -pointsize 24 label:":Kerning $i:" label_$i.jpg
done
will generate the following images. You must simply find the right kerning value for match the grid. (for monospaced font - like your Courier)
If you have a non mono-typed font that you want to force into a grid, you can use this script:
#!/usr/bin/env bash
rm test*png
font=~/Library/Fonts/WittenbergerFrakturMTStd.otf
gridsize=32x32
chr() {
case "$1" in
64 ) echo '\#' ;;
92 ) echo '\\' ;;
* ) printf \\$(printf '%03o' $1)
esac
}
for i in {32..127}; do
c=$( chr $i )
echo -n "$c: "
convert -background transparent -density 90 -pointsize 12 -gravity center -font "$font" label:"$c" -extent $gridsize test-$i.png
done
convert test-{32..127}.png +append test.png