import jalview.api.AlignmentColsCollectionI;
import jalview.api.AlignmentRowsCollectionI;
-import jalview.api.AlignmentViewPanel;
import jalview.api.RendererListenerI;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
import jalview.renderer.seqfeatures.FeatureColourFinder;
-import jalview.util.Platform;
+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.event.ActionEvent;
-import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
-import java.awt.image.DataBufferInt;
-import java.awt.image.WritableRaster;
import java.beans.PropertyChangeSupport;
-import java.util.BitSet;
-import java.util.Iterator;
-
-import javax.swing.Timer;
public class OverviewRenderer
{
// image to render on
private BufferedImage miniMe;
- /**
- * Number of pixelsPerCol;
- */
+ // raw number of pixels to allocate to each column
private float pixelsPerCol;
- /**
- * Number of visible columns per pixel.
- *
- */
- private float colsPerPixel;
-
// raw number of pixels to allocate to each row
private float pixelsPerSeq;
private OverviewResColourFinder resColFinder;
- private boolean showProgress;
-
- private AlignmentViewPanel panel;
-
- // private int sequencesHeight;
-
- public OverviewRenderer(AlignmentViewPanel panel,
- jalview.api.FeatureRenderer fr, OverviewDimensions od,
- AlignmentI alignment, ResidueShaderI resshader,
- OverviewResColourFinder colFinder)
- {
- this(panel, fr, od, alignment, resshader, colFinder, true);
- }
-
- /**
- * @param panel
- * @param fr
- * @param od
- * @param alignment
- * @param resshader
- * @param colFinder
- * @param shwoProgress
- * possibly not, in JavaScript and for testng
- */
- public OverviewRenderer(AlignmentViewPanel panel,
- jalview.api.FeatureRenderer fr, OverviewDimensions od,
- AlignmentI alignment, ResidueShaderI resshader,
- OverviewResColourFinder colFinder, boolean showProgress)
+ public OverviewRenderer(FeatureRenderer fr, OverviewDimensions od,
+ AlignmentI alignment,
+ ResidueShaderI resshader, OverviewResColourFinder colFinder)
{
- {
- this.panel = panel;
finder = new FeatureColourFinder(fr);
- al = alignment;
- shader = resshader;
resColFinder = colFinder;
- this.showProgress = showProgress;
- w = od.getWidth();
- h = od.getHeight();
- rows = od.getRows(alignment);
- cols = od.getColumns(alignment);
- graphHeight = od.getGraphHeight();
- alignmentHeight = od.getSequencesHeight();
+ al = alignment;
+ shader = resshader;
- pixelsPerSeq = od.getPixelsPerSeq();
pixelsPerCol = od.getPixelsPerCol();
- colsPerPixel = Math.max(1, 1f / pixelsPerCol);
- }
+ pixelsPerSeq = od.getPixelsPerSeq();
+ graphHeight = od.getGraphHeight();
+ miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
+ BufferedImage.TYPE_INT_RGB);
}
- final static int STATE_INIT = 0;
- final static int STATE_NEXT = 1;
- final static int STATE_DONE = 2;
-
- int state;
-
- boolean isJS = Platform.isJS();
-
- Timer timer;
- int delay = (isJS ? 1 : 0);
-
- int seqIndex;
-
- int pixelRow;
-
- private Integer row;
-
/**
- * Draw alignment rows and columns onto an image. This method is asynchronous
- * in JavaScript and interruptible in Java.
- *
- * Whether hidden rows or columns are drawn depends upon the type of
- * collection.
+ * Draw alignment rows and columns onto an image
*
- * Updated to skip through high-density sequences, where columns/pixels > 1.
- *
- * When the process is complete, the image is passed to the AlignmentViewPanel
- * provided by the constructor.
- *
- * @param rows
- * collection of rows to be drawn
- * @param cols
- * collection of columns to be drawn
+ * @param rit
+ * Iterator over rows to be drawn
+ * @param cit
+ * Iterator over columns to be drawn
* @return image containing the drawing
- *
- * @author Bob Hanson 2019.07.30
*/
- public void drawMiniMe()
+ public BufferedImage draw(AlignmentRowsCollectionI rows,
+ AlignmentColsCollectionI cols)
{
- state = STATE_INIT;
- mainLoop();
- }
+ int rgbcolor = Color.white.getRGB();
+ int seqIndex = 0;
+ int pixelRow = 0;
+ int alignmentHeight = miniMe.getHeight() - graphHeight;
+ int totalPixels = miniMe.getWidth() * alignmentHeight;
- protected void mainLoop()
- {
- out: while (!redraw)
- {
- switch (state)
- {
- case STATE_INIT:
- init();
- state = STATE_NEXT;
- continue;
- case STATE_NEXT:
- if (!rowIterator.hasNext())
- {
- state = STATE_DONE;
- continue;
- }
- nextRow();
- if (!loop())
- {
- // Java
- continue;
- }
- // JavaScript
- return;
- case STATE_DONE:
- break out;
- }
- // Java will continue without a timeout
- }
- done();
- }
+ int lastRowUpdate = 0;
+ int lastUpdate = 0;
+ changeSupport.firePropertyChange(UPDATE, -1, 0);
- private void init()
- {
- rowIterator = rows.iterator();
- seqIndex = 0;
- pixelRow = 0;
- lastRowUpdate = 0;
- lastUpdate = 0;
- totalPixels = w * alignmentHeight;
-
- if (showProgress)
- {
- changeSupport.firePropertyChange(UPDATE, -1, 0);
- }
-
- miniMe = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
- WritableRaster raster = miniMe.getRaster();
- DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
- pixels = db.getBankData()[0];
- bscol = cols.getOverviewBitSet();
- Platform.timeCheck(null, Platform.TIME_MARK);
- }
-
- private void nextRow()
- {
- row = rowIterator.next();
- // get details of this alignment row
- SequenceI seq = rows.getSequence(row);
-
- // 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) * pixelsPerSeq), h);
- for (int pixelCol = 0, colNext = 0, pixelEnd = 0, icol = bscol
- .nextSetBit(0); icol >= 0; icol = getNextCol(icol, pixelEnd))
+ for (int alignmentRow : rows)
{
if (redraw)
{
break;
}
-
- ++colNext;
- pixelEnd = getNextPixel(colNext, colNext);
-
- if (pixelCol == pixelEnd)
- {
- break;
- }
- else if (pixelCol < pixelEnd)
+
+ // get details of this alignment row
+ 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)
{
- int rgb = getColumnColourFromSequence(allGroups, seq, icol);
- // fill in the appropriate number of pixels
- // System.out.println(
- // "OR colNext=" + colNext + " " + pixelCol
- // + "-" + pixelEnd + " icol=" + icol + " " + rgb + " "
- // + pixelsPerCol);
- for (int row = pixelRow; row < endRow; ++row)
+ if (redraw)
{
- for (int col = pixelCol; col < pixelEnd; ++col)
- {
- // BH 2019.07.27 was:
- //
- // miniMe.setRGB(col, row, rgbcolor);
- //
- // but just directly writing to the int[] pixel buffer
- // is three times faster by my experimentation
- pixels[row * w + col] = rgb;
- ndone++;
- }
+ break;
}
- pixelCol = pixelEnd;
- // store last update value
- if (showProgress)
+
+ // calculate where this column extends to in pixels
+ int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
+ miniMe.getWidth() - 1);
+
+ // 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)
{
+ rgbcolor = getColumnColourFromSequence(allGroups, seq,
+ alignmentCol);
+
+ // fill in the appropriate number of pixels
+ for (int row = pixelRow; row <= endRow; ++row)
+ {
+ for (int col = pixelCol; col <= endCol; ++col)
+ {
+ miniMe.setRGB(col, row, rgbcolor);
+ }
+ }
+
+ // store last update value
lastUpdate = sendProgressUpdate(
- pixelEnd * (endRow - 1 - pixelRow),
- totalPixels, lastRowUpdate, lastUpdate);
+ (pixelCol + 1) * (endRow - pixelRow), totalPixels,
+ lastRowUpdate, lastUpdate);
+
+ pixelCol = endCol + 1;
}
+ colIndex++;
}
- }
- if (pixelRow < endRow)
- {
- pixelRow = endRow;
- // store row offset and last update value
- if (showProgress)
+ if (pixelRow != endRow + 1)
{
- // BH 2019.07.29 was (old) endRow + 1 (now endRow), but should be
- // pixelRow + 1, surely
- lastRowUpdate = sendProgressUpdate(endRow, alignmentHeight, 0,
+ // store row offset and last update value
+ lastRowUpdate = sendProgressUpdate(endRow + 1, alignmentHeight, 0,
lastUpdate);
lastUpdate = lastRowUpdate;
+ pixelRow = endRow + 1;
}
+ seqIndex++;
}
- }
- /**
- * The next column is either the next set bit (when there are multiple pixels
- * per column) or the next set bit for the column that aligns with the next
- * pixel (when there are more columns than pixels).
- *
- * @param icol
- * @param pixel
- * @return
- */
- private int getNextCol(int icol, int pixel)
- {
- return bscol.nextSetBit(
- pixelsPerCol >= 1 ? icol + 1 : (int) (pixel * colsPerPixel));
- }
-
- private int getNextPixel(int icol, int pixel)
- {
- return Math.min(
- pixelsPerCol >= 1 || pixel == 0
- ? Math.round(icol * pixelsPerCol)
- : pixel,
- w);
- }
-
- private ActionListener listener = new ActionListener()
- {
- @Override
- public void actionPerformed(ActionEvent e)
+ overlayHiddenRegions(rows, cols);
+ // final update to progress bar if present
+ if (redraw)
{
- mainLoop();
- }
-
- };
-
- private boolean loop()
- {
- if (delay <= 0)
- {
- return false;
- }
- if (timer == null)
- {
- timer = new Timer(delay, listener);
- timer.setRepeats(false);
- timer.start();
+ sendProgressUpdate(pixelRow - 1, alignmentHeight, 0, 0);
}
else
{
- timer.restart();
+ sendProgressUpdate(alignmentHeight, miniMe.getHeight(), 0, 0);
}
- return true;
+ return miniMe;
}
- private void done()
- {
- if (!redraw)
- {
- Platform.timeCheck(
- "overviewrender " + ndone + " pixels row:" + row + " redraw:"
- + redraw,
- Platform.TIME_MARK);
- }
-
- overlayHiddenRegions();
- if (showProgress)
- {
- // final update to progress bar if present
- if (redraw)
- {
- // aborted in Java
- // BH was pixelRow - 1, but that could go negative
- sendProgressUpdate(pixelRow, alignmentHeight, 0, 0);
- }
- else
- {
- // sendProgressUpdate(alignmentHeight, miniMe.getHeight(), 0, 0);
- sendProgressUpdate(1, 1, 0, 0);
- }
- }
- panel.overviewDone(miniMe);
- }
-
- int ndone = 0;
-
- private AlignmentRowsCollectionI rows;
-
- private AlignmentColsCollectionI cols;
-
- Iterator<Integer> rowIterator;
-
- int alignmentHeight;
-
- int totalPixels;
-
- int lastRowUpdate;
-
- int lastUpdate;
-
- int[] pixels;
-
- BitSet bscol;
-
- int w, h;
-
/*
* Calculate progress update value and fire event
* @param rowOffset number of rows to offset calculation by
* column position to get colour for
* @return colour of sequence at this position, as RGB
*/
- int getColumnColourFromSequence(SequenceGroup[] allGroups, SequenceI seq,
- int icol)
+ int getColumnColourFromSequence(SequenceGroup[] allGroups,
+ SequenceI seq, int lastcol)
{
- return (seq == null || icol >= seq.getLength()
- ? resColFinder.GAP_COLOUR
- : resColFinder.getResidueColourInt(true, shader, allGroups, seq,
- icol, finder));
+ Color color = resColFinder.GAP_COLOUR;
+
+ if ((seq != null) && (seq.getLength() > lastcol))
+ {
+ color = resColFinder.getResidueColour(true, shader, allGroups, seq,
+ lastcol, finder);
+ }
+
+ 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()
+ private void overlayHiddenRegions(AlignmentRowsCollectionI rows,
+ AlignmentColsCollectionI cols)
{
if (cols.hasHidden() || rows.hasHidden())
{
- BufferedImage mask = buildHiddenImage();
+ 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);
- g.dispose();
}
}
* height of overview in pixels
* @return BufferedImage containing mask of hidden regions
*/
- private BufferedImage buildHiddenImage()
+ private BufferedImage buildHiddenImage(AlignmentRowsCollectionI rows,
+ AlignmentColsCollectionI cols, int width, int height)
{
// new masking image
- BufferedImage hiddenImage = new BufferedImage(w, h,
+ 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();
- g2d.setColor(hidden);
// set background to transparent
// g2d.setComposite(AlphaComposite.Clear);
// g2d.fillRect(0, 0, width, height);
// set next colour to opaque
g2d.setComposite(AlphaComposite.Src);
- // System.out.println(cols.getClass().getName());
- if (cols.hasHidden())
+ for (int alignmentCol : cols)
{
- // AllColsCollection only
- BitSet bs = cols.getHiddenBitSet();
- for (int pixelCol = -1, icol2 = 0, icol = bs
- .nextSetBit(0); icol >= 0; icol = bs.nextSetBit(icol2))
+ if (redraw)
{
- if (redraw)
- {
- break;
- }
- icol2 = bs.nextClearBit(icol + 1);
- int pixelEnd = getNextPixel(icol2, 0);
- if (pixelEnd > pixelCol)
+ 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))
{
- pixelCol = getNextPixel(icol, 0);
- g2d.fillRect(pixelCol, 0, Math.max(1, pixelEnd - pixelCol),
- h);
- pixelCol = pixelEnd;
+ g2d.setColor(hidden);
+ g2d.fillRect(pixelCol, 0, endCol - pixelCol + 1, height);
}
+
+ pixelCol = endCol + 1;
}
+ colIndex++;
+
}
- if (rows.hasHidden())
+
+ int seqIndex = 0;
+ int pixelRow = 0;
+ for (int alignmentRow : rows)
{
- int seqIndex = 0;
- int pixelRow = 0;
- for (int alignmentRow : rows)
+ if (redraw)
{
- if (redraw)
- {
- break;
- }
+ break;
+ }
- // calculate where this row extends to in pixels
- int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq),
- h);
+ // 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.fillRect(0, pixelRow, w, endRow - 1 - pixelRow);
- }
- pixelRow = endRow;
+ // 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++;
}
- g2d.dispose();
+
return hiddenImage;
}
/**
* Draw the alignment annotation in the overview panel
*
+ * @param g
+ * the graphics object to draw on
* @param anno
* alignment annotation information
+ * @param y
+ * y-position for the annotation graph
+ * @param cols
+ * the collection of columns used in the overview panel
*/
- public void drawGraph(AlignmentAnnotation anno)
+ public void drawGraph(Graphics g, AlignmentAnnotation anno, int y,
+ AlignmentColsCollectionI cols)
{
- int y = graphHeight;
- Graphics g = miniMe.getGraphics();
- g.translate(0, alignmentHeight);
-
Annotation[] annotations = anno.annotations;
- float max = anno.graphMax;
g.setColor(Color.white);
- g.fillRect(0, 0, w, y);
+ g.fillRect(0, 0, miniMe.getWidth(), y);
- for (int pixelCol = 0, colNext = 0, pixelEnd = 0, len = annotations.length, icol = bscol
- .nextSetBit(0); icol >= 0
- && icol < len; icol = getNextCol(icol, pixelEnd))
+ int height;
+ int colIndex = 0;
+ int pixelCol = 0;
+ for (int alignmentCol : cols)
{
if (redraw)
{
- if (showProgress)
- {
- changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0);
- }
+ changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0);
break;
}
- ++colNext;
- pixelEnd = getNextPixel(colNext, colNext);
- Annotation ann = annotations[icol];
- if (ann != null)
+ if (alignmentCol >= annotations.length)
{
- Color color = ann.colour;
- g.setColor(color == null ? Color.black : color);
- int height = Math.min(y, (int) ((ann.value / max) * y));
- g.fillRect(pixelCol, y - height, Math.max(1, pixelEnd - pixelCol),
- height);
+ break; // no more annotations to draw here
}
- pixelCol = pixelEnd;
- }
+ else
+ {
+ int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
+ miniMe.getWidth() - 1);
- g.translate(0, -alignmentHeight);
- g.dispose();
+ if (annotations[alignmentCol] != null)
+ {
+ if (annotations[alignmentCol].colour == null)
+ {
+ g.setColor(Color.black);
+ }
+ else
+ {
+ g.setColor(annotations[alignmentCol].colour);
+ }
- if (showProgress)
- {
- changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1,
- MAX_PROGRESS);
- }
+ height = (int) ((annotations[alignmentCol].value / anno.graphMax)
+ * y);
+ if (height > y)
+ {
+ height = y;
+ }
+
+ g.fillRect(pixelCol, y - height, endCol - pixelCol + 1, height);
+ }
+ pixelCol = endCol + 1;
+ colIndex++;
+ }
+ }
+ changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1,
+ MAX_PROGRESS);
}
/**