You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/chalk/core/kis_group_layer.cc

429 lines
12 KiB

/*
* 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 tqparent, 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 (tqparent() == 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->tqparent().data() == this)
return layer->index();
return -1;
}
void KisGroupLayer::setIndex(KisLayerSP layer, int index)
{
if (layer->tqparent().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->tqparent() || m_layers.tqcontains(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->tqparent().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->tqparent().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 = (tqparent() != 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 tqmask/selection into account, so we need
// to make a difference between a paintlayer with a tqmask, 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"