/* * Copyright (c) 2005 Cyrille Berger * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "tqbrush.h" #include "tqcolor.h" #include "tqfontinfo.h" #include "tqfontmetrics.h" #include "tqpen.h" #include "tqregion.h" #include "tqwmatrix.h" #include #include #include #include #include #include #include #include #include #include #include "kis_brush.h" #include "kis_global.h" #include "kis_image.h" #include "kis_iterators_pixel.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "kis_pattern.h" #include "kis_rect.h" #include "kis_colorspace.h" #include "kis_types.h" #include "kis_vec.h" #include "kis_selection.h" #include "kis_convolution_painter.h" KisKernelSP KisKernel::fromTQImage(const TQImage& img) { KisKernelSP k = new KisKernel; k->width = img.width(); k->height = img.height(); k->offset = 0; uint count = k->width * k->height; k->data = new TQ_INT32[count]; TQ_INT32* itData = k->data; TQ_UINT8* itImg = img.bits(); k->factor = 0; for(uint i = 0; i < count; ++i , ++itData, itImg+=4) { *itData = 255 - ( *itImg + *(itImg+1) + *(itImg+2) ) / 3; k->factor += *itData; } return k; } KisConvolutionPainter::KisConvolutionPainter() : super() { } KisConvolutionPainter::KisConvolutionPainter(KisPaintDeviceSP device) : super(device) { } void KisConvolutionPainter::applyMatrix(KisKernelSP kernel, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, KisConvolutionBorderOp borderOp, KisChannelInfo::enumChannelFlags channelFlags ) { // Make the area we cover as small as possible if (m_device->hasSelection()) { TQRect r = m_device->selection()->selectedRect().intersect(TQRect(x, y, w, h)); x = r.x(); y = r.y(); w = r.width(); h = r.height(); } if ( w == 0 && h == 0 ) return; // Determine the kernel's extent from the center pixel TQ_INT32 kw, kh, khalfWidth, khalfHeight, xLastMinuskhw, yLastMinuskhh; kw = kernel->width; kh = kernel->height; khalfWidth = (kw - 1) / 2; khalfHeight = (kh - 1) / 2; xLastMinuskhw = x + (w - khalfWidth); yLastMinuskhh = y + (h - khalfHeight); // Don't try to convolve on an area smaller than the kernel, or with a kernel that is not square or has no center pixel. if (w < kw || h < kh || (kw&1) == 0 || (kh&1) == 0 || kernel->factor == 0 ) return; m_cancelRequested = false; int lastProgressPercent = 0; emit notifyProgress(0); KisColorSpace * cs = m_device->colorSpace(); // Determine whether we convolve border pixels, or not. switch (borderOp) { case BORDER_DEFAULT_FILL : break; case BORDER_REPEAT: applyMatrixRepeat(kernel, x, y, w, h, channelFlags); return; case BORDER_WRAP: case BORDER_AVOID: default : x += khalfWidth; y += khalfHeight; w -= kw - 1; h -= kh - 1; } // Iterate over all pixels in our rect, create a cache of pixels around the current pixel and convolve them in the colorstrategy. int cacheSize = kw * kh; int cdepth = cs -> pixelSize(); TQ_UINT8** pixelPtrCache = new TQ_UINT8*[cacheSize]; for (int i = 0; i < cacheSize; i++) pixelPtrCache[i] = new TQ_UINT8[cdepth]; // pixelPtrCache.fill(0); // row == the y position of the pixel we want to change in the paint device int row = y; for (; row < y + h; ++row) { // col = the x position of the pixel we want to change int col = x; KisHLineIteratorPixel hit = m_device->createHLineIterator(x, row, w, true); bool needFull = true; while (!hit.isDone()) { // Iterate over all contributing pixels that are covered by the kernel // krow = the y position in the kernel matrix if(needFull) { TQ_INT32 i = 0; for (TQ_INT32 krow = 0; krow < kh; ++krow) { // col - khalfWidth = the left starting point of the kernel as centered on our pixel // krow - khalfHeight = the offset for the top of the kernel as centered on our pixel // kw = the width of the kernel // Fill the cache with pointers to the pixels under the kernel KisHLineIteratorPixel kit = m_device->createHLineIterator(col - khalfWidth, (row - khalfHeight) + krow, kw, false); while (!kit.isDone()) { memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); ++kit; ++i; } } needFull = false; Q_ASSERT (i==kw*kh); } else { for (TQ_INT32 krow = 0; krow < kh; ++krow) { // shift the cache to the left TQ_UINT8** d = pixelPtrCache + krow * kw; //memmove( d, d + 1, (kw-1)*sizeof(TQ_UINT8*)); for (int i = 0; i < (kw-1); i++) { memcpy(d[i], d[i+1], cdepth); } } TQ_INT32 i = kw - 1; KisVLineIteratorPixel kit = m_device->createVLineIterator(col + khalfWidth, row - khalfHeight, kh, false); while (!kit.isDone()) { memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); ++kit; i += kw; } } if (hit.isSelected()) { cs->convolveColors(pixelPtrCache, kernel->data, channelFlags, hit.rawData(), kernel->factor, kernel->offset, kw * kh); // pixelPtrCache.fill(0); } ++col; ++hit; } int progressPercent = 100 - ((((y + h) - row) * 100) / h); if (progressPercent > lastProgressPercent) { emit notifyProgress(progressPercent); lastProgressPercent = progressPercent; if (m_cancelRequested) { for (int i = 0; i < cacheSize; i++) delete[] pixelPtrCache[i]; delete[] pixelPtrCache; return; } } } addDirtyRect(TQRect(x, y, w, h)); emit notifyProgressDone(); for (int i = 0; i < cacheSize; i++) delete[] pixelPtrCache[i]; delete[] pixelPtrCache; } void KisConvolutionPainter::applyMatrixRepeat(KisKernelSP kernel, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_INT32 h, KisChannelInfo::enumChannelFlags channelFlags) { int lastProgressPercent = 0; // Determine the kernel's extent from the center pixel TQ_INT32 kw, kh, khalfWidth, khalfHeight, xLastMinuskhw, yLastMinuskhh; kw = kernel->width; kh = kernel->height; khalfWidth = (kw - 1) / 2; khalfHeight = (kh - 1) / 2; xLastMinuskhw = x + (w - khalfWidth); yLastMinuskhh = y + (h - khalfHeight); KisColorSpace * cs = m_device->colorSpace(); // Iterate over all pixels in our rect, create a cache of pixels around the current pixel and convolve them in the colorstrategy. int cacheSize = kw * kh; int cdepth = cs -> pixelSize(); TQ_UINT8** pixelPtrCache = new TQ_UINT8*[cacheSize]; for (int i = 0; i < cacheSize; i++) pixelPtrCache[i] = new TQ_UINT8[cdepth]; // row == the y position of the pixel we want to change in the paint device int row = y; for (; row < y + h; ++row) { // col = the x position of the pixel we want to change int col = x; KisHLineIteratorPixel hit = m_device->createHLineIterator(x, row, w, true); bool needFull = true; TQ_INT32 itStart = row - khalfHeight; TQ_INT32 itH = kh; if(itStart < 0) { itH += itStart; itStart = 0; } else if(itStart + kh > yLastMinuskhh) { itH -= itStart + kh - yLastMinuskhh; } KisVLineIteratorPixel kit = m_device->createVLineIterator(col + khalfWidth, itStart, itH, false); while (!hit.isDone()) { // Iterate over all contributing pixels that are covered by the kernel // krow = the y position in the kernel matrix if(needFull) // The cache has not been fill, so we need to fill it { TQ_INT32 i = 0; TQ_INT32 krow = 0; if( row < khalfHeight ) { // We are just outside the layer, all the row in the cache will be identical // so we need to create them only once, and then to copy them if( x < khalfWidth) { // the left pixels are outside of the layer, in the corner TQ_INT32 kcol = 0; KisHLineIteratorPixel kit = m_device->createHLineIterator(0, 0, kw, false); for(; kcol < (khalfWidth - x) + 1; ++kcol) { // First copy the address of the topleft pixel memcpy(pixelPtrCache[kcol], kit.oldRawData(), cdepth); } for(; kcol < kw; ++kcol) { // Then copy the address of the rest of the line ++kit; memcpy(pixelPtrCache[kcol], kit.oldRawData(), cdepth); } } else { uint kcol = 0; KisHLineIteratorPixel kit = m_device->createHLineIterator(col - khalfWidth, 0, kw, false); while (!kit.isDone()) { memcpy(pixelPtrCache[kcol], kit.oldRawData(), cdepth); ++kit; ++kcol; } } krow = 1; // we have allready done the first krow for(;krow < (khalfHeight - row); ++krow) { // Copy the first line in the current line for (int i = 0; i < kw; i++) memcpy(pixelPtrCache[krow * kw + i], pixelPtrCache[i], cdepth); } i = krow * kw; } TQ_INT32 itH = kh; if(row + khalfHeight > yLastMinuskhh) { itH += yLastMinuskhh - row - khalfHeight; } for (; krow < itH; ++krow) { // col - khalfWidth = the left starting point of the kernel as centered on our pixel // krow - khalfHeight = the offset for the top of the kernel as centered on our pixel // kw = the width of the kernel // Fill the cache with pointers to the pixels under the kernel TQ_INT32 itHStart = col - khalfWidth; TQ_INT32 itW = kw; if(itHStart < 0) { itW += itHStart; itHStart = 0; } KisHLineIteratorPixel kit = m_device->createHLineIterator(itHStart, (row - khalfHeight) + krow, itW, false); if( col < khalfWidth ) { for(; i < krow * kw + ( kw - itW ); i+= 1) { memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); } } while (!kit.isDone()) { memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); ++kit; ++i; } } TQ_INT32 lastvalid = i - kw; for(; krow < kh; ++krow) { // Copy the last valid line in the current line for (int i = 0; i < kw; i++) memcpy(pixelPtrCache[krow * kw + i], pixelPtrCache[lastvalid + i], cdepth); } needFull = false; } else { /* for (TQ_INT32 krow = 0; krow < kh; ++krow) { // shift the cache to the left TQ_UINT8** d = pixelPtrCache + krow * kw; // memmove( d, d + 1, (kw-1)*sizeof(TQ_UINT8*)); for (int i = 0; i < (kw-1); i++) { memcpy(d[i], d[i+1], cdepth); } }*/ TQ_UINT8* firstincache = pixelPtrCache[0]; memmove(pixelPtrCache, pixelPtrCache + 1, (cacheSize - 1) * sizeof(TQ_UINT8*) ); pixelPtrCache[cacheSize - 1] = firstincache; if(col < xLastMinuskhw) { TQ_INT32 i = kw - 1; // KisVLineIteratorPixel kit = m_device->createVLineIterator(col + khalfWidth, itStart, itH, false); kit.nextCol(); if( row < khalfHeight ) { for(; i < (khalfHeight- row ) * kw; i+=kw) { memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); } } while (!kit.isDone()) { memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); ++kit; i += kw; } TQ_INT32 lastvalid = i - kw; for(;i < kw*kh; i+=kw) { memcpy(pixelPtrCache[i], pixelPtrCache[lastvalid], cdepth); } } } if (hit.isSelected()) { cs->convolveColors(pixelPtrCache, kernel->data, channelFlags, hit.rawData(), kernel->factor, kernel->offset, kw * kh); } ++col; ++hit; } int progressPercent = 100 - ((((y + h) - row) * 100) / h); if (progressPercent > lastProgressPercent) { emit notifyProgress(progressPercent); lastProgressPercent = progressPercent; if (m_cancelRequested) { for (int i = 0; i < cacheSize; i++) delete[] pixelPtrCache[i]; delete[] pixelPtrCache; return; } } } addDirtyRect(TQRect(x, y, w, h)); emit notifyProgressDone(); for (int i = 0; i < cacheSize; i++) delete[] pixelPtrCache[i]; delete[] pixelPtrCache; }