Sharp: Adding a shadow

Created on 4 Dec 2018  路  4Comments  路  Source: lovell/sharp

Is it even possible to add a shadow to an image?

I have been trying to add a shadow to an image but I doesn't seem possible.

I couldn't get the simple shadow running because when you create a shadow with SVG, for example:

<svg width="200" height="200">
  <defs>
    <filter id="shadow">
      <feDropShadow dx="4" dy="8" stdDeviation="4"/>
    </filter>
  </defs>

  <circle cx="50%" cy="50%" r="80" style="filter:url(#shadow);"/>
</svg>

and overlay that with overlayWith(Buffer.from(<ABOVE SVG>), { cutout: true }) it will simply ignore the filter (shadow) and cut it out like i wasn't there.

The idea I have as a workaround is make an SVG that is the same shape and size, position it behind the image and add a blur to the SVG.

<svg width="200" height="200">
  <defs>
    <filter id="f1" x="0" y="0">
      <feGaussianBlur in="SourceGraphic" stdDeviation="15" />
    </filter>
  </defs>
  <rect width="90" height="90" fill="yellow" filter="url(#f1)" />
</svg>

Here is an HTML example of how that would look (JSFiddle):

<svg width="200" height="200">

  <filter id="blurMe">
    <feGaussianBlur in="SourceGraphic" stdDeviation="5" />
  </filter>


  <circle cx="60" cy="60" r="50" fill="black" filter="url(#blurMe)" id="shadow" />
  <circle cx="60"  cy="60" r="52" fill="dodgerblue" id="image" />
</svg>

The problem with this idea is I don't know how to stack layers in Sharp.

Thank you in advance.

question

Most helpful comment

Came across this issue while trying to do something similar, feDropShadow is still unsupported but I managed to achieve what I wanted to do by blurring a rectangle (an svg the same size as the input image, with a margin) to act as the shadow and then using composites. Hope this helps anyone looking for the same thing!

const OUTPUT_BACKGROUND = '#bada55'
const OUTPUT_WIDTH = 1200
const OUTPUT_HEIGHT = 630
const SHADOW_MARGIN = 40
const SHADOW_BLUR = 15
const SHADOW_OFFSET = 6
const SHADOW_OPACITY = 0.3

const stream = await sharp(inputPath)
const { width, height } = await stream.metadata()

const shadow = await sharp(
  Buffer.from(`
    <svg
      width="${width + SHADOW_MARGIN * 2}"
      height="${height + SHADOW_MARGIN * 2}"
    >
      <rect
        width="${width}"
        height="${height}"
        x="${SHADOW_MARGIN}"
        y="${SHADOW_MARGIN + SHADOW_OFFSET}"
        fill="rgba(0, 0, 0, ${SHADOW_OPACITY})"
      />
    </svg>`)
)
  .blur(SHADOW_BLUR)
  .toBuffer()

const image = await stream
  .resize({
    height,
    width,
  })
  .toBuffer()

await sharp({
  create: {
    width: OUTPUT_WIDTH,
    height: OUTPUT_HEIGHT,
    channels: 3,
    background: OUTPUT_BACKGROUND,
  },
})
  .composite([
    { input: shadow, blend: 'multiply' },
    { input: image, blend: 'over' },
  ])
  .jpeg()
  .toFile(outputPath)

All 4 comments

Hello, feDropShadow is unsupported by librsvg - it's only at W3C draft status - https://drafts.fxtf.org/filter-effects/

See #728 for multiple composition, some of the linked issues provide possible workarounds for now.

Hope this helped, please feel free to re-open with more details if not.

Came across this issue while trying to do something similar, feDropShadow is still unsupported but I managed to achieve what I wanted to do by blurring a rectangle (an svg the same size as the input image, with a margin) to act as the shadow and then using composites. Hope this helps anyone looking for the same thing!

const OUTPUT_BACKGROUND = '#bada55'
const OUTPUT_WIDTH = 1200
const OUTPUT_HEIGHT = 630
const SHADOW_MARGIN = 40
const SHADOW_BLUR = 15
const SHADOW_OFFSET = 6
const SHADOW_OPACITY = 0.3

const stream = await sharp(inputPath)
const { width, height } = await stream.metadata()

const shadow = await sharp(
  Buffer.from(`
    <svg
      width="${width + SHADOW_MARGIN * 2}"
      height="${height + SHADOW_MARGIN * 2}"
    >
      <rect
        width="${width}"
        height="${height}"
        x="${SHADOW_MARGIN}"
        y="${SHADOW_MARGIN + SHADOW_OFFSET}"
        fill="rgba(0, 0, 0, ${SHADOW_OPACITY})"
      />
    </svg>`)
)
  .blur(SHADOW_BLUR)
  .toBuffer()

const image = await stream
  .resize({
    height,
    width,
  })
  .toBuffer()

await sharp({
  create: {
    width: OUTPUT_WIDTH,
    height: OUTPUT_HEIGHT,
    channels: 3,
    background: OUTPUT_BACKGROUND,
  },
})
  .composite([
    { input: shadow, blend: 'multiply' },
    { input: image, blend: 'over' },
  ])
  .jpeg()
  .toFile(outputPath)

Wow, thanks @papandreou! This is exactly what I was looking for!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

emmtte picture emmtte  路  3Comments

vermin1337 picture vermin1337  路  3Comments

kachurovskiy picture kachurovskiy  路  3Comments

terbooter picture terbooter  路  3Comments

tomercagan picture tomercagan  路  3Comments