Split a Bash script with ImageMagick instructions into smaller pieces? - bash

I have this bash script, feeding drawing information into ImageMagick, which starts like this:
#!/bin/bash
convert -size 2200x2200 xc:white \
-fill '#FFFEFF' -draw 'point 1112,1111' \
-fill '#FFFEFE' -draw 'point 1112,1112' \
-fill '#FFFEFE' -draw 'point 1111,1112' \
-fill '#FFFEFE' -draw 'point 1110,1112' \
-fill '#FFFEFE' -draw 'point 1110,1111' \
******ON & ON 4.2 MILLION LINES MORE*******
spectrumspiral.png;
My problem is I keep getting warnings about argument list too long, terminal says 'Killed', warning about 'fork: cannot allocate memory etc.
I've tried adjusting ulimit -s to a much higher value to no avail. Really want to make this image. Any idea how I can feed the terminal chunks of this script at a time? Or something to that end.
I've heard xargs can be used for things like this, but I haven't been able to find a specific implementation that fits the nature of this problem.

If you create an image with 4 pixels in it (1 red, 1 white, 1 blue and 1 black), you can tell ImageMagick to output the resulting image as text file as follows:
convert -size 1x1 xc:red xc:white xc:blue xc:black +append -depth 8 -colorspace RGB image.txt
# ImageMagick pixel enumeration: 4,1,255,rgb
0,0: (255,0,0) #FF0000 rgb(255,0,0)
1,0: (255,255,255) #FFFFFF rgb(255,255,255)
2,0: (0,0,255) #0000FF rgb(0,0,255)
3,0: (0,0,0) #000000 rgb(0,0,0)
By the same token, if you feed that text file into ImageMagick, it can recreate the image:
cat image.txt | convert txt:- output.img
So, all we need to do is convert your -fill commands into a text file of the format that ImageMagick likes. So we can do this to yourFile
sed -E 's/.*(#[0-9A-F]+).* ([0-9]+)\,([0-9]+).*/\2,\3:\1/g' yourFile
and we will get something like this:
0,0:#3C4AD8
0,1:#269531
0,2:#CF2C7C
...
...
which we can then pipe into awk to rearrange how ImageMagick likes it:
awk -F: '
BEGIN{print "# ImageMagick pixel enumeration: 2200,2200,255,rgb"}
{
coords=$1;colour=$2
rh="0x" substr(colour,2,2);
gh="0x" substr(colour,4,2);
bh="0x" substr(colour,6,2);
r=strtonum(rh);
g=strtonum(gh);
b=strtonum(bh);
printf "%s: (%d,%d,%d) %s\n",coords,r,g,b,colour;
}'
So, if we put all that together, the following script should be able to create your beloved image into the file spectrumspiral.png:
#!/bin/bash
sed -E 's/.*(#[0-9A-F]+).* ([0-9]+)\,([0-9]+).*/\2,\3:\1/g' yourFile | awk -F: '
BEGIN{print "# ImageMagick pixel enumeration: 2200,2200,255,rgb"}
{
coords=$1;colour=$2
rh="0x" substr(colour,2,2);
gh="0x" substr(colour,4,2);
bh="0x" substr(colour,6,2);
r=strtonum(rh);
g=strtonum(gh);
b=strtonum(bh);
printf "%s: (%d,%d,%d) %s\n",coords,r,g,b,colour;
}' | convert txt:- spectrumspiral.png

Try using the shell's here-document
#!/bin/bash
convert - <<EOS spectrumspiral.png
-size 2200x2200 xc:white
-fill '#FFFEFF' -draw point '1112,1111'
-fill '#FFFEFE' -draw 'point 1112,1112'
-fill '#FFFEFE' -draw 'point 1111,1112'
-fill '#FFFEFE' -draw 'point 1110,1112'
-fill '#FFFEFE' -draw 'point 1110,1111'
# ******ON & ON 4.2 MILLION LINES MORE*****
EOS
My system doesn't have convert so I can test that this works.
Most linux programs (including convert) can accept input from StdIn, which in the cmd-line above is represented by the - char. The - tells the command to expect input, not from file, but as if it was being typed at the keyboard, ie StdIn. The <<EOS .... EOS is the here-doc, and it represents bash 'typing' in all of that text into the commands stdIn input.
You may or may not need all of the quoting, if you already have there, I don't think it will hurt. If this doesn't work as is, use a small sample file of input (like above) and test various scenarios until it creates a file for you.
IHTH

Related

For loop to anotate images with imagemagick from text file

I want to simplify my Christmas card making process. I have already built a frame with some text, and I wanted to personalize each card with the recipients name by using imagemagick and some bash power. But so far I'm failing bad :(
This is what I tried at first:
for i in names.txt;
do convert -font Akaya-Telivigala-Regular -fill black -stroke black -strokewidth 1\
-pointsize 190 -draw 'text 640,730 $i' chrisFrame.svg -resize 70%\
greetings_$i.jpg;
done
But it fails with convert-im6.q16: non-conforming drawing primitive definition i' # error/draw.c/RenderMVGContent/4300.`.
Seeing that imagemagick was trying to use $i as a primitive, I tried to enclose it in double quotes, as I would do if I was passing the names by hand:
for i in names.txt;
do convert -font Akaya-Telivigala-Regular -fill black -stroke black -strokewidth 1\
-pointsize 190 -draw 'text 640,730 "$i"' chrisFrame.svg -resize 70%\
greetings_$i.jpg;
done
Of course it generated a beautiful file named greetings_names.txt.jpg with $i instead of the name.
The content of the names.txt file is nothing else than
John,
Jane,
What am I doing wrong and how can I make it work?
With the gracious help of #GeeMack and after correcting an error in the call of the for loop, the solution is this:
for i in `cat names.txt`;
do convert -font Akaya-Telivigala-Regular -fill black -stroke black\
-strokewidth 1 -pointsize 190 -draw "text 640,730 '$i'"\
chrisFrame.svg -resize 70% greeting_$i.jpg;
done
Effectively, inverting the single and double quotes made the trick.
Please notice that it was necessary to change also the line
for i in names.txt
for
for i in `cat names.txt`
EDIT TO ADD:
After #Jetchisel comment regarding why one should not use for to read lines out of a file, the correct code will be:
while read i
do convert -font Akaya-Telivigala-Regular -fill black\
-stroke black -strokewidth 1 -pointsize 190
-draw "text 640,730 '$i'" chrisFrame.svg\
-resize 70% greetings_$i.jpg;
done < names.txt

imagemagick: how to use the %[fx:...] operator in draw rectangle command?

I can do this to draw a red rectangle on an image:
convert original.png -fill red -draw "rectangle 10,20 150,40" result.png
The 150,40 is the right bottom coordinate. However if I use the %[fx:...] operator in there, like so:
convert original.png -fill red -draw "rectangle 10,20 %[fx:w-30],40" result.png
The %[fx:w-30] is supposed to evalute to the image's width minus 30.
However I'm getting an error:
convert: non-conforming drawing primitive definition `rectangle' # error/draw.c/DrawImage/4227.
I have also tried single quotes (') instead of double (") but that made no difference.
My imagemagick version is 7.0.7-36.
What am I doing wrong? What's the correct way of using the %[fx:...] operator in the above example?
Thanks to #GeeMack the solution is: use magick instead of convert, so it becomes:
magick original.png -fill red -draw "rectangle 10,20 %[fx:w-30],40" result.png
If you're still on ImageMagick 6, you can use the identify command to output a formatted string and then use that in the convert command. Using shell syntax, this can be expressed as follows:
convert original.png -fill red -draw "$(identify -format 'rectangle 10,20 %[fx:w-30],40' original.png)" result.png

color pixel count with GraphicsMagick

I need to count pixels in an image that are not background color.
I am calling this from PHP (it's from ImageMagick):
gm convert test.png -fill black +opaque "rgb(255,255,255)" -fill white -opaque "rgb(255,255,255)" -print "pixels = %[fx:w*h*mean]\n"
But it does not give any result, nothing.
I tried using histogram instead:
gm convert test.png -define histogram:unique-colors=true -format %c histogram:info.txt
That works, but gives values for every color and more details, I just need a single number please.
You have got a couple of issues here. You seem to be trying to mix GraphicsMagick with ImageMagick when they are not the same thing.
Firstly, GraphicsMagick does not have the +opaque operator that ImageMagick has.
Secondly, it doesn't have the -fx operator that ImageMagick has for doing maths.
I would suggest you move to, the more powerful, ImageMagick. Then it will work as you expect:
# Create a test image
convert -size 200x200 xc:black xc:white xc:red +append image.png
# Count the white pixels
convert image.png -fill black +opaque "rgb(255,255,255)" -print "pixels = %[fx:w*h*mean]\n" nul:
pixels = 40000
If you really, really must do it with GraphicsMagick, I can only suggest the following - which is heavily based on #GlennRanders-Pehrson answer here:
gm convert image.png +matte -matte -transparent white -operator matte negate 1 result.png
gm identify -verbose result.png | grep -EA5 "Opacity:|Geometry:" | grep -E "Mean|Geometry"
Geometry: 600x200
Mean: 43690.00 (0.6667)
Mean: 43690.00 (0.6667)
And your answer will be:
600 * 200 * (1 - 0.667)

Count how many pixels with a specific color in an image?

I want to loop through a folder of images and output to console how many pixels are #333212, how many are #332211 etc. Is this possible in PHP? I found a package that manipulates images but not one that can detect colors of each pixel. Does such a tool or function exist in the PHP library?
EDIT: Doesn't have to be in PHP, the less packages I have to install the better.
You can do this quite easily with ImageMagick, like this. Say we want to count the red pixels...
# First create a little test strip with black, white, red, green and blue parts
convert -size 50x50 xc:black xc:white xc:red xc:lime xc:blue +append out.png
Now convert anything non-red to black so that only red pixels remain
convert out.png -fill black +opaque red n.png
Now count the red pixels by cloning the resulting picture and making the clone fully black (by setting everything to zero), and running a comparison to count how many pixels are not black
convert n.png \
\( +clone -evaluate set 0 \) \
-metric AE -compare \
-format "%[distortion]" info:
2500
And 2500 looks like 50px by 50px to me :-)
Note
The AE is the Absolute Error, i.e. a simple count of the number of differing pixels. The -format "%[distortion]" info: part causes ImageMagick to output the count (%distortion) as a number (info:) rather than as an image.
Obviously, you replace red with "#333212" for your problem.
You can also do all that in one visit, like this:
convert input.png \
-fill black +opaque red \
\( +clone -evaluate set 0 \) \
-metric AE -compare \
-format "%[distortion]" info:

make all values but one in large raster file 0

I have a large raster file downloaded from Earth Engine. I want to turn it to a boolean file, keeping only one value (13) and make all other values either NA or 0. The file is so large it crashes QGIS and ArcMap when I try to process it, is there a way to do this using GDAL or bash? The file is a tif file.
You can do that with ImageMagick which is installed on most Linux distros and is available for macOS and Windows.
Make a test image at the command line - value 13 in the middle, red and blue around that:
convert -size 30x20 xc:"gray(13)" \
-bordercolor red -border 10 \
-bordercolor blue -border 10 start.tif
Now fill with black, everything that is not value 13:
convert start.tif -fill black +opaque "gray(13)" result.tif
Or, somewhat easier to see - fill everything that is not value 13 with cyan and change everything that is value 13 to yellow:
convert start.tif \
-fill cyan +opaque "gray(13)" \
-fill yellow -opaque "gray(13)" result.tif
Here's a gdal solution:
Your input is input.tif:
gdal_calc.py --calc="A==13" -A input.tif --type=Byte --outfile=output.tif
With R you can do
library(raster)
library(rgdal)
r <- raster("input.tif")
x <- calc(r, function(i){ i==13 }, filename="output.tif", datatype="INT1U")
Or use raster::reclassify

Resources