[Stencil Buffer] setStencilImage throws wrong error / has inconsistent documentation

STR:

Load a 32x32 transparent image like this:
stencil

And set using setStencilImage

--CoreLibs

import 'CoreLibs/graphics'

import 'CoreLibs/object'

gfx = playdate.graphics

playdate.display.setRefreshRate(30)

stencil = gfx.image.new('assets/images/stencil')

---@diagnostic disable-next-line: duplicate-set-field

function playdate.update()

print(stencil:getSize())

gfx.setStencilImage(stencil)

end

Expected:

The stencil would be set.

What actually happened:

Error thrown:

image

Additional information:

It seems that it happens because the stencil has transparency in the PNG. It doesnt need to be fully transparent, just have any transparent pixel.

It either should throw a correct error or allow transparency. Also this should be specified in the documentation

Also, in this documentation, it says that only if 'tile' is set it needs to be multiple of 32x32, but its being required whether its set or not


debugChicken Bug Report Table:

Number: 1
Severity: Medium
Cringeness: High

1 Like

This does seem like a bug. Thanks for letting us know about the problem, we will look into it!

1 Like

Ah, dumb typo. I have it checking against the unpadded size instead of the full width. :man_facepalming: Putting a fix in for that now. Thanks for catching it!

1 Like

So, after fixing, will we be able to use the stencil as an easier way to add a mask to an image? I’ve come across that bug every time I tried to use the stencil functions and thought I was just doing it wrong

Stencils and masks do the same thing--control whether or not image pixels are drawn--but masks are aligned with the image and stencils are aligned with the screen.

We have the fix targeted for 2.0.2, should be out this week if all goes well.

1 Like

@Gamma @dave I just wanna fix the misinformation that stencil as masks are not the same thing, at least not in playdate. Their behaviour varies a lot

although playdate SDK images support image masking with the method, there seems to be a problem -- it will override the resulting masked-image with black even if the souce of the image is transparent

I mean, if they were supposed to be the same then there is yet another bug, the behaviour described above

Okay, I see the confusion. Playdate images implement transparency via a 1-bit mask data plane separate from the 1-bit bitmap data. When you call image:setMaskImage() you're overwriting the existing mask data. If you want to add to it instead, here's another way:

function addMaskToImage(image, mask)
	gfx.pushContext(image:getMaskImage())
	gfx.setImageDrawMode(gfx.kDrawModeFillBlack)
	mask:draw(0, 0)
	gfx.popContext()
end
2 Likes

Oh i finally understand the issue. So it seems the mask is already being used even if you do not set any mask. I thought for sure that the data was 2-bit to support transparency in itself.

That is essentially why stencil is different. Its yet a separate buffer so it works as more expected from a mask.

I dont think that your code would achieve what I want, in the example in my repository it does not render anything
image

So XORing like I did is the actual way to go if we still wanna be keeping only two 1-bit matrixes.

I really think that there should be either an extra matrix just for actual masking that is not just replacing the original transparency from the original image.

Or if you guys dont wanna take the extra matrix overhead, add an option to setMaskImage to XOR with 1-bit data source, which in my opinion should be the default behaviour

Derp. That's what I get for posting code without testing.. I wanted kDrawModeWhiteTransparent there instead of kDrawModeFillBlack. :sweat_smile:

3 Likes

awesome, i dont know why but this code works indeed, thanks!

Thanks for this snippet, it GREATLY simplified some of my mask drawing in my current project!

1 Like

K I'm gonna have to say that after some tests, kDrawModeWhiteTransparent still won't work if the mask itself uses transparency instead of black.

so this is still the only code that i know that still works on all cases

function setAnActualMaskToImage(image, mask)
	local maskCopy = mask:copy()

	gfx.pushContext(maskCopy)
	gfx.setImageDrawMode(gfx.kDrawModeFillBlack)
	image:draw(0, 0)
	gfx.setImageDrawMode(gfx.kDrawModeXOR)
	mask:draw(0, 0)
	gfx.popContext()

	image:setMaskImage(maskCopy)
end
1 Like