Wednesday Aug 26, 2009

Get a swing component rendered to an image

Today my friend contacted me with an issue he faced with his program in swing. He wanted to get a swing component painted on to an image, except that he didn't want to show the component on screen. This was the code snippet he sent me:


    JPanel panel = new JPanel();
    panel.setSize(200, 200);
    panel.setBackground(Color.BLUE);
    panel.setLayout(new BorderLayout());
    panel.add(new JLabel("Hello"));

    //Get the panel rendered to an image
    BufferedImage image = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
    Graphics g = image.createGraphics();
    panel.paintAll(g);
    g.dispose();
    ImageIO.write(image, "jpeg", <file>);
    image.flush();
  

Now, if the JPanel was added to a JFrame and shown, all worked well - the JPanel along with the JLabel in it was being correctly captured as image. But, when the JPanel wasn't added anywhere, only the blue background of JPanel was being captured as image. 

This seems to be a painting optimization being done in java. As per the documentation of Component.paint method


    For performance reasons, Components with zero width or height aren't 
    considered to need painting when they are first shown, and also aren't 
    considered to need repair.
  

My colleague Praveen came up with a workaround for this. The code was changed as follows and that solved the issue. Now, the JPanel along with its content is being captured correctly.


    Graphics g = image.createGraphics();
    panel.addNotify();
    panel.validate();
    panel.paintAll(g);
    g.dispose();
  

This seems to work in headless mode too.

Monday Aug 03, 2009

Reflection in JavaFX

Last week I was trying to learn how to use JavaFX's reflection APIs - I was working on a test framework and had to use reflection to run a test. My requirement was quite simple: I had to instantiate a JavaFX class using reflection and had to call one of its functions. I didn't realize its difficult until I went through JavaFX's reflection api. I was expecting an api set similar to Java's reflection api, but I was wrong. I couldn't find much documentation on using it either. The sole blog I could find on reflection, didn't talk about what I was looking for. So, I finally had to experiment with reflection API to figure out how it works. I'm just jotting down what I learned, may be it could help someone taking the same route.

Here is a sample JavaFX class.


    public class ClassA {
        public var count: Integer;
        init {
            println("ClassA init");
        }
        public function methodA(): Void {
            println("methodA called");
        }
        public function methodB(str: String, num: Integer): Integer {
            println("methodB called {str} {num}");
            return 20;
        }
        public function methodC(): Number[] {
            println("methodC");
            return [10.0, 100.6, 1000.8];
        }
        public function methodD(num: Integer, seq: String[]): Void {
            println("methodD called {num} {sizeof seq}");
        }
    }
  

The classes for JavaFX reflection api is located in javafx.reflect package. For reflective operations you need an instance of 'FXContext' to operate on. In order to access objects and types in the current JVM, you have 'FXLocal.Context', which is an implementation of 'FXContext' using Java reflection (as per the API doc). I believe FXLocal.Context is what we need to use.


    var context = FXLocal.Context.getInstance();
  

Below code is self explanatory. I've given some comments on what they do, or rather my understanding of what they do :-)


    //This returns an instance of FXLocal.ClassType, a subclass of FXClassType.
    var cls = context.findClass("ClassA");

    //Creates an instance of 'ClassA'
    var objValue = cls.newInstance();
  

The above 'newInstance' call creates an instance of 'ClassA'. But the same can be done in two steps as below


    //A raw uninitialized object is returned by 'allocate()' function.
    var objValue = cls.allocate();

    //'initialize()' function of FXObjectValue makes the 'init' of the class to be called.
    objValue = objValue.initialize();
  

This is beneficial because you may need to initialize any variables of the class before it's 'init' is called, as follows:


    var objValue = cls.allocate();
    var num: Integer = 10;
    objValue.initVar("count", context.mirrorOf(num));
    objValue = objValue.initialize();
  

Note that the above 'objValue' is not a JavaFX 'object', but an instance of 'FXLocal.ObjectValue'. You can get an object from 'objValue', cast it to a class you want. That is covered in Jim's blog.

Now, we can look at how to call functions of this class⁞. Let us see how to call 'methodA()' which neither has any arguments nor any return value. 'getFunction' method of FXClassType accepts method name and a sequence of argument types of the function. Since 'methodA' is a no args function, an empty sequence is passed.


    var fnA = cls.getFunction("methodA", []);
    fnA.invoke(objValue, []);
  
Below is how the function 'methodB' is called. The function accepts two arguments, a String and an Integer. FXContext has methods which return the 'FXType' of different data types.


    var argString = "argument";
    var number = 20;
    var fnB = cl.getFunction("methodB", [context.getStringType(), context.getIntegerType()]);
    var retVal = fnB.invoke(ni, [context.mirrorOf(argString), context.mirrorOf(number)]);
  

It can be seen that methodB returns an Integer. It is fairly simple to retrieve the return value from FXValue.


    var retInt = Integer.parseInt(retVal.getValueString());
  

We also have a function 'methodC' which returns a Number sequence. This is how the return value is obtained from the FXValue


    var fnC = cl.getFunction("methodC", []);
    var fnCVal = fnC.invoke(ni, []);
    if (fnCVal.getItemCount() > 0) {
        for (i in [0..fnCVal.getItemCount() - 1) {
            var f = Float.parseFloat(fnCVal.getItem(i).getValueString());
            println("Value at index {i} is {f}");
        }
    }
  

I couldn't get to call a function which accepts a sequence argument. Somehow, 'getFunction' throws a ClassCastException for me. I tried both these ways, but both threw CCE for me.


    var inputSeq = ["ABC", "DEF", "GHI"];
    var fnD = cl.getFunction("methodD", [context.getIntegerType(), context.getStringType().getSequenceType()]);
    
    //or
    
    var valSeq = context.makeSequenceValue(for (i in inputSeq) context.mirrorOf(i), sizeof inputSeq, context.getStringType());
    var fnD = cl.getFunction("methodD", [context.getIntegerType(), valSeq.getType()]);
  

I'm still trying to figure out the right way to do it - or it could be a bug!

Update: This in fact turned out to be a JavaFX bug: see JFXC-3381. For now, you can loop through all the functions returned by 'getFunctions' method to find the method you are looking for, though I'm not sure if calling that method will work without issues.