@@ -586,3 +586,334 @@ void QgsColorRampTransformer::setColorRamp( QgsColorRamp* ramp )
586586{
587587 mGradientRamp .reset ( ramp );
588588}
589+
590+
591+ //
592+ // QgsCurveTransform
593+ //
594+
595+ bool sortByX ( const QgsPoint& a, const QgsPoint& b )
596+ {
597+ return a.x () < b.x ();
598+ }
599+
600+ QgsCurveTransform::QgsCurveTransform ()
601+ {
602+ mControlPoints << QgsPoint ( 0 , 0 ) << QgsPoint ( 1 , 1 );
603+ calcSecondDerivativeArray ();
604+ }
605+
606+ QgsCurveTransform::QgsCurveTransform ( const QList<QgsPoint>& controlPoints )
607+ : mControlPoints( controlPoints )
608+ {
609+ std::sort ( mControlPoints .begin (), mControlPoints .end (), sortByX );
610+ calcSecondDerivativeArray ();
611+ }
612+
613+ QgsCurveTransform::~QgsCurveTransform ()
614+ {
615+ delete [] mSecondDerivativeArray ;
616+ }
617+
618+ QgsCurveTransform::QgsCurveTransform ( const QgsCurveTransform& other )
619+ : mControlPoints( other.mControlPoints )
620+ {
621+ if ( other.mSecondDerivativeArray )
622+ {
623+ mSecondDerivativeArray = new double [ mControlPoints .count ()];
624+ memcpy ( mSecondDerivativeArray , other.mSecondDerivativeArray , sizeof ( double ) * mControlPoints .count () );
625+ }
626+ }
627+
628+ QgsCurveTransform& QgsCurveTransform::operator =( const QgsCurveTransform & other )
629+ {
630+ mControlPoints = other.mControlPoints ;
631+ if ( other.mSecondDerivativeArray )
632+ {
633+ delete [] mSecondDerivativeArray ;
634+ mSecondDerivativeArray = new double [ mControlPoints .count ()];
635+ memcpy ( mSecondDerivativeArray , other.mSecondDerivativeArray , sizeof ( double ) * mControlPoints .count () );
636+ }
637+ return *this ;
638+ }
639+
640+ void QgsCurveTransform::setControlPoints ( const QList<QgsPoint>& points )
641+ {
642+ mControlPoints = points;
643+ std::sort ( mControlPoints .begin (), mControlPoints .end (), sortByX );
644+ calcSecondDerivativeArray ();
645+ }
646+
647+ void QgsCurveTransform::addControlPoint ( double x, double y )
648+ {
649+ QgsPoint point ( x, y );
650+ if ( mControlPoints .contains ( point ) )
651+ return ;
652+
653+ mControlPoints << point;
654+ std::sort ( mControlPoints .begin (), mControlPoints .end (), sortByX );
655+ calcSecondDerivativeArray ();
656+ }
657+
658+ void QgsCurveTransform::removeControlPoint ( double x, double y )
659+ {
660+ for ( int i = 0 ; i < mControlPoints .count (); ++i )
661+ {
662+ if ( qgsDoubleNear ( mControlPoints .at ( i ).x (), x )
663+ && qgsDoubleNear ( mControlPoints .at ( i ).y (), y ) )
664+ {
665+ mControlPoints .removeAt ( i );
666+ break ;
667+ }
668+ }
669+ calcSecondDerivativeArray ();
670+ }
671+
672+ // this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
673+ // which in turn was adapted from
674+ // http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
675+
676+ double QgsCurveTransform::y ( double x ) const
677+ {
678+ int n = mControlPoints .count ();
679+ if ( n < 2 )
680+ return x; // invalid
681+ else if ( n < 3 )
682+ {
683+ // linear
684+ if ( x <= mControlPoints .at ( 0 ).x () )
685+ return mControlPoints .at ( 0 ).y ();
686+ else if ( x >= mControlPoints .at ( n - 1 ).x () )
687+ return mControlPoints .at ( 1 ).y ();
688+ else
689+ {
690+ double dx = mControlPoints .at ( 1 ).x () - mControlPoints .at ( 0 ).x ();
691+ double dy = mControlPoints .at ( 1 ).y () - mControlPoints .at ( 0 ).y ();
692+ return x * ( dy / dx ) + mControlPoints .at ( 0 ).y ();
693+ }
694+ }
695+
696+ // safety check
697+ if ( x <= mControlPoints .at ( 0 ).x () )
698+ return mControlPoints .at ( 0 ).y ();
699+ if ( x >= mControlPoints .at ( n - 1 ).x () )
700+ return mControlPoints .at ( n - 1 ).y ();
701+
702+ // find corresponding segment
703+ QList<QgsPoint>::const_iterator pointIt = mControlPoints .constBegin ();
704+ QgsPoint currentControlPoint = *pointIt;
705+ ++pointIt;
706+ QgsPoint nextControlPoint = *pointIt;
707+
708+ for ( int i = 0 ; i < n - 1 ; ++i )
709+ {
710+ if ( x < nextControlPoint.x () )
711+ {
712+ // found segment
713+ double h = nextControlPoint.x () - currentControlPoint.x ();
714+ double t = ( x - currentControlPoint.x () ) / h;
715+
716+ double a = 1 - t;
717+
718+ return a*currentControlPoint.y () + t*nextControlPoint.y () + ( h*h / 6 )*(( a*a*a - a )*mSecondDerivativeArray [i] + ( t*t*t - t )*mSecondDerivativeArray [i+1 ] );
719+ }
720+
721+ ++pointIt;
722+ if ( pointIt == mControlPoints .constEnd () )
723+ break ;
724+
725+ currentControlPoint = nextControlPoint;
726+ nextControlPoint = *pointIt;
727+ }
728+
729+ // should not happen
730+ return x;
731+ }
732+
733+ // this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
734+ // which in turn was adapted from
735+ // http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
736+
737+ QVector<double > QgsCurveTransform::y ( const QVector<double >& x ) const
738+ {
739+ QVector<double > result;
740+
741+ int n = mControlPoints .count ();
742+ if ( n < 3 )
743+ {
744+ // invalid control points - use simple transform
745+ Q_FOREACH ( double i, x )
746+ result << y ( i );
747+
748+ return result;
749+ }
750+
751+ // find corresponding segment
752+ QList<QgsPoint>::const_iterator pointIt = mControlPoints .constBegin ();
753+ QgsPoint currentControlPoint = *pointIt;
754+ ++pointIt;
755+ QgsPoint nextControlPoint = *pointIt;
756+
757+ int xIndex = 0 ;
758+ double currentX = x.at ( xIndex );
759+ // safety check
760+ while ( currentX <= currentControlPoint.x () )
761+ {
762+ result << currentControlPoint.y ();
763+ xIndex++;
764+ currentX = x.at ( xIndex );
765+ }
766+
767+ for ( int i = 0 ; i < n - 1 ; ++i )
768+ {
769+ while ( currentX < nextControlPoint.x () )
770+ {
771+ // found segment
772+ double h = nextControlPoint.x () - currentControlPoint.x ();
773+
774+ double t = ( currentX - currentControlPoint.x () ) / h;
775+
776+ double a = 1 - t;
777+
778+ result << a*currentControlPoint.y () + t*nextControlPoint.y () + ( h*h / 6 )*(( a*a*a - a )*mSecondDerivativeArray [i] + ( t*t*t - t )*mSecondDerivativeArray [i+1 ] );
779+ xIndex++;
780+ if ( xIndex == x.count () )
781+ return result;
782+
783+ currentX = x.at ( xIndex );
784+ }
785+
786+ ++pointIt;
787+ if ( pointIt == mControlPoints .constEnd () )
788+ break ;
789+
790+ currentControlPoint = nextControlPoint;
791+ nextControlPoint = *pointIt;
792+ }
793+
794+ // safety check
795+ while ( xIndex < x.count () )
796+ {
797+ result << nextControlPoint.y ();
798+ xIndex++;
799+ }
800+
801+ return result;
802+ }
803+
804+ bool QgsCurveTransform::readXml ( const QDomElement& elem, const QDomDocument& )
805+ {
806+ QString xString = elem.attribute ( QStringLiteral ( " x" ) );
807+ QString yString = elem.attribute ( QStringLiteral ( " y" ) );
808+
809+ QStringList xVals = xString.split ( ' ,' );
810+ QStringList yVals = yString.split ( ' ,' );
811+ if ( xVals.count () != yVals.count () )
812+ return false ;
813+
814+ QList< QgsPoint > newPoints;
815+ bool ok = false ;
816+ for ( int i = 0 ; i < xVals.count (); ++i )
817+ {
818+ double x = xVals.at ( i ).toDouble ( &ok );
819+ if ( !ok )
820+ return false ;
821+ double y = yVals.at ( i ).toDouble ( &ok );
822+ if ( !ok )
823+ return false ;
824+ newPoints << QgsPoint ( x, y );
825+ }
826+ setControlPoints ( newPoints );
827+ return true ;
828+ }
829+
830+ bool QgsCurveTransform::writeXml ( QDomElement& transformElem, QDomDocument& ) const
831+ {
832+ QStringList x;
833+ QStringList y;
834+ Q_FOREACH ( const QgsPoint& p, mControlPoints )
835+ {
836+ x << qgsDoubleToString ( p.x () );
837+ y << qgsDoubleToString ( p.y () );
838+ }
839+
840+ transformElem.setAttribute ( QStringLiteral ( " x" ), x.join ( ' ,' ) );
841+ transformElem.setAttribute ( QStringLiteral ( " y" ), y.join ( ' ,' ) );
842+
843+ return true ;
844+ }
845+
846+ // this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
847+ // which in turn was adapted from
848+ // http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
849+
850+ void QgsCurveTransform::calcSecondDerivativeArray ()
851+ {
852+ int n = mControlPoints .count ();
853+ if ( n < 3 )
854+ return ; // cannot proceed
855+
856+ delete[] mSecondDerivativeArray ;
857+
858+ double * matrix = new double [ n * 3 ];
859+ double * result = new double [ n ];
860+ matrix[0 ] = 0 ;
861+ matrix[1 ] = 1 ;
862+ matrix[2 ] = 0 ;
863+ result[0 ] = 0 ;
864+ QList<QgsPoint>::const_iterator pointIt = mControlPoints .constBegin ();
865+ QgsPoint pointIm1 = *pointIt;
866+ ++pointIt;
867+ QgsPoint pointI = *pointIt;
868+ ++pointIt;
869+ QgsPoint pointIp1 = *pointIt;
870+
871+ for ( int i = 1 ; i < n - 1 ; ++i )
872+ {
873+ matrix[i * 3 + 0 ] = ( pointI.x () - pointIm1.x () ) / 6.0 ;
874+ matrix[i * 3 + 1 ] = ( pointIp1.x () - pointIm1.x () ) / 3.0 ;
875+ matrix[i * 3 + 2 ] = ( pointIp1.x () - pointI.x () ) / 6.0 ;
876+ result[i] = ( pointIp1.y () - pointI.y () ) / ( pointIp1.x () - pointI.x () ) - ( pointI.y () - pointIm1.y () ) / ( pointI.x () - pointIm1.x () );
877+
878+ // shuffle points
879+ pointIm1 = pointI;
880+ pointI = pointIp1;
881+ ++pointIt;
882+ if ( pointIt == mControlPoints .constEnd () )
883+ break ;
884+
885+ pointIp1 = *pointIt;
886+ }
887+ matrix[( n-1 )*3 + 0 ] = 0 ;
888+ matrix[( n-1 )*3 + 1 ] = 1 ;
889+ matrix[( n-1 ) * 3 +2 ] = 0 ;
890+ result[n-1 ] = 0 ;
891+
892+ // solving pass1 (up->down)
893+ for ( int i = 1 ; i < n; ++i )
894+ {
895+ double k = matrix[i * 3 + 0 ] / matrix[( i-1 ) * 3 + 1 ];
896+ matrix[i * 3 + 1 ] -= k * matrix[( i-1 )*3 +2 ];
897+ matrix[i * 3 + 0 ] = 0 ;
898+ result[i] -= k * result[i-1 ];
899+ }
900+ // solving pass2 (down->up)
901+ for ( int i = n - 2 ; i >= 0 ; --i )
902+ {
903+ double k = matrix[i*3 +2 ] / matrix[( i+1 )*3 +1 ];
904+ matrix[i*3 +1 ] -= k * matrix[( i+1 )*3 +0 ];
905+ matrix[i*3 +2 ] = 0 ;
906+ result[i] -= k * result[i+1 ];
907+ }
908+
909+ // return second derivative value for each point
910+ mSecondDerivativeArray = new double [n];
911+ for ( int i = 0 ;i < n;++i )
912+ {
913+ mSecondDerivativeArray [i] = result[i] / matrix[( i*3 )+1 ];
914+ }
915+
916+ delete[] result;
917+ delete[] matrix;
918+ }
919+
0 commit comments