/* * Copyright (c) 2004 Boudewijn Rempt * * 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 #include #include #include #include "kis_layer.h" #include "kis_debug_areas.h" #include "kis_types.h" #include "kis_colorspace_factory_registry.h" #include "kis_fill_painter.h" #include "kis_iterators_pixel.h" #include "kis_integer_maths.h" #include "kis_image.h" #include "kis_datamanager.h" #include "kis_fill_painter.h" #include "kis_selection.h" KisSelection::KisSelection(KisPaintDeviceSP dev) : super(dev->parentLayer() , KisMetaRegistry::instance()->csRegistry()->getAlpha8() , (TQString("selection for ") + dev->name()).latin1()) , m_parentPaintDevice(dev) , m_doCacheExactRect(false) , m_dirty(false) { Q_ASSERT(dev); } KisSelection::KisSelection() : super(KisMetaRegistry::instance()->csRegistry()->getAlpha8(), "anonymous selection") , m_parentPaintDevice(0), m_dirty(false) { } KisSelection::KisSelection(const KisSelection& rhs) : super(rhs), m_parentPaintDevice(rhs.m_parentPaintDevice), m_doCacheExactRect(rhs.m_doCacheExactRect), m_cachedExactRect(rhs.m_cachedExactRect), m_dirty(rhs.m_dirty) { } KisSelection::~KisSelection() { } TQ_UINT8 KisSelection::selected(TQ_INT32 x, TQ_INT32 y) { KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, false); TQ_UINT8 *pix = iter.rawData(); return *pix; } void KisSelection::setSelected(TQ_INT32 x, TQ_INT32 y, TQ_UINT8 s) { KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, true); TQ_UINT8 *pix = iter.rawData(); *pix = s; } TQImage KisSelection::maskImage() { // If part of a KisAdjustmentLayer, there may be no parent device. TQImage img; TQRect bounds; if (m_parentPaintDevice) { bounds = m_parentPaintDevice->exactBounds(); bounds = bounds.intersect( m_parentPaintDevice->image()->bounds() ); img = TQImage(bounds.width(), bounds.height(), 32); } else { bounds = TQRect( 0, 0, image()->width(), image()->height()); img = TQImage(bounds.width(), bounds.height(), 32); } KisHLineIteratorPixel it = createHLineIterator(bounds.x(), bounds.y(), bounds.width(), false); for (int y2 = bounds.y(); y2 < bounds.height() - bounds.y(); ++y2) { int x2 = 0; while (!it.isDone()) { TQ_UINT8 s = MAX_SELECTED - *(it.rawData()); TQ_INT32 c = tqRgb(s, s, s); img.setPixel(x2, y2, c); ++x2; ++it; } it.nextRow(); } return img; } void KisSelection::select(TQRect r) { KisFillPainter painter(this); KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); painter.fillRect(r, KisColor(TQt::white, cs), MAX_SELECTED); TQ_INT32 x, y, w, h; extent(x, y, w, h); } void KisSelection::clear(TQRect r) { KisFillPainter painter(this); KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); painter.fillRect(r, KisColor(TQt::white, cs), MIN_SELECTED); } void KisSelection::clear() { TQ_UINT8 defPixel = MIN_SELECTED; m_datamanager->setDefaultPixel(&defPixel); m_datamanager->clear(); } void KisSelection::invert() { TQ_INT32 x,y,w,h; extent(x, y, w, h); KisRectIterator it = createRectIterator(x, y, w, h, true); while ( ! it.isDone() ) { // CBR this is wrong only first byte is inverted // BSAR: But we have always only one byte in this color model :-). *(it.rawData()) = MAX_SELECTED - *(it.rawData()); ++it; } TQ_UINT8 defPixel = MAX_SELECTED - *(m_datamanager->defaultPixel()); m_datamanager->setDefaultPixel(&defPixel); } bool KisSelection::isTotallyUnselected(TQRect r) { if(*(m_datamanager->defaultPixel()) != MIN_SELECTED) return false; TQRect sr = selectedExactRect(); return ! r.intersects(sr); } bool KisSelection::isProbablyTotallyUnselected(TQRect r) { if(*(m_datamanager->defaultPixel()) != MIN_SELECTED) return false; TQRect sr = selectedRect(); return ! r.intersects(sr); } TQRect KisSelection::selectedRect() const { if(*(m_datamanager->defaultPixel()) == MIN_SELECTED || !m_parentPaintDevice) return extent(); else return extent().unite(m_parentPaintDevice->extent()); } TQRect KisSelection::selectedExactRect() const { if(m_doCacheExactRect) return m_cachedExactRect; else if(*(m_datamanager->defaultPixel()) == MIN_SELECTED || !m_parentPaintDevice) return exactBounds(); else return exactBounds().unite(m_parentPaintDevice->exactBounds()); } void KisSelection::stopCachingExactRect() { kdDebug() << "stop caching the exact rect" << endl; m_doCacheExactRect = false; } void KisSelection::startCachingExactRect() { kdDebug() << "start caching the exact rect" << endl; if(*(m_datamanager->defaultPixel()) == MIN_SELECTED || !m_parentPaintDevice) m_cachedExactRect = exactBounds(); else m_cachedExactRect = exactBounds().unite(m_parentPaintDevice->exactBounds()); m_doCacheExactRect = true; } void KisSelection::paintUniformSelectionRegion(TQImage img, const TQRect& imageRect, const TQRegion& uniformRegion) { Q_ASSERT(img.size() == imageRect.size()); Q_ASSERT(imageRect.contains(uniformRegion.boundingRect())); if (img.isNull() || img.size() != imageRect.size() || !imageRect.contains(uniformRegion.boundingRect())) { return; } if (*m_datamanager->defaultPixel() == MIN_SELECTED) { TQRegion region = uniformRegion & TQRegion(imageRect); if (!region.isEmpty()) { TQMemArray rects = region.tqrects(); for (unsigned int i = 0; i < rects.count(); i++) { TQRect r = rects[i]; for (TQ_INT32 y = 0; y < r.height(); ++y) { TQRgb *imagePixel = reinterpret_cast(img.scanLine(r.y() - imageRect.y() + y)); imagePixel += r.x() - imageRect.x(); TQ_INT32 numPixels = r.width(); while (numPixels > 0) { TQRgb srcPixel = *imagePixel; TQ_UINT8 srcGrey = (tqRed(srcPixel) + tqGreen(srcPixel) + tqBlue(srcPixel)) / 9; TQ_UINT8 srcAlpha = tqAlpha(srcPixel); srcGrey = UINT8_MULT(srcGrey, srcAlpha); TQ_UINT8 dstAlpha = TQMAX(srcAlpha, 192); TQRgb dstPixel = tqRgba(128 + srcGrey, 128 + srcGrey, 165 + srcGrey, dstAlpha); *imagePixel = dstPixel; ++imagePixel; --numPixels; } } } } } } void KisSelection::paintSelection(TQImage img, TQ_INT32 imageRectX, TQ_INT32 imageRectY, TQ_INT32 imageRectWidth, TQ_INT32 imageRectHeight) { Q_ASSERT(img.size() == TQSize(imageRectWidth, imageRectHeight)); if (img.isNull() || img.size() != TQSize(imageRectWidth, imageRectHeight)) { return; } TQRect imageRect(imageRectX, imageRectY, imageRectWidth, imageRectHeight); TQRect selectionExtent = extent(); selectionExtent.setLeft(selectionExtent.left() - 1); selectionExtent.setTop(selectionExtent.top() - 1); selectionExtent.setWidth(selectionExtent.width() + 2); selectionExtent.setHeight(selectionExtent.height() + 2); TQRegion uniformRegion = TQRegion(imageRect); uniformRegion -= TQRegion(selectionExtent); if (!uniformRegion.isEmpty()) { paintUniformSelectionRegion(img, imageRect, uniformRegion); } TQRect nonuniformRect = imageRect & selectionExtent; if (!nonuniformRect.isEmpty()) { const TQ_INT32 imageRectOffsetX = nonuniformRect.x() - imageRectX; const TQ_INT32 imageRectOffsetY = nonuniformRect.y() - imageRectY; imageRectX = nonuniformRect.x(); imageRectY = nonuniformRect.y(); imageRectWidth = nonuniformRect.width(); imageRectHeight = nonuniformRect.height(); const TQ_INT32 NUM_SELECTION_ROWS = 3; TQ_UINT8 *selectionRow[NUM_SELECTION_ROWS]; TQ_INT32 aboveRowIndex = 0; TQ_INT32 centreRowIndex = 1; TQ_INT32 belowRowIndex = 2; selectionRow[aboveRowIndex] = new TQ_UINT8[imageRectWidth + 2]; selectionRow[centreRowIndex] = new TQ_UINT8[imageRectWidth + 2]; selectionRow[belowRowIndex] = new TQ_UINT8[imageRectWidth + 2]; readBytes(selectionRow[centreRowIndex], imageRectX - 1, imageRectY - 1, imageRectWidth + 2, 1); readBytes(selectionRow[belowRowIndex], imageRectX - 1, imageRectY, imageRectWidth + 2, 1); for (TQ_INT32 y = 0; y < imageRectHeight; ++y) { TQ_INT32 oldAboveRowIndex = aboveRowIndex; aboveRowIndex = centreRowIndex; centreRowIndex = belowRowIndex; belowRowIndex = oldAboveRowIndex; readBytes(selectionRow[belowRowIndex], imageRectX - 1, imageRectY + y + 1, imageRectWidth + 2, 1); const TQ_UINT8 *aboveRow = selectionRow[aboveRowIndex] + 1; const TQ_UINT8 *centreRow = selectionRow[centreRowIndex] + 1; const TQ_UINT8 *belowRow = selectionRow[belowRowIndex] + 1; TQRgb *imagePixel = reinterpret_cast(img.scanLine(imageRectOffsetY + y)); imagePixel += imageRectOffsetX; for (TQ_INT32 x = 0; x < imageRectWidth; ++x) { TQ_UINT8 centre = *centreRow; if (centre != MAX_SELECTED) { // this is where we come if the pixels should be blue or bluish TQRgb srcPixel = *imagePixel; TQ_UINT8 srcGrey = (tqRed(srcPixel) + tqGreen(srcPixel) + tqBlue(srcPixel)) / 9; TQ_UINT8 srcAlpha = tqAlpha(srcPixel); // Colour influence is proportional to alphaPixel. srcGrey = UINT8_MULT(srcGrey, srcAlpha); TQRgb dstPixel; if (centre == MIN_SELECTED) { //this is where we come if the pixels should be blue (or red outline) TQ_UINT8 left = *(centreRow - 1); TQ_UINT8 right = *(centreRow + 1); TQ_UINT8 above = *aboveRow; TQ_UINT8 below = *belowRow; // Stop unselected transparent areas from appearing the same // as selected transparent areas. TQ_UINT8 dstAlpha = TQMAX(srcAlpha, 192); // now for a simple outline based on 4-connectivity if (left != MIN_SELECTED || right != MIN_SELECTED || above != MIN_SELECTED || below != MIN_SELECTED) { dstPixel = tqRgba(255, 0, 0, dstAlpha); } else { dstPixel = tqRgba(128 + srcGrey, 128 + srcGrey, 165 + srcGrey, dstAlpha); } } else { dstPixel = tqRgba(UINT8_BLEND(tqRed(srcPixel), srcGrey + 128, centre), UINT8_BLEND(tqGreen(srcPixel), srcGrey + 128, centre), UINT8_BLEND(tqBlue(srcPixel), srcGrey + 165, centre), srcAlpha); } *imagePixel = dstPixel; } aboveRow++; centreRow++; belowRow++; imagePixel++; } } delete [] selectionRow[aboveRowIndex]; delete [] selectionRow[centreRowIndex]; delete [] selectionRow[belowRowIndex]; } } void KisSelection::paintSelection(TQImage img, const TQRect& scaledImageRect, const TQSize& scaledImageSize, const TQSize& imageSize) { if (img.isNull() || scaledImageRect.isEmpty() || scaledImageSize.isEmpty() || imageSize.isEmpty()) { return; } Q_ASSERT(img.size() == scaledImageRect.size()); if (img.size() != scaledImageRect.size()) { return; } TQ_INT32 imageWidth = imageSize.width(); TQ_INT32 imageHeight = imageSize.height(); TQRect selectionExtent = extent(); selectionExtent.setLeft(selectionExtent.left() - 1); selectionExtent.setTop(selectionExtent.top() - 1); selectionExtent.setWidth(selectionExtent.width() + 2); selectionExtent.setHeight(selectionExtent.height() + 2); double xScale = static_cast(scaledImageSize.width()) / imageWidth; double yScale = static_cast(scaledImageSize.height()) / imageHeight; TQRect scaledSelectionExtent; scaledSelectionExtent.setLeft(static_cast(selectionExtent.left() * xScale)); scaledSelectionExtent.setRight(static_cast(ceil((selectionExtent.right() + 1) * xScale)) - 1); scaledSelectionExtent.setTop(static_cast(selectionExtent.top() * yScale)); scaledSelectionExtent.setBottom(static_cast(ceil((selectionExtent.bottom() + 1) * yScale)) - 1); TQRegion uniformRegion = TQRegion(scaledImageRect); uniformRegion -= TQRegion(scaledSelectionExtent); if (!uniformRegion.isEmpty()) { paintUniformSelectionRegion(img, scaledImageRect, uniformRegion); } TQRect nonuniformRect = scaledImageRect & scaledSelectionExtent; if (!nonuniformRect.isEmpty()) { const TQ_INT32 scaledImageRectXOffset = nonuniformRect.x() - scaledImageRect.x(); const TQ_INT32 scaledImageRectYOffset = nonuniformRect.y() - scaledImageRect.y(); const TQ_INT32 scaledImageRectX = nonuniformRect.x(); const TQ_INT32 scaledImageRectY = nonuniformRect.y(); const TQ_INT32 scaledImageRectWidth = nonuniformRect.width(); const TQ_INT32 scaledImageRectHeight = nonuniformRect.height(); const TQ_INT32 imageRowLeft = static_cast(scaledImageRectX / xScale); const TQ_INT32 imageRowRight = static_cast((ceil((scaledImageRectX + scaledImageRectWidth - 1 + 1) / xScale)) - 1); const TQ_INT32 imageRowWidth = imageRowRight - imageRowLeft + 1; const TQ_INT32 imageRowStride = imageRowWidth + 2; const TQ_INT32 NUM_SELECTION_ROWS = 3; TQ_INT32 aboveRowIndex = 0; TQ_INT32 centreRowIndex = 1; TQ_INT32 belowRowIndex = 2; TQ_INT32 aboveRowSrcY = -3; TQ_INT32 centreRowSrcY = -3; TQ_INT32 belowRowSrcY = -3; TQ_UINT8 *selectionRows = new TQ_UINT8[imageRowStride * NUM_SELECTION_ROWS]; TQ_UINT8 *selectionRow[NUM_SELECTION_ROWS]; selectionRow[0] = selectionRows + 1; selectionRow[1] = selectionRow[0] + imageRowStride; selectionRow[2] = selectionRow[0] + (2 * imageRowStride); for (TQ_INT32 y = 0; y < scaledImageRectHeight; ++y) { TQ_INT32 scaledY = scaledImageRectY + y; TQ_INT32 srcY = (scaledY * imageHeight) / scaledImageSize.height(); TQ_UINT8 *aboveRow; TQ_UINT8 *centreRow; TQ_UINT8 *belowRow; if (srcY - 1 == aboveRowSrcY) { aboveRow = selectionRow[aboveRowIndex]; centreRow = selectionRow[centreRowIndex]; belowRow = selectionRow[belowRowIndex]; } else if (srcY - 1 == centreRowSrcY) { TQ_INT32 oldAboveRowIndex = aboveRowIndex; aboveRowIndex = centreRowIndex; centreRowIndex = belowRowIndex; belowRowIndex = oldAboveRowIndex; aboveRow = selectionRow[aboveRowIndex]; centreRow = selectionRow[centreRowIndex]; belowRow = selectionRow[belowRowIndex]; readBytes(belowRow - 1, imageRowLeft - 1, srcY + 1, imageRowStride, 1); } else if (srcY - 1 == belowRowSrcY) { TQ_INT32 oldAboveRowIndex = aboveRowIndex; TQ_INT32 oldCentreRowIndex = centreRowIndex; aboveRowIndex = belowRowIndex; centreRowIndex = oldAboveRowIndex; belowRowIndex = oldCentreRowIndex; aboveRow = selectionRow[aboveRowIndex]; centreRow = selectionRow[centreRowIndex]; belowRow = selectionRow[belowRowIndex]; if (belowRowIndex == centreRowIndex + 1) { readBytes(centreRow - 1, imageRowLeft - 1, srcY, imageRowStride, 2); } else { readBytes(centreRow - 1, imageRowLeft - 1, srcY, imageRowStride, 1); readBytes(belowRow - 1, imageRowLeft - 1, srcY + 1, imageRowStride, 1); } } else { aboveRowIndex = 0; centreRowIndex = 1; belowRowIndex = 2; aboveRow = selectionRow[aboveRowIndex]; centreRow = selectionRow[centreRowIndex]; belowRow = selectionRow[belowRowIndex]; readBytes(selectionRows, imageRowLeft - 1, srcY - 1, imageRowStride, NUM_SELECTION_ROWS); } aboveRowSrcY = srcY - 1; centreRowSrcY = aboveRowSrcY + 1; belowRowSrcY = centreRowSrcY + 1; TQRgb *imagePixel = reinterpret_cast(img.scanLine(scaledImageRectYOffset + y)); imagePixel += scaledImageRectXOffset; for (TQ_INT32 x = 0; x < scaledImageRectWidth; ++x) { TQ_INT32 scaledX = scaledImageRectX + x; TQ_INT32 srcX = (scaledX * imageWidth) / scaledImageSize.width(); TQ_UINT8 centre = *(centreRow + srcX - imageRowLeft); if (centre != MAX_SELECTED) { // this is where we come if the pixels should be blue or bluish TQRgb srcPixel = *imagePixel; TQ_UINT8 srcGrey = (tqRed(srcPixel) + tqGreen(srcPixel) + tqBlue(srcPixel)) / 9; TQ_UINT8 srcAlpha = tqAlpha(srcPixel); // Colour influence is proportional to alphaPixel. srcGrey = UINT8_MULT(srcGrey, srcAlpha); TQRgb dstPixel; if (centre == MIN_SELECTED) { //this is where we come if the pixels should be blue (or red outline) TQ_UINT8 left = *(centreRow + (srcX - imageRowLeft) - 1); TQ_UINT8 right = *(centreRow + (srcX - imageRowLeft) + 1); TQ_UINT8 above = *(aboveRow + (srcX - imageRowLeft)); TQ_UINT8 below = *(belowRow + (srcX - imageRowLeft)); // Stop unselected transparent areas from appearing the same // as selected transparent areas. TQ_UINT8 dstAlpha = TQMAX(srcAlpha, 192); // now for a simple outline based on 4-connectivity if (left != MIN_SELECTED || right != MIN_SELECTED || above != MIN_SELECTED || below != MIN_SELECTED) { dstPixel = tqRgba(255, 0, 0, dstAlpha); } else { dstPixel = tqRgba(128 + srcGrey, 128 + srcGrey, 165 + srcGrey, dstAlpha); } } else { dstPixel = tqRgba(UINT8_BLEND(tqRed(srcPixel), srcGrey + 128, centre), UINT8_BLEND(tqGreen(srcPixel), srcGrey + 128, centre), UINT8_BLEND(tqBlue(srcPixel), srcGrey + 165, centre), srcAlpha); } *imagePixel = dstPixel; } imagePixel++; } } delete [] selectionRows; } } void KisSelection::setDirty(const TQRect& rc) { if (m_dirty) super::setDirty(rc); } void KisSelection::setDirty() { if (m_dirty) super::setDirty(); }