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.util.Platform;
34 import jalview.viewmodel.OverviewDimensions;
36 import java.awt.AlphaComposite;
37 import java.awt.Color;
38 import java.awt.Graphics;
39 import java.awt.Graphics2D;
40 import java.awt.event.ActionEvent;
41 import java.awt.event.ActionListener;
42 import java.awt.image.BufferedImage;
43 import java.awt.image.DataBufferInt;
44 import java.awt.image.WritableRaster;
45 import java.beans.PropertyChangeSupport;
46 import java.util.BitSet;
47 import java.util.Iterator;
49 import javax.swing.Timer;
51 public class OverviewRenderer
53 // transparency of hidden cols/seqs overlay
54 private final float TRANSPARENCY = 0.5f;
56 public static final String UPDATE = "OverviewUpdate";
58 private static final int MAX_PROGRESS = 100;
60 private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
63 private FeatureColourFinder finder;
66 private BufferedImage miniMe;
69 * Number of pixelsPerCol;
71 private float pixelsPerCol;
74 * Number of visible columns per pixel.
77 private float colsPerPixel;
79 // raw number of pixels to allocate to each row
80 private float pixelsPerSeq;
82 // height in pixels of graph
83 private int graphHeight;
85 // flag to indicate whether to halt drawing
86 private volatile boolean redraw = false;
88 // reference to alignment, needed to get sequence groups
89 private AlignmentI al;
91 private ResidueShaderI shader;
93 private OverviewResColourFinder resColFinder;
95 private boolean showProgress;
97 private AlignmentViewPanel panel;
99 // private int sequencesHeight;
101 public OverviewRenderer(AlignmentViewPanel panel,
102 jalview.api.FeatureRenderer fr, OverviewDimensions od,
103 AlignmentI alignment, ResidueShaderI resshader,
104 OverviewResColourFinder colFinder)
106 this(panel, fr, od, alignment, resshader, colFinder, true);
116 * @param shwoProgress
117 * possibly not, in JavaScript and for testng
119 public OverviewRenderer(AlignmentViewPanel panel,
120 jalview.api.FeatureRenderer fr, OverviewDimensions od,
121 AlignmentI alignment, ResidueShaderI resshader,
122 OverviewResColourFinder colFinder, boolean showProgress)
126 finder = new FeatureColourFinder(fr);
129 resColFinder = colFinder;
130 this.showProgress = showProgress;
134 rows = od.getRows(alignment);
135 cols = od.getColumns(alignment);
136 graphHeight = od.getGraphHeight();
137 alignmentHeight = od.getSequencesHeight();
139 pixelsPerSeq = od.getPixelsPerSeq();
140 pixelsPerCol = od.getPixelsPerCol();
141 colsPerPixel = Math.max(1, 1f / pixelsPerCol);
145 final static int STATE_INIT = 0;
146 final static int STATE_NEXT = 1;
147 final static int STATE_DONE = 2;
151 boolean isJS = Platform.isJS();
154 int delay = (isJS ? 1 : 0);
163 * Draw alignment rows and columns onto an image. This method is asynchronous
164 * in JavaScript and interruptible in Java.
166 * Whether hidden rows or columns are drawn depends upon the type of
169 * Updated to skip through high-density sequences, where columns/pixels > 1.
171 * When the process is complete, the image is passed to the AlignmentViewPanel
172 * provided by the constructor.
175 * collection of rows to be drawn
177 * collection of columns to be drawn
178 * @return image containing the drawing
180 * @author Bob Hanson 2019.07.30
182 public void drawMiniMe()
188 protected void mainLoop()
199 if (!rowIterator.hasNext())
215 // Java will continue without a timeout
222 rowIterator = rows.iterator();
227 totalPixels = w * alignmentHeight;
231 changeSupport.firePropertyChange(UPDATE, -1, 0);
234 miniMe = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
235 WritableRaster raster = miniMe.getRaster();
236 DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
237 pixels = db.getBankData()[0];
238 bscol = cols.getOverviewBitSet();
239 Platform.timeCheck(null, Platform.TIME_MARK);
242 private void nextRow()
244 row = rowIterator.next();
245 // get details of this alignment row
246 SequenceI seq = rows.getSequence(row);
248 // rate limiting step when rendering overview for lots of groups
249 SequenceGroup[] allGroups = al.findAllGroups(seq);
251 // calculate where this row extends to in pixels
252 int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq), h);
253 for (int pixelCol = 0, colNext = 0, pixelEnd = 0, icol = bscol
254 .nextSetBit(0); icol >= 0; icol = getNextCol(icol, pixelEnd))
262 pixelEnd = getNextPixel(colNext, colNext);
264 if (pixelCol == pixelEnd)
268 else if (pixelCol < pixelEnd)
270 int rgb = getColumnColourFromSequence(allGroups, seq, icol);
271 // fill in the appropriate number of pixels
272 // System.out.println(
273 // "OR colNext=" + colNext + " " + pixelCol
274 // + "-" + pixelEnd + " icol=" + icol + " " + rgb + " "
276 for (int row = pixelRow; row < endRow; ++row)
278 for (int col = pixelCol; col < pixelEnd; ++col)
280 // BH 2019.07.27 was:
282 // miniMe.setRGB(col, row, rgbcolor);
284 // but just directly writing to the int[] pixel buffer
285 // is three times faster by my experimentation
286 pixels[row * w + col] = rgb;
291 // store last update value
294 lastUpdate = sendProgressUpdate(
295 pixelEnd * (endRow - 1 - pixelRow),
296 totalPixels, lastRowUpdate, lastUpdate);
301 if (pixelRow < endRow)
304 // store row offset and last update value
307 // BH 2019.07.29 was (old) endRow + 1 (now endRow), but should be
308 // pixelRow + 1, surely
309 lastRowUpdate = sendProgressUpdate(endRow, alignmentHeight, 0,
311 lastUpdate = lastRowUpdate;
317 * The next column is either the next set bit (when there are multiple pixels
318 * per column) or the next set bit for the column that aligns with the next
319 * pixel (when there are more columns than pixels).
325 private int getNextCol(int icol, int pixel)
327 return bscol.nextSetBit(
328 pixelsPerCol >= 1 ? icol + 1 : (int) (pixel * colsPerPixel));
331 private int getNextPixel(int icol, int pixel)
334 pixelsPerCol >= 1 || pixel == 0
335 ? Math.round(icol * pixelsPerCol)
340 private ActionListener listener = new ActionListener()
343 public void actionPerformed(ActionEvent e)
350 private boolean loop()
358 timer = new Timer(delay, listener);
359 timer.setRepeats(false);
374 "overviewrender " + ndone + " pixels row:" + row + " redraw:"
379 overlayHiddenRegions();
382 // final update to progress bar if present
386 // BH was pixelRow - 1, but that could go negative
387 sendProgressUpdate(pixelRow, alignmentHeight, 0, 0);
391 // sendProgressUpdate(alignmentHeight, miniMe.getHeight(), 0, 0);
392 sendProgressUpdate(1, 1, 0, 0);
395 panel.overviewDone(miniMe);
400 private AlignmentRowsCollectionI rows;
402 private AlignmentColsCollectionI cols;
404 Iterator<Integer> rowIterator;
421 * Calculate progress update value and fire event
422 * @param rowOffset number of rows to offset calculation by
423 * @return new rowOffset - return value only to be used when at end of a row
425 private int sendProgressUpdate(int position, int maximum, int rowOffset,
428 int newUpdate = rowOffset
429 + Math.round(MAX_PROGRESS * ((float) position / maximum));
430 if (newUpdate > lastUpdate)
432 changeSupport.firePropertyChange(UPDATE, rowOffset, newUpdate);
439 * Find the RGB value of the colour of a sequence at a specified column position
442 * sequence to get colour for
444 * column position to get colour for
445 * @return colour of sequence at this position, as RGB
447 int getColumnColourFromSequence(SequenceGroup[] allGroups, SequenceI seq,
450 return (seq == null || icol >= seq.getLength()
451 ? resColFinder.GAP_COLOUR
452 : resColFinder.getResidueColourInt(true, shader, allGroups, seq,
457 * Overlay the hidden regions on the overview image
460 private void overlayHiddenRegions()
462 if (cols.hasHidden() || rows.hasHidden())
464 BufferedImage mask = buildHiddenImage();
466 Graphics2D g = (Graphics2D) miniMe.getGraphics();
467 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
469 g.drawImage(mask, 0, 0, miniMe.getWidth(), miniMe.getHeight(), null);
475 * Build a masking image of hidden columns and rows to be applied on top of
476 * the main overview image.
479 * collection of rows the overview is built over
481 * collection of columns the overview is built over
483 * width of overview in pixels
485 * height of overview in pixels
486 * @return BufferedImage containing mask of hidden regions
488 private BufferedImage buildHiddenImage()
491 BufferedImage hiddenImage = new BufferedImage(w, h,
492 BufferedImage.TYPE_INT_ARGB);
494 Color hidden = resColFinder.getHiddenColour();
496 Graphics2D g2d = (Graphics2D) hiddenImage.getGraphics();
498 g2d.setColor(hidden);
499 // set background to transparent
500 // g2d.setComposite(AlphaComposite.Clear);
501 // g2d.fillRect(0, 0, width, height);
503 // set next colour to opaque
504 g2d.setComposite(AlphaComposite.Src);
506 // System.out.println(cols.getClass().getName());
507 if (cols.hasHidden())
509 // AllColsCollection only
510 BitSet bs = cols.getHiddenBitSet();
511 for (int pixelCol = -1, icol2 = 0, icol = bs
512 .nextSetBit(0); icol >= 0; icol = bs.nextSetBit(icol2))
518 icol2 = bs.nextClearBit(icol + 1);
519 int pixelEnd = getNextPixel(icol2, 0);
520 if (pixelEnd > pixelCol)
522 pixelCol = getNextPixel(icol, 0);
523 g2d.fillRect(pixelCol, 0, Math.max(1, pixelEnd - pixelCol),
529 if (rows.hasHidden())
533 for (int alignmentRow : rows)
540 // calculate where this row extends to in pixels
541 int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq),
544 // get details of this alignment row
545 if (rows.isHidden(alignmentRow))
547 g2d.fillRect(0, pixelRow, w, endRow - 1 - pixelRow);
557 * Draw the alignment annotation in the overview panel
560 * alignment annotation information
562 public void drawGraph(AlignmentAnnotation anno)
565 Graphics g = miniMe.getGraphics();
566 g.translate(0, alignmentHeight);
568 Annotation[] annotations = anno.annotations;
569 float max = anno.graphMax;
570 g.setColor(Color.white);
571 g.fillRect(0, 0, w, y);
573 for (int pixelCol = 0, colNext = 0, pixelEnd = 0, len = annotations.length, icol = bscol
574 .nextSetBit(0); icol >= 0
575 && icol < len; icol = getNextCol(icol, pixelEnd))
581 changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0);
587 pixelEnd = getNextPixel(colNext, colNext);
588 Annotation ann = annotations[icol];
591 Color color = ann.colour;
592 g.setColor(color == null ? Color.black : color);
593 int height = Math.min(y, (int) ((ann.value / max) * y));
594 g.fillRect(pixelCol, y - height, Math.max(1, pixelEnd - pixelCol),
600 g.translate(0, -alignmentHeight);
605 changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1,
612 * Allows redraw flag to be set
615 * value to set redraw to: true = redraw is occurring, false = no
618 public void setRedraw(boolean b)
626 public void addPropertyChangeListener(RendererListenerI listener)
628 changeSupport.addPropertyChangeListener(listener);
631 public void removePropertyChangeListener(RendererListenerI listener)
633 changeSupport.removePropertyChangeListener(listener);