ImageMagick way too slow drawing text - imagemagick

I've got ImageMagick drawing dots with coordinates on an image, but it's terribly slow.
For context, I'm making a program to move the mouse cursor by typing two keys.
Here's my script:
#!/bin/bash
image=screen.jpg
chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789'
gap=30
dimensions="$(identify -format '%w %h\n' "$image")" || exit
width=${dimensions% *} height=${dimensions#* }
cmd="convert -pointsize 16 -fill red -draw '$(
ychars="$chars"
for y in $(seq $gap $gap "$((height-gap))"); do
yc="${ychars%${ychars#?}}" ychars="${ychars#?}"
xchars="$chars"
for x in $(seq $gap $gap $((width-gap))); do
xc="${xchars%${xchars#?}}" xchars="${xchars#?}"
printf 'rectangle %s,%s %s,%s text %s,%s %s ' $((x-1)) $((y-1)) $((x+1)) $((y+1)) $((x+2)) $((y-2)) "$yc$xc"
done
done
)' $image result.jpg"
eval "$cmd"
It generates a command that will draw screen.jpg with coordinates, the result is result.jpg.
The command it generates looks like this (but much much longer):
convert -pointsize 16 -fill red -draw 'rectangle 29,29 31,31 text 32,28 aa rectangle 59,29 61,31 text 62,28 ab rectangle 89,29 91,31 text 92,28 ac rectangle 119,29 121,31 text 122,28 ad' screen.jpg coords.jpg
The command is generated instantly but ImageMagick takes over 7 seconds on my 1920 x 1080 screenshot (taken using import -window root screen.jpg).
I tried GraphicsMagick too with little difference.
Am I doing something stupid, why is it so slow?
How can I improve the performance?
I was hoping for under a second or two.
If nothing else works, I'll make an image of coordinates and slap it on instead, but I was hoping it could be dynamic.

Related

Create character sheet using imagemagick

I currently modding for F1 Challenge 99 02 and i want to change the game font. The format F1C uses is basically a sheet of characters arranged in a grid-like formation. Below is an example:
How can i make similar images with a desired font using Imagemagick (or other cli software)?
I modified one of my Windows scripts and tested it with ImageMagick 6.8.9-9 on a bash shell.
The first part of it uses a couple "for" loops and "sed" to create a text file containing a list of all the characters.
> chars.txt
for x in 2 3 4 5 6 7 8 9 A B C D E F ;
do for y in 0 1 2 3 4 5 6 7 8 9 A B C D E F ;
do echo X | sed "s/.*/label:\x${x}${y}/g" >> chars.txt ;
done ;
done
The lines in that file look like this...
label:
label:!
label:"
label:#
label:$
label:%
...
The prefix "label:" on each line lets IM read that file as if it's a long list of individual labels. IM reads that file like this "#chars.txt" to get all the images to create the character sheet. Here is the IM command that works on my bash...
imfont=Times-Roman
convert -font ${imfont} -pointsize 24 -size 36x36 -gravity center -background none \
label:"\ " #chars.txt +gravity -compose copy -bordercolor blue -shave 1 -border 1 \
+append +repage -crop 360x36 -append +repage charsheet.png
You should be able to make the grid from any installed font by setting the variable "imfont" to your choice.
The grid is created on a transparent background. To put it on a colored background, white for example, use "-background #FFFFFF -flatten" before writing the output file.
Edited to add: Notice how the command includes the "label:\ " for the space character ahead of reading the list of labels. On my installation IM won't make a label for a space unless I escape it with a backslash "\", so I just add that label manually.

ImageMagick - Trim / Crop to contiguous objects

How do you do the equivalent of this step in Photoshop.
https://gyazo.com/180a507c0f3c9b342fe33ce218cd512e
Supposed there are two contiguous objects in an image, and you want to create exact sized crops around each one and output as two files. (Generalize to N files)
You can do that with "Connected Component Analysis" to find the contiguous blobs.
Start Image
convert shapes.png -colorspace gray -negate -threshold 10% \
-define connected-components:verbose=true \
-connected-components 8 -normalize output.png
Sample Output
Objects (id: bounding-box centroid area mean-color):
0: 416x310+0+0 212.3,145.2 76702 srgb(0,0,0)
1: 141x215+20+31 90.0,146.2 26129 srgb(255,255,255)
2: 141x215+241+75 311.0,190.2 26129 srgb(255,255,255)
Notice how each blob, or contiguous object, is "labelled" or identified with its own unique colour (shade of grey).
So there is a header line telling you what the fields are followed by 3 blobs, i.e. one per line of output. The first line is the entire image and not much use. The second one is 141 px wide and 215 px tall starting at +20+31 from the top-left corner. The third one is the same size (because I copied the shape) and starts as +241+75 from the top-left corner.
Now stroke red around the final indicated rectangle - bearing in mind that rectangle takes top-left and bottom-right corners rather than top-left corner plus width and height.
convert shapes.png -stroke red -fill none -draw "rectangle 241,75 382,290" z.png
And crop it:
convert shapes.png -crop 141x215+241+75 z.png
And here is the extracted part:
If you want to generalise, you can just pipe the ImageMagick output into awk and pick out the geometry field:
convert shapes.png -colorspace gray -negate -threshold 10% -define connected-components:verbose=true -connected-components 8 -normalize output.png | awk 'NR>2{print $2}'
Sample Output
141x215+20+31
141x215+241+75

Adding white line between text lines

I am trying to do OCR using Tesseract overall results seems acceptable. The images are very very long receipts and we are scanning using scanner, the quality is better. Only issue is that in receipts few characters are joint between two lines
Please see the attached sample image. You may see in the first line character 'p' and in the second line character M are joint. This is causing problem in OCR.
SO, the real question is may we add a white line or square between every text line ?
You can do that for this image in Imagemagick by trimming the image to remove surrounding white and adding the same amount of black. Then average that image down to one column and looking for the brightest row. I start and stop 4 pixels from the top and bottom to avoid any really bright rows in those regions. Once I find the brightest row, I splice in 4 rows of white between the top and bottom regions divided by that row. This is not the most elegant way. But it shows the potential. One could likely pipe the list of row values to AWK and search for the max value in more efficient manner than saving to an array and using a for loop. Unix syntax with Imagemagick.
Input:
max=0
row=0
arr=()
arr=(`convert text.png -fuzz 50% -trim -background black -flatten -colorspace gray -scale 1x! -depth 8 txt:- | tail -n +2 | sed -n 's/^.*gray[(]\(.*\)[)]$/\1/p'`)
num=${#arr[*]}
#echo "${arr[*]}"
for ((i=4; i<num-4; i++)); do
val="${arr[$i]}"
max=`convert xc: -format "%[fx:$val>$max?$val:$max]" info:`
row=`convert xc: -format "%[fx:$val==$max?$i:$row]" info:`
#echo "$i $val $max $row"
done
convert text.png -gravity north -splice 0x4+0+$row text2.png
If you want less space, you can change to -splice 0x1+0+$row, but it won't change much. It is not writing over your image, but inserting white between the existing rows.
But by doing the processing above, your OCR still may not recognize the p or M, since the bottom of the p is cut off and appended to the M.
If you have more than two lines of text, you will have to search the column for approximately evenly spaced maxima.

Image magic text writing on image dynamically

I am building a image editor with php, jquery, image magic, I am trying to write text on image dynamic position but not getting the exact command of image magic.
I tried imagemagic commands of writing text but these commands are for fixed position. e.g i need to write text on image "hey love" on position (x: 831, y: 38) x is width and y is height now what i need is exact command able to write text on dynamic positions.
convert temp.jpg -gravity North -pointsize 30 -annotate +0+100 'Love you mom' temp1.jpg
this command uses gravity i have dynamic positions
You can do it like this:
convert -size 400x250 xc:cyan -pointsize 18 -fill magenta -draw "text 250,65 'Love you Mum'" result.png

ImageMagick: Decreasing RGB values of all pixels in an image

Specifically, with a given image I'm trying to decrease the RGB values for each pixel by 100.
For example, if a pixel has R: 232, G: 40, B: 120 then I want the new RGB values to be R: 132, G: 0, B: 20.
I have tried this solution that I found on the ImageMagick forums:
convert input.jpg -channel R -evaluate subtract 25700 \
-channel G -evaluate subtract 25700 \
-channel B -evaluate subtract 25700 output.jpg
Edit: the reason I use 25700 is because apparently you need to multiply the rgb value by 257. 100 * 257 = 25700.
While it appears to work at first (clearly darkening the image), it seems that certain pixels will not change and for what I'm doing it's vital that they do (I'm running a trim on the resulting image, trying to trim away the border with pixel values of 0).
An common problem is that I'll end up with a pixel that has a RGB values of 3, 0, 0, but I'll want that pixel to have values of 0 for RGB and increase the constant I subtract by - but it doesn't seem to work.
Any ideas? Thanks!
Honestly, I don't really understand what the value 25700 in your commandline should achieve.
However, I suggest a different commandline to you, using the more powerful -fx operator. A bit more complicated looking, but hopefully more intuitively to understand...
But first, I'm looking at your description and see you want to subtract a fixed number of 120 from each of the current R, G, and B color values. So this is a gray pixel color... and as you can look up in ImageMagick's color built-in color list, its name is gray47:
convert -list color | grep '(120,120,120)'
gray47 srgb(120,120,120) X11 XPM
grey47 srgb(120,120,120) SVG X11
This leads me to the following command:
convert \
input.jpg \
-channel red -fx 'r - gray47' \
-channel green -fx 'g - gray47' \
-channel blue -fx 'b - gray47' \
output.jpg
This way or writing the command will probably open your eyes to some easily derived modifications should you need those in future...
To have an immediate preview window of the result popping up (without writing it to a file) you can also use -show: as output, like this:
convert \
input.jpg \
-channel red -fx 'r - gray47' \
-channel green -fx 'g - gray47' \
-channel blue -fx 'b - gray47' \
-show:
Update
If you want to check for the real differences of each pixel, you can make ImageMagick print out the color value for each pixel:
convert input.jpg input.txt
convert output.jpg output.txt
The format of the .txt file is pretty easy to understand, once you know that the first columns give the Pixel zero-based coordinates: 123,456: means: 124th (!) column, 457th () row.
Now you can compare the two .txt files to your heart's content even in an automated, scripted version, without a need to resort to Gimp. :-)
You could even use input.txt and apply a Perl-, Ruby-, Python- or Shellscript onto each of the pixel values to distract your 120 value from each channel, save it as output2.txt and then convert it back to JPEG:
convert output2.txt output2.jpg
Then look for pixel differences between the two output images:
compare output.jpg output2.jpg delta.jpg
compare output.jpg output2.jpg view:
An all-white plane will mean 'no differences' , any red pixels will hint to some sort of delta.
Now if that answer doesn't earn me an upvote, I don't know which would... :-)
Ken, no, you don't need to write a big parser. It's quite easily done with a few shell commands. Test them first, then put them into a Shell or Batch script. Something like this (as Bash script):
#!/bin/bash
echo " ATTENTION: this script can take a loooong time to complete..."
echo " (This script is made to convert PNG files with an Alpha channel."
echo " for other types of images, you need to slightly modify it.)"
echo
echo " This script takes an 8-bit RGBA input image and creates a darker output image."
echo " Its method is: subtract the value of 100 from each color channel's numeric value."
echo
input="${1}"
_im_header=$(identify -format "%W,%H" "${input}")
echo "# ImageMagick pixel enumeration: ${_im_header},255,rgba" > input-minus-120.txt
convert "${input}" input.txt
cat input.txt \
| \
sed 's#) .*$#)#; s# ##g; s#:#: #; s#(# #; s#)##; s#,# #g; s# #,#' \
| \
while read coord red green blue alpha; do
echo -n "${coord}";
echo -n " (";
echo -n " $(($red - 100)),";
echo -n " $(($green - 100)),";
echo -n " $(($blue - 100)),";
echo -n " $(($alpha))";
echo -n " ) ";
echo;
done \
| sed 's#-[0-9]*#0#g' \
>> input-minus-120.txt
convert input-minus-120.txt output-minus-120.jpg
This script required 153 seconds to run on a MacBook Pro, processing a 1080x889 Pixels PNG file of 750 kByte.
The generated input.txt had 960120 lines (the number of Pixels in the PNG).
So the performance of this brute force shell script is about 6275 Pixels/second.

Resources