diff --git a/data/skins/default/sheet.png b/data/skins/default/sheet.png
index 4c3401f7c..f7efa1f53 100644
Binary files a/data/skins/default/sheet.png and b/data/skins/default/sheet.png differ
diff --git a/data/skins/default/skin.xml b/data/skins/default/skin.xml
index 76aaa5fc7..d917981dc 100644
--- a/data/skins/default/skin.xml
+++ b/data/skins/default/skin.xml
@@ -18,6 +18,14 @@
+
+
+
+
+
+
+
+
@@ -179,6 +187,8 @@
+
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f8ccd7a3a..2825333ed 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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
diff --git a/src/document.cpp b/src/document.cpp
index bf008e74e..f6d458ca4 100644
--- a/src/document.cpp
+++ b/src/document.cpp
@@ -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
diff --git a/src/document.h b/src/document.h
index c5dc6eb41..311109c00 100644
--- a/src/document.h
+++ b/src/document.h
@@ -23,6 +23,7 @@
#include "base/shared_ptr.h"
#include "base/unique_ptr.h"
#include "document_id.h"
+#include "gfx/transformation.h"
#include
@@ -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 m_mask;
bool m_maskVisible;
+ // Current transformation.
+ gfx::Transformation m_transformation;
+
DISABLE_COPYING(Document);
};
diff --git a/src/gfx/CMakeLists.txt b/src/gfx/CMakeLists.txt
index 3475dbe18..d9f44adc2 100644
--- a/src/gfx/CMakeLists.txt
+++ b/src/gfx/CMakeLists.txt
@@ -3,4 +3,5 @@
add_library(gfx-lib
hsv.cpp
- rgb.cpp)
+ rgb.cpp
+ transformation.cpp)
diff --git a/src/gfx/transformation.cpp b/src/gfx/transformation.cpp
new file mode 100644
index 000000000..c152891c0
--- /dev/null
+++ b/src/gfx/transformation.cpp
@@ -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
+
+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 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 newBoundsOrigin =
+ rotatePoint(corners.leftTop(),
+ PointT(newPivot.x, newPivot.y),
+ -m_angle);
+
+ // Change the new pivot.
+ m_pivot = newPivot;
+ m_bounds = Rect(Point(newBoundsOrigin), m_bounds.getSize());
+}
+
+PointT Transformation::rotatePoint(
+ const PointT& point,
+ const PointT& 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(
+ pivot.x + dx*cos - dy*sin,
+ pivot.y + dy*cos + dx*sin);
+}
+
+}
diff --git a/src/gfx/transformation.h b/src/gfx/transformation.h
new file mode 100644
index 000000000..c14869a60
--- /dev/null
+++ b/src/gfx/transformation.h
@@ -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
+
+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& operator[](int index) { return m_corners[index]; }
+ const PointT& operator[](int index) const { return m_corners[index]; }
+
+ const PointT& leftTop() const { return m_corners[LEFT_TOP]; }
+ const PointT& rightTop() const { return m_corners[RIGHT_TOP]; }
+ const PointT& rightBottom() const { return m_corners[RIGHT_BOTTOM]; }
+ const PointT& leftBottom() const { return m_corners[LEFT_BOTTOM]; }
+
+ void leftTop(const PointT& pt) { m_corners[LEFT_TOP] = pt; }
+ void rightTop(const PointT& pt) { m_corners[RIGHT_TOP] = pt; }
+ void rightBottom(const PointT& pt) { m_corners[RIGHT_BOTTOM] = pt; }
+ void leftBottom(const PointT& 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 > 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 rotatePoint(const PointT& point,
+ const PointT& pivot, double angle);
+
+private:
+ Rect m_bounds;
+ Point m_pivot;
+ double m_angle;
+};
+
+} // namespace gfx
+
+#endif
diff --git a/src/gui/system.h b/src/gui/system.h
index 4ccf8d232..aaf5ed9c5 100644
--- a/src/gui/system.h
+++ b/src/gui/system.h
@@ -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
};
diff --git a/src/la/LICENSE.txt b/src/la/LICENSE.txt
new file mode 100644
index 000000000..0b1015aef
--- /dev/null
+++ b/src/la/LICENSE.txt
@@ -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.
diff --git a/src/la/vector2d.h b/src/la/vector2d.h
new file mode 100644
index 000000000..ff9a676c6
--- /dev/null
+++ b/src/la/vector2d.h
@@ -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
+
+namespace la {
+
+template
+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
+la::Vector2d operator*(const T& f, const la::Vector2d& v) {
+ return la::Vector2d(v.x*f, v.y*f);
+}
+
+#endif // LA_VECTOR2D_H_INCLUDED
diff --git a/src/raster/mask.cpp b/src/raster/mask.cpp
index 7423cc286..efdf79c02 100644
--- a/src/raster/mask.cpp
+++ b/src/raster/mask.cpp
@@ -83,6 +83,11 @@ void Mask::unfreeze()
shrink();
}
+gfx::Rect Mask::getBounds() const
+{
+ return gfx::Rect(x, y, w, h);
+}
+
//////////////////////////////////////////////////////////////////////
Mask* mask_new()
diff --git a/src/raster/mask.h b/src/raster/mask.h
index 711765618..d2ca60396 100644
--- a/src/raster/mask.h
+++ b/src/raster/mask.h
@@ -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();
diff --git a/src/raster/rotate.cpp b/src/raster/rotate.cpp
index 9e1e0dfe9..9509db6ee 100644
--- a/src/raster/rotate.cpp
+++ b/src/raster/rotate.cpp
@@ -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 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 {
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(spr, l_spr_x>>16, l_spr_y>>16), 255);
+ ++m_addr;
}
};
-class GrayscaleDelegate {
+class GrayscaleDelegate : public GenericDelegate {
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(spr, l_spr_x>>16, l_spr_y>>16), 255);
+ ++m_addr;
}
};
-class IndexedDelegate {
+class IndexedDelegate : public GenericDelegate {
public:
- IndexedDelegate() { }
+ void feedLine(Image* spr, fixed l_spr_x, fixed l_spr_y) {
+ register int c = image_getpixel_fast(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 {
+ 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(spr, l_spr_x>>16, l_spr_y>>16))
+ *m_addr |= (1<(bmp, sprite, xs, ys, false);
break;
+
+ case IMAGE_BITMAP:
+ ase_parallelogram_map(bmp, sprite, xs, ys, false);
+ break;
}
}
diff --git a/src/skin/skin_parts.h b/src/skin/skin_parts.h
index 3954d528f..ec891f151 100644
--- a/src/skin/skin_parts.h
+++ b/src/skin/skin_parts.h
@@ -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
};
diff --git a/src/skin/skin_theme.cpp b/src/skin/skin_theme.cpp
index 29b231f9f..2b17c918e 100644
--- a/src/skin/skin_theme.cpp
+++ b/src/skin/skin_theme.cpp
@@ -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();
}
diff --git a/src/tools/inks.h b/src/tools/inks.h
index f84cdaa62..f789c846f 100644
--- a/src/tools/inks.h
+++ b/src/tools/inks.h
@@ -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);
}
}
diff --git a/src/widgets/editor/handle_type.h b/src/widgets/editor/handle_type.h
new file mode 100644
index 000000000..3bab15aa7
--- /dev/null
+++ b/src/widgets/editor/handle_type.h
@@ -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
diff --git a/src/widgets/editor/moving_pixels_state.cpp b/src/widgets/editor/moving_pixels_state.cpp
index ea8febba4..1cee3aaba 100644
--- a/src/widgets/editor/moving_pixels_state.cpp
+++ b/src/widgets/editor/moving_pixels_state.cpp
@@ -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
-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(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();
+}
diff --git a/src/widgets/editor/moving_pixels_state.h b/src/widgets/editor/moving_pixels_state.h
index affa404f3..129ccc2a5 100644
--- a/src/widgets/editor/moving_pixels_state.h
+++ b/src/widgets/editor/moving_pixels_state.h
@@ -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;
};
diff --git a/src/widgets/editor/pixels_movement.cpp b/src/widgets/editor/pixels_movement.cpp
index f55321df8..592e25005 100644
--- a/src/widgets/editor/pixels_movement.cpp
+++ b/src/widgets/editor/pixels_movement.cpp
@@ -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
+static inline const la::Vector2d point2Vector(const gfx::PointT& pt) {
+ return la::Vector2d(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 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 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)
diff --git a/src/widgets/editor/pixels_movement.h b/src/widgets/editor/pixels_movement.h
index 3a069a881..8830aba06 100644
--- a/src/widgets/editor/pixels_movement.h
+++ b/src/widgets/editor/pixels_movement.h
@@ -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
diff --git a/src/widgets/editor/standby_state.cpp b/src/widgets/editor/standby_state.cpp
index 387ccc4d9..408c99659 100644
--- a/src/widgets/editor/standby_state.cpp
+++ b/src/widgets/editor/standby_state.cpp
@@ -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
@@ -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 "<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, ¶ms);
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));
+ }
+}
diff --git a/src/widgets/editor/standby_state.h b/src/widgets/editor/standby_state.h
index c21ae16aa..9669e0f55 100644
--- a/src/widgets/editor/standby_state.h
+++ b/src/widgets/editor/standby_state.h
@@ -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
diff --git a/src/widgets/editor/transform_handles.cpp b/src/widgets/editor/transform_handles.cpp
new file mode 100644
index 000000000..fd2615862
--- /dev/null
+++ b/src/widgets/editor/transform_handles.cpp
@@ -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
+
+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(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 x(corners.size());
+ std::vector y(corners.size());
+ for (size_t c=0; ceditorToScreen(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 x(corners.size());
+ std::vector y(corners.size());
+ for (size_t c=0; ceditorToScreen(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(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(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(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(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;
+ }
+}
diff --git a/src/widgets/editor/transform_handles.h b/src/widgets/editor/transform_handles.h
new file mode 100644
index 000000000..a055f91f6
--- /dev/null
+++ b/src/widgets/editor/transform_handles.h
@@ -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
+
+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