Skip to content

Commit

Permalink
Merge pull request #13969 from m0dB/mark-improvements
Browse files Browse the repository at this point in the history
mark rendering improvements
  • Loading branch information
daschuer authored Dec 18, 2024
2 parents fc59bff + 62f2a22 commit 4440d2f
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 111 deletions.
9 changes: 9 additions & 0 deletions src/util/roundtopixel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <cmath>

inline auto createFunctionRoundToPixel(float devicePixelRatio) {
return [devicePixelRatio](float pos) {
return std::round(pos * devicePixelRatio) / devicePixelRatio;
};
}
56 changes: 30 additions & 26 deletions src/waveform/renderers/allshader/digitsrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
#include <QPainterPath>
#include <cmath>

#include "./util/assert.h"
#include "util/assert.h"
#include "util/roundtopixel.h"
#include "waveform/renderers/allshader/matrixforwidgetgeometry.h"
#include "waveform/renderers/allshader/vertexdata.h"

Expand Down Expand Up @@ -85,13 +86,12 @@ void allshader::DigitsRenderer::updateTexture(
}
}

qreal space;
float space;

QFont font;
QFontMetricsF metrics{font};
font.setFamily("Open Sans");
qreal totalTextWidth;
qreal maxTextHeight;
float maxTextHeight;
bool retry = false;
do {
// At small sizes, we need to limit the pen width, to avoid drawing artifacts.
Expand All @@ -100,21 +100,19 @@ void allshader::DigitsRenderer::updateTexture(
// The pen width is twice the outline size
m_penWidth = std::min(maxPenWidth, OUTLINE_SIZE * 2);

space = static_cast<qreal>(m_penWidth) / 2;
space = static_cast<float>(m_penWidth) / 2;
font.setPointSizeF(fontPointSize);

const qreal maxHeightWithoutSpace = std::floor(maxHeight) - space * 2 - 1;
const float maxHeightWithoutSpace = std::floor(maxHeight) - space * 2 - 1;

metrics = QFontMetricsF{font};

totalTextWidth = 0;
maxTextHeight = 0;

for (int i = 0; i < NUM_CHARS; i++) {
const QString text(indexToChar(i));
const auto rect = metrics.tightBoundingRect(text);
maxTextHeight = std::max(maxTextHeight, rect.height());
totalTextWidth += metrics.horizontalAdvance(text);
maxTextHeight = std::max(maxTextHeight, static_cast<float>(rect.height()));
}
if (m_adjustedFontPointSize == 0.f && !retry && maxTextHeight > maxHeightWithoutSpace) {
// We need to adjust the font size to fit in the maxHeight.
Expand All @@ -129,13 +127,28 @@ void allshader::DigitsRenderer::updateTexture(
}
} while (retry);

m_height = static_cast<float>(std::ceil(maxTextHeight) + space * 2 + 1);
m_height = static_cast<float>(std::ceil(maxTextHeight)) + space * 2.f + 1.f;

// Space around the digits
totalTextWidth += (space * 2 + 1) * NUM_CHARS;
totalTextWidth = std::ceil(totalTextWidth);
const float y = maxTextHeight + space - 0.5f;

const qreal y = maxTextHeight + space - 0.5;
auto roundToPixel = createFunctionRoundToPixel(devicePixelRatio);

float totalTextWidth{};
std::array<float, NUM_CHARS> xs;
// determine x position and with of each of the chars in the texture image.
for (int i = 0; i < NUM_CHARS; i++) {
xs[i] = totalTextWidth;
float w = roundToPixel(static_cast<float>(
metrics.horizontalAdvance(indexToChar(i)))) +
space + space + 1.f;
totalTextWidth += w;
m_width[i] = static_cast<float>(w);
}
for (int i = 0; i < NUM_CHARS; i++) {
// position of character at index i in the texture, normalized
m_offset[i] = static_cast<float>(xs[i] / totalTextWidth);
}
m_offset[NUM_CHARS] = 1.f;

QImage image(std::lround(totalTextWidth * devicePixelRatio),
std::lround(m_height * devicePixelRatio),
Expand All @@ -153,20 +166,18 @@ void allshader::DigitsRenderer::updateTexture(
painter.setBrush(QColor(0, 0, 0, OUTLINE_ALPHA));
painter.setPen(pen);
painter.setFont(font);
qreal x = 0;
QPainterPath path;
for (int i = 0; i < NUM_CHARS; i++) {
const QString text(indexToChar(i));
path.addText(QPointF(x + space + 0.5, y), font, text);
x += metrics.horizontalAdvance(text) + space + space + 1;
path.addText(QPointF(xs[i] + space + 0.5, y), font, text);
}
painter.drawPath(path);
}

{
// Apply Gaussian blur to dark outline
auto blur = std::make_unique<QGraphicsBlurEffect>();
blur->setBlurRadius(static_cast<qreal>(m_penWidth) / 3);
blur->setBlurRadius(static_cast<float>(m_penWidth) / 3);

QGraphicsScene scene;
auto item = std::make_unique<QGraphicsPixmapItem>();
Expand All @@ -187,19 +198,12 @@ void allshader::DigitsRenderer::updateTexture(
painter.setPen(Qt::white);
painter.setBrush(Qt::white);

qreal x = 0;
QPainterPath path;
for (int i = 0; i < NUM_CHARS; i++) {
const QString text(indexToChar(i));
path.addText(QPointF(x + space + 0.5, y), font, text);
// position and width of character at index i in the texture
m_offset[i] = static_cast<float>(x / totalTextWidth);
const auto xp = x;
x += metrics.horizontalAdvance(text) + space + space + 1;
m_width[i] = static_cast<float>(x - xp);
path.addText(QPointF(xs[i] + space + 0.5, y), font, text);
}
painter.drawPath(path);
m_offset[NUM_CHARS] = 1.f;
}

m_texture.setData(image);
Expand Down
89 changes: 39 additions & 50 deletions src/waveform/renderers/allshader/waveformrendermark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "track/track.h"
#include "util/colorcomponents.h"
#include "util/roundtopixel.h"
#include "waveform/renderers/allshader/matrixforwidgetgeometry.h"
#include "waveform/renderers/allshader/rgbadata.h"
#include "waveform/renderers/allshader/vertexdata.h"
Expand All @@ -20,6 +21,8 @@
// only to draw on a QImage. This is only done once when needed and the images are
// then used as textures to be drawn with a GLSL shader.

namespace {

class TextureGraphics : public WaveformMark::Graphics {
public:
TextureGraphics(const QImage& image) {
Expand All @@ -33,19 +36,9 @@ class TextureGraphics : public WaveformMark::Graphics {
OpenGLTexture2D m_texture;
};

// Both allshader::WaveformRenderMark and the non-GL ::WaveformRenderMark derive
// from WaveformRenderMarkBase. The base-class takes care of updating the marks
// when needed and flagging them when their image needs to be updated (resizing,
// cue changes, position changes)
//
// While in the case of ::WaveformRenderMark those images can be updated immediately,
// in the case of allshader::WaveformRenderMark we need to do that when we have an
// openGL context, as we create new textures.
//
// The boolean argument for the WaveformRenderMarkBase constructor indicates
// that updateMarkImages should not be called immediately.
constexpr float kPlayPosWidth{11.f};
constexpr float kPlayPosOffset{-(kPlayPosWidth - 1.f) / 2.f};

namespace {
QString timeSecToString(double timeSec) {
int hundredths = std::lround(timeSec * 100.0);
int seconds = hundredths / 100;
Expand All @@ -58,6 +51,18 @@ QString timeSecToString(double timeSec) {

} // namespace

// Both allshader::WaveformRenderMark and the non-GL ::WaveformRenderMark derive
// from WaveformRenderMarkBase. The base-class takes care of updating the marks
// when needed and flagging them when their image needs to be updated (resizing,
// cue changes, position changes)
//
// While in the case of ::WaveformRenderMark those images can be updated immediately,
// in the case of allshader::WaveformRenderMark we need to do that when we have an
// openGL context, as we create new textures.
//
// The boolean argument for the WaveformRenderMarkBase constructor indicates
// that updateMarkImages should not be called immediately.

allshader::WaveformRenderMark::WaveformRenderMark(
WaveformWidgetRenderer* waveformWidget,
::WaveformRendererAbstract::PositionSource type)
Expand Down Expand Up @@ -204,6 +209,8 @@ void allshader::WaveformRenderMark::paintGL() {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

auto roundToPixel = createFunctionRoundToPixel(devicePixelRatio);

for (const auto& pMark : std::as_const(m_marks)) {
pMark->setBreadth(slipActive ? m_waveformRenderer->getBreadth() / 2
: m_waveformRenderer->getBreadth());
Expand Down Expand Up @@ -235,36 +242,27 @@ void allshader::WaveformRenderMark::paintGL() {
continue;
}

const float currentMarkPoint =
std::round(
static_cast<float>(
m_waveformRenderer
->transformSamplePositionInRendererWorld(
samplePosition, positionType)) *
devicePixelRatio) /
devicePixelRatio;
const float currentMarkPos = static_cast<float>(
m_waveformRenderer->transformSamplePositionInRendererWorld(
samplePosition, positionType));
if (pMark->isShowUntilNext() &&
samplePosition >= playPosition + 1.0 &&
samplePosition < nextMarkPosition) {
nextMarkPosition = samplePosition;
}
const double sampleEndPosition = pMark->getSampleEndPosition();

// Pixmaps are expected to have the mark stroke at the center,
// and preferably have an odd width in order to have the stroke
// exactly at the sample position.
const float markHalfWidth = pTexture->width() / devicePixelRatio / 2.f;
const float drawOffset = currentMarkPoint - markHalfWidth;
const float markWidth = pTexture->width() / devicePixelRatio;
const float drawOffset = currentMarkPos + pMark->getOffset();

bool visible = false;
// Check if the current point needs to be displayed.
if (drawOffset > -markHalfWidth &&
drawOffset < m_waveformRenderer->getLength() +
markHalfWidth) {
if (drawOffset > -markWidth &&
drawOffset < m_waveformRenderer->getLength()) {
drawTexture(matrix,
drawOffset,
roundToPixel(drawOffset),
!m_isSlipRenderer && slipActive
? m_waveformRenderer->getBreadth() / 2
? roundToPixel(m_waveformRenderer->getBreadth() / 2.f)
: 0,
pTexture);
visible = true;
Expand All @@ -273,19 +271,16 @@ void allshader::WaveformRenderMark::paintGL() {
// Check if the range needs to be displayed.
if (samplePosition != sampleEndPosition && sampleEndPosition != Cue::kNoPosition) {
DEBUG_ASSERT(samplePosition < sampleEndPosition);
const float currentMarkEndPoint = static_cast<
float>(
m_waveformRenderer
->transformSamplePositionInRendererWorld(
sampleEndPosition, positionType));

if (visible || currentMarkEndPoint > 0) {
const float currentMarkEndPos = static_cast<float>(
m_waveformRenderer->transformSamplePositionInRendererWorld(
sampleEndPosition, positionType));
if (visible || currentMarkEndPos > 0.f) {
QColor color = pMark->fillColor();
color.setAlphaF(0.4f);

drawMark(matrix,
QRectF(QPointF(currentMarkPoint, 0),
QPointF(currentMarkEndPoint,
QRectF(QPointF(roundToPixel(currentMarkPos), 0),
QPointF(roundToPixel(currentMarkEndPos),
m_waveformRenderer
->getBreadth())),
color);
Expand All @@ -301,24 +296,18 @@ void allshader::WaveformRenderMark::paintGL() {
}
m_waveformRenderer->setMarkPositions(marksOnScreen);

const float currentMarkPoint =
std::round(static_cast<float>(
m_waveformRenderer->getPlayMarkerPosition() *
m_waveformRenderer->getLength()) *
devicePixelRatio) /
devicePixelRatio;

const float playMarkerPos = static_cast<float>(m_waveformRenderer->getPlayMarkerPosition() *
m_waveformRenderer->getLength());
if (m_playPosMarkTexture.isStorageAllocated()) {
const float markHalfWidth = m_playPosMarkTexture.width() / devicePixelRatio / 2.f;
const float drawOffset = currentMarkPoint - markHalfWidth;
const float drawOffset = roundToPixel(playMarkerPos + kPlayPosOffset);

drawTexture(matrix, drawOffset, 0.f, &m_playPosMarkTexture);
}

if (WaveformWidgetFactory::instance()->getUntilMarkShowBeats() ||
WaveformWidgetFactory::instance()->getUntilMarkShowTime()) {
updateUntilMark(playPosition, nextMarkPosition);
drawUntilMark(matrix, currentMarkPoint + 20);
drawUntilMark(matrix, roundToPixel(playMarkerPos + 20.f));
}
}

Expand Down Expand Up @@ -395,7 +384,7 @@ void allshader::WaveformRenderMark::updatePlayPosMarkTexture() {

const float lineX = 5.5f;

imgwidth = 11.f;
imgwidth = kPlayPosWidth;
imgheight = height;

QImage image(static_cast<int>(imgwidth * devicePixelRatio),
Expand Down
Loading

0 comments on commit 4440d2f

Please sign in to comment.