Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to improve anti-aliasing?



  • Hi! I am trying to make a line plot in QWidget. To obtain the line plot I map the X and Y points of my data point to its correct position in QWidget and then draw a line using QPainter's drawLine() function connecting previous point to the current point. The data points are huge (in tens or hundreds of thousands). What I get is the following:

    Aliased:
    Aliased.PNG

    Anti-alised:
    AntiAliased.PNG
    In both of the above examples, the pen width is 1 and 100,000 data points are plotted in a sine wave with frequency 10 and amplitude 1. There is one data point at the center which is of value 2 which stands out, this is because I need to accommodate spikes in my plot. Since I need to accommodate spikes, I cannot use any kind of algorithm based smoothing (such as using Bezier Curves). This leaves me with options such as anti-alising and painting tricks, but as you can see above anti-alising doesn't produce a good result. I have tried the following as well:

    • Using double precision instead of integer precision for mapping the X and Y data points to it's correct position on QWidget. This significantly increases the calculation time but the final plot looks the same as if I would have done the calculation with integer precision.
    • Painting my line plot on a QImage 4 times the size of the plot area, then applying anti-alising to it and then shrink it to correct size. This also increases calculation time significantly but the final plot again looks the same as above.

    I don't think using OpenGL Paint Engine is a good idea as explained in this QOpenGLPaintDevice Class document. Is there any way I can improve the rendering of my line plots?



  • @CJha Found a solution. This works quite fast, 100k data points plotted in around 15 - 20 ms.

    void mainWindow::paintEvent(QPaintEvent *event)
    {
        /* Constructing Line Plot */
        double numOfPoints{100000}; // Data Length is 100 Thousand
        QVector<double> data; // Data Vector of 100 Thousand
        for(int ii = 0; ii < numOfPoints; ++ii)
        {
            // Sine Wave: Amplitude = 1; Frequency = 10
            data << (1 * sin (2 * M_PI * 5 * ii/numOfPoints));
        }
        int midPoint = round(numOfPoints/2);
        data[midPoint] = 2; // Adding a single point of amplitude 2 at 50 thousandth data point
        double dataMin{-1}; // Minimum amplitude of data
        double dataMax{2}; // Maximum amplitude of data
    
        /* Setting up Painter */
        QPainter p(this);
        p.setPen(Qt::blue);
        p.setRenderHint(QPainter::Antialiasing);
        QPolygonF polyline; // To draw lines to connect two points with different x-coordinates
        QPainterPath path; // To draw abstract lines with same x-coordinate but different y-coordinates
    
        /* NOTE: QPainter path is used because it has a moveTo functionality i.e. path's coordinates can be changed without creating a line from previous coordinate to new one. Same is not true for QPolygonF and so it is used to draw the continuous line */
    
        /************************************************ 
         ***** Mapping Data to rect() of mainWindow *****
         ************************************************/
        QVector<int> py; // Point Y
        QVector<int> sy; // Serial Y
        QVector<int> sySize; // Sizes of sy
        QVector<double> val; // Y Value for different groups of data
        int x_cur{0}; // x-value for current data set
        int x_prv{0}; // x-value for previous data set
        int y{0}; 
        int majy{0}; // Major Y i.e. the value which QPolygonF will follow
        double miny{0}; // Minimum Y: Start Point of QPainterPath
        double maxy{0}; // Minimum Y: End Point of QPainterPath
        int idy{0}; // ID to identity Value of Major Y
    
        /* This only works for data size larger than width of screen. For data size smaller than screen a much simpler approach is used but is not shown here */
        if(data.size() > width())
        {
            path.clear();
            for(int ii = 0; ii < (width() + 1); ++ii)
            {
                py.clear();
                x_cur = round(ii * (numOfPoints/width()));
                if(ii != 0)
                {
                    for(int jj = x_prv; jj < x_cur; ++jj)
                    {
                        y = round(height() * (dataMin + (dataMax - dataMin) - data[jj]) / (dataMax - dataMin));
                        if(!py.contains(y)) { py << y; }
                    }
                    if(py.size() == 1) { polyline << QPointF((ii - 1.0), py[0]); }
                    else
                    {
                        std::sort(py.begin(), py.end());
                        sy.clear();
                        sySize.clear();
                        val.clear();
                        majy = 0;
                        miny = 0.0;
                        maxy = 0.0;
                        idy = 0;
                        for(int jj = 0; jj < py.size(); ++jj)
                        {
                            if(jj == 0) { sy << py[jj]; }
                            else
                            {
                                if(py[jj] == (py[jj - 1] + 1)) { sy << py[jj]; }
                                else
                                {
                                    double newVal{0.0};
                                    for(int kk = 0; kk < sy.size(); ++kk)
                                    {
                                        newVal = newVal + sy[kk];
                                    }
                                    val.append(newVal / sy.size());
                                    sySize << sy.size();
                                    sy.clear();
                                    sy << py[jj];
                                }
                            }
                        }
                        double newVal{0.0};
                        for(int kk = 0; kk < sy.size(); ++kk)
                        {
                            newVal = newVal + sy[kk];
                        }
                        val.append(newVal / sy.size());
                        sySize << sy.size();
                        if (val.size() == 1) { polyline << QPointF((ii - 1.0), val[0]); }
                        else
                        {
                            for(int kk = 0; kk < sySize.size(); ++kk)
                            {
                                if(sySize[kk] > majy)
                                {
                                    majy = sySize[kk];
                                    idy = kk;
                                }
                            }
                            auto result = std::minmax_element(val.begin(), val.end());
                            miny = *result.first;
                            maxy = *result.second;
                            polyline << QPointF((ii - 1.0), val[idy]);
                            path.moveTo(QPointF((ii - 1.0), miny));
                            path.lineTo(QPointF((ii - 1.0), maxy));
                        }
                    }
                }
                x_prv = x_cur;
            }
        }
        p.drawPolyline(polyline);
        p.drawPath(path);
    }
    

    At the end this is what I get:

    AliasedSolved.PNG


  • Lifetime Qt Champion

    Hi
    I could not reproduce it as such.

    alt text

    Maybe your pixels are a bit off but you said you used doubles and i assume you then
    also used the QPointF version ?

    void MainWindow::paintEvent(QPaintEvent *event)
    {
        QPainter p(this);
        p.setPen( Qt::blue );
        p.setRenderHint( QPainter::Antialiasing );
        QPolygonF polyline;
        int h_div_2 = height() / 2;
        for( int x = 0; x < width(); x++)
        {
        float y = h_div_2 + sin( x * 6.28f / width() ) * h_div_2;
        polyline << QPointF( x, y );
        }
        p.drawPolyline( polyline );
    }
    


  • @mrjj Thanks for the reply. You are constructing the data based on the size of your MainWindow i.e. you are using the width() of the MainWindow in your calculation of sine wave, this will always produce a smooth curve because it will calculate the exact y-coordinate for each x-coordinate of your MainWindow. I have to plot the recorded data with high sampling frequency i.e. the data that I am trying to plot is independent of the width() and height() of my mainWindow and so I have to map it to my mainWindow:

    void mainWindow::paintEvent(QPaintEvent *event)
    {
        /* Constructing Demo Line Plot */
        int numOfPoints{100000}; // Data Length is 100 Thousand
        QVector<double> data; // Data Vector of 100 Thousand
        for(int ii = 0; ii < numOfPoints; ++ii)
        {
            data << (1 * sin (2 * M_PI * 5 * ii/numOfPoints)); // Sine Wave: Amplitude = 1; Frequency = 5
        }
        data[50000] = 2; // Adding a single point of amplitude 2 at 50 thousandth data point
        double dataMin{-1.0}; // Minimum amplitude of data
        double dataMax{2.0}; // Maximum amplitude of data
    
        /* Setting up Painter */
        QPainter p(this);
        p.setPen(Qt::blue);
        p.setRenderHint(QPainter::Antialiasing);
        QPolygonF polyline;
    
        /* Mapping data to rect() of mainWindow */
        for(int ii = 0; ii < numOfPoints; ++ii)
        {
            double px = width() * ii / numOfPoints;
            double py = height() * (dataMin + (dataMax - dataMin) - data[ii]) / (dataMax - dataMin);
            polyline << QPointF(px, py);
        }
        p.drawPolyline(polyline);
    }
    

    This produces the following result:

    AntiAliasedNew.PNG

    The lines are not smooth, and I don't know how to fix it.



  • @CJha Found a solution. This works quite fast, 100k data points plotted in around 15 - 20 ms.

    void mainWindow::paintEvent(QPaintEvent *event)
    {
        /* Constructing Line Plot */
        double numOfPoints{100000}; // Data Length is 100 Thousand
        QVector<double> data; // Data Vector of 100 Thousand
        for(int ii = 0; ii < numOfPoints; ++ii)
        {
            // Sine Wave: Amplitude = 1; Frequency = 10
            data << (1 * sin (2 * M_PI * 5 * ii/numOfPoints));
        }
        int midPoint = round(numOfPoints/2);
        data[midPoint] = 2; // Adding a single point of amplitude 2 at 50 thousandth data point
        double dataMin{-1}; // Minimum amplitude of data
        double dataMax{2}; // Maximum amplitude of data
    
        /* Setting up Painter */
        QPainter p(this);
        p.setPen(Qt::blue);
        p.setRenderHint(QPainter::Antialiasing);
        QPolygonF polyline; // To draw lines to connect two points with different x-coordinates
        QPainterPath path; // To draw abstract lines with same x-coordinate but different y-coordinates
    
        /* NOTE: QPainter path is used because it has a moveTo functionality i.e. path's coordinates can be changed without creating a line from previous coordinate to new one. Same is not true for QPolygonF and so it is used to draw the continuous line */
    
        /************************************************ 
         ***** Mapping Data to rect() of mainWindow *****
         ************************************************/
        QVector<int> py; // Point Y
        QVector<int> sy; // Serial Y
        QVector<int> sySize; // Sizes of sy
        QVector<double> val; // Y Value for different groups of data
        int x_cur{0}; // x-value for current data set
        int x_prv{0}; // x-value for previous data set
        int y{0}; 
        int majy{0}; // Major Y i.e. the value which QPolygonF will follow
        double miny{0}; // Minimum Y: Start Point of QPainterPath
        double maxy{0}; // Minimum Y: End Point of QPainterPath
        int idy{0}; // ID to identity Value of Major Y
    
        /* This only works for data size larger than width of screen. For data size smaller than screen a much simpler approach is used but is not shown here */
        if(data.size() > width())
        {
            path.clear();
            for(int ii = 0; ii < (width() + 1); ++ii)
            {
                py.clear();
                x_cur = round(ii * (numOfPoints/width()));
                if(ii != 0)
                {
                    for(int jj = x_prv; jj < x_cur; ++jj)
                    {
                        y = round(height() * (dataMin + (dataMax - dataMin) - data[jj]) / (dataMax - dataMin));
                        if(!py.contains(y)) { py << y; }
                    }
                    if(py.size() == 1) { polyline << QPointF((ii - 1.0), py[0]); }
                    else
                    {
                        std::sort(py.begin(), py.end());
                        sy.clear();
                        sySize.clear();
                        val.clear();
                        majy = 0;
                        miny = 0.0;
                        maxy = 0.0;
                        idy = 0;
                        for(int jj = 0; jj < py.size(); ++jj)
                        {
                            if(jj == 0) { sy << py[jj]; }
                            else
                            {
                                if(py[jj] == (py[jj - 1] + 1)) { sy << py[jj]; }
                                else
                                {
                                    double newVal{0.0};
                                    for(int kk = 0; kk < sy.size(); ++kk)
                                    {
                                        newVal = newVal + sy[kk];
                                    }
                                    val.append(newVal / sy.size());
                                    sySize << sy.size();
                                    sy.clear();
                                    sy << py[jj];
                                }
                            }
                        }
                        double newVal{0.0};
                        for(int kk = 0; kk < sy.size(); ++kk)
                        {
                            newVal = newVal + sy[kk];
                        }
                        val.append(newVal / sy.size());
                        sySize << sy.size();
                        if (val.size() == 1) { polyline << QPointF((ii - 1.0), val[0]); }
                        else
                        {
                            for(int kk = 0; kk < sySize.size(); ++kk)
                            {
                                if(sySize[kk] > majy)
                                {
                                    majy = sySize[kk];
                                    idy = kk;
                                }
                            }
                            auto result = std::minmax_element(val.begin(), val.end());
                            miny = *result.first;
                            maxy = *result.second;
                            polyline << QPointF((ii - 1.0), val[idy]);
                            path.moveTo(QPointF((ii - 1.0), miny));
                            path.lineTo(QPointF((ii - 1.0), maxy));
                        }
                    }
                }
                x_prv = x_cur;
            }
        }
        p.drawPolyline(polyline);
        p.drawPath(path);
    }
    

    At the end this is what I get:

    AliasedSolved.PNG


Log in to reply