1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2020 MuseScore BVBA and others
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19
20 #include "mssplashscreen.h"
21
22 #include "musescore.h"
23 #include "libmscore/fraction.h"
24 #include "config.h"
25
26 #include <random>
27
28 #include <QDate>
29
30 namespace Ms {
31
32 const QSize MsSplashScreen::designSize { 720, 405 };
33
34 const QRectF MsSplashScreen::designDevBuildIconRect { 25.0, 51.0, 670.0, 38.0 };
35 const QRectF MsSplashScreen::designDevBuildTextRect { 25.0, 98.0, 670.0, 46.0 };
36 const QRectF MsSplashScreen::designLogotypeRect { 25.0, 153.0, 670.0, 74.0 };
37 const QRectF MsSplashScreen::designMessageTextRect { 25.0, 270.0, 670.0, 56.0 };
38 const QRectF MsSplashScreen::designMiscTextRect { 25.0, 326.0, 670.0, 56.0 };
39
40 const double MsSplashScreen::gradientDitherAmount { 0.25 };
41
42 const std::tuple<QColor, QColor> MsSplashScreen::stableBuildGradientColors { "#0885DD", "#0C70B6" };
43 const std::tuple<QColor, QColor> MsSplashScreen::unstableBuildGradientColors { "#04426E", "#063759" };
44
45 const QColor MsSplashScreen::textColor { 0xCCFFFFFF };
46
47 //---------------------------------------------------------
48 // MsSplashScreen constructor
49 //---------------------------------------------------------
50
MsSplashScreen()51 MsSplashScreen::MsSplashScreen()
52 : QSplashScreen(QPixmap(designSize))
53 , _bgImage(createBackgroundImage(width(), height(), MuseScore::unstable() ? unstableBuildGradientColors : stableBuildGradientColors))
54 , _devBuildIconRenderer(QString(":/data/maintenance.svg"), this)
55 , _miscText(QString(tr("Version %1")).arg(VERSION) + "\nwww.musescore.org")
56
57 , _devBuildIconRect(scaleSvgRect(designDevBuildIconRect, _devBuildIconRenderer))
58 , _devBuildTextRect(scaleRect(designDevBuildTextRect))
59 , _messageTextRect(scaleRect(designMessageTextRect))
60 , _miscTextRect(scaleRect(designMiscTextRect))
61 {
62 setWindowTitle("MuseScore Startup");
63
64 QDate d = QDate::currentDate();
65 if (d.day() >= 24 && d.day() <=27 && d.month() == 12) {
66 _logotypeRenderer.load(QString(":/data/musescore-logotype-c.svg"));
67 }
68 else {
69 _logotypeRenderer.load(QString(":/data/musescore-logotype.svg"));
70 }
71
72 _logotypeRect = scaleSvgRect(designLogotypeRect, _logotypeRenderer);
73
74
75 #ifdef Q_OS_MAC
76 // To have session dialog on top of splash screen on Mac.
77 setWindowFlags(Qt::FramelessWindowHint);
78 #endif
79 }
80
81 //---------------------------------------------------------
82 // drawContents
83 //---------------------------------------------------------
84
drawContents(QPainter * painter)85 void MsSplashScreen::drawContents(QPainter* painter)
86 {
87 painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
88
89 // Background gradient.
90 painter->drawImage(0, 0, _bgImage);
91
92 // Development build icon and text.
93 if (MuseScore::unstable()) {
94 _devBuildIconRenderer.render(painter, _devBuildIconRect);
95 drawDebugRect(painter, _devBuildIconRect, 0xFF, 0x80, 0x80);
96
97 painter->setPen(textColor);
98 painter->drawText(_devBuildTextRect, Qt::AlignHCenter | Qt::AlignTop, tr("Development Build"));
99 drawDebugRect(painter, _devBuildTextRect, 0xFF, 0x80, 0x80);
100 }
101
102 // MuseScore logotype.
103 _logotypeRenderer.render(painter, _logotypeRect);
104 drawDebugRect(painter, _logotypeRect, 0xFF, 0x80, 0x80);
105
106 // Status text.
107 painter->setPen(textColor);
108 painter->drawText(_messageTextRect, Qt::AlignHCenter | Qt::AlignTop, message());
109 drawDebugRect(painter, _messageTextRect, 0x80, 0xFF, 0x80);
110
111 // Miscellaneous text (version information and Web site).
112 painter->drawText(_miscTextRect, Qt::AlignRight | Qt::AlignBottom, _miscText);
113 #if defined(WIN_PORTABLE)
114 // Additional text for Windows Portable version.
115 painter->setPen(textColor);
116 painter->drawText(_miscTextRect, Qt::AlignLeft | Qt::AlignBottom, tr("Portable version") + " (portableapps.com)");
117 #endif
118 drawDebugRect(painter, _miscTextRect, 0x80, 0x80, 0xFF);
119 }
120
121 //---------------------------------------------------------
122 // scaleX
123 //---------------------------------------------------------
124
scaleX(const qreal designX) const125 qreal MsSplashScreen::scaleX(const qreal designX) const
126 {
127 return designX * width() / designSize.width();
128 }
129
130 //---------------------------------------------------------
131 // scaleY
132 //---------------------------------------------------------
133
scaleY(const qreal designY) const134 qreal MsSplashScreen::scaleY(const qreal designY) const
135 {
136 return designY * height() / designSize.height();
137 }
138
139 //---------------------------------------------------------
140 // scaleRect
141 //---------------------------------------------------------
142
scaleRect(const QRectF & designRect) const143 QRectF MsSplashScreen::scaleRect(const QRectF& designRect) const
144 {
145 return QRectF(scaleX(designRect.x()), scaleY(designRect.y()), scaleX(designRect.width()), scaleY(designRect.height()));
146 }
147
148 //---------------------------------------------------------
149 // scaleSvgRect
150 //---------------------------------------------------------
151
scaleSvgRect(const QRectF & designRect,const QSvgRenderer & renderer) const152 QRectF MsSplashScreen::scaleSvgRect(const QRectF& designRect, const QSvgRenderer& renderer) const
153 {
154 // Maximize the height of the SVG asset within the available height while maintaining the original aspect ratio.
155 const auto svgViewBox = renderer.viewBoxF();
156 const auto arCorrectedDesignWidth = designRect.height() * svgViewBox.width() / svgViewBox.height();
157
158 return scaleRect(QRectF(designRect.x() + (designRect.width() - arCorrectedDesignWidth) / 2, designRect.y(), arCorrectedDesignWidth, designRect.height()));
159 }
160
161 //---------------------------------------------------------
162 // createBackgroundImage
163 //---------------------------------------------------------
164
createBackgroundImage(const int w,const int h,const std::tuple<QColor,QColor> & gradientColors)165 QImage MsSplashScreen::createBackgroundImage(const int w, const int h, const std::tuple<QColor, QColor>& gradientColors)
166 {
167 QImage result(w, h, QImage::Format_ARGB32);
168
169 // First, create an image containing a vertical gradient (top to bottom). This image will later be rotated to the correct
170 // angle so that the gradient runs from the top-left to the bottom-right corner. The final gradient needs fill the entire
171 // image without getting truncated, so we calculate the minimum size of the rectangle that is needed to enclose the
172 // background image after being rotated to the required angle. We round up the final size to the nearest pixel to ensure that
173 // we don't cut any corners (literally).
174 const auto gradientAngle = std::atan2(h, w);
175 const auto gradientImageWidth = static_cast<int>(std::ceil(h * std::sin(gradientAngle) + w * std::cos(gradientAngle)));
176 const auto gradientImageHeight = static_cast<int>(std::ceil(2.0 * w * std::sin(gradientAngle)));
177 QImage gradientImage(gradientImageWidth, gradientImageHeight, QImage::Format_ARGB32);
178 drawVerticalGradient(gradientImage, gradientColors, gradientDitherAmount);
179
180 // Now that we have the gradient image, transfer it to the background image, rotating it around the center.
181 QPainter painter(&result);
182 painter.setRenderHints(QPainter::SmoothPixmapTransform);
183 painter.translate(0.5 * w, 0.5 * h);
184 painter.rotate(-qRadiansToDegrees(gradientAngle));
185 painter.translate(-0.5 * w, -0.5 * h);
186 painter.drawImage((w - gradientImageWidth) / 2, (h - gradientImageHeight) / 2, gradientImage);
187
188 return result;
189 }
190
191 //---------------------------------------------------------
192 // drawVerticalGradient
193 //---------------------------------------------------------
194
drawVerticalGradient(QImage & image,const std::tuple<QColor,QColor> & colors,const double ditherAmount)195 void MsSplashScreen::drawVerticalGradient(QImage& image, const std::tuple<QColor, QColor>& colors, const double ditherAmount)
196 {
197 // Although we could ask Qt to render our gradient for us, we don't, because Qt produces unsightly banding artifacts when
198 // rendering gradients of colors that are very similar to each other. This happens because 24 bits of color just aren't
199 // enough to render a smooth gradient in those cases. So instead, we render the gradient ourselves, calculating everything in
200 // floating point and then dithering. This randomly distributes the inevitable rounding errors such that the average value of
201 // all the pixels in each scan line ends up as close as possible to the correct floating-point value.
202 //
203 // Dithering eliminates the banding but introduces some visual noise. For that reason, the caller gets to specify the amount
204 // of dither to be applied (i.e., the standard deviation of the error-distribution function, expressed in pixel values from 0
205 // to 255). It's best to specify the smallest amount of dither that makes the banding unnoticeable.
206 //
207 // Note: We interpolate over the HSV color space, which produces much better results than RGB and is also convenient to use
208 // because QColor supports it natively. It would be even better to use a perceptually uniform color space such as CIELUV, but
209 // that would be both more complicated and more computationally expensive. Fortunately, for our purposes (since we happen to
210 // be rendering gradients between colors with very similar hues), HSV is more than sufficient.
211 std::default_random_engine rng;
212
213 const auto& topColor = std::get<0>(colors);
214 const auto& bottomColor = std::get<1>(colors);
215
216 for (int y = 0; y < image.height(); y++) {
217 const auto progress = static_cast<double>(y) / (image.height() - 1);
218
219 const auto h = topColor.hsvHueF() + (bottomColor.hsvHueF() - topColor.hsvHueF() ) * progress;
220 const auto s = topColor.hsvSaturationF() + (bottomColor.hsvSaturationF() - topColor.hsvSaturationF()) * progress;
221 const auto v = topColor.valueF() + (bottomColor.valueF() - topColor.valueF() ) * progress;
222
223 const auto color(QColor::fromHsvF(h, s, v));
224
225 const auto r = color.redF() * 0xFF;
226 const auto g = color.greenF() * 0xFF;
227 const auto b = color.blueF() * 0xFF;
228
229 const auto rRounded = std::round(r + 0.5);
230 const auto gRounded = std::round(g + 0.5);
231 const auto bRounded = std::round(b + 0.5);
232
233 std::normal_distribution<> rDist(r - rRounded, ditherAmount);
234 std::normal_distribution<> gDist(g - gRounded, ditherAmount);
235 std::normal_distribution<> bDist(b - bRounded, ditherAmount);
236
237 const auto scanLine = reinterpret_cast<QRgb*>(image.scanLine(y));
238 for (int x = 0; x < image.width(); x++)
239 scanLine[x] = qRgb(
240 qBound(0x00, static_cast<int>(rRounded + rDist(rng)), 0xFF),
241 qBound(0x00, static_cast<int>(gRounded + gDist(rng)), 0xFF),
242 qBound(0x00, static_cast<int>(bRounded + bDist(rng)), 0xFF)
243 );
244 }
245 }
246
247 //---------------------------------------------------------
248 // drawDebugRect
249 //---------------------------------------------------------
250
drawDebugRect(QPainter * const painter,const QRectF & rect,const int r,const int g,const int b,const int a)251 void MsSplashScreen::drawDebugRect(QPainter* const painter, const QRectF& rect, const int r, const int g, const int b, const int a)
252 {
253 // Set this to 1 to help visualize the layout.
254 #if 0
255 painter->fillRect(rect, QColor(r, g, b, a));
256 #else
257 Q_UNUSED(painter);
258 Q_UNUSED(rect);
259 Q_UNUSED(r);
260 Q_UNUSED(g);
261 Q_UNUSED(b);
262 Q_UNUSED(a);
263 #endif
264 }
265
266 }
267