New anti-aliased graphics library for BBCSDL

Discussions related to graphics (2D and 3D), animation and games programming
David Williams

New anti-aliased graphics library for BBCSDL

Post by David Williams »

Richard has been working on a new library for BBCSDL that renders various kinds of shape with smooth (anti-aliased) edges, such as lines of abritrary thickness, PolyBezier curves, filled and (thick) outline circles and (optionally angled) ellipses, and polygons. Some BB4W users may be aware of, and might even have used, one of its standard libraries, GDIPLIB, which uses Windows GDI Plus API functions to draw nice anti-aliased graphics. Richard's new library, aagfxlib, goes some way to providing similar functionality.

The images below are from a demo program written by Richard. One uses GDIPLIB and GDI+ to do the rendering, the other uses aagfxlib and the newly enhanced SDL2_gfx:

GDI Plus:

Image


aagfxlib:

Image


Richard points out that he has "hit a snag" when it comes to his library's use with Linux due to an issue with an SDL function (SDL_RenderDrawLine) that hasn't been fixed since he reported it nearly 3 years ago. My understanding is that this new library will be included in the next release of BBCSDL.


David.
--
DDRM

Re: New anti-aliased graphics library for BBCSDL

Post by DDRM »

Thanks for posting that, David, and please pass on my congratulations to Richard. It looks very nice. Certainly the step-up in quality with GDIPLib is quite substantial, and I imagine this is the same for SDL. The only difference I can spot is at the base of the heart, where GDIP gives a fine, extended point, while aagfxlib gives a square butt - unless that's a coding issue? I note that the internal "point" is rounded in both cases.

Hmmm. Is there a gap in the top right corner of the black square/diamond? Flood filling would check.

Now to get the blue arrow to weave in and out of the brown ellipse... Yes, I know the solution is to copy part of the blue arrow on top....

:-)

D
DDRM

Re: New anti-aliased graphics library for BBCSDL

Post by DDRM »

Here's a cut-down version of a little ditty I did with GDIP some time ago, if anyone is interested... If anyone wants the full version, let me know, and I'll find a way...
D

Code: Select all

 REM Celtic_Knots v2.1 by David Marples  07/08/2007
      REM It uses the GDIP library to generate anti-aliased curves
      REM which is pretty, but won't work if you don't have the relevant bit of windows!
      REM As the name suggests, it draws Celtic-style knotwork.
      REM It works on the principle that if you draw each square as two lines, with one
      REM going top left to bottom right overlying one going TR to BL, then it will always be valid.
      REM Lines going through the corners are confusing, but consistent with this!
      REM You can control how many squares horizontally and vertically are filled,
      REM and whether or not the grid is shown.
      REM You can choose the symmetry of the knot, with options being reflections horizontally or vertically,
      REM or 2 or 4-fold rotational symmetry (the latter won't work with non-square arrays!).
      REM By left-clicking on the knot, you can re-arrange the strands (symmetry setting can be changed for this)
      REM Right-clicking on a strand will bring up a dialogue, allowing you to change to another pre-set colour pattern,
      REM, alter the colour patterns, and change the width of the line for that colour pattern.
      :
      MODE 21:REM 800x600: try MODEs 0 (640x512), 23 (1024x768), or 15 (1280x1024) as alternatives
      xres%=@vdu%!208
      yres%=@vdu%!212
      numsqx%=10
      numsqy%=10
      maxnumsq%=100
      DIM sq&(maxnumsq%,maxnumsq%,3)     :REM byte array to store colour and knot data
      showgrid%=TRUE
      knotdone%=FALSE
      dia_shown%=FALSE

      sym$="none"
      DIM text% 255
      PROCsetupGDI
      PROCsetupcons
      ON ERROR PROCtidycons:PROC_gdipexit:QUIT
      ON CLOSE PROCtidycons:PROC_gdipexit:QUIT
      ON SYS PROCdobuttons(@wparam%,@lparam%) : RETURN

      DIM cols%(15,2)     :REM stores data for 16 colour pairs
      cols%()=&FFFFFF
      cols%(0,1)=&000000
      cols%(1,1)=&800000
      cols%(2,1)=&008000
      cols%(3,1)=&000080
      cols%(4,1)=&808000
      cols%(5,1)=&800080
      cols%(6,1)=&008080
      cols%(7,1)=&808080
      cols%(8,1)=&B04040
      cols%(9,1)=&40B040
      cols%(10,1)=&4040B0
      cols%(11,1)=&40B0B0
      cols%(12,1)=&B0B040
      cols%(13,1)=&B040B0
      cols%(14,1)=&B0B0B0
      cols%(15,1)=&F00000

      sp%=2*yres% DIV (numsqy%+2)
      REM Set initial background colour for all strands to white,
      REM and set the line thickness to be 1/8 of the box size
      FOR x%=0 TO 15
        cols%(x%,0)=&FFFFFF
        cols%(x%,2)=sp% DIV 8
      NEXT x%

      REPEAT
        REM OK, so the main program doesn't do much! It waits for you to press a button,
        REM or click the mouse, then leaps into action!
        q$=INKEY$(1)
        REM You CAN bail out this way, but you are meant to use the button!
        MOUSE x%,y%,z%
        IF z%<>0 THEN PROCmouse(x%,y%,z%)
        REPEAT:MOUSE x%,y%,z%:UNTIL z%=0
      UNTIL q$="q" OR q$="Q"
      PROCtidycons
      PROC_gdipexit
      QUIT
      :
      DEFPROCmouse(x%,y%,z%)
      LOCAL sp%,sx%,sy%,sz%,op%,newp%
      REM A mouse button has been clicked: find where the mouse was at the time
      IF NOT knotdone% THEN ENDPROC:REM we haven't drawn a knot to edit!
      sp%=2*yres% DIV (numsqy%+2)
      IF  (2*yres% DIV (numsqx%+2))<sp% THEN sp%=2*yres% DIV (numsqx%+2)
      IF x%<sp% OR y%<sp% OR x%>sp%*(numsqx%+1) OR y%>sp%*(numsqy%+1) OR dia_shown% THEN ENDPROC:REM outside the knot
      sx%=(x% DIV sp%)
      sy%=(y% DIV sp%)
      REM sx% and sy% give the square we are in
      x%-=sx%*sp%
      y%-=sy%*sp%
      IF 2*y%<sp% THEN
        IF 2*x%<sp% THEN sz%=3 ELSE sz%=2
      ELSE
        IF 2*x%<sp% THEN sz%=0 ELSE sz%=1
      ENDIF
      REM sz% gives which corner we are in (0= top left, 1=top right, 2=br,3=bl)
      IF 2*x%>sp% THEN x%=sp%-x%
      IF 2*y%>sp% THEN y%=sp%-y%
      sx%-=1
      sy%-=1
      IF z%=4 THEN
        REM Left-click: reorganise knot
        PROCchecksym
        IF y%>x% THEN sq&(sx%,sy%,sz%)=1 ELSE sq&(sx%,sy%,sz%)=3
        IF y%<4 AND x%<4 THEN sq&(sx%,sy%,sz%)=2
        PROCsetpat(sq&(),numsqx%,numsqy%,sx%,sy%,sz%,sym$)
        PROCspreadsym(sx%,sy%,sz%)
        PROCclearcols
        PROCsetcol(sq&(),numsqx%,numsqy%)
      ELSE
        REM Disabled in this cut-down version...
        SYS "MessageBox", @hwnd%, "Line styling removed in this cut-dwn version", "Celtic Knot Can't!", 0
      ENDIF
      PROCdraw(numsqx%,numsqy%,xres%,yres%)
      ENDPROC:REM PROCmouse
      :
      DEFPROCclearcols
      LOCAL x%,y%,z%
      REM reset all the colour bits to 0, to allow retracing
      FOR x%=0 TO numsqx%-1
        FOR y%=0 TO numsqy%-1
          FOR z%=0 TO 3
            sq&(x%,y%,z%)AND=3
          NEXT z%
        NEXT y%
      NEXT x%
      ENDPROC:REM clearcols

      DEFPROCchecksym
      LOCAL sel%
      REM find out what symmetry is set, and return the relevant string
      SYS "SendMessage", lb1%, &147,0,0  TO sel%
      CASE sel% OF
        WHEN 0:sym$="none"
        WHEN 1:sym$="mh"
        WHEN 2:sym$="mv"
        WHEN 3:sym$="m4"
        WHEN 4:sym$="r2"
        WHEN 5:sym$="r4"
      ENDCASE
      ENDPROC :REM checksym
      :
      DEFFNvalidrange(n%,min%,max%)
      IF n%<min% THEN =min%
      IF n%>max% THEN =max%
      =n%
      :
      DEFPROCdobuttons(w%,l%)
      LOCAL content$,sel%,message$
      w%=w% AND &FFFF
      CASE w% OF
        WHEN 1:
          REM OK selected - check line width and close the dialogue box
          cols%(newn%,2)=VAL(FNgetdlgtext(linechardlg%, 500))
          IF cols%(newn%,2)<3 THEN cols%(newn%,2)=3:SYS "SetDlgItemText", !linechardlg%, 500, STR$(cols%(n%,2))
          PROC_closedialog(linechardlg%)
          dia_shown%=FALSE
        WHEN 11:
          REM change in number of squares horizontally
          SYS "GetWindowText", eb1%, text%, 255
          content$ = $$text%
          numsqx%=FNvalidrange(VAL(content$),2,maxnumsq%)
          SYS "SendMessage", eb1ud%, &467, 0, numsqx% : REM UDM_SETPOS
        WHEN 21:
          REM change in number of squares vertically
          SYS "GetWindowText", eb2%, text%, 255
          content$ = $$text%
          numsqy%=FNvalidrange(VAL(content$),2,maxnumsq%)
          SYS "SendMessage", eb2ud%, &467, 0, numsqy% : REM UDM_SETPOS
        WHEN 31:
          REM Button to draw knot has been pressed
          REM First we need to get the size of knot to draw, from the edit boxes
          SYS "GetWindowText", eb1%, text%, 255
          content$ = $$text%
          numsqx%=FNvalidrange(VAL(content$),2,maxnumsq%)
          SYS "SendMessage", eb1ud%, &467, 0, numsqx% : REM UDM_SETPOS
          SYS "GetWindowText", eb2%, text%, 255
          content$ = $$text%
          numsqy%=FNvalidrange(VAL(content$),2,maxnumsq%)
          SYS "SendMessage", eb2ud%, &467, 0, numsqy% : REM UDM_SETPOS
          REM now we need to sort out the symmetry required
          PROCchecksym
          REM Now call the routine to work out the knot and draw it
          PROCdo_it
        WHEN 32:
          REM Show/Hide grid button pressed: toggle flag, toggle button, and if a knot has been calculated, redraw it
          IF showgrid% THEN
            showgrid%=FALSE
            SYS "SetWindowText",gbut%,"Show grid"
          ELSE
            showgrid%=TRUE
            SYS "SetWindowText",gbut%,"Hide grid"
          ENDIF
          IF knotdone% THEN CLG:PROCdraw(numsqx%,numsqy%,xres%,yres%)
        WHEN 33: PROCtidycons:PROC_gdipexit:QUIT    :REM "Quit"button pressed:let's get out of here!
        WHEN 36:
          REM "Help" button pressed
          message$="Welcome to the Celtic Knot Drawing program, version 2.1, by David Marples"+CHR$(13)+CHR$(13)
          message$+="It works on the principle that if you draw each square as two lines, with one going top left to bottom right and the"+CHR$(13)
          message$+="overlying one going TR to BL, then it will always be valid."+CHR$(13)+CHR$(13)
          message$+="By modifying the boxes at the top right, you can control how many squares horizontally and vertically are filled,"+CHR$(13)
          message$+="and you can choose the symmetry of the knot, with options being reflections horizontally or vertically, or"+CHR$(13)
          message$+="2 or 4-fold rotational symmetry (the latter won't work with non-square arrays!)."+CHR$(13)+CHR$(13)
          message$+="When you are ready, click 'Draw it', and the knot will be generated."+CHR$(13)+CHR$(13)
          message$+="By left-clicking on the knot, you can re-arrange the strands (symmetry setting can be changed for this)"+CHR$(13)
          message$+="By clicking on the 'Hide Grid' button, you can toggle the square grid on and off."+CHR$(13)
    
          SYS "MessageBox", @hwnd%, message$, "Celtic Knot Help", 0
    
      ENDCASE
      ENDPROC: REM PROCdobuttons
      :
      DEFPROCdo_it
      LOCAL x%,y%,z%,tx%,ty%,tz%
      REM Set up a square graphics window the size of the height of the screen (the rest will be used for controls)
      VDU 24,0;0;2*yres%-1;2*yres%-1;
      CLG
      sq&()=&FF
      FOR x%=0 TO numsqx%-1
        FOR y%=0 TO numsqy%-1
          FOR z%=0 TO 3
            REM each square contains two lines: the bottom one runs top right to bottom left
            REM and the upper one runs TL to BR: these 4 points are indicated by the z value (TL,TR,BR,BL)
            IF sq&(x%,y%,z%)=&FF THEN
              sq&(x%,y%,z%)=RND(3)
              PROCsetpat(sq&(),numsqx%,numsqy%,x%,y%,z%,sym$)
              tx%=numsqx%-x%-1
              ty%=numsqy%- y%-1
              REM deal with symmetry
              CASE sym$ OF
                WHEN "mh"
                  IF z%=0 OR z%=2 THEN tz%=z%+1 ELSE tz%=z%-1
                  sq&(tx%,y%,tz%)=sq&(x%,y%,z%)
                  PROCsetpat(sq&(),numsqx%,numsqy%,tx%,y%,tz%,sym$)
                WHEN "mv"
                  tz%=3-z%
                  sq&(x%,ty%,tz%)=sq&(x%,y%,z%)
                  PROCsetpat(sq&(),numsqx%,numsqy%,x%,ty%,tz%,sym$)
                WHEN "m4"
                  IF z%=0 OR z%=2 THEN tz%=z%+1 ELSE tz%=z%-1
                  sq&(tx%,y%,tz%)=sq&(x%,y%,z%)
                  PROCsetpat(sq&(),numsqx%,numsqy%,tx%,y%,tz%,sym$)
                  tz%=3-z%
                  sq&(x%,ty%,tz%)=sq&(x%,y%,z%)
                  PROCsetpat(sq&(),numsqx%,numsqy%,x%,ty%,tz%,sym$)
                  tz%=(z%+2) MOD 4
                  sq&(tx%,ty%,tz%)=sq&(x%,y%,z%)
                  PROCsetpat(sq&(),numsqx%,numsqy%,tx%,ty%,tz%,sym$)
                WHEN "r2"
                  tz%=(z%+2) MOD 4
                  sq&(tx%,ty%,tz%)=sq&(x%,y%,z%)
                  PROCsetpat(sq&(),numsqx%,numsqy%,tx%,ty%,tz%,sym$)
                WHEN "r4"
                  tz%=(z%+1) MOD 4
                  sq&(y%,tx%,tz%)=4-sq&(x%,y%,z%)
                  PROCsetpat(sq&(),numsqx%,numsqy%,y%,tx%,tz%,sym$)
                  tz%=(z%+2) MOD 4
                  sq&(tx%,ty%,tz%)=sq&(x%,y%,z%)
                  PROCsetpat(sq&(),numsqx%,numsqy%,tx%,ty%,tz%,sym$)
                  tz%=(z%+3) MOD 4
                  sq&(ty%,x%,tz%)=4-sq&(x%,y%,z%)
                  PROCsetpat(sq&(),numsqx%,numsqy%,ty%,x%,tz%,sym$)
              ENDCASE
            ENDIF
          NEXT z%
        NEXT y%
      NEXT x%
      knotdone%=TRUE
      PROCsetcol(sq&(),numsqx%,numsqy%)
      PROCdraw(numsqx%,numsqy%,xres%,yres%)
      ENDPROC
      :
      DEFPROCspreadsym(x%,y%,z%)
      LOCAL tx%,ty%,tz%
      REM this routine handles changes in the knot due to edits, and makes changes appropriately symmetrical
      tx%=numsqx%-x%-1
      ty%=numsqy%- y%-1
      CASE sym$ OF
        WHEN "mh"
          IF z%=0 OR z%=2 THEN tz%=z%+1 ELSE tz%=z%-1
          sq&(tx%,y%,tz%)=sq&(x%,y%,z%)
          PROCsetpat(sq&(),numsqx%,numsqy%,tx%,y%,tz%,sym$)
        WHEN "mv"
          tz%=3-z%
          sq&(x%,ty%,tz%)=sq&(x%,y%,z%)
          PROCsetpat(sq&(),numsqx%,numsqy%,x%,ty%,tz%,sym$)
        WHEN "m4"
          IF z%=0 OR z%=2 THEN tz%=z%+1 ELSE tz%=z%-1
          sq&(tx%,y%,tz%)=sq&(x%,y%,z%)
          PROCsetpat(sq&(),numsqx%,numsqy%,tx%,y%,tz%,sym$)
          tz%=3-z%
          sq&(x%,ty%,tz%)=sq&(x%,y%,z%)
          PROCsetpat(sq&(),numsqx%,numsqy%,x%,ty%,tz%,sym$)
          tz%=(z%+2) MOD 4
          sq&(tx%,ty%,tz%)=sq&(x%,y%,z%)
          PROCsetpat(sq&(),numsqx%,numsqy%,tx%,ty%,tz%,sym$)
        WHEN "r2"
          tz%=(z%+2) MOD 4
          sq&(tx%,ty%,tz%)=sq&(x%,y%,z%)
          PROCsetpat(sq&(),numsqx%,numsqy%,tx%,ty%,tz%,sym$)
        WHEN "r4"
          tz%=(z%+1) MOD 4
          sq&(y%,tx%,tz%)=4-sq&(x%,y%,z%)
          PROCsetpat(sq&(),numsqx%,numsqy%,y%,tx%,tz%,sym$)
          tz%=(z%+2) MOD 4
          sq&(tx%,ty%,tz%)=sq&(x%,y%,z%)
          PROCsetpat(sq&(),numsqx%,numsqy%,tx%,ty%,tz%,sym$)
          tz%=(z%+3) MOD 4
          sq&(ty%,x%,tz%)=4-sq&(x%,y%,z%)
          PROCsetpat(sq&(),numsqx%,numsqy%,ty%,x%,tz%,sym$)
      ENDCASE
      ENDPROC
      :
      DEFPROCsetpat(sq&(),numsqx%,numsqy%,x%,y%,z%,sym$)
      LOCAL nsx%,nsy%
      nsx%=numsqx%-1
      nsy%=numsqy%-1
      REM each corner can have one of 4 types: 0=corner of grid, the others indicate passes through in x, corner, or y directions
      IF x%=0 AND (z%=0 OR z%=3) THEN sq&(x%,y%,z%)=3
      IF x%=nsx% AND (z%=1 OR z%=2) THEN sq&(x%,y%,z%)=3
      IF y%=0 AND (z%=2 OR z%=3) THEN sq&(x%,y%,z%)=1
      IF y%=nsy% AND (z%=0 OR z%=1) THEN sq&(x%,y%,z%)=1
      IF x%=0 AND y%=0 THEN sq&(x%,y%,3)=0
      IF x%=0 AND y%=nsy% THEN sq&(x%,y%,0)=0
      IF x%=nsx% AND y%=0 THEN sq&(x%,y%,2)=0
      IF x%=nsx% AND y%=nsy% THEN sq&(x%,y%,1)=0
      CASE z% OF
        WHEN 0:
          IF y%<nsy% THEN sq&(x%,y%+1,3)=sq&(x%,y%,z%):IF x%>0 THEN sq&(x%-1,y%+1,2)=sq&(x%,y%,z%)
          IF x%>0 THEN sq&(x%-1,y%,1)=sq&(x%,y%,z%)
        WHEN 1:
          IF y%<nsy% THEN sq&(x%,y%+1,2)=sq&(x%,y%,z%):IF x%<nsx% THEN sq&(x%+1,y%+1,3)=sq&(x%,y%,z%)
          IF x%<nsx% THEN sq&(x%+1,y%,0)=sq&(x%,y%,z%)
        WHEN 2:
          IF y%>0 THEN sq&(x%,y%-1,1)=sq&(x%,y%,z%):IF x%<nsx% THEN sq&(x%+1,y%-1,0)=sq&(x%,y%,z%)
          IF x%<nsx% THEN sq&(x%+1,y%,3)=sq&(x%,y%,z%)
        WHEN 3:
          IF y%>0 THEN sq&(x%,y%-1,0)=sq&(x%,y%,z%):IF x%>0 THEN sq&(x%-1,y%-1,1)=sq&(x%,y%,z%)
          IF x%>0 THEN sq&(x%-1,y%,2)=sq&(x%,y%,z%)
      ENDCASE
      ENDPROC
      :
      DEFPROCsetcol(sq&(),numsqx%,numsqy%)
      LOCAL x%,y%,cn%,colbit%,tx%,ty%,nsx%,nsy%
      REM Finds strands which aren't coloured yet, allocates them a colour,
      REM and then follows along that strand to the end, or until it meets itself (using procfollow)
      nsx%=numsqx%-1
      nsy%=numsqy%-1
      cn%=1
      colbit%=(cn%*16) AND &F0
      FOR x%=0 TO nsx%
        FOR y%=0 TO nsy%
          IF sq&(x%,y%,0) DIV 16=0 THEN
            sq&(x%,y%,0)+=colbit%
            REM sq&(x%,y%,2)+=colbit%
            CASE sq&(x%,y%,0) AND 3 OF
              WHEN 1:PROCfollow(sq&(),x%-1,y%,1,colbit%)
              WHEN 2:PROCfollow(sq&(),x%-1,y%+1,2,colbit%)
              WHEN 3:PROCfollow(sq&(),x%,y%+1,3,colbit%)
            ENDCASE
            CASE sq&(x%,y%,2) AND 3 OF
              WHEN 1:PROCfollow(sq&(),x%+1,y%,3,colbit%)
              WHEN 2:PROCfollow(sq&(),x%+1,y%-1,0,colbit%)
              WHEN 3:PROCfollow(sq&(),x%,y%-1,1,colbit%)
            ENDCASE
            cn%+=1
            IF cn%>15 THEN cn%=1
            colbit%=(cn%*16) AND &F0
          ENDIF
          IF sq&(x%,y%,1)DIV 16=0 THEN
            sq&(x%,y%,1)+=colbit%
            REM sq&(x%,y%,3)+=colbit%
            CASE sq&(x%,y%,1) AND 3 OF
              WHEN 1:PROCfollow(sq&(),x%+1,y%,0,colbit%)
              WHEN 2:PROCfollow(sq&(),x%+1,y%+1,3,colbit%)
              WHEN 3:PROCfollow(sq&(),x%,y%+1,2,colbit%)
            ENDCASE
            CASE sq&(x%,y%,3) AND 3 OF
              WHEN 1:PROCfollow(sq&(),x%-1,y%,2,colbit%)
              WHEN 2:PROCfollow(sq&(),x%-1,y%-1,1,colbit%)
              WHEN 3:PROCfollow(sq&(),x%,y%-1,0,colbit%)
            ENDCASE
            cn%+=1
            IF cn%>15 THEN cn%=1
            colbit%=(cn%*16) AND &F0
          ENDIF
          sq&(x%,y%,2)=16*(sq&(x%,y%,0) DIV 16)+(sq&(x%,y%,2) AND 3)
          sq&(x%,y%,3)=16*(sq&(x%,y%,1) DIV 16)+(sq&(x%,y%,3) AND 3)
        NEXT y%
      NEXT x%
      ENDPROC
      :
      DEFPROCfollow(sq&(),x%,y%,z%,colbit%)
      REM OK, here's the bit that actually does the following!
      IF (sq&(x%,y%,z%) AND &F0)<>colbit% THEN
        CASE z% OF
          WHEN 0:
            sq&(x%,y%,0)=colbit%+(sq&(x%,y%,0)AND 3)
            sq&(x%,y%,2)=colbit%+(sq&(x%,y%,2)AND 3)
            CASE sq&(x%,y%,2) AND 3 OF
              WHEN 1:PROCfollow(sq&(),x%+1,y%,3,colbit%)
              WHEN 2:PROCfollow(sq&(),x%+1,y%-1,0,colbit%)
              WHEN 3:PROCfollow(sq&(),x%,y%-1,1,colbit%)
            ENDCASE
          WHEN 1:
            sq&(x%,y%,1)=colbit%+(sq&(x%,y%,1)AND 3)
            sq&(x%,y%,3)=colbit%+(sq&(x%,y%,3)AND 3)
            CASE sq&(x%,y%,3) AND 3 OF
              WHEN 1:PROCfollow(sq&(),x%-1,y%,2,colbit%)
              WHEN 2:PROCfollow(sq&(),x%-1,y%-1,1,colbit%)
              WHEN 3:PROCfollow(sq&(),x%,y%-1,0,colbit%)
            ENDCASE
          WHEN 2:
            sq&(x%,y%,2)=colbit%+(sq&(x%,y%,2)AND 3)
            sq&(x%,y%,0)=colbit%+(sq&(x%,y%,0)AND 3)
            CASE sq&(x%,y%,0) AND 3 OF
              WHEN 1:PROCfollow(sq&(),x%-1,y%,1,colbit%)
              WHEN 2:PROCfollow(sq&(),x%-1,y%+1,2,colbit%)
              WHEN 3:PROCfollow(sq&(),x%,y%+1,3,colbit%)
            ENDCASE
          WHEN 3:
            sq&(x%,y%,1)=colbit%+(sq&(x%,y%,1)AND 3)
            sq&(x%,y%,3)=colbit%+(sq&(x%,y%,3)AND 3)
            CASE sq&(x%,y%,1) AND 3 OF
              WHEN 1:PROCfollow(sq&(),x%+1,y%,0,colbit%)
              WHEN 2:PROCfollow(sq&(),x%+1,y%+1,3,colbit%)
              WHEN 3:PROCfollow(sq&(),x%,y%+1,2,colbit%)
            ENDCASE
        ENDCASE
      ENDIF
      ENDPROC
      :
      DEFPROCdraw(numsqx%,numsqy%,xres%,yres%)
      LOCAL x%,y%,sp%,pen%,dx1%,dy1%,dx2%,dy2%,c1%,c2%,nsx%,nsy%,pts%()
      REM logically enough, draws the knot (and grid if required)
      DIM pts%(3,1)
      sp%=2*yres% DIV (numsqy%+2)
      IF  (2*yres% DIV (numsqx%+2))<sp% THEN sp%=2*yres% DIV (numsqx%+2)
      nsx%=numsqx%-1
      nsy%=numsqy%-1
      CLG
      IF showgrid% THEN
        REM Show the grid of squares on which knot is constructed
        FOR x%=1 TO numsqx%+1
          LINE x%*sp%,sp%,x%*sp%,sp%*(numsqy%+1)
        NEXT x%
        FOR y%=1 TO numsqy%+1
          LINE sp%,y%*sp%,sp%*(numsqx%+1),y%*sp%
        NEXT y%
      ENDIF
      :
      REM For each square, draw 2 lines using Bezier curves (points calculated in PROCsetarray)
      FOR x%=0 TO nsx%
        FOR y%=0 TO nsy%
          CASE sq&(x%,y%,1) AND 3 OF
            WHEN 1: dx1%=sp%:dy1%=3*sp% DIV 4
            WHEN 2: dx1%=sp%:dy1%=sp%
            WHEN 3: dx1%=3*sp% DIV 4:dy1%=sp%
            WHEN 0:dx1%=3*sp% DIV 4:dy1%=3*sp% DIV 4
          ENDCASE
          CASE sq&(x%,y%,3) AND 3 OF
            WHEN 1: dx2%=0:dy2%=sp% DIV 4
            WHEN 2: dx2%=0:dy2%=0
            WHEN 3: dx2%=sp% DIV 4:dy2%=0
            WHEN 0:dx2%=sp% DIV 4:dy2%=sp% DIV 4
          ENDCASE
          PROCsetarray(pts%(),x%,y%,sp%,dx1%,dy1%,dx2%,dy2%)
          c1%=(sq&(x%,y%,1) AND &F0) DIV 16
    
          PROCdrawcurve(c1%,sp%)
          :
          CASE sq&(x%,y%,0) AND 3 OF
            WHEN 1: dx1%=0:dy1%=3*sp% DIV 4
            WHEN 2: dx1%=0:dy1%=sp%
            WHEN 3: dx1%=sp% DIV 4:dy1%=sp%
            WHEN 0:dx1%=sp% DIV 4:dy1%=3*sp% DIV 4
          ENDCASE
          CASE sq&(x%,y%,2) AND 3 OF
            WHEN 1: dx2%=sp%:dy2%=sp% DIV 4
            WHEN 2: dx2%=sp%:dy2%=0
            WHEN 3: dx2%=3*sp% DIV 4:dy2%=0
            WHEN 0:dx2%=3*sp% DIV 4:dy2%=sp% DIV 4
          ENDCASE
          PROCsetarray(pts%(),x%,y%,sp%,dx1%,dy1%,dx2%,dy2%)
          c1%=(sq&(x%,y%,0) AND &F0)  DIV 16
          PROCdrawcurve(c1%,sp%)
        NEXT y%
      NEXT x%
      FOR x%=0 TO nsx%
        FOR y%=0 TO nsy%
          IF (sq&(x%,y%,3) AND 3)=2 THEN
            REM If line goes through diagonals, tidy up top line again
            PROCsetarray(pts%(),x%,y%,sp%,sp% DIV 5,sp% DIV 5,-1*(sp% DIV 5),-1*(sp% DIV 5))
            c1%=(sq&(x%,y%,1) AND &F0) DIV 16
            PROCdrawcurve(c1%,sp%)
          ENDIF
        NEXT y%
      NEXT x%
      SYS "InvalidateRect", @hwnd%, 0, 0
      ENDPROC
      :
      DEFPROCsetarray(pts%(),x%,y%,sp%,dx1%,dy1%,dx2%,dy2%)
      REM set up curve in array pts%
      pts%(0,0)=sp%*(x%+1)+dx1%
      pts%(0,1)=sp%*(y%+1)+dy1%
      CASE dx1% OF
        WHEN 0: pts%(1,0)=pts%(0,0)+sp% DIV 2 :pts%(0,0)-=1
        WHEN sp% DIV 4,3*sp% DIV 4:pts%(1,0)=pts%(0,0)
        WHEN sp%: pts%(1,0)=pts%(0,0)-sp% DIV 2:pts%(0,0)+=1
        WHEN sp% DIV 5:pts%(1,0)=sp%*(x%+1)
      ENDCASE
      CASE dy1% OF
        WHEN 0: pts%(1,1)=pts%(0,1)+sp% DIV 2:pts%(0,1)-=1
        WHEN sp% DIV 4,3*sp% DIV 4:pts%(1,1)=pts%(0,1)
        WHEN sp%: pts%(1,1)=pts%(0,1)-sp% DIV 2:pts%(0,1)+=1
        WHEN sp% DIV 5:pts%(1,1)=sp%*(y%+1)
      ENDCASE
      pts%(3,0)=sp%*(x%+1)+dx2%
      pts%(3,1)=sp%*(y%+1)+dy2%
      CASE dx2% OF
        WHEN 0: pts%(2,0)=pts%(3,0)+sp% DIV 2:pts%(3,0)-=1
        WHEN sp% DIV 4,3*sp% DIV 4:pts%(2,0)=pts%(3,0)
        WHEN sp%: pts%(2,0)=pts%(3,0)-sp% DIV 2:pts%(3,0)+=1
        WHEN -1*(sp% DIV 5):pts%(2,0)=sp%*(x%+1)
      ENDCASE
      CASE dy2% OF
        WHEN 0: pts%(2,1)=pts%(3,1)+sp% DIV 2 :pts%(3,1)-=1
        WHEN sp% DIV 4,3*sp% DIV 4:pts%(2,1)=pts%(3,1)
        WHEN sp%: pts%(2,1)=pts%(3,1)-sp% DIV 2 :pts%(3,1)+=1
        WHEN -1*(sp% DIV 5):pts%(2,1)=sp%*(y%+1)
      ENDCASE
      IF dx1%=0 AND dy1%=0 THEN pts%(0,0)+=sp% DIV 5:pts%(0,1)+=sp% DIV 5
      IF dx1%=sp% AND dy1%=sp% THEN pts%(0,0)-=sp% DIV 5:pts%(0,1)-=sp% DIV 5
      IF dx2%=0 AND dy2%=0 THEN pts%(3,0)+=sp% DIV 5:pts%(3,1)+=sp% DIV 5
      IF dx2%=sp% AND dy2%=sp% THEN pts%(3,0)-=sp% DIV 5:pts%(3,1)-=sp% DIV 5
      ENDPROC  :REM PROCsetarray
      :
      DEFPROCdrawcurve(col%,sp%)
      LOCAL w1%,w2%
      REM Draw Bezier curve using GDIP, and colours set in cols%(col%)
      REM Lines are drawn twice, once in background colour, then again in foreground (but thinner)
      IF cols%(col%,2)=&FFFFFF THEN w1%=sp% DIV 8 ELSE w1%=cols%(col%,2)
      w2%=w1% DIV 2
      IF w2%=0 THEN w2%=1:w1%=3

      pencolour% = &FF000000+cols%(col%,0) : REM. Opaque, colour background to col%
      penstyle% = LineStartFlat + LineEndFlat
      IF ((pts%(3,0)=sp%+(sp% DIV 4)) AND (pts%(3,1)=sp%+(sp% DIV 4))) OR ((pts%(3,0)=sp%*numsqx%+(3*sp% DIV 4)) AND (pts%(3,1)=sp%+(sp% DIV 4))) THEN penstyle%+=LineEndRoundAnchor
      IF ((pts%(0,0)=sp%+(sp% DIV 4)) AND (pts%(0,1)=sp%*numsqy%+(3*sp% DIV 4))) OR ((pts%(0,0)=sp%*numsqx%+(3*sp% DIV 4)) AND (pts%(0,1)=sp%*numsqy%+(3*sp% DIV 4))) THEN penstyle%+=LineStartRoundAnchor
      penwidth = w1%
      pen% = FN_gdipcreatepen(pencolour%, penstyle%, penwidth)
      PROC_gdipbezier(pen%, pts%(0,0),pts%(0,1), pts%(1,0),pts%(1,1),pts%(2,0),pts%(2,1),pts%(3,0),pts%(3,1))
      PROC_gdipdeletepen(pen%)


      pencolour% = &FF000000+cols%(col%,1) : REM. Opaque, foreground colour col%
      penstyle% = LineStartFlat + LineEndFlat
      IF ((pts%(3,0)=sp%+(sp% DIV 4)) AND (pts%(3,1)=sp%+(sp% DIV 4))) OR ((pts%(3,0)=sp%*numsqx%+(3*sp% DIV 4)) AND (pts%(3,1)=sp%+(sp% DIV 4))) THEN penstyle%+=LineEndRoundAnchor
      IF ((pts%(0,0)=sp%+(sp% DIV 4)) AND (pts%(0,1)=sp%*numsqy%+(3*sp% DIV 4))) OR ((pts%(0,0)=sp%*numsqx%+(3*sp% DIV 4)) AND (pts%(0,1)=sp%*numsqy%+(3*sp% DIV 4))) THEN penstyle%+=LineStartRoundAnchor
      penwidth = w2%
      pen% = FN_gdipcreatepen(pencolour%, penstyle%, penwidth)
      PROC_gdipbezier(pen%, pts%(0,0),pts%(0,1), pts%(1,0),pts%(1,1),pts%(2,0),pts%(2,1),pts%(3,0),pts%(3,1))
      PROC_gdipdeletepen(pen%)
      ENDPROC  :REM PROCdrawcurve
      :
      DEFPROCsetupGDI
      INSTALL @lib$+"gdiplib"
      PROC_gdipinit
      LineEndFlat = 0
      LineEndSquare = 1
      LineEndRound = 2
      LineEndTriangle = 3
      LineEndSquareAnchor = &11
      LineEndRoundAnchor = &12
      LineEndDiamond = &13
      LineEndArrow = &14

      LineStartFlat=0
      LineStartSquare = &100
      LineStartRound = &200
      LineStartTriangle = &300
      LineStartSquareAnchor = &1100
      LineStartRoundAnchor = &1200
      LineStartDiamond = &1300
      LineStartArrow = &1400
      ENDPROC: REM PROCsetupGDI
      :
      DEFPROCsetupcons
      INSTALL @lib$+"WINLIB5"

      butw%=130
      buth%=20
      lpos%=xres%-butw%-50
      IF lpos%<yres%+1 THEN lpos%=yres%+1
      hpos%=40 * yres%/512
      hspace%=52 *yres%/512

      *FONT ARIAL 12
      VDU 5
      REM set up edit box for no. of boxes horizontally
      eb1%=FN_editbox("8",lpos%,hpos%,butw%+15,buth%,11,&812000)
      eb1ud%=FN_createwindow("msctls_updown32","",0,0,0,0,12,&96,0)
      SYS "SendMessage", eb1ud%, &469, eb1%, 0 : REM UDM_SETBUDDY
      SYS "SendMessage", eb1ud%, &465, 2, (2 << 16)+maxnumsq% : REM UDM_SETRANGE
      SYS "SendMessage", eb1ud%, &467, 0, numsqx% : REM UDM_SETPOS
      MOVE lpos%*2,(yres%-hpos%)*2+40
      PRINT "Squares horizontally"

      REM set up edit box for no. of boxes vertically
      eb2%=FN_editbox("8",lpos%,hpos%+hspace%,butw%+15,buth%,21,&12000)
      eb2ud%=FN_createwindow("msctls_updown32","",0,0,0,0,22,&96,0)
      SYS "SendMessage", eb2ud%, &469, eb2%, 0 : REM UDM_SETBUDDY
      SYS "SendMessage", eb2ud%, &465, 2, (2 << 16)+maxnumsq% : REM UDM_SETRANGE
      SYS "SendMessage", eb2ud%, &467, 0, numsqy% : REM UDM_SETPOS
      MOVE lpos%*2,(yres%-hpos%-hspace%)*2+40
      PRINT "Squares vertically"

      REM set up list box to control symmetry
      lb1%=FN_combobox("",lpos%,hpos%+hspace%*2,butw%,buth%*7,41,3)
      SYS "SendMessage", lb1%, &143, 0, "None"
      SYS "SendMessage", lb1%, &143, 0, "L/R mirror"
      SYS "SendMessage", lb1%, &143, 0, "T/B mirror"
      SYS "SendMessage", lb1%, &143, 0, "4-fold mirror"
      SYS "SendMessage", lb1%, &143, 0, "Rotation order 2"
      SYS "SendMessage", lb1%, &143, 0, "Rotation order 4"
      SYS "SendMessage", lb1%, &14E, 0,0
      MOVE lpos%*2,(yres%-hpos%-2*hspace%)*2+40
      PRINT "Symmetry"


      pbut%=FN_button("Draw it!",lpos%,hpos%+3*hspace%,butw%,buth%*2,31,&20000)
      IF showgrid% THEN gbut%=FN_button("Hide grid",lpos%,hpos%+4*hspace%,butw%,buth%*2,32,&20000)  ELSE gbut%=FN_button("Show grid",lpos%,hpos%+4*hspace%,butw%,buth%*2,32,&20000)
      REM qbut%=FN_button("Save knot",lpos%,hpos%+5*hspace%,butw%,buth%*2,34,&10000)
      REM qbut%=FN_button("Load knot",lpos%,hpos%+6*hspace%,butw%,buth%*2,35,&10000)
      qbut%=FN_button("Help",lpos%,hpos%+7*hspace%,butw%,buth%*2,36,&10000)
      qbut%=FN_button("Quit",lpos%,hpos%+8*hspace%,butw%,buth%*2,33,&10000)
      VDU4
      ENDPROC:REM PROCsetupcons
      :
      DEFPROCtidycons
      PROC_closewindow(eb1ud%)
      PROC_closewindow(eb1%)
      PROC_closewindow(eb2ud%)
      PROC_closewindow(eb2%)
      PROC_closewindow(pbut%)
      PROC_closewindow(gbut%)
      PROC_closewindow(qbut%)
      ENDPROC
    
David Williams

Re: New anti-aliased graphics library for BBCSDL

Post by David Williams »

DDRM wrote: Mon 02 Sep 2019, 08:45 Here's a cut-down version of a little ditty I did with GDIP some time ago
Beautiful and clever!
David Williams

Re: New anti-aliased graphics library for BBCSDL

Post by David Williams »

DDRM wrote: Mon 02 Sep 2019, 08:14 The only difference I can spot is at the base of the heart, where GDIP gives a fine, extended point, while aagfxlib gives a square butt - unless that's a coding issue? I note that the internal "point" is rounded in both cases.
I raised the same point, and this was Richard's response:

> The aagfxlib version has the point truncated, whereas the GDIPLIB version
> terminates in a sharp-ish point.

Indeed. I prefer the way BBCSDL renders it; the 'sharp point' produced
by GDI+ is an artefact of the way it is mitreing (or, more to the point,
not) the join between two almost parallel lines. That is controllable
using the LineJoin enumeration I think:

https://docs.microsoft.com/en-us/window ... s-linejoin

The BBCSDL library isn't making any attempt to 'join' the lines (I am
assuming, for simplicity, that a PolyBezier will usually be used to
render a smooth curve, not something with an inflection point like a heart).

David.
--
DDRM

Re: New anti-aliased graphics library for BBCSDL

Post by DDRM »

Interesting. Yes, it looks as though you could increase the miter limit if you really wanted that exaggerated point. I'd probably go for a rounded join, as a good compromise, but that's always going to be a personal choice.

Thanks for the kind comments on the celtic knot program. it's quite useful for designing borders etc - especially with the left-right mirroring on, so you know they'll join up. I've started pyrographing knotwork bands on to some of my woodturning. There's a bug in the line scaling, which I introduced when I added the ability to change line widths/colours (disabled in this version) - lines stay the same width no matter the knot size. If this annoys you, go to DEFPROCDrawcurve() and replace

IF cols%(col%,2)=&FFFFFF THEN w1%=sp% DIV 8 ELSE w1%=cols%(col%,2)

with the simplification

w1%=sp% DIV 8

If you change the 8 to a 4, the curves becomes almost space-filling!

You may also wish to alter the next line, setting w2% to a higher fraction of w1% (w1% and w2% gives the background and foreground widths, so if w2% is a bigger fraction the white border behind the colour gets thinner).

You can amend the knot that has been created by left-clicking between curves, near the corner of the grid squares: if you click ON the crossing, you'll get a cross-over junction there. If symmetry is set, that will be preserved (so your change will act elsewhere), but you can switch symmetry to "none", and then make local changes.

Best wishes,

D