2

Question: I need to upgrade an old Embarcadero VCL graphic math application by introducing antialiased lines. So, I wrote in C++ the algorithm indicated in the page: https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm.

How to write correctly the function 'plot' to draw the pixel at (x,y) with a brightness 'c', especially on the Embarcadero VCL.

Solution: This solution has been possible by the contribution of @Spektre (use of a union to mix colors according to some brightness). pC is a canvas pointer, funcColor is the line intended color, and are properties of the Observer class:

//Antialiased line:
void Observer::aaLine(int x0, int y0, int x1, int y1)
{
union {
        uint32_t dd;//The color value
        uint8_t  db[4];//To work on channels: {00.RR.GG.BB}
    } c, c0;//Line color, and background color

    //Color mixer, with calculations on each channel, because there is no
    //Alpha channel with VCL:
    auto plot = [&](int X, int Y, float brightness){
        c.dd  = funcColor;//Line color
        c0.dd = pC->Pixels[X][Y];//Background color
        //Find coefficients to simulate transparency, where there is not:
        //Front color is augmented when background is decreased:
        for(int i = 0; i < 3; ++i)
            c.db[i] = int(c.db[i] * brightness + c0.db[i] * (1 - brightness));
        //Output obtained by conversion:
        pC->Pixels[X][Y] = static_cast<TColor>(c.dd);
    };

    //Wu's algorithm:
    //Fractional part of x:
    auto fpart  = [](double x) { return x - floor(x); };
    auto rfpart = [&](double x) { return 1 - fpart(x); };

    bool steep = abs(y1 - y0) > abs(x1 - x0);//Means slope > 45 deg.

    if(steep) {
        std::swap(x0, y0);
        std::swap(x1, y1);
    }

    if( x0 > x1 ) {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }

    double  dx = x1 - x0, dy = y1 - y0, gradient = (dx == 0. ? 1. : dy/dx) ;

    //Handle first endpoint
    double xend = x0,
         yend  = y0 + gradient * (xend - x0),
         xgap  = rfpart(x0 + 0.5),
         xpxl1 = xend, // this will be used in the main loop
         ypxl1 = floor(yend);

    if( steep ) {
        plot(ypxl1,   xpxl1, rfpart(yend) * xgap);
        plot(ypxl1+1, xpxl1,  fpart(yend) * xgap);
    }
    else {
        plot(xpxl1, ypxl1  , rfpart(yend) * xgap);
        plot(xpxl1, ypxl1+1,  fpart(yend) * xgap);
    }
    auto intery = yend + gradient; // first y-intersection for the main loop

    //Handle second endpoint
    xend = round(x1);
    yend = y1 + gradient * (xend - x1);
    xgap = fpart(x1 + 0.5);
    auto xpxl2 = xend, //this will be used in the main loop
         ypxl2 = floor(yend);

    if( steep ){
        plot(ypxl2  , xpxl2, rfpart(yend) * xgap);
        plot(ypxl2+1, xpxl2,  fpart(yend) * xgap);
        //Main loop:
        for(double x = xpxl1 + 1 ; x <= xpxl2 - 1 ; x += 1) {
            plot(int(intery)  , x, rfpart(intery));
            plot(int(intery+1), x,  fpart(intery));
            intery += gradient;
        }
    }
    else {
        plot(xpxl2, ypxl2,  rfpart(yend) * xgap);
        plot(xpxl2, ypxl2+1, fpart(yend) * xgap);
        //Main loop:
        for(double x = xpxl1 + 1 ; x <= xpxl2 - 1 ; x += 1) {
            plot(x, int(intery),  rfpart(intery));
            plot(x, int(intery+1), fpart(intery));
            intery += gradient;
        }
    }    
}//Observer::aaLine.

The source code above is updated, and works for me as a solution. The image below comes from tests: Blue's are NOT antialiased, and Red's ones are the results from the solution above. I am satisfied with what I want to do. Red=anti-alias, Blue=no anti-alias

Hugo
  • 157
  • 2
  • 9
  • Here (https://neftali.clubdelphi.com/redimensionar-una-imagen-antialiasing/) you can find a sample and a explanation for antialiasing with VCL (delphi). If you are working with C++ you can adapt the code. It's in spanish but you can use Google translate. – Germán Estévez -Neftalí- Dec 05 '22 at 10:07
  • Is it rather, a problem of missing pixels when reducing images, instead of anti-alias when drawing a line ? But anyway this is definitely interesting because we intend also, to create miniatures of images made by the app. So it's useful for creating app thumbnails. – Hugo Dec 05 '22 at 15:58
  • 8-bit RGB code values are typically not linear with respect to brightness. – Wyck Dec 15 '22 at 15:24
  • _traduce_ is likely not the word you intended. – Wyck Dec 15 '22 at 15:27
  • @Wyck. Well... English is not my mother tongue, so what do you suggest instead ? Thanks – Hugo Dec 15 '22 at 15:34
  • I'm not sure what you meant. Did you mean "reduce"? It sounds like you want to draw an anti-aliased line over an arbitrary background, but you want the line to be a specific colour that is not just black on white or white on black, but you'd like to be able to supply a specific colour for the line and have it be blended with the background appropriately, but the sample you are working from does not support that feature and you have unsatisfactory results of some kind with your code. Is that right? – Wyck Dec 15 '22 at 15:45
  • Probably the word "represent", "translate", or "express" would be better ? (or another ? sorry it's quite uneasy for me, so I will re-edit for that). For the rest of what you explain, I agree: the solution is not perfect I presume, but the VCL is not also (by far), and this solution is enough for me: it is not a graphics-art application, it is a math application. The solution is fast, easy to understand, and maintainable. The quality improvement of the app thanks to this simple solution is HUGE, and I am fully satisfied. – Hugo Dec 15 '22 at 16:35
  • Post Q&A rewritten to take into account the remarks of the readers. – Hugo Dec 15 '22 at 19:04

1 Answers1

0

I think your problem lies in this:

auto plot = [&](double X, double Y, double brighness){
    pC->Pixels[X][Y] = brightness; };

If I understand it correctly pC is some target TCanvas ... this has 2 major problems:

  1. pC->Pixels[X][Y] = brightness; will handle brightness as color according to selected mode (so copy,xor,... or whatever) and not as brightness.

    I would use form of alpha blending where you take originaly render color (or background) and wanted color of rendered line and mix it with brightness as parameter:

     TColor c0=pC->Pixels[X][Y],c0=color of your line;
    
     // here mix colors c = (c0*(1.0-brightness)) + (c1*brightness)
     // however you need to do this according to selected pixelformat of you graphic object and color channel wise...
    
     pC->Pixels[X][Y]=c;
    

    Beware VCL transparency does not use alpha parameter its just opaque or not ... For more info about the mixing see similar:

    especially pay attention to the:

     union
             {
             DWORD dd;
             BYTE db[4];
             } c,c0;
    

    as TColor is 32bit int anyway ...

  2. speed of pC->Pixels[X][Y] in VCL (or any GDI based api) is pitiful at best

    in case you handle many pixels you should consider to use ScanLine[Y] from Graphics::TBitmap ... and render to bitmap as backbufer. This usually improve speed from ~1000 to ~10000 times. for more info see:

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • In your code: ``` // here mix colors c = (c0*(1.0-brightness)) + (c1*brightness) ``` If c0 is the asked color of the line, what is c1 ? Thank you for your complete answer above. – Hugo Dec 05 '22 at 15:50
  • @Hugo `c0` is color what is already rendered below your line (backround or anything else if you rendered there something already) and `c1` is desired color of you line ... – Spektre Dec 05 '22 at 16:09