Skip to content

Fix icons that fail to convert

I wrote a little Python script (see below) to find the remaining icons that failed to convert.

Before After
Screenshot_20240521_043203 Screenshot_20240521_043048

(plus a few not shown)

It found 4 different categories:

  1. SVGs that contain paths with hard-coded colors. Those are fixed in commit 2cf058ac
  2. SVGs that were missing a semicolon here:
<style type="text/css" id="current-color-scheme">
      .ColorScheme-Text {
        color:#232629   <-- missing ;
      }
      </style>

This is missing according to the RegEx "\\.ColorScheme-(\\S+){color:(#[0-9a-fA-F]+);}". It would be possible to instead make the semicolon optional with "\\.ColorScheme-(\\S+){color:(#[0-9a-fA-F]+);?}" —or even doing that anyway, to guard against future breakage.

I just stuck to fixing the (relative) minority of SVG files that were missing this semicolon, as I'm not entirely sure about the ramifications of making that semicolon optional.

I did this by finding the pattern (#[0-9a-fA-F]{6})\n and replacing it with $1;\n, as well as (#[0-9a-fA-F]{6})\} and $1;}.

These are fixed in 457072af and c7669a8b

  1. The applet previews in applets/256. These are essentially screenshots and are correct.
  2. Intentionally dark SVGs like folder-black and flag-black

Those were all icons that failed conversion.

Here the python script, for reference. This requires the PySide6 package. It could be trivially rewritten in C++, but I don't think it provides much value as part of a testing toolchain, since this requires manual oversight for cases 3 and 4 (unless special exceptions are added for the applets/256 directory and icons with "black" in the file name). It also doesn't explicitly check for the missing semicolon, just the symptom of its absence.

from pathlib import Path
import PySide6.QtCore as Qc
import PySide6.QtGui as Qg


def get_average_color(path: Path) -> tuple[Qg.QColor, float]:
    """
    Render the svg as a pixmap and gather the average color from all
    opaque pixels.

    :param path: path to the svg.
    :return: average color and saturation.
    """
    pixel = Qg.QIcon(str(path)).pixmap(Qc.QSize(8, 8))
    image = pixel.toImage()
    colors = []
    for x in range(image.width()):
        for y in range(image.height()):
            color = image.pixelColor(x, y)
            if color.alpha() > 0:
                colors.append(color)
    if not colors:
        print(f"No opaque pixels found in {path}")
        return Qg.QColor(), 0

    r = sum(color.red() for color in colors) // len(colors)
    g = sum(color.green() for color in colors) // len(colors)
    b = sum(color.blue() for color in colors) // len(colors)
    s = sum(color.saturationF() for color in colors) / len(colors)
    return Qg.QColor(r, g, b), s


def is_color_bright_enough(color: Qg.QColor, avg_saturation: float) -> bool:
    """
    Check if the color is bright/colorful enough to be visible on a dark background.

    :param color: color to check.
    :param avg_saturation: average saturation of the color.
    :return: True if the color is bright enough.
    """
    return color.lightnessF() > 0.25 or avg_saturation > 0.5


def main():
    app = Qg.QGuiApplication()
    good_icons = []
    bad_icons = []

    path = Path("")
    for file in path.rglob("*.svg"):
        color = get_average_color(file)
        if is_color_bright_enough(*color):
            good_icons.append(file)
        else:
            bad_icons.append(file)

    print(f"Good icons: {len(good_icons)}")
    print(f"Bad icons: {len(bad_icons)}")
    print("Bad icons:")
    for icon in bad_icons:
        print(icon.resolve())


if __name__ == "__main__":
    main()
Edited by Corbin Schwimmbeck

Merge request reports