JAL-3369 JAL-3253-applet adds embedded dim checking for overview frame
[jalview.git] / src / jalview / renderer / OverviewRenderer.java
index c9096e2..45cc944 100644 (file)
@@ -22,6 +22,7 @@ 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,14 +31,23 @@ 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;
 
 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
 {
@@ -75,10 +85,25 @@ public class OverviewRenderer
 
   private OverviewResColourFinder resColFinder;
 
-  public OverviewRenderer(FeatureRenderer fr, OverviewDimensions od,
+  private boolean showProgress;
+
+  private AlignmentViewPanel panel;
+
+  public OverviewRenderer(AlignmentViewPanel panel, FeatureRenderer fr,
+          OverviewDimensions od,
           AlignmentI alignment,
           ResidueShaderI resshader, OverviewResColourFinder colFinder)
   {
+    this(panel, fr, od, alignment, resshader, colFinder, true);
+  }
+
+  public OverviewRenderer(AlignmentViewPanel panel,
+          jalview.api.FeatureRenderer fr,
+          OverviewDimensions od,
+          AlignmentI alignment, ResidueShaderI resshader,
+          OverviewResColourFinder colFinder, boolean showProgress)
+  {
+    this.panel = panel;
     finder = new FeatureColourFinder(fr);
     resColFinder = colFinder;
 
@@ -90,108 +115,243 @@ public class OverviewRenderer
     graphHeight = od.getGraphHeight();
     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
             BufferedImage.TYPE_INT_RGB);
+    this.showProgress = showProgress;
   }
 
-  /**
-   * Draw alignment rows and columns onto an image
-   * 
-   * @param rit
-   *          Iterator over rows to be drawn
-   * @param cit
-   *          Iterator over columns to be drawn
-   * @return image containing the drawing
-   */
-  public BufferedImage draw(AlignmentRowsCollectionI rows,
-          AlignmentColsCollectionI cols)
+  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;
+
+  void mainLoop()
+  {
+    while (!redraw)
+    {
+      switch (state)
+      {
+      case STATE_INIT:
+        seqIndex = 0;
+        pixelRow = 0;
+        state = STATE_NEXT;
+        continue;
+      case STATE_NEXT:
+        if (iter.hasNext())
+        {
+          nextRow();
+        }
+        else
+        {
+          state = STATE_DONE;
+        }
+        break;
+      case STATE_DONE:
+        done();
+        return;
+      }
+      if (delay > 0)
+      {
+        jsloop();
+        return;
+      }
+    }
+    done();
+  }
+
+  private void jsloop()
+  {
+    if (timer == null)
+    {
+      timer = new Timer(delay, new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          mainLoop();
+        }
+
+      });
+      timer.setRepeats(false);
+      timer.start();
+    }
+    else
+    {
+      timer.restart();
+    }
+  }
+
+  private void nextRow()
   {
-    int rgbcolor = Color.white.getRGB();
-    int seqIndex = 0;
-    int pixelRow = 0;
-    int alignmentHeight = miniMe.getHeight() - graphHeight;
-    int totalPixels = miniMe.getWidth() * alignmentHeight;
+    row = iter.next();
+    // System.out.println("OR row " + r);
+    // get details of this alignment row
+    SequenceI seq = rows.getSequence(row);
 
-    int lastRowUpdate = 0;
-    int lastUpdate = 0;
-    changeSupport.firePropertyChange(UPDATE, -1, 0);
+    // rate limiting step when rendering overview for lots of groups
+    SequenceGroup[] allGroups = al.findAllGroups(seq);
 
-    for (int alignmentRow : rows)
+    // calculate where this row extends to in pixels
+    int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq), h);
+
+    for (int pixelCol = 0, colIndex = 0, c = bscol
+            .nextSetBit(0); c >= 0; c = bscol.nextSetBit(c + 1))
     {
       if (redraw)
       {
         break;
       }
-    
-      // 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 column extends to in pixels
+      int endCol = Math.min(Math.round((++colIndex) * pixelsPerCol), w);
 
-      // 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)
+      // 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)
       {
-        if (redraw)
-        {
-          break;
-        }
-    
-        // 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)
+        // System.out.println("OR pc ec " + pixelCol + " " + endCol);
+        int rgb = getColumnColourFromSequence(allGroups, seq, c);
+        // fill in the appropriate number of pixels
+        for (int row = pixelRow; row < endRow; ++row)
         {
-          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)
           {
-            for (int col = pixelCol; col <= endCol; ++col)
-            {
-              miniMe.setRGB(col, row, rgbcolor);
-            }
+            // 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++;
           }
+        }
+        // }
 
-          // store last update value
-          lastUpdate = sendProgressUpdate(
-                  (pixelCol + 1) * (endRow - pixelRow), totalPixels,
-                  lastRowUpdate, lastUpdate);
-
-          pixelCol = endCol + 1;
+        pixelCol = endCol;
+        // store last update value
+        if (showProgress)
+        {
+          lastUpdate = sendProgressUpdate(endCol * (endRow - 1 - pixelRow),
+                  totalPixels, lastRowUpdate, lastUpdate);
         }
-        colIndex++;
       }
-
-      if (pixelRow != endRow + 1)
+    }
+    if (pixelRow < endRow)
+    {
+      pixelRow = endRow;
+      // store row offset and last update value
+      if (showProgress)
       {
-        // store row offset and last update value
-        lastRowUpdate = sendProgressUpdate(endRow + 1, alignmentHeight, 0,
+        // BH 2019.07.29 was (old) endRow + 1 (now endRow), but should be
+        // pixelRow + 1, surely
+        lastRowUpdate = sendProgressUpdate(endRow, alignmentHeight, 0,
                 lastUpdate);
         lastUpdate = lastRowUpdate;
-        pixelRow = endRow + 1;
       }
-      seqIndex++;
     }
+  }
+
+  private void done()
+  {
+    Platform.timeCheck(
+            "overviewrender " + ndone + " pixels row:" + row + " redraw:"
+                    + redraw,
+            Platform.TIME_MARK);
 
     overlayHiddenRegions(rows, cols);
-    // final update to progress bar if present
-    if (redraw)
+    if (showProgress)
     {
-      sendProgressUpdate(pixelRow - 1, alignmentHeight, 0, 0);
+      // 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);
+      }
     }
-    else
+    panel.overviewDone(miniMe);
+  }
+
+  int ndone = 0;
+
+  private AlignmentRowsCollectionI rows;
+
+  private AlignmentColsCollectionI cols;
+
+  Iterator<Integer> iter;
+
+  int alignmentHeight;
+
+  int totalPixels;
+
+  int lastRowUpdate;
+
+  int lastUpdate;
+
+  int[] pixels;
+
+  BitSet bscol = new BitSet();
+
+  int w, h;
+
+  /**
+   * Draw alignment rows and columns onto an image
+   * 
+   * @param rit
+   *          Iterator over rows to be drawn
+   * @param cit
+   *          Iterator over columns to be drawn
+   * @return image containing the drawing
+   */
+  public BufferedImage draw(AlignmentRowsCollectionI rows,
+          AlignmentColsCollectionI cols)
+  {
+    this.rows = rows;
+    this.cols = cols;
+    iter = rows.iterator();
+
+    w = miniMe.getWidth();
+    h = miniMe.getHeight();
+    alignmentHeight = h - graphHeight;
+    totalPixels = w * alignmentHeight;
+    lastRowUpdate = 0;
+    lastUpdate = 0;
+
+    if (showProgress)
+    {
+      changeSupport.firePropertyChange(UPDATE, -1, 0);
+    }
+
+    WritableRaster raster = miniMe.getRaster();
+    DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
+    Platform.timeCheck(null, Platform.TIME_MARK);
+    pixels = db.getBankData()[0];
+    bscol.clear();
+    for (int c : cols)
     {
-      sendProgressUpdate(alignmentHeight, miniMe.getHeight(), 0, 0);
+      bscol.set(c);
     }
+    state = STATE_INIT;
+    mainLoop();
+
     return miniMe;
   }
 
@@ -222,18 +382,13 @@ 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 lastcol)
+  int getColumnColourFromSequence(SequenceGroup[] allGroups, SequenceI seq,
+          int icol)
   {
-    Color color = resColFinder.GAP_COLOUR;
-
-    if ((seq != null) && (seq.getLength() > lastcol))
-    {
-      color = resColFinder.getResidueColour(true, shader, allGroups, seq,
-              lastcol, finder);
-    }
-
-    return color.getRGB();
+    return (seq == null || icol >= seq.getLength()
+            ? resColFinder.GAP_COLOUR
+            : resColFinder.getResidueColour(true, shader, allGroups, seq,
+                    icol, finder)).getRGB();
   }
 
   /**
@@ -256,6 +411,7 @@ public class OverviewRenderer
       g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
               TRANSPARENCY));
       g.drawImage(mask, 0, 0, miniMe.getWidth(), miniMe.getHeight(), null);
+      g.dispose();
     }
   }
 
@@ -280,9 +436,6 @@ public class OverviewRenderer
     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();
@@ -294,55 +447,59 @@ public class OverviewRenderer
     // set next colour to opaque
     g2d.setComposite(AlphaComposite.Src);
 
-    for (int alignmentCol : cols)
+    if (cols.hasHidden())
     {
-      if (redraw)
+      int colIndex = 0;
+      int pixelCol = 0;
+      for (int alignmentCol : cols)
       {
-        break;
-      }
+        if (redraw)
+        {
+          break;
+        }
 
-      // calculate where this column extends to in pixels
-      int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
-              hiddenImage.getWidth() - 1);
+        // calculate where this column extends to in pixels
+        int endCol = Math.min(Math.round((++colIndex) * pixelsPerCol),
+                width);
 
-      if (pixelCol <= endCol)
-      {
-        // determine the colour based on the sequence and column position
-        if (cols.isHidden(alignmentCol))
+        // endCol is one more than old endCol
+        if (pixelCol < endCol)
         {
-          g2d.setColor(hidden);
-          g2d.fillRect(pixelCol, 0, endCol - pixelCol + 1, height);
+          // determine the colour based on the sequence and column position
+          if (cols.isHidden(alignmentCol))
+          {
+            g2d.setColor(hidden);
+            g2d.fillRect(pixelCol, 0, endCol - pixelCol, height);
+          }
+          pixelCol = endCol;
         }
-
-        pixelCol = endCol + 1;
       }
-      colIndex++;
-
     }
-
-    int seqIndex = 0;
-    int pixelRow = 0;
-    for (int alignmentRow : rows)
+    if (rows.hasHidden())
     {
-      if (redraw)
+      int seqIndex = 0;
+      int pixelRow = 0;
+      for (int alignmentRow : rows)
       {
-        break;
-      }
+        if (redraw)
+        {
+          break;
+        }
 
-      // calculate where this row extends to in pixels
-      int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1,
-              miniMe.getHeight() - 1);
+        // calculate where this row extends to in pixels
+        int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq),
+                height);
 
-      // get details of this alignment row
-      if (rows.isHidden(alignmentRow))
-      {
-        g2d.setColor(hidden);
-        g2d.fillRect(0, pixelRow, width, endRow - pixelRow + 1);
+        // get details of this alignment row
+        if (rows.isHidden(alignmentRow))
+        {
+          g2d.setColor(hidden);
+          g2d.fillRect(0, pixelRow, width, endRow - 1 - pixelRow);
+        }
+        pixelRow = endRow;
       }
-      pixelRow = endRow + 1;
-      seqIndex++;
     }
-
+    g2d.dispose();
     return hiddenImage;
   }
 
@@ -362,56 +519,45 @@ public class OverviewRenderer
           AlignmentColsCollectionI cols)
   {
     Annotation[] annotations = anno.annotations;
+    float max = anno.graphMax;
     g.setColor(Color.white);
-    g.fillRect(0, 0, miniMe.getWidth(), y);
+    int width = miniMe.getWidth();
+    g.fillRect(0, 0, width, y);
 
-    int height;
     int colIndex = 0;
     int pixelCol = 0;
-    for (int alignmentCol : cols)
+    for (int icol : cols)
     {
       if (redraw)
       {
-        changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0);
+        if (showProgress)
+        {
+          changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0);
+        }
         break;
       }
 
-      if (alignmentCol >= annotations.length)
+      if (icol >= annotations.length)
       {
         break; // no more annotations to draw here
       }
-      else
+      int endCol = Math.min(Math.round((++colIndex) * pixelsPerCol), width);
+      Annotation ann = annotations[icol];
+      if (ann != null)
       {
-        int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
-                miniMe.getWidth() - 1);
-
-        if (annotations[alignmentCol] != null)
-        {
-          if (annotations[alignmentCol].colour == null)
-          {
-            g.setColor(Color.black);
-          }
-          else
-          {
-            g.setColor(annotations[alignmentCol].colour);
-          }
-
-          height = (int) ((annotations[alignmentCol].value / anno.graphMax)
-                  * y);
-          if (height > y)
-          {
-            height = y;
-          }
-
-          g.fillRect(pixelCol, y - height, endCol - pixelCol + 1, height);
-        }
+        Color color = ann.colour;
+        g.setColor(color == null ? Color.black : color);
 
-        pixelCol = endCol + 1;
-        colIndex++;
+        int height = Math.min(y, (int) ((ann.value / max) * y));
+        g.fillRect(pixelCol, y - height, endCol - pixelCol, height);
       }
+      pixelCol = endCol;
+    }
+    if (showProgress)
+    {
+      changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1,
+              MAX_PROGRESS);
     }
-    changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1,
-            MAX_PROGRESS);
   }
 
   /**