Soft shadow

I have a really lovely shadow behind an interface element, generated using just a circle and a radial fill:

2019-02-05_1300.png

local shadow = display.newCircle( 100, 100, 50 ) shadow.alpha = .25 shadow.fill.effect = "generator.radialGradient" shadow.fill.effect.color1 = { .5,.5,.5, 1 } shadow.fill.effect.color2 = { 0,0,0, 0 } shadow.fill.effect.center\_and\_radiuses = { 0.5, 0.5, 0.1, .5 } shadow.fill.effect.aspectRatio = 1 local circle = display.newCircle( 100, 100, 25 )

I’m trying to get the same effect for other shapes, but having no luck.

I figured putting a black rectangle in front of a large white rectangle, grabbing it with a capture and applying a blur would do it, but (at least on Windows) this is doing nothing at all.

local g = display.newGroup() g.x, g.y = 400, 400 local back = display.newRoundedRect( g, 0, 0, 450, 450, 50 ) local front = display.newRoundedRect( g, 0, 0, 400, 400, 50 ) front.fill = {0,0,0} timer.performWithDelay( 300, function() local combined = display.newSnapshot( 500, 500 ) combined.group:insert( g ) combined.x, combined.y = 400, 400 combined:invalidate() combined.fill.effect = "filter.blurGaussian" combined.fill.effect.horizontal.blurSize = 20 combined.fill.effect.horizontal.sigma = 140 combined.fill.effect.vertical.blurSize = 20 combined.fill.effect.vertical.sigma = 140 end )

Has anyone had any luck with this sort of effect?

I figured putting a black rectangle in front of a large white rectangle, grabbing it with a capture and applying a blur would do it

conceptually yes, and nearly there, but the foreground object doesn’t belong in the blurred snapshot, fe (somewhat unconventional indentation used to help illustrate create vs modify portions):

-- for convenience: local ACW, ACH = display.actualContentWidth, display.actualContentHeight local CCX, CCY = display.contentCenterX, display.contentCenterY local RECTWH, RECTR, BLURR = ACW/2, ACW/10, ACW/10 -- a background to "notice" the shadow against: local bkgnd = display.newRect(CCX,CCY,ACW,ACH) bkgnd.fill.effect = "generator.linearGradient" bkgnd.fill.effect.color1 = { 0.1,0.2,0.3,1 } bkgnd.fill.effect.color2 = { 0.6,0.8,1.0,1 } -- a black rect to use as basis for shadow blur local shade = display.newRoundedRect(0,0,RECTWH,RECTWH,RECTR) shade:setFillColor(0,0,0) -- a snapshot large enough to hold the black rect and its blur local shadow = display.newSnapshot(RECTWH+BLURR\*2,RECTWH+BLURR\*2) shadow.group:insert(shade) shadow.fill.effect = "filter.blurGaussian" shadow.fill.effect.horizontal.blurSize = BLURR shadow.fill.effect.vertical.blurSize = BLURR -- a foreground rect to cover up shadow's basis local front = display.newRoundedRect(0,0,RECTWH,RECTWH,RECTR) front:setFillColor(1,0.9,0.8) -- the whole set of things needed to accomplish the overall effect: local combined = display.newGroup() combined:insert(shadow) combined:insert(front) combined:translate(CCX,CCY)

So you’re putting the black rectangle in the snapshot and blurring that. Nice.

My approach was assuming that the black portion needed an area to blur into, hence the white rectangle.

I’ve done this before but can’t find the code and thought perhaps Windows didn’t like the Gfx2.0 mechanism. (Don’t have my usual mac here.)

I’ve not tried your code yet, but this is what I’ve just worked out after reviewing a piece of my old code:

local g = display.newGroup() g.x, g.y = display.actualCenterX, display.actualCenterY display.newRect( g, 0, 0, 600, 600 ) display.newRoundedRect( g, 0, 0, 300, 300, 50 ).fill = {1,0,0} local function blur( group, removeOriginal ) local capture = display.capture( group, { saveToPhotoLibrary=false, captureOffscreenArea=false } ) capture.x, capture.y = group.x, group.y capture.fill.effect = "filter.blurGaussian" capture.fill.effect.horizontal.blurSize = 512 capture.fill.effect.horizontal.sigma = 512 capture.fill.effect.vertical.blurSize = 512 capture.fill.effect.vertical.sigma = 512 if (removeOriginal) then group:removeSelf() end return capture end local function greyed( group, removeOriginal ) local capture = display.capture( group, { saveToPhotoLibrary=false, captureOffscreenArea=false } ) capture.x, capture.y = group.x, group.y capture.fill.effect = "filter.grayscale" if (removeOriginal) then group:removeSelf() end return capture end timer.performWithDelay( 500, function() local blurred = blur(g, true) blurred = blur(blurred, true) blurred = blur(blurred, true) blurred = blur(blurred, true) local greyed = greyed(blurred, true) greyed.alpha = .3 end, 1 )

OK, bit of an improvement here. Some points of note:

  1. a background is required to avoid distortions in the captures (as well as rendering the blur)

  2. a timer delay does not appear to be required (but is wise regardless)

This code requires a display group to apply the shadow to because it will generate the shadow and insert it at the bottom of the group.

local lib = {} --[[Shadow constructor. Takes a display group and optional colour match to the background. Renders a shadow Params: group: Requires that the object the shadow is generated for is a display group. backgroundColour: The colour that the shadow should be rendered to. spread: Amount the blur is rendered by to increase its spread. alpha: The alpha value of the blur. Default is 1. notgrey: True if the shadow should be coloured like the group. Default is false. Returns: The shadow which is inserted into the group is returned, but will have index 1 anyway.]] function lib.new( group, backgroundColour, spread, alpha, notgrey ) local backed = lib.back(group, backgroundColour, spread) local blurred = lib.blur(backed, true, spread or 0) if (notgrey == nil or notgrey) then blurred = lib.greyed(blurred, true) end blurred.alpha = alpha or 1 local x, y = lib.shadowLocation( group ) group:insert( 1, blurred ) blurred.x, blurred.y = x, y return blurred end --[[SUPPORTING FUNCTIONS]] function lib.cap( group ) local capture = display.capture( group, { saveToPhotoLibrary=false, captureOffscreenArea=true } ) capture.x, capture.y = group.x, group.y return capture end function lib.shadowLocation( group ) local x, y = (group.contentBounds.xMin+group.contentBounds.xMax)/2, (group.contentBounds.yMin+group.contentBounds.yMax)/2 return group:contentToLocal( x, y ) end function lib.back( group, colour, spread ) local x, y = lib.shadowLocation( group ) local rect = display.newRect( group, x, y, group.width+(150\*(spread or 1)), group.height+(150\*(spread or 1)) ) rect:toBack() rect.fill = colour or {1,1,1} local c = lib.cap( group ) rect:removeSelf() return c end function lib.blur( group, removeOriginal, count ) local capture = lib.cap( group ) capture.fill.effect = "filter.blurGaussian" capture.fill.effect.horizontal.blurSize = 512 capture.fill.effect.horizontal.sigma = 512 capture.fill.effect.vertical.blurSize = 512 capture.fill.effect.vertical.sigma = 512 if (count \> 0) then capture = lib.blur( capture, true, count-1 ) end if (removeOriginal) then group:removeSelf() end return capture end function lib.greyed( group, removeOriginal ) local capture = lib.cap( group ) capture.fill.effect = "filter.grayscale" if (removeOriginal) then group:removeSelf() end return capture end --[[DEMO]] local a = display.newGroup() a.x, a.y = display.actualCenterX, display.actualContentHeight\*.25 display.newRoundedRect( a, 0, 0, 300, 300, 50 ).fill = {1,0,0} local b = display.newGroup() b.x, b.y = display.actualCenterX-150, display.actualContentHeight\*.5-150 display.newRoundedRect( b, 150, 150, 300, 300, 50 ).fill = {0,0,1} local d = display.newGroup() d.x, d.y = display.actualCenterX+150, display.actualContentHeight\*.75 display.newRoundedRect( d, -150, 0, 300, 300, 50 ).fill = {0,1,0} local c = {.96} lib.new( a, c, 2 ) lib.new( b, c, nil, nil, false ) lib.new( d, c, nil, 1 ) return lib

I figured putting a black rectangle in front of a large white rectangle, grabbing it with a capture and applying a blur would do it

conceptually yes, and nearly there, but the foreground object doesn’t belong in the blurred snapshot, fe (somewhat unconventional indentation used to help illustrate create vs modify portions):

-- for convenience: local ACW, ACH = display.actualContentWidth, display.actualContentHeight local CCX, CCY = display.contentCenterX, display.contentCenterY local RECTWH, RECTR, BLURR = ACW/2, ACW/10, ACW/10 -- a background to "notice" the shadow against: local bkgnd = display.newRect(CCX,CCY,ACW,ACH) bkgnd.fill.effect = "generator.linearGradient" bkgnd.fill.effect.color1 = { 0.1,0.2,0.3,1 } bkgnd.fill.effect.color2 = { 0.6,0.8,1.0,1 } -- a black rect to use as basis for shadow blur local shade = display.newRoundedRect(0,0,RECTWH,RECTWH,RECTR) shade:setFillColor(0,0,0) -- a snapshot large enough to hold the black rect and its blur local shadow = display.newSnapshot(RECTWH+BLURR\*2,RECTWH+BLURR\*2) shadow.group:insert(shade) shadow.fill.effect = "filter.blurGaussian" shadow.fill.effect.horizontal.blurSize = BLURR shadow.fill.effect.vertical.blurSize = BLURR -- a foreground rect to cover up shadow's basis local front = display.newRoundedRect(0,0,RECTWH,RECTWH,RECTR) front:setFillColor(1,0.9,0.8) -- the whole set of things needed to accomplish the overall effect: local combined = display.newGroup() combined:insert(shadow) combined:insert(front) combined:translate(CCX,CCY)

So you’re putting the black rectangle in the snapshot and blurring that. Nice.

My approach was assuming that the black portion needed an area to blur into, hence the white rectangle.

I’ve done this before but can’t find the code and thought perhaps Windows didn’t like the Gfx2.0 mechanism. (Don’t have my usual mac here.)

I’ve not tried your code yet, but this is what I’ve just worked out after reviewing a piece of my old code:

local g = display.newGroup() g.x, g.y = display.actualCenterX, display.actualCenterY display.newRect( g, 0, 0, 600, 600 ) display.newRoundedRect( g, 0, 0, 300, 300, 50 ).fill = {1,0,0} local function blur( group, removeOriginal ) local capture = display.capture( group, { saveToPhotoLibrary=false, captureOffscreenArea=false } ) capture.x, capture.y = group.x, group.y capture.fill.effect = "filter.blurGaussian" capture.fill.effect.horizontal.blurSize = 512 capture.fill.effect.horizontal.sigma = 512 capture.fill.effect.vertical.blurSize = 512 capture.fill.effect.vertical.sigma = 512 if (removeOriginal) then group:removeSelf() end return capture end local function greyed( group, removeOriginal ) local capture = display.capture( group, { saveToPhotoLibrary=false, captureOffscreenArea=false } ) capture.x, capture.y = group.x, group.y capture.fill.effect = "filter.grayscale" if (removeOriginal) then group:removeSelf() end return capture end timer.performWithDelay( 500, function() local blurred = blur(g, true) blurred = blur(blurred, true) blurred = blur(blurred, true) blurred = blur(blurred, true) local greyed = greyed(blurred, true) greyed.alpha = .3 end, 1 )

OK, bit of an improvement here. Some points of note:

  1. a background is required to avoid distortions in the captures (as well as rendering the blur)

  2. a timer delay does not appear to be required (but is wise regardless)

This code requires a display group to apply the shadow to because it will generate the shadow and insert it at the bottom of the group.

local lib = {} --[[Shadow constructor. Takes a display group and optional colour match to the background. Renders a shadow Params: group: Requires that the object the shadow is generated for is a display group. backgroundColour: The colour that the shadow should be rendered to. spread: Amount the blur is rendered by to increase its spread. alpha: The alpha value of the blur. Default is 1. notgrey: True if the shadow should be coloured like the group. Default is false. Returns: The shadow which is inserted into the group is returned, but will have index 1 anyway.]] function lib.new( group, backgroundColour, spread, alpha, notgrey ) local backed = lib.back(group, backgroundColour, spread) local blurred = lib.blur(backed, true, spread or 0) if (notgrey == nil or notgrey) then blurred = lib.greyed(blurred, true) end blurred.alpha = alpha or 1 local x, y = lib.shadowLocation( group ) group:insert( 1, blurred ) blurred.x, blurred.y = x, y return blurred end --[[SUPPORTING FUNCTIONS]] function lib.cap( group ) local capture = display.capture( group, { saveToPhotoLibrary=false, captureOffscreenArea=true } ) capture.x, capture.y = group.x, group.y return capture end function lib.shadowLocation( group ) local x, y = (group.contentBounds.xMin+group.contentBounds.xMax)/2, (group.contentBounds.yMin+group.contentBounds.yMax)/2 return group:contentToLocal( x, y ) end function lib.back( group, colour, spread ) local x, y = lib.shadowLocation( group ) local rect = display.newRect( group, x, y, group.width+(150\*(spread or 1)), group.height+(150\*(spread or 1)) ) rect:toBack() rect.fill = colour or {1,1,1} local c = lib.cap( group ) rect:removeSelf() return c end function lib.blur( group, removeOriginal, count ) local capture = lib.cap( group ) capture.fill.effect = "filter.blurGaussian" capture.fill.effect.horizontal.blurSize = 512 capture.fill.effect.horizontal.sigma = 512 capture.fill.effect.vertical.blurSize = 512 capture.fill.effect.vertical.sigma = 512 if (count \> 0) then capture = lib.blur( capture, true, count-1 ) end if (removeOriginal) then group:removeSelf() end return capture end function lib.greyed( group, removeOriginal ) local capture = lib.cap( group ) capture.fill.effect = "filter.grayscale" if (removeOriginal) then group:removeSelf() end return capture end --[[DEMO]] local a = display.newGroup() a.x, a.y = display.actualCenterX, display.actualContentHeight\*.25 display.newRoundedRect( a, 0, 0, 300, 300, 50 ).fill = {1,0,0} local b = display.newGroup() b.x, b.y = display.actualCenterX-150, display.actualContentHeight\*.5-150 display.newRoundedRect( b, 150, 150, 300, 300, 50 ).fill = {0,0,1} local d = display.newGroup() d.x, d.y = display.actualCenterX+150, display.actualContentHeight\*.75 display.newRoundedRect( d, -150, 0, 300, 300, 50 ).fill = {0,1,0} local c = {.96} lib.new( a, c, 2 ) lib.new( b, c, nil, nil, false ) lib.new( d, c, nil, 1 ) return lib