#include "tiio_sprite.h"
#include "tsystem.h"
#include "tsound.h"
#include <QProcess>
#include <QDir>
#include <QtGui/QImage>
#include <QStringList>
#include <QPainter>
#include <QTextStream>
#include "toonz/preferences.h"
#include "toonz/toonzfolders.h"
#include "trasterimage.h"
#include "timageinfo.h"
//===========================================================
//
// TImageWriterSprite
//
//===========================================================
class TImageWriterSprite : public TImageWriter {
public:
int m_frameIndex;
TImageWriterSprite(const TFilePath &path, int frameIndex,
TLevelWriterSprite *lwg)
: TImageWriter(path), m_frameIndex(frameIndex), m_lwg(lwg) {
m_lwg->addRef();
}
~TImageWriterSprite() { m_lwg->release(); }
bool is64bitOutputSupported() override { return false; }
void save(const TImageP &img) override { m_lwg->save(img, m_frameIndex); }
private:
TLevelWriterSprite *m_lwg;
};
//===========================================================
//
// TLevelWriterSprite;
//
//===========================================================
TLevelWriterSprite::TLevelWriterSprite(const TFilePath &path,
TPropertyGroup *winfo)
: TLevelWriter(path, winfo) {
if (!m_properties) m_properties = new Tiio::SpriteWriterProperties();
std::string scale = m_properties->getProperty("Scale")->getValueAsString();
m_scale = QString::fromStdString(scale).toInt();
std::string topPadding =
m_properties->getProperty("Top Padding")->getValueAsString();
m_topPadding = QString::fromStdString(topPadding).toInt();
std::string bottomPadding =
m_properties->getProperty("Bottom Padding")->getValueAsString();
m_bottomPadding = QString::fromStdString(bottomPadding).toInt();
std::string leftPadding =
m_properties->getProperty("Left Padding")->getValueAsString();
m_leftPadding = QString::fromStdString(leftPadding).toInt();
std::string rightPadding =
m_properties->getProperty("Right Padding")->getValueAsString();
m_rightPadding = QString::fromStdString(rightPadding).toInt();
m_format = QString::fromStdWString(
((TEnumProperty *)(m_properties->getProperty("Format")))->getValue());
TBoolProperty *trim =
(TBoolProperty *)m_properties->getProperty("Trim Empty Space");
m_trim = trim->getValue();
if (TSystem::doesExistFileOrLevel(m_path)) TSystem::deleteFile(m_path);
}
//-----------------------------------------------------------
TLevelWriterSprite::~TLevelWriterSprite() {
int finalWidth = m_right - m_left + 1;
int finalHeight = m_bottom - m_top + 1;
int resizedWidth = finalWidth * m_scale / 100;
int resizedHeight = finalHeight * m_scale / 100;
for (QImage *image : m_images) {
QImage copy = image->copy(m_left, m_top, finalWidth, finalHeight);
if (m_scale != 100) {
int width = (copy.width() * m_scale) / 100;
int height = (copy.height() * m_scale) / 100;
copy = copy.scaled(width, height);
}
m_imagesResized.push_back(copy);
}
for (QImage *image : m_images) {
delete image;
}
m_images.clear();
int horizDim = 1;
int vertDim = 1;
int vertPadding = m_topPadding + m_bottomPadding;
int horizPadding = m_leftPadding + m_rightPadding;
int totalVertPadding = 0;
int totalHorizPadding = 0;
int spriteSheetWidth;
int spriteSheetHeight;
if (m_format == "Grid") {
// Calculate Grid Size
while (horizDim * horizDim < m_imagesResized.size()) horizDim++;
totalHorizPadding = horizDim * horizPadding;
spriteSheetWidth = horizDim * resizedWidth + totalHorizPadding;
vertDim = horizDim;
// Figure out if there is one row too many
// (Such as 6 images needs 3 x 2 grid)
if (vertDim * vertDim - vertDim >= m_imagesResized.size()) {
vertDim = vertDim - 1;
}
totalVertPadding = vertDim * vertPadding;
spriteSheetHeight = vertDim * resizedHeight + totalVertPadding;
} else if (m_format == "Vertical") {
spriteSheetWidth = resizedWidth + horizPadding;
spriteSheetHeight = m_imagesResized.size() * (resizedHeight + vertPadding);
vertDim = m_imagesResized.size();
} else if (m_format == "Horizontal") {
spriteSheetWidth = m_imagesResized.size() * (resizedWidth + horizPadding);
spriteSheetHeight = resizedHeight + vertPadding;
horizDim = m_imagesResized.size();
} else if (m_format == "Individual") {
for (int i = 0; i < m_imagesResized.size(); i++) {
QString path = m_path.getQString();
QString newEnding = "_" + QString::number(i) + ".png";
path = path.replace(".spritesheet", newEnding);
m_imagesResized[i].save(path, "PNG", -1);
}
}
if (m_format != "Individual") {
QImage spriteSheet = QImage(spriteSheetWidth, spriteSheetHeight,
QImage::Format_ARGB32_Premultiplied);
spriteSheet.fill(qRgba(0, 0, 0, 0));
QPainter painter;
painter.begin(&spriteSheet);
int row = 0;
int column = 0;
int rowPadding;
int columnPadding;
int currentImage = 0;
while (row < vertDim) {
while (column < horizDim) {
rowPadding = m_topPadding;
columnPadding = m_leftPadding;
rowPadding += row * vertPadding;
columnPadding += column * horizPadding;
painter.drawImage(column * resizedWidth + columnPadding,
row * resizedHeight + rowPadding,
m_imagesResized[currentImage]);
currentImage++;
column++;
if (currentImage >= m_imagesResized.size()) break;
}
column = 0;
row++;
if (currentImage >= m_imagesResized.size()) break;
}
painter.end();
QString path = m_path.getQString();
path = path.replace(".spritesheet", ".png");
spriteSheet.save(path, "PNG", -1);
path = path.replace(".png", ".txt");
QFile file(path);
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream out(&file);
out << "Total Images: " << m_imagesResized.size() << "\n";
out << "Individual Image Width: " << resizedWidth << "\n";
out << "Individual Image Height: " << resizedHeight << "\n";
out << "Individual Image Width with Padding: "
<< resizedWidth + horizPadding << "\n";
out << "Individual Image Height with Padding: "
<< resizedHeight + vertPadding << "\n";
out << "Images Across: " << horizDim << "\n";
out << "Images Down : " << vertDim << "\n";
out << "Top Padding: " << m_topPadding << "\n";
out << "Bottom Padding: " << m_bottomPadding << "\n";
out << "Left Padding: " << m_leftPadding << "\n";
out << "Right Padding: " << m_rightPadding << "\n";
out << "Horizontal Space Between Images: " << horizPadding << "\n";
out << "Vertical Space Between Images: " << vertPadding << "\n";
file.close();
}
m_imagesResized.clear();
}
//-----------------------------------------------------------
TImageWriterP TLevelWriterSprite::getFrameWriter(TFrameId fid) {
if (!fid.getLetter().isEmpty()) return TImageWriterP(0);
int index = fid.getNumber();
TImageWriterSprite *iwg = new TImageWriterSprite(m_path, index, this);
return TImageWriterP(iwg);
}
//-----------------------------------------------------------
void TLevelWriterSprite::setFrameRate(double fps) {}
void TLevelWriterSprite::saveSoundTrack(TSoundTrack *st) {}
//-----------------------------------------------------------
void TLevelWriterSprite::save(const TImageP &img, int frameIndex) {
m_frameIndexOrder.push_back(frameIndex);
std::sort(m_frameIndexOrder.begin(), m_frameIndexOrder.end());
TRasterImageP tempImage(img);
TRasterImage *image = (TRasterImage *)tempImage->cloneImage();
m_lx = image->getRaster()->getLx();
m_ly = image->getRaster()->getLy();
int m_bpp = image->getRaster()->getPixelSize();
int totalBytes = m_lx * m_ly * m_bpp;
image->getRaster()->yMirror();
// lock raster to get data
image->getRaster()->lock();
void *buffin = image->getRaster()->getRawData();
assert(buffin);
void *buffer = malloc(totalBytes);
memcpy(buffer, buffin, totalBytes);
image->getRaster()->unlock();
// create QImage save format
QString m_intermediateFormat = "png";
QByteArray ba = m_intermediateFormat.toUpper().toLatin1();
const char *format = ba.data();
QImage *qi = new QImage((uint8_t *)buffer, m_lx, m_ly,
QImage::Format_ARGB32_Premultiplied);
int l = qi->width(), r = 0, t = qi->height(), b = 0;
if (m_trim) {
for (int y = 0; y < qi->height(); ++y) {
QRgb *row = (QRgb *)qi->scanLine(y);
bool rowFilled = false;
for (int x = 0; x < qi->width(); ++x) {
if (qAlpha(row[x])) {
rowFilled = true;
r = std::max(r, x);
if (l > x) {
l = x;
x = r;
}
}
}
if (rowFilled) {
t = std::min(t, y);
b = y;
}
}
} else {
l = 0;
r = qi->width() - 1;
t = 0;
b = qi->height() - 1;
}
if (m_firstPass) {
m_firstPass = false;
m_left = l;
m_right = r;
m_top = t;
m_bottom = b;
} else {
if (l < m_left) m_left = l;
if (r > m_right) m_right = r;
if (t < m_top) m_top = t;
if (b > m_bottom) m_bottom = b;
}
QImage *newQi = new QImage(m_lx, m_ly, QImage::Format_ARGB32_Premultiplied);
newQi->fill(qRgba(0, 0, 0, 0));
QPainter painter(newQi);
painter.drawImage(QPoint(0, 0), *qi);
// Make sure to order the images according to their frame index
// Not just what comes out first
std::vector<int>::iterator it;
it = find(m_frameIndexOrder.begin(), m_frameIndexOrder.end(), frameIndex);
int pos = std::distance(m_frameIndexOrder.begin(), it);
m_images.insert(m_images.begin() + pos, newQi);
delete image;
delete qi;
free(buffer);
}
Tiio::SpriteWriterProperties::SpriteWriterProperties()
: m_topPadding("Top Padding", 0, 100, 0)
, m_bottomPadding("Bottom Padding", 0, 100, 0)
, m_leftPadding("Left Padding", 0, 100, 0)
, m_rightPadding("Right Padding", 0, 100, 0)
, m_scale("Scale", 1, 100, 100)
, m_format("Format")
, m_trim("Trim Empty Space", true) {
m_format.addValue(L"Grid");
m_format.addValue(L"Vertical");
m_format.addValue(L"Horizontal");
m_format.addValue(L"Individual");
m_format.setValue(L"Grid");
bind(m_format);
bind(m_topPadding);
bind(m_bottomPadding);
bind(m_leftPadding);
bind(m_rightPadding);
bind(m_scale);
bind(m_trim);
}
void Tiio::SpriteWriterProperties::updateTranslation() {
m_topPadding.setQStringName(tr("Top Padding"));
m_bottomPadding.setQStringName(tr("Bottom Padding"));
m_leftPadding.setQStringName(tr("Left Padding"));
m_rightPadding.setQStringName(tr("Right Padding"));
m_scale.setQStringName(tr("Scale"));
m_format.setQStringName(tr("Format"));
m_format.setItemUIName(L"Grid", tr("Grid"));
m_format.setItemUIName(L"Vertical", tr("Vertical"));
m_format.setItemUIName(L"Horizontal", tr("Horizontal"));
m_format.setItemUIName(L"Individual", tr("Individual"));
m_trim.setQStringName(tr("Trim Empty Space"));
}
// Tiio::Reader* Tiio::makeSpriteReader(){ return nullptr; }
// Tiio::Writer* Tiio::makeSpriteWriter(){ return nullptr; }