import com.sun.glf.demos.*;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.font.*;
import java.awt.color.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;

import com.sun.glf.*;
import com.sun.glf.util.*;
import com.sun.glf.goodies.*;


public class LightPaintingComposition  implements CompositionFactory{
	
	//inizializziamo tutte le variabili che useremo in seguito
  File imageFile = new File("syberia30.jpg");

  String text = "Walk the Talk";

  Font textFont = new Font("serif", Font.BOLD, 80);

  Glyph glyph = new Glyph(new Font("serif", Font.PLAIN, 200), '@');

  int symbolTextGap = -200;

  int bannerGap = 20;

  Dimension margins = new Dimension(50, 50);

  Dimension lightOffset = new Dimension(400, 400);

  Anchor lightAnchor = Anchor.TOP_LEFT;

  Dimension lightSize = new Dimension(2000, 2000);

  Color lightColor = Color.white;

  int textureBlurRadius = 5;

  float ambiantLight = 0.3f;

  float lightIntensity = 2.5f;

  int heightScale = 15;

  Color extrudedColor = Color.gray;

  Color embossedColor = Color.black;

  LitSurfaceType surfaceType = LitSurfaceType.NORMAL;

  float carvedRedScale = 1f;

  float carvedGreenScale = 1f;

  float carvedBlueScale = 1f;

  Color backgroundColor = Color.white;

  boolean extrudeOrPunch = false;

  //la nostra classe implementa CompositionFactory perciò
  //per ogni variabile vanno ridefiniti i metodi che le gestiscono
  public boolean getExtrudeOrPunch(){
    return extrudeOrPunch;
  }

  public void setExtrudeOrPunch(boolean extrudeOrPunch){
    this.extrudeOrPunch = extrudeOrPunch;
  }

  public Color getBackgroundColor(){
    return backgroundColor;
  }

  public void setBackgroundColor(Color backgroundColor){
    this.backgroundColor = backgroundColor;
  }

  public float getCarvedRedScale(){
    return carvedRedScale;
  }

  public void setCarvedRedScale(float carvedRedScale){
    this.carvedRedScale = carvedRedScale;
  }

  public float getCarvedGreenScale(){
    return carvedGreenScale;
  }

  public void setCarvedGreenScale(float carvedGreenScale){
    this.carvedGreenScale = carvedGreenScale;
  }

  public float getCarvedBlueScale(){
    return carvedBlueScale;
  }

  public void setCarvedBlueScale(float carvedBlueScale){
    this.carvedBlueScale = carvedBlueScale;
  }

  public LitSurfaceType getSurfaceType(){
    return surfaceType;
  }

  public void setSurfaceType(LitSurfaceType surfaceType){
    this.surfaceType = surfaceType;
  }
  
  public Color getExtrudedColor(){
    return extrudedColor;
  }

  public void setExtrudedColor(Color extrudedColor){
    this.extrudedColor = extrudedColor;
  }

  public Color getEmbossedColor(){
    return embossedColor;
  }

  public void setEmbossedColor(Color embossedColor){
    this.embossedColor = embossedColor;
  }

  public File getImageFile(){
    return imageFile;
  }

  public void setImageFile(File imageFile){
    this.imageFile = imageFile;
  }

  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 Glyph getGlyph(){
    return glyph;
  }

  public void setGlyph(Glyph glyph){
    this.glyph = glyph;
  }

  public int getSymbolTextGap(){
    return symbolTextGap;
  }

  public void setSymbolTextGap(int symbolTextGap){
    this.symbolTextGap = symbolTextGap;
  }

  public int getBannerGap(){
    return bannerGap;
  }

  public void setBannerGap(int bannerGap){
    this.bannerGap = bannerGap;
  }

  public Dimension getMargins(){
    return margins;
  }

  public void setMargins(Dimension margins){
    this.margins = margins;
  }

  public Dimension getLightOffset(){
    return lightOffset;
  }

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

  public Anchor getLightAnchor(){
    return lightAnchor;
  }

  public void setLightAnchor(Anchor lightAnchor){
    this.lightAnchor = lightAnchor;
  }

  public Dimension getLightSize(){
    return lightSize;
  }

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

  public Color getLightColor(){
    return lightColor;
  }

  public void setLightColor(Color lightColor){
    this.lightColor = lightColor;
  }

  public int getTextureBlurRadius(){
    return textureBlurRadius;
  }

  public void setTextureBlurRadius(int textureBlurRadius){
    this.textureBlurRadius = textureBlurRadius;
  }

  public float getAmbiantLight(){
    return ambiantLight;
  }

  public void setAmbiantLight(float ambiantLight){
    this.ambiantLight = ambiantLight;
  }

  public float getLightIntensity(){
    return lightIntensity;
  }

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

  public int getHeightScale(){
    return heightScale;
  }

  public void setHeightScale(int heightScale){
    this.heightScale = heightScale;
  }

  //il metodo build si occupa della costruzione della composizione
  //il codice riguardante i vari livelli prende posto qui
  public Composition build(){

    //carichiamo un'immagine per la texture dello sfondo
    BufferedImage image = Toolbox.loadImage(imageFile, BufferedImage.TYPE_INT_ARGB_PRE);
    if(image==null)
      throw new Error("Could not load : " + imageFile);

    //con il seguente codice costruiamo la forma di base che utilizzeremo    
    //recuperiamo la prima forma attraverso l'attributo glyph
    Shape symbol = glyph.getShape();
    
    //creiamo la forma del testo e gestiamo la posizione
    Shape textShape = TextLayer.makeTextBlock(text, textFont);
    Position symbolPosition = new Position(Anchor.LEFT, symbolTextGap, 0);
    symbol = symbolPosition.createTransformedShape(symbol, textShape.getBounds());
    //creiamo l'oggetto finale con un GeneralPath
    GeneralPath bannerShape = new GeneralPath(textShape);
    bannerShape.append(symbol, false);

    //creaiamo la composizione e gestiamo le dimensioni
    Rectangle bounds = bannerShape.getBounds();
    Dimension size = new Dimension(bounds.width + 2*margins.width, bounds.height*3 + 2*bannerGap + 2*margins.height);
    LayerComposition cmp = new LayerComposition(size);
    cmp.setBackgroundPaint(backgroundColor);

    //inseriamo l'immagine di sfondo nella composizione
    //e adattiamo le dimensioni
    if(image.getWidth()>size.width)
      image = image.getSubimage(0, 0, size.width, image.getHeight());
    if(image.getHeight()>size.height)
      image = image.getSubimage(0, 0, image.getWidth(), size.height);
    
    AffineTransform textureScale = AffineTransform.getScaleInstance(size.width/(float)image.getWidth(),
								    size.height/(float)image.getHeight());
    AffineTransformOp textureScaler = new AffineTransformOp(textureScale, AffineTransformOp.TYPE_BILINEAR);
    image = textureScaler.filter(image, null);

    //creiamo le tre forme, una per ogni livello
    Position embossedPosition = new Position(Anchor.TOP_RIGHT, margins.width, margins.height);
    Shape embossedBannerShape = embossedPosition.createTransformedShape(bannerShape, cmp.getBounds());
    
    Position carvedPosition = new Position(Anchor.LEFT, margins.width, 0);
    Shape carvedBannerShape = carvedPosition.createTransformedShape(bannerShape, cmp.getBounds());
    
    Position extrudedPosition = new Position(Anchor.BOTTOM_LEFT, margins.width, margins.height);
    Shape extrudedBannerShape = extrudedPosition.createTransformedShape(bannerShape, cmp.getBounds());
    
    //creaimo la luce e le ElevationMap che utilizzeremo per i LightOp
    //creiamo la SpotLight
    Rectangle lightRect = cmp.getBounds();
    lightRect.x = lightOffset.width;
    lightRect.y = lightOffset.height;
    SpotLight spot = LightsStudio.getSpotLight(lightRect, 
					       lightAnchor, 
					       lightSize,
					       lightIntensity, lightColor);

    //creaiamo la prima ElevationMap e la aggiungiamo alla prima LitSurface (dell'estrusione)
    ElevationMap extrudedMap = buildExtrudedMap(size, extrudedBannerShape, textureBlurRadius, heightScale, extrudeOrPunch);
    LitSurface backgroundSurface = new LitSurface(ambiantLight, surfaceType, extrudedMap);
    backgroundSurface.addLight(spot);

    //creiamo ElevationMap e Litsurface per il banner in rilievo
    ElevationMap embossedMap = new ElevationMap(embossedBannerShape, textureBlurRadius, true, heightScale);
    LitSurface embossedBannerSurface = new LitSurface(ambiantLight, LitSurfaceType.NORMAL, embossedMap);
    embossedBannerSurface.addLight(spot);

    //creiamo ElevationMap e Litsurface per il banner inciso
    ElevationMap carvedMap = new ElevationMap(carvedBannerShape, textureBlurRadius, false, heightScale);
    LitSurface carvedBannerSurface = new LitSurface(ambiantLight, LitSurfaceType.NORMAL, carvedMap);
    carvedBannerSurface.addLight(spot);

    //creaimo il livello di sfondo e lo filtriamo
    ImageLayer backgroundLayer = new ImageLayer(cmp, image);
    backgroundLayer.setRasterFilter(new LightOp(backgroundSurface));

    //costruiamo il livello per il banner inciso con la relativa texture (presa dallo sfondo)
    //e applichiamo il filtro LightOp
    TexturePaint texturePaint = new TexturePaint(image, cmp.getBounds());
    FillRenderer texturePainter = new FillRenderer(texturePaint);
    ShapeLayer carvedBannerLayer = new ShapeLayer(cmp, carvedBannerShape.getBounds(), texturePainter);
    LightOp carver = new LightOp(carvedBannerSurface);
    //applichiamo un filtro RescaleOp per aumentare la leggibilità
    RescaleOp scaleOp = new RescaleOp(new float[]{carvedRedScale, carvedGreenScale, carvedBlueScale, 1}, new float[]{0, 0, 0, 0}, null);
    //il CompositeRasterOp serve per applicare l'effetto dei due filtri creati
    CompositeRasterOp carvedFilter = new CompositeRasterOp(scaleOp, carver);
    carvedBannerLayer.setRasterFilter(carvedFilter);
    carvedBannerLayer.setLayerMask(carvedBannerShape);

    //costruiamo il livello per il banner in rilievo
    ShapeLayer embossedBannerLayer = new ShapeLayer(cmp, embossedBannerShape.getBounds(), texturePainter);
    embossedBannerLayer.setLayerMask(embossedBannerShape);
    //applichiamo il filtro LightOp
    LightOp embosser = new LightOp(embossedBannerSurface);
    embossedBannerLayer.setRasterFilter(embosser);

    //creiamo l'ultimo livello, quello con l'ElevationMap personalizzata
    ShapeLayer extrudedAdjustmentLayer = new ShapeLayer(cmp, extrudedBannerShape, texturePainter);

    //creaiamo un livello per aggiungere un bagliore scuro sul banner in rilievo
    //per aumentare la leggibilità
    ShapeLayer embossedStandOutLayer = new ShapeLayer(cmp, embossedBannerShape, new StrokeRenderer(embossedColor, textureBlurRadius));
    ConvolveOp blur = new ConvolveOp(new GaussianKernel(textureBlurRadius));
    embossedStandOutLayer.setImageFilter(blur, new Dimension(textureBlurRadius*2, textureBlurRadius*2));

    //definizione della pila dei layer nella composizione
    //e dei RenderingHints
    cmp.setLayers(new Layer[]{backgroundLayer, extrudedAdjustmentLayer, 
				carvedBannerLayer, embossedStandOutLayer, 
				embossedBannerLayer});
		cmp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
			 RenderingHints.VALUE_ANTIALIAS_ON);
    return cmp;
  }

  //metodo per creare un'estrusione personalizzata
  private ElevationMap buildExtrudedMap(Dimension size, 
					Shape shape, 
					int blurRadius, 
					int heightScale,
					boolean extrudeOrPunch){
    //creaimo una BufferedImage grande abbastanza per lo
    //sfocamento dell'estrusione e settiamo i RenderingHints
    BufferedImage buffer = new BufferedImage(size.width, 
					     size.height,
					     BufferedImage.TYPE_BYTE_GRAY);
    Graphics2D g = buffer.createGraphics();
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
		       RenderingHints.VALUE_ANTIALIAS_ON);

    g.setPaint(Color.white);
    g.fill(shape);
    g.dispose();

    //filtro per la sfocatura
    ConvolveOp blur = new ConvolveOp(new GaussianKernel(blurRadius));
    buffer = blur.filter(buffer, null);

    //sovrapponiamo una scritta nitida per aumentare la leggibilità
    g = buffer.createGraphics();
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
		       RenderingHints.VALUE_ANTIALIAS_ON);
    g.setPaint(Color.white);
    g.fill(shape);
    g.dispose();

    return new ElevationMap(buffer, extrudeOrPunch, heightScale);
  }
	
public static void main(String args[])
	{
      CompositionFrame a = new CompositionFrame("LightPaintingComposition");
      a.setSize(800,800);
      
      LightPaintingComposition lpc = new LightPaintingComposition();
      a.setComposition(lpc.build());
      a.setVisible(true);
      
  }

}
