tdesktop/Telegram/SourceFiles/codegen/style/sprite_generator.cpp

181 lines
5.7 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "codegen/style/sprite_generator.h"
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtCore/QBuffer>
#include <QtGui/QPainter>
#include <QtGui/QColor>
#include <functional>
#include "codegen/style/parsed_file.h"
using Module = codegen::style::structure::Module;
using Struct = codegen::style::structure::Struct;
using Variable = codegen::style::structure::Variable;
using Tag = codegen::style::structure::TypeTag;
namespace codegen {
namespace style {
using structure::logFullName;
namespace {
constexpr int kErrorSpritesIntersect = 841;
constexpr int kErrorCouldNotGenerate = 842;
constexpr int kErrorCouldNotSerialize = 843;
constexpr int kErrorCouldNotOpen = 844;
constexpr int kErrorCouldNotWrite = 845;
} // namespace
SpriteGenerator::SpriteGenerator(const structure::Module &module, bool forceReGenerate)
: module_(module)
, forceReGenerate_(forceReGenerate)
, basePath_(QFileInfo(module.filepath()).dir().absolutePath()) {
}
bool SpriteGenerator::writeSprites() {
if (!collectSprites()) {
return false;
}
if (sprites_.isEmpty()) {
return true;
}
sprite2x_ = QImage(basePath_ + "/art/sprite_200x.png");
if (sprite2x_.isNull()) {
common::logError(common::kErrorFileNotFound, "/art/sprite_200x.png") << "sprite file was not found";
return false;
}
std::vector<int> sizes = { 5, 6 };
std::vector<const char *> postfixes = { "125", "150" };
for (int i = 0, l = sizes.size(); i < l; ++i) {
auto sprite = generateSprite(sizes[i]);
QString filepath = basePath_ + "/art/sprite_" + postfixes[i] + "x.png";
if (sprite.isNull()) {
common::logError(kErrorCouldNotGenerate, filepath) << "could not generate sprite file";
return false;
}
QByteArray spriteData;
{
QBuffer spriteBuffer(&spriteData);
if (!sprite.save(&spriteBuffer, "PNG")) {
common::logError(kErrorCouldNotSerialize, filepath) << "could not serialize sprite file";
return false;
}
}
QFile file(filepath);
if (!forceReGenerate_ && file.open(QIODevice::ReadOnly)) {
if (file.readAll() == spriteData) {
continue;
}
file.close();
}
if (!file.open(QIODevice::WriteOnly)) {
common::logError(kErrorCouldNotOpen, filepath) << "could not open sprite file for write";
return false;
}
if (file.write(spriteData) != spriteData.size()) {
common::logError(kErrorCouldNotWrite, filepath) << "could not write sprite file";
return false;
}
// Touch resource file.
filepath = basePath_ + "/telegram.qrc";
QFile qrc(filepath);
if (qrc.open(QIODevice::ReadOnly)) {
auto qrcContent = qrc.readAll();
qrc.close();
if (!qrc.open(QIODevice::WriteOnly)) {
common::logError(kErrorCouldNotOpen, filepath) << "could not open .qrc file for write";
return false;
}
if (qrc.write(qrcContent) != qrcContent.size()) {
common::logError(kErrorCouldNotWrite, filepath) << "could not write .qrc file";
return false;
}
}
}
return true;
}
bool SpriteGenerator::collectSprites() {
std::function<bool(const Variable&)> collector = [this, &collector](const Variable &variable) {
auto value = variable.value;
if (value.type().tag == Tag::Sprite) {
auto v(value.Sprite());
if (!v.width || !v.height) return true;
QRect vRect(v.left, v.top, v.width, v.height);
bool found = false;
for (auto var : sprites_) {
auto sprite = var.value.Sprite();
QRect spriteRect(sprite.left, sprite.top, sprite.width, sprite.height);
if (spriteRect == vRect) {
found = true;
} else if (spriteRect.intersects(vRect)) {
common::logError(kErrorSpritesIntersect, module_.filepath()) << "sprite '" << logFullName(variable.name) << "' intersects with '" << logFullName(var.name) << "'";
return false;
}
}
if (!found) {
sprites_.push_back(variable);
}
} else if (value.type().tag == Tag::Struct) {
auto fields = variable.value.Fields();
if (!fields) {
return false;
}
for (auto field : *fields) {
if (!collector(field.variable)) {
return false;
}
}
}
return true;
};
return module_.enumVariables(collector);
}
QImage SpriteGenerator::generateSprite(int scale) {
auto convert = [scale](int value) -> int { return structure::data::pxAdjust(value, scale); };
QImage result(convert(sprite2x_.width() / 2), convert(sprite2x_.height() / 2), sprite2x_.format());
{
QPainter p(&result);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, result.width(), result.height(), QColor(0, 0, 0, 0));
for (auto variable : sprites_) {
auto sprite = variable.value.Sprite();
auto copy = sprite2x_.copy(sprite.left * 2, sprite.top * 2, sprite.width * 2, sprite.height * 2);
copy = copy.scaled(convert(sprite.width), convert(sprite.height), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
p.drawImage(convert(sprite.left), convert(sprite.top), copy);
}
}
return result;
}
} // namespace style
} // namespace codegen