Skip to content

Commit

Permalink
✨ [Amp story desktop one panel] [background blur] Do not blur transpa…
Browse files Browse the repository at this point in the history
…rent images (ampproject#35525)

* Example page

* Rename template.

* whitespace

* Compress png

* Update extensions/amp-story/1.0/background-blur.js

Co-authored-by: Gabriel Majoulet <[email protected]>

* Update extensions/amp-story/1.0/background-blur.js

Co-authored-by: Gabriel Majoulet <[email protected]>

Co-authored-by: Gabriel Majoulet <[email protected]>
  • Loading branch information
processprocess and gmajoulet authored Aug 10, 2021
1 parent c0fe58e commit cd5cdd7
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
color: #fff;
font-weight: normal;
line-height: 1.174;
text-transform: uppercase;
}
</style>
</head>
Expand All @@ -38,12 +37,12 @@

<amp-story-page id="cover">
<amp-story-grid-layer template="fill">
<amp-img src="https://images.unsplash.com/photo-1484151709479-3996843263cf?dpr=2&auto=format&fit=crop&w=568&h=379&q=60&cs=tinysrgb&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D"
width="900" height="1600">
</amp-img>
<amp-img src="https://images.unsplash.com/photo-1484151709479-3996843263cf?dpr=2&auto=format&fit=crop&w=568&h=379&q=60&cs=tinysrgb&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D"
width="900" height="1600">
</amp-img>
</amp-story-grid-layer>
<amp-story-grid-layer template="vertical">
<h3>Hello, amp-story!</h3>
<h3>Background jpeg shuld be used for blur.</h3>
</amp-story-grid-layer>
</amp-story-page>

Expand All @@ -53,19 +52,66 @@ <h3>Hello, amp-story!</h3>
width="900" height="1600">
</amp-img>
</amp-story-grid-layer>
<amp-story-grid-layer template="vertical">
<h3>Background jpeg shuld be used for blur.</h3>
</amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="no-image-page">
<amp-story-grid-layer template="vertical">
<p>No image page, background should fade to black.</p>
</amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="page-2">
<amp-story-page id="video-page">
<amp-story-grid-layer template="fill">
<amp-video autoplay loop
width="400"
height="750"
poster="./img/poster2.jpg"
layout="fill">
<source src="./video/p1.mp4" type="video/mp4">
</amp-video>
</amp-story-grid-layer>
<amp-story-grid-layer template="vertical">
<h1>Second Page</h1>
<p>This is the second page of this story.</p>
<h3>First frame of video should blur.</h3>
<h3>If slow connection, poster image should blur.</h3>
</amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="page-3">
<amp-story-page id="transparent-png-and-img-page">
<amp-story-grid-layer template="thirds">
<amp-img grid-area="upper-third" src="img/amplogo.png" width="120" height="120">
</amp-img>
<amp-img grid-area="middle-third" src="https://images.unsplash.com/photo-1440658172029-9d9e5cdc127c?dpr=2&auto=format&fit=crop&w=568&h=344&q=60&cs=tinysrgb&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D"
width="90" height="90">
</amp-img>
<p template="lower-third">
jpeg should blur even though it's smaller than png.
</p>
</amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="transparent-png-only-page">
<amp-story-grid-layer template="thirds">
<amp-img grid-area="upper-third" src="img/amplogo.png" width="120" height="120">
</amp-img>
<p template="lower-third">
png with transparency should not blur. Should fade to black.
</p>
</amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="solid-png-only-page">
<amp-story-grid-layer template="fill">
<amp-img src="img/ad-coffee.png"
width="90" height="90">
</amp-img>
</amp-story-grid-layer>
<amp-story-grid-layer template="vertical">
<h1>Third Page</h1>
<p>This is the third page of this story.</p>
<h3 template="lower-third">
Solid png (no transparency) should blur.
</h3>
</amp-story-grid-layer>
</amp-story-page>
</amp-story>
Expand Down
Binary file added examples/amp-story/img/ad-coffee.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
158 changes: 117 additions & 41 deletions extensions/amp-story/1.0/background-blur.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ export class BackgroundBlur {
/** @private @const {!Element} */
this.element_ = element;

/** @private @const {!Array<Element>} */
this.mediaElements_ = null;

/** @private @const {!Element} */
this.canvas_ = null;

Expand Down Expand Up @@ -86,58 +89,130 @@ export class BackgroundBlur {
}

/**
* Update the background to the specified page's background.
* Update the media elements and call the first iteration of media element to blur.
* @param {!Element} pageElement
*/
update(pageElement) {
const mediaEl = this.getBiggestMediaEl_(pageElement);
this.mediaElements_ = this.getBiggestMediaElements_(pageElement);
this.findAndBlurMediaEl_(this.mediaElements_[0]);
}

/**
* Ensures element exists, is loaded and is not a transparent png or gif.
* Recursive if the mediaEl is png or gif with transparent pixels.
* @private
* @param {?Element} mediaEl
*/
findAndBlurMediaEl_(mediaEl) {
if (!mediaEl) {
user().info(CLASS_NAME, 'No amp-img or amp-video found.');
this.animate_();
this.animateBlur_();
return;
}

// Ensure element is loaded before calling animate.
whenUpgradedToCustomElement(mediaEl)
.then(() => mediaEl.signals().whenSignal(CommonSignals.LOAD_END))
.then(
() => {
// If image, render it.
if (mediaEl.tagName === 'AMP-IMG') {
this.animate_(mediaEl.querySelector('img'));
return;
}

// If video, render first frame or poster image.
const innerVideoEl = mediaEl.querySelector('video');
const alreadyHasData = innerVideoEl.readyState >= HAVE_CURRENT_DATA;
if (alreadyHasData) {
this.animate_(innerVideoEl);
return;
}
// If video doesnt have data, render from the poster image.
const posterSrc = mediaEl.getAttribute('poster');
if (!posterSrc) {
this.animate_();
user().info(CLASS_NAME, 'No "poster" attribute on amp-video.');
return;
}
const img = new Image();
img.onload = () => this.animate_(img);
img.src = posterSrc;
},
() => {
user().error(CLASS_NAME, 'Failed to load the amp-img or amp-video.');
this.ensureMediaLoaded_(mediaEl).then((loadedMediaEl) => {
// If image:
if (loadedMediaEl.tagName === 'AMP-IMG') {
// First check if it has transparent pixels.
if (this.isTransparentGifOrPng_(loadedMediaEl)) {
// If transparent, try again with the next element in the array.
this.findAndBlurMediaEl_(this.getNextMediaEl_(loadedMediaEl));
return;
}
);
this.animateBlur_(loadedMediaEl.querySelector('img'));
return;
}

// If video, render first frame or poster image.
const innerVideoEl = loadedMediaEl.querySelector('video');
const alreadyHasData = innerVideoEl.readyState >= HAVE_CURRENT_DATA;
if (alreadyHasData) {
this.animateBlur_(innerVideoEl);
return;
}
// If video doesnt have data, render from the poster image.
const posterSrc = loadedMediaEl.getAttribute('poster');
if (!posterSrc) {
this.animateBlur_();
user().info(CLASS_NAME, 'No "poster" attribute on amp-video.');
return;
}
const img = new Image();
img.onload = () => this.animateBlur_(img);
img.src = posterSrc;
});
}

/**
* @private
* @param {?Element} mediaEl
* @return {boolean}
*/
isTransparentGifOrPng_(mediaEl) {
if (!this.isGifOrPng_(mediaEl)) {
return false;
}
const imgEl = mediaEl.querySelector('img');
const canvas = this.win_.document.createElement('canvas');
canvas.width = canvas.height = CANVAS_SIZE;
const context = canvas.getContext('2d');
context.drawImage(imgEl, 0, 0, CANVAS_SIZE, CANVAS_SIZE);
const imgData = context.getImageData(0, 0, CANVAS_SIZE, CANVAS_SIZE).data;
// Image data pixel values are in sets of 4: r, g, b, a.
// For this reason we increment in 4.
for (let i = 0; i < imgData.length; i += 4) {
const pixelAlphaVal = imgData[i + 3];
if (pixelAlphaVal < 255) {
return true;
}
}
return false;
}

/**
* @private
* @param {?Element} mediaEl
* @return {boolean}
*/
isGifOrPng_(mediaEl) {
const src = mediaEl.getAttribute('src').toLowerCase();
return src.includes('.png') || src.includes('.gif');
}

/**
* @private
* @param {?Element} mediaEl
* @return {?Element}
*/
getNextMediaEl_(mediaEl) {
const currentMediaElIdx = this.mediaElements_.indexOf(mediaEl);
return this.mediaElements_[currentMediaElIdx + 1];
}

/**
* @private
* @param {?Element} mediaEl
* @return {Promise}
*/
ensureMediaLoaded_(mediaEl) {
return new Promise((resolve) => {
whenUpgradedToCustomElement(mediaEl)
.then(() => mediaEl.signals().whenSignal(CommonSignals.LOAD_END))
.then(() => {
resolve(mediaEl);
})
.catch(() => {
user().error(CLASS_NAME, 'Failed to load the amp-img or amp-video.');
});
});
}

/**
* Animated background transition.
* Animate background transition.
* @private
* @param {?Element} fillElement
*/
animate_(fillElement) {
animateBlur_(fillElement) {
this.drawOffscreenCanvas_(fillElement);
// Do not animate on first load.
if (this.firstLoad_) {
Expand Down Expand Up @@ -197,12 +272,13 @@ export class BackgroundBlur {
}

/**
* Get active page's biggest amp-img or amp-video element.
* Gets a list of the active page's amp-img or amp-video elements
* sorted by size (possibly empty).
* @private
* @param {!Element} pageElement
* @return {?Element} An amp-img, amp-video or null.
* @return {!Array<Element>}
*/
getBiggestMediaEl_(pageElement) {
getBiggestMediaElements_(pageElement) {
const getSize = (el) => {
if (!el) {
return false;
Expand All @@ -215,6 +291,6 @@ export class BackgroundBlur {
pageElement,
'amp-story-grid-layer amp-img, amp-story-grid-layer amp-video'
)
).sort((firstEl, secondEl) => getSize(secondEl) - getSize(firstEl))[0];
).sort((firstEl, secondEl) => getSize(secondEl) - getSize(firstEl));
}
}

0 comments on commit cd5cdd7

Please sign in to comment.