diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index e60bf8945..e5cef9da4 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -100,6 +100,7 @@ add_library(app-library data_recovery.cpp document.cpp document_api.cpp + document_exporter.cpp document_location.cpp document_undo.cpp documents.cpp diff --git a/src/app/app.cpp b/src/app/app.cpp index ccd04a131..7e4ce759e 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -29,6 +29,7 @@ #include "app/commands/params.h" #include "app/console.h" #include "app/data_recovery.h" +#include "app/document_exporter.h" #include "app/document_location.h" #include "app/document_observer.h" #include "app/drop_files.h" @@ -112,6 +113,7 @@ App::App(int argc, const char* argv[]) , m_legacy(NULL) , m_isGui(false) , m_isShell(false) + , m_exporter(NULL) { ASSERT(m_instance == NULL); m_instance = this; @@ -124,6 +126,14 @@ App::App(int argc, const char* argv[]) m_legacy = new LegacyModules(isGui() ? REQUIRE_INTERFACE: 0); m_files = options.files(); + if (options.hasExporterParams()) { + m_exporter.reset(new DocumentExporter); + + m_exporter->setDataFilename(options.data()); + m_exporter->setTextureFilename(options.sheet()); + m_exporter->setScale(options.scale()); + } + // Register well-known image file types. FileFormatsManager::instance().registerAllFormats(); @@ -186,6 +196,7 @@ int App::run() PRINTF("Processing options...\n"); { + UIContext* context = UIContext::instance(); Console console; for (FileList::iterator it = m_files.begin(), @@ -199,17 +210,29 @@ int App::run() } else { // Mount and select the sprite - UIContext* context = UIContext::instance(); context->addDocument(document); - if (isGui()) { - // Recent file + // Add the given file in the argument as a "recent file" only + // if we are running in GUI mode. If the program is executed + // in batch mode this is not desirable. + if (isGui()) getRecentFiles()->addRecentFile(it->c_str()); - } + + // Add the document to the exporter. + if (m_exporter != NULL) + m_exporter->addDocument(document); } } } + // Export + if (m_exporter != NULL) { + PRINTF("Exporting sheet...\n"); + + m_exporter->exportSheet(); + m_exporter.reset(NULL); + } + // Run the GUI if (isGui()) { // Support to drop files from Windows explorer diff --git a/src/app/app.h b/src/app/app.h index 8b46d38be..5ea9f8107 100644 --- a/src/app/app.h +++ b/src/app/app.h @@ -39,6 +39,7 @@ namespace raster { namespace app { class Document; + class DocumentExporter; class LegacyModules; class LoggerModule; class MainWindow; @@ -90,6 +91,7 @@ namespace app { bool m_isShell; base::UniquePtr m_mainWindow; FileList m_files; + base::UniquePtr m_exporter; }; void app_refresh_screen(); diff --git a/src/app/app_options.cpp b/src/app/app_options.cpp index 1a79c9d4c..19c2a43ab 100644 --- a/src/app/app_options.cpp +++ b/src/app/app_options.cpp @@ -24,6 +24,7 @@ #include "base/path.h" +#include #include namespace app { @@ -35,10 +36,20 @@ AppOptions::AppOptions(int argc, const char* argv[]) , m_startUI(true) , m_startShell(false) , m_verbose(false) + , m_scale(1.0) { - Option& palette = m_po.add("palette").requiresValue("GFXFILE").description("Use a specific palette by default"); + Option& palette = m_po.add("palette").requiresValue("").description("Use a specific palette by default"); Option& shell = m_po.add("shell").description("Start an interactive console to execute scripts"); Option& batch = m_po.add("batch").description("Do not start the UI"); + // Option& dataFormat = m_po.add("format").requiresValue("").description("Select the format for the sprite sheet data"); + Option& data = m_po.add("data").requiresValue("").description("File to store the sprite sheet metadata (.json file)"); + //Option& textureFormat = m_po.add("texture-format").requiresValue("").description("Output texture format."); + Option& sheet = m_po.add("sheet").requiresValue("").description("Image file to save the texture (.png)"); + //Option& scale = m_po.add("scale").requiresValue("").description(""); + //Option& scaleMode = m_po.add("scale-mode").requiresValue("").description("Export the first given document to a JSON object"); + //Option& splitLayers = m_po.add("split-layers").description("Specifies that each layer of the given file should be saved as a different image in the sheet."); + //Option& rotsprite = m_po.add("rotsprite").requiresValue("").description("Specifies different angles to export the given image."); + //Option& merge = m_po.add("merge").requiresValue("").description("Merge several sprite sheets in one."); Option& verbose = m_po.add("verbose").description("Explain what is being done (in stderr or a log file)"); Option& help = m_po.add("help").mnemonic('?').description("Display this help and exits"); Option& version = m_po.add("version").description("Output version information and exit"); @@ -49,6 +60,13 @@ AppOptions::AppOptions(int argc, const char* argv[]) m_verbose = verbose.enabled(); m_paletteFileName = palette.value(); m_startShell = shell.enabled(); + // m_dataFormat = dataFormat.value(); + m_data = data.value(); + // m_textureFormat = textureFormat.value(); + m_sheet = sheet.value(); + // if (scale.enabled()) + // m_scale = std::strtod(scale.value().c_str(), NULL); + // m_scaleMode = scaleMode.value(); if (help.enabled()) { showHelp(); diff --git a/src/app/app_options.h b/src/app/app_options.h index cd1bdeb4a..fa7b4dc62 100644 --- a/src/app/app_options.h +++ b/src/app/app_options.h @@ -41,6 +41,22 @@ public: return m_po.values(); } + // Export options + const std::string& dataFormat() const { return m_dataFormat; } + const std::string& data() const { return m_data; } + const std::string& textureFormat() const { return m_textureFormat; } + const std::string& sheet() const { return m_sheet; } + const double scale() const { return m_scale; } + const std::string& scaleMode() const { return m_scaleMode; } + + bool hasExporterParams() { + return + !m_dataFormat.empty() || + !m_data.empty() || + !m_textureFormat.empty() || + !m_sheet.empty(); + } + private: void showHelp(); void showVersion(); @@ -51,6 +67,13 @@ private: bool m_startShell; bool m_verbose; std::string m_paletteFileName; + + std::string m_dataFormat; + std::string m_data; + std::string m_textureFormat; + std::string m_sheet; + double m_scale; + std::string m_scaleMode; }; } // namespace app diff --git a/src/app/document_api.cpp b/src/app/document_api.cpp index 2e71a55ab..0a9ca695c 100644 --- a/src/app/document_api.cpp +++ b/src/app/document_api.cpp @@ -91,8 +91,7 @@ void DocumentApi::setSpriteSize(Sprite* sprite, int w, int h) ASSERT(w > 0); ASSERT(h > 0); - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetSpriteSize(getObjects(), sprite)); sprite->setSize(w, h); @@ -151,8 +150,7 @@ void DocumentApi::setPixelFormat(Sprite* sprite, PixelFormat newFormat, Ditherin return; // Change pixel format of the stock of images. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetStockPixelFormat(getObjects(), sprite->getStock())); sprite->getStock()->setPixelFormat(newFormat); @@ -177,7 +175,7 @@ void DocumentApi::setPixelFormat(Sprite* sprite, PixelFormat newFormat, Ditherin } // Change sprite's pixel format. - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetSpritePixelFormat(getObjects(), sprite)); sprite->setPixelFormat(newFormat); @@ -190,7 +188,7 @@ void DocumentApi::setPixelFormat(Sprite* sprite, PixelFormat newFormat, Ditherin // frame. if (newFormat == IMAGE_GRAYSCALE) { // Add undoers to revert all palette changes. - if (undo->isEnabled()) { + if (undoEnabled()) { PalettesList palettes = sprite->getPalettes(); for (PalettesList::iterator it = palettes.begin(); it != palettes.end(); ++it) { Palette* palette = *it; @@ -217,8 +215,7 @@ void DocumentApi::addFrame(Sprite* sprite, FrameNumber newFrame) // Add the frame in the sprite structure, it adjusts the total // number of frames in the sprite. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::AddFrame(getObjects(), m_document, sprite, newFrame)); sprite->addFrame(newFrame); @@ -303,8 +300,7 @@ void DocumentApi::removeFrame(Sprite* sprite, FrameNumber frame) // Add undoers to restore the removed frame from the sprite (to // restore the number and durations of frames). - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::RemoveFrame(getObjects(), m_document, sprite, frame)); // Remove the frame from the sprite. This is the low level @@ -355,8 +351,7 @@ void DocumentApi::setTotalFrames(Sprite* sprite, FrameNumber frames) ASSERT(frames >= 1); // Add undoers. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetTotalFrames(getObjects(), m_document, sprite)); // Do the action. @@ -372,8 +367,7 @@ void DocumentApi::setTotalFrames(Sprite* sprite, FrameNumber frames) void DocumentApi::setFrameDuration(Sprite* sprite, FrameNumber frame, int msecs) { // Add undoers. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetFrameDuration( getObjects(), sprite, frame)); @@ -390,8 +384,7 @@ void DocumentApi::setFrameDuration(Sprite* sprite, FrameNumber frame, int msecs) void DocumentApi::setConstantFrameRate(Sprite* sprite, int msecs) { // Add undoers. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) { + if (undoEnabled()) { for (FrameNumber fr(0); frgetTotalFrames(); ++fr) m_undoers->pushUndoer(new undoers::SetFrameDuration( getObjects(), sprite, fr)); @@ -489,8 +482,7 @@ void DocumentApi::addCel(LayerImage* layer, Cel* cel) ASSERT(layer); ASSERT(cel); - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::AddCel(getObjects(), layer, cel)); layer->addCel(cel); @@ -531,8 +523,7 @@ void DocumentApi::removeCel(LayerImage* layer, Cel* cel) if (!used) removeImageFromStock(sprite, cel->getImage()); - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::RemoveCel(getObjects(), layer, cel)); @@ -548,8 +539,7 @@ void DocumentApi::setCelFramePosition(Sprite* sprite, Cel* cel, FrameNumber fram ASSERT(cel); ASSERT(frame >= 0); - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetCelFrame(getObjects(), cel)); cel->setFrame(frame); @@ -565,8 +555,7 @@ void DocumentApi::setCelPosition(Sprite* sprite, Cel* cel, int x, int y) { ASSERT(cel); - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetCelPosition(getObjects(), cel)); cel->setPosition(x, y); @@ -615,8 +604,7 @@ LayerFolder* DocumentApi::newLayerFolder(Sprite* sprite) void DocumentApi::addLayer(LayerFolder* folder, Layer* newLayer, Layer* afterThis) { // Add undoers. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::AddLayer(getObjects(), m_document, newLayer)); @@ -643,8 +631,7 @@ void DocumentApi::removeLayer(Layer* layer) m_document->notifyObservers(&DocumentObserver::onRemoveLayer, ev); // Add undoers. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::RemoveLayer(getObjects(), m_document, layer)); // Do the action. @@ -655,8 +642,7 @@ void DocumentApi::removeLayer(Layer* layer) void DocumentApi::configureLayerAsBackground(LayerImage* layer) { // Add undoers. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) { + if (undoEnabled()) { m_undoers->pushUndoer(new undoers::SetLayerFlags(getObjects(), layer)); m_undoers->pushUndoer(new undoers::SetLayerName(getObjects(), layer)); m_undoers->pushUndoer(new undoers::MoveLayer(getObjects(), layer)); @@ -668,8 +654,7 @@ void DocumentApi::configureLayerAsBackground(LayerImage* layer) void DocumentApi::restackLayerAfter(Layer* layer, Layer* afterThis) { - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::MoveLayer(getObjects(), layer)); layer->getParent()->stackLayer(layer, afterThis); @@ -730,7 +715,6 @@ void DocumentApi::backgroundFromLayer(LayerImage* layer, int bgcolor) ASSERT(layer->getSprite() != NULL); ASSERT(layer->getSprite()->getBackgroundLayer() == NULL); - DocumentUndo* undo = m_document->getUndo(); Sprite* sprite = layer->getSprite(); // create a temporary image to draw each frame of the new @@ -765,7 +749,7 @@ void DocumentApi::backgroundFromLayer(LayerImage* layer, int bgcolor) // same size of cel-image and bg-image if (bg_image->getWidth() == cel_image->getWidth() && bg_image->getHeight() == cel_image->getHeight()) { - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::ImageArea(getObjects(), cel_image, 0, 0, cel_image->getWidth(), cel_image->getHeight())); @@ -805,8 +789,7 @@ void DocumentApi::layerFromBackground(Layer* layer) ASSERT(layer->getSprite() != NULL); ASSERT(layer->getSprite()->getBackgroundLayer() != NULL); - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) { + if (undoEnabled()) { m_undoers->pushUndoer(new undoers::SetLayerFlags(getObjects(), layer)); m_undoers->pushUndoer(new undoers::SetLayerName(getObjects(), layer)); } @@ -821,8 +804,6 @@ void DocumentApi::flattenLayers(Sprite* sprite, int bgcolor) Image* cel_image; Cel* cel; - DocumentUndo* undo = m_document->getUndo(); - // Create a temporary image. base::UniquePtr image_wrap(Image::create(sprite->getPixelFormat(), sprite->getWidth(), @@ -851,7 +832,7 @@ void DocumentApi::flattenLayers(Sprite* sprite, int bgcolor) ASSERT(cel_image != NULL); // We have to save the current state of `cel_image' in the undo. - if (undo->isEnabled()) { + if (undoEnabled()) { Dirty* dirty = new Dirty(cel_image, image, image->getBounds()); dirty->saveImagePixels(cel_image); m_undoers->pushUndoer(new undoers::DirtyArea( @@ -895,8 +876,7 @@ int DocumentApi::addImageInStock(Sprite* sprite, Image* image) int imageIndex = sprite->getStock()->addImage(image); // Add undoers. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::AddImage(getObjects(), sprite->getStock(), imageIndex)); @@ -911,8 +891,7 @@ void DocumentApi::removeImageFromStock(Sprite* sprite, int imageIndex) Image* image = sprite->getStock()->getImage(imageIndex); ASSERT(image); - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::RemoveImage(getObjects(), sprite->getStock(), imageIndex)); @@ -927,8 +906,7 @@ void DocumentApi::replaceStockImage(Sprite* sprite, int imageIndex, Image* newIm ASSERT(oldImage); // Replace the image in the stock. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::ReplaceImage(getObjects(), sprite->getStock(), imageIndex)); @@ -952,14 +930,13 @@ void DocumentApi::clearMask(Layer* layer, Cel* cel, int bgcolor) return; Mask* mask = m_document->getMask(); - DocumentUndo* undo = m_document->getUndo(); // If the mask is empty or is not visible then we have to clear the // entire image in the cel. if (!m_document->isMaskVisible()) { // If the layer is the background then we clear the image. if (layer->isBackground()) { - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::ImageArea(getObjects(), image, 0, 0, image->getWidth(), image->getHeight())); @@ -986,7 +963,7 @@ void DocumentApi::clearMask(Layer* layer, Cel* cel, int bgcolor) if (x1 > x2 || y1 > y2) return; - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::ImageArea(getObjects(), image, x1, y1, x2-x1+1, y2-y1+1)); @@ -1014,8 +991,7 @@ void DocumentApi::flipImage(Image* image, raster::algorithm::FlipType flipType) { // Insert the undo operation. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) { + if (undoEnabled()) { m_undoers->pushUndoer (new undoers::FlipImage (getObjects(), image, bounds, flipType)); @@ -1033,8 +1009,7 @@ void DocumentApi::flipImageWithMask(Image* image, const Mask* mask, raster::algo raster::algorithm::flip_image_with_mask(flippedImage, mask, flipType, bgcolor); // Insert the undo operation. - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) { + if (undoEnabled()) { base::UniquePtr dirty((new Dirty(image, flippedImage, image->getBounds()))); dirty->saveImagePixels(image); @@ -1061,8 +1036,7 @@ void DocumentApi::copyToCurrentMask(Mask* mask) ASSERT(m_document->getMask()); ASSERT(mask); - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetMask(getObjects(), m_document)); @@ -1073,8 +1047,7 @@ void DocumentApi::setMaskPosition(int x, int y) { ASSERT(m_document->getMask()); - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetMaskPosition(getObjects(), m_document)); m_document->getMask()->setOrigin(x, y); @@ -1083,8 +1056,7 @@ void DocumentApi::setMaskPosition(int x, int y) void DocumentApi::deselectMask() { - DocumentUndo* undo = m_document->getUndo(); - if (undo->isEnabled()) + if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetMask(getObjects(), m_document)); @@ -1101,10 +1073,8 @@ void DocumentApi::setPalette(Sprite* sprite, FrameNumber frame, Palette* newPale currentSpritePalette->countDiff(newPalette, &from, &to); if (from >= 0 && to >= from) { - DocumentUndo* undo = m_document->getUndo(); - // Add undo information to save the range of pal entries that will be modified. - if (undo->isEnabled()) { + if (undoEnabled()) { m_undoers->pushUndoer (new undoers::SetPaletteColors(getObjects(), sprite, currentSpritePalette, @@ -1116,4 +1086,11 @@ void DocumentApi::setPalette(Sprite* sprite, FrameNumber frame, Palette* newPale } } +bool DocumentApi::undoEnabled() +{ + return + m_undoers != NULL && + m_document->getUndo()->isEnabled(); +} + } // namespace app diff --git a/src/app/document_api.h b/src/app/document_api.h index efa342135..1475ee664 100644 --- a/src/app/document_api.h +++ b/src/app/document_api.h @@ -118,6 +118,7 @@ namespace app { void copyPreviousFrame(Layer* layer, FrameNumber frame); void moveFrameBeforeLayer(Layer* layer, FrameNumber frame, FrameNumber beforeFrame); void configureLayerAsBackground(LayerImage* layer); + bool undoEnabled(); Document* m_document; undo::UndoersCollector* m_undoers; diff --git a/src/app/document_exporter.cpp b/src/app/document_exporter.cpp new file mode 100644 index 000000000..c0ab73348 --- /dev/null +++ b/src/app/document_exporter.cpp @@ -0,0 +1,329 @@ +/* Aseprite + * Copyright (C) 2001-2013 David Capello + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/document_exporter.h" + +#include "app/document.h" +#include "app/document_api.h" +#include "app/file/file.h" +#include "base/compiler_specific.h" +#include "base/path.h" +#include "base/unique_ptr.h" +#include "gfx/size.h" +#include "raster/cel.h" +#include "raster/dithering_method.h" +#include "raster/image.h" +#include "raster/layer.h" +#include "raster/palette.h" +#include "raster/sprite.h" +#include "raster/stock.h" + +#include +#include +#include + +using namespace raster; + +namespace app { + +class DocumentExporter::Sample { +public: + Sample(Document* document, Sprite* sprite, + FrameNumber frame, const std::string& filename) : + m_document(document), + m_sprite(sprite), + m_frame(frame), + m_filename(filename) { + } + + Document* document() const { return m_document; } + Sprite* sprite() const { return m_sprite; } + FrameNumber frame() const { return m_frame; } + std::string filename() const { return m_filename; } + const gfx::Size& originalSize() const { return m_originalSize; } + const gfx::Rect& trimmedBounds() const { return m_trimmedBounds; } + const gfx::Rect& inTextureBounds() const { return m_inTextureBounds; } + + bool trimmed() const { + return m_trimmedBounds.x > 0 + || m_trimmedBounds.y > 0 + || m_trimmedBounds.w != m_originalSize.w + || m_trimmedBounds.h != m_originalSize.h; + } + + void setOriginalSize(const gfx::Size& size) { m_originalSize = size; } + void setTrimmedBounds(const gfx::Rect& bounds) { m_trimmedBounds = bounds; } + void setInTextureBounds(const gfx::Rect& bounds) { m_inTextureBounds = bounds; } + +private: + Document* m_document; + Sprite* m_sprite; + FrameNumber m_frame; + std::string m_filename; + gfx::Size m_originalSize; + gfx::Rect m_trimmedBounds; + gfx::Rect m_inTextureBounds; +}; + +class DocumentExporter::Samples { +public: + typedef std::list List; + typedef List::iterator iterator; + typedef List::const_iterator const_iterator; + + void addSample(const Sample& sample) { + m_samples.push_back(sample); + } + + iterator begin() { return m_samples.begin(); } + iterator end() { return m_samples.end(); } + const_iterator begin() const { return m_samples.begin(); } + const_iterator end() const { return m_samples.end(); } + +private: + List m_samples; +}; + +class DocumentExporter::LayoutSamples { +public: + virtual ~LayoutSamples() { } + virtual void layoutSamples(Samples& samples) = 0; +}; + +class DocumentExporter::SimpleLayoutSamples : + public DocumentExporter::LayoutSamples { +public: + void layoutSamples(Samples& samples) OVERRIDE { + const Sprite* oldSprite = NULL; + + gfx::Point framePt(0, 0); + for (Samples::iterator it=samples.begin(), end=samples.end(); + it != end; ++it) { + const Sprite* sprite = it->sprite(); + gfx::Size size(sprite->getWidth(), sprite->getHeight()); + + it->setOriginalSize(size); + it->setTrimmedBounds(gfx::Rect(gfx::Point(0, 0), size)); + it->setInTextureBounds(gfx::Rect(framePt, size)); + + // All frames of each sprite in one row. + if (oldSprite != NULL && oldSprite != it->sprite()) { + framePt.x = 0; + framePt.y += size.h; + } + else { + framePt.x += size.w; + } + + oldSprite = it->sprite(); + } + } +}; + +void DocumentExporter::exportSheet() +{ + // We output the metadata to std::cout if the user didn't specify a file. + std::ofstream fos; + std::streambuf* osbuf; + if (m_dataFilename.empty()) + osbuf = std::cout.rdbuf(); + else { + fos.open(m_dataFilename.c_str(), std::ios::out); + osbuf = fos.rdbuf(); + } + std::ostream os(osbuf); + + // Steps for sheet construction: + // 1) Capture the samples (each sprite+frame pair) + Samples samples; + captureSamples(samples); + + // 2) Layout those samples in a texture field. + SimpleLayoutSamples layout; + layout.layoutSamples(samples); + + // 3) Create and render the texture. + base::UniquePtr textureDocument( + createEmptyTexture(samples)); + + Sprite* texture = textureDocument->getSprite(); + Image* textureImage = texture->getStock()->getImage( + static_cast(texture->getFolder()->getFirstLayer()) + ->getCel(FrameNumber(0))->getImage()); + + renderTexture(samples, textureImage); + + // Save the metadata. + createDataFile(samples, os, textureImage); + + // Save the image files. + if (!m_textureFilename.empty()) { + textureDocument->setFilename(m_textureFilename.c_str()); + save_document(textureDocument.get()); + } +} + +void DocumentExporter::captureSamples(Samples& samples) +{ + std::vector buf(32); + + for (std::vector::iterator + it = m_documents.begin(), + end = m_documents.end(); it != end; ++it) { + Document* document = *it; + Sprite* sprite = document->getSprite(); + + for (FrameNumber frame=FrameNumber(0); + framegetTotalFrames(); ++frame) { + base::string filename = document->getFilename(); + + if (sprite->getTotalFrames() > FrameNumber(1)) { + int frameNumWidth = + (sprite->getTotalFrames() < 10)? 1: + (sprite->getTotalFrames() < 100)? 2: + (sprite->getTotalFrames() < 1000)? 3: 4; + std::sprintf(&buf[0], "%0*d", frameNumWidth, frame); + + base::string path = base::get_file_path(filename); + base::string title = base::get_file_title(filename); + base::string ext = base::get_file_extension(filename); + filename = base::join_path(path, title + &buf[0] + "." + ext); + } + + samples.addSample(Sample(document, sprite, frame, filename)); + } + } +} + +Document* DocumentExporter::createEmptyTexture(const Samples& samples) +{ + Palette* palette = NULL; + PixelFormat pixelFormat = IMAGE_INDEXED; + gfx::Rect fullTextureBounds; + int maxColors = 256; + + for (Samples::const_iterator + it = samples.begin(), + end = samples.end(); it != end; ++it) { + // We try to render an indexed image. But if we find a sprite with + // two or more palettes, or two of the sprites have different + // palettes, we've to use RGB format. + if (pixelFormat == IMAGE_INDEXED) { + if (it->sprite()->getPixelFormat() != IMAGE_INDEXED) { + pixelFormat = IMAGE_RGB; + } + else if (it->sprite()->getPalettes().size() > 1) { + pixelFormat = IMAGE_RGB; + } + else if (palette != NULL + && palette->countDiff(it->sprite()->getPalette(FrameNumber(0)), NULL, NULL) > 0) { + pixelFormat = IMAGE_RGB; + } + else + palette = it->sprite()->getPalette(FrameNumber(0)); + } + + fullTextureBounds = fullTextureBounds.createUnion(it->inTextureBounds()); + } + + base::UniquePtr document(Document::createBasicDocument(pixelFormat, + fullTextureBounds.w, fullTextureBounds.h, maxColors)); + + if (palette != NULL) + document->getSprite()->setPalette(palette, false); + + return document.release(); +} + +void DocumentExporter::renderTexture(const Samples& samples, Image* textureImage) +{ + textureImage->clear(0); + + for (Samples::const_iterator + it = samples.begin(), + end = samples.end(); it != end; ++it) { + // Make the sprite compatible with the texture so the render() + // works correctly. + if (it->sprite()->getPixelFormat() != textureImage->getPixelFormat()) { + DocumentApi docApi(it->document(), NULL); // DocumentApi without undo + docApi.setPixelFormat(it->sprite(), textureImage->getPixelFormat(), + DITHERING_NONE); + } + + it->sprite()->render(textureImage, + it->inTextureBounds().x - it->trimmedBounds().x, + it->inTextureBounds().y - it->trimmedBounds().y, + it->frame()); + } +} + +void DocumentExporter::createDataFile(const Samples& samples, std::ostream& os, Image* textureImage) +{ + os << "{ \"frames\": {\n"; + for (Samples::const_iterator + it = samples.begin(), + end = samples.end(); it != end; ) { + gfx::Size srcSize = it->originalSize(); + gfx::Rect spriteSourceBounds = it->trimmedBounds(); + gfx::Rect frameBounds = it->inTextureBounds(); + + os << " \"" << it->filename() << "\": {\n" + << " \"frame\": { " + << "\"x\": " << frameBounds.x << ", " + << "\"y\": " << frameBounds.y << ", " + << "\"w\": " << frameBounds.w << ", " + << "\"h\": " << frameBounds.h << " },\n" + << " \"rotated\": false,\n" + << " \"trimmed\": " << (it->trimmed() ? "true": "false") << ",\n" + << " \"spriteSourceSize\": { " + << "\"x\": " << spriteSourceBounds.x << ", " + << "\"y\": " << spriteSourceBounds.y << ", " + << "\"w\": " << spriteSourceBounds.w << ", " + << "\"h\": " << spriteSourceBounds.h << " },\n" + << " \"sourceSize\": { " + << "\"w\": " << srcSize.w << ", " + << "\"h\": " << srcSize.h << " },\n" + << " \"duration\": " << it->sprite()->getFrameDuration(it->frame()) << "\n" + << " }"; + + if (++it != samples.end()) + os << ",\n"; + else + os << "\n"; + } + + os << " },\n" + << " \"meta\": {\n" + << " \"app\": \"" << WEBSITE << "\",\n" + << " \"version\": \"" << VERSION << "\",\n"; + if (!m_textureFilename.empty()) + os << " \"image\": \"" << m_textureFilename.c_str() << "\",\n"; + os << " \"format\": \"" << (textureImage->getPixelFormat() == IMAGE_RGB ? "RGBA8888": "I8") << "\",\n" + << " \"size\": { " + << "\"w\": " << textureImage->getWidth() << ", " + << "\"h\": " << textureImage->getHeight() << " },\n" + << " \"scale\": \"" << m_scale << "\"\n" + << " }\n" + << "}\n"; +} + +} // namespace app diff --git a/src/app/document_exporter.h b/src/app/document_exporter.h new file mode 100644 index 000000000..7ea27b881 --- /dev/null +++ b/src/app/document_exporter.h @@ -0,0 +1,111 @@ +/* Aseprite + * Copyright (C) 2001-2013 David Capello + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef APP_DOCUMENT_EXPORTER_H_INCLUDED +#define APP_DOCUMENT_EXPORTER_H_INCLUDED + +#include "base/disable_copying.h" +#include "gfx/fwd.h" + +#include +#include + +namespace raster { + class Image; +} + +namespace app { + class Document; + + class DocumentExporter { + public: + enum DataFormat { + JsonDataFormat, + DefaultDataFormat = JsonDataFormat + }; + + enum TextureFormat { + JsonTextureFormat, + DefaultTextureFormat = JsonTextureFormat + }; + + enum ScaleMode { + DefaultScaleMode + }; + + DocumentExporter() : + m_dataFormat(DefaultDataFormat), + m_textureFormat(DefaultTextureFormat), + m_scaleMode(DefaultScaleMode) { + } + + void setDataFormat(DataFormat format) { + m_dataFormat = format; + } + + void setDataFilename(const std::string& filename) { + m_dataFilename = filename; + } + + void setTextureFormat(TextureFormat format) { + m_textureFormat = format; + } + + void setTextureFilename(const std::string& filename) { + m_textureFilename = filename; + } + + void setScale(double scale) { + m_scale = scale; + } + + void setScaleMode(ScaleMode mode) { + m_scaleMode = mode; + } + + void addDocument(Document* document) { + m_documents.push_back(document); + } + + void exportSheet(); + + private: + class Sample; + class Samples; + class LayoutSamples; + class SimpleLayoutSamples; + + void captureSamples(Samples& samples); + Document* createEmptyTexture(const Samples& samples); + void renderTexture(const Samples& samples, raster::Image* textureImage); + void createDataFile(const Samples& samples, std::ostream& os, raster::Image* textureImage); + + DataFormat m_dataFormat; + std::string m_dataFilename; + TextureFormat m_textureFormat; + std::string m_textureFilename; + double m_scale; + ScaleMode m_scaleMode; + std::vector m_documents; + + DISABLE_COPYING(DocumentExporter); + }; + +} // namespace app + +#endif diff --git a/src/app/ui_context.cpp b/src/app/ui_context.cpp index 8d555af78..18832cda4 100644 --- a/src/app/ui_context.cpp +++ b/src/app/ui_context.cpp @@ -134,6 +134,10 @@ void UIContext::onAddDocument(Document* document) // base method Context::onAddDocument(document); + // We don't create views in batch mode. + if (!App::instance()->isGui()) + return; + // Add a new view for this document DocumentView* view = new DocumentView(document, DocumentView::Normal);