diff --git a/src/app/app.cpp b/src/app/app.cpp index 9119a06cc..7425a6b09 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -1,5 +1,5 @@ /* Aseprite - * Copyright (C) 2001-2014 David Capello + * Copyright (C) 2001-2015 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 @@ -213,6 +213,7 @@ void App::initialize(const AppOptions& options) PRINTF("Processing options...\n"); bool ignoreEmpty = false; + bool trim = false; // Open file specified in the command line if (!options.values().empty()) { @@ -267,6 +268,10 @@ void App::initialize(const AppOptions& options) else if (opt == &options.ignoreEmpty()) { ignoreEmpty = true; } + // --trim + else if (opt == &options.trim()) { + trim = true; + } // --filename-format else if (opt == &options.filenameFormat()) { filenameFormat = value.value(); @@ -285,7 +290,10 @@ void App::initialize(const AppOptions& options) std::string format = filenameFormat; - Command* command = CommandsModule::instance()->getCommandByName(CommandId::SaveFileCopyAs); + Command* saveAsCommand = CommandsModule::instance()->getCommandByName(CommandId::SaveFileCopyAs); + Command* trimCommand = CommandsModule::instance()->getCommandByName(CommandId::AutocropSprite); + Command* undoCommand = CommandsModule::instance()->getCommandByName(CommandId::Undo); + if (splitLayersSaveAs) { std::vector layers; doc->sprite()->getLayersList(layers); @@ -308,10 +316,21 @@ void App::initialize(const AppOptions& options) fmt = filename_formatter(format, value.value(), show->name(), -1, false); + // TODO --trim command with --save-as doesn't make too + // much sense as we lost the trim rectangle + // information (e.g. we don't have sheet .json) Also, + // we should trim each frame individually (a process + // that can be done only in fop_operate()). + if (trim) + ctx->executeCommand(trimCommand); + Params params; params.set("filename", fn.c_str()); params.set("filename-format", fmt.c_str()); - ctx->executeCommand(command, ¶ms); + ctx->executeCommand(saveAsCommand, ¶ms); + + if (trim) // Undo trim command + ctx->executeCommand(undoCommand); } } else { @@ -324,10 +343,16 @@ void App::initialize(const AppOptions& options) layer->setReadable(layer->name() == importLayerSaveAs); } + if (trim) + ctx->executeCommand(trimCommand); + Params params; params.set("filename", value.value().c_str()); params.set("filename-format", format.c_str()); - ctx->executeCommand(command, ¶ms); + ctx->executeCommand(saveAsCommand, ¶ms); + + if (trim) // Undo trim command + ctx->executeCommand(undoCommand); } } } @@ -400,12 +425,15 @@ void App::initialize(const AppOptions& options) } // Export - if (m_exporter != NULL) { + if (m_exporter) { PRINTF("Exporting sheet...\n"); if (ignoreEmpty) m_exporter->setIgnoreEmptyCels(true); + if (trim) + m_exporter->setTrimCels(true); + m_exporter->exportSheet(); m_exporter.reset(NULL); } diff --git a/src/app/app_options.cpp b/src/app/app_options.cpp index 055745002..643801531 100644 --- a/src/app/app_options.cpp +++ b/src/app/app_options.cpp @@ -49,6 +49,7 @@ AppOptions::AppOptions(int argc, const char* argv[]) , m_splitLayers(m_po.add("split-layers").description("Import each layer of the next given sprite as\na separated image in the sheet")) , m_importLayer(m_po.add("import-layer").requiresValue("").description("Import just one layer of the next given sprite")) , m_ignoreEmpty(m_po.add("ignore-empty").description("Do not export empty frames/cels")) + , m_trim(m_po.add("trim").description("Trim all images before exporting")) , m_filenameFormat(m_po.add("filename-format").requiresValue("").description("Special format to generate filenames")) , m_verbose(m_po.add("verbose").description("Explain what is being done")) , m_help(m_po.add("help").mnemonic('?').description("Display this help and exits")) diff --git a/src/app/app_options.h b/src/app/app_options.h index a9c5b8b0f..4a4d54b25 100644 --- a/src/app/app_options.h +++ b/src/app/app_options.h @@ -57,6 +57,7 @@ public: const Option& splitLayers() const { return m_splitLayers; } const Option& importLayer() const { return m_importLayer; } const Option& ignoreEmpty() const { return m_ignoreEmpty; } + const Option& trim() const { return m_trim; } const Option& filenameFormat() const { return m_filenameFormat; } bool hasExporterParams() const; @@ -85,6 +86,7 @@ private: Option& m_splitLayers; Option& m_importLayer; Option& m_ignoreEmpty; + Option& m_trim; Option& m_filenameFormat; Option& m_verbose; diff --git a/src/app/commands/cmd_undo.cpp b/src/app/commands/cmd_undo.cpp index 47b030f43..954d000b1 100644 --- a/src/app/commands/cmd_undo.cpp +++ b/src/app/commands/cmd_undo.cpp @@ -103,11 +103,13 @@ void UndoCommand::onExecute(Context* context) } } - StatusBar::instance() - ->showTip(1000, "%s %s", - (m_type == Undo ? "Undid": "Redid"), - (m_type == Undo ? undo->getNextUndoLabel(): - undo->getNextRedoLabel())); + StatusBar* statusbar = StatusBar::instance(); + if (statusbar) + statusbar->showTip(1000, "%s %s", + (m_type == Undo ? "Undid": "Redid"), + (m_type == Undo ? + undo->getNextUndoLabel(): + undo->getNextRedoLabel())); // Effectively undo/redo. if (m_type == Undo) diff --git a/src/app/document_exporter.cpp b/src/app/document_exporter.cpp index dcbfd20da..6b8ca01c9 100644 --- a/src/app/document_exporter.cpp +++ b/src/app/document_exporter.cpp @@ -1,5 +1,5 @@ /* Aseprite - * Copyright (C) 2001-2013 David Capello + * Copyright (C) 2001-2015 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 @@ -59,7 +59,10 @@ public: m_sprite(sprite), m_layer(layer), m_frame(frame), - m_filename(filename) { + m_filename(filename), + m_originalSize(sprite->width(), sprite->height()), + m_trimmedBounds(0, 0, sprite->width(), sprite->height()), + m_inTextureBounds(0, 0, sprite->width(), sprite->height()) { } Document* document() const { return m_document; } @@ -128,37 +131,39 @@ public: const Layer* oldLayer = NULL; gfx::Point framePt(0, 0); + gfx::Size rowSize(0, 0); + for (auto& sample : samples) { const Sprite* sprite = sample.sprite(); const Layer* layer = sample.layer(); - gfx::Size size(sprite->width(), sprite->height()); + gfx::Size size = sample.trimmedBounds().getSize(); if (oldSprite) { - // If the user didn't specified a width for the texture, we put - // each sprite/layer in a different row. - if (width == 0) { - // New sprite or layer, go to next row. - if (oldSprite != sprite || oldLayer != layer) { - framePt.x = 0; - framePt.y += oldSprite->height(); // We're skipping the previous sprite height - } - } - // When a texture width is specified, we can put different - // sprites/layers in each row until we reach the texture - // right-border. - else if (framePt.x+size.w > width) { - framePt.x = 0; - framePt.y += oldSprite->height(); - // TODO framePt.y+size.h > height ? + // If the user didn't specify a width for the texture, we put + // each sprite/layer in a different row. + if (width == 0) { + // New sprite or layer, go to next row. + if (oldSprite != sprite || oldLayer != layer) { + framePt.x = 0; + framePt.y += rowSize.h; + rowSize = size; } + } + // When a texture width is specified, we can put different + // sprites/layers in each row until we reach the texture + // right-border. + else if (framePt.x+size.w > width) { + framePt.x = 0; + framePt.y += rowSize.h; + rowSize = size; + } } - sample.setOriginalSize(size); - sample.setTrimmedBounds(gfx::Rect(gfx::Point(0, 0), size)); sample.setInTextureBounds(gfx::Rect(framePt, size)); // Next frame position. framePt.x += size.w; + rowSize = rowSize.createUnion(size); oldSprite = sprite; oldLayer = layer; @@ -172,15 +177,8 @@ public: void layoutSamples(Samples& samples, int& width, int& height) override { gfx::PackingRects pr; - for (auto& sample : samples) { - const Sprite* sprite = sample.sprite(); - gfx::Size size(sprite->width(), sprite->height()); - - sample.setOriginalSize(size); - sample.setTrimmedBounds(gfx::Rect(gfx::Point(0, 0), size)); - - pr.add(size); - } + for (auto& sample : samples) + pr.add(sample.trimmedBounds().getSize()); if (width == 0 || height == 0) { gfx::Size sz = pr.bestFit(); @@ -208,6 +206,7 @@ DocumentExporter::DocumentExporter() , m_scale(1.0) , m_scaleMode(DefaultScaleMode) , m_ignoreEmptyCels(false) + , m_trimCels(false) { } @@ -267,7 +266,6 @@ void DocumentExporter::exportSheet() void DocumentExporter::captureSamples(Samples& samples) { - ImageBufferPtr checkEmptyImageBuf; std::vector buf(32); for (auto& item : m_documents) { @@ -299,30 +297,39 @@ void DocumentExporter::captureSamples(Samples& samples) Sample sample(doc, sprite, layer, frame, filename); - if (m_ignoreEmptyCels) { + if (m_ignoreEmptyCels || m_trimCels) { if (layer && layer->isImage() && !static_cast(layer)->getCel(frame)) { // Empty cel this sample completely continue; } - base::UniquePtr checkEmptyImage( + base::UniquePtr sampleRender( Image::create(sprite->pixelFormat(), sprite->width(), sprite->height(), - checkEmptyImageBuf)); + m_sampleRenderBuf)); - checkEmptyImage->setMaskColor(sprite->transparentColor()); - clear_image(checkEmptyImage, sprite->transparentColor()); - renderSample(sample, checkEmptyImage, 0, 0); + sampleRender->setMaskColor(sprite->transparentColor()); + clear_image(sampleRender, sprite->transparentColor()); + renderSample(sample, sampleRender); gfx::Rect frameBounds; - if (!algorithm::shrink_bounds(checkEmptyImage, frameBounds, - sprite->transparentColor())) { + raster::color_t refColor; + + if (m_trimCels) + refColor = get_pixel(sampleRender, 0, 0); + else if (m_ignoreEmptyCels) + refColor = sprite->transparentColor(); + + if (!algorithm::shrink_bounds(sampleRender, frameBounds, refColor)) { // If shrink_bounds returns false, it's because the whole // image is transparent (equal to the mask color). continue; } + + if (m_trimCels) + sample.setTrimmedBounds(frameBounds); } samples.addSample(sample); @@ -388,10 +395,7 @@ void DocumentExporter::renderTexture(const Samples& samples, Image* textureImage DITHERING_NONE); } - int x = sample.inTextureBounds().x - sample.trimmedBounds().x; - int y = sample.inTextureBounds().y - sample.trimmedBounds().y; - - renderSample(sample, textureImage, x, y); + renderSample(sample, textureImage); } } @@ -446,14 +450,24 @@ void DocumentExporter::createDataFile(const Samples& samples, std::ostream& os, << "}\n"; } -void DocumentExporter::renderSample(const Sample& sample, raster::Image* dst, int x, int y) +void DocumentExporter::renderSample(const Sample& sample, raster::Image* dst) { + gfx::Rect trimmed = sample.trimmedBounds(); + base::UniquePtr tmp( + Image::create(sample.sprite()->pixelFormat(), + trimmed.w, trimmed.h, + m_sampleRenderBuf)); + if (sample.layer()) { - layer_render(sample.layer(), dst, x, y, sample.frame()); + layer_render(sample.layer(), tmp, -trimmed.x, -trimmed.y, sample.frame()); } else { - sample.sprite()->render(dst, x, y, sample.frame()); + sample.sprite()->render(tmp, -trimmed.x, -trimmed.y, sample.frame()); } + + copy_image(dst, tmp, + sample.inTextureBounds().x, + sample.inTextureBounds().y); } } // namespace app diff --git a/src/app/document_exporter.h b/src/app/document_exporter.h index 7fce95a19..007a36a8d 100644 --- a/src/app/document_exporter.h +++ b/src/app/document_exporter.h @@ -1,5 +1,5 @@ /* Aseprite - * Copyright (C) 2001-2014 David Capello + * Copyright (C) 2001-2015 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 @@ -22,10 +22,11 @@ #include "base/disable_copying.h" #include "gfx/fwd.h" +#include "raster/image_buffer.h" #include -#include #include +#include namespace raster { class Image; @@ -93,6 +94,10 @@ namespace app { m_ignoreEmptyCels = ignore; } + void setTrimCels(bool trim) { + m_trimCels = trim; + } + void setFilenameFormat(const std::string& format) { m_filenameFormat = format; } @@ -114,7 +119,7 @@ namespace app { Document* createEmptyTexture(const Samples& samples); void renderTexture(const Samples& samples, raster::Image* textureImage); void createDataFile(const Samples& samples, std::ostream& os, raster::Image* textureImage); - void renderSample(const Sample& sample, raster::Image* dst, int x, int y); + void renderSample(const Sample& sample, raster::Image* dst); class Item { public: @@ -136,8 +141,10 @@ namespace app { double m_scale; ScaleMode m_scaleMode; bool m_ignoreEmptyCels; + bool m_trimCels; Items m_documents; std::string m_filenameFormat; + raster::ImageBufferPtr m_sampleRenderBuf; DISABLE_COPYING(DocumentExporter); }; diff --git a/src/app/settings/ui_settings_impl.cpp b/src/app/settings/ui_settings_impl.cpp index 066298bee..ea28029a5 100644 --- a/src/app/settings/ui_settings_impl.cpp +++ b/src/app/settings/ui_settings_impl.cpp @@ -382,12 +382,14 @@ bool UISettingsImpl::getAutoSelectLayer() app::Color UISettingsImpl::getFgColor() { - return ColorBar::instance()->getFgColor(); + ColorBar* colorbar = ColorBar::instance(); + return colorbar ? colorbar->getFgColor(): app::Color::fromMask(); } app::Color UISettingsImpl::getBgColor() { - return ColorBar::instance()->getBgColor(); + ColorBar* colorbar = ColorBar::instance(); + return colorbar ? colorbar->getBgColor(): app::Color::fromMask(); } tools::Tool* UISettingsImpl::getCurrentTool()