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

CSS3 scale property produces bad quality on lower-than-config-resolution windows #609

Open
fin-ger opened this Issue Apr 6, 2017 · 10 comments

Comments

Projects
None yet
2 participants
@fin-ger

fin-ger commented Apr 6, 2017

There are several other issues in this repo that deal with sizing/scaling a slide/step to fit into the browser
window. This issue deals with visual artifacts produced by the CSS scale property.

As far as I understood the current implementation the scale value will be <1 if the browser windows content area is too small to fit a given resolution inside the browser window. The resolution (in px) is given via defaults or the data-width and data-height attributes on the impress root element. This will NOT "perfectly" fit a presentation into windows that provide higher resolution than the configured one (common on high-dpi displays). Also this will create aliasing artifacts on windows that are smaller than the configured resolution (the scale value is <1). I think (!) the aliasing artifacts are created by the browser when rendering the "scene" in its native resolution (e.g. 1920x1080) and downscaling this scene to e.g. 1024x576 (common beamer resolution). Hinting and antialiasing done by the vector graphics rasterizer of the browser are destroyed (correct me if I'm wrong).

This is a magnified screenshot of native resolution font rasterization:
native-resolution

This is a magnified screenshot of the scaled font rasterization:
aliasing

You can see that the scaled scene has more aliasing artifacts than the native resolution image.

I tried to accomplish an always-fitting-slide without using the CSS scale property with vw and vh properties at a slide aspect ratio of 16:9. This approach can set the font-size to e.g. 4vh to scale all the content with the slide. When using em to size all the content it should work pretty good. I did not use impress.js for this example, so it remains to be seen if such a change can be integrated into impress.js.

Example without CSS scale (jsfiddle)
Update: Example without CSS scale (jsfiddle) - correct font scale

This approach would make a presentation fully independent to the users browser window resolution. The translate calls that are done by impress.js for moving the slide/step canvas has to be adopted to not use px but use vw/vh respectively. I played around with the units of the translate property and vw/vh seem to work for translations.

Please add comments for further investigation of this issue.

@henrikingo

This comment has been minimized.

Show comment
Hide comment
@henrikingo

henrikingo Apr 11, 2017

Contributor

Hi @fin-ger

This is an interesting study you've made. And indeed it seems vw and vh would be very useful units for creating slide shows, since the idea with impress.js is to scale contents of a slide to match screen size.

I think one question I have from reading your above entry: It's possible in impress.js, that you will see content from other slides in the background of the current slide. Example: http://impress.github.io/impress.js/#/its-in-3d They may have a different scale (so different size). How would content on such a slide scale, if it was using vw and vh?

Contributor

henrikingo commented Apr 11, 2017

Hi @fin-ger

This is an interesting study you've made. And indeed it seems vw and vh would be very useful units for creating slide shows, since the idea with impress.js is to scale contents of a slide to match screen size.

I think one question I have from reading your above entry: It's possible in impress.js, that you will see content from other slides in the background of the current slide. Example: http://impress.github.io/impress.js/#/its-in-3d They may have a different scale (so different size). How would content on such a slide scale, if it was using vw and vh?

@fin-ger

This comment has been minimized.

Show comment
Hide comment
@fin-ger

fin-ger Apr 22, 2017

@henrikingo

I thought about your input and came up with another proof-of-concept implementation that shows how to implement a vh/vw-based presentation framework.

Proof-of-concept with 3D

I noticed 2 things while implementing this:

  1. The em-based sizing of text leads to jittering while a transition is performed.
  2. As I am using a container which enables % sizing in the slides, a reflow gets triggered on all slides when the size of the container changes. This typically happens when a zoom or slide transition is performed. The result is a bad rendering performance (frame time) when running this approach on a weaker PC.

I know that there are tricks to optimize reflows but I don't know how to apply them (if even possible).

Also the new design forbids the use of px (or the result is highly dependent on the size of the browser window 😄).

Feel free to play around with the constants - it's quite funny to see how everything automagically adopts (or explodes) 🤣

fin-ger commented Apr 22, 2017

@henrikingo

I thought about your input and came up with another proof-of-concept implementation that shows how to implement a vh/vw-based presentation framework.

Proof-of-concept with 3D

I noticed 2 things while implementing this:

  1. The em-based sizing of text leads to jittering while a transition is performed.
  2. As I am using a container which enables % sizing in the slides, a reflow gets triggered on all slides when the size of the container changes. This typically happens when a zoom or slide transition is performed. The result is a bad rendering performance (frame time) when running this approach on a weaker PC.

I know that there are tricks to optimize reflows but I don't know how to apply them (if even possible).

Also the new design forbids the use of px (or the result is highly dependent on the size of the browser window 😄).

Feel free to play around with the constants - it's quite funny to see how everything automagically adopts (or explodes) 🤣

@fin-ger

This comment has been minimized.

Show comment
Hide comment
@fin-ger

fin-ger Apr 23, 2017

I added some transparency to the slides and the rendering performance is really bad...

3D vw/vh presentation concept

I think a combination of the transform: scale(...) and the vw/vh solution will give the best results.

During the transition of slides, the old transform: scale(...) should be used for the following reasons:

  1. When using transform: scale(...) to size a slide, only the framebuffer texture of the transformed div gets resized -> no reflow
  2. As the texture of the slide is scaled, the jitter produced by the interpolated font-size when doing a transition with vw/vh, is avoided.

We have to be careful on which DOM element we apply the transformation. What we don't want is one big framebuffer texture that scales all slides as this will consume huge amounts of VRAM which may not be applicable on all PCs. Instead we want multiple smaller framebuffer textures that get updated independently (e.g. per slide).

After the transition of slides the above shown vw/vh approach should be favored instead of transform: scale(...) for the following reasons:

  1. The text rendering result is better as hinting and antialiasing information get preserved.
  2. The slides adapt to the window size.

The challenge is to integrate transform: scale(...) into the vw/vh approach with equal sizing to prevent jumps when the transition ends.

An alternative solution would be to implement the container in a way that no reflow gets triggered e.g. not using top/bottom/left/right/font-size css properties to create a zoom effect which effectively implements the data-scale attribute. I can't think of any solution but maybe someone else has an idea...

EDIT: I forgot to mention that the performance drain (the reflow) only occurs when the data-scale attribute of a slide differs to the previous slide.

fin-ger commented Apr 23, 2017

I added some transparency to the slides and the rendering performance is really bad...

3D vw/vh presentation concept

I think a combination of the transform: scale(...) and the vw/vh solution will give the best results.

During the transition of slides, the old transform: scale(...) should be used for the following reasons:

  1. When using transform: scale(...) to size a slide, only the framebuffer texture of the transformed div gets resized -> no reflow
  2. As the texture of the slide is scaled, the jitter produced by the interpolated font-size when doing a transition with vw/vh, is avoided.

We have to be careful on which DOM element we apply the transformation. What we don't want is one big framebuffer texture that scales all slides as this will consume huge amounts of VRAM which may not be applicable on all PCs. Instead we want multiple smaller framebuffer textures that get updated independently (e.g. per slide).

After the transition of slides the above shown vw/vh approach should be favored instead of transform: scale(...) for the following reasons:

  1. The text rendering result is better as hinting and antialiasing information get preserved.
  2. The slides adapt to the window size.

The challenge is to integrate transform: scale(...) into the vw/vh approach with equal sizing to prevent jumps when the transition ends.

An alternative solution would be to implement the container in a way that no reflow gets triggered e.g. not using top/bottom/left/right/font-size css properties to create a zoom effect which effectively implements the data-scale attribute. I can't think of any solution but maybe someone else has an idea...

EDIT: I forgot to mention that the performance drain (the reflow) only occurs when the data-scale attribute of a slide differs to the previous slide.

@henrikingo

This comment has been minimized.

Show comment
Hide comment
@henrikingo

henrikingo Apr 23, 2017

Contributor

Thanks for looking into this more. The limitations of scaling are annoying users regularly. (As you can see from above referenced issue about scaling SVG.) It would be great if there was a solution. I think your results are encouraging, although it is not clear to me if a solution along these lines will be feasible to implement in impress.js in a backward compatible way? (I'm really a backend developer - what you're doing is definitively stretching the limits of my knowledge.)

We have to be careful on which DOM element we apply the transformation. What we don't want is one big framebuffer texture that scales all slides as this will consume huge amounts of VRAM which may not be applicable on all PCs. Instead we want multiple smaller framebuffer textures that get updated independently (e.g. per slide).

Isn't this what impress.js currently does? And if we want all slides to be potentially visible during a presentation, isn't it logically necessary to re-scale all of them all the time?

This is the reason bartaz has been hesitant to officially support it on phones, because it could easily run out of RAM. One patch proposed a solution (for mobile devices) to only show the previous, current and next slides to limit RAM consumption. For more advanced presentations, that want other slides to be visible - such as in the background of the current slide - this is quite limiting of course.

Contributor

henrikingo commented Apr 23, 2017

Thanks for looking into this more. The limitations of scaling are annoying users regularly. (As you can see from above referenced issue about scaling SVG.) It would be great if there was a solution. I think your results are encouraging, although it is not clear to me if a solution along these lines will be feasible to implement in impress.js in a backward compatible way? (I'm really a backend developer - what you're doing is definitively stretching the limits of my knowledge.)

We have to be careful on which DOM element we apply the transformation. What we don't want is one big framebuffer texture that scales all slides as this will consume huge amounts of VRAM which may not be applicable on all PCs. Instead we want multiple smaller framebuffer textures that get updated independently (e.g. per slide).

Isn't this what impress.js currently does? And if we want all slides to be potentially visible during a presentation, isn't it logically necessary to re-scale all of them all the time?

This is the reason bartaz has been hesitant to officially support it on phones, because it could easily run out of RAM. One patch proposed a solution (for mobile devices) to only show the previous, current and next slides to limit RAM consumption. For more advanced presentations, that want other slides to be visible - such as in the background of the current slide - this is quite limiting of course.

@fin-ger

This comment has been minimized.

Show comment
Hide comment
@fin-ger

fin-ger Apr 23, 2017

Isn't this what impress.js currently does?

Yes I think this is what impress.js currently does. The thing that I wanted to point out is that we have to be careful to not remove this feature.

And if we want all slides to be potentially visible during a presentation, isn't it logically necessary to re-scale all of them all the time?

I thought of something like this for a transition from slide A to slide B:

  1. User triggers transition from slide A to slide B
  2. Content reflow to fit dimension of slide B (no transition for top/bottom/left/right/font-size)
  3. Use transform: scale(...) to fit dimension of slide A
  4. Enable transform transitions
  5. Transition to transform: scale(1)

This avoids a reflow for every transition frame but only reflows at the beginning of each transition. During the transition itself the hinting/antialising artifacts will still appear but I think this will barely be noticed by any user.

fin-ger commented Apr 23, 2017

Isn't this what impress.js currently does?

Yes I think this is what impress.js currently does. The thing that I wanted to point out is that we have to be careful to not remove this feature.

And if we want all slides to be potentially visible during a presentation, isn't it logically necessary to re-scale all of them all the time?

I thought of something like this for a transition from slide A to slide B:

  1. User triggers transition from slide A to slide B
  2. Content reflow to fit dimension of slide B (no transition for top/bottom/left/right/font-size)
  3. Use transform: scale(...) to fit dimension of slide A
  4. Enable transform transitions
  5. Transition to transform: scale(1)

This avoids a reflow for every transition frame but only reflows at the beginning of each transition. During the transition itself the hinting/antialising artifacts will still appear but I think this will barely be noticed by any user.

@fin-ger

This comment has been minimized.

Show comment
Hide comment
@fin-ger

fin-ger Oct 8, 2017

I implemented the above approach

Single reflow per slide transition

Problems that occured while implementing this were:

  1. When changing the global scale on the container element the static perspective leads to a "jump" at the start of the transition. I am now also transitioning the perspective property of the impress element additionally to the scale property on the container. As the perspective does not translate 1:1 to a scale, the transition seems a bit bouncy when using the same transition function on both properties. Currently I cannot think of a solution for eliminating the bouncy perspective transition.
  2. When a small slide (e.g. 10% the size of the largest slide) is shown, other slides that are of "normal" size allocate a huge framebuffer (e.g. 9180x5164 pixels for the first slide when viewing the smallest in my example - ~180MiB VRAM). This is expected behavior as we want the magnified slides to be rendered in native resolution to avoid artifacts. However most slides will eventually be outside the camera frustum and therefore waste tremendous amounts of VRAM. Additionally one could conclude that rendering artifacts on non-active slides are not relevant for most user.

To solve the above problems:

  1. Set display: none to all slides that are completely outside of the camera frustum. This will additionally improve performance when reflowing the content at the beginning of a transition as less slides need a reflow (hidden elements are not reflown). Ideas are welcome for how to detect slides outside of the camera frustum. The simplest (and dumbest) approach would be to do a plane-to-frustum collision on the CPU (js) which will drain the performance on weaker devices even more.
  2. Set a scale property on non-active slides to reduce the framebuffer size. This could be e.g. set to match half of the window resolution.

fin-ger commented Oct 8, 2017

I implemented the above approach

Single reflow per slide transition

Problems that occured while implementing this were:

  1. When changing the global scale on the container element the static perspective leads to a "jump" at the start of the transition. I am now also transitioning the perspective property of the impress element additionally to the scale property on the container. As the perspective does not translate 1:1 to a scale, the transition seems a bit bouncy when using the same transition function on both properties. Currently I cannot think of a solution for eliminating the bouncy perspective transition.
  2. When a small slide (e.g. 10% the size of the largest slide) is shown, other slides that are of "normal" size allocate a huge framebuffer (e.g. 9180x5164 pixels for the first slide when viewing the smallest in my example - ~180MiB VRAM). This is expected behavior as we want the magnified slides to be rendered in native resolution to avoid artifacts. However most slides will eventually be outside the camera frustum and therefore waste tremendous amounts of VRAM. Additionally one could conclude that rendering artifacts on non-active slides are not relevant for most user.

To solve the above problems:

  1. Set display: none to all slides that are completely outside of the camera frustum. This will additionally improve performance when reflowing the content at the beginning of a transition as less slides need a reflow (hidden elements are not reflown). Ideas are welcome for how to detect slides outside of the camera frustum. The simplest (and dumbest) approach would be to do a plane-to-frustum collision on the CPU (js) which will drain the performance on weaker devices even more.
  2. Set a scale property on non-active slides to reduce the framebuffer size. This could be e.g. set to match half of the window resolution.
@henrikingo

This comment has been minimized.

Show comment
Hide comment
@henrikingo

henrikingo Oct 8, 2017

Contributor

HI @fin-ger

Cool, thanks for sharing. I will try your solution at a later point, as I'm very interested in solving this issue (one way or another).

Related to solution requiring lots of RAM, kind of related is the mobile plugin, which allows you to display: none all but the current, previous and next slides. Of course, that's not what you want here, because you'd probably want some background slide to always be visible. But you could then use additional CSS (div.background) to make that so, and hide the ones you don't need.

Note, it could also be useful to know that impress.js sets a class on body element, which has the name of the active slide. For example, when active slide is "step-1": body.impress-on-step-1.

We're working on merging my stuff into impress.js repo, so expect the mobile plugin to show up some weeks from now.

Contributor

henrikingo commented Oct 8, 2017

HI @fin-ger

Cool, thanks for sharing. I will try your solution at a later point, as I'm very interested in solving this issue (one way or another).

Related to solution requiring lots of RAM, kind of related is the mobile plugin, which allows you to display: none all but the current, previous and next slides. Of course, that's not what you want here, because you'd probably want some background slide to always be visible. But you could then use additional CSS (div.background) to make that so, and hide the ones you don't need.

Note, it could also be useful to know that impress.js sets a class on body element, which has the name of the active slide. For example, when active slide is "step-1": body.impress-on-step-1.

We're working on merging my stuff into impress.js repo, so expect the mobile plugin to show up some weeks from now.

@fin-ger

This comment has been minimized.

Show comment
Hide comment
@fin-ger

fin-ger Oct 8, 2017

Reduced VRAM

I tried to fix the 2nd problem and ran in the following issues:

When implementing solution 2 I noticed that the mix of height, width, font-size and scale leads to "broken" slides as seen when viewing slide 2 and inspecting the rendering layers in chrome. It seems like the vertices of the plane get distorted... Possibly I'm stretching the capabilities of the rendering engine.

When implementing solution 1 I used the top,left,bottom,right boundaries of the slides (in screen coordinates) to "collide" with the visible area. This only handles the x and y coordinates and is not capable of hiding slides behind the "camera" as z is not taken into account. I could not find a way to get the z boundaries of a DOM element 😁
Additionally I can only evaluate which slide is visible and which not when the transition is finished. This leads to timing issues (transition is done in css, collision in js) and slides will appear in the background when the transition is finished. To prevent the next slide to be hidden when doing a transition (it might not be visible from the previous one) I always show the next slide on transition start.

I think my current implementation will work well for presentations that use small amounts of 3D (e.g. not my example 🤣 ) where slides are not covered by other slides.

Issues that remain:

  1. Timing issues for show/hide of out-of-frustum slides
  2. Browser rendering issues when using complex 3D
  3. Handle z boundaries of slides

fin-ger commented Oct 8, 2017

Reduced VRAM

I tried to fix the 2nd problem and ran in the following issues:

When implementing solution 2 I noticed that the mix of height, width, font-size and scale leads to "broken" slides as seen when viewing slide 2 and inspecting the rendering layers in chrome. It seems like the vertices of the plane get distorted... Possibly I'm stretching the capabilities of the rendering engine.

When implementing solution 1 I used the top,left,bottom,right boundaries of the slides (in screen coordinates) to "collide" with the visible area. This only handles the x and y coordinates and is not capable of hiding slides behind the "camera" as z is not taken into account. I could not find a way to get the z boundaries of a DOM element 😁
Additionally I can only evaluate which slide is visible and which not when the transition is finished. This leads to timing issues (transition is done in css, collision in js) and slides will appear in the background when the transition is finished. To prevent the next slide to be hidden when doing a transition (it might not be visible from the previous one) I always show the next slide on transition start.

I think my current implementation will work well for presentations that use small amounts of 3D (e.g. not my example 🤣 ) where slides are not covered by other slides.

Issues that remain:

  1. Timing issues for show/hide of out-of-frustum slides
  2. Browser rendering issues when using complex 3D
  3. Handle z boundaries of slides
@fin-ger

This comment has been minimized.

Show comment
Hide comment
@fin-ger

fin-ger Oct 8, 2017

fyi: I added an SVG to prove that it's fully scalable on any resolution:

SVG example added on slide 3

Edit:
I removed the automatic show/hide of slides as this solves 1 and 3. I solved 2 by adjusting the arrangement of the 2nd slide a bit (removed complexity).

SVG without slide show/hide

fin-ger commented Oct 8, 2017

fyi: I added an SVG to prove that it's fully scalable on any resolution:

SVG example added on slide 3

Edit:
I removed the automatic show/hide of slides as this solves 1 and 3. I solved 2 by adjusting the arrangement of the 2nd slide a bit (removed complexity).

SVG without slide show/hide

@henrikingo

This comment has been minimized.

Show comment
Hide comment
@henrikingo

henrikingo Oct 31, 2017

Contributor

Hi @fin-ger

Thanks for your work on investigating this issue. Partly thanks to following you, I've found out a way to get non-blurry background images also with current impress.js. Here's a demo I did to explain how to make a non-blurred background and the pro's and con's of both ways: http://openlife.cc/blogs/2017/october/impressjs-howto-slides-over-background-image

As you've discovered, having a large background image that's also sharp, will use up HUGE amounts of RAM. In fact, images often failed to load for me. So I'm not sure that we could switch to such a solution in impress.js in general, rather it is better that the user use their own CSS to achieve this if they want to. This way they can also manage when they want to consume that much RAM and when not.

Contributor

henrikingo commented Oct 31, 2017

Hi @fin-ger

Thanks for your work on investigating this issue. Partly thanks to following you, I've found out a way to get non-blurry background images also with current impress.js. Here's a demo I did to explain how to make a non-blurred background and the pro's and con's of both ways: http://openlife.cc/blogs/2017/october/impressjs-howto-slides-over-background-image

As you've discovered, having a large background image that's also sharp, will use up HUGE amounts of RAM. In fact, images often failed to load for me. So I'm not sure that we could switch to such a solution in impress.js in general, rather it is better that the user use their own CSS to achieve this if they want to. This way they can also manage when they want to consume that much RAM and when not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment