// TnzCore includes #include "tsimplecolorstyles.h" #include "timage_io.h" #include "tconvert.h" #include "tvectorimage.h" #include "tpixelutils.h" #include "tsystem.h" #include "tstream.h" // Qt includes #include #include "tpalette.h" #include #include PERSIST_IDENTIFIER(TPalette, "palette") TPersistDeclarationT auxPaletteDeclaration("vectorpalette"); DEFINE_CLASS_CODE(TPalette, 30) //************************************************************************************* // Local namespace stuff //************************************************************************************* namespace { const std::string pointToString(const TColorStyle::PickedPosition &point) { if (point.frame == 0) return std::to_string(point.pos.x) + "," + std::to_string(point.pos.y); else return std::to_string(point.pos.x) + "," + std::to_string(point.pos.y) + "," + std::to_string(point.frame); } // splitting string with ',' const TColorStyle::PickedPosition stringToPoint(const std::string &string) { std::string buffer; std::stringstream ss(string); std::vector result; while (std::getline(ss, buffer, ',')) // split with comma result.push_back(buffer); int x = std::stoi(result[0]); int y = std::stoi(result[1]); int frame = 0; if (result.size() == 3) // getting the third part of string - if any. frame = std::stoi(result[2]); return {TPoint(x, y), frame}; } // convert refLevelFids to string for saving std::string fidsToString(const std::vector &fids) { std::string str; QList numList; for (const auto fid : fids) { int num = fid.getNumber(); if (numList.isEmpty() || num == numList.last() + 1) { numList.push_back(num); continue; } // print if (numList.count() == 1) str += std::to_string(numList[0]) + ","; else if (numList.count() == 2) str += std::to_string(numList[0]) + "," + std::to_string(numList[1]) + ","; else str += std::to_string(numList[0]) + "-" + std::to_string(numList.last()) + ","; numList.clear(); numList.push_back(num); } if (numList.count() == 1) str += std::to_string(numList[0]); else if (numList.count() == 2) str += std::to_string(numList[0]) + "," + std::to_string(numList[1]); else str += std::to_string(numList[0]) + "-" + std::to_string(numList.last()); return str; } // convert loaded string to refLevelFids std::vector strToFids(std::string fidsStr) { std::vector ret; QString str = QString::fromStdString(fidsStr); QStringList chunks = str.split(',', QString::SkipEmptyParts); for (const auto &chunk : chunks) { QStringList nums = chunk.split('-', QString::SkipEmptyParts); assert(nums.count() > 0 && nums.count() <= 2); if (nums.count() == 1) ret.push_back(TFrameId(nums[0].toInt())); else { // nums.count() == 2 assert(nums[0].toInt() < nums[1].toInt()); for (int i = nums[0].toInt(); i <= nums[1].toInt(); i++) ret.push_back(TFrameId(i)); } } return ret; } } // namespace //=================================================================== // // TPalette::Page // //------------------------------------------------------------------- TPalette::Page::Page(std::wstring name) : m_name(name), m_index(-1), m_palette(0) {} //------------------------------------------------------------------- TColorStyle *TPalette::Page::getStyle(int index) const { assert(m_palette); if (0 <= index && index < getStyleCount()) return m_palette->getStyle(m_styleIds[index]); else return 0; } //------------------------------------------------------------------- int TPalette::Page::getStyleId(int index) const { assert(m_palette); if (0 <= index && index < getStyleCount()) return m_styleIds[index]; else return -1; } //------------------------------------------------------------------- int TPalette::Page::addStyle(int styleId) { assert(m_palette); if (styleId < 0 || styleId >= m_palette->getStyleCount()) return -1; if (m_palette->m_styles[styleId].first != 0) return -1; m_palette->m_styles[styleId].first = this; int indexInPage = int(m_styleIds.size()); m_styleIds.push_back(styleId); return indexInPage; } //------------------------------------------------------------------- int TPalette::Page::addStyle(TColorStyle *style) { assert(m_palette); int stylesCount = int(m_palette->m_styles.size()); int styleId; for (styleId = 0; styleId < stylesCount; styleId++) if (m_palette->m_styles[styleId].first == 0) break; if (styleId >= stylesCount - 1) return addStyle(m_palette->addStyle(style)); m_palette->setStyle(styleId, style); return addStyle(styleId); } //------------------------------------------------------------------- int TPalette::Page::addStyle(TPixel32 color) { return addStyle(new TSolidColorStyle(color)); } //------------------------------------------------------------------- void TPalette::Page::insertStyle(int indexInPage, int styleId) { assert(m_palette); if (styleId < 0 || styleId >= m_palette->getStyleCount()) return; if (m_palette->m_styles[styleId].first != 0) return; m_palette->m_styles[styleId].first = this; if (indexInPage < 0) indexInPage = 0; else if (indexInPage > getStyleCount()) indexInPage = getStyleCount(); m_styleIds.insert(m_styleIds.begin() + indexInPage, styleId); } //------------------------------------------------------------------- void TPalette::Page::insertStyle(int indexInPage, TColorStyle *style) { assert(m_palette); int styleId = m_palette->addStyle(style); if (styleId >= 0) insertStyle(indexInPage, styleId); } //------------------------------------------------------------------- void TPalette::Page::insertStyle(int indexInPage, TPixel32 color) { assert(m_palette); int styleId = m_palette->addStyle(color); if (styleId >= 0) insertStyle(indexInPage, styleId); } //------------------------------------------------------------------- void TPalette::Page::removeStyle(int indexInPage) { if (indexInPage < 0 || indexInPage >= getStyleCount()) return; assert(m_palette); int styleId = getStyleId(indexInPage); assert(0 <= styleId && styleId < m_palette->getStyleCount()); assert(m_palette->m_styles[styleId].first == this); m_palette->m_styles[styleId].first = 0; m_styleIds.erase(m_styleIds.begin() + indexInPage); } //------------------------------------------------------------------- int TPalette::Page::search(int styleId) const { std::vector::const_iterator it = std::find(m_styleIds.begin(), m_styleIds.end(), styleId); if (it == m_styleIds.end()) return -1; else return it - m_styleIds.begin(); } //------------------------------------------------------------------- int TPalette::Page::search(TColorStyle *style) const { assert(style); assert(m_palette); for (int i = 0; i < getStyleCount(); i++) if (m_palette->getStyle(m_styleIds[i]) == style) return i; return -1; } //=================================================================== // // TPalette // //------------------------------------------------------------------- TPalette::TPalette() : m_version(0) , m_isCleanupPalette(false) , m_currentFrame(-1) , m_dirtyFlag(false) , m_mutex(QMutex::Recursive) , m_isLocked(false) , m_askOverwriteFlag(false) , m_shortcutScopeIndex(0) , m_currentStyleId(1) { QString tempName(QObject::tr("colors")); std::wstring pageName = tempName.toStdWString(); Page *page = addPage(pageName); page->addStyle(TPixel32(255, 255, 255, 0)); page->addStyle(TPixel32(0, 0, 0, 255)); getStyle(0)->setName(L"color_0"); getStyle(1)->setName(L"color_1"); for (int i = 0; i < 10; i++) m_shortcuts['0' + i] = i; } //------------------------------------------------------------------- TPalette::~TPalette() { std::set table; int i = 0; for (i = 0; i < getStyleCount(); i++) { assert(table.find(getStyle(i)) == table.end()); table.insert(getStyle(i)); } clearPointerContainer(m_pages); } //------------------------------------------------------------------- TPalette *TPalette::clone() const { TPalette *palette = new TPalette; palette->assign(this); return palette; } //------------------------------------------------------------------- TColorStyle *TPalette::getStyle(int index) const { if (0 <= index && index < getStyleCount()) return m_styles[index].second.getPointer(); else { static TSolidColorStyle *ss = new TSolidColorStyle(TPixel32::Red); ss->addRef(); return ss; } } //------------------------------------------------------------------- int TPalette::getStyleInPagesCount() const { int styleInPagesCount = 0; for (int i = 0; i < getStyleCount(); i++) if (m_styles[i].first != 0) styleInPagesCount++; return styleInPagesCount; } //------------------------------------------------------------------- int TPalette::getFirstUnpagedStyle() const { for (int i = 0; i < getStyleCount(); i++) if (m_styles[i].first == 0) return i; return -1; } //------------------------------------------------------------------- /*! Adding style with new styleId. Even if there are deleted styles in the * palette, the new style will be appended to the end of the list. */ int TPalette::addStyle(TColorStyle *style) { // limit the number of cleanup style to 7 if (isCleanupPalette() && getStyleInPagesCount() >= 8) return -1; int styleId = int(m_styles.size()); if (styleId < 4096) { // checking if the style is overlapped int i = 0; for (i = 0; i < styleId; i++) if (getStyle(i) == style) break; if (i == styleId) { m_styles.push_back(std::make_pair((Page *)0, style)); return styleId; } } delete style; return -1; } //------------------------------------------------------------------- int TPalette::addStyle(const TPixel32 &color) { return addStyle(new TSolidColorStyle(color)); } //------------------------------------------------------------------- void TPalette::setStyle(int styleId, TColorStyle *style) { std::unique_ptr styleOwner(style); int styleCount = getStyleCount(); if (0 <= styleId && styleId < styleCount) { // Find out if the supplied style is already in the palette // with a different style id. In that case, bail out as a noop. for (int i = 0; i < styleCount; ++i) if (style == getStyle(i)) return; // Substitution can take place if (typeid(*m_styles[styleId].second.getPointer()) != typeid(*style)) m_styleAnimationTable.erase(styleId); m_styles[styleId].second = styleOwner.release(); } } //------------------------------------------------------------------- void TPalette::setStyle(int styleId, const TPixelRGBM32 &color) { setStyle(styleId, new TSolidColorStyle(color)); } //------------------------------------------------------------------- int TPalette::getPageCount() const { return int(m_pages.size()); } //------------------------------------------------------------------- TPalette::Page *TPalette::getPage(int pageIndex) { if (0 <= pageIndex && pageIndex < getPageCount()) { Page *page = m_pages[pageIndex]; assert(page->getIndex() == pageIndex); assert(page->m_palette == this); return page; } else return 0; } //------------------------------------------------------------------- const TPalette::Page *TPalette::getPage(int pageIndex) const { if (0 <= pageIndex && pageIndex < getPageCount()) { Page *page = m_pages[pageIndex]; assert(page->getIndex() == pageIndex); assert(page->m_palette == this); return page; } else return 0; } //------------------------------------------------------------------- TPalette::Page *TPalette::addPage(std::wstring name) { Page *page = new Page(name); page->m_index = getPageCount(); page->m_palette = this; m_pages.push_back(page); return page; } //------------------------------------------------------------------- void TPalette::erasePage(int index) { Page *page = getPage(index); if (!page) return; m_pages.erase(m_pages.begin() + index); int i; for (i = 0; i < getPageCount(); i++) m_pages[i]->m_index = i; for (i = 0; i < page->getStyleCount(); i++) m_styles[page->getStyleId(i)].first = 0; page->m_palette = 0; delete page; } //------------------------------------------------------------------- void TPalette::movePage(Page *page, int dstPageIndex) { assert(page); assert(page->m_palette == this); dstPageIndex = tcrop(dstPageIndex, 0, getPageCount() - 1); if (dstPageIndex == page->getIndex()) return; m_pages.erase(m_pages.begin() + page->getIndex()); m_pages.insert(m_pages.begin() + dstPageIndex, page); for (int i = 0; i < getPageCount(); i++) m_pages[i]->m_index = i; assert(page->getIndex() == dstPageIndex); } //------------------------------------------------------------------- TPalette::Page *TPalette::getStylePage(int styleId) const { if (0 <= styleId && styleId < getStyleCount()) return m_styles[styleId].first; else return 0; } //------------------------------------------------------------------- int TPalette::getClosestStyle(const TPixel32 &color) const { struct locals { static inline int getDistance2(const TPixel32 &a, const TPixel32 &b) { return (a.r - b.r) * (a.r - b.r) + (a.g - b.g) * (a.g - b.g) + (a.b - b.b) * (a.b - b.b) + (a.m - b.m) * (a.m - b.m); } }; // locals if (color == TPixel32::Transparent) return 0; int bestIndex = -1; int bestDistance = 255 * 255 * 4 + 1; for (int i = 1; i < (int)m_styles.size(); i++) { // if(i==FirstUserStyle+2) continue; TSolidColorStyle *scs = dynamic_cast(m_styles[i].second.getPointer()); if (scs) { int d = locals::getDistance2(scs->getMainColor(), color); if (d < bestDistance) { bestIndex = i; bestDistance = d; } } } return bestIndex; } //------------------------------------------------------------------- bool TPalette::getFxRects(const TRect &rect, TRect &rectIn, TRect &rectOut) { int i; bool ret = false; int borderIn, borderOut, fullBorderIn = 0, fullBorderOut = 0; for (i = 0; i < (int)m_styles.size(); i++) if (m_styles[i].second->isRasterStyle()) { m_styles[i].second->getRasterStyleFx()->getEnlargement(borderIn, borderOut); fullBorderIn = std::max(fullBorderIn, borderIn); fullBorderOut = std::max(fullBorderOut, borderOut); ret = true; } rectIn = rect.enlarge(fullBorderIn); rectOut = rect.enlarge(fullBorderOut); return ret; } //=================================================================== // // I/O // //------------------------------------------------------------------- namespace { class StyleWriter final : public TOutputStreamInterface { TOStream &m_os; int m_index; public: static TFilePath m_rootDir; StyleWriter(TOStream &os, int index) : m_os(os), m_index(index) {} static void setRootDir(const TFilePath &fp) { m_rootDir = fp; } TOutputStreamInterface &operator<<(double x) override { m_os << x; return *this; }; TOutputStreamInterface &operator<<(int x) override { m_os << x; return *this; }; TOutputStreamInterface &operator<<(std::string x) override { m_os << x; return *this; }; TOutputStreamInterface &operator<<(UCHAR x) override { m_os << (int)x; return *this; }; TOutputStreamInterface &operator<<(USHORT x) override { m_os << (int)x; return *this; }; TOutputStreamInterface &operator<<(const TPixel32 &x) override { m_os << x; return *this; }; TOutputStreamInterface &operator<<(const TRaster32P &ras) override { assert(m_rootDir != TFilePath()); std::string name = "texture_" + std::to_string(m_index); m_os << name; TFilePath filename = ((m_rootDir + "textures") + name).withType("bmp"); if (!TFileStatus(m_rootDir + "textures").doesExist()) { try { TSystem::mkDir(m_rootDir + "textures"); } catch (...) { } } TImageWriter::save(filename, ras); return *this; }; }; //------------------------------------------------------------------- class StyleReader final : public TInputStreamInterface { TIStream &m_is; //!< Wrapped input stream. VersionNumber m_version; //!< Palette version number (overrides m_is's one). public: static TFilePath m_rootDir; public: StyleReader(TIStream &is, const VersionNumber &version) : m_is(is), m_version(version) {} static void setRootDir(const TFilePath &fp) { m_rootDir = fp; } TInputStreamInterface &operator>>(double &x) override { m_is >> x; return *this; } TInputStreamInterface &operator>>(int &x) override { m_is >> x; return *this; } TInputStreamInterface &operator>>(std::string &x) override { m_is >> x; return *this; } TInputStreamInterface &operator>>(UCHAR &x) override { int v; m_is >> v; x = v; return *this; } TInputStreamInterface &operator>>(USHORT &x) override { int v; m_is >> v; x = v; return *this; } TInputStreamInterface &operator>>(TRaster32P &x) override { assert(m_rootDir != TFilePath()); std::string name; m_is >> name; TFilePath filename = ((m_rootDir + "textures") + name).withType("bmp"); TRasterP ras; if (TImageReader::load(filename, ras)) { x = ras; } return *this; } TInputStreamInterface &operator>>(TPixel32 &x) override { m_is >> x; return *this; } /*! \details Explicitly ovverrides the stream's version, returning m_version. This is necessary since palettes have their \a own version number, which is \a not the TIStream's file one. */ VersionNumber versionNumber() const override { return m_version; } //!< Returns the palette's version number. }; TFilePath StyleWriter::m_rootDir = TFilePath(); TFilePath StyleReader::m_rootDir = TFilePath(); } // namespace //=================================================================== void TPalette::setRootDir(const TFilePath &fp) { StyleWriter::setRootDir(fp); StyleReader::setRootDir(fp); } //------------------------------------------------------------------- void TPalette::saveData(TOStream &os) { os.child("version") << 71 << 0; // Inserting the version tag at this level. // This is necessary to support the tpl format if (m_refImgPath != TFilePath()) { // since it performs *untagged* stream output if (m_areRefLevelFidsSpecified) { std::map attr; attr["fids"] = fidsToString(m_refLevelFids); os.openChild("refImgPath", attr); } else os.openChild("refImgPath"); os << m_refImgPath; // (the palette is streamed directly). os.closeChild(); } os.openChild("styles"); { for (int i = 0; i < getStyleCount(); ++i) { TColorStyleP style = m_styles[i].second; if (style->getPickedPosition().pos == TPoint()) os.openChild("style"); else { std::map attr; attr["pickedpos"] = pointToString(style->getPickedPosition()); os.openChild("style", attr); } { StyleWriter w(os, i); style->save(w); } os.closeChild(); } } os.closeChild(); os.openChild("stylepages"); { for (int i = 0; i < getPageCount(); ++i) { Page *page = getPage(i); os.openChild("page"); { os.child("name") << page->getName(); os.openChild("indices"); { int m = page->getStyleCount(); for (int j = 0; j < m; ++j) os << page->getStyleId(j); } os.closeChild(); } os.closeChild(); } } os.closeChild(); if (isAnimated()) { os.openChild("animation"); { StyleAnimationTable::iterator sat, saEnd = m_styleAnimationTable.end(); for (sat = m_styleAnimationTable.begin(); sat != saEnd; ++sat) { int styleId = sat->first; StyleAnimation &animation = sat->second; std::map attributes; attributes["id"] = std::to_string(styleId); os.openChild("style", attributes); { StyleAnimation::iterator kt, kEnd = animation.end(); for (kt = animation.begin(); kt != kEnd; ++kt) { int frame = kt->first; TColorStyle *cs = kt->second.getPointer(); assert(cs); attributes.clear(); attributes["frame"] = std::to_string(frame); /*os.openChild("keycolor", attributes); // Up to Toonz 7.0, animations saved os << cs->getMainColor(); // the main color only os.closeChild();*/ // os.openChild("keyframe", attributes); { StyleWriter w(os, sat->first); kt->second->save(w); } os.closeChild(); } } os.closeChild(); } } os.closeChild(); } // salvo gli shortcuts solo se sono non standard int i; for (i = 0; i < 10; ++i) if (getShortcutValue('0' + i) != i) break; if (i < 10) { os.openChild("shortcuts"); { for (i = 0; i < 10; i++) os << getShortcutValue('0' + i); } os.closeChild(); } if (isLocked()) { os.openChild("lock"); os << 1; os.closeChild(); } } //------------------------------------------------------------------- void TPalette::loadData(TIStream &is) { m_styles.clear(); clearPointerContainer(m_pages); VersionNumber version = is.getVersion(); std::string tagName; while (is.openChild(tagName)) { if (tagName == "version") { is >> version.first >> version.second; if (version > VersionNumber(71, 0)) throw TException("palette, unsupported version number"); } else if (tagName == "styles") { while (!is.eos()) // I think while(is.openChild(tagName)) { // would be better. However, I don't trust if (!is.openChild(tagName) || tagName != "style") // TIStream's implementation very much. Keeping it throw TException( "palette, expected tag