[30692] | 1 | /* $Id: UIImageTools.cpp 101396 2023-10-10 05:46:03Z vboxsync $ */
|
---|
| 2 | /** @file
|
---|
[52727] | 3 | * VBox Qt GUI - Implementation of utility classes and functions for image manipulation.
|
---|
[30692] | 4 | */
|
---|
| 5 |
|
---|
| 6 | /*
|
---|
[98103] | 7 | * Copyright (C) 2010-2023 Oracle and/or its affiliates.
|
---|
[30692] | 8 | *
|
---|
[96407] | 9 | * This file is part of VirtualBox base platform packages, as
|
---|
| 10 | * available from https://www.virtualbox.org.
|
---|
| 11 | *
|
---|
| 12 | * This program is free software; you can redistribute it and/or
|
---|
| 13 | * modify it under the terms of the GNU General Public License
|
---|
| 14 | * as published by the Free Software Foundation, in version 3 of the
|
---|
| 15 | * License.
|
---|
| 16 | *
|
---|
| 17 | * This program is distributed in the hope that it will be useful, but
|
---|
| 18 | * WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
| 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
| 20 | * General Public License for more details.
|
---|
| 21 | *
|
---|
| 22 | * You should have received a copy of the GNU General Public License
|
---|
| 23 | * along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
| 24 | *
|
---|
| 25 | * SPDX-License-Identifier: GPL-3.0-only
|
---|
[30692] | 26 | */
|
---|
| 27 |
|
---|
[72043] | 28 | /* Qt includes: */
|
---|
[76606] | 29 | #include <QPainter>
|
---|
[84911] | 30 | #include <QPainterPath>
|
---|
| 31 | #include <QPainterPathStroker>
|
---|
[101396] | 32 | #include <QPalette>
|
---|
[72043] | 33 |
|
---|
| 34 | /* GUI include */
|
---|
[95100] | 35 | #include "UIDesktopWidgetWatchdog.h"
|
---|
[76606] | 36 | #include "UIImageTools.h"
|
---|
[30692] | 37 |
|
---|
[72043] | 38 | /* External includes: */
|
---|
[35415] | 39 | #include <math.h>
|
---|
| 40 |
|
---|
[52730] | 41 |
|
---|
[72043] | 42 | QImage UIImageTools::toGray(const QImage &image)
|
---|
[30692] | 43 | {
|
---|
| 44 | QImage result = image.convertToFormat(QImage::Format_ARGB32);
|
---|
| 45 | for (int y = 0; y < result.height(); ++y)
|
---|
| 46 | {
|
---|
| 47 | QRgb *pScanLine = (QRgb*)result.scanLine(y);
|
---|
| 48 | for (int x = 0; x < result.width(); ++x)
|
---|
| 49 | {
|
---|
| 50 | const int g = qGray(pScanLine[x]);
|
---|
| 51 | pScanLine[x] = qRgba(g, g, g, qAlpha(pScanLine[x]));
|
---|
| 52 | }
|
---|
| 53 | }
|
---|
| 54 | return result;
|
---|
| 55 | }
|
---|
| 56 |
|
---|
[72043] | 57 | void UIImageTools::dimImage(QImage &image)
|
---|
[30692] | 58 | {
|
---|
| 59 | for (int y = 0; y < image.height(); ++y)
|
---|
| 60 | {
|
---|
[72043] | 61 | QRgb *pScanLine = (QRgb*)image.scanLine(y);
|
---|
[30692] | 62 | if (y % 2)
|
---|
| 63 | {
|
---|
| 64 | if (image.depth() == 32)
|
---|
| 65 | {
|
---|
| 66 | for (int x = 0; x < image.width(); ++x)
|
---|
| 67 | {
|
---|
[72043] | 68 | const int iGray = qGray(pScanLine[x]) / 2;
|
---|
| 69 | pScanLine[x] = qRgba(iGray, iGray, iGray, qAlpha(pScanLine[x]));
|
---|
[30692] | 70 | }
|
---|
| 71 | }
|
---|
| 72 | else
|
---|
[72043] | 73 | ::memset(pScanLine, 0, image.bytesPerLine());
|
---|
[30692] | 74 | }
|
---|
| 75 | else
|
---|
| 76 | {
|
---|
| 77 | if (image.depth() == 32)
|
---|
| 78 | {
|
---|
| 79 | for (int x = 0; x < image.width(); ++x)
|
---|
| 80 | {
|
---|
[72043] | 81 | const int iGray = (2 * qGray(pScanLine[x])) / 3;
|
---|
| 82 | pScanLine[x] = qRgba(iGray, iGray, iGray, qAlpha(pScanLine[x]));
|
---|
[30692] | 83 | }
|
---|
| 84 | }
|
---|
| 85 | }
|
---|
| 86 | }
|
---|
| 87 | }
|
---|
| 88 |
|
---|
[77701] | 89 | void UIImageTools::blurImage(const QImage &source, QImage &destination, int iRadius)
|
---|
[30692] | 90 | {
|
---|
[72043] | 91 | /* Blur in two steps, first horizontal and then vertical: */
|
---|
[30692] | 92 | QImage tmpImage(source.size(), QImage::Format_ARGB32);
|
---|
[72043] | 93 | blurImageHorizontal(source, tmpImage, iRadius);
|
---|
[77701] | 94 | blurImageVertical(tmpImage, destination, iRadius);
|
---|
[30692] | 95 | }
|
---|
| 96 |
|
---|
[77701] | 97 | void UIImageTools::blurImageHorizontal(const QImage &source, QImage &destination, int iRadius)
|
---|
[30692] | 98 | {
|
---|
| 99 | QSize s = source.size();
|
---|
| 100 | for (int y = 0; y < s.height(); ++y)
|
---|
| 101 | {
|
---|
| 102 | int rt = 0;
|
---|
| 103 | int gt = 0;
|
---|
| 104 | int bt = 0;
|
---|
| 105 | int at = 0;
|
---|
| 106 |
|
---|
| 107 | /* In the horizontal case we can just use the scanline, which is
|
---|
| 108 | * much faster than accessing every pixel with the QImage::pixel
|
---|
| 109 | * method. Unfortunately this doesn't work in the vertical case. */
|
---|
[72043] | 110 | QRgb *ssl = (QRgb*)source.scanLine(y);
|
---|
[77701] | 111 | QRgb *dsl = (QRgb*)destination.scanLine(y);
|
---|
[72043] | 112 | /* First process the horizontal zero line at once: */
|
---|
| 113 | int b = iRadius + 1;
|
---|
| 114 | for (int x1 = 0; x1 <= iRadius; ++x1)
|
---|
[30692] | 115 | {
|
---|
| 116 | QRgb rgba = ssl[x1];
|
---|
| 117 | rt += qRed(rgba);
|
---|
| 118 | gt += qGreen(rgba);
|
---|
| 119 | bt += qBlue(rgba);
|
---|
| 120 | at += qAlpha(rgba);
|
---|
| 121 | }
|
---|
[72043] | 122 | /* Set the new weighted pixel: */
|
---|
[30692] | 123 | dsl[0] = qRgba(rt / b, gt / b, bt / b, at / b);
|
---|
| 124 |
|
---|
| 125 | /* Now process the rest */
|
---|
| 126 | for (int x = 1; x < s.width(); ++x)
|
---|
| 127 | {
|
---|
[72043] | 128 | /* Subtract the pixel which fall out of our blur matrix: */
|
---|
| 129 | int x1 = x - iRadius - 1;
|
---|
[30692] | 130 | if (x1 >= 0)
|
---|
| 131 | {
|
---|
[72043] | 132 | /* Adjust the weight (necessary for the border case): */
|
---|
| 133 | --b;
|
---|
[30692] | 134 | QRgb rgba = ssl[x1];
|
---|
| 135 | rt -= qRed(rgba);
|
---|
| 136 | gt -= qGreen(rgba);
|
---|
| 137 | bt -= qBlue(rgba);
|
---|
| 138 | at -= qAlpha(rgba);
|
---|
| 139 | }
|
---|
| 140 |
|
---|
[72043] | 141 | /* Add the pixel which get into our blur matrix: */
|
---|
| 142 | int x2 = x + iRadius;
|
---|
[30692] | 143 | if (x2 < s.width())
|
---|
| 144 | {
|
---|
[72043] | 145 | /* Adjust the weight (necessary for the border case): */
|
---|
| 146 | ++b;
|
---|
[30692] | 147 | QRgb rgba = ssl[x2];
|
---|
| 148 | rt += qRed(rgba);
|
---|
| 149 | gt += qGreen(rgba);
|
---|
| 150 | bt += qBlue(rgba);
|
---|
| 151 | at += qAlpha(rgba);
|
---|
| 152 | }
|
---|
[72043] | 153 | /* Set the new weighted pixel: */
|
---|
[30692] | 154 | dsl[x] = qRgba(rt / b, gt / b, bt / b, at / b);
|
---|
| 155 | }
|
---|
| 156 | }
|
---|
| 157 | }
|
---|
[35415] | 158 |
|
---|
[77701] | 159 | void UIImageTools::blurImageVertical(const QImage &source, QImage &destination, int iRadius)
|
---|
[30692] | 160 | {
|
---|
| 161 | QSize s = source.size();
|
---|
[77701] | 162 | destination = QImage(s, source.format());
|
---|
[30692] | 163 | for (int x = 0; x < s.width(); ++x)
|
---|
| 164 | {
|
---|
| 165 | int rt = 0;
|
---|
| 166 | int gt = 0;
|
---|
| 167 | int bt = 0;
|
---|
| 168 | int at = 0;
|
---|
| 169 |
|
---|
[72043] | 170 | /* First process the vertical zero line at once: */
|
---|
| 171 | int b = iRadius + 1;
|
---|
| 172 | for (int y1 = 0; y1 <= iRadius; ++y1)
|
---|
[30692] | 173 | {
|
---|
| 174 | QRgb rgba = source.pixel(x, y1);
|
---|
| 175 | rt += qRed(rgba);
|
---|
| 176 | gt += qGreen(rgba);
|
---|
| 177 | bt += qBlue(rgba);
|
---|
| 178 | at += qAlpha(rgba);
|
---|
| 179 | }
|
---|
[72043] | 180 | /* Set the new weighted pixel: */
|
---|
[77701] | 181 | destination.setPixel(x, 0, qRgba(rt / b, gt / b, bt / b, at / b));
|
---|
[30692] | 182 |
|
---|
[72043] | 183 | /* Now process the rest: */
|
---|
[30692] | 184 | for (int y = 1; y < s.height(); ++y)
|
---|
| 185 | {
|
---|
[72043] | 186 | /* Subtract the pixel which fall out of our blur matrix: */
|
---|
| 187 | int y1 = y - iRadius - 1;
|
---|
[30692] | 188 | if (y1 >= 0)
|
---|
| 189 | {
|
---|
[72043] | 190 | --b; /* Adjust the weight (necessary for the border case): */
|
---|
[30692] | 191 | QRgb rgba = source.pixel(x, y1);
|
---|
| 192 | rt -= qRed(rgba);
|
---|
| 193 | gt -= qGreen(rgba);
|
---|
| 194 | bt -= qBlue(rgba);
|
---|
| 195 | at -= qAlpha(rgba);
|
---|
| 196 | }
|
---|
| 197 |
|
---|
[72043] | 198 | /* Add the pixel which get into our blur matrix: */
|
---|
| 199 | int y2 = y + iRadius;
|
---|
[30692] | 200 | if (y2 < s.height())
|
---|
| 201 | {
|
---|
[72043] | 202 | ++b; /* Adjust the weight (necessary for the border case): */
|
---|
[30692] | 203 | QRgb rgba = source.pixel(x, y2);
|
---|
| 204 | rt += qRed(rgba);
|
---|
| 205 | gt += qGreen(rgba);
|
---|
| 206 | bt += qBlue(rgba);
|
---|
| 207 | at += qAlpha(rgba);
|
---|
| 208 | }
|
---|
[72043] | 209 | /* Set the new weighted pixel: */
|
---|
[77701] | 210 | destination.setPixel(x, y, qRgba(rt / b, gt / b, bt / b, at / b));
|
---|
[30692] | 211 | }
|
---|
| 212 | }
|
---|
| 213 | }
|
---|
| 214 |
|
---|
[95100] | 215 | static QImage betaLabelImage(QSize size, QWidget *pHint)
|
---|
[35415] | 216 | {
|
---|
[95100] | 217 | /* Calculate device pixel ratio: */
|
---|
[97681] | 218 | const double dDpr = pHint ? UIDesktopWidgetWatchdog::devicePixelRatio(pHint) : UIDesktopWidgetWatchdog::devicePixelRatio(-1);
|
---|
[95100] | 219 | if (dDpr > 1.0)
|
---|
| 220 | size *= dDpr;
|
---|
| 221 |
|
---|
[72043] | 222 | /* Beta label: */
|
---|
[35415] | 223 | QColor bgc(246, 179, 0);
|
---|
[72043] | 224 | QImage i(size, QImage::Format_ARGB32);
|
---|
[35415] | 225 | i.fill(Qt::transparent);
|
---|
| 226 | QPainter p(&i);
|
---|
| 227 | p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
|
---|
| 228 | p.setPen(Qt::NoPen);
|
---|
[72043] | 229 |
|
---|
| 230 | /* Background: */
|
---|
[35415] | 231 | p.setBrush(bgc);
|
---|
[72043] | 232 | p.drawRect(0, 0, size.width(), size.height());
|
---|
| 233 |
|
---|
| 234 | /* The black stripes: */
|
---|
[35424] | 235 | p.setPen(QPen(QColor(70, 70, 70), 5));
|
---|
[72043] | 236 | float c = ((float)size.width() / size.height()) + 1;
|
---|
| 237 | float g = (size.width() / (c - 1));
|
---|
[35415] | 238 | for (int i = 0; i < c; ++i)
|
---|
[72043] | 239 | p.drawLine((int)(-g / 2 + g * i), size.height(), (int)(-g / 2 + g * (i + 1)), 0);
|
---|
| 240 |
|
---|
| 241 | /* The text: */
|
---|
[35415] | 242 | QFont f = p.font();
|
---|
[95100] | 243 | if (dDpr > 1.0)
|
---|
| 244 | f.setPointSize(f.pointSize() * dDpr);
|
---|
[35415] | 245 | f.setBold(true);
|
---|
| 246 | QPainterPath tp;
|
---|
| 247 | tp.addText(0, 0, f, "BETA");
|
---|
| 248 | QRectF r = tp.boundingRect();
|
---|
[72043] | 249 |
|
---|
| 250 | /* Center the text path: */
|
---|
| 251 | p.translate((size.width() - r.width()) / 2, size.height() - (size.height() - r.height()) / 2);
|
---|
[35415] | 252 | QPainterPathStroker pps;
|
---|
| 253 | QPainterPath pp = pps.createStroke(tp);
|
---|
| 254 | p.setPen(QPen(bgc.darker(80), 2, Qt::SolidLine, Qt::RoundCap));
|
---|
| 255 | p.drawPath(pp);
|
---|
| 256 | p.setBrush(Qt::black);
|
---|
| 257 | p.setPen(Qt::NoPen);
|
---|
| 258 | p.drawPath(tp);
|
---|
| 259 | p.end();
|
---|
[35424] | 260 |
|
---|
[72043] | 261 | /* Smoothing: */
|
---|
| 262 | QImage i1(size, QImage::Format_ARGB32);
|
---|
[35424] | 263 | i1.fill(Qt::transparent);
|
---|
| 264 | QPainter p1(&i1);
|
---|
| 265 | p1.setCompositionMode(QPainter::CompositionMode_Source);
|
---|
| 266 | p1.drawImage(0, 0, i);
|
---|
| 267 | p1.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
---|
[72043] | 268 | QLinearGradient lg(0, 0, size.width(), 0);
|
---|
[35424] | 269 | lg.setColorAt(0, QColor(Qt::transparent));
|
---|
| 270 | lg.setColorAt(0.20, QColor(Qt::white));
|
---|
| 271 | lg.setColorAt(0.80, QColor(Qt::white));
|
---|
| 272 | lg.setColorAt(1, QColor(Qt::transparent));
|
---|
[72043] | 273 | p1.fillRect(0, 0, size.width(), size.height(), lg);
|
---|
[35424] | 274 | p1.end();
|
---|
[95100] | 275 | if (dDpr > 1.0)
|
---|
| 276 | i1.setDevicePixelRatio(dDpr);
|
---|
[35424] | 277 |
|
---|
| 278 | return i1;
|
---|
| 279 | }
|
---|
| 280 |
|
---|
[95100] | 281 | QPixmap UIImageTools::betaLabel(const QSize &size /* = QSize(80, 16) */, QWidget *pHint /* = 0 */)
|
---|
[35424] | 282 | {
|
---|
[95100] | 283 | return QPixmap::fromImage(betaLabelImage(size, pHint));
|
---|
[35424] | 284 | }
|
---|
[101396] | 285 |
|
---|
| 286 | QColor UIImageTools::suitableForegroundColor(const QPalette &pal, const QColor &background)
|
---|
| 287 | {
|
---|
| 288 | /* Get possible foreground colors: */
|
---|
| 289 | const QColor simpleText = pal.color(QPalette::Active, QPalette::Text);
|
---|
| 290 | const QColor highlightText = pal.color(QPalette::Active, QPalette::HighlightedText);
|
---|
| 291 | QColor lightText = simpleText.black() < highlightText.black() ? simpleText : highlightText;
|
---|
| 292 | QColor darkText = simpleText.black() > highlightText.black() ? simpleText : highlightText;
|
---|
| 293 | if (lightText.black() > 128)
|
---|
| 294 | lightText = QColor(Qt::white);
|
---|
| 295 | if (darkText.black() < 128)
|
---|
| 296 | darkText = QColor(Qt::black);
|
---|
| 297 |
|
---|
| 298 | /* Measure background luminance: */
|
---|
| 299 | double dLuminance = (0.299 * background.red() + 0.587 * background.green() + 0.114 * background.blue()) / 255;
|
---|
| 300 | //printf("luminance = %f\n", dLuminance);
|
---|
| 301 |
|
---|
| 302 | /* Gather foreground color for background one: */
|
---|
| 303 | return dLuminance > 0.5 ? darkText : lightText;
|
---|
| 304 | }
|
---|