Add opaque/transparent mode in context bar for transformations (fix #546)

With this change now we add a "mask" image/parameter in rotation
functions. In this way we can identify which specific pixels are
inside the original mask/selection, and in opaque mode we can
include/scale/rotate all those pixels inside the mask, whatever
value they are, even if they are the mask color.

Fixes #730
This commit is contained in:
David Capello 2015-07-23 22:42:14 -03:00
parent ba4c34a70f
commit 35229e99a6
23 changed files with 388 additions and 227 deletions

View File

@ -110,6 +110,7 @@
</section>
<section id="selection">
<option id="mode" type="app::tools::SelectionMode" default="app::tools::SelectionMode::DEFAULT" />
<option id="opaque" type="bool" default="false" />
<option id="transparent_color" type="app::Color" />
<option id="rotation_algorithm" type="app::tools::RotationAlgorithm" default="app::tools::RotationAlgorithm::DEFAULT" />
</section>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -375,6 +375,8 @@
<part id="ink_default" x="144" y="144" w="16" h="16" />
<part id="ink_composite" x="160" y="144" w="16" h="16" />
<part id="ink_lock_alpha" x="176" y="144" w="16" h="16" />
<part id="selection_opaque" x="208" y="176" w="16" h="10" />
<part id="selection_masked" x="224" y="176" w="16" h="10" />
</parts>
<stylesheet>

View File

@ -90,7 +90,9 @@ private:
// Stretch the 'image'
m_thumbnail.reset(Image::create(image->pixelFormat(), thumb_w, thumb_h));
clear_image(m_thumbnail, 0);
algorithm::scale_image(m_thumbnail, image, 0, 0, thumb_w, thumb_h);
algorithm::scale_image(m_thumbnail, image,
0, 0, thumb_w, thumb_h,
0, 0, image->width(), image->height());
}
// Close file

View File

@ -477,18 +477,72 @@ protected:
}
};
class ContextBar::TransparentColorField : public ColorButton
{
class ContextBar::TransparentColorField : public HBox {
public:
TransparentColorField() : ColorButton(app::Color::fromMask(), IMAGE_RGB) {
Change.connect(Bind<void>(&TransparentColorField::onChange, this));
TransparentColorField(ContextBar* owner)
: m_icon(1)
, m_maskColor(app::Color::fromMask(), IMAGE_RGB)
, m_owner(owner) {
addChild(&m_icon);
addChild(&m_maskColor);
m_icon.addItem(static_cast<SkinTheme*>(getTheme())->get_part(PART_SELECTION_OPAQUE));
gfx::Size sz = m_icon.getItem(0)->getPreferredSize();
sz.w += 2*guiscale();
m_icon.getItem(0)->setMinSize(sz);
setOpaque(Preferences::instance().selection.opaque());
m_icon.ItemChange.connect(Bind<void>(&TransparentColorField::onPopup, this));
m_maskColor.Change.connect(Bind<void>(&TransparentColorField::onChangeColor, this));
}
protected:
void onChange() {
Preferences::instance().selection.transparentColor(getColor());
private:
void onPopup() {
gfx::Rect bounds = getBounds();
Menu menu;
MenuItem
opaque("Opaque"),
masked("Transparent");
menu.addChild(&opaque);
menu.addChild(&masked);
if (Preferences::instance().selection.opaque())
opaque.setSelected(true);
else
masked.setSelected(true);
opaque.Click.connect(Bind<void>(&TransparentColorField::setOpaque, this, true));
masked.Click.connect(Bind<void>(&TransparentColorField::setOpaque, this, false));
menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h));
}
void onChangeColor() {
Preferences::instance().selection.transparentColor(
m_maskColor.getColor());
}
void setOpaque(bool opaque) {
int part = (opaque ? PART_SELECTION_OPAQUE: PART_SELECTION_MASKED);
m_icon.getItem(0)->setIcon(
static_cast<SkinTheme*>(getTheme())->get_part(part));
m_maskColor.setVisible(!opaque);
Preferences::instance().selection.opaque(opaque);
if (!opaque) {
Preferences::instance().selection.transparentColor(
m_maskColor.getColor());
}
if (m_owner)
m_owner->layout();
}
ButtonSet m_icon;
ColorButton m_maskColor;
ContextBar* m_owner;
};
class ContextBar::RotAlgorithmField : public ComboBox
@ -860,7 +914,7 @@ ContextBar::ContextBar()
addChild(m_selectionOptionsBox = new HBox());
m_selectionOptionsBox->addChild(m_dropPixels = new DropPixelsField());
m_selectionOptionsBox->addChild(m_selectionMode = new SelectionModeField);
m_selectionOptionsBox->addChild(m_transparentColor = new TransparentColorField);
m_selectionOptionsBox->addChild(m_transparentColor = new TransparentColorField(this));
m_selectionOptionsBox->addChild(m_rotAlgo = new RotAlgorithmField());
addChild(m_brushType = new BrushTypeField(this));

View File

@ -1417,8 +1417,11 @@ void Editor::setZoomAndCenterInMouse(Zoom zoom,
}
}
void Editor::pasteImage(const Image* image, const gfx::Point& pos)
void Editor::pasteImage(const Image* image, const Mask* mask)
{
ASSERT(image);
ASSERT(mask);
// Change to a selection tool: it's necessary for PixelsMovement
// which will use the extra cel for transformation preview, and is
// not compatible with the drawing cursor preview which overwrite
@ -1433,8 +1436,8 @@ void Editor::pasteImage(const Image* image, const gfx::Point& pos)
Sprite* sprite = this->sprite();
// Check bounds where the image will be pasted.
int x = pos.x;
int y = pos.y;
int x = mask->bounds().x;
int y = mask->bounds().y;
{
// Then we check if the image will be visible by the user.
Rect visibleBounds = getVisibleSpriteBounds().shrink(4*ui::guiscale());
@ -1450,12 +1453,12 @@ void Editor::pasteImage(const Image* image, const gfx::Point& pos)
// pasted image.
m_brushPreview.hide();
Mask mask2(*mask);
mask2.setOrigin(x, y);
PixelsMovementPtr pixelsMovement(
new PixelsMovement(UIContext::instance(),
getSite(), image, gfx::Point(x, y), "Paste"));
// Select the pasted image so the user can move it and transform it.
pixelsMovement->maskImage(image);
getSite(), image, &mask2, "Paste"));
setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle)));
}

View File

@ -178,7 +178,7 @@ namespace app {
void setZoomAndCenterInMouse(render::Zoom zoom,
const gfx::Point& mousePos, ZoomBehavior zoomBehavior);
void pasteImage(const Image* image, const gfx::Point& pos);
void pasteImage(const Image* image, const Mask* mask);
void startSelectionTransformation(const gfx::Point& move);

View File

@ -71,7 +71,7 @@ MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMo
}
// Setup mask color
setTransparentColor(Preferences::instance().selection.transparentColor());
onTransparentColorChange();
// Hook BeforeCommandExecution signal so we know if the user wants
// to execute other command, so we can drop pixels.
@ -79,6 +79,9 @@ MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMo
context->BeforeCommandExecution.connect(&MovingPixelsState::onBeforeCommandExecution, this);
// Listen to any change to the transparent color from the ContextBar.
m_opaqueConn =
Preferences::instance().selection.opaque.AfterChange.connect(
Bind<void>(&MovingPixelsState::onTransparentColorChange, this));
m_transparentConn =
Preferences::instance().selection.transparentColor.AfterChange.connect(
Bind<void>(&MovingPixelsState::onTransparentColorChange, this));
@ -371,11 +374,13 @@ bool MovingPixelsState::onKeyDown(Editor* editor, KeyMessage* msg)
// Copy the floating image to the clipboard.
{
Document* document = editor->document();
gfx::Point origin;
base::UniquePtr<Image> floatingImage(m_pixelsMovement->getDraggedImageCopy(origin));
base::UniquePtr<Image> floatingImage;
base::UniquePtr<Mask> floatingMask;
m_pixelsMovement->getDraggedImageCopy(floatingImage, floatingMask);
clipboard::copy_image(floatingImage.get(),
document->sprite()->palette(editor->frame()),
origin);
floatingMask.get(),
document->sprite()->palette(editor->frame()));
}
// In case of "Cut" command.
@ -489,8 +494,12 @@ void MovingPixelsState::onBeforeLayerChanged(Editor* editor)
void MovingPixelsState::onTransparentColorChange()
{
app::Color color = Preferences::instance().selection.transparentColor();
setTransparentColor(color);
bool opaque = Preferences::instance().selection.opaque();
setTransparentColor(
opaque,
opaque ?
app::Color::fromMask():
Preferences::instance().selection.transparentColor());
}
void MovingPixelsState::onDropPixels(ContextBarObserver::DropAction action)
@ -514,7 +523,7 @@ void MovingPixelsState::onDropPixels(ContextBarObserver::DropAction action)
}
}
void MovingPixelsState::setTransparentColor(const app::Color& color)
void MovingPixelsState::setTransparentColor(bool opaque, const app::Color& color)
{
ASSERT(m_pixelsMovement);
@ -522,7 +531,7 @@ void MovingPixelsState::setTransparentColor(const app::Color& color)
ASSERT(layer);
m_pixelsMovement->setMaskColor(
color_utils::color_for_target_mask(color, ColorTarget(layer)));
opaque, color_utils::color_for_target_mask(color, ColorTarget(layer)));
}
void MovingPixelsState::dropPixels()

View File

@ -63,7 +63,7 @@ namespace app {
// ContextObserver
void onBeforeCommandExecution(Command* command);
void setTransparentColor(const app::Color& color);
void setTransparentColor(bool opaque, const app::Color& color);
void dropPixels();
bool isActiveDocument() const;
@ -78,6 +78,7 @@ namespace app {
bool m_discarded;
ScopedConnection m_ctxConn;
ScopedConnection m_opaqueConn;
ScopedConnection m_transparentConn;
};

View File

@ -47,9 +47,11 @@ static inline const base::Vector2d<double> point2Vector(const gfx::PointT<T>& pt
return base::Vector2d<double>(pt.x, pt.y);
}
PixelsMovement::PixelsMovement(Context* context,
PixelsMovement::PixelsMovement(
Context* context,
Site site,
const Image* moveThis, const gfx::Point& initialPos,
const Image* moveThis,
const Mask* mask,
const char* operationName)
: m_reader(context)
, m_site(site)
@ -64,22 +66,25 @@ PixelsMovement::PixelsMovement(Context* context,
, m_originalImage(Image::createCopy(moveThis))
, m_maskColor(m_sprite->transparentColor())
{
m_initialData = gfx::Transformation(gfx::Rect(initialPos, gfx::Size(moveThis->width(), moveThis->height())));
m_initialData = gfx::Transformation(mask->bounds());
m_currentData = m_initialData;
m_initialMask = new Mask(*mask);
m_currentMask = new Mask(*mask);
m_rotAlgoConn =
Preferences::instance().selection.rotationAlgorithm.AfterChange.connect(
Bind<void>(&PixelsMovement::onRotationAlgorithmChange, this));
// The extra cel must be null, because if it's not null, it means
// that someone else is using it (e.g. the editor brush preview),
// and its owner could destroy our new "extra cel".
ASSERT(!m_document->getExtraCel());
redrawExtraImage();
m_initialMask = new Mask(*m_document->mask());
m_currentMask = new Mask(*m_document->mask());
m_rotAlgoConn =
Preferences::instance().selection.rotationAlgorithm.AfterChange.connect(
Bind<void>(&PixelsMovement::onRotationAlgorithmChange, this));
redrawCurrentMask();
updateDocumentMask();
update_screen_for_document(m_document);
}
PixelsMovement::~PixelsMovement()
@ -167,16 +172,6 @@ void PixelsMovement::catchImageAgain(const gfx::Point& pos, HandleType handle)
}
}
void PixelsMovement::maskImage(const Image* image)
{
m_currentMask->replace(m_currentData.bounds());
m_initialMask->copyFrom(m_currentMask);
updateDocumentMask();
update_screen_for_document(m_document);
}
void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
{
gfx::Transformation::Corners oldCorners;
@ -412,29 +407,35 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
}
}
Image* PixelsMovement::getDraggedImageCopy(gfx::Point& origin)
void PixelsMovement::getDraggedImageCopy(base::UniquePtr<Image>& outputImage,
base::UniquePtr<Mask>& outputMask)
{
gfx::Transformation::Corners corners;
m_currentData.transformBox(corners);
gfx::Rect bounds = m_currentData.transformedBounds();
base::UniquePtr<Image> image(Image::create(m_sprite->pixelFormat(), bounds.w, bounds.h));
gfx::Point leftTop(corners[0]);
gfx::Point rightBottom(corners[0]);
for (size_t i=1; i<corners.size(); ++i) {
if (leftTop.x > corners[i].x) leftTop.x = (int)corners[i].x;
if (leftTop.y > corners[i].y) leftTop.y = (int)corners[i].y;
if (rightBottom.x < corners[i].x) rightBottom.x = (int)corners[i].x;
if (rightBottom.y < corners[i].y) rightBottom.y = (int)corners[i].y;
drawImage(image, bounds.getOrigin(), false);
// Draw mask without shrinking it, so the mask size is equal to the
// "image" render.
base::UniquePtr<Mask> mask(new Mask);
drawMask(mask, false);
// Now we can shrink and crop the image.
gfx::Rect oldMaskBounds = mask->bounds();
mask->shrink();
gfx::Rect newMaskBounds = mask->bounds();
if (newMaskBounds != oldMaskBounds) {
newMaskBounds.x -= oldMaskBounds.x;
newMaskBounds.y -= oldMaskBounds.y;
image.reset(crop_image(image,
newMaskBounds.x,
newMaskBounds.y,
newMaskBounds.w,
newMaskBounds.h, 0));
}
int width = rightBottom.x - leftTop.x;
int height = rightBottom.y - leftTop.y;
base::UniquePtr<Image> image(Image::create(m_sprite->pixelFormat(), width, height));
drawImage(image, leftTop, false);
origin = leftTop;
return image.release();
outputImage.reset(image.release());
outputMask.reset(mask.release());
}
void PixelsMovement::stampImage()
@ -572,13 +573,13 @@ gfx::Size PixelsMovement::getInitialImageSize() const
return m_initialData.bounds().getSize();
}
void PixelsMovement::setMaskColor(color_t mask_color)
void PixelsMovement::setMaskColor(bool opaque, color_t mask_color)
{
ContextWriter writer(m_reader, 5000);
m_opaque = opaque;
m_maskColor = mask_color;
redrawExtraImage();
update_screen_for_document(m_document);
}
@ -601,19 +602,7 @@ void PixelsMovement::redrawExtraImage()
void PixelsMovement::redrawCurrentMask()
{
gfx::Transformation::Corners corners;
m_currentData.transformBox(corners);
// Transform mask
gfx::Rect bounds = m_currentData.transformedBounds();
m_currentMask->replace(bounds);
m_currentMask->freeze();
clear_image(m_currentMask->bitmap(), 0);
drawParallelogram(m_currentMask->bitmap(), m_initialMask->bitmap(),
corners, bounds.getOrigin());
m_currentMask->unfreeze();
drawMask(m_currentMask, true);
}
void PixelsMovement::drawImage(doc::Image* dst, const gfx::Point& pt, bool renderOriginalLayer)
@ -622,10 +611,9 @@ void PixelsMovement::drawImage(doc::Image* dst, const gfx::Point& pt, bool rende
gfx::Transformation::Corners corners;
m_currentData.transformBox(corners);
gfx::Rect bounds = corners.bounds();
dst->setMaskColor(m_sprite->transparentColor());
gfx::Rect bounds = m_currentData.transformedBounds();
dst->clear(dst->maskColor());
if (renderOriginalLayer)
@ -634,11 +622,38 @@ void PixelsMovement::drawImage(doc::Image* dst, const gfx::Point& pt, bool rende
gfx::Clip(bounds.x-pt.x, bounds.y-pt.y, bounds),
BlendMode::SRC);
m_originalImage->setMaskColor(m_maskColor);
drawParallelogram(dst, m_originalImage, corners, pt);
color_t maskColor = m_maskColor;
if (m_opaque) {
if (m_originalImage->pixelFormat() == IMAGE_INDEXED)
maskColor = -1;
else
maskColor = 0;
}
m_originalImage->setMaskColor(maskColor);
drawParallelogram(dst, m_originalImage, m_initialMask, corners, pt);
}
void PixelsMovement::drawParallelogram(doc::Image* dst, doc::Image* src,
void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
{
gfx::Transformation::Corners corners;
m_currentData.transformBox(corners);
gfx::Rect bounds = corners.bounds();
mask->replace(bounds);
if (shrink)
mask->freeze();
clear_image(mask->bitmap(), 0);
drawParallelogram(mask->bitmap(),
m_initialMask->bitmap(),
nullptr,
corners, bounds.getOrigin());
if (shrink)
mask->unfreeze();
}
void PixelsMovement::drawParallelogram(
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
const gfx::Transformation::Corners& corners,
const gfx::Point& leftTop)
{
@ -658,7 +673,8 @@ retry:; // In case that we don't have enough memory for RotSprite
switch (rotAlgo) {
case tools::RotationAlgorithm::FAST:
doc::algorithm::parallelogram(dst, src,
doc::algorithm::parallelogram(
dst, src, (mask ? mask->bitmap(): nullptr),
int(corners.leftTop().x-leftTop.x),
int(corners.leftTop().y-leftTop.y),
int(corners.rightTop().x-leftTop.x),
@ -671,7 +687,8 @@ retry:; // In case that we don't have enough memory for RotSprite
case tools::RotationAlgorithm::ROTSPRITE:
try {
doc::algorithm::rotsprite_image(dst, src,
doc::algorithm::rotsprite_image(
dst, src, (mask ? mask->bitmap(): nullptr),
int(corners.leftTop().x-leftTop.x),
int(corners.leftTop().y-leftTop.y),
int(corners.rightTop().x-leftTop.x),

View File

@ -20,6 +20,7 @@
namespace doc {
class Image;
class Mask;
class Sprite;
}
@ -45,13 +46,11 @@ namespace app {
LockAxisMovement = 16
};
// The "moveThis" image specifies the chunk of pixels to be moved.
// The "x" and "y" parameters specify the initial position of the image.
PixelsMovement(Context* context,
Site site,
const Image* moveThis,
const gfx::Point& initialPos,
const char* operationName);
Site site,
const Image* moveThis,
const Mask* mask,
const char* operationName);
~PixelsMovement();
void cutMask();
@ -59,17 +58,14 @@ namespace app {
void catchImage(const gfx::Point& pos, HandleType handle);
void catchImageAgain(const gfx::Point& pos, HandleType handle);
// Creates a mask for the given image. Useful when the user paste a
// image from the clipboard.
void maskImage(const Image* image);
// Moves the image to the new position (relative to the start
// position given in the ctor).
void moveImage(const gfx::Point& pos, MoveModifier moveModifier);
// Returns a copy of the current image being dragged with the
// current transformation.
Image* getDraggedImageCopy(gfx::Point& origin);
void getDraggedImageCopy(base::UniquePtr<Image>& outputImage,
base::UniquePtr<Mask>& outputMask);
// Copies the image being dragged in the current position.
void stampImage();
@ -82,7 +78,7 @@ namespace app {
gfx::Rect getImageBounds();
gfx::Size getInitialImageSize() const;
void setMaskColor(color_t mask_color);
void setMaskColor(bool opaque, color_t mask_color);
// Flips the image and mask in the given direction in "flipType".
// Flip Horizontally/Vertically commands are replaced calling this
@ -97,7 +93,8 @@ namespace app {
void redrawExtraImage();
void redrawCurrentMask();
void drawImage(doc::Image* dst, const gfx::Point& pos, bool renderOriginalLayer);
void drawParallelogram(doc::Image* dst, doc::Image* src,
void drawMask(doc::Mask* dst, bool shrink);
void drawParallelogram(doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
const gfx::Transformation::Corners& corners,
const gfx::Point& leftTop);
void updateDocumentMask();
@ -118,6 +115,7 @@ namespace app {
gfx::Transformation m_currentData;
Mask* m_initialMask;
Mask* m_currentMask;
bool m_opaque;
color_t m_maskColor;
ScopedConnection m_rotAlgoConn;
};

View File

@ -413,13 +413,13 @@ void StandbyState::transformSelection(Editor* editor, MouseMessage* msg, HandleT
EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
Document* document = editor->document();
base::UniquePtr<Image> tmpImage(new_image_from_mask(editor->getSite()));
gfx::Point origin = document->mask()->bounds().getOrigin();
PixelsMovementPtr pixelsMovement(
new PixelsMovement(UIContext::instance(),
editor->getSite(),
tmpImage, origin,
"Transformation"));
editor->getSite(),
tmpImage,
document->mask(),
"Transformation"));
// If the Ctrl key is pressed start dragging a copy of the selection
if (customization && customization->isCopySelectionKeyPressed())

View File

@ -180,6 +180,9 @@ namespace app {
PART_INK_COMPOSITE,
PART_INK_LOCK_ALPHA,
PART_SELECTION_OPAQUE,
PART_SELECTION_MASKED,
PARTS
};

View File

@ -281,6 +281,8 @@ SkinTheme::SkinTheme()
sheet_mapping["ink_default"] = PART_INK_DEFAULT;
sheet_mapping["ink_composite"] = PART_INK_COMPOSITE;
sheet_mapping["ink_lock_alpha"] = PART_INK_LOCK_ALPHA;
sheet_mapping["selection_opaque"] = PART_SELECTION_OPAQUE;
sheet_mapping["selection_masked"] = PART_SELECTION_MASKED;
}
SkinTheme::~SkinTheme()

View File

@ -35,7 +35,8 @@
#include "render/quantization.h"
#ifdef _WIN32
#define USE_NATIVE_WIN32_CLIPBOARD
// TODO Re-enable native clipboard with custom format to save image+mask when possible
//#define USE_NATIVE_WIN32_CLIPBOARD
#endif
#ifdef USE_NATIVE_WIN32_CLIPBOARD
@ -80,24 +81,21 @@ namespace {
using namespace doc;
static void set_clipboard_image(Image* image, Palette* palette, bool set_system_clipboard);
static bool copy_from_document(const Site& site);
static bool first_time = true;
static base::SharedPtr<Palette> clipboard_palette;
static PalettePicks clipboard_picks;
static ImageRef clipboard_image;
static base::SharedPtr<Mask> clipboard_mask;
static ClipboardRange clipboard_range;
static gfx::Point clipboard_pos(0, 0);
static void on_exit_delete_clipboard()
{
clipboard_palette.reset();
clipboard_image.reset();
clipboard_mask.reset();
}
static void set_clipboard_image(Image* image, Palette* palette, bool set_system_clipboard)
static void set_clipboard_image(Image* image, Mask* mask, Palette* palette, bool set_system_clipboard)
{
if (first_time) {
first_time = false;
@ -107,11 +105,12 @@ static void set_clipboard_image(Image* image, Palette* palette, bool set_system_
clipboard_palette.reset(palette);
clipboard_picks.clear();
clipboard_image.reset(image);
clipboard_mask.reset(mask);
// copy to the Windows clipboard
#ifdef USE_NATIVE_WIN32_CLIPBOARD
if (set_system_clipboard)
set_win32_clipboard_bitmap(image, palette);
set_win32_clipboard_bitmap(image, mask, palette);
#endif
clipboard_range.invalidate();
@ -128,10 +127,12 @@ static bool copy_from_document(const Site& site)
if (!image)
return false;
clipboard_pos = document->mask()->bounds().getOrigin();
const Mask* mask = document->mask();
const Palette* pal = document->sprite()->palette(site.frame());
set_clipboard_image(image, pal ? new Palette(*pal): NULL, true);
set_clipboard_image(image,
(mask ? new Mask(*mask): nullptr),
(pal ? new Palette(*pal): nullptr), true);
return true;
}
@ -165,7 +166,7 @@ void clipboard::get_document_range_info(Document** document, DocumentRange* rang
void clipboard::clear_content()
{
set_clipboard_image(NULL, NULL, true);
set_clipboard_image(nullptr, nullptr, nullptr, true);
}
void clipboard::cut(ContextWriter& writer)
@ -207,7 +208,7 @@ void clipboard::copy_range(const ContextReader& reader, const DocumentRange& ran
ContextWriter writer(reader);
set_clipboard_image(NULL, NULL, true);
clear_content();
clipboard_range.setRange(writer.document(), range);
// TODO Replace this with a signal, because here the timeline
@ -216,12 +217,12 @@ void clipboard::copy_range(const ContextReader& reader, const DocumentRange& ran
->getTimeline()->activateClipboardRange();
}
void clipboard::copy_image(Image* image, Palette* pal, const gfx::Point& point)
void clipboard::copy_image(const Image* image, const Mask* mask, const Palette* pal)
{
set_clipboard_image(Image::createCopy(image),
pal ? new Palette(*pal): NULL, true);
clipboard_pos = point;
set_clipboard_image(
Image::createCopy(image),
(mask ? new Mask(*mask): nullptr),
(pal ? new Palette(*pal): nullptr), true);
}
void clipboard::copy_palette(const Palette* palette, const doc::PalettePicks& picks)
@ -229,7 +230,9 @@ void clipboard::copy_palette(const Palette* palette, const doc::PalettePicks& pi
if (!picks.picks())
return; // Do nothing case
set_clipboard_image(nullptr, new Palette(*palette), true);
set_clipboard_image(nullptr,
nullptr,
new Palette(*palette), true);
clipboard_picks = picks;
}
@ -256,7 +259,8 @@ void clipboard::paste()
}
#endif
if (!clipboard_image)
if (!clipboard_image ||
!clipboard_mask)
return;
Palette* dst_palette = dstSpr->palette(editor->frame());
@ -282,7 +286,8 @@ void clipboard::paste()
}
// Change to MovingPixelsState
editor->pasteImage(src_image.get(), clipboard_pos);
editor->pasteImage(src_image.get(),
clipboard_mask.get());
break;
}

View File

@ -15,6 +15,7 @@
namespace doc {
class Image;
class Mask;
class Palette;
class PalettePicks;
}
@ -43,7 +44,7 @@ namespace app {
void cut(ContextWriter& context);
void copy(const ContextReader& context);
void copy_range(const ContextReader& context, const DocumentRange& range);
void copy_image(Image* image, Palette* palette, const gfx::Point& point);
void copy_image(const Image* image, const Mask* mask, const Palette* palette);
void copy_palette(const Palette* palette, const PalettePicks& picks);
void paste();

View File

@ -57,7 +57,7 @@ static bool win32_clipboard_contains_bitmap()
* Changes the Windows clipboard content to the specified image. The
* palette is optional and only used if the image is IMAGE_INDEXED type.
*/
static void set_win32_clipboard_bitmap(Image* image, Palette* palette)
static void set_win32_clipboard_bitmap(const Image* image, const Mask* mask, Palette* palette)
{
HWND hwnd = static_cast<HWND>(she::instance()->defaultDisplay()->nativeHandle());
if (!win32_open_clipboard(hwnd))

View File

@ -12,11 +12,12 @@
#endif
#include "base/pi.h"
#include "fixmath/fixmath.h"
#include "doc/blend_funcs.h"
#include "doc/image_impl.h"
#include "doc/mask.h"
#include "doc/primitives.h"
#include "doc/primitives_fast.h"
#include "fixmath/fixmath.h"
#include <cmath>
@ -25,34 +26,34 @@ namespace algorithm {
using namespace fixmath;
static void ase_parallelogram_map_standard(Image *bmp, Image *sprite, fixed xs[4], fixed ys[4]);
static void ase_rotate_scale_flip_coordinates(fixed w, fixed h,
fixed x, fixed y,
fixed cx, fixed cy,
fixed angle,
fixed scale_x, fixed scale_y,
int h_flip, int v_flip,
fixed xs[4], fixed ys[4]);
static void ase_parallelogram_map_standard(
Image* bmp, const Image* sprite, const Image* mask,
fixed xs[4], fixed ys[4]);
static void ase_rotate_scale_flip_coordinates(
fixed w, fixed h,
fixed x, fixed y,
fixed cx, fixed cy,
fixed angle,
fixed scale_x, fixed scale_y,
int h_flip, int v_flip,
fixed xs[4], fixed ys[4]);
template<typename ImageTraits, typename BlendFunc>
static void image_scale_tpl(Image* dst, const Image* src, int x, int y, int w, int h, BlendFunc blend)
static void image_scale_tpl(
Image* dst, const Image* src,
int dst_x, int dst_y, int dst_w, int dst_h,
int src_x, int src_y, int src_w, int src_h, BlendFunc blend)
{
int src_w = src->width();
int src_h = src->height();
gfx::Clip clip(x, y, 0, 0, w, h);
if (!clip.clip(dst->width(), dst->height(), src->width(), src->height()))
return;
LockImageBits<ImageTraits> dst_bits(dst, clip.dstBounds());
LockImageBits<ImageTraits> dst_bits(dst, gfx::Rect(dst_x, dst_y, dst_w, dst_h));
typename LockImageBits<ImageTraits>::iterator dst_it = dst_bits.begin();
for (int v=0; v<clip.size.h; ++v) {
for (int u=0; u<clip.size.w; ++u) {
for (int v=0; v<dst_h; ++v) {
for (int u=0; u<dst_w; ++u) {
color_t src_color =
get_pixel_fast<ImageTraits>(src,
src_w*(clip.src.x+u)/w,
src_h*(clip.src.y+v)/h);
src_w*(src_x+u)/dst_w,
src_h*(src_y+v)/dst_h);
*dst_it = blend(*dst_it, src_color);
++dst_it;
}
@ -81,33 +82,52 @@ private:
color_t m_mask;
};
void scale_image(Image *dst, Image *src, int x, int y, int w, int h)
void scale_image(Image* dst, const Image* src,
int dst_x, int dst_y, int dst_w, int dst_h,
int src_x, int src_y, int src_w, int src_h)
{
if (w == src->width() && src->height() == h)
dst->copy(src, gfx::Clip(x, y, 0, 0, w, h));
else {
switch (dst->pixelFormat()) {
gfx::Clip clip(dst_x, dst_y, src_x, src_y, dst_w, dst_h);
if (src_w == dst_w && src_h == dst_h) {
dst->copy(src, clip);
return;
}
case IMAGE_RGB:
image_scale_tpl<RgbTraits>(dst, src, x, y, w, h, rgba_blender);
break;
if (!clip.clip(dst->width(), dst->height(), src->width(), src->height()))
return;
case IMAGE_GRAYSCALE:
image_scale_tpl<GrayscaleTraits>(dst, src, x, y, w, h, grayscale_blender);
break;
switch (dst->pixelFormat()) {
case IMAGE_INDEXED:
image_scale_tpl<IndexedTraits>(dst, src, x, y, w, h, if_blender(src->maskColor()));
break;
case IMAGE_RGB:
image_scale_tpl<RgbTraits>(
dst, src,
dst_x, dst_y, dst_w, dst_h,
src_x, src_y, src_w, src_h, rgba_blender);
break;
case IMAGE_BITMAP:
image_scale_tpl<BitmapTraits>(dst, src, x, y, w, h, if_blender(0));
break;
}
case IMAGE_GRAYSCALE:
image_scale_tpl<GrayscaleTraits>(
dst, src,
dst_x, dst_y, dst_w, dst_h,
src_x, src_y, src_w, src_h, grayscale_blender);
break;
case IMAGE_INDEXED:
image_scale_tpl<IndexedTraits>(
dst, src,
dst_x, dst_y, dst_w, dst_h,
src_x, src_y, src_w, src_h, if_blender(src->maskColor()));
break;
case IMAGE_BITMAP:
image_scale_tpl<BitmapTraits>(
dst, src,
dst_x, dst_y, dst_w, dst_h,
src_x, src_y, src_w, src_h, if_blender(0));
break;
}
}
void rotate_image(Image *dst, Image *src, int x, int y, int w, int h,
void rotate_image(Image* dst, const Image* src, int x, int y, int w, int h,
int cx, int cy, double angle)
{
fixed xs[4], ys[4];
@ -120,35 +140,38 @@ void rotate_image(Image *dst, Image *src, int x, int y, int w, int h,
fixdiv(itofix(h), itofix(src->height())),
false, false, xs, ys);
ase_parallelogram_map_standard (dst, src, xs, ys);
ase_parallelogram_map_standard(dst, src, nullptr, xs, ys);
}
/* 1-----2
| |
4-----3
*/
void parallelogram(Image *bmp, Image *sprite,
void parallelogram(Image* bmp, const Image* sprite, const Image* mask,
int x1, int y1, int x2, int y2,
int x3, int y3, int x4, int y4)
{
fixed xs[4], ys[4];
xs[0] = itofix (x1);
ys[0] = itofix (y1);
xs[1] = itofix (x2);
ys[1] = itofix (y2);
xs[2] = itofix (x3);
ys[2] = itofix (y3);
xs[3] = itofix (x4);
ys[3] = itofix (y4);
xs[0] = itofix(x1);
ys[0] = itofix(y1);
xs[1] = itofix(x2);
ys[1] = itofix(y2);
xs[2] = itofix(x3);
ys[2] = itofix(y3);
xs[3] = itofix(x4);
ys[3] = itofix(y4);
ase_parallelogram_map_standard (bmp, sprite, xs, ys);
ase_parallelogram_map_standard(bmp, sprite, mask, xs, ys);
}
// Scanline drawers.
template<class Traits, class Delegate>
static void draw_scanline(Image *bmp, Image *spr,
static void draw_scanline(
Image* bmp,
const Image* spr,
const Image* mask,
fixed l_bmp_x, int bmp_y_i,
fixed r_bmp_x,
fixed l_spr_x, fixed l_spr_y,
@ -160,8 +183,16 @@ static void draw_scanline(Image *bmp, Image *spr,
delegate.lockBits(bmp, gfx::Rect(l_bmp_x, bmp_y_i, r_bmp_x - l_bmp_x + 1, 1));
gfx::Rect maskBounds = (mask ? mask->bounds(): spr->bounds());
for (int x=(int)l_bmp_x; x<=(int)r_bmp_x; ++x) {
delegate.feedLine(spr, l_spr_x>>16, l_spr_y>>16);
int u = l_spr_x>>16;
int v = l_spr_y>>16;
if (!mask ||
(maskBounds.contains(u, v) && get_pixel_fast<BitmapTraits>(mask, u, v)))
delegate.putPixel(spr, u, v);
delegate.nextPixel();
l_spr_x += spr_dx;
l_spr_y += spr_dy;
@ -183,6 +214,11 @@ public:
m_bits.unlock();
}
void nextPixel() {
ASSERT(m_it != m_end);
++m_it;
}
private:
ImageBits<Traits> m_bits;
@ -196,14 +232,12 @@ public:
m_mask_color = mask_color;
}
void feedLine(Image* spr, int spr_x, int spr_y) {
void putPixel(const Image* spr, int spr_x, int spr_y) {
ASSERT(m_it != m_end);
int c = spr->getPixel(spr_x, spr_y);
int c = get_pixel_fast<RgbTraits>(spr, spr_x, spr_y);
if ((rgba_geta(m_mask_color) == 0) || ((c & rgba_rgb_mask) != (m_mask_color & rgba_rgb_mask)))
*m_it = rgba_blender_normal(*m_it, c, 255);
++m_it;
}
private:
@ -216,14 +250,12 @@ public:
m_mask_color = mask_color;
}
void feedLine(Image* spr, int spr_x, int spr_y) {
void putPixel(const Image* spr, int spr_x, int spr_y) {
ASSERT(m_it != m_end);
int c = spr->getPixel(spr_x, spr_y);
int c = get_pixel_fast<GrayscaleTraits>(spr, spr_x, spr_y);
if ((graya_geta(m_mask_color) == 0) || ((c & graya_v_mask) != (m_mask_color & graya_v_mask)))
*m_it = graya_blender_normal(*m_it, c, 255);
++m_it;
}
private:
@ -236,13 +268,12 @@ public:
m_mask_color(mask_color) {
}
void feedLine(Image* spr, int spr_x, int spr_y) {
void putPixel(const Image* spr, int spr_x, int spr_y) {
ASSERT(m_it != m_end);
color_t c = spr->getPixel(spr_x, spr_y);
color_t c = get_pixel_fast<IndexedTraits>(spr, spr_x, spr_y);
if (c != m_mask_color)
*m_it = c;
++m_it;
}
private:
@ -251,13 +282,12 @@ private:
class BitmapDelegate : public GenericDelegate<BitmapTraits> {
public:
void feedLine(Image* spr, int spr_x, int spr_y) {
void putPixel(const Image* spr, int spr_x, int spr_y) {
ASSERT(m_it != m_end);
int c = spr->getPixel(spr_x, spr_y);
int c = get_pixel_fast<BitmapTraits>(spr, spr_x, spr_y);
if (c != 0) // TODO
*m_it = c;
++m_it;
}
};
@ -287,7 +317,8 @@ public:
*/
template<class Traits, class Delegate>
static void ase_parallelogram_map(
Image *bmp, Image *spr, fixed xs[4], fixed ys[4],
Image* bmp, const Image* spr, const Image* mask,
fixed xs[4], fixed ys[4],
int sub_pixel_accuracy, Delegate delegate)
{
/* Index in xs[] and ys[] to topmost point. */
@ -659,7 +690,7 @@ static void ase_parallelogram_map(
}
}
}
draw_scanline<Traits, Delegate>(bmp, spr,
draw_scanline<Traits, Delegate>(bmp, spr, mask,
l_bmp_x_rounded, bmp_y_i, r_bmp_x_rounded,
l_spr_x_rounded, l_spr_y_rounded,
spr_dx, spr_dy, delegate);
@ -690,32 +721,33 @@ static void ase_parallelogram_map(
* _parallelogram_map() function since then you can bypass it and define
* your own scanline drawer, eg. for anti-aliased rotations.
*/
static void ase_parallelogram_map_standard(Image *bmp, Image *sprite,
fixed xs[4], fixed ys[4])
static void ase_parallelogram_map_standard(
Image* bmp, const Image* sprite, const Image* mask,
fixed xs[4], fixed ys[4])
{
switch (bmp->pixelFormat()) {
case IMAGE_RGB: {
RgbDelegate delegate(sprite->maskColor());
ase_parallelogram_map<RgbTraits, RgbDelegate>(bmp, sprite, xs, ys, false, delegate);
ase_parallelogram_map<RgbTraits, RgbDelegate>(bmp, sprite, mask, xs, ys, false, delegate);
break;
}
case IMAGE_GRAYSCALE: {
GrayscaleDelegate delegate(sprite->maskColor());
ase_parallelogram_map<GrayscaleTraits, GrayscaleDelegate>(bmp, sprite, xs, ys, false, delegate);
ase_parallelogram_map<GrayscaleTraits, GrayscaleDelegate>(bmp, sprite, mask, xs, ys, false, delegate);
break;
}
case IMAGE_INDEXED: {
IndexedDelegate delegate(sprite->maskColor());
ase_parallelogram_map<IndexedTraits, IndexedDelegate>(bmp, sprite, xs, ys, false, delegate);
ase_parallelogram_map<IndexedTraits, IndexedDelegate>(bmp, sprite, mask, xs, ys, false, delegate);
break;
}
case IMAGE_BITMAP: {
BitmapDelegate delegate;
ase_parallelogram_map<BitmapTraits, BitmapDelegate>(bmp, sprite, xs, ys, false, delegate);
ase_parallelogram_map<BitmapTraits, BitmapDelegate>(bmp, sprite, mask, xs, ys, false, delegate);
break;
}
}

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -13,14 +13,15 @@ namespace doc {
namespace algorithm {
void scale_image(Image* dst, Image* src,
int x, int y, int w, int h);
void scale_image(Image* dst, const Image* src,
int dst_x, int dst_y, int dst_w, int dst_h,
int src_x, int src_y, int src_w, int src_h);
void rotate_image(Image* dst, Image* src,
void rotate_image(Image* dst, const Image* src,
int x, int y, int w, int h,
int cx, int cy, double angle);
void parallelogram(Image* bmp, Image* sprite,
void parallelogram(Image* dst, const Image* src, const Image* mask,
int x1, int y1, int x2, int y2,
int x3, int y3, int x4, int y4);

View File

@ -161,15 +161,15 @@ static void image_scale2x(Image* dst, const Image* src, int src_w, int src_h)
}
}
void rotsprite_image(Image* bmp, Image* spr,
void rotsprite_image(Image* bmp, const Image* spr, const Image* mask,
int x1, int y1, int x2, int y2,
int x3, int y3, int x4, int y4)
{
static ImageBufferPtr buf1, buf2, buf3; // TODO non-thread safe
static ImageBufferPtr buf[3]; // TODO non-thread safe
if (!buf1) buf1.reset(new ImageBuffer(1));
if (!buf2) buf2.reset(new ImageBuffer(1));
if (!buf3) buf3.reset(new ImageBuffer(1));
for (int i=0; i<3; ++i)
if (!buf[i])
buf[i].reset(new ImageBuffer(1));
int xmin = MIN(x1, MIN(x2, MIN(x3, x4)));
int xmax = MAX(x1, MAX(x2, MAX(x3, x4)));
@ -179,9 +179,10 @@ void rotsprite_image(Image* bmp, Image* spr,
int rot_height = ymax - ymin + 1;
int scale = 8;
base::UniquePtr<Image> bmp_copy(Image::create(bmp->pixelFormat(), rot_width*scale, rot_height*scale, buf1));
base::UniquePtr<Image> tmp_copy(Image::create(spr->pixelFormat(), spr->width()*scale, spr->height()*scale, buf2));
base::UniquePtr<Image> spr_copy(Image::create(spr->pixelFormat(), spr->width()*scale, spr->height()*scale, buf3));
base::UniquePtr<Image> bmp_copy(Image::create(bmp->pixelFormat(), rot_width*scale, rot_height*scale, buf[0]));
base::UniquePtr<Image> tmp_copy(Image::create(spr->pixelFormat(), spr->width()*scale, spr->height()*scale, buf[1]));
base::UniquePtr<Image> spr_copy(Image::create(spr->pixelFormat(), spr->width()*scale, spr->height()*scale, buf[2]));
base::UniquePtr<Image> msk_copy;
color_t maskColor = spr->maskColor();
@ -189,23 +190,38 @@ void rotsprite_image(Image* bmp, Image* spr,
tmp_copy->setMaskColor(maskColor);
spr_copy->setMaskColor(maskColor);
bmp_copy->clear(bmp->maskColor());
spr_copy->clear(maskColor);
spr_copy->copy(spr, gfx::Clip(spr->bounds()));
for (int i=0; i<3; ++i) {
clear_image(tmp_copy, maskColor);
// clear_image(tmp_copy, maskColor);
image_scale2x(tmp_copy, spr_copy, spr->width()*(1<<i), spr->height()*(1<<i));
spr_copy->copy(tmp_copy, gfx::Clip(tmp_copy->bounds()));
}
doc::algorithm::parallelogram(
bmp_copy, spr_copy,
if (mask) {
// Same ImageBuffer than tmp_copy
msk_copy.reset(Image::create(IMAGE_BITMAP, mask->width()*scale, mask->height()*scale, buf[1]));
clear_image(msk_copy, 0);
scale_image(msk_copy, mask,
0, 0, msk_copy->width(), msk_copy->height(),
0, 0, mask->width(), mask->height());
}
clear_image(bmp_copy, maskColor);
clear_image(bmp_copy, 0);
scale_image(bmp_copy, bmp,
0, 0, bmp_copy->width(), bmp_copy->height(),
xmin, ymin, rot_width, rot_height);
parallelogram(
bmp_copy, spr_copy, msk_copy.get(),
(x1-xmin)*scale, (y1-ymin)*scale, (x2-xmin)*scale, (y2-ymin)*scale,
(x3-xmin)*scale, (y3-ymin)*scale, (x4-xmin)*scale, (y4-ymin)*scale);
doc::algorithm::scale_image(bmp, bmp_copy,
xmin, ymin, bmp_copy->width()/scale, bmp_copy->height()/scale);
scale_image(bmp, bmp_copy,
xmin, ymin, rot_width, rot_height,
0, 0, bmp_copy->width(), bmp_copy->height());
}
} // namespace algorithm

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -13,7 +13,7 @@ namespace doc {
namespace algorithm {
void rotsprite_image(Image* bmp, Image* spr,
void rotsprite_image(Image* dst, const Image* src, const Image* mask,
int x1, int y1, int x2, int y2,
int x3, int y3, int x4, int y4);

View File

@ -198,6 +198,12 @@ public:
pt.y >= y && pt.y < y+h;
}
bool contains(const T& u, const T& v) const {
return
u >= x && u < x+w &&
v >= y && v < y+h;
}
// Returns true if this rectangle entirely contains the rc rectangle.
bool contains(const RectT& rc) const {
if (isEmpty() || rc.isEmpty())

View File

@ -58,6 +58,14 @@ public:
return *this;
}
gfx::Rect bounds() const {
Rect bounds;
for (int i=0; i<Corners::NUM_OF_CORNERS; ++i)
bounds = bounds.createUnion(gfx::Rect((int)m_corners[i].x,
(int)m_corners[i].y, 1, 1));
return bounds;
}
private:
std::vector<PointT<double> > m_corners;
};