Package ar.rules

Source Code of ar.rules.SeamCarving$Weights

package ar.rules;


import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;

import ar.Aggregates;
import ar.Renderer;
import ar.Transfer;
import ar.aggregates.AggregateUtils;
import ar.aggregates.wrappers.TransposeWrapper;
import ar.rules.SeamCarving.AbstractCarver.SeamList;
import ar.rules.combinators.Seq;
import ar.util.Util;

/** Seam-carving is a content-sensitive image resizing technique.
*
* The basic idea is that not all pixels are equally important.
* Therefore, some can be removed without changing the image as
* much as others.  The low-importance pixels do not need to all be
* in the same row (or column) but they do need to be contiguous and
* only one removed from each column (or row). 
*
* This collection of classes adapts the technique to the abstract
* rendering framework and generalizes it from just pixels to arbitrary
* aggregate sets.
*
*
* Original paper and optimal seam-finding:
* Avidan and Shamir; "Seam Carving for Content Aware Image Resizing"
* http://www.win.tue.nl/~wstahw/edu/2IV05/seamcarving.pdf
*
* Faster, approximate seam finding methods:
* Huang, Fu, Rosin, Qi; "Real-time content-aware image resizing"
* http://link.springer.com/article/10.1007%2Fs11432-009-0041-9#page-1
*/
public class SeamCarving {
  /**Calculate the difference between two values.**/
  public static interface Delta<A> {public double delta(A left, A right);}
 
  /**Carve horizontally or vertically?**/
  public enum Direction {H,V}
 
  /**Interface for instrumented Carvers.**/
  public static abstract class AbstractCarver<A> implements Transfer.Specialized<A,A>{
    protected final Delta<A> delta;
    protected final A empty;
    protected final Direction dir;
    protected final int seams;
   
    public AbstractCarver(Delta<A> delta, Direction dir, A empty, int seams)  {
      this.delta = delta;
      this.empty = empty;
      this.dir = dir;
      this.seams= seams;
    }
   
    @Override public final A emptyValue() {return empty;}

    @Override public final ar.Transfer.Specialized<A, A> specialize(Aggregates<? extends A> aggregates) {return this;}

    @Override
    public final Aggregates<A> process(Aggregates<? extends A> aggregates, Renderer rend) {
      if (seams ==0) {return AggregateUtils.copy(aggregates,emptyValue());}
     
      if (dir == Direction.H) {
        Aggregates<? extends A> transposed = TransposeWrapper.transpose(aggregates);
        int[][] seamList = verticalSeamList(transposed , rend).seams;
        Aggregates<A> aggs = carveN(transposed, sortRows(transpose(seamList)));
        return TransposeWrapper.transpose(aggs);     
      } else {
        int[][] seamList = verticalSeamList(aggregates, rend).seams;
        return carveN(aggregates, sortRows(transpose(seamList)));
      }
    }
   
    /**Determine a set of vertical seams.
     */
    public abstract SeamList verticalSeamList(Aggregates<? extends A> aggregates, Renderer rend);
   
   
    public static final class SeamList {
      final int[][] seams;
      final double[] weights;
      final double maxWeight, minWeight;
     
      public SeamList(int[][] seamList, double[] weights) {
        this.seams = seamList;
        this.weights = weights;

        double maxWeight=Double.NEGATIVE_INFINITY, minWeight=Double.POSITIVE_INFINITY;
        for (int i=0; i<weights.length; i++) {
          maxWeight = Math.max(maxWeight, weights[i]);
          minWeight = Math.min(minWeight, weights[i]);
        }
        this.maxWeight= maxWeight;
        this.minWeight= minWeight;
      }
    }
  }
 
  /**Find and remove seams per the w1 method of Huang, et al.
   *
   * Each pair of adjacent rows is treated independently in a pre-processing step
   * to determine the best linkage between all items in the rows (thus "RowPair").
   *
   * This carving method finds all of the seams it will remove entirely in the source
   * image, and thus does not create as nice of images as the CarveIncremental BUT it is
   * much faster.  The principal downfall of this method is that it only uses local information
   * to create linkages.
   */
  public static class CarveSweep<A> extends AbstractCarver<A> {
    /**
     * @param delta Comparison function used to compute the energy matrix
     * @param dir Direction seams run
     * @param empty
     * @param seams How many seams to remove
     */
    public CarveSweep(Delta<A> delta, Direction dir, A empty, int seams)  {super(delta, dir, empty, seams);}
   
    @Override
    public SeamList verticalSeamList(Aggregates<? extends A> aggregates, Renderer rend) {
      if (seams == 0) {return new SeamList(new int[0][0],new double[0]);}
     
      Aggregates<Double> pixelEnergy = rend.transfer(aggregates, new Energy<>(delta));
      EdgeWeights weights = new EdgeWeights(pixelEnergy);

      //Matchings encode the offset to get to the matched node in the next level down.  Will always be -1/0/1
      Aggregates<Integer> matchings = matchings(weights, AggregateUtils.make(pixelEnergy, 0d));
      return computeSeamList(seams, matchings, weights);
    }
   
    /**Utility class, encapsulates a local energy matrix and computes
     * a between-pixel energy matrix.
     */
    public static final class EdgeWeights implements Weights {
      final Aggregates<Double> pixelEnergy;
      public EdgeWeights(Aggregates<Double> pixelEnergy) {this.pixelEnergy = pixelEnergy;}

      public double between(int x1, int y1, int x2, int y2) {
        if (!validPoint(x1,y1) || !validPoint(x2,y2)) {return Double.NEGATIVE_INFINITY;}
        return pixelEnergy.get(x1, y1) * pixelEnergy.get(x2, y2); //w1 version
        //return pixelEnergy.get(x1, y1) + pixelEnergy.get(x2, y2); //w version
      }
     
      public boolean validPoint(int x, int y) {
        return x >= pixelEnergy.lowX() && x < pixelEnergy.highX()
            && y >= pixelEnergy.lowY() && y < pixelEnergy.highY();
      }
    }
  }
 
  /**Find and remove seams per the w2 method of Huang, et al.
   *
   * Uses a reverse cumulative energy matrix (similar to CarveIncremental)
   * but creates all seams at once (like CarveSweep).
   * In a reverse cumulative energy matrix M, the value M(i,j) is the lowest-cost
   * seam that starts at (i,j) and goes to the last row.  This integrates
   * some global information into the matching equations with a cost of only
   * one additional sweep through the matrix.
   */
  public static class CarveTwoSweeps<A> extends AbstractCarver<A> {
    public CarveTwoSweeps(Delta<A> delta, Direction dir, A empty, int seams)  {
      super(delta, dir, empty, seams);
    }
   
    @Override
    public SeamList verticalSeamList(Aggregates<? extends A> aggregates, Renderer rend) {
      if (seams == 0) {return new SeamList(new int[0][0],new double[0]);}
     
      Aggregates<Double> globalEnergy = rend.transfer(
          aggregates,
          new Seq.Specialized<>(new Energy<>(delta), new CumulativeEnergy()));
     
      Aggregates<Double> cumEng = AggregateUtils.make(globalEnergy, Double.NEGATIVE_INFINITY);
      for (int x=cumEng.lowX(); x< cumEng.highX(); x++) {cumEng.set(x, cumEng.lowY(), 0d);}

      EdgeWeights weights = new EdgeWeights(globalEnergy, cumEng);
 
      Aggregates<Integer> matchings = matchings(weights, cumEng);
     
      return computeSeamList(seams, matchings, weights);
    }
 
    /**Utility class, encapsulates a local energy matrix and computes
     * a between-pixel energy matrix.
     */
    public static final class EdgeWeights implements Weights {
      final Aggregates<Double> globalEnergy;
      final Aggregates<Double> cumEnergy;
      public EdgeWeights(Aggregates<Double> globalEnergy, Aggregates<Double> cumEnergy) {
        this.globalEnergy = globalEnergy;
        this.cumEnergy = cumEnergy;
      }
 
      public double between(int x1, int y1, int x2, int y2) {
        if (!validPoint(x1,y1) || !validPoint(x2,y2)) {return Double.NEGATIVE_INFINITY;}
        double w = cumEnergy.get(x1, y1) * globalEnergy.get(x2, y2); //cumEnergy to here * best-case energy to last row
        if (w!=0) {System.out.println(w);}
        return w;
       
      }
     
      public boolean validPoint(int x, int y) {
        return x >= globalEnergy.lowX() && x < globalEnergy.highX()
            && y >= globalEnergy.lowY() && y < globalEnergy.highY();
      }
    }
  }
 
  /**Find exactly N seams and remove them.  Unlike CarveSweep and CarveTwoSweeps,
   * which calculate seams for all pixels in all rows, this version only calculates
   * N total seams.
   */
  public static class CarveSweepN<A> extends AbstractCarver<A> {
    public CarveSweepN(Delta<A> delta, Direction dir, A empty, int seams)  {
      super(delta, dir, empty, seams);
    }
       
    @Override
    public SeamList verticalSeamList(Aggregates<? extends A> aggregates, Renderer rend) {
      if (seams == 0) {return new SeamList(new int[0][0],new double[0]);}
   
      Aggregates<Double> pixelEnergy = rend.transfer(aggregates, new Energy<>(delta));
      Aggregates<Double> globalEnergy = rend.transfer(pixelEnergy, new CumulativeEnergy());
     
      Aggregates<Double> cumEng = AggregateUtils.make(globalEnergy, Double.NEGATIVE_INFINITY);

      EdgeWeights weights = new EdgeWeights(globalEnergy, cumEng);
 
      Aggregates<Integer> matchings = nSeamsMatchings(seams, weights, cumEng, globalEnergy);
     
      return new SeamList(compileSeamList(matchings, seams), seamEnergies(cumEng, cumEng.highY()-1));
    }
   
    public int[][] compileSeamList(Aggregates<Integer> matchings, int seams) {
      int[][] seamList = new int[seams][matchings.highY()-matchings.lowY()];
     
      for (int y=matchings.lowY(); y<matchings.highY(); y++) {
        int offset=0;
        for (int x=matchings.lowX(); x<matchings.highX(); x++){
          int v = matchings.get(x, y);
          if (v != Integer.MIN_VALUE) {
            seamList[offset][y-matchings.lowY()] = x;
            offset++;
          }
        }
      }
     
      return seamList;
    }
   
    /** Concurrent find N seams.  Uses cumulative energy combined with
     * @param weights Function to determine the weight between two points
     * @param cumEng Place to store the cumulative energy of a seam.  Will be DESTRUCTIVELY updated.
     * @return
     */
    private static final Integer ZERO = 0;
    private static final Integer ONE = 1;
    private static final Integer NEGATIVE_ONE = -1;
    private static final Aggregates<Integer> nSeamsMatchings(int seams, Weights weights, Aggregates<Double> cumEng, Aggregates<Double> globalEnergy) {
     
      //Order global energy starts
      SortedMap<Double, List<Integer>> seamOrder = new TreeMap<>();
        for (int x = globalEnergy.lowX(); x < globalEnergy.highX(); x++) {
            Double seamEnergy = globalEnergy.get(x, 0);
          List<Integer> idxs = seamOrder.get(seamEnergy);
          if (idxs==null) {idxs = new ArrayList<>();}
          idxs.add(x);
          seamOrder.put(seamEnergy, idxs);
        }

        //Mix up the order a bit
        Random r = new Random(7253);
        for (Map.Entry<Double, List<Integer>> e: seamOrder.entrySet()) {
          Collections.shuffle(e.getValue(), r);
        }

       
        //Select start positions in order of potential energy
      Aggregates<Integer> matches = AggregateUtils.make(cumEng, Integer.MIN_VALUE);
        for (int i=0;i<seams; i++) {
          List<Integer> headList = seamOrder.get(seamOrder.firstKey());
          int x= headList.get(0);
          matches.set(x+matches.lowX(), matches.lowY(), 0);//Flag selected starts with a valid value
          cumEng.set(x+matches.lowX(), matches.lowY(), 0d);
          headList.remove(0);
          if (headList.size()==0) {seamOrder.remove(seamOrder.firstKey());}
        }
     
      //Proceed by rows through the space
      //Naming imagines a slice of two rows arranged like this:
      //  col       m-2  m-1   m
        //  row k:     A    B    C
      //  row k+1:   X    Y    Z
      // Processing tries to find the match for "B"
        //
        // The only unusual item is that if AY is currently selected, then BZ is disallowed and A may be switched to AX. 
        // This keeps negative-slope parallel lines from forming, full handling of which is O(n^2).  Disallowing the left parallel case makes this O(n).
        // (If the algorithm went from right-to-left through the rows then right-parallel lines would be disallowed).
      for (int y=cumEng.lowY(); y < cumEng.highY(); y++) {
        for (int x=cumEng.lowX(); x<cumEng.highX(); x++) {
          if (cumEng.get(x,y).equals(cumEng.defaultValue())) {continue;}

          double AX = weights.between(x-1, y, x-1, y+1);
          double AY = weights.between(x-1, y, x, y+1);
          boolean AXExists = !Double.isInfinite(AX);
          boolean AYExists = !Double.isInfinite(AY);

          double BX  = weights.between(x, y, x-1, y+1);
          double BY  = weights.between(x, y, x, y+1);
          double BZ  = weights.between(x, y, x+1, y+1);
         
          boolean doAX=false, doAY=false, doBX=false, doBY=false, doBZ=false;
         
          if (!AXExists && !AYExists) {
            if      (BY <= BX && BY <= BZ) {doBY=true;} //Preference for straight down
            else if (BX <= BY && BX <= BZ) {doBX=true;}
            else if (BZ <= BX && BZ <= BY) {doBZ=true;}
            else {throw new Error("Missed case...");}
          } else {//Need to check some compound options
            double o1 = AX+BY, o2=AY+BX, o3=AX+BZ;//compound options
            if      (BY <= BX && BY <= BZ) {doBY=true;}
            else if (BZ <= BX && BZ <= BY) {doBZ=true;}
            else if (o1 <= o2 && o1 <= o3) {doAX=true;doBY=true;}
            else if (o2 <= o1 && o2 <= o3) {doAY=true;doBX=true;}
            else if (o3 <= o1 && o3 <= o2) {doAX=true;doBZ=true;}
            else {throw new Error("Missed case...");}
          }
         
          if (doAX) {
            matches.set(x-1, y, ZERO);
            cumEng.set(x-1, y+1, cumEng.get(x-1,y)+AX);
          }
         
          if (doAY) {
            matches.set(x, y, ONE);
            cumEng.set(x-1, y+1, cumEng.get(x-1,y)+BY);
          }
         
          if (doBX) {
            matches.set(x, y, NEGATIVE_ONE);
            cumEng.set(x-1, y+1, cumEng.get(x,y)+BX);
          }
         
          if (doBY) {
            matches.set(x, y, ZERO);
            cumEng.set(x, y+1, cumEng.get(x,y)+BY);
          }
         
          if (doBZ) {
            matches.set(x, y, ONE);
            cumEng.set(x+1, y+1, cumEng.get(x,y)+BZ);
          }
        }

      }
      return matches;
    }
 
    /**Utility class, encapsulates a local energy matrix and computes
     * a between-pixel energy matrix.
     */
    public static final class EdgeWeights implements Weights {
      final Aggregates<Double> refEnergy;
      final Aggregates<Double> cumEnergy;
      public EdgeWeights(Aggregates<Double> refEnergy, Aggregates<Double> cumEnergy) {
        this.refEnergy = refEnergy;
        this.cumEnergy = cumEnergy;
      }
 
      public double between(int x1, int y1, int x2, int y2) {
        if (!validPoint(x1,y1) || !validPoint(x2,y2)) {return Double.POSITIVE_INFINITY;}
        return cumEnergy.get(x1, y1) * refEnergy.get(x2, y2); //cumEnergy to here * energy in next step
       
      }
     
      public boolean validPoint(int x, int y) {
        return x >= refEnergy.lowX() && x < refEnergy.highX()
            && y >= refEnergy.lowY() && y < refEnergy.highY();
      }
    }
  }
 
  public interface Weights {public double between(int x1, int y1, int x2, int y2);}

  /**Transpose a 2D array of ints.  Assumes the 2D array is rectangular.**/
  public static int[][] transpose(int[][] in) {
    int width = in.length;
    int height = in[0].length;
    int[][] out = new int[height][width];
   
    for (int x=0; x<width;x++) {
      for (int y=0; y<height; y++) {
        out[y][x]=in[x][y];
      }
    }
    return out;
  }
 
  /**Sorts each row of the passed matrix IN PLACE.**/
  public static int[][] sortRows(int[][] in) {
      for (int i=0;i<in.length;i++) {
        Arrays.sort(in[i]);
      }
      return in;
  }
 
  /**Follow a seam, accumulate each index along the way so the seam can be followed
   * without the matchings matrix.**/
  public static final int[] compileVSeam(int x, Aggregates<Integer> matchings) {
    final int[] seam = new int[matchings.highY()-matchings.lowY()];
    for (int y=0;y<seam.length;y++) {
      seam[y] =x;
      x = x+matchings.get(x, y+matchings.lowY());
    }
    return seam;
  }
 
 

  /** Match each pixel in each row with another pixel in the next row down.
   *
   * All matchings are 1:1 and the total matching maximizes the sum of the energy
   * in pairs between the rows.  This maximization has been shown to increase
   * the variance, therefore it tends to produce seams that are either clearly
   * important or clearly not. 
   *
   * This is a strictly local calculation.  There are other methods (method w2 in particular)
   * that also take into account global information.
   *
   * @param weights Function to determine the weight between two points
   * @param cumEng Place to store the cumulative energy of a seam.  Will be DESTRUCTIVELY updated.
   * @return
   */
  private static final Integer ZERO = 0;
  private static final Integer ONE = 1;
  private static final Integer NEGATIVE_ONE = -1;
  public static final Aggregates<Integer> matchings(Weights weights, Aggregates<Double> cumEng) {
    Aggregates<Integer> matches = AggregateUtils.make(cumEng, Integer.MIN_VALUE);
   
    //Proceed by rows through the space
    //Naming imagines a slice of two rows arranged like this:
    //  col       m-2  m-1   m
    //  row k:     A    B    C
    //  row k+1:   X    Y    Z
    // Processing tries to find the match for "C" (either Y or Z) but may change A and B as well in one case
    for (int y=cumEng.lowY(); y < cumEng.highY(); y++) {
      double F1=0,F2=0,F3=0;
      for (int x=cumEng.lowX(); x<cumEng.highX(); x++) {
        double CZ = weights.between(x,y,x,y+1);
        double CY= weights.between(x,y,x-1,y+1);
        double BZ = weights.between(x-1,y,x,y+1);
        double AX = weights.between(x-2,y,x-2,y+1);
       
        if (matches.get(x-2,y)==1 && matches.get(x-1,y)==-1) { //Prior nodes are cross-linked, so things could get complicated...
          double FA=F1+CZ;    //C does down, simple to handle
          double FB=F3+CY+BZ+AX;  //C goes left, and A needs to change too
          if (FA >= FB) { // C points down (CZ linked), no changes required.
            matches.set(x, y, ZERO)//point C's linkage
            cumEng.set(x, y+1, cumEng.get(x,y)+CZ); //point Z's cumulative energy
            F3=F2;
            F2=F1;
            F1=FA;
          } else { // C points left, B points right and A points down.
            matches.set(x, y, NEGATIVE_ONE); //point C linkage
            matches.set(x-1, y, ONE);     //point B
            matches.set(x-2, y, ZERO);     //point A

            cumEng.set(x, y+1, cumEng.get(x,y)+CY);         //Point Z cumulative energy
            cumEng.set(x-1, y+1, cumEng.get(x-1,y)+BZ);       //Point Y
            cumEng.set(x-2, y+1, cumEng.get(x-2,y)+AX);       //Point X
           
            F3=F2;
            F2=F1;
            F1=FB;
          }
        } else {
          double FA=F1+CZ;
          double FB=F2+CY+BZ;
          if (FA >= FB) { //B can keep pointing wherever it was, point C down
            matches.set(x, y, ZERO);
            cumEng.set(x, y+1, cumEng.get(x,y)+CZ); //point Z's cumulative energy

            F3=F2;
            F2=F1;
            F1=FA;
          } else { // B was already going down, now just point it right instead
            matches.set(x, y, NEGATIVE_ONE);
            matches.set(x-1, y, ONE);

            cumEng.set(x, y+1, cumEng.get(x,y)+CY);         //Point Z cumulative energy
            cumEng.set(x-1, y+1, cumEng.get(x-1,y)+BZ);       //Point Y

            F3=F2;
            F2=F1;
            F1=FB;
          }
        }
      }
    }
    return matches;
  }
 
  /**Locate the indicated number of seams given the matchings and energy functions.
   * Assumes the matching function is total (e.g., all values are -1/0/1).
   * Will pick the lowest energy seams first.
   *
   * @param seams Number of seams to remove
   * @param matchings Total matching
   * @param energy Energy function.
   * @return For x=int[A][B], x is the B'th x index to drop in row A
   */
  private static SeamList computeSeamList(int seams, Aggregates<Integer> matchings, Weights energy) {
    //Compute seam totals by iterating down the matchings  matrix
    double[] seamEnergies = new double[matchings.highX()-matchings.lowX()];
    for (int x=matchings.lowX(); x<matchings.highX(); x++) {
      int sourceX=x;
      for (int y=matchings.lowY(); y<matchings.highY()-1; y++) {//If you look down past the last row, you get -inf....
        int targetX = sourceX + matchings.get(sourceX,y);
        seamEnergies[x-matchings.lowX()] += energy.between(sourceX, y, targetX, y+1);
        sourceX=targetX;
      }
    }
   
   
    //Order seams
    SortedMap<Double, List<Integer>> seamOrder = new TreeMap<>();
      for (int i = 0; i < seamEnergies.length; i++) {
          Double seamEnergy = seamEnergies[i];
        List<Integer> idxs = seamOrder.get(seamEnergy);
        if (idxs==null) {idxs = new ArrayList<>();}
        idxs.add(i+matchings.lowX());
        seamOrder.put(seamEnergy, idxs);
      }

//      //Mix up the order
      Random r = new Random(7253);
      for (Map.Entry<Double, List<Integer>> e: seamOrder.entrySet()) {
        Collections.shuffle(e.getValue(), r);
      }
     
      //Select seams in order
      int[][] seamPoints = new int[seams][];
      double[] selectedSeamEnergies = new double[seams];
     
      for (int i=0;i<seams; i++) {
        selectedSeamEnergies[i] = seamOrder.firstKey();
        List<Integer> headList = seamOrder.get(selectedSeamEnergies[i]);
        int targetSeam = headList.get(0);
        headList.remove(0);
        if (headList.size()==0) {seamOrder.remove(seamOrder.firstKey());}
        seamPoints[i] = compileVSeam(targetSeam, matchings);         
      }
           
      return new SeamList(seamPoints, selectedSeamEnergies);
  }
 
  /**Remove N seams.*
   *
   * @param aggregates Item to carve
   * @param dropList List of items to remove.  For x=int[A][B], x is the B'th x index to drop in row A (i.e., a sorted transpose of a seam list).
   * */
  public static <A> Aggregates<A> carveN(final Aggregates<? extends A> aggregates, final int[][] dropList) {
      //Carve ALL seam-points out of the aggregates...
    Aggregates<A> result = AggregateUtils.make(aggregates.lowX(),
                          aggregates.lowY(),
                          aggregates.highX()-dropList[0].length,
                          aggregates.highY(), (A) aggregates.defaultValue());
   
      for (int y=aggregates.lowY(); y<aggregates.highY(); y++) {
        int i = y-aggregates.lowY();
        int dropCount=0;
        for (int x=aggregates.lowX(); x<aggregates.highX(); x++) {
          if (dropCount < dropList[i].length && dropList[i][dropCount] == x) {
            dropCount++;
            continue;
          }
          result.set(x-dropCount, y, aggregates.get(x, y));
        }
      }
      return result;
  }
 
 
  /**Computes the energy of a set of aggregates.
   */
  public static class Energy<A> implements Transfer.ItemWise<A, Double> {
    public Delta<A> delta;
   
    public Energy(Delta<A> delta) {this.delta=delta;}

    @Override public Double emptyValue() {return 0d;}
    @Override
    public Specialized<A, Double> specialize(Aggregates<? extends A> aggregates) {return this;}

    @Override
    public Double at(int x, int y, Aggregates<? extends A> aggregates) {
      A empty = aggregates.defaultValue();
      return delta.delta(aggregates.get(x,y), empty);
    }

    @Override
    public Aggregates<Double> process(Aggregates<? extends A> aggregates, Renderer rend) {
      return rend.transfer(aggregates, this);
    }
  }

  public static class CumulativeEnergy implements Transfer.Specialized<Double, Double> {
    @Override public Double emptyValue() {return 0d;}
    @Override
    public Specialized<Double, Double> specialize(Aggregates<? extends Double> aggregates) {return this;}

    //TODO: Parallelize
    @Override
    public Aggregates<Double> process(Aggregates<? extends Double> aggregates, Renderer render) {
      Aggregates<Double> cached = AggregateUtils.make(aggregates, 0d);
     
      //Copy the first row over...
      for (int x = aggregates.lowX(); x<aggregates.highX(); x++) {
        cached.set(x,aggregates.lowY(), aggregates.get(x,aggregates.lowY()));
      }
     
      for (int y = aggregates.lowY()+1; y<aggregates.highY(); y++) {
        for (int x = aggregates.lowX(); x<aggregates.highX(); x++) {
          double upLeft = x-1 >= aggregates.lowX() ? cached.get(x-1, y-1) : Double.MAX_VALUE;
          double up = cached.get(x, y-1);
          double upRight = x+1 < aggregates.highX() ? cached.get(x+1, y-1) :Double.MAX_VALUE;
         
          double min = Math.min(upRight, Math.min(upLeft, up));
          cached.set(x, y, min+aggregates.get(x, y));
        }
      }
      return cached;
    }
  }
 

 
  public static final class LeftValue<A extends Number> implements Delta<A> {
    public double delta(Number left, Number right) {return left.doubleValue();}
  }

 
  public static final class DeltaDouble implements Delta<Double> {
    public double delta(Double left, Double right) {return left-right;}
  }

  public static final class DeltaInteger implements Delta<Integer> {
    public double delta(Integer left, Integer right) {return left-right;}
  }

  public static final class DeltaLuminance implements Delta<Color> {
    public double delta(Color left, Color right) {return lum(left)-lum(right);}
    public static double lum(Color c) {return 0.299*c.getRed() + 0.587*c.getGreen() + 0.114*c.getBlue();}
  }
 
  /** Euclidean distance between two colors in RGB space.**/
  public static final class RGBEuclid implements Delta<Color> {
    public double delta(Color left, Color right) {
      return Math.sqrt(
          Math.pow(left.getRed()  - right.getRed(), 2)
        + Math.pow(left.getGreen()- right.getGreen(), 2)
        + Math.pow(left.getBlue() - right.getBlue(), 2));
    }
  }
 
 
  /**Find and remove a seams, per the Avidan and Shamir method but only remove one seam at a time.
   *
   * This class will calculate the full energy matrix each time a seam is removed.
   * Since the whole matrix is calculated each time, its slow BUT it is also stateless.
   *
   * For faster results, try other "Carve" classes.
   * Each has a different set of tradeoffs, but they all run significantly faster. 
   *
   * **/
  public static class CarveIncremental<A> implements Transfer.Specialized<A,A> {
    protected final Delta<A> delta;
    protected final A empty;
    protected final Direction dir;
    protected final int seams;
   
    public CarveIncremental(Delta<A> delta, Direction dir, A empty, int seams)  {
      this.delta = delta;
      this.empty = empty;
      this.dir = dir;
      this.seams= seams;
    }
   

    @Override public A emptyValue() {return empty;}

    @Override
    public ar.Transfer.Specialized<A, A> specialize(Aggregates<? extends A> aggregates) {return this;}

    @Override
    public Aggregates<A> process(Aggregates<? extends A> aggregates, Renderer rend) {
      if (dir == Direction.H) {
        return new TransposeWrapper<>(carve(new TransposeWrapper<>(aggregates), rend));
      } else {
        return carve(aggregates, rend);
      }
    }
   
    private Aggregates<A> carve(Aggregates<? extends A> aggregates, Renderer rend) {
      Aggregates<? extends Double> pixelEnergy = rend.transfer(aggregates, new Energy<>(delta));
      CumulativeEnergy cumulativeEnergy = new CumulativeEnergy();
     
      @SuppressWarnings("unchecked")
      Aggregates<A> rslt = (Aggregates<A>) aggregates;
     
      for (int i=0; i<seams; i++) {
        Aggregates<Double> cumEng = rend.transfer(pixelEnergy, cumulativeEnergy);
        int selectedSeam = selectSeam(seamEnergies(cumEng, cumEng.highY()-1), i);
        int[] seam = findVSeam(cumEng, selectedSeam);
        rslt = carve(rslt, seam);
        pixelEnergy = carve(pixelEnergy,seam);
      }
      return rslt;
    }


    /**
     */
    public static final void correctSeam(int[][] seams, int focus) {
      for (int entry=0; entry < seams[focus].length; entry++) {
        int increase =0;
        for (int seam=focus-1; seam>=0; seam--) {
          if (seams[seam][entry] <= focus) {increase++;}
        }
        seams[focus][entry] += increase;
      }
    }
   
    public static int[] findVSeam(Aggregates<? extends Double> cumEng, int selectedSeam) {
      int[] vseam = new int[cumEng.highY()-cumEng.lowY()];
      vseam[vseam.length-1] = selectedSeam;
      for (int y = cumEng.highY()-2; y>=cumEng.lowY(); y--) {
        int x = vseam[y-cumEng.lowY()+1]; //Get the x value for the next row down 

        double upLeft = x-1 >= cumEng.lowX() ? cumEng.get(x-1, y) : Double.MAX_VALUE;
        double up = cumEng.get(x, y);
        double upRight = x+1 < cumEng.highX() ? cumEng.get(x+1, y) : Double.MAX_VALUE;

        if (upLeft < up && upLeft < upRight) {x = x-1;}
        else if (up > upRight) {x = x+1;}
       
        vseam[y-cumEng.lowY()] = x;
      }
      return vseam;
    }
   
   
    /**
     * @param seamEnergies Array of seam energies
     * @param seamIdx Is this the 1st/2nd/3rd call to nextSeam?
     * @return The index of the selected seam in seamEnergies
     */
    private static final int selectSeam(final double[] seamEnergies, final int seamIdx) {
      int selected=0;
      double min = Integer.MAX_VALUE;
      int nearPosition=(7817*seamIdx)%seamEnergies.length;

      for (int x = 0; x < seamEnergies.length; x++) {
        Double eng = seamEnergies[x];

        //Use the current x as the start if it has lower energy than anything found before OR
        //   it has the same energy but is closer to the "nearPosition"
        if (min > eng  
            || (min == eng && (Math.abs(nearPosition-x) < Math.abs(nearPosition-selected)))) {
          min = eng;
          selected = x;
        }
      }

      return selected;
    }
   
    /**Carves a vertical seam out.**/
    private static final <A> Aggregates<A> carve(Aggregates<? extends A> aggs, int[] vseam) {
      Aggregates<A> rslt =
          AggregateUtils.make(aggs.lowX(), aggs.lowY(), aggs.highX()-1, aggs.highY(), (A) aggs.defaultValue());
     
      for (int y = aggs.lowY(); y<aggs.highY(); y++) {
        int split = vseam[y-aggs.lowY()];
        for (int x=aggs.lowX(); x<split; x++) {rslt.set(x, y, aggs.get(x,y));}
        for (int x=split; x<aggs.highX(); x++) {rslt.set(x, y, aggs.get(x+1,y));}
      }
      return rslt;
    }
  }
 
  /**Get just the seam energies by reading one row of the passed set of aggregates.**/
  private static final double[] seamEnergies(Aggregates<? extends Double> cumEng, int row) {
    final double[] energies = new double[cumEng.highX()-cumEng.lowX()];
    for (int x=0; x<energies.length; x++) {
      energies[x] = cumEng.get(x, row);
    }
    return energies;
  }

 

 
  public static final class DrawSeams<A> implements Transfer.Specialized<A, Color> {
    private final AbstractCarver<A> carver;
    private final Color lowSeam,highSeam;
    private final Color notSeam = Util.CLEAR;
   
    public DrawSeams(AbstractCarver<A> carver, Color lowSeam, Color highSeam) {
      this.carver = carver;
      this.lowSeam = lowSeam;
      this.highSeam = highSeam;
    }

    @Override public Color emptyValue() {return notSeam;}

    @Override public ar.Transfer.Specialized<A, Color> specialize(Aggregates<? extends A> aggregates) {return this;}

    @Override
    public Aggregates<Color> process(Aggregates<? extends A> aggregates, Renderer rend) {
      if (carver.dir == Direction.H) {
        SeamList seamList = carver.verticalSeamList(TransposeWrapper.transpose(aggregates), rend);
        return TransposeWrapper.transpose(colorSeams(aggregates, seamList));     
      } else {
        SeamList seamList = carver.verticalSeamList(aggregates, rend);
        return colorSeams(aggregates, seamList);
      }
    }

    private Aggregates<Color> colorSeams(Aggregates<?> like, SeamList seamList) {
      Aggregates<Color> seams = AggregateUtils.make(like, notSeam);
     
      for (int x=0; x<seamList.seams.length;x++) {
        for (int y=0; y<seamList.seams[x].length;y++) {
          seams.set(seamList.seams[x][y], y, Util.interpolate(lowSeam, highSeam, seamList.minWeight, seamList.maxWeight, seamList.weights[x]));
        }
      }
      return seams;
    }
  }
}
TOP

Related Classes of ar.rules.SeamCarving$Weights

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.