Well, this is a bit quick and dirty, but is sort of along the ideas of the stencil buffer mentioned above:
local kernel = { name = "outline", category = "filter" } kernel.vertexData = { { index = 0, name = "extra", default = 1.725, min = 0 }, { index = 1, name = "cutoff", default = 0, min = 0 } } kernel.vertex = [[// varying P\_UV vec2 v\_our\_uv; // see note on v\_TexCoord P\_POSITION vec2 VertexKernel (P\_POSITION vec2 pos) { P\_UV vec2 offset = sign(CoronaTexCoord - .5); pos += offset \* CoronaVertexUserData.xx; v\_TexCoord += offset \* CoronaTexelSize.xy; // undocumented varying! (more proper to just use another of our own) return pos; }]] kernel.fragment = [[// varying P\_UV vec2 v\_our\_uv; // see note above on v\_TexCoord (if nervous, use this instead of uv below) P\_COLOR float Sample (P\_UV vec2 uv) { P\_COLOR float alpha = texture2D(CoronaSampler0, uv).a; return step(CoronaVertexUserData.y, alpha); } P\_COLOR vec4 FragmentKernel (P\_UV vec2 uv) { P\_COLOR float sum = 0.; sum += Sample(uv - vec2(CoronaTexelSize.x \* CoronaVertexUserData.x, 0.)); sum += Sample(uv + vec2(CoronaTexelSize.x \* CoronaVertexUserData.x, 0.)); sum += Sample(uv - vec2(0., CoronaTexelSize.y \* CoronaVertexUserData.x)); sum += Sample(uv + vec2(0., CoronaTexelSize.y \* CoronaVertexUserData.x)); sum \*= step(sum, 3.); // can vary from 1 to 3 (0 = nothing, 4 = part of original) return CoronaColorScale(vec4(min(sum, 1.))); // saturate to rgba = 1 }]] graphics.defineEffect(kernel) local File = "game/dot/gfx/Toon-Up.png" local Mode = 0 if Mode == 0 then local aa = display.newImage(File, display.contentCenterX, display.contentCenterY) local rr = display.newRect(aa.x,aa.y,aa.width,aa.height) rr.fill = { type = "image", filename = File } rr.fill.effect = "filter.custom.outline" rr.fill.effect.cutoff = .5 elseif Mode == 1 then -- poor man's sprite (nothing really handy) local frames, w, h = {}, 0, 0 for \_, name in ipairs{ "game/dot/gfx/Toon-Down.png", "game/dot/gfx/Toon-Half.png", File } do local temp = display.newImage(name) frames[#frames + 1] = { name = name, w = temp.width, h = temp.height } w, h = math.max(w, temp.width), math.max(h, temp.height) temp:removeSelf() end local canvas = graphics.newTexture{ type = "canvas", width = w, height = h, pixelWidth = math.min(w, display.contentWidth), pixelHeight = math.min(h, display.contentHeight) } local frame = 0 local aa = display.newRect(display.contentCenterX, display.contentCenterY, 1, 1) local ii = display.newRect(0, 0, 1, 1) local rr = display.newRect(display.contentCenterX, display.contentCenterY, 1, 1) rr.fill = { type = "image", filename = canvas.filename, baseDir = canvas.baseDir } rr.fill.effect = "filter.custom.outline" rr.fill.effect.cutoff = .25 canvas:draw(ii) local function UpdateFrame () frame = frame + 1 if frame \> #frames then frame = 1 end local info = frames[frame] local ifill = { type = "image", filename = info.name } aa.width, aa.height, aa.fill = info.w, info.h, ifill ii.xScale, ii.yScale, ii.width, ii.height, ii.fill = w / info.w, h / info.h, info.w, info.h, ifill rr.width, rr.height = info.w, info.h end timer.performWithDelay(250, function() UpdateFrame() canvas:draw(ii) canvas:invalidate("cache") end, 0) end
Mode = 0 for regular image, Mode = 1 for a “sprite” (just a canvas, which was slightly more general).
Test graphics attached.