void crossfade() {
    int[] pixA = startView.getPixels();
    int[] pixM = morphView.getPixels();
    int[] pixB = endView.getPixels();

    int width = morphView.getImgWidth();
    int height = morphView.getImgHeight();
    double a = morphPos;

    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        int posM = y * width + x;

        int argb = pixA[posM];
        int rA = (argb >> 16) & 0xff;
        int gA = (argb >> 8) & 0xff;
        int bA = (argb) & 0xff;

        argb = pixB[posM];

        int rB = (argb >> 16) & 0xff;
        int gB = (argb >> 8) & 0xff;
        int bB = (argb) & 0xff;

        int rM = (int) ((1 - a) * rA + a * rB);
        int gM = (int) ((1 - a) * gA + a * gB);
        int bM = (int) ((1 - a) * bA + a * bB);

        pixM[posM] = 0xFF000000 | (rM << 16) | (gM << 8) | bM;
      }
    }
  }
  private synchronized void calculate() {
    long startTime = System.currentTimeMillis();

    switch (method.getSelectedIndex()) {
      case 0:
        crossfade();
        break;
      case 1:
        scaleLeft();
        break;
        // TODO: add the other methods here
      default:
        Arrays.fill(morphView.getPixels(), 0xffffffff); // white image
        break;
    }

    morphView.applyChanges();

    long time = System.currentTimeMillis() - startTime;
    statusLine.setText("Processing time: " + time + " ms");
  }
  void scaleLeft() {

    // This implements a simple nearest neighbor scaling.
    // You may replace it by a bilinear scaling for better visual results

    int[] pixA = startView.getPixels();
    int[] pixM = morphView.getPixels();

    int width = morphView.getImgWidth();
    int height = morphView.getImgHeight();
    double a = morphPos;

    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        int posM = y * width + x;

        // current scaling
        double xS = scalingX * a + 1.0 * (1 - a);
        double yS = scalingY * a + 1.0 * (1 - a);

        // scaled coordinates in image A
        int xA = (int) (x * xS);
        int yA = (int) (y * yS);

        int argb = 0xffffffff; // white pixel

        if (xA >= 0 && xA < width && yA >= 0 && yA < height) {
          // we are inside image A
          argb = pixA[yA * width + xA];
        }

        int rM = (argb >> 16) & 0xff;
        int gM = (argb >> 8) & 0xff;
        int bM = (argb) & 0xff;

        pixM[posM] = 0xFF000000 | (rM << 16) | (gM << 8) | bM;
      }
    }
  }
  public Morph() {
    super(new BorderLayout(borderWidth, borderWidth));

    setBorder(BorderFactory.createEmptyBorder(borderWidth, borderWidth, borderWidth, borderWidth));

    // load the default images
    File input1 = new File("RedApple.jpg");
    if (!input1.canRead())
      input1 = openFile("Open Image 1"); // file not found, choose another image

    File input2 = new File("GreenApple.jpg");
    if (!input2.canRead())
      input2 = openFile("Open Image 2"); // file not found, choose another image

    startView = new ImageView(input1);
    startView.setMaxSize(new Dimension(maxWidth, maxHeight));

    endView = new ImageView(input2);
    endView.setMaxSize(new Dimension(maxWidth, maxHeight));

    // create empty image for morphing
    morphView = new ImageView(startView.getImgWidth(), startView.getImgHeight());
    morphView.setMaxSize(new Dimension(maxWidth, maxHeight));

    // control panel
    JPanel controls = new JPanel(new GridBagLayout());
    GridBagConstraints c = new GridBagConstraints();
    c.insets = new Insets(0, borderWidth, 0, 0);

    // transmission methods
    String[] methodNames = {
      "Cross-fading",
      "Scale left image",
      "Scale right image",
      "Scale & move left image",
      "Scale & move right image",
      "Morphing"
    };

    method = new JComboBox<String>(methodNames);
    method.setSelectedIndex(0); // set initial method
    method.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            calculate();
          }
        });
    // morphing position
    JLabel morphLabel = new JLabel("Morphing Position:");
    morphPos = 0;
    morphSlider =
        new JSlider(JSlider.HORIZONTAL, 0, sliderGranularity, (int) (morphPos * sliderGranularity));
    morphSlider.addChangeListener(
        new ChangeListener() {
          public void stateChanged(ChangeEvent e) {
            morphPos = morphSlider.getValue() / (double) sliderGranularity;
            calculate();
          }
        });

    controls.add(method, c);
    controls.add(morphLabel, c);
    controls.add(morphSlider, c);

    // images
    JPanel images = new JPanel(new GridLayout(1, 3));
    images.add(startView);
    images.add(morphView);
    images.add(endView);

    // status panel
    JPanel status = new JPanel(new GridLayout(1, 3));

    // some status text
    statusLine = new JLabel(" ");
    status.add(statusLine, c);

    add(controls, BorderLayout.NORTH);
    add(images, BorderLayout.CENTER);
    add(status, BorderLayout.SOUTH);

    calculate();
  }