Define transparent pixels

How do I define that the pixels around this princess should be transparent, but the princess should be white?

image

That depends on how the princess image is stored. If it's a static image, you can simply add a transparency channel to the source PNG and erase everything around it.

2 Likes

I have a somewhat related question.

I have a gfx.image created at runtime with default clear background, but has been drawn on with black and white. I would like to partially 'erase' by drawing gfx.kColorClear draw calls, but using something like a gfx.setPattern as a poor man's fade effect.

Is this the totally incorrect approach to do this?

That's a sensible approach I'd say. Also look at gfx.setDitherPattern which takes an alpha value and applies it with a choice of built-in dither types.

Sorry - I should have specified that the approach I described wasn't working. If I try the below, I get the following:

test = gfx.image.new(4000,2400)
gfx.pushContext(test)
gfx.fillRect(30,60,100,100)
gfx.setColor(gfx.kColorClear)
gfx.setPattern({0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA})
gfx.fillRect(30,60,50,100)
gfx.popContext(test)
(Then during update, draw this image after everything else onscreen)

playdate-20230818-085057
I would like the lines in the background to show through the grey area of the drawn square. Drawing with kColorClear does the right thing, but at 100% of course (just a sanity check that I can draw to clear).

gfx.setColor(gfx.kColorClear)
gfx.fillRect(40,70,50,80)

playdate-20230818-093016

I was expecting/hoping that by setting a pattern after setting gfx.setColor(kColorClear), patterns would behave such that 'on' would be "draw the current cell as transparent" and 'off' would be "don't do anything", but it is the case that the pattern is drawn with the two colors white and black regardless of the setColor mode, and is not transparent.

I also tried extending the pattern to use the "additional 8 numbers can be specified for an alpha mask bitmap." specified in the documentation, to just see what happens (alpha mask sounded like maybe transparency?), but extending the list does not seem to do anything with transparency behavior. I'm confused about the use of the term mask in this part of the documentation. (Inside Playdate) I have tried drawing and setting masks atop a black region, drawn in white, black, and clear, none of which 'punch a hole' in a black region of the masked image, so my understanding of what a mask does here isn't correct.

I also have experimented with the dithering patterns, and am not finding success 'punching holes' into a black area by drawing rectangles on top of already black regions after setting a dithering mode.

Ultimately, I have a large image onto which I will be drawing player tracks over time, which I want to 'fade the edges of' by drawing rectangles of a clear mesh over the outer parts of the image (which will ultimately be sparsely populated with black and white dots, which I would like to fade). I can punch a large hole by drawing a rectangle while in kColorClear, but can't figure out a method to draw a pattern of clear pixels (without doing a massive number of 'drawPixel' calls). It seems like drawing faded applies to the entirety of the rendered image.

(Sorry if this was inappropriate thread etiquette but thank you for the input)

Oh, yeah. The problem is that setPattern and setDitherPattern are INSTEAD of a color, not used in conjunction WITH a color.

What you want is actually a stencil.

setStencilPattern can take EITHER a hex code pattern OR a dither type with alpha value.

https://sdk.play.date/inside-playdate/#f-graphics.setStencilPattern

(Or you could assemble the desired transparent portion in a separate image, then apply a mask to that. I don't know why you would, but you could!)

Alright after some messing around, I think a stencil is not what I want, because I want to be preparing the transparent area of an image that is larger than the display space (so that when that large image gets scrolled to its edge, the fade-off is there). From my understanding, a stencil is just for within the screen area? (might be totally off)

I was able to figure out how to do what I wanted, using the mask application approach you suggested, but after getting very confused about getMask and drawing to the alpha channel.
First some failures:
Starting point: I would like to be able to 'cut out' a portion of the black area, by drawing onto the mask of the image containing the black area.
playdate-20230818-152659

"Oh OK, I should define a mask for my image - I'll make a new image with a white background as the mask, draw to it in black, then assign it as a mask to my image that I want to obscure in certain spots."

testimage = gfx.image.new(4000,2400)
mask = gfx.image.new(4000,2400, gfx.kColorWhite)
gfx.pushContext(testimage)
gfx.fillRect(30,70,100,40) --draw the black rectangle
gfx.popContext()

gfx.pushContext(mask)
gfx.setPattern({0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA})
gfx.fillRect(40,0,50,200) --draw a mesh overlay that will be the mask
gfx.popContext()
testimage:setMaskImage(mask)

Result:
playdate-20230818-152325
OK so this seems to totally throw the original drawing on testimage in the garbage? (can see no boundary of switching polarity in the region with the original black rectangle) Also I'm backwards in terms of who to block or let through. So I switch the background on the mask to black.

Result:
playdate-20230818-152519
OK, so I'm clear in that outer region, but what about my original rectangle? I seem to only be drawing directly to the first testimage by adjusting the mask?

Then I realized that maybe the fact that there already is a mask is the issue. Useful fact that wasn't clear to me: images created with no background color have an image mask applied by default to achieve that transparency. The whole 'two bit' reality of images here is something that is very confusing for me.

So - instead of making a new mask, I'll try to draw onto the already set mask, by getting it.

test = gfx.image.new(4000,2400)
print("Does a new image without bg have a mask? ",test:hasMask()) -- returns true
mask = test:getMaskImage()
gfx.pushContext(test)
gfx.fillRect(30,70,100,40)
gfx.popContext()

gfx.pushContext(mask)
gfx.setPattern({0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA})
gfx.fillRect(40,0,50,200)
gfx.popContext()
test:setMaskImage(mask) --(Not actually needed)

Result:
playdate-20230818-153249
OK so this is maybe closer to what I want, but why on earth is anything I'm drawing in the mask layer being actually drawn to screen? Isn't the mask image just a one bit thing, where once assigned to another image, it will tell the underlying image to be rendered or left clear?

Turns out the answer is no. I had to draw in the mask white with the dithering pattern I wanted as the latter 8 arguments in the setPattern field, leaving the first 8 as 0x00. (There may be some way to use the actual dithering patterns to do this, but I'm finding them to just be drawing to the actual image bits as well)

So - I need to get the mask, and draw strictly in the alpha channel of the mask.

Code:

test = gfx.image.new(4000,2400)
mask = test:getMaskImage()
gfx.pushContext(test)
gfx.fillRect(30,70,100,40)
gfx.popContext()

gfx.pushContext(mask)
gfx.setPattern({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA}) --write blanks in the first set, then do the dither pattern in the second set of eight values, the 'alpha' values
gfx.fillRect(40,0,50,200)
gfx.popContext()
test:setMaskImage(mask) --(Not actually needed)

Result - Success

playdate-20230818-155735

One thing that would have saved my efforts here is if setPattern would allow you to 'set the last eight bits' without doing anything with the first - I didn't want to mess with the underlying image data, I just wanted to mess with the transparency. Drawing with gfx.setPattern({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA}) in the original image whites out the underlying image. The 'white bits' in an image set as a mask appear to be just ignored, but the 'black bits' are actually rendered to the screen. It seems like a mask should just be working with the first bit (and be interpreted by the maskee as 'should I render my own data or not'), and not be a whole second two bit thing whose values in the second bit have any impact. Instead, both the alpha and original image value of the 'mask' seem to impact what gets rendered.

I also literally know nothing about typical terminology and usage of masks / image display, so this is all coming from a total layperson's set of expectations. Maybe this will be helpful for someone later.

If you can show how to do this with the stencil, I'd love to see it too. I couldn't really accomplish much with it.

Edit:
Two further notes:

  1. I never needed to re-set the mask to the testimage once I got it; any edits were to the actual mask itself.
  2. later drawing to the base image after having defined that mask seems to get rid of the mask in that area. Turns out that in my case this is convenient, but seems like not the way a mask should behave (?). See below:

test = gfx.image.new(4000,2400)
mask = test:getMaskImage()
gfx.pushContext(test)
gfx.fillRect(30,70,100,40)
gfx.popContext()

gfx.pushContext(mask)
gfx.setPattern({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA})
gfx.fillRect(40,0,50,200)
gfx.popContext()
gfx.setColor(gfx.kColorBlack)
gfx.pushContext(test) -- Now adding some new stuff to the original image
gfx.fillRect(45,100,10,20)
gfx.popContext()

Result: new stuff ignores the previously drawn transparency in the mask.
playdate-20230818-165023

Maybe this is the intended behavior, but doesn't seem right to me, since any work put into pre-preparing the transparency mask is 'lost' (again, fine in my case since I want to be able to draw new, fresh stuff if the user returns to a faded region). It appears fillRect with the color set to black is writing to the alpha channel of the mask also. Though I suppose it must if the default behavior if the mask of a new image is to be all white with all alpha channel switches set to on - drawing in black must also flip the alpha state of the mask otherwise it would be confusing for users. Seems like you would not want to destroy your mask state in certain situations - I suppose users would find workarounds.