#include "paletteutil.h" #include "log.h" #include #include PaletteUtil::PaletteUtil() { } QList PaletteUtil::parse(QString filepath, bool *error) { QFileInfo info(filepath); QString extension = info.completeSuffix(); if (extension.isNull()) { logError(QString("Failed to parse palette file '%1' because it has an unrecognized extension '%2'").arg(filepath).arg(extension)); *error = true; return QList(); } extension = extension.toLower(); if (extension == "pal") { return parsePal(filepath, error); } else if (extension == "act") { return parseAdobeColorTable(filepath, error); } else if (extension == "tpl") { return parseTileLayerPro(filepath, error); } else if (extension == "gpl") { return parseAdvancePaletteEditor(filepath, error); } else { logError(QString("Unsupported palette file. Supported formats are: .pal")); *error = true; } return QList(); } QList PaletteUtil::parsePal(QString filepath, bool *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { *error = true; logError(QString("Could not open palette file '%1': ").arg(filepath) + file.errorString()); return QList(); } QTextStream in(&file); QString firstLine = in.readLine(); if (firstLine == "JASC-PAL") { file.close(); return parseJASC(filepath, error); } else { file.close(); return parseAdvanceMapPal(filepath, error); } } QList PaletteUtil::parseJASC(QString filepath, bool *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { *error = true; logError(QString("Could not open JASC palette file '%1': ").arg(filepath) + file.errorString()); return QList(); } QTextStream in(&file); if (in.readLine() != "JASC-PAL") { *error = true; logError(QString("JASC palette file '%1' had an unexpected format. First line must be 'JASC-PAL'.").arg(filepath)); file.close(); return QList(); } if (in.readLine() != "0100") { *error = true; logError(QString("JASC palette file '%1' had an unexpected format. Second line must be '0100'.").arg(filepath)); file.close(); return QList(); } QString numColorsStr = in.readLine(); bool numOk; int numColors = numColorsStr.toInt(&numOk); if (!numOk) { *error = true; logError(QString("JASC palette file '%1' had an unexpected format. Third line must be the number of colors.").arg(filepath)); return QList(); } QList palette; QRegularExpression re("(?\\d+)\\s(?\\d+)\\s(?\\d+)"); while (!in.atEnd() && numColors > 0) { numColors--; QString line = in.readLine(); QRegularExpressionMatch match = re.match(line); if (match.hasMatch()) { QString redStr = match.captured("red"); QString greenStr = match.captured("green"); QString blueStr = match.captured("blue"); bool redOk, greenOk, blueOk; int red = redStr.toInt(&redOk); int green = greenStr.toInt(&greenOk); int blue = blueStr.toInt(&blueOk); if (!redOk || !greenOk || !blueOk) { *error = true; logError(QString("JASC palette file '%1' had an unexpected format. Invalid color '%2'.").arg(filepath).arg(line)); return QList(); } palette.append(qRgb(this->clampColorValue(red), this->clampColorValue(green), this->clampColorValue(blue))); } else { *error = true; logError(QString("JASC palette file '%1' had an unexpected format. Invalid color '%2'.").arg(filepath).arg(line)); file.close(); return QList(); } } file.close(); return palette; } QList PaletteUtil::parseAdvanceMapPal(QString filepath, bool *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { *error = true; logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString()); return QList(); } QByteArray in = file.readAll(); file.close(); if (in.length() % 4 != 0) { *error = true; logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length())); return QList(); } QList palette; int i = 0; while (i < in.length()) { unsigned char red = static_cast(in.at(i)); unsigned char green = static_cast(in.at(i + 1)); unsigned char blue = static_cast(in.at(i + 2)); palette.append(qRgb(this->clampColorValue(red), this->clampColorValue(green), this->clampColorValue(blue))); i += 4; } return palette; } QList PaletteUtil::parseAdobeColorTable(QString filepath, bool *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { *error = true; logError(QString("Could not open Adobe Color Table palette file '%1': ").arg(filepath) + file.errorString()); return QList(); } QByteArray in = file.readAll(); file.close(); if (in.length() != 0x300) { *error = true; logError(QString("Adobe Color Table palette file '%1' had an unexpected format. File's length must be exactly 768, but the length is %2.").arg(filepath).arg(in.length())); return QList(); } QList palette; int i = 0; while (i < in.length()) { unsigned char red = static_cast(in.at(i)); unsigned char green = static_cast(in.at(i + 1)); unsigned char blue = static_cast(in.at(i + 2)); palette.append(qRgb(this->clampColorValue(red), this->clampColorValue(green), this->clampColorValue(blue))); i += 3; } return palette; } QList PaletteUtil::parseTileLayerPro(QString filepath, bool *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { *error = true; logError(QString("Could not open Tile Layer Pro palette file '%1': ").arg(filepath) + file.errorString()); return QList(); } QByteArray in = file.readAll(); file.close(); if (in.length() < 4 || in.at(0) != 'T' || in.at(1) != 'L' || in.at(2) != 'P' || in.at(3) != 0) { *error = true; logError(QString("Tile Layer Pro palette file '%1' had an unexpected format. The TLP header is missing.").arg(filepath).arg(in.length())); return QList(); } if (in.length() != 0x304) { *error = true; logError(QString("Tile Layer Pro palette file '%1' had an unexpected format. File's length must be exactly 772, but the length is %2.").arg(filepath).arg(in.length())); return QList(); } QList palette; int i = 4; while (i < in.length()) { unsigned char red = static_cast(in.at(i)); unsigned char green = static_cast(in.at(i + 1)); unsigned char blue = static_cast(in.at(i + 2)); palette.append(qRgb(this->clampColorValue(red), this->clampColorValue(green), this->clampColorValue(blue))); i += 3; } return palette; } QList PaletteUtil::parseAdvancePaletteEditor(QString filepath, bool *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { *error = true; logError(QString("Could not open GPL palette file '%1': ").arg(filepath) + file.errorString()); return QList(); } QTextStream in(&file); if (in.readLine() != "[APE Palette]") { *error = true; logError(QString("GPL palette file '%1' had an unexpected format. First line must be '[APE Palette]'.").arg(filepath)); file.close(); return QList(); } QList palette; while (!in.atEnd()) { QString line = in.readLine().trimmed(); if (line.isEmpty()) { continue; } bool ok; unsigned int raw = line.toUInt(&ok); if (!ok) { *error = true; logError(QString("GPL palette file '%1' had an unexpected format. Invalid color '%2'.").arg(filepath).arg(line)); file.close(); return QList(); } raw = ((raw & 0xFF)<< 8) | ((raw >> 8) & 0xFF); int red = (raw & 0x1F) * 8; int green = ((raw >> 5) & 0x1F) * 8; int blue = ((raw >> 10) & 0x1F) * 8; palette.append(qRgb(this->clampColorValue(red), this->clampColorValue(green), this->clampColorValue(blue))); } file.close(); return palette; } void PaletteUtil::writeJASC(QString filepath, QVector palette, int offset, int nColors) { if (!nColors) { logWarn(QString("Cannot save a palette with no colors.")); return; } if (offset > palette.size() || offset + nColors > palette.size()) { logWarn("Palette offset out of range for color table."); return; } QString text = "JASC-PAL\n0100\n"; text += QString::number(nColors) + "\n"; for (int i = offset; i < offset + nColors; i++) { QRgb color = palette.at(i); text += QString::number(qRed(color)) + " " + QString::number(qGreen(color)) + " " + QString::number(qBlue(color)) + "\n"; } QFile file(filepath); if (file.open(QIODevice::WriteOnly)) { file.write(text.toUtf8()); } else { logWarn(QString("Could not write to file '%1': ").arg(filepath) + file.errorString()); } } int PaletteUtil::clampColorValue(int value) { if (value < 0) { value = 0; } if (value > 255) { value = 255; } return value; }