imagemagick detect coordinates of transparent areas - ruby-on-rails

I have a PNG image with transparent areas, squares/rectangle areas that contains transparency.
I would like to know if there is some way that i can know the top,lef,width,height of theses transparent areas in the image.
Thanks for any help

Updated Answer
In the intervening years, I have come across a simpler solution, so I thought I would update it so anyone else seeing it can get the benefit of the latest and greatest.
Start off just the same, by extracting the alpha layer to an image of its own and invert it:
convert start.png -alpha extract -negate intermediate.png
Now perform a "Connected Component Analysis" on that:
convert start.png -alpha extract -negate \
-define connected-components:verbose=true \
-define connected-components:area-threshold=100 \
-connected-components 8 -auto-level result.png
Objects (id: bounding-box centroid area mean-color):
0: 256x256+0+0 128.7,130.4 62740 srgb(0,0,0)
3: 146x8+103+65 175.5,68.5 1168 srgb(255,255,255)
2: 9x93+29+42 33.0,88.0 837 srgb(255,255,255)
1: 113x7+4+21 60.0,24.0 791 srgb(255,255,255)
You will see there is a header line and 4 lines of output and each has a colour at the end, the first line is black, and corresponds to the entire shape, and the the last three are white, corresponding to the three transparent areas. It is basically the second field on each of the last three lines that you want. So, 146x8+103+65 means a box 146px wide by 103px tall offset 103px to the right of the top-left corner, and 65px down from the top-left corner.
If I draw those in, in red, you can see what it has identified:
convert result.png -stroke red -fill none -strokewidth 1 \
-draw "rectangle 103,65 249,73" \
-draw "rectangle 29,42 38,135" \
-draw "rectangle 4,21 117,28" result.png
Original Answer
The following may help you get to an answer but I have not developed it all the way through to completion - people often ask questions and then never log in again and there is quite a lot of effort involved...
Let's start with this input image - where the white areas are transparent:
You can extract the alpha channel from an image with ImageMagick like this:
convert input.png -alpha extract -negate alpha.png
which gives this, where white areas are transparent
Ok, one approach is to find the bounding box of the white areas, you can do this with trim and it will give you the bounding box that encloses the white areas:
convert input.png -alpha extract -format "%#" info:
245x114+4+21
So the bounding box is 245px wide and 114px high starting at offset +4+21 from top left. I can draw that on the image to show it:
So, that is a start.
Also, you can get ImageMagick to enumerate the pixels in text format, so you can run this command
convert input.png -alpha extract -negate txt: | more
# ImageMagick pixel enumeration: 256,256,255,gray
0,0: (0,0,0) #000000 gray(0)
1,0: (0,0,0) #000000 gray(0)
2,0: (0,0,0) #000000 gray(0)
which tells you that the image is 256x256 and that the first 3 pixels are all black. If you want the white ones (i.e. transparent ones) you can do this:
convert input.png -alpha extract -negate txt: | grep FFFFFF | more
4,21: (255,255,255) #FFFFFF gray(255)
5,21: (255,255,255) #FFFFFF gray(255)
6,21: (255,255,255) #FFFFFF gray(255)
7,21: (255,255,255) #FFFFFF gray(255)
This tells you that pixel 4,21 is the top left corner of your transparent area - I'm glad it matches the output from the bounding box method above :-)
So, you can easily get a list of all the pixels that are transparent. This approach could be developed, or something similar coded up in Ruby (RMagick) to find contiguous areas of black - but that is beyond the scope of this answer for the moment - as I am not a Ruby programmer :-)
Ok, I have learned some Ruby this afternoon and, no laughter please, this is my first Ruby program. It is probably pretty ugly and more like Perl or C (my preferred languages) but it works and finds the rectangular transparent areas.
#!/usr/bin/ruby
require 'RMagick'
include Magick
infile=ARGV[0]
img = ImageList.new(infile)
w=img.columns
h=img.rows
#Extract alpha channel into pixel array
px=img.export_pixels(0,0,w,h,"A")
for row in 0..h-1
for col in 0..w-1
thispx=px[w*row+col]
if thispx<32768 then
a=row
b=col
# Find extent (c) of rectangle towards right
for r in col..w-1
thispx=px[w*row+r]
if thispx<32768
c=r
else
break
end
end
# Find extent (d) of rectangle towards bottom
for s in row..h-1
thispx=px[w*s+col]
if thispx<32768
d=s
else
break
end
end
# Blank this rectangle as we have located it
for r in row..d
for s in col..c
px[w*r+s]=65535
end
end
# Tell caller about this rectangle
printf "%d,%d %d,%d\n",a,b,d,c
end
end
end
Run it like this:
bounds.rb input.png

Related

Find colored box in image and create a mask file

I would like to create a hotfolder for my motion camera, into which I can drop images marking areas, which should be excluded in motion recognition via a *pgm mask. On these images, there is a small area marked with a transparent box with a magenta colored outline. My aim is to replace this box and outline with a black solid box and the rest of the image with white. (Tried to post samples here, but not enough reputation to do so.)
I know how to do this "by foot" using gimp, but I cannot figure out a clever and simple way achieving this with imagemagick.
I tried googling for solutions with -trim and -virtual-pixel, but no luck. Any help would be appreciated.
I'll do this step-by-step so you can see the intermediate parts in case you are on Windows and bash doesn't work.
First, let's make make everything that is not within 10% of your magenta colour, namely rgb(225,75,130), into lime green:
magick source.jpg -fill lime -fuzz 10% +opaque "rgb(225,75,130)" result.png
Ok, now let's get the trim box - i.e. all the constant junk that ImageMagick could trim off to focus on the magenta bit.
magick source.jpg -fill black -fuzz 10% +opaque "rgb(225,75,130)" -format '%#' info:
14x66+426+118
So your magenta box is 14x66pixels and located at offset 426,118 from the top-left. Now we want to get those in bash variables w,h,x,y. We need to change x and + into spaces using tr:
read w h x y < <(magick source.jpg -fill black -fuzz 10% +opaque "rgb(225,75,130)" -format '%#' info: | tr 'x+' ' ')
If we print this we get:
echo $w, $h, $x, $y
14, 66, 426, 118
Now we want to draw a rectangle, but that needs top-left and bottom-right, so we need to do some maths:
((x1=x+w))
((y1=y+h))
Ok, now we can load the original image, make it fully white, then draw our black rectangle:
magick source.jpg -threshold -1 -fill black -draw "rectangle $x,$y $x1,$y1" -depth 8 mask.pgm
So, the whole thing boils down to:
#!/bin/bash
read w h x y < <(magick source.jpg -fill black -fuzz 10% +opaque "rgb(225,75,130)" -format '%#' info: | tr 'x+' ' ')
echo $w, $h, $x, $y
((x1=x+w))
((y1=y+h))
magick source.jpg -threshold -1 -fill black -draw "rectangle $x,$y $x1,$y1" -depth 8 mask.pgm
There are other (maybe more elegant) ways of doing it, using flood-fills and/or connected components but I didn't want it to rely on your magenta box being "watertight", i.e. not rely on the sides being continuous and complete.
Also, if the size of your images is known and constant, you can avoid reloading the original and making it white by thresholding like I do in the last line and just create a canvas of the known dimensions, i.e.:
magick -size ${W}x${H} xc:white -fill black -draw "rectangle $x,$y $x1,$y1" -depth 8 mask.pgm

ImageMagick count how many identical or almost identical color pixels are on the edges left and right of image

This is an example PNG image:
it's 50x40 but enlarged here to see it better.
The hex of bottom left and bottom right pixels is F9E4B7
I need to know how many pixels there are on vertical edges from the bottom left and right with almost this color, like 1% fuzz because they are not all exactly F9E4B7 but some are very slightly different.
In this case it would be 20px of each side:
I'll mark the areas which need to be counted just to make it perfectly clear:
I also need to be able to count how many pixels are exactly F9E4B7, which in this case would be 15 left and 20 right, I'll mark the slightly different areas so it's clear where the counter needs to stop:
The way to do that in Imagemagick is to use fuzz to change all your pixels close to that color to white and the rest black. The crop the first or last column and use -fx to get the count from mean*width*height. So for example for the left full column of the whole image, one has
Unix Imagemagick 6 syntax:
convert tan.png -alpha off \
-fuzz 1% -fill white -opaque "#F9E4B7" -fill black +opaque white \
-gravity west -crop 1x+0+0 -format "%[fx:round(mean*w*h)]\n" info:
Windows Imagemagick Syntax:
convert tan.png -alpha off ^
-fuzz 1% -fill white -opaque "#F9E4B7" -fill black +opaque white ^
-gravity west -crop 1x+0+0 -format "%[fx:round(mean*w*h)]\n" info:
Result:
108
To get the right side, use -gravity east.
For exact count, use -fuzz 0
For Imagemagick 7, change convert to magick

remove white background with ImageMagick but not the white inside picture?

im trying to delete the background with a batch process in pictures of products for an e-commerce site.
The problem is that the script also remove the white color in the inside of the product, leaving the product transparent in some areas..
For example:
Command:
convert *.jpg -set filename: %t -fuzz 5% -transparent white %[filename:].png
this is the best I can get..im ok with this result around the product, but I need that the white inside the product remains white and not transparent.
The problem with your current approach is that it doesn't respect boundaries, it is just applied globally, making all white pixels transparent regardless of their connectivity to the background.
Instead, you will get on better using a "floodfill" that only floods into areas that are within the fuzz distance of the top-left corner pixel.
So, I chose an unused colour of magenta so you can see what is happening:
convert product.jpg -fuzz 5% -fill magenta -draw 'color 0,0 floodfill' result.png
You would then follow that with the command to make magenta transparent like this:
convert product.jpg -fuzz 5% -fill magenta -draw 'color 0,0 floodfill' -transparent magenta result.png

Find nearest point horizontal in imagemagick?

I'm trying to find the nearest point to the point (red in this case) in this image. In this image this output find the first line point from right
how I could do this
output
Please help me.
This looks fun! Let's dump the image to text using ImageMagick:
convert image.png txt:
# ImageMagick pixel enumeration: 337,218,65535,srgb
0,0: (65535,65535,65535) #FFFFFF white
1,0: (65535,65535,65535) #FFFFFF white
2,0: (65535,65535,65535) #FFFFFF white
3,0: (65535,65535,65535) #FFFFFF white
4,0: (65535,65535,65535) #FFFFFF white
...
...
221,79: (0,0,0) #000000 black
221,80: (0,0,0) #000000 black
221,81: (0,0,0) #000000 black
221,82: (0,0,0) #000000 black
...
...
Ok, now let's use awk to find all black pixels and print their (x,y) coordinates:
convert image.png txt: | awk -F'[,:]' '/black/{x=$1;y=$2;print x,y}'
221 79
221 80
221 81
221 82
221 83
221 84
...
...
Ok, now let's tell awk where the red pixel is by passing in rx (red x-coordinate) and ry (red y-coordinate). Then also, calculate the sum of the squares of the x-distance and y-distance from red to each black pixel. When it is less (i.e. nearer) than any seen so far, save the location. Print the nearest location at the end.
convert image.png txt: | awk -F'[,:]' -v rx=318 -v ry=127 '
BEGIN{m=999999}
/black/{
x=$1; y=$2; d2=(rx-x)*(rx-x)+(ry-y)*(ry-y)
if(d2<m){m=d2;xm=x;ym=y}
}
END{print xm,ym}'
277 127
So, that is the answer... (277,127). Let's check it by drawing a cyan circle there:
convert image.png -fill cyan -draw "circle 277,127 277,132" check.png
On re-reading the question, I note that you actually only want the horizontally closest point whereas my solution above caters for the general case in any direction. If you just want horizontal offset, and you know the horizontal line is at y-coordinate 127, you can just extract that specific row from the image and simplify things like this:
convert image.png -crop x1+0+127 txt: | awk -F'[,:]' -v rx=318 '
BEGIN{m=999999} /black/{x=$1;d=(rx-x)*(rx-x);if(d<m){m=d;xm=x}} END{print xm}'
277
If you don't like awk, you can just do it by eyeball...
convert image.png -crop x1+0+127 txt: | grep -E "black|red"
221,0: (0,0,0) #000000 black
277,0: (0,0,0) #000000 black <--- nearest black to red
314,0: (65535,0,0) #FF0000 red
315,0: (65535,0,0) #FF0000 red
316,0: (65535,0,0) #FF0000 red
317,0: (65535,0,0) #FF0000 red
318,0: (65535,0,0) #FF0000 red
319,0: (65535,0,0) #FF0000 red
320,0: (65535,0,0) #FF0000 red
How did I find the coordinates of the red pixel? I used ImageMagick's sub-image search looking for a red pixel like this:
compare -metric rmse -subimage-search -dissimilarity-threshold 1 image.png \( -size 1x1 xc:red \) null:
0 (0) # 317,121
Notes:
I just used the sum of the squares rather than the square root of the sum of the squares because it is computationally faster and the results are the same because it holds that if a>b, then a * a > b * b in this case.
I used slightly different rx and ry from those generated by the sub-image search because OP says he had the coordinates and the ones found by sub-image search don't find the exact centre of the rather large red blob, but instead the top-leftmost edge of the red blob.

How do I avoid this ImageMagick anti-aliasing discoloration?

Update:
Thanks to a suggestion from paddy, it has come to my attention that the anti-aliasing looks fine on one of my machines, but not the other.
I'm trying to create a 24x24 png that features a filled shape with a transparent background. Here is the command I am working with:
convert -size 24x24 xc:none \
-fill red \
-draw "path 'M 0,0 L 24,0 A 24,24 0 0,0 0,24 Z' " result.png
"xc: _____" specifies a background color. (maybe this is where I'm mistaken?)
The output gives me the correct shape, but the border between the shape and the transparent area (i.e. the anti-aliased edge between color and transparency) is noticeably darkened:
The anti-aliasing shown on the right is what I want from ImageMagick. Here's what I get for different values of xc:
As you can see, the anti-aliasing blends fine with solid colors, but not with transparency. The ImageMagick guide has a section on anti-aliasing and the problems it can cause, but reading through it, none of it's examples cover transparency. Any ideas?
Have you tried setting -background Red -alpha Background? I suspect that the background color is defaulting to black, which is then being blurred with the red against the transparent background.
Try xc:transparent over xc:none
convert -size 24x24 xc:transparent \
-fill red \
-draw "path 'M 0,0 L 24,0 A 24,24 0 0,0 0,24 Z' " result.png
As #paddy pointed out in the comments, this issue was resolved in later versions of IM
Updating to a more recent version of ImageMagick/convert (6.9.1-6 Q16 i686 2015-06-30) fixed the problem for me. (Thanks, paddy)

Resources