Skip to content

Commit

Permalink
fix(sharp) wrap sharp calls in try/catch to avoid crashing on bad ima…
Browse files Browse the repository at this point in the history
…ges (#28645)

* added try/catch blocks around image processing

* fixed return on getImageMetadata
  • Loading branch information
nategiraudeau committed Jan 14, 2021
1 parent bf6f264 commit 004acf0
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 60 deletions.
106 changes: 58 additions & 48 deletions packages/gatsby-plugin-sharp/src/duotone.js
@@ -1,4 +1,5 @@
const sharp = require(`./safe-sharp`)
const { reportError } = require(`./report-error`)

module.exports = async function duotone(duotone, format, pipeline) {
const duotoneGradient = createDuotoneGradient(
Expand All @@ -13,38 +14,42 @@ module.exports = async function duotone(duotone, format, pipeline) {
quality: pipeline.options.jpegQuality,
}

const duotoneImage = await pipeline
.raw()
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
for (let i = 0; i < data.length; i = i + info.channels) {
const r = data[i + 0]
const g = data[i + 1]
const b = data[i + 2]
try {
const duotoneImage = await pipeline
.raw()
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
for (let i = 0; i < data.length; i = i + info.channels) {
const r = data[i + 0]
const g = data[i + 1]
const b = data[i + 2]

// @see https://en.wikipedia.org/wiki/Relative_luminance
const avg = Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b)
// @see https://en.wikipedia.org/wiki/Relative_luminance
const avg = Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b)

data[i + 0] = duotoneGradient[avg][0]
data[i + 1] = duotoneGradient[avg][1]
data[i + 2] = duotoneGradient[avg][2]
}
data[i + 0] = duotoneGradient[avg][0]
data[i + 1] = duotoneGradient[avg][1]
data[i + 2] = duotoneGradient[avg][2]
}

return sharp(data, {
raw: info,
}).toFormat(format, { ...options })
})
return sharp(data, {
raw: info,
}).toFormat(format, { ...options })
})

if (duotone.opacity) {
return overlayDuotone(
duotoneImage,
pipeline,
duotone.opacity,
format,
options
)
} else {
return duotoneImage
if (duotone.opacity) {
return overlayDuotone(
duotoneImage,
pipeline,
duotone.opacity,
format,
options
)
} else {
return duotoneImage
}
} catch (err) {
return null
}
}

Expand Down Expand Up @@ -100,25 +105,30 @@ async function overlayDuotone(
percentGrey
)

const duotoneWithTransparency = await duotoneImage
.joinChannel(percentTransparency, {
raw: { width: info.width, height: info.height, channels: 1 },
})
.raw()
.toBuffer()
try {
const duotoneWithTransparency = await duotoneImage
.joinChannel(percentTransparency, {
raw: { width: info.width, height: info.height, channels: 1 },
})
.raw()
.toBuffer()

return await originalImage
.composite([
{
input: duotoneWithTransparency,
blend: `over`,
raw: { width: info.width, height: info.height, channels: 4 },
},
])
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) =>
sharp(data, {
raw: info,
}).toFormat(format, { ...options })
)
return await originalImage
.composite([
{
input: duotoneWithTransparency,
blend: `over`,
raw: { width: info.width, height: info.height, channels: 4 },
},
])
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) =>
sharp(data, {
raw: info,
}).toFormat(format, { ...options })
)
} catch (err) {
reportError(`Failed to process image ${originalImage}`, err)
return originalImage
}
}
31 changes: 19 additions & 12 deletions packages/gatsby-plugin-sharp/src/image-data.ts
Expand Up @@ -6,6 +6,7 @@ import { rgbToHex, calculateImageSizes, getSrcSet, getSizes } from "./utils"
import { traceSVG, getImageSizeAsync, base64, batchQueueImageResizing } from "."
import sharp from "./safe-sharp"
import { createTransformObject } from "./plugin-options"
import { reportError } from "./report-error"

const DEFAULT_BLURRED_IMAGE_WIDTH = 20

Expand All @@ -30,7 +31,7 @@ const metadataCache = new Map<string, IImageMetadata>()
export async function getImageMetadata(
file: FileNode,
getDominantColor?: boolean
): Promise<IImageMetadata> {
): Promise<IImageMetadata | undefined> {
if (!getDominantColor) {
// If we don't need the dominant color we can use the cheaper size function
const { width, height, type } = await getImageSizeAsync(file)
Expand All @@ -40,18 +41,24 @@ export async function getImageMetadata(
if (metadata && process.env.NODE_ENV !== `test`) {
return metadata
}
const pipeline = sharp(file.absolutePath)

const { width, height, density, format } = await pipeline.metadata()
try {
const pipeline = sharp(file.absolutePath)

const { dominant } = await pipeline.stats()
// Fallback in case sharp doesn't support dominant
const dominantColor = dominant
? rgbToHex(dominant.r, dominant.g, dominant.b)
: `#000000`
const { width, height, density, format } = await pipeline.metadata()

const { dominant } = await pipeline.stats()
// Fallback in case sharp doesn't support dominant
const dominantColor = dominant
? rgbToHex(dominant.r, dominant.g, dominant.b)
: `#000000`

metadata = { width, height, density, format, dominantColor }
metadataCache.set(file.internal.contentDigest, metadata)
} catch (err) {
reportError(`Failed to process image ${file.absolutePath}`, err)
}

metadata = { width, height, density, format, dominantColor }
metadataCache.set(file.internal.contentDigest, metadata)
return metadata
}

Expand Down Expand Up @@ -143,7 +150,7 @@ export async function generateImageData({

let primaryFormat: ImageFormat | undefined
if (useAuto) {
primaryFormat = normalizeFormat(metadata.format || file.extension)
primaryFormat = normalizeFormat(metadata?.format || file.extension)
} else if (formats.has(`png`)) {
primaryFormat = `png`
} else if (formats.has(`jpg`)) {
Expand Down Expand Up @@ -334,7 +341,7 @@ export async function generateImageData({
imageProps.placeholder = {
fallback,
}
} else if (metadata.dominantColor) {
} else if (metadata?.dominantColor) {
imageProps.backgroundColor = metadata.dominantColor
}

Expand Down

0 comments on commit 004acf0

Please sign in to comment.