/* BlinkenLightsInteractiveMovieProgram
 * version 0.5 date 2004-11-19
 * Copyright (C) 2004: Stefan Schuermans <1stein@schuermans.info>
 * Copyleft: GNU public license - http://www.gnu.org/copyleft/gpl.html
 * a blinkenarea.org project
 * powered by eventphone.de
 */

import java.awt.*;

public class BlinkenFrame
{

  private int height;
  private int width;
  private int channels;
  private int maxval;
  private int duration;
  private byte[][][] data;

  BlinkenFrame( int height, int width, int channels, int maxval, int duration )
  {
    if( height < 1 ) height = 1;
    if( height > 1024 ) height = 1024;
    if( width < 1 ) width = 1;
    if( width > 1024 ) width = 1024;
    if( channels < 1 ) channels = 1;
    if( channels > 16 ) channels = 16;
    if( maxval < 1 ) maxval = 1;
    if( maxval > 255 ) maxval = 255;
    if( duration < 1 ) duration = 1;
    if( duration > 65535 ) duration = 65535;

    this.height = height;
    this.width = width;
    this.channels = channels;
    this.maxval = maxval;
    this.duration = duration;
    data = new byte[height][width][channels];
  }

  BlinkenFrame( BlinkenFrame frame )
  {
    int y, x, c;
    height = frame.height;
    width = frame.width;
    channels = frame.channels;
    maxval = frame.maxval;
    duration = frame.duration;
    data = new byte[height][width][channels];
    for( y = 0; y < height; y++ )
      for( x = 0; x < width; x++ )
        for( c = 0; c < channels; c++ )
          data[y][x][c] = frame.data[y][x][c];
  }

  public void clear( )
  {
    int x, y, c;
    for( y = 0; y < height; y++ )
      for( x = 0; x < width; x++ )
        for( c = 0; c < channels; c++ )
          data[y][x][c] = 0;
  }

  public int getHeight( )
  {
    return height;
  }

  public int getWidth( )
  {
    return width;
  }

  public int getChannels( )
  {
    return channels;
  }

  public int getMaxval( )
  {
    return maxval;
  }

  public int getDuration( )
  {
    return duration;
  }

  public void setDuration( int duration )
  {
    if( duration < 1 ) duration = 1;
    if( duration > 65535 ) duration = 65535;
    this.duration = duration;
  }

  public byte getPixel( int y, int x, int c )
  {
    if( y < 0 || y >= height ||
        x < 0 || x >= width ||
	c < 0 || c >= channels )
      return 0;
    return data[y][x][c];
  }

  public void setPixel( int y, int x, int c, byte val )
  {
    if( y < 0 || y >= height ||
        x < 0 || x >= width ||
	c < 0 || c >= channels )
      return;
    if( val > maxval )
      val = (byte)maxval;
    data[y][x][c] = val;
  }

  public Color getColor( int y, int x )
  {
    if( y < 0 || y >= height ||
        x < 0 || x >= width ||
        channels < 1 )
      return new Color( 0, 0, 0 );
    if( channels == 1 )
      return new Color( (((int)data[y][x][0] & 0xFF) * 255 + maxval / 2) / maxval,
                        (((int)data[y][x][0] & 0xFF) * 255 + maxval / 2) / maxval,
                        (((int)data[y][x][0] & 0xFF) * 255 + maxval / 2) / maxval );
    if( channels == 2 )
      return new Color( (((int)data[y][x][0] & 0xFF) * 255 + maxval / 2) / maxval,
                        (((int)data[y][x][1] & 0xFF) * 255 + maxval / 2) / maxval, 0 );
    return new Color( (((int)data[y][x][0] & 0xFF) * 255 + maxval / 2) / maxval,
                      (((int)data[y][x][1] & 0xFF) * 255 + maxval / 2) / maxval,
                      (((int)data[y][x][2] & 0xFF) * 255 + maxval / 2) / maxval );
  }

  public void setColor( int y, int x, Color color )
  {
    int alpha, alpha_, c;
    if( y < 0 || y >= height ||
        x < 0 || x >= width )
      return;
    alpha = color.getAlpha( );
    alpha_ = 255 - alpha;
    if( channels >= 1 )
      data[y][x][0] = (byte)(( ((color.getRed( ) * maxval + 127) / 255) * alpha
                             + ((int)data[y][x][0] & 0xFF) * alpha_
                             ) / 255);
    if( channels >= 2 )
      data[y][x][1] = (byte)(( ((color.getGreen( ) * maxval + 127) / 255) * alpha
                             + ((int)data[y][x][1] & 0xFF) * alpha_
                             ) / 255);
    if( channels >= 3 )
      data[y][x][2] = (byte)(( ((color.getBlue( ) * maxval + 127) / 255) * alpha
                             + ((int)data[y][x][2] & 0xFF) * alpha_
                             ) / 255);
    for( c = 3; c < channels; c++ )
      data[y][x][c] = (byte)(( 0
                             + ((int)data[y][x][c] & 0xFF) * alpha_
                             ) / 255);
  }

  public void resize( int height, int width, int channels, int maxval )
  {
    byte[][][] data;
    int y, x, c;
    int emptyY, emptyX, skipY, skipX, rangeY, rangeX, val, div;

    if( height < 1 ) height = 1;
    if( height > 1024 ) height = 1024;
    if( width < 1 ) width = 1;
    if( width > 1024 ) width = 1024;
    if( channels < 1 ) channels = 1;
    if( channels > 16 ) channels = 16;
    if( maxval < 1 ) maxval = 1;
    if( maxval > 255 ) maxval = 255;

    if( height == this.height &&
        width == this.width &&
        channels == this.channels &&
        maxval == this.maxval )
      return;

    //allocate new data array
    data = new byte[height][width][channels];
    for( y = 0; y < height; y++ )
      for( x = 0; x < width; x++ )
        for( c = 0; c < channels; c++ )
          data[y][x][c] = 0;

    //get number of pixels to skip / to leave empty in X and Y direction
    if( height > this.height )
    {
      emptyY = (height - this.height) / 2; 
      skipY = 0;
      rangeY = this.height;
    }
    else
    {
      emptyY = 0;
      skipY = (this.height - height) / 2;
      rangeY = height;
    }
    if( width > this.width )
    {
      emptyX = (width - this.width) / 2; 
      skipX = 0;
      rangeX = this.width;
    }
    else
    {
      emptyX = 0;
      skipX = (this.width - width) / 2;
      rangeX = width;
    }

    //resize frame with help of calculated parameters
    for( y = 0; y < rangeY; y++ )
    {
      for( x = 0; x < rangeX; x++ )
      {
        if( channels >= this.channels ) //add channels: copy last channel into new channels
	{
          for( c = 0; c < this.channels; c++ )
            data[emptyY + y][emptyX + x][c] = (byte)((((int)this.data[skipY + y][skipX + x][c] & 0xFF) * maxval + this.maxval / 2) / this.maxval);
	  for( ; c < channels; c++ )
	    data[emptyY + y][emptyX + x][c] = data[emptyY + y][emptyX + x][this.channels - 1];
	}
	else //remove channels: merge leftover channels with last kept channel
	{
          val = 0;
          for( c = 0; c < channels - 1; c++ )
            data[emptyY + y][emptyX + x][c] = (byte)((((int)this.data[skipY + y][skipX + x][c] & 0xFF) * maxval + this.maxval / 2) / this.maxval);
          for( c = channels - 1; c < this.channels; c++ )
            val += (int)this.data[skipY + y][skipX + x][c] & 0xFF;
          div = this.maxval * (this.channels - channels + 1);
          data[emptyY + y][emptyX + x][channels - 1] = (byte)((val * maxval + div / 2) / div);
	}
      }
    }

    this.height = height;
    this.width = width;
    this.channels = channels;
    this.maxval = maxval;
    this.data = data;
  }

  public void scale( int height, int width )
  {
    byte[][][] data;
    double scaleHor, scaleVer, ox, oy, ox1, oy1, val;
    int c, nx, ny, x, y, oxi, oyi, ox1i, oy1i;

    if( height < 1 ) height = 1;
    if( height > 1024 ) height = 1024;
    if( width < 1 ) width = 1;
    if( width > 1024 ) width = 1024;

    if( height == this.height &&
        width == this.width )
      return;

    scaleHor = (double)width / (double)this.width;
    scaleVer = (double)height / (double)this.height;

    //allocate new data array
    data = new byte[height][width][channels];

    //scale every channel
    for( c = 0; c < channels; c++ )
    {
      for( ny = 0; ny < height; ny++ )
      {
        for( nx = 0; nx < width; nx++ )
        {
          oy = (double)ny / scaleVer; //sub-pixel exact range in old picture
          ox = (double)nx / scaleHor;
          oy1 = (double)(ny + 1) / scaleVer - 0.000001;
          ox1 = (double)(nx + 1) / scaleHor - 0.000001;
          if( oy < 0 || ox < 0 || oy1 >= this.height || ox1 >= this.width) //out of old picture
            data[ny][nx][c] = 0;
          else
          {
            oyi = (int)oy;
            oxi = (int)ox;
            oy1i = (int)oy1;
            ox1i = (int)ox1;
            if( oyi == oy1i )
            {
              if( oxi == ox1i) //one source pixel
              {
                val = (double)((int)this.data[oyi][oxi][c] & 0xFF);
              }
              else //one line of source pixels
              {
                val = (double)((int)this.data[oyi][oxi][c] & 0xFF) * (1 - ox + oxi)
                    + (double)((int)this.data[oyi][ox1i][c] & 0xFF) * (ox1 - ox1i);
                for( x = oxi + 1; x < ox1i; x++ )
                  val += (double)((int)this.data[oyi][x][c] & 0xFF);
                val /= ox1 - ox;
              }
            }
            else //one column of source pixels
            {
              if( oxi == ox1i )
              {
                val = (double)((int)this.data[oyi][oxi][c] & 0xFF) * (1 - oy + oyi)
                    + (double)((int)this.data[oy1i][oxi][c] & 0xFF) * (oy1 - oy1i);
                for( y = oyi + 1; y < oy1i; y++ )
                  val += (double)((int)this.data[y][oxi][c] & 0xFF);
                val /= oy1 - oy;
              }
              else //rectangle of source pixels
              {
                val = (double)((int)this.data[oyi][oxi][c] & 0xFF) * (1 - oy + oyi) * (1 - ox + oxi)
                    + (double)((int)this.data[oyi][ox1i][c] & 0xFF) * (1 - oy + oyi) * (ox1 - ox1i)
                    + (double)((int)this.data[oy1i][oxi][c] & 0xFF) * (oy1 - oy1i) * (1 - ox + oxi)
                    + (double)((int)this.data[oy1i][ox1i][c] & 0xFF) * (oy1 - oy1i) * (ox1 - ox1i);
                for( y = oyi + 1; y < oy1i; y++ )
                {
                  val += (double)((int)this.data[y][oxi][c] & 0xFF) * (1 - ox + oxi)
                       + (double)((int)this.data[y][ox1i][c] & 0xFF) * (ox1 - ox1i);
                }
                for( x = oxi + 1; x < ox1i; x++ )
                {
                  val += (double)((int)this.data[oyi][x][c] & 0xFF) * (1 - oy + oyi)
                       + (double)((int)this.data[oy1i][x][c] & 0xFF) * (oy1 - oy1i);
                }
                for( y = oyi + 1; y < oy1i; y++ )
                  for( x = oxi + 1; x < ox1i; x++ )
                    val += (double)((int)this.data[y][x][c] & 0xFF);
                val /= (oy1 - oy) * (ox1 - ox);
              }
            }
            data[ny][nx][c] = (byte)(int)(val + 0.5);
          }
        } //for( nx ...
      } //for( ny ...
    } //for( c ...

    this.height = height;
    this.width = width;
    this.data = data;
  }

  public String toString( )
  {
    String str = "";
    int h, w, c, val;
    for( h = 0; h < height; h++ )
    {
      for( w = 0; w < width; w++ )
      {
        val = 0;
        for( val = 0, c = 0; c < channels; c++ )
          val += (data[h][w][c] & 0xFF);
        val = val * 7 / maxval / channels;
        str = str + " -+*%#&@".substring( val, val + 1); 
      }
      str = str + "\n";
    }
    return str + duration + " ms\n";
  }

} //public class BlinkenFrame
