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.
427 lines
15 KiB
427 lines
15 KiB
/*
|
|
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <cfloat>
|
|
|
|
#include "qbrush.h"
|
|
#include "qcolor.h"
|
|
#include "qfontinfo.h"
|
|
#include "qfontmetrics.h"
|
|
#include "qpen.h"
|
|
#include "qregion.h"
|
|
#include "qwmatrix.h"
|
|
#include <qimage.h>
|
|
#include <qmap.h>
|
|
#include <qpainter.h>
|
|
#include <qpixmap.h>
|
|
#include <qpointarray.h>
|
|
#include <qrect.h>
|
|
#include <qstring.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <klocale.h>
|
|
|
|
#include <qcolor.h>
|
|
|
|
#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::fromQImage(const QImage& 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 Q_INT32[count];
|
|
Q_INT32* itData = k->data;
|
|
Q_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, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h,
|
|
KisConvolutionBorderOp borderOp,
|
|
KisChannelInfo::enumChannelFlags channelFlags )
|
|
{
|
|
// Make the area we cover as small as possible
|
|
if (m_device->hasSelection()) {
|
|
|
|
QRect r = m_device->selection()->selectedRect().intersect(QRect(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
|
|
Q_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();
|
|
Q_UINT8** pixelPtrCache = new Q_UINT8*[cacheSize];
|
|
for (int i = 0; i < cacheSize; i++)
|
|
pixelPtrCache[i] = new Q_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)
|
|
{
|
|
Q_INT32 i = 0;
|
|
for (Q_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 (Q_INT32 krow = 0; krow < kh; ++krow) { // shift the cache to the left
|
|
Q_UINT8** d = pixelPtrCache + krow * kw;
|
|
//memmove( d, d + 1, (kw-1)*sizeof(Q_UINT8*));
|
|
for (int i = 0; i < (kw-1); i++) {
|
|
memcpy(d[i], d[i+1], cdepth);
|
|
}
|
|
}
|
|
Q_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(QRect(x, y, w, h));
|
|
|
|
emit notifyProgressDone();
|
|
|
|
for (int i = 0; i < cacheSize; i++)
|
|
delete[] pixelPtrCache[i];
|
|
delete[] pixelPtrCache;
|
|
}
|
|
|
|
void KisConvolutionPainter::applyMatrixRepeat(KisKernelSP kernel, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h,
|
|
KisChannelInfo::enumChannelFlags channelFlags)
|
|
{
|
|
int lastProgressPercent = 0;
|
|
// Determine the kernel's extent from the center pixel
|
|
Q_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();
|
|
Q_UINT8** pixelPtrCache = new Q_UINT8*[cacheSize];
|
|
for (int i = 0; i < cacheSize; i++)
|
|
pixelPtrCache[i] = new Q_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;
|
|
|
|
Q_INT32 itStart = row - khalfHeight;
|
|
Q_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
|
|
{
|
|
Q_INT32 i = 0;
|
|
Q_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
|
|
Q_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;
|
|
}
|
|
Q_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
|
|
Q_INT32 itHStart = col - khalfWidth;
|
|
Q_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;
|
|
}
|
|
}
|
|
Q_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 (Q_INT32 krow = 0; krow < kh; ++krow) { // shift the cache to the left
|
|
Q_UINT8** d = pixelPtrCache + krow * kw;
|
|
// memmove( d, d + 1, (kw-1)*sizeof(Q_UINT8*));
|
|
for (int i = 0; i < (kw-1); i++) {
|
|
memcpy(d[i], d[i+1], cdepth);
|
|
}
|
|
}*/
|
|
Q_UINT8* firstincache = pixelPtrCache[0];
|
|
memmove(pixelPtrCache, pixelPtrCache + 1, (cacheSize - 1) * sizeof(Q_UINT8*) );
|
|
pixelPtrCache[cacheSize - 1] = firstincache;
|
|
if(col < xLastMinuskhw)
|
|
{
|
|
Q_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;
|
|
}
|
|
Q_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(QRect(x, y, w, h));
|
|
|
|
emit notifyProgressDone();
|
|
for (int i = 0; i < cacheSize; i++)
|
|
delete[] pixelPtrCache[i];
|
|
delete[] pixelPtrCache;
|
|
}
|