X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Frenderer%2FOverviewRenderer.java;fp=src%2Fjalview%2Frenderer%2FOverviewRenderer.java;h=c9096e290e5a417d8ee813168744477ce6e02ee5;hb=4f77328104498504339216829abf5ea87e2791ec;hp=36b5847b8193716cdc403356e1d5fae81f5507fb;hpb=2b8c0785318a3528e1876e8e2dd48b7d831eae69;p=jalview.git diff --git a/src/jalview/renderer/OverviewRenderer.java b/src/jalview/renderer/OverviewRenderer.java index 36b5847..c9096e2 100644 --- a/src/jalview/renderer/OverviewRenderer.java +++ b/src/jalview/renderer/OverviewRenderer.java @@ -22,7 +22,6 @@ package jalview.renderer; import jalview.api.AlignmentColsCollectionI; import jalview.api.AlignmentRowsCollectionI; -import jalview.api.AlignmentViewPanel; import jalview.api.RendererListenerI; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; @@ -30,23 +29,15 @@ import jalview.datamodel.Annotation; 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 { @@ -65,17 +56,9 @@ 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; @@ -92,331 +75,126 @@ public class OverviewRenderer 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 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 @@ -444,30 +222,40 @@ public class OverviewRenderer * 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(); } } @@ -485,17 +273,20 @@ public class OverviewRenderer * 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); @@ -503,109 +294,124 @@ public class OverviewRenderer // 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); } /**