Forked from matsuyama-k1/puppeteer-larger-than-16384px.ts
Created
October 3, 2024 14:47
-
-
Save pirate/8edf1d2f866d75ddfa5558dd9c1b05da to your computer and use it in GitHub Desktop.
one solution for taking screen shot larger than 16384px with puppeteer. https://github.com/puppeteer/puppeteer/issues/359
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Page } from "puppeteer"; | |
import sharp from "sharp"; | |
// Max texture size of the software GL backand of chronium. (16384px or 4096px) | |
// https://issues.chromium.org/issues/41347676 | |
export const MAX_SIZE_PX = 16384; | |
const takeFullPageScreenshot = async (page: Page) => { | |
const pageHeight = await getPageHeight(page); | |
const deviceScaleFactor = page.viewport()?.deviceScaleFactor ?? 1; | |
const scaledHeight = pageHeight * deviceScaleFactor; | |
let screenshot: Uint8Array; | |
if (scaledHeight > MAX_SIZE_PX) { | |
screenshot = await captureLargeScreenshot(page); // here! | |
} else { | |
screenshot = await page.screenshot({ | |
fullPage: true, | |
}); | |
} | |
return screenshot; | |
}; | |
export default takeFullPageScreenshot; | |
const captureLargeScreenshot = async (page: Page) => { | |
const viewport = page.viewport()!; | |
const deviceScaleFactor = viewport.deviceScaleFactor ?? 1; | |
const width = viewport.width; | |
const pageHeight = await getPageHeight(page); | |
const screenshots: Uint8Array[] = []; | |
const screenshotPromises = []; | |
let currentYPosition = 0; | |
const scaledPageHeight = pageHeight * deviceScaleFactor; | |
while (currentYPosition < scaledPageHeight) { | |
const clipHeight = Math.min( | |
Math.floor(MAX_SIZE_PX / deviceScaleFactor), | |
scaledPageHeight - currentYPosition | |
); | |
const screenshotPromise = page.screenshot({ | |
clip: { | |
x: 0, | |
y: currentYPosition, | |
width: width, | |
height: clipHeight, | |
}, | |
omitBackground: true, | |
type: "png", | |
}); | |
screenshotPromises.push(screenshotPromise); | |
currentYPosition += clipHeight; | |
} | |
const values = await Promise.all(screenshotPromises); | |
screenshots.push(...values); | |
try { | |
const screenshotBuffer = await stitchImages( | |
width * deviceScaleFactor, | |
scaledPageHeight, | |
screenshots | |
); | |
const uint8Array = new Uint8Array(screenshotBuffer); | |
return uint8Array; | |
} catch (err) { | |
console.error("Error stitching screenshots:", err); | |
throw err; // Propagate the error | |
} | |
}; | |
const stitchImages = async ( | |
w: number, | |
h: number, | |
screenshots: Uint8Array[] | |
): Promise<Buffer> => { | |
let currentHeight = 0; | |
const compositeOperations = []; | |
for (let i = 0; i < screenshots.length; i++) { | |
const screenshot = screenshots[i]; | |
try { | |
const img = sharp(screenshot); | |
const { height: imgHeight } = await img.metadata(); | |
// Convert Uint8Array to Buffer | |
const bufferInput = Buffer.from(screenshot); | |
// Collect composite operations | |
compositeOperations.push({ | |
input: bufferInput, | |
top: currentHeight, | |
left: 0, | |
}); | |
currentHeight += imgHeight ?? 0; | |
} catch (err) { | |
console.error(`Error processing screenshot ${i}:`, err); | |
throw err; | |
} | |
} | |
const img = sharp({ | |
create: { | |
width: w, | |
height: h, | |
channels: 4, | |
background: { r: 255, g: 255, b: 255, alpha: 0 }, | |
}, | |
limitInputPixels: h * w, | |
}); | |
const result = img.composite(compositeOperations); | |
return await result.png().toBuffer(); | |
}; | |
const getPageHeight = async (page: Page): Promise<number> => { | |
return await page.evaluate(() => document.documentElement.scrollHeight); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment