diff --git a/ico/icon-themes.qrc b/ico/icon-themes.qrc
index 84c781a0e..ce55438b9 100644
--- a/ico/icon-themes.qrc
+++ b/ico/icon-themes.qrc
@@ -290,7 +290,6 @@
themes/qet-dark/16x16/conductor-edit.png
themes/qet-dark/16x16/configure-toolbars.png
themes/qet-dark/16x16/configure.png
- themes/qet-dark/16x16/diagram.png
themes/qet-dark/16x16/dialog-cancel.png
themes/qet-dark/16x16/dialog-ok.png
themes/qet-dark/16x16/document-export.png
@@ -331,9 +330,6 @@
themes/qet-dark/16x16/folder-open.png
themes/qet-dark/16x16/folder-show-all.png
themes/qet-dark/16x16/folder.png
- themes/qet-dark/16x16/folio-delete.png
- themes/qet-dark/16x16/folio-new.png
- themes/qet-dark/16x16/folio-properties.png
themes/qet-dark/16x16/folio-ref-coming.png
themes/qet-dark/16x16/go-company.png
themes/qet-dark/16x16/go-down-double.png
@@ -348,7 +344,6 @@
themes/qet-dark/16x16/item-copy.png
themes/qet-dark/16x16/item-move.png
themes/qet-dark/16x16/kdenlive-show-video.png
- themes/qet-dark/16x16/label.png
themes/qet-dark/16x16/list-add.png
themes/qet-dark/16x16/list-remove.png
themes/qet-dark/16x16/masquer.png
@@ -381,15 +376,7 @@
themes/qet-dark/22x22/arrow-right.png
themes/qet-dark/22x22/configure-toolbars.png
themes/qet-dark/22x22/configure.png
- themes/qet-dark/22x22/diagram.png
- themes/qet-dark/22x22/diagram_add.png
- themes/qet-dark/22x22/folio-new.png
- themes/qet-dark/22x22/diagram_bg.png
- themes/qet-dark/22x22/diagram_del.png
- themes/qet-dark/22x22/folio-delete.png
themes/qet-dark/22x22/dialog-cancel.png
- themes/qet-dark/22x22/dialog-information.png
- themes/qet-dark/22x22/folio-properties.png
themes/qet-dark/22x22/dialog-ok.png
themes/qet-dark/22x22/document-export.png
themes/qet-dark/22x22/document-import.png
@@ -425,7 +412,6 @@
themes/qet-dark/22x22/go-up.png
themes/qet-dark/22x22/grid.png
themes/qet-dark/22x22/insert-image.png
- themes/qet-dark/22x22/label.png
themes/qet-dark/22x22/landscape.png
themes/qet-dark/22x22/line.png
themes/qet-dark/22x22/list-add.png
@@ -436,7 +422,6 @@
themes/qet-dark/22x22/object-locked.png
themes/qet-dark/22x22/object-rotate-right.png
themes/qet-dark/22x22/object-unlocked.png
- themes/qet-dark/22x22/pdf-import.png
themes/qet-dark/22x22/polygon.png
themes/qet-dark/22x22/portrait.png
themes/qet-dark/22x22/preferences-desktop-user.png
@@ -472,7 +457,6 @@
themes/qet-dark/48x48/user-away.png
themes/qet-dark/48x48/view-pim-journal.png
themes/qet-dark/128x128/plasmagik.png
- themes/qet-dark/128x128/printer.png
themes/qet-dark/128x128/settings.png
themes/qet-dark/scalable/edit-opacity.svg
themes/qet-dark/scalable/image-flip-horizontal.svg
diff --git a/ico/themes/qet-dark/128x128/printer.png b/ico/themes/qet-dark/128x128/printer.png
deleted file mode 100644
index 6270960f9..000000000
Binary files a/ico/themes/qet-dark/128x128/printer.png and /dev/null differ
diff --git a/ico/themes/qet-dark/16x16/diagram.png b/ico/themes/qet-dark/16x16/diagram.png
deleted file mode 100644
index 8c9a49b99..000000000
Binary files a/ico/themes/qet-dark/16x16/diagram.png and /dev/null differ
diff --git a/ico/themes/qet-dark/16x16/folio-delete.png b/ico/themes/qet-dark/16x16/folio-delete.png
deleted file mode 100644
index f1a9f324a..000000000
Binary files a/ico/themes/qet-dark/16x16/folio-delete.png and /dev/null differ
diff --git a/ico/themes/qet-dark/16x16/folio-new.png b/ico/themes/qet-dark/16x16/folio-new.png
deleted file mode 100644
index 392a4c3ac..000000000
Binary files a/ico/themes/qet-dark/16x16/folio-new.png and /dev/null differ
diff --git a/ico/themes/qet-dark/16x16/folio-properties.png b/ico/themes/qet-dark/16x16/folio-properties.png
deleted file mode 100644
index b7d5f8091..000000000
Binary files a/ico/themes/qet-dark/16x16/folio-properties.png and /dev/null differ
diff --git a/ico/themes/qet-dark/16x16/label.png b/ico/themes/qet-dark/16x16/label.png
deleted file mode 100644
index 69668b163..000000000
Binary files a/ico/themes/qet-dark/16x16/label.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/diagram.png b/ico/themes/qet-dark/22x22/diagram.png
deleted file mode 100644
index a50d55fff..000000000
Binary files a/ico/themes/qet-dark/22x22/diagram.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/diagram_add.png b/ico/themes/qet-dark/22x22/diagram_add.png
deleted file mode 100644
index b9cd38bdc..000000000
Binary files a/ico/themes/qet-dark/22x22/diagram_add.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/diagram_bg.png b/ico/themes/qet-dark/22x22/diagram_bg.png
deleted file mode 100644
index 069b8d7eb..000000000
Binary files a/ico/themes/qet-dark/22x22/diagram_bg.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/diagram_del.png b/ico/themes/qet-dark/22x22/diagram_del.png
deleted file mode 100644
index a4c24e4b7..000000000
Binary files a/ico/themes/qet-dark/22x22/diagram_del.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/dialog-information.png b/ico/themes/qet-dark/22x22/dialog-information.png
deleted file mode 100644
index ba5792420..000000000
Binary files a/ico/themes/qet-dark/22x22/dialog-information.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/folio-delete.png b/ico/themes/qet-dark/22x22/folio-delete.png
deleted file mode 100644
index a4c24e4b7..000000000
Binary files a/ico/themes/qet-dark/22x22/folio-delete.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/folio-new.png b/ico/themes/qet-dark/22x22/folio-new.png
deleted file mode 100644
index b9cd38bdc..000000000
Binary files a/ico/themes/qet-dark/22x22/folio-new.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/folio-properties.png b/ico/themes/qet-dark/22x22/folio-properties.png
deleted file mode 100644
index ba5792420..000000000
Binary files a/ico/themes/qet-dark/22x22/folio-properties.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/label.png b/ico/themes/qet-dark/22x22/label.png
deleted file mode 100644
index 32b9b0fea..000000000
Binary files a/ico/themes/qet-dark/22x22/label.png and /dev/null differ
diff --git a/ico/themes/qet-dark/22x22/pdf-import.png b/ico/themes/qet-dark/22x22/pdf-import.png
deleted file mode 100644
index 6b4bf74f0..000000000
Binary files a/ico/themes/qet-dark/22x22/pdf-import.png and /dev/null differ
diff --git a/misc/make_icon_themes.py b/misc/make_icon_themes.py
index 48a8fb50f..ec1eca287 100644
--- a/misc/make_icon_themes.py
+++ b/misc/make_icon_themes.py
@@ -38,6 +38,13 @@ An icon counts as line art when fewer than 20% of its visible pixels are
saturated. Its dark copy keeps hue and alpha and inverts lightness, scaled
so pure black becomes (220, 220, 220), the dark palette's text color.
+An icon drawn as a light object is left alone even when it is not
+saturated: a page sheet, a printer body, the white half of the background
+swatch. Such art already reads on a dark toolbar, and inverting it would
+turn the white body black (the PDF import icon, GitHub #919). The test is
+the share of visible pixels that are near white: 30% or more and the icon
+inherits from the light theme untouched.
+
Requires Pillow. Idempotent: running it twice changes nothing.
"""
@@ -85,6 +92,8 @@ SVGS = [
]
SATURATED_FRACTION = 0.20 # at or above this an icon is "colored"
+WHITE_LIGHTNESS = 0.85 # a visible pixel this light counts as white
+WHITE_FRACTION = 0.30 # at or above this an icon is "light art"
INK = 220 / 255.0 # lightness of pure black after inversion
SVG_INK = "#dcdcdc"
@@ -107,6 +116,16 @@ def is_line_art(image):
return saturated / len(pixels) < SATURATED_FRACTION
+def has_light_fill(image):
+ """True when the icon is mostly white: a page, a sheet, a light body."""
+ pixels = visible_pixels(image)
+ if not pixels:
+ return False
+ white = sum(1 for r, g, b, _ in pixels
+ if colorsys.rgb_to_hls(r / 255, g / 255, b / 255)[1] > WHITE_LIGHTNESS)
+ return white / len(pixels) >= WHITE_FRACTION
+
+
def invert_lightness(image):
"""Invert lightness, keeping hue, saturation and alpha.
@@ -177,7 +196,7 @@ def main():
light = [] # (alias, source) pairs, paths relative to ico/
dark = [] # paths relative to ico/, files exist on disk
changed = 0
- line_art = colored = 0
+ line_art = colored = light_art = 0
for size in SIZES:
folder = ICO / size
@@ -188,7 +207,10 @@ def main():
names.append(ALIASES[rel])
image = Image.open(png).convert("RGBA")
art = is_line_art(image)
- if art:
+ if art and has_light_fill(image):
+ art = False
+ light_art += 1
+ elif art:
line_art += 1
dark_image = None
else:
@@ -213,6 +235,15 @@ def main():
changed += write_if_changed(target, text)
dark.append(f"themes/qet-dark/scalable/{name}")
+ # Drop dark files from an earlier run that are no longer generated, so
+ # a reclassified icon falls back to the light theme instead of keeping
+ # a stale copy.
+ for stale in sorted((THEMES / "qet-dark").glob("*/*")):
+ if stale.is_file() and stale.name != "index.theme" \
+ and str(stale.relative_to(ICO)) not in dark:
+ stale.unlink()
+ changed += 1
+
dirs = SIZES + ["scalable"]
changed += write_if_changed(THEMES / "qet" / "index.theme",
index_theme("qet", "QElectroTech icons", dirs))
@@ -231,8 +262,8 @@ def main():
qrc += [" ", "", ""]
changed += write_if_changed(QRC, "\n".join(qrc))
- print(f"{line_art} line-art icons, {colored} colored icons, {len(SVGS)} SVGs; "
- f"{changed} files written")
+ print(f"{line_art} line-art icons, {light_art} light icons, {colored} colored icons, "
+ f"{len(SVGS)} SVGs; {changed} files written or removed")
if __name__ == "__main__":
diff --git a/tests/qttest/tst_qeticons.cpp b/tests/qttest/tst_qeticons.cpp
index db3b95191..1825ffe72 100644
--- a/tests/qttest/tst_qeticons.cpp
+++ b/tests/qttest/tst_qeticons.cpp
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -34,11 +35,14 @@
table that uses them.
Every icon name must resolve in both themes. The dark theme's files
- must be light ink. A toolbar button painted with Fusion must show its
- icon at 3:1 (WCAG 1.4.11) in both themes, with the disabled state
- reading weaker than the enabled one, which is what was reversed on
- macOS before (GitHub #466, #870). And the icons the elements panel
- draws in its 50 px slots must stay small.
+ must read on the dark palette, and an icon drawn as a light object
+ (a page sheet, the PDF import icon of GitHub #919) must come through
+ the dark theme untouched rather than inverted into a black page. A
+ toolbar button painted with Fusion must show its icon at 3:1 (WCAG
+ 1.4.11) in both themes, with the disabled state reading weaker than
+ the enabled one, which is what was reversed on macOS before (GitHub
+ #466, #870). And the icons the elements panel draws in its 50 px
+ slots must stay small.
*/
class tst_qeticons : public QObject
{
@@ -47,7 +51,8 @@ class tst_qeticons : public QObject
private slots:
void initTestCase();
void everyIconResolvesInBothThemes();
- void darkThemeFilesAreLightInk();
+ void darkThemeFilesReadOnDarkPalette();
+ void lightIconsStayLightInDarkTheme();
void toolbarIconIsReadable_data();
void toolbarIconIsReadable();
void panelProjectIconStaysSmall();
@@ -70,18 +75,26 @@ namespace {
return names;
}
- /// Lightness of the lightest visible pixel: the ink, in a dark-theme file.
- double maxLightness(const QImage &image)
+ /**
+ Mean color of the visible pixels: what the icon reads as, taken
+ as a whole, on the background behind it. A page-shaped icon whose
+ white body was inverted keeps a light border, so the lightest
+ pixel would pass it; the mean does not.
+ */
+ QColor meanVisibleColor(const QImage &image)
{
- double max = 0.0;
+ qint64 r = 0, g = 0, b = 0, n = 0;
for (int y = 0; y < image.height(); ++y)
for (int x = 0; x < image.width(); ++x)
{
const QColor c = image.pixelColor(x, y);
- if (c.alpha() > 64) max = qMax(max, c.lightnessF());
+ if (c.alpha() <= 64) continue;
+ r += c.red(); g += c.green(); b += c.blue(); ++n;
}
- return max;
+ return n ? QColor(r / n, g / n, b / n) : QColor();
}
+
+ const QStringList kSizes = {"16x16", "22x22", "32x32", "48x48", "128x128"};
}
void tst_qeticons::initTestCase()
@@ -111,27 +124,60 @@ void tst_qeticons::everyIconResolvesInBothThemes()
QVERIFY2(!QIcon::fromTheme(name).pixmap(22).isNull(), qPrintable(name));
}
-void tst_qeticons::darkThemeFilesAreLightInk()
+/**
+ Every file the generator put in the dark theme, taken as a whole,
+ must reach 3:1 against the dark palette's window color. Inverted
+ line art passes by a wide margin; an inverted light object (a white
+ page turned black) sits below 2:1 and fails, which is the defect
+ misc/make_icon_themes.py now avoids by leaving such icons to the
+ light theme.
+*/
+void tst_qeticons::darkThemeFilesReadOnDarkPalette()
{
+ const QColor window = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Window);
int checked = 0;
- for (const QString &size : {"16x16", "22x22", "32x32"})
+ for (const QString &size : kSizes)
{
QDir dir(QString(":/ico/themes/qet-dark/%1").arg(size));
for (const QString &file : dir.entryList({"*.png"}, QDir::Files))
{
const QImage dark(dir.filePath(file));
- const QImage light(QString(":/ico/themes/qet/%1/%2").arg(size, file));
- QVERIFY2(!dark.isNull() && !light.isNull(), qPrintable(file));
- // A page-shaped icon keeps its white fill dark after inversion,
- // so the mean would mislead; what matters is that the ink is light.
- QVERIFY2(maxLightness(dark) >= 0.8,
- qPrintable(QString("%1/%2 has no light ink").arg(size, file)));
+ QVERIFY2(!dark.isNull(), qPrintable(file));
+ const QColor mean = meanVisibleColor(dark);
+ QVERIFY2(mean.isValid(), qPrintable(file));
+ const double contrast = QET::Palette::contrastRatio(mean, window);
+ QVERIFY2(contrast >= kIconRatio,
+ qPrintable(QString("%1/%2 reads %3:1 on the dark window").arg(size, file).arg(contrast)));
++checked;
}
}
QVERIFY(checked > 100);
}
+/**
+ Icons drawn as a light object are not in the dark theme; the theme
+ inherits them from "qet". Asking the dark theme for them must return
+ the light art, not a dark page (GitHub #919, the PDF import icon).
+*/
+void tst_qeticons::lightIconsStayLightInDarkTheme()
+{
+ const QColor window = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Window);
+ QIcon::setThemeName(QStringLiteral("qet-dark"));
+ for (const QString &name : {"pdf-import", "diagram", "label", "folio-new", "folio-delete",
+ "folio-properties", "diagram_bg"})
+ {
+ QVERIFY2(!QFile::exists(QString(":/ico/themes/qet-dark/22x22/%1.png").arg(name)),
+ qPrintable(QString("%1 has a dark copy; the generator inverted a light icon").arg(name)));
+ const QImage image = QIcon::fromTheme(name).pixmap(22).toImage();
+ QVERIFY2(!image.isNull(), qPrintable(name));
+ const QColor mean = meanVisibleColor(image);
+ QVERIFY2(mean.lightnessF() > 0.6,
+ qPrintable(QString("%1 comes out dark in the dark theme (lightness %2)")
+ .arg(name).arg(mean.lightnessF())));
+ QVERIFY2(QET::Palette::contrastRatio(mean, window) >= kIconRatio, qPrintable(name));
+ }
+}
+
void tst_qeticons::toolbarIconIsReadable_data()
{
QTest::addColumn("theme");