Mask dimensions are not multiples of 4 (Dynamic mask code sample)

I’ve been trying to write some code which would create dynamic masks per visible area requirements. The API documentation states that mask image dimensions need to be multiples of four (though this does not apply to the visible area within the mask.) In trying to create dynamic masks I kept running up against an odd issue. The masks, though a multiple of four, would have visual artifacts similar to non-four-multiple masks.

I believe that masks need to be a multiple of 8, not 4, and so here is the dynamic mask code.

API mask documentation: http://docs.coronalabs.com/api/library/graphics/newMask.html#bitmap-mask-image-requirements

You’ll need a content image to apply the masking to; I used this one: http://content.screencast.com/users/HoraceBury/folders/Default/media/b9c654a2-9e6f-48ca-aeaf-50bb0d4d595b/test.png

In this code the content image is displayed and increasing sized masks (starting at 81x81) are generated, with the dimensions printed in the console. To generate the next size mask hit the white circle. The green circle is just for my edification while debugging and simply dumps out the current size and it’s remainder value when divided by 4 and 8, as evidence.

main.lua:
[lua]-- filler background colour
local s = display.newRect( 0, 0, display.contentWidth, display.contentHeight )
s:setFillColor( 0, 0, 255 )

local createMask = nil
local size = 80
local text = display.newText(size, display.contentCenterX+200, 50, native.systemFont, 50)
text:setTextColor(0,0,0)
local button = display.newCircle( 50,50,50 )
function button:tap(e)
size = size + 1
text.text = size
createMask(size,size)
end
button:addEventListener(“tap”,button)

local button2 = display.newCircle( 150,50,50 )
function button2:tap(e)
print('GOOD(4,8): ‘…size…’ ‘…(size%4)…’ '…(size%8))
end
button2:setFillColor(0,255,0)
button2:addEventListener(“tap”,button2)

– create content group
local group = display.newGroup()
local content = display.newImage( group, “test.png” )
content.x,content.y = display.contentCenterX,display.contentCenterY

– create mask
createMask = function()
local maskgroup = display.newGroup()
local invisible = display.newRect(maskgroup,0,0,size,size)
local visible = display.newRect(maskgroup,0,0,size-4,size-4)

invisible.x,invisible.y = 0,0
visible.x,visible.y = 0,0
invisible:setFillColor(0,0,0)
maskgroup.x,maskgroup.y = 200,200

print(‘dims’,maskgroup.width,maskgroup.height,size%4,size%8)
display.save( maskgroup, “newmask.png”, system.TemporaryDirectory )
–display.newImage( “newmask.png”, system.TemporaryDirectory )
maskgroup.alpha = .6 – hide the original image of the mask

local mask = graphics.newMask( “newmask.png”, system.TemporaryDirectory )
group:setMask( mask )
group.maskX,group.maskY=display.contentCenterX,display.contentCenterY
maskgroup:removeSelf()

timer.performWithDelay(500,function()
group:setMask(nil)
end,1)
end[/lua] [import]uid: 8271 topic_id: 28419 reply_id: 328419[/import]

Hi, does the following do what you want?

generators.lua:

[lua]— This module supports generating resources dynamically.


– Permission is hereby granted, free of charge, to any person obtaining
– a copy of this software and associated documentation files (the
– “Software”), to deal in the Software without restriction, including
– without limitation the rights to use, copy, modify, merge, publish,
– distribute, sublicense, and/or sell copies of the Software, and to
– permit persons to whom the Software is furnished to do so, subject to
– the following conditions:

– The above copyright notice and this permission notice shall be
– included in all copies or substantial portions of the Software.

– THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
– EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
– MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
– IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
– CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
– TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
– SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

– [MIT license: http://www.opensource.org/licenses/mit-license.php]

– Standard library imports –
local open = io.open

– Modules –
–local utils = require(“utils”)

– Corona globals –
local display = display
local system = system

– Exports –
local M = {}

– Add 3 pixels to each side, then add (4 - 1) to round up to next multiple of 4 –
local Rounding = 3 * 2 + 3

– Helper to get extra padding and report odd counts
local function Extra (n)
local padding = Rounding - (n + Rounding) % 4
local odd = padding % 2

return (padding - odd) / 2, odd
end

– Reads a 4-byte hex out of a file as an integer
– TODO: This must be feasible in a more clean way…
local function HexToNum (file)
local sum, mul, str = 0, 2^24, file:read(4)

for char in str:gmatch(".") do
local num = char:byte()

if num ~= 0 then
sum = sum + mul * num
end

mul = mul / 256
end

return sum
end

— Generates a rectangular mask, for use with graphics.setMask.
@uint w Mask width…
@uint h …and height.
@param name File name to assign to generated mask; if absent, one will be auto-generated.
@param base_dir Directory where mask is stored; if absent, system.TemporaryDirectory.
@treturn string Mask file name.
@treturn number xscale Scale to apply to mask to fit _w_…
@treturn number yscale …and to fit _h_.
function M.NewMask (w, h, name, base_dir)
local group = display.newGroup()
local xpad, ew = Extra(w)
local ypad, eh = Extra(h)

display.newRect(group, xpad, ypad, w + ew, h + eh)

local tedge = display.newRect(group, 0, 0, w + xpad * 2, ypad)
local ledge = display.newRect(group, 0, ypad, xpad, h)
local redge = display.newRect(group, w + xpad + ew, ypad, xpad, h)
local bedge = display.newRect(group, 0, h + ypad + eh, w + xpad * 2, ypad)

tedge:setFillColor(0)
ledge:setFillColor(0)
redge:setFillColor(0)
bedge:setFillColor(0)

base_dir = base_dir or system.TemporaryDirectory
– name = name or utils.NewName() … “.png”

display.save(group, name, base_dir)

group:removeSelf()

local xscale, yscale

if system.getInfo(“platformName”) == “Win” then
xscale, yscale = 2, 1.95 – If reading the PNG fails, punt…

local png = open(system.pathForFile(name, base_dir), “rb”)

if png then
png:read(12)

if png:read(4) == “IHDR” then
xscale = w / HexToNum(png)
yscale = h / HexToNum(png)
end

png:close()
end
end

return name, xscale or 1, yscale or 1
end

– Export the module.
return M[/lua]

and then in createMask():

[lua] local name, sx, sy = require(“generators”).NewMask(size, size, “newmask.png”)

local mask = graphics.newMask( “newmask.png”, system.TemporaryDirectory )

group:setMask( mask )

group.maskX,group.maskY=display.contentCenterX,display.contentCenterY
group.maskScaleX, group.maskScaleY = sx, sy[/lua]

The square seemed to not jump around, then, but I wasn’t quite able to diagnose if that matched your problem. :slight_smile:

If it does help, the other considerations were:

* Multiple of 4, but ALSO adding at least 3 to each side (thus often rounding up to multiples of 8).

* On the simulator (I only have Windows to test on right now), correcting for the scale.

I’m not sure why I didn’t think to just overlay two rects. Oh well… [import]uid: 27791 topic_id: 28419 reply_id: 114817[/import]

I think your code does the same as I posted here, if I’m reading it correctly…

https://developer.coronalabs.com/code/dynamic-mask-creation

I wanted to be sure that the mask image dimensions would not cause strange things to happen and I think I got there, however it works out the rounding up to 8.

Maybe you can tell me if I’m missing something? [import]uid: 8271 topic_id: 28419 reply_id: 114887[/import]

Hi, sorry for the late reply (Internet out at home…).

It could be that you’re right about rounding up to 8, though I didn’t run into that myself.* The only key insight I might suggest is that you didn’t add 6 first (3 black pixels per side)… after that, rounding up to a multiple of 4 will often amount to rounding up to a multiple of 8. But then, if you were close to one of those multiples of 8 already, it doesn’t make sense.

Oh well, I don’t know. :slight_smile:

* - Then again, I’ve hardly run much of a stress test… mine started as a proof of concept and otherwise is just thrown on a few custom widgets here and there. [import]uid: 27791 topic_id: 28419 reply_id: 115074[/import]

I’m trying to make this work and apparently display.save() only saves JPEGs (at least on iOS)

Can JPEG’s be used for masks? [import]uid: 19626 topic_id: 28419 reply_id: 115563[/import]

Yes. My code relies on that. [import]uid: 8271 topic_id: 28419 reply_id: 116346[/import]