//Author: Andrei Borziak, 
//Copyright © 2003.  All rights reserved.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import java.io.*;
import java.util.Vector;

/**
 * random images generator
 */
public class JMemoryImage extends JApplet implements ActionListener 
{
	private boolean     m_bApplet= true;//false when application
	private JComboBox   m_cbChoices;    //image type combo box
	private JComboBox   m_cbTrans;      //transform type combo box
	private JLabel      m_lbIcon;       //label used to display an image
	private JButton     m_btnSave;      //"save" button
	private JButton     m_btnRefresh;   //"refresh" button
	private JTextField  m_txtColor;     //text field for color
	private JTextField  m_txtNoiseMax;  //text field for noise max value
	private JTextField  m_txtNoiseLike; //text field for noise probability
	private JCheckBox   m_chkAnim;      //check box for animation
	private JCheckBox   m_chkRota;      //check box for random rotation
	private int         m_iStyle = 5;   //image style
	private int         m_w = 256;      //width of image
	private int         m_h = 256;      //height of image
	private int[]       m_pix;          //array of pixels
	private Image       m_img;          //to build the image
	private int         m_iSave = 0;    //index of saved file
	private int         m_iTransform= 0;//tranform style
	private boolean     m_bRotate = false;//if true add rotatation
	private double      m_a11 = 1.0, m_a12 = 0.0, m_a21 = 0.0, m_a22 = 1.0, 
	                    m_x0 = 0.0, m_y0 = 0.0;         //transform coefficients
	private double      m_a11bz = 1.0, m_a12bz = 0.0, m_a21bz = 0.0, m_a22bz = 1.0, 
	                    m_x0bz = 0.0, m_y0bz = 0.0;     //initial rotation coefficients
	private double      m_phase1, m_phase2, m_phase3, m_phase4; //random phases
	private double      m_frequ1, m_frequ2, m_frequ3, m_frequ4; //random frequencies
	private double      m_trXM, m_trYM, m_trW, m_trH;   //"heavy" transform parameters
	private Color       m_clrMain = Color.red;          //main drawing color
	private Color       m_clrBack = Color.red;          //second drawing color
	private boolean     m_bGetNewColor = true;          //if true get new color
	private double      m_vx, m_vy;                     //velocity for animation
	private Timer       m_timer;                        //animation timer
	final private int   RAD_NUM = 200;                  //number of radius
	private double      m_radius[]= new double[RAD_NUM];//array of radius
	private double      m_iBorderSharp = 5.0;           //border sharpness
	private double      m_dNoiseMax = 0.1;              //maximum noise value, 0.5 makes multi-colored
	private double      m_dNoiseLikely = 0.3;           //noise probability, 0.8 adds slight variations 
	private double      m_a, m_b, m_c, m_cmax;          //line equation coefficients
	private Vector      m_vecClouds = new Vector();     //vector of Cloud objects
    
	public void init()
	{
		m_pix = new int[ m_w * m_h ];

		// This line prevents the "Swing: checked access to system event queue" message seen in some browsers.
		getRootPane().putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);
		
		getContentPane().setLayout(null);
		Dimension sz = getSize();
		if( sz.width == 0 && sz.height == 0 )
		{
		    sz.width = Math.max( 470, m_w + 200 );
		    sz.height = Math.max( 370, m_h + 80 );
		    setSize( sz.width, sz.height );
		}
		//first column of controls				
		m_lbIcon = new JLabel();
		m_lbIcon.setBounds( 10, 70, m_w, m_h );
		getContentPane().add( m_lbIcon );

		JLabel lb4 = new JLabel( "Shape" );
		lb4.setBounds( 10, 10, 70, 20 );
		getContentPane().add( lb4 );
		String[] phases = { "RGB colors", "circles", "arrows", "strips", 
			"random squares", "blue sky", "spiral", "pyramid",
			"chess", "chains", "tooth-comb", "ink spot", 
			"tor" };
		m_cbChoices = new JComboBox( phases );
		m_cbChoices.setSelectedIndex( m_iStyle );
		m_cbChoices.setBounds( 80, 10, 186, 20 );
		m_cbChoices.addActionListener( this );
		getContentPane().add( m_cbChoices );
		
		JLabel lb5 = new JLabel( "Transform" );
		lb5.setBounds( 10, 40, 70, 20 );
		getContentPane().add( lb5 );
		String[] sTrans = { "none", "increasing noise", "a12 sinus", "a11 double sinus",
			"ball on", "ball under", "ax^2", "bar code" };
		m_cbTrans = new JComboBox( sTrans );
		m_cbTrans.setSelectedIndex( m_iTransform );
		m_cbTrans.setBounds( 80, 40, 186, 20 );
		m_cbTrans.addActionListener( this );
		getContentPane().add( m_cbTrans );
		
		//second column of controls
		int ix2 = Math.max( m_w + 20, 280 );
		
		m_btnRefresh = new JButton( "Refresh" );
		m_btnRefresh.setBounds( ix2, 10, 80, 20 );
		m_btnRefresh.addActionListener( this );
		getContentPane().add( m_btnRefresh );

		if( m_bApplet == false )
		{
			m_btnSave = new JButton( "Save" );
			m_btnSave.setBounds( ix2 + 90, 10, 80, 20 );
			m_btnSave.addActionListener( this );
			getContentPane().add( m_btnSave );
		}
		
		m_chkAnim = new JCheckBox( "Animate" );
		m_chkAnim.setBounds( ix2, 40, 80, 20 );
		m_chkAnim.addActionListener( this );
		getContentPane().add( m_chkAnim );
		
		m_chkRota = new JCheckBox( "Rotate" );
		m_chkRota.setBounds( ix2 + 90, 40, 80, 20 );
		m_chkRota.addActionListener( this );
		getContentPane().add( m_chkRota );
		
		JLabel lb1 = new JLabel( "Color" );
		lb1.setBounds( ix2, 70, 40, 20 );
		getContentPane().add( lb1 );
		m_txtColor = new JTextField();
		m_txtColor.setBounds( ix2 + 40, 70, 130, 20 );
		getContentPane().add( m_txtColor );
		
		JLabel lb2 = new JLabel( "Noise max" );
		lb2.setBounds( ix2, 100, 70, 20 );
		getContentPane().add( lb2 );
		m_txtNoiseMax = new JTextField();
		m_txtNoiseMax.setText( "" + m_dNoiseMax );
		m_txtNoiseMax.setBounds( ix2 + 70, 100, 100, 20 );
		getContentPane().add( m_txtNoiseMax );
		
		JLabel lb3 = new JLabel( "Noise like" );
		lb3.setBounds( ix2, 130, 70, 20 );
		getContentPane().add( lb3 );
		m_txtNoiseLike = new JTextField();
		m_txtNoiseLike.setText( "" + m_dNoiseLikely );
		m_txtNoiseLike.setBounds( ix2 + 70, 130, 100, 20 );
		getContentPane().add( m_txtNoiseLike );
		
		m_timer = new Timer( 500, this );
		makeIcon( m_iStyle );
	}
	//paint this applet
	public void paint( Graphics g )
	{
	    super.paint( g );
//	    g.drawImage( m_img, 10, 60, null );
	}
	public void stop() 
	{   //stop animation
		m_chkAnim.setSelected( false );
		startAnimation( false );
	}
	//make an icon by style
	public void makeIcon( int iStyle )
	{        
		long lst = System.currentTimeMillis();
		int w = m_w;
		int h = m_h;
		int[] pix = m_pix;
		int red, green, blue;
		int index = 0;
		int x0 = w / 2;
		int y0 = h / 2;
		int halfside = Math.max( x0, y0 );
		
		if( iStyle == 0 )
		{   //RGB colors
		    //x0 += 20;   //to shift green maximum
		    //y0 += 20;
		    if( m_bGetNewColor ) initRandomTransform();
		    for (int y = 0; y < h; y++) {
			    red = (y * 255) / (h - 1);
			    for (int x = 0; x < w; x++) {
				    blue = (x * 255) / (w - 1);
//				    green = ( red * blue ) / 100;
//				    if( green > 255 ) green = 255;
//			        int r = (int)Math.min( x0 - Math.abs( x - x0 ), y0 - Math.abs( y - y0 ) );
			        int r = halfside - (int) Math.sqrt( ( x - m_trXM ) * ( x - m_trXM ) + ( y - m_trYM ) * ( y - m_trYM ) );
				    green = (r * 255) / halfside;
				    if( green < 0 ) green = 0; else if( green > 255 ) green = 255;
				    pix[index++] = (255 << 24) | (red << 16) | (green << 8 ) | blue;
			    }
		    }
		}
		else if( iStyle == 1 || iStyle == 2 || iStyle == 3 || iStyle == 7 || 
		         iStyle == 8 || iStyle == 9 || iStyle == 10 || iStyle == 11 ||
		         iStyle == 12 || iStyle == 6 )
		{   //circles, arrows, stripes, pyramids, chess peaks, tooth-comb, ink spot
            if( m_bGetNewColor ) initRandomTransform();
            m_clrMain = getRandomColor( 127, 127, 127, 255 );
            prepare( iStyle );
			int maxblue = m_clrMain.getBlue();
			int maxgreen = m_clrMain.getGreen();
			int maxred = m_clrMain.getRed();
            int wide = (int)( Math.random() * 10.0 ) + 1;
		    for (int y = 0; y < h; y++) {
			    for (int x = 0; x < w; x++) {
			        Dimension sz = transform( 0, x, y, w, h );
			        double r = getAmpl( iStyle, sz.width, sz.height, w, h );
				    blue = ( (int)(r * maxblue) / wide ) * wide;
				    green = ( (int)(r * maxgreen) / wide ) * wide;
				    red = ( (int)(r * maxred) / wide ) * wide;
				    pix[index++] = (255 << 24) | (red << 16) | (green << 8 ) | blue;
				    changeTransform( x, y, w, h );
			    }
		    }
		}
        else if( iStyle == 4 )
        {   //random squares
            if( m_bGetNewColor )
            {
                m_vecClouds.removeAllElements();
                for( int z = 0; z < 500; z++ ) {
                    Cloud cl = new Cloud();
                    cl.x = (int) ( Math.random() * w );
                    cl.y = (int) ( Math.random() * h );
                    cl.w = (int) ( Math.random() * 20 ) + 3;
                    cl.h = (int) ( Math.random() * 20 ) + 3;
                    cl.clr = getRandomColor( 127, 127, 127, 255 );
                    m_vecClouds.addElement( cl );
                }
            }
            for( int z = 0; z < m_vecClouds.size(); z++ ) {
                Cloud cl = (Cloud)m_vecClouds.elementAt( z );
                int randx = cl.x;
                int randy = cl.y;
                int randw = cl.w;
                int randh = cl.h;
                Color clrMain = cl.clr;
				blue = clrMain.getBlue();
				green = clrMain.getGreen();
				red = clrMain.getRed();
		        for( int y = randy; y < randy + randh && y < h; y++ ) {
		            int k = y * w;
			        for( int x = randx; x < randx + randw && x < w; x++ ) {
			            pix[ k + x ] = (255 << 24) | (red << 16) | (green << 8 ) | blue;
			        }
			    }
			    randx += (int) ( m_vx + Math.random() - 0.5 );
			    if( randx > m_w ) randx = 0; else if( randx < 0 ) randx = m_w;
			    cl.x = randx;
			    randy += (int) ( m_vy + Math.random() - 0.5 );
			    if( randy > m_h ) randy = 0; else if( randy < 0 ) randy = m_h;
			    cl.y = randy;
			}
		}
        else if( iStyle == 5 )
        {   //blue sky
            if( m_bGetNewColor ) m_clrMain = getRandomColor( 190, 205, 245, 20 );
            prepare( iStyle );
			int maxblue = m_clrMain.getBlue();
			int maxgreen = m_clrMain.getGreen();
			int maxred = m_clrMain.getRed();
            int wide = (int)( Math.random() * 6.0 ) + 1;
            if( m_bGetNewColor ) m_clrBack = getRandomColor( 30, 20, 128, 40 );
			int minblue = m_clrBack.getBlue();
			int mingreen = m_clrBack.getGreen();
			int minred = m_clrBack.getRed();
		    for (int y = 0; y < h; y++) {
			    for (int x = 0; x < w; x++) {
			        //Dimension sz = transform( 0, x, y, w, h );
			        //double r = 1.0 - 0.5 * getAmpl( iStyle, sz.width, sz.height, w, h );
			        double r1 = 0.5 - 0.4 * getAmpl( iStyle, x, y, w, h );
			        double r2 = 1.0 - r1;
				    blue = ( (int)( r1 * maxblue + r2 * minblue ) / wide ) * wide;
				    green = ( (int)( r1 * maxgreen + r2 * mingreen ) / wide ) * wide;
				    red = ( (int)( r1 * maxred + r2 * minred ) / wide ) * wide;
				    pix[index++] = (255 << 24) | (red << 16) | (green << 8 ) | blue;
				    //changeTransform( x, y, w, h );
			    }
		    }
		    //clouds
		    double alpha = 0.1;
		    double beta = 1.0 - alpha;
            if( m_bGetNewColor )
            {   //create new set
                m_vecClouds.removeAllElements();
                int total = (int) ( Math.random() * 10 ) + 1;   //number of clouds
                for( int ncl = 0; ncl < total; ncl++ ) {
                    int clx = (int) ( Math.random() * w );
                    int cly = (int) ( Math.random() * h );
                    int clw = (int) ( Math.random() * 100 ) + 50;
                    int clh = (int) ( Math.random() * 50 ) + 25;
                    int nsub = clw * clh / 10;  //subclouds
                    for( int z = 1; z <= nsub; z++ ) {
                        int size = ( ( nsub - z ) * 20 ) / nsub + 1;
                        int randw = (int) ( Math.random() * size ) + 1;
                        int randh = (int) ( Math.random() * size * 0.5 ) + 1;
                        int randx = (int)( normal( randw ) * clw ) + clx;
                        int randy = (int)( normal( 3 ) * clh ) + cly;
                
                        Cloud cl = new Cloud();
                        cl.x = randx;
                        cl.y = randy;
                        cl.w = randw;
                        cl.h = randh;
                        cl.clr = getRandomColor( 225, 225, 225, 40 );
                        m_vecClouds.addElement( cl );
                    }
                }
            }
            for( int z = 0; z < m_vecClouds.size(); z++ ) {
                Cloud cl = (Cloud)m_vecClouds.elementAt( z );
                int randx = cl.x;
                int randy = cl.y;
                int randw = cl.w;
                int randh = cl.h;
                Color clrMain = cl.clr;
				blue = clrMain.getBlue();
				green = clrMain.getGreen();
				red = clrMain.getRed();
		        for( int y = randy; y < randy + randh && y < h; y++ ) {
		            int k = y * w;
			        for( int x = randx; x < randx + randw && x < w; x++ ) {
			            //mix colors
			            int pix2 = pix[ k + x ];
				        int blue2 = (int)( ( pix2 & 0xff ) * beta + blue * alpha );
				        int green2 = (int)( ( ( pix2 & 0xff00 ) >> 8 ) * beta + green * alpha );
				        int red2 = (int)( ( ( pix2 & 0xff0000 ) >> 16 ) * beta + red * alpha );
			            pix[ k + x ] = (255 << 24) | (red2 << 16) | (green2 << 8 ) | blue2;
			        }
			    }
			    //move
			    randx += (int) ( 5.0 + Math.random() - 0.5 );
			    if( randx > m_w ) randx = 0; else if( randx < 0 ) randx = m_w;
			    cl.x = randx;
			    randy += (int) ( -1.0 + Math.random() - 0.5 );
			    if( randy > m_h ) randy = 0; else if( randy < 0 ) randy = m_h;
			    cl.y = randy;
			}
		}
/* squeze and rotate           m_a11 = Math.random() * 2.0 - 1.0;
m_a12 = Math.random() * 2.0 - 1.0;
m_a21 = Math.random() * 2.0 - 1.0;
m_a22 = m_a11 != 0.0 ? ( 1.0 + m_a12 * m_a21 ) / m_a11 : 1.0;
//center test lines
for (int y = 0; y < h; y += 3) {
	pix[ y * w + x0 ] = (255 << 24) | (255 << 16);
}
for (int x = 0; x < w; x += 3) {
	pix[ y0 * w + x ] = (255 << 24) | (255 << 16);
}*/	
		m_img = createImage( new MemoryImageSource( w, h, pix, 0, w ) );		
		m_lbIcon.setIcon( new ImageIcon( m_img ) );
		lst = System.currentTimeMillis() - lst;
		System.out.println( "calc=" + lst );
		m_timer.setDelay( (int)lst + 50 );
	}
	//return the amplitude
	public double getAmpl( int iStyle, int x, int y, int w, int h )
	{
	    int x0 = w / 2;
	    int y0 = h / 2;
	    int half = Math.max( x0, y0 );
	    int x1 = x % w;
        int y1 = y % h;
		double noise = ( Math.random() > m_dNoiseLikely ) ? 0.0 : 
		    ( Math.random() - 0.5 ) * m_dNoiseMax;
		double z = 0.0, r;
	    
	    if( iStyle == 1 )
        {   //circles
            r = Math.sqrt( x0 * x0 + y0 * y0 );
			z = Math.sqrt( ( x1 - x0 ) * ( x1 - x0 ) + ( y1 - y0 ) * ( y1 - y0 ) ) / r + noise;
        }
	    else if( iStyle == 2 )
        {   //arrows
            z = Math.min( Math.abs( x1 - x0 ), Math.abs( y1 - y0 ) ) / ( (double)half ) + noise;
        }
        else if( iStyle == 3 || iStyle == 5 )
        {   //strips, ax + by + c = 0
            double cpos = ( m_a * x1 + m_b * y1 ); //positive
			z = cpos / m_cmax + noise;
		}
	    else if( iStyle == 7 )
        {   //pyramid
            r = Math.min( x0 - Math.abs( x1 - x0 ), y0 - Math.abs( y1 - y0 ) );
			z = r / half + noise;
        }
	    else if( iStyle == 8 )
        {   //chess
            int posx = x1 / 32;
            int posy = y1 / 32;
            z = ( posx + posy ) % 2 + noise;
        }
	    else if( iStyle == 9 )
        {   //peaks
            r = 1.0 + Math.sin( m_phase2 + ( m_frequ2 * y1 ) / h ) * Math.sin( m_phase1 + ( m_frequ1 * x1 ) / w );
			z = r / 2.0 + noise;
        }
	    else if( iStyle == 10 )
        {   //tooth-comb
            int i = (int)( x / ( (double)w ) * RAD_NUM );
            if( i >= RAD_NUM || i < 0 ) i = 0;
            r = 2.0 * m_radius[ i ];   //r - positive from 0 to 200.
			z = r < y ? ( y - r ) / ( h - r ) + noise : 0.0;
        }
	    else if( iStyle == 11 )
        {   //ink spot
//			if( Math.abs( x - m_trXM ) > m_trW / 2 || Math.abs( y - m_trYM ) > m_trH / 2 ) 
			if( Math.abs( x1 - x0 ) > 100 || Math.abs( y1 - y0 ) > 100 ) 
                return 0.0;
            
            if( x1 == x0 && y1 == y0 ) return 1.0;
            
            double v = Math.sqrt( ( x1 - x0 ) * ( x1 - x0 ) + ( y1 - y0 ) * ( y1 - y0 ) );
            double fi = Math.acos( ( x1 - x0 ) / v );   //0 - PI
            if( y1 - y0 < 0 ) fi = 2.0 * Math.PI - fi;
            int i = (int)( fi / ( 2.0 * Math.PI ) * RAD_NUM );
            if( i >= RAD_NUM || i < 0 ) i = 0;
            r = m_radius[ i ];   //r - positive from 0 to 100.
			z = ( r >= v ) ? ( 1.0 - v / 90.0 + noise ) : 0.0;   //90, not 100 to make the borders darker
        }
	    else if( iStyle == 12 )
        {   //tor
            int r1 = (int) Math.sqrt( ( x1 - x0 ) * ( x1 - x0 ) + ( y1 - y0 ) * ( y1 - y0 ) );
            int i = r1 % 20;
			z =  m_radius[ i ] / 100.0 + noise;
        }
	    else if( iStyle == 6 )
        {   //spiral
            double r1 = Math.sqrt( ( x1 - x0 ) * ( x1 - x0 ) + ( y1 - y0 ) * ( y1 - y0 ) );
            double fi = Math.acos( ( x1 - x0 ) / r1 );   //0 - PI
            if( y1 - y0 < 0 ) fi = 2.0 * Math.PI - fi;
            int i = ( (int)( r1 + fi / Math.PI * 10.0 + 20.0 * m_phase2 ) ) % 20;
			z =  m_radius[ i ] / 100.0 + noise;
        }
        
        if( z > 1.0 ) z = 1.0;
		else if( z < 0.0 ) z = 0.0;
		return z;
	}
	//make preparation fot the image type
	public void prepare( int iStyle )
	{
	    if( iStyle == 3 )
	    {   //stripes, blue sky
            m_a = Math.random();
            m_b = Math.sqrt( 1 - m_a * m_a );
            double cmin = - ( m_a * m_w + m_b * m_h );
            m_cmax = 0 - cmin;
        }
	    else if( iStyle == 5 )
	    {   //stripes, blue sky
            m_b = 0.95 + 0.05 * Math.sin( m_phase2 );
            m_a = Math.sqrt( 1 - m_b * m_b );
            double cmin = - ( m_a * m_w + m_b * m_h );
            m_cmax = 0 - cmin;
        }
	    else if( iStyle == 10 || iStyle == 11 || iStyle == 12  || iStyle == 6 )
	    {   //tooth-comb, ink spot
            double fr = m_frequ1 + m_frequ2 + m_frequ3 + m_frequ4;
	        for( int i = 0; i < RAD_NUM; i++ )
	        {
                double fi = ( i * Math.PI * 2.0 ) / RAD_NUM;
                //r - positive from 0 to 100.
                double r = ( 5.0 + fr + m_frequ1 * Math.sin( m_phase1 + ( 1.0 * fi ) ) +
                    m_frequ2 * Math.sin( m_phase2 + ( 2.0 * fi ) ) + 
                    m_frequ3 * Math.sin( m_phase3 + ( 4.0 * fi ) ) + 
                    m_frequ4 * Math.sin( m_phase4 + ( 8.0 * fi ) ) ) * 100.0 / ( 5.0 + 2.0 * fr );
                m_radius[ i ] = r + m_iBorderSharp * Math.random();
            }
	    }
	}
    //psevdo-normal distribution with average 0.5
    private double normal( int n )
    {
        double x = 0.0;
        for( int i = 0; i < n && i < 12; i++ ) x += Math.random();
        return x / n;
    }
    //return a color close to basic RGB with defined deviation
    public Color getRandomColor( int red, int green, int blue, int devi )
    {
        if( !m_bGetNewColor ) return m_clrMain;
        
        String strCol = ( m_txtColor == null ) ? "" : m_txtColor.getText();
        if( strCol.length() > 0 )
        {   //create a color using text field - higher priority
	        java.util.StringTokenizer tok = new java.util.StringTokenizer( strCol, "," );
	        String str;
	        int cols[] = new int[ 3 ], i;
	        try {
	            for( i = 0; i < 3; i++ )
	            {
	                str = tok.nextToken();
	                cols[ i ] = Integer.parseInt( str );
	            }
	            Color clr = new Color( cols[ 0 ], cols[ 1 ], cols[ 2 ] );
	            return clr;
	        } catch( Exception e ) { System.err.println( e ); }
	    }
        
        int red2 = red + (int)( ( Math.random() - 0.5 ) * devi );
        int green2 = green + (int)( ( Math.random() - 0.5 ) * devi );
        int blue2 = blue + (int)( ( Math.random() - 0.5 ) * devi );
        
        if( red2 > 255 ) red2 = 255;
        else if( red2 < 0 ) red2 = 0;
        
        if( green2 > 255 ) green2 = 255;
        else if( green2 < 0 ) green2 = 0;
        
        if( blue2 > 255 ) blue2 = 255;
        else if( blue2 < 0 ) blue2 = 0;
        
        return new Color( red2, green2, blue2 );
    }
    //init random transform
    public void initRandomTransform()
    {
		try {
		    m_dNoiseMax = Double.parseDouble( m_txtNoiseMax.getText() );
		    m_dNoiseLikely = Double.parseDouble( m_txtNoiseLike.getText() );
	    } catch( Exception e ) { System.err.println( e ); }
		
        m_x0 = 0.0;
        m_y0 = 0.0;
        //rotate
        m_a11 = m_bRotate ? Math.random() : 1.0;
        m_a12 = Math.sqrt( 1.0 - m_a11 * m_a11 );
        m_a21 = - m_a12;
        m_a22 = m_a11 != 0.0 ? ( 1.0 + m_a12 * m_a21 ) / m_a11 : 1.0;
            
        if( m_bRotate )
        {   //amplify as well
            double dx = Math.random() * 2.0;
            double dy = Math.random() * 2.0;
            m_a11 *= dx;
            m_a12 *= dx;
            m_a21 *= dy;
            m_a22 *= dy;
        }

        m_phase1 = Math.random() * Math.PI * 2.0;
        m_phase2 = Math.random() * Math.PI * 2.0;
        m_phase3 = Math.random() * Math.PI * 2.0;
        m_phase4 = Math.random() * Math.PI * 2.0;
        
        m_frequ1 = ( (int)( Math.random() * 6.0 ) ) * 2.0 * Math.PI;
        m_frequ2 = ( (int)( Math.random() * 6.0 ) ) * 2.0 * Math.PI;
        m_frequ3 = ( (int)( Math.random() * 6.0 ) ) * 2.0 * Math.PI;
        m_frequ4 = ( (int)( Math.random() * 6.0 ) ) * 2.0 * Math.PI;
        
        m_trXM = (int) ( normal( 12 ) * m_w );
        m_trYM = (int) ( normal( 12 ) * m_h );
        m_trW = (int) ( 100 + 0.3 * Math.random() * m_w );
        m_trH = (int) ( 100 + 0.3 * Math.random() * m_h );
        
        m_a11bz = m_a11;
        m_a12bz = m_a12;
        m_a21bz = m_a21;
        m_a22bz = m_a22;
        m_x0bz = m_x0;
        m_y0bz = m_y0;
    }
    //make transform
    Dimension transform( int iStyle, int x, int y, int w, int h )
    {
        int x1 = (int) Math.floor( m_a11 * x + m_a12 * y + m_x0 );
        int y1 = (int) Math.floor( m_a21 * x + m_a22 * y + m_y0 );
        x1 = x1 % w;
        y1 = y1 % h;
        if( x1 < 0 ) x1 += w;
        if( y1 < 0 ) y1 += h;
        return new Dimension( x1, y1 );
    }
    //change transform coefficients
    public void changeTransform( int x, int y, int w, int h )
    {
        if( m_iTransform == 1 && x == 0 )
        {   //increasing noise
            int i = (int)( Math.random() * 8 );
            double r = ( Math.random() - 0.5 ) * 0.01;
            switch( i ) {
                case 0: m_a11 += r; break;
                case 1: m_a12 += r; break;
                case 2: m_a21 += r; break;
                case 3: m_a22 += r; break;
                case 4: m_x0 ++; break;
                case 5: m_y0 ++; break;
                case 6: m_x0 --; break;
                case 7: m_y0 --; break;
            }
        }
        else if( m_iTransform == 2 )
        {   //a12 double sinus
			m_a12 = 0.1 * Math.sin( Math.PI * y / ( 0.2 * h ) + m_phase1 );
			//m_a21 = m_a12;//cool
			//m_a21 = - m_a12;//similar to prev
			//m_a21 = ( m_a12 == 0.0 ) ? 0.0 : + 0.01 / m_a12;//very stohastic when coef > 0.01
			m_a21 = - 0.1 * Math.sin( Math.PI * x / ( 0.2 * w ) + m_phase2 );
        }
        else if( m_iTransform == 3 )
        {   //a11 double sinus
			double dx = 1.0 + 7.0 / ( Math.abs( x ) < 4 ? 4 : x ) * Math.sin( Math.PI * y / ( 0.25 * h ) ) * Math.sin( Math.PI * x / ( 0.25 * w ) + m_phase1 );
			double dy = 1.0 + 7.0 / ( Math.abs( y ) < 4 ? 4 : y ) * Math.sin( Math.PI * x / ( 0.25 * w ) ) * Math.sin( Math.PI * y / ( 0.25 * h ) + m_phase2 );
			
			m_a11 = m_a11bz * dx;
			m_a12 = m_a12bz * dx;
			m_a21 = m_a21bz * dy;
			m_a22 = m_a22bz * dy;
            //1. nice zig-zag by Y
			//m_a11 = 1.0 + 0.25 * Math.sin( Math.PI * y / ( 0.2 * h ) + m_phase1 ) * Math.exp( - ( 2.0 * x ) / w );
			
			//2. if no rotation this is random rectahgles
			//m_a11 = 1.0 + 0.25 * Math.sin( Math.PI * x / ( 0.2 * w ) + m_phase1 ) * Math.exp( - ( 2.0 * x ) / w );
			//m_a22 = 1.0 + 0.25 * Math.sin( Math.PI * y / ( 0.2 * h ) + m_phase2 ) * Math.exp( - ( 2.0 * y ) / h );
			
			//3. zig-zag by Y and random height of slice by Y
			//m_a11 = 1.0 + 0.25 * Math.sin( Math.PI * y / ( 0.2 * h ) + m_phase1 ) * Math.exp( - ( 2.0 * x ) / w );
			//m_a22 = 1.0 + 0.25 * Math.sin( Math.PI * y / ( 0.2 * h ) + m_phase2 ) * Math.exp( - ( 2.0 * y ) / h );
			
			//4. zig-zag by Y and broken rects
			//m_a11 = 1.0 + 0.25 * Math.sin( Math.PI * y / ( 0.2 * h ) + m_phase1 ) * Math.exp( - ( 2.0 * x ) / w );
			//m_a22 = 1.0 + 0.25 * Math.sin( Math.PI * x / ( 0.2 * w ) + m_phase2 ) * Math.exp( - ( 2.0 * y ) / h );

			//5. distorted a bit rectangels
			//m_a11 = 1.0 + 0.25 * Math.sin( Math.PI * y / ( 2.2 * h ) + m_phase1 ) * Math.exp( - ( 2.0 * y ) / h );
			//m_a22 = 1.0 + 0.25 * Math.sin( Math.PI * x / ( 2.2 * w ) + m_phase2 ) * Math.exp( - ( 2.0 * y ) / h );
        }
        else if( m_iTransform == 4 || m_iTransform == 5 )
        {   //heavy, light
			double dx, dy;
			if( Math.abs( x - m_trXM ) > m_trW / 2 || Math.abs( y - m_trYM ) > m_trH / 2 ) 
			{
			    dx = 1.0;
			    dy = 1.0;
			}
			else 
			{
			    double kx = 10.0 * ( 1.0 - ( 2.0 * Math.abs( y - m_trYM ) ) / m_trH );
			    double ky = 10.0 * ( 1.0 - ( 2.0 * Math.abs( x - m_trXM ) ) / m_trW );
			    if( m_iTransform == 5 )
			    {
			        kx = -kx;
			        ky = -ky;
			    }
			    dx = 1.0 + kx / ( Math.abs( x ) < 4 ? 4 : x ) * Math.sin( Math.PI * ( x - m_trXM ) / ( 0.5 * m_trW ) );
			    dy = 1.0 + ky / ( Math.abs( y ) < 4 ? 4 : y ) * Math.sin( Math.PI * ( y - m_trYM ) / ( 0.5 * m_trH ) );
			}
			m_a11 = m_a11bz * dx;
			m_a12 = m_a12bz * dx;
			m_a21 = m_a21bz * dy;
			m_a22 = m_a22bz * dy;
		}
        else if( m_iTransform == 6 )
        {   //ax^2
			double dx = 1.0 + ( 1.0 * x ) / w;
			double dy = 1.0 + ( 1.0 * y ) / h;
			
			m_a11 = m_a11bz * dx;
			m_a12 = m_a12bz * dx;
			m_a21 = m_a21bz * dy;
			m_a22 = m_a22bz * dy;
		}
        else if( m_iTransform == 7 )
        {   //bar code
			double dx = 1.0 + 8.0 / ( Math.abs( x ) < 8 ? 8 : x ) * Math.sin( Math.PI * x / ( 0.2 * w ) + m_phase1 );
			double dy = 1.0 + 8.0 / ( Math.abs( y ) < 8 ? 8 : y ) * Math.sin( Math.PI * y / ( 0.2 * h ) + m_phase2 );
			
			m_a11 = m_a11bz * dx;
			m_a12 = m_a12bz * dx;
			m_a21 = m_a21bz * dy;
			m_a22 = m_a22bz * dy;
		}
/*        else if( m_iTransform == 13 )
        {   //rotate a bit
            m_a11 = 0.99 * m_a11 - 0.14 * m_a12;
            m_a12 = 0.14 * m_a11 + 0.99 * m_a12;
            m_a21 = 0.99 * m_a21 - 0.14 * m_a22;
            m_a22 = 0.14 * m_a21 + 0.99 * m_a22;
        }*/
    }
    //implementation of ActionListener interface
    public void actionPerformed(ActionEvent event) 
    {
	    if( m_cbChoices.equals( event.getSource() ) ) 
	    {
	        m_iStyle = m_cbChoices.getSelectedIndex();
	        makeIcon( m_iStyle );
	        repaint( 10, 70, m_w, m_h );
	    }
	    else if( m_cbTrans.equals( event.getSource() ) ) 
	    {
	        m_iTransform = m_cbTrans.getSelectedIndex();
	        makeIcon( m_iStyle );
	        repaint( 10, 70, m_w, m_h );
	    }
	    else if( m_btnRefresh != null && m_btnRefresh.equals( event.getSource() ) ) 
	    {
	        makeIcon( m_iStyle );
	        repaint( 10, 70, m_w, m_h );
	    }
	    else if( m_btnSave != null && m_btnSave.equals( event.getSource() ) ) 
	    {
	        save();
	    }
	    else if( m_chkAnim.equals( event.getSource() ) )
	    {
	        startAnimation( m_chkAnim.isSelected() );
	    }
	    else if( m_chkRota.equals( event.getSource() ) )
	    {
	        m_bRotate = m_chkRota.isSelected();
	    }
	    else if( m_timer.equals( event.getSource() ) )
	    {   //animation timer event
            m_phase2 += 0.1;
            //adjust color
            int devi = ( m_iStyle == 5 ) ? 5 : 10;
            int red2 = m_clrMain.getRed() + (int)( ( Math.random() - 0.5 ) * devi );
            int green2 = m_clrMain.getGreen() + (int)( ( Math.random() - 0.5 ) * devi );
            int blue2 = m_clrMain.getBlue() + (int)( ( Math.random() - 0.5 ) * devi );        
            if( red2 > 255 ) red2 = 255;
                else if( red2 < 0 ) red2 = 0;       
            if( green2 > 255 ) green2 = 255;
                else if( green2 < 0 ) green2 = 0;            
            if( blue2 > 255 ) blue2 = 255;  
                else if( blue2 < 0 ) blue2 = 0;            
	        m_clrMain = new Color( red2, green2, blue2 );
	        //move "ball"
	        m_trXM += m_vx;
	        if( ( m_trXM < 0 && m_vx < 0 ) || ( m_trXM > m_w && m_vx > 0 ) ) m_vx = -m_vx;
	        m_trYM += m_vy;
	        if( ( m_trYM < 0 && m_vy < 0 ) || ( m_trYM > m_h && m_vy > 0 ) ) m_vy = -m_vy;
            //System.err.println( "x=" + m_trXM + ",y=" + m_trYM + ",m_vx=" + m_vx );
 	        makeIcon( m_iStyle );
	        repaint( 10, 70, m_w, m_h );
	    }
    }
    //save image to the file "out.jpg"
    public void save()
    {
        int w = m_w;
        int h = m_h;
        int [] pixels = m_pix;
        BufferedImage bimg = new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
        bimg.setRGB(0,0,w,h,pixels,0,w);

        //encode as a JPEG
        try {
/*            // use Java Advanced Imaging API, next 5 lines
            FileOutputStream fos = new FileOutputStream("out" + m_iSave++ + ".jpg");
            com.sun.media.jai.codec.JPEGEncodeParam encodeParam = new com.sun.media.jai.codec.JPEGEncodeParam();
            com.sun.media.jai.codec.ImageEncoder jpeg = com.sun.media.jai.codec.ImageCodec.createImageEncoder("JPEG", fos, encodeParam);
            jpeg.encode(bimg);
            fos.close();*/
            // or JDK 1.4 
            //javax.imageio.ImageIO.write(bimg, "JPEG", new File("out" + m_iSave++ + ".jpg") ); 
        } 
        catch(Exception e) { 
            System.err.println( e.toString() );
        }
    }
    //animation support
    public synchronized void startAnimation( boolean bStart ) 
    {
        if( bStart ) m_timer.start();
        else m_timer.stop();
        m_bGetNewColor = !bStart;
        m_vx = 5.0 + Math.random() * 10.0;
        m_vy = -5.0 - Math.random() * 10.0;
    }
    //main method
    public static void main(String[] args) 
    {
        int w = 256, h = 256;
        //create a new instance of JMemoryImage
        JMemoryImage ima = new JMemoryImage();
        ima.m_bApplet = false;
        ima.m_w = w;
        ima.m_h = h;
        ima.init();

        //create a frame and container for the panels.
        JFrame frame = new JFrame("JMemoryImage");
        frame.setBounds( 120, 120, ima.getWidth(), ima.getHeight() );
//        frame.setResizable( false );

        //set the look and feel
        try {
            UIManager.setLookAndFeel(
                UIManager.getCrossPlatformLookAndFeelClassName() );
        } catch(Exception e) { System.err.println( e.toString() ); }
        	
        //exit when the window is closed.
        frame.addWindowListener( new WindowAdapter()
            {
                public void windowIconified(WindowEvent e) 
                {   //stop animation
                    JFrame fr = (JFrame)e.getSource();
                    JMemoryImage me = (JMemoryImage) fr.getContentPane().getComponentAt( 100, 100 );
                    if( me != null ) me.stop();
                }
		        public void windowClosing(java.awt.event.WindowEvent event)
		        {
			        System.exit(0);
		        }
            }
        );
        frame.getContentPane().add( ima );
        	
        //show application
        //frame.pack(); //don't do with applet class
        frame.setVisible(true);
    }
    
    class Cloud
    {
        int     x, y, w, h; //position, size
        double  alpha;
        Color   clr;
    }
}