/* * Copyright (c) 2004 Michael Thaler filters * Copyright (c) 2005 Casper Boemann * Copyright (c) 2005 Boudewijn Rempt right angle rotators * * 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 "kis_debug_areas.h" #include "kis_paint_device.h" #include "kis_selection.h" #include "kis_transform_worker.h" #include "kis_progress_display_interface.h" #include "kis_iterators_pixel.h" #include "kis_filter_strategy.h" #include "kis_layer.h" #include "kis_painter.h" KisTransformWorker::KisTransformWorker(KisPaintDeviceSP dev, double xscale, double yscale, double xshear, double yshear, double rotation, TQ_INT32 xtranslate, TQ_INT32 ytranslate, KisProgressDisplayInterface *progress, KisFilterStrategy *filter, bool fixBorderAlpha) { m_dev= dev; m_xscale = xscale; m_yscale = yscale; m_xshear = xshear; m_yshear = yshear; m_rotation = rotation, m_xtranslate = xtranslate; m_ytranslate = ytranslate; m_progress = progress; m_filter = filter; m_fixBorderAlpha = fixBorderAlpha; } void KisTransformWorker::rotateNone(KisPaintDeviceSP src, KisPaintDeviceSP dst) { KisSelectionSP dstSelection; TQ_INT32 pixelSize = src->pixelSize(); TQRect r; KisColorSpace *cs = src->colorSpace(); if(src->hasSelection()) { r = src->selection()->selectedExactRect(); dstSelection = dst->selection(); } else { r = src->exactBounds(); dstSelection = new KisSelection(dst); // essentially a dummy to be deleted } KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), r.top(), r.width(), true); KisHLineIterator vit = dst->createHLineIterator(r.x(), r.top(), r.width(), true); KisHLineIterator dstSelIt = dstSelection->createHLineIterator(r.x(), r.top(), r.width(), true); for (TQ_INT32 i = 0; i < r.height(); ++i) { while (!hit.isDone()) { if (hit.isSelected()) { memcpy(vit.rawData(), hit.rawData(), pixelSize); // XXX: Should set alpha = alpha*(1-selectedness) cs->setAlpha(hit.rawData(), 0, 1); } *(dstSelIt.rawData()) = hit.selectedness(); ++hit; ++vit; ++dstSelIt; } hit.nextRow(); vit.nextRow(); dstSelIt.nextRow(); //progress info m_progressStep += r.width(); if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) { m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; emit notifyProgress(m_lastProgressReport); } if (m_cancelRequested) { break; } } } void KisTransformWorker::rotateRight90(KisPaintDeviceSP src, KisPaintDeviceSP dst) { KisSelectionSP dstSelection; TQ_INT32 pixelSize = src->pixelSize(); TQRect r; KisColorSpace *cs = src->colorSpace(); if(src->hasSelection()) { r = src->selection()->selectedExactRect(); dstSelection = dst->selection(); } else { r = src->exactBounds(); dstSelection = new KisSelection(dst); // essentially a dummy to be deleted } for (TQ_INT32 y = r.bottom(); y >= r.top(); --y) { KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), y, r.width(), true); KisVLineIterator vit = dst->createVLineIterator(-y, r.x(), r.width(), true); KisVLineIterator dstSelIt = dstSelection->createVLineIterator(-y, r.x(), r.width(), true); while (!hit.isDone()) { if (hit.isSelected()) { memcpy(vit.rawData(), hit.rawData(), pixelSize); // XXX: Should set alpha = alpha*(1-selectedness) cs->setAlpha(hit.rawData(), 0, 1); } *(dstSelIt.rawData()) = hit.selectedness(); ++hit; ++vit; ++dstSelIt; } //progress info m_progressStep += r.width(); if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) { m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; emit notifyProgress(m_lastProgressReport); } if (m_cancelRequested) { break; } } } void KisTransformWorker::rotateLeft90(KisPaintDeviceSP src, KisPaintDeviceSP dst) { kdDebug() << "rotateLeft90 called\n"; KisSelectionSP dstSelection; TQ_INT32 pixelSize = src->pixelSize(); TQRect r; KisColorSpace *cs = src->colorSpace(); if(src->hasSelection()) { r = src->selection()->selectedExactRect(); dstSelection = dst->selection(); } else { r = src->exactBounds(); dstSelection = new KisSelection(dst); // essentially a dummy to be deleted } TQ_INT32 x = 0; for (TQ_INT32 y = r.top(); y <= r.bottom(); ++y) { // Read the horizontal line from back to front, write onto the vertical column KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), y, r.width(), true); KisVLineIterator vit = dst->createVLineIterator(y, -r.x() - r.width(), r.width(), true); KisVLineIterator dstSelIt = dstSelection->createVLineIterator(y, -r.x() - r.width(), r.width(), true); hit += r.width() - 1; while (!vit.isDone()) { if (hit.isSelected()) { memcpy(vit.rawData(), hit.rawData(), pixelSize); // XXX: Should set alpha = alpha*(1-selectedness) cs->setAlpha(hit.rawData(), 0, 1); } *(dstSelIt.rawData()) = hit.selectedness(); --hit; ++vit; ++dstSelIt; } ++x; //progress info m_progressStep += r.width(); if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) { m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; emit notifyProgress(m_lastProgressReport); } if (m_cancelRequested) { break; } } } void KisTransformWorker::rotate180(KisPaintDeviceSP src, KisPaintDeviceSP dst) { kdDebug() << "Rotating 180\n"; KisSelectionSP dstSelection; TQ_INT32 pixelSize = src->pixelSize(); TQRect r; KisColorSpace *cs = src->colorSpace(); if(src->hasSelection()) { r = src->selection()->selectedExactRect(); dstSelection = dst->selection(); } else { r = src->exactBounds(); dstSelection = new KisSelection(dst); // essentially a dummy to be deleted } for (TQ_INT32 y = r.top(); y <= r.bottom(); ++y) { KisHLineIteratorPixel srcIt = src->createHLineIterator(r.x(), y, r.width(), true); KisHLineIterator dstIt = dst->createHLineIterator(-r.x() - r.width(), -y, r.width(), true); KisHLineIterator dstSelIt = dstSelection->createHLineIterator(-r.x() - r.width(), -y, r.width(), true); srcIt += r.width() - 1; while (!dstIt.isDone()) { if (srcIt.isSelected()) { memcpy(dstIt.rawData(), srcIt.rawData(), pixelSize); // XXX: Should set alpha = alpha*(1-selectedness) cs->setAlpha(srcIt.rawData(), 0, 1); } *(dstSelIt.rawData()) = srcIt.selectedness(); --srcIt; ++dstIt; ++dstSelIt; } //progress info m_progressStep += r.width(); if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) { m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; emit notifyProgress(m_lastProgressReport); } if (m_cancelRequested) { break; } } } template iter createIterator(KisPaintDevice *dev, TQ_INT32 start, TQ_INT32 lineNum, TQ_INT32 len); template <> KisHLineIteratorPixel createIterator (KisPaintDevice *dev, TQ_INT32 start, TQ_INT32 lineNum, TQ_INT32 len) { return dev->createHLineIterator(start, lineNum, len, true); } template <> KisVLineIteratorPixel createIterator (KisPaintDevice *dev, TQ_INT32 start, TQ_INT32 lineNum, TQ_INT32 len) { return dev->createVLineIterator(lineNum, start, len, true); } template void calcDimensions (KisPaintDevice *dev, TQ_INT32 &srcStart, TQ_INT32 &srcLen, TQ_INT32 &firstLine, TQ_INT32 &numLines); template <> void calcDimensions (KisPaintDevice *dev, TQ_INT32 &srcStart, TQ_INT32 &srcLen, TQ_INT32 &firstLine, TQ_INT32 &numLines) { if(dev->hasSelection()) { TQRect r = dev->selection()->selectedExactRect(); r.rect(&srcStart, &firstLine, &srcLen, &numLines); } else dev->exactBounds(srcStart, firstLine, srcLen, numLines); } template <> void calcDimensions (KisPaintDevice *dev, TQ_INT32 &srcStart, TQ_INT32 &srcLen, TQ_INT32 &firstLine, TQ_INT32 &numLines) { if(dev->hasSelection()) { TQRect r = dev->selection()->selectedExactRect(); r.rect(&firstLine, &srcStart, &numLines, &srcLen); } else dev->exactBounds(firstLine, srcStart, numLines, srcLen); } struct FilterValues { TQ_UINT8 numWeights; TQ_UINT8 *weight; ~FilterValues() {delete [] weight;} }; template void KisTransformWorker::transformPass(KisPaintDevice *src, KisPaintDevice *dst, double floatscale, double shear, TQ_INT32 dx, KisFilterStrategy *filterStrategy, bool fixBorderAlpha) { TQ_INT32 lineNum,srcStart,firstLine,srcLen,numLines; TQ_INT32 center, begin, end; /* filter calculation variables */ TQ_UINT8 *data; TQ_UINT8 pixelSize = src->pixelSize(); KisSelectionSP dstSelection; KisColorSpace * cs = src->colorSpace(); TQ_INT32 scale; TQ_INT32 scaleDenom; TQ_INT32 shearFracOffset; if(src->hasSelection()) dstSelection = dst->selection(); else dstSelection = new KisSelection(dst); // essentially a dummy to be deleted calcDimensions (src, srcStart, srcLen, firstLine, numLines); scale = int(floatscale*srcLen); scaleDenom = srcLen; if(scaleDenom == 0) return; TQ_INT32 support = filterStrategy->intSupport(); TQ_INT32 dstLen, dstStart; TQ_INT32 invfscale = 256; // handle magnification/minification if(abs(scale) < scaleDenom) { support *= scaleDenom; support /= scale; invfscale *= scale; invfscale /= scaleDenom; if(scale < 0) // handle mirroring { support = -support; invfscale = -invfscale; } } // handle mirroring if(scale < 0) dstLen = - scale; else dstLen = scale; // Calculate extra length (in each side) needed due to shear TQ_INT32 extraLen = (support+256)>>8 + 1; TQ_UINT8 *tmpLine = new TQ_UINT8[(srcLen +2*extraLen)* pixelSize]; TQ_CHECK_PTR(tmpLine); TQ_UINT8 *tmpSel = new TQ_UINT8[srcLen+2*extraLen]; TQ_CHECK_PTR(tmpSel); //allocate space for colors const TQ_UINT8 **colors = new const TQ_UINT8 *[2*support+1]; // Precalculate weights FilterValues *filterWeights = new FilterValues[256]; for(int center = 0; center<256; ++center) { TQ_INT32 begin = (255 + center - support)>>8; // takes ceiling by adding 255 TQ_INT32 span = ((center + support)>>8) - begin + 1; // takes floor to get end. Subtracts begin to get span TQ_INT32 t = (((begin<<8) - center) * invfscale)>>8; TQ_INT32 dt = invfscale; filterWeights[center].weight = new TQ_UINT8[span]; //printf("%d (",center); TQ_UINT32 sum=0; for(int num = 0; numintValueAt(t) * invfscale; tmpw >>=8; filterWeights[center].weight[num] = tmpw; //printf(" %d=%d,%d",t,filterWeights[center].weight[num],tmpw); t += dt; sum+=tmpw; } //printf(" )%d sum =%d",span,sum); if(sum!=255) { double fixfactor= 255.0/sum; sum=0; for(int num = 0; num(src, srcStart - extraLen, lineNum, srcLen+2*extraLen); TQ_INT32 i = 0; while(!srcIt.isDone()) { TQ_UINT8 *data; data = srcIt.rawData(); memcpy(&tmpLine[i*pixelSize], data, pixelSize); if(srcIt.isSelected()) { // XXX: Should set alpha = alpha*(1-selectedness) cs->setAlpha(data, 0, 1); tmpSel[i] = 255; } else { tmpSel[i] = 0; } ++srcIt; i++; } T dstIt = createIterator (dst, dstStart, lineNum, dstLen); T dstSelIt = createIterator (dstSelection, dstStart, lineNum, dstLen); i=0; while(!dstIt.isDone()) { if(scaleDenom<2500) center = ((i<<8) * scaleDenom) / scale; else { if(scaleDenom<46000) // real limit is actually 46340 pixels center = ((i * scaleDenom) / scale)<<8; else center = ((i<<8)/scale * scaleDenom) / scale; // XXX fails for sizes over 2^23 pixels src width } if(scale < 0) center += srcLen<<8; center += 128*scaleDenom/scale;//xxx doesn't work for scale<0; center += (extraLen<<8) + shearFracOffset; // find contributing pixels begin = (255 + center - support)>>8; // takes ceiling by adding 255 end = (center + support)>>8; // takes floor ////printf("sup=%d begin=%d end=%d",support,begin,end); TQ_UINT8 selectedness = tmpSel[center>>8]; if(selectedness) { int num=0; for(int srcpos = begin; srcpos <= end; ++srcpos) { colors[num] = &tmpLine[srcpos*pixelSize]; num++; } data = dstIt.rawData(); cs->mixColors(colors, filterWeights[center&255].weight, filterWeights[center&255].numWeights, data); //possibly fix the alpha of the border if user wants it if(fixBorderAlpha && (i==0 || i==dstLen-1)) cs->setAlpha(data, cs->getAlpha(&tmpLine[(center>>8)*pixelSize]), 1); data = dstSelIt.rawData(); *data = selectedness; } ++dstSelIt; ++dstIt; i++; } //progress info m_progressStep += dstLen; if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) { m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; emit notifyProgress(m_lastProgressReport); } if (m_cancelRequested) { break; } } delete [] colors; delete [] tmpLine; delete [] tmpSel; delete [] filterWeights; } bool KisTransformWorker::run() { //progress info m_cancelRequested = false; if(m_progress) m_progress->setSubject(this, true, true); m_progressTotalSteps = 0; m_progressStep = 0; TQRect r; if(m_dev->hasSelection()) r = m_dev->selection()->selectedExactRect(); else r = m_dev->exactBounds(); KisPaintDeviceSP tmpdev1 = new KisPaintDevice(m_dev->colorSpace(),"transform_tmpdev1");; KisPaintDeviceSP tmpdev2 = new KisPaintDevice(m_dev->colorSpace(),"transform_tmpdev2");; KisPaintDeviceSP tmpdev3 = new KisPaintDevice(m_dev->colorSpace(),"transform_tmpdev2");; KisPaintDeviceSP srcdev = m_dev; double xscale = m_xscale; double yscale = m_yscale; double xshear = m_xshear; double yshear = m_yshear; double rotation = m_rotation; TQ_INT32 xtranslate = m_xtranslate; TQ_INT32 ytranslate = m_ytranslate; if(rotation < 0.0) rotation = -fmod(-rotation, 2*M_PI) + 2*M_PI; else rotation = fmod(rotation, 2*M_PI); int rotQuadrant = int(rotation /(M_PI/2) + 0.5) & 3; // Figure out how we will do the initial right angle rotations double tmp; switch(rotQuadrant) { default: // just to shut up the compiler case 0: m_progressTotalSteps = 0; break; case 1: rotation -= M_PI/2; tmp = xscale; xscale=yscale; yscale=tmp; m_progressTotalSteps = r.width() * r.height(); break; case 2: rotation -= M_PI; m_progressTotalSteps = r.width() * r.height(); break; case 3: rotation -= -M_PI/2 + 2*M_PI; tmp = xscale; xscale = yscale; yscale = tmp; m_progressTotalSteps = r.width() * r.height(); break; } // Calculate some auxillary values yshear = sin(rotation); xshear = -tan(rotation/2); xtranslate -= int(xshear*ytranslate); // Calculate progress steps m_progressTotalSteps += int(yscale * r.width() * r.height()); m_progressTotalSteps += int(xscale * r.width() * (r.height() * yscale + r.width()*yshear)); m_lastProgressReport=0; // Now that we have everything in place it's time to do the actual right angle rotations switch(rotQuadrant) { default: // just to shut up the compiler case 0: break; case 1: rotateRight90(srcdev, tmpdev1); srcdev = tmpdev1; break; case 2: rotate180(srcdev, tmpdev1); srcdev = tmpdev1; break; case 3: rotateLeft90(srcdev, tmpdev1); srcdev = tmpdev1; break; } // Handle simple move case possibly with rotation of 90,180,270 if(rotation == 0.0 && xscale == 1.0 && yscale == 1.0) { if(rotQuadrant==0) { // Though not nessesay in the general case because we make several passes // We need to move (not just copy) the data to a temp dev so we can move them back rotateNone(srcdev, tmpdev1); srcdev = tmpdev1; } if(m_dev->hasSelection()) m_dev->selection()->clear(); srcdev->move(srcdev->getX() + xtranslate, srcdev->getY() + ytranslate); rotateNone(srcdev, m_dev); //progress info emit notifyProgressDone(); m_dev->emitSelectionChanged(); return m_cancelRequested; } if ( m_cancelRequested) { emit notifyProgressDone(); return false; } transformPass (srcdev, tmpdev2, xscale, yscale*xshear, 0, m_filter, m_fixBorderAlpha); if(m_dev->hasSelection()) m_dev->selection()->clear(); if ( m_cancelRequested) { emit notifyProgressDone(); return false; } // Now do the second pass transformPass (tmpdev2.data(), tmpdev3.data(), yscale, yshear, ytranslate, m_filter, m_fixBorderAlpha); if(m_dev->hasSelection()) m_dev->selection()->clear(); if ( m_cancelRequested) { emit notifyProgressDone(); return false; } if (xshear != 0.0) transformPass (tmpdev3, m_dev, 1.0, xshear, xtranslate, m_filter, m_fixBorderAlpha); else { // No need to filter again when we are only scaling tmpdev3->move(tmpdev3->getX() + xtranslate, tmpdev3->getY()); rotateNone(tmpdev3, m_dev); } if (m_dev->parentLayer()) { m_dev->parentLayer()->setDirty(); } //progress info emit notifyProgressDone(); m_dev->emitSelectionChanged(); return m_cancelRequested; }