mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-30 04:20:23 +00:00
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:
parent
181c75bb93
commit
07edcdb1e2
Binary file not shown.
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 9.6 KiB |
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -3,4 +3,5 @@
|
||||
|
||||
add_library(gfx-lib
|
||||
hsv.cpp
|
||||
rgb.cpp)
|
||||
rgb.cpp
|
||||
transformation.cpp)
|
||||
|
82
src/gfx/transformation.cpp
Normal file
82
src/gfx/transformation.cpp
Normal 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
97
src/gfx/transformation.h
Normal 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
|
@ -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
29
src/la/LICENSE.txt
Normal 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
78
src/la/vector2d.h
Normal 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
|
@ -83,6 +83,11 @@ void Mask::unfreeze()
|
||||
shrink();
|
||||
}
|
||||
|
||||
gfx::Rect Mask::getBounds() const
|
||||
{
|
||||
return gfx::Rect(x, y, w, h);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
Mask* mask_new()
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
41
src/widgets/editor/handle_type.h
Normal file
41
src/widgets/editor/handle_type.h
Normal 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
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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, ¶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));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
234
src/widgets/editor/transform_handles.cpp
Normal file
234
src/widgets/editor/transform_handles.cpp
Normal 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;
|
||||
}
|
||||
}
|
55
src/widgets/editor/transform_handles.h
Normal file
55
src/widgets/editor/transform_handles.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user