Enzo

Wednesday, May 16, 2012

Yes...we can...

A long long time ago i wrote the last blog post but now i'm back...
Some of you might already know that i've changed my job (again) and now am working for canoo engineering in Basel (Switzerland).
To make it short...i really love it :)


Yesterday i was playing around with the new release of Adobe Fireworks CS6 to learn about the new features of the new version. Because i'm working on JavaFX 2.x now a lot i had a special interest in the new CSS capabilities of Fireworks CS6 to check if they might be of use for my JavaFX development. 
As an very small example i will take the following image (you might know it from the iPhone):




If you take a look at the design in Adobe Fireworks it's made out of three shapes:

  • background
  • gloss
  • arrow



If i would like to convert this image to JavaFX i could do this by using my own FXG Converter but that would mean i have to use the FXG file format which is only available on Adobe products. So to make this blogpost more usefull i decided to stick to a more widely used format...SVG.
To be able to export SVG from Fireworks one has to install a little extension created by Aaron Beall which is named Export and could be found here. (This extension will also work on Fireworks CS5)
Now with this extension in place i exported the SVG file of the image and got the following file:



<?xml version="1.0" standalone="no"?>
<!-- Generator: Adobe Fireworks CS6, Export SVG Extension by Aaron Beall (http://fireworks.abeall.com) . Version: 0.6.0  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="" viewBox="0 0 128 128" style="background-color:#ffffff00" version="1.1"
     xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
     xml:space="preserve" x="0px" y="0px" width="128px" height="128px">
  <defs>
    <filter id="filter1" x="-100%" y="-100%" width="300%" height="300%">
      <!-- Drop Shadow -->
      <feOffset result="out" in="SourceGraphic" dx="-1.2941" dy="-4.8296"/>
      <feColorMatrix result="out" in="out" type="matrix" 
                     values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.451 0"/>
      <feGaussianBlur result="out" in="out" stdDeviation="3"/>
      <feBlend in="SourceGraphic" in2="out" mode="normal" result="Drop_Shadow1"/>
    </filter>
  </defs>
  <path id="background" 
        d="M 62.6381 6.8085 C 31.0529 6.8085 5.447 32.4156 5.447 64 
           C 5.447 66.482 5.6577 68.9089 5.9636 71.3072 C 9.5577 99.4342 33.5351 
           121.1915 62.6381 121.1915 C 91.7415 121.1915 115.7188 99.4342 119.313 
           71.3072 C 119.6185 68.9089 119.83 66.482 119.83 64 C 119.83 32.4156 
           94.2232 6.8085 62.6381 6.8085 Z" 
        stroke="#ffffff" 
        stroke-width="11" 
        fill="#6096e3"/>
  <path id="gloss" 
        d="M 62.6381 114.383 C 88.9706 114.383 110.6626 94.6968 113.9152 69.2487 C 
           100.6936 60.109 82.6032 54.4681 62.6381 54.4681 C 42.673 54.4681 24.5821 
           60.109 11.3617 69.2487 C 14.6139 94.6968 36.3059 114.383 62.6381 114.383 
           Z" 
        fill="#236ed8"/>
  <path id="arrow" 
        filter="url(#filter1)" 
        d="M 44.7016 41.0951 L 69.3251 65.7207 L 44.5005 90.2008 L 55.7281 101.4289 
           L 92.0866 65.0698 L 56.5768 29.5638 L 44.7016 41.0951 Z" 
        fill="#ffffff"/>
</svg>



So you might ask yourself why i choosed svg for the conversion, well there's a really simple reason named SVGPath. With this Object you will be able to simple copy the path definition from the svg file and paste it as a string into your JavaFX code. The arrow of the example would look like this:



String arrow = "M 44.7016 41.0951 L 69.3251 65.7207 L 44.5005 90.2008 L 55.7281 
                101.4289 L 92.0866 65.0698 L 56.5768 29.5638 L 44.7016 41.0951 Z";        
SVGPath arrowShape = SVGPathBuilder.create()
                                   .content(arrow)                                        
                                   .build();

That's easy isn't it? But now we also need to style these shapes with css and that's unfortunately not as easy as the shape "conversion". Well it's still easy enough but you can't simply copy paste some code. This is because the css definition in JavaFX has it's own syntax (which is nothing special if you take a look at all the different browser vendors which do exactly the same).


In Adobe Fireworks CS6 you will find support for CSS in a way that makes working with stylesheets a charm. For the background in our example it will look like this:




You could see that i've selected the background circle and the CSS Properties window shows the css style definition for the selected shape. But if you know the JavaFX css definitions you will also see that these are different and that you have to modify them manually which will lead to this:


-fx-fill        : rgb(96, 150, 227);
-fx-stroke      : rgb(255, 255, 255);
-fx-stroke-width: 11px;


This information is also in the svg file and could be taken from there too which means it's up to you where you take it from.
The advantage of the css support in Adobe Fireworks CS6 is more that you could modify the drawing and directly get the css style information like colors and gradients.


In the end the conversion of the drawing led to the following JavaFX code:



Pane pane = new Pane();


String background       = "M 62.6381 6.8085 C 31.0529 6.8085 5.447 32.4156 5.447 64 
                           C 5.447 66.482 5.6577 68.9089 5.9636 71.3072 C 9.5577 
                           99.4342 33.5351 121.1915 62.6381 121.1915 C 91.7415 
                           121.1915 115.7188 99.4342 119.313 71.3072 C 119.6185 
                           68.9089 119.83 66.482 119.83 64 C 119.83 32.4156 94.2232 
                           6.8085 62.6381 6.8085 Z";
String backgroundStyle  = "-fx-fill        : rgb(96,150,227);" +
                          "-fx-stroke      : rgb(255,255,255);" +
                          "-fx-stroke-width: 11px;";
SVGPath backgroundShape = SVGPathBuilder.create()
                                        .content(background)
                                        .style(backgroundStyle)
                                        .build();


String gloss       = "M 62.6381 114.383 C 88.9706 114.383 110.6626 94.6968 113.9152 
                      69.2487 C 100.6936 60.109 82.6032 54.4681 62.6381 54.4681 C 
                      42.673 54.4681 24.5821 60.109 11.3617 69.2487 C 14.6139 
                      94.6968 36.3059 114.383 62.6381 114.383 Z";
String glossStyle  = "-fx-fill  : rgb(35,110,216);" +
                     "-fx-stroke: transparent;";
SVGPath glossShape = SVGPathBuilder.create()
                                   .content(gloss)
                                   .style(glossStyle)
                                   .build();


String arrow       = "M 44.7016 41.0951 L 69.3251 65.7207 L 44.5005 90.2008 L 
                      55.7281 101.4289 L 92.0866 65.0698 L 56.5768 29.5638 L 
                      44.7016 41.0951 Z";
String arrowStyle  = "-fx-fill  : rgb(255,255,255);" +
                     "-fx-stroke: transparent;" +
                     "-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.35), 4, 0.1, 
                                             -2px, -4px);";
SVGPath arrowShape = SVGPathBuilder.create()
                                   .content(arrow)
                                   .style(arrowStyle)
                                   .build();


pane.getChildren().addAll(backgroundShape, glossShape, arrowShape);



And here you will see the original (left) and the converted version (right):




So if you have to convert a shape from your drawing to code you might want to use SVGPath which makes this task a piece of cake.


The nice thing about this approach is that once the path is a SVGPath you could do everything with it that works on javafx.scene.shape.Shape like scaling, adding effects etc.


And there's one more thing that might be interesting for you...one could use the SVG paths directly in css. This will allow you to separate the visualization completely from the code. In this case you would only define a StackPane in the code and apply a style to the stackpane object. The appearance of this stackpane object is then completely taken from the given css definition.
The code for the example above would than look like this:



Pane pane = new Pane();


StackPane backgroundPane   = new StackPane();
String backgroundPaneStyle = 
    "-fx-background-color  : transparent, rgb(255,255,255), rgb(96,150,227);" +
    "-fx-shape             : \"M 62.6381 6.8085 C 31.0529 6.8085 5.447 32.4156 
                               5.447 64 C 5.447 66.482 5.6577 68.9089 5.9636 
                               71.3072 C 9.5577 99.4342 33.5351 121.1915 62.6381 
                               121.1915 C 91.7415 121.1915 115.7188 99.4342 119.313 
                               71.3072 C 119.6185 68.9089 119.83 66.482 119.83 64 C 
                               119.83 32.4156 94.2232 6.8085 62.6381 6.8085 Z\";" +
    "-fx-background-insets : 0, 0, 11;" +
    "-fx-padding           : 0 128 128 0;";
backgroundPane.setStyle(backgroundPaneStyle);


StackPane glossPane   = new StackPane();
String glossPaneStyle = 
    "-fx-background-color: transparent, transparent, rgb(35,110,216);" +
    "-fx-shape           : \"M 62.6381 114.383 C 88.9706 114.383 110.6626 94.6968 
                             113.9152 69.2487 C 100.6936 60.109 82.6032 54.4681 
                             62.6381 54.4681 C 42.673 54.4681 24.5821 60.109  
                             11.3617 69.2487 C 14.6139 94.6968 36.3059 114.383 
                             62.6381 114.383 Z\";" +
    "-fx-padding         : 0 103 60 0;";
glossPane.setStyle(glossPaneStyle);
glossPane.relocate(13, 56);


StackPane arrowPane = new StackPane();
String arrowPaneStyle =
    "-fx-background-color: transparent, transparent, white;" +
    "-fx-shape  : \"M 44.7016 41.0951 L 69.3251 65.7207 L 44.5005 90.2008 L 55.7281 
                    101.4289 L 92.0866 65.0698 L 56.5768 29.5638 L 44.7016 41.0951 
                    Z\";" +
    "-fx-padding: 0 48 73 0;" +       
    "-fx-effect : dropshadow(gaussian, rgba(0,0,0,0.35), 4, 0.1, -2px, -4px);";
arrowPane.setStyle(arrowPaneStyle);
arrowPane.relocate(45, 30);


pane.getChildren().addAll(backgroundPane, glossPane, arrowPane);



And to be complete again a comparison between the original and the style based approach:








Keep in mind that you would usually store the style information in a separate css file instead of using it inline.


That's it so far...i hope this post will be usefull for one or the other...




Keep coding...

12 comments:

  1. Very interesting! Thx.

    ReplyDelete
  2. Не плохо. Ещё бы автоматизировать этот процесс (написать программу, которая на вход получает svg код и выдаёт java код) - было бы вообще шикарно.))

    ReplyDelete
    Replies
    1. Yes you are right and as far as I know it exists something like that in the e(fx)clipse project already which could be found on github.

      Delete
    2. Yes - e(fx)clipse (http://efxclipse.org) has support for this. There's already a ticket to make this available as an selfcontained set of jars so that one can e.g. use it in Netbeans as well (http://efxclipse.org/trac/ticket/91)

      Delete
  3. Hi, I really love your work and i will put it in my next java project (an html parser).
    I have a question:
    where i can found the test source code for realizing the sweap effect of a radar with java swing ?

    Thanks so much

    ReplyDelete
    Replies
    1. Hi Giordano,
      Well that's an easy one. You simply have to create a ConicalGradientPaint which is part of the SteelSeries lib. So if you check out the sources at kenai.com you will find the sweep effect in the source of the radar class.
      Cheers,
      Gerrit

      Delete
    2. Thanks Han.Solo, i've tried to search on kenai but (i'm not lazy, i've really look for it) but i didn't find nothing:
      Which file have i need to download exactly? Can you write me the exact url??
      I see many different version, from 1.3 to 3.4 ?
      In which of them will i find the radar example?

      Thanks again

      Delete
    3. You just have to search for SteelSeries and check out the latest version in the v3 repo. There you will find the Radar class that contains the code you're looking for, should not be a big deal.
      Cheers,
      Gerrit

      Delete
  4. Ok, Thanks, i will work on it... looking the way how to animate in a Swing JPanel

    ReplyDelete
  5. Hi,
    What changes would be needed to make the shape resize itself when the stackpane is resized.

    ReplyDelete
  6. Very clear post! Thanks man!

    ReplyDelete