src/colors.cpp

Wed, 19 Apr 2023 22:42:43 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Wed, 19 Apr 2023 22:42:43 +0300
changeset 380
16f6717a218b
parent 264
76a025db4948
permissions
-rw-r--r--

Use QFileInfo to represent paths

#include <QRegExp>
#include <QIODevice>
#include "src/colors.h"

const ColorDefinition unknownColor{{}, {}, "Unknown", "???"};

template<typename... Args>
static QString replaced(QString text, Args&&... args)
{
	text.replace(args...);
	return text;
}

/**
 * @brief Parses an LDConfig.ldr line from a string
 * @param string LDConfig.ldr line to parse
 */
static auto loadColorFromString(const QString& string)
{
	std::optional<std::pair<ColorIndex, ColorDefinition>> result;
	static const QRegExp pattern{QStringLiteral(
		R"(^\s*0 \!COLOUR\s+([^\s]+)\s+)"
		R"(CODE\s+(\d+)\s+)"
		R"(VALUE\s+(\#[0-9a-fA-F]{3,6})\s+)"
		R"(EDGE\s+(\#[0-9a-fA-F]{3,6}))"
		R"((?:\s+ALPHA\s+(\d+))?)"
	)};
	if (pattern.indexIn(string) != -1)
	{
		const int code = pattern.cap(2).toInt();
		const QString name = pattern.cap(1);
		ColorDefinition definition = {
			.faceColor = pattern.cap(3),
			.edgeColor = pattern.cap(4),
			.name = name,
			.displayName = replaced(name, "_", " "),
		};
		if (not pattern.cap(5).isEmpty())
		{
			const int alpha = pattern.cap(5).toInt();
			definition.faceColor.setAlpha(alpha);
		}
		result = std::make_pair(ColorIndex{code}, definition);
	}
	return result;
}

/**
 * @brief Loads colors from LDConfig.ldr
 */
std::optional<ColorTable> loadColorTable(QIODevice &device, QTextStream &errors)
{
	std::optional<ColorTable> result;
	if (device.isReadable())
	{
		result.emplace();
		QTextStream stream{&device};
		QString line;
		while (stream.readLineInto(&line))
		{
			const auto pair = loadColorFromString(line);
			if (pair.has_value()) {
				(*result)[pair->first] = pair->second;
			}
		}
	}
	else
	{
		errors << "could not read colors";
	}
	return result;
}

/**
 * @brief Calculates the luma-value for the given color.
 * @details c.f. https://en.wikipedia.org/wiki/Luma_(video)
 * @param color
 * @returns luma value [0, 1]
 */
qreal luma(const QColor& color)
{
	return luma(color.redF(), color.greenF(), color.blueF());
}

//! @brief Returns a direct color index that codes the specified color value
ColorIndex directColor(const QColor& color)
{
	return directColor(color.red(), color.green(), color.blue());
}

//! @brief Returns a face color for @param color, taking direct colors into account
std::optional<QColor> colorFace(ColorIndex color, const ColorTable& colorTable)
{
	std::optional<QColor> result;
	if (isDirectColor(color)) {
		const std::array<int, 3> rgb = directColorRgb(color);
		result = QColor{rgb[0], rgb[1], rgb[2]};
	}
	else {
		const ColorDefinition* def = findInMap(colorTable, color);
		if (def != nullptr) {
			result = def->faceColor;
		}
	}
	return result;
}

//! @brief Returns an edge color for @param color, taking direct colors into account
std::optional<QColor> colorEdge(ColorIndex color, const ColorTable& colorTable)
{
	if (isDirectColor(color)) {
		const std::array<int, 3> rgb = directColorRgb(color);
		return (luma(rgb[0], rgb[1], rgb[2]) < 102) ? Qt::white : Qt::black;
	}
	else {
		return colorFace(color, colorTable);
	}
}

QDataStream& operator<<(QDataStream& stream, ColorIndex color)
{
	return stream << color.index;
}

QDataStream& operator>>(QDataStream& stream, ColorIndex& color)
{
	return stream >> color.index;
}

std::optional<QString> colorDisplayName(ColorIndex color, const ColorTable &colorTable)
{
	std::optional<QString> result;
	if (isDirectColor(color)) {
		result = colorFace(color, colorTable).value_or(QColor{}).name();
	}
	else {
		const ColorDefinition* def = findInMap(colorTable, color);
		if (def != nullptr) {
			result = def->displayName;
		}
	}
	return result;
}

mercurial