private static final int MAX_PROGRESS = 100;
+ final static int STATE_INIT = 0;
+
+ final static int STATE_NEXT = 1;
+
+ final static int STATE_DONE = 2;
+
+ private int state;
+
+ private Timer timer;
+
+ private boolean isJS = Platform.isJS();
+
+ private int delay = (isJS ? 1 : 0);
+
+ private int seqIndex;
+
+ private int pixelRow;
+
+ private Integer row;
+
private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
this);
*/
private float colsPerPixel;
- // raw number of pixels to allocate to each row
+ /**
+ * raw number of pixels to allocate to each row
+ */
private float pixelsPerSeq;
+ /**
+ * true when colsPerPixel > 1
+ */
+ private boolean skippingColumns;
+
+ /**
+ * pre-calculated list of columns needed for a "dense" overview, where there
+ * are more columns than pixels
+ */
+
+ private int[] columnsToShow;
+
// height in pixels of graph
private int graphHeight;
OverviewResColourFinder colFinder, boolean showProgress)
{
{
- 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();
-
- pixelsPerSeq = od.getPixelsPerSeq();
- pixelsPerCol = od.getPixelsPerCol();
- colsPerPixel = Math.max(1, 1f / pixelsPerCol);
+ 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();
+
+ pixelsPerSeq = od.getPixelsPerSeq();
+ pixelsPerCol = od.getPixelsPerCol();
+ colsPerPixel = Math.max(1, 1f / pixelsPerCol);
+
+ skippingColumns = (pixelsPerCol < 1);
+
}
}
- 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.
lastRowUpdate = 0;
lastUpdate = 0;
totalPixels = w * alignmentHeight;
-
if (showProgress)
{
changeSupport.firePropertyChange(UPDATE, -1, 0);
DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
pixels = db.getBankData()[0];
bscol = cols.getOverviewBitSet();
+ if (skippingColumns)
+ {
+ columnsToShow = calcColumnsToShow();
+ }
+
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
// calculate where this row extends to in pixels
int endRow = Math.min(Math.round((++seqIndex) * pixelsPerSeq), h);
+ // this is the key modification -- we use bscol to jump to the next column
+ // when there are more columns than pixels.
+
for (int pixelCol = 0, colNext = 0, pixelEnd = 0, icol = bscol
- .nextSetBit(0); icol >= 0; icol = getNextCol(icol, pixelEnd))
+ .nextSetBit(0); icol >= 0; icol = getNextCol(icol, colNext))
{
+ // asynchronous exit flag
if (redraw)
{
break;
{
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)
{
for (int col = pixelCol; col < pixelEnd; ++col)
if (showProgress)
{
lastUpdate = sendProgressUpdate(
- pixelEnd * (endRow - 1 - pixelRow),
- totalPixels, lastRowUpdate, lastUpdate);
+ pixelEnd * (endRow - 1 - pixelRow), totalPixels,
+ lastRowUpdate, lastUpdate);
}
}
}
/**
- * 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).
+ * Precalculate the columns that will be used for each pixel in a dense
+ * overview. So we have to skip through the bscol BitSet to pick up one
+ * (representative?) column for each pixel.
+ *
+ * Note that there is no easy solution if we want to do color averaging, but
+ * this method might be adapted to do that. Or it could be adapted to pick the
+ * "most representative color" for a group of columns.
+ *
+ * @author Bob Hanson 2019.09.03
+ * @return a -1 terminated int[]
+ */
+ private int[] calcColumnsToShow()
+ {
+ int[] a = new int[w + 1];
+ float colBuffer = 0;
+ float offset = bscol.nextSetBit(0);
+ if (offset < 0)
+ {
+ return new int[] { -1 };
+ }
+ int pixel = 0;
+ a[pixel++] = (int) offset;
+ // for example, say we have 10 pixels per column:
+ // ...............xxxxxxxx....xxxxxx.........xxxxxx......
+ // nextSet(i).....^...........^..............^...........
+ // nextClear..............^.........^..............^.....
+ // run lengths....|--n1--|....|-n2-|.........|-n3-|......
+ // 10 pixel/col...|---pixel1---||-----pixel2------|......
+ // pixel..........^0............^1.......................
+ for (int i, iClear = -1; pixel < w
+ && (i = bscol.nextSetBit(iClear + 1)) >= 0;)
+ {
+ // find the next clear bit
+ iClear = bscol.nextClearBit(i + 1);
+ // add the run length n1, n2, n3 to grow the column buffer
+ colBuffer += iClear - i; // n1, n2, etc.
+ // add columns if we have accumulated enough pixels
+
+ while (colBuffer > colsPerPixel && pixel < w)
+ {
+ colBuffer -= colsPerPixel;
+ offset += colsPerPixel;
+ a[pixel++] = i + (int) offset;
+ }
+ // set back column pointer relative to the next run
+ offset = -colBuffer;
+ }
+ // add a terminator
+ a[pixel] = -1;
+ return a;
+ }
+
+ /**
+ * The next column is either a precalculated pixel (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).
+ *
+ * When columns are hidden, this value is precalculated; otherwise it is
+ * calculated here.
*
* @param icol
* @param pixel
+ * pixel pointer into columnsToShow
* @return
*/
private int getNextCol(int icol, int pixel)
{
- return bscol.nextSetBit(
- pixelsPerCol >= 1 ? icol + 1 : (int) (pixel * colsPerPixel));
+ return (skippingColumns ? columnsToShow[pixel]
+ : bscol.nextSetBit(icol + 1));
}
+ /**
+ * Derive the next pixel from either as the given pixel (when we are skipping
+ * columns because this is a dense overview and the pixel known), or from the
+ * current column based on pixels/column. The latter is used for drawing the
+ * hidden-column mask or for overviews that have more pixels across than
+ * columns.
+ *
+ * @param icol
+ * @param pixel
+ * @return
+ */
private int getNextPixel(int icol, int pixel)
{
- return Math.min(
- pixelsPerCol >= 1 || pixel == 0
- ? Math.round(icol * pixelsPerCol)
- : pixel,
- w);
+ return Math.min(skippingColumns && pixel > 0 ? pixel
+ : Math.round(icol * pixelsPerCol), w);
}
private ActionListener listener = new ActionListener()
for (int pixelCol = 0, colNext = 0, pixelEnd = 0, len = annotations.length, icol = bscol
.nextSetBit(0); icol >= 0
- && icol < len; icol = getNextCol(icol, pixelEnd))
+ && icol < len; icol = getNextCol(icol, colNext))
{
if (redraw)
{