/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package picturepresenter;

import ij.ImagePlus;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Slider;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.stage.FileChooser;
import javax.imageio.ImageIO;


public class SampleController implements Initializable {
    
    private ImagePlus imagePlus;
    
    List<String>  prahovaniChoice;
    
    @FXML
    private ChoiceBox<String> choiceBoxAlgorithm;
    @FXML
    private ChoiceBox<String> choiceBoxImage;
    @FXML
    private ChoiceBox<String> choiceBoxDitheringClass;
    @FXML
    private ChoiceBox<String> choiceBoxDitheringPrahSubClass;
    @FXML
    private ChoiceBox<String> choiceBoxDitheringSubClass;
    @FXML
    private ChoiceBox<String> choiceColorCount;
    int colorCount = 0;
    @FXML
    private ImageView imageViewOriginal;
    @FXML
    private ImageView ImageViewOutput;
    
    @FXML
    private Image lenaImage;
    @FXML
    private Image mrizImage;
    @FXML
    private Image kaktusImage;
    @FXML
    private Image houbyImage;
    @FXML
    private Image krajinaImage;
    @FXML
    private Image zvireImage;
       
    @FXML
    private RadioButton vodorovne;
    @FXML
    private RadioButton stridave;
    @FXML
    private ToggleGroup group = new ToggleGroup();
    @FXML
    private Slider      prahSlider;
    private double prahValue = 0.0;
    @FXML
    private Label      prahLabel;
    @FXML
    private Label      prahpLabel;
    @FXML
    private Label      pocetBarev;
    @FXML
    private Label      pruchodObrazem;
    @FXML
    private ImageView colorsImageView;
    
    
    @FXML
    private void handleSaveButtonAction(ActionEvent event) //uloen obrzku
    {
      FileChooser fileChooser = new FileChooser();
      File file = fileChooser.showSaveDialog(null);
        
      BufferedImage image = SwingFXUtils.fromFXImage(ImageViewOutput.getImage(), null);
      BufferedImage imageRGB = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.OPAQUE); //odstrann alfa kanlu
      Graphics2D graphics = imageRGB.createGraphics();
      graphics.drawImage(image, 0, 0, null);
        try {
            ImageIO.write (imageRGB, "png", file);
        } catch (IOException ex) {
            System.out.println("Expection: "+ex);
            Logger.getLogger(SampleController.class.getName()).log(Level.SEVERE, null, ex);
        }
      graphics.dispose();
    }    
    
    @FXML
    private void handleButtonAction(ActionEvent event) //tlatko aplikovat
    {
        Image original = imageViewOriginal.getImage();
        
        Image updatedImage = null;
        switch (choiceBoxDitheringClass.getValue()) //vbr metody
        {
          case "Prahovn":
          {
              switch(choiceBoxDitheringPrahSubClass.getValue())
              {
                  case "Automatick": updatedImage = otsu(original, colorCount, false, false);
                      break;
                  case "Manuln":    updatedImage = prah(original, prahValue);
                      break;
              }
          }
             break;
          case "Nhodn rozptyl":     updatedImage = nahodnyrozptyl(original);                           
              break;
          case "Maticov rozptyl":  ;     updatedImage = matice(original, colorCount, false, false) ;                      
              break;
          case "Distribuce chyby":
              switch(choiceBoxDitheringSubClass.getValue())
              {
                  case "Floyd-Steinberg": updatedImage = floyd(original, colorCount, false, false) ;
                      break;
                  case "Jarvis": updatedImage = jarvis(original, colorCount, false, false) ;
                      break;
                  case "Stucki": updatedImage = stucki(original, colorCount, false, false) ;
                      break;    
                  case "Atkinson": updatedImage = atkinson(original, colorCount, false, false) ;
                      break;
                  case "Burkes": updatedImage = burkes(original, colorCount, false, false) ;
                      break;
                  case "Sierra": updatedImage = sierra(original, colorCount, false, false) ;
                      break;
                  case "Shiau-Fan": updatedImage = shiau(original, colorCount, false, false) ;
                      break;
                  
              }
        }
        
        ImageViewOutput.setImage(updatedImage);        
        ImageViewOutput.setVisible(true);
    }
       
    @Override
    public void initialize(URL url, ResourceBundle rb) { //naten obrzk
        
        // create images
        lenaImage = new Image("/picturepresenter/lena.png");
        mrizImage = new Image("/picturepresenter/mriz.png");
        kaktusImage = new Image("/picturepresenter/kaktus.png");
        houbyImage = new Image("/picturepresenter/houby.png");
        krajinaImage = new Image("/picturepresenter/krajina.png");
        zvireImage = new Image("/picturepresenter/zvire.png");
        imageViewOriginal.setImage(new Image("/picturepresenter/lena.png"));
        ImageViewOutput.setImage(new Image("/picturepresenter/ramecek.PNG"));
         
        choiceBoxImage.getSelectionModel().selectedItemProperty().addListener(
           new ChangeListener<String>(){
               
            @Override //inicializace obrzk
            public void changed(ObservableValue<? extends String> ov, String t, String t1) {
                if(t1!=null)
                {
                  switch(t1) { //vbr obrzk
                    case "Lena":  imageViewOriginal.setImage(lenaImage);                            
                            break;
                    case "M":  imageViewOriginal.setImage(mrizImage);                            
                            break;
                    case "Kaktus":  imageViewOriginal.setImage(kaktusImage);                            
                            break;
                    case "Houby":  imageViewOriginal.setImage(houbyImage);                            
                            break;
                    case "Krajina":  imageViewOriginal.setImage(krajinaImage);                            
                            break;
                    case "Zve":  imageViewOriginal.setImage(zvireImage);                            
                            break;
                }
              }    
           }       
        });
        
        choiceBoxDitheringSubClass.setVisible(false);
        
        vodorovne.setToggleGroup(group); //prchod obrazem
        vodorovne.setSelected(true);
        stridave.setToggleGroup(group);
        
        prahValue = 0.5; //zkladn nastaven hodnoty prahu
        prahSlider.adjustValue(prahValue);
        prahLabel.setText("0.5");
        
        prahSlider.valueProperty().addListener(new ChangeListener<Number>() //slider prahovn
        {
          @Override
          public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) 
          {
            
            String value = newValue.toString();
            prahValue = newValue.doubleValue();
            
            if(value.length() > 3)
                value = value.substring(0,3);
            prahLabel.setText( value );
          }
        });
        
        colorsImageView.setImage(new Image("/picturepresenter/2.PNG")); //nastaven potu barev
        choiceColorCount.getSelectionModel().selectedItemProperty().addListener(
          new ChangeListener<String>()
            {
            @Override
            public void changed(ObservableValue<? extends String> ov, String t, String t1) 
            {
                if(t1!=null)
                {
                  switch(t1) //vbr barev
                  {
                    case "2": colorCount = 2; 
                              colorsImageView.setImage(new Image("/picturepresenter/2.PNG"));
                            break;
                    case "4": colorCount = 4; 
                            colorsImageView.setImage(new Image("/picturepresenter/4.PNG"));
                            break;
                    case "8": colorCount = 8; 
                            colorsImageView.setImage(new Image("/picturepresenter/8.PNG"));
                            break;
                    case "16": colorCount = 16; 
                            colorsImageView.setImage(new Image("/picturepresenter/16.PNG"));
                            break;
                    case "32": colorCount = 32;
                            colorsImageView.setImage(new Image("/picturepresenter/32.PNG"));
                            break;                          
                    case "64": colorCount = 64; 
                            colorsImageView.setImage(new Image("/picturepresenter/64.PNG"));
                           break;
                    case "128": colorCount = 128; 
                           colorsImageView.setImage(new Image("/picturepresenter/128.PNG"));
                           break;
                    case "256": colorCount = 256; 
                           colorsImageView.setImage(new Image("/picturepresenter/256.PNG"));
                           break;    
                  }
                }
            }
            });
                        
        //nastaven vyjmek zobrazen u jednotlivch metod
        choiceBoxDitheringClass.getSelectionModel().selectedItemProperty().addListener(
           new ChangeListener<String>(){
            @Override
            public void changed(ObservableValue<? extends String> ov, String t, String t1) {
                if(t1!=null)
                {
                  switch(t1) {
                    case "Prahovn":  
                        choiceBoxDitheringPrahSubClass.setItems(FXCollections.observableArrayList("Automatick","Manuln")); 
                        choiceBoxDitheringSubClass.setVisible(false);
                        choiceBoxDitheringPrahSubClass.setVisible(true);
                        pruchodObrazem.setVisible(false);
                        vodorovne.setVisible(false);
                        stridave.setVisible(false);
                    break;
                    case "Nhodn rozptyl":  ; 
                        choiceBoxDitheringSubClass.setVisible(false);
                        choiceBoxDitheringPrahSubClass.setVisible(false);
                        choiceColorCount.setVisible(true);
                        choiceColorCount.setValue("2");
                        pocetBarev.setVisible(true);
                        prahLabel.setVisible(false);
                        prahpLabel.setVisible(false);
                        prahSlider.setVisible(false);
                        pruchodObrazem.setVisible(false);
                        vodorovne.setVisible(false);
                        stridave.setVisible(false);
                    break;
                    case "Maticov rozptyl":  ;
                        choiceBoxDitheringSubClass.setVisible(false);
                        choiceBoxDitheringPrahSubClass.setVisible(false);
                        choiceColorCount.setVisible(true); 
                        pocetBarev.setVisible(true);
                        prahLabel.setVisible(false);
                        prahpLabel.setVisible(false);
                        prahSlider.setVisible(false);
                        pruchodObrazem.setVisible(false);
                        vodorovne.setVisible(false);
                        stridave.setVisible(false);
                    break;
                    case "Distribuce chyby":                    
                        choiceBoxDitheringSubClass.setItems(FXCollections.observableArrayList("Floyd-Steinberg","Jarvis","Stucki","Atkinson","Burkes","Sierra","Shiau-Fan")); 
                        choiceBoxDitheringPrahSubClass.setVisible(false);
                        choiceBoxDitheringSubClass.setVisible(true);
                        choiceColorCount.setVisible(true);
                        pocetBarev.setVisible(true);
                        prahLabel.setVisible(false);
                        prahpLabel.setVisible(false);
                        prahSlider.setVisible(false);
                        pruchodObrazem.setVisible(true);
                        vodorovne.setVisible(true);
                        stridave.setVisible(true);
                    break;
                }
              }    
           }       
        });
        
        //nastaven vyjmek u prahovn
        choiceBoxDitheringPrahSubClass.getSelectionModel().selectedItemProperty().addListener(
           new ChangeListener<String>(){
            @Override
            public void changed(ObservableValue<? extends String> ov, String t, String t1) {
                if(t1!=null)
                {
                  switch(t1) {
                    case "Automatick":  
                        choiceBoxDitheringSubClass.setVisible(false);
                        choiceBoxDitheringPrahSubClass.setVisible(true);
                        choiceColorCount.setVisible(true);
                        choiceColorCount.setValue("2");
                        pocetBarev.setVisible(true);
                        prahLabel.setVisible(false);
                        prahpLabel.setVisible(false);
                        prahSlider.setVisible(false);
                    break;
                    case "Manuln":  ; 
                        choiceBoxDitheringSubClass.setVisible(false);
                        choiceBoxDitheringPrahSubClass.setVisible(true);
                        choiceColorCount.setVisible(true);
                        choiceColorCount.setValue("2");
                        pocetBarev.setVisible(true);
                        prahLabel.setVisible(true);
                        prahpLabel.setVisible(true);
                        prahSlider.setVisible(true);
                    break;
                    
                }
              }    
           }       
        });
        
    }
    
    static double[] reduceColor(double actual_pixel_color, int ColorsCount) //reduke barev
    {
        return reduceColor( actual_pixel_color, ColorsCount, false);
    }
    
    static double[] reduceColor(double actual_pixel_color, int ColorsCount, boolean adaptive)
    {
      double[] result = new double[2];  
      switch(ColorsCount)
      {
          case 2: if( actual_pixel_color < 0.5 )
                  {
                    result[0] = 0.0;
                    result[1] = actual_pixel_color;
                  }
                  else
                  {
                    result[0] = 1.0;
                    result[1] = actual_pixel_color - 1.0;
                  }
              break;
          case 4:
          case 8:      
          case 16:  
          case 32:    
          case 64:       
          case 128:
          case 256:
          default:
              result[0] = 0.0;
              double krok = 1.0/((double)(ColorsCount-1));
              for (int i = 1; i < ColorsCount; i++ )
              {
                if(i+1 == ColorsCount)
                {
                    result[0] = 1.0;
                }
                if(actual_pixel_color <= (i * krok) -krok/2. )
                {
                    break;
                }
                result[0] = (i * krok);
              }
              break;
      }
        
      if(result[0] > 1.0)
          result[0] = 1.0;
       
      result[1] = actual_pixel_color - result[0];
       
      return result;
    }
    
    static double color2Double(Color color) //pevoid barvy na double
    {
        double result = 0.0;
        result = 0.299 * color.getRed() + 0.587 * color.getGreen() + 0.11 * color.getBlue();
        return result;
    }
    
    static Color double2Color(double color) //pevod double na barvu
    {
     if(color > 1.0)  
        color = 1.0;
     if (color < 0.0)  
        color =  0.0;
      Color result = new Color(color,color,color,1);
      return result;
    }
    
    
    static Image otsu(Image input, int ColorsCount) //agroritmus uren automatickho prahu
    {
        return otsu(input, ColorsCount, false, false);
    }
    static Image otsu(Image input, int ColorsCount, boolean adaptive_pallete, boolean alternativePassage)
    {
      int histIndexes = 256; //histogram
      int[] hist = new int[histIndexes];
      for (int i = 0; i < hist.length; i++)
      {
        hist[i] =0;
      }
  
      PixelReader pixelReader = input.getPixelReader();
      for (int readY = 0; readY < input.getHeight(); readY++) //cyklus prchodu obrazem
      {
        for (int readX = 0; readX < input.getWidth(); readX++) 
        {
             Color color = pixelReader.getColor(readX, readY);
             double actual_pixel = color2Double(color);
             int histIndex = (int)(actual_pixel * (double)histIndexes);
             
             if(histIndex <= histIndexes-1 )
               hist[histIndex] = hist[histIndex]+1;
             else
                 hist[histIndexes-1] = hist[histIndexes-1]+1;
                 
        }
      }
		int k,kStar; //inicializace promnnch
		double N1, N;  
		double BCV, BCVmax; 
		double num, denom;
		double Sk; 
		double S, L=histIndexes; 

		S = N = 0;
		for (k=0; k<L; k++){
			S += (double)k * hist[k];	
			N += hist[k];		
		}

		Sk = 0;
		N1 = hist[0];
		BCV = 0;
		BCVmax=0;
		kStar = 0;

		for (k=1; k<L-1; k++) { //cyklus prchodu obrazem
			Sk += (double)k * hist[k];
			N1 += hist[k];

			denom = (double)( N1) * (N - N1);

			if (denom != 0 ){
				num = ( (double)N1 / N ) * S - Sk;
				BCV = (num * num) / denom;
			}
			else
				BCV = 0;

			if (BCV >= BCVmax){
				BCVmax = BCV;
				kStar = k;
			}
		}
                
                System.out.println((double)kStar/(double)histIndexes);
		return prah(input, (double) kStar/ (double)histIndexes);
    }
    

    
    static Image prah(Image input,double prah) //prahovn
    {
        WritableImage outputImage = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter pixelOutputWriter = outputImage.getPixelWriter();
        PixelReader pixelReader = input.getPixelReader();
        
        for (int readY = 0; readY < input.getHeight(); readY++) //cyklus prochzen
        {
            for (int readX = 0; readX < input.getWidth(); readX++) 
            {
                Color color = pixelReader.getColor(readX, readY);
                double actual_pixel = color2Double(color);
                
                if ( actual_pixel > prah) //rozhodnut o zpisu barvy
                {
                   pixelOutputWriter.setColor(readX,readY, new Color(1,1, 1,1.0));
                }
                else
                {
                    pixelOutputWriter.setColor(readX,readY, new Color(0,0,0,1.0));
                }
            }
        }
        return outputImage; //vstup
    }
    
    static Image nahodnyrozptyl(Image input) //nhodn rozptyl
    {
      WritableImage output = new WritableImage((int)input.getWidth(),(int)input.getHeight());
      PixelWriter outputWriter = output.getPixelWriter();
      PixelReader pixelReader = input.getPixelReader();
      
      for (int readX = 0; readX < output.getWidth(); readX++) //cyklus prchodu
      {
        for (int readY = 0; readY < output.getHeight(); readY++) 
        {
          Color color = pixelReader.getColor(readX, readY);
          double grey = color2Double(color);
          if( grey > new Random().nextDouble()) //rozhodnut o zpisu barvy
            outputWriter.setColor(readX, readY, new Color(1,1,1,1));
          else
            outputWriter.setColor(readX, readY, new Color(0,0,0,1));
        }
      }
      return output;
    }
    
    
    
    static Image matice(Image input, int ColorsCount)
    {
        return matice(input, ColorsCount, false, false);
    }
    static Image matice(Image input, int ColorsCount, boolean adaptive_pallete, boolean alternativePassage)
    {
        boolean backStep = false; //inicializace promnnch
        double error = 0.0;
        double temp1 = 0.0;
        double temp2 = 0.0;
        double temp3 = 0.0;
        double temp4 = 0.0;       
        WritableImage outputImage = new WritableImage(
                 (int)input.getWidth(),
                 (int)input.getHeight());
        
        PixelWriter pixelWriter = outputImage.getPixelWriter();
        PixelReader originalPixelReader = input.getPixelReader();
        WritableImage wInput = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter wTempInputPixelWriter = wInput.getPixelWriter();
        
        for (int copyY = 0; copyY < wInput.getHeight(); copyY++) //kopie obrzku
        {
            for (int copyX = 0; copyX < wInput.getWidth(); copyX++) 
            {
                wTempInputPixelWriter.setColor(copyX,copyY,originalPixelReader.getColor( copyX, copyY));
            }
        }
        
        PixelReader pixelReader = wInput.getPixelReader(); 
        for (int readY = 0; readY < input.getHeight(); readY++) //cyklus prchodu obrazem
        {
            for (int readX = 0; readX < input.getWidth(); readX++) 
            {
                Color color = pixelReader.getColor(readX, readY);
                double actual_pixel = color2Double(color);
                
                double[] reduce_with_error = reduceColor(actual_pixel, ColorsCount, adaptive_pallete);
                pixelWriter.setColor(readX,readY, double2Color( reduce_with_error[0] ) );
                error = reduce_with_error[1]/16.;
    
                if( readX < input.getWidth()-1 ) //prchod po pixelech
                {
                  temp1 = color2Double(pixelReader.getColor(readX+1, readY)); //uren pozic pixelu
                  temp1 += error *(7./4.);                                    // vpoet chyby  
                  wTempInputPixelWriter.setColor(readX+1,readY, double2Color(temp1)); //zapsn chyby na pozici
                }
                if( readY < input.getHeight() -1 )
                {
                    temp2 = color2Double(pixelReader.getColor(readX, readY+1));
                    temp2 += error* (11./4.);
                    wTempInputPixelWriter.setColor(readX,readY+1, double2Color(temp2));
                    if( readX < input.getWidth() -1 )
                    { 
                       temp3 = color2Double(pixelReader.getColor(readX+1, readY+1));
                       temp3 += error*(4./4.);
                       wTempInputPixelWriter.setColor(readX+1,readY+1, double2Color(temp3));
                    }
                    if( readX > 0 )
                    {
                        
                    }
                }
            }
        }
        return outputImage; //vstup
    }
    
    
    
    static Image floyd(Image input, int ColorsCount)
    {
        return floyd(input, ColorsCount, false, false);
    }
    static Image floyd(Image input, int ColorsCount, boolean adaptive_pallete, boolean alternativePassage)
    {
        boolean backStep = false;
        double error = 0.0;
        double temp1 = 0.0;
        double temp2 = 0.0;
        double temp3 = 0.0;
        double temp4 = 0.0;    
        
        WritableImage outputImage = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter pixelWriter = outputImage.getPixelWriter();
        PixelReader originalPixelReader = input.getPixelReader();
        WritableImage wInput = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter wTempInputPixelWriter = wInput.getPixelWriter();
        
        for (int copyY = 0; copyY < wInput.getHeight(); copyY++) //kopie obrzku
        {
            for (int copyX = 0; copyX < wInput.getWidth(); copyX++) 
            {
                wTempInputPixelWriter.setColor(copyX,copyY,originalPixelReader.getColor( copyX, copyY));
            }
        }
        
        PixelReader pixelReader = wInput.getPixelReader(); 
        for (int readY = 0; readY < input.getHeight(); readY++) //cyklus prchodu obrazem
        {
            for (int X = 0; X < input.getWidth(); X++) 
            {
                int readX=0;                                    //smr prchodu obrazem
                if(backStep)
                {
                    readX = (int)(outputImage.getWidth() -1 - X);
                }
                else 
                {
                    readX = X;
                }
                
                Color color = pixelReader.getColor(readX, readY);
                double actual_pixel = color2Double(color);
                
                double[] reduce_with_error = reduceColor(actual_pixel, ColorsCount, adaptive_pallete);
                pixelWriter.setColor(readX,readY, double2Color( reduce_with_error[0] ) );
                error = reduce_with_error[1];
    
                if( readX < input.getWidth()-1 ) //prchod po pixelech
                {
                  temp1 = color2Double(pixelReader.getColor(readX+1, readY)); //uren pozic pixelu
                  temp1 += error *(7./16.);                                   // vpoet chyby 
                  wTempInputPixelWriter.setColor(readX+1,readY, double2Color(temp1));//zapsn chyby na pozici
                }
                if( readY < input.getHeight() -1 )
                {
                    temp2 = color2Double(pixelReader.getColor(readX, readY+1));
                    temp2 += error* (5./16.);
                    wTempInputPixelWriter.setColor(readX,readY+1, double2Color(temp2));
                    if( readX < input.getWidth() -1 )
                    { 
                       temp3 = color2Double(pixelReader.getColor(readX+1, readY+1));
                       temp3 += error*(1./16.);
                       wTempInputPixelWriter.setColor(readX+1,readY+1, double2Color(temp3));
                    }
                    if( readX > 0 )
                    {
                       temp4 = color2Double(pixelReader.getColor(readX-1, readY+1));
                       temp4 += error *(3./16.);
                       wTempInputPixelWriter.setColor(readX-1,readY+1, double2Color(temp4));
                    }
                }
            }
            if(alternativePassage)
            {
                backStep = ! backStep;
            }
        }
        return outputImage;
    }
    
    static Image jarvis(Image input,int ColorsCount )
    {
        return jarvis(input, ColorsCount,false,false);
    }
    static Image jarvis(Image input, int ColorsCount,boolean adaptive_pallete,boolean alternativePassage )
    {         
        boolean backStep = false;
        double error = 0.0;
        double temp1 = 0.0;
        double temp2 = 0.0;
        double temp3 = 0.0;
        double temp4 = 0.0;
        double temp5 = 0.0;
        double temp6 = 0.0;
        double temp7 = 0.0;
        double temp8 = 0.0;
        double temp9 = 0.0;
        double temp10 = 0.0;
        double temp11 = 0.0;
        double temp12 = 0.0;
        
        WritableImage outputImage = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter pixelWriter = outputImage.getPixelWriter();
        PixelReader originalPixelReader = input.getPixelReader();
        WritableImage wInput = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter wTempInputPixelWriter = wInput.getPixelWriter();
        
        for (int copyY = 0; copyY < wInput.getHeight(); copyY++) 
        {
            for (int copyX = 0; copyX < wInput.getWidth(); copyX++) 
            {
                wTempInputPixelWriter.setColor(copyX,copyY,originalPixelReader.getColor( copyX, copyY));
            }
        }
        
        PixelReader pixelReader = wInput.getPixelReader();         
        for (int readY = 0; readY < input.getHeight(); readY++) 
        {
            for (int X = 0; X < input.getWidth(); X++) 
            {
                int readX=0;
                if(backStep)
                {
                    readX = (int)(outputImage.getWidth() -1 - X);
                }
                else 
                {
                    readX = X;
                }
                
                Color color = pixelReader.getColor(readX, readY);
                double actual_pixel = color2Double(color);
                
                double[] reduce_with_error = reduceColor(actual_pixel, ColorsCount, adaptive_pallete);
                pixelWriter.setColor(readX,readY, double2Color( reduce_with_error[0] ) );
                error = reduce_with_error[1];
                
                if( readX < input.getWidth() -2 )
                {
                    temp1 = color2Double(pixelReader.getColor(readX+1, readY));
                    temp1=temp1+error*(7./48.);
                    wTempInputPixelWriter.setColor(readX+1,readY, double2Color(temp1));
             
                    temp2 = color2Double(pixelReader.getColor(readX+2, readY));
                    temp2=temp2+error*(5./48.);
                    wTempInputPixelWriter.setColor(readX+2,readY, double2Color(temp2));
                }
                if( readY < input.getHeight() -2 )
                {
                    temp3 = color2Double(pixelReader.getColor(readX, readY+1));
                    temp3=temp3+error*(7./48.); 
                    wTempInputPixelWriter.setColor(readX,readY+1, double2Color(temp3));
                         
                    temp4 = color2Double(pixelReader.getColor(readX, readY+2));
                    temp4=temp4+error*(5./48.);
                    wTempInputPixelWriter.setColor(readX,readY+2, double2Color(temp4));
                    if( readX < input.getWidth() -2 )   
                    {
                         temp5 = color2Double(pixelReader.getColor(readX+1, readY+1));
                         temp5=temp5+error*(5./48.); 
                         wTempInputPixelWriter.setColor(readX+1,readY+1, double2Color(temp5));

                         temp6 = color2Double(pixelReader.getColor(readX+2, readY+1));
                         temp6=temp6+error*(3./48.); 
                         wTempInputPixelWriter.setColor(readX+2,readY+1, double2Color(temp6));

                         temp7 = color2Double(pixelReader.getColor(readX+1, readY+2));
                         temp7=temp7+error*(3./48.); 
                         wTempInputPixelWriter.setColor(readX+1,readY+2, double2Color(temp7));

                         temp8 = color2Double(pixelReader.getColor(readX+2, readY+2));
                         temp8=temp8+error*(1./48.); 
                         wTempInputPixelWriter.setColor(readX+2,readY+2, double2Color(temp8));
                    }
                    if( readX > 1 )
                    {
                        temp9 = color2Double(pixelReader.getColor(readX-2, readY+1));
                        temp9=temp9+error*(3./48.); 
                        wTempInputPixelWriter.setColor(readX-2,readY+1, double2Color(temp9));

                        temp10 = color2Double(pixelReader.getColor(readX-1, readY+1));
                        temp10=temp10+error*(5./48.); 
                        wTempInputPixelWriter.setColor(readX-1,readY+1, double2Color(temp10));

                        temp11 = color2Double(pixelReader.getColor(readX-2, readY+2));
                        temp11=temp11+error*(1./48.); 
                        wTempInputPixelWriter.setColor(readX-2,readY+2, double2Color(temp11));

                        temp12 = color2Double(pixelReader.getColor(readX-1, readY+2));
                        temp12=temp12+error*(3./48.);  
                        wTempInputPixelWriter.setColor(readX-1,readY+2, double2Color(temp12));
                    }
                }
                
            }
            if(alternativePassage)
            {
                backStep = ! backStep;
            }
        }
        return outputImage;
    }
    
    static Image stucki(Image input,int ColorsCount )
    {
        return stucki(input, ColorsCount,false,false);
    }
    static Image stucki(Image input, int ColorsCount,boolean adaptive_pallete,boolean alternativePassage )
    {         
        boolean backStep = false;
        double error = 0.0;
        double temp1 = 0.0;
        double temp2 = 0.0;
        double temp3 = 0.0;
        double temp4 = 0.0;
        double temp5 = 0.0;
        double temp6 = 0.0;
        double temp7 = 0.0;
        double temp8 = 0.0;
        double temp9 = 0.0;
        double temp10 = 0.0;
        double temp11 = 0.0;
        double temp12 = 0.0;
        WritableImage outputImage = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter pixelWriter = outputImage.getPixelWriter();
        PixelReader originalPixelReader = input.getPixelReader();
        WritableImage wInput = new WritableImage(originalPixelReader,(int)input.getWidth(),(int)input.getHeight());
        PixelWriter wTempInputPixelWriter = wInput.getPixelWriter();
        
        for (int copyY = 0; copyY < wInput.getHeight(); copyY++) 
        {
            for (int copyX = 0; copyX < wInput.getWidth(); copyX++) 
            {
                wTempInputPixelWriter.setColor(copyX,copyY,originalPixelReader.getColor( copyX, copyY));
            }
        }
        
        PixelReader pixelReader = wInput.getPixelReader();         
        for (int readY = 0; readY < input.getHeight(); readY++) 
        {
            for (int X = 0; X < input.getWidth(); X++) 
            {
                int readX=0;
                if(backStep)
                {
                    readX = (int)(outputImage.getWidth() -1 - X);
                }
                else 
                {
                    readX = X;
                }
                
                Color color = pixelReader.getColor(readX, readY);
                double actual_pixel = color2Double(color);
                
                double[] reduce_with_error = reduceColor(actual_pixel, ColorsCount, adaptive_pallete);
                pixelWriter.setColor(readX,readY, double2Color( reduce_with_error[0] ) );
                error = reduce_with_error[1];
                
                if( readX < input.getWidth() -2 )
                {
                    temp1 = color2Double(pixelReader.getColor(readX+1, readY));
                    temp1=temp1+error*(8./42.);
                    wTempInputPixelWriter.setColor(readX+1,readY, double2Color(temp1));
             
                    temp2 = color2Double(pixelReader.getColor(readX+2, readY));
                    temp2=temp2+error*(4./42.);
                    wTempInputPixelWriter.setColor(readX+2,readY, double2Color(temp2));
                }
                if( readY < input.getHeight() -2 )
                {
                    temp3 = color2Double(pixelReader.getColor(readX, readY+1));
                    temp3=temp3+error*(8./42.); 
                    wTempInputPixelWriter.setColor(readX,readY+1, double2Color(temp3));
                         
                    temp4 = color2Double(pixelReader.getColor(readX, readY+2));
                    temp4=temp4+error*(4./42.);
                    wTempInputPixelWriter.setColor(readX,readY+2, double2Color(temp4));
                  if( readX < input.getWidth() -2 )
                  {
                       temp5 = color2Double(pixelReader.getColor(readX+1, readY+1));
                       temp5=temp5+error*(4./42.); 
                       wTempInputPixelWriter.setColor(readX+1,readY+1, double2Color(temp5));
                               
                       temp6 = color2Double(pixelReader.getColor(readX+2, readY+1));
                       temp6=temp6+error*(2./42.); 
                       wTempInputPixelWriter.setColor(readX+2,readY+1, double2Color(temp6));
                                 
                       temp7 = color2Double(pixelReader.getColor(readX+1, readY+2));
                       temp7=temp7+error*(2./42.); 
                       wTempInputPixelWriter.setColor(readX+1,readY+2, double2Color(temp7));
                 
                       temp8 = color2Double(pixelReader.getColor(readX+2, readY+2));
                       temp8=temp8+error*(1./42.); 
                       wTempInputPixelWriter.setColor(readX+2,readY+2, double2Color(temp8));
                  }
                  if( readX > 1 )
                  {
                      temp9 = color2Double(pixelReader.getColor(readX-2, readY+1));
                       temp9=temp9+error*(2./42.); 
                      wTempInputPixelWriter.setColor(readX-2,readY+1, double2Color(temp9));
                 
                      temp10 = color2Double(pixelReader.getColor(readX-1, readY+1));
                      temp10=temp10+error*(4./42.); 
                      wTempInputPixelWriter.setColor(readX-1,readY+1, double2Color(temp10));
                 
                      temp11 = color2Double(pixelReader.getColor(readX-2, readY+2));
                      temp11=temp11+error*(1./42.); 
                      wTempInputPixelWriter.setColor(readX-2,readY+2, double2Color(temp11));
                 
                      temp12 = color2Double(pixelReader.getColor(readX-1, readY+2));
                      temp12=temp12+error*(2./42.);  
                      wTempInputPixelWriter.setColor(readX-1,readY+2, double2Color(temp12));
                  }
                }
            }
            if(alternativePassage)
            {
                backStep = ! backStep;
            }
        }
        return outputImage;
    }
 
    static Image burkes(Image input,int ColorsCount,int Bin )
    {
      return burkes(input , ColorsCount, false , false);
    }
    static Image burkes(Image input,int ColorsCount,boolean adaptive_pallete, boolean alternativePassage )
    {   
        boolean backStep = false;
        double error = 0.0;
        double temp1 = 0.0;
        double temp2 = 0.0;
        double temp3 = 0.0;
        double temp4 = 0.0;
        double temp5 = 0.0;
        double temp6 = 0.0;
        double temp7 = 0.0;
        
        WritableImage outputImage = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter pixelWriter = outputImage.getPixelWriter();
        PixelReader originalPixelReader = input.getPixelReader();
        WritableImage wInput = new WritableImage(originalPixelReader,(int)input.getWidth(),(int)input.getHeight());
        PixelWriter wInputPixelWriter = wInput.getPixelWriter();
        
        for (int copyY = 0; copyY < wInput.getHeight(); copyY++) 
        {
            for (int copyX = 0; copyX < wInput.getWidth(); copyX++) 
            {
                wInputPixelWriter.setColor(copyX,copyY,originalPixelReader.getColor( copyX, copyY));
            }
        }

        PixelReader pixelReader = wInput.getPixelReader();
        for (int readY = 0; readY < wInput.getHeight(); readY++) 
        {
            for (int X = 0; X < wInput.getWidth(); X++) 
            {
                int readX=0;
                if(backStep)
                {
                    readX = (int)(wInput.getWidth() -1 - X);
                }
                else 
                {
                    readX = X;
                }
                Color color = pixelReader.getColor(readX, readY);
                double actual_pixel = color2Double(color);
                
                double[] reduce_with_error = reduceColor(actual_pixel, ColorsCount, adaptive_pallete);
                pixelWriter.setColor(readX,readY, double2Color( reduce_with_error[0] ) );
                error = reduce_with_error[1];
                
                if( readX < wInput.getWidth() -2 )
                {
                  temp1 = color2Double(pixelReader.getColor(readX+1, readY));
                  temp1 = temp1 +error*(8./32.);
                  wInputPixelWriter.setColor(readX+1,readY, double2Color(temp1));
                  
                  temp2 = color2Double(pixelReader.getColor(readX+2, readY));
                  temp2 = temp2+error*(4./32.); 
                  wInputPixelWriter.setColor(readX+2,readY, double2Color(temp2));
                }
                if( readY < input.getHeight() -2 )
                {
                  temp3 = color2Double(pixelReader.getColor(readX, readY+1));
                  temp3 = temp3+error*(8./32.); 
                  wInputPixelWriter.setColor(readX,readY+1, double2Color(temp3));
                  
                  if( readX < wInput.getWidth() -2 )
                  {
                    temp4 = color2Double(pixelReader.getColor(readX+1, readY+1));
                    temp4 = temp4 +error*(4./32.);
                    wInputPixelWriter.setColor(readX+1,readY+1, double2Color(temp4));
                  
                    temp5 = color2Double(pixelReader.getColor(readX+2, readY+1));
                    temp5 = temp5+error*(2./32.); 
                    wInputPixelWriter.setColor(readX+2,readY+1, double2Color(temp5));
                  }
                  if( readX > 1 )
                  {
                    temp6 = color2Double(pixelReader.getColor(readX-2, readY+1)); 
                    temp6 = temp6+error*(2./32.); 
                    wInputPixelWriter.setColor(readX-2,readY+1, double2Color(temp6));
                    
                    temp7 = color2Double(pixelReader.getColor(readX-1, readY+1));
                    temp7 = temp7+error*(4./32.); 
                    wInputPixelWriter.setColor(readX-1,readY+1, double2Color(temp7));
                  }
                }
            }
            if(alternativePassage)
            {
                backStep = ! backStep;
            }
        }
        return outputImage;
    }
    
    static Image atkinson(Image input, int ColorsCount) 
    {
        return atkinson(input, ColorsCount, false, false);
    }
    static Image atkinson(Image input, int ColorsCount, boolean adaptive_pallete,boolean alternativePassage )
    {
        boolean backStep = false;
        double error = 0.0;
        double temp1 = 0.0;
        double temp2 = 0.0;
        double temp3 = 0.0;
        double temp4 = 0.0;
        double temp5 = 0.0;
        double temp6 = 0.0;
        
        WritableImage outputImage = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter pixelWriter = outputImage.getPixelWriter();
        PixelReader originalPixelReader = input.getPixelReader();
        WritableImage wInput = new WritableImage(originalPixelReader,(int)input.getWidth(),(int)input.getHeight());
        PixelWriter wTempInputPixelWriter = wInput.getPixelWriter();
        
        for (int copyY = 0; copyY < wInput.getHeight(); copyY++) 
        {
            for (int copyX = 0; copyX < wInput.getWidth(); copyX++) 
            {
                wTempInputPixelWriter.setColor(copyX,copyY,originalPixelReader.getColor( copyX, copyY));
            }
        }

        PixelReader pixelReader = wInput.getPixelReader(); 
        for (int readY = 0; readY < input.getHeight(); readY++) 
        {
            for (int X = 0; X < input.getWidth(); X++) 
            {
                int readX=0;
                if(backStep)
                {
                    readX = (int)(outputImage.getWidth() -1 - X);
                }
                else 
                {
                    readX = X;
                }
                
                Color color = pixelReader.getColor(readX, readY);
                double actual_pixel = color2Double(color);
                
                double[] reduce_with_error = reduceColor(actual_pixel, ColorsCount, adaptive_pallete);
                pixelWriter.setColor(readX,readY, double2Color( reduce_with_error[0] ) );
                error = reduce_with_error[1];
                
                if( readX < input.getWidth()-2 )
                {
                    temp1 = color2Double(pixelReader.getColor(readX+1, readY));
                    temp1 = temp1+error*(1./8.);
                    wTempInputPixelWriter.setColor(readX+1,readY, double2Color(temp1));
             
                    temp2 = color2Double(pixelReader.getColor(readX+2, readY));
                    temp2 = temp2+error*(1./8.); 
                    wTempInputPixelWriter.setColor(readX+2,readY, double2Color(temp2));
                }
                if( readY < input.getHeight() -2)
                {
                    temp3 = color2Double(pixelReader.getColor(readX, readY+1));
                    temp3 = temp3+error*(1./8.);
                    wTempInputPixelWriter.setColor(readX,readY+1, double2Color(temp3));
                         
                    temp4 = color2Double(pixelReader.getColor(readX, readY+2));
                    temp4 = temp4+error*(1./8.);
                    wTempInputPixelWriter.setColor(readX,readY+2, double2Color(temp4));
                  if( readX < input.getWidth()-2 )
                  {
                      temp5 = color2Double(pixelReader.getColor(readX+1, readY+1));
                      temp5 = temp5+error*(1./8.);
                      wTempInputPixelWriter.setColor(readX+1,readY+1, double2Color(temp5));
                  }
                  if( readX > 1 )
                  {
                      temp6 = color2Double(pixelReader.getColor(readX-1, readY+1));
                      temp6 = temp6+error*(1./8.);
                      wTempInputPixelWriter.setColor(readX-1,readY+1, double2Color(temp6));
                  }
                }
            }
            if(alternativePassage)
            {
                backStep = ! backStep;
            }
        }
        return outputImage;
    }
    
    static Image sierra(Image input, int ColorsCount) 
    {
        return sierra(input, ColorsCount, false, false);
    }
    static Image sierra(Image input, int ColorsCount, boolean adaptive_pallete,boolean alternativePassage )
    {
        boolean backStep = false;
        double error = 0.0;
        double temp1 = 0.0;
        double temp2 = 0.0;
        double temp3 = 0.0;
        double temp4 = 0.0;
        double temp5 = 0.0;
        double temp6 = 0.0;
        double temp7 = 0.0;
        double temp8 = 0.0;
        double temp9 = 0.0;
        double temp10 = 0.0;
        double temp11 = 0.0;
        
        WritableImage outputImage = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter pixelWriter = outputImage.getPixelWriter();
        PixelReader originalPixelReader = input.getPixelReader();
        WritableImage wInput = new WritableImage(originalPixelReader,(int)input.getWidth(),(int)input.getHeight());
        PixelWriter wTempInputPixelWriter = wInput.getPixelWriter();
        
        for (int copyY = 0; copyY < wInput.getHeight(); copyY++) 
        {
            for (int copyX = 0; copyX < wInput.getWidth(); copyX++) 
            {
                wTempInputPixelWriter.setColor(copyX,copyY,originalPixelReader.getColor( copyX, copyY));
            }
        }

        PixelReader pixelReader = wInput.getPixelReader(); 
        for (int readY = 0; readY < input.getHeight(); readY++) 
        {
            for (int X = 0; X < input.getWidth(); X++) 
            {
                int readX=0;
                if(backStep)
                {
                    readX = (int)(outputImage.getWidth() -1 - X);
                }
                else 
                {
                    readX = X;
                }
                
                Color color = pixelReader.getColor(readX, readY);
                double actual_pixel = color2Double(color);
                
                double[] reduce_with_error = reduceColor(actual_pixel, ColorsCount, adaptive_pallete);
                pixelWriter.setColor(readX,readY, double2Color( reduce_with_error[0] ) );
                error = reduce_with_error[1];
                
                if( readX < input.getWidth()-2 )
                {
                    temp1 = color2Double(pixelReader.getColor(readX+1, readY));
                    temp1 = temp1+error*(5./32.);
                    wTempInputPixelWriter.setColor(readX+1,readY, double2Color(temp1));
             
                    temp2 = color2Double(pixelReader.getColor(readX+2, readY));
                    temp2 = temp2+error*(3./32.); 
                    wTempInputPixelWriter.setColor(readX+2,readY, double2Color(temp2));
                }
                if( readY < input.getHeight() -2)
                {
                    temp3 = color2Double(pixelReader.getColor(readX, readY+1));
                    temp3 = temp3+error*(5./32.);
                    wTempInputPixelWriter.setColor(readX,readY+1, double2Color(temp3));
                         
                    temp4 = color2Double(pixelReader.getColor(readX, readY+2));
                    temp4 = temp4+error*(3./32.);
                    wTempInputPixelWriter.setColor(readX,readY+2, double2Color(temp4));
                  if( readX < input.getWidth()-2 )
                  {
                      temp5 = color2Double(pixelReader.getColor(readX+1, readY+1));
                      temp5 = temp5+error*(4./32.);
                      wTempInputPixelWriter.setColor(readX+1,readY+1, double2Color(temp5));
                               
                      temp6 = color2Double(pixelReader.getColor(readX+2, readY+1));
                      temp6 = temp6+error*(2./32.);
                      wTempInputPixelWriter.setColor(readX+2,readY+1, double2Color(temp6));
                                 
                      temp7 = color2Double(pixelReader.getColor(readX+1, readY+2));
                      temp7 = temp7+error*(2./32.);
                      wTempInputPixelWriter.setColor(readX+1,readY+2, double2Color(temp7));
                  }
                  if( readX > 1 )
                  {
                      temp8 = color2Double(pixelReader.getColor(readX-2, readY+1));
                      temp8 = temp8+error*(2./32.);
                      wTempInputPixelWriter.setColor(readX-2,readY+1, double2Color(temp8));
                 
                      temp9 = color2Double(pixelReader.getColor(readX-1, readY+1));
                      temp9 = temp9+error*(4./32.);
                      wTempInputPixelWriter.setColor(readX-1,readY+1, double2Color(temp9));
                                 
                      temp10 = color2Double(pixelReader.getColor(readX-1, readY+2));
                      temp10 = temp10+error*(2./32.);
                      wTempInputPixelWriter.setColor(readX-1,readY+2, double2Color(temp10));
                  }
                }
            }
            if(alternativePassage)
            {
                backStep = ! backStep;
            }
        }
        return outputImage;
    }
    
    static Image shiau(Image input,int ColorsCount,int Bin )
    {
      return shiau(input , ColorsCount, false , false);
    }
    static Image shiau(Image input,int ColorsCount,boolean adaptive_pallete, boolean alternativePassage )
    {   
        boolean backStep = false;
        double error = 0.0;
        double temp1 = 0.0;
        double temp2 = 0.0;
        double temp3 = 0.0;
        double temp4 = 0.0;
        double temp5 = 0.0;
        
        WritableImage outputImage = new WritableImage((int)input.getWidth(),(int)input.getHeight());
        PixelWriter pixelWriter = outputImage.getPixelWriter();
        PixelReader originalPixelReader = input.getPixelReader();
        WritableImage wInput = new WritableImage(originalPixelReader,(int)input.getWidth(),(int)input.getHeight());
        PixelWriter wInputPixelWriter = wInput.getPixelWriter();
        
        for (int copyY = 0; copyY < wInput.getHeight(); copyY++) 
        {
            for (int copyX = 0; copyX < wInput.getWidth(); copyX++) 
            {
                wInputPixelWriter.setColor(copyX,copyY,originalPixelReader.getColor( copyX, copyY));
            }
        }

        PixelReader pixelReader = wInput.getPixelReader();
        for (int readY = 0; readY < wInput.getHeight(); readY++) 
        {
            for (int X = 0; X < wInput.getWidth(); X++) 
            {
                int readX=0;
                if(backStep)
                {
                    readX = (int)(wInput.getWidth() -1 - X);
                }
                else 
                {
                    readX = X;
                }
                Color color = pixelReader.getColor(readX, readY);
                double actual_pixel = color2Double(color);
                
                double[] reduce_with_error = reduceColor(actual_pixel, ColorsCount, adaptive_pallete);
                pixelWriter.setColor(readX,readY, double2Color( reduce_with_error[0] ) );
                error = reduce_with_error[1];
                
                if( readX < wInput.getWidth() -3 )
                {
                  temp1 = color2Double(pixelReader.getColor(readX+1, readY));
                  temp1 = temp1 +error*(8./16.);
                  wInputPixelWriter.setColor(readX+1,readY, double2Color(temp1));
                }
                if( readY < input.getHeight() -3 )
                {
                  temp2 = color2Double(pixelReader.getColor(readX, readY+1));
                  temp2 = temp2+error*(4./16.); 
                  wInputPixelWriter.setColor(readX,readY+1, double2Color(temp2));
                  if( readX > 2 )
                  {
                    temp3 = color2Double(pixelReader.getColor(readX-3, readY+1)); 
                    temp3 = temp3+error*(1./16.); 
                    wInputPixelWriter.setColor(readX-3,readY+1, double2Color(temp3));
                    
                    temp4 = color2Double(pixelReader.getColor(readX-2, readY+1));
                    temp4 = temp4+error*(1./16.); 
                    wInputPixelWriter.setColor(readX-2,readY+1, double2Color(temp4));
                    
                    temp5 = color2Double(pixelReader.getColor(readX-1, readY+1));
                    temp5 = temp5+error*(2./16.); 
                    wInputPixelWriter.setColor(readX-1,readY+1, double2Color(temp5));
                  }
                }
            }
            if(alternativePassage)
            {
                backStep = ! backStep;
            }
        }
        return outputImage;
    }    
}