X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Frenderer%2FOverviewRenderer.java;h=c9096e290e5a417d8ee813168744477ce6e02ee5;hb=26501f76ef450657c530de489b8404356a98aca3;hp=60f80b128dd9636fb5d9ca93f1d8b8e251509865;hpb=e0aee2e0eb48c09a557a61fece6c0f4fdcae110b;p=jalview.git diff --git a/src/jalview/renderer/OverviewRenderer.java b/src/jalview/renderer/OverviewRenderer.java index 60f80b1..c9096e2 100644 --- a/src/jalview/renderer/OverviewRenderer.java +++ b/src/jalview/renderer/OverviewRenderer.java @@ -22,22 +22,36 @@ package jalview.renderer; import jalview.api.AlignmentColsCollectionI; import jalview.api.AlignmentRowsCollectionI; +import jalview.api.RendererListenerI; import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; import jalview.datamodel.Annotation; +import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.renderer.seqfeatures.FeatureRenderer; import jalview.viewmodel.OverviewDimensions; +import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.image.BufferedImage; +import java.beans.PropertyChangeSupport; public class OverviewRenderer { - private FeatureColourFinder finder; + // transparency of hidden cols/seqs overlay + private final float TRANSPARENCY = 0.5f; + + public static final String UPDATE = "OverviewUpdate"; + + private static final int MAX_PROGRESS = 100; + + private PropertyChangeSupport changeSupport = new PropertyChangeSupport( + this); - private jalview.api.SequenceRenderer sr; + private FeatureColourFinder finder; // image to render on private BufferedImage miniMe; @@ -48,14 +62,32 @@ public class OverviewRenderer // raw number of pixels to allocate to each row private float pixelsPerSeq; - public OverviewRenderer(jalview.api.SequenceRenderer seqRenderer, - FeatureRenderer ftRenderer, OverviewDimensions od) + // height in pixels of graph + private int graphHeight; + + // flag to indicate whether to halt drawing + private volatile boolean redraw = false; + + // reference to alignment, needed to get sequence groups + private AlignmentI al; + + private ResidueShaderI shader; + + private OverviewResColourFinder resColFinder; + + public OverviewRenderer(FeatureRenderer fr, OverviewDimensions od, + AlignmentI alignment, + ResidueShaderI resshader, OverviewResColourFinder colFinder) { - sr = seqRenderer; - finder = new FeatureColourFinder(ftRenderer); + finder = new FeatureColourFinder(fr); + resColFinder = colFinder; + + al = alignment; + shader = resshader; pixelsPerCol = od.getPixelsPerCol(); pixelsPerSeq = od.getPixelsPerSeq(); + graphHeight = od.getGraphHeight(); miniMe = new BufferedImage(od.getWidth(), od.getHeight(), BufferedImage.TYPE_INT_RGB); } @@ -75,66 +107,243 @@ public class OverviewRenderer int rgbcolor = Color.white.getRGB(); int seqIndex = 0; int pixelRow = 0; + int alignmentHeight = miniMe.getHeight() - graphHeight; + int totalPixels = miniMe.getWidth() * alignmentHeight; + + int lastRowUpdate = 0; + int lastUpdate = 0; + changeSupport.firePropertyChange(UPDATE, -1, 0); + for (int alignmentRow : rows) { + if (redraw) + { + break; + } + // get details of this alignment row - boolean hidden = rows.isHidden(alignmentRow); SequenceI seq = rows.getSequence(alignmentRow); + // rate limiting step when rendering overview for lots of groups + SequenceGroup[] allGroups = al.findAllGroups(seq); + // calculate where this row extends to in pixels int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1, miniMe.getHeight() - 1); - + int colIndex = 0; int pixelCol = 0; for (int alignmentCol : cols) { + if (redraw) + { + break; + } + // calculate where this column extends to in pixels - int endCol = Math.min( - Math.round((colIndex + 1) * pixelsPerCol) - 1, + int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1, miniMe.getWidth() - 1); - - // determine the colour based on the sequence and column position - rgbcolor = getColumnColourFromSequence(seq, - hidden || cols.isHidden(alignmentCol), alignmentCol, finder); - - // fill in the appropriate number of pixels - for (int row = pixelRow; row <= endRow; ++row) + + // don't do expensive colour determination if we're not going to use it + // NB this is important to avoid performance issues in the overview + // panel + if (pixelCol <= endCol) { - for (int col = pixelCol; col <= endCol; ++col) + rgbcolor = getColumnColourFromSequence(allGroups, seq, + alignmentCol); + + // fill in the appropriate number of pixels + for (int row = pixelRow; row <= endRow; ++row) { - miniMe.setRGB(col, row, rgbcolor); + for (int col = pixelCol; col <= endCol; ++col) + { + miniMe.setRGB(col, row, rgbcolor); + } } - } - pixelCol = endCol + 1; + // store last update value + lastUpdate = sendProgressUpdate( + (pixelCol + 1) * (endRow - pixelRow), totalPixels, + lastRowUpdate, lastUpdate); + + pixelCol = endCol + 1; + } colIndex++; } - pixelRow = endRow + 1; + + if (pixelRow != endRow + 1) + { + // store row offset and last update value + lastRowUpdate = sendProgressUpdate(endRow + 1, alignmentHeight, 0, + lastUpdate); + lastUpdate = lastRowUpdate; + pixelRow = endRow + 1; + } seqIndex++; } + + overlayHiddenRegions(rows, cols); + // final update to progress bar if present + if (redraw) + { + sendProgressUpdate(pixelRow - 1, alignmentHeight, 0, 0); + } + else + { + sendProgressUpdate(alignmentHeight, miniMe.getHeight(), 0, 0); + } return miniMe; } /* - * Find the colour of a sequence at a specified column position + * Calculate progress update value and fire event + * @param rowOffset number of rows to offset calculation by + * @return new rowOffset - return value only to be used when at end of a row */ - private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq, - boolean isHidden, int lastcol, FeatureColourFinder fcfinder) + private int sendProgressUpdate(int position, int maximum, int rowOffset, + int lastUpdate) { - Color color = Color.white; + int newUpdate = rowOffset + + Math.round(MAX_PROGRESS * ((float) position / maximum)); + if (newUpdate > lastUpdate) + { + changeSupport.firePropertyChange(UPDATE, rowOffset, newUpdate); + return newUpdate; + } + return newUpdate; + } + + /* + * Find the RGB value of the colour of a sequence at a specified column position + * + * @param seq + * sequence to get colour for + * @param lastcol + * column position to get colour for + * @return colour of sequence at this position, as RGB + */ + int getColumnColourFromSequence(SequenceGroup[] allGroups, + SequenceI seq, int lastcol) + { + Color color = resColFinder.GAP_COLOUR; if ((seq != null) && (seq.getLength() > lastcol)) { - color = sr.getResidueColour(seq, lastcol, fcfinder); + color = resColFinder.getResidueColour(true, shader, allGroups, seq, + lastcol, finder); } - if (isHidden) + return color.getRGB(); + } + + /** + * Overlay the hidden regions on the overview image + * + * @param rows + * collection of rows the overview is built over + * @param cols + * collection of columns the overview is built over + */ + private void overlayHiddenRegions(AlignmentRowsCollectionI rows, + AlignmentColsCollectionI cols) + { + if (cols.hasHidden() || rows.hasHidden()) { - color = color.darker().darker(); + BufferedImage mask = buildHiddenImage(rows, cols, miniMe.getWidth(), + miniMe.getHeight()); + + Graphics2D g = (Graphics2D) miniMe.getGraphics(); + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + TRANSPARENCY)); + g.drawImage(mask, 0, 0, miniMe.getWidth(), miniMe.getHeight(), null); } + } - return color.getRGB(); + /** + * Build a masking image of hidden columns and rows to be applied on top of + * the main overview image. + * + * @param rows + * collection of rows the overview is built over + * @param cols + * collection of columns the overview is built over + * @param width + * width of overview in pixels + * @param height + * height of overview in pixels + * @return BufferedImage containing mask of hidden regions + */ + private BufferedImage buildHiddenImage(AlignmentRowsCollectionI rows, + AlignmentColsCollectionI cols, int width, int height) + { + // new masking image + BufferedImage hiddenImage = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + + int colIndex = 0; + int pixelCol = 0; + + Color hidden = resColFinder.getHiddenColour(); + + Graphics2D g2d = (Graphics2D) hiddenImage.getGraphics(); + + // set background to transparent + // g2d.setComposite(AlphaComposite.Clear); + // g2d.fillRect(0, 0, width, height); + + // set next colour to opaque + g2d.setComposite(AlphaComposite.Src); + + for (int alignmentCol : cols) + { + if (redraw) + { + break; + } + + // calculate where this column extends to in pixels + int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1, + hiddenImage.getWidth() - 1); + + if (pixelCol <= endCol) + { + // determine the colour based on the sequence and column position + if (cols.isHidden(alignmentCol)) + { + g2d.setColor(hidden); + g2d.fillRect(pixelCol, 0, endCol - pixelCol + 1, height); + } + + pixelCol = endCol + 1; + } + colIndex++; + + } + + int seqIndex = 0; + int pixelRow = 0; + for (int alignmentRow : rows) + { + if (redraw) + { + break; + } + + // calculate where this row extends to in pixels + int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1, + miniMe.getHeight() - 1); + + // get details of this alignment row + if (rows.isHidden(alignmentRow)) + { + g2d.setColor(hidden); + g2d.fillRect(0, pixelRow, width, endRow - pixelRow + 1); + } + pixelRow = endRow + 1; + seqIndex++; + } + + return hiddenImage; } /** @@ -144,15 +353,13 @@ public class OverviewRenderer * the graphics object to draw on * @param anno * alignment annotation information - * @param charWidth - * alignment character width value * @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 charWidth, - int y, AlignmentColsCollectionI cols) + public void drawGraph(Graphics g, AlignmentAnnotation anno, int y, + AlignmentColsCollectionI cols) { Annotation[] annotations = anno.annotations; g.setColor(Color.white); @@ -163,14 +370,19 @@ public class OverviewRenderer int pixelCol = 0; for (int alignmentCol : cols) { + if (redraw) + { + changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0); + break; + } + if (alignmentCol >= annotations.length) { break; // no more annotations to draw here } else { - int endCol = Math.min( - Math.round((colIndex + 1) * pixelsPerCol) - 1, + int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1, miniMe.getWidth() - 1); if (annotations[alignmentCol] != null) @@ -184,7 +396,8 @@ public class OverviewRenderer g.setColor(annotations[alignmentCol].colour); } - height = (int) ((annotations[alignmentCol].value / anno.graphMax) * y); + height = (int) ((annotations[alignmentCol].value / anno.graphMax) + * y); if (height > y) { height = y; @@ -192,9 +405,37 @@ public class OverviewRenderer g.fillRect(pixelCol, y - height, endCol - pixelCol + 1, height); } + pixelCol = endCol + 1; colIndex++; } } + changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, + MAX_PROGRESS); + } + + /** + * Allows redraw flag to be set + * + * @param b + * value to set redraw to: true = redraw is occurring, false = no + * redraw + */ + public void setRedraw(boolean b) + { + synchronized (this) + { + redraw = b; + } + } + + public void addPropertyChangeListener(RendererListenerI listener) + { + changeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(RendererListenerI listener) + { + changeSupport.removePropertyChangeListener(listener); } }