Segítségkérés algebrai számoláshoz

Fórumok

Adott egy klasszikus Bresenham vonalhúzó. Ez hibaértéket számol az alfa meghatározásához, az indulópontban és a végpontban ez a hiba 0 (tehát az alfa itt 255, egyáltalán nem átlátszó), a két pont között pedig megfelelően számítódik (és abból az alfa az "a" változóba).

Ezt kellene átalakítani úgy, hogy vegyen figyelembe egy induló és érkező hibaértéket az alapján, hogy a koordináták alsó 8 bitje mi (ez a mostani kód csak azt a speciális esetet kezeli, amikor minden alsó 8 bit 0, ezt kellene általánosítani).
Megjegyzés: 256-tal felszorzott koordináták adódnak át (fix pontos aritmetika, alsó 8 bit: 0=0 egész pixelkoorináta, 127=0.5, 255=0.999999).

/* c[4] - szín, RGBA
 * x0,y0 - induló koordináta (alsó 8 bit a törtrész)
 * x1,y1 - vég koordináta (alsó 8 bit a törtrész)
 */
void line(uint8_t c[4], int32_t x0, int32_t y0, int32_t x1, int32_t y1)
{
    int sx, sy, x2, a;
    int dx, dy, err, e2;

    /* most csak levágjuk a törtrészt, hogy az eredeti Bresenhamnak egész pixelkoordinátákat adjunk át.
     * Levágás helyett kéne beleszámolni a törtészt az err, e2 léptetésébe */
    x0 /= 256;
    y0 /= 256;
    x1 /= 256;
    y1 /= 256;

    if(!c[3] || (x0 == x1 && y0 == y1)) return;

    sx = x0 < x1 ? 1 : -1;
    sy = y0 < y1 ? 1 : -1;
    dx = abs(x1-x0);
    dy = abs(y1-y0);

    err = dx*dx+dy*dy;
    e2 = err == 0 ? 1 : 0xffff7fl/sqrt(err);

    dx *= e2; dy *= e2; err = dx-dy;
    while(1) {
        a = err-dx+dy; if(a < 0) a = -a;
        a = 255 - (a >> 16); a = a * c[3] / 255;
        setpixel(x0, y0, c[0], c[1], c[2], a);
        e2 = err; x2 = x0;
        if(2*e2 >= -dx) {
            if(x0 == x1) break;
            if(e2+dy < 0xff0000l) {
                a = 255 - ((e2+dy) >> 16); a = a * c[3] / 255;
                setpixel(x0, y0 + sy, c[0], c[1], c[2], a);
            }
            err -= dy; x0 += sx;
        }
        if(2*e2 <= dy) {
            if(y0 == y1) break;
            if(dx-e2 < 0xff0000l) {
                a = 255 - ((dx-e2) >> 16); a = a * c[3] / 255;
                setpixel(x2 + sx, y0, c[0], c[1], c[2], a);
            }
            err += dx; y0 += sy;
        }
    }
}

Ha van kedve valakinek kibogarászni ezt, azért iszonyat hálás lennék (ha nagyon ráfeküdnék, biztos meg tudnám oldani, de az agyam most egy C fordító belső lelkivilágával van tele és nem akar átállni, folyton félreszámolom).
Egy Nyílt és Szabad Forráskódú projekthez kell, cserébe listáznám a megoldó nevét, mint kontributor és öregbíteném a hírnevét.

Forrásügyileg egyébként itt található, a hívása vonal esetén itt (ez csak felszorozza a koordinátákat 256-al), Bezier görbe esetén meg itt. Ez utóbbi rekurzívan felbontja az ívet több darab összefüggő vonalra, melyek már eshetnek törtpixelre. Viszont amikor ezeket a köztes koordinátákat átadom a vonalhúzónak, akkor kénytelen vagyok eldobni a törtrészt, mert most még nem kezeli azt. Ehhez kellene, hogy a törtrészből kiszámoljam az induló és vég hibaértéket, hogy aztán a Bresenham úgy húzza a vonalat, hogy már eleve anti-aliasolt pixelekkel indítson, ami most csak a ciklus közepén történik.

EDIT: Leegyszerűsítettem a kiírást, és belinkeltem, hogy pontosan mihez kell.

Hozzászólások

Szerkesztve: 2024. 10. 26., szo – 09:38

Utoljára 2004 környékén foglalkoztam C -vel, azóta csak java. Szóval, ha valami nagy baromság, akkor mea culpa.

 

#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <stdlib.h>

void setpixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a);

/* c[4] - szín, RGBA
 * x0,y0 - induló koordináta
 * x1,y1 - vég koordináta
 * e0 - induló hibaérték (0 és 255 között)
 * e1 -vég hibaérték (0 és 255 között)
 */
void line(uint8_t c[4], int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t e0, int16_t e1) {
    int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1, x2, a;
    int dx = abs(x1-x0), dy = abs(y1-y0), err, e2;

    if(!c[3] || (x0 == x1 && y0 == y1)) return;

    /* ide kéne valami okosság, ez az e0 = 0 és e1 = 0 eset,
     * err - két pont Descartes távolságának négyzete
     * e2 - távolság reciproka */

    // Kis távolságértékek használata esetén (e0 és e1 figyelembevétele)
    if (e0 > 0 || e1 > 0) {
        err = dx * dx + dy * dy;          // Két pont közötti távolság négyzete
        e2 = err == 0 ? 1 : 0xffff7f / sqrt(err);   // Távolság reciprokának kiszámítása

        // Az e0 kezdő hibaérték és e1 vég hibaérték figyelembevétele
        dx = (dx * e2 * e0) / 255;
        dy = (dy * e2 * e1) / 255;
        err = dx - dy;
    } else {
        err = dx * dx + dy * dy;
        e2 = err == 0 ? 1 : 0xffff7f / sqrt(err);  // Ha e0 és e1 0, alapértékek
        dx *= e2;
        dy *= e2;
        err = dx - dy;
    }

    while(1) {
        a = err-dx+dy; if(a < 0) a = -a;
        a = 255 - (a >> 16);
        /* a legelső iterációban itt most az a-nak (255-e0)-nak kéne lennie, a legutolsóban pedig (255-e1)-nek */
        a = a * c[3] / 255;
        setpixel(x0, y0, c[0], c[1], c[2], a);
        e2 = err; x2 = x0;
        if(2*e2 >= -dx) {
            if(x0 == x1) break;
            if(e2+dy < 0xff0000l) {
                a = 255 - ((e2+dy) >> 16); a = a * c[3] / 255;
                setpixel(x0, y0 + sy, c[0], c[1], c[2], a);
            }
            err -= dy; x0 += sx;
        }
        if(2*e2 <= dy) {
            if(y0 == y1) break;
            if(dx-e2 < 0xff0000l) {
                a = 255 - ((dx-e2) >> 16); a = a * c[3] / 255;
                setpixel(x2 + sx, y0, c[0], c[1], c[2], a);
            }
            err += dx; y0 += sy;
        }
    }
}

void setpixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
    printf("Pixel at (%d, %d) - Color RGBA(%d, %d, %d, %d)\n", x, y, r, g, b, a);
}

int main() {

    uint8_t color[4] = {255, 0, 0, 255};

    int16_t x0 = 2, y0 = 2;
    int16_t x1 = 8, y1 = 8;
    int16_t e0 = 50, e1 = 200;

    printf("Drawing line from (%d, %d) to (%d, %d) with e0=%d and e1=%d:\n", x0, y0, x1, y1, e0, e1);
    line(color, x0, y0, x1, y1, e0, e1);

    return 0;
}

Output:
Drawing line from (2, 2) to (8, 8) with e0=50 and e1=200:
Pixel at (2, 2) - Color RGBA(255, 0, 0, 255)
Pixel at (2, 3) - Color RGBA(255, 0, 0, 220)
Pixel at (3, 2) - Color RGBA(255, 0, 0, 140)
Pixel at (3, 3) - Color RGBA(255, 0, 0, 105)
Pixel at (4, 2) - Color RGBA(255, 0, 0, 25)
Pixel at (5, 2) - Color RGBA(255, 0, 0, 166)
Pixel at (6, 2) - Color RGBA(255, 0, 0, 51)
Pixel at (7, 2) - Color RGBA(255, 0, 0, 192)
Pixel at (8, 2) - Color RGBA(255, 0, 0, 77)

De hát ez nem jó eredmény. e0=50 esetén az első pont helyesen
Pixel at (2, 2) - Color RGBA(255, 0, 0, 205)
az alfa semmiképp sem lehet 255, mert az az e0=0 eset lenne. Egyébként jó az irány, valahogy így kéne megoldani, az induláskori err és e2 manipulálásával.

Valami ilyesmi ?

#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <stdlib.h>

void setpixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a);

void line(uint8_t c[4], int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t e0, int16_t e1)
{
  int sx = x0 < x1 ? 1 : -1;
  int dx = abs(x1 - x0), dy = abs(y1 - y0), err = dx - dy;

  if (!c[3] || (x0 == x1 && y0 == y1))
    return;

  int start_alpha = c[3] - e0;
  int end_alpha = c[3] - e1;
  int total_steps = abs(x1 - x0);
  int a_step = (start_alpha - end_alpha) / total_steps;

  int a = start_alpha;

  while (1)
  {
    int adjusted_a = a * c[3] / 255;
    setpixel(x0, y0, c[0], c[1], c[2], adjusted_a);

    if (x0 == x1 && y0 == y1)
      break;

    int e2 = 2 * err;
    if (e2 >= -dy)
    {
      err -= dy;
      x0 += sx;
    }

    a -= a_step;
  }
}

void setpixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
  printf("Pixel at (%d, %d) - Color RGBA(%d, %d, %d, %d)\n", x, y, r, g, b, a);
}

int main()
{
  uint8_t color[4] = {255, 0, 0, 255};
  int16_t x0 = 2, y0 = 2;
  int16_t x1 = 8, y1 = 2;
  int16_t e0 = 50, e1 = 200;

  printf("Drawing line from (%d, %d) to (%d, %d) with e0=%d and e1=%d:\n", x0, y0, x1, y1, e0, e1);
  line(color, x0, y0, x1, y1, e0, e1);

  return 0;
}

Output:
 

Drawing line from (2, 2) to (8, 2) with e0=50 and e1=200:
Pixel at (2, 2) - Color RGBA(255, 0, 0, 205)
Pixel at (3, 2) - Color RGBA(255, 0, 0, 180)
Pixel at (4, 2) - Color RGBA(255, 0, 0, 155)
Pixel at (5, 2) - Color RGBA(255, 0, 0, 130)
Pixel at (6, 2) - Color RGBA(255, 0, 0, 105)
Pixel at (7, 2) - Color RGBA(255, 0, 0, 80)
Pixel at (8, 2) - Color RGBA(255, 0, 0, 55)

Sajnos még mindig nem jó az eredmény. Az alfa NEM interpolálható direktben e0 és e1-ből. Mivel itt ez a dx > dy lejtésű eset, tehát két sornyi pixelt kellene rajzolnia, valami ilyesmit:
Pixel at (2, 2) - Color RGBA(255, 0, 0, 205)
Pixel at (2, 3) - Color RGBA(255, 0, 0, 50)
Pixel at (3, 2) - Color RGBA(255, 0, 0, 180)
Pixel at (3, 3) - Color RGBA(255, 0, 0, 75)
Pixel at (4, 2) - Color RGBA(255, 0, 0, 155)
Pixel at (4, 3) - Color RGBA(255, 0, 0, 100)
Pixel at (5, 2) - Color RGBA(255, 0, 0, 130)
Pixel at (5, 3) - Color RGBA(255, 0, 0, 125)
Pixel at (6, 2) - Color RGBA(255, 0, 0, 105)
Pixel at (6, 3) - Color RGBA(255, 0, 0, 150)
Pixel at (7, 2) - Color RGBA(255, 0, 0, 80)
Pixel at (7, 3) - Color RGBA(255, 0, 0, 175)
Pixel at (8, 2) - Color RGBA(255, 0, 0, 55)
Pixel at (8, 3) - Color RGBA(255, 0, 0, 200)

Ha egymás alá írom a megfelelő koordinátákra a számokat, egyértelműbb lesz, miért ez a jó eredmény:

205 180 155 130 105  80  55
 50  75 100 125 150 175 200

Az induló pont a 2,2-es koordinátán van, a vége azonban már a 3. sorban kéne legyen, mivel e1 hibaérték 200 (több, mint 127). Tehát ez egy enyhén fentről lefelé lejtő vonal kéne legyen.

De felejtsük el az e0, e1-et, átfogalmaztam a feladatot úgy, hogy a koordináták vannak felszorozva 256-al, úgy talán könnyebb lesz a megoldás.

És így ?

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>

void setpixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a);

void line(uint8_t c[4], int16_t x0, int16_t y0, int16_t x1, int16_t y1)
{
  int dx = abs(x1 - x0);
  int dy = abs(y1 - y0);
  int sx = x0 < x1 ? 1 : -1;
  int sy = y0 < y1 ? 1 : -1;
  int err = dx - dy;

  while (1)
  {
    int coverage = abs((err * 255) / (dx > dy ? dx : dy));
    int primary_alpha = 255 - coverage;
    int secondary_alpha = coverage;

    setpixel(x0, y0, c[0], c[1], c[2], primary_alpha * c[3] / 255);

    setpixel(x0, y0, c[0], c[1], c[2], secondary_alpha * c[3] / 255);

    // End condition if we've reached the last coordinate
    if (x0 == x1 && y0 == y1)
      break;

    // Bresenham's step adjustment
    int e2 = 2 * err;
    if (e2 > -dy)
    {
      err -= dy;
      x0 += sx;
    }
    if (e2 < dx)
    {
      err += dx;
      y0 += sy;
    }
  }
}

void setpixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
  printf("Pixel at (%d, %d) - Color RGBA(%d, %d, %d, %d)\n", x, y, r, g, b, a);
}

int main()
{
  uint8_t color[4] = {255, 0, 0, 255};
  int16_t x0 = 2, y0 = 2;
  int16_t x1 = 8, y1 = 3;

  printf("Drawing line from (%d, %d) to (%d, %d):\n", x0, y0, x1, y1);
  line(color, x0, y0, x1, y1);

  return 0;
}

Output:
Drawing line from (2, 2) to (8, 3):
Pixel at (2, 2) - Color RGBA(255, 0, 0, 43)
Pixel at (2, 2) - Color RGBA(255, 0, 0, 212)
Pixel at (3, 2) - Color RGBA(255, 0, 0, 85)
Pixel at (3, 2) - Color RGBA(255, 0, 0, 170)
Pixel at (4, 2) - Color RGBA(255, 0, 0, 128)
Pixel at (4, 2) - Color RGBA(255, 0, 0, 127)
Pixel at (5, 2) - Color RGBA(255, 0, 0, 170)
Pixel at (5, 2) - Color RGBA(255, 0, 0, 85)
Pixel at (6, 3) - Color RGBA(255, 0, 0, 214)
Pixel at (6, 3) - Color RGBA(255, 0, 0, 41)
Pixel at (7, 3) - Color RGBA(255, 0, 0, 0)
Pixel at (7, 3) - Color RGBA(255, 0, 0, 255)
Pixel at (8, 3) - Color RGBA(255, 0, 0, 43)
Pixel at (8, 3) - Color RGBA(255, 0, 0, 212)

de nem inkabb az a problemad, hogy a 2 vegpontot most int-ben kell megadni, pedig igazabol azok float-ok, mert nem feltetlen a racspontban lesz a vege? es akkor az alpha is jol szamolodna...

Nem, pont ez a lényege a Breshenam algoritmusnak, hogy nem számolja ki a vonal függvényértékét (hanem léptet) és int only. A ciklusmagban még csak szorzás sincs (ha megnézed, nálam is csak az alfablendingnél van szorzás / osztás, a koordinátaszámításnál csak összeadás / kivonás van. Ezért zseni Breshenam).

Ezt nem értem én se. Szerintem amit bzt akar itt, az lehetetlen, vagyis ezzel az int only algoritmussal mindenképpen, mert ennek pont az a lényege, hogy int képpontból indul int képpontig, és nem lehet vele floating-ból indítani. Ehhez kéne keresnie egy teljesen más vonalrajzoló algoritmust.

Nem is értem, hogy ez milyen projektbe kell. Azt írta, hogy FOSS projekt, de én arra lennék kíváncsi, hogy mit csinál, mire való, milyen libeket használnak hozzá, úgy talán jobban lehetne ajánlani alternatív megoldást.

The world runs on Excel spreadsheets. (Dylan Beattie)

Szerintem amit bzt akar itt, az lehetetlen, vagyis ezzel az int only algoritmussal mindenképpen

Ezer százalék, hogy lehetséges, hiszen a köztes értékekre pont ezt számolja ki a mostani kód. Csak annyi kéne, hogy a hiba ne nulláról induljon, mint most.

én arra lennék kíváncsi, hogy mit csinál, mire való, milyen libeket használnak hozzá, úgy talán jobban lehetne ajánlani alternatív megoldást.

Nem akarok alternatív megoldást, mert már alaposan körbenéztem és mind sokkal rosszabb, mint ami most van. Nincsenek libek, én írom a libet. Hogy pontosan mihez kell, azt meg itt részletesen kifejtettem. De hozzáadtam a leíráshoz egy ismertetőt.

igen ismerem. irtam 25 eve antialiasolt vonalhuzot, a Radikal demoban hasznaltuk.

en ugy csinaltam akkor, hogy 24.8 fixpontos integerrel mukodott, igy eleg volt az x/y interpolalni (ugyanugy szorzas nelkul, minden pixelnel csak hozzaadtam 1-1 szamot x es y-hoz), es a felso 24 bitben volt a pixel koordinataja, az also 8 bitben pedig az alpha ertek hozza.

es igy a kezdo/veg koordinata is lehetett subpixel pontos eleve.

mondjuk en kulon kezeltem azt az esetet ha x iranyban lepegettunk egyet es az y volt antialisolva (egymas alatt kellett 2 pixelt reszben alphasan megrajzolni) es kulon a masikat amikor y-ban ment egyesevel es az x volt tort. igy meg jobban lehetett optimalizalni... (a legelejen pedig ha y1>y2 volt akkor felcsereltem a 2 veget)

en ugy csinaltam akkor, hogy 24.8 fixpontos integerrel mukodott

Én is úgy csinálom.

mondjuk en kulon kezeltem azt az esetet ha x iranyban lepegettunk egyet es az y volt antialisolva (egymas alatt kellett 2 pixelt reszben alphasan megrajzolni) es kulon a masikat amikor y-ban ment egyesevel es az x volt tort. igy meg jobban lehetett optimalizalni...

Nem igazán. A mai gépeken akkor lenne ennek értelme, ha a ciklusmagban egyáltalán nem lenne "if", azonban mindenképp tudnod kell, hogy egy vagy két pixelt kell-e épp kitenni, így nem tudod az elágazást (és az azzal járó CPU predictive execution-höz tárolt instruction pipeline cache flush-ét) elkerülni, szóval túl sokat nem jelent, ha két ciklusra szeded szét. Sőt, ha így egy ciklusban teljes egészében belefér az instruction cache-be, akkor még esély is van rá, hogy nem történik instruction cache flush.

De ez csak a mai gépeken igaz, régen más volt a helyzet, akkoriban simán megérte szétszedni (sőt, az Intel pentiumos optimalizációs kézikönyve direkt ajánlotta is ezt a trükköt, "for if A else B" helyett "if for A else for B").

Csak gyorsan csináltam egy leegyszerűsített próbát, ez lett az eredmény: Árpi, bzt, ilyesmire gondoltok?

from PIL import Image

image = Image.new('RGBA', (320, 200), (0, 0, 0, 0))

x0 = (10 << 8) + 0x80
y0 = (10 << 8) + 0x80
x1 = (15 << 8) + 0x80
y1 = (130 << 8) + 0x80
dx = x1 - x0
dy = y1 - y0

step_x = int(dx / (dy >> 8))
x0 += (dx % (dy >> 8)) >> 1

for _ in range(dy >> 8):
    a = x0 & 0xff
    image.putpixel((x0 >> 8, y0 >> 8), (255, 255, 255, a))
    image.putpixel(((x0 >> 8) + 1, y0 >> 8), (255, 255, 255, 0xff - a))
    y0 += 0x100
    x0 += step_x


image.save('line.png')

Ez az egyik irány csak, amikor dy > dx és előre fele megy az X és az Y is.

a 0x80 az XY koordináta értékadásakor a törtrész, tehát onnan indul a vonal. A felső 8-24 bitben van az egész rész.

https://imgur.com/a/mkFTovU

(vagy lehet cserélgetni a vonalat, ha bzt erre gondolt: https://imgur.com/a/U1UofX6)

Vagy ez még mindig nem az? :)

Itt a Bresenham algoritmussal azért nehézkes, ehelyett a növekményes helyett, mert ugye ott az x0-t mindig 0x100-al növelnénk, ha az err elérte e dy-t. Tehát az alfát abból számolni nehézkes. Ha az err-ből számoljuk az alfát, akkor meg át kell számolni 0-255-ig értékre. - ha jól gondolom...

----

Egy másik kérdés: linux alatt hogy a csudába lehet dosbox alá fordítani 32 bites, vagy 16 bites valós módú C programot? :) Háromféle módon is próbáltam, abból az egyik sikeres volt, de nagyon nyakatekert, gcc-vel, djgpp-vel, és watcomc-vel nem sikerült (a watcomc le sem fordult, a djgpp nem működik valamiért fedora linux alatt).

Szerkesztve: 2024. 10. 26., szo – 10:54

Tehát ha jól értem, akkor

X1,Y1-X2,Y2-be akarsz húzni egy vonalat,
de A1-A2-ből is kell egy átmenet, annyi lépésben, amennyi pont kikerül a képernyőre.

Nekem neked magyarázom a vonalhúzót, hanem magamnak is leírom, hogy értsük meg mi történik:

A feladat itt, hogy az X vagy Y irányokból az egyikből mindig minden iterációban egyet lépünk.
A másik két irányból, meg amikor szükséges, akkor valamennyit.

Tehát van egy vonal:

XX....
..XX..
....XX

itt például X irányba mindig 1-et lépünk, Y irányba meg időnként.

Így lehet amúgy 8 irány ez alapján egy X-Y vonalhúzóval.

\ 1|2 /
 \ | /
8 \|/ 3
---+----    
7 /|\ 4
 / | \
/ 6|5 \

Tehát például az 5-ös iránynál:

Y irányba mindig egyet lépünk, dy-szor (y0-y1 közötti különbség). Az x0-x1 között a különbség dx:

dy = abs(y0 - y1)
dx = abs(x0 - x2)
for (y = y0; y < y1; y++) {
}

A Breshenam algoritmus lényege, hogy egész számokat használ. Emiatt bevezet egy "hibát" az legyen mondjuk err. Tehát a dx az kisebb most mint a dy (5-ös irány). Ha az err-hez hozzáadogatjuk a dx-et, és az eléri a dy-t, a vonalhúzó algoritmus szerint akkor kell az aktuális x értéket léptetni egyel, az err-ből pedig kivonni a dy-t. És a történet indul újra, de már a korrigált értékkel, mert megmaradt az err-ben a "hiba" ami a túlcsordulással keletkezett.

Azért, hogy ne ilyen legyen egy vonal:

X.....
.XXX..
....XX

Hanem ilyen:

XX....
..XX..
....XX

Az err kezdeti értéke a dy / 2

--

Gondoljuk tovább a rutint:
Ha Te A1-ből A2-be akarsz ugyanilyen módon vonalat húzni, neked akkor is az X vagy az Y irányba kell minden iterációban lépni egyet. Tehát például:

x0 = 0
y0 = 0
A0 = 0

x1 = 10
y1 = 2
A1 = 255

A probléma, ha ezt is a Bresenham algoritmussal közelítjuk meg, hogy ha az err2-höz (ami az alfához tartozik) adogatod hozzá a dA-t, van olyan eset is, amikor többször is túlcsordul dy-on (amerre mindig 1-et lépsz). (például a fenti paraméterek).

Itt ezt az irányt nem Bresenham algoritmussal kellene megoldani, hanem ha jól tudom DDA-nak hívják azt, amikor a kezdő A0-hoz egy tört részt is tartalmazó számot adogatsz hozzá minden iterációban.

Hogy ne kelljen float-okkal számolnod, és gyors legyen, ezt úgy szokták megoldani, hogy ha pl dy-szor kell az A-t növelned A0-ból A1-be, akkor dA-t felszorozzák, és ezt osztják le dy-el, tehát pl:

pl: n = (dA << 16) / dy

n a növekmény, ez az a szám, amivel minden iterációban növelned kell az a-t. Így az alsó 16 bit-ben lesz a törtrész, a felső 8 bitben (16-23-ig) pedig az A értéke.

Szerintem kombinálnod kell ezt a két algoritmust. PC-n egy osztás belefér.

Elvileg olyan nem fordul elő, hogy ha 0-255-ig akarsz egy átmenetet, akkor túlcsorduljon és a legutolsó pont megint 0 legyen, mert 255-lesz. (nem tud, mert tört részeket kezelsz az alsó 16 bitben).

Remélem nem írtam nagy hülyeségeket, még nem ittam meg a kávémat. :D A C rutinon jojózott a szemem. :)

 

(Még egy felvetés: nem tudom, hogy PC-n a Bresenham algoritmus a gyorsabb-e, vagy egyáltalán számít-e ez, lehet hogy simán mehetnél ezzel a másodikkal X vagy Y irányba is.
Ebben nincsenek elágazások sem elvileg, és csak egy plusz osztás az ára. - de ezt nem tudom, csak eszembe jutott. :) )

Tehát ha jól értem, akkor

X1,Y1-X2,Y2-be akarsz húzni egy vonalat,
de A1-A2-ből is kell egy átmenet, annyi lépésben, amennyi pont kikerül a képernyőre.

Nem egészen, de majdnem. A vonalhúzó algoritmust olyan ember találta ki, aki mindannyiunknál százszor okosabb, és nincs is vele gond, szépen húzza a vonalat a megfelelő anti-aliasolt értékekkel (ez lehet több képpont is, tehát nem A1-A2 átmenet, hanem X1,Y1-X2,Y2 átmenet hibaértékeiből számítódik. A lejtéstől függően egy képpontonként 1 vagy 2 pixelt is jelenthet, eltérő alfával). (*)

A gondom csupán csak annyi, hogy ez a Descartes távolságot használja a hibaérték kiszámításához, tehát a kezdő és a végpont esetén ez 0, nekem meg valahol a közepén kéne bekapcsolódnom, ahol a hiba nem 0.

Példának: legyen ez az eredeti line(), meg legyen a line'() a javított. Ha húzunk line()-al egy vonalat A-ból B-be, akkor minden jó. Nekünk olyan line'() kéne, amire igaz az, hogy A-ból (A+B)/2-be húzva és (A+B)/2-be húzva ugyanazt kapjuk, mint az eredeti line() esetén A-ból B-be. Azaz úgy kell tudni vonalat húzni, hogy a hibaérték nem nulláról indul és érkezik, hanem megadott értékről / értékre.

(*) - megpróbálom szemléltetni, miért nem A1-A2 átmenetről van szó.

Első eset. Tegyük fel, hogy a kiszámított érték pont egy pixelre esik. Ekkor mindegy a lejtés, az eredmény:

+---+
|   |
|255|
|   |
+---+

Namost tegyük fel, hogy a dx nagyobb, mint a dy, és nem egész pixelre esik, hanem csak 3/4-ében, ekkor:

+---+
|   |
|192|
|   |
+---+
+---+
|   |
| 63|
|   |
+---+

Aztán van az az eset, amikor a dy nagyobb, mint a dx, és szintén 3/4-re esik, ekkor megint két pixelt kell állítani:

+---++---+
|   ||   |
|192|| 63|
|   ||   |
+---++---+

Tehát az alfa csatorna esetén nem sima átmenetről van szó.

Igen, pontosan ez lenne a lényeg, hogy míg a line() mindig egész pixelkoordinátáról indul / érkezik, addig a line'()-nak tudnia kéne egy olyan félútról indulni / érkezni, ami nem egész koordináta. Azaz a line'() indulásnál azt az err és e2 értéket kéne tudni kiszámolni, ami az eredeti line() a ciklusának a felénél volt az értékük.

Ah, ertem! Akkor ket iranybol kozelitenem meg a problemat: egyreszt olyan algoritmusra van-e szukseged ami kimondottan a vonal felehez erkezik es/vagy onnan indulva folytatja vagy opcionalisan/kesobbiekben szukseged lehet mas (pl kis egesz szamokkal megadhato, racionalis) aranyokra is? Masreszt pedig kimondottan azert keresed az algoritmust mert valami miatt felbe kell hagynod a vonal rajzolasat ugy hogy kesobb ugyanonnan folytatsd? Vagy ennel altalanosabb a kerdes? 

Szerintem mindegyikre van jo megoldas, es en nem feltetlen skalaznam be az err ertekeket 0 255 koze kozvetlenul hanem inkabb a novekmenybol leptetnem. Kicsit kesobb megirom mire gondolt a kolto. 

Ennél nagyobb falat. A lényeg az, hogy ez a vonalhúzó algoritmus egy egyenes anti-aliasolt vonalat húz A-ból B-be, ahol A is és B is egész pixelekre esik, de a köztes értékek nem. Nekem ezt egy, két, három, vagy akár több ponton meg kell törnöm és lejtést váltanom. Ezeket a köztes pontokat már kiszámolom, azt is tudom, hogy mennyire "lógnak le" az egész pixel koordinátáról, és a lejtés is megvan. Az egyetlen, ami hibádzik, hogy ezeknél a köztes pontoknál nem veszi figyelembe a korábbi hibahatárt, mert az eredeti Breshenam mindig egész pixelkoordinátákból indul ki és érkezik, emiatt a töréspontokon "elveszik" az anti-alias. Ez így elég bonyi, nem véletlenül próbáltam leegyszerűsíteni a feladatot.

Mondjuk úgy, van A, B és C pont, ahol C garantáltan az A és B köztötti vonalra esik, és nem egész pixelkoordinátájú. Ekkor line(A,B) ugyanazt kell kirajzolja, mint két line'(A,C) és line'(C,B) hívás egymásután (kerekítési hiba ide vagy oda ugyanazt).

Aha, koszi, igy mar asszem vilagos. Valami ilyesmit dobtam most ossze, egy kvadransos valtozatban:

 

#include <stdio.h>

int setpixel(int x,int y,int alpha)
{
 fprintf(stdout,"%d %d %d\n",x,y,alpha);
 return(0);
}

/* now works only for 0<x and |y|<=x, i.e. non-steep lines towards right: */
int line_native(int end_x,int end_y)
{
 int    increment,x,y;

 increment=0;
 x=0;
 y=0;
 while ( x <= end_x )
  {     if ( increment==0 )
         {      setpixel(x,y,255);
         }
        else if ( 0<increment )
         {      setpixel(x,y,255*(end_x-increment)/end_x);
                setpixel(x,y+1,255*increment/end_x);
         }
        else
         {      setpixel(x,y-1,255*(-increment)/end_x);
                setpixel(x,y,255*(end_x+increment)/end_x);
         }

        x++;
        increment += end_y;
        if ( end_x <= 2*increment )
         {      increment -= end_x;
                y++;
         }
        else if ( 2*increment <= -end_x )
         {      increment += end_x;
                y--;
         }
  }
 return(0);
}

int line_section(int end_x,int end_y,int *p_x,int *p_y,int waypoint_x,int *p_increment)
{
 int    increment,x,y;

 increment=*p_increment;
 x=*p_x;
 y=*p_y;

 while ( x <= waypoint_x )
  {     if ( increment==0 )
         {      setpixel(x,y,255);
         }
        else if ( 0<increment )
         {      setpixel(x,y,255*(end_x-increment)/end_x);
                setpixel(x,y+1,255*increment/end_x);
         }
        else
         {      setpixel(x,y-1,255*(-increment)/end_x);
                setpixel(x,y,255*(end_x+increment)/end_x);
         }

        x++;
        increment += end_y;
        if ( end_x <= 2*increment )
         {      increment -= end_x;
                y++;
         }
        else if ( 2*increment <= -end_x )
         {      increment += end_x;
                y--;
         }
  }

 *p_increment=increment;
 *p_x=x;
 *p_y=y;
 
return(0);
}

int main(void)
{
 int    x,y,increment;

 line_native(10,3);

 printf("\n");

 x=y=0;
 increment=0;
 line_section(10,3,&x,&y,4,&increment);
 line_section(10,3,&x,&y,10,&increment);

 return(0);
}

A line_native() meg a 2x line_section() ugyanazt csinalja. A tukorkepi kvadrans az ugye ugyanez csak az x elojelei megfordulnak, a maradek ket kvadransban meg az x es y szerepe felcserelodik.

A lenyeg az esetedben hogyha a meredekseg csak "kicsit" valtozik, hogy a waypoint-okat beallitod jol (hogy hol torjon meg a meredekseg) de az end_y-t minden tovabbi nelkul varialhatod ugy hogy az inkremenst meg a pixel-allapotot (x,y) viszont ugye nem bantod. Ekkor a "hiba" (pixelek szetkentsege) nem valtozik az atmenetnel, tehat szep marad a torottvonalad. Egyebkent ugy egesz elegans lehetne az implementacio hogy nemcsak y-inkremensunk van hanem a ket alfa-t is inkrementaljuk az end_x-nek megfeleloen. Az az osztas ottan nekem nem tetszik :]

A gond ott van ennel a megkozelitesnel hogyha ugy torik meg a torottvonalad hogy kvadranst kell hogy valts. Akkor ez az elv mar nem megy (pl laposan indul majd valamikortol 45 foknal meredekebb lesz az egyeneset). Ebben az esetben szerintem nem uszod meg az A'rpi-fele felvetest, miszerint tortpixel-koordinatakkal kell szamolnod mar. Ami persze maradhat egesz aritmetika is ugyanigy, csak persze nyilvan az trukkosebb lesz meg nem is feltletlen jol definitalt az az eset.

Ez sajnos nem jó. Feleslegesen pakolgatod egyébként a x,y-et pointerekbe, nem kell.

A gond ott van ennel a megkozelitesnel hogyha ugy torik meg a torottvonalad hogy kvadranst kell hogy valts.

Pontosan. A részszakaszokra hívott line'() más-más lejtésű lehet, azonban mindig pontosan ugyanabból a pontból indul (törtrészre ua.), ahonnan a korábbi line'() végzett, tehát a vége alfa és a kezdő alfa lejtéstől függetlenül megegyezik (a másodlagos pont viszont már eltérő lehet).

Lecseréltem a feladatot úgy, hogy a koordináták vannak felszorozva 256-tal, talán így érthetőbb és könnyebb lesz megoldani. A Breshanam szépsége, hogy nem számít neki a kvadráns. Minnél többet nézem, annál inkább rájövök, ez az egyszerű kis ciklus micsoda brilliáns megoldás.

Hát ezzel nem vagyok előrébb, mert pont hogy a linkelt megoldás kiváltásához kell... Az ugyanis iszonyat, ráadásul nem is működik jól.

Helyette vonalak listájára bontom a Bezier ívet, és külön megrajzolom a vonalakat egyesével. Ez tökéletesen működik is, összehasonlíthatatlanul gyorsabb, mint a fenti megoldás, az egyetlen bökkenő, hogy a vonalak kezdetén és végén elveszik az anti-alias, pont ezt kellene orvosolni.

Oh, ertem... akkor pont forditott volt az irany, oke! 

Az a baj hogy igy hirtelen nem latom azt hogy kvadrans-valtas eseten hogyan lehetne az antialiasingot szepen kezelni. Ismet csak hangosan gondolkodom: lehet hogy akkor egy kör-rajzolo algoritmust erdemes megnezni. Ott egy negyed-szeletben definite van egy kvadrans-valtas mindig. A novekmenyes algoritmus ott sem bonyolult (sot, meg egyszerubb is mert a fo ciklusban meg szorzas sincs, csak osszeadas meg kivonas), de hogy anti-aliasolni ott hogy kell/lehet az jo kerdes. 

Példának: legyen ez az eredeti line(), meg legyen a line'() a javított. Ha húzunk line()-al egy vonalat A-ból B-be, akkor minden jó. Nekünk olyan line'() kéne, amire igaz az, hogy A-ból (A+B)/2-be húzva és (A+B)/2-be húzva ugyanazt kapjuk, mint az eredeti line() esetén A-ból B-be. Azaz úgy kell tudni vonalat húzni, hogy a hibaérték nem nulláról indul és érkezik, hanem megadott értékről / értékre.
 

oké, de ezt teheted, az xy vonalhúzóba ez benne is van, amit írtam. A növekményes részben (alfa interpolálás) is lehet vele számolni (amúgy ott biztos kell?)

Tehát a c rutint elkezdtem megnézni, majd inkább távolabbról futottam neki. :) Ennek így működnie kellene. (Tehát az alfát is interpolálni akarod a0-ból a1–be, jól értem, ugye? Vagy másról beszélsz, és nem fogtam?)

Tehát az alfát is interpolálni akarod a0-ból a1–be, jól értem, ugye?

Nem. Pont azt próbáltam elmagyarázni, hogy az alfa még véletlenül sem interpolált. Az kéne, hogy az eredeti ciklus felénél lévő err és e2 értékeket számítsuk ki mindjárt indulásnál e0 függvényében, illetve hogy a ciklusmagon belül az err és e2 számításnál figyelembe kell venni azt is, mennyi az e1.

Ha ez segít, akkor bementnek e0 és e1 helyett lehet x0,y0 és x1,y1 256-al felszorzott érték, ahol az alsó 8 bit tárolja a törtrészt (fixpontos aritmetika, 0 = 0, 127 = 0.5, 255 = 0.9999999). Ekkor az induláskori err és e2 számítás marad az, ami volt, viszont a léptetést át kell írni, mert nem futhat a ciklus 256-szor többször le.

Utána olvasok majd, hogy működik ez az anti-aliasolt vonal húzás

A megoldáshoz nem kell tudni, elég annyi, hogy a megfelelő érték legyen az err és e2 változókban.

De röviden a lényeg annyi, hogy ha dx > dy, akkor két egymás alatti pixelről van szó, egyébként két egymás mellettiről (igazából négy pixelt kéne számításba venni, de a Bresenham szépsége, hogy elég kettő). Ahelyett, hogy a pixelt beállítanánk, attól függően, hogy mennyire "lóg át" a másik pixelbe, arányosan alfa-blendinget végzünk mindkettőn.
- Normál pixeles vonal: https://media.geeksforgeeks.org/wp-content/uploads/anti-aliasing-4.png
- Anti-aliasolva ugyanaz: https://media.geeksforgeeks.org/wp-content/uploads/anti-aliasing-5.png
Ezeken az ábrákon szürkeárnyalat jelzi az alfa csatornát, fekete a 255 (nem átlátszó), fehér pedig a 0 (teljesen átlátszó). A lényeg, hogy a törtpixel mértékétől függően, annak arányában két pixelhez is hozzá kell keverni a kívánt színt.

Azt hiszem [hogy] értem: nem egész koordinátáról (A+B/2 szeretnél indulni.

Tehát az kell neked, hogy a mostani algoritmus milyen értékeket vesz fel középen, és ezzel kell indulni.

A legegyszerűbb megoldás az lenne, hogy az (A+B)/2-ből visszaszámolod A-t (B ismert), és onnan indulsz, de csak A+B/2-től hívod a setpixel-t. Esetleg csak a legelső egész koordinátáig mész vissza.

A helyes megoldás pedig valóban az, hogy megérted az algoritmust és akkor tudod hogy milyen err, e2-vel kell indulni. (Ebben esetleg segíthet ha kiíratod (A+B)/2 környékén ezeket.

Tehát az kell neked, hogy a mostani algoritmus milyen értékeket vesz fel középen, és ezzel kell indulni.

Nem. Az (A+B)/2 csak egy példa volt, lehet bármi más is.

A lényeg, hogy jelenleg a pixelléptetés és az alfaszámítás ugyanazt a hibát használja. Az kellene, hogy ez a léptetés ne 0-ról induljon és érkezzen, hanem vegyen figyelembe egy induló és érkező hibaértéket. Ez utóbbi abból számítódik, hogy mennyire nem egész koordináta az (x0,y0) és az (x1,y1). A mostani kód az a speciális eset, amikor az indulóhiba és érkezési hiba egyaránt 0, ezt kellene általánosítani.

A helyes megoldás pedig valóban az, hogy megérted az algoritmust és akkor tudod hogy milyen err, e2-vel kell indulni.

Tudom, csakhogy erre most nincs kapacitásom (fordító belső lelkivilágával van tele a fejem), azért kértem, hogy hátha valamelyik HUP-os kolléga kiszámolná ezt helyettem.