1 #include "waveform/renderers/waveformrendermark.h"
2
3 #include <QDomNode>
4 #include <QPainter>
5 #include <QPainterPath>
6
7 #include "control/controlproxy.h"
8 #include "engine/controls/cuecontrol.h"
9 #include "moc_waveformrendermark.cpp"
10 #include "track/track.h"
11 #include "util/color/color.h"
12 #include "util/painterscope.h"
13 #include "waveform/renderers/waveformwidgetrenderer.h"
14 #include "waveform/waveform.h"
15 #include "widget/wimagestore.h"
16 #include "widget/wskincolor.h"
17 #include "widget/wwidget.h"
18
19 namespace {
20 const int kMaxCueLabelLength = 23;
21 } // namespace
22
WaveformRenderMark(WaveformWidgetRenderer * waveformWidgetRenderer)23 WaveformRenderMark::WaveformRenderMark(
24 WaveformWidgetRenderer* waveformWidgetRenderer) :
25 WaveformRendererAbstract(waveformWidgetRenderer) {
26 }
27
setup(const QDomNode & node,const SkinContext & context)28 void WaveformRenderMark::setup(const QDomNode& node, const SkinContext& context) {
29 WaveformSignalColors signalColors = *m_waveformRenderer->getWaveformSignalColors();
30 m_marks.setup(m_waveformRenderer->getGroup(), node, context, signalColors);
31 }
32
draw(QPainter * painter,QPaintEvent *)33 void WaveformRenderMark::draw(QPainter* painter, QPaintEvent* /*event*/) {
34 PainterScope PainterScope(painter);
35 // Maps mark objects to their positions in the widget.
36 QMap<WaveformMarkPointer, int> marksOnScreen;
37 /*
38 //DEBUG
39 for (int i = 0; i < m_markPoints.size(); i++) {
40 if (m_waveformWidget->getTrackSamples())
41 painter->drawText(40*i,12+12*(i%3),QString::number(m_markPoints[i]->get() / (double)m_waveformWidget->getTrackSamples()));
42 }
43 */
44
45 painter->setWorldMatrixEnabled(false);
46
47 for (const auto& pMark : m_marks) {
48 if (!pMark->isValid()) {
49 continue;
50 }
51
52 if (pMark->hasVisible() && !pMark->isVisible()) {
53 continue;
54 }
55
56 // Generate image on first paint can't be done in setup since we need to
57 // wait for the render widget to be resized yet.
58 if (pMark->m_image.isNull()) {
59 generateMarkImage(pMark);
60 }
61
62 double samplePosition = pMark->getSamplePosition();
63 if (samplePosition != -1.0) {
64 double currentMarkPoint =
65 m_waveformRenderer->transformSamplePositionInRendererWorld(samplePosition);
66 if (m_waveformRenderer->getOrientation() == Qt::Horizontal) {
67 // Pixmaps are expected to have the mark stroke at the center,
68 // and preferrably have an odd width in order to have the stroke
69 // exactly at the sample position.
70 const int markHalfWidth =
71 static_cast<int>(pMark->m_image.width() / 2.0 /
72 m_waveformRenderer->getDevicePixelRatio());
73
74 // Check if the current point needs to be displayed.
75 if (currentMarkPoint > -markHalfWidth && currentMarkPoint < m_waveformRenderer->getWidth() + markHalfWidth) {
76 const int drawOffset = static_cast<int>(currentMarkPoint) - markHalfWidth;
77 painter->drawImage(drawOffset, 0, pMark->m_image);
78 marksOnScreen[pMark] = drawOffset;
79 }
80 } else {
81 const int markHalfHeight = static_cast<int>(pMark->m_image.height() / 2.0);
82 if (currentMarkPoint > -markHalfHeight &&
83 currentMarkPoint < m_waveformRenderer->getHeight() +
84 markHalfHeight) {
85 const int drawOffset = static_cast<int>(currentMarkPoint) - markHalfHeight;
86 painter->drawImage(0, drawOffset, pMark->m_image);
87 marksOnScreen[pMark] = drawOffset;
88 }
89 }
90 }
91 }
92 m_waveformRenderer->setMarkPositions(marksOnScreen);
93 }
94
onResize()95 void WaveformRenderMark::onResize() {
96 // Delete all marks' images. New images will be created on next paint.
97 for (const auto& pMark : m_marks) {
98 pMark->m_image = QImage();
99 }
100 }
101
onSetTrack()102 void WaveformRenderMark::onSetTrack() {
103 slotCuesUpdated();
104
105 TrackPointer trackInfo = m_waveformRenderer->getTrackInfo();
106 if (!trackInfo) {
107 return;
108 }
109 connect(trackInfo.get(),
110 &Track::cuesUpdated,
111 this,
112 &WaveformRenderMark::slotCuesUpdated);
113 }
114
slotCuesUpdated()115 void WaveformRenderMark::slotCuesUpdated() {
116 TrackPointer trackInfo = m_waveformRenderer->getTrackInfo();
117 if (!trackInfo) {
118 return;
119 }
120
121 QList<CuePointer> loadedCues = trackInfo->getCuePoints();
122 for (const CuePointer& pCue : loadedCues) {
123 int hotCue = pCue->getHotCue();
124 if (hotCue == Cue::kNoHotCue) {
125 continue;
126 }
127
128 // Here we assume no two cues can have the same hotcue assigned,
129 // because WaveformMarkSet stores one mark for each hotcue.
130 WaveformMarkPointer pMark = m_marks.getHotCueMark(hotCue);
131 if (pMark.isNull()) {
132 continue;
133 }
134
135 QString newLabel = pCue->getLabel();
136 QColor newColor = mixxx::RgbColor::toQColor(pCue->getColor());
137 if (pMark->m_text.isNull() || newLabel != pMark->m_text ||
138 !pMark->fillColor().isValid() ||
139 newColor != pMark->fillColor()) {
140 pMark->m_text = newLabel;
141 int dimBrightThreshold = m_waveformRenderer->getDimBrightThreshold();
142 pMark->setBaseColor(newColor, dimBrightThreshold);
143 generateMarkImage(pMark);
144 }
145 }
146 }
147
generateMarkImage(WaveformMarkPointer pMark)148 void WaveformRenderMark::generateMarkImage(WaveformMarkPointer pMark) {
149 // Load the pixmap from file.
150 // If that succeeds loading the text and stroke is skipped.
151 float devicePixelRatio = m_waveformRenderer->getDevicePixelRatio();
152 if (!pMark->m_pixmapPath.isEmpty()) {
153 QString path = pMark->m_pixmapPath;
154 // Use devicePixelRatio to properly scale the image
155 QImage image = *WImageStore::getImage(path, devicePixelRatio);
156 //QImage image = QImage(path);
157 // If loading the image didn't fail, then we're done. Otherwise fall
158 // through and render a label.
159 if (!image.isNull()) {
160 pMark->m_image =
161 image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
162 //WImageStore::correctImageColors(&pMark->m_image);
163 // Set the pixel/device ratio AFTER loading the image in order to get
164 // a truely scaled source image.
165 // See https://doc.qt.io/qt-5/qimage.html#setDevicePixelRatio
166 // Also, without this some Qt-internal issue results in an offset
167 // image when calculating the center line of pixmaps in draw().
168 pMark->m_image.setDevicePixelRatio(devicePixelRatio);
169 return;
170 }
171 }
172
173 QPainter painter;
174
175 // Determine mark text.
176 QString label = pMark->m_text;
177 if (pMark->getHotCue() >= 0) {
178 if (!label.isEmpty()) {
179 label.prepend(": ");
180 }
181 label.prepend(QString::number(pMark->getHotCue() + 1));
182 if (label.size() > kMaxCueLabelLength) {
183 label = label.left(kMaxCueLabelLength - 3) + "...";
184 }
185 }
186
187 // This alone would pick the OS default font, or that set by Qt5 Settings (qt5ct)
188 // respectively. This would mostly not be notable since contemporary OS and distros
189 // use a proven sans-serif anyway. Though, some user fonts may be lacking glyphs
190 // we use for the intro/outro markers for example.
191 QFont font;
192 // So, let's just use Open Sans which is used by all official skins to achieve
193 // a consistent skin design.
194 font.setFamily("Open Sans");
195 // Use a pixel size like everywhere else in Mixxx, which can be scaled well
196 // in general.
197 // Point sizes would work if only explicit Qt scaling QT_SCALE_FACTORS is used,
198 // though as soon as other OS-based font and app scaling mechanics join the
199 // party the resulting font size is hard to predict (affects all supported OS).
200 font.setPixelSize(13);
201 font.setWeight(75); // bold
202 font.setItalic(false);
203
204 QFontMetrics metrics(font);
205
206 //fixed margin ...
207 QRect wordRect = metrics.tightBoundingRect(label);
208 const int marginX = 1;
209 const int marginY = 1;
210 wordRect.moveTop(marginX + 1);
211 wordRect.moveLeft(marginY + 1);
212 wordRect.setHeight(wordRect.height() + (wordRect.height() % 2));
213 wordRect.setWidth(wordRect.width() + (wordRect.width()) % 2);
214 //even wordrect to have an even Image >> draw the line in the middle !
215
216 int labelRectWidth = wordRect.width() + 2 * marginX + 4;
217 int labelRectHeight = wordRect.height() + 2 * marginY + 4;
218
219 QRectF labelRect(0, 0, (float)labelRectWidth, (float)labelRectHeight);
220
221 int width;
222 int height;
223
224 if (m_waveformRenderer->getOrientation() == Qt::Horizontal) {
225 width = 2 * labelRectWidth + 1;
226 height = m_waveformRenderer->getHeight();
227 } else {
228 width = m_waveformRenderer->getWidth();
229 height = 2 * labelRectHeight + 1;
230 }
231
232 pMark->m_image = QImage(
233 static_cast<int>(width * devicePixelRatio),
234 static_cast<int>(height * devicePixelRatio),
235 QImage::Format_ARGB32_Premultiplied);
236 pMark->m_image.setDevicePixelRatio(devicePixelRatio);
237
238 Qt::Alignment markAlignH = pMark->m_align & Qt::AlignHorizontal_Mask;
239 Qt::Alignment markAlignV = pMark->m_align & Qt::AlignVertical_Mask;
240
241 if (markAlignH == Qt::AlignHCenter) {
242 labelRect.moveLeft((width - labelRectWidth) / 2);
243 } else if (markAlignH == Qt::AlignRight) {
244 labelRect.moveRight(width - 1);
245 }
246
247 if (markAlignV == Qt::AlignVCenter) {
248 labelRect.moveTop((height - labelRectHeight) / 2);
249 } else if (markAlignV == Qt::AlignBottom) {
250 labelRect.moveBottom(height - 1);
251 }
252
253 pMark->m_label.setAreaRect(labelRect);
254
255 // Fill with transparent pixels
256 pMark->m_image.fill(QColor(0, 0, 0, 0).rgba());
257
258 painter.begin(&pMark->m_image);
259 painter.setRenderHint(QPainter::TextAntialiasing);
260
261 painter.setWorldMatrixEnabled(false);
262
263 // Draw marker lines
264 if (m_waveformRenderer->getOrientation() == Qt::Horizontal) {
265 int middle = width / 2;
266 pMark->m_linePosition = middle;
267 if (markAlignH == Qt::AlignHCenter) {
268 if (labelRect.top() > 0) {
269 painter.setPen(pMark->fillColor());
270 painter.drawLine(QLineF(middle, 0, middle, labelRect.top()));
271
272 painter.setPen(pMark->borderColor());
273 painter.drawLine(QLineF(middle - 1, 0, middle - 1, labelRect.top()));
274 painter.drawLine(QLineF(middle + 1, 0, middle + 1, labelRect.top()));
275 }
276
277 if (labelRect.bottom() < height) {
278 painter.setPen(pMark->fillColor());
279 painter.drawLine(QLineF(middle, labelRect.bottom(), middle, height));
280
281 painter.setPen(pMark->borderColor());
282 painter.drawLine(QLineF(middle - 1, labelRect.bottom(), middle - 1, height));
283 painter.drawLine(QLineF(middle + 1, labelRect.bottom(), middle + 1, height));
284 }
285 } else { // AlignLeft || AlignRight
286 painter.setPen(pMark->fillColor());
287 painter.drawLine(middle, 0, middle, height);
288
289 painter.setPen(pMark->borderColor());
290 painter.drawLine(middle - 1, 0, middle - 1, height);
291 painter.drawLine(middle + 1, 0, middle + 1, height);
292 }
293 } else { // Vertical
294 int middle = height / 2;
295 pMark->m_linePosition = middle;
296 if (markAlignV == Qt::AlignVCenter) {
297 if (labelRect.left() > 0) {
298 painter.setPen(pMark->fillColor());
299 painter.drawLine(QLineF(0, middle, labelRect.left(), middle));
300
301 painter.setPen(pMark->borderColor());
302 painter.drawLine(QLineF(0, middle - 1, labelRect.left(), middle - 1));
303 painter.drawLine(QLineF(0, middle + 1, labelRect.left(), middle + 1));
304 }
305
306 if (labelRect.right() < width) {
307 painter.setPen(pMark->fillColor());
308 painter.drawLine(QLineF(labelRect.right(), middle, width, middle));
309
310 painter.setPen(pMark->borderColor());
311 painter.drawLine(QLineF(labelRect.right(), middle - 1, width, middle - 1));
312 painter.drawLine(QLineF(labelRect.right(), middle + 1, width, middle + 1));
313 }
314 } else { // AlignTop || AlignBottom
315 painter.setPen(pMark->fillColor());
316 painter.drawLine(0, middle, width, middle);
317
318 painter.setPen(pMark->borderColor());
319 painter.drawLine(0, middle - 1, width, middle - 1);
320 painter.drawLine(0, middle + 1, width, middle + 1);
321 }
322 }
323
324 // Draw the label rect
325 painter.setPen(pMark->borderColor());
326 painter.setBrush(QBrush(pMark->fillColor()));
327 painter.drawRoundedRect(labelRect, 2.0, 2.0);
328
329 // Draw text
330 painter.setBrush(QBrush(QColor(0, 0, 0, 0)));
331 painter.setFont(font);
332 painter.setPen(pMark->labelColor());
333 painter.drawText(labelRect, Qt::AlignCenter, label);
334 }
335