Search code examples
imagemagickminimagick

Detecting Transparent Pixels in mini_magick


I have an image with known dimensions, let's say 8x8. I am needing to know definitively if there are any non-transparent pixels around the outer edge pixels of the image using MiniMagick. I'd love to know how to do this in ImageMagick as well, if that's possible (for the sake of my own understanding), but I really only need this in MiniMagick for now.

If anyone even has half-answers, feel free to throw something at this issue. I'm willing to play around with concepts as well. Thanks!


Solution

  • I would approach it like this. Fill the inner, non-edge pixels with a fully transparent nothingness - a hole, if you like. Then any opacity remaining in the image must be as a result of some opacity in the edge pixels. So if we overlay the newly-created image with a hole on top of any background and that results in a change to the background, we can deduce there must be non-transparent pixels in the new image, and therefore in the edges of the new image as we already made sure there were no non-transparent pixels in the middle. It sounds harder than it is!

    #!/bin/bash
    # First get dimensions of inner "hole", it's the width minus 2 and height minus 2
    # so for a 16x16 image, it will be 14x14
    inner=$(convert input.png -format "%[fx:w-2]x%[fx:h-2]" info:)
    
    # Now punch a fully transparent hole in the input image
    convert input.png                                      \ 
            -size $inner xc:none -alpha set -geometry +1+1 \
            -compose copy -composite tmp.png
    

    input.png

    enter image description here

    tmp.png

    enter image description here

    So, now we overlay the image with a hole on top of a white background, and then compare the result with a plain white image, and count how many pixels differ.

    convert -size 16x16 xc:white tmp.png -composite \
            xc:white -metric AE -compare -format "%[distortion]" info:
    60
    

    And there are 60 => 16 across the top, 16 across the bottom and 14 down each vertical edge. If we repeat the exercise with a fully transparent original image, the answer will be zero. So the test you need is whether the error metric (number of differing pixels) is non-zero.

    Actually, in the shell and as your images are small, you would probably just convert the image to text and let awk find the edge pixels, like this:

    # Create opaque image
    convert -size 16x16 xc:red PNG32:input.png
    
    # Find pixels in row 0, row 15, col 0, col 15 where transparency (last digit before closing paren) is non-zero
    convert input.png txt: | awk '/,0:|,15:|^0,|^15,/ && !/,0)/'
    0,0: (255,0,0,1)  #FF0000  red
    1,0: (255,0,0,1)  #FF0000  red
    2,0: (255,0,0,1)  #FF0000  red
    3,0: (255,0,0,1)  #FF0000  red
    4,0: (255,0,0,1)  #FF0000  red
    5,0: (255,0,0,1)  #FF0000  red
    6,0: (255,0,0,1)  #FF0000  red
    7,0: (255,0,0,1)  #FF0000  red
    8,0: (255,0,0,1)  #FF0000  red
    9,0: (255,0,0,1)  #FF0000  red
    10,0: (255,0,0,1)  #FF0000  red
    11,0: (255,0,0,1)  #FF0000  red
    12,0: (255,0,0,1)  #FF0000  red
    13,0: (255,0,0,1)  #FF0000  red
    14,0: (255,0,0,1)  #FF0000  red
    15,0: (255,0,0,1)  #FF0000  red
    0,1: (255,0,0,1)  #FF0000  red
    15,1: (255,0,0,1)  #FF0000  red
    0,2: (255,0,0,1)  #FF0000  red
    15,2: (255,0,0,1)  #FF0000  red
    0,3: (255,0,0,1)  #FF0000  red
    15,3: (255,0,0,1)  #FF0000  red
    0,4: (255,0,0,1)  #FF0000  red
    15,4: (255,0,0,1)  #FF0000  red
    0,5: (255,0,0,1)  #FF0000  red
    15,5: (255,0,0,1)  #FF0000  red
    0,6: (255,0,0,1)  #FF0000  red
    15,6: (255,0,0,1)  #FF0000  red
    0,7: (255,0,0,1)  #FF0000  red
    15,7: (255,0,0,1)  #FF0000  red
    0,8: (255,0,0,1)  #FF0000  red
    15,8: (255,0,0,1)  #FF0000  red
    0,9: (255,0,0,1)  #FF0000  red
    15,9: (255,0,0,1)  #FF0000  red
    0,10: (255,0,0,1)  #FF0000  red
    15,10: (255,0,0,1)  #FF0000  red
    0,11: (255,0,0,1)  #FF0000  red
    15,11: (255,0,0,1)  #FF0000  red
    0,12: (255,0,0,1)  #FF0000  red
    15,12: (255,0,0,1)  #FF0000  red
    0,13: (255,0,0,1)  #FF0000  red
    15,13: (255,0,0,1)  #FF0000  red
    0,14: (255,0,0,1)  #FF0000  red
    15,14: (255,0,0,1)  #FF0000  red
    0,15: (255,0,0,1)  #FF0000  red
    1,15: (255,0,0,1)  #FF0000  red
    2,15: (255,0,0,1)  #FF0000  red
    3,15: (255,0,0,1)  #FF0000  red
    4,15: (255,0,0,1)  #FF0000  red
    5,15: (255,0,0,1)  #FF0000  red
    6,15: (255,0,0,1)  #FF0000  red
    7,15: (255,0,0,1)  #FF0000  red
    8,15: (255,0,0,1)  #FF0000  red
    9,15: (255,0,0,1)  #FF0000  red
    10,15: (255,0,0,1)  #FF0000  red
    11,15: (255,0,0,1)  #FF0000  red
    12,15: (255,0,0,1)  #FF0000  red
    13,15: (255,0,0,1)  #FF0000  red
    14,15: (255,0,0,1)  #FF0000  red
    15,15: (255,0,0,1)  #FF0000  red
    
    # Now create fully transparent image
    convert -size 16x16 xc:none PNG32:input.png
    
    # Find any non-opaque pixels
    convert input.png txt: | awk '/,0:|,15:|^0,|^15,/ && !/,0)/'
    
    # None
    

    If you don't like hard-coding the 15s into awk like I have done, you could of course, pick up the width and height from the first line of the output of convert where it appears like this:

    # ImageMagick pixel enumeration: 16,16,255,srgba
    

    and make a search pattern with the last row/col along these lines - just ask if you don't know how to do it:

    awk 'NR==1                  {... pattern="0,|,0:..." }   # First line, get dimensions, make pattern
         $0 ~ pattern && !/,0)/ {print "..."; exit}'         # Find non-transparent edge pixels