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