diff --git a/core/libs/dimg/filters/transform/autocrop.cpp b/core/libs/dimg/filters/transform/autocrop.cpp index 88cc66aa5f..d9e391018a 100644 --- a/core/libs/dimg/filters/transform/autocrop.cpp +++ b/core/libs/dimg/filters/transform/autocrop.cpp @@ -1,1055 +1,1089 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-10-18 * Description : Auto Crop analyzer * Algorithm based on black point detection on * the basis of spiral traversal * * Copyright (C) 2013 by Sayantan Datta * Copyright (C) 2013-2020 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "autocrop.h" // C++ includes #include #include // Qt includes #include #include #include #include #include #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN AutoCrop::Private { public: explicit Private() { } QRect cropArea; }; AutoCrop::AutoCrop(DImg* const img, QObject* const parent) : DImgThreadedAnalyser(parent, QLatin1String("AutoCrop")), d(new Private) { setOriginalImage(*img); } AutoCrop::~AutoCrop() { delete d; } QRect AutoCrop::spiralClockwiseTraversal(const QImage& source, int topCrop, int bottomCrop) { int i, j, ni; if (topCrop == -1) { topCrop = 0; } if (bottomCrop == -1) { bottomCrop = source.height(); } QSize resultsize = QSize(source.width(), (source.height() - topCrop - (source.height() - bottomCrop))); QImage threshold = QImage(resultsize, QImage::Format_RGB888); for (i = topCrop, ni = 0 ; i < bottomCrop ; ++i, ++ni) { for (j = 0 ; j < source.width() ; ++j) { threshold.setPixel(j, ni, source.pixel(j, i)); } } QColor c; int limitcolumn = threshold.width(); int limitrow = threshold.height(); int centeri = ((limitrow/2)-1); int centerj = ((limitcolumn/2)-1); int startrighti = 0; int startrightj = 0; int endrighti = 0; int endrightj = 0; int startlefti = 0; int startleftj = 0; int endlefti = 0; int endleftj = 0; int startupi = 0; int startupj = 0; int endupi = 0; int endupj = 0; int startdowni = 0; int startdownj = 0; int enddowni = 0; int enddownj = 0; int travelright = 0; int travelleft = 0; int travelup = 0; int traveldown = 0; int rightmargin = 0; int leftmargin = 0; int bottommargin = 0; int topmargin = 0; int counter = 0; bool fixtopmargin = false; bool fixrightmargin = false; bool fixleftmargin = false; bool fixbottommargin = false; - //int count = limitcolumn + limitrow -1; +/* + int count = limitcolumn + limitrow -1; +*/ bool rightEdge = false; bool leftEdge = false; bool topEdge = false; bool bottomEdge = false; endupi = centeri; endupj = centerj; travelright = -1; traveldown = -1; travelleft = 0; travelup = 0; qCDebug(DIGIKAM_DIMG_LOG) << "Center pixel : " << centerj << " , " << centeri; while (true) { //qCDebug(DIGIKAM_DIMG_LOG) << "count = "<= threshold.width()) { qCDebug(DIGIKAM_DIMG_LOG) << "We cannot go right anymore"; fixrightmargin = true; rightmargin = limitcolumn - 1; rightEdge = true; } } break; } travelright += 2; if (fixrightmargin == true) + { travelright--; + } if (fixleftmargin == true) + { travelright--; + } //qCDebug(DIGIKAM_DIMG_LOG) << "TRAVELING RIGHT"; //qCDebug(DIGIKAM_DIMG_LOG) << "Endupi" << endupi; startrighti = endupi; startrightj = endupj; endrightj = startrightj + travelright; //qCDebug(DIGIKAM_DIMG_LOG) << "Moving Right EndRight = " << endrightj; if (endrightj >= limitcolumn) { qCDebug(DIGIKAM_DIMG_LOG) << "We have reached limitcolumn, i.e. width"; endrightj = limitcolumn - 1; fixrightmargin = true; rightmargin = limitcolumn - 1; counter++; travelright--; rightEdge = true; } i = startrighti; j = startrightj; (void)j; // Remove clang warnings. for (j = startrightj + 1 ; j <= endrightj ; ++j) { //qCDebug(DIGIKAM_DIMG_LOG) << "At pixel "<< j << " , " << i; c = QColor::fromRgb(threshold.pixel(j,i)); if (c == Qt::black) { // we have found an empty space + fixtopmargin = true; topmargin = i; endupi++; travelup--; break; } } endrighti = startrighti; break; } case 1: // traveling down { if (fixrightmargin == true) { if (fixbottommargin == false) { enddowni++; if (enddowni >= limitrow) { fixbottommargin = true; bottommargin = limitrow - 1; bottomEdge = true; } } //endrightj--; //qCDebug(DIGIKAM_DIMG_LOG) << "Traveling down : Case Skipped\n"; break; } traveldown += 2; - if (fixbottommargin==true) + if (fixbottommargin == true) + { traveldown--; + } - if (fixtopmargin==true) + if (fixtopmargin == true) + { traveldown--; + } startdowni = endrighti; startdownj = endrightj; enddowni = startdowni + traveldown; //qCDebug(DIGIKAM_DIMG_LOG) << "Moving Down EndDown = " << enddowni; if (enddowni >= limitrow) { qCDebug(DIGIKAM_DIMG_LOG) << "We have reached limitrow, i.e. Height"; enddowni = limitrow - 1; counter++; bottommargin = limitrow - 1; fixbottommargin = true; traveldown--; bottomEdge = true; } i = startdowni; (void)i; // Remove clang warning. j = startdownj; for (i = startdowni + 1 ; i <= enddowni ; ++i) { // qCDebug(DIGIKAM_DIMG_LOG) << "At pixel "<< j << " , " << i; c = QColor::fromRgb(threshold.pixel(j,i)); if (c == Qt::black) { //we have found an empty space + fixrightmargin = true; rightmargin = j; endrightj--; travelright--; break; } } enddownj = startdownj; break; } case 2 : //traveling left { if (fixbottommargin == true) { if (fixleftmargin == false) { endleftj--; if (endleftj < 0) { fixleftmargin = true; leftmargin = 0; leftEdge = true; } } break; } travelleft += 2; if (fixleftmargin == true) + { travelleft--; + } if (fixrightmargin == true) + { travelleft--; + } startlefti = enddowni; startleftj = enddownj; endleftj = startleftj - travelleft; //qCDebug(DIGIKAM_DIMG_LOG) << "Moving Left Endleft = " << endleftj; if (endleftj < 0) { qCDebug(DIGIKAM_DIMG_LOG) << "We have gone too left"; endleftj = 0; counter++; leftmargin = 0; fixleftmargin = true; travelleft--; leftEdge = true; } i = startlefti; j = startleftj; (void)j; // Remove clang warning. for (j = startleftj - 1 ; j >= endleftj ; --j) { //qCDebug(DIGIKAM_DIMG_LOG) << "At pixel "<< j << " , " << i; c = QColor::fromRgb(threshold.pixel(j,i)); if (c == Qt::black) { - //we have found an empty space + // we have found an empty space + fixbottommargin = true; bottommargin = i; enddowni--; traveldown--; break; } } endlefti = startlefti; break; } case 3: //traveling up { if (fixleftmargin == true) { if (fixtopmargin == false) { endupi--; endupj = leftmargin; if (endupi < 0) { fixtopmargin = true; topmargin = 0; topEdge = true; } } break; } travelup += 2; if (fixbottommargin == true) + { travelup--; + } if (fixtopmargin == true) + { travelup--; + } startupi = endlefti; startupj = endleftj; endupi = startupi - travelup; //qCDebug(DIGIKAM_DIMG_LOG) << "Moving Up Endup = " << endupi; if (endupi < 0) { qCDebug(DIGIKAM_DIMG_LOG) << "We have gone too right"; endupi = 0; topEdge = true; counter++; fixtopmargin = true; topmargin = 0; travelup--; } i = startupi; (void)i; // Remove clang warning. j = startupj; for (i = startupi - 1 ; i >= endupi ; --i) { //qCDebug(DIGIKAM_DIMG_LOG) << "At pixel "<< j << " , " << i; c = QColor::fromRgb(threshold.pixel(j, i)); if (c == Qt::black) { - //we have found an empty space + // we have found an empty space + fixleftmargin = true; leftmargin = j; endleftj++; travelleft--; break; } } endupj = startupj; break; } } counter++; - if (fixbottommargin == true && - fixtopmargin == true && - fixleftmargin == true && - fixrightmargin == true) + if ((fixbottommargin == true) && + (fixtopmargin == true) && + (fixleftmargin == true) && + (fixrightmargin == true)) break; } // qCDebug(DIGIKAM_DIMG_LOG) << "Count : " << count; qCDebug(DIGIKAM_DIMG_LOG) << "Endupi : " << endupi; qCDebug(DIGIKAM_DIMG_LOG) << "Endupj : " << endupj; qCDebug(DIGIKAM_DIMG_LOG) << "Endrighti : " << endrighti; qCDebug(DIGIKAM_DIMG_LOG) << "Endrightj : " << endrightj; qCDebug(DIGIKAM_DIMG_LOG) << "Enddowni : " << enddowni; qCDebug(DIGIKAM_DIMG_LOG) << "Enddownj : " << enddownj; qCDebug(DIGIKAM_DIMG_LOG) << "Endlefti : " << endlefti; qCDebug(DIGIKAM_DIMG_LOG) << "Endleftj : " << endleftj; qCDebug(DIGIKAM_DIMG_LOG) << "Done" << endl; qCDebug(DIGIKAM_DIMG_LOG) << "Left Margin : " << leftmargin; qCDebug(DIGIKAM_DIMG_LOG) << "Right Margin : " << rightmargin; qCDebug(DIGIKAM_DIMG_LOG) << "Top Margin : " << topmargin; qCDebug(DIGIKAM_DIMG_LOG) << "Bottom Margin : " << bottommargin; qCDebug(DIGIKAM_DIMG_LOG) << "Done" << endl; qCDebug(DIGIKAM_DIMG_LOG) << "Left Edge : " << leftEdge; qCDebug(DIGIKAM_DIMG_LOG) << "Right Edge : " << rightEdge; qCDebug(DIGIKAM_DIMG_LOG) << "Top Edge : " << topEdge; qCDebug(DIGIKAM_DIMG_LOG) << "Bottom Edge : " << bottomEdge; qCDebug(DIGIKAM_DIMG_LOG) << "Done" << endl; if (bottomEdge) { bottommargin++; } if (topEdge) { topmargin--; } if (leftEdge) { leftmargin--; } if (rightEdge) { rightmargin++; } //----------------------releasing images QPoint icp1; icp1.setX(leftmargin + 1); icp1.setY(topCrop + topmargin + 1); QPoint icp2; icp2.setX(rightmargin - 1); icp2.setY(topCrop + bottommargin - 1); QRect cropArea; cropArea.setTopLeft(icp1); cropArea.setBottomRight(icp2); return cropArea; } void AutoCrop::startAnalyse() { QImage img = m_orgImage.copyQImage(); int breakflag = 0; int topRow = -1; int topColumn = -1; int bottomRow = -1; int bottomColumn = -1; int leftRow = -1; int leftColumn = -1; int rightRow = -1; int rightColumn = -1; QColor c; int i,j; postProgress(5); //---------------------- Finding the outer boundaries of the image (i.e. with black portions) - /* This would be done in 4 steps + /** + * This would be done in 4 steps * 1. Search column wise: * (a) From the left to the right, this is to get the left boundary * (b) From the right to the left, this is to get the right boundary * 2. Search row wise : * (a) From the top to the bottom, this is to get the top boundary * (b) From the bottom to the top, this is to get the bottom boundary */ // 1(a) Traversing the image from top to bottom, left to right, to get left boundary breakflag = 0; int width = img.width(); int height = img.height(); for (i = 0 ; i < width ; ++i) { for (j = 0 ; j < height ; ++j) { c = QColor::fromRgb(img.pixel(i, j)); - if (c == Qt::black || !c.isValid()) + if ((c == Qt::black) || !c.isValid()) { // Nothing to do. } else { //we have found our pixel leftRow = j; leftColumn = i; breakflag = 1; break; } } if (breakflag == 1) + { break; + } } qCDebug(DIGIKAM_DIMG_LOG) << "Done Till step 1(a)"; postProgress(30); //1(b) Traversing the image from top to bottom, right to left, to get right boundary breakflag = 0; (void)breakflag; // Remove clang warnings. for (i = 0 ; i < width ; ++i) { for (j = 0 ; j < height ; ++j) { c = QColor::fromRgb(img.pixel(i, j)); - if (c == Qt::black || !c.isValid()) + if ((c == Qt::black) || !c.isValid()) { // Nothing to do. } else { //we have found our pixel rightRow = j; rightColumn = i; break; } } } qCDebug(DIGIKAM_DIMG_LOG) << "Done Till step 1(b)"; postProgress(50); // 2(a) Traversing the image left to right, top to down, to get top boundary breakflag = 0; for (i = 0 ; i < height ; ++i) { for (j = 0 ; j < width ; ++j) { c = QColor::fromRgb(img.pixel(j, i)); - if (c == Qt::black || !c.isValid()) + if ((c == Qt::black) || !c.isValid()) { // Nothing to do. } else { //we have found our pixel topRow = i; topColumn = j; breakflag = 1; break; } } if (breakflag == 1) + { break; + } } qCDebug(DIGIKAM_DIMG_LOG) << "Done Till step 2(a)"; postProgress(70); // 2(b) Traversing the image from left to right, bottom up, to get lower boundary breakflag = 0; for (i = height - 1 ; i >= 0 ; --i) { for (j = 0 ; j < width ; ++j) { c = QColor::fromRgb(img.pixel(j,i)); - if (c == Qt::black || !c.isValid()) + if ((c == Qt::black) || !c.isValid()) { // Nothing to do. } else { //we have found our pixel bottomRow = i; bottomColumn = j; breakflag = 1; break; } } if (breakflag == 1) + { break; + } } qCDebug(DIGIKAM_DIMG_LOG) << "Done Till step 2(b)"; postProgress(90); //------making the required output-------------------- QString outercropParameters; outercropParameters.append(QLatin1String("TopMost Pixel : ( ")); outercropParameters.append(QString::number(topRow)); outercropParameters.append(QLatin1String(", ")); outercropParameters.append(QString::number(topColumn)); outercropParameters.append(QLatin1String(")\nBottomMost Pixel : ( ")); outercropParameters.append(QString::number(bottomRow)); outercropParameters.append(QLatin1String(", ")); outercropParameters.append(QString::number(bottomColumn)); outercropParameters.append(QLatin1String(")\nLeftMost Pixel : ( ")); outercropParameters.append(QString::number(leftRow)); outercropParameters.append(QLatin1String(", ")); outercropParameters.append(QString::number(leftColumn)); outercropParameters.append(QLatin1String(")\nRightMost Pixel : ( ")); outercropParameters.append(QString::number(rightRow)); outercropParameters.append(QLatin1String(", ")); outercropParameters.append(QString::number(rightColumn)); outercropParameters.append(QLatin1String(")\nDONE")); qCDebug(DIGIKAM_DIMG_LOG) << outercropParameters; postProgress(91); QPoint p1; p1.setX(leftColumn); p1.setY(topRow); QPoint p2; p2.setX(rightColumn); p2.setY(bottomRow); QRect crop; crop.setTopLeft(p1); crop.setBottomRight(p2); // crop Image to outerCrop QImage result; QSize resultsize = QSize(crop.width(), crop.height()); result = QImage(resultsize, QImage::Format_RGB888); int ni, nj; qCDebug(DIGIKAM_DIMG_LOG) << "Outer Crop area:"; qCDebug(DIGIKAM_DIMG_LOG) << "From "<< crop.top() << " to " << crop.bottom() << " & " << crop.left() << " to " << crop.right(); for (i = crop.top(), ni = 0 ; i <= crop.bottom() ; ++i, ++ni) { for (j = crop.left(), nj = 0 ; j <= crop.right() ; ++j, ++nj) { result.setPixel(nj, ni, img.pixel(j, i)); } } //---------------------threshold the image QImage threshold = QImage(resultsize,QImage::Format_RGB888); int toggleflag1 = 0,toggleflag2 = 0; int whitepixelCount = 0; //-----initialize for (i = 0 ; i < result.height() ; ++i) { for (j = 0 ; j < result.width() ; ++j) { threshold.setPixel(j, i, Qt::black); } } //----------mark points on horizontal scan for (i = 0 ; i < result.height() ; ++i) { toggleflag1 = -1; toggleflag2 = -1; for (j = 0 ; j < result.width() ; ++j) { c = QColor::fromRgb(result.pixel(j, i)); if (c != Qt::black) { toggleflag1 = j; break; } } for (j = (result.width()-1) ; j >= 0 ; --j) { c = QColor::fromRgb(result.pixel(j, i)); if (c != Qt::black) { toggleflag2 = j; break; } } if (toggleflag1 >= 0) { for (j = toggleflag1 ; j <= toggleflag2 ; ++j) { threshold.setPixel(j, i, qRgb(255, 255, 255)); } } } //----------fill black points on vertical scan for (j = 0 ; j < result.width() ; ++j) { toggleflag1 = -1; toggleflag2 = -2; for (i = 0 ; i < result.height() ; ++i) { c = QColor::fromRgb(result.pixel(j, i)); if (c != Qt::black) { toggleflag1 = i; break; } } for (i = (result.height()-1) ; i >= 0 ; --i) { c = QColor::fromRgb(result.pixel(j, i)); if (c != Qt::black) { toggleflag2 = i; break; } } if (toggleflag1 >= 0) { for (i = 0 ; i < toggleflag1 ; ++i) { threshold.setPixel(j, i, qRgb(0, 0, 0)); } } if (toggleflag2 >= 0) { for (i = (toggleflag2+1) ; i < (result.height()) ; ++i) { threshold.setPixel(j, i, qRgb(0, 0, 0)); } } } // ---count of white pixel in threshold for (i = 0 ; i < threshold.height() ; ++i) { for (j = 0 ; j < threshold.width() ; ++j) { c = QColor::fromRgb(threshold.pixel(j, i)); if (c == Qt::white) { whitepixelCount++; } } } qCDebug(DIGIKAM_DIMG_LOG) << "White pixel count in thresholded image = " << whitepixelCount; qCDebug(DIGIKAM_DIMG_LOG) << "Thresholding Complete\n"; //---------------------inner crop QRect InrCrop = spiralClockwiseTraversal(threshold,-1,-1); QPoint icp1; icp1.setX(InrCrop.topLeft().x() + leftColumn); icp1.setY(InrCrop.topLeft().y() + topRow); QPoint icp2; icp2.setX(InrCrop.bottomRight().x() + leftColumn); icp2.setY(InrCrop.bottomRight().y() + topRow); QRect cropArea; cropArea.setTopLeft(icp1); cropArea.setBottomRight(icp2); qCDebug(DIGIKAM_DIMG_LOG) << "cropArea : "< (whitepixelCount / 1.43)) { d->cropArea.setTopLeft(icp1); d->cropArea.setBottomRight(icp2); qCDebug(DIGIKAM_DIMG_LOG) << "Inner Crop Area : " << d->cropArea; return; } else { //threshold.save("ThresholdedImage.jpg",0,100); qCDebug(DIGIKAM_DIMG_LOG) << "Area not adequate!"; qCDebug(DIGIKAM_DIMG_LOG) << "Extra Cropping Required"; // --- Step 2 -- Search between local minima qCDebug(DIGIKAM_DIMG_LOG) << "In local minima function"; // We need to find the maxima between the first two local minima from either side int* const blackpointCount = new int[threshold.width()]; int leftminima = 0; int rightminima = (threshold.width()-1); int topCropLine = 0; int bottomCropLine = threshold.height()-1; int temp; int temppos; int count; (void) bottomCropLine; // Remove clang warnings. // initialize black point count for (i = 0 ; i < threshold.width() ; ++i) { blackpointCount[i] = 0; } for (j = 0 ; j < threshold.width() ; ++j) { count = 0; for (i = 0 ; i < threshold.height(); ++i) { c = QColor::fromRgb(threshold.pixel(j, i)); if (c == Qt::black) { count++; } else { break; } } blackpointCount[j] = count; } qCDebug(DIGIKAM_DIMG_LOG) << "Top black element count Data Entry Completed"; // --- Searching left minima for (j = 1 ; j < threshold.width() ; ++j) { if ((blackpointCount[j] > blackpointCount[j-1]) && (blackpointCount[j] < (0.2*threshold.height()))) { leftminima = j-1; break; } } for (j = (threshold.width()-2) ; j >= 0 ; --j) { if ((blackpointCount[j] > blackpointCount[j+1]) && (blackpointCount[j] < (0.2*threshold.height()))) { rightminima = j+1; break; } } qCDebug(DIGIKAM_DIMG_LOG) << "Top Part right minima : " << rightminima << " Left Minima : " << leftminima; // --- find the maximum among these minima temp = blackpointCount[leftminima]; temppos = leftminima; for (j = leftminima+1 ; j <= rightminima ; ++j) { if (temp < blackpointCount[j]) { temp = blackpointCount[j]; temppos = j; } } topCropLine = temp; qCDebug(DIGIKAM_DIMG_LOG) << "Found top crop line"; qCDebug(DIGIKAM_DIMG_LOG) << "Found in column = " << temppos << "and the topCropLine is "<< topCropLine; qCDebug(DIGIKAM_DIMG_LOG) << "Searching for bottom crop line"; //-----For the bottom of the image // initialize black point count + for (i = 0 ; i < threshold.width() ; ++i) { blackpointCount[i] = 0; } for (j = 0 ; j < threshold.width() ; ++j) { count = 0; for (i = (threshold.height()-1) ; i >= 0; --i) { c = QColor::fromRgb(result.pixel(j, i)); if (c == Qt::black) { count++; } else { break; } } blackpointCount[j] = count; } qCDebug(DIGIKAM_DIMG_LOG) << "Bottom black element count Data Entry Completed"; // --- Searching left minima for (j = 1 ; j < threshold.width() ; ++j) { if ((blackpointCount[j] > blackpointCount[j-1]) && (blackpointCount[j] < (0.2*threshold.height()))) { leftminima = j-1; break; } } for (j = (threshold.width()-2) ; j >= 0 ; --j) { if ((blackpointCount[j] > blackpointCount[j+1]) && (blackpointCount[j] < (0.2*threshold.height()))) { rightminima = j+1; break; } } // --- find the maximum among these minima temp = blackpointCount[leftminima]; temppos = leftminima; for (j = leftminima+1 ; j <= rightminima ; ++j) { if (temp < blackpointCount[j]) { temp = blackpointCount[j]; temppos = j; } } bottomCropLine = temp; qCDebug(DIGIKAM_DIMG_LOG) << "Found top crop line"; qCDebug(DIGIKAM_DIMG_LOG) << "Found in column = " << temppos; QRect newCrop = spiralClockwiseTraversal(threshold, topCropLine, (threshold.height()-bottomCropLine)); if (newCrop != crop) { icp1.setX(newCrop.topLeft().x() + leftColumn); icp1.setY(newCrop.topLeft().y() + topRow); icp2.setX(newCrop.bottomRight().x() + leftColumn); icp2.setY(newCrop.bottomRight().y() + topRow); d->cropArea.setTopLeft(icp1); d->cropArea.setBottomRight(icp2); } delete [] blackpointCount; } qCDebug(DIGIKAM_DIMG_LOG) << "Inner Crop Area : " << cropArea; /* return(cropArea); resultsize = QSize (cropArea.width(), cropArea.height()); QImage ic = QImage(resultsize,img.format()); for (i = cropArea.top(), ni = 0 ; i <= cropArea.bottom() ; i++, ni++ ) { for (j = cropArea.left(), nj = 0 ; j <= cropArea.right() ; j++, nj++ ) { ic.setPixel(nj, ni, img.pixel(j, i)); } } qCDebug(DIGIKAM_DIMG_LOG) << "From "<cropArea; } QRect AutoCrop::autoInnerCrop() const { return d->cropArea; } } // namespace Digikam diff --git a/core/libs/dimg/filters/transform/autocrop.h b/core/libs/dimg/filters/transform/autocrop.h index 9d6ee4d67e..d3ac7ab8d4 100644 --- a/core/libs/dimg/filters/transform/autocrop.h +++ b/core/libs/dimg/filters/transform/autocrop.h @@ -1,79 +1,83 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-10-18 * Description : Auto Crop analyzer * * Copyright (C) 2013 by Sayantan Datta * Copyright (C) 2013-2020 by Gilles Caulier * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_AUTO_CROP_H #define DIGIKAM_AUTO_CROP_H // Qt includes #include #include #include // Local includes #include "digikam_export.h" #include "nrfilter.h" #include "dimg.h" #include "dimgthreadedanalyser.h" namespace Digikam { class DIGIKAM_EXPORT AutoCrop : public DImgThreadedAnalyser { public: - /** Standard constructor with image container to parse + /** + * Standard constructor with image container to parse */ explicit AutoCrop(DImg* const orgImage, QObject* const parent = nullptr); ~AutoCrop(); - /** Perform auto-crop analyze to find best inner crop. Use autoInnerCrop() - * to get computed area. + /** + * Perform auto-crop analyze to find best inner crop. Use autoInnerCrop() + * to get computed area. */ - void startAnalyse() override; + void startAnalyse() override; - /** Return inner crop area detected by startAnalyse(). + /** + * Return inner crop area detected by startAnalyse(). */ QRect autoInnerCrop() const; private: - /** Takes in a binary image and crops it on the basis of black point - * detection, spirally moving outwards. - * topCrop can be set to explicitly crop a upper portion of the image - * bottomCrop can be set to explicitly crop a bottom portion of the image + /** + * Takes in a binary image and crops it on the basis of black point + * detection, spirally moving outwards. + * topCrop can be set to explicitly crop a upper portion of the image + * bottomCrop can be set to explicitly crop a bottom portion of the image */ QRect spiralClockwiseTraversal(const QImage& source, int topCrop = -1, int bottomCrop = -1); private: class Private; Private* d; }; } // namespace Digikam #endif // DIGIKAM_AUTO_CROP_H diff --git a/core/libs/dimg/filters/transform/contentawarefilter.cpp b/core/libs/dimg/filters/transform/contentawarefilter.cpp index bf3039d16c..4595aec5c0 100644 --- a/core/libs/dimg/filters/transform/contentawarefilter.cpp +++ b/core/libs/dimg/filters/transform/contentawarefilter.cpp @@ -1,444 +1,460 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-02-01 * Description : Content aware resizer class. * * Copyright (C) 2009 by Julien Pontabry * Copyright (C) 2009-2020 by Gilles Caulier * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #include "contentawarefilter.h" // Liquid rescale library include // Pragma directives to reduce warnings from Liblqr header files. #if defined(Q_CC_GNU) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" #endif #include // Restore warnings #if defined(Q_CC_GNU) # pragma GCC diagnostic pop #endif // Qt includes #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { // Static methods. LqrRetVal s_carverProgressInit(const gchar* init_message); LqrRetVal s_carverProgressUpdate(gdouble percentage); LqrRetVal s_carverProgressEnd(const gchar* end_message); // Static members. -/** Resizement is decomposed in 2 stages: horizontal and vertical. +/** + * Resizement is decomposed in 2 stages: horizontal and vertical. */ bool s_stage = false; bool s_wResize = false; bool s_hResize = false; ContentAwareFilter* s_resiser = nullptr; static LqrEnergyFuncBuiltinType toLqrEnergy(ContentAwareContainer::EnergyFunction func) { switch (func) { case ContentAwareContainer::GradientNorm: default: return LQR_EF_GRAD_NORM; case ContentAwareContainer::SumOfAbsoluteValues: return LQR_EF_GRAD_SUMABS; case ContentAwareContainer::XAbsoluteValue: return LQR_EF_GRAD_XABS; case ContentAwareContainer::LumaGradientNorm: return LQR_EF_LUMA_GRAD_NORM; case ContentAwareContainer::LumaSumOfAbsoluteValues: return LQR_EF_LUMA_GRAD_SUMABS; case ContentAwareContainer::LumaXAbsoluteValue: return LQR_EF_LUMA_GRAD_XABS; } } static LqrResizeOrder toLqrOrder(Qt::Orientation direction) { switch (direction) { case Qt::Horizontal: default: return LQR_RES_ORDER_HOR; case Qt::Vertical: return LQR_RES_ORDER_VERT; } } // -------------------------------------------------------------------------------------- class Q_DECL_HIDDEN ContentAwareFilter::Private { public: explicit Private() + : carver(nullptr), + progress(nullptr) { - carver = nullptr; - progress = nullptr; } ContentAwareContainer settings; LqrCarver* carver; LqrProgress* progress; }; ContentAwareFilter::ContentAwareFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } ContentAwareFilter::ContentAwareFilter(DImg* const orgImage, QObject* const parent, const ContentAwareContainer& settings) : DImgThreadedFilter(orgImage, parent, QLatin1String("ContentAwareFilter")), d(new Private) { initFilter(); s_stage = false; s_resiser = this; d->settings = settings; d->carver = lqr_carver_new_ext(m_orgImage.bits(), m_orgImage.width(), m_orgImage.height(), 4, m_orgImage.sixteenBit() ? LQR_COLDEPTH_16I : LQR_COLDEPTH_8I); if (d->carver) { // Non null carver object operations // Ask Lqr library to preserve our picture + lqr_carver_set_preserve_input_image(d->carver); // Initialize the carver object + lqr_carver_init(d->carver, d->settings.step, d->settings.rigidity); // Create a progress object + d->progress = lqr_progress_new(); lqr_progress_set_init(d->progress, s_carverProgressInit); lqr_progress_set_update(d->progress, s_carverProgressUpdate); lqr_progress_set_end(d->progress, s_carverProgressEnd); lqr_carver_set_progress(d->carver, d->progress); lqr_carver_set_side_switch_frequency(d->carver, d->settings.side_switch_freq); // Set enlargement steps as suggested by Carlo Baldassi + lqr_carver_set_enl_step(d->carver, 1.5); // Choose a gradient function + lqr_carver_set_energy_function_builtin(d->carver, toLqrEnergy(d->settings.func)); // Choose the resize order + lqr_carver_set_resize_order(d->carver, toLqrOrder(d->settings.resize_order)); // Set a bias if any mask + if (!d->settings.mask.isNull()) { buildBias(d->settings.mask); } // Set skin tone mask if option is activated + if (d->settings.preserve_skin_tones) { buildSkinToneBias(); } } } ContentAwareFilter::~ContentAwareFilter() { cancelFilter(); if (d->carver) { lqr_carver_destroy(d->carver); } delete d; } QString ContentAwareFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Content-Aware Filter")); } void ContentAwareFilter::getEnergyImage() { if (!d->carver) { return; } int w = lqr_carver_get_width(d->carver); int h = lqr_carver_get_height(d->carver); guchar* buff = (guchar*) malloc(w * h * 3 * sizeof(guchar)); lqr_carver_get_energy_image(d->carver, buff, 1, LQR_COLDEPTH_8I, LQR_RGBA_IMAGE); } void ContentAwareFilter::filterImage() { if (!d->carver) { return; } uint x = 0; uint y = 0; uint w = 0; uint h = 0; s_wResize = (m_orgImage.width() == d->settings.width) ? false : true; s_hResize = (m_orgImage.height() == d->settings.height) ? false : true; // Liquid rescale + lqr_carver_resize(d->carver, d->settings.width, d->settings.height); if (!runningFlag()) { return; } // Create a new image + w = lqr_carver_get_width(d->carver); h = lqr_carver_get_height(d->carver); m_destImage = DImg(w, h, m_orgImage.sixteenBit()); // Write pixels in the DImg structure image + lqr_carver_scan_reset(d->carver); void* rgb = nullptr; uchar* rgbOut8 = nullptr; unsigned short* rgbOut16 = nullptr; if (m_orgImage.sixteenBit()) { while (runningFlag() && lqr_carver_scan_ext(d->carver, (gint*)&x, (gint*)&y, &rgb)) { rgbOut16 = (unsigned short*)rgb; m_destImage.setPixelColor(x, y, DColor(rgbOut16[2], rgbOut16[1], rgbOut16[0], 65535, true)); } } else { while (runningFlag() && lqr_carver_scan_ext(d->carver, (gint*)&x, (gint*)&y, &rgb)) { rgbOut8 = (uchar*)rgb; m_destImage.setPixelColor(x, y, DColor(rgbOut8[2], rgbOut8[1], rgbOut8[0], 255, false)); } } } void ContentAwareFilter::progressCallback(int progress) { if (progress % 5 == 0) { postProgress(progress); } //qCDebug(DIGIKAM_DIMG_LOG) << "Content Aware Resizing: " << progress << " %"; } void ContentAwareFilter::cancelFilter() { // Handle cancel operations with lqr library. + qCDebug(DIGIKAM_DIMG_LOG) << "Stop LibLqr computation..."; lqr_carver_cancel(d->carver); DImgThreadedFilter::cancelFilter(); } bool ContentAwareFilter::isSkinTone(const DColor& color) { // NOTE: color is previously converted to eight bits. + double R = color.red() / 255.0; double G = color.green() / 255.0; double B = color.blue() / 255.0; double S = R + G + B; - return(((B / G) < 1.249) && + return( + ((B / G) < 1.249) && ((S / 3.0 * R) > 0.696) && ((1.0 / 3.0 - B / S) > 0.014) && ((G / (3.0 * S)) < 0.108) ); } void ContentAwareFilter::buildSkinToneBias() { DColor c; - for (uint x = 0; x < m_orgImage.width(); ++x) + for (uint x = 0 ; x < m_orgImage.width() ; ++x) { - for (uint y = 0; y < m_orgImage.height(); ++y) + for (uint y = 0 ; y < m_orgImage.height() ; ++y) { c = m_orgImage.getPixelColor(x, y); c.convertToEightBit(); gdouble bias = 10000 * isSkinTone(c); lqr_carver_bias_add_xy(d->carver, bias, x, y); } } } void ContentAwareFilter::buildBias(const QImage& mask) { QColor pixColor; int r, g, b, a; - for (int x = 0; x < mask.width(); ++x) + for (int x = 0 ; x < mask.width() ; ++x) { - for (int y = 0; y < mask.height(); ++y) + for (int y = 0 ; y < mask.height() ; ++y) { pixColor = QColor::fromRgba(mask.pixel(x, y)); pixColor.getRgb(&r, &g, &b, &a); gdouble bias = 0.0; if (g == 255) { bias = 1000000.0; } if (r == 255) { bias = -1000000.0; } lqr_carver_bias_add_xy(d->carver, bias, x, y); } } } FilterAction ContentAwareFilter::filterAction() { bool isReproducible = d->settings.mask.isNull(); DefaultFilterAction action(isReproducible); action.addParameter(QLatin1String("height"), d->settings.height); action.addParameter(QLatin1String("preserve_skin_tones"), d->settings.preserve_skin_tones); action.addParameter(QLatin1String("rigidity"), d->settings.rigidity); action.addParameter(QLatin1String("side_switch_freq"), d->settings.side_switch_freq); action.addParameter(QLatin1String("step"), d->settings.step); action.addParameter(QLatin1String("width"), d->settings.width); action.addParameter(QLatin1String("func"), d->settings.func); action.addParameter(QLatin1String("resize_order"), d->settings.resize_order); return std::move(action); } void ContentAwareFilter::readParameters(const FilterAction& action) { d->settings.height = action.parameter(QLatin1String("height")).toUInt(); d->settings.preserve_skin_tones = action.parameter(QLatin1String("preserve_skin_tones")).toBool(); d->settings.rigidity = action.parameter(QLatin1String("rigidity")).toDouble(); d->settings.side_switch_freq = action.parameter(QLatin1String("side_switch_freq")).toInt(); d->settings.step = action.parameter(QLatin1String("step")).toInt(); d->settings.width = action.parameter(QLatin1String("width")).toUInt(); d->settings.func = (ContentAwareContainer::EnergyFunction)action.parameter(QLatin1String("func")).toInt(); d->settings.resize_order = (Qt::Orientation)action.parameter(QLatin1String("resize_order")).toInt(); } // ------------------------------------------------------------------------------------ // Static methods. LqrRetVal s_carverProgressInit(const gchar* /*init_message*/) { if (!s_stage) { s_resiser->progressCallback(0); } else { s_resiser->progressCallback(50); } return LQR_OK; } LqrRetVal s_carverProgressUpdate(gdouble percentage) { int progress; if (!s_stage) { if (!s_wResize || !s_hResize) { progress = (int)(percentage * 100.0); } else { progress = (int)(percentage * 50.0); } } else { progress = (int)(50.0 + percentage * 50.0); } s_resiser->progressCallback(progress); + return LQR_OK; } LqrRetVal s_carverProgressEnd(const gchar* /*end_message*/) { if (!s_stage) { if (!s_wResize || !s_hResize) { s_resiser->progressCallback(100); } else { s_resiser->progressCallback(50); } s_stage = true; } else { s_resiser->progressCallback(100); } return LQR_OK; } } // namespace Digikam diff --git a/core/libs/dimg/filters/transform/contentawarefilter.h b/core/libs/dimg/filters/transform/contentawarefilter.h index 2aadf602c0..2424f79bf4 100644 --- a/core/libs/dimg/filters/transform/contentawarefilter.h +++ b/core/libs/dimg/filters/transform/contentawarefilter.h @@ -1,148 +1,152 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-02-01 * Description : Content aware resizer class. * * Copyright (C) 2009 by Julien Pontabry * Copyright (C) 2009-2020 by Gilles Caulier * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_CONTENT_AWARE_FILTER_H #define DIGIKAM_CONTENT_AWARE_FILTER_H // Qt includes #include // Local includes #include "digikam_config.h" #include "dcolor.h" #include "digikam_export.h" #include "dimgthreadedfilter.h" namespace Digikam { class DIGIKAM_EXPORT ContentAwareContainer { public: enum EnergyFunction { GradientNorm = 0, SumOfAbsoluteValues, XAbsoluteValue, LumaGradientNorm, LumaSumOfAbsoluteValues, LumaXAbsoluteValue }; +public: + ContentAwareContainer() + : preserve_skin_tones(false), + width(0), + height(0), + step(1), + side_switch_freq(4), + rigidity(0.0), + func(GradientNorm), + resize_order(Qt::Horizontal) { - preserve_skin_tones = false; - width = 0; - height = 0; - step = 1; - side_switch_freq = 4; - rigidity = 0.0; - func = GradientNorm; - resize_order = Qt::Horizontal; }; ~ContentAwareContainer() { }; public: bool preserve_skin_tones; uint width; uint height; int step; int side_switch_freq; double rigidity; QImage mask; EnergyFunction func; Qt::Orientation resize_order; }; // ----------------------------------------------------------------------------------------- class DIGIKAM_EXPORT ContentAwareFilter : public DImgThreadedFilter { public: explicit ContentAwareFilter(QObject* const parent = nullptr); - explicit ContentAwareFilter(DImg* const orgImage, QObject* const parent = nullptr, const ContentAwareContainer& settings = ContentAwareContainer()); + explicit ContentAwareFilter(DImg* const orgImage, + QObject* const parent = nullptr, + const ContentAwareContainer& settings = ContentAwareContainer()); ~ContentAwareFilter(); void progressCallback(int progress); static QString FilterIdentifier() { return QLatin1String("digikam:ContentAwareFilter"); } static QString DisplayableName(); static QList SupportedVersions() { return QList() << 1; } static int CurrentVersion() { return 1; } - virtual QString filterIdentifier() const override + virtual QString filterIdentifier() const override { return FilterIdentifier(); } - virtual FilterAction filterAction() override; - void readParameters(const FilterAction& action) override; + virtual FilterAction filterAction() override; + void readParameters(const FilterAction& action) override; private: - void cancelFilter() override; - void filterImage() override; + void cancelFilter() override; + void filterImage() override; void buildBias(const QImage& mask); void buildSkinToneBias(); inline bool isSkinTone(const DColor& c); void getEnergyImage(); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_CONTENT_AWARE_FILTER_H diff --git a/core/libs/dimg/filters/transform/freerotationfilter.cpp b/core/libs/dimg/filters/transform/freerotationfilter.cpp index 909a3c3d94..144623f98b 100644 --- a/core/libs/dimg/filters/transform/freerotationfilter.cpp +++ b/core/libs/dimg/filters/transform/freerotationfilter.cpp @@ -1,435 +1,448 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-07-18 * Description : Free rotation threaded image filter. * * Copyright (C) 2004-2020 by Gilles Caulier * Copyright (C) 2009-2010 by Andi Clemens * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #include "freerotationfilter.h" // C++ includes #include #include // KDE includes #include // Local includes #include "dimg.h" #include "dpixelsaliasfilter.h" #include "digikam_globals.h" namespace Digikam { class Q_DECL_HIDDEN FreeRotationFilter::Private { public: explicit Private() { } FreeRotationContainer settings; }; FreeRotationFilter::FreeRotationFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } FreeRotationFilter::FreeRotationFilter(DImg* const orgImage, QObject* const parent, const FreeRotationContainer& settings) : DImgThreadedFilter(orgImage, parent, QLatin1String("FreeRotation")), d(new Private) { d->settings = settings; initFilter(); } FreeRotationFilter::~FreeRotationFilter() { cancelFilter(); delete d; } QString FreeRotationFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Free Rotation")); } QSize FreeRotationFilter::getNewSize() const { return d->settings.newSize; } double FreeRotationFilter::calculateAngle(int x1, int y1, int x2, int y2) { QPoint p1(x1, y1); QPoint p2(x2, y2); return calculateAngle(p1, p2); } double FreeRotationFilter::calculateAngle(const QPoint& p1, const QPoint& p2) { // check for invalid points. This should have been handled by the calling method, // but we want to be really sure here - if (p1.x() < 0 || - p1.y() < 0 || - p2.x() < 0 || - p2.y() < 0) + if ((p1.x() < 0) || + (p1.y() < 0) || + (p2.x() < 0) || + (p2.y() < 0)) { return 0.0; } // check if points are equal + if (p1 == p2) { return 0.0; } // if y() is equal, no angle needs to be calculated + if (p1.y() == p2.y()) { return 0.0; } // if x() is equal, angle equals 90° + if (p1.x() == p2.x()) { return 90.0; } // do we rotate to the left (counter clock wise)? + bool ccw = ((p1.x() < p2.x()) && (p2.y() > p1.y())) || ((p1.x() > p2.x()) && (p2.y() < p1.y())); // calculate the angle + double ly = fabs((double)p2.y() - p1.y()); double lx = fabs((double)p2.x() - p1.x()); double angle = atan2(ly, lx) * 180.0 / M_PI; angle = ccw ? -angle : angle; return angle; } void FreeRotationFilter::filterImage() { int progress; - int w, h, nw, nh, j, i = 0; + int w, h, nw, nh, j, i = 0; int nNewHeight, nNewWidth; int nhdx, nhdy, nhsx, nhsy; double lfSin, lfCos, lfx, lfy; - int nWidth = m_orgImage.width(); - int nHeight = m_orgImage.height(); + int nWidth = m_orgImage.width(); + int nHeight = m_orgImage.height(); uchar* pBits = m_orgImage.bits(); unsigned short* pBits16 = reinterpret_cast(m_orgImage.bits()); // first of all, we need to calculate the sin and cos of the given angle - lfSin = sin(d->settings.angle * -DEG2RAD); - lfCos = cos(d->settings.angle * -DEG2RAD); + lfSin = sin(d->settings.angle * -DEG2RAD); + lfCos = cos(d->settings.angle * -DEG2RAD); // now, we have to calc the new size for the destination image if ((lfSin * lfCos) < 0) { nNewWidth = lround(fabs(nWidth * lfCos - nHeight * lfSin)); nNewHeight = lround(fabs(nWidth * lfSin - nHeight * lfCos)); } else { nNewWidth = lround(fabs(nWidth * lfCos + nHeight * lfSin)); nNewHeight = lround(fabs(nWidth * lfSin + nHeight * lfCos)); } // getting the destination's center position nhdx = nNewWidth / 2; nhdy = nNewHeight / 2; // getting the source's center position nhsx = nWidth / 2; nhsy = nHeight / 2; // now, we have to alloc a new image bool sixteenBit = m_orgImage.sixteenBit(); m_destImage = DImg(nNewWidth, nNewHeight, sixteenBit, m_orgImage.hasAlpha()); if (m_destImage.isNull()) { return; } m_destImage.fill(DColor(d->settings.backgroundColor.rgb(), sixteenBit)); uchar* pResBits = m_destImage.bits(); unsigned short* pResBits16 = reinterpret_cast(m_destImage.bits()); DPixelsAliasFilter alias; // main loop - for (h = 0; runningFlag() && (h < nNewHeight); ++h) + for (h = 0 ; runningFlag() && (h < nNewHeight) ; ++h) { nh = h - nhdy; - for (w = 0; runningFlag() && (w < nNewWidth); ++w) + for (w = 0 ; runningFlag() && (w < nNewWidth) ; ++w) { nw = w - nhdx; i = setPosition(nNewWidth, w, h); lfx = (double)nw * lfCos - (double)nh * lfSin + nhsx; lfy = (double)nw * lfSin + (double)nh * lfCos + nhsy; if (isInside(nWidth, nHeight, (int)lfx, (int)lfy)) { if (d->settings.antiAlias) { if (!sixteenBit) + { alias.pixelAntiAliasing(pBits, nWidth, nHeight, lfx, lfy, &pResBits[i + 3], &pResBits[i + 2], &pResBits[i + 1], &pResBits[i]); + } else + { alias.pixelAntiAliasing16(pBits16, nWidth, nHeight, lfx, lfy, &pResBits16[i + 3], &pResBits16[i + 2], &pResBits16[i + 1], &pResBits16[i]); + } } else { j = setPosition(nWidth, (int)lfx, (int)lfy); for (int p = 0 ; p < 4 ; ++p) { if (!sixteenBit) { pResBits[i] = pBits[j]; } else { pResBits16[i] = pBits16[j]; } ++i; ++j; } } } } // Update the progress bar in dialog. + progress = (int)(((double) h * 100.0) / nNewHeight); - if (progress % 5 == 0) + if ((progress % 5) == 0) { postProgress(progress); } } // Compute the rotated destination image size using original image dimensions. + int W, H; double absAngle = fabs(d->settings.angle); // stop here when no angle was set + if (absAngle == 0.0) { return; } if (absAngle < 90.0) { W = (int)(d->settings.orgW * cos(absAngle * DEG2RAD) + d->settings.orgH * sin(absAngle * DEG2RAD)); H = (int)(d->settings.orgH * cos(absAngle * DEG2RAD) + d->settings.orgW * sin(absAngle * DEG2RAD)); } else { H = (int)(d->settings.orgW * cos((absAngle - 90.0) * DEG2RAD) + d->settings.orgH * sin((absAngle - 90.0) * DEG2RAD)); W = (int)(d->settings.orgH * cos((absAngle - 90.0) * DEG2RAD) + d->settings.orgW * sin((absAngle - 90.0) * DEG2RAD)); } // Auto-cropping destination image without black holes around. + QRect autoCrop; switch (d->settings.autoCrop) { case FreeRotationContainer::WidestArea: { // 'Widest Area' method (by Renchi Raju). autoCrop.setX((int)(nHeight * sin(absAngle * DEG2RAD))); autoCrop.setY((int)(nWidth * sin(absAngle * DEG2RAD))); autoCrop.setWidth((int)(nNewWidth - 2 * nHeight * sin(absAngle * DEG2RAD))); autoCrop.setHeight((int)(nNewHeight - 2 * nWidth * sin(absAngle * DEG2RAD))); if (!autoCrop.isValid()) { m_destImage = DImg(m_orgImage.width(), m_orgImage.height(), m_orgImage.sixteenBit(), m_orgImage.hasAlpha()); m_destImage.fill(DColor(d->settings.backgroundColor.rgb(), sixteenBit)); d->settings.newSize = QSize(); } else { m_destImage = m_destImage.copy(autoCrop); d->settings.newSize.setWidth((int)(W - 2 * d->settings.orgH * sin(absAngle * DEG2RAD))); d->settings.newSize.setHeight((int)(H - 2 * d->settings.orgW * sin(absAngle * DEG2RAD))); } break; } case FreeRotationContainer::LargestArea: { // 'Largest Area' method (by Gerhard Kulzer). float gamma = 0.0f; if (nHeight > nWidth) { gamma = atan((float) nWidth / (float) nHeight); if (absAngle < 90.0) { autoCrop.setHeight((int)((float) nWidth / cos(absAngle * DEG2RAD) / (tan(gamma) + tan(absAngle * DEG2RAD)))); autoCrop.setWidth((int)((float) autoCrop.height() * tan(gamma))); } else { autoCrop.setWidth((int)((float) nWidth / cos((absAngle - 90.0) * DEG2RAD) / (tan(gamma) + tan((absAngle - 90.0) * DEG2RAD)))); autoCrop.setHeight((int)((float) autoCrop.width() * tan(gamma))); } } else { gamma = atan((float) nHeight / (float) nWidth); if (absAngle < 90.0) { autoCrop.setWidth((int)((float) nHeight / cos(absAngle * DEG2RAD) / (tan(gamma) + tan(absAngle * DEG2RAD)))); autoCrop.setHeight((int)((float) autoCrop.width() * tan(gamma))); } else { autoCrop.setHeight((int)((float) nHeight / cos((absAngle - 90.0) * DEG2RAD) / (tan(gamma) + tan((absAngle - 90.0) * DEG2RAD)))); autoCrop.setWidth((int)((float) autoCrop.height() * tan(gamma))); } } autoCrop.moveCenter(QPoint(nNewWidth / 2, nNewHeight / 2)); if (!autoCrop.isValid()) { m_destImage = DImg(m_orgImage.width(), m_orgImage.height(), m_orgImage.sixteenBit(), m_orgImage.hasAlpha()); m_destImage.fill(DColor(d->settings.backgroundColor.rgb(), sixteenBit)); d->settings.newSize = QSize(); } else { m_destImage = m_destImage.copy(autoCrop); gamma = atan((float) d->settings.orgH / (float) d->settings.orgW); if (absAngle < 90.0) { d->settings.newSize.setWidth((int)((float) d->settings.orgH / cos(absAngle * DEG2RAD) / (tan(gamma) + tan(absAngle * DEG2RAD)))); d->settings.newSize.setHeight((int)((float) d->settings.newSize.width() * tan(gamma))); } else { d->settings.newSize.setHeight((int)((float) d->settings.orgH / cos((absAngle - 90.0) * DEG2RAD) / (tan(gamma) + tan((absAngle - 90.0) * DEG2RAD)))); d->settings.newSize.setWidth((int)((float) d->settings.newSize.height() * tan(gamma))); } } break; } default: // No auto cropping. { d->settings.newSize.setWidth(W); d->settings.newSize.setHeight(H); break; } } } int FreeRotationFilter::setPosition(int Width, int X, int Y) { return (Y * Width * 4 + 4 * X); } bool FreeRotationFilter::isInside(int Width, int Height, int X, int Y) { bool bIsWOk = ((X < 0) ? false : (X >= Width) ? false : true); bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true); return (bIsWOk && bIsHOk); } FilterAction FreeRotationFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("angle"), d->settings.angle); action.addParameter(QLatin1String("antiAlias"), d->settings.antiAlias); action.addParameter(QLatin1String("autoCrop"), d->settings.autoCrop); action.addParameter(QLatin1String("newSize"), d->settings.newSize); action.addParameter(QLatin1String("orgH"), d->settings.orgH); action.addParameter(QLatin1String("orgW"), d->settings.orgW); action.addParameter(QLatin1String("backgroundColorR"), d->settings.backgroundColor.red()); action.addParameter(QLatin1String("backgroundColorG"), d->settings.backgroundColor.green()); action.addParameter(QLatin1String("backgroundColorB"), d->settings.backgroundColor.blue()); action.addParameter(QLatin1String("backgroundColorA"), d->settings.backgroundColor.alpha()); return action; } void FreeRotationFilter::readParameters(const FilterAction& action) { d->settings.angle = action.parameter(QLatin1String("angle")).toDouble(); d->settings.antiAlias = action.parameter(QLatin1String("antiAlias")).toBool(); d->settings.autoCrop = action.parameter(QLatin1String("autoCrop")).toInt(); d->settings.newSize = action.parameter(QLatin1String("newSize")).toSize(); d->settings.orgH = action.parameter(QLatin1String("orgH")).toInt(); d->settings.orgW = action.parameter(QLatin1String("orgW")).toInt(); d->settings.backgroundColor.setRed(action.parameter(QLatin1String("backgroundColorR")).toInt()); d->settings.backgroundColor.setGreen(action.parameter(QLatin1String("backgroundColorG")).toInt()); d->settings.backgroundColor.setBlue(action.parameter(QLatin1String("backgroundColorB")).toInt()); d->settings.backgroundColor.setAlpha(action.parameter(QLatin1String("backgroundColorA")).toInt()); } } // namespace Digikam diff --git a/core/libs/dimg/filters/transform/freerotationfilter.h b/core/libs/dimg/filters/transform/freerotationfilter.h index 1680670a2e..637b3f1efd 100644 --- a/core/libs/dimg/filters/transform/freerotationfilter.h +++ b/core/libs/dimg/filters/transform/freerotationfilter.h @@ -1,139 +1,141 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-07-18 * Description : Free rotation threaded image filter. * * Copyright (C) 2004-2020 by Gilles Caulier * Copyright (C) 2009-2010 by Andi Clemens * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_FREE_ROTATION_FILTER_H #define DIGIKAM_FREE_ROTATION_FILTER_H // Qt includes #include #include // Local includes #include "digikam_export.h" #include "dimgthreadedfilter.h" namespace Digikam { class DIGIKAM_EXPORT FreeRotationContainer { public: enum AutoCropTypes { NoAutoCrop = 0, WidestArea, LargestArea }; public: FreeRotationContainer() + : antiAlias(true), + autoCrop(NoAutoCrop), + orgW(0), + orgH(0), + angle(0.0), + backgroundColor(Qt::black) { - angle = 0.0; - antiAlias = true; - autoCrop = NoAutoCrop; - backgroundColor = Qt::black; - orgW = 0; - orgH = 0; }; - ~FreeRotationContainer() {}; + ~FreeRotationContainer() + { + }; public: bool antiAlias; int autoCrop; int orgW; int orgH; double angle; QSize newSize; QColor backgroundColor; }; // ----------------------------------------------------------------------------------------- class DIGIKAM_EXPORT FreeRotationFilter : public DImgThreadedFilter { public: explicit FreeRotationFilter(QObject* const parent = nullptr); explicit FreeRotationFilter(DImg* const orgImage, QObject* const parent=nullptr, const FreeRotationContainer& settings=FreeRotationContainer()); virtual ~FreeRotationFilter(); QSize getNewSize() const; static double calculateAngle(int x1, int y1, int x2, int y2); static double calculateAngle(const QPoint& p1, const QPoint& p2); static QString FilterIdentifier() { return QLatin1String("digikam:FreeRotationFilter"); } static QString DisplayableName(); static QList SupportedVersions() { return QList() << 1; } static int CurrentVersion() { return 1; } - virtual QString filterIdentifier() const override + virtual QString filterIdentifier() const override { return FilterIdentifier(); } - virtual FilterAction filterAction() override; - void readParameters(const FilterAction& action) override; + virtual FilterAction filterAction() override; + void readParameters(const FilterAction& action) override; private: - void filterImage() override; + void filterImage() override; inline int setPosition (int Width, int X, int Y); inline bool isInside (int Width, int Height, int X, int Y); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_FREE_ROTATION_FILTER_H diff --git a/core/libs/dimg/filters/transform/freerotationsettings.cpp b/core/libs/dimg/filters/transform/freerotationsettings.cpp index 3cfd19e3fe..eae003adeb 100644 --- a/core/libs/dimg/filters/transform/freerotationsettings.cpp +++ b/core/libs/dimg/filters/transform/freerotationsettings.cpp @@ -1,207 +1,210 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-16 * Description : Free rotation settings view. * * Copyright (C) 2010-2020 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "freerotationsettings.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dexpanderbox.h" #include "dnuminput.h" #include "digikam_debug.h" #include "dcombobox.h" namespace Digikam { class Q_DECL_HIDDEN FreeRotationSettings::Private { public: explicit Private() : antialiasInput(nullptr), angleInput(nullptr), fineAngleInput(nullptr), autoCropCB(nullptr) - {} + { + } static const QString configAutoCropTypeEntry; static const QString configAntiAliasingEntry; public: QCheckBox* antialiasInput; DIntNumInput* angleInput; DDoubleNumInput* fineAngleInput; DComboBox* autoCropCB; }; const QString FreeRotationSettings::Private::configAutoCropTypeEntry(QLatin1String("Auto Crop Type")); const QString FreeRotationSettings::Private::configAntiAliasingEntry(QLatin1String("Anti Aliasing")); // -------------------------------------------------------- FreeRotationSettings::FreeRotationSettings(QWidget* const parent) : QWidget(parent), d(new Private) { - const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); + const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QGridLayout* const grid = new QGridLayout(this); // -------------------------------------------------------- QLabel* const label3 = new QLabel(i18n("Main angle:")); d->angleInput = new DIntNumInput; d->angleInput->setRange(-180, 180, 1); d->angleInput->setDefaultValue(0); d->angleInput->setWhatsThis(i18n("An angle in degrees by which to rotate the image. " "A positive angle rotates the image clockwise; " "a negative angle rotates it counter-clockwise.")); QLabel* const label4 = new QLabel(i18n("Fine angle:")); d->fineAngleInput = new DDoubleNumInput; d->fineAngleInput->setRange(-1.0, 1.0, 0.01); d->fineAngleInput->setDefaultValue(0); d->fineAngleInput->setWhatsThis(i18n("This value in degrees will be added to main angle value " "to set fine target angle.")); d->antialiasInput = new QCheckBox(i18n("Anti-Aliasing")); d->antialiasInput->setWhatsThis(i18n("Enable this option to apply the anti-aliasing filter " "to the rotated image. " "In order to smooth the target image, it will be blurred a little.")); QLabel* const label5 = new QLabel(i18n("Auto-crop:")); d->autoCropCB = new DComboBox; d->autoCropCB->addItem(i18nc("no autocrop", "None")); d->autoCropCB->addItem(i18n("Widest Area")); d->autoCropCB->addItem(i18n("Largest Area")); d->autoCropCB->setDefaultIndex(FreeRotationContainer::NoAutoCrop); d->autoCropCB->setWhatsThis(i18n("Select the method to process image auto-cropping " "to remove black frames around a rotated image here.")); // ------------------------------------------------------------- grid->addWidget(label3, 0, 0, 1, 1); grid->addWidget(d->angleInput, 1, 0, 1, 2); grid->addWidget(label4, 2, 0, 1, 1); grid->addWidget(d->fineAngleInput, 3, 0, 1, 2); grid->addWidget(d->antialiasInput, 4, 0, 1, -1); grid->addWidget(label5, 5, 0, 1, 1); grid->addWidget(d->autoCropCB, 5, 1, 1, 1); grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(spacing); // ------------------------------------------------------------- connect(d->antialiasInput, SIGNAL(toggled(bool)), this, SIGNAL(signalSettingsChanged())); connect(d->autoCropCB, SIGNAL(activated(int)), this, SIGNAL(signalSettingsChanged())); connect(d->angleInput, SIGNAL(valueChanged(int)), this, SIGNAL(signalSettingsChanged())); connect(d->fineAngleInput, SIGNAL(valueChanged(double)), this, SIGNAL(signalSettingsChanged())); } FreeRotationSettings::~FreeRotationSettings() { delete d; } FreeRotationContainer FreeRotationSettings::settings() const { FreeRotationContainer prm; prm.angle = (double)d->angleInput->value() + d->fineAngleInput->value(); prm.antiAlias = d->antialiasInput->isChecked(); prm.autoCrop = d->autoCropCB->currentIndex(); + return prm; } void FreeRotationSettings::setSettings(const FreeRotationContainer& settings) { blockSignals(true); d->angleInput->setValue((int)(settings.angle)); d->fineAngleInput->setValue(settings.angle - (double)d->angleInput->value()); d->antialiasInput->setChecked(settings.antiAlias); d->autoCropCB->setCurrentIndex(settings.autoCrop); blockSignals(false); } void FreeRotationSettings::resetToDefault() { blockSignals(true); d->angleInput->slotReset(); d->fineAngleInput->slotReset(); d->antialiasInput->setChecked(true); d->autoCropCB->slotReset(); blockSignals(false); } FreeRotationContainer FreeRotationSettings::defaultSettings() const { FreeRotationContainer prm; prm.angle = d->angleInput->defaultValue(); prm.antiAlias = true; prm.autoCrop = d->autoCropCB->defaultIndex(); + return prm; } void FreeRotationSettings::readSettings(KConfigGroup& group) { d->autoCropCB->setCurrentIndex(group.readEntry(d->configAutoCropTypeEntry, d->autoCropCB->defaultIndex())); d->antialiasInput->setChecked(group.readEntry(d->configAntiAliasingEntry, true)); d->angleInput->slotReset(); d->fineAngleInput->slotReset(); } void FreeRotationSettings::writeSettings(KConfigGroup& group) { FreeRotationContainer prm = settings(); group.writeEntry(d->configAutoCropTypeEntry, d->autoCropCB->currentIndex()); group.writeEntry(d->configAntiAliasingEntry, d->antialiasInput->isChecked()); } } // namespace Digikam diff --git a/core/libs/dimg/filters/transform/freerotationsettings.h b/core/libs/dimg/filters/transform/freerotationsettings.h index 13ae0ccd1a..b299cb352e 100644 --- a/core/libs/dimg/filters/transform/freerotationsettings.h +++ b/core/libs/dimg/filters/transform/freerotationsettings.h @@ -1,71 +1,71 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-16 * Description : Free rotation settings view. * * Copyright (C) 2010-2020 by Gilles Caulier * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_FREE_ROTATION_SETTINGS_H #define DIGIKAM_FREE_ROTATION_SETTINGS_H // Local includes #include // Local includes #include "digikam_export.h" #include "freerotationfilter.h" class KConfigGroup; namespace Digikam { class DIGIKAM_EXPORT FreeRotationSettings : public QWidget { Q_OBJECT public: explicit FreeRotationSettings(QWidget* const parent); ~FreeRotationSettings(); FreeRotationContainer defaultSettings() const; void resetToDefault(); - FreeRotationContainer settings() const; + FreeRotationContainer settings() const; void setSettings(const FreeRotationContainer& settings); void readSettings(KConfigGroup& group); void writeSettings(KConfigGroup& group); Q_SIGNALS: void signalSettingsChanged(); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_FREE_ROTATION_SETTINGS_H diff --git a/core/libs/dimg/filters/transform/shearfilter.cpp b/core/libs/dimg/filters/transform/shearfilter.cpp index a38a38b224..735f1403ff 100644 --- a/core/libs/dimg/filters/transform/shearfilter.cpp +++ b/core/libs/dimg/filters/transform/shearfilter.cpp @@ -1,273 +1,289 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-07-18 * Description : Shear tool threaded image filter. * * Copyright (C) 2005-2020 by Gilles Caulier * Copyright (C) 2010 by Martin Klapetek * * Original Shear algorithms copyrighted 2005 by * Pieter Z. Voloshyn . * * 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, 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. * * ============================================================ */ #include "shearfilter.h" // C++ includes #include #include // KDE includes #include // Local includes #include "digikam_globals.h" #include "dimg.h" #include "dpixelsaliasfilter.h" namespace Digikam { class Q_DECL_HIDDEN ShearFilter::Private { public: explicit Private() + : antiAlias(true), + orgW(0), + orgH(0), + hAngle(0), + vAngle(0), + backgroundColor(Qt::black) { - antiAlias = true; - orgW = 0; - orgH = 0; - hAngle = 0; - vAngle = 0; - backgroundColor = Qt::black; } bool antiAlias; int orgW; int orgH; float hAngle; float vAngle; QColor backgroundColor; QSize newSize; }; ShearFilter::ShearFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } ShearFilter::ShearFilter(DImg* const orgImage, QObject* const parent, float hAngle, float vAngle, bool antialiasing, const QColor& backgroundColor, int orgW, int orgH) : DImgThreadedFilter(orgImage, parent, QLatin1String("sheartool")), d(new Private) { d->hAngle = hAngle; d->vAngle = vAngle; d->orgW = orgW; d->orgH = orgH; d->antiAlias = antialiasing; d->backgroundColor = backgroundColor; initFilter(); } ShearFilter::~ShearFilter() { cancelFilter(); } QString ShearFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Shear Tool")); } QSize ShearFilter::getNewSize() const { return d->newSize; } void ShearFilter::filterImage() { int progress; - int x, y, p = 0, pt; + int x, y, p = 0, pt; int new_width, new_height; double nx, ny, dx, dy; double horz_factor, vert_factor; double horz_add, vert_add; double horz_beta_angle, vert_beta_angle; int nWidth = m_orgImage.width(); int nHeight = m_orgImage.height(); uchar* pBits = m_orgImage.bits(); unsigned short* pBits16 = reinterpret_cast(m_orgImage.bits()); // get beta ( complementary ) angle for horizontal and vertical angles + horz_beta_angle = (((d->hAngle < 0.0) ? 180.0 : 90.0) - d->hAngle) * DEG2RAD; vert_beta_angle = (((d->vAngle < 0.0) ? 180.0 : 90.0) - d->vAngle) * DEG2RAD; // get new distance for width and height values + horz_add = nHeight * ((d->hAngle < 0.0) ? sin(horz_beta_angle) : cos(horz_beta_angle)); vert_add = nWidth * ((d->vAngle < 0.0) ? sin(vert_beta_angle) : cos(vert_beta_angle)); // get absolute values for the distances + horz_add = fabs(horz_add); vert_add = fabs(vert_add); // get new image size ( original size + distance ) + new_width = (int)horz_add + nWidth; new_height = (int)vert_add + nHeight; // get scale factor for width and height + horz_factor = horz_add / new_height; vert_factor = vert_add / new_width; // if horizontal angle is greater than zero... // else, initial distance is equal to maximum distance ( in negative form ) + if (d->hAngle > 0.0) { // initial distance is zero and scale is negative ( to decrease ) - dx = 0; + + dx = 0; horz_factor *= -1.0; } else { dx = -horz_add; } // if vertical angle is greater than zero... // else, initial distance is equal to maximum distance ( in negative form ) + if (d->vAngle > 0.0) { // initial distance is zero and scale is negative ( to decrease ) dy = 0; vert_factor *= -1.0; } else { dy = -vert_add; } // allocates a new image with the new size bool sixteenBit = m_orgImage.sixteenBit(); m_destImage = DImg(new_width, new_height, sixteenBit, m_orgImage.hasAlpha()); m_destImage.fill(DColor(d->backgroundColor.rgb(), sixteenBit)); uchar* pResBits = m_destImage.bits(); unsigned short* pResBits16 = reinterpret_cast(m_destImage.bits()); DPixelsAliasFilter alias; - for (y = 0; y < new_height; ++y) + for (y = 0 ; y < new_height ; ++y) { - for (x = 0; x < new_width; ++x, p += 4) + for (x = 0 ; x < new_width ; ++x, p += 4) { // get new positions + nx = x + dx + y * horz_factor; ny = y + dy + x * vert_factor; // if is inside the source image + if (isInside(nWidth, nHeight, lround(nx), lround(ny))) { if (d->antiAlias) { if (!sixteenBit) + { alias.pixelAntiAliasing(pBits, nWidth, nHeight, nx, ny, &pResBits[p + 3], &pResBits[p + 2], &pResBits[p + 1], &pResBits[p]); + } else + { alias.pixelAntiAliasing16(pBits16, nWidth, nHeight, nx, ny, &pResBits16[p + 3], &pResBits16[p + 2], &pResBits16[p + 1], &pResBits16[p]); + } } else { pt = setPosition(nWidth, lround(nx), lround(ny)); for (int z = 0 ; z < 4 ; ++z) { if (!sixteenBit) { pResBits[p + z] = pBits[pt + z]; } else { pResBits16[p + z] = pBits16[pt + z]; } } } } } // Update the progress bar in dialog. + progress = (int)(((double)y * 100.0) / new_height); - if (progress % 5 == 0) + if ((progress % 5) == 0) { postProgress(progress); } } // To compute the rotated destination image size using original image dimensions. + int W = (int)(fabs(d->orgH * ((d->hAngle < 0.0) ? sin(horz_beta_angle) : cos(horz_beta_angle)))) + d->orgW; int H = (int)(fabs(d->orgW * ((d->vAngle < 0.0) ? sin(vert_beta_angle) : cos(vert_beta_angle)))) + d->orgH; d->newSize.setWidth(W); d->newSize.setHeight(H); } FilterAction ShearFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("antiAlias"), d->antiAlias); action.addParameter(QLatin1String("hAngle"), d->hAngle); action.addParameter(QLatin1String("orgH"), d->orgH); action.addParameter(QLatin1String("orgW"), d->orgW); action.addParameter(QLatin1String("vAngle"), d->vAngle); action.addParameter(QLatin1String("backgroundColorR"), d->backgroundColor.red()); action.addParameter(QLatin1String("backgroundColorG"), d->backgroundColor.green()); action.addParameter(QLatin1String("backgroundColorB"), d->backgroundColor.blue()); action.addParameter(QLatin1String("backgroundColorA"), d->backgroundColor.alpha()); return action; } void ShearFilter::readParameters(const FilterAction& action) { d->antiAlias = action.parameter(QLatin1String("antiAlias")).toBool(); d->hAngle = action.parameter(QLatin1String("hAngle")).toFloat(); d->orgH = action.parameter(QLatin1String("orgH")).toInt(); d->orgW = action.parameter(QLatin1String("orgW")).toInt(); d->vAngle = action.parameter(QLatin1String("vAngle")).toFloat(); d->backgroundColor.setRed(action.parameter(QLatin1String("backgroundColorR")).toInt()); d->backgroundColor.setGreen(action.parameter(QLatin1String("backgroundColorG")).toInt()); d->backgroundColor.setBlue(action.parameter(QLatin1String("backgroundColorB")).toInt()); d->backgroundColor.setAlpha(action.parameter(QLatin1String("backgroundColorA")).toInt()); } } // namespace Digikam diff --git a/core/libs/dimg/filters/transform/shearfilter.h b/core/libs/dimg/filters/transform/shearfilter.h index a21c71a924..880cc6bc93 100644 --- a/core/libs/dimg/filters/transform/shearfilter.h +++ b/core/libs/dimg/filters/transform/shearfilter.h @@ -1,104 +1,104 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-07-18 * Description : Shear tool threaded image filter. * * Copyright (C) 2005-2020 by Gilles Caulier * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_SHEAR_FILTER_H #define DIGIKAM_SHEAR_FILTER_H // Qt includes #include #include // Local includes #include "digikam_export.h" #include "dimgthreadedfilter.h" #include "digikam_globals.h" namespace Digikam { class DIGIKAM_EXPORT ShearFilter : public DImgThreadedFilter { public: explicit ShearFilter(QObject* const parent = nullptr); explicit ShearFilter(DImg* const orgImage, QObject* const parent=nullptr, float hAngle=0.0, float vAngle=0.0, bool antialiasing=true, const QColor& backgroundColor=Qt::black, int orgW=0, int orgH=0); ~ShearFilter(); QSize getNewSize() const; static QString FilterIdentifier() { return QLatin1String("digikam:ShearFilter"); } static QString DisplayableName(); static QList SupportedVersions() { return QList() << 1; } static int CurrentVersion() { return 1; } - virtual QString filterIdentifier() const override + virtual QString filterIdentifier() const override { return FilterIdentifier(); } - virtual FilterAction filterAction() override; - void readParameters(const FilterAction& action) override; + virtual FilterAction filterAction() override; + void readParameters(const FilterAction& action) override; private: - void filterImage() override; + void filterImage() override; inline int setPosition (int Width, int X, int Y) { return (Y*Width*4 + 4*X); }; inline bool isInside (int Width, int Height, int X, int Y) { bool bIsWOk = ((X < 0) ? false : (X >= Width ) ? false : true); bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true); return (bIsWOk && bIsHOk); }; private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_SHEAR_FILTER_H