JAL-3383 JAL-3253-applet
[jalview.git] / src / jalview / renderer / seqfeatures / FeatureRenderer.java
index cd2a90a..9988076 100644 (file)
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.renderer.seqfeatures;
 
+import jalview.api.AlignViewportI;
+import jalview.api.FeatureColourI;
+import jalview.datamodel.ContiguousI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.util.Comparison;
+import jalview.util.Platform;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 
 import java.awt.AlphaComposite;
 import java.awt.Color;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
 
-public class FeatureRenderer extends
-        jalview.viewmodel.seqfeatures.FeatureRendererModel
+public class FeatureRenderer extends FeatureRendererModel
 {
+  private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
+          .getInstance(AlphaComposite.SRC_OVER, 1.0f);
 
-  FontMetrics fm;
-
-  int charOffset;
-
-  boolean offscreenRender = false;
+  /**
+   * Constructor given a viewport
+   * 
+   * @param viewport
+   */
+  public FeatureRenderer(AlignViewportI viewport)
+  {
+    this.av = viewport;
+  }
 
   /**
-   * DOCUMENT ME!
+   * Renders the sequence using the given feature colour between the given start
+   * and end columns. Returns true if at least one column is drawn, else false
+   * (the feature range does not overlap the start and end positions).
    * 
    * @param g
-   *          DOCUMENT ME!
    * @param seq
-   *          DOCUMENT ME!
-   * @param sg
-   *          DOCUMENT ME!
+   * @param featureStart
+   * @param featureEnd
+   * @param featureColour
    * @param start
-   *          DOCUMENT ME!
    * @param end
-   *          DOCUMENT ME!
-   * @param x1
-   *          DOCUMENT ME!
    * @param y1
-   *          DOCUMENT ME!
-   * @param width
-   *          DOCUMENT ME!
-   * @param height
-   *          DOCUMENT ME!
+   * @param colourOnly
+   * @return
    */
-  protected SequenceI lastSeq;
-
-  char s;
-
-  int i;
-
-  int av_charHeight, av_charWidth;
-
-  boolean av_validCharWidth, av_isShowSeqFeatureHeight;
-
-  protected void updateAvConfig()
+  boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
+          int featureEnd, Color featureColour, int start, int end, int y1,
+          boolean colourOnly)
   {
-    av_charHeight = av.getCharHeight();
-    av_charWidth = av.getCharWidth();
-    av_validCharWidth = av.isValidCharWidth();
-    av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight();
-  }
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+    boolean validCharWidth = av.isValidCharWidth();
 
-  void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
-          Color featureColour, int start, int end, int y1)
-  {
-    updateAvConfig();
-    if (((fstart <= end) && (fend >= start)))
+    if (featureStart > end || featureEnd < start)
     {
-      if (fstart < start)
-      { // fix for if the feature we have starts before the sequence start,
-        fstart = start; // but the feature end is still valid!!
-      }
-
-      if (fend >= end)
-      {
-        fend = end;
-      }
-      int pady = (y1 + av_charHeight) - av_charHeight / 5;
-      for (i = fstart; i <= fend; i++)
-      {
-        s = seq.getCharAt(i);
-
-        if (jalview.util.Comparison.isGap(s))
-        {
-          continue;
-        }
-
-        g.setColor(featureColour);
-
-        g.fillRect((i - start) * av_charWidth, y1, av_charWidth,
-                av_charHeight);
-
-        if (offscreenRender || !av_validCharWidth)
-        {
-          continue;
-        }
-
-        g.setColor(Color.white);
-        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
-        g.drawString(String.valueOf(s), charOffset
-                + (av_charWidth * (i - start)), pady);
+      return false;
+    }
 
-      }
+    if (featureStart < start)
+    {
+      featureStart = start;
     }
-  }
+    if (featureEnd >= end)
+    {
+      featureEnd = end;
+    }
+    int pady = (y1 + charHeight) - charHeight / 5;
 
-  void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
-          Color featureColour, int start, int end, int y1, byte[] bs)
-  {
-    updateAvConfig();
-    if (((fstart <= end) && (fend >= start)))
+    FontMetrics fm = g.getFontMetrics();
+    char s = '\0';
+    for (int i = featureStart; i <= featureEnd; i++)
     {
-      if (fstart < start)
-      { // fix for if the feature we have starts before the sequence start,
-        fstart = start; // but the feature end is still valid!!
-      }
 
-      if (fend >= end)
-      {
-        fend = end;
-      }
-      int pady = (y1 + av_charHeight) - av_charHeight / 5;
-      int ystrt = 0, yend = av_charHeight;
-      if (bs[0] != 0)
-      {
-        // signed - zero is always middle of residue line.
-        if (bs[1] < 128)
-        {
-          yend = av_charHeight * (128 - bs[1]) / 512;
-          ystrt = av_charHeight - yend / 2;
-        }
-        else
-        {
-          ystrt = av_charHeight / 2;
-          yend = av_charHeight * (bs[1] - 128) / 512;
-        }
-      }
-      else
-      {
-        yend = av_charHeight * bs[1] / 255;
-        ystrt = av_charHeight - yend;
+      // colourOnly is just for Overview -- no need to check this again
 
-      }
-      for (i = fstart; i <= fend; i++)
+      if (!colourOnly && Comparison.isGap(s = seq.getCharAt(i)))
       {
-        s = seq.getCharAt(i);
-
-        if (jalview.util.Comparison.isGap(s))
-        {
-          continue;
-        }
-
-        g.setColor(featureColour);
-        int x = (i - start) * av_charWidth;
-        g.drawRect(x, y1, av_charWidth, av_charHeight);
-        g.fillRect(x, y1 + ystrt, av_charWidth, yend);
+        continue;
+      }
 
-        if (offscreenRender || !av_validCharWidth)
-        {
-          continue;
-        }
+      g.setColor(featureColour);
 
-        g.setColor(Color.black);
-        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
-        g.drawString(String.valueOf(s), charOffset
-                + (av_charWidth * (i - start)), pady);
+      g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
 
+      if (colourOnly)
+      {
+        return true;
       }
-    }
-  }
 
-  BufferedImage offscreenImage;
+      if (!validCharWidth)
+      {
+        continue;
+      }
 
-  public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
-  {
-    return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
+      g.setColor(Color.white);
+      int charOffset = (charWidth - fm.charWidth(s)) / 2;
+      g.drawString(String.valueOf(s),
+              charOffset + (charWidth * (i - start)), pady);
+    }
+    return true;
   }
 
   /**
-   * This is used by the Molecule Viewer and Overview to get the accurate
-   * colourof the rendered sequence
+   * 
+   * BH - this method is never called?
+   * 
+   * Renders the sequence using the given SCORE feature colour between the given
+   * start and end columns. Returns true if at least one column is drawn, else
+   * false (the feature range does not overlap the start and end positions).
+   * 
+   * @param g
+   * @param seq
+   * @param fstart
+   * @param fend
+   * @param featureColour
+   * @param start
+   * @param end
+   * @param y1
+   * @param bs
+   * @param colourOnly
+   * @return
    */
-  public synchronized int findFeatureColour(int initialCol, final SequenceI seq,
-          int column)
+  boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
+          int fend, Color featureColour, int start, int end, int y1,
+          byte[] bs, boolean colourOnly)
   {
-    if (!av.isShowSequenceFeatures())
+    if (fstart > end || fend < start)
     {
-      return initialCol;
+      return false;
+    }
+
+    if (fstart < start)
+    { // fix for if the feature we have starts before the sequence start,
+      fstart = start; // but the feature end is still valid!!
     }
 
-    final SequenceI aseq = (seq.getDatasetSequence() != null) ? seq
-            .getDatasetSequence() : seq;
-    if (seq != lastSeq)
+    if (fend >= end)
     {
-      lastSeq = seq;
-      sequenceFeatures = aseq.getSequenceFeatures();
-      if (sequenceFeatures != null)
-      {
-        sfSize = sequenceFeatures.length;
-      }
+      fend = end;
     }
-    else
+    int charHeight = av.getCharHeight();
+    int pady = (y1 + charHeight) - charHeight / 5;
+    int ystrt = 0, yend = charHeight;
+    if (bs[0] != 0)
     {
-      if (sequenceFeatures != aseq.getSequenceFeatures())
+      // signed - zero is always middle of residue line.
+      if (bs[1] < 128)
       {
-        sequenceFeatures = aseq.getSequenceFeatures();
-        if (sequenceFeatures != null)
-        {
-          sfSize = sequenceFeatures.length;
-        }
+        yend = charHeight * (128 - bs[1]) / 512;
+        ystrt = charHeight - yend / 2;
+      }
+      else
+      {
+        ystrt = charHeight / 2;
+        yend = charHeight * (bs[1] - 128) / 512;
       }
     }
-
-    if (sequenceFeatures == null || sfSize == 0)
-    {
-      return initialCol;
-    }
-
-    if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
+    else
     {
-      return Color.white.getRGB();
-    }
+      yend = charHeight * bs[1] / 255;
+      ystrt = charHeight - yend;
 
-    // Only bother making an offscreen image if transparency is applied
-    if (transparency != 1.0f && offscreenImage == null)
-    {
-      offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
     }
 
-    currentColour = null;
-    // TODO: non-threadsafe - each rendering thread needs its own instance of
-    // the feature renderer - or this should be synchronized.
-    offscreenRender = true;
-
-    if (offscreenImage != null)
-    {
-      offscreenImage.setRGB(0, 0, initialCol);
-      drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
+    FontMetrics fm = g.getFontMetrics();
+    int charWidth = av.getCharWidth();
 
-      return offscreenImage.getRGB(0, 0);
-    }
-    else
+    for (int i = fstart; i <= fend; i++)
     {
-      drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
+      char s = seq.getCharAt(i);
 
-      if (currentColour == null)
+      if (Comparison.isGap(s))
       {
-        return initialCol;
+        continue;
       }
-      else
+
+      g.setColor(featureColour);
+      int x = (i - start) * charWidth;
+      g.drawRect(x, y1, charWidth, charHeight);
+      g.fillRect(x, y1 + ystrt, charWidth, yend);
+
+      if (colourOnly || !av.isValidCharWidth())
       {
-        return ((Integer) currentColour).intValue();
+        continue;
       }
-    }
 
+      g.setColor(Color.black);
+      int charOffset = (charWidth - fm.charWidth(s)) / 2;
+      g.drawString(String.valueOf(s),
+              charOffset + (charWidth * (i - start)), pady);
+    }
+    return true;
   }
 
-  private volatile SequenceFeature[] sequenceFeatures;
-
-  int sfSize;
-
-  int sfindex;
-
-  int spos;
-
-  int epos;
-
-  public synchronized void drawSequence(Graphics g, final SequenceI seq,
-          int start, int end, int y1)
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Color findFeatureColour(SequenceI seq, int column, Graphics g)
   {
-    final SequenceI aseq = (seq.getDatasetSequence() != null) ? seq
-            .getDatasetSequence() : seq;
-    if (aseq.getSequenceFeatures() == null
-            || aseq.getSequenceFeatures().length == 0)
+    // BH 2019.08.01
+    // this is already checked in FeatureColorFinder
+    // if (!av.isShowSequenceFeatures())
+    // {
+    // return null;
+    // }
+
+    // column is 'base 1' but getCharAt is an array index (ie from 0)
+    if (Comparison.isGap(seq.getCharAt(column - 1)))
     {
-      return;
+      /*
+       * returning null allows the colour scheme to provide gap colour
+       * - normally white, but can be customised
+       */
+      return null;
     }
 
-    if (g != null)
+    Color renderedColour = null;
+    if (transparency == 1.0f)
     {
-      fm = g.getFontMetrics();
+      /*
+       * simple case - just find the topmost rendered visible feature colour
+       */
+      renderedColour = findFeatureColour(seq, column);
     }
-
-    updateFeatures();
-
-    if (lastSeq == null || seq != lastSeq
-            || aseq.getSequenceFeatures() != sequenceFeatures)
+    else
     {
-      lastSeq = seq;
-      sequenceFeatures = aseq.getSequenceFeatures();
+      /*
+       * transparency case - draw all visible features in render order to
+       * build up a composite colour on the graphics context
+       */
+      renderedColour = drawSequence(g, seq, column, column, 0, true);
     }
+    return renderedColour;
+  }
 
-    if (transparency != 1 && g != null)
+  /**
+   * Draws the sequence features on the graphics context, or just determines the
+   * colour that would be drawn (if flag colourOnly is true). Returns the last
+   * colour drawn (which may not be the effective colour if transparency
+   * applies), or null if no feature is drawn in the range given.
+   * 
+   * @param g
+   *          the graphics context to draw on (may be null only if t == 1 from
+   *          colourOnly==true)
+   * @param seq
+   * @param start
+   *          start column
+   * @param end
+   *          end column
+   * @param y1
+   *          vertical offset at which to draw on the graphics
+   * @param colourOnly
+   *          if true, only do enough to determine the colour for the position,
+   *          do not draw the character
+   * @return
+   */
+  public synchronized Color drawSequence(final Graphics g,
+          final SequenceI seq, int start, int end, int y1,
+          boolean colourOnly)
+  {
+    // from SeqCanvas and OverviewRender
+    /*
+     * if columns are all gapped, or sequence has no features, nothing to do
+     */
+    ContiguousI visiblePositions;
+    if (!seq.getFeatures().hasFeatures() || (visiblePositions = seq
+            .findPositions(start + 1, end + 1)) == null)
     {
-      Graphics2D g2 = (Graphics2D) g;
-      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
-              transparency));
+      return null;
     }
 
-    if (!offscreenRender)
+    int vp0 = visiblePositions.getBegin();
+    int vp1 = visiblePositions.getEnd();
+
+    updateFeatures();
+
+    if (transparency != 1f) // g cannot be null here if trans == 1f - BH // && g
+                            // != null)
     {
-      spos = lastSeq.findPosition(start);
-      epos = lastSeq.findPosition(end);
+      ((Graphics2D) g).setComposite(
+              AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+              transparency));
     }
 
-    sfSize = sequenceFeatures.length;
-    String type;
-    for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
-    {
-      type = renderOrder[renderIndex];
+    Color drawnColour = null;
 
-      if (type == null || !showFeatureOfType(type))
+    /*
+     * iterate over features in ordering of their rendering (last is on top)
+     */
+    for (int renderIndex = 0, n = renderOrder.length; renderIndex < n; renderIndex++)
+    {
+      String type = renderOrder[renderIndex];
+      if (!seq.hasFeatures(type) || !showFeatureOfType(type))
       {
         continue;
       }
 
-      // loop through all features in sequence to find
-      // current feature to render
-      for (sfindex = 0; sfindex < sfSize; sfindex++)
+      FeatureColourI fc = getFeatureStyle(type);
+      List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(vp0,
+              vp1, type);
+
+      // colourOnly (i.e. Overview) can only be here if translucent, so
+      // there is no need to check for filtering
+      if (!colourOnly && fc.isSimpleColour())
+      {
+        filterFeaturesForDisplay(overlaps);
+      }
+
+      for (int i = overlaps.size(); --i >= 0;)
       {
-        if (!sequenceFeatures[sfindex].type.equals(type))
+        SequenceFeature sf = overlaps.get(i);
+        Color featureColour = getColor(sf, fc);
+        if (featureColour == null)
         {
+          /*
+           * feature excluded by visibility settings, filters, or colour threshold
+           */
           continue;
         }
 
-        if (featureGroups != null
-                && sequenceFeatures[sfindex].featureGroup != null
-                && sequenceFeatures[sfindex].featureGroup.length() != 0
-                && featureGroups
-                        .containsKey(sequenceFeatures[sfindex].featureGroup)
-                && !((Boolean) featureGroups
-                        .get(sequenceFeatures[sfindex].featureGroup))
-                        .booleanValue())
+        /*
+         * if feature starts/ends outside the visible range,
+         * restrict to visible positions (or if a contact feature,
+         * to a single position)
+         */
+        int sf0 = sf.getBegin();
+        int sf1 = sf.getEnd();
+        int visibleStart = sf0;
+        if (visibleStart < vp0)
         {
-          continue;
+          visibleStart = sf.isContactFeature() ? sf1 : vp0;
         }
-
-        if (!offscreenRender
-                && (sequenceFeatures[sfindex].getBegin() > epos || sequenceFeatures[sfindex]
-                        .getEnd() < spos))
+        int visibleEnd = sf1;
+        if (visibleEnd > vp1)
         {
-          continue;
+          visibleEnd = sf.isContactFeature() ? sf0 : vp1;
         }
 
-        if (offscreenRender && offscreenImage == null)
-        {
-          if (sequenceFeatures[sfindex].begin <= start
-                  && sequenceFeatures[sfindex].end >= start)
-          {
-            // this is passed out to the overview and other sequence renderers
-            // (e.g. molecule viewer) to get displayed colour for rendered
-            // sequence
-            currentColour = new Integer(
-                    getColour(sequenceFeatures[sfindex]).getRGB());
-            // used to be retreived from av.featuresDisplayed
-            // currentColour = av.featuresDisplayed
-            // .get(sequenceFeatures[sfindex].type);
+        int featureStartCol = seq.findIndex(visibleStart);
+        int featureEndCol = (sf.begin == sf.end ? featureStartCol
+                : seq.findIndex(visibleEnd));
 
-          }
-        }
-        else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
-        {
+        // Color featureColour = getColour(sequenceFeature);
 
-          renderFeature(g, seq,
-                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                  getColour(sequenceFeatures[sfindex])
-                  // new Color(((Integer) av.featuresDisplayed
-                  // .get(sequenceFeatures[sfindex].type)).intValue())
-                  , start, end, y1);
-          renderFeature(g, seq,
-                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                  getColour(sequenceFeatures[sfindex])
-                  // new Color(((Integer) av.featuresDisplayed
-                  // .get(sequenceFeatures[sfindex].type)).intValue())
-                  , start, end, y1);
+        boolean isContactFeature = sf.isContactFeature();
 
+        if (isContactFeature)
+        {
+          boolean drawn = renderFeature(g, seq, featureStartCol - 1,
+                  featureStartCol - 1, featureColour, start, end, y1,
+                  colourOnly);
+          drawn |= renderFeature(g, seq, featureEndCol - 1,
+                  featureEndCol - 1, featureColour, start, end, y1,
+                  colourOnly);
+          if (drawn)
+          {
+            drawnColour = featureColour;
+          }
         }
-        else if (showFeature(sequenceFeatures[sfindex]))
+        else
         {
-          if (av_isShowSeqFeatureHeight
-                  && sequenceFeatures[sfindex].score != Float.NaN)
+          /*
+           * showing feature score by height of colour
+           * is not implemented as a selectable option 
+           *
+          if (av.isShowSequenceFeaturesHeight()
+                  && !Float.isNaN(sequenceFeature.score))
           {
-            renderScoreFeature(g, seq,
-                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                    getColour(sequenceFeatures[sfindex]), start, end, y1,
-                    normaliseScore(sequenceFeatures[sfindex]));
+            boolean drawn = renderScoreFeature(g, seq,
+                    seq.findIndex(sequenceFeature.begin) - 1,
+                    seq.findIndex(sequenceFeature.end) - 1, featureColour,
+                    start, end, y1, normaliseScore(sequenceFeature),
+                    colourOnly);
+            if (drawn)
+            {
+              drawnColour = featureColour;
+            }
           }
           else
           {
-            renderFeature(g, seq,
-                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                    getColour(sequenceFeatures[sfindex]), start, end, y1);
+          */
+          boolean drawn = renderFeature(g, seq, featureStartCol - 1,
+                  featureEndCol - 1, featureColour, start, end, y1,
+                  colourOnly);
+          if (drawn)
+          {
+            drawnColour = featureColour;
           }
+          /*}*/
         }
-
       }
-
     }
 
-    if (transparency != 1.0f && g != null && transparencyAvailable)
+    if (transparency != 1.0f)
     {
-      Graphics2D g2 = (Graphics2D) g;
-      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
-              1.0f));
+      /*
+       * reset transparency
+       */
+      ((Graphics2D) g).setComposite(NO_TRANSPARENCY);
     }
-  }
-
-  boolean transparencyAvailable = true;
-
-  protected void setTransparencyAvailable(boolean isTransparencyAvailable)
-  {
-    transparencyAvailable = isTransparencyAvailable;
-  }
 
-  @Override
-  public boolean isTransparencyAvailable()
-  {
-    return transparencyAvailable;
+    return drawnColour;
   }
 
   /**
@@ -427,9 +433,83 @@ public class FeatureRenderer extends
    * discover and display.
    * 
    */
+  @Override
   public void featuresAdded()
   {
-    lastSeq = null;
     findAllFeatures();
   }
+
+  private List<SequenceFeature> overlaps = (Platform.isJS()
+          ? new ArrayList<>()
+          : null);
+
+  /**
+   * Returns the sequence feature colour rendered at the given column position,
+   * or null if none found. The feature of highest render order (i.e. on top) is
+   * found, subject to both feature type and feature group being visible, and
+   * its colour returned. This method is suitable when no feature transparency
+   * applied (only the topmost visible feature colour is rendered).
+   * <p>
+   * Note this method does not check for a gap in the column so would return the
+   * colour for features enclosing a gapped column. Check for gap before calling
+   * if different behaviour is wanted.
+   * 
+   * BH 2019.07.30
+   * 
+   * Adds a result ArrayList to parameters in order to avoid an unnecessary
+   * construction of that for every pixel checked.
+   * 
+   * 
+   * @param seq
+   * @param column
+   *          (1..)
+   * @return
+   */
+  private Color findFeatureColour(SequenceI seq, int column)
+  {
+    /*
+     * check for new feature added while processing
+     */
+    updateFeatures();
+
+    /*
+     * inspect features in reverse renderOrder (the last in the array is 
+     * displayed on top) until we find one that is rendered at the position
+     */
+    for (int renderIndex = renderOrder.length; --renderIndex >= 0;)
+    {
+      String type = renderOrder[renderIndex];
+      if (!seq.hasFeatures(type) || !showFeatureOfType(type))
+      {
+        continue;
+      }
+
+      if (overlaps != null)
+      {
+        overlaps.clear();
+      }
+      List<SequenceFeature> list = seq.findFeatures(column, type, overlaps);
+      if (list.size() > 0)
+      {
+        for (int i = 0, n = list.size(); i < n; i++)
+        {
+          SequenceFeature sf = list.get(i);
+          if (featureGroupNotShown(sf))
+          {
+            continue;
+          }
+          Color col = getColour(sf);
+          if (col != null)
+          {
+            return col;
+          }
+        }
+      }
+    }
+
+    /*
+     * no displayed feature found at position
+     */
+    return null;
+  }
 }