How to generate an image with a number in it? - bash

This command from ImageMagic creates an image writing the value of variable var in its center:
convert -size 512x512 xc:none -font "Free-Monospaced-Bold" -pointsize 180 -gravity center -draw 'text 0,0 '"$var"' ' "$var".png
While it works well for strings, it fails when the value of var is a number:
convert-im6.q16: non-conforming drawing primitive definition `text' # error/draw.c/DrawImage/3265.
Since variables in bash are untyped, there's no way to transform it into a string (command printf will leave it as it is, a number), so the point must be to change the text argument in the command.
How to generate the image when the variable is numeric (positive, negative and real numbers)?

While the documentation for -draw's text operator only says that the string to render needs to be quoted if it has spaces, it appears it also needs to be quoted if it's numeric. So...
-draw "text 0,0 '$var' "

You can also put a number in an image in Imagemagick using -annotate in place of -draw.
var=7
convert -size 512x512 xc:none -font "Free-Monospaced-Bold" -pointsize 180 -gravity center -annotate +0+0 "$var" "$var".png
Or
var=7
convert -size 512x512 xc:none -font "Free-Monospaced-Bold" -pointsize 180 -gravity center -annotate +0+0 $var $var.png
But safest to use double quotes

Related

imagmagick: selectively fill pixels based on r/g/b condition?

Is there a way to selectively replace colors, based on an arithmetic expression using a pixel's R or G or B value?
Example: say I have an RGB image "foobar.png" and I want to change all pixels whose Red channel is <100 into white.
In pseudocode:
for (all pixels in image) { if (pixel.red < 100) then pixel = 0xffffff; }
Is there a way to pull this off with ImageMagick?
This is similar but slightly different from emcconville's excellent solution. This is in Unix syntax.
#1 compute the 100 out of 255 threshold in percent
#2 read the input
#3 clone the input and make it completely white
#4 clone the input and separate the red channel, threshold and negate so that the white part represents values less than 100 out of 255
#5 use the threshold image as a mask in a composite to select between the original and the white images
#6 write the output
thresh=`convert xc: -format "%[fx:100*100/255]" info:`
convert image.png \
\( -clone 0 -fill white -colorize 100 \) \
\( -clone 0 -channel r -separate +channel -threshold $thresh% -negate \) \
-compose over -composite \
result.png
You can use FX expressions.
Say I create a test image...
convert -size 400x400 gradient:red-blue input.png
Replace any pixel with a red value < 100 (assuming max-value is a 8bit quantum of 255), can be expressed by..
convert input.png -fx 'r < (100/255) ? #FFFFFF : u' output.png
Update
FX is powerful, but slow. It'll also draw harsh edges. Another approach is to separate the RED channel, convert it to a mask, and composite over the other channels. This can be done with -evaluate-sequance MAX, or set the alpha channel & compose over a white background.
Create an example input image.
convert -size 400x400 xc:white \
-sparse-color shepards '0 0 red 400 0 blue 400 400 green 0 400 yellow ' \
input.png
convert -size 400x400 xc:white \
\( input.png \
\( +clone -separate -delete 1,2 \
-negate -level 39% -negate \
\) \
-compose CopyOpacity -composite \
\) -compose Atop -composite output.png

Number of islands on a map

I've got a map (a picture on a computer) and I'd like to count the islands (whatever the size).
I've thought of x and y projections before counting 1D values but it only works for small numbers and not for specific dispositions.
Is there a known and efficient algorithm to do that?
Thanks.
Updated Answer
Ok, it seems you don't really have a map, you just have a list of 16384 numbers each representing land or sea but no indication of the size of the image it corresponds to - which isn't going to help you determine the adjacency of any pieces of land!
As there are 16,384 values, let's see what the factors are that could give that size:
1x16,384
2x8,192
...
32x512
64,256
128x128
I tried each of hose by putting a simple PGM format header on the front and converting the result to a JPG but nothing looks recognisable so I can't do anything till you tell me how the file works - with width and height.
{ echo P2; echo 128 128 ; echo 65535; tr " " "\n" < ~/Desktop/image_raw.txt; } | convert PGM:- -auto-level map.jpg
Original Answer
ImageMagick will do that for you quite easily using "Connected Components Analysis". It is installed on most Linux distros and is available for OSX and Windows.
As you haven't provided a map, let's use this one of Hawaii.
Just at the command-line without any programming, you can run:
convert hawaii.png -colorspace gray -negate -threshold 10% \
-define connected-components:verbose=true \
-define connected-components:area-threshold=100 \
-connected-components 8 -auto-level output.png
Output
Objects (id: bounding-box centroid area mean-color):
0: 1303x892+0+0 633.0,443.3 1072404 srgba(0,0,0,1)
7: 273x307+956+489 1074.8,639.4 47500 srgba(255,255,255,1)
5: 163x132+825+330 898.1,395.2 12978 srgba(255,255,255,1)
3: 145x116+509+188 580.6,251.7 9895 srgba(255,255,255,1)
1: 117x96+210+79 271.3,124.5 8407 srgba(255,255,255,1)
4: 139x56+702+291 769.9,317.9 5598 srgba(255,255,255,1)
6: 71x61+752+353 787.5,381.5 3087 srgba(255,255,255,1)
2: 58x72+119+121 147.3,157.1 2407 srgba(255,255,255,1)
That tells you there are 7 islands - one line per island plus one for the whole image = the first one.
I can box them in like this:
convert hawaii.png -stroke red -fill none -strokewidth 1 \
-draw "rectangle 956,489 1229,796" \
-draw "rectangle 825,330 988,462" \
-draw "rectangle 509,188 654,304" \
-draw "rectangle 210,79 327,175" \
-draw "rectangle 702,291 841,347" \
-draw "rectangle 752,353 823,414" \
-draw "rectangle 119,121 177,193" \
boxed.png

Numbering/Labeling images (eg 1,2,3 or A,B,C) with ImageMagick montage

I'm using
montage *.tif output.tif
to combine several images to one. I would now like them to be numbered (sometimes 1,2,3 …; somtimes A,B,C …)
What possibilities are there to label the single images in the combined? Is there an option to put the label not underneath the picture but eg in the upper left or lower right corner?
Unfortunately I could't really figure out how to use the -label command to achieve that. Thanks for any suggestions.
If you want to invest a little more effort you can have more control. If you do it like this, you can label the images "on-the-fly" as you montage them rather than having to save them all labelled and then montage them. You can also control the width, in terms of number of images per line, more easily:
#!/bin/bash
number=0
for f in *.tif; do
convert "$f" -gravity northwest -annotate +0+0 "$number" miff:-
((number++))
done | montage -tile x3 - result.png
It takes advantage of ImageMagick miff format, which means Multiple Image File Format, to concatenate all the output images and send them into the stdin of the montage command.
Or, you can change the script like this:
#!/bin/bash
number=0
for f in *.tif; do
convert "$f" -gravity northwest -fill white -annotate +0+0 "$number" miff:-
((number++))
done | montage -tile 2x - result.png
to get
Or maybe this...
#!/bin/bash
number=0
for f in *.tif; do
convert "$f" -gravity northwest -background gray90 label:"$number" -composite miff:-
((number++))
done | montage -tile 2x - result.png
Or with letters...
#!/bin/bash
number=0
letters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
for f in *.tif; do
label=${letters:number:1}
convert "$f" -gravity northwest -background gray90 label:"$label" -composite miff:-
((number++))
done | montage -tile 2x - result.png
What possibilities are there to label the single images in the combined?
Iterate with a for loop.
for INDEX in {A,B,C}; do
convert ${INDEX}.jpg labeled_${INDEX}.jpg
done
Is there an option to put the label not underneath the picture but eg in the upper left or lower right corner?
Try using the -annotate with -gravity
convert rose: -fill white \
-gravity NorthWest -annotate +0+0 "A" \
A.png
convert rose: -fill white \
-gravity SouthEast -annotate +0+0 "B" \
B.png
Based on the answers above, I wrote a small perl script to combine figures into labelled subfigures, which might be useful to others. It works for me on a Mac, but I didn't really test it.
Usage: pics2grid geometry output_file [-title title] [-bycols] input_files
where the geometry format is [# cols]x[# rows] and input_files accept wild cards. If your title contains spaces, the title must be quoted and the spaces escaped.
I paste it below, but it can also be downloaded from http://endress.org/progs.html#subfigs
#!/usr/bin/perl -l
use strict;
use warnings;
use autodie;
use Getopt::Long 'HelpMessage';
### Process options and arguments ###
if (#ARGV < 3){
HelpMessage(1);
}
if (($ARGV[0] !~ /^\d+x/) && ($ARGV[0] !~ /x\d+$/)){
HelpMessage(1);
}
my $geometry = shift #ARGV;
my $output_file = shift #ARGV;
my $title = "";
my $bycols = "";
GetOptions ('bycols' => \$bycols,
'title=s' => \$title,
'help' => sub { HelpMessage() },
) or HelpMessage(1);
# Wildcards are automatically expanded in #ARGV, but just make sure that they are
my #input_files = map { glob } #ARGV;
$title = "-title $title"
if ($title);
if ($bycols){
die "When option -bycols is set, a fully specified geometry is needed."
unless ($geometry =~ /^\d+x\d+$/);
}
#input_files = reorder_input_files (\#input_files, $geometry)
if ($bycols);
### Define the labels for the subfigures. If you want different figures, change it here. ###
my #labels = "a".."z";
#labels = #labels[0..$#input_files];
#labels = reorder_input_files (\#labels, $geometry)
if ($bycols);
my $pic_data;
foreach my $f (#input_files){
# Pictures are combined by rows
my $lab = shift #labels;
$pic_data .= `convert \"$f\" -pointsize 48 -font Arial-Regular -gravity northwest -annotate +20+10 \'$lab\' \\
-bordercolor black -border 1 \\
miff:-`;
}
open (OUT, "| montage -tile $geometry -geometry +0+0 -background white -pointsize 60 -font Arial-Regular $title - $output_file");
print OUT $pic_data;
close (OUT);
sub reorder_input_files {
my ($input_files, $geometry) = #_;
my ($ncols, $nrows) = split (/x/, $geometry);
my #rows = ([]) x $nrows;
foreach my $i (0..$#$input_files){
my #tmp_array = #{$rows[$i % $nrows]};
push (#tmp_array, $input_files->[$i]);
$rows[$i % $nrows] = \#tmp_array;
}
my #reordered_files = ();
map {push (#reordered_files, #$_)} #rows;
return (#reordered_files);
}
=head1 NAME
pics2grid - arrange pictures on a grid of sub-figures and number the individual pictures with letters
=head1 SYNOPSIS
pics2grid geometry output [-title title] [-bycols] inputfiles
The inputfiles argument accepts wild cards.
Geometry format: [\# cols]x[\# rows].
Unless you set the option -bycols, specifying either
the number of rows or of columns is sufficient.
Options:
--title,-t Title. If your title contains spaces, the title needs
to be quoted *and* the spaces need to be escaped.
--bycols,-b Arrange and number pictures by columns rather than rows
--geometry,-g Not yet implemented
--help,-h Print this help message
Examples:
# Create grid with 3 columns and 2 rows in which images are arranged by *rows*
pics2grid.pl 3x2 output.pdf input1.png input2.png input3.png\
input4.png input5.png input6.png
# Create grid with 2 columns and 3 rows in which images are arranged by *columns*
pics2grid.pl 2x3 output.pdf -bycols input1.png input2.png input3.png\
input4.png input5.png input6.png
# Same as above but with a title including spaces.
# Note that the title needs to be quoted *and* the space needs to be escaped
# (i.e., put \ in front of the space)
pics2grid.pl 2x3 output.pdf -bycols -title "My\ title" input1.png\
input2.png input3.png input4.png input5.png input6.png
# Create grid with 4 columns of all png files in the current directory. Images
# are arranged by *columns*.
# It will stop labeling the subfigures for more than 26 images
pics2grid.pl 4x output.pdf *.png
=head1 VERSION
0.01
=cut

Print text to image in colors with imagemagic

I want to print text on image but in different colors
So far I got this
convert -pointsize 50 Picture3.jpg -background none label:'Some Text' -flatten layer_simple.jpg
and the result is image printed with "Some Text" on the left top corner.
The color is black
also I do not know hot to use the -gravity center,
convert -fill blue -pointsize 50 Picture3.jpg -background none label:'Some Text' -flatten layer_simple.jpg
-fill blue , is the answer

Expand a variable inside `` command

I could not find the exact reference to what I'm doing...
I have the following script that does not expand the variable inside the command:
#!/bin/bash
name="my name"
`convert -pointsize 250 -font /usr/share/fonts/truetype/msttcorefonts/impact.ttf -fill black -draw 'text 330,900 "$name"' tag.jpg name_my.jpg`
This results in an image that has the text $name instead of the content of name.
I actually need to read lines from a file and rund the command on each name so my real script is(has the same problem):
arr=(`cat names.txt`)
for (( i=0; i<${len}; i+=2 ));
do
`convert -pointsize 250 -font /usr/share/fonts/truetype/msttcorefonts/impact.ttf -fill black -draw 'text 330,900 "$(${arr[i]} ${arr[i+1]})"' tag.jpg name_${arr[i]}.jpg`
done
Your problem is single quotes ('') not backticks. Because $name is within them, it won't be expanded. Instead, you should use double quotes and you can escape the inner quotes like this:
`convert -pointsize 250 -font /usr/share/fonts/truetype/msttcorefonts/impact.ttf -fill black -draw "text 330,900 \"$name\"" tag.jpg name_my.jpg`
you have an escaping problem.
either use proper escaping with backslash, or make sure otherewise that the $args are not "protected" by single quotes.
e.g.
name="bla"
# using escape character \
value1="foo \"${name}\""
# putting single-quotes inside double-quotes
value2="foo '"${name}"'"
to better see what is going on, try to break down the problem into multiple smaller problems.
e.g. create the "draw" command with all expansions before using it in convert
name="my name"
draw="text 330, 900 '"${name}"'"
convert -pointsize 250 -fill black -draw "${draw}" tag.jpg name_my.jpg

Resources