|
|
|
/*
|
|
|
|
* Copyright (c) 2005 Casper Boemann <cbr@boemann.dk>
|
|
|
|
*
|
|
|
|
* 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., 675 mass ave, cambridge, ma 02139, usa.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kglobal.h>
|
|
|
|
#include <tqimage.h>
|
|
|
|
#include <tqdatetime.h>
|
|
|
|
|
|
|
|
#include "kis_types.h"
|
|
|
|
#include "kis_layer.h"
|
|
|
|
#include "kis_group_layer.h"
|
|
|
|
#include "kis_layer_visitor.h"
|
|
|
|
#include "kis_debug_areas.h"
|
|
|
|
#include "kis_image.h"
|
|
|
|
#include "kis_paint_device.h"
|
|
|
|
#include "kis_merge_visitor.h"
|
|
|
|
#include "kis_fill_painter.h"
|
|
|
|
|
|
|
|
KisGroupLayer::KisGroupLayer(KisImage *img, const TQString &name, TQ_UINT8 opacity) :
|
|
|
|
super(img, name, opacity),
|
|
|
|
m_x(0),
|
|
|
|
m_y(0)
|
|
|
|
{
|
|
|
|
m_projection = new KisPaintDevice(this, img->colorSpace(), name.latin1());
|
|
|
|
}
|
|
|
|
|
|
|
|
KisGroupLayer::KisGroupLayer(const KisGroupLayer &rhs) :
|
|
|
|
super(rhs),
|
|
|
|
m_x(rhs.m_x),
|
|
|
|
m_y(rhs.m_y)
|
|
|
|
{
|
|
|
|
for(vKisLayerSP_cit it = rhs.m_layers.begin(); it != rhs.m_layers.end(); ++it)
|
|
|
|
{
|
|
|
|
this->addLayer(it->data()->clone(), 0);
|
|
|
|
}
|
|
|
|
m_projection = new KisPaintDevice(*rhs.m_projection.data());
|
|
|
|
m_projection->setParentLayer(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
KisLayerSP KisGroupLayer::clone() const
|
|
|
|
{
|
|
|
|
return new KisGroupLayer(*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
KisGroupLayer::~KisGroupLayer()
|
|
|
|
{
|
|
|
|
m_layers.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void KisGroupLayer::setDirty(bool propagate)
|
|
|
|
{
|
|
|
|
KisLayer::setDirty(propagate);
|
|
|
|
if (propagate) emit (sigDirty(m_dirtyRect));
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisGroupLayer::setDirty(const TQRect & rc, bool propagate)
|
|
|
|
{
|
|
|
|
KisLayer::setDirty(rc, propagate);
|
|
|
|
if (propagate) emit sigDirty(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisGroupLayer::resetProjection(KisPaintDevice* to)
|
|
|
|
{
|
|
|
|
if (to)
|
|
|
|
m_projection = new KisPaintDevice(*to); /// XXX ### look into Copy on Write here (CoW)
|
|
|
|
else
|
|
|
|
m_projection = new KisPaintDevice(this, image()->colorSpace(), name().latin1());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KisGroupLayer::paintLayerInducesProjectionOptimization(KisPaintLayer* l) {
|
|
|
|
return l && l->paintDevice()->colorSpace() == m_image->colorSpace() && l->visible()
|
|
|
|
&& l->opacity() == OPACITY_OPAQUE && !l->temporaryTarget() && !l->hasMask();
|
|
|
|
}
|
|
|
|
|
|
|
|
KisPaintDeviceSP KisGroupLayer::projection(const TQRect & rect)
|
|
|
|
{
|
|
|
|
// We don't have a parent, and we've got only one child: abuse the child's
|
|
|
|
// paint device as the projection if the child is visible and 100% opaque
|
|
|
|
if (parent() == 0 && childCount() == 1) {
|
|
|
|
KisPaintLayerSP l = dynamic_cast<KisPaintLayer*>(firstChild().data());
|
|
|
|
if (paintLayerInducesProjectionOptimization(l)) {
|
|
|
|
l->setClean(rect);
|
|
|
|
setClean(rect);
|
|
|
|
return l->paintDevice();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// No need for updates, we're clean
|
|
|
|
if (!dirty()) {
|
|
|
|
return m_projection;
|
|
|
|
}
|
|
|
|
// No need for updates -- the desired area wasn't dirty
|
|
|
|
if (!rect.intersects(m_dirtyRect)) {
|
|
|
|
return m_projection;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Okay, we need to update the intersection between
|
|
|
|
// what's dirty and what's asked us to be updated.
|
|
|
|
// XXX Nooo, that doesn't work, since the call to setClean following this, is actually:
|
|
|
|
// m_dirtyRect = TQRect(); So the non-intersecting part gets brilliantly lost otherwise.
|
|
|
|
const TQRect rc = m_dirtyRect;//rect.intersect(m_dirtyRect);
|
|
|
|
|
|
|
|
updateProjection(rc);
|
|
|
|
setClean(rect);
|
|
|
|
|
|
|
|
return m_projection;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint KisGroupLayer::childCount() const
|
|
|
|
{
|
|
|
|
return m_layers.count();
|
|
|
|
}
|
|
|
|
|
|
|
|
KisLayerSP KisGroupLayer::firstChild() const
|
|
|
|
{
|
|
|
|
return at(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
KisLayerSP KisGroupLayer::lastChild() const
|
|
|
|
{
|
|
|
|
return at(childCount() - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
KisLayerSP KisGroupLayer::at(int index) const
|
|
|
|
{
|
|
|
|
if (childCount() && index >= 0 && kClamp(uint(index), uint(0), childCount() - 1) == uint(index))
|
|
|
|
return m_layers.at(reverseIndex(index));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int KisGroupLayer::index(KisLayerSP layer) const
|
|
|
|
{
|
|
|
|
if (layer->parent().data() == this)
|
|
|
|
return layer->index();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisGroupLayer::setIndex(KisLayerSP layer, int index)
|
|
|
|
{
|
|
|
|
if (layer->parent().data() != this)
|
|
|
|
return;
|
|
|
|
//TODO optimize
|
|
|
|
removeLayer(layer);
|
|
|
|
addLayer(layer, index);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KisGroupLayer::addLayer(KisLayerSP newLayer, int x)
|
|
|
|
{
|
|
|
|
if (x < 0 || kClamp(uint(x), uint(0), childCount()) != uint(x) ||
|
|
|
|
newLayer->parent() || m_layers.contains(newLayer))
|
|
|
|
{
|
|
|
|
kdWarning() << "invalid input to KisGroupLayer::addLayer(KisLayerSP newLayer, int x)!" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
uint index(x);
|
|
|
|
if (index == 0)
|
|
|
|
m_layers.append(newLayer);
|
|
|
|
else
|
|
|
|
m_layers.insert(m_layers.begin() + reverseIndex(index) + 1, newLayer);
|
|
|
|
for (uint i = childCount() - 1; i > index; i--)
|
|
|
|
at(i)->m_index++;
|
|
|
|
newLayer->m_parent = this;
|
|
|
|
newLayer->m_index = index;
|
|
|
|
newLayer->setImage(image());
|
|
|
|
newLayer->setDirty(newLayer->extent());
|
|
|
|
setDirty();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KisGroupLayer::addLayer(KisLayerSP newLayer, KisLayerSP aboveThis)
|
|
|
|
{
|
|
|
|
if (aboveThis && aboveThis->parent().data() != this)
|
|
|
|
{
|
|
|
|
kdWarning() << "invalid input to KisGroupLayer::addLayer(KisLayerSP newLayer, KisLayerSP aboveThis)!" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return addLayer(newLayer, aboveThis ? aboveThis->index() : childCount());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KisGroupLayer::removeLayer(int x)
|
|
|
|
{
|
|
|
|
if (x >= 0 && kClamp(uint(x), uint(0), childCount() - 1) == uint(x))
|
|
|
|
{
|
|
|
|
uint index(x);
|
|
|
|
for (uint i = childCount() - 1; i > index; i--)
|
|
|
|
at(i)->m_index--;
|
|
|
|
KisLayerSP removedLayer = at(index);
|
|
|
|
|
|
|
|
removedLayer->m_parent = 0;
|
|
|
|
removedLayer->m_index = -1;
|
|
|
|
m_layers.erase(m_layers.begin() + reverseIndex(index));
|
|
|
|
setDirty(removedLayer->extent());
|
|
|
|
if (childCount() < 1) {
|
|
|
|
// No tqchildren, nothing to show for it.
|
|
|
|
m_projection->clear();
|
|
|
|
setDirty();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
kdWarning() << "invalid input to KisGroupLayer::removeLayer()!" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KisGroupLayer::removeLayer(KisLayerSP layer)
|
|
|
|
{
|
|
|
|
if (layer->parent().data() != this)
|
|
|
|
{
|
|
|
|
kdWarning() << "invalid input to KisGroupLayer::removeLayer()!" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return removeLayer(layer->index());
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisGroupLayer::setImage(KisImage *image)
|
|
|
|
{
|
|
|
|
super::setImage(image);
|
|
|
|
for (vKisLayerSP_it it = m_layers.begin(); it != m_layers.end(); ++it)
|
|
|
|
{
|
|
|
|
(*it)->setImage(image);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQRect KisGroupLayer::extent() const
|
|
|
|
{
|
|
|
|
TQRect groupExtent;
|
|
|
|
|
|
|
|
for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it)
|
|
|
|
{
|
|
|
|
groupExtent |= (*it)->extent();
|
|
|
|
}
|
|
|
|
|
|
|
|
return groupExtent;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQRect KisGroupLayer::exactBounds() const
|
|
|
|
{
|
|
|
|
TQRect groupExactBounds;
|
|
|
|
|
|
|
|
for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it)
|
|
|
|
{
|
|
|
|
groupExactBounds |= (*it)->exactBounds();
|
|
|
|
}
|
|
|
|
|
|
|
|
return groupExactBounds;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_INT32 KisGroupLayer::x() const
|
|
|
|
{
|
|
|
|
return m_x;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisGroupLayer::setX(TQ_INT32 x)
|
|
|
|
{
|
|
|
|
TQ_INT32 delta = x - m_x;
|
|
|
|
|
|
|
|
for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it)
|
|
|
|
{
|
|
|
|
KisLayerSP layer = *it;
|
|
|
|
layer->setX(layer->x() + delta);
|
|
|
|
}
|
|
|
|
m_x = x;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_INT32 KisGroupLayer::y() const
|
|
|
|
{
|
|
|
|
return m_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisGroupLayer::setY(TQ_INT32 y)
|
|
|
|
{
|
|
|
|
TQ_INT32 delta = y - m_y;
|
|
|
|
|
|
|
|
for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it)
|
|
|
|
{
|
|
|
|
KisLayerSP layer = *it;
|
|
|
|
layer->setY(layer->y() + delta);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_y = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQImage KisGroupLayer::createThumbnail(TQ_INT32 w, TQ_INT32 h)
|
|
|
|
{
|
|
|
|
return m_projection->createThumbnail(w, h);
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisGroupLayer::updateProjection(const TQRect & rc)
|
|
|
|
{
|
|
|
|
if (!m_dirtyRect.isValid()) return;
|
|
|
|
|
|
|
|
// Get the first layer in this group to start compositing with
|
|
|
|
KisLayerSP child = lastChild();
|
|
|
|
|
|
|
|
// No child -- clear the projection. Without tqchildren, a group layer is empty.
|
|
|
|
if (!child) m_projection->clear();
|
|
|
|
|
|
|
|
KisLayerSP startWith = 0;
|
|
|
|
KisAdjustmentLayerSP adjLayer = 0;
|
|
|
|
KisLayerSP tmpPaintLayer = 0;
|
|
|
|
|
|
|
|
// If this is the rootlayer, don't do anything with adj. layers that are below the
|
|
|
|
// first paintlayer
|
|
|
|
bool gotPaintLayer = (parent() != 0);
|
|
|
|
|
|
|
|
// Look through all the child layers, searching for the first dirty layer
|
|
|
|
// if it's found, and if we have found an adj. layer before the the dirty layer,
|
|
|
|
// composite from the first adjustment layer searching back from the first dirty layer
|
|
|
|
while (child) {
|
|
|
|
KisAdjustmentLayerSP tmpAdjLayer = dynamic_cast<KisAdjustmentLayer*>(child.data());
|
|
|
|
if (tmpAdjLayer) {
|
|
|
|
if (gotPaintLayer) {
|
|
|
|
// If this adjustment layer is dirty, start compositing with the
|
|
|
|
// previous layer, if there's one.
|
|
|
|
if (tmpAdjLayer->dirty(rc) && adjLayer != 0 && adjLayer->visible()) {
|
|
|
|
startWith = adjLayer->prevSibling();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (tmpAdjLayer->visible() && !tmpAdjLayer->dirty(rc)) {
|
|
|
|
// This is the first adj. layer that is not dirty -- the perfect starting point
|
|
|
|
adjLayer = tmpAdjLayer;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
startWith = tmpPaintLayer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
tmpPaintLayer = child;
|
|
|
|
gotPaintLayer = true;
|
|
|
|
// A non-adjustmentlayer that's dirty; if there's an adjustmentlayer
|
|
|
|
// with a cache, we'll start from there.
|
|
|
|
if (child->dirty(rc)) {
|
|
|
|
if (adjLayer != 0 && adjLayer->visible()) {
|
|
|
|
// the first layer on top of the adj. layer
|
|
|
|
startWith = adjLayer->prevSibling();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
startWith = child;
|
|
|
|
}
|
|
|
|
// break here: if there's no adj layer, we'll start with the layer->lastChild
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
child = child->prevSibling();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (adjLayer != 0 && startWith == 0 && gotPaintLayer && adjLayer->prevSibling()) {
|
|
|
|
startWith = adjLayer->prevSibling();
|
|
|
|
}
|
|
|
|
|
|
|
|
// No adj layer -- all layers inside the group must be recomposited
|
|
|
|
if (adjLayer == 0) {
|
|
|
|
startWith = lastChild();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (startWith == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool first = true; // The first layer in a stack needs special compositing
|
|
|
|
|
|
|
|
// Fill the projection either with the cached data, or erase it.
|
|
|
|
KisFillPainter gc(m_projection);
|
|
|
|
if (adjLayer != 0) {
|
|
|
|
gc.bitBlt(rc.left(), rc.top(),
|
|
|
|
COMPOSITE_COPY, adjLayer->cachedPaintDevice(), OPACITY_OPAQUE,
|
|
|
|
rc.left(), rc.top(), rc.width(), rc.height());
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
gc.eraseRect(rc);
|
|
|
|
first = true;
|
|
|
|
}
|
|
|
|
gc.end();
|
|
|
|
|
|
|
|
KisMergeVisitor visitor(m_projection, rc);
|
|
|
|
|
|
|
|
child = startWith;
|
|
|
|
|
|
|
|
while(child)
|
|
|
|
{
|
|
|
|
if(first)
|
|
|
|
{
|
|
|
|
// Copy the lowest layer rather than compositing it with the background
|
|
|
|
// or an empty image. This means the layer's composite op is ignored,
|
|
|
|
// which is consistent with Photoshop and gimp.
|
|
|
|
const KisCompositeOp cop = child->compositeOp();
|
|
|
|
const bool block = child->signalsBlocked();
|
|
|
|
child->blockSignals(true);
|
|
|
|
// Composite op copy doesn't take a mask/selection into account, so we need
|
|
|
|
// to make a difference between a paintlayer with a mask, and one without
|
|
|
|
KisPaintLayer* l = dynamic_cast<KisPaintLayer*>(child.data());
|
|
|
|
if (l && l->hasMask())
|
|
|
|
child->m_compositeOp = COMPOSITE_OVER;
|
|
|
|
else
|
|
|
|
child->m_compositeOp = COMPOSITE_COPY;
|
|
|
|
child->blockSignals(block);
|
|
|
|
child->accept(visitor);
|
|
|
|
child->blockSignals(true);
|
|
|
|
child->m_compositeOp = cop;
|
|
|
|
child->blockSignals(block);
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
child->accept(visitor);
|
|
|
|
|
|
|
|
child = child->prevSibling();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "kis_group_layer.moc"
|