JAL-3383 get color as int options removed (to separate branch 3443)
[jalview.git] / src / jalview / renderer / OverviewRenderer.java
index 22c75c3..82e89e5 100644 (file)
@@ -30,7 +30,6 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.seqfeatures.FeatureColourFinder;
-import jalview.renderer.seqfeatures.FeatureRenderer;
 import jalview.util.Platform;
 import jalview.viewmodel.OverviewDimensions;
 
@@ -51,13 +50,31 @@ import javax.swing.Timer;
 
 public class OverviewRenderer
 {
-  // transparency of hidden cols/seqs overlay
-  private final float TRANSPARENCY = 0.5f;
-
   public static final String UPDATE = "OverviewUpdate";
 
+  // transparency of hidden cols/seqs overlay
+  private static final float TRANSPARENCY = 0.5f;
+
   private static final int MAX_PROGRESS = 100;
 
+  private static final int STATE_INIT = 0;
+
+  private static final int STATE_NEXT = 1;
+
+  private static final int STATE_DONE = 2;
+
+  private int state;
+
+  private Timer timer;
+
+  private int delay = (Platform.isJS() ? 1 : 0);
+
+  private int seqIndex;
+
+  private int pixelRow;
+
+  private Integer row;
+
   private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
           this);
 
@@ -77,9 +94,23 @@ public class OverviewRenderer
    */
   private float colsPerPixel;
 
-  // raw number of pixels to allocate to each row
+  /**
+   * raw number of pixels to allocate to each row
+   */
   private float pixelsPerSeq;
 
+  /**
+   * true when colsPerPixel > 1
+   */
+  private boolean skippingColumns;
+
+  /**
+   * pre-calculated list of columns needed for a "dense" overview, where there
+   * are more columns than pixels
+   */
+
+  private int[] columnsToShow;
+
   // height in pixels of graph
   private int graphHeight;
 
@@ -97,16 +128,48 @@ public class OverviewRenderer
 
   private AlignmentViewPanel panel;
 
-  private int sequencesHeight;
+  private int ndone = 0;
 
-  public OverviewRenderer(AlignmentViewPanel panel, FeatureRenderer fr,
-          OverviewDimensions od,
-          AlignmentI alignment,
-          ResidueShaderI resshader, OverviewResColourFinder colFinder)
+  private AlignmentRowsCollectionI rows;
+
+  private AlignmentColsCollectionI cols;
+
+  private Iterator<Integer> rowIterator;
+
+  private int alignmentHeight;
+
+  private int totalPixels;
+
+  private int lastRowUpdate;
+
+  private int lastUpdate;
+
+  private int[] pixels;
+
+  private BitSet bscol;
+
+  private final int w;
+
+  private final int h;
+
+  public OverviewRenderer(AlignmentViewPanel panel,
+          jalview.api.FeatureRenderer fr, OverviewDimensions od,
+          AlignmentI alignment, ResidueShaderI resshader,
+          OverviewResColourFinder colFinder)
   {
     this(panel, fr, od, alignment, resshader, colFinder, true);
   }
 
+  /**
+   * @param panel
+   * @param fr
+   * @param od
+   * @param alignment
+   * @param resshader
+   * @param colFinder
+   * @param showProgress
+   *          possibly not, in JavaScript and for testng
+   */
   public OverviewRenderer(AlignmentViewPanel panel,
           jalview.api.FeatureRenderer fr, OverviewDimensions od,
           AlignmentI alignment, ResidueShaderI resshader,
@@ -130,25 +193,9 @@ public class OverviewRenderer
     pixelsPerCol = od.getPixelsPerCol();
     colsPerPixel = Math.max(1, 1f / pixelsPerCol);
 
+    skippingColumns = (pixelsPerCol < 1);
   }
 
-  final static int STATE_INIT = 0;
-  final static int STATE_NEXT = 1;
-  final static int STATE_DONE = 2;
-
-  int state;
-
-  boolean isJS = Platform.isJS();
-
-  Timer timer;
-  int delay = (isJS ? 1 : 0);
-
-  int seqIndex;
-
-  int pixelRow;
-
-  private Integer row;
-
   /**
    * Draw alignment rows and columns onto an image. This method is asynchronous
    * in JavaScript and interruptible in Java.
@@ -215,7 +262,6 @@ public class OverviewRenderer
     lastRowUpdate = 0;
     lastUpdate = 0;
     totalPixels = w * alignmentHeight;
-
     if (showProgress)
     {
       changeSupport.firePropertyChange(UPDATE, -1, 0);
@@ -224,15 +270,19 @@ public class OverviewRenderer
     miniMe = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
     WritableRaster raster = miniMe.getRaster();
     DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
-    Platform.timeCheck(null, Platform.TIME_MARK);
     pixels = db.getBankData()[0];
-    bscol = cols.getOverviewBitSet();
+    bscol = cols.getShownBitSet();
+    if (skippingColumns)
+    {
+      columnsToShow = calcColumnsToShow();
+    }
+
+    Platform.timeCheck(null, Platform.TIME_MARK);
   }
 
   private void nextRow()
   {
     row = rowIterator.next();
-    // get details of this alignment row
     SequenceI seq = rows.getSequence(row);
 
     // rate limiting step when rendering overview for lots of groups
@@ -240,9 +290,13 @@ public class OverviewRenderer
 
     // calculate where this row extends to in pixels
     int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq), h);
+    // this is the key modification -- we use bscol to jump to the next column
+    // when there are more columns than pixels.
+
     for (int pixelCol = 0, colNext = 0, pixelEnd = 0, icol = bscol
-            .nextSetBit(0); icol >= 0; icol = getNextCol(icol, pixelEnd))
+            .nextSetBit(0); icol >= 0; icol = getNextCol(icol, colNext))
     {
+      // asynchronous exit flag
       if (redraw)
       {
         break;
@@ -259,10 +313,6 @@ public class OverviewRenderer
       {
         int rgb = getColumnColourFromSequence(allGroups, seq, icol);
         // fill in the appropriate number of pixels
-        // System.out.println(
-        // "OR colNext=" + colNext + " " + pixelCol
-        // + "-" + pixelEnd + " icol=" + icol + " " + rgb + " "
-        // + pixelsPerCol);
         for (int row = pixelRow; row < endRow; ++row)
         {
           for (int col = pixelCol; col < pixelEnd; ++col)
@@ -282,8 +332,8 @@ public class OverviewRenderer
         if (showProgress)
         {
           lastUpdate = sendProgressUpdate(
-                  pixelEnd * (endRow - 1 - pixelRow),
-                  totalPixels, lastRowUpdate, lastUpdate);
+                  pixelEnd * (endRow - 1 - pixelRow), totalPixels,
+                  lastRowUpdate, lastUpdate);
         }
       }
 
@@ -304,27 +354,92 @@ public class OverviewRenderer
   }
 
   /**
-   * The next column is either the next set bit (when there are multiple pixels
-   * per column) or the next set bit for the column that aligns with the next
-   * pixel (when there are more columns than pixels).
+   * Precalculate the columns that will be used for each pixel in a dense
+   * overview. So we have to skip through the bscol BitSet to pick up one
+   * (representative?) column for each pixel.
+   * 
+   * Note that there is no easy solution if we want to do color averaging, but
+   * this method might be adapted to do that. Or it could be adapted to pick the
+   * "most representative color" for a group of columns.
+   * 
+   * @author Bob Hanson 2019.09.03
+   * @return a -1 terminated int[]
+   */
+  private int[] calcColumnsToShow()
+  {
+    int[] a = new int[w + 1];
+    float colBuffer = 0;
+    float offset = bscol.nextSetBit(0);
+    if (offset < 0)
+    {
+      return new int[] { -1 };
+    }
+    int pixel = 0;
+    a[pixel++] = (int) offset;
+    // for example, say we have 10 pixels per column:
+    // ...............xxxxxxxx....xxxxxx.........xxxxxx......
+    // nextSet(i).....^...........^..............^...........
+    // nextClear..............^.........^..............^.....
+    // run lengths....|--n1--|....|-n2-|.........|-n3-|......
+    // 10 pixel/col...|---pixel1---||-----pixel2------|......
+    // pixel..........^0............^1.......................
+    for (int i, iClear = -1; pixel < w
+            && (i = bscol.nextSetBit(iClear + 1)) >= 0;)
+    {
+      // find the next clear bit
+      iClear = bscol.nextClearBit(i + 1);
+      // add the run length n1, n2, n3 to grow the column buffer
+      colBuffer += iClear - i; // n1, n2, etc.
+      // add columns if we have accumulated enough pixels
+
+      while (colBuffer > colsPerPixel && pixel < w)
+      {
+        colBuffer -= colsPerPixel;
+        offset += colsPerPixel;
+        a[pixel++] = i + (int) offset;
+      }
+      // set back column pointer relative to the next run
+      offset = -colBuffer;
+    }
+    // add a terminator
+    a[pixel] = -1;
+    return a;
+  }
+
+  /**
+   * The next column is either a precalculated pixel (when there are multiple
+   * pixels per column) or the next set bit for the column that aligns with the
+   * next pixel (when there are more columns than pixels).
+   * 
+   * When columns are hidden, this value is precalculated; otherwise it is
+   * calculated here.
    * 
    * @param icol
    * @param pixel
+   *          pixel pointer into columnsToShow
    * @return
    */
   private int getNextCol(int icol, int pixel)
   {
-    return bscol.nextSetBit(
-            pixelsPerCol >= 1 ? icol + 1 : (int) (pixel * colsPerPixel));
+    return (skippingColumns ? columnsToShow[pixel]
+            : bscol.nextSetBit(icol + 1));
   }
 
+  /**
+   * Derive the next pixel from either as the given pixel (when we are skipping
+   * columns because this is a dense overview and the pixel known), or from the
+   * current column based on pixels/column. The latter is used for drawing the
+   * hidden-column mask or for overviews that have more pixels across than
+   * columns.
+   * 
+   * @param icol
+   * @param pixel
+   * @return
+   */
   private int getNextPixel(int icol, int pixel)
   {
-    return Math.min(
-            pixelsPerCol >= 1 || pixel == 0
-                    ? Math.round(icol * pixelsPerCol)
-                    : pixel,
-            w);
+    return Math.min(skippingColumns && pixel > 0 ? pixel
+            : Math.round(icol * pixelsPerCol), w);
   }
 
   private boolean loop()
@@ -342,7 +457,6 @@ public class OverviewRenderer
         {
           mainLoop();
         }
-
       });
       timer.setRepeats(false);
       timer.start();
@@ -356,10 +470,13 @@ public class OverviewRenderer
 
   private void done()
   {
-    Platform.timeCheck(
-            "overviewrender " + ndone + " pixels row:" + row + " redraw:"
-                    + redraw,
-            Platform.TIME_MARK);
+    if (!redraw)
+    {
+      Platform.timeCheck(
+              "overviewrender " + ndone + " pixels row:" + row + " redraw:"
+                      + redraw,
+              Platform.TIME_MARK);
+    }
 
     overlayHiddenRegions();
     if (showProgress)
@@ -377,31 +494,10 @@ public class OverviewRenderer
         sendProgressUpdate(1, 1, 0, 0);
       }
     }
+
     panel.overviewDone(miniMe);
   }
 
-  int ndone = 0;
-
-  private AlignmentRowsCollectionI rows;
-
-  private AlignmentColsCollectionI cols;
-
-  Iterator<Integer> rowIterator;
-
-  int alignmentHeight;
-
-  int totalPixels;
-
-  int lastRowUpdate;
-
-  int lastUpdate;
-
-  int[] pixels;
-
-  BitSet bscol;
-
-  int w, h;
-
   /*
    * Calculate progress update value and fire event
    * @param rowOffset number of rows to offset calculation by
@@ -433,9 +529,9 @@ public class OverviewRenderer
           int icol)
   {
     return (seq == null || icol >= seq.getLength()
-            ? resColFinder.GAP_COLOUR
-            : resColFinder.getResidueColourInt(true, shader, allGroups, seq,
-                    icol, finder));
+            ? resColFinder.gapColourInt
+            : resColFinder.getResidueColour(true, shader, allGroups, seq,
+                    icol, finder).getRGB());
   }
 
   /**
@@ -529,7 +625,9 @@ public class OverviewRenderer
         // get details of this alignment row
         if (rows.isHidden(alignmentRow))
         {
-          g2d.fillRect(0, pixelRow, w, endRow - 1 - pixelRow);
+          // BH 2019.09.24 fixes JAL-3440 Java+JavaScript off by one row in
+          // height
+          g2d.fillRect(0, pixelRow, w, endRow - pixelRow);
         }
         pixelRow = endRow;
       }
@@ -557,7 +655,7 @@ public class OverviewRenderer
 
     for (int pixelCol = 0, colNext = 0, pixelEnd = 0, len = annotations.length, icol = bscol
             .nextSetBit(0); icol >= 0
-                    && icol < len; icol = getNextCol(icol, pixelEnd))
+                    && icol < len; icol = getNextCol(icol, colNext))
     {
       if (redraw)
       {