Punching a circular hole in an image

Is there a simple way to “punch a hole” in an image (i.e., make the corresponding pixels transparent)?

(Ideally, the solution shouldn’t involve shaders since I’m trying to teach this to an 8-year old.)

There is quite a bit of topics about this if you search the forums

@daveedvdv You can do img:set(x,y,r,g,b,a) or img:set(x,y,color()).

Just replace the word “img” with your images name, and fill in the other values.

@daveedvdv - this is surprisingly hard. The only way to punch a hole in an image is not to draw the pixels. You can either do this with
1 a shader, or
2 a mesh with very cunning formula that leaves a hole in the middle, or
3 by individually setting individual pixels blank, eg
img:set(x,y,0,0,0,0) --sets pixel x,y blank
4 do it outside Codea in a drawing program (draw a transparent shape over the image), then import it to Codea (not via the photo library, because that destroys transparent pixels, but via the Dropbox folder linked to Codea)

I think option 3 or 4 is the most obvious for you

@CodeaNoob: Any suggestions which search terms my be fruitful? I tried “punch” and “circular hole” before posting, but that didn’t show up anything relevant.

@Prynok: Thanks, but I was trying to avoid teaching her how to draw a circle pixel-by-pixel :wink: I.e., she knows “ellipse(x, y, R)” will draw her circle, but she has no idea of the algorithm/geometry underneath it.

Hmm… I guess she could try to draw with a certain color and then replace it with transparency. That would probably end up being messy on anti-aliased edges though…

Thanks @Ignatz.

She tried #4 (with Art Studio, via exporting to the pasteboard) and had slight alignment issues (she does everything on the iPad, with imprecise input) although it did “kindof” work. I suspect for now it’s her best avenue though. Prynok’s suggestion — which is also your #3might be within her reach by checking which pixels to clear based on the results of an opaque “ellipse” into an image, but that’s probably a couple of evenings working on getting her to understand the notion of “drawing contexts”.

@Daveedvdv If you are trying to make the inside of a circle transparent, then you could fill it with transparency, and make the border (stroke) the color you want.

@Daveedvdv - yes, if you’re going to have to delete individual pixels, the easiest way is to use setContext to get Codea to draw onto the image in memory, then sprite a one-colour shape onto the image where you want the hole, then loop through all the pixels setting that colour to transparent wherever you find it.

@Prynok: Thanks. She’s actually trying to punch holes in a rectangle. (She has coded up a Connect4 game, and would now like to get the pieces to “fall” behind the game board (which would be that rectangle with 42 holes punched in it). Just drawing with transparency doesn’t work, of course.

@Ignatz: Thanks for the confirmation. It occurred to me that she should give your #4 option another go, but doing it in two steps. First she can programmatically draw the board with opaque colors, export it to Art Studio, using the magic wand tool to select the circles and clear them, then export back to her sprites collection.

Looking through the Codea Reference I didn’t see a way to export an image to the pasteboard. So I’ll suggest she use a screenshot to export to Art Studio (that might cause alignment issues again, but it should be easier with a rectangle than with circles).

(FWIW, I did some experiments with blendMode a couple of days ago, and was surprised that drawing an ellipse with a mode that I expected to clear the destination pixels ended up clearing a rectangular shape instead. Is that as expected?)

@daveedvdv - I’m not on my iPad right now, but it may be because the ellipse is sitting within a rectangular image, and that whole (rectangular) image is being blended.

Your solution for Connect4 sounds very sensible. There is no way to copy it to the pasteboard, but you can save it to the Dropbox folder, wihch you can then access from your computer (after pressing the “Sync” button in Codea’s Dropbox folder to synchronise the images). Remember when you bring it back, to do that via Dropbox and not Photos.

@daveedvdv There’s a fifth option, which I believe Andrew Stacey came up with for one of my apps, which is much simpler than a shader, pixel setting or coming from outside of codea.

Using Blendmodes you can remove all those pixels to hole punch your shape desired, I whipped up a connect4 board example for you, touch the screen to see the ellipse behind the board. This may not be simple enough…

function setup()
    displayMode(FULLSCREEN)
    board = image(WIDTH-100,HEIGHT-100)
    local mx,my = (WIDTH-100)/7,(HEIGHT-100)/6
    pushStyle()
        --Disable smoothing to stop mixing alphas at edges (creates bug)
        noSmooth()
        --Draw to our board image
        setContext(board)
        --Set colour of the board
        background(40,50,240)
        
        noStroke()
        fill(0, 0, 0, 255)
        --This is the blend mode:
        blendMode(ZERO,ONE_MINUS_SRC_ALPHA,ZERO,ONE_MINUS_SRC_ALPHA)
        --Draw any primitive after this point that you want to be removed from the board (holes to punch)
        for x=1,7 do
            for y=1,6 do
                ellipse(mx*x-mx/2,my*y-my/2,50)
            end
        end
        setContext()
    popStyle()
end

function draw()
    background(70,50,60)
    --Draw ellipse before board so it appears behind.
    pushStyle()
        fill(50,220,20)
        ellipse(CurrentTouch.x,CurrentTouch.y,80)
    popStyle()
    --Draw the board
    sprite(board,WIDTH/2,HEIGHT/2)
end

Thanks for the tip, @Ignatz! She doesn’t have a DropBox account yet, but I think it’s safe enough to set that up for her.

Thank you all for the quick responses — much appreciated.

@Luatee: Wow! Many thanks!!

I thought I tried that (more or less) a few days ago, but as I mentioned in an early response my ellipses were rendering as rectangles (it wasn’t even a bounding rectangle, if I recall right). I see your code code mentions a bug with smooth() mode; perhaps that was related. I’ll try that when I get back to the iPad.

Thanks again.

@Luatee: I just tried it, and it works perfectly. In fact, it seems to work even with smooth() enabled — so I’m not sure what I did wrong earlier, but I’m sure grateful for this solution. I think my daughter be able to grasp it, and she’ll be super-excited to see the falling pieces when it’s all put together.

@Daveedvdv that’s good to hear, look forward to seeing connect4! The smooth() bug means there will be translucent pixels at the cutoff point between alpha and no alpha as it smooths colours in to each other to make it easier on the eye. This may not matter so much as you don’t change scale.

I remember that it took us a few goes to get the blendMode right. Ellipses are actually rectangles with a special shader so the obvious blendMode produces the rectangle, not an ellipse.

@Daveedvdv, Here’s something,

I looked up “Hole in Circle” and it popped ut

@Luatee: Thanks. I’m not sure she’ll publish it (maybe we’ll get there, because she already told me she wants to “sell it on the App store” and get rich :wink: ), but she’ll be showing off what she’s got so far at her school’s science fair this weekend. (I don’t think she’ll have the time to work out the “falling pieces” before that either.)

@Andrew_Stacey: Thanks for the explanation. I suspect I had slightly different parameters for srcAlpha and dstAlpha, and that would cause the problem I saw given the draw-ellipse-via-shader approach.

@CodeaNoob: Thanks for the tip! One of the comments mentioned that a negative radius produces a filled bounding box for circle with the circle left as a hole. I hope that’s not just a bug, but for now it’s actually the easiest way for me to teach a solution to my daughter. For those whose didn’t know, try something like:

ellipse(200, 200, -100)

over a nontrivial image.

Very nice one @luatee and @Andrew_Stacey

Even though this chain happened three years ago, I thought it might be useful for those looking. Credit to Jmv38.

Draw the image
Then do this: blendMode( ZERO, ONE_MINUS_SRC_ALPHA )
Then draw a circle
Then use blendMode() to reset it