reducing the dimension of an applet

activity
initial post: 02 dec 2008
last update: 02 dec 2008

I will show in this post how I significantly reduced the size of an applet.
The applet jar was 232 KB plus some libraries I used that sum 1.63 MB.
And after some acrobatics I make it to be 168 KB as jar and 75.2 KB as pack200 pack.gz.

my applet: scanApplet.jar - 232K
Filters.jar - 330K
jxlayer.jar - 50K
swingx-0.9.4.jar - 1,191K
TimingFramework-1.0.jar - 100K


The first method I know was trough obfuscation. I am using ProGuard for this. First I had some problems because of dependencies of swingx but while searching for a solution, I found a tool made public by Romain Guy named Harvester. Here is the link: http://www.jroller.com/gfx/entry/get_what_you_need_from


Very interesting... In the same blog is a link to a similar tool, an ant task named ClassFileSet that is definitely more reliable.


ClassFileSet is inspecting dependencies into a bunch of classes starting from a root class (or multiple root classes), and then is copying the root classes with their dependencies in another directory.


My applet has dependences on swingx.jar, Filters.jar (com.jhlabs), TimingFramework.jar and JXLayer.

So here is for example some ant code that extracts classes from swingx:

<target name="make-swingx">
  <antcall target="clean-dir">
 <param name="param.dirName" value="${dir.applet.deploy}/temp"/>
  </antcall>
  <unzip src="${dir.lib}/swing/swingx-0.9.4.jar" dest="${dir.applet.deploy}/temp"/>
  <copy todir="${dir.applet.deploy}/swingx-tmp">
 <classfileset id="reqdClasses" dir="${dir.applet.deploy}/temp">
   <root classname="org.jdesktop.swingx.JXPanel"/>
   <root classname="org.jdesktop.swingx.painter.BusyPainter"/>
   <root classname="org.jdesktop.swingx.painter.ImagePainter"/>
   <root classname="org.jdesktop.swingx.painter.PainterIcon"/>
   <root classname="org.jdesktop.swingx.painter.TextPainter"/>
   <root classname="org.jdesktop.swingx.graphics.GraphicsUtilities"/>
   <root classname="org.jdesktop.swingx.graphics.ShadowRenderer"/>
   <root classname="org.jdesktop.swingx.image.GaussianBlurFilter"/>
   <root classname="org.jdesktop.swingx.icon.EmptyIcon"/>
 </classfileset>
  </copy>
  <jar jarfile="${dir.applet.deploy}/unsigned/mini-swingx.jar">
 <fileset dir="${dir.applet.deploy}/swingx-tmp">
   <include name="**/*.*"/>
 </fileset>
  </jar>
  <antcall target="clean-dir">
 <param name="param.dirName" value="${dir.applet.deploy}/swingx-tmp"/>
  </antcall>
</target>



After running the task: in swingx-tmp are copied 51 files (144K) from a total of 878 files (2,195 K).

Now my mini-swingx.jar has 78K.
The same with:
Filters.jar from 330K to 10K
jxlayer.jar from 50K to 22K
TimingFramework-1.0.jar from 100K to 36K


In root tags from classfileset are all my imports collected from my applet classes (fortunately not too many).


If you wonder what clean-dir is, here is the code:

    <target name="clean-dir">
        <antcall target="make-writable-dir">
            <param name="param.dirName" value="${param.dirName}"/>
        </antcall>
        <delete failonerror="false" dir="${param.dirName}"/>
        <mkdir dir="${param.dirName}"/>
    </target>

    <target name="make-writable-dir">
        <echo>Make writable: attrib -R ${param.dirName} /S /D</echo>
        <exec executable="attrib">
            <arg line="-R"/>
            <arg line="${param.dirName}\*"/>
            <arg line="/S"/>
            <arg line="/D"/>
        </exec>
    </target>



Here is the main task that I call to make my applet:


<target name="build">
    <antcall target="clean-dir">
        <param name="param.dirName" value="${dir.applet.deploy}"/>
    </antcall>
    <mkdir dir="${dir.applet.deploy}/unsigned"/>
    <mkdir dir="${dir.applet.deploy}/unsigned/obfuscated"/>
    <mkdir dir="${dir.applet.deploy}/signed"/>
    <mkdir dir="${dir.applet.deploy}/distributable"/>

    <antcall target="make-swingx"/>
    <antcall target="make-filters"/>
    <antcall target="make-timing"/>
    <antcall target="make-jxlayer"/>

    <antcall target="make.unsigned-jars"/>
    <antcall target="obfuscate.scan-jar"/>

    <antcall target="normalize.obfuscated-jar"/>

    <antcall target="sign.jar">
        <param name="param.jarsToSign" value="obfuscated/scanui.jar,morena.jar,morena_windows.jar,morena_osx.jar,morena_license.jar"/>
    </antcall>

    <antcall target="pack200.signed-jar"/>

    <!--<antcall target="copy.jar-to-distributable">
        <param name="param.jarsToCopy" value="scanui.jar,morena.jar,morena_windows.jar,morena_osx.jar,morena_license.jar"/>
    </antcall>-->
</target>



So, after extracting only the necessary classes from used libraries, I make my applet jar and copy the dependencies of my dependencies (filters.jar depends on java3d - vecmath.jar, and swingx on MultipleGradientPaint.jar and swing-worker-1.2.jar). I'm not completely sure that this is really necessary. This is done in make.unsigned-jars task:

    <target name="make.unsigned-jars">
        <echo>applet deploy to: ${dir.applet.deploy}</echo>
        <jar jarfile="${dir.applet.deploy}/unsigned/scanui.jar">
            <fileset dir="${dir.classes.compiled-by-ide}">
                <include name="**/*.*"/>
            </fileset>
        </jar>
        <echo>Copy: ${lib.obfuscate},${lib.morena}</echo>
        <copy todir="${dir.applet.deploy}/unsigned" flatten="true">
            <filelist dir="${dir.lib}" files="${lib.obfuscate},${lib.morena}"/>
        </copy>
    </target>



After that I go to the obfuscation:


<target name="obfuscate.scan-jar">
    <echo>OBFUSCATE!!!!!!!!</echo>
    <taskdef resource="proguard/ant/task.properties" classpath="../proGuard/proguard.jar" />
    <proguard printseeds="on" configuration="appletObfuscation.pro">
        <!-- Specify the input jars, output jars, and library jars. -->
        <injar file="${dir.applet.deploy}/unsigned/scanui.jar"/>
        <injar file="${dir.applet.deploy}/unsigned/mini-filters.jar"/>
        <injar file="${dir.applet.deploy}/unsigned/mini-jxlayer.jar"/>
        <injar file="${dir.applet.deploy}/unsigned/mini-timing.jar"/>
        <injar file="${dir.applet.deploy}/unsigned/mini-swingx.jar"/>
        
        <outjar file="${dir.applet.deploy}/unsigned/obfuscated/scanui.jar"/>
        
        <libraryjar file="${dir.applet.deploy}/unsigned/swing-worker-1.2.jar"/>
        <libraryjar file="${dir.applet.deploy}/unsigned/MultipleGradientPaint.jar"/>
        <libraryjar file="${dir.applet.deploy}/unsigned/vecmath.jar"/>
        <libraryjar file="${dir.applet.deploy}/unsigned/morena.jar"/>

        <libraryjar file="${java.home}\lib\resources.jar"/>
        <libraryjar file="${java.home}\lib\rt.jar"/>
        <libraryjar file="${java.home}\lib\jsse.jar"/>
        <libraryjar file="${java.home}\lib\jce.jar"/>
        <libraryjar file="${java.home}\lib\charsets.jar"/>
        <libraryjar file="${java.home}\lib\ext\dnsns.jar"/>
        <libraryjar file="${java.home}\lib\ext\localedata.jar"/>
        <libraryjar file="${java.home}\lib\ext\sunjce_provider.jar"/>
        <libraryjar file="${java.home}\lib\ext\sunmscapi.jar"/>
        <libraryjar file="${java.home}\lib\ext\sunpkcs11.jar"/>
    </proguard>
</target>



Here, again, I added too much jars from jre.
Below are my obfuscation properties:


appletObfuscation.pro
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-forceprocessing
-optimizationpasses 9
-printmapping logs\proguard_printMapping.txt
-overloadaggressively
-verbose

# needed by org.jdesktop.animation.timing.interpolation.PropertySetter
-keepclassmembers class ro.arhinet.scan.ui.effects.message.DetailsView {
    private float alpha;
    public float getAlpha();
    public void setAlpha(float);
}

# needed by org.jdesktop.animation.timing.interpolation.PropertySetter
-keepclassmembers class ro.arhinet.scan.ui.effects.PulsatingBorder {
    private float thickness;
    public float getThickness();
    public void setThickness(float);
}

-keepclassmembers class * extends java.lang.Enum {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# Keep - Applets. Keep all extensions of java.applet.Applet.
-keep public class * extends java.applet.Applet

# Also keep - Serialization code. Keep all fields and methods that are used for
# serialization.
-keepclassmembers class * extends java.io.Serializable {
    static final long serialVersionUID;
    static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# Also keep - Database drivers. Keep all implementations of java.sql.Driver.
-keep class * extends java.sql.Driver

# Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI,
# along with the special 'createUI' method.
-keep class * extends javax.swing.plaf.ComponentUI {
    public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent);
}

# Keep names - Native method names. Keep all native class/method names.
-keepclasseswithmembers,allowshrinking class * {
    native <methods>;
}

# Remove - System method calls. Remove all invocations of System
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.System {
    public static long currentTimeMillis();
    static java.lang.Class getCallerClass();
    public static int identityHashCode(java.lang.Object);
    public static java.lang.SecurityManager getSecurityManager();
    public static java.util.Properties getProperties();
    public static java.lang.String getProperty(java.lang.String);
    public static java.lang.String getenv(java.lang.String);
    public static java.lang.String mapLibraryName(java.lang.String);
    public static java.lang.String getProperty(java.lang.String,java.lang.String);
}

# Remove - Math method calls. Remove all invocations of Math
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.Math {
    public static double sin(double);
    public static double cos(double);
    public static double tan(double);
    public static double asin(double);
    public static double acos(double);
    public static double atan(double);
    public static double toRadians(double);
    public static double toDegrees(double);
    public static double exp(double);
    public static double log(double);
    public static double log10(double);
    public static double sqrt(double);
    public static double cbrt(double);
    public static double IEEEremainder(double,double);
    public static double ceil(double);
    public static double floor(double);
    public static double rint(double);
    public static double atan2(double,double);
    public static double pow(double,double);
    public static int round(float);
    public static long round(double);
    public static double random();
    public static int abs(int);
    public static long abs(long);
    public static float abs(float);
    public static double abs(double);
    public static int max(int,int);
    public static long max(long,long);
    public static float max(float,float);
    public static double max(double,double);
    public static int min(int,int);
    public static long min(long,long);
    public static float min(float,float);
    public static double min(double,double);
    public static double ulp(double);
    public static float ulp(float);
    public static double signum(double);
    public static float signum(float);
    public static double sinh(double);
    public static double cosh(double);
    public static double tanh(double);
    public static double hypot(double,double);
    public static double expm1(double);
    public static double log1p(double);
}

# Remove - Number method calls. Remove all invocations of Number
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.* extends java.lang.Number {
    public static java.lang.String toString(byte);
    public static java.lang.Byte valueOf(byte);
    public static byte parseByte(java.lang.String);
    public static byte parseByte(java.lang.String,int);
    public static java.lang.Byte valueOf(java.lang.String,int);
    public static java.lang.Byte valueOf(java.lang.String);
    public static java.lang.Byte decode(java.lang.String);
    public int compareTo(java.lang.Byte);
    public static java.lang.String toString(short);
    public static short parseShort(java.lang.String);
    public static short parseShort(java.lang.String,int);
    public static java.lang.Short valueOf(java.lang.String,int);
    public static java.lang.Short valueOf(java.lang.String);
    public static java.lang.Short valueOf(short);
    public static java.lang.Short decode(java.lang.String);
    public static short reverseBytes(short);
    public int compareTo(java.lang.Short);
    public static java.lang.String toString(int,int);
    public static java.lang.String toHexString(int);
    public static java.lang.String toOctalString(int);
    public static java.lang.String toBinaryString(int);
    public static java.lang.String toString(int);
    public static int parseInt(java.lang.String,int);
    public static int parseInt(java.lang.String);
    public static java.lang.Integer valueOf(java.lang.String,int);
    public static java.lang.Integer valueOf(java.lang.String);
    public static java.lang.Integer valueOf(int);
    public static java.lang.Integer getInteger(java.lang.String);
    public static java.lang.Integer getInteger(java.lang.String,int);
    public static java.lang.Integer getInteger(java.lang.String,java.lang.Integer);
    public static java.lang.Integer decode(java.lang.String);
    public static int highestOneBit(int);
    public static int lowestOneBit(int);
    public static int numberOfLeadingZeros(int);
    public static int numberOfTrailingZeros(int);
    public static int bitCount(int);
    public static int rotateLeft(int,int);
    public static int rotateRight(int,int);
    public static int reverse(int);
    public static int signum(int);
    public static int reverseBytes(int);
    public int compareTo(java.lang.Integer);
    public static java.lang.String toString(long,int);
    public static java.lang.String toHexString(long);
    public static java.lang.String toOctalString(long);
    public static java.lang.String toBinaryString(long);
    public static java.lang.String toString(long);
    public static long parseLong(java.lang.String,int);
    public static long parseLong(java.lang.String);
    public static java.lang.Long valueOf(java.lang.String,int);
    public static java.lang.Long valueOf(java.lang.String);
    public static java.lang.Long valueOf(long);
    public static java.lang.Long decode(java.lang.String);
    public static java.lang.Long getLong(java.lang.String);
    public static java.lang.Long getLong(java.lang.String,long);
    public static java.lang.Long getLong(java.lang.String,java.lang.Long);
    public static long highestOneBit(long);
    public static long lowestOneBit(long);
    public static int numberOfLeadingZeros(long);
    public static int numberOfTrailingZeros(long);
    public static int bitCount(long);
    public static long rotateLeft(long,int);
    public static long rotateRight(long,int);
    public static long reverse(long);
    public static int signum(long);
    public static long reverseBytes(long);
    public int compareTo(java.lang.Long);
    public static java.lang.String toString(float);
    public static java.lang.String toHexString(float);
    public static java.lang.Float valueOf(java.lang.String);
    public static java.lang.Float valueOf(float);
    public static float parseFloat(java.lang.String);
    public static boolean isNaN(float);
    public static boolean isInfinite(float);
    public static int floatToIntBits(float);
    public static int floatToRawIntBits(float);
    public static float intBitsToFloat(int);
    public static int compare(float,float);
    public boolean isNaN();
    public boolean isInfinite();
    public int compareTo(java.lang.Float);
    public static java.lang.String toString(double);
    public static java.lang.String toHexString(double);
    public static java.lang.Double valueOf(java.lang.String);
    public static java.lang.Double valueOf(double);
    public static double parseDouble(java.lang.String);
    public static boolean isNaN(double);
    public static boolean isInfinite(double);
    public static long doubleToLongBits(double);
    public static long doubleToRawLongBits(double);
    public static double longBitsToDouble(long);
    public static int compare(double,double);
    public boolean isNaN();
    public boolean isInfinite();
    public int compareTo(java.lang.Double);
    public <init>(byte);
    public <init>(short);
    public <init>(int);
    public <init>(long);
    public <init>(float);
    public <init>(double);
    public <init>(java.lang.String);
    public byte byteValue();
    public short shortValue();
    public int intValue();
    public long longValue();
    public float floatValue();
    public double doubleValue();
    public int compareTo(java.lang.Object);
    public boolean equals(java.lang.Object);
    public int hashCode();
    public java.lang.String toString();
}

# Remove - String method calls. Remove all invocations of String
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.String {
    public <init>();
    public <init>(byte[]);
    public <init>(byte[],int);
    public <init>(byte[],int,int);
    public <init>(byte[],int,int,int);
    public <init>(byte[],int,int,java.lang.String);
    public <init>(byte[],java.lang.String);
    public <init>(char[]);
    public <init>(char[],int,int);
    public <init>(java.lang.String);
    public <init>(java.lang.StringBuffer);
    public static java.lang.String copyValueOf(char[]);
    public static java.lang.String copyValueOf(char[],int,int);
    public static java.lang.String valueOf(boolean);
    public static java.lang.String valueOf(char);
    public static java.lang.String valueOf(char[]);
    public static java.lang.String valueOf(char[],int,int);
    public static java.lang.String valueOf(double);
    public static java.lang.String valueOf(float);
    public static java.lang.String valueOf(int);
    public static java.lang.String valueOf(java.lang.Object);
    public static java.lang.String valueOf(long);
    public boolean contentEquals(java.lang.StringBuffer);
    public boolean endsWith(java.lang.String);
    public boolean equalsIgnoreCase(java.lang.String);
    public boolean equals(java.lang.Object);
    public boolean matches(java.lang.String);
    public boolean regionMatches(boolean,int,java.lang.String,int,int);
    public boolean regionMatches(int,java.lang.String,int,int);
    public boolean startsWith(java.lang.String);
    public boolean startsWith(java.lang.String,int);
    public byte[] getBytes();
    public byte[] getBytes(java.lang.String);
    public char charAt(int);
    public char[] toCharArray();
    public int compareToIgnoreCase(java.lang.String);
    public int compareTo(java.lang.Object);
    public int compareTo(java.lang.String);
    public int hashCode();
    public int indexOf(int);
    public int indexOf(int,int);
    public int indexOf(java.lang.String);
    public int indexOf(java.lang.String,int);
    public int lastIndexOf(int);
    public int lastIndexOf(int,int);
    public int lastIndexOf(java.lang.String);
    public int lastIndexOf(java.lang.String,int);
    public int length();
    public java.lang.CharSequence subSequence(int,int);
    public java.lang.String concat(java.lang.String);
    public java.lang.String replaceAll(java.lang.String,java.lang.String);
    public java.lang.String replace(char,char);
    public java.lang.String replaceFirst(java.lang.String,java.lang.String);
    public java.lang.String[] split(java.lang.String);
    public java.lang.String[] split(java.lang.String,int);
    public java.lang.String substring(int);
    public java.lang.String substring(int,int);
    public java.lang.String toLowerCase();
    public java.lang.String toLowerCase(java.util.Locale);
    public java.lang.String toString();
    public java.lang.String toUpperCase();
    public java.lang.String toUpperCase(java.util.Locale);
    public java.lang.String trim();
}

# Remove - StringBuffer method calls. Remove all invocations of StringBuffer
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.StringBuffer {
    public <init>();
    public <init>(int);
    public <init>(java.lang.String);
    public <init>(java.lang.CharSequence);
    public java.lang.String toString();
    public char charAt(int);
    public int capacity();
    public int codePointAt(int);
    public int codePointBefore(int);
    public int indexOf(java.lang.String,int);
    public int lastIndexOf(java.lang.String);
    public int lastIndexOf(java.lang.String,int);
    public int length();
    public java.lang.String substring(int);
    public java.lang.String substring(int,int);
}

# Remove - StringBuilder method calls. Remove all invocations of StringBuilder
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.StringBuilder {
    public <init>();
    public <init>(int);
    public <init>(java.lang.String);
    public <init>(java.lang.CharSequence);
    public java.lang.String toString();
    public char charAt(int);
    public int capacity();
    public int codePointAt(int);
    public int codePointBefore(int);
    public int indexOf(java.lang.String,int);
    public int lastIndexOf(java.lang.String);
    public int lastIndexOf(java.lang.String,int);
    public int length();
    public java.lang.String substring(int);
    public java.lang.String substring(int,int);
}



Note that PropertySetter from TimingFramework use introspection. Also java 5 enums need some special attention. The rest of the properties are copied from ProGuard site.


After obfuscation, my whole jar has 157K.


For Pack200 I followed the examples from: http://java.sun.com/j2se/1.5.0/docs/guide/deployment/deployment-guide/pack200.html.


The sign task is:

<target name="sign.jar">
    <signjar destDir="${dir.applet.deploy}/signed"
        alias="arhinet" keystore="${basedir}/certif/arhinetstore"
        storepass="xxx"
        keypass="xxx"
        preservelastmodified="true">
    <path>
        <filelist dir="${dir.applet.deploy}/unsigned" files="${param.jarsToSign}"/>
    </path>
    <flattenmapper/>
    </signjar>
</target>



And tasks normalize.obfuscated-jar and pack200.signed-jar are simple system calls to ${java.home}/bin/pack200.exe:


<target name="normalize.obfuscated-jar">
    <exec executable="${java.home}/bin/pack200.exe" spawn="true">
        <arg line="--repack ${dir.applet.deploy}/unsigned/obfuscated/scanui.jar"/>
    </exec>
</target>

<target name="pack200.signed-jar">
    <exec executable="${java.home}/bin/pack200.exe" spawn="true">
        <arg line="${dir.applet.deploy}/signed/scanui.jar.pack.gz ${dir.applet.deploy}/signed/scanui.jar"/>
    </exec>
</target>



After signing the jar, my applet is approximately the same size: 168K.
But the pack.gz file is very small: scanui.jar.pack.gz - 75K

And miraculously the applet is still working!

swing page transition (fade in / out)

activity
initial post: 01 dec 2008
last update: 02 dec 2008

Few months ago I wanted some kind of page transition effect for a wizard I made in swing (an applet).
I found a nice effect in examples provided with an older release of JXLayer (from dev.java.net). The demo is still available at http://swinghelper.dev.java.net (the TabbedPaneAnimationDemo.jnlp link).
Now the project is stand alone (http://jxlayer.dev.java.net) and in version 3 was a major api change but the demo was not ported to the new JXLayer api.

I make some tries to port the TabbedPaneAnimationDemo to latest JXLayer, I failed, and I decide to implement the effect myself.

Here is my approach:

I have:

1. a container(a JXPanel that I named FadeEffectTransitionContainer) on with I set as layout a CardLayout. - This is the container that holds all wizard pages and the pages are switched with CardLayout.show().
2. and wizard pages that are also JXPanels.

The effect I wanted to be the same as that provided by TabbedPaneAnimationDemo: old wizard page disappear with diminishing alpha while the new page appear with increasing alpha.

For a while I was trying to override paint (or paintComponents) in main panel and from this method to call paint on child panels. I was not able to make it work in that way. So I will tell you how I finally make this effect.

When the page in wizard is about to change, I make 2 screenshots of the 2 panels involved in page transition:

private BufferedImage takePicture(JXPanel aPanel){
    BufferedImage image = createBuffer(aPanel.getWidth(), aPanel.getHeight());
    Graphics g = image.getGraphics();
    aPanel.paint(g);
    g.dispose();
    return image;
}

private BufferedImage createBuffer(int width, int height) {
    return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}


And this 2 images are painted on the FadeEffectTransitionContainer in paint(Graphics g). First the old panel(the picture of it) then the new panel with increasing alpha.

Here is a screen-shot:



I see that with some containers (JPanel, JXRadioGroup etc.) added in my pages, if I make them transparent (setOpaque(false)), I have some garbage on them when I make the images. So I make them opaque. And after that I realize that I don't need to make a diminishing alpha on the old page that disappear because the page that appear been opaque I get the same effect.


Here is the main container of the wizard:

FadeEffectTransitionContainer.java
package ro.arhinet.scan.ui.effects.transition;

import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.math.BigDecimal;
import org.jdesktop.swingx.JXPanel;

/**
 *
 * @author Mihai Vasilache
 */
public class FadeEffectTransitionContainer extends JXPanel{
    private boolean isPaintingEffects;

    public BufferedImage oldPanelPicture;
    public BufferedImage newPanelPicture;

    private BigDecimal alpha = new BigDecimal("1");;
    
    @Override
    public void paint(Graphics g){
        if( ! isPaintingEffects){
            super.paint(g);
            return;
        }

        eraseBackground(g);
        doCustomPaint(g);
    }

    private void eraseBackground(Graphics g){
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
    }

    private void doCustomPaint(Graphics g_param){
        //paint old panel with no alpha
        Graphics2D g2 = (Graphics2D) g_param.create();
        g2.drawImage(oldPanelPicture, 0, 0, null);
        g2.dispose();

        //paint the new panel on top of old panel
        //because of panels been opaque the old panel apear with dimishing alpha
        Graphics2D g3 = (Graphics2D) g_param.create();
        setAlphaOnGraphics((Graphics2D)g3);
        g3.drawImage(newPanelPicture, 0, 0, null);
        g3.dispose();
    }

    protected void setAlphaOnGraphics(Graphics2D g2) {
        BigDecimal negativeAlpha = new BigDecimal("1").subtract(alpha);
        Composite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, negativeAlpha.floatValue());
        g2.setComposite(composite);
    }

    public void startPaintingEffects(){
        isPaintingEffects = true;
    }

    public void stopPaintingEffects(){
        isPaintingEffects = false;
    }

    public void setOldPanelPicture(BufferedImage thePicture){
        oldPanelPicture = thePicture;
    }

    public void setNewPanelPicture(BufferedImage thePicture){
        newPanelPicture = thePicture;
    }

    public void setAlphaTransition(BigDecimal alpha) {
        this.alpha = alpha;
    }

    public BigDecimal getAlphaTransition() {
        return alpha;
    }
}



And this is the class that controls the alpha and has a timer to apply the effect:

PanelChangeAnimator.java
package ro.arhinet.scan.ui.effects.transition;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.math.BigDecimal;
import javax.swing.Timer;
import org.jdesktop.swingx.JXPanel;

public class PanelChangeAnimator {

    private Timer timer;
    private float delta;
    private BigDecimal alpha = new BigDecimal("1");

    JXPanel oldPanel;
    JXPanel newPanel;
    FadeEffectTransitionContainer effectsContainer;
    private static PanelChangeAnimator instanceRunning;

    public PanelChangeAnimator() {
        this(50, .05f);
    }

    private PanelChangeAnimator(int delay, final float delta) {
        setDelta(delta);
        timer = new Timer(delay, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (effectsContainer.getAlphaTransition().floatValue() == 0.0f) {
                    timer.stop();
                    effectsContainer.stopPaintingEffects();
                    synchronized(PanelChangeAnimator.class){
                        instanceRunning = null;
                    }
                    return;
                }
                alpha = alpha.subtract(new BigDecimal(String.valueOf(delta)));
                BigDecimal negativeAlpha = new BigDecimal("1").subtract(alpha);
                effectsContainer.setAlphaTransition(alpha);
                //otherwise the child components of the new Panel (visible one
                //in CardLayout) paints themselfs with alpha=1 (on mouse over)
                newPanel.setAlpha(negativeAlpha.floatValue());
                effectsContainer.repaint();
            }
        });
    }

    public void setDelta(float delta) {
        if (delta <= 0 || delta > 1) {
            throw new IllegalArgumentException();
        }
        this.delta = delta;
    }

    private boolean stopPrevious(){
        synchronized(PanelChangeAnimator.class){
            if (instanceRunning == null){
                instanceRunning = this;
                return false;
            }

            instanceRunning.timer.stop();
            instanceRunning.effectsContainer.stopPaintingEffects();
            instanceRunning.newPanel.setAlpha(1f);
            instanceRunning = this;
            return true;
        }
    }

    public void changePanels(JXPanel newPanel, JXPanel oldPanel, FadeEffectTransitionContainer container) {
        stopPrevious();
        this.effectsContainer = container;
        this.oldPanel = oldPanel;
        this.newPanel = newPanel;
        this.alpha = new BigDecimal("1");

        takePicturesOfPanels();
        
        effectsContainer.setAlphaTransition(alpha);
        effectsContainer.startPaintingEffects();
        effectsContainer.repaint();
        timer.start();
    }

    private void takePicturesOfPanels(){
        boolean oldPanelOldVisible = oldPanel.isVisible();
        float oldPanelOldAlpha = oldPanel.getAlpha();
        oldPanel.setVisible(true);
        oldPanel.setBackground(Color.WHITE);
        oldPanel.setOpaque(true);
        oldPanel.setAlpha(1f);
        effectsContainer.setOldPanelPicture(takePicture(oldPanel));
        oldPanel.setVisible(oldPanelOldVisible);
        oldPanel.setAlpha(oldPanelOldAlpha);

        boolean newPanelOldVisible = newPanel.isVisible();
        float newPanelOldAlpha = newPanel.getAlpha();
        newPanel.setVisible(true);
        newPanel.setBackground(Color.WHITE);
        newPanel.setOpaque(true);
        newPanel.setAlpha(1f);
        effectsContainer.setNewPanelPicture(takePicture(newPanel));
        newPanel.setVisible(newPanelOldVisible);
        newPanel.setAlpha(newPanelOldAlpha);

//      try{
//          String fname = System.currentTimeMillis() + "";
//          ImageIO.write(effectsContainer.b1, "gif", new File("c:\\TEMP\\io\\" + fname + ".gif"));
//          ImageIO.write(effectsContainer.b2, "gif", new File("c:\\TEMP\\io\\" + fname + "_2.gif"));
//      }catch(Exception e){
//          e.printStackTrace();
//          throw new RuntimeException(e);
//      }
    }

    private BufferedImage takePicture(JXPanel aPanel){
        BufferedImage image = createBuffer(aPanel.getWidth(), aPanel.getHeight());
        Graphics g = image.getGraphics();
        aPanel.paint(g);
        g.dispose();
        return image;
    }

    protected BufferedImage createBuffer(int width, int height) {
        return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    }


}


And pages are changed with these lines of code:

    ((CardLayout) container.getLayout()).show(container,  newPanelName);
    new PanelChangeAnimator().changePanels(newPanel, currentPanel, container);


And of course, the container is built in that way:

    CardLayout cardLayout = new CardLayout();
    container = new FadeEffectTransitionContainer();
    container.setOpaque(true);
    container.setBackground(Color.WHITE);
    container.setLayout(cardLayout);
    ....
    container.add(aStepPanel, aStepName);
    ....

table of contents

activity
initial post: 01 aug 2008
last update: 01 aug 2008
Working on it

programming resources

activity
initial post: 01 aug 2008
last update: 01 aug 2008
Comming soon...

gui resources

activity
initial post: 14 iul 2008
last update: 01 aug 2008
This page links to guys that i found on the internet and have interesting things about gui on their sites.

layout
  • Jenifer Tidwell - Designing Interfaces: Patterns for Effective Interaction Design. this book is a very good resource about organizing gui layout.
  • another resource i found is on a PowerBuilder site: Ken Howe's GUI Standards
  • an eyetracking study on serch forms on dev.uxmatters.com.
design patterns frameworks

flex swing