Qt Forum

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Unsolved

    Update: Forum Guidelines & Code of Conduct


    Qt World Summit: Early-Bird Tickets

    Solved How to improve anti-aliasing?

    General and Desktop
    2
    4
    754
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • CJha
      CJha last edited by CJha

      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 1 Reply Last reply Reply Quote 0
      • CJha
        CJha @CJha last edited by

        @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

        1 Reply Last reply Reply Quote 4
        • mrjj
          mrjj Lifetime Qt Champion last edited by

          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 );
          }
          
          CJha 1 Reply Last reply Reply Quote 2
          • CJha
            CJha @mrjj last edited by

            @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.

            1 Reply Last reply Reply Quote 0
            • CJha
              CJha @CJha last edited by

              @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

              1 Reply Last reply Reply Quote 4
              • First post
                Last post