#include "tiio_sprite.h" #include "../toonz/tapp.h" #include "tsystem.h" #include "tsound.h" #include #include #include #include #include #include #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() != 0) 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::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; }