2013-08-09 08:01:20 +08:00
|
|
|
/* Aseprite
|
2013-01-27 23:13:13 +08:00
|
|
|
* Copyright (C) 2001-2013 David Capello
|
2012-01-06 06:45:03 +08:00
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2012-01-06 06:45:03 +08:00
|
|
|
#include "config.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#endif
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/commands/command.h"
|
|
|
|
|
#include "app/context_access.h"
|
|
|
|
|
#include "app/document_api.h"
|
2012-06-16 10:37:59 +08:00
|
|
|
#include "app/find_widget.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ini_file.h"
|
|
|
|
|
#include "app/job.h"
|
2012-06-16 10:37:59 +08:00
|
|
|
#include "app/load_widget.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/modules/gui.h"
|
|
|
|
|
#include "app/modules/palettes.h"
|
|
|
|
|
#include "app/ui_context.h"
|
|
|
|
|
#include "app/undo_transaction.h"
|
2012-01-06 06:45:03 +08:00
|
|
|
#include "base/bind.h"
|
2012-01-09 09:34:36 +08:00
|
|
|
#include "base/unique_ptr.h"
|
2013-11-10 06:59:05 +08:00
|
|
|
#include "raster/algorithm/resize_image.h"
|
2012-01-06 06:45:03 +08:00
|
|
|
#include "raster/cel.h"
|
|
|
|
|
#include "raster/image.h"
|
|
|
|
|
#include "raster/mask.h"
|
2013-11-10 06:59:05 +08:00
|
|
|
#include "raster/primitives.h"
|
2012-01-06 06:45:03 +08:00
|
|
|
#include "raster/sprite.h"
|
|
|
|
|
#include "raster/stock.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "ui/ui.h"
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
#include <allegro/unicode.h>
|
|
|
|
|
|
2013-03-31 07:10:54 +08:00
|
|
|
#define PERC_FORMAT "%.1f"
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
namespace app {
|
|
|
|
|
|
|
|
|
|
using namespace ui;
|
2013-11-10 06:59:05 +08:00
|
|
|
using raster::algorithm::ResizeMethod;
|
2013-08-06 08:20:19 +08:00
|
|
|
|
|
|
|
|
class SpriteSizeJob : public Job {
|
2013-03-12 07:29:45 +08:00
|
|
|
ContextWriter m_writer;
|
|
|
|
|
Document* m_document;
|
2012-01-06 06:45:03 +08:00
|
|
|
Sprite* m_sprite;
|
|
|
|
|
int m_new_width;
|
|
|
|
|
int m_new_height;
|
|
|
|
|
ResizeMethod m_resize_method;
|
|
|
|
|
|
|
|
|
|
inline int scale_x(int x) const { return x * m_new_width / m_sprite->getWidth(); }
|
|
|
|
|
inline int scale_y(int y) const { return y * m_new_height / m_sprite->getHeight(); }
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
2013-03-12 07:29:45 +08:00
|
|
|
SpriteSizeJob(const ContextReader& reader, int new_width, int new_height, ResizeMethod resize_method)
|
2012-01-06 06:45:03 +08:00
|
|
|
: Job("Sprite Size")
|
2013-03-12 07:29:45 +08:00
|
|
|
, m_writer(reader)
|
|
|
|
|
, m_document(m_writer.document())
|
|
|
|
|
, m_sprite(m_writer.sprite())
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
|
|
|
|
m_new_width = new_width;
|
|
|
|
|
m_new_height = new_height;
|
|
|
|
|
m_resize_method = resize_method;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [working thread]
|
|
|
|
|
*/
|
|
|
|
|
virtual void onJob()
|
|
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
UndoTransaction undoTransaction(m_writer.context(), "Sprite Size");
|
|
|
|
|
DocumentApi api = m_writer.document()->getApi();
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
// Get all sprite cels
|
|
|
|
|
CelList cels;
|
|
|
|
|
m_sprite->getCels(cels);
|
|
|
|
|
|
|
|
|
|
// For each cel...
|
|
|
|
|
int progress = 0;
|
|
|
|
|
for (CelIterator it = cels.begin(); it != cels.end(); ++it, ++progress) {
|
|
|
|
|
Cel* cel = *it;
|
|
|
|
|
|
|
|
|
|
// Change its location
|
2013-03-12 07:29:45 +08:00
|
|
|
api.setCelPosition(m_sprite, cel, scale_x(cel->getX()), scale_y(cel->getY()));
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
// Get cel's image
|
|
|
|
|
Image* image = m_sprite->getStock()->getImage(cel->getImage());
|
|
|
|
|
if (!image)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Resize the image
|
2013-11-10 06:59:05 +08:00
|
|
|
int w = scale_x(image->getWidth());
|
|
|
|
|
int h = scale_y(image->getHeight());
|
2012-02-13 10:21:06 +08:00
|
|
|
Image* new_image = Image::create(image->getPixelFormat(), MAX(1, w), MAX(1, h));
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-11-10 06:59:05 +08:00
|
|
|
raster::algorithm::fixup_image_transparent_colors(image);
|
|
|
|
|
raster::algorithm::resize_image(image, new_image,
|
|
|
|
|
m_resize_method,
|
|
|
|
|
m_sprite->getPalette(cel->getFrame()),
|
|
|
|
|
m_sprite->getRgbMap(cel->getFrame()));
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-03-12 07:29:45 +08:00
|
|
|
api.replaceStockImage(m_sprite, cel->getImage(), new_image);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
jobProgress((float)progress / cels.size());
|
|
|
|
|
|
|
|
|
|
// cancel all the operation?
|
|
|
|
|
if (isCanceled())
|
|
|
|
|
return; // UndoTransaction destructor will undo all operations
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resize mask
|
|
|
|
|
if (m_document->isMaskVisible()) {
|
2013-08-06 08:20:19 +08:00
|
|
|
base::UniquePtr<Image> old_bitmap
|
2013-11-10 06:59:05 +08:00
|
|
|
(crop_image(m_document->getMask()->getBitmap(), -1, -1,
|
|
|
|
|
m_document->getMask()->getBitmap()->getWidth()+2,
|
|
|
|
|
m_document->getMask()->getBitmap()->getHeight()+2, 0));
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-11-10 06:59:05 +08:00
|
|
|
int w = scale_x(old_bitmap->getWidth());
|
|
|
|
|
int h = scale_y(old_bitmap->getHeight());
|
2013-08-06 08:20:19 +08:00
|
|
|
base::UniquePtr<Mask> new_mask(new Mask);
|
2012-01-09 09:34:36 +08:00
|
|
|
new_mask->replace(scale_x(m_document->getMask()->getBounds().x-1),
|
|
|
|
|
scale_y(m_document->getMask()->getBounds().y-1), MAX(1, w), MAX(1, h));
|
2013-11-10 06:59:05 +08:00
|
|
|
algorithm::resize_image(old_bitmap, new_mask->getBitmap(),
|
|
|
|
|
m_resize_method,
|
|
|
|
|
m_sprite->getPalette(FrameNumber(0)), // Ignored
|
|
|
|
|
m_sprite->getRgbMap(FrameNumber(0))); // Ignored
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-01-10 06:13:48 +08:00
|
|
|
// Reshrink
|
2013-11-10 06:59:05 +08:00
|
|
|
new_mask->intersect(new_mask->getBounds());
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-01-10 06:13:48 +08:00
|
|
|
// Copy new mask
|
2013-03-12 07:29:45 +08:00
|
|
|
api.copyToCurrentMask(new_mask);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-01-10 06:13:48 +08:00
|
|
|
// Regenerate mask
|
|
|
|
|
m_document->resetTransformation();
|
2012-01-06 06:45:03 +08:00
|
|
|
m_document->generateMaskBoundaries();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// resize sprite
|
2013-03-12 07:29:45 +08:00
|
|
|
api.setSpriteSize(m_sprite, m_new_width, m_new_height);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
// commit changes
|
|
|
|
|
undoTransaction.commit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
class SpriteSizeCommand : public Command {
|
2012-01-06 06:45:03 +08:00
|
|
|
public:
|
|
|
|
|
SpriteSizeCommand();
|
2014-03-30 04:08:40 +08:00
|
|
|
Command* clone() const OVERRIDE { return new SpriteSizeCommand(*this); }
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
bool onEnabled(Context* context);
|
|
|
|
|
void onExecute(Context* context);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void onLockRatioClick();
|
2012-04-19 09:57:36 +08:00
|
|
|
void onWidthPxChange();
|
|
|
|
|
void onHeightPxChange();
|
|
|
|
|
void onWidthPercChange();
|
|
|
|
|
void onHeightPercChange();
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
CheckBox* m_lockRatio;
|
2012-04-19 09:57:36 +08:00
|
|
|
Entry* m_widthPx;
|
|
|
|
|
Entry* m_heightPx;
|
|
|
|
|
Entry* m_widthPerc;
|
|
|
|
|
Entry* m_heightPerc;
|
2012-01-06 06:45:03 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SpriteSizeCommand::SpriteSizeCommand()
|
|
|
|
|
: Command("SpriteSize",
|
|
|
|
|
"Sprite Size",
|
|
|
|
|
CmdRecordableFlag)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SpriteSizeCommand::onEnabled(Context* context)
|
|
|
|
|
{
|
|
|
|
|
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
|
|
|
|
ContextFlags::HasActiveSprite);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SpriteSizeCommand::onExecute(Context* context)
|
|
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
const ContextReader reader(UIContext::instance()); // TODO use the context in sprite size command
|
|
|
|
|
const Sprite* sprite(reader.sprite());
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
// load the window widget
|
2013-08-06 08:20:19 +08:00
|
|
|
base::UniquePtr<Window> window(app::load_widget<Window>("sprite_size.xml", "sprite_size"));
|
2012-06-16 10:37:59 +08:00
|
|
|
m_widthPx = app::find_widget<Entry>(window, "width_px");
|
|
|
|
|
m_heightPx = app::find_widget<Entry>(window, "height_px");
|
|
|
|
|
m_widthPerc = app::find_widget<Entry>(window, "width_perc");
|
|
|
|
|
m_heightPerc = app::find_widget<Entry>(window, "height_perc");
|
|
|
|
|
m_lockRatio = app::find_widget<CheckBox>(window, "lock_ratio");
|
|
|
|
|
ComboBox* method = app::find_widget<ComboBox>(window, "method");
|
|
|
|
|
Widget* ok = app::find_widget<Widget>(window, "ok");
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
m_widthPx->setTextf("%d", sprite->getWidth());
|
|
|
|
|
m_heightPx->setTextf("%d", sprite->getHeight());
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
m_lockRatio->Click.connect(Bind<void>(&SpriteSizeCommand::onLockRatioClick, this));
|
2012-04-19 09:57:36 +08:00
|
|
|
m_widthPx->EntryChange.connect(Bind<void>(&SpriteSizeCommand::onWidthPxChange, this));
|
|
|
|
|
m_heightPx->EntryChange.connect(Bind<void>(&SpriteSizeCommand::onHeightPxChange, this));
|
|
|
|
|
m_widthPerc->EntryChange.connect(Bind<void>(&SpriteSizeCommand::onWidthPercChange, this));
|
|
|
|
|
m_heightPerc->EntryChange.connect(Bind<void>(&SpriteSizeCommand::onHeightPercChange, this));
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
method->addItem("Nearest-neighbor");
|
|
|
|
|
method->addItem("Bilinear");
|
2013-11-10 06:59:05 +08:00
|
|
|
method->setSelectedItemIndex(get_config_int("SpriteSize", "Method",
|
|
|
|
|
raster::algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR));
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-01-11 23:43:25 +08:00
|
|
|
window->remapWindow();
|
|
|
|
|
window->centerWindow();
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
load_window_pos(window, "SpriteSize");
|
|
|
|
|
window->setVisible(true);
|
2012-07-09 10:24:42 +08:00
|
|
|
window->openWindowInForeground();
|
2012-01-06 06:45:03 +08:00
|
|
|
save_window_pos(window, "SpriteSize");
|
|
|
|
|
|
2013-01-11 23:43:25 +08:00
|
|
|
if (window->getKiller() == ok) {
|
2012-04-19 09:57:36 +08:00
|
|
|
int new_width = m_widthPx->getTextInt();
|
|
|
|
|
int new_height = m_heightPx->getTextInt();
|
2012-01-06 06:45:03 +08:00
|
|
|
ResizeMethod resize_method =
|
2013-04-04 07:31:02 +08:00
|
|
|
(ResizeMethod)method->getSelectedItemIndex();
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
|
set_config_int("SpriteSize", "Method", resize_method);
|
|
|
|
|
|
|
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
SpriteSizeJob job(reader, new_width, new_height, resize_method);
|
2012-01-06 06:45:03 +08:00
|
|
|
job.startJob();
|
2014-05-03 04:04:55 +08:00
|
|
|
job.waitJob();
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
|
2013-03-12 07:29:45 +08:00
|
|
|
ContextWriter writer(reader);
|
|
|
|
|
update_screen_for_document(writer.document());
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SpriteSizeCommand::onLockRatioClick()
|
|
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
const ContextReader reader(UIContext::instance()); // TODO use the context in sprite size command
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
onWidthPxChange();
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
void SpriteSizeCommand::onWidthPxChange()
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
const ContextReader reader(UIContext::instance()); // TODO use the context in sprite size command
|
|
|
|
|
const Sprite* sprite(reader.sprite());
|
2012-04-19 09:57:36 +08:00
|
|
|
int width = m_widthPx->getTextInt();
|
2012-01-06 06:45:03 +08:00
|
|
|
double perc = 100.0 * width / sprite->getWidth();
|
|
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
m_widthPerc->setTextf(PERC_FORMAT, perc);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
if (m_lockRatio->isSelected()) {
|
|
|
|
|
m_heightPerc->setTextf(PERC_FORMAT, perc);
|
|
|
|
|
m_heightPx->setTextf("%d", sprite->getHeight() * width / sprite->getWidth());
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
void SpriteSizeCommand::onHeightPxChange()
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
const ContextReader reader(UIContext::instance()); // TODO use the context in sprite size command
|
|
|
|
|
const Sprite* sprite(reader.sprite());
|
2012-04-19 09:57:36 +08:00
|
|
|
int height = m_heightPx->getTextInt();
|
2012-01-06 06:45:03 +08:00
|
|
|
double perc = 100.0 * height / sprite->getHeight();
|
|
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
m_heightPerc->setTextf(PERC_FORMAT, perc);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
if (m_lockRatio->isSelected()) {
|
|
|
|
|
m_widthPerc->setTextf(PERC_FORMAT, perc);
|
|
|
|
|
m_widthPx->setTextf("%d", sprite->getWidth() * height / sprite->getHeight());
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
void SpriteSizeCommand::onWidthPercChange()
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
const ContextReader reader(UIContext::instance()); // TODO use the context in sprite size command
|
|
|
|
|
const Sprite* sprite(reader.sprite());
|
2012-04-19 09:57:36 +08:00
|
|
|
double width = m_widthPerc->getTextDouble();
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
m_widthPx->setTextf("%d", (int)(sprite->getWidth() * width / 100));
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
if (m_lockRatio->isSelected()) {
|
|
|
|
|
m_heightPx->setTextf("%d", (int)(sprite->getHeight() * width / 100));
|
|
|
|
|
m_heightPerc->setText(m_widthPerc->getText());
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
void SpriteSizeCommand::onHeightPercChange()
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
const ContextReader reader(UIContext::instance()); // TODO use the context in sprite size command
|
|
|
|
|
const Sprite* sprite(reader.sprite());
|
2012-04-19 09:57:36 +08:00
|
|
|
double height = m_heightPerc->getTextDouble();
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
m_heightPx->setTextf("%d", (int)(sprite->getHeight() * height / 100));
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2012-04-19 09:57:36 +08:00
|
|
|
if (m_lockRatio->isSelected()) {
|
|
|
|
|
m_widthPx->setTextf("%d", (int)(sprite->getWidth() * height / 100));
|
|
|
|
|
m_widthPerc->setText(m_heightPerc->getText());
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Command* CommandFactory::createSpriteSizeCommand()
|
|
|
|
|
{
|
|
|
|
|
return new SpriteSizeCommand;
|
|
|
|
|
}
|
2013-08-06 08:20:19 +08:00
|
|
|
|
|
|
|
|
} // namespace app
|