Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions Plugins/LfpViewer/LfpChannelDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,15 @@ void LfpChannelDisplay::pxPaint()
jto_wholechannel = display->lfpChannelBitmap.getHeight() - 1;
};

// Each clamp above only moves one end of the span, so a channel lying
// above or below the bitmap window ends up with jto < jfrom. The bitmap
// only covers the visible viewport plus a margin, and LfpDisplay paints
// every channel that overlaps that window at all, so this is the normal
// state of the channels straddling its top and bottom edges.
const bool channelSpanIsVisible = jfrom_wholechannel <= jto_wholechannel;

// draw most recent drawn sample position
if (ito_local < display->lfpChannelBitmap.getWidth() - 1)
if (channelSpanIsVisible && ito_local < display->lfpChannelBitmap.getWidth() - 1)
{
overlayGraphics.setColour (Colours::yellow);
overlayGraphics.fillRect (ito_local + 1, jfrom_wholechannel, 1, jto_wholechannel - jfrom_wholechannel + 1); // draw yellow line
Expand Down Expand Up @@ -433,7 +440,11 @@ void LfpChannelDisplay::pxPaintHistory (int playhead, int rightEdge, int maxScre
jto_wholechannel = display->lfpChannelBitmap.getHeight() - 1;
};

if (playhead < rightEdge - 1)
// See pxPaint(): the one-sided clamps above can leave jto < jfrom for a
// channel that sits outside the bitmap window.
const bool channelSpanIsVisible = jfrom_wholechannel <= jto_wholechannel;

if (channelSpanIsVisible && playhead < rightEdge - 1)
{
overlayGraphics.setColour (Colours::yellow);
overlayGraphics.fillRect (playhead + 1, jfrom_wholechannel, 1, jto_wholechannel - jfrom_wholechannel + 1); // draw yellow line
Expand Down Expand Up @@ -698,6 +709,9 @@ void LfpChannelDisplay::pxPaintHistory (int playhead, int rightEdge, int maxScre

void LfpChannelDisplay::drawEventOverlay (const int rawEventState, int x, int yfrom, int yto, Graphics& g)
{
if (yto < yfrom)
return; // channel span lies outside the channel bitmap

float alpha = channelHeight > 5 ? 0.3f : 0.5f;
for (int ev_ch = 0; ev_ch < 8; ev_ch++)
{
Expand Down
5 changes: 3 additions & 2 deletions Plugins/LfpViewer/LfpDisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ namespace LfpViewer
Holds and draws all of the LfpDisplayChannel and lfpDisplayChannelInfo
instances.

All of the channels and channelInfos are drawn here to a "master" bitmap
lfpChannelBitmap with height equal to the sum of all channel heights. This
All of the channels and channelInfos are drawn here to a bitmap
lfpChannelBitmap covering the visible viewport plus a small vertical
margin, positioned at channelBitmapYOrigin in component coordinates. This
bitmap is drawn by the LfpViewport using Viewport::setViewedComponent.

*/
Expand Down
54 changes: 54 additions & 0 deletions Plugins/LfpViewer/Tests/LfpDisplayNodeTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

#include <stdio.h>

#include <iostream>
#include <string>

#include "gtest/gtest.h"

#include "../LfpDisplayCanvas.h"
Expand Down Expand Up @@ -602,3 +605,54 @@ TEST_F (LfpDisplayNodeLowRateTests, LowRateTraceIsConnected)

processor->stopAcquisition();
}

/*
The channel bitmap only covers the visible viewport plus a small margin, so
a tall channel stack leaves some channels hanging off its top or bottom
edge. Those channels are still painted, and their vertical extent has to be
clipped to the bitmap before it is handed to Graphics::fillRect. Passing a
negative height there trips a jassertquiet in juce_GraphicsContext.cpp,
which floods the console on every refresh of a Debug build.

Logger::outputDebugString writes to stderr, so capturing stderr around the
refresh is enough to detect the assertion.
*/
TEST_F (LfpDisplayNodeTests, OffscreenChannelsDoNotAssertOnNegativeOverlaySpan)
{
const int canvasX = 600;
const int canvasY = 400;
const int tallChannelHeight = 120; // 16 channels -> 1920px stack, far taller than the viewport

std::unique_ptr<LfpViewer::LfpDisplayCanvas> canvas =
std::make_unique<LfpViewer::LfpDisplayCanvas> (processor, LfpViewer::SplitLayouts::SINGLE, false);
canvas->updateSettings();
canvas->setSize (canvasX, canvasY);
canvas->resized();
canvas->setVisible (true);
canvas->setChannelHeight (0, tallChannelHeight);
canvas->refreshState();

processor->startAcquisition();
canvas->beginAnimation();

testing::internal::CaptureStderr();

for (int block = 0; block < 4; block++)
{
auto inputBuffer = createBufferSinusoidal (5, numChannels, 500, 125);
writeBlock (inputBuffer);
canvas->refreshState();
}

const std::string captured = testing::internal::GetCapturedStderr();

// Echo whatever was captured so a failure is still visible in the test log.
std::cerr << captured;

EXPECT_EQ (captured.find ("juce_GraphicsContext.cpp"), std::string::npos)
<< "LFP Viewer passed an out-of-range rectangle to a Graphics call while "
"painting channels that hang off the edge of the channel bitmap:\n"
<< captured;

processor->stopAcquisition();
}
Loading