-
-
Notifications
You must be signed in to change notification settings - Fork 461
Add confetti.shapesFromImage method to create confetti from image source #243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -857,6 +857,42 @@ | |
| }; | ||
| } | ||
|
|
||
| function shapeFromImage(imageData) { | ||
| var src = imageData.src; | ||
| var scalar = 'scalar' in imageData ? imageData.scalar : 1; | ||
|
|
||
| var scale = 1 / scalar; | ||
|
|
||
| var img = new Image(); | ||
| img.src = src; | ||
|
|
||
| return promise(function (resolve) { | ||
|
Comment on lines
+860
to
+867
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, so I know why you did this. It is so convenient for this lib to just load the image for you, even from a URL. I've seen this backfire a lot in libraries though, from folks who don't totally understand how image loading works thinking that the library has a bug when their images are not served as expected. Not to mention, the image here doesn't include error handling (because why not add two problems to one comment, am I right?) Anyway, I think a much more flexible API would be |
||
| img.addEventListener('load', function() { | ||
| var size = 10 * scalar; | ||
|
|
||
| var sx = 'x' in imageData ? imageData.x : 0; | ||
| var sy = 'y' in imageData ? imageData.y : 0; | ||
| var sWidth = 'width' in imageData ? imageData.width : img.naturalWidth; | ||
| var sHeight = 'height' in imageData ? imageData.height : img.naturalHeight; | ||
|
|
||
| var x = 0; | ||
| var y = 0; | ||
| var width = size; | ||
| var height = size * sHeight / sWidth; | ||
|
|
||
| var canvas = new OffscreenCanvas(width, height); | ||
| var ctx = canvas.getContext('2d'); | ||
| ctx.drawImage(img, sx, sy, sWidth, sHeight, x, y, width, height); | ||
|
|
||
| resolve({ | ||
| type: 'bitmap', | ||
| bitmap: canvas.transferToImageBitmap(), | ||
| matrix: [scale, 0, 0, scale, -width * scale / 2, -height * scale / 2] | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| module.exports = function() { | ||
| return getDefaultFire().apply(this, arguments); | ||
| }; | ||
|
|
@@ -866,6 +902,7 @@ | |
| module.exports.create = confettiCannon; | ||
| module.exports.shapeFromPath = shapeFromPath; | ||
| module.exports.shapeFromText = shapeFromText; | ||
| module.exports.shapeFromImage = shapeFromImage; | ||
| }((function () { | ||
| if (typeof window !== 'undefined') { | ||
| return window; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -816,6 +816,74 @@ test('[text] shoots confetti of an emoji shape', async t => { | |
| t.is(t.context.image.hash(), 'cPpcSrcCjdC'); | ||
| }); | ||
|
|
||
| const shapeFromImageImage = async (page, args) => { | ||
| const { base64png, ...shape } = await page.evaluate(` | ||
| Promise.resolve().then(async () => { | ||
| const { bitmap, ...shape } = await confetti.shapeFromImage(${JSON.stringify(args)}); | ||
|
|
||
| const canvas = document.createElement('canvas'); | ||
| canvas.width = bitmap.width; | ||
| canvas.height = bitmap.height; | ||
| const ctx = canvas.getContext('2d'); | ||
| ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height); | ||
|
|
||
| return { | ||
| ...shape, | ||
| base64png: canvas.toDataURL('image/png') | ||
| }; | ||
| }); | ||
| `); | ||
|
|
||
| return { | ||
| ...shape, | ||
| buffer: base64ToBuffer(base64png) | ||
| }; | ||
| }; | ||
|
|
||
| test('[image] shapeFromImage renders an image', async t => { | ||
| const page = t.context.page = await fixturePage(); | ||
|
|
||
| const { buffer, ...shape } = await shapeFromImageImage(page, { src: 'data:image/gif;base64,R0lGODlhBQAFAIABAP8AAAAAACH5BAEKAAEALAAAAAAFAAUAAAIIjA+RwKxuUigAOw', scalar: 2 }); | ||
|
|
||
| t.context.buffer = buffer; | ||
| t.context.image = await readImage(buffer); | ||
|
|
||
| t.deepEqual({ | ||
| hash: t.context.image.hash(), | ||
| ...shape | ||
| }, { | ||
| type: 'bitmap', | ||
| matrix: [0.5, 0, 0, 0.5, -5, -5], | ||
| hash: '80anMEa00G0' | ||
| }); | ||
| }); | ||
|
|
||
| // this test renders a black canvas in a headless browser | ||
| // but works fine when it is not headless | ||
| // eslint-disable-next-line ava/no-skip-test | ||
|
Comment on lines
+861
to
+863
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic of this test is copied from the corresponding emoji one, so I copied this comment over too, even though for me it seemed to render fine in headless.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not entirely surprising. That test was written quite a while ago, and I know a lot of work was done on headless mode since. It's possible both tests just work now. |
||
| test('[image] shoots confetti of an image', async t => { | ||
| const page = t.context.page = await fixturePage(); | ||
|
|
||
| await page.evaluate(`Promise.resolve().then(async () => { | ||
| window.__image = await confetti.shapeFromImage({ src: 'data:image/gif;base64,R0lGODlhBQAFAIABAP8AAAAAACH5BAEKAAEALAAAAAAFAAUAAAIIjA+RwKxuUigAOw', scalar: 2 }); | ||
| })`); | ||
|
|
||
| // these parameters should create an image | ||
| // that is the same every time | ||
| t.context.buffer = await confettiImage(page, { | ||
| startVelocity: 0, | ||
| gravity: 0, | ||
| scalar: 10, | ||
| flat: 1, | ||
| ticks: 1000, | ||
| // eslint-disable-next-line no-undef | ||
| shapes: [() => __image] | ||
| }); | ||
| t.context.image = await readImage(t.context.buffer); | ||
|
|
||
| t.is(t.context.image.hash(), '9D_pL$p_Sr_'); | ||
| }); | ||
|
|
||
| /* | ||
| * Custom canvas | ||
| */ | ||
|
|
@@ -1247,3 +1315,9 @@ test('[esm] exposed confetti method has a `shapeFromText` property', async t => | |
|
|
||
| t.is(await page.evaluate(`typeof confettiAlias.shapeFromText`), 'function'); | ||
| }); | ||
|
|
||
| test('[esm] exposed confetti method has a `shapeFromImage` property', async t => { | ||
| const page = t.context.page = await fixturePage('fixtures/page.module.html'); | ||
|
|
||
| t.is(await page.evaluate(`typeof confettiAlias.shapeFromImage`), 'function'); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be a moot point given my other comment about sprites, but I think this is a bad thing to demonstrate. SVG paths perform better than images (and text), so when you can you are far better off using
shapeFromPathwhen at all possible. This demonstration looks like an endorsement to use SVGs as images.