Wespal 0.5.0, and musings on KDE Frameworks

Wespal version 0.5.0 is out now! And it’s a very packed release this time around, more than even 0.4.0.

The main highlights for this release include built-in support for Krita and OpenRaster image formats (.kra, .ora) on all platforms, once again courtesy of KDE Frameworks 6; a redesigned main preview UI, a new Settings dialog, a Base64 image URI generator, and several quality-of-life changes and fixes.

Downloads for Windows, macOS, and Linux are available at the Project page or at the v0.5.0 release page on GitHub.

Note: Windows and macOS users may need to work around app signing requirements.

There are so, so many changes this time around that if you want a full list of them, it’d be best if you looked at the changelog found in the v0.5.0 release page on GitHub. To give you an idea, this was originally meant to be Wespal version 0.4.1, but I ended up cramming so many significant features into it that I decided to bump it to 0.5.0 instead.

Let’s go over a few of the more notable additions for this release, before I go into more detail about a couple of them and bore you with an extensive technical rant involving KDE.

Settings dialog in v0.5.0

  • I’ve completely redesigned the main preview UI, and implemented a new Settings dialog which merges the functionality of the old Colour Range and Palette Editor dialogs, as well as adds a few more general options.

  • The preview UI now offers four different display modes (more on this later):

    • Vertical Split (default)
    • Horizontal Split (old/classic mode)
    • Swipe
    • Onion Skin
  • Added middle-click panning, 1600% zoom, user-configurable default zoom, and an Edit menu.

  • Added support for Krita (.kra), OpenRaster (.ora), and Adobe Photoshop (.psd) files using an internal stripped-down version of KImageFormats from KDE Frameworks 6 (more on this later).

  • Added a button to generate image paths including Wesnoth Image Path Functions from the current transform preview, which will be particularly convenient when working with the new...

Base64 generation in v0.5.0

  • Added a menu action to generate Base64 data URIs for use in Wesnoth (documentation) without requiring the use of other external apps/pages.

  • Added Colour Blend ~BLEND(red, green, blue, %) and Colour Shift ~CS(red, green, blue) transform modes to complement the existing Recolour and Palette Swap modes. These are probably not too interesting for artistic purposes, but WML content creators may find it easier to tweak the parameters of these functions in the game by previewing them in Wespal first.

  • The Palette Editor and Colour Range Editor now offer the ability to create new items using predefined Wesnoth palettes/colour ranges as templates, to enable easy experimentation without copy-pasting values/lists from WML.

  • The Palette Editor now offers the option to add colours from the current (unrecoloured) image to the selected palette.

  • Colour ranges now accept the map marker (fourth) parameter used in actual Wesnoth code to represent the colour used for controlled villages and units in the minimap.

  • Palettes can be exported as GIMP Palette (.gpl) files in case you ever want to do that for... some reason.
    (It was just a fun idea that occurred to me one night while using the bathroom, don’t take it too seriously.) 😭

  • Made folder locations be remembered better and in a way that makes more sense across multiple Open or Save operations during the same session.

  • A couple of bug fixes related to Wayland. In particular, the application icon should actually be shown in the titlebar on window managers that display those, as long as Wespal is installed (make install).

  • Prevented KDE Plasma 6’s Breeze style from applying the “drag windows from all empty areas” option to Wespal’s main window until I get around to implementing a better solution (issue #13).

  • The pre-built Windows and macOS binaries are now compiled against Qt 6.7.0, enabling platform-native icons to be used in a few places where previously Wespal had no icons or used Tango alternatives.

  • I implemented a CI build system using GitHub Actions as well as a unit test suite which covers basically all of the backend functionality that is essential for Wespal’s primary mission. This way I’m guaranteed not to break things right before a release without finding out before tagging. 😄

    • (The CI build and tests run against Qt 6.4, which is the minimum required version as of this writing. This allows me to guarantee Wespal can be built on Debian stable without any hassle.)
  • Git tag names starting with this version use the format vXX.YY.ZZ, with the v in front, as opposed to without it. I’m told everyone prefers this system nowadays.


With the full list of changes out of the way, let me now focus on a couple of specific features new to this release.

This release of Wespal offers multiple display modes in order to best suit the artist or content creator’s workflow.

Vertical split mode screenshot

The original and transformed images are shown in a vertical stack, with the original on top. This is the new default mode and it’s better suited to the new UI layout than the classic Horizontal Split mode.

Horizontal split mode screenshot

The original and transformed images are shown side-by-side, with the original on the left. This was the previous mode used in Wespal versions 0.1.0 through 0.4.0.

Swipe mode screenshot

Together these comprise the “composite” preview modes. They allow you to preview image changes in a more interactive fashion, where you can move a slider to gradually reveal the transformed version over the original image. I totally did not steal this idea from GitHub’s diff viewer... 😋

I’m too lazy to provide a screen recording that shows exactly what these two look like, so I’ll just let you use your imagination or try them out for yourself. It is worth noting that they won’t be particularly useful with the Colour range or Palette swap transforms — they are best suited for checking out large-scale image changes such as the Colour blend and Colour shift transforms, as well as other future options that have a more visible effect on the image.

Krita logo

On Windows and macOS, Wespal 0.5.0 sees built-in support for the GIMP (.xcf) image format be joined by support for Krita (.kra), OpenRaster (.ora), and Adobe Photoshop (.psd) files.

Bear in mind that unlike GIMP and Photoshop, the Krita and OpenRaster formats simply have a flat rendered image representation in PNG format embedded into the file — which is in reality just a Zip archive with a predefined directory structure. This means that Krita and OpenRaster images are a no-brainer to support via image plugins, while GIMP and (especially) Photoshop are bound to suffer from image format incompatibilities, especially if you use layer effects and the like.

Note that none of this is enabled by default if you are building from source, as the expectation is that you are building on Linux or a BSD derivative that may have KDE Frameworks 6 installed, either because of KDE Plasma or KDE applications, or simply because you can easily install it yourself. In that case, you’re better off using the system’s own version of KImageFormats since it will continue to receive bug fixes while I am dead or asleep.

If you don’t have KDE Frameworks 6 installed (e.g. it is not available in your distro), don’t want to install it, or you’re just a maverick, you can enable Wespal’s own bundled KImageFormats plugins by passing -DENABLE_BUILTIN_IMAGE_PLUGINS to CMake at configuration time.

$ mkdir build && cd build
$ cmake .. -DENABLE_BUILTIN_IMAGE_PLUGINS=ON
$ make -j4
$ sudo make install

(To clarify, if you use KDE Frameworks 5, Wespal will not be able to make use of its own version of KImageFormats since it is built against Qt 5, and Wespal is not, and I do not intend to provide a Qt 5 backport of Wespal.)

Warning
Extremely dense technical talk ahead

KDE logo

While — quite ironically — adding XCF and PSD support to Wespal was incredibly simple due to the KImageFormats plugins for these two formats having no additional dependencies outside of Qt, Krita and OpenRaster proved a much trickier puzzle.

Because as I mentioned above, Krita and OpenRaster files are in reality Zip files, reading them necessitates using a Zip archive reader library, which Qt does not provide. KDE Frameworks, naturally, has a solution for this, called KArchive. The problem is that despite not depending on anything else — other than ECM, which I’ll get to later — bundling KArchive proved a much harder puzzle to crack for someone who needs static linking on Windows and macOS.

And I cannot exactly blame the KDE devs for this, after all, making static linking exceptionally hard is useful to deter both GNU LGPL license violations, and static linking itself — people have pointed out in the wake of the xz backdoor incident (which coincided with the release of Wespal 0.4.0, go figure) that static linking makes it hard, if not impossible, to handle situations where a library needs to be quickly upgraded or downgrading in response to a security incident. Dynamic linking is a good thing, actually.

However, even my attempt at dynamic linking as a stepping stone proved fruitless when I simply could not figure out how to point both KArchive and KImageFormats’s CMake recipes to KDE’s Extended CMake Modules (ECM) package on macOS, let alone Windows, at least not when providing the three Frameworks libraries as unmodified Git submodules. It seems like ECM is absolutely meant to be installed system-wide. KDE does have a way around this in order to aid developers who need to use newer versions of KDE Frameworks than their system provides, in the form of Craft. However, rather than taking Craft apart and figuring out how I could adapt its CMake environment configuration strategies, I opted for the path of least resistance, and looked at what was already sitting on my desk.

My approach with Wespal so far has been to grab only the plugins I need from KImageFormats and ignore the rest — nobody’s going to use RAW image files with Wesnoth, after all (I hope).

The Krita and OpenRaster image formats, like I said, really just revolve around a particular Zip file structure, based on the Open Document file structure. In order for KImageFormats to read an document in either format, it really just grabs the mergedimage.png within and read it like it would any other PNG.

So at the heart of the KraHandler and OraHandler classes, there is a simple read() method with a common implementation:

bool KraHandler::read(QImage *image)
{
KZip zip(device());
if (!zip.open(QIODevice::ReadOnly)) {
return false;
}
const KArchiveEntry *entry = zip.directory()->entry(QStringLiteral("mergedimage.png"));
if (!entry || !entry->isFile()) {
return false;
}
const KZipFileEntry *fileZipEntry = static_cast<const KZipFileEntry *>(entry);
image->loadFromData(fileZipEntry->data(), "PNG");
return true;
}

Almost sounds like I’m killing the magic in KDE software here, but it really is that simple. Zip goes in, PNG goes out. So, really, here you could use any API that returns a QByteArray that looks like a raw PNG file. There aren’t too many references to KArchive’s classes, so why not just, sort of... pull KArchive out... and...

QuaZip is a small Qt library that provides a QIODevice-based interface to Minizip, which uses the ubiquitous Zlib.

Because QuaZip conveniently provides a CMake recipe and static linking options, integrating it into my toolchain was dead simple. The hardest part really was tying vcpkg and CMake together in both my end and in the GitHub Actions workflow.

Having done that and had a look at the QuaZip documentation, it turns out that the changes to the aforementioned read() method are completely trivial:

bool KraHandler::read(QImage *image)
{
QuaZip zip(device());
if (!zip.open(QuaZip::mdUnzip)) {
return false;
}
zip.setCurrentFile("mergedimage.png");
if(!zip.hasCurrentFile()) {
return false;
}
QuaZipFile file(&zip);
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
image->loadFromData(file.readAll(), "PNG");
return true;
}

And that’s about it, really.

Wespal 0.5.0 on Windows 11

New in Qt 6.7, it is possible to use native platform icons on Windows and macOS by requesting Freedesktop.org-compliant icon names. Not all icons have a mapping to the native icon font, but for those that do have one, Qt 6.7 is very much a blessing. So naturally, I decided to immediately swap Qt 6.6.3 with 6.7.0 in the tooling I use for building the Windows and macOS versions of Wespal.

Now, Qt 6.7 also adds a new Windows11 QStyle engine that’s only available on, well, Windows 11. For reference, the previous style engine that’s normally used by default in Windows 10 actually dates from the Windows Vista days, and is in fact called... WindowsVista. It does not look like Windows Vista on Windows 10, it’s just called that — I assume the Microsoft API it uses hasn’t changed much since then.

The problem with the Windows11 engine is... uh... well, just look at it:

Wespal 0.5.0 with the Windows 11 style engine

Not pictured: surprisingly thin menu items attached to thicc menu bar

I honestly do not know what this engine is supposed to imitate, but it looks worse than an actual native Windows 11 application, and is in fact intended to look that way. What I can only assume is not intended is Qt’s own Colour Picker dialog being essentially unusable with it.

My best guess is that the Windows11 engine is supposed to mimic the look of “modern” Windows apps — at least from one of the hundreds of UI frameworks Microsoft has developed over the past decade — but even then I have some serious doubts about the end results. I imagine that at least the usability issues are going to be solved later down the line. The crude look-and-feel aspects of it? Not so sure.

Because there is pretty much no way in hell I want to use a Windows app that looks like this, I decided to just forcefully tell Qt to use the WindowsVista QStyle engine on startup.

#if defined(Q_OS_WINDOWS) && QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
// Avoid the icky Windows 11 style being enabled by default
QApplication::setStyle("WindowsVista");
#endif

There is a distant possibility of someone finding out that they can’t use -style Fusion on Windows anymore because of this, I suppose. I guess I will deal with it when the time comes — or just keep claiming I’ll fix it on the next release until the person gives up.


To clarify my earlier statements about KDE making it difficult to use their software in a statically-linked fashion:

I don’t consider it a strong negative point about KDE. Generally-speaking, even though I haven’t used desktop Linux for the past couple of years, I continue to hold KDE and its flagship Plasma desktop environment in very high regard, and to this day it puzzles me to see distributions constantly preference Gnome despite the latter’s inflexible policies actively hurting third-party app developers over the years. And I’d even dare liken the very rich and tightly-integrated set of tools KDE provides both to developers (Frameworks) and users (Applications) to the truest embodiment of the FOSS spirit.

I just really, really wish that KDE’s “simple by default, powerful when needed” philosophy hadn’t failed me this one particular time. Who knows, maybe I will figure it out another day, when I have more spare time in my hands.

I still have quite a lot of other things planned for Wespal, with my utmost priority right now being the implementation of a document model in order to allow opening multiple unrelated (or maybe even related) files. My second priority is improving the view implementation as well, since currently Wespal uses an impossibly clunky approach that I devised years back when I didn’t have much of a proper idea of programming at all. This approach just keeps repeatedly biting me in the rear, especially as KDE Breeze seems to have changed in some way that makes its stealing of events more aggressive than it used to be — or it could be a Wayland-induced issue, I’m not entirely sure.

Wesnoth’s art and content developers community is nowhere near as active now as it was in its heyday, but it is my hope that providing improved tools to them will at least help motivate people by having them spend less time fighting the game’s WML cache and debug console, and more time crafting and testing their creations.