Add transformation support (scale/rotate) to the selected area.

+ Added support to rotate images of IMAGE_BITMAP type.
+ Added transformation_handle & pivot_handle parts in sheet.png.
+ Added rotation cursors in sheet.png.
+ Added gfx::Transformation class and Document::m_transformation field.
+ Added StandbyState::Decorator and TransformHandles class to draw
  transformation handles.
+ Modified PixelsMovement class to support transformation handles.
+ Added new Linear Algebra library with Vector2d class.
This commit is contained in:
David Capello 2012-01-01 23:08:25 -03:00
parent 181c75bb93
commit 07edcdb1e2
26 changed files with 1257 additions and 97 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -18,6 +18,14 @@
<cursor id="size_bl" x="80" y="176" w="16" h="16" focusx="8" focusy="8" />
<cursor id="size_b" x="80" y="192" w="16" h="16" focusx="8" focusy="8" />
<cursor id="size_br" x="80" y="208" w="16" h="16" focusx="8" focusy="8" />
<cursor id="rotate_tl" x="240" y="96" w="16" h="16" focusx="8" focusy="8" />
<cursor id="rotate_t" x="240" y="112" w="16" h="16" focusx="8" focusy="8" />
<cursor id="rotate_tr" x="240" y="128" w="16" h="16" focusx="8" focusy="8" />
<cursor id="rotate_l" x="240" y="144" w="16" h="16" focusx="8" focusy="8" />
<cursor id="rotate_r" x="240" y="160" w="16" h="16" focusx="8" focusy="8" />
<cursor id="rotate_bl" x="240" y="176" w="16" h="16" focusx="8" focusy="8" />
<cursor id="rotate_b" x="240" y="192" w="16" h="16" focusx="8" focusy="8" />
<cursor id="rotate_br" x="240" y="208" w="16" h="16" focusx="8" focusy="8" />
<cursor id="eyedropper" x="80" y="224" w="16" h="16" focusx="0" focusy="15" />
</cursors>
@ -179,6 +187,8 @@
<part id="drop_down_button_right_hot" x="72" y="32" w1="2" w2="1" w3="3" h1="4" h2="6" h3="6" />
<part id="drop_down_button_right_focused" x="55" y="48" w1="2" w2="2" w3="3" h1="4" h2="6" h3="6" />
<part id="drop_down_button_right_selected" x="71" y="48" w1="2" w2="2" w3="3" h1="4" h2="6" h3="6" />
<part id="transformation_handle" x="208" y="144" w="5" h="5" />
<part id="pivot_handle" x="224" y="144" w="9" h="9" />
</parts>
</skin>

View File

@ -320,6 +320,7 @@ add_library(aseprite-library
widgets/editor/select_box_state.cpp
widgets/editor/standby_state.cpp
widgets/editor/tool_loop_impl.cpp
widgets/editor/transform_handles.cpp
widgets/fileview.cpp
widgets/groupbut.cpp
widgets/hex_color_entry.cpp

View File

@ -278,6 +278,8 @@ void Document::setMask(const Mask* mask)
{
m_mask.reset(mask_new_copy(mask));
m_maskVisible = true;
resetTransformation();
}
bool Document::isMaskVisible() const
@ -293,6 +295,27 @@ void Document::setMaskVisible(bool visible)
m_maskVisible = visible;
}
//////////////////////////////////////////////////////////////////////
// Transformation
gfx::Transformation Document::getTransformation() const
{
return m_transformation;
}
void Document::setTransformation(const gfx::Transformation& transform)
{
m_transformation = transform;
}
void Document::resetTransformation()
{
if (m_mask)
m_transformation = gfx::Transformation(m_mask->getBounds());
else
m_transformation = gfx::Transformation();
}
//////////////////////////////////////////////////////////////////////
// Copying

View File

@ -23,6 +23,7 @@
#include "base/shared_ptr.h"
#include "base/unique_ptr.h"
#include "document_id.h"
#include "gfx/transformation.h"
#include <string>
@ -145,6 +146,13 @@ public:
// being hidden and shown to him).
void setMaskVisible(bool visible);
//////////////////////////////////////////////////////////////////////
// Transformation
gfx::Transformation getTransformation() const;
void setTransformation(const gfx::Transformation& transform);
void resetTransformation();
//////////////////////////////////////////////////////////////////////
// Copying
@ -222,6 +230,9 @@ private:
UniquePtr<Mask> m_mask;
bool m_maskVisible;
// Current transformation.
gfx::Transformation m_transformation;
DISABLE_COPYING(Document);
};

View File

@ -3,4 +3,5 @@
add_library(gfx-lib
hsv.cpp
rgb.cpp)
rgb.cpp
transformation.cpp)

View File

@ -0,0 +1,82 @@
// ASE gfx library
// Copyright (C) 2001-2011 David Capello
//
// This source file is ditributed under a BSD-like license, please
// read LICENSE.txt for more information.
#include "config.h"
#include "gfx/transformation.h"
#include "gfx/point.h"
#include "gfx/size.h"
#include <cmath>
namespace gfx {
Transformation::Transformation()
{
m_bounds.x = 0;
m_bounds.y = 0;
m_bounds.w = 0;
m_bounds.h = 0;
m_angle = 0.0;
m_pivot.x = 0;
m_pivot.y = 0;
}
Transformation::Transformation(const Rect& bounds)
: m_bounds(bounds)
{
m_angle = 0.0;
m_pivot.x = bounds.x + bounds.w/2;
m_pivot.y = bounds.y + bounds.h/2;
}
void Transformation::transformBox(Corners& corners) const
{
PointT<double> pivot_f(m_pivot);
// TODO We could create a composed 4x4 matrix with all
// transformation and apply the same matrix to avoid calling
// rotatePoint/cos/sin functions 4 times, anyway, it's not
// critical at this point.
corners = m_bounds;
for (size_t c=0; c<corners.size(); ++c)
corners[c] = Transformation::rotatePoint(corners[c], pivot_f, m_angle);
}
void Transformation::displacePivotTo(const Point& newPivot)
{
// Calculate the rotated corners
Corners corners;
transformBox(corners);
// Rotate-back (-angle) the position of the rotated origin (corners[0])
// using the new pivot.
PointT<double> newBoundsOrigin =
rotatePoint(corners.leftTop(),
PointT<double>(newPivot.x, newPivot.y),
-m_angle);
// Change the new pivot.
m_pivot = newPivot;
m_bounds = Rect(Point(newBoundsOrigin), m_bounds.getSize());
}
PointT<double> Transformation::rotatePoint(
const PointT<double>& point,
const PointT<double>& pivot,
double angle)
{
double cos = std::cos(-angle);
double sin = std::sin(-angle);
double dx = (point.x-pivot.x);
double dy = (point.y-pivot.y);
return PointT<double>(
pivot.x + dx*cos - dy*sin,
pivot.y + dy*cos + dx*sin);
}
}

97
src/gfx/transformation.h Normal file
View File

@ -0,0 +1,97 @@
// ASE gfx library
// Copyright (C) 2001-2011 David Capello
//
// This source file is ditributed under a BSD-like license, please
// read LICENSE.txt for more information.
#ifndef TRANSFORMATION_H_INCLUDED
#define TRANSFORMATION_H_INCLUDED
#include "gfx/point.h"
#include "gfx/rect.h"
#include <vector>
namespace gfx {
// Represents a transformation that can be done by the user in the
// document when he/she moves the mask using the selection handles.
class Transformation
{
public:
class Corners {
public:
enum {
LEFT_TOP = 0,
RIGHT_TOP = 1,
RIGHT_BOTTOM = 2,
LEFT_BOTTOM = 3,
NUM_OF_CORNERS = 4
};
Corners() : m_corners(NUM_OF_CORNERS) { }
size_t size() const { return m_corners.size(); }
PointT<double>& operator[](int index) { return m_corners[index]; }
const PointT<double>& operator[](int index) const { return m_corners[index]; }
const PointT<double>& leftTop() const { return m_corners[LEFT_TOP]; }
const PointT<double>& rightTop() const { return m_corners[RIGHT_TOP]; }
const PointT<double>& rightBottom() const { return m_corners[RIGHT_BOTTOM]; }
const PointT<double>& leftBottom() const { return m_corners[LEFT_BOTTOM]; }
void leftTop(const PointT<double>& pt) { m_corners[LEFT_TOP] = pt; }
void rightTop(const PointT<double>& pt) { m_corners[RIGHT_TOP] = pt; }
void rightBottom(const PointT<double>& pt) { m_corners[RIGHT_BOTTOM] = pt; }
void leftBottom(const PointT<double>& pt) { m_corners[LEFT_BOTTOM] = pt; }
Corners& operator=(const gfx::Rect& bounds) {
m_corners[LEFT_TOP].x = bounds.x;
m_corners[LEFT_TOP].y = bounds.y;
m_corners[RIGHT_TOP].x = bounds.x + bounds.w;
m_corners[RIGHT_TOP].y = bounds.y;
m_corners[RIGHT_BOTTOM].x = bounds.x + bounds.w;
m_corners[RIGHT_BOTTOM].y = bounds.y + bounds.h;
m_corners[LEFT_BOTTOM].x = bounds.x;
m_corners[LEFT_BOTTOM].y = bounds.y + bounds.h;
return *this;
}
private:
std::vector<PointT<double> > m_corners;
};
Transformation();
Transformation(const Rect& bounds);
// Simple getters and setters. The angle is in radians.
const Rect& bounds() const { return m_bounds; }
const Point& pivot() const { return m_pivot; }
double angle() const { return m_angle; }
void bounds(const Rect& bounds) { m_bounds = bounds; }
void pivot(const Point& pivot) { m_pivot = pivot; }
void angle(double angle) { m_angle = angle; }
// Applies the transformation (rotation with angle/pivot) to the
// current bounds (m_bounds).
void transformBox(Corners& corners) const;
// Changes the pivot to another location, adjusting the bounds to
// keep the current rotated-corners in the same location.
void displacePivotTo(const Point& newPivot);
// Static helper method to rotate points.
static PointT<double> rotatePoint(const PointT<double>& point,
const PointT<double>& pivot, double angle);
private:
Rect m_bounds;
Point m_pivot;
double m_angle;
};
} // namespace gfx
#endif

View File

@ -54,6 +54,14 @@ enum {
JI_CURSOR_SIZE_BL,
JI_CURSOR_SIZE_B,
JI_CURSOR_SIZE_BR,
JI_CURSOR_ROTATE_TL,
JI_CURSOR_ROTATE_T,
JI_CURSOR_ROTATE_TR,
JI_CURSOR_ROTATE_L,
JI_CURSOR_ROTATE_R,
JI_CURSOR_ROTATE_BL,
JI_CURSOR_ROTATE_B,
JI_CURSOR_ROTATE_BR,
JI_CURSOR_EYEDROPPER,
JI_CURSORS
};

29
src/la/LICENSE.txt Normal file
View File

@ -0,0 +1,29 @@
ASEPRITE Linear Algebra Library
Copyright (c) 2001-2011 David Capello
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the author nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

78
src/la/vector2d.h Normal file
View File

@ -0,0 +1,78 @@
// ASEPRITE Linear Algebra Library
// Copyright (C) 2001-2011 David Capello
//
// This source file is ditributed under a BSD-like license, please
// read LICENSE.txt for more information.
#ifndef LA_VECTOR2D_H_INCLUDED
#define LA_VECTOR2D_H_INCLUDED
#include <cmath>
namespace la {
template<typename T>
class Vector2d {
public:
T x, y;
Vector2d() {
x = y = 0.0;
}
Vector2d(const T& u, const T& v) {
x = u; y = v;
}
Vector2d(const Vector2d& v) {
x = v.x; y = v.y;
}
Vector2d operator-() const { return Vector2d(-x, -y); }
Vector2d operator+(const Vector2d& v) const { return Vector2d(x+v.x, y+v.y); }
Vector2d operator-(const Vector2d& v) const { return Vector2d(x-v.x, y-v.y); }
T operator*(const Vector2d& v) const { return dotProduct(v); }
Vector2d operator*(const T& f) const { return Vector2d(x*f, y*f); }
Vector2d operator/(const T& f) const { return Vector2d(x/f, y/f); }
Vector2d& operator= (const Vector2d& v) { x=v.x; y=v.y; return *this; }
Vector2d& operator+=(const Vector2d& v) { x+=v.x; y+=v.y; return *this; }
Vector2d& operator-=(const Vector2d& v) { x-=v.x; y-=v.y; return *this; }
Vector2d& operator*=(const T& f) { x*=f; y*=f; return *this; }
Vector2d& operator/=(const T& f) { x/=f; y/=f; return *this; }
T magnitude() const {
return std::sqrt(x*x + y*y);
}
T dotProduct(const Vector2d& v) const {
return x*v.x + y*v.y;
}
Vector2d projectOn(const Vector2d& v) const {
return v * (this->dotProduct(v) / std::pow(v.magnitude(), 2));
}
T angle() const {
return std::atan2(y, x);
}
Vector2d normalize() const {
return Vector2d(*this) / magnitude();
}
Vector2d getNormal() const {
return Vector2d(y, -x);
}
};
} // namespace la
template<typename T>
la::Vector2d<T> operator*(const T& f, const la::Vector2d<T>& v) {
return la::Vector2d<T>(v.x*f, v.y*f);
}
#endif // LA_VECTOR2D_H_INCLUDED

View File

@ -83,6 +83,11 @@ void Mask::unfreeze()
shrink();
}
gfx::Rect Mask::getBounds() const
{
return gfx::Rect(x, y, w, h);
}
//////////////////////////////////////////////////////////////////////
Mask* mask_new()

View File

@ -19,18 +19,18 @@
#ifndef RASTER_MASK_H_INCLUDED
#define RASTER_MASK_H_INCLUDED
#include "gfx/rect.h"
#include "raster/gfxobj.h"
#include "raster/image.h"
// Represents the selection (selected pixels, 0/1, 0=non-selected, 1=selected)
class Mask : public GfxObj
{
int m_freeze_count;
public:
// TODO private this
char* name; /* mask name */
int x, y, w, h; /* region bounds */
Image* bitmap; /* bitmapped image mask */
char* name; // Mask name
int x, y, w, h; // Region bounds
Image* bitmap; // Bitmapped image mask
Mask();
Mask(const Mask& mask);
@ -52,12 +52,17 @@ public:
image_getpixel(bitmap, u-x, v-y));
}
gfx::Rect getBounds() const;
// These functions can be used to disable the automatic call to
// "shrink" method (so you can do a lot of modifications without
// lossing time shrink the mask in each little operation)
// lossing time shrinking the mask in each little operation).
void freeze();
void unfreeze();
// Returns true if the mask is frozen (See freeze/unfreeze functions).
bool isFrozen() const { return m_freeze_count > 0; }
// Adds the specified rectangle in the mask/selection
void add(int x, int y, int w, int h);
@ -71,6 +76,8 @@ public:
private:
void initialize();
int m_freeze_count;
};
Mask* mask_new();

View File

@ -132,50 +132,78 @@ static void draw_scanline(Image *bmp, Image *spr,
fixed l_spr_x, fixed l_spr_y,
fixed spr_dx, fixed spr_dy)
{
Traits::address_t addr, end_addr;
Traits::address_t* spr_line = (Traits::address_t*)spr->line;
Delegate delegate;
r_bmp_x >>= 16;
l_bmp_x >>= 16;
addr = ((Traits::address_t*)bmp->line)[bmp_y_i];
end_addr = addr + r_bmp_x;
addr += l_bmp_x;
for (; addr <= end_addr; ++addr) {
delegate.putpixel(addr, spr_line, l_spr_x, l_spr_y);
delegate.startScan(bmp, l_bmp_x, bmp_y_i);
for (int x=(int)l_bmp_x; x<=(int)r_bmp_x; ++x) {
delegate.feedLine(spr, l_spr_x, l_spr_y);
l_spr_x += spr_dx;
l_spr_y += spr_dy;
}
}
class RgbDelegate {
template<class Traits>
class GenericDelegate {
protected:
typename Traits::address_t m_addr;
public:
void startScan(Image* bmp, int x, int y) {
m_addr = ((Traits::address_t*)bmp->line)[y]+x;
}
};
class RgbDelegate : public GenericDelegate<RgbTraits> {
BLEND_COLOR m_blender;
public:
RgbDelegate() : m_blender(_rgba_blenders[BLEND_MODE_NORMAL]) { }
void putpixel(RgbTraits::address_t addr, RgbTraits::address_t* spr_line, fixed l_spr_x, fixed l_spr_y) {
*addr = m_blender(*addr, spr_line[l_spr_y>>16][l_spr_x>>16], 255);
void feedLine(Image* spr, fixed l_spr_x, fixed l_spr_y) {
*m_addr = m_blender(*m_addr, image_getpixel_fast<RgbTraits>(spr, l_spr_x>>16, l_spr_y>>16), 255);
++m_addr;
}
};
class GrayscaleDelegate {
class GrayscaleDelegate : public GenericDelegate<GrayscaleTraits> {
BLEND_COLOR m_blender;
public:
GrayscaleDelegate() : m_blender(_graya_blenders[BLEND_MODE_NORMAL]) { }
void putpixel(GrayscaleTraits::address_t addr, GrayscaleTraits::address_t* spr_line, fixed l_spr_x, fixed l_spr_y) {
*addr = m_blender(*addr, spr_line[l_spr_y>>16][l_spr_x>>16], 255);
void feedLine(Image* spr, fixed l_spr_x, fixed l_spr_y) {
*m_addr = m_blender(*m_addr, image_getpixel_fast<GrayscaleTraits>(spr, l_spr_x>>16, l_spr_y>>16), 255);
++m_addr;
}
};
class IndexedDelegate {
class IndexedDelegate : public GenericDelegate<IndexedTraits> {
public:
IndexedDelegate() { }
void feedLine(Image* spr, fixed l_spr_x, fixed l_spr_y) {
register int c = image_getpixel_fast<IndexedTraits>(spr, l_spr_x>>16, l_spr_y>>16);
if (c != 0) // TODO
*m_addr = c;
++m_addr;
}
};
void putpixel(IndexedTraits::address_t addr, IndexedTraits::address_t* spr_line, fixed l_spr_x, fixed l_spr_y) {
register int c = spr_line[l_spr_y>>16][l_spr_x>>16];
if (c != 0) *addr = c;
class BitmapDelegate : public GenericDelegate<BitmapTraits> {
div_t m_d;
public:
void startScan(Image* bmp, int x, int y) {
m_d = div(x, 8);
m_addr = ((BitmapTraits::address_t*)bmp->line)[y] + m_d.quot;
}
void feedLine(Image* spr, fixed l_spr_x, fixed l_spr_y) {
if (image_getpixel_fast<BitmapTraits>(spr, l_spr_x>>16, l_spr_y>>16))
*m_addr |= (1<<m_d.rem);
else
*m_addr &= ~(1<<m_d.rem);
_image_bitmap_next_bit(m_d, m_addr);
}
};
@ -623,6 +651,10 @@ static void ase_parallelogram_map_standard(Image *bmp, Image *sprite,
case IMAGE_INDEXED:
ase_parallelogram_map<IndexedTraits, IndexedDelegate>(bmp, sprite, xs, ys, false);
break;
case IMAGE_BITMAP:
ase_parallelogram_map<BitmapTraits, BitmapDelegate>(bmp, sprite, xs, ys, false);
break;
}
}

View File

@ -540,6 +540,9 @@ enum {
PART_DROP_DOWN_BUTTON_RIGHT_SELECTED_SW,
PART_DROP_DOWN_BUTTON_RIGHT_SELECTED_W,
PART_TRANSFORMATION_HANDLE,
PART_PIVOT_HANDLE,
PARTS
};

View File

@ -73,6 +73,14 @@ static struct
{ "size_bl", 0, 0 }, // JI_CURSOR_SIZE_BL
{ "size_b", 0, 0 }, // JI_CURSOR_SIZE_B
{ "size_br", 0, 0 }, // JI_CURSOR_SIZE_BR
{ "rotate_tl", 0, 0 }, // JI_CURSOR_ROTATE_TL
{ "rotate_t", 0, 0 }, // JI_CURSOR_ROTATE_T
{ "rotate_tr", 0, 0 }, // JI_CURSOR_ROTATE_TR
{ "rotate_l", 0, 0 }, // JI_CURSOR_ROTATE_L
{ "rotate_r", 0, 0 }, // JI_CURSOR_ROTATE_R
{ "rotate_bl", 0, 0 }, // JI_CURSOR_ROTATE_BL
{ "rotate_b", 0, 0 }, // JI_CURSOR_ROTATE_B
{ "rotate_br", 0, 0 }, // JI_CURSOR_ROTATE_BR
{ "eyedropper", 0, 0 }, // JI_CURSOR_EYEDROPPER
};
@ -209,6 +217,8 @@ SkinTheme::SkinTheme()
sheet_mapping["drop_down_button_right_hot"] = PART_DROP_DOWN_BUTTON_RIGHT_HOT_NW;
sheet_mapping["drop_down_button_right_focused"] = PART_DROP_DOWN_BUTTON_RIGHT_FOCUSED_NW;
sheet_mapping["drop_down_button_right_selected"] = PART_DROP_DOWN_BUTTON_RIGHT_SELECTED_NW;
sheet_mapping["transformation_handle"] = PART_TRANSFORMATION_HANDLE;
sheet_mapping["pivot_handle"] = PART_PIVOT_HANDLE;
reload_skin();
}

View File

@ -22,6 +22,7 @@
#include "app/color_utils.h"
#include "context.h"
#include "document.h"
#include "raster/mask.h"
#include "undo/undo_history.h"
#include "undoers/set_mask.h"
@ -269,6 +270,7 @@ public:
}
else {
loop->getMask()->unfreeze();
loop->getDocument()->setTransformation(Transformation(loop->getMask()->getBounds()));
loop->getDocument()->setMaskVisible(true);
}
}

View File

@ -0,0 +1,41 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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 WIDGETS_EDITOR_HANDLE_TYPE_H_INCLUDED
#define WIDGETS_EDITOR_HANDLE_TYPE_H_INCLUDED
// Handles available to transform a region of pixels in the editor.
enum HandleType {
// No handle selected
NoHandle,
// This is the handle to move the pixels region, generally, the
// whole region activates this handle.
//MoveHandle,
// One of the region's corders to scale.
ScaleNWHandle, ScaleNHandle, ScaleNEHandle,
ScaleWHandle, ScaleEHandle,
ScaleSWHandle, ScaleSHandle, ScaleSEHandle,
// One of the region's corders to rotate.
RotateNWHandle, RotateNHandle, RotateNEHandle,
RotateWHandle, RotateEHandle,
RotateSWHandle, RotateSHandle, RotateSEHandle,
// Handle used to move the pivot
PivotHandle,
};
#endif

View File

@ -37,11 +37,12 @@
#include "widgets/editor/editor_customization_delegate.h"
#include "widgets/editor/pixels_movement.h"
#include "widgets/editor/standby_state.h"
#include "widgets/editor/transform_handles.h"
#include "widgets/statebar.h"
#include <allegro.h>
MovingPixelsState::MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity)
MovingPixelsState::MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity, HandleType handle)
{
EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
@ -62,7 +63,7 @@ MovingPixelsState::MovingPixelsState(Editor* editor, Message* msg, Image* imge,
m_pixelsMovement->cutMask();
editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y);
m_pixelsMovement->catchImage(x, y);
m_pixelsMovement->catchImage(x, y, handle);
// Setup mask color
setTransparentColor(app_get_statusbar()->getTransparentColor());
@ -90,6 +91,9 @@ bool MovingPixelsState::onBeforeChangeState(Editor* editor)
// Drop pixels if the user press a button outside the selection
m_pixelsMovement->dropImage();
editor->getDocument()->resetTransformation();
delete m_pixelsMovement;
m_pixelsMovement = NULL;
@ -119,13 +123,37 @@ bool MovingPixelsState::onMouseDown(Editor* editor, Message* msg)
{
ASSERT(m_pixelsMovement != NULL);
Decorator* decorator = static_cast<Decorator*>(editor->getDecorator());
Document* document = editor->getDocument();
// Transform selected pixels
if (document->isMaskVisible() &&
decorator->getTransformHandles(editor)) {
TransformHandles* transfHandles = decorator->getTransformHandles(editor);
// Get the handle covered by the mouse.
HandleType handle = transfHandles->getHandleAtPoint(editor,
gfx::Point(msg->mouse.x, msg->mouse.y),
getTransformation(editor));
if (handle != NoHandle) {
// Re-catch the image
int x, y;
editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y);
m_pixelsMovement->catchImageAgain(x, y, handle);
editor->captureMouse();
return true;
}
}
// Start "moving pixels" loop
if (editor->isInsideSelection() && (msg->mouse.left ||
msg->mouse.right)) {
// Re-catch the image
int x, y;
editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y);
m_pixelsMovement->catchImageAgain(x, y);
m_pixelsMovement->catchImageAgain(x, y, NoHandle);
editor->captureMouse();
return true;
@ -147,6 +175,9 @@ bool MovingPixelsState::onMouseUp(Editor* editor, Message* msg)
// Drop the image temporarily in this location (where the user releases the mouse)
m_pixelsMovement->dropImageTemporarily();
// Redraw the new pivot location.
invalidate();
editor->releaseMouse();
return true;
}
@ -198,7 +229,7 @@ bool MovingPixelsState::onSetCursor(Editor* editor)
ASSERT(m_pixelsMovement != NULL);
// Move selection
if (m_pixelsMovement->isDragging() || editor->isInsideSelection()) {
if (m_pixelsMovement->isDragging()) {
editor->hideDrawingCursor();
jmouse_set_cursor(JI_CURSOR_MOVE);
return true;
@ -214,7 +245,7 @@ bool MovingPixelsState::onKeyDown(Editor* editor, Message* msg)
EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
if (customization && customization->isCopySelectionKeyPressed()) {
// If the user press the CTRL key when he is dragging pixels (but
// If the user presses the CTRL key when he is dragging pixels (but
// not pressing the mouse buttons).
if (!jmouse_b(0) && m_pixelsMovement) {
// Drop pixels (sure the user will press the mouse button to
@ -239,11 +270,13 @@ bool MovingPixelsState::onUpdateStatusBar(Editor* editor)
{
ASSERT(m_pixelsMovement != NULL);
gfx::Rect bounds = m_pixelsMovement->getImageBounds();
const gfx::Transformation& transform(getTransformation(editor));
app_get_statusbar()->setStatusText
(100, "Pos %d %d, Size %d %d",
bounds.x, bounds.y, bounds.w, bounds.h);
(100, "Pos %d %d, Size %d %d, Angle %.1f",
transform.bounds().x, transform.bounds().y,
transform.bounds().w, transform.bounds().h,
180.0 * transform.angle() / PI);
return true;
}
@ -277,3 +310,8 @@ void MovingPixelsState::dropPixels(Editor* editor)
// receive an onBeforeChangeState event after this call.
editor->backToPreviousState();
}
gfx::Transformation MovingPixelsState::getTransformation(Editor* editor)
{
return m_pixelsMovement->getTransformation();
}

View File

@ -20,6 +20,7 @@
#define WIDGETS_EDITOR_MOVING_PIXELS_STATE_H_INCLUDED
#include "base/compiler_specific.h"
#include "widgets/editor/handle_type.h"
#include "widgets/editor/standby_state.h"
#include "widgets/statebar.h"
@ -30,7 +31,8 @@ class PixelsMovement;
class MovingPixelsState : public StandbyState, StatusBarListener
{
public:
MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity);
MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity,
HandleType handle);
virtual ~MovingPixelsState();
virtual bool onBeforeChangeState(Editor* editor) OVERRIDE;
@ -44,6 +46,8 @@ public:
virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE;
virtual gfx::Transformation getTransformation(Editor* editor) OVERRIDE;
protected:
// StatusBarListener interface
virtual void dispose() OVERRIDE;
@ -53,7 +57,7 @@ private:
void setTransparentColor(const Color& color);
void dropPixels(Editor* editor);
// Helper member to move selection.
// Helper member to move/translate selection and pixels.
PixelsMovement* m_pixelsMovement;
};

View File

@ -22,33 +22,49 @@
#include "app.h"
#include "document.h"
#include "la/vector2d.h"
#include "modules/gui.h"
#include "raster/cel.h"
#include "raster/image.h"
#include "raster/mask.h"
#include "raster/rotate.h"
#include "raster/sprite.h"
#include "util/expand_cel_canvas.h"
using namespace gfx;
template<typename T>
static inline const la::Vector2d<double> point2Vector(const gfx::PointT<T>& pt) {
return la::Vector2d<double>(pt.x, pt.y);
}
PixelsMovement::PixelsMovement(Document* document, Sprite* sprite, const Image* moveThis, int initial_x, int initial_y, int opacity)
PixelsMovement::PixelsMovement(Document* document, Sprite* sprite, const Image* moveThis, int initialX, int initialY, int opacity)
: m_documentReader(document)
, m_sprite(sprite)
, m_undoTransaction(document, "Pixels Movement")
, m_initial_x(initial_x)
, m_initial_y(initial_y)
, m_firstDrop(true)
, m_isDragging(false)
, m_adjustPivot(false)
, m_handle(NoHandle)
, m_originalImage(image_new_copy(moveThis))
{
m_initialData = gfx::Transformation(gfx::Rect(initialX, initialY, moveThis->w, moveThis->h));
m_currentData = m_initialData;
DocumentWriter documentWriter(m_documentReader);
documentWriter->prepareExtraCel(initial_x, initial_y, moveThis->w, moveThis->h, opacity);
documentWriter->prepareExtraCel(0, 0, m_sprite->getWidth(), m_sprite->getHeight(), opacity);
Image* extraImage = documentWriter->getExtraCelImage();
image_copy(extraImage, moveThis, 0, 0);
image_clear(extraImage, extraImage->mask_color);
image_copy(extraImage, moveThis, initialX, initialY);
m_initialMask = new Mask(*documentWriter->getMask());
m_currentMask = new Mask(*documentWriter->getMask());
}
PixelsMovement::~PixelsMovement()
{
delete m_originalImage;
delete m_initialMask;
delete m_currentMask;
}
void PixelsMovement::cutMask()
@ -73,25 +89,27 @@ void PixelsMovement::copyMask()
update_screen_for_document(m_documentReader);
}
void PixelsMovement::catchImage(int x, int y)
void PixelsMovement::catchImage(int x, int y, HandleType handle)
{
m_catch_x = x;
m_catch_y = y;
m_catchX = x;
m_catchY = y;
m_isDragging = true;
m_handle = handle;
}
void PixelsMovement::catchImageAgain(int x, int y)
void PixelsMovement::catchImageAgain(int x, int y, HandleType handle)
{
// Create a new UndoTransaction to move the pixels to other position
const Cel* cel = m_documentReader->getExtraCel();
m_initial_x = cel->getX();
m_initial_y = cel->getY();
m_initialData = m_currentData;
m_isDragging = true;
m_catch_x = x;
m_catch_y = y;
m_catchX = x;
m_catchY = y;
// Hide the mask (do not deselect it, it will be moved them using m_undoTransaction.setMaskPosition)
m_handle = handle;
// Hide the mask (do not deselect it, it will be moved them using
// m_undoTransaction.setMaskPosition)
Mask emptyMask;
{
DocumentWriter documentWriter(m_documentReader);
@ -101,57 +119,188 @@ void PixelsMovement::catchImageAgain(int x, int y)
update_screen_for_document(m_documentReader);
}
Rect PixelsMovement::moveImage(int x, int y)
gfx::Rect PixelsMovement::moveImage(int x, int y)
{
DocumentWriter documentWriter(m_documentReader);
Cel* cel = documentWriter->getExtraCel();
Image* image = documentWriter->getExtraCelImage();
Cel* cel = documentWriter->getExtraCel();
int x1, y1, x2, y2;
int u1, v1, u2, v2;
x1 = cel->getX();
y1 = cel->getY();
x2 = cel->getX() + image->w;
y2 = cel->getY() + image->h;
x1 = m_initialData.bounds().x;
y1 = m_initialData.bounds().y;
x2 = m_initialData.bounds().x + m_initialData.bounds().w;
y2 = m_initialData.bounds().y + m_initialData.bounds().h;
int new_x = m_initial_x + x - m_catch_x;
int new_y = m_initial_y + y - m_catch_y;
bool updateBounds = false;
int dx, dy;
// No movement
if (cel->getX() == new_x && cel->getY() == new_y)
return Rect();
dx = ((x - m_catchX) * cos(m_currentData.angle()) +
(y - m_catchY) * -sin(m_currentData.angle()));
dy = ((x - m_catchX) * sin(m_currentData.angle()) +
(y - m_catchY) * cos(m_currentData.angle()));
cel->setPosition(new_x, new_y);
switch (m_handle) {
u1 = cel->getX();
v1 = cel->getY();
u2 = cel->getX() + image->w;
v2 = cel->getY() + image->h;
case NoHandle:
x1 += dx;
y1 += dy;
x2 += dx;
y2 += dy;
updateBounds = true;
break;
return Rect(MIN(x1, u1), MIN(y1, v1),
MAX(x2, u2) - MIN(x1, u1) + 1,
MAX(y2, v2) - MIN(y1, v1) + 1);
case ScaleNWHandle:
x1 = MIN(x1+dx, x2-1);
y1 = MIN(y1+dy, y2-1);
updateBounds = true;
break;
case ScaleNHandle:
y1 = MIN(y1+dy, y2-1);
updateBounds = true;
break;
case ScaleNEHandle:
x2 = MAX(x2+dx, x1+1);
y1 = MIN(y1+dy, y2-1);
updateBounds = true;
break;
case ScaleWHandle:
x1 = MIN(x1+dx, x2-1);
updateBounds = true;
break;
case ScaleEHandle:
x2 = MAX(x2+dx, x1+1);
updateBounds = true;
break;
case ScaleSWHandle:
x1 = MIN(x1+dx, x2-1);
y2 = MAX(y2+dy, y1+1);
updateBounds = true;
break;
case ScaleSHandle:
y2 = MAX(y2+dy, y1+1);
updateBounds = true;
break;
case ScaleSEHandle:
x2 = MAX(x2+dx, x1+1);
y2 = MAX(y2+dy, y1+1);
updateBounds = true;
break;
case RotateNWHandle:
case RotateNHandle:
case RotateNEHandle:
case RotateWHandle:
case RotateEHandle:
case RotateSWHandle:
case RotateSHandle:
case RotateSEHandle:
{
gfx::Point abs_initial_pivot = m_initialData.pivot();
gfx::Point abs_pivot = m_currentData.pivot();
double newAngle =
m_initialData.angle()
+ atan2((double)(-y + abs_pivot.y),
(double)(+x - abs_pivot.x))
- atan2((double)(-m_catchY + abs_initial_pivot.y),
(double)(+m_catchX - abs_initial_pivot.x));
m_currentData.angle(newAngle);
}
break;
case PivotHandle:
{
// Calculate the new position of the pivot
gfx::Point newPivot(m_initialData.pivot().x + (x - m_catchX),
m_initialData.pivot().y + (y - m_catchY));
m_currentData = m_initialData;
m_currentData.displacePivotTo(newPivot);
}
break;
}
if (updateBounds) {
m_currentData.bounds(gfx::Rect(x1, y1, x2 - x1, y2 - y1));
m_adjustPivot = true;
}
gfx::Transformation::Corners corners;
m_currentData.transformBox(corners);
// Transform the extra-cel which is the chunk of pixels that the user is moving.
image_clear(documentWriter->getExtraCelImage(), 0);
image_parallelogram(documentWriter->getExtraCelImage(), m_originalImage,
corners.leftTop().x, corners.leftTop().y,
corners.rightTop().x, corners.rightTop().y,
corners.rightBottom().x, corners.rightBottom().y,
corners.leftBottom().x, corners.leftBottom().y);
// Transform mask
mask_replace(m_currentMask, 0, 0, m_sprite->getWidth(), m_sprite->getHeight());
m_currentMask->freeze();
image_clear(m_currentMask->bitmap, 0);
image_parallelogram(m_currentMask->bitmap, m_initialMask->bitmap,
corners.leftTop().x, corners.leftTop().y,
corners.rightTop().x, corners.rightTop().y,
corners.rightBottom().x, corners.rightBottom().y,
corners.leftBottom().x, corners.leftBottom().y);
m_currentMask->unfreeze();
if (m_firstDrop)
m_undoTransaction.copyToCurrentMask(m_currentMask);
else
documentWriter->setMask(m_currentMask);
documentWriter->setTransformation(m_currentData);
return gfx::Rect(0, 0, m_sprite->getWidth(), m_sprite->getHeight());
}
void PixelsMovement::dropImageTemporarily()
{
m_isDragging = false;
const Cel* cel = m_documentReader->getExtraCel();
{
DocumentWriter documentWriter(m_documentReader);
// Show the mask again in the new position
if (m_firstDrop) {
m_firstDrop = false;
m_undoTransaction.setMaskPosition(cel->getX(), cel->getY());
// TODO Add undo information so the user can undo each transformation step.
// Displace the pivot to the new location:
if (m_adjustPivot) {
m_adjustPivot = false;
// Get the a factor for the X/Y position of the initial pivot
// position inside the initial non-rotated bounds.
gfx::PointT<double> pivotPosFactor(m_initialData.pivot() - m_initialData.bounds().getOrigin());
pivotPosFactor.x /= m_initialData.bounds().w;
pivotPosFactor.y /= m_initialData.bounds().h;
// Get the current transformed bounds.
gfx::Transformation::Corners corners;
m_currentData.transformBox(corners);
// The new pivot will be located from the rotated left-top
// corner a distance equal to the transformed bounds's
// width/height multiplied with the previously calculated X/Y
// factor.
la::Vector2d<double> newPivot(corners.leftTop().x,
corners.leftTop().y);
newPivot += pivotPosFactor.x * point2Vector(corners.rightTop() - corners.leftTop());
newPivot += pivotPosFactor.y * point2Vector(corners.leftBottom() - corners.leftTop());
m_currentData.displacePivotTo(gfx::Point(newPivot.x, newPivot.y));
}
else {
documentWriter->getMask()->x = cel->getX();
documentWriter->getMask()->y = cel->getY();
}
documentWriter->generateMaskBoundaries();
documentWriter->generateMaskBoundaries(m_currentMask);
}
update_screen_for_document(m_documentReader);
@ -173,8 +322,8 @@ void PixelsMovement::dropImage()
m_sprite->getCurrentLayer(), TILED_NONE);
image_merge(expandCelCanvas.getDestCanvas(), image,
cel->getX()-expandCelCanvas.getCel()->getX(),
cel->getY()-expandCelCanvas.getCel()->getY(),
-expandCelCanvas.getCel()->getX(),
-expandCelCanvas.getCel()->getY(),
cel->getOpacity(), BLEND_MODE_NORMAL);
expandCelCanvas.commit();
@ -190,7 +339,7 @@ bool PixelsMovement::isDragging()
return m_isDragging;
}
Rect PixelsMovement::getImageBounds()
gfx::Rect PixelsMovement::getImageBounds()
{
const Cel* cel = m_documentReader->getExtraCel();
const Image* image = m_documentReader->getExtraCelImage();
@ -198,7 +347,7 @@ Rect PixelsMovement::getImageBounds()
ASSERT(cel != NULL);
ASSERT(image != NULL);
return Rect(cel->getX(), cel->getY(), image->w, image->h);
return gfx::Rect(cel->getX(), cel->getY(), image->w, image->h);
}
void PixelsMovement::setMaskColor(uint32_t mask_color)

View File

@ -21,22 +21,29 @@
#include "document_wrappers.h"
#include "undo_transaction.h"
#include "widgets/editor/handle_type.h"
class Document;
class Image;
class Sprite;
// Uses the extra cel of the sprite to move/paste the specified image.
// Helper class to move pixels interactively and control undo history
// correctly. The extra cel of the sprite is temporally used to show
// feedback, drag, and drop the specified image in the constructor
// (which generally would be the selected region or the clipboard
// content).
class PixelsMovement
{
public:
// The "moveThis" image specifies the chunk of pixels to be moved.
// The "x" and "y" parameters specify the initial position of the image.
PixelsMovement(Document* document, Sprite* sprite, const Image* moveThis, int x, int y, int opacity);
~PixelsMovement();
void cutMask();
void copyMask();
void catchImage(int x, int y);
void catchImageAgain(int x, int y);
void catchImage(int x, int y, HandleType handle);
void catchImageAgain(int x, int y, HandleType handle);
// Moves the image to the new position (relative to the start
// position given in the ctor). Returns the rectangle that should be
@ -51,14 +58,22 @@ public:
void setMaskColor(uint32_t mask_color);
const gfx::Transformation& getTransformation() const { return m_currentData; }
private:
const DocumentReader m_documentReader;
Sprite* m_sprite;
UndoTransaction m_undoTransaction;
int m_initial_x, m_initial_y;
int m_catch_x, m_catch_y;
bool m_firstDrop;
bool m_isDragging;
bool m_adjustPivot;
HandleType m_handle;
Image* m_originalImage;
int m_catchX, m_catchY;
gfx::Transformation m_initialData;
gfx::Transformation m_currentData;
Mask* m_initialMask;
Mask* m_currentMask;
};
#endif

View File

@ -40,10 +40,12 @@
#include "widgets/editor/drawing_state.h"
#include "widgets/editor/editor.h"
#include "widgets/editor/editor_customization_delegate.h"
#include "widgets/editor/handle_type.h"
#include "widgets/editor/moving_cel_state.h"
#include "widgets/editor/moving_pixels_state.h"
#include "widgets/editor/scrolling_state.h"
#include "widgets/editor/tool_loop_impl.h"
#include "widgets/editor/transform_handles.h"
#include "widgets/statebar.h"
#include <allegro.h>
@ -56,17 +58,57 @@ enum WHEEL_ACTION { WHEEL_NONE,
WHEEL_BG,
WHEEL_FRAME };
static int rotated_size_cursors[] = {
JI_CURSOR_SIZE_R,
JI_CURSOR_SIZE_TR,
JI_CURSOR_SIZE_T,
JI_CURSOR_SIZE_TL,
JI_CURSOR_SIZE_L,
JI_CURSOR_SIZE_BL,
JI_CURSOR_SIZE_B,
JI_CURSOR_SIZE_BR
};
static int rotated_rotate_cursors[] = {
JI_CURSOR_ROTATE_R,
JI_CURSOR_ROTATE_TR,
JI_CURSOR_ROTATE_T,
JI_CURSOR_ROTATE_TL,
JI_CURSOR_ROTATE_L,
JI_CURSOR_ROTATE_BL,
JI_CURSOR_ROTATE_B,
JI_CURSOR_ROTATE_BR
};
static inline bool has_shifts(Message* msg, int shift)
{
return ((msg->any.shifts & shift) == shift);
}
StandbyState::StandbyState()
: m_decorator(new Decorator(this))
{
}
StandbyState::~StandbyState()
{
delete m_decorator;
}
void StandbyState::onAfterChangeState(Editor* editor)
{
editor->setDecorator(m_decorator);
}
void StandbyState::onCurrentToolChange(Editor* editor)
{
tools::Tool* currentTool = editor->getCurrentEditorTool();
// If the user change from a selection tool to a non-selection tool,
// or viceversa, we've to show or hide the transformation handles.
// TODO Compare the ink (isSelection()) of the previous tool with
// the new one.
editor->invalidate();
}
bool StandbyState::onMouseDown(Editor* editor, Message* msg)
@ -82,7 +124,9 @@ bool StandbyState::onMouseDown(Editor* editor, Message* msg)
// Each time an editor is clicked the current editor and the active
// document are set.
set_current_editor(editor);
context->setActiveDocument(editor->getDocument());
Document* document = editor->getDocument();
context->setActiveDocument(document);
// Start scroll loop
if (msg->mouse.middle || clicked_ink->isScrollMovement()) {
@ -109,11 +153,39 @@ bool StandbyState::onMouseDown(Editor* editor, Message* msg)
editor->setState(EditorStatePtr(new MovingCelState(editor, msg)));
}
}
return true;
}
// Transform selected pixels
if (document->isMaskVisible() &&
m_decorator->getTransformHandles(editor)) {
TransformHandles* transfHandles = m_decorator->getTransformHandles(editor);
// Get the handle covered by the mouse.
HandleType handle = transfHandles->getHandleAtPoint(editor,
gfx::Point(msg->mouse.x, msg->mouse.y),
document->getTransformation());
if (handle != NoHandle) {
int x, y, opacity;
Image* image = sprite->getCurrentImage(&x, &y, &opacity);
if (image) {
if (!sprite->getCurrentLayer()->is_writable()) {
Alert::show(PACKAGE "<<The layer is locked.||&Close");
return true;
}
// Change to MovingPixelsState
editor->setState(EditorStatePtr(new MovingPixelsState(editor, msg, image, x, y, opacity, handle)));
}
return true;
}
}
// Move selected pixels
else if (editor->isInsideSelection() &&
current_tool->getInk(0)->isSelection() &&
msg->mouse.left) {
if (editor->isInsideSelection() &&
current_tool->getInk(0)->isSelection() &&
msg->mouse.left) {
int x, y, opacity;
Image* image = sprite->getCurrentImage(&x, &y, &opacity);
if (image) {
@ -123,11 +195,13 @@ bool StandbyState::onMouseDown(Editor* editor, Message* msg)
}
// Change to MovingPixelsState
editor->setState(EditorStatePtr(new MovingPixelsState(editor, msg, image, x, y, opacity)));
editor->setState(EditorStatePtr(new MovingPixelsState(editor, msg, image, x, y, opacity, NoHandle)));
}
return true;
}
// Call the eyedropper command
else if (clicked_ink->isEyedropper()) {
if (clicked_ink->isEyedropper()) {
Command* eyedropper_cmd =
CommandsModule::instance()->getCommandByName(CommandId::Eyedropper);
@ -137,11 +211,13 @@ bool StandbyState::onMouseDown(Editor* editor, Message* msg)
UIContext::instance()->executeCommand(eyedropper_cmd, &params);
return true;
}
// Start the Tool-Loop
else if (sprite->getCurrentLayer()) {
if (sprite->getCurrentLayer()) {
tools::ToolLoop* toolLoop = create_tool_loop(editor, context, msg);
if (toolLoop)
editor->setState(EditorStatePtr(new DrawingState(toolLoop, editor, msg)));
return true;
}
return true;
@ -285,6 +361,10 @@ bool StandbyState::onSetCursor(Editor* editor)
// If the current tool change selection (e.g. rectangular marquee, etc.)
if (current_ink->isSelection()) {
// See if the cursor is in some selection handle.
if (m_decorator->onSetCursor(editor))
return true;
// Move pixels
if (editor->isInsideSelection()) {
EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
@ -381,3 +461,118 @@ bool StandbyState::onUpdateStatusBar(Editor* editor)
return true;
}
gfx::Transformation StandbyState::getTransformation(Editor* editor)
{
return editor->getDocument()->getTransformation();
}
//////////////////////////////////////////////////////////////////////
// Decorator
StandbyState::Decorator::Decorator(StandbyState* standbyState)
: m_transfHandles(NULL)
, m_standbyState(standbyState)
{
}
StandbyState::Decorator::~Decorator()
{
delete m_transfHandles;
}
TransformHandles* StandbyState::Decorator::getTransformHandles(Editor* editor)
{
if (!m_transfHandles)
m_transfHandles = new TransformHandles();
return m_transfHandles;
}
bool StandbyState::Decorator::onSetCursor(Editor* editor)
{
if (!editor->getDocument()->isMaskVisible())
return false;
const gfx::Transformation transformation(m_standbyState->getTransformation(editor));
TransformHandles* tr = getTransformHandles(editor);
HandleType handle = tr->getHandleAtPoint(editor,
gfx::Point(jmouse_x(0), jmouse_y(0)),
transformation);
int newCursor = JI_CURSOR_NORMAL;
switch (handle) {
case ScaleNWHandle: newCursor = JI_CURSOR_SIZE_TL; break;
case ScaleNHandle: newCursor = JI_CURSOR_SIZE_T; break;
case ScaleNEHandle: newCursor = JI_CURSOR_SIZE_TR; break;
case ScaleWHandle: newCursor = JI_CURSOR_SIZE_L; break;
case ScaleEHandle: newCursor = JI_CURSOR_SIZE_R; break;
case ScaleSWHandle: newCursor = JI_CURSOR_SIZE_BL; break;
case ScaleSHandle: newCursor = JI_CURSOR_SIZE_B; break;
case ScaleSEHandle: newCursor = JI_CURSOR_SIZE_BR; break;
case RotateNWHandle: newCursor = JI_CURSOR_ROTATE_TL; break;
case RotateNHandle: newCursor = JI_CURSOR_ROTATE_T; break;
case RotateNEHandle: newCursor = JI_CURSOR_ROTATE_TR; break;
case RotateWHandle: newCursor = JI_CURSOR_ROTATE_L; break;
case RotateEHandle: newCursor = JI_CURSOR_ROTATE_R; break;
case RotateSWHandle: newCursor = JI_CURSOR_ROTATE_BL; break;
case RotateSHandle: newCursor = JI_CURSOR_ROTATE_B; break;
case RotateSEHandle: newCursor = JI_CURSOR_ROTATE_BR; break;
case PivotHandle: newCursor = JI_CURSOR_HAND; break;
default:
return false;
}
// Adjust the cursor depending the current transformation angle.
fixed angle = ftofix(128.0 * transformation.angle() / PI);
angle = fixadd(angle, itofix(16));
angle &= (255<<16);
angle >>= 16;
angle /= 32;
if (newCursor >= JI_CURSOR_SIZE_TL && newCursor <= JI_CURSOR_SIZE_BR) {
size_t num = sizeof(rotated_size_cursors) / sizeof(rotated_size_cursors[0]);
size_t c;
for (c=num-1; c>0; --c)
if (rotated_size_cursors[c] == newCursor)
break;
newCursor = rotated_size_cursors[(c+angle) % num];
}
else if (newCursor >= JI_CURSOR_ROTATE_TL && newCursor <= JI_CURSOR_ROTATE_BR) {
size_t num = sizeof(rotated_rotate_cursors) / sizeof(rotated_rotate_cursors[0]);
size_t c;
for (c=num-1; c>0; --c)
if (rotated_rotate_cursors[c] == newCursor)
break;
newCursor = rotated_rotate_cursors[(c+angle) % num];
}
// Hide the drawing cursor (just in case) and show the new system cursor.
editor->hideDrawingCursor();
jmouse_set_cursor(newCursor);
return true;
}
void StandbyState::Decorator::preRenderDecorator(EditorPreRender* render)
{
// Do nothing
}
void StandbyState::Decorator::postRenderDecorator(EditorPostRender* render)
{
Editor* editor = render->getEditor();
// Draw transformation handles (if the mask is visible and isn't frozen).
if (editor->getDocument()->isMaskVisible() &&
!editor->getDocument()->getMask()->isFrozen()) {
// And draw only when the user has a selection tool as active tool.
tools::Tool* currentTool = editor->getCurrentEditorTool();
if (currentTool->getInk(0)->isSelection())
getTransformHandles(editor)->drawHandles(editor,
m_standbyState->getTransformation(editor));
}
}

View File

@ -20,13 +20,19 @@
#define WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED
#include "base/compiler_specific.h"
#include "gfx/transformation.h"
#include "widgets/editor/editor_decorator.h"
#include "widgets/editor/editor_state.h"
class TransformHandles;
class StandbyState : public EditorState
{
public:
StandbyState();
virtual ~StandbyState();
virtual void onAfterChangeState(Editor* editor) OVERRIDE;
virtual void onCurrentToolChange(Editor* editor) OVERRIDE;
virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE;
@ -39,6 +45,30 @@ public:
// Returns true as the standby state is the only one which shows the
// pen-preview.
virtual bool requirePenPreview() OVERRIDE { return true; }
virtual gfx::Transformation getTransformation(Editor* editor);
protected:
class Decorator : public EditorDecorator
{
public:
Decorator(StandbyState* standbyState);
virtual ~Decorator();
TransformHandles* getTransformHandles(Editor* editor);
bool onSetCursor(Editor* editor);
// EditorDecorator overrides
void preRenderDecorator(EditorPreRender* render) OVERRIDE;
void postRenderDecorator(EditorPostRender* render) OVERRIDE;
private:
TransformHandles* m_transfHandles;
StandbyState* m_standbyState;
};
private:
Decorator* m_decorator;
};
#endif // WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED

View File

@ -0,0 +1,234 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#include "config.h"
#include "widgets/editor/transform_handles.h"
#include "skin/skin_theme.h"
#include "widgets/editor/editor.h"
#include <allegro.h>
static const int HANDLES = 8;
static struct HandlesInfo {
// These indices are used to calculate the position of each handle.
//
// handle.x = (corners[i1].x + corners[i2].x) / 2
// handle.y = (corners[i1].y + corners[i2].y) / 2
//
// Corners reference (i1, i2):
//
// 0,0 0,1 1,1
// 0,3 1,2
// 3,3 3,2 2,2
//
int i1, i2;
// The angle bias of this specific handle.
fixed angle;
// The exact handle type ([0] for scaling, [1] for rotating).
HandleType handle[2];
} handles_info[HANDLES] = {
{ 1, 2, 0 << 16, { ScaleEHandle, RotateEHandle } },
{ 1, 1, 32 << 16, { ScaleNEHandle, RotateNEHandle } },
{ 0, 1, 64 << 16, { ScaleNHandle, RotateNHandle } },
{ 0, 0, 96 << 16, { ScaleNWHandle, RotateNWHandle } },
{ 0, 3, 128 << 16, { ScaleWHandle, RotateWHandle } },
{ 3, 3, 160 << 16, { ScaleSWHandle, RotateSWHandle } },
{ 3, 2, 192 << 16, { ScaleSHandle, RotateSHandle } },
{ 2, 2, 224 << 16, { ScaleSEHandle, RotateSEHandle } },
};
TransformHandles::TransformHandles()
{
}
TransformHandles::~TransformHandles()
{
}
HandleType TransformHandles::getHandleAtPoint(Editor* editor, const gfx::Point& pt, const gfx::Transformation& transform)
{
SkinTheme* theme = static_cast<SkinTheme*>(CurrentTheme::get());
BITMAP* gfx = theme->get_part(PART_TRANSFORMATION_HANDLE);
fixed angle = ftofix(128.0 * transform.angle() / PI);
gfx::Transformation::Corners corners;
transform.transformBox(corners);
std::vector<int> x(corners.size());
std::vector<int> y(corners.size());
for (size_t c=0; c<corners.size(); ++c)
editor->editorToScreen(corners[c].x, corners[c].y, &x[c], &y[c]);
int handle_rs[2] = { gfx->w*2, gfx->w*3 };
for (int i=0; i<2; ++i) {
int handle_r = handle_rs[i];
for (size_t c=0; c<HANDLES; ++c) {
if (inHandle(pt,
(x[handles_info[c].i1]+x[handles_info[c].i2])/2,
(y[handles_info[c].i1]+y[handles_info[c].i2])/2,
handle_r, handle_r,
angle + handles_info[c].angle)) {
return handles_info[c].handle[i];
}
}
}
// Check if the cursor is in the pivot
if (getPivotHandleBounds(editor, transform, corners).contains(pt))
return PivotHandle;
return NoHandle;
}
void TransformHandles::drawHandles(Editor* editor, const gfx::Transformation& transform)
{
BITMAP* bmp = ji_screen;
fixed angle = ftofix(128.0 * transform.angle() / PI);
gfx::Transformation::Corners corners;
transform.transformBox(corners);
std::vector<int> x(corners.size());
std::vector<int> y(corners.size());
for (size_t c=0; c<corners.size(); ++c)
editor->editorToScreen(corners[c].x, corners[c].y, &x[c], &y[c]);
#if 0 // Uncomment this if you want to see the bounds in red (only for debugging purposes)
// -----------------------------------------------
{
int x1, y1, x2, y2;
x1 = transform.bounds().x;
y1 = transform.bounds().y;
x2 = x1 + transform.bounds().w;
y2 = y1 + transform.bounds().h;
editor->editorToScreen(x1, y1, &x1, &y1);
editor->editorToScreen(x2, y2, &x2, &y2);
rect(bmp, x1, y1, x2, y2, makecol(255, 0, 0));
x1 = transform.pivot().x;
y1 = transform.pivot().y;
editor->editorToScreen(x1, y1, &x1, &y1);
circle(bmp, x1, y1, 4, makecol(255, 0, 0));
}
// -----------------------------------------------
#endif
// Draw corner handle
for (size_t c=0; c<HANDLES; ++c) {
drawHandle(bmp,
(x[handles_info[c].i1]+x[handles_info[c].i2])/2,
(y[handles_info[c].i1]+y[handles_info[c].i2])/2,
angle + handles_info[c].angle);
}
// Draw the pivot
{
gfx::Rect pivotBounds = getPivotHandleBounds(editor, transform, corners);
SkinTheme* theme = static_cast<SkinTheme*>(CurrentTheme::get());
BITMAP* gfx = theme->get_part(PART_PIVOT_HANDLE);
draw_sprite_ex(bmp, gfx, pivotBounds.x, pivotBounds.y, DRAW_SPRITE_TRANS, DRAW_SPRITE_NO_FLIP);
}
}
gfx::Rect TransformHandles::getPivotHandleBounds(Editor* editor,
const gfx::Transformation& transform,
const gfx::Transformation::Corners& corners)
{
SkinTheme* theme = static_cast<SkinTheme*>(CurrentTheme::get());
BITMAP* gfx = theme->get_part(PART_PIVOT_HANDLE);
int pvx, pvy;
editor->editorToScreen(transform.pivot().x, transform.pivot().y, &pvx, &pvy);
pvx += (1 << editor->getZoom()) / 2;
pvy += (1 << editor->getZoom()) / 2;
return gfx::Rect(pvx-gfx->w/2, pvy-gfx->h/2, gfx->w, gfx->h);
}
bool TransformHandles::inHandle(const gfx::Point& pt, int x, int y, int gfx_w, int gfx_h, fixed angle)
{
SkinTheme* theme = static_cast<SkinTheme*>(CurrentTheme::get());
BITMAP* gfx = theme->get_part(PART_TRANSFORMATION_HANDLE);
adjustHandle(x, y, gfx_w, gfx_h, angle);
return (pt.x >= x && pt.x < x+gfx_w &&
pt.y >= y && pt.y < y+gfx_h);
}
void TransformHandles::drawHandle(BITMAP* bmp, int x, int y, fixed angle)
{
SkinTheme* theme = static_cast<SkinTheme*>(CurrentTheme::get());
BITMAP* gfx = theme->get_part(PART_TRANSFORMATION_HANDLE);
adjustHandle(x, y, gfx->w, gfx->h, angle);
draw_sprite_ex(bmp, gfx, x, y, DRAW_SPRITE_TRANS, DRAW_SPRITE_NO_FLIP);
}
void TransformHandles::adjustHandle(int& x, int& y, int handle_w, int handle_h, fixed angle)
{
angle = fixadd(angle, itofix(16));
angle &= (255<<16);
angle >>= 16;
angle /= 32;
// Adjust x,y position depending the angle of the handle
switch (angle) {
case 0:
y = y-handle_h/2;
break;
case 1:
y = y-handle_h;
break;
case 2:
x = x-handle_w/2;
y = y-handle_h;
break;
case 3:
x = x-handle_w;
y = y-handle_h;
break;
case 4:
x = x-handle_w;
y = y-handle_h/2;
break;
case 5:
x = x-handle_w;
break;
case 6:
x = x-handle_w/2;
break;
case 7:
// x and y are correct
break;
}
}

View File

@ -0,0 +1,55 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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 WIDGETS_EDITOR_TRANSFORM_HANDLES_H_INCLUDED
#define WIDGETS_EDITOR_TRANSFORM_HANDLES_H_INCLUDED
#include "gfx/point.h"
#include "gfx/transformation.h"
#include "widgets/editor/handle_type.h"
#include <allegro/fixed.h>
class Editor;
struct BITMAP;
// Helper class to do hit-detection and render transformation handles
// and rotation pivot.
class TransformHandles
{
public:
TransformHandles();
~TransformHandles();
// Returns the handle in the given mouse point (pt) when the user
// has applied the given transformation to the selection.
HandleType getHandleAtPoint(Editor* editor, const gfx::Point& pt, const gfx::Transformation& transform);
void drawHandles(Editor* editor, const gfx::Transformation& transform);
private:
gfx::Rect getPivotHandleBounds(Editor* editor,
const gfx::Transformation& transform,
const gfx::Transformation::Corners& corners);
bool inHandle(const gfx::Point& pt, int x, int y, int gfx_w, int gfx_h, fixed angle);
void drawHandle(BITMAP* bmp, int x, int y, fixed angle);
void adjustHandle(int& x, int& y, int handle_w, int handle_h, fixed angle);
};
#endif