/*
Marco Riommi
Java Applicato alla Grafica 2D - SMC
aa 05/06
*/

import com.sun.glf.demos.gallery.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.event.*;
import java.io.*;
import com.sun.glf.*;
import com.sun.glf.goodies.*;
import com.sun.glf.util.*;
import com.sun.glf.util.*;

public class BrushedMetal implements CompositionFactory{
  
  //inizializziamo tutte le variabili che useremo in seguito
  Dimension size = new Dimension(800, 600);

  int textureNoiseDensity = 32;

  Color textureNoiseColor = new Color(200, 200, 200);

  Color textureColor = new Color(100, 100, 100);

  int textureBlurWidth = 100;

  int textureScaleFactor = 40;

  int textureHeightScale = 8;

  int lightFocus = 4;

  //parametri per le SpotLight
  int lightOffset = -100;
  Dimension lightSize = new Dimension(1600, 1000);
  Anchor lightAnchorYellow = Anchor.TOP;
  Anchor lightAnchorRed = Anchor.BOTTOM;
  double lightIntensity = 3.2;
  Color lightColorYellow = new Color(255, 230, 50);
  Color lightColorRed = new Color(255, 150, 100);
  double lightAmbient = .2;

  String text = "SMC";

  Font textFont = new Font("serif", Font.BOLD, 320);
  
  String longTextTop = "Scienza dei Media e";
  
  String longTextBottom = "della Comunicazione";
  
  Font longTextFont = new Font("sansserif", Font.BOLD, 76);

  Color textShadowColor = new Color(0, 0, 0, 128);

  Dimension textShadowOffset = new Dimension(20, 20);

  int textSeparationOffset = 2;

  Color textSeparationColor = Color.gray;

  LitSurfaceType surfaceType = LitSurfaceType.METALLIC;

  boolean useTextureMap = true;

  int shadowBlurRadius = 8;
  
  File imageFile = new File("syberia84.jpg");
  
  int bumpAngleTop = 45;
  
  int bumpAngleBottom = -45;

  //la classe implementa CompositionFactory perciò
  //bisogna ridefinire i seguenti metodi
  public int getShadowBlurRadius(){
    return shadowBlurRadius;
  }

  public void setShadowBlurRadius(int shadowBlurRadius){
    this.shadowBlurRadius = shadowBlurRadius;
  }

  public int getLightFocus(){
    return lightFocus;
  }

  public void setLightFocus(int lightFocus){
    this.lightFocus = lightFocus;
  }

  public boolean getUseTextureMap(){
    return useTextureMap;
  }

  public void setUseTextureMap(boolean useTextureMap){
    this.useTextureMap = useTextureMap;
  }

  public LitSurfaceType getSurfaceType(){
    return surfaceType;
  }

  public void setSurfaceType(LitSurfaceType surfaceType){
    this.surfaceType = surfaceType;
  }

  public int getTextSeparationOffset(){
    return textSeparationOffset;
  }

  public void setTextSeparationOffset(int textSeparationOffset){
    this.textSeparationOffset = textSeparationOffset;
  }

  public Color getTextSeparationColor(){
    return textSeparationColor;
  }

  public void setTextSeparationColor(Color textSeparationColor){
    this.textSeparationColor = textSeparationColor;
  }

  public String getText(){
    return text;
  }

  public void setText(String text){
    this.text = text;
  }

  public Font getTextFont(){
    return textFont;
  }

  public void setTextFont(Font textFont){
    this.textFont = textFont;
  }
  
  public String getLongTextTop(){
    return longTextTop;
  }

  public void setLongTextTop(String longTextTop){
    this.longTextTop = longTextTop;
  }

	public String getLongTextBottom(){
    return longTextBottom;
  }

  public void setLongTextBottom(String longTextBottom){
    this.longTextBottom = longTextBottom;
  }

  public Font getLongTextFont(){
    return longTextFont;
  }

  public void setLongTextFont(Font longTextFont){
    this.longTextFont = longTextFont;
  }

  public Color getTextShadowColor(){
    return textShadowColor;
  }

  public void setTextShadowColor(Color textShadowColor){
    this.textShadowColor = textShadowColor;
  }

  public Dimension getTextShadowOffset(){
    return textShadowOffset;
  }

  public void setTextShadowOffset(Dimension textShadowOffset){
    this.textShadowOffset = textShadowOffset;
  }

  public Dimension getSize(){
    return size;
  }

  public void setSize(Dimension size){
    this.size = size;
  }

  public int getTextureNoiseDensity(){
    return textureNoiseDensity;
  }

  public void setTextureNoiseDensity(int textureNoiseDensity){
    this.textureNoiseDensity = textureNoiseDensity;
  }

  public Color getTextureNoiseColor(){
    return textureNoiseColor;
  }

  public void setTextureNoiseColor(Color textureNoiseColor){
    this.textureNoiseColor = textureNoiseColor;
  }

  public Color getTextureColor(){
    return textureColor;
  }

  public void setTextureColor(Color textureColor){
    this.textureColor = textureColor;
  }

  public int getTextureBlurWidth(){
    return textureBlurWidth;
  }

  public void setTextureBlurWidth(int textureBlurWidth){
    this.textureBlurWidth = textureBlurWidth;
  }

  public int getTextureScaleFactor(){
    return textureScaleFactor;
  }

  public void setTextureScaleFactor(int textureScaleFactor){
    this.textureScaleFactor = textureScaleFactor;
  }

  public int getTextureHeightScale(){
    return textureHeightScale;
  }

  public void setTextureHeightScale(int textureHeightScale){
    this.textureHeightScale = textureHeightScale;
  }

  public int getLightOffset(){
    return lightOffset;
  }

  public void setLightOffset(int lightOffset){
    this.lightOffset = lightOffset;
  }

  public Dimension getLightSize(){
    return lightSize;
  }

  public void setLightSize(Dimension lightSize){
    this.lightSize = lightSize;
  }

  public Anchor getLightAnchorYellow(){
    return lightAnchorYellow;
  }

  public void setLightAnchorYellow(Anchor lightAnchorYellow){
    this.lightAnchorYellow = lightAnchorYellow;
  }
  
  public Anchor getLightAnchorRed(){
    return lightAnchorRed;
  }

  public void setLightAnchorRed(Anchor lightAnchorRed){
    this.lightAnchorRed = lightAnchorRed;
  }

  public double getLightIntensity(){
    return lightIntensity;
  }

  public void setLightIntensity(double lightIntensity){
    this.lightIntensity = lightIntensity;
  }

  public Color getLightColorYellow(){
    return lightColorYellow;
  }

  public void setLightColorYellow(Color lightColorYellow){
    this.lightColorYellow = lightColorYellow;
  }

	public Color getLightColorRed(){
    return lightColorRed;
  }

  public void setLightColorRed(Color lightColorRed){
    this.lightColorRed = lightColorRed;
  }

  public double getLightAmbient(){
    return lightAmbient;
  }

  public void setLightAmbient(double lightAmbient){
    this.lightAmbient = lightAmbient;
  }

  //il seguente metodo serve a creare la texture con l'effetto
  //striato metallico che verrà applicata sulle scritte
  private BufferedImage createTexture(){
    
    // 1.crea una BufferedImage di base
    int bufWidth = 2*textureBlurWidth + (int)Math.rint(size.width/textureScaleFactor);
    int bufHeight = 2*textureBlurWidth + size.height;
    BufferedImage buf = new BufferedImage(bufWidth, bufHeight, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = buf.createGraphics();
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    // 2.mette un colore di sfondo
    g.setPaint(textureColor);
    g.fillRect(0, 0, bufWidth, size.height);
    
    // 3.aggiunge un disturbo casuale tracciando rettangolini di un altro colore
    int cols = bufWidth/10 + 1;
    int rows = bufHeight/10 + 1;
    g.setPaint(textureNoiseColor);
    Rectangle2D.Float noiseRect = new Rectangle2D.Float(0, 0, 1, 1);
    //le dimensioni e il numero dei rettangolini sono definiti dai 
    //seguenti cicli e in maniera casuale
    for (int i=0; i<cols; i++){
      for(int j=0; j<rows; j++){
	for(int k=0; k<textureNoiseDensity; k++){
	  noiseRect.x = i*10 + (float)(10*Math.random());
	  noiseRect.y = j*10 + (float)(10*Math.random());
	  noiseRect.width = 10*(float)Math.random();
	  noiseRect.height = (float)Math.random();
	  g.fill(noiseRect);
	}
      }
    }

    // 4.sfocamento orizzontale del disturbo per un effetto più realistico
    float horBlur[] = new float[textureBlurWidth];
    for(int i=0; i<textureBlurWidth; i++)
      horBlur[i] = 1f/(float)textureBlurWidth;
    Kernel horBlurKernel = new Kernel(textureBlurWidth, 1, horBlur);
    ConvolveOp blur = new ConvolveOp(horBlurKernel);
    buf = blur.filter(buf, null);
    buf = buf.getSubimage(textureBlurWidth, textureBlurWidth, size.width/textureScaleFactor, size.height);

    // 5.scala l'immagine per itensificare l'effetto 'brush'
    RenderingHints rh = new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    AffineTransform scale = AffineTransform.getScaleInstance(textureScaleFactor, 1);
    AffineTransformOp scaleOp = new AffineTransformOp(scale, rh);
    buf = scaleOp.filter(buf, null);
    
    return buf;
  }

  //il seguente metodo crea la composizione e i vari livelli
  public Composition build(){
  	//creiamo la composizione
    LayerComposition cmp = new LayerComposition(size);
    Rectangle cmpRect = new Rectangle(0, 0, size.width, size.height);

    //creiamo una SpotLight gialla e la posizioniamo
    Rectangle lightRectYellow = (Rectangle)cmpRect.clone();
    AffineTransform lightPositionAdjustmentYellow = lightAnchorYellow.getAdjustmentTranslation(lightOffset, lightOffset);
    lightRectYellow.x = (int)lightPositionAdjustmentYellow.getTranslateX();
    lightRectYellow.y = (int)lightPositionAdjustmentYellow.getTranslateY();
    SpotLight lightYellow = LightsStudio.getSpotLight(lightRectYellow, 
						lightAnchorYellow, 
						lightSize,
						lightIntensity, lightColorYellow);
    lightYellow.setNp(lightFocus);
    
    //creiamo una SpotLight rossa e la posizioniamo
    Rectangle lightRectRed = (Rectangle)cmpRect.clone();
    AffineTransform lightPositionAdjustmentRed = lightAnchorRed.getAdjustmentTranslation(lightOffset, lightOffset);
    lightRectRed.x = (int)lightPositionAdjustmentRed.getTranslateX();
    lightRectRed.y = (int)lightPositionAdjustmentRed.getTranslateY();
    SpotLight lightRed = LightsStudio.getSpotLight(lightRectRed, 
						lightAnchorRed, 
						lightSize,
						lightIntensity, lightColorRed);
    lightRed.setNp(lightFocus);
    
    //creiamo una LitSurface e aggiungiamo le SpotLight
    LitSurface litSurface = new LitSurface(lightAmbient, surfaceType, null);
    litSurface.addLight(lightYellow);
    litSurface.addLight(lightRed);

    //creiamo un LightOp usando la LitSurface appena creata
    LightOp op = new LightOp(litSurface);

    //costruiamo il livello di sfondo caricando una BufferedImage riscalata
    //per adattarla alla composizione
    BufferedImage img = Toolbox.loadImage(imageFile, BufferedImage.TYPE_INT_ARGB_PRE);
    AffineTransform textureScale = AffineTransform.getScaleInstance(size.width/(float)img.getWidth(), size.height/(float)img.getHeight());
    AffineTransformOp textureScaler = new AffineTransformOp(textureScale, AffineTransformOp.TYPE_BILINEAR);
    img = textureScaler.filter(img, null);
    ImageLayer litBackground = new ImageLayer(cmp,img);
    litBackground.setRasterFilter(op);

    //creiamo il filtro composito per sfuocare l'ombra
    float blurScale = 3f/shadowBlurRadius;
    GaussianKernel kernel = new GaussianKernel(3);
    
    AffineTransform shrinkTxf = AffineTransform.getScaleInstance(blurScale, blurScale);
    AffineTransform blowUpTxf = AffineTransform.getScaleInstance(1/blurScale, 1/blurScale);
    AffineTransformOp shrinkOp = new AffineTransformOp(shrinkTxf, null);
    AffineTransformOp blowUpOp = new AffineTransformOp(blowUpTxf, AffineTransformOp.TYPE_BILINEAR);
    ConvolveOp smallBlurOp = new ConvolveOp(kernel);

    CompositeOp blur = new CompositeOp(new BufferedImageOp[]{shrinkOp, smallBlurOp, blowUpOp});
    Dimension blurMargins = new Dimension(shadowBlurRadius*2, shadowBlurRadius*2);

    //creiamo il livello con la scritta centrale
    Shape textShape = TextLayer.makeTextBlock(text, textFont, size.width, TextAlignment.CENTER);
    textShape = Position.CENTER.createTransformedShape(textShape, cmpRect);
    ShapeLayer textShadowLayer = new ShapeLayer(cmp, textShape, new FillRenderer(textShadowColor));
    AffineTransform shadowAdjustment = lightAnchorYellow.getAdjustmentTranslation(textShadowOffset.width, textShadowOffset.height);
    textShadowLayer.setTransform(shadowAdjustment);
    textShadowLayer.setImageFilter(blur, blurMargins);
    
    //creiamo il livello con la scritta superiore e gli applichiamo
    //le opportune tasformazioni
    Shape longTextShapeTop = TextLayer.makeTextBlock(longTextTop, longTextFont, size.width, TextAlignment.CENTER);
    BumpTransform transformTop = new BumpTransform((float)bumpAngleTop);
    longTextShapeTop = transformTop.transform(longTextShapeTop);
    longTextShapeTop = Position.TOP.createTransformedShape(longTextShapeTop, cmpRect);
    ShapeLayer longTextShadowLayerTop = new ShapeLayer(cmp, longTextShapeTop, new FillRenderer(textShadowColor));
    AffineTransform longShadowAdjustmentTop = lightAnchorYellow.getAdjustmentTranslation(textShadowOffset.width, textShadowOffset.height);
    longTextShadowLayerTop.setTransform(longShadowAdjustmentTop);
    longTextShadowLayerTop.setImageFilter(blur, blurMargins);
    
    //creiamo il livello con la scritta inferiore e gli applichiamo
    //le opportune tasformazioni
    Shape longTextShapeBottom = TextLayer.makeTextBlock(longTextBottom, longTextFont, size.width, TextAlignment.CENTER);
    BumpTransform transformBottom = new BumpTransform((float)bumpAngleBottom);
    longTextShapeBottom = transformBottom.transform(longTextShapeBottom);
    longTextShapeBottom = Position.BOTTOM.createTransformedShape(longTextShapeBottom, cmpRect);
    ShapeLayer longTextShadowLayerBottom = new ShapeLayer(cmp, longTextShapeBottom, new FillRenderer(textShadowColor));
    AffineTransform longShadowAdjustmentBottom = lightAnchorRed.getAdjustmentTranslation(textShadowOffset.width, textShadowOffset.height);
    longTextShadowLayerBottom.setTransform(longShadowAdjustmentBottom);
    longTextShadowLayerBottom.setImageFilter(blur, blurMargins);

    //aggiungiamo un livello per separare l'ombra dal testo
    ShapeLayer textSeparationLayerTop= new ShapeLayer(cmp, 
						      textShape,
						      new FillRenderer(textSeparationColor));
    textSeparationLayerTop.setTransform(lightAnchorYellow.getAdjustmentTranslation(-textSeparationOffset, -textSeparationOffset));

    //creiamo la texture da applicare alle scritte usando il metodo
    //visto in precedenza
    BufferedImage brushedMetal = createTexture();
    BufferedImage longBrushedMetalTop = createTexture();
    BufferedImage longBrushedMetalBottom = createTexture();

    //creiamo la LitSurface e applichiamo una ElevationMap alla critta centrale
    litSurface = new LitSurface(lightAmbient, surfaceType, null);
    if(useTextureMap){
      ElevationMap map = new ElevationMap(brushedMetal, true, textureHeightScale);
      litSurface.setElevationMap(map);
    }
    litSurface.addLight(lightYellow);
    litSurface.addLight(lightRed);
    op = new LightOp(litSurface);

    //creiamo gli ImageLayer con le textures create e le applichiamo
    //usando come maschera i livelli con le scritte
    ImageLayer metalTextLayer = new ImageLayer(cmp, brushedMetal, Position.CENTER);
    metalTextLayer.setRasterFilter(op);
    metalTextLayer.setLayerMask(textShape);
    
    ImageLayer metalLongTextLayerTop = new ImageLayer(cmp, longBrushedMetalTop, Position.TOP);
    metalLongTextLayerTop.setRasterFilter(op);
    metalLongTextLayerTop.setLayerMask(longTextShapeTop);
    
    ImageLayer metalLongTextLayerBottom = new ImageLayer(cmp, longBrushedMetalBottom, Position.BOTTOM);
    metalLongTextLayerBottom.setRasterFilter(op);
    metalLongTextLayerBottom.setLayerMask(longTextShapeBottom);

    //settiamo i RenderingHints e la pila dei livelli della composizione
    cmp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    cmp.setLayers(new Layer[]{litBackground, textShadowLayer, textSeparationLayerTop, metalTextLayer, longTextShadowLayerTop, metalLongTextLayerTop, longTextShadowLayerBottom, metalLongTextLayerBottom});

    return cmp;
  }
  
 public static void main(String args[])
	{
      CompositionFrame mioFrame = new CompositionFrame("BrushedMetal by Marco Riommi");
      BrushedMetal bm = new BrushedMetal();
      
      mioFrame.setSize(800,800);
      mioFrame.setComposition(bm.build());
      mioFrame.setVisible(true);
      
  }
  
}
