Package de.sciss.eisenkraut.util

Source Code of de.sciss.eisenkraut.util.DebugView$VectorDisplay

/*
*  DebugView.java
*  Eisenkraut
*
*  Copyright (c) 2004-2014 Hanns Holger Rutz. All rights reserved.
*
*  This software is published under the GNU General Public License v3+
*
*
*  For further information, please contact Hanns Holger Rutz at
*  contact@sciss.de
*/

package de.sciss.eisenkraut.util;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Vector;

import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

import de.sciss.eisenkraut.math.MathUtil;
import de.sciss.gui.Axis;
import de.sciss.gui.VectorSpace;

public class DebugView
{
  public DebugView( float[] data, int off, int length, String descr )
  {
    final float[] dataCopy = new float[ length ];
   
    System.arraycopy( data, off, dataCopy, 0, length );
   
    int width = 256;
    int decimF = Math.max( 1, 2 * length / width );
    int decimLen = length / decimF;
   
    float[] decim = new float[ decimLen ];
    float f1, f2, f3;
   
    f2 = Float.NEGATIVE_INFINITY;
    f3 = Float.POSITIVE_INFINITY;
    for( int i = 0, j = 0; i < decimLen; ) {
      f1 = dataCopy[ j++ ];
      for( int k = 1; k < decimF; k++ ) {
        f1 = Math.max( f1, dataCopy[ j++ ]);
      }
      decim[ i++ ] = f1;
      f2 = Math.max( f2, f1 );
      f3 = Math.min( f3, f1 );
    }
    if( Float.isInfinite( f2 )) f2 = 1f;
    if( Float.isInfinite( f3 )) f3 = 0f;

    VectorDisplay ggVectorDisplay = new VectorDisplay( decim );
    ggVectorDisplay.setMinMax( f3, f2 );
//    ggVectorDisplay.addMouseListener( mia );
//    ggVectorDisplay.addMouseMotionListener( mia );
//    ggVectorDisplay.addTopPainter( tp );
//    ggVectorDisplay.setPreferredSize( new Dimension( width, 256 )); // XXX
    JPanel displayPane = new JPanel( new BorderLayout() );
    displayPane.add( ggVectorDisplay, BorderLayout.CENTER );
    Axis haxis      = new Axis( Axis.HORIZONTAL );
    Axis vaxis      = new Axis( Axis.VERTICAL, Axis.FIXEDBOUNDS );
final VectorSpace spc = VectorSpace.createLinSpace( 0, length - 1, f3, f2, null, null, null, null );
haxis.setSpace( spc );
vaxis.setSpace( spc );
    Box box        = Box.createHorizontalBox();
    box.add( Box.createHorizontalStrut( vaxis.getPreferredSize().width ));
    box.add( haxis );
    displayPane.add( box, BorderLayout.NORTH );
    displayPane.add( vaxis, BorderLayout.WEST );
   
    JFrame f = new JFrame( descr );
    f.setSize( width, 256 );
    f.getContentPane().add( displayPane, BorderLayout.CENTER );
    f.setVisible( true );
  }
 
  public static DebugView fftMag( float[] fftBuf, int numBins, boolean log, String descr )
  {
    final float[]  data = new float[ numBins ];
    float      f1, f2;
    for( int i = 0, j = 0; i < numBins; i++ ) {
      f1 = fftBuf[ j++ ];
      f2 = fftBuf[ j++ ];
      f1 = (float) Math.sqrt( f1 * f1 + f2 * f2 );
      data[ i ] = log ? (float) Math.max( -160, MathUtil.linearToDB( f1 )) : f1;
    }
    return new DebugView( data, 0, numBins, descr );
  }

  public static DebugView fftPhase( float[] fftBuf, int numBins, boolean deg, String descr )
  {
    final float[]  data = new float[ numBins ];
    float      f1, f2;
    for( int i = 0, j = 0; i < numBins; i++ ) {
      f1 = fftBuf[ j++ ];
      f2 = fftBuf[ j++ ];
      f1 = (float) Math.atan2( f2, f1 );
      if( deg ) f1 *= (float) (180 / Math.PI);
      data[ i ] = f1;
    }
    return new DebugView( data, 0, numBins, descr );
  }

  /**
   *  A <code>VectorDisplay</code> is a two dimensional
   *  panel which plots a sampled function (32bit float) and allows
   *  the user to edit this one dimensional data vector
   *  (or table, simply speaking). It implements
   *  the <code>EventManager.Processor</code> interface
   *  to handle custom <code>VectorDisplayEvent</code>s
   *  and implements the <code>VirtualSurface</code>
   *  interface to allow transitions between screen
   *  space and normalized virtual space.
   *  <p>
   *  Often it is convenient to attach a popup menu
   *  created by the static method in <code>VectorTransformer</code>.
   *  <p>
   *  Examples of using <code>VectorDisplay</code>s aer
   *  the <code>SigmaReceiverEditor</code> and the
   *  <code>SimpleTransmitterEditor</code>.
   *
   *  @author    Hanns Holger Rutz
   *  @version  0.65, 11-Aug-04
   *
   *  @see    de.sciss.meloncillo.math.VectorTransformer#createPopupMenu( VectorDisplay )
   *  @see    VectorDisplayListener
   *  @see    VectorDisplayEvent
   *  @see    de.sciss.meloncillo.receiver.SigmaReceiverEditor
   *  @see    de.sciss.meloncillo.transmitter.SimpleTransmitterEditor
   *
   *  @todo    a vertical (y) wrapping mode should be implemented
   *        similar to x-wrap, useful for circular value spaces like angles
   *  @todo    due to a bug in horizontal wrapping, the modified span
   *        information is wrong?
   */
  public static class VectorDisplay
  extends JComponent  // extends JPanel
  // implements 
  {
    private float[]    vector;

    private final GeneralPath   path    = new GeneralPath();
    private Shape        pathTrns;
    private TextLayout      txtLay    = null;
    private Rectangle2D      txtBounds;
    private Dimension      recentSize;
    private Image        image    = null;

    private static final Stroke  strkLine  = new BasicStroke( 0.5f );
    private static final Paint  pntArea    = new Color( 0x42, 0x5E, 0x9D, 0x7F );
    private static final Paint  pntLine    = Color.black;
    private static final Paint  pntLabel  = new Color( 0x00, 0x00, 0x00, 0x3F );
   
    private final AffineTransform trnsScreenToVirtual  = new AffineTransform();
    private final AffineTransform trnsVirtualToScreen  = new AffineTransform();

    private float    min      = 0.0f;    // minimum vector value
    private float    max      = 1.0f;    // maximum vector value
    private boolean    fillArea  = true;    // fill area under the vector polyline
    private String    label    = null;    // optional text label

    // --- top painter ---

    private final Vector collTopPainters = new Vector();
   
    /**
     *  Creates a new VectorDisplay with an empty vector.
     *  The defaults are wrapX = false, wrapY = false,
     *  min = 0, max = 1.0, fillArea = true, no label
     */
    public VectorDisplay()
    {
      this( new float[0] );
    }
   
    /**
     *  Creates a new VectorDisplay with an given initial vector.
     *  The defaults are wrapX = false, wrapY = false,
     *  min = 0, max = 1.0, fillArea = true, no label
     *
     *  @param  vector  the initial vector data
     */
    public VectorDisplay( float[] vector )
    {
      setOpaque( false );
      setMinimumSize( new Dimension( 64, 16 ));
//      setPreferredSize( new Dimension( 288, 144 )); // XXX
      recentSize  = getMinimumSize();
      setVector( null, vector );
//      addComponentListener( this );
    }

    /**
     *  Replaces the existing vector by another one.
     *  This dispatches a <code>VectorDisplayEvent</code>
     *  to registered listeners.
     *
     *  @param  source  the source in the <code>VectorDisplayEvent</code>.
     *          use <code>null</code> to prevent event dispatching.
     *  @param  vector  the new vector data
     */
    public void setVector( Object source, float[] vector )
    {
      this.vector = vector;
     
      recalcPath();
      repaint();
    }
   
    /**
     *  Gets the current data array.
     *
     *  @return    the current vector data of the editor. valid data
     *        is from index 0 to the end of the array.
     *
     *  @warning      the returned array is not a copy and therefore
     *            any modifications are forbidden. this also implies
     *            that relevant data be copied by the listener
     *            immediately upon receiving the vector.
     *  @synchronization  should only be called in the event thread
     */
    public float[] getVector()
    {
      return vector;
    }

    /**
     *  Changes the allowed range for vector values.
     *  Influences the graphics display such that
     *  the top margin of the panel corresponds to max
     *  and the bottom margin corresponds to min. Also
     *  user drawings are limited to these values unless
     *  wrapY is set to true (not yet implemented).
     *
     *  @param  min    new minimum y value
     *  @param  max    new maximum y value
     *
     *  @warning  the current vector is left untouched,
     *        even if values lie outside the new
     *        allowed range.
     */
    public void setMinMax( float min, float max )
    {
      if( this.min != min || this.max != max ) {
        this.min  = min;
        this.max  = max;
        repaint();
      }
    }

    /**
     *  Gets the minimum allowed y value
     *
     *  @return    the minimum specified function value
     */
    public float getMin()
    {
      return min;
    }

    /**
     *  Gets the maximum allowed y value
     *
     *  @return    the maximum specified function value
     */
    public float getMax()
    {
      return max;
    }

    /**
     *  Set the graph display mode
     *
     *  @param  fillArea  if <code>false</code>, a hairline is
     *            drawn to connect the sample values. if
     *            <code>true</code>, the area below the
     *            curve is additionally filled with a
     *            translucent colour.
     */
    public void setFillArea( boolean fillArea )
    {
      if( this.fillArea != fillArea ) {
        this.fillArea   = fillArea;
        repaint();
      }
    }

    /**
     *  Select the allowed range for vector values.
     *  Influences the graphics display.
     */
    public void setLabel( String label )
    {
      if( this.label == null || label == null || !this.label.equals( label )) {
        txtLay    = null;
        this.label  = label;
        repaint();
      }
    }

    public void paintComponent( Graphics g )
    {
      super.paintComponent( g );
     
      Dimension  d  = getSize();

      if( d.width != recentSize.width || d.height != recentSize.height ) {
        recentSize = d;
        recalcTransforms();
        recreateImage();
        redrawImage();
      } else if( pathTrns == null ) {
        recalcTransforms();
        recreateImage()// XXX since we don't clear the background any more to preserve Aqua LAF
        redrawImage();
      }

      if( image != null ) {
        g.drawImage( image, 0, 0, this );
      }

      // --- invoke top painters ---
      if( !collTopPainters.isEmpty() ) {
        Graphics2D    g2      = (Graphics2D) g;
//        AffineTransform  trnsOrig  = g2.getTransform();

//        g2.transform( trnsVirtualToScreen );
        g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
//        for( int i = 0; i < collTopPainters.size(); i++ ) {
//          ((TopPainter) collTopPainters.get( i )).paintOnTop( g2 );
//        }
//        g2.setTransform( trnsOrig );
      }
    }
   
    /**
     *  Registers a new top painter.
     *  If the top painter wants to paint
     *  a specific portion of the surface,
     *  it must make an appropriate repaint call!
     *
     *  @param  p   the painter to be added to the paint queue
     *
     *  @synchronization  this method must be called in the event thread
     */
//    public void addTopPainter( TopPainter p )
//    {
//      if( !collTopPainters.contains( p )) {
//        collTopPainters.add( p );
//      }
//    }

    /**
     *  Removes a registered top painter.
     *
     *  @param  p   the painter to be removed from the paint queue
     *
     *  @synchronization  this method must be called in the event thread
     */
//    public void removeTopPainter( TopPainter p )
//    {
//      collTopPainters.remove( p );
//    }
   
    private void recreateImage()
    {
      if( image != null ) image.flush();
      image = createImage( recentSize.width, recentSize.height );
    }
   
    private void redrawImage()
    {
      if( image == null ) return;

      Graphics2D g2 = (Graphics2D) image.getGraphics();
//      g2.setColor( Color.white );
//      g2.fillRect( 0, 0, recentSize.width, recentSize.height );
      g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
//      g2.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
      if( fillArea ) {
        g2.setPaint( pntArea );
        g2.fill( pathTrns );
      }
      g2.setStroke( strkLine );
      g2.setPaint( pntLine );
      g2.draw( pathTrns );
      if( label != null ) {
        g2.setPaint( pntLabel );
        if( txtLay == null ) {
          txtLay    = new TextLayout( label, getFont(), g2.getFontRenderContext() );
          txtBounds   = txtLay.getBounds();
        }
        txtLay.draw( g2, recentSize.width - (float) txtBounds.getWidth() - 4,
                 recentSize.height - (float) txtBounds.getHeight() );
      }
      g2.dispose();
    }

    /*
     *  Recalculates a Java2D path from the vector
     *  that will be used for painting operations
     */
    private void recalcPath()
    {
      int      i;
      float    f1;
      float    f2 = (min - max) / recentSize.height + min;
     
      path.reset();
      if( vector.length > 0 ) {
        f1  = 1.0f / vector.length;
        path.moveTo( -0.01f, f2 );
        path.lineTo( -0.01f, vector[0] );
        for( i = 0; i < vector.length; i++ ) {
          path.lineTo( i * f1, vector[i] );
        }
        path.lineTo( 1.01f, vector[vector.length-1] );
        path.lineTo( 1.01f, f2 );
        path.closePath();
  // System.out.println( "recalced path" );
      }
      pathTrns = null;
    }

  // ---------------- VirtualSurface interface ----------------

    /*
     *  Recalculates the transforms between
     *  screen and virtual space
     */
    private void recalcTransforms()
    {
  // System.out.println( "recalc trns for "+recentSize.width+" x "+recentSize.height );

      trnsVirtualToScreen.setToTranslation( 0.0, recentSize.height );
      trnsVirtualToScreen.scale( recentSize.width,recentSize.height / (min - max) );
      trnsVirtualToScreen.translate( 0.0, -min );
      trnsScreenToVirtual.setToTranslation( 0.0, min );
      trnsScreenToVirtual.scale( 1.0 / recentSize.width, (min - max) / recentSize.height );
      trnsScreenToVirtual.translate( 0.0, -recentSize.height );

      pathTrns = path.createTransformedShape( trnsVirtualToScreen );
    }
   
    /**
     *  Converts a location on the screen
     *  into a point the virtual space.
     *  Neither input nor output point need to
     *  be limited to particular bounds
     *
     *  @param  screenPt    point in screen space
     *  @return the input point transformed to virtual space
     */
    public Point2D screenToVirtual( Point2D screenPt )
    {
      return trnsScreenToVirtual.transform( screenPt, null );
    }

    /**
     *  Converts a shape in the screen space
     *  into a shape in the virtual space.
     *
     *  @param  screenShape    arbitrary shape in screen space
     *  @return the input shape transformed to virtual space
     */
    public Shape screenToVirtual( Shape screenShape )
    {
      return trnsScreenToVirtual.createTransformedShape( screenShape );
    }

    /**
     *  Converts a point in the virtual space
     *  into a location on the screen.
     *
     *  @param  virtualPt   point in the virtual space whose
     *            visible bounds are (0, 0 ... 1, 1)
     *  @return        point in the display space
     */
    public Point2D virtualToScreen( Point2D virtualPt )
    {
      return trnsVirtualToScreen.transform( virtualPt, null );
    }

    /**
     *  Converts a shape in the virtual space
     *  into a shape on the screen.
     *
     *  @param  virtualShape  arbitrary shape in virtual space
     *  @return the input shape transformed to screen space
     */
    public Shape virtualToScreen( Shape virtualShape )
    {
      return trnsVirtualToScreen.createTransformedShape( virtualShape );
    }

    /**
     *  Converts a rectangle in the virtual space
     *  into a rectangle suitable for Graphics clipping
     *
     *  @param  virtualClip    a rectangle in virtual space
     *  @return the input rectangle transformed to screen space,
     *      suitable for graphics clipping operations
     */
    public Rectangle virtualToScreenClip( Rectangle2D virtualClip )
    {
      Point2D screenPt1 = trnsVirtualToScreen.transform( new Point2D.Double( virtualClip.getMinX(),
                                           virtualClip.getMinY() ), null );
      Point2D screenPt2 = trnsVirtualToScreen.transform( new Point2D.Double( virtualClip.getMaxX(),
                                           virtualClip.getMaxY() ), null );
   
      return new Rectangle( (int) Math.floor( screenPt1.getX() ), (int) Math.floor( screenPt1.getY() ),
                  (int) Math.ceil( screenPt2.getX() ), (int) Math.ceil( screenPt2.getY() ));
    }
  }
}
TOP

Related Classes of de.sciss.eisenkraut.util.DebugView$VectorDisplay

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.