2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.renderer;
23 import jalview.api.AlignmentColsCollectionI;
24 import jalview.api.AlignmentRowsCollectionI;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.api.RendererListenerI;
27 import jalview.datamodel.AlignmentAnnotation;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.Annotation;
30 import jalview.datamodel.SequenceGroup;
31 import jalview.datamodel.SequenceI;
32 import jalview.renderer.seqfeatures.FeatureColourFinder;
33 import jalview.renderer.seqfeatures.FeatureRenderer;
34 import jalview.util.Platform;
35 import jalview.viewmodel.OverviewDimensions;
37 import java.awt.AlphaComposite;
38 import java.awt.Color;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.event.ActionEvent;
42 import java.awt.event.ActionListener;
43 import java.awt.image.BufferedImage;
44 import java.awt.image.DataBufferInt;
45 import java.awt.image.WritableRaster;
46 import java.beans.PropertyChangeSupport;
47 import java.util.BitSet;
48 import java.util.Iterator;
50 import javax.swing.Timer;
52 public class OverviewRenderer
54 // transparency of hidden cols/seqs overlay
55 private final float TRANSPARENCY = 0.5f;
57 public static final String UPDATE = "OverviewUpdate";
59 private static final int MAX_PROGRESS = 100;
61 private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
64 private FeatureColourFinder finder;
67 private BufferedImage miniMe;
69 // raw number of pixels to allocate to each column
70 private float pixelsPerCol;
72 // raw number of pixels to allocate to each row
73 private float pixelsPerSeq;
75 // height in pixels of graph
76 private int graphHeight;
78 // flag to indicate whether to halt drawing
79 private volatile boolean redraw = false;
81 // reference to alignment, needed to get sequence groups
82 private AlignmentI al;
84 private ResidueShaderI shader;
86 private OverviewResColourFinder resColFinder;
88 private boolean showProgress;
90 private AlignmentViewPanel panel;
92 public OverviewRenderer(AlignmentViewPanel panel, FeatureRenderer fr,
93 OverviewDimensions od,
95 ResidueShaderI resshader, OverviewResColourFinder colFinder)
97 this(panel, fr, od, alignment, resshader, colFinder, true);
100 public OverviewRenderer(AlignmentViewPanel panel,
101 jalview.api.FeatureRenderer fr,
102 OverviewDimensions od,
103 AlignmentI alignment, ResidueShaderI resshader,
104 OverviewResColourFinder colFinder, boolean showProgress)
107 finder = new FeatureColourFinder(fr);
108 resColFinder = colFinder;
113 pixelsPerCol = od.getPixelsPerCol();
114 pixelsPerSeq = od.getPixelsPerSeq();
115 graphHeight = od.getGraphHeight();
116 miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
117 BufferedImage.TYPE_INT_RGB);
118 this.showProgress = showProgress;
121 final static int STATE_INIT = 0;
122 final static int STATE_NEXT = 1;
123 final static int STATE_DONE = 2;
127 boolean isJS = Platform.isJS();
130 int delay = (isJS ? 1 : 0);
172 private void jsloop()
176 timer = new Timer(delay, new ActionListener()
179 public void actionPerformed(ActionEvent e)
185 timer.setRepeats(false);
194 private void nextRow()
197 // System.out.println("OR row " + r);
198 // get details of this alignment row
199 SequenceI seq = rows.getSequence(row);
201 // rate limiting step when rendering overview for lots of groups
202 SequenceGroup[] allGroups = al.findAllGroups(seq);
204 // calculate where this row extends to in pixels
205 int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq), h);
207 for (int pixelCol = 0, colIndex = 0, c = bscol
208 .nextSetBit(0); c >= 0; c = bscol.nextSetBit(c + 1))
215 // calculate where this column extends to in pixels
216 int endCol = Math.min(Math.round((++colIndex) * pixelsPerCol), w);
218 // don't do expensive colour determination if we're not going to use it
219 // NB this is important to avoid performance issues in the overview
222 if (pixelCol < endCol)
224 // System.out.println("OR pc ec " + pixelCol + " " + endCol);
225 int rgb = getColumnColourFromSequence(allGroups, seq, c);
226 // fill in the appropriate number of pixels
227 for (int row = pixelRow; row < endRow; ++row)
229 for (int col = pixelCol; col < endCol; ++col)
231 // BH 2019.07.27 was:
233 // miniMe.setRGB(col, row, rgbcolor);
235 // but just directly writing to the int[] pixel buffer
236 // is three times faster by my experimentation
237 pixels[row * w + col] = rgb;
244 // store last update value
247 lastUpdate = sendProgressUpdate(endCol * (endRow - 1 - pixelRow),
248 totalPixels, lastRowUpdate, lastUpdate);
252 if (pixelRow < endRow)
255 // store row offset and last update value
258 // BH 2019.07.29 was (old) endRow + 1 (now endRow), but should be
259 // pixelRow + 1, surely
260 lastRowUpdate = sendProgressUpdate(endRow, alignmentHeight, 0,
262 lastUpdate = lastRowUpdate;
270 "overviewrender " + ndone + " pixels row:" + row + " redraw:"
274 overlayHiddenRegions(rows, cols);
277 // final update to progress bar if present
281 // BH was pixelRow - 1, but that could go negative
282 sendProgressUpdate(pixelRow, alignmentHeight, 0, 0);
286 // sendProgressUpdate(alignmentHeight, miniMe.getHeight(), 0, 0);
287 sendProgressUpdate(1, 1, 0, 0);
290 panel.overviewDone(miniMe);
295 private AlignmentRowsCollectionI rows;
297 private AlignmentColsCollectionI cols;
299 Iterator<Integer> iter;
311 BitSet bscol = new BitSet();
316 * Draw alignment rows and columns onto an image
319 * Iterator over rows to be drawn
321 * Iterator over columns to be drawn
322 * @return image containing the drawing
324 public BufferedImage draw(AlignmentRowsCollectionI rows,
325 AlignmentColsCollectionI cols)
329 iter = rows.iterator();
331 w = miniMe.getWidth();
332 h = miniMe.getHeight();
333 alignmentHeight = h - graphHeight;
334 totalPixels = w * alignmentHeight;
340 changeSupport.firePropertyChange(UPDATE, -1, 0);
343 WritableRaster raster = miniMe.getRaster();
344 DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
345 Platform.timeCheck(null, Platform.TIME_MARK);
346 pixels = db.getBankData()[0];
359 * Calculate progress update value and fire event
360 * @param rowOffset number of rows to offset calculation by
361 * @return new rowOffset - return value only to be used when at end of a row
363 private int sendProgressUpdate(int position, int maximum, int rowOffset,
366 int newUpdate = rowOffset
367 + Math.round(MAX_PROGRESS * ((float) position / maximum));
368 if (newUpdate > lastUpdate)
370 changeSupport.firePropertyChange(UPDATE, rowOffset, newUpdate);
377 * Find the RGB value of the colour of a sequence at a specified column position
380 * sequence to get colour for
382 * column position to get colour for
383 * @return colour of sequence at this position, as RGB
385 int getColumnColourFromSequence(SequenceGroup[] allGroups, SequenceI seq,
388 return (seq == null || icol >= seq.getLength()
389 ? resColFinder.GAP_COLOUR
390 : resColFinder.getResidueColour(true, shader, allGroups, seq,
391 icol, finder)).getRGB();
395 * Overlay the hidden regions on the overview image
398 * collection of rows the overview is built over
400 * collection of columns the overview is built over
402 private void overlayHiddenRegions(AlignmentRowsCollectionI rows,
403 AlignmentColsCollectionI cols)
405 if (cols.hasHidden() || rows.hasHidden())
407 BufferedImage mask = buildHiddenImage(rows, cols, miniMe.getWidth(),
410 Graphics2D g = (Graphics2D) miniMe.getGraphics();
411 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
413 g.drawImage(mask, 0, 0, miniMe.getWidth(), miniMe.getHeight(), null);
419 * Build a masking image of hidden columns and rows to be applied on top of
420 * the main overview image.
423 * collection of rows the overview is built over
425 * collection of columns the overview is built over
427 * width of overview in pixels
429 * height of overview in pixels
430 * @return BufferedImage containing mask of hidden regions
432 private BufferedImage buildHiddenImage(AlignmentRowsCollectionI rows,
433 AlignmentColsCollectionI cols, int width, int height)
436 BufferedImage hiddenImage = new BufferedImage(width, height,
437 BufferedImage.TYPE_INT_ARGB);
439 Color hidden = resColFinder.getHiddenColour();
441 Graphics2D g2d = (Graphics2D) hiddenImage.getGraphics();
443 // set background to transparent
444 // g2d.setComposite(AlphaComposite.Clear);
445 // g2d.fillRect(0, 0, width, height);
447 // set next colour to opaque
448 g2d.setComposite(AlphaComposite.Src);
450 if (cols.hasHidden())
454 for (int alignmentCol : cols)
461 // calculate where this column extends to in pixels
462 int endCol = Math.min(Math.round((++colIndex) * pixelsPerCol),
465 // endCol is one more than old endCol
466 if (pixelCol < endCol)
468 // determine the colour based on the sequence and column position
469 if (cols.isHidden(alignmentCol))
471 g2d.setColor(hidden);
472 g2d.fillRect(pixelCol, 0, endCol - pixelCol, height);
478 if (rows.hasHidden())
482 for (int alignmentRow : rows)
489 // calculate where this row extends to in pixels
490 int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq),
493 // get details of this alignment row
494 if (rows.isHidden(alignmentRow))
496 g2d.setColor(hidden);
497 g2d.fillRect(0, pixelRow, width, endRow - 1 - pixelRow);
507 * Draw the alignment annotation in the overview panel
510 * the graphics object to draw on
512 * alignment annotation information
514 * y-position for the annotation graph
516 * the collection of columns used in the overview panel
518 public void drawGraph(Graphics g, AlignmentAnnotation anno, int y,
519 AlignmentColsCollectionI cols)
521 Annotation[] annotations = anno.annotations;
522 float max = anno.graphMax;
523 g.setColor(Color.white);
524 int width = miniMe.getWidth();
525 g.fillRect(0, 0, width, y);
529 for (int icol : cols)
535 changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0);
540 if (icol >= annotations.length)
542 break; // no more annotations to draw here
544 int endCol = Math.min(Math.round((++colIndex) * pixelsPerCol), width);
545 Annotation ann = annotations[icol];
548 Color color = ann.colour;
549 g.setColor(color == null ? Color.black : color);
551 int height = Math.min(y, (int) ((ann.value / max) * y));
552 g.fillRect(pixelCol, y - height, endCol - pixelCol, height);
558 changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1,
564 * Allows redraw flag to be set
567 * value to set redraw to: true = redraw is occurring, false = no
570 public void setRedraw(boolean b)
578 public void addPropertyChangeListener(RendererListenerI listener)
580 changeSupport.addPropertyChangeListener(listener);
583 public void removePropertyChangeListener(RendererListenerI listener)
585 changeSupport.removePropertyChangeListener(listener);