Skip to content

Commit

Permalink
Add a class to create colorized sprite variants
Browse files Browse the repository at this point in the history
We need a method to make sprites depend on the player color (see #429). After
some research, it seems that a standard technique involves shifting pixels in
the image that have a specific hue. This is a quite simple method with very few
free parameters; one could imagine changing the saturation and lightness as
well to create "darker" or "more gray" nations.

Create a class that encapsulates all of this so it's easy to change later. It
generates colorized sprites on the fly (might create flicker as this is an
expensive operation...) and caches them for later use. The sprites are only
meant to be freed at tileset unload, so there is no way to clear the cache.

See #429.
  • Loading branch information
lmoureaux committed Jul 9, 2022
1 parent 973a3df commit 7547ac4
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 0 deletions.
1 change: 1 addition & 0 deletions client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ add_library(
climap.cpp
climisc.cpp
clinet.cpp
colorizer.cpp
colors.cpp
colors_common.cpp
connectdlg.cpp
Expand Down
66 changes: 66 additions & 0 deletions client/colorizer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2022 Louis Moureaux <[email protected]>
*
* SPDX-License-Identifier: GPLv3-or-later
*/

#include "colorizer.h"

#include <QBrush>
#include <QPainter>

namespace freeciv {

/**
* @class colorizer Swaps colors in QPixmap
*
* Starting from a base pixmap, this class generates new pixmaps with one
* color replaced (for instance, all pink pixels changed to green).
*/

/**
* Creates a colorizer that will replace every pixel of the given hue.
* Passing a negative hue disables colorization.
*/
colorizer::colorizer(const QPixmap &base, int hue_to_replace,
QObject *parent)
: QObject(parent), m_base(base), m_base_image(base.toImage()),
m_hue_to_replace(hue_to_replace)
{
}

/**
* Returns a pixmap with some pixels changed to the target color. The pixmap
* is cached for later use.
*/
const QPixmap *colorizer::pixmap(const QColor &color) const
{
// Easy cases with nothing to do
if (m_hue_to_replace < 0 || !color.isValid()) {
return &m_base;
}

// Draw it if we don't have it yet
if (m_cache.count(color.rgba()) == 0) {
auto new_hue = color.hslHue();
auto image = m_base_image.copy();

// Iterate through pixels and replace the hue
for (int x = 0; x < image.width(); ++x) {
for (int y = 0; y < image.height(); ++y) {
auto pixel = image.pixelColor(x, y);
if (pixel.hslHue() == m_hue_to_replace) {
image.setPixelColor(x, y,
QColor::fromHsl(new_hue, pixel.hslSaturation(),
pixel.lightness()));
}
}
}

m_cache[color.rgba()] = QPixmap::fromImage(image);
}

return &m_cache[color.rgba()];
}

} // namespace freeciv
37 changes: 37 additions & 0 deletions client/colorizer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2022 Louis Moureaux <[email protected]>
*
* SPDX-License-Identifier: GPLv3-or-later
*/

#pragma once

#include <QBitmap>
#include <QColor>
#include <QPixmap>

#include <map>

namespace freeciv {

class colorizer : public QObject {
Q_OBJECT

public:
explicit colorizer(const QPixmap &base, int hue_to_replace,
QObject *parent = nullptr);
virtual ~colorizer() = default;

/// Returns the base pixmap used by this colorizer
QPixmap base() const { return m_base; }

const QPixmap *pixmap(const QColor &color) const;

private:
QPixmap m_base;
QImage m_base_image;
int m_hue_to_replace;
mutable std::map<QRgb, QPixmap> m_cache;
};

} // namespace freeciv

0 comments on commit 7547ac4

Please sign in to comment.