src/colors.cpp

Fri, 01 Jul 2022 16:46:43 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Fri, 01 Jul 2022 16:46:43 +0300
changeset 312
2637134bc37c
parent 264
76a025db4948
permissions
-rw-r--r--

Fix right click to delete not really working properly
Instead of removing the point that had been added, it would remove
the point that is being drawn, which would cause it to overwrite the
previous point using the new point, causing a bit of a delay

#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