Commit b883668c authored by Dmitry Kazakov's avatar Dmitry Kazakov

Implement Split Alpha functionality

This feature combined with Isolate Layer feature allows user to
edit alpha channel separately and save the resulting layer with
color channels having real color data even though alpha is zero.

http://docs.unity3d.com/Manual/HOWTO-alphamaps.html

CCMAIL:kimageshop@kde.org
parent a2a0f7d8
......@@ -117,7 +117,11 @@
<Action name="convert_to_selection_mask"/>
</Menu>
<Separator/>
<Menu name="LayerSplitAlpha"><text>S&amp;plit Alpha</text>
<Action name="split_alpha_into_mask"/>
<Action name="split_alpha_write"/>
<Action name="split_alpha_save_merged"/>
</Menu>
<Separator/>
<Action name="mirrorNodeX"/>
<Action name="mirrorNodeY"/>
......
......@@ -495,6 +495,11 @@ void KisLayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex
addActionToMenu(convertToMenu, "convert_to_transform_mask");
addActionToMenu(convertToMenu, "convert_to_selection_mask");
QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha"));
addActionToMenu(splitAlphaMenu, "split_alpha_into_mask");
addActionToMenu(splitAlphaMenu, "split_alpha_write");
addActionToMenu(splitAlphaMenu, "split_alpha_save_merged");
addActionToMenu(&menu, "isolate_layer");
}
menu.addSeparator();
......
......@@ -31,23 +31,25 @@ class KRITAUI_EXPORT KisAction : public KAction
public:
enum ActivationFlag {
NONE = 0,
ACTIVE_NODE = 1,
ACTIVE_DEVICE = 2,
ACTIVE_LAYER = 4,
ACTIVE_SHAPE_LAYER = 8,
PIXELS_SELECTED = 16,
SHAPES_SELECTED = 32,
PIXEL_SELECTION_WITH_PIXELS = 64,
PIXELS_IN_CLIPBOARD = 128,
SHAPES_IN_CLIPBOARD = 256,
NEVER_ACTIVATE = 512
ACTIVE_NODE = 0x1,
ACTIVE_DEVICE = 0x2,
ACTIVE_LAYER = 0x4,
ACTIVE_TRANSPARENCY_MASK = 0x8,
ACTIVE_SHAPE_LAYER = 0x10,
PIXELS_SELECTED = 0x20,
SHAPES_SELECTED = 0x40,
PIXEL_SELECTION_WITH_PIXELS = 0x80,
PIXELS_IN_CLIPBOARD = 0x100,
SHAPES_IN_CLIPBOARD = 0x200,
NEVER_ACTIVATE = 0x400
};
Q_DECLARE_FLAGS(ActivationFlags, ActivationFlag)
enum ActivationCondition {
NO_CONDITION = 0,
ACTIVE_NODE_EDITABLE = 1,
SELECTION_EDITABLE = 2
ACTIVE_NODE_EDITABLE = 0x1,
ACTIVE_NODE_EDITABLE_PAINT_DEVICE = 0x2,
SELECTION_EDITABLE = 0x4
};
Q_DECLARE_FLAGS(ActivationConditions, ActivationCondition)
......
......@@ -84,7 +84,7 @@ KisAction *KisActionManager::actionByName(const QString &name) const
void KisActionManager::updateGUI()
{
KisNodeSP node = d->view->activeNode();
KisLayerSP layer = d->view->activeLayer();
KisLayerSP layer = dynamic_cast<KisLayer*>(node.data());
//TODO other flags
KisAction::ActivationFlags flags;
......@@ -93,6 +93,9 @@ void KisActionManager::updateGUI()
}
if (node) {
flags |= KisAction::ACTIVE_NODE;
if (node->inherits("KisTransparencyMask")) {
flags |= KisAction::ACTIVE_TRANSPARENCY_MASK;
}
}
if (layer) {
flags |= KisAction::ACTIVE_LAYER;
......@@ -121,6 +124,9 @@ void KisActionManager::updateGUI()
if (node && node->isEditable()) {
conditions |= KisAction::ACTIVE_NODE_EDITABLE;
}
if (node && node->hasEditablePaintDevice()) {
conditions |= KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE;
}
if (d->view->selectionEditable()) {
conditions |= KisAction::SELECTION_EDITABLE;
}
......@@ -198,6 +204,9 @@ void KisActionManager::dumpActionFlags()
if (flags & KisAction::ACTIVE_LAYER) {
out << " Active layer\n";
}
if (flags & KisAction::ACTIVE_TRANSPARENCY_MASK) {
out << " Active transparency mask\n";
}
if (flags & KisAction::ACTIVE_NODE) {
out << " Active node\n";
}
......@@ -228,6 +237,9 @@ void KisActionManager::dumpActionFlags()
if (conditions & KisAction::ACTIVE_NODE_EDITABLE) {
out << " Active Node editable\n";
}
if (conditions & KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE) {
out << " Active Node has editable paint device\n";
}
if (conditions & KisAction::SELECTION_EDITABLE) {
out << " Selection is editable\n";
}
......
......@@ -48,6 +48,12 @@ void KisNodeCommandsAdapter::beginMacro(const KUndo2MagicString& macroName)
m_view->image()->undoAdapter()->beginMacro(macroName);
}
void KisNodeCommandsAdapter::addExtraCommand(KUndo2Command *command)
{
Q_ASSERT(m_view->image()->undoAdapter());
m_view->image()->undoAdapter()->addCommand(command);
}
void KisNodeCommandsAdapter::endMacro()
{
Q_ASSERT(m_view->image()->undoAdapter());
......
......@@ -41,6 +41,7 @@ public:
virtual ~KisNodeCommandsAdapter();
public:
void beginMacro(const KUndo2MagicString& macroName);
void addExtraCommand(KUndo2Command *command);
void endMacro();
void addNode(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis);
void addNode(KisNodeSP node, KisNodeSP parent, quint32 index);
......
......@@ -22,6 +22,8 @@
#include <kactioncollection.h>
#include <kmimetype.h>
#include <kmessagebox.h>
#include <KoIcon.h>
#include <KoProperties.h>
......@@ -32,6 +34,10 @@
#include <KoFilterManager.h>
#include <KoFileDialog.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <kis_types.h>
#include <kis_node.h>
#include <kis_selection.h>
......@@ -55,16 +61,22 @@
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_processing_applicator.h"
#include "kis_sequential_iterator.h"
#include "kis_transaction.h"
#include "processing/kis_mirror_processing_visitor.h"
struct KisNodeManager::Private {
Private(KisNodeManager *_q) : q(_q) {}
~Private() {
delete layerManager;
delete maskManager;
}
KisNodeManager *q;
KisView2 * view;
KisDoc2 * doc;
KisLayerManager * layerManager;
......@@ -80,6 +92,15 @@ struct KisNodeManager::Private {
QSignalMapper nodeCreationSignalMapper;
QSignalMapper nodeConversionSignalMapper;
void saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity);
void mergeTransparencyMaskAsAlpha(bool writeToLayers);
};
bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node)
......@@ -133,7 +154,7 @@ bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node)
}
KisNodeManager::KisNodeManager(KisView2 * view, KisDoc2 * doc)
: m_d(new Private())
: m_d(new Private(this))
{
m_d->view = view;
m_d->doc = doc;
......@@ -293,6 +314,24 @@ void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManage
actionManager->addAction("isolate_layer", action, actionCollection);
connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool)));
action = new KisAction(koIcon("edit-copy"), i18n("Alpha into Mask"), this);
action->setActivationFlags(KisAction::ACTIVE_LAYER);
action->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE);
actionManager->addAction("split_alpha_into_mask", action, actionCollection);
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask()));
action = new KisAction(koIcon("transparency-enabled"), i18n("Write as Alpha"), this);
action->setActivationFlags(KisAction::ACTIVE_TRANSPARENCY_MASK);
action->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE);
actionManager->addAction("split_alpha_write", action, actionCollection);
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite()));
action = new KisAction(koIcon("document-save"), i18n("Save Merged..."), this);
action->setActivationFlags(KisAction::ACTIVE_TRANSPARENCY_MASK);
// HINT: we can save even when the nodes are not editable
actionManager->addAction("split_alpha_save_merged", action, actionCollection);
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged()));
connect(m_d->view->image(), SIGNAL(sigIsolatedModeChanged()),
this, SLOT(slotUpdateIsolateModeAction()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction()));
......@@ -944,17 +983,15 @@ void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionN
nodesUpdated();
}
void KisNodeManager::saveNodeAsImage()
void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity)
{
KisNodeSP node = activeNode();
if (!node) {
qWarning() << "BUG: Save Node As Image was called without any node selected";
return;
}
KoFileDialog dialog(m_d->view, KoFileDialog::SaveFile, "krita/savenodeasimage");
dialog.setCaption(i18n("Export \"%1\"", node->name()));
KoFileDialog dialog(view, KoFileDialog::SaveFile, "krita/savenodeasimage");
dialog.setCaption(i18n("Export \"%1\"", defaultName));
dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
dialog.setMimeTypeFilters(KoFilterManager::mimeFilter("application/x-krita", KoFilterManager::Export));
QString filename = dialog.url();
......@@ -968,29 +1005,18 @@ void KisNodeManager::saveNodeAsImage()
KMimeType::Ptr mime = KMimeType::findByUrl(url);
QString mimefilter = mime->name();
KisImageWSP image = m_d->view->image();
QRect savedRect = image->bounds() | node->exactBounds();
KisDoc2 d;
d.prepareForImport();
KisPaintDeviceSP device = node->paintDevice();
if (!device) {
device = node->projection();
}
KisImageSP dst = new KisImage(d.createUndoStore(),
savedRect.width(),
savedRect.height(),
bounds.width(),
bounds.height(),
device->compositionSourceColorSpace(),
node->name());
dst->setResolution(image->xRes(), image->yRes());
defaultName);
dst->setResolution(xRes, yRes);
d.setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", node->opacity());
KisPainter gc(paintLayer->paintDevice());
gc.bitBlt(QPoint(0, 0), device, savedRect);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity);
paintLayer->paintDevice()->makeCloneFrom(device, bounds);
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->initialRefreshGraph();
......@@ -999,5 +1025,139 @@ void KisNodeManager::saveNodeAsImage()
d.exportDocument(url);
}
void KisNodeManager::saveNodeAsImage()
{
KisNodeSP node = activeNode();
if (!node) {
qWarning() << "BUG: Save Node As Image was called without any node selected";
return;
}
KisImageWSP image = m_d->view->image();
QRect saveRect = image->bounds() | node->exactBounds();
KisPaintDeviceSP device = node->paintDevice();
if (!device) {
device = node->projection();
}
m_d->saveDeviceAsImage(device, node->name(),
saveRect,
image->xRes(), image->yRes(),
node->opacity());
}
void KisNodeManager::slotSplitAlphaIntoMask()
{
KisNodeSP node = activeNode();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice());
KisPaintDeviceSP srcDevice = node->paintDevice();
const KoColorSpace *srcCS = srcDevice->colorSpace();
const QRect processRect = srcDevice->exactBounds();
KisPaintDeviceSP selectionDevice =
new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
m_d->commandsAdapter->beginMacro(kundo2_i18n("Split Alpha into a Mask"));
KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice);
KisSequentialIterator srcIt(srcDevice, processRect);
KisSequentialIterator dstIt(selectionDevice, processRect);
do {
quint8 *srcPtr = srcIt.rawData();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->opacityU8(srcPtr);
srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
m_d->commandsAdapter->addExtraCommand(transaction.endAndTake());
createNode("KisTransparencyMask", false, selectionDevice);
m_d->commandsAdapter->endMacro();
}
void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers)
{
KisNodeSP node = q->activeNode();
KisNodeSP parentNode = node->parent();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask"));
if (!parentNode->hasEditablePaintDevice()) {
KMessageBox::information(view,
i18n("Cannot write alpha channel of "
"the parent layer \"%1\".\n"
"The operation will be cancelled.").arg(parentNode->name()),
i18n("Layer %1 is not editable").arg(parentNode->name()),
"messagebox_splitAlphaIntoLockedLayer");
return;
}
KIS_ASSERT_RECOVER_RETURN(parentNode->hasEditablePaintDevice());
KisPaintDeviceSP dstDevice =
writeToLayers ?
parentNode->paintDevice() :
new KisPaintDevice(*parentNode->paintDevice());
const KoColorSpace *dstCS = dstDevice->colorSpace();
KisPaintDeviceSP selectionDevice = node->paintDevice();
KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1);
const QRect processRect =
selectionDevice->exactBounds() | dstDevice->exactBounds();
QScopedPointer<KisTransaction> transaction;
if (writeToLayers) {
commandsAdapter->beginMacro(kundo2_i18n("Write Alpha into a Layer"));
transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice));
}
KisSequentialIterator srcIt(selectionDevice, processRect);
KisSequentialIterator dstIt(dstDevice, processRect);
do {
quint8 *alpha8Ptr = srcIt.rawData();
quint8 *dstPtr = dstIt.rawData();
dstCS->setOpacity(dstPtr, *alpha8Ptr, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
if (writeToLayers) {
commandsAdapter->addExtraCommand(transaction->endAndTake());
commandsAdapter->removeNode(node);
commandsAdapter->endMacro();
} else {
KisImageWSP image = view->image();
QRect saveRect = image->bounds() | dstDevice->exactBounds();
saveDeviceAsImage(dstDevice, parentNode->name(),
saveRect,
image->xRes(), image->yRes(),
OPACITY_OPAQUE_U8);
}
}
void KisNodeManager::slotSplitAlphaWrite()
{
m_d->mergeTransparencyMaskAsAlpha(true);
}
void KisNodeManager::slotSplitAlphaSaveMerged()
{
m_d->mergeTransparencyMaskAsAlpha(false);
}
#include "kis_node_manager.moc"
......@@ -198,6 +198,10 @@ public slots:
// merges the active layer with the layer below it.
void mergeLayerDown();
void slotSplitAlphaIntoMask();
void slotSplitAlphaWrite();
void slotSplitAlphaSaveMerged();
public:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment