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