How to test for an image fully inside another?

BlitzPlus Forums/BlitzPlus Programming/How to test for an image fully inside another?

While I'm familiar (to some degree) with the imagescollide, imagesoverlap, imagerectoverlap, imagerectcollide functions, I'm lost as to how to determine if one image is fully inside another (where both have transparency around the outer portion).

For example, I can deal with two rectangles using their respective x,y,width,height. However, if the smaller object is not a simple rectangle, but rather a hexagon or circle image, I'm not sure how to proceed.

If the larger object is also not a simple rectangle, I'm further lost.

I'm trying to code picking up an irregularly shaped card (say a hexagon shaped card) with a number of circular tokens on it. I need to detect which tokens are fully on the hex shaped card.

Calculate the offset of the first image over the second, and then check each pixel underneath the first image to see if the pixel below it is solid or not. If any pixels are over transparent pixels in image 2, or any pixels are over areas not overed by image 2 at all, then image 1 is not completely inside image 2.

; Calculate offset of Image 1 over Image 2:
Xoff = I2x - I1x
YOff = I2y - I1y

;Now, for each pixel in image 1, add that offset to its ;location to determine its location in image 2.

LockBuffer ImageBuffer(Image1)
LockBuffer ImageBuffer(Image2)

Inside = True

For Y = 0 to ImageHeight(Image1)-1
   For X = 0 to ImageWidth(Image1)-1

      ; Calculate the pixel in image 2 this pixel in image 1 is above:
      X2 = X + XOff
      Y2 = Y + YOff

      ; If the pixel lies within image 2:
      If (X2 >= 0) AND (X2 < ImageWidth(Image2)) AND (Y2 >= 0) AND (Y2 < ImageHeight(Image2))

         ; Is this pixel of image1 opaque?
         If ReadPixelFast(Image1, X, Y) <> 0

            ; Is this pixel of image2 transparent?
            If ReadPixelFast(Image2, X2, Y2) = 0

              ; Pixel1 is outside Image2.  Exit both loops!
                Inside = False
                Y = ImageHeight(Image1)
                X = InageWidth(Image1)

            else
               ; There are pixels from both images at this location, and both are opaque.
            endif
         else
            ; This pixel of image 1 is transparent, so it doesn't matter what is beneath it.
         endif

      Else
         ; Pixel is outside Image2.  Exit both loops!
         Inside = False
         Y = ImageHeight(Image1)
         X = InageWidth(Image1)
      Endif
   next
next

UnlockBuffer ImageBuffer(Image1)
UnlockBuffer ImageBuffer(Image2)



Use ImagesCollide()...

Graphics 800, 600, 0, 2
SeedRnd MilliSecs()

AutoMidHandle True

;make 'card'
card = CreateImage(256, 256)
SetBuffer ImageBuffer(card)
Color Rand(100, 255), Rand(100, 255), Rand(100, 255)
ClsColor 0, 0, 0
Cls
For c=1 To 100
	Oval Rand(1, 155), Rand(1, 155), 100, 100, 1
Next
SetBuffer BackBuffer()

;make collision mask
mask = CreateImage(ImageWidth(card), ImageHeight(card))
LockBuffer ImageBuffer(card)
LockBuffer ImageBuffer(mask)

For x=0 To (ImageWidth(card) - 1)
	For y=0 To (ImageHeight(card) - 1)
		rgb = ReadPixelFast(x, y, ImageBuffer(card)) And $FFFFFF
		
		If rgb Then rgb = 0 Else rgb = $FFFFFF
		
		WritePixelFast x, y, rgb, ImageBuffer(mask)
	Next
Next

UnlockBuffer ImageBuffer(mask)
UnlockBuffer ImageBuffer(card)

;make counter
counter = CreateImage(104, 104)
SetBuffer ImageBuffer(counter)
Color Rand(100, 255), Rand(100, 255), Rand(100, 255)
ClsColor 0, 0, 0
Cls
Color 255, 255, 255
Oval 0, 0, 104, 104, 1
Color 0, 0, 200
Oval 4, 4, 96, 96, 1
SetBuffer BackBuffer()	

on_card = False

Repeat
	mx = MouseX()
	my = MouseY()
	
	Cls
	
	If MouseDown(1)
		DrawImage mask, 400, 300
	Else
		DrawImage card, 400, 300
	EndIf
	
	;draw 'counter'
	DrawImage counter, mx, my
	
	;check if counter is on card
	If RectsOverlap(400 - 128, 300 - 128, 256, 256, mx - 52, my - 52, 104, 104)
		If ImagesCollide(mask, 400, 300, 0, counter, mx, my, 0)
			on_card = False
		Else
			on_card = True
		EndIf
	Else
		on_card = False
	EndIf
	
	Color 255, 255, 255
	Text 12, 12, "Counter fully on card = " + on_card
	
	Text 400, 588, "LMB to show mask", 1, 1
	
	Flip
Until KeyHit(1)

End


What about setting a centre point for both shapes and checking that without any graphic collision routines!

Grey,
If the underlying image is a closed filled rectangle, I can check to see if the outside edges of the top shape fall within those bounds. No problem.

something like
local x1, y1, w1, h1 ; upperleft corner x,y and width/height of first image
local x2,y2,w2,h2 ; upperleft corner x,y and width/height of second image

if (((x2>=x1) and (x2<=(x1+w1))) and ((y2>=y1)and(y2<=(y1+h1)))) ; upperleft corner of 2nd image is inside 1st image
    if ((((x2+w2)>=x1) and ((x2+w2)<=(x1+w1))) and (((y2+h2)>=y1)and((y2+h2)<=(y1+h1)))) ; lower right corner of 2nd image is inside 1st image
        return True ; 2nd image is inside 1st
    endif
endif



If the 2nd image is not a rectangle, but is a uniform shape (circle, rectangle, rhombus, hexagon, etc.) and the underlying 1st image is a rectangle, the same code applies.

When the underlying image is not a rectangle, it appears I have to start pixel checking.

If the 2nd image is a rectangle, I can just check the four corners.

sswift,
thanks for the code snippet - I'll have to walk through it before commenting.

Squatting Neville,
thanks for the code snippet - very nice demonstration. I'll have to walk through it more before figuring out which approach to take.

It appears at initial glance, that sswift's approach will also handle holes in the images while Squatting Neville is more straightforward (what I was expecting to see the response based on ).

Squatting's method is clever, I had to run it and click the left mouse button to figure out what he was doing.

And his method should handle holes in the image just line mine would.

His method requires you to build and maintain inverse mask images though.

My method is basically to replace the imagescollide function with my own which essentially does what the inverse mask method is doing.

His method might be faster, but might result in messier code since you'll have to construct that inverse mask every time you load a new image, maintain it and free it. You'd lose any speed benefit and then some if you built the inverse mask at runtime, as needed, I think.

Given that, I think my method is probably the better of the two, though the ideal would be for imagescollide to support a flag to invert the mask check.