X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Frenderer%2Fseqfeatures%2FFeatureRenderer.java;h=9988076f7fbc5821164145e182003d6b9907863a;hb=5b833c4ea17f7e45fe1532b36219fcbfba3c0e4a;hp=d05d15833f42060c8e414042cee1cd6fde6604cf;hpb=17e77c3f2949a0729322b4a8d907f3f34b6a9914;p=jalview.git diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index d05d158..9988076 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -1,6 +1,6 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9) - * Copyright (C) 2015 The Jalview Authors + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * @@ -20,394 +20,412 @@ */ 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; - - protected SequenceI lastSeq; - - char s; - - int i; - - int av_charHeight, av_charWidth; - - boolean av_validCharWidth, av_isShowSeqFeatureHeight; - - protected void updateAvConfig() + /** + * Constructor given a viewport + * + * @param viewport + */ + public FeatureRenderer(AlignViewportI viewport) { - av_charHeight = av.getCharHeight(); - av_charWidth = av.getCharWidth(); - av_validCharWidth = av.isValidCharWidth(); - av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight(); + this.av = viewport; } - void renderFeature(Graphics g, SequenceI seq, int fstart, int fend, - Color featureColour, int start, int end, int y1) + /** + * 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 + * @param seq + * @param featureStart + * @param featureEnd + * @param featureColour + * @param start + * @param end + * @param y1 + * @param colourOnly + * @return + */ + boolean renderFeature(Graphics g, SequenceI seq, int featureStart, + int featureEnd, Color featureColour, int start, int end, int y1, + boolean colourOnly) { - updateAvConfig(); - if (((fstart <= end) && (fend >= 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!! - } + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + boolean validCharWidth = av.isValidCharWidth(); - 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); + if (featureStart > end || featureEnd < start) + { + 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; - } + 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); + g.setColor(featureColour); - if (offscreenRender || !av_validCharWidth) - { - continue; - } + g.fillRect((i - start) * charWidth, y1, charWidth, charHeight); - g.setColor(Color.black); - charOffset = (av_charWidth - fm.charWidth(s)) / 2; - g.drawString(String.valueOf(s), charOffset - + (av_charWidth * (i - start)), pady); + 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!! } - SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures(); - if (seq != lastSeq) + if (fend >= end) { - lastSeq = seq; - lastSequenceFeatures = sequenceFeatures; - if (lastSequenceFeatures != null) - { - sfSize = lastSequenceFeatures.length; - } + fend = end; } - else + int charHeight = av.getCharHeight(); + int pady = (y1 + charHeight) - charHeight / 5; + int ystrt = 0, yend = charHeight; + if (bs[0] != 0) { - if (lastSequenceFeatures != sequenceFeatures) + // signed - zero is always middle of residue line. + if (bs[1] < 128) { - lastSequenceFeatures = sequenceFeatures; - if (lastSequenceFeatures != null) - { - sfSize = lastSequenceFeatures.length; - } + yend = charHeight * (128 - bs[1]) / 512; + ystrt = charHeight - yend / 2; + } + else + { + ystrt = charHeight / 2; + yend = charHeight * (bs[1] - 128) / 512; } } - - if (lastSequenceFeatures == 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[] lastSequenceFeatures; - - 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) { - SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures(); - if (sequenceFeatures == null || sequenceFeatures.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 - || sequenceFeatures != lastSequenceFeatures) + else { - lastSeq = seq; - lastSequenceFeatures = sequenceFeatures; + /* + * 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 = lastSequenceFeatures.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 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;) { - final SequenceFeature sequenceFeature = lastSequenceFeatures[sfindex]; - if (!sequenceFeature.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 - && sequenceFeature.featureGroup != null - && sequenceFeature.featureGroup.length() != 0 - && featureGroups.containsKey(sequenceFeature.featureGroup) - && !featureGroups.get(sequenceFeature.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 - && (sequenceFeature.getBegin() > epos || sequenceFeature - .getEnd() < spos)) + int visibleEnd = sf1; + if (visibleEnd > vp1) { - continue; + visibleEnd = sf.isContactFeature() ? sf0 : vp1; } - if (offscreenRender && offscreenImage == null) + int featureStartCol = seq.findIndex(visibleStart); + int featureEndCol = (sf.begin == sf.end ? featureStartCol + : seq.findIndex(visibleEnd)); + + // Color featureColour = getColour(sequenceFeature); + + boolean isContactFeature = sf.isContactFeature(); + + if (isContactFeature) { - if (sequenceFeature.begin <= start - && sequenceFeature.end >= start) + 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) { - // 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(sequenceFeature).getRGB()); - // used to be retreived from av.featuresDisplayed - // currentColour = av.featuresDisplayed - // .get(sequenceFeatures[sfindex].type); - + drawnColour = featureColour; } } - else if (sequenceFeature.type.equals("disulfide bond")) - { - renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1, - seq.findIndex(sequenceFeature.begin) - 1, - getColour(sequenceFeature) - // new Color(((Integer) av.featuresDisplayed - // .get(sequenceFeatures[sfindex].type)).intValue()) - , start, end, y1); - renderFeature(g, seq, seq.findIndex(sequenceFeature.end) - 1, - seq.findIndex(sequenceFeature.end) - 1, - getColour(sequenceFeature) - // new Color(((Integer) av.featuresDisplayed - // .get(sequenceFeatures[sfindex].type)).intValue()) - , start, end, y1); - - } - else if (showFeature(sequenceFeature)) + else { - if (av_isShowSeqFeatureHeight + /* + * showing feature score by height of colour + * is not implemented as a selectable option + * + if (av.isShowSequenceFeaturesHeight() && !Float.isNaN(sequenceFeature.score)) { - renderScoreFeature(g, seq, + boolean drawn = renderScoreFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1, - seq.findIndex(sequenceFeature.end) - 1, - getColour(sequenceFeature), start, end, y1, - normaliseScore(sequenceFeature)); + seq.findIndex(sequenceFeature.end) - 1, featureColour, + start, end, y1, normaliseScore(sequenceFeature), + colourOnly); + if (drawn) + { + drawnColour = featureColour; + } } else { - renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1, - seq.findIndex(sequenceFeature.end) - 1, - getColour(sequenceFeature), 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; } /** @@ -415,9 +433,83 @@ public class FeatureRenderer extends * discover and display. * */ + @Override public void featuresAdded() { - lastSeq = null; findAllFeatures(); } + + private List 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). + *

+ * 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 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; + } }