#include #include #include "toonz/tdistort.h" #include "traster.h" #include "trastercm.h" #include "tgeometry.h" #include "tpixelutils.h" #include //**************************************************************************************** // Local namespace stuff //**************************************************************************************** namespace { inline double dist(const TPointD &a, const TPointD &b) { return norm(b - a); } } //======================================================================================== namespace { typedef struct { TUINT32 val; double tot; } BLOB24; //--------------------------------------------------------------------------------- TPixelCM32 filterPixel(const TPointD &pos, const TRasterCM32P &rasIn) { TPointD distance = TPointD( areAlmostEqual(pos.x, tfloor(pos.x), 0.001) ? 0.0 : fabs(pos.x - tfloor(pos.x)), areAlmostEqual(pos.y, tfloor(pos.y), 0.001) ? 0.0 : fabs(pos.y - tfloor(pos.y))); TPoint nearPos(tfloor(pos.x), tfloor(pos.y)); if (distance == TPointD(0.0, 0.0)) { if (nearPos.x >= 0 && nearPos.x < rasIn->getLx() && nearPos.y >= 0 && nearPos.y < rasIn->getLy()) return rasIn->pixels(nearPos.y)[nearPos.x]; else return TPixelCM32(); } int i, j, k = 0; TPixelCM32 P[4]; for (j = 0; j < 2; ++j) for (i = 0; i < 2; ++i) P[i + 2 * j] = nearPos.x + i < rasIn->getLx() && nearPos.x + i >= 0 && nearPos.y + j < rasIn->getLy() && nearPos.y + j >= 0 ? rasIn->pixels(nearPos.y + j)[nearPos.x + i] : TPixelCM32(); if (P[0] == P[1] && P[1] == P[2] && P[2] == P[3]) return P[0]; double w[4], sum = 0; for (j = 1; j >= 0; --j) for (i = 1; i >= 0; --i) sum += w[k++] = fabs(distance.x - i) * fabs(distance.y - j); for (i = 0; i < 4; i++) w[i] /= sum; TPixelCM32 outPix; TUINT32 tone; double tone_tot = 0.0; BLOB24 currBlob, paintBlobs[4], inkBlobs[4]; int paintBlobsSize = 0, inkBlobsSize = 0; // Doing the same thing in TRop::resample for (i = 0; i < 4; ++i) { // Build paint blobs and sort them tone = P[i].getTone(); tone_tot += tone * w[i]; currBlob.val = P[i].getPaint(); currBlob.tot = tone * w[i]; for (j = 0; j < paintBlobsSize; j++) if (paintBlobs[j].val == currBlob.val) break; if (j < paintBlobsSize) paintBlobs[j].tot += currBlob.tot; else paintBlobs[paintBlobsSize++] = currBlob; for (; j > 0 && paintBlobs[j].tot > paintBlobs[j - 1].tot; j--) std::swap(paintBlobs[j], paintBlobs[j - 1]); // Same for ink blobs currBlob.val = P[i].getInk(); currBlob.tot = (TPixelCM32::getToneMask() - tone) * w[i]; for (j = 0; j < inkBlobsSize; j++) if (inkBlobs[j].val == currBlob.val) break; if (j < inkBlobsSize) inkBlobs[j].tot += currBlob.tot; else inkBlobs[inkBlobsSize++] = currBlob; for (; j > 0 && inkBlobs[j].tot > inkBlobs[j - 1].tot; j--) std::swap(inkBlobs[j], inkBlobs[j - 1]); } tone = troundp(tone_tot); outPix.setPaint(paintBlobs[0].val); outPix.setInk(inkBlobs[0].val); outPix.setTone(tone); return outPix; } //--------------------------------------------------------------------------------- void resample(const TRasterCM32P &rasIn, TRasterCM32P &rasOut, const TDistorter &distorter, const TPoint &p) { if (rasOut->getLx() < 1 || rasOut->getLy() < 1 || rasIn->getLx() < 1 || rasIn->getLy() < 1) return; std::unique_ptr preImages(new TPointD[distorter.maxInvCount()]); int x, y; int i; for (y = 0; y < rasOut->getLy(); y++) { TPixelCM32 *pix = rasOut->pixels(y); TPixelCM32 *endPix = pix + rasOut->getLx(); for (x = 0; pix != endPix; pix++, x++) { TPixelCM32 pixDown; int count = distorter.invMap(convert(p) + TPointD(x + 0.5, y + 0.5), preImages.get()); for (i = count - 1; i >= 0; --i) { TPixelCM32 pixUp; TPointD preImage(preImages[i].x - 0.5, preImages[i].y - 0.5); if (preImage.x > -1 && preImage.y > -1 && preImage.x < rasIn->getLx() + 1 && preImage.y < rasIn->getLy() + 1) pixUp = filterPixel(preImage, rasIn); pixDown.setPaint(pixUp.getPaint() ? pixUp.getPaint() : pixDown.getPaint()); pixDown.setInk(pixUp.getInk() ? pixUp.getInk() : pixDown.getInk()); pixDown.setTone(pixUp == TPixelCM32() ? pixDown.getTone() : pixUp.getTone()); } *pix = pixDown; } } } //--------------------------------------------------------------------------------- template PIXEL closest_pixel(const TPointD &pos, const TRasterPT &rasIn) { TPoint nearPos(tround(pos.x), tround(pos.y)); if (!rasIn->getBounds().contains(nearPos)) return PIXEL(0, 0, 0, 0); return rasIn->pixels(nearPos.y)[nearPos.x]; } //--------------------------------------------------------------------------------- template void resampleClosestPixel(const TRasterPT &rasIn, TRasterPT &rasOut, const TDistorter &distorter, const TPoint &p) { if (rasOut->getLx() < 1 || rasOut->getLy() < 1 || rasIn->getLx() < 1 || rasIn->getLy() < 1) return; std::unique_ptr preImages(new TPointD[distorter.maxInvCount()]); int x, y; int i; for (y = 0; y < rasOut->getLy(); y++) { T *pix = rasOut->pixels(y); T *endPix = pix + rasOut->getLx(); x = 0; for (; pix != endPix; pix++, x++) { T pixDown(0, 0, 0, 0); // preImages.clear(); int count = distorter.invMap(convert(p) + TPointD(x + 0.5, y + 0.5), preImages.get()); for (i = count - 1; i >= 0; --i) { T pixUp(0, 0, 0, 0); TPointD preImage(preImages[i].x - 0.5, preImages[i].y - 0.5); if (preImage.x > -1 && preImage.y > -1 && preImage.x < rasIn->getLx() + 1 && preImage.y < rasIn->getLy() + 1) pixUp = closest_pixel(preImage, rasIn); pixDown = pixUp; } *pix = pixDown; } } } //--------------------------------------------------------------------------------- TPixelCM32 closest_pixel(const TPointD &pos, const TRasterCM32P &rasIn) { TPoint nearPos(tround(pos.x), tround(pos.y)); if (!rasIn->getBounds().contains(nearPos)) return TPixelCM32(0, 0, 255); return rasIn->pixels(nearPos.y)[nearPos.x]; } //--------------------------------------------------------------------------------- void resampleClosestPixel(const TRasterCM32P &rasIn, TRasterCM32P &rasOut, const TDistorter &distorter, const TPoint &p) { if (rasOut->getLx() < 1 || rasOut->getLy() < 1 || rasIn->getLx() < 1 || rasIn->getLy() < 1) return; std::unique_ptr preImages(new TPointD[distorter.maxInvCount()]); int x, y; int i; for (y = 0; y < rasOut->getLy(); y++) { TPixelCM32 *pix = rasOut->pixels(y); TPixelCM32 *endPix = pix + rasOut->getLx(); x = 0; for (; pix != endPix; pix++, x++) { TPixelCM32 pixDown(0, 0, 255); int count = distorter.invMap(convert(p) + TPointD(x + 0.5, y + 0.5), preImages.get()); for (i = count - 1; i >= 0; --i) { TPixelCM32 pixUp(0, 0, 255); TPointD preImage(preImages[i].x - 0.5, preImages[i].y - 0.5); if (preImage.x > -1 && preImage.y > -1 && preImage.x < rasIn->getLx() + 1 && preImage.y < rasIn->getLy() + 1) pixUp = closest_pixel(preImage, rasIn); pixDown = pixUp; } *pix = pixDown; } } } //================================================================================= template PIXEL filterPixel(double a, double b, PIXEL *lineSrc, int lineLength, int lineWrap) { // Retrieve the interesting pixel interval double x0 = std::max(a, 0.0); double x1 = std::min(b, (double)lineLength); int x0Floor = tfloor(x0); int x0Ceil = tceil(x0); int x1Floor = tfloor(x1); if (x0 >= x1) return PIXEL::Transparent; PIXEL *pix = lineSrc + x0Floor * lineWrap; // Build the sums double k, sumr = 0, sumg = 0, sumb = 0, summ = 0; // Fractionary part on the beginning if (x0Ceil > x0) { k = x0Ceil - x0; sumr += k * pix->r; sumg += k * pix->g; sumb += k * pix->b; summ += k * pix->m; pix += lineWrap; } // Intermediate pixels (ie when pixels contract) int x; for (x = x0Ceil; x < x1Floor; ++x, pix += lineWrap) { sumr += pix->r; sumg += pix->g; sumb += pix->b; summ += pix->m; } // Fractionary part on the end if (x1 < lineLength) { k = x1 - x; sumr += k * pix->r; sumg += k * pix->g; sumb += k * pix->b; summ += k * pix->m; } // Finally, divide per the total weight double length = b - a; // 'transparent' pixels outside image range *are* considered sumr = sumr / length; sumg = sumg / length; sumb = sumb / length; summ = summ / length; return PIXEL(sumr, sumg, sumb, summ); } //--------------------------------------------------------------------------------- template PIXEL filterPixel(double a, double b, double c, double d, const TRasterPT &rasIn, PIXEL *temp) { if (bool(a < b) == false && bool(b <= a) == false) return PIXEL::Transparent; // NaNs else std::swap(a, b); if (bool(c < d) == false && bool(d <= c) == false) return PIXEL::Transparent; else std::swap(c, d); // Deal the magnification case, assuming that intervals have at least length // 1. This actually stands for: // 1. Their midpoint is bilinear filtered whenever their former length was // less than 1 (see fractionary parts computing above). // 2. This behaviour is continuous with respect to interval lengths - that // is, we pass from supersampling to subsampling in a smooth manner. if (b - a < 1) { double v = 0.5 * (a + b); a = v - 0.5; b = v + 0.5; } if (d - c < 1) { double v = 0.5 * (c + d); c = v - 0.5; d = v + 0.5; } // Now, filter each column in [a, b] double x0 = std::max(a, 0.0); double x1 = std::min(b, (double)rasIn->getLx()); if (x0 >= x1) return PIXEL::Transparent; int xEnd = tceil(x1); for (int x = tfloor(x0); x < xEnd; ++x) temp[x] = filterPixel( c, d, rasIn->pixels(0) + x, rasIn->getLy(), rasIn->getWrap()); // Then, filter temp return filterPixel(a, b, temp, rasIn->getLx(), 1); } //--------------------------------------------------------------------------------- template void resample(const TRasterPT &rasIn, TRasterPT &rasOut, const TDistorter &distorter, const TPoint &p) { if (rasOut->getLx() < 1 || rasOut->getLy() < 1 || rasIn->getLx() < 1 || rasIn->getLy() < 1) return; int invsCount = distorter.maxInvCount(); // Allocate buffers std::array, 2> invs = { std::unique_ptr( new TPointD[invsCount * (rasOut->getLx() + 1)]), std::unique_ptr( new TPointD[invsCount * (rasOut->getLx() + 1)]), }; std::array, 2> counts = { std::unique_ptr(new int[rasOut->getLx() + 1]), std::unique_ptr(new int[rasOut->getLx() + 1]), }; std::unique_ptr temp(new T[rasIn->getLx()]); TPointD shift(convert(p) + TPointD(0.5, 0.5)); // Fill in the first inverses (lower edge of output image) { TPointD *currOldInv = invs[0].get(); int *oldCounts = counts[0].get(); for (int x = 0; x <= rasOut->getLx(); currOldInv += invsCount, ++x) oldCounts[x] = distorter.invMap(shift + TPointD(x, 0.0), currOldInv); } // For each output row for (int y = 0; y < rasOut->getLy(); ++y) { // Alternate inverse buffers TPointD *oldInvs = invs[y % 2].get(); TPointD *newInvs = invs[(y + 1) % 2].get(); int *oldCounts = counts[y % 2].get(); int *newCounts = counts[(y + 1) % 2].get(); // Build the new inverses { TPointD *currNewInv = newInvs; for (int x = 0; x <= rasOut->getLx(); currNewInv += invsCount, ++x) newCounts[x] = distorter.invMap(shift + TPointD(x, y + 1.0), currNewInv); } // Filter each pixel in the row T *pix = rasOut->pixels(y); TPointD *currOldInv = oldInvs; TPointD *currNewInv = newInvs; for (int x = 0; x < rasOut->getLx(); currOldInv += invsCount, currNewInv += invsCount, ++x, ++pix) { T pixDown(0, 0, 0, 0); int count = std::min({oldCounts[x], oldCounts[x + 1], newCounts[x]}); for (int i = 0; i < count; ++i) { T pixUp(0, 0, 0, 0); pixUp = filterPixel( currOldInv[i].x - 0.5, (currOldInv + invsCount)[i].x - 0.5, currOldInv[i].y - 0.5, currNewInv[i].y - 0.5, rasIn, temp.get()); pixDown = overPix(pixDown, pixUp); } *pix = pixDown; } } } } // namespace //============================================================================================== void distort(TRasterP &outRas, const TRasterP &inRas, const TDistorter &distorter, const TPoint &dstPos, const TRop::ResampleFilterType &filter) { TRaster32P inRas32 = inRas; TRaster64P inRas64 = inRas; TRasterCM32P inRasCM32 = inRas; TRaster32P outRas32 = outRas; TRaster64P outRas64 = outRas; TRasterCM32P outRasCM32 = outRas; if (filter == TRop::Bilinear) { if (inRas32) ::resample(inRas32, outRas32, distorter, dstPos); else if (inRas64) ::resample(inRas64, outRas64, distorter, dstPos); else if (inRasCM32 && outRasCM32) ::resample(inRasCM32, outRasCM32, distorter, dstPos); else assert(0); } else if (filter == TRop::ClosestPixel) { if (inRas32) ::resampleClosestPixel(inRas32, outRas32, distorter, dstPos); else if (inRas64) ::resampleClosestPixel(inRas64, outRas64, distorter, dstPos); else if (inRasCM32 && outRasCM32) ::resampleClosestPixel(inRasCM32, outRasCM32, distorter, dstPos); else assert(0); } else assert(0); } //================================================================================================ // // BilinearDistorterBase // //================================================================================================ BilinearDistorterBase::BilinearDistorterBase( const TPointD &p00s, const TPointD &p10s, const TPointD &p01s, const TPointD &p11s, const TPointD &p00d, const TPointD &p10d, const TPointD &p01d, const TPointD &p11d) : TQuadDistorter(p00s, p10s, p01s, p11s, p00d, p10d, p01d, p11d) { m_A = p00d; m_B = p10d - p00d; m_C = p01d - p00d; m_D = p11d - p01d - p10d + p00d; m_a = m_D.x * m_C.y - m_C.x * m_D.y; m_b0 = m_B.x * m_C.y - m_C.x * m_B.y; } //-------------------------------------------------------------------------------- inline TPointD BilinearDistorterBase::map(const TPointD &p) const { double t = (p.x - m_p00s.x) / (m_p10s.x - m_p00s.x); double s = (p.y - m_p00s.y) / (m_p01s.y - m_p00s.y); return (1 - s) * (1 - t) * m_p00d + (1 - s) * t * m_p10d + s * (1 - t) * m_p01d + s * t * m_p11d; } //-------------------------------------------------------------------------------- int BilinearDistorterBase::invMap(const TPointD &p, TPointD *results) const { // See the book "Digital Image Warping" by G. Wolberg double b = m_D.x * (m_A.y - p.y) + m_D.y * (p.x - m_A.x) + m_b0; double c = m_B.x * (m_A.y - p.y) + m_B.y * (p.x - m_A.x); if (fabs(m_a) > 0.001) { // If delta <0, the points cannot be inverse-mapped double delta = sq(b) - 4.0 * m_a * c; if (delta < 0) return 0; double sqrtDelta = sqrt(delta); double k = 0.5 / m_a; double v1 = k * (-b + sqrtDelta); double v2 = k * (-b - sqrtDelta); double u1, u2; double den = m_B.x + m_D.x * v1; if (fabs(den) > 0.01) u1 = (p.x - m_A.x - m_C.x * v1) / den; else u1 = (p.y - m_A.y - m_C.y * v1) / (m_B.y + m_D.y * v1); den = m_B.x + m_D.x * v2; if (fabs(den) > 0.01) u2 = (p.x - m_A.x - m_C.x * v2) / den; else u2 = (p.y - m_A.y - m_C.y * v2) / (m_B.y + m_D.y * v2); TPointD difP10_P00 = m_p10s - m_p00s; TPointD difP01_P00 = m_p01s - m_p00s; results[0] = m_p00s + u1 * difP10_P00 + v1 * difP01_P00; results[1] = m_p00s + u2 * difP10_P00 + v2 * difP01_P00; return 2; } else { // The equation reduces to first order. double v = -c / b; double u = (p.x - m_A.x - m_C.x * v) / (m_B.x + m_D.x * v); results[0] = m_p00s + u * (m_p10s - m_p00s) + v * (m_p01s - m_p00s); return 1; } } //================================================================================================ // // BilinearDistorter // //================================================================================================ BilinearDistorter::BilinearDistorter(const TPointD &p00s, const TPointD &p10s, const TPointD &p01s, const TPointD &p11s, const TPointD &p00d, const TPointD &p10d, const TPointD &p01d, const TPointD &p11d) : TQuadDistorter(p00s, p10s, p01s, p11s, p00d, p10d, p01d, p11d) { m_refToSource.m_p00 = p00s; m_refToSource.m_p10 = p10s; m_refToSource.m_p01 = p01s; m_refToSource.m_p11 = p11s; m_refToDest.m_p00 = p00d; m_refToDest.m_p10 = p10d; m_refToDest.m_p01 = p01d; m_refToDest.m_p11 = p11d; m_refToSource.m_A = p00s; m_refToSource.m_B = p10s - p00s; m_refToSource.m_C = p01s - p00s; m_refToSource.m_D = p11s - p01s - p10s + p00s; m_refToDest.m_A = p00d; m_refToDest.m_B = p10d - p00d; m_refToDest.m_C = p01d - p00d; m_refToDest.m_D = p11d - p01d - p10d + p00d; m_refToSource.m_a = m_refToSource.m_D.x * m_refToSource.m_C.y - m_refToSource.m_C.x * m_refToSource.m_D.y; m_refToSource.m_b0 = m_refToSource.m_B.x * m_refToSource.m_C.y - m_refToSource.m_C.x * m_refToSource.m_B.y; m_refToDest.m_a = m_refToDest.m_D.x * m_refToDest.m_C.y - m_refToDest.m_C.x * m_refToDest.m_D.y; m_refToDest.m_b0 = m_refToDest.m_B.x * m_refToDest.m_C.y - m_refToDest.m_C.x * m_refToDest.m_B.y; } //--------------------------------------------------------------------------------- BilinearDistorter::~BilinearDistorter() {} //-------------------------------------------------------------------------------- TPointD BilinearDistorter::map(const TPointD &p) const { TPointD sourceToRefPoints[2]; int returnCount = m_refToSource.invMap(p, sourceToRefPoints); if (returnCount > 0) return m_refToDest.map(sourceToRefPoints[0]); return TConsts::napd; } //-------------------------------------------------------------------------------- inline int BilinearDistorter::invMap(const TPointD &p, TPointD *results) const { int returnCount = m_refToDest.invMap(p, results); for (int i = 0; i < returnCount; ++i) results[i] = m_refToSource.map(results[i]); return returnCount; } //-------------------------------------------------------------------------------- inline TPointD BilinearDistorter::Base::map(const TPointD &p) const { return (1 - p.y) * (1 - p.x) * m_p00 + (1 - p.y) * p.x * m_p10 + p.y * (1 - p.x) * m_p01 + p.y * p.x * m_p11; } //-------------------------------------------------------------------------------- int BilinearDistorter::Base::invMap(const TPointD &p, TPointD *results) const { // See the book "Digital Image Warping" by G. Wolberg double b = m_D.x * (m_A.y - p.y) + m_D.y * (p.x - m_A.x) + m_b0; double c = m_B.x * (m_A.y - p.y) + m_B.y * (p.x - m_A.x); if (fabs(m_a) > 0.001) { // If delta <0, the points cannot be inverse-mapped double delta = sq(b) - 4.0 * m_a * c; if (delta < 0) return 0; double sqrtDelta = sqrt(delta); double k = 0.5 / m_a; double v1 = k * (-b + sqrtDelta); double v2 = k * (-b - sqrtDelta); double u1, u2; double den = m_B.x + m_D.x * v1; if (fabs(den) > 0.01) u1 = (p.x - m_A.x - m_C.x * v1) / den; else u1 = (p.y - m_A.y - m_C.y * v1) / (m_B.y + m_D.y * v1); den = m_B.x + m_D.x * v2; if (fabs(den) > 0.01) u2 = (p.x - m_A.x - m_C.x * v2) / den; else u2 = (p.y - m_A.y - m_C.y * v2) / (m_B.y + m_D.y * v2); results[0] = TPointD(u1, v1); results[1] = TPointD(u2, v2); return 2; } else { // The equation reduces to first order. double v = -c / b; double u = (p.x - m_A.x - m_C.x * v) / (m_B.x + m_D.x * v); results[0] = TPointD(u, v); return 1; } } //================================================================================= // // TPerspect // //================================================================================= PerspectiveDistorter::TPerspect::TPerspect() : a11(1.0) , a12(0.0) , a13(0.0) , a21(0.0) , a22(1.0) , a23(0.0) , a31(0.0) , a32(0.0) , a33(1.0) {} //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect::TPerspect(double p11, double p12, double p13, double p21, double p22, double p23, double p31, double p32, double p33) : a11(p11) , a12(p12) , a13(p13) , a21(p21) , a22(p22) , a23(p23) , a31(p31) , a32(p32) , a33(p33) {} //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect::TPerspect(const TPerspect &p) : a11(p.a11) , a12(p.a12) , a13(p.a13) , a21(p.a21) , a22(p.a22) , a23(p.a23) , a31(p.a31) , a32(p.a32) , a33(p.a33) {} //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect::~TPerspect() {} //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect &PerspectiveDistorter::TPerspect::operator=( const PerspectiveDistorter::TPerspect &p) { a11 = p.a11; a12 = p.a12; a13 = p.a13; a21 = p.a21; a22 = p.a22; a23 = p.a23; a31 = p.a31; a32 = p.a32; a33 = p.a33; return *this; } //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect PerspectiveDistorter::TPerspect::operator*( const PerspectiveDistorter::TPerspect &p) const { return PerspectiveDistorter::TPerspect( a11 * p.a11 + a12 * p.a21 + a13 * p.a31, a11 * p.a12 + a12 * p.a22 + a13 * p.a32, a11 * p.a13 + a12 * p.a23 + a13 * p.a33, a21 * p.a11 + a22 * p.a21 + a23 * p.a31, a21 * p.a12 + a22 * p.a22 + a23 * p.a32, a21 * p.a13 + a22 * p.a23 + a23 * p.a33, a31 * p.a11 + a32 * p.a21 + a33 * p.a31, a31 * p.a12 + a32 * p.a22 + a33 * p.a32, a31 * p.a13 + a32 * p.a23 + a33 * p.a33); } //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect PerspectiveDistorter::TPerspect::operator*( const TAffine &aff) const { return operator*(TPerspect(aff.a11, aff.a12, aff.a13, aff.a21, aff.a22, aff.a23, 0.0, 0.0, 1.0)); } //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect operator*( const TAffine &aff, const PerspectiveDistorter::TPerspect &p) { return PerspectiveDistorter::TPerspect(aff.a11, aff.a12, aff.a13, aff.a21, aff.a22, aff.a23, 0.0, 0.0, 1.0) * p; } //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect PerspectiveDistorter::TPerspect::operator*=( const PerspectiveDistorter::TPerspect &p) { return *this * p; } //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect PerspectiveDistorter::TPerspect::inv() const { return TPerspect( a22 * a33 - a23 * a32, a13 * a32 - a12 * a33, a12 * a23 - a13 * a22, a23 * a31 - a21 * a33, a11 * a33 - a13 * a31, a13 * a21 - a11 * a23, a21 * a32 - a22 * a31, a12 * a31 - a11 * a32, a11 * a22 - a12 * a21); } //--------------------------------------------------------------------------------- double PerspectiveDistorter::TPerspect::det() const { return a11 * a22 * a33 + a12 * a23 * a31 + a13 * a21 * a32 - a13 * a22 * a31 - a11 * a23 * a32 - a12 * a21 * a33; } //--------------------------------------------------------------------------------- bool PerspectiveDistorter::TPerspect::operator==( const PerspectiveDistorter::TPerspect &p) const { return a11 == p.a11 && a12 == p.a12 && a13 == p.a13 && a21 == p.a21 && a22 == p.a22 && a23 == p.a23 && a31 == p.a31 && a32 == p.a32 && a33 == p.a33; } //--------------------------------------------------------------------------------- bool PerspectiveDistorter::TPerspect::operator!=( const PerspectiveDistorter::TPerspect &p) const { return !(*this == p); } //--------------------------------------------------------------------------------- bool PerspectiveDistorter::TPerspect::isIdentity(double err) const { return ((a11 - 1.0) * (a11 - 1.0) + (a22 - 1.0) * (a22 - 1.0) + (a33 - 1.0) * (a33 - 1.0) + a12 * a12 + a13 * a13 + a21 * a21 + a23 * a23 + a31 * a31 + a32 * a32) < err; } //--------------------------------------------------------------------------------- TPointD PerspectiveDistorter::TPerspect::operator*(const TPointD &p) const { double den = (a31 * p.x + a32 * p.y + a33); double x = (a11 * p.x + a12 * p.y + a13) / den; double y = (a21 * p.x + a22 * p.y + a23) / den; return TPointD(x, y); } //--------------------------------------------------------------------------------- T3DPointD PerspectiveDistorter::TPerspect::operator*(const T3DPointD &p) const { return T3DPointD(a11 * p.x + a12 * p.y + a13 * p.z, a21 * p.x + a22 * p.y + a23 * p.z, a31 * p.x + a32 * p.y + a33 * p.z); } //--------------------------------------------------------------------------------- TRectD PerspectiveDistorter::TPerspect::operator*(const TRectD &rect) const { if (rect != TConsts::infiniteRectD) { TPointD p1 = *this * rect.getP00(), p2 = *this * rect.getP01(), p3 = *this * rect.getP10(), p4 = *this * rect.getP11(); return TRectD( std::min({p1.x, p2.x, p3.x, p4.x}), std::min({p1.y, p2.y, p3.y, p4.y}), std::max({p1.x, p2.x, p3.x, p4.x}), std::max({p1.y, p2.y, p3.y, p4.y})); } else return TConsts::infiniteRectD; } //================================================================================================ // // PerspectiveDistorter // //================================================================================================ PerspectiveDistorter::PerspectiveDistorter( const TPointD &p00s, const TPointD &p10s, const TPointD &p01s, const TPointD &p11s, const TPointD &p00d, const TPointD &p10d, const TPointD &p01d, const TPointD &p11d) : TQuadDistorter(p00s, p10s, p01s, p11s, p00d, p10d, p01d, p11d) { computeMatrix(); } //--------------------------------------------------------------------------------- PerspectiveDistorter::~PerspectiveDistorter() {} //--------------------------------------------------------------------------------- void PerspectiveDistorter::computeMatrix() { // Since source and destination points are intended in hundreds of pixels in // their refs, // and inverting makes squares with respect to their elements' size, we'd // better put the // quadrilaterals in more numerically stable references before inversions. double srcSize = std::max({dist(m_p00s, m_p10s), dist(m_p00s, m_p01s), dist(m_p10s, m_p11s), dist(m_p01s, m_p11s)}); double dstSize = std::max({dist(m_p00d, m_p10d), dist(m_p00d, m_p01d), dist(m_p10d, m_p11d), dist(m_p01d, m_p11d)}); TAffine toSrcNormalizedRef(TScale(1.0 / srcSize) * TTranslation(-m_p00s)); TAffine toSrcRef(TTranslation(m_p00s) * TScale(srcSize)); TAffine toDstNormalizedRef(TScale(1.0 / dstSize) * TTranslation(-m_p00d)); TAffine toDstRef(TTranslation(m_p00d) * TScale(dstSize)); TPointD p00s; TPointD p10s(toSrcNormalizedRef * m_p10s); TPointD p01s(toSrcNormalizedRef * m_p01s); TPointD p11s(toSrcNormalizedRef * m_p11s); TPointD p00d; TPointD p10d(toDstNormalizedRef * m_p10d); TPointD p01d(toDstNormalizedRef * m_p01d); TPointD p11d(toDstNormalizedRef * m_p11d); // compute a matrix from (0,0), (1,0), (1,1), (0,1) to m_startPoints. TPerspect m1 = computeSquareToMatrix(p00s, p10s, p01s, p11s); // compute a matrix from (0,0), (1,0), (1,1), (0,1) to m_endPoints. TPerspect m2 = computeSquareToMatrix(p00d, p10d, p01d, p11d); m_matrix = m2 * m1.inv(); m_matrixInv = toSrcRef * m_matrix.inv() * toDstNormalizedRef; m_matrix = toDstRef * m_matrix * toSrcNormalizedRef; } //--------------------------------------------------------------------------------- double PerspectiveDistorter::determinant(double a11, double a12, double a21, double a22) { return a11 * a22 - a12 * a21; } //--------------------------------------------------------------------------------- PerspectiveDistorter::TPerspect PerspectiveDistorter::computeSquareToMatrix( const TPointD &p00, const TPointD &p10, const TPointD &p01, const TPointD &p11) { TPointD d1 = p10 - p11; TPointD d2 = p01 - p11; TPointD d3 = p00 - p10 + p11 - p01; TPerspect matrix; matrix.a31 = determinant(d3.x, d2.x, d3.y, d2.y) / determinant(d1.x, d2.x, d1.y, d2.y); matrix.a32 = determinant(d1.x, d3.x, d1.y, d3.y) / determinant(d1.x, d2.x, d1.y, d2.y); matrix.a11 = p10.x - p00.x + matrix.a31 * p10.x; matrix.a12 = p01.x - p00.x + matrix.a32 * p01.x; matrix.a13 = p00.x; matrix.a21 = p10.y - p00.y + matrix.a31 * p10.y; matrix.a22 = p01.y - p00.y + matrix.a32 * p01.y; matrix.a23 = p00.y; matrix.a33 = 1; return matrix; } //--------------------------------------------------------------------------------- TPointD PerspectiveDistorter::map(const TPointD &p) const { return m_matrix * p; } //--------------------------------------------------------------------------------- int PerspectiveDistorter::invMap(const TPointD &p, TPointD *results) const { results[0] = m_matrixInv * p; return 1; } //********************************************************************************************** // Rect inversion methods //********************************************************************************************** //============================= // PerspectiveDistorter //============================= // IDEA: Lines are mapped into lines through this distortion and through the // inverse; plus, // the distortion is 1-1. There is one major issue: across the horizon line of // the perspective // projection (ie the pre-image of {z=0}, which could eventually degenerate to // the whole plane) // the jacobian sign is inverted. Observe, further, that the mapping is // infinitely differentiable // apart from neighbourhoods of the horizon line. // It can be shown that bounding estimates based on corner samples on one side // of the horizon line // may prove false on the other. Therefore, we shall separate such estimates in // the two cases, // and then sum them together. //--------------------------------------------------------------------------------- //! Returns the jacobian (on source ref) associated to the pre-image of passed //! destination point. void PerspectiveDistorter::getJacobian(const TPointD &destPoint, TPointD &srcPoint, TPointD &xDer, TPointD &yDer) const { srcPoint = m_matrixInv * destPoint; T3DPointD im(m_matrix * T3DPointD(srcPoint.x, srcPoint.y, 1.0)); double imZInv = 1.0 / im.z, minusImZInvSq = -sq(imZInv); TPerspect projectionJac(imZInv, 0.0, minusImZInvSq * im.x, 0.0, imZInv, minusImZInvSq * im.y, 0.0, 0.0, 0.0); TPerspect result(projectionJac * m_matrix); xDer.x = result.a11; xDer.y = result.a21; yDer.x = result.a12; yDer.y = result.a22; } //--------------------------------------------------------------------------------- inline void updateResult(const TPointD &srcCorner, const TPointD &xDer, const TPointD &yDer, int rectSideX, int rectSideY, bool &hasPositiveResults, bool &hasNegativeResults, TRectD &posResult, TRectD &negResult) { const int securityAddendum = 5; // Adding a 5 border to the result just to be // sure about approx errors... int jacobianSign = tsign(cross(xDer, yDer)); int sideDerXAgainstRectSideX = rectSideX * tsign(-xDer.y); int sideDerXAgainstRectSideY = rectSideY * tsign(xDer.x); int sideDerYAgainstRectSideX = rectSideX * tsign(-yDer.y); int sideDerYAgainstRectSideY = rectSideY * tsign(yDer.x); if (jacobianSign > 0) { hasPositiveResults = true; if (sideDerXAgainstRectSideX != -sideDerXAgainstRectSideY) { // Rect lies on one side of the derivative line extension. Therefore, the // inverted rect can be updated. if (sideDerXAgainstRectSideX > 0 || sideDerXAgainstRectSideY > 0) { posResult.y0 = std::min(posResult.y0, srcCorner.y - securityAddendum); } else { posResult.y1 = std::max(posResult.y1, srcCorner.y + securityAddendum); } } if (sideDerYAgainstRectSideX != -sideDerYAgainstRectSideY) { if (sideDerYAgainstRectSideX > 0 || sideDerYAgainstRectSideY > 0) { posResult.x1 = std::max(posResult.x1, srcCorner.x + securityAddendum); } else { posResult.x0 = std::min(posResult.x0, srcCorner.x - securityAddendum); } } } else if (jacobianSign < 0) { hasNegativeResults = true; if (sideDerXAgainstRectSideX != -sideDerXAgainstRectSideY) { if (sideDerXAgainstRectSideX > 0 || sideDerXAgainstRectSideY > 0) { negResult.y1 = std::max(posResult.y1, srcCorner.y + securityAddendum); } else { negResult.y0 = std::min(posResult.y0, srcCorner.y - securityAddendum); } } if (sideDerYAgainstRectSideX != -sideDerYAgainstRectSideY) { if (sideDerYAgainstRectSideX > 0 || sideDerYAgainstRectSideY > 0) { negResult.x0 = std::min(posResult.x0, srcCorner.x - securityAddendum); } else { negResult.x1 = std::max(posResult.x1, srcCorner.x + securityAddendum); } } } } //--------------------------------------------------------------------------------- TRectD PerspectiveDistorter::invMap(const TRectD &rect) const { // For each corner, find the jacobian. Then, decide by which side the rect's // pre-image lie with respect to the partial derivatives. // Observe that the two horizon-separated semiplanes do not compete - // the requirement for each is added to the result. TPointD srcCorner, xDer, yDer; double maxD = (std::numeric_limits::max)(); TRectD positiveResult(maxD, maxD, -maxD, -maxD); TRectD negativeResult(positiveResult); bool hasPositiveResults = false, hasNegativeResults = false; getJacobian(rect.getP00(), srcCorner, xDer, yDer); updateResult(srcCorner, xDer, yDer, 1, 1, hasPositiveResults, hasNegativeResults, positiveResult, negativeResult); getJacobian(rect.getP10(), srcCorner, xDer, yDer); updateResult(srcCorner, xDer, yDer, -1, 1, hasPositiveResults, hasNegativeResults, positiveResult, negativeResult); getJacobian(rect.getP01(), srcCorner, xDer, yDer); updateResult(srcCorner, xDer, yDer, 1, -1, hasPositiveResults, hasNegativeResults, positiveResult, negativeResult); getJacobian(rect.getP11(), srcCorner, xDer, yDer); updateResult(srcCorner, xDer, yDer, -1, -1, hasPositiveResults, hasNegativeResults, positiveResult, negativeResult); // If some maxD remain, no bound on that side was found. So replace with // the opposite (unlimited on that side) maxD. if (positiveResult.x0 == maxD) positiveResult.x0 = -maxD; if (positiveResult.x1 == -maxD) positiveResult.x1 = maxD; if (positiveResult.y0 == maxD) positiveResult.y0 = -maxD; if (positiveResult.y1 == -maxD) positiveResult.y1 = maxD; if (negativeResult.x0 == maxD) negativeResult.x0 = -maxD; if (negativeResult.x1 == -maxD) negativeResult.x1 = maxD; if (negativeResult.y0 == maxD) negativeResult.y0 = -maxD; if (negativeResult.y1 == -maxD) negativeResult.y1 = maxD; return hasPositiveResults ? hasNegativeResults ? positiveResult + negativeResult : positiveResult : hasNegativeResults ? negativeResult : TConsts::infiniteRectD; } //================================================================================= //============================= // BilinearDistorter //============================= // IDEA: This time, lines are mapped to curves, in general. Plus, from 0 to 2 // possible inverses // may exist for the same destination point. // Given the separation of the bilinear mapping in two bilinear mappings with an // intermediate reference for the // convex coordinates, it can be shown that a bounding estimate for the inverse // of a rect can be found this way: // 1. Inverse-map the corners of the rect in the convex-reference (ie find // their convex coordinates) // 2. Make their bounding box // 3. Map its corners to the source reference // 4. Return their bounding box // In order to show that this actually works, consider the following result: // A rect maps to a convex quadrilateral through a forward bilinear mapping (ie // passages 3->4 and 2->1). // It is sufficient to consider that lines map to lines through one such // mapping, and that since it is // also everywhere differentiable, its local behaviour around corners is that of // a linear function, through // which convex angles are only mappable to other convex angles. //--------------------------------------------------------------------------------- TRectD BilinearDistorter::invMap(const TRectD &rect) const { // Build the convex coordinates of the rect's corners. TPointD invs[8]; int count[4]; count[0] = m_refToDest.invMap(rect.getP00(), &invs[0]); count[1] = m_refToDest.invMap(rect.getP10(), &invs[2]); count[2] = m_refToDest.invMap(rect.getP01(), &invs[4]); count[3] = m_refToDest.invMap(rect.getP11(), &invs[6]); double maxD = (std::numeric_limits::max)(); TRectD bbox(maxD, maxD, -maxD, -maxD); int i, j; for (i = 0; i < 4; ++i) { for (j = 0; j < count[i]; ++j) { TPointD &inv(invs[j + 2 * i]); bbox.x0 = std::min(bbox.x0, inv.x); bbox.y0 = std::min(bbox.y0, inv.y); bbox.x1 = std::max(bbox.x1, inv.x); bbox.y1 = std::max(bbox.y1, inv.y); } } if (bbox.x1 <= bbox.x0 || bbox.y1 <= bbox.y0) return TConsts::infiniteRectD; // Should happen only if all counts are 0 invs[0] = m_refToSource.map(bbox.getP00()); invs[1] = m_refToSource.map(bbox.getP10()); invs[2] = m_refToSource.map(bbox.getP01()); invs[3] = m_refToSource.map(bbox.getP11()); bbox.x0 = std::min({invs[0].x, invs[1].x, invs[2].x, invs[3].x}); bbox.y0 = std::min({invs[0].y, invs[1].y, invs[2].y, invs[3].y}); bbox.x1 = std::max({invs[0].x, invs[1].x, invs[2].x, invs[3].x}); bbox.y1 = std::max({invs[0].y, invs[1].y, invs[2].y, invs[3].y}); return bbox.enlarge(5); // Enlarge a little just to be sure }