Search code examples
htmlcssextjsjavafxsencha-touch

JavaFX WebView doesn't display masked images in Sencha Touch app


I am working on a JavaFX application which uses the JavaFX WebView browser to display a Sencha Touch webapp. Long story short, the Sencha Touch application was originally being accessed through a different browser, but in the future it needs to be accessed on a device running the JavaFX WebView. This makes things a little convoluted, but it's generally been a pretty seamless transition.

The issue I'm having comes from our use of the -webkit-mask-image CSS property. We were using this property to quickly color a number of buttons and other images to fit in with whatever coloring scheme was being used. Unfortunately, the JavaFX WebView seems pretty confused about what to do with -webkit-mask-image, and all those images were being distorted.

I did my homework and came up with this blog post which describes how to achieve effects similar to -webkit-mask-image in other browsers, using svg's foreignObject.

As per that article, I added the following html to my Sencha Touch application just to see how it would render in the JavaFX WebView:

<svg width="400px" height="300px">
  <defs>
    <mask id="mask" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">
      <image width="400px" height="300px" xlink:href="foo.png"></image>
    </mask>
  </defs>
  <foreignObject width="400px" height="300px" style="mask: url(#mask);">
    <div style="display:inline-block;background-color:blue;width:400px;height:300px">
    </div>
  </foreignObject>
</svg>

The masking image "foo.png" is a white image with some transparencies. When we were using -webkit-mask-image, the mask image was black, but I inverted it as per the blog post's advice. This actually worked beautifully in Firefox, which I was hoping was good news (because previously our image masking wasn't working in Firefox). Firefox displayed a nice blue div masked by "foo.png". My long nightmare was over!

Except it wasn't. In the JavaFX WebView, it just displays a blue box. To make sure it had access to "foo.png", I tried it as just a regular old <img> tag, and that displays the image, so that's not the issue.

I did find this bug on OpenJDK, which sounds pretty similar to my issue, although it's still listed as "open" and didn't lead me very far.

Also, I found this question in which the author is trying to do something similar. The difference between this and what I'm trying to do is that, in that solution, he wants to display in image and mask/show certain parts of that image. What I'm trying to do is have a colored div, masked by an image with transparencies. Here's an example:

.something-red {
  background-color: red;
  width: 400px;
  height: 365px;
  -webkit-mask-size: 400px;
  -webkit-mask-image: url(http://i.imgur.com/cWSCfGl.png);
}
<div class='something-red'>
</div>

My temporary solution has been to replace our image masks with static images that have been pre-colored (by me). This doesn't look as good as it did when we were able to mask images, and of course it also means I'd have to create duplicate images with different colors for any color scheme we wanted to use.

Is there a way to achieve functionality like -webkit-mask-image in the JavaFX WebView browser? Thank you so much for taking the time to read my question, hopefully I've provided enough information!


Solution

  • I found a way to simulate SVG masking in the JavaFX browser. Instead of trying to force support of -webkit-mask-image, I found a browser polyfill to enable the type of SVG masking described in the article I referenced in the question.

    The Modernizr polyfill collection includes a myriad of javascript libraries meant to help add cross-browser support for several features. After trying a few of these, I settled on canvg, which translates an SVG definition to a canvas element, with objects drawn on it. Take a look at his example page to see it in action - it lets you edit the SVG input, so you can see if the code works for you.

    So, to implement this solution, you have to do four things. First, create a canvas element wherever you need your image to be. Note that you should probably provide it with a width and height, whatever the size of your image is.

    <canvas id='foo'></canvas>
    

    Next, grab a reference to your canvas. This will be used by the canvg library.

    var canvas = document.getElementById('foo');
    

    Third, create your svg. The canvg supports a few different formats, but I'm just providing it as an XML string:

    var svg =
            '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">' +
                '<defs>'+
                  '<mask id="Mask" maskUnits="userSpaceOnUse">'+
                    '<image width="100" height="100" xlink:href="fooBar.png"></image>' +
                  '</mask>'+
                  '<rect id="rect" x="0" y="0" width="100" height="100" style="stroke-width:0px"/>'+
                '</defs>'+
                '<use xlink:href="#rect" mask="url(#Mask)" fill="red"/>'+
            '</svg>';
    

    Note that this is doing something very similar to the SVG masking article I referenced in my question, but it's not exactly the same. It defines a mask, then a rectangle, then simply masks the rectangle.

    The last thing you do is call canvg like so:

    canvg(canvas, svg);
    

    This should enable image masking in the JavaFX browser, but since I asked specifically about Sencha Touch, I'll mention how I added this to my Sencha Touch application.

    After your element is rendered on the screen, grab a reference to it using Ext's component query:

    var component = Ext.ComponentQuery.query('#itemId');

    Next, generate your canvas HTML element as described above. Then, do the following:

    component.setHtml(yourCanvasHtmlElement);
    

    Now, your component contains an empty canvas element. That's the element that canvg will be using. Make the call to canvg as described above, and your canvas element will update.