2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
|
|
|
// 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 version 2 as
|
|
|
|
// published by the Free Software Foundation.
|
2012-01-06 06:45:03 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
|
#include "app/app.h"
|
|
|
|
#include "app/commands/command.h"
|
|
|
|
#include "app/commands/params.h"
|
|
|
|
#include "app/context_access.h"
|
|
|
|
#include "app/document_api.h"
|
2014-08-25 20:06:38 +08:00
|
|
|
#include "app/document_range.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/job.h"
|
|
|
|
#include "app/modules/gui.h"
|
2015-02-09 22:40:43 +08:00
|
|
|
#include "app/transaction.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ui/color_bar.h"
|
2014-08-25 20:06:38 +08:00
|
|
|
#include "app/ui/main_window.h"
|
|
|
|
#include "app/ui/timeline.h"
|
|
|
|
#include "app/util/range_utils.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
#include "base/convert_to.h"
|
2014-10-21 09:21:31 +08:00
|
|
|
#include "doc/cel.h"
|
2015-02-09 22:40:43 +08:00
|
|
|
#include "doc/cels_range.h"
|
2014-10-21 09:21:31 +08:00
|
|
|
#include "doc/image.h"
|
|
|
|
#include "doc/mask.h"
|
|
|
|
#include "doc/sprite.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "ui/ui.h"
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
namespace app {
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
class RotateCommand : public Command {
|
2012-01-06 06:45:03 +08:00
|
|
|
public:
|
2014-08-25 20:06:38 +08:00
|
|
|
RotateCommand();
|
|
|
|
Command* clone() const override { return new RotateCommand(*this); }
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
protected:
|
|
|
|
void onLoadParams(Params* params);
|
|
|
|
bool onEnabled(Context* context);
|
|
|
|
void onExecute(Context* context);
|
2014-10-29 22:58:03 +08:00
|
|
|
std::string onGetFriendlyName() const;
|
2014-08-25 20:06:38 +08:00
|
|
|
|
|
|
|
private:
|
|
|
|
bool m_flipMask;
|
|
|
|
int m_angle;
|
2012-01-06 06:45:03 +08:00
|
|
|
};
|
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
class RotateJob : public Job
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
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_angle;
|
2014-08-25 20:06:38 +08:00
|
|
|
CelList m_cels;
|
|
|
|
bool m_rotateSprite;
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
public:
|
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
RotateJob(const ContextReader& reader, int angle, const CelList& cels, bool rotateSprite)
|
2012-01-06 06:45:03 +08:00
|
|
|
: Job("Rotate Canvas")
|
2013-03-12 07:29:45 +08:00
|
|
|
, m_writer(reader)
|
|
|
|
, m_document(m_writer.document())
|
|
|
|
, m_sprite(m_writer.sprite())
|
2014-08-25 20:06:38 +08:00
|
|
|
, m_cels(cels)
|
|
|
|
, m_rotateSprite(rotateSprite)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
|
|
|
m_angle = angle;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
2015-02-09 22:40:43 +08:00
|
|
|
// [working thread]
|
2012-01-06 06:45:03 +08:00
|
|
|
virtual void onJob()
|
|
|
|
{
|
2015-01-19 09:05:33 +08:00
|
|
|
Transaction transaction(m_writer.context(), "Rotate Canvas");
|
|
|
|
DocumentApi api = m_document->getApi(transaction);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-01-21 12:56:14 +08:00
|
|
|
// 1) Rotate cel positions
|
|
|
|
for (Cel* cel : m_cels) {
|
|
|
|
Image* image = cel->image();
|
|
|
|
if (!image)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (m_angle) {
|
|
|
|
case 180:
|
|
|
|
api.setCelPosition(m_sprite, cel,
|
|
|
|
m_sprite->width() - cel->x() - image->width(),
|
|
|
|
m_sprite->height() - cel->y() - image->height());
|
|
|
|
break;
|
|
|
|
case 90:
|
|
|
|
api.setCelPosition(m_sprite, cel,
|
|
|
|
m_sprite->height() - cel->y() - image->height(),
|
|
|
|
cel->x());
|
|
|
|
break;
|
|
|
|
case -90:
|
|
|
|
api.setCelPosition(m_sprite, cel,
|
|
|
|
cel->y(),
|
|
|
|
m_sprite->width() - cel->x() - image->width());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2) Rotate images
|
2014-08-25 20:06:38 +08:00
|
|
|
int i = 0;
|
|
|
|
for (Cel* cel : m_cels) {
|
2014-07-30 12:28:15 +08:00
|
|
|
Image* image = cel->image();
|
2015-02-09 22:40:43 +08:00
|
|
|
if (image) {
|
2015-01-04 21:58:14 +08:00
|
|
|
ImageRef new_image(Image::create(image->pixelFormat(),
|
|
|
|
m_angle == 180 ? image->width(): image->height(),
|
|
|
|
m_angle == 180 ? image->height(): image->width()));
|
2014-10-21 09:21:31 +08:00
|
|
|
doc::rotate_image(image, new_image, m_angle);
|
2014-08-25 20:06:38 +08:00
|
|
|
|
2015-01-04 21:58:14 +08:00
|
|
|
api.replaceImage(m_sprite, cel->imageRef(), new_image);
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
jobProgress((float)i / m_cels.size());
|
|
|
|
++i;
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
// cancel all the operation?
|
|
|
|
if (isCanceled())
|
2015-01-19 09:05:33 +08:00
|
|
|
return; // Transaction destructor will undo all operations
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// rotate mask
|
|
|
|
if (m_document->isMaskVisible()) {
|
2014-07-30 12:28:15 +08:00
|
|
|
Mask* origMask = m_document->mask();
|
2013-08-06 08:20:19 +08:00
|
|
|
base::UniquePtr<Mask> new_mask(new Mask());
|
2014-07-30 12:28:15 +08:00
|
|
|
const gfx::Rect& origBounds = origMask->bounds();
|
2012-01-06 06:45:03 +08:00
|
|
|
int x = 0, y = 0;
|
|
|
|
|
|
|
|
switch (m_angle) {
|
|
|
|
case 180:
|
2014-07-30 12:28:15 +08:00
|
|
|
x = m_sprite->width() - origBounds.x - origBounds.w;
|
|
|
|
y = m_sprite->height() - origBounds.y - origBounds.h;
|
2012-01-06 06:45:03 +08:00
|
|
|
break;
|
|
|
|
case 90:
|
2014-07-30 12:28:15 +08:00
|
|
|
x = m_sprite->height() - origBounds.y - origBounds.h;
|
2012-01-09 09:34:36 +08:00
|
|
|
y = origBounds.x;
|
2012-01-06 06:45:03 +08:00
|
|
|
break;
|
|
|
|
case -90:
|
2012-01-09 09:34:36 +08:00
|
|
|
x = origBounds.y;
|
2014-07-30 12:28:15 +08:00
|
|
|
y = m_sprite->width() - origBounds.x - origBounds.w;
|
2012-01-06 06:45:03 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the new rotated mask
|
2014-12-14 06:10:54 +08:00
|
|
|
new_mask->replace(
|
|
|
|
gfx::Rect(x, y,
|
|
|
|
m_angle == 180 ? origBounds.w: origBounds.h,
|
|
|
|
m_angle == 180 ? origBounds.h: origBounds.w));
|
2014-10-21 09:21:31 +08:00
|
|
|
doc::rotate_image(origMask->bitmap(), new_mask->bitmap(), m_angle);
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
// change the sprite's size
|
2014-08-25 20:06:38 +08:00
|
|
|
if (m_rotateSprite && m_angle != 180)
|
2014-07-30 12:28:15 +08:00
|
|
|
api.setSpriteSize(m_sprite, m_sprite->height(), m_sprite->width());
|
2012-01-06 06:45:03 +08:00
|
|
|
|
|
|
|
// commit changes
|
2015-01-19 09:05:33 +08:00
|
|
|
transaction.commit();
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
RotateCommand::RotateCommand()
|
|
|
|
: Command("Rotate",
|
2012-01-06 06:45:03 +08:00
|
|
|
"Rotate Canvas",
|
|
|
|
CmdRecordableFlag)
|
|
|
|
{
|
2014-08-25 20:06:38 +08:00
|
|
|
m_flipMask = false;
|
2012-01-06 06:45:03 +08:00
|
|
|
m_angle = 0;
|
|
|
|
}
|
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
void RotateCommand::onLoadParams(Params* params)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2014-08-25 20:06:38 +08:00
|
|
|
std::string target = params->get("target");
|
|
|
|
m_flipMask = (target == "mask");
|
|
|
|
|
2012-01-06 06:45:03 +08:00
|
|
|
if (params->has_param("angle")) {
|
2014-09-21 22:59:39 +08:00
|
|
|
m_angle = strtol(params->get("angle").c_str(), NULL, 10);
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
bool RotateCommand::onEnabled(Context* context)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
|
|
|
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
|
|
|
ContextFlags::HasActiveSprite);
|
|
|
|
}
|
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
void RotateCommand::onExecute(Context* context)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
ContextReader reader(context);
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2014-08-25 20:06:38 +08:00
|
|
|
CelList cels;
|
|
|
|
bool rotateSprite = false;
|
|
|
|
|
|
|
|
// Flip the mask or current cel
|
|
|
|
if (m_flipMask) {
|
|
|
|
DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range();
|
|
|
|
if (range.enabled())
|
2015-02-09 22:40:43 +08:00
|
|
|
cels = get_unique_cels(reader.sprite(), range);
|
2014-08-25 20:06:38 +08:00
|
|
|
else if (reader.cel())
|
|
|
|
cels.push_back(reader.cel());
|
|
|
|
}
|
|
|
|
// Flip the whole sprite
|
|
|
|
else if (reader.sprite()) {
|
2015-02-09 22:40:43 +08:00
|
|
|
for (Cel* cel : reader.sprite()->uniqueCels())
|
|
|
|
cels.push_back(cel);
|
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
rotateSprite = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cels.empty()) // Nothing to do
|
|
|
|
return;
|
|
|
|
|
|
|
|
RotateJob job(reader, m_angle, cels, rotateSprite);
|
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
|
|
|
reader.document()->generateMaskBoundaries();
|
|
|
|
update_screen_for_document(reader.document());
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
std::string RotateCommand::onGetFriendlyName() const
|
|
|
|
{
|
|
|
|
std::string text = "Rotate";
|
|
|
|
|
|
|
|
if (m_flipMask)
|
|
|
|
text += " Selection";
|
|
|
|
else
|
|
|
|
text += " Sprite";
|
|
|
|
|
|
|
|
text += " " + base::convert_to<std::string>(m_angle) + "\xc2\xb0";
|
|
|
|
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
2014-08-25 20:06:38 +08:00
|
|
|
Command* CommandFactory::createRotateCommand()
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2014-08-25 20:06:38 +08:00
|
|
|
return new RotateCommand;
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2013-08-06 08:20:19 +08:00
|
|
|
|
|
|
|
} // namespace app
|