JAL-3253-applet JAL-3383
authorhansonr <hansonr@STO24954W.ad.stolaf.edu>
Mon, 29 Jul 2019 21:05:34 +0000 (16:05 -0500)
committerhansonr <hansonr@STO24954W.ad.stolaf.edu>
Tue, 30 Jul 2019 18:09:10 +0000 (13:09 -0500)
Significant improvement to Overview rendering. See JAL-3383 issue for
details.

27 files changed:
1  2 
src/jalview/api/AlignmentColsCollectionI.java
src/jalview/appletgui/OverviewCanvas.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/AllColsCollection.java
src/jalview/datamodel/HiddenColumns.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/VisibleColsCollection.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/OverviewCanvas.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/io/AlignFile.java
src/jalview/io/FeaturesFile.java
src/jalview/jbgui/GAlignmentPanel.java
src/jalview/renderer/OverviewRenderer.java
src/jalview/renderer/OverviewResColourFinder.java
src/jalview/renderer/ResidueColourFinder.java
src/jalview/renderer/seqfeatures/FeatureColourFinder.java
src/jalview/viewmodel/OverviewDimensions.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java

@@@ -20,6 -20,6 +20,8 @@@
   */
  package jalview.api;
  
++import java.util.BitSet;
++
  public interface AlignmentColsCollectionI extends Iterable<Integer>
  {
    /**
     * @return true if there is at least 1 hidden column
     */
    public boolean hasHidden();
++
++  /**
++   * Get the visible-column bitset, possibly containing hidden columns (which
++   * may or may not be hidden in the overview).
++   * 
++   * @return a BitSet
++   */
++  public BitSet getOverviewBitSet();
++
++  /**
++   * Get the hidden-column bitset, (which may or may not be hidden in the
++   * overview).
++   * 
++   * @return
++   */
++  BitSet getHiddenBitSet();
  }
@@@ -129,8 -129,8 +129,7 @@@ public class OverviewCanvas extends Com
      or = new OverviewRenderer(panel.ap, fr, od, av.getAlignment(),
              av.getResidueShading(), new OverviewResColourFinder());
      offscreen = nullFrame.createImage(od.getWidth(), od.getHeight());
--    or.draw(od.getRows(av.getAlignment()),
--            od.getColumns(av.getAlignment()));
++    or.drawMiniMe();
    }
  
    @Override
  
    public void finalizeDraw(BufferedImage miniMe)
    {
--    Graphics mg = miniMe.getGraphics();
--
--    // checks for conservation annotation to make sure overview works for DNA
--    // too
--    if (showAnnotation)
--    {
--      mg.translate(0, od.getSequencesHeight());
--      or.drawGraph(mg, av.getAlignmentConservationAnnotation(),
--              od.getGraphHeight(), od.getColumns(av.getAlignment()));
--      mg.translate(0, -od.getSequencesHeight());
--    }
--
      if (restart)
      {
        restart = false;
      }
      else
      {
++      this.miniMe = miniMe;
++      // checks for conservation annotation to make sure overview works for DNA
++      // too
++      if (showAnnotation)
++      {
++        or.drawGraph(av.getAlignmentConservationAnnotation());
++      }
        updaterunning = false;
++      repaint();
      }
    }
  
@@@ -255,11 -255,11 +255,11 @@@ public class OverviewPanel extends Pane
    @Override
    public void run()
    {
++    setBoxPosition();
      canvas.draw(av.isShowSequenceFeatures(),
              (av.isShowAnnotation()
                      && av.getAlignmentConservationAnnotation() != null),
              ap.seqPanel.seqCanvas.getFeatureRenderer());
--    setBoxPosition();
    }
  
    /**
@@@ -414,12 -414,12 +414,12 @@@ public class Alignment implements Align
  
      synchronized (groups)
      {
--      temp.clear();
        int gSize = groups.size();
        if (gSize == 0)
        {
          return noGroups;
        }
++      temp.clear();
        for (int i = 0; i < gSize; i++)
        {
          SequenceGroup sg = groups.get(i);
      }
    }
  
++  @Override
++  public void resetColors()
++  {
++    for (int i = getHeight(); --i >= 0;)
++    {
++      sequences.get(i).resetColors();
++    }
++    // if (dataset != null)
++    // {
++    // dataset.resetColors();
++    // }
++  }
++
  }
@@@ -624,4 -624,4 +624,10 @@@ public interface AlignmentI extends Ann
    public HiddenColumns propagateInsertions(SequenceI profileseq,
            AlignmentView input);
  
++  /**
++   * Remove all color already assigned to sequences, causing them to be be
++   * recalculated.
++   */
++  void resetColors();
++
  }
@@@ -22,6 -22,6 +22,7 @@@ package jalview.datamodel
  
  import jalview.api.AlignmentColsCollectionI;
  
++import java.util.BitSet;
  import java.util.Iterator;
  
  public class AllColsCollection implements AlignmentColsCollectionI
    {
      return hidden.hasHiddenColumns();
    }
++
++  private BitSet bsVisible;
++
++  @Override
++  public BitSet getHiddenBitSet()
++  {
++    return hidden.getBitset();
++  }
++
++  /**
++   * return ALL columns, not just the truly visible ones
++   */
++  @Override
++  public BitSet getOverviewBitSet()
++  {
++    if (bsVisible == null)
++    {
++      bsVisible = new BitSet(end + 1);
++      bsVisible.set(0, end + 1);
++    }
++    return bsVisible;
++  }
  }
@@@ -86,6 -86,6 +86,20 @@@ public class HiddenColumn
     */
    private List<int[]> hiddenColumns = new ArrayList<>();
  
++  private BitSet hiddenBitSet;
++
++  public BitSet getBitset()
++  {
++    if (hiddenBitSet == null)
++    {
++      hiddenBitSet = new BitSet();
++      for (int[] range : hiddenColumns)
++      {
++        hiddenBitSet.set(range[0], range[1] + 1);
++      }
++    }
++    return hiddenBitSet;
++  }
    /**
     * Constructor
     */
                prevHiddenCount);
      } finally
      {
++      hiddenBitSet = null;
        LOCK.writeLock().unlock();
      }
    }
        insertRangeAtOverlap(i, start, end, region);
        added = true;
      }
++    hiddenBitSet = null;
      return added;
    }
  
      }
      numColumns += region[1] - oldend;
      hiddenColumns.subList(i + 1, endi + 1).clear();
++    hiddenBitSet = null;
    }
  
    /**
  
      } finally
      {
++      hiddenBitSet = null;
        LOCK.writeLock().unlock();
      }
    }
  
      } finally
      {
++      hiddenBitSet = null;
        LOCK.writeLock().unlock();
      }
    }
        }
      } finally
      {
++      hiddenBitSet = null;
        LOCK.writeLock().unlock();
      }
    }
  
      } finally
      {
++      hiddenBitSet = null;
        LOCK.readLock().unlock();
      }
    }
        for (int firstSet = tohide
                .nextSetBit(start), lastSet = start; firstSet >= start
                        && lastSet <= end; firstSet = tohide
--                      .nextSetBit(lastSet))
++                              .nextSetBit(lastSet))
        {
          lastSet = tohide.nextClearBit(firstSet);
          if (lastSet <= end)
        cursor = new HiddenColumnsCursor(hiddenColumns);
      } finally
      {
++      hiddenBitSet = null;
        LOCK.writeLock().unlock();
      }
    }
        }
      } finally
      {
++      hiddenBitSet = null;
        LOCK.writeLock().unlock();
      }
    }
      {
        LOCK.writeLock().lock();
  
--      BitSet hiddenBitSet = new BitSet();
--      for (int[] range : hiddenColumns)
--      {
--        hiddenBitSet.set(range[0], range[1] + 1);
--      }
--      hiddenBitSet.andNot(updates);
++      getBitset().andNot(updates);
        hiddenColumns.clear();
        hideColumns(hiddenBitSet);
      } finally
      {
++      hiddenBitSet = null;
        LOCK.writeLock().unlock();
      }
    }
@@@ -28,7 -28,7 +28,6 @@@ import jalview.util.DBRefUtils
  import jalview.util.MapList;
  import jalview.util.StringUtils;
  
--import java.awt.Color;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.BitSet;
@@@ -2027,6 -2027,6 +2026,7 @@@ public class Sequence extends ASequenc
    @Override
    public void sequenceChanged()
    {
++    argb = null;
      changeCount++;
    }
  
      return 0;
    }
  
++  private int[] argb;
++
    @Override
--  public Color getColor(int i)
++  public int getColor(int i)
    {
--    return null;
++    return argb == null ? 0 : argb[i];
    }
  
    @Override
--  public Color setColor(int i, Color c)
++  public int setColor(int i, int rgb)
    {
--    return c;
++    if (argb == null)
++    {
++      argb = new int[this.sequence.length];
++    }
++    return (argb[i] = rgb);
    }
  
    @Override
    public void resetColors()
    {
++    argb = null;
    }
  }
@@@ -25,7 -25,7 +25,6 @@@ import jalview.datamodel.features.Seque
  import jalview.util.MapList;
  import jalview.ws.params.InvalidArgumentException;
  
--import java.awt.Color;
  import java.util.BitSet;
  import java.util.Iterator;
  import java.util.List;
@@@ -585,9 -585,9 +584,9 @@@ public interface SequenceI extends ASeq
     */
    public int firstResidueOutsideIterator(Iterator<int[]> it);
  
--  public Color getColor(int i);
++  public int getColor(int i);
  
--  public Color setColor(int i, Color c);
++  public int setColor(int i, int rgb);
  
    public void resetColors();
  
@@@ -22,6 -22,6 +22,7 @@@ package jalview.datamodel
  
  import jalview.api.AlignmentColsCollectionI;
  
++import java.util.BitSet;
  import java.util.Iterator;
  
  public class VisibleColsCollection implements AlignmentColsCollectionI
@@@ -32,6 -32,6 +33,8 @@@
  
    HiddenColumns hidden;
  
++  private BitSet bsVisible;
++
    public VisibleColsCollection(int s, int e, HiddenColumns h)
    {
      start = s;
      return false;
    }
  
++  /**
++   * Only the visible columns.
++   */
++  @Override
++  public BitSet getOverviewBitSet()
++  {
++    if (bsVisible == null)
++    {
++      bsVisible = new BitSet(end + 1);
++    }
++    bsVisible.clear();
++    bsVisible.set(start, end + 1);
++    bsVisible.andNot(hidden.getBitset());
++
++    return bsVisible;
++  }
++
++  @Override
++  public BitSet getHiddenBitSet()
++  {
++    return new BitSet();
++  }
++
  }
@@@ -4525,6 -4525,6 +4525,7 @@@ public class AlignFrame extends GAlignF
      {
        viewport.setShowSequenceFeatures(true);
        showSeqFeatures.setSelected(true);
++      alignPanel.getAlignment().resetColors();
      }
  
    }
@@@ -70,7 -70,7 +70,14 @@@ import java.util.List
  import javax.swing.SwingUtilities;
  
  /**
-- * DOCUMENT ME!
++ * The main panel of an AlignFrame, containing holders for the IdPanel,
++ * SeqPanel, AnnotationLabels (a JPanel), and AnnotationPanel.
++ * 
++ * Additional holders contain an IdPanelWidthAdjuster space above the idPanel,
++ * AnnotationScroller (JScrollPane for AnnotationPanel), and vertical and
++ * horizontal scrollbars.
++ * 
++ * 
   * 
   * @author $author$
   * @version $Revision: 1.161 $
@@@ -838,6 -838,6 +845,7 @@@ public class AlignmentPanel extends GAl
  
        if (overviewPanel != null)
        {
++        getAlignment().resetColors();
          overviewPanel.updateOverviewImage();
        }
      }
@@@ -45,11 -45,11 +45,11 @@@ public class FeatureRendere
    {
      super(alignPanel.av);
      this.ap = alignPanel;
--    if (alignPanel.getSeqPanel() != null
--            && alignPanel.getSeqPanel().seqCanvas != null
--            && alignPanel.getSeqPanel().seqCanvas.fr != null)
++    SeqPanel sp = alignPanel.getSeqPanel();
++    if (sp != null && sp.seqCanvas != null && sp.seqCanvas.fr != null)
      {
--      transferSettings(alignPanel.getSeqPanel().seqCanvas.fr);
++      sp.clearColors();
++      transferSettings(sp.seqCanvas.fr);
      }
    }
  }
@@@ -73,15 -73,15 +73,18 @@@ public class OverviewCanvas extends JPa
  
    private OverviewPanel panel;
  
++  private boolean showProgress;
++
    public OverviewCanvas(OverviewPanel panel,
            OverviewDimensions overviewDims,
            AlignViewportI alignvp, ProgressPanel pp)
    {
      this.panel = panel;
      od = overviewDims;
++    lastMiniMe = null;
      av = alignvp;
      progressPanel = pp;
--
++    showProgress = (pp != null);
      sr = new SequenceRenderer(av);
      sr.renderGaps = false;
      fr = new jalview.renderer.seqfeatures.FeatureRenderer(av);
    public void resetOviewDims(OverviewDimensions overviewDims)
    {
      od = overviewDims;
++    lastMiniMe = null;
    }
  
    /**
        else
        {
          updaterunning = true;
++        restart = false;
        }
        return restart;
      }
      this.showSequenceFeatures = showSequenceFeatures;
      this.showAnnotation = showAnnotation;
      this.featureRenderer = featureRenderer;
--
--    // System.out.println("OC draw " + ++ndraw + " showseqf="
--    // + showSequenceFeatures + " showAnno=" + showAnnotation);
--
      if (showSequenceFeatures)
      {
        fr.transferSettings(featureRenderer);
      }
--
      setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
--
      AlignmentI al = av.getAlignment();
--    or = new OverviewRenderer(panel.ap, fr, od, al,
--            av.getResidueShading(), cf,
--            progressPanel != null);
--    if (progressPanel != null)
++    or = new OverviewRenderer(panel.ap, fr, od, al, av.getResidueShading(),
++            cf, showProgress);
++    if (showProgress)
      {
        or.addPropertyChangeListener(progressPanel);
      }
--    or.draw(od.getRows(al), od.getColumns(al));
++    or.drawMiniMe();
    }
  
--  void finalizeDraw(BufferedImage miniMe)
++  synchronized void finalizeDraw(BufferedImage miniMe)
    {
--    Graphics mg = miniMe.getGraphics();
--    if (showAnnotation)
++
++    if (or == null)
      {
--      mg.translate(0, od.getSequencesHeight());
--      or.drawGraph(mg, av.getAlignmentConservationAnnotation(),
--              od.getGraphHeight(), od.getColumns(av.getAlignment()));
--      mg.translate(0, -od.getSequencesHeight());
++      System.out.println("OC or is null");
      }
--    mg.dispose(); // BH 2019
--    if (progressPanel != null)
++    else if (showProgress)
      {
        or.removePropertyChangeListener(progressPanel);
      }
--    or = null;
      if (restart)
      {
++      or = null;
        restart = false;
        if (!disposed)
        {
      }
      else
      {
++      if (showAnnotation)
++      {
++        or.drawGraph(av.getAlignmentConservationAnnotation());
++      }
++      or = null;
        updaterunning = false;
        lastMiniMe = miniMe;
        repaint();
      }
--
    }
++
    @Override
    public void paintComponent(Graphics g)
    {
      else if (drawMe)
      {
        // is this a resize?
--      if (w != od.getWidth() ||  h != od.getHeight()) {
--      // if there is annotation, scale the alignment and annotation
--      // separately
--      if (od.getGraphHeight() <= 0 && od.getSequencesHeight() <= 0)
++      if (w != od.getWidth() || h != od.getHeight())
        {
--        od.setWidth(w);
--        od.setHeight(h);
++
++        lastMiniMe = null;
          return;
--      }
--        // System.out.println("OC new subimages");
--      BufferedImage topImage = lastMiniMe.getSubimage(0, 0, od.getWidth(),
--              od.getSequencesHeight());
--      BufferedImage bottomImage = lastMiniMe.getSubimage(0,
--              od.getSequencesHeight(), od.getWidth(), od.getGraphHeight());
--
--      // must be done at this point as we rely on using old width/height
--      // above, and new width/height below
--      od.setWidth(w);
--      od.setHeight(h);
--
--      // stick the images back together so lastMiniMe is consistent in the
--      // event of a repaint - BUT probably not thread safe
--      // System.out.println("OC new lastminime " + w + " " + h);
--      lastMiniMe = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
--      Graphics lg = lastMiniMe.getGraphics();
--      lg.drawImage(topImage, 0, 0, w, od.getSequencesHeight(), null);
--      lg.drawImage(bottomImage, 0, od.getSequencesHeight(), w,
--              od.getGraphHeight(), this);
--      lg.dispose();
--      // BH 2019: removed -- this is now taken care of using vpbox in
--      // OverviewDimension
--      // // make sure the box is in the right place
--      // od.setBoxPosition(av.getAlignment().getHiddenSequences(),
--      // av.getAlignment().getHiddenColumns());
++        // // if there is annotation, scale the alignment and annotation
++        // // separately
++        // if (od.getGraphHeight() <= 0 && od.getSequencesHeight() <= 0)
++        // {
++        // od.setWidth(w);
++        // od.setHeight(h);
++        // return;
++        // }
++        // try
++        // {
++        // BufferedImage topImage = lastMiniMe.getSubimage(0, 0,
++        // od.getWidth(), od.getSequencesHeight());
++        //
++        // BufferedImage bottomImage = lastMiniMe.getSubimage(0,
++        // od.getSequencesHeight(), od.getWidth(),
++        // od.getGraphHeight());
++        //
++        // // must be done at this point as we rely on using old width/height
++        // // above, and new width/height below
++        // od.setWidth(w);
++        // od.setHeight(h);
++        //
++        // // stick the images back together so lastMiniMe is consistent in the
++        // // event of a repaint - BUT probably not thread safe
++        //
++        // // right -- this fails with fast user action.
++        //
++        // lastMiniMe = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
++        // Graphics lg = lastMiniMe.getGraphics();
++        // lg.drawImage(topImage, 0, 0, w, od.getSequencesHeight(), null);
++        // lg.drawImage(bottomImage, 0, od.getSequencesHeight(), w,
++        // od.getGraphHeight(), this);
++        // lg.dispose();
++        //
++        // } catch (RasterFormatException e)
++        // {
++        // System.out.println("OC Raster Exception " + lastMiniMe.getWidth()
++        // + "/" + w + "," + lastMiniMe.getHeight() + "/" + h + " "
++        // + od.getSequencesHeight() + " " + od.getGraphHeight());
++        // }
++        // BH 2019: removed -- this is now taken care of using vpbox in
++        // OverviewDimension
++        // // make sure the box is in the right place
++        // od.setBoxPosition(av.getAlignment().getHiddenSequences(),
++        // av.getAlignment().getHiddenColumns());
        }
      }
  
    {
      disposed = true;
      od = null;
++    lastMiniMe = null;
      synchronized (this)
      {
        setRestart("dispose");
@@@ -130,6 -130,6 +130,7 @@@ public class OverviewPanel extends JPan
          if (getWidth() == od.getWidth()
                  && getHeight() == od.getHeight() + ph)
          {
++          // BH: resizing is now exceptionally fast.
            updateOverviewImage();
          }
          else
              }
              od.setWidth(w);
              od.setHeight(h - ph);
--            repaint();
++            updateOverviewImage();
++            // repaint();
            }
            // BH 2019.07.29 this is unnecessary -- it is what layout managers are
            // for:
    }
  
    /**
 -   * Updates the overview image when the related alignment panel is updated
 +   * Updates the overview image when the related alignment panel is updated.
 +   * 
 +   * Cases:
 +   * 
++   * AlignFrame.setFeatureGroupState
++   * 
++   * AlignmentPanel.paintAlignment(true,...) (117 references)
++   * 
++   * OverviewPanel..componentResized() OverviewPanel.toggleHiddenColumns()
++   * 
++   * PopupMenu for action.reveal_sequences, action.reveal_all
++   * 
++   * SliderPanel.mouseReleased()
 +   * 
     */
    public void updateOverviewImage()
    {
    {
      if (canvas != null)
      {
++      setBoxPosition();
        canvas.draw(av.isShowSequenceFeatures(),
                (av.isShowAnnotation()
                        && av.getAlignmentConservationAnnotation() != null),
                ap.getFeatureRenderer());
--      setBoxPosition();
      }
    }
  
@@@ -569,31 -569,31 +569,29 @@@ public class ScalePanel extends JPane
    @Override
    public void propertyChange(PropertyChangeEvent evt)
    {
--    // Respond to viewport change events (e.g. alignment panel was scrolled)
--    // Both scrolling and resizing change viewport ranges: scrolling changes
--    // both start and end points, but resize only changes end values.
--    // Here we only want to fastpaint on a scroll, with resize using a normal
--    // paint, so scroll events are identified as changes to the horizontal or
--    // vertical start value.
      switch (evt.getPropertyName())
      {
      case ViewportRanges.STARTRES:
      case ViewportRanges.STARTRESANDSEQ:
      case ViewportRanges.MOVE_VIEWPORT:
        // scroll event, repaint panel
--      // TODO: check this?
--      // BH: This is actually quite strange. AlignmentPanel is taking care of
--      // all of
--      // this with fast paint, so why indirectly trigger a repaint from the
--      // ScalePanel?
--
++      // original comment:
        // Call repaint on alignment panel so that repaints from other alignment
        // panel components can be aggregated. Otherwise performance of the
        // overview
        // window and others may be adversely affected.
--      // av.getAlignPanel().repaint();
--      System.out.println("ScalePanel propertyChange disabled "
--              + evt.getPropertyName());
++
++      // TODO: check this?
++      // BH: This is actually quite strange. AlignmentPanel is taking care of
++      // all of this with fast paint, so why indirectly trigger a repaint from
++      // the ScalePanel? Where do we see this behavior necessary?
++      // I have set this to check for a trigger from some other ViewportRanges,
++      // but I don't actually think that is possible.
++
++      if (evt.getSource() != av.getRanges())
++      {
++        av.getAlignPanel().repaint();
++      }
        break;
      }
    }
@@@ -375,15 -375,15 +375,15 @@@ public class SeqCanvas extends JPanel i
      int charHeight = av.getCharHeight();
      int charWidth = av.getCharWidth();
  
--    int width = getWidth();
--    int height = getHeight();
++    int availWidth = getWidth();
++    int availHeight = getHeight();
  
--    width -= (width % charWidth);
--    height -= (height % charHeight);
++    availWidth -= (availWidth % charWidth);
++    availHeight -= (availHeight % charHeight);
  
      // BH 2019 can't possibly fastPaint if either width or height is 0
  
--    if (width == 0 || height == 0)
++    if (availWidth == 0 || availHeight == 0)
      {
        return;
      }
        // img is a cached version of the last view we drew.
        // If we have no img or the size has changed, make a new one.
        //
--      if (img == null || width != img.getWidth()
--              || height != img.getHeight())
++      if (img == null || availWidth != img.getWidth()
++              || availHeight != img.getHeight())
        {
--        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
++        img = new BufferedImage(availWidth, availHeight,
++                BufferedImage.TYPE_INT_RGB);
        }
  
        Graphics2D gg = (Graphics2D) img.getGraphics();
        }
  
        gg.setColor(Color.white);
--      gg.fillRect(0, 0, img.getWidth(), img.getHeight());
++      gg.fillRect(0, 0, availWidth, availHeight);
  
        if (av.getWrapAlignment())
        {
--        drawWrappedPanel(gg, width, height, ranges.getStartRes());
++        drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
        }
        else
        {
    }
  
    /**
--   * Returns the visible width of the canvas in residues, after allowing for
--   * East or West scales (if shown)
++   * Using the current font, determine fields labelWidthEast and labelWidthWest,
++   * and return the number of residues that can fill the remaining width.
     * 
--   * @param canvasWidth
++   * @param width
     *          the width in pixels (possibly including scales)
     * 
--   * @return
++   * @return the visible width in residues, after allowing for East or West
++   *         scales (if shown)
++   * 
     */
--  public int getWrappedCanvasWidth(int canvasWidth)
++  public int getWrappedCanvasWidth(int width)
    {
      int charWidth = av.getCharWidth();
  
      FontMetrics fm = getFontMetrics(av.getFont());
  
--    int labelWidth = 0;
--    
--    if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
--    {
--      labelWidth = getLabelWidth(fm);
--    }
++    int labelWidth = (av.getScaleRightWrapped() || av.getScaleLeftWrapped()
++            ? getLabelWidth(fm)
++            : 0);
  
      labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
  
      labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
  
--    return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
++    return (width - labelWidthEast - labelWidthWest) / charWidth;
    }
  
    /**
        maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
      }
  
++    // quick int log10
      int length = 0;
      for (int i = maxWidth; i > 0; i /= 10)
      {
     * window
     * 
     * @param g
--   * @param canvasWidth
++   * @param availWidth
     *          available width in pixels
--   * @param canvasHeight
++   * @param availHeight
     *          available height in pixels
     * @param startColumn
     *          the first column (0...) of the alignment to draw
     */
--  public void drawWrappedPanel(Graphics g, int canvasWidth,
--          int canvasHeight, final int startColumn)
++  public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
++          final int startColumn)
    {
--    int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
--            canvasHeight);
++    int wrappedWidthInResidues = calculateWrappedGeometry(availWidth,
++            availHeight);
  
      av.setWrappedWidth(wrappedWidthInResidues);
  
      // we need to call this again to make sure the startColumn +
      // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
      // correctly.
--    calculateWrappedGeometry(canvasWidth, canvasHeight);
++    calculateWrappedGeometry(availWidth, availHeight);
  
      /*
       * draw one width at a time (excluding any scales shown),
      {
        int endColumn = Math
                .min(maxWidth, start + wrappedWidthInResidues - 1);
--      drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
++      drawWrappedWidth(g, ypos, start, endColumn, availHeight);
        ypos += wrappedRepeatHeightPx;
        start += wrappedWidthInResidues;
        currentWidth++;
     * <li>whether scales are shown left, right or above the alignment</li>
     * </ul>
     * 
--   * @param canvasWidth
--   * @param canvasHeight
++   * @param availWidth
++   * @param availHeight
     * @return the number of residue columns in each width
     */
--  protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
++  protected int calculateWrappedGeometry(int availWidth, int availHeight)
    {
      int charHeight = av.getCharHeight();
  
       * ensuring a part height includes at least one sequence
       */
      ViewportRanges ranges = av.getRanges();
--    wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
--    int remainder = canvasHeight % wrappedRepeatHeightPx;
++    wrappedVisibleWidths = availHeight / wrappedRepeatHeightPx;
++    int remainder = availHeight % wrappedRepeatHeightPx;
      if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
      {
        wrappedVisibleWidths++;
      /*
       * compute width in residues; this also sets East and West label widths
       */
--    int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
++    int wrappedWidthInResidues = getWrappedCanvasWidth(availWidth);
  
      /*
       *  limit visibleWidths to not exceed width of alignment
@@@ -74,7 -74,7 +74,8 @@@ import javax.swing.Timer
  import javax.swing.ToolTipManager;
  
  /**
-- * DOCUMENT ME!
++ * The main scrollable region containing the alignment and just to the right of
++ * the IDPanel.
   * 
   * @author $author$
   * @version $Revision: 1.130 $
@@@ -223,7 -223,7 +224,7 @@@ public class SeqPanel extends JPane
    SearchResultsI lastSearchResults;
  
    /**
--   * Creates a new SeqPanel object
++   * Create a new SeqPanel.
     * 
     * @param viewport
     * @param alignPanel
              true);
    }
  
++  public void clearColors()
++  {
++    av.getAlignment().getSequences();
++    // TODO Auto-generated method stub
++
++  }
++
  }
@@@ -323,14 -323,14 +323,15 @@@ public abstract class AlignFile extend
     */
    protected void initData()
    {
--    seqs = new Vector<SequenceI>();
--    annotations = new Vector<AlignmentAnnotation>();
--    seqGroups = new ArrayList<SequenceGroup>();
++    seqs = new Vector<>();
++    annotations = new Vector<>();
++    seqGroups = new ArrayList<>();
      parseCalled = false;
    }
  
    /**
--   * DOCUMENT ME!
++   * Create the seqs Vector from a set of parsed sequences in an AlignFile,
++   * FeaturesFile, RnamlFile, or StockholmFile.
     * 
     * @param s
     *          DOCUMENT ME!
    @Override
    public void setSeqs(SequenceI[] s)
    {
--    seqs = new Vector<SequenceI>();
++    seqs = new Vector<>();
  
      for (int i = 0; i < s.length; i++)
      {
    {
      if (newickStrings == null)
      {
--      newickStrings = new Vector<String[]>();
++      newickStrings = new Vector<>();
      }
      newickStrings.addElement(new String[] { treeName, newickString });
    }
@@@ -829,14 -829,14 +829,15 @@@ public class FeaturesFile extends Align
      AlignViewportI av = getViewport();
      if (av != null)
      {
--      if (av.getAlignment() != null)
++      AlignmentI a = av.getAlignment();
++      if (a != null)
        {
--        dataset = av.getAlignment().getDataset();
++        dataset = a.getDataset();
        }
        if (dataset == null)
        {
          // working in the applet context ?
--        dataset = av.getAlignment();
++        dataset = a;
        }
      }
      else
@@@ -123,6 -123,6 +123,7 @@@ public class GAlignmentPanel extends JP
      hscrollFillerPanel.setPreferredSize(new Dimension(70, 10));
      hscrollHolder.setBackground(Color.white);
      annotationScroller.setBorder(null);
++    annotationScroller.setBackground(Color.BLUE);
      annotationScroller.setPreferredSize(new Dimension(10, 80));
      this.setPreferredSize(new Dimension(220, 166));
  
@@@ -66,9 -66,9 +66,17 @@@ public class OverviewRendere
    // image to render on
    private BufferedImage miniMe;
  
--  // raw number of pixels to allocate to each column
++  /**
++   * Number of pixelsPerCol;
++   */
    private float pixelsPerCol;
  
++  /**
++   * Number of visible columns per pixel.
++   * 
++   */
++  private float colsPerPixel;
++
    // raw number of pixels to allocate to each row
    private float pixelsPerSeq;
  
@@@ -89,6 -89,6 +97,8 @@@
  
    private AlignmentViewPanel panel;
  
++  private int sequencesHeight;
++
    public OverviewRenderer(AlignmentViewPanel panel, FeatureRenderer fr,
            OverviewDimensions od,
            AlignmentI alignment,
    }
  
    public OverviewRenderer(AlignmentViewPanel panel,
--          jalview.api.FeatureRenderer fr,
--          OverviewDimensions od,
++          jalview.api.FeatureRenderer fr, OverviewDimensions od,
            AlignmentI alignment, ResidueShaderI resshader,
            OverviewResColourFinder colFinder, boolean showProgress)
    {
      this.panel = panel;
      finder = new FeatureColourFinder(fr);
--    resColFinder = colFinder;
--
      al = alignment;
      shader = resshader;
++    resColFinder = colFinder;
++    this.showProgress = showProgress;
  
--    pixelsPerCol = od.getPixelsPerCol();
--    pixelsPerSeq = od.getPixelsPerSeq();
++    w = od.getWidth();
++    h = od.getHeight();
++    rows = od.getRows(alignment);
++    cols = od.getColumns(alignment);
      graphHeight = od.getGraphHeight();
--    miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
--            BufferedImage.TYPE_INT_RGB);
--    this.showProgress = showProgress;
++    alignmentHeight = od.getSequencesHeight();
++
++    pixelsPerSeq = od.getPixelsPerSeq();
++    pixelsPerCol = od.getPixelsPerCol();
++    colsPerPixel = Math.max(1, 1f / pixelsPerCol);
++
    }
  
    final static int STATE_INIT = 0;
  
    private Integer row;
  
--  void mainLoop()
++  /**
++   * 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.
++   * 
++   * 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
++   * @return image containing the drawing
++   * 
++   * @author Bob Hanson 2019.07.30
++   */
++  public void drawMiniMe()
    {
--    while (!redraw)
++    state = STATE_INIT;
++    mainLoop();
++  }
++
++  protected void mainLoop()
++  {
++    out: while (!redraw)
      {
        switch (state)
        {
        case STATE_INIT:
--        seqIndex = 0;
--        pixelRow = 0;
++        init();
          state = STATE_NEXT;
          continue;
        case STATE_NEXT:
--        if (iter.hasNext())
++        if (!rowIterator.hasNext())
          {
--          nextRow();
++          state = STATE_DONE;
++          continue;
          }
--        else
++        nextRow();
++        if (!loop())
          {
--          state = STATE_DONE;
++          continue;
          }
--        break;
--      case STATE_DONE:
--        done();
--        return;
--      }
--      if (delay > 0)
--      {
--        jsloop();
          return;
++      case STATE_DONE:
++        break out;
        }
++      // Java will continue without a timeout
      }
      done();
    }
  
--  private void jsloop()
++  private void init()
    {
--    if (timer == null)
--    {
--      timer = new Timer(delay, new ActionListener()
--      {
--        @Override
--        public void actionPerformed(ActionEvent e)
--        {
--          mainLoop();
--        }
++    rowIterator = rows.iterator();
++    seqIndex = 0;
++    pixelRow = 0;
++    lastRowUpdate = 0;
++    lastUpdate = 0;
++    totalPixels = w * alignmentHeight;
  
--      });
--      timer.setRepeats(false);
--      timer.start();
--    }
--    else
++    if (showProgress)
      {
--      timer.restart();
++      changeSupport.firePropertyChange(UPDATE, -1, 0);
      }
++
++    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();
    }
  
    private void nextRow()
    {
--    row = iter.next();
++    row = rowIterator.next();
      // System.out.println("OR row " + r);
      // get details of this alignment row
      SequenceI seq = rows.getSequence(row);
  
      // 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))
++    for (int pixelCol = 0, colNext = 0, pixelEnd = 0, icol = bscol
++            .nextSetBit(0); icol >= 0; icol = getNextCol(icol, pixelEnd))
      {
        if (redraw)
        {
          break;
        }
  
--      // calculate where this column extends to in pixels
--      int endCol = Math.min(Math.round((++colIndex) * pixelsPerCol), w);
--
--      // 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
++      ++colNext;
++      pixelEnd = getNextPixel(colNext, colNext);
  
--      if (pixelCol < endCol)
++      if (pixelCol == pixelEnd)
        {
--        // System.out.println("OR pc ec " + pixelCol + " " + endCol);
--        int rgb = getColumnColourFromSequence(allGroups, seq, c);
++        break;
++      }
++      else if (pixelCol < pixelEnd)
++      {
++        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 < endCol; ++col)
++          for (int col = pixelCol; col < pixelEnd; ++col)
            {
              // BH 2019.07.27 was:
              //
              ndone++;
            }
          }
--        // }
--
--        pixelCol = endCol;
++        pixelCol = pixelEnd;
          // store last update value
          if (showProgress)
          {
--          lastUpdate = sendProgressUpdate(endCol * (endRow - 1 - pixelRow),
++          lastUpdate = sendProgressUpdate(
++                  pixelEnd * (endRow - 1 - pixelRow),
                    totalPixels, lastRowUpdate, lastUpdate);
          }
        }
++
      }
      if (pixelRow < endRow)
      {
      }
    }
  
++  /**
++   * 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 boolean loop()
++  {
++    if (delay <= 0)
++    {
++      return false;
++    }
++    if (timer == null)
++    {
++      timer = new Timer(delay, new ActionListener()
++      {
++        @Override
++        public void actionPerformed(ActionEvent e)
++        {
++          mainLoop();
++        }
++
++      });
++      timer.setRepeats(false);
++      timer.start();
++    }
++    else
++    {
++      timer.restart();
++    }
++    return true;
++  }
++
    private void done()
    {
      Platform.timeCheck(
                      + redraw,
              Platform.TIME_MARK);
  
--    overlayHiddenRegions(rows, cols);
++    overlayHiddenRegions();
      if (showProgress)
      {
        // final update to progress bar if present
  
    private AlignmentColsCollectionI cols;
  
--  Iterator<Integer> iter;
++  Iterator<Integer> rowIterator;
  
    int alignmentHeight;
  
  
    int[] pixels;
  
--  BitSet bscol = new BitSet();
++  BitSet bscol;
  
    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)
--    {
--      bscol.set(c);
--    }
--    state = STATE_INIT;
--    mainLoop();
--
--    return miniMe;
--  }
--
    /*
     * Calculate progress update value and fire event
     * @param rowOffset number of rows to offset calculation by
    {
      return (seq == null || icol >= seq.getLength()
              ? resColFinder.GAP_COLOUR
--            : resColFinder.getResidueColour(true, shader, allGroups, seq,
--                    icol, finder)).getRGB();
++            : resColFinder.getResidueColourInt(true, shader, allGroups, seq,
++                    icol, finder));
    }
  
    /**
     * 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(AlignmentRowsCollectionI rows,
--          AlignmentColsCollectionI cols)
++  private void overlayHiddenRegions()
    {
      if (cols.hasHidden() || rows.hasHidden())
      {
--      BufferedImage mask = buildHiddenImage(rows, cols, miniMe.getWidth(),
--              miniMe.getHeight());
++      BufferedImage mask = buildHiddenImage();
  
        Graphics2D g = (Graphics2D) miniMe.getGraphics();
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
     *          height of overview in pixels
     * @return BufferedImage containing mask of hidden regions
     */
--  private BufferedImage buildHiddenImage(AlignmentRowsCollectionI rows,
--          AlignmentColsCollectionI cols, int width, int height)
++  private BufferedImage buildHiddenImage()
    {
      // new masking image
--    BufferedImage hiddenImage = new BufferedImage(width, height,
++    BufferedImage hiddenImage = new BufferedImage(w, h,
              BufferedImage.TYPE_INT_ARGB);
  
      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);
      // set next colour to opaque
      g2d.setComposite(AlphaComposite.Src);
  
++    // System.out.println(cols.getClass().getName());
      if (cols.hasHidden())
      {
--      int colIndex = 0;
--      int pixelCol = 0;
--      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)
          {
            break;
          }
--
--        // calculate where this column extends to in pixels
--        int endCol = Math.min(Math.round((++colIndex) * pixelsPerCol),
--                width);
--
--        // endCol is one more than old endCol
--        if (pixelCol < endCol)
++        icol2 = bs.nextClearBit(icol + 1);
++        int pixelEnd = getNextPixel(icol2, 0);
++        if (pixelEnd > pixelCol)
          {
--          // 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 = getNextPixel(icol, 0);
++          g2d.fillRect(pixelCol, 0, Math.max(1, pixelEnd - pixelCol),
++                  h);
++          pixelCol = pixelEnd;
          }
        }
      }
  
          // calculate where this row extends to in pixels
          int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq),
--                height);
++                h);
  
          // get details of this alignment row
          if (rows.isHidden(alignmentRow))
          {
--          g2d.setColor(hidden);
--          g2d.fillRect(0, pixelRow, width, endRow - 1 - pixelRow);
++          g2d.fillRect(0, pixelRow, w, endRow - 1 - pixelRow);
          }
          pixelRow = endRow;
        }
    /**
     * 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(Graphics g, AlignmentAnnotation anno, int y,
--          AlignmentColsCollectionI cols)
++  public void drawGraph(AlignmentAnnotation anno)
    {
++    int y = graphHeight;
++    Graphics g = miniMe.getGraphics();
++    g.translate(0, alignmentHeight);
++
      Annotation[] annotations = anno.annotations;
      float max = anno.graphMax;
      g.setColor(Color.white);
--    int width = miniMe.getWidth();
--    g.fillRect(0, 0, width, y);
++    g.fillRect(0, 0, w, y);
  
--    int colIndex = 0;
--    int pixelCol = 0;
--    for (int icol : cols)
++    for (int pixelCol = 0, colNext = 0, pixelEnd = 0, len = annotations.length, icol = bscol
++            .nextSetBit(0); icol >= 0
++                    && icol < len; icol = getNextCol(icol, pixelEnd))
      {
        if (redraw)
        {
          break;
        }
  
--      if (icol >= annotations.length)
--      {
--        break; // no more annotations to draw here
--      }
--      int endCol = Math.min(Math.round((++colIndex) * pixelsPerCol), width);
++      ++colNext;
++      pixelEnd = getNextPixel(colNext, colNext);
        Annotation ann = annotations[icol];
        if (ann != null)
        {
          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, endCol - pixelCol, height);
++        g.fillRect(pixelCol, y - height, Math.max(1, pixelEnd - pixelCol),
++                height);
        }
--      pixelCol = endCol;
++      pixelCol = pixelEnd;
      }
++
++    g.translate(0, -alignmentHeight);
++    g.dispose();
++
      if (showProgress)
      {
        changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1,
                MAX_PROGRESS);
      }
++
    }
  
    /**
@@@ -22,15 -22,15 +22,16 @@@ package jalview.renderer
  
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
++import jalview.renderer.seqfeatures.FeatureColourFinder;
  import jalview.util.Comparison;
  
  import java.awt.Color;
  
  public class OverviewResColourFinder extends ResidueColourFinder
  {
--  final Color GAP_COLOUR; // default colour to use at gaps
++  final int GAP_COLOUR; // default colour to use at gaps
  
--  final Color RESIDUE_COLOUR; // default colour to use at residues
++  final int RESIDUE_COLOUR; // default colour to use at residues
  
    final Color HIDDEN_COLOUR; // colour for hidden regions
  
    {
      if (useLegacyColouring)
      {
--      GAP_COLOUR = Color.white;
--      RESIDUE_COLOUR = Color.lightGray;
--      HIDDEN_COLOUR = hiddenCol;
++      GAP_COLOUR = Color.white.getRGB();
++      RESIDUE_COLOUR = Color.lightGray.getRGB();
      }
      else
      {
--      GAP_COLOUR = gapCol;
--      RESIDUE_COLOUR = Color.white;
--      HIDDEN_COLOUR = hiddenCol;
++      GAP_COLOUR = gapCol.getRGB();
++      RESIDUE_COLOUR = Color.white.getRGB();
      }
++    HIDDEN_COLOUR = hiddenCol;
    }
  
--  @Override
--  public Color getBoxColour(ResidueShaderI shader, SequenceI seq, int i)
++  public int getBoxColourInt(ResidueShaderI shader, SequenceI seq, int i)
    {
--    seq.resetColors();
--    Color c = seq.getColor(i);
--    if (c != null)
--    {
--      return c;
--    }
      char currentChar = seq.getCharAt(i);
      // In the overview window, gaps are coloured grey, unless the colour scheme
      // specifies a gap colour, in which case gaps honour the colour scheme
      boolean isGap = Comparison.isGap(currentChar);
      if (shader.getColourScheme() == null)
      {
--      return seq.setColor(i, isGap ? GAP_COLOUR : RESIDUE_COLOUR);
++      return (isGap ? GAP_COLOUR : RESIDUE_COLOUR);
      }
--    return seq.setColor(i,
--            isGap && !shader.getColourScheme().hasGapColour() ? GAP_COLOUR
--                    : shader.findColour(currentChar, i, seq));
++    return (isGap && !shader.getColourScheme().hasGapColour() ? GAP_COLOUR
++                    : shader.findColour(currentChar, i, seq).getRGB());
    }
  
++  public int getResidueColourInt(boolean showBoxes, ResidueShaderI shader,
++          SequenceGroup[] allGroups, final SequenceI seq, int i,
++          FeatureColourFinder finder)
++  {
++
++    int c = seq.getColor(i);
++    if (c != 0)
++    {
++      return c;
++    }
++
++    int col = getResidueBoxColourInt(showBoxes, shader, allGroups, seq,
++            i);
++
++
++    // if there's a FeatureColourFinder we might override the residue colour
++    // here with feature colouring
++    return seq.setColor(i,
++            finder == null || finder.noFeaturesDisplayed() ? col
++            : finder.findFeatureColourInt(col, seq, i));
++  }
++  
    /**
--   * {@inheritDoc} In the overview, the showBoxes setting is ignored, as the
--   * overview displays the colours regardless.
++   * In the overview, the showBoxes setting is ignored, as the overview displays
++   * the colours regardless.
     */
--  @Override
--  protected Color getResidueBoxColour(boolean showBoxes,
++  protected int getResidueBoxColourInt(boolean showBoxes,
            ResidueShaderI shader, SequenceGroup[] allGroups, SequenceI seq,
            int i)
    {
              i);
      ResidueShaderI currentShader = (currentSequenceGroup == null ? shader
              : currentSequenceGroup.getGroupColourScheme());
--    return getBoxColour(currentShader, seq, i);
++    return getBoxColourInt(currentShader, seq, i);
    }
  
    /**
@@@ -116,12 -116,12 +116,13 @@@ public class ResidueColourFinde
    public SequenceGroup getCurrentSequenceGroup(SequenceGroup[] allGroups,
            int res)
    {
--    if (allGroups == null)
++    int n;
++    if (allGroups == null || (n = allGroups.length) == 0)
      {
        return null;
      }
  
--    for (int i = 0; i < allGroups.length; i++)
++    for (int i = 0; i < n; i++)
      {
        if ((allGroups[i].getStartRes() <= res)
                && (allGroups[i].getEndRes() >= res))
@@@ -117,13 -117,13 +117,48 @@@ public class FeatureColourFinde
      return c;
    }
  
++  public int findFeatureColourInt(int defaultColour, SequenceI seq,
++          int column)
++  {
++    // if (noFeaturesDisplayed())
++    // {
++    // return defaultColour;
++    // }
++
++    Graphics g = null;
++
++    /*
++     * if transparency applies, provide a notional 1x1 graphics context 
++     * that has been primed with the default colour
++     */
++    if (featureRenderer.getTransparency() != 1f)
++    {
++      g = goff;
++      if (defaultColour != 0)
++      {
++        offscreenImage.setRGB(0, 0, defaultColour);
++      }
++    }
++
++    Color c = featureRenderer.findFeatureColour(seq, column + 1, g);
++    if (c == null)
++    {
++      return defaultColour;
++    }
++
++    if (g != null)
++    {
++      return offscreenImage.getRGB(0, 0);
++    }
++    return c.getRGB();
++  }
    /**
     * Answers true if feature display is turned off, or there are no features
     * configured to be visible
     * 
     * @return
     */
--  boolean noFeaturesDisplayed()
++  public boolean noFeaturesDisplayed()
    {
      if (featureRenderer == null
              || !featureRenderer.getViewport().isShowSequenceFeatures())
@@@ -304,10 -304,10 +304,10 @@@ public abstract class OverviewDimension
      boxY = Math.round(vpbox.y / heightRatio);
  
      // boxWidth is the width in residues translated to pixels
--    boxWidth = Math.round(vpbox.width / widthRatio);
++    boxWidth = Math.max(1, Math.round(vpbox.width / widthRatio));
  
      // boxHeight is the height in sequences translated to pixels
--    boxHeight = Math.round(vpbox.height / heightRatio);
++    boxHeight = Math.max(1, Math.round(vpbox.height / heightRatio));
    }
  
    /**
@@@ -266,11 -266,11 +266,14 @@@ public abstract class FeatureRendererMo
  
    boolean findingFeatures = false;
  
++  int nup;
++
    protected boolean updateFeatures()
    {
      if (av.getFeaturesDisplayed() == null || renderOrder == null
              || newFeatureAdded)
      {
++      System.out.println("updateFeatures " + ++nup);
        findAllFeatures();
        if (av.getFeaturesDisplayed().getVisibleFeatureCount() < 1)
        {