Commit c16a2c45 authored by Piotr Różański's avatar Piotr Różański

#37423 better signal rendering

parent 47221975
......@@ -24,6 +24,7 @@ svarog/output.bin
# NetBeans configuration
nb-configuration.xml
nbactions.xml
nbproject/
# InteliJ
.idea
......
......@@ -4,7 +4,6 @@
package org.signalml.app.view.signal;
import static java.lang.String.format;
import static org.signalml.app.util.i18n.SvarogI18n._;
import static org.signalml.app.util.i18n.SvarogI18n._R;
......@@ -21,7 +20,6 @@
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
......@@ -59,7 +57,6 @@
import org.signalml.domain.montage.system.ChannelFunction;
import org.signalml.domain.signal.SignalProcessingChain;
import org.signalml.domain.signal.raw.RawSignalSampleSource;
import org.signalml.domain.signal.samplesource.ChangeableMultichannelSampleSource;
import org.signalml.domain.signal.samplesource.MultichannelSampleSource;
import org.signalml.domain.signal.samplesource.OriginalMultichannelSampleSource;
import org.signalml.domain.tag.StyledTagSet;
......@@ -147,7 +144,7 @@
private int blocksPerPage;
private final long firstSampleTimestamp;
private GeneralPath generalPath = new GeneralPath(GeneralPath.WIND_EVEN_ODD,50000);
private final SignalRenderer renderer = new SignalRenderer();
private SignalPlotColumnHeader signalPlotColumnHeader = null;
private SignalPlotRowHeader signalPlotRowHeader = null;
......@@ -805,7 +802,6 @@ private int computeVisibleChannelCount(int height) {
@Override
protected void paintComponent(Graphics gOrig) {
int i;
Graphics2D g = (Graphics2D)gOrig;
Rectangle clip = g.getClipBounds();
......@@ -832,7 +828,7 @@ protected void paintComponent(Graphics gOrig) {
int endBlock = (int) Math.ceil(clipEndX / pixelPerBlock);
g.setColor(Color.GRAY);
for (i=startBlock; i <= endBlock; i++) {
for (int i=startBlock; i <= endBlock; i++) {
g.drawLine((int)(i * pixelPerBlock), clip.y, (int)(i * pixelPerBlock), clipEndY);
}
}
......@@ -846,7 +842,7 @@ protected void paintComponent(Graphics gOrig) {
int endPage = (int) Math.ceil(clipEndX / pixelPerPage);
g.setColor(Color.RED);
for (i=startPage; i <= endPage; i++) {
for (int i=startPage; i <= endPage; i++) {
g.drawLine((int)(i * pixelPerPage), clip.y, (int)(i * pixelPerPage), clipEndY);
}
}
......@@ -900,11 +896,6 @@ protected void paintComponent(Graphics gOrig) {
g.setComposite(AlphaComposite.SrcOver);
}
int firstSample, lastSample, length;
double realX, x, y;
double lastX = 0;
double lastY = 0;
visibleCount = 0;
channel=startChannel;
while (visibleCount < maxNumberOfChannels && channel<channelCount) {
......@@ -915,12 +906,12 @@ protected void paintComponent(Graphics gOrig) {
visibleCount ++;
// those must be offset by one to get correct partial redraw
// offset again by one, this time in terms of samples
firstSample = (int) Math.max(0, Math.floor((clip.x-1) / timeZoomFactor) - 1);
lastSample = (int) Math.min(sampleCount[channel] - 1, Math.ceil((clipEndX+1) / timeZoomFactor) + 1);
int firstSample = (int) Math.max(0, Math.floor((clip.x-1) / timeZoomFactor) - 1);
int lastSample = (int) Math.min(sampleCount[channel] - 1, Math.ceil((clipEndX+1) / timeZoomFactor) + 1);
if (lastSample < firstSample) {
continue;
}
length = 1 + lastSample - firstSample;
int length = 1 + lastSample - firstSample;
if (samples == null || samples.length < length) {
samples = new double[length];
}
......@@ -935,118 +926,14 @@ protected void paintComponent(Graphics gOrig) {
throw ex;
}
realX = firstSample * timeZoomFactor;
double pixelPerValueForChannel= channelsPlotOptionsModel.getPixelsPerValue(channel);
y = samples[0] * pixelPerValueForChannel;
if (clamped)
{
if (y > clampLimit) {
y = channelLevel[channel] - clampLimit;
} else if (y < -clampLimit) {
y = channelLevel[channel] + clampLimit;
} else {
y = channelLevel[channel] - y;
}
} else {
y = channelLevel[channel] - y;
}
generalPath.reset();
if (!antialiased) {
x = StrictMath.floor(realX + 0.5);
y = StrictMath.floor(y + 0.5);
generalPath.moveTo(x, y);
lastX = x;
lastY = y;
} else {
generalPath.moveTo(realX, y);
}
int sampleSkip = 1;
if (optimizeSignalDisplaying) {
//optimize signal display displays at most two sample for each pixel.
sampleSkip = (int) (1/timeZoomFactor);
sampleSkip /= 2;
if (sampleSkip < 1)
sampleSkip = 1;
}
//if the user selects a piece of the signal, we shouldn't break the rule
//that we always take the sampleSkip's sample!
int startingFrom = sampleSkip - firstSample % sampleSkip;
if (firstSample % sampleSkip == 0)
startingFrom = 0;
if (optimizeSignalDisplaying) {
//in each step we want to display the same samples even though few new were added
OriginalMultichannelSampleSource source = signalChain.getSource();
if (source instanceof ChangeableMultichannelSampleSource) {
ChangeableMultichannelSampleSource changeableSource = (ChangeableMultichannelSampleSource) source;
long addedSamples = changeableSource.getAddedSamplesCount();
int changeableCorrection = 0;
if (addedSamples % sampleSkip != 0)
changeableCorrection = (int) (sampleSkip - addedSamples % sampleSkip);
startingFrom += changeableCorrection;
if (startingFrom >= sampleSkip)
startingFrom -= sampleSkip;
}
}
for (i=startingFrom; i<length; i += sampleSkip) {
y = samples[i] * pixelPerValueForChannel;
if (clamped)
{
if (y > clampLimit) {
y = channelLevel[channel] - clampLimit;
} else if (y < -clampLimit) {
y = channelLevel[channel] + clampLimit;
} else {
y = channelLevel[channel] - y;
}
} else {
y = channelLevel[channel] - y;
}
realX = ((firstSample+i) * timeZoomFactor);
if (antialiased) {
generalPath.lineTo(realX, y);
} else {
// if not antialiased then round to integer in order to prevent aliasing affects
// (which cause slave plots to display the signal slightly differently)
// expand Math.round for performance, StrictMath.floor is native
x = StrictMath.floor(realX + 0.5);
y = StrictMath.floor(y + 0.5);
if (x != lastX || y != lastY) {
generalPath.lineTo(x, y);
}
lastX = x;
lastY = y;
}
}
renderer.render(
g, channel, samples, length, firstSample, clip,
channelLevel[channel], timeZoomFactor, pixelPerValueForChannel,
clamped ? clampLimit : null
);
g.draw(generalPath);
channel++;
}
......
package org.signalml.app.view.signal;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.GeneralPath;
/**
* Class responsible for signal rendering.
* Objects of this class are instantiated and used by SignalPlot.
* This is a stateless object with respect to the render() method.
*
* @author piotr.rozanski@braintech.pl
*/
public class SignalRenderer {
private int pointCount = 0;
private Point.Double[] points;
private final GeneralPath shape = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 2048);
/**
* Perform signal rendering.
* Only the signal itself (time vs value) will be rendered,
* without any axes or additional labels.
*
* @param g graphics object (canvas) to render the signal onto
* @param channel channel number (first channel = 0)
* @param samples array of sample values
* @param sampleCount number of samples to use (sampleCount &le; samples.count)
* @param firstSample actual index of the first sample relative to the start of the signal
* @param clip clipping rectangle for rendering
* @param channelLevel vertical offset of channel in pixels
* @param xScale scale coefficient for time axis
* @param yScale scale coefficient for value axis
* @param clampLimit half of the channel height in pixels if signal is to be clamped, null otherwise
*/
public void render(Graphics2D g, int channel, double[] samples, int sampleCount, int firstSample, Rectangle clip, double channelLevel, double xScale, double yScale, Integer clampLimit) {
if (sampleCount > 2 * clip.width) {
// we have much more samples than pixels,
// so at each pixel we want to represent minimum and maximum values
setPointCount(2 * clip.width);
for (int p=0; p<clip.width; ++p) {
int x = clip.x + p;
// pixel at x corresponds to samples [i0..iN-1]
int i0 = Math.max(0, (int) Math.round((x - 0.5) / xScale) - firstSample);
int iN = Math.min(sampleCount-1, (int) Math.round((x + 0.5) / xScale) - firstSample);
double min, max;
min = max = samples[i0] * yScale;
for (int i=i0+1; i<iN; ++i) {
double sample = samples[i] * yScale;
min = Math.min(min, sample);
max = Math.max(max, sample);
}
int odd = p & 1;
int even = 1 - odd;
// if p is even, we draw a line from min to max
// if p is odd, we draw a line from max to min
points[2*p+odd].x = x;
points[2*p+odd].y = min;
points[2*p+even].x = x;
points[2*p+even].y = max;
}
} else {
// we have as much signal samples as pixels,
// so we draw a line with all samples
setPointCount(sampleCount);
for (int p=0; p<sampleCount; ++p) {
int i = p;
points[p].x = (firstSample+i) * xScale;
points[p].y = samples[i] * yScale;
}
}
// drawing line from points[]
shape.reset();
shape.moveTo(points[0].x, translateY(channelLevel, points[0].y, clampLimit));
for (int p=1; p<pointCount; ++p) {
double y = translateY(channelLevel, points[p].y, clampLimit);
shape.lineTo(points[p].x, y);
}
g.draw(shape);
}
private void setPointCount(int pointCount) {
this.pointCount = pointCount;
if (points == null || points.length < pointCount) {
points = new Point.Double[pointCount];
for (int i=0; i<pointCount; ++i) {
points[i] = new Point.Double();
}
}
}
private double translateY(double y0, double y, Integer clampLimit) {
if (clampLimit != null) {
if (y > clampLimit) {
y = clampLimit;
} else if (y < -clampLimit) {
y = -clampLimit;
}
}
return y0 - y;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment