JAL-3210 Barebones gradle/buildship/eclipse. See README
[jalview.git] / src / jalview / renderer / OverviewRenderer.java
index 36b5847..c9096e2 100644 (file)
@@ -22,7 +22,6 @@ package jalview.renderer;
 
 import jalview.api.AlignmentColsCollectionI;
 import jalview.api.AlignmentRowsCollectionI;
-import jalview.api.AlignmentViewPanel;
 import jalview.api.RendererListenerI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
@@ -30,23 +29,15 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.seqfeatures.FeatureColourFinder;
-import jalview.util.Platform;
+import jalview.renderer.seqfeatures.FeatureRenderer;
 import jalview.viewmodel.OverviewDimensions;
 
 import java.awt.AlphaComposite;
 import java.awt.Color;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
 import java.awt.image.BufferedImage;
-import java.awt.image.DataBufferInt;
-import java.awt.image.WritableRaster;
 import java.beans.PropertyChangeSupport;
-import java.util.BitSet;
-import java.util.Iterator;
-
-import javax.swing.Timer;
 
 public class OverviewRenderer
 {
@@ -65,17 +56,9 @@ public class OverviewRenderer
   // image to render on
   private BufferedImage miniMe;
 
-  /**
-   * Number of pixelsPerCol;
-   */
+  // raw number of pixels to allocate to each column
   private float pixelsPerCol;
 
-  /**
-   * Number of visible columns per pixel.
-   * 
-   */
-  private float colsPerPixel;
-
   // raw number of pixels to allocate to each row
   private float pixelsPerSeq;
 
@@ -92,331 +75,126 @@ public class OverviewRenderer
 
   private OverviewResColourFinder resColFinder;
 
-  private boolean showProgress;
-
-  private AlignmentViewPanel panel;
-
-  // private int sequencesHeight;
-
-  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 shwoProgress
-   *          possibly not, in JavaScript and for testng
-   */
-  public OverviewRenderer(AlignmentViewPanel panel,
-          jalview.api.FeatureRenderer fr, OverviewDimensions od,
-          AlignmentI alignment, ResidueShaderI resshader,
-          OverviewResColourFinder colFinder, boolean showProgress)
+  public OverviewRenderer(FeatureRenderer fr, OverviewDimensions od,
+          AlignmentI alignment,
+          ResidueShaderI resshader, OverviewResColourFinder colFinder)
   {
-    {
-    this.panel = panel;
     finder = new FeatureColourFinder(fr);
-    al = alignment;
-    shader = resshader;
     resColFinder = colFinder;
-    this.showProgress = showProgress;
 
-    w = od.getWidth();
-    h = od.getHeight();
-    rows = od.getRows(alignment);
-    cols = od.getColumns(alignment);
-    graphHeight = od.getGraphHeight();
-    alignmentHeight = od.getSequencesHeight();
+    al = alignment;
+    shader = resshader;
 
-    pixelsPerSeq = od.getPixelsPerSeq();
     pixelsPerCol = od.getPixelsPerCol();
-    colsPerPixel = Math.max(1, 1f / pixelsPerCol);
-    }
+    pixelsPerSeq = od.getPixelsPerSeq();
+    graphHeight = od.getGraphHeight();
+    miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
+            BufferedImage.TYPE_INT_RGB);
   }
 
-  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.
-   * 
-   * Whether hidden rows or columns are drawn depends upon the type of
-   * collection.
+   * Draw alignment rows and columns onto an image
    * 
-   * Updated to skip through high-density sequences, where columns/pixels > 1.
-   * 
-   * When the process is complete, the image is passed to the AlignmentViewPanel
-   * provided by the constructor.
-   * 
-   * @param rows
-   *          collection of rows to be drawn
-   * @param cols
-   *          collection of columns to be drawn
+   * @param rit
+   *          Iterator over rows to be drawn
+   * @param cit
+   *          Iterator over columns to be drawn
    * @return image containing the drawing
-   * 
-   * @author Bob Hanson 2019.07.30
    */
-  public void drawMiniMe()
+  public BufferedImage draw(AlignmentRowsCollectionI rows,
+          AlignmentColsCollectionI cols)
   {
-    state = STATE_INIT;
-    mainLoop();
-  }
+    int rgbcolor = Color.white.getRGB();
+    int seqIndex = 0;
+    int pixelRow = 0;
+    int alignmentHeight = miniMe.getHeight() - graphHeight;
+    int totalPixels = miniMe.getWidth() * alignmentHeight;
 
-  protected void mainLoop()
-  {
-    out: while (!redraw)
-    {
-      switch (state)
-      {
-      case STATE_INIT:
-        init();
-        state = STATE_NEXT;
-        continue;
-      case STATE_NEXT:
-        if (!rowIterator.hasNext())
-        {
-          state = STATE_DONE;
-          continue;
-        }
-        nextRow();
-        if (!loop())
-        {
-          // Java
-          continue;
-        }
-        // JavaScript
-        return;
-      case STATE_DONE:
-        break out;
-      }
-      // Java will continue without a timeout
-    }
-    done();
-  }
+    int lastRowUpdate = 0;
+    int lastUpdate = 0;
+    changeSupport.firePropertyChange(UPDATE, -1, 0);
 
-  private void init()
-  {
-    rowIterator = rows.iterator();
-    seqIndex = 0;
-    pixelRow = 0;
-    lastRowUpdate = 0;
-    lastUpdate = 0;
-    totalPixels = w * alignmentHeight;
-
-    if (showProgress)
-    {
-      changeSupport.firePropertyChange(UPDATE, -1, 0);
-    }
-
-    miniMe = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
-    WritableRaster raster = miniMe.getRaster();
-    DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
-    pixels = db.getBankData()[0];
-    bscol = cols.getOverviewBitSet();
-    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
-    SequenceGroup[] allGroups = al.findAllGroups(seq);
-
-    // calculate where this row extends to in pixels
-    int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq), h);
-    for (int pixelCol = 0, colNext = 0, pixelEnd = 0, icol = bscol
-            .nextSetBit(0); icol >= 0; icol = getNextCol(icol, pixelEnd))
+    for (int alignmentRow : rows)
     {
       if (redraw)
       {
         break;
       }
-
-      ++colNext;
-      pixelEnd = getNextPixel(colNext, colNext);
-
-      if (pixelCol == pixelEnd)
-      {
-        break;
-      }
-      else if (pixelCol < pixelEnd)
+    
+      // get details of this alignment row
+      SequenceI seq = rows.getSequence(alignmentRow);
+
+      // rate limiting step when rendering overview for lots of groups
+      SequenceGroup[] allGroups = al.findAllGroups(seq);
+
+      // calculate where this row extends to in pixels
+      int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1,
+              miniMe.getHeight() - 1);
+    
+      int colIndex = 0;
+      int pixelCol = 0;
+      for (int alignmentCol : cols)
       {
-        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)
+        if (redraw)
         {
-          for (int col = pixelCol; col < pixelEnd; ++col)
-          {
-            // BH 2019.07.27 was:
-            //
-            // miniMe.setRGB(col, row, rgbcolor);
-            //
-            // but just directly writing to the int[] pixel buffer
-            // is three times faster by my experimentation
-            pixels[row * w + col] = rgb;
-            ndone++;
-          }
+          break;
         }
-        pixelCol = pixelEnd;
-        // store last update value
-        if (showProgress)
+    
+        // calculate where this column extends to in pixels
+        int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
+                miniMe.getWidth() - 1);
+    
+        // don't do expensive colour determination if we're not going to use it
+        // NB this is important to avoid performance issues in the overview
+        // panel
+        if (pixelCol <= endCol)
         {
+          rgbcolor = getColumnColourFromSequence(allGroups, seq,
+                  alignmentCol);
+    
+          // fill in the appropriate number of pixels
+          for (int row = pixelRow; row <= endRow; ++row)
+          {
+            for (int col = pixelCol; col <= endCol; ++col)
+            {
+              miniMe.setRGB(col, row, rgbcolor);
+            }
+          }
+
+          // store last update value
           lastUpdate = sendProgressUpdate(
-                  pixelEnd * (endRow - 1 - pixelRow),
-                  totalPixels, lastRowUpdate, lastUpdate);
+                  (pixelCol + 1) * (endRow - pixelRow), totalPixels,
+                  lastRowUpdate, lastUpdate);
+
+          pixelCol = endCol + 1;
         }
+        colIndex++;
       }
 
-    }
-    if (pixelRow < endRow)
-    {
-      pixelRow = endRow;
-      // store row offset and last update value
-      if (showProgress)
+      if (pixelRow != endRow + 1)
       {
-        // BH 2019.07.29 was (old) endRow + 1 (now endRow), but should be
-        // pixelRow + 1, surely
-        lastRowUpdate = sendProgressUpdate(endRow, alignmentHeight, 0,
+        // store row offset and last update value
+        lastRowUpdate = sendProgressUpdate(endRow + 1, alignmentHeight, 0,
                 lastUpdate);
         lastUpdate = lastRowUpdate;
+        pixelRow = endRow + 1;
       }
+      seqIndex++;
     }
-  }
 
-  /**
-   * 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).
-   * 
-   * @param icol
-   * @param pixel
-   * @return
-   */
-  private int getNextCol(int icol, int pixel)
-  {
-    return bscol.nextSetBit(
-            pixelsPerCol >= 1 ? icol + 1 : (int) (pixel * colsPerPixel));
-  }
-
-  private int getNextPixel(int icol, int pixel)
-  {
-    return Math.min(
-            pixelsPerCol >= 1 || pixel == 0
-                    ? Math.round(icol * pixelsPerCol)
-                    : pixel,
-            w);
-  }
-
-  private ActionListener listener = new ActionListener()
-  {
-    @Override
-    public void actionPerformed(ActionEvent e)
+    overlayHiddenRegions(rows, cols);
+    // final update to progress bar if present
+    if (redraw)
     {
-      mainLoop();
-    }
-
-  };
-
-  private boolean loop()
-  {
-    if (delay <= 0)
-    {
-      return false;
-    }
-    if (timer == null)
-    {
-      timer = new Timer(delay, listener);
-      timer.setRepeats(false);
-      timer.start();
+      sendProgressUpdate(pixelRow - 1, alignmentHeight, 0, 0);
     }
     else
     {
-      timer.restart();
+      sendProgressUpdate(alignmentHeight, miniMe.getHeight(), 0, 0);
     }
-    return true;
+    return miniMe;
   }
 
-  private void done()
-  {
-    if (!redraw)
-    {
-      Platform.timeCheck(
-              "overviewrender " + ndone + " pixels row:" + row + " redraw:"
-                      + redraw,
-              Platform.TIME_MARK);
-    }
-
-    overlayHiddenRegions();
-    if (showProgress)
-    {
-      // final update to progress bar if present
-      if (redraw)
-      {
-        // aborted in Java
-        // BH was pixelRow - 1, but that could go negative
-        sendProgressUpdate(pixelRow, alignmentHeight, 0, 0);
-      }
-      else
-      {
-        // sendProgressUpdate(alignmentHeight, miniMe.getHeight(), 0, 0);
-        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
@@ -444,30 +222,40 @@ public class OverviewRenderer
    *          column position to get colour for
    * @return colour of sequence at this position, as RGB
    */
-  int getColumnColourFromSequence(SequenceGroup[] allGroups, SequenceI seq,
-          int icol)
+  int getColumnColourFromSequence(SequenceGroup[] allGroups,
+          SequenceI seq, int lastcol)
   {
-    return (seq == null || icol >= seq.getLength()
-            ? resColFinder.GAP_COLOUR
-            : resColFinder.getResidueColourInt(true, shader, allGroups, seq,
-                    icol, finder));
+    Color color = resColFinder.GAP_COLOUR;
+
+    if ((seq != null) && (seq.getLength() > lastcol))
+    {
+      color = resColFinder.getResidueColour(true, shader, allGroups, seq,
+              lastcol, finder);
+    }
+
+    return color.getRGB();
   }
 
   /**
    * Overlay the hidden regions on the overview image
    * 
+   * @param rows
+   *          collection of rows the overview is built over
+   * @param cols
+   *          collection of columns the overview is built over
    */
-  private void overlayHiddenRegions()
+  private void overlayHiddenRegions(AlignmentRowsCollectionI rows,
+          AlignmentColsCollectionI cols)
   {
     if (cols.hasHidden() || rows.hasHidden())
     {
-      BufferedImage mask = buildHiddenImage();
+      BufferedImage mask = buildHiddenImage(rows, cols, miniMe.getWidth(),
+              miniMe.getHeight());
 
       Graphics2D g = (Graphics2D) miniMe.getGraphics();
       g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
               TRANSPARENCY));
       g.drawImage(mask, 0, 0, miniMe.getWidth(), miniMe.getHeight(), null);
-      g.dispose();
     }
   }
 
@@ -485,17 +273,20 @@ public class OverviewRenderer
    *          height of overview in pixels
    * @return BufferedImage containing mask of hidden regions
    */
-  private BufferedImage buildHiddenImage()
+  private BufferedImage buildHiddenImage(AlignmentRowsCollectionI rows,
+          AlignmentColsCollectionI cols, int width, int height)
   {
     // new masking image
-    BufferedImage hiddenImage = new BufferedImage(w, h,
+    BufferedImage hiddenImage = new BufferedImage(width, height,
             BufferedImage.TYPE_INT_ARGB);
 
+    int colIndex = 0;
+    int pixelCol = 0;
+
     Color hidden = resColFinder.getHiddenColour();
 
     Graphics2D g2d = (Graphics2D) hiddenImage.getGraphics();
 
-    g2d.setColor(hidden);
     // set background to transparent
     // g2d.setComposite(AlphaComposite.Clear);
     // g2d.fillRect(0, 0, width, height);
@@ -503,109 +294,124 @@ public class OverviewRenderer
     // set next colour to opaque
     g2d.setComposite(AlphaComposite.Src);
 
-    // System.out.println(cols.getClass().getName());
-    if (cols.hasHidden())
+    for (int alignmentCol : cols)
     {
-      // AllColsCollection only
-      BitSet bs = cols.getHiddenBitSet();
-      for (int pixelCol = -1, icol2 = 0, icol = bs
-              .nextSetBit(0); icol >= 0; icol = bs.nextSetBit(icol2))
+      if (redraw)
       {
-        if (redraw)
-        {
-          break;
-        }
-        icol2 = bs.nextClearBit(icol + 1);
-        int pixelEnd = getNextPixel(icol2, 0);
-        if (pixelEnd > pixelCol)
+        break;
+      }
+
+      // calculate where this column extends to in pixels
+      int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
+              hiddenImage.getWidth() - 1);
+
+      if (pixelCol <= endCol)
+      {
+        // determine the colour based on the sequence and column position
+        if (cols.isHidden(alignmentCol))
         {
-          pixelCol = getNextPixel(icol, 0);
-          g2d.fillRect(pixelCol, 0, Math.max(1, pixelEnd - pixelCol),
-                  h);
-          pixelCol = pixelEnd;
+          g2d.setColor(hidden);
+          g2d.fillRect(pixelCol, 0, endCol - pixelCol + 1, height);
         }
+
+        pixelCol = endCol + 1;
       }
+      colIndex++;
+
     }
-    if (rows.hasHidden())
+
+    int seqIndex = 0;
+    int pixelRow = 0;
+    for (int alignmentRow : rows)
     {
-      int seqIndex = 0;
-      int pixelRow = 0;
-      for (int alignmentRow : rows)
+      if (redraw)
       {
-        if (redraw)
-        {
-          break;
-        }
+        break;
+      }
 
-        // calculate where this row extends to in pixels
-        int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq),
-                h);
+      // calculate where this row extends to in pixels
+      int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1,
+              miniMe.getHeight() - 1);
 
-        // get details of this alignment row
-        if (rows.isHidden(alignmentRow))
-        {
-          g2d.fillRect(0, pixelRow, w, endRow - 1 - pixelRow);
-        }
-        pixelRow = endRow;
+      // get details of this alignment row
+      if (rows.isHidden(alignmentRow))
+      {
+        g2d.setColor(hidden);
+        g2d.fillRect(0, pixelRow, width, endRow - pixelRow + 1);
       }
+      pixelRow = endRow + 1;
+      seqIndex++;
     }
-    g2d.dispose();
+
     return hiddenImage;
   }
 
   /**
    * Draw the alignment annotation in the overview panel
    * 
+   * @param g
+   *          the graphics object to draw on
    * @param anno
    *          alignment annotation information
+   * @param y
+   *          y-position for the annotation graph
+   * @param cols
+   *          the collection of columns used in the overview panel
    */
-  public void drawGraph(AlignmentAnnotation anno)
+  public void drawGraph(Graphics g, AlignmentAnnotation anno, int y,
+          AlignmentColsCollectionI cols)
   {
-    int y = graphHeight;
-    Graphics g = miniMe.getGraphics();
-    g.translate(0, alignmentHeight);
-
     Annotation[] annotations = anno.annotations;
-    float max = anno.graphMax;
     g.setColor(Color.white);
-    g.fillRect(0, 0, w, y);
+    g.fillRect(0, 0, miniMe.getWidth(), y);
 
-    for (int pixelCol = 0, colNext = 0, pixelEnd = 0, len = annotations.length, icol = bscol
-            .nextSetBit(0); icol >= 0
-                    && icol < len; icol = getNextCol(icol, pixelEnd))
+    int height;
+    int colIndex = 0;
+    int pixelCol = 0;
+    for (int alignmentCol : cols)
     {
       if (redraw)
       {
-        if (showProgress)
-        {
-          changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0);
-        }
+        changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0);
         break;
       }
 
-      ++colNext;
-      pixelEnd = getNextPixel(colNext, colNext);
-      Annotation ann = annotations[icol];
-      if (ann != null)
+      if (alignmentCol >= annotations.length)
       {
-        Color color = ann.colour;
-        g.setColor(color == null ? Color.black : color);
-        int height = Math.min(y, (int) ((ann.value / max) * y));
-        g.fillRect(pixelCol, y - height, Math.max(1, pixelEnd - pixelCol),
-                height);
+        break; // no more annotations to draw here
       }
-      pixelCol = pixelEnd;
-    }
+      else
+      {
+        int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
+                miniMe.getWidth() - 1);
 
-    g.translate(0, -alignmentHeight);
-    g.dispose();
+        if (annotations[alignmentCol] != null)
+        {
+          if (annotations[alignmentCol].colour == null)
+          {
+            g.setColor(Color.black);
+          }
+          else
+          {
+            g.setColor(annotations[alignmentCol].colour);
+          }
 
-    if (showProgress)
-    {
-      changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1,
-              MAX_PROGRESS);
-    }
+          height = (int) ((annotations[alignmentCol].value / anno.graphMax)
+                  * y);
+          if (height > y)
+          {
+            height = y;
+          }
+
+          g.fillRect(pixelCol, y - height, endCol - pixelCol + 1, height);
+        }
 
+        pixelCol = endCol + 1;
+        colIndex++;
+      }
+    }
+    changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1,
+            MAX_PROGRESS);
   }
 
   /**