[calligra] krita: Implement Split Alpha functionality
Dmitry Kazakov
dimula73 at gmail.com
Wed Nov 26 10:45:49 UTC 2014
Git commit b22aa1146899b21114eabe239bcd54c0b1e247f0 by Dmitry Kazakov.
Committed on 26/11/2014 at 10:44.
Pushed by dkazakov into branch 'master'.
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 at kde.org
M +5 -1 krita/krita.rc
M +5 -0 krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
M +14 -12 krita/ui/kis_action.h
M +13 -1 krita/ui/kis_action_manager.cpp
M +6 -0 krita/ui/kis_node_commands_adapter.cpp
M +1 -0 krita/ui/kis_node_commands_adapter.h
M +188 -28 krita/ui/kis_node_manager.cpp
M +4 -0 krita/ui/kis_node_manager.h
http://commits.kde.org/calligra/b22aa1146899b21114eabe239bcd54c0b1e247f0
diff --git a/krita/krita.rc b/krita/krita.rc
index e8adff6..3716bb8 100644
--- a/krita/krita.rc
+++ b/krita/krita.rc
@@ -117,7 +117,11 @@
<Action name="convert_to_selection_mask"/>
</Menu>
<Separator/>
-
+ <Menu name="LayerSplitAlpha"><text>S&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"/>
diff --git a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
index 7025fc3..8728d54 100644
--- a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
+++ b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
@@ -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();
diff --git a/krita/ui/kis_action.h b/krita/ui/kis_action.h
index 18cca90..f73b215 100644
--- a/krita/ui/kis_action.h
+++ b/krita/ui/kis_action.h
@@ -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)
diff --git a/krita/ui/kis_action_manager.cpp b/krita/ui/kis_action_manager.cpp
index 4562d41..1391d2f 100644
--- a/krita/ui/kis_action_manager.cpp
+++ b/krita/ui/kis_action_manager.cpp
@@ -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";
}
diff --git a/krita/ui/kis_node_commands_adapter.cpp b/krita/ui/kis_node_commands_adapter.cpp
index d4e6179..7ed1f58 100644
--- a/krita/ui/kis_node_commands_adapter.cpp
+++ b/krita/ui/kis_node_commands_adapter.cpp
@@ -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());
diff --git a/krita/ui/kis_node_commands_adapter.h b/krita/ui/kis_node_commands_adapter.h
index f74b442..ada5878 100644
--- a/krita/ui/kis_node_commands_adapter.h
+++ b/krita/ui/kis_node_commands_adapter.h
@@ -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);
diff --git a/krita/ui/kis_node_manager.cpp b/krita/ui/kis_node_manager.cpp
index 9241f03..b914f74 100644
--- a/krita/ui/kis_node_manager.cpp
+++ b/krita/ui/kis_node_manager.cpp
@@ -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"
diff --git a/krita/ui/kis_node_manager.h b/krita/ui/kis_node_manager.h
index 0e70419..2f57b15 100644
--- a/krita/ui/kis_node_manager.h
+++ b/krita/ui/kis_node_manager.h
@@ -198,6 +198,10 @@ public slots:
// merges the active layer with the layer below it.
void mergeLayerDown();
+ void slotSplitAlphaIntoMask();
+ void slotSplitAlphaWrite();
+ void slotSplitAlphaSaveMerged();
+
public:
More information about the kimageshop
mailing list