setMask in HTML?

Sorry, seems I might be flooding the forums today!

Going back to the drawing board, we were right the first time - it’s the mask creation that isn’t working properly with the HTML5 build.

More precisely, I’m creating  mask files on the fly by drawing sprites to a display group, applying filters to turn them monochrome, and then saving that group out to a temporary file with display.save.

The filtering is as follows:

maskCutout.fill.effect = "filter.brightness" maskCutout.fill.effect.intensity = 10

(maskCutout being the sprite that’s drawn as a white area)

For the record, Corona docs state that intensity is a 0 to 1 value but previous testing has shown 10 creates a pure white in both the simulator and Android.

And the saving part is as follows:

display.save(maskGroup, { filename=maskFile, baseDir = system.TemporaryDirectory, isFullResolution = true, captureOffscreenArea = true })

Later on I’m loading this in as a mark:

foobar = graphics.newMask(maskFile, system.TemporaryDirectory) anothersprite:setMask(foobar)

Now to the point - with the HTML5 build, either these masks aren’t being created, or they’re being created wrongly. I’m not sure as I’ve been unable to find them! If I do the following I’m only seeing relative path names and can’t for the life of me find where they actually sit:

print(system.pathForFile(maskFile, system.ApplicationSupportDirectory)) print(system.pathForFile(maskFile, system.CachesDirectory)) print(system.pathForFile(maskFile, system.DocumentsDirectory)) print(system.pathForFile(maskFile, system.ResourceDirectory)) print(system.pathForFile(maskFile, system.TemporaryDirectory))

Whatever is happening, index-debug.html just spits out “Unsupported image format” errors at the setMask line.

Thanks for the detailed bug report. We will fix it soon.

Are you 100% sure your saved mask files meet the requirements for masks?  This is very hard to do correctly and is often torpedoed by some scaling side-effect you didn’t notice.

Since you can’t inspect the files directly, it is hard to be sure.

The easiest way to check your masks is to actually draw one with newImage() and then see what the dimensions are.  

As far as I’m aware, yes. I recall spending hours (and hours… and hours… and probably hours) getting them to save out the right size, with the bordering, etc while content scaling tried to get in the way. But as you say, it’s a tad difficult to be sure without being able to see the resulting files.

I’ll experiment over the weekend with actually outputting the result to see if I can find any clues. The difficulty originally though was getting the files to save out at the same dimension as the render so if this is a re-occurrence of that, I’m only going to be able to tell if I can find the files.

…I’m curious. Does this mean you’ve found an issue to resolve? =)

If I load the resulting image in to display.newImage after saving it out, the result looks good. It’s black and white so the filtering is working and it looks to be the expected size. This also confirms the actual save is working, despite my still not being able to find the physical files.

This actually might be a bug with setMask then?

How soon after saving it are you trying to use it?

You may need to wait a few frames.

@richard11 Can you please provide a simple testcase ?  It will help to find out and fix the bug. Thanks.

Sorry for the silence - things have gotten crazily busy here!

I’ll extract the code out into a basic test for you over the weekend @vitaly1

@roaminggamer, it runs a while loop to await file creation before attempting to use it, but perhaps this is the problem? This has worked a treat in everything we’ve tested on but perhaps Javascript detects that the file exists before it’s completely written? Or perhaps because this is Windows and everything else we’ve tested on has been Android… I wonder if Windows creates the file before writing where Android creates it to memory and then writes to disk last…?

display.save(maskGroup, { filename=maskFile, baseDir = system.TemporaryDirectory, isFullResolution = true, captureOffscreenArea = true }) -- Wait until the file has been created before trying to load it while(convenience.isFile(maskFile, system.TemporaryDirectory) == false) do end vars.layerMasks[index] = graphics.newMask(maskFile, system.TemporaryDirectory)

Ha - totally forgot that convenience is one of my libraries, sorry. The below is the convenience.isFile() function that the above uses…

module.isFile = function(filename, baseDir) local fileHandle = io.open(system.pathForFile(filename, baseDir), "r") if(fileHandle) then return true else return false end end

You’re reading the file while trying to write it?  I would use a timer to wait for a frame or two.

@richard11 Looks like you aren’t closing the file when it exists.

Now there’s a rookie mistake if ever I saw one. Good spot, thanks. Fixed now! Though this doesn’t seem to have affected anything.

You’re reading the file while trying to write it?  I would use a timer to wait for a frame or two.

Just to test, I tried the following but still no luck. In the simulator there’s now some serious glitching as it tries to create the files (unsurprisingly) but in Javascript I’m seeing no difference at all, which is odd:

while(convenience.isFile(maskFile, system.TemporaryDirectory) == false) do end local function listener(event) vars.layerMasks[index] = graphics.newMask(maskFile, system.TemporaryDirectory) end timer.performWithDelay(3000, listener)

@richard11 can you try DocumentsDirectory instead of TemporaryDirectory, just as test for now.

Same result I’m afraid, even leaving in the 3 second delay before trying to use the image.:

Couldnot load /documentsDir/mask_68.png: Unsupported image format

In case this is a clue, Firefox also gives the following error during load but then runs the same as in Chrome:

Error: Failed to mount IDBFS

 InvalidStateError: A mutation operation was attempted on a database that did not allow mutations.

Hmm…

If I take one of the mask images created while running in the simulator and load that in as a mask file, skipping the whole generation process, it works.

If I then try to load a file that doesn’t actually exist as a mask, nothing happens. No masking obviously, but also no error which suggests that the above “Unsupported image format” error is actually legitimate. There’s something wrong with the created mask images rather than that the temp directory can’t be written to or something.

Seems it’s display.save that’s corrupting png files, but without being able to actually see the resulting files I can’t be completely sure. Interestingly, if I switch to saving a jpg, the browser crashes!

OK, I’ve been fiddling with this all day and getting nowhere. The masks are definitely being rendered properly to a display group but something is going wrong when saving that group to file, or when trying to load the resulting file with graphics.newMask().

Here’s my function, inclusive of a few debug prints:

module.createMask = function(index, sheet, frame) if(vars.layerMasks[index] == nil) then local lceil = math.ceil local maskFile = "mask" .. index .. ".png" local maskCutout = display.newImage(sheet, frame) -- Masks have to be divisible by 4, and have a 3px border... oddly. local maskWidth = lceil((maskCutout.width + 6) / 4) \* 4 local maskHeight = lceil((maskCutout.height + 6) / 4) \* 4 local maskGroup = display.newGroup() -- Tile sized blackout. local maskArea = display.newRect(maskGroup, maskWidth / 2, maskHeight / 2, maskWidth, maskHeight) maskArea:setFillColor(0) maskGroup:insert(maskCutout) maskCutout.x = maskWidth / 2 maskCutout.y = maskHeight / 2 -- Turn to white maskCutout.fill.effect = "filter.brightness" maskCutout.fill.effect.intensity = 10 -- documentation states 1 is the max, but... if(display.contentScaleX ~= 1 or display.contentScaleY ~= 1) then -- Compensate for content scaling local scaledW = 1 / (maskWidth / (maskGroup.contentWidth \* display.contentScaleX)) local scaledH = 1 / (maskHeight / (maskGroup.contentHeight \* display.contentScaleY)) print("original = " .. maskGroup.contentWidth .. " x " .. maskGroup.contentHeight) print("contentScale = " .. display.contentScaleX) maskGroup:scale(scaledW, scaledH) print("new = " .. maskGroup.contentWidth .. " x " .. maskGroup.contentHeight) print(maskGroup.contentWidth .. " / " .. display.contentScaleX .. " = " .. (maskGroup.contentWidth / display.contentScaleX)) print(maskGroup.contentHeight .. " / " .. display.contentScaleY .. " = " .. (maskGroup.contentHeight / display.contentScaleY)) end -- Save this as an image to then load in as a mask display.save(maskGroup, { filename=maskFile, baseDir = system.TemporaryDirectory, captureOffscreenArea = true, backgroundColor = { 0 } }) -- Wait until the file has been created before trying to load it while(convenience.isFile(maskFile, system.TemporaryDirectory) == false) do end vars.layerMasks[index] = graphics.newMask(maskFile, system.TemporaryDirectory) maskGroup:removeSelf() end return true end

The basic concept is to create a black canvas that’s divisible by 4 and at least 6px bigger than the content, to create the necessary 3px border. Then render the content (a sprite frame) onto that canvas, apply some filters to turn it white, and finally save out to an image file. Obviously content scaling then gets in the way so the group is scaled accordingly to compensate, forcing the resulting file to be the desired dimensions rather than the pre-save render being those dimensions.

Tested on various devices, in various resolutions, with flawless results every time. With the HTML5 build I can’t see the resulting images - they don’t appear to ever actually exist on my system, or if they do they’re given obfuscated file names, so I can’t verify that the resulting files are as expected but I can paste the results of those print() lines:

original = 264 x 200

contentScale = 0.78850102424622

new = 208 x 157

208 / 0.78850102424622 = 263.79166748558

157 / 0.78850102424622 = 199.11197978479

I’m wondering if this is resulting in a 264x199 image instead of a 264x200 image since 199 isn’t divisible by 4. But again I can’t see the resulting files. Or could it be that display.save() is actually dumping the literally rendered-to-screen output which, thanks to the resuling app being stretched to full browser viewport size, isn’t necessarily the same dimensions as maskGroup.contentWidth and maskGroup.contentHeight give?

I’m pretty stumped now. Definitely not going to get any further without being able to see the resulting files  :unsure:

For the record, removing the maskGroup:removeSelf() line confirms that the applied filters etc are working.

@richard11 can you please provide a full simple test project (main.lua + build.settings + config.lua) which demonstrates the issue ? 

Everytime I see this, it’s either:

  • I’m running on private mode

or

  • I’m running from local folder, without the use of a server.

Maybe it’s the same for you?