Display an array of color in C [closed]
Asked Answered
C

1

0

My program both writes and reads arrays of colors like this one:

struct Image {
    size_t width;
    size_t height;
    struct Color *data;
}

struct Color {
    char r;
    char g;
    char b;
}

How can I display such an array on the screen in C?

Criticize answered 9/2, 2014 at 15:14 Comment(9)
Display as in "draw an image" or display as in "print contents of array in a descriptive manner"?Bloodstain
C programming doesn't have a standard library for manipulating graphics. There is allot of libraries providing graphics features. You will have to find the one matches your needs.Manton
Also, what have you tried...Bloodstain
display as "draw an image" I've tried SDL2.0 but I need to do double buffering and SDL2.0 doesn't seem to be suitable for that task...Criticize
What operating system are you working with?Ala
look at directXAdeline
why to heck go to complex DirectX for a newbie programmer look for Canvas in help to your IDE or use bitmaps. Which IDE you use ? MSVC++, gcc, borland ? graphics access is specific to IDE/compiler sometimes ...Gurglet
I have to implement a rasterizer so DirectX was not such a bad suggestion I think but indeed I would prefer to work with bitmaps...Criticize
double/back buffering with bitmap is easy ... just before frame clear bitmap render image to it and after is all done copy it to window at once to avoid flickering ... in timer or in some window event like onpaintGurglet
G
6

Graphics rendering:

I am used to win32 and Borland C++ environments, so I stick to it, but the differences on other environments are mostly only in class names. First some approaches:

  1. console/text modes

You can use text graphics (ASCII art I think in English). Where point is represented by character. Intensity is made by more or less filled chars. Usually have a table of characters sorted by intensity like " ..:+*#" and use that instead of colors. For printing out something, you can use iostream, like cout << "text" << endl; or printf from stdio I think (I am not been using old-style console output for more than a decade).

Text modes video RAM (VRAM) starts at 0B000:0000 if you have the privileges for it you can do direct access like this:

    char far *scr = (char far*)0x0B0000000;
    scr[0] = 'A'; // Print A to left upper corner

But on Windows you can forget about direct access.

  1. VGA graphics mode

(DOS only, not Windows; this is doing direct direct access to the VGA hardware). Here is a small example:

 // Turbo C++ for 16-bit real mode DOS
        //==============================================================================
        char far* scr;              // VGA screen
        const _sx= 320;             // Physical screen size
        const _sy= 200;
        //==============================================================================
        void gfxinit();
        void cls();
        void pnt(int x,int y,char c);
        //==============================================================================
        void gfxinit()
            {
            asm {   mov ax,19               // This switches  VGA to 320*200*256 color mode (fits inside a single 64 KB segment so no funny stuff is needed)
                int 16
                }
            for (int i=0;i<256;i++) asm { // This overwrites 256 color palette with some BW gradients
                mov dx,0x3C8
                mov ax,i
                out dx,al              // Overwrite color al = i 
                inc dx
                shr al,2               // al=al>>2
                out dx,al              // r,g,b or b,g,r not sure now 
                out dx,al              // All values are 6-bit long, therefore the shr al,2 
                out dx,al
                }
            scr=(char far*)0xA0000000;     // VRAM start address
            }
        //==============================================================================
        void cls()   // This clears the screen with zeros
            {
            asm {   push    es
                mov ax,0xA000
                mov es,ax
                mov di,0x0000
                sub ax,ax
                mov cx,32000
                rep stosw
                pop es
                }
            }
        //==============================================================================
        void pnt(int x,int y,char c) // This draws a single point of color c
            {
            unsigned int adr;
            if (x<_sx)
             if (x>=0)
              if (y<_sy)
               if (y>=0)
                {
                y=y*_sx;
                adr=x+y;
                scr[adr]=c;
                }
            }
        //==============================================================================

VESA access is similar, but you have to deal with segment crossing and paging. Here is a small Turbo C++ example:

VESA.h

// Turbo C++, still 16-bit DOS, 
// but using VESA calls to set modes instead of VGA registers
    //==============================================================================
    //=== Globals: =================================================================
    //==============================================================================
    char far* scr=(char far*)0xA0000000;    // VGA/VESA memory pointer
    int VESA_page,VESA_pages;       // Actual page and total pages
    int VESA_xs,VESA_ys,VESA_bpp;       // Video mode properties
    int VESA_page_xy[64]={-1,-1};       // Starting x,y for each page
    const int VESAmodes[]=          // Usable video modes table
        {
         320, 200, 8,0x150,
         640, 480, 8,0x101,
         800, 600, 8,0x103,
        1024, 768, 8,0x105,
        1280,1024, 8,0x107,
    
         320, 200,16,0x10E,
         640, 480,16,0x111,
         800, 600,16,0x114,
        1024, 768,16,0x117,
    
         320, 200,32,0x10F,
         640, 480,32,0x112,
         800, 600,32,0x115,
    
        0,0,0,0
        };
    //==============================================================================
    //=== Headers: =================================================================
    //==============================================================================
    int  VESAmode(int xs,int ys,int bpp);   // Set video mode
    void VESApage(int page);        // Set page
    void VESAexit();            // Return to VGA text mode
    void VESAcls();             // Clear with 0
    void VESApnt(int x,int y,unsigned int c); // Render 8/16 bpp point
    void VESApnt32(int x,int y,int r,int g ,int b); // render 32bpp point
    //==============================================================================
    //=== Graphic: =================================================================
    //==============================================================================
    int VESAmode(int xs,int ys,int bpp)
        {
        int i,mode,x,y;
        unsigned int adr0,adr,dx,dy;
        // find video mode
        for (i=0;VESAmodes[i];i+=4)
         if (VESAmodes[i+0]==xs)
          if (VESAmodes[i+1]==ys)
           if (VESAmodes[i+2]==bpp)
            break;
        if (!VESAmodes[i]) return 0;
        mode=VESAmodes[i+3];
        VESA_xs=xs;
        VESA_ys=ys;
        VESA_bpp=bpp;

        // Compute start x,y for each page>0
        dx=bpp>>3;
        dy=xs*dx;
        VESA_pages=1;
        for (adr=i=x=y=0;y<VESA_ys;y++)
            {
            adr0=adr;
            adr+=dy;
            if (adr0>adr)
                {
                while (adr>0) { adr-=dx; x--; }
                while (x<0) { x+=VESA_xs; y--; }
                VESA_page_xy[i]=x; i++;
                VESA_page_xy[i]=y+1; i++;
                VESA_pages++;
                }
            }
        VESA_page_xy[i]=-1; i++;
        VESA_page_xy[i]=-1; i++;
    
    // Set video mode
        asm {
            mov bx,mode
            mov ax,0x4F02
            int 16
            }
        VESApage(0);
    /*
        // Set palette to grayscale
        if (VESAbpp==8)
         for (int i=0;i<256;i++) asm {
            mov dx,0x3C8
            mov ax,i
            out dx,al
            inc dx
            shr al,2
            out dx,al
            out dx,al
            out dx,al
            }
    */
        return 1;
        }
    //==============================================================================
    void VESApage(int page)
        {
        int p=page;
        asm {
            mov dx,p
            mov bx,0
            mov ax,0x4f05
            int 16
            }
        VESA_page=page;
        }
    //==============================================================================
    void VESAexit()
        {
        asm     {
            // Wait for key press
            mov ax,0
            int 0x16
            // VGA 80x25 text mode
            mov ax,3
            int 16
            }
        }
    //==============================================================================
    void VESAcls()
        {
        int i;
        for (i=0;i<VESA_pages;i++)
            {
            VESApage(i);
            asm     {
                push es
                mov ax,0xA000
                mov es,ax
                mov di,0x0000
                mov ax,0
                mov cx,32000
                rep stosw
                pop es
                }
            }
        }
    //==============================================================================
    void VESApnt(int x,int y,unsigned int c)
        {
        unsigned int adr;
        int p;
        // inside screen?
        if ((x>=0)&&(x<VESA_xs))
         if ((y>=0)&&(y<VESA_ys))
            {
            // Low 16 bit of address
            adr=y;
            adr*=VESA_xs;
            adr+=x;
            adr*=(VESA_bpp>>3);
            // Page
            for (p=0;VESA_page_xy[p+p+0]>=0;p++)
                {
                if (VESA_page_xy[p+p+1]>y) break;
                if (VESA_page_xy[p+p+1]<y) continue;
                if (VESA_page_xy[p+p+0]>x) break;
                }
            if (p!=VESA_page) VESApage(p);
            // Render
            scr[adr]=c;
            if (VESA_bpp==16)
                {
                adr++; if (adr==0) VESApage(p+1);
                scr[adr]=(c>>8);
                }
            }
        }
    //==============================================================================
    void VESApnt32(int x,int y,int r,int g ,int b)
        {
        unsigned int adr;
        int p;
        // inside screen?
        if ((x>=0)&&(x<VESA_xs))
         if ((y>=0)&&(y<VESA_ys))
            {
            // Low 16 bit of address
            adr=y;
            adr*=VESA_xs;
            adr+=x;
            adr*=(VESA_bpp>>3);
            // Page
            for (p=0;VESA_page_xy[p+p+0]>=0;p++)
                {
                if (VESA_page_xy[p+p+1]>y) break;
                if (VESA_page_xy[p+p+1]<y) continue;
                if (VESA_page_xy[p+p+0]>x) break;
                }
            if (p!=VESA_page) VESApage(p);
            // Render
            scr[adr]=b; adr++; if (adr==0) VESApage(p+1);
            scr[adr]=g; adr++; if (adr==0) VESApage(p+1);
            scr[adr]=r;
            }
        }
    //==============================================================================
    //=== End. =====================================================================
    //==============================================================================

main.cpp

    //==============================================================================
    //=== Includes: ================================================================
    //==============================================================================
    #include "vesa.h"
    //==============================================================================
    //=== Main: ====================================================================
    //==============================================================================
    void main()
        {
        if (!VESAmode(800,600,32)) return;
        VESAcls();
        int x,y;
        unsigned int c;
        for (y=0;y<VESA_ys;y++)
         for (x=0;x<VESA_xs;x++)
            {
            if (VESA_bpp== 8)
                {
                c=x+y;
                VESApnt(x,y,c);
                }
            if (VESA_bpp==16)
                {
                c=(x&31)+((y&63)<<5);
                VESApnt(x,y,c);
                }
            if (VESA_bpp==32) VESApnt32(x,y,x,x+y,y);
            }
    
        VESAexit();
        }
    //==============================================================================
    //=== End. =====================================================================
    //==============================================================================
  1. GDI - usable on Windows

Canvas is graphic subcomponent of visual components on Windows. In Borland is the class TCanvas named Canvas. All windows has it also, PaintBoxes, Bitmaps, .... It is the GDI interface between Windows and your application. It has subcomponents like Pen, Brush, and Font for lines, fills or text paper, texts ink.

    Form1->Canvas->Pen->Color=clYellow;
    Form1->Canvas->MoveTo(10,10);
    Form1->Canvas->LineTo(100,150);

where Form1 is my VCL window. This code draws a yellow line.

GDI has many functions like Arc, Ellipse, Pixels[][],.... See the built-in help of your IDE for more information.

  1. GDI Bitmap

This is a special object. It is a bitmap with an OS graphic handle (DC device context). This allows a bitmap to be something like a window and have access to GDI:

    Graphics::TBitmap *bmp=new Graphics::TBitmap;
    bmp->Width=100;
    bmp->Height=100;
    bmp->HandleType=bmDIB;    // Allows use of ScanLine
    bmp->PixelFormat=pf32bit; // 32-bit - the same as int so we can use int* for pixels pointer

This creates a VCL bitmap and sets it to 100x100x32 bit with direct access. Now you can access the ScanLine property. Also bmp->Canvas is present, so you can do all GDI stuff too.

    int *p=bmp->ScanLine[10]; // p = pointer to y=10 line of bitmap
    p[20]=0;                    // Draw dot on x=20,y=10   color=0x00000000 which is black
    int c = p[15];              // Read pixel x=15,y=10 from bitmap to c

Be careful to stay with x,y inside a bitmap or an exception will be thrown. The color coding depends on pixelformat, and usually it is 0x00RRGGBB or 0x00BBGGRR. I think this approach is the best option for you. Also, you can draw any GDI object to any other GDI object:

    Form1->Canvas->Draw(0, 0, bmp);

This draws your bitmap to the window, so you can see it actually.

  1. Graphics library

There are many, but the most used are OpenGL and DirectX. I prefer OpenGL, because it is simpler to implement (at least for starters) and also OpenGL is cross-platform and DirectX is Windows only. Also when I started coding there wasn't any DirecX. When I started using OpenGL all vendors had it included in the drivers. Now the only vendors which are still up to date are Nvidia and ATI (AMD). There is almost always some driver issue between them, but in general Nvidia is better for OpenGL (has bugs in the DirectX implementation) and ATI (AMD versions only) is better for DirectX (it has bugs in the OpenGL implementation). But for basic operations you are fine (problems gets on more advanced functions).

Vendors like Intel, SiS, etc. have stopped their implementations on newer OpenGL versions. At least, I do not know of any driver better than OpenGL 3.3 for them.

To get started with OpenGL, see OpenGL get Device Context.

I strongly recommend to start with GDI + Bitmap first. You can do a lot with them. I am still using it for non-complex rendering.

As mentioned before, I am Borland (VCL style) friendly, so if you use different compiler/IDE then change the GDI object names to correspond your environment. I think Canvas is the same and bitmap is HBitmap, but better check your help/documentation. At least you know what to search for.

Other platforms and stuff

Gurglet answered 11/2, 2014 at 10:21 Comment(11)
Hi Spektre, thank you for this complete answer! I'm used to OpenGLES 2.0. But I have to implement a software renderer. Do you think bitmaps are the most suitable for rasterization?Criticize
yes bitmaps are the easiest way ,... and with propper scanline property use is also efficient (get 70fps on my software 3D render supporting phong textures reflections ...)Gurglet
would be awesome if someone adds something like my answer for MSVC++ gcc and so on ... to have all basic rendering approaches at one site simple and clear ...Gurglet
On Windows you can forget about this also... Here is a small example: - I thought that was going to be an example of "forgetting about it" and doing something different on Windows. But it's using out instructions, so it is doing direct HW access. It would benefit from a comment at the top of the code block like // Turbo C++, for 16-bit DOS with VGA. (If that's accurate.)Bursary
@PeterCordes feel free to edit it in ... however the out is just to set the palette you can ommit it ... IIRC on earlier windows (9x this worked without problems)Gurglet
@PeterCordes btw if you use DLLPortIO driver you can use direct VRAM access and in/out instructions even on XP W2K and W7 ... there where also password protected version of DLLPortIO (usually to make LPT usable in Windows again after MS screw it up) and still be "safe" from hack from outside as raw DLLPortIO is a huge security hole granting full HW access to anything that opens its filenameGurglet
Interesting. On Windows, do you mean in a native 32-bit Windows executable? (Not a virtualized 16-bit environment with emulated VGA hardware? Like somewhat more modern 32-bit Windows would do with NTDVM I think.) That code block itself will clearly only work in real-mode DOS, though: cls() uses mov es, ax to set ES base = VGA base, and wouldn't work in (32 or 16-bit) protected mode, and using a 16-bit address in DI instead of EDI would also not be safe.Bursary
anywhere where you can use VGA BIOS to set up the videomode .... unless you do it yourself with VGA register access ... so even 32bit or 64 bit code is possible... However these days there are usually No VGA just emulated crap so in such case virtualized 16bit apps only ...Gurglet
(I fortunately never had to use Win9x as my main desktop (except for one summer job), just Linux, so I'm only familiar with the DOS stuff from the tons of Stack Overflow questions about obsolete DOS crap.)Bursary
I mean yeah you can ask the OS for ioperm / iopl privileges to let you access IO registers, but actually changing the VGA mode is likely to confuse most GUIs! I guess if you can get the GUI (X11 server, or Windows desktop) to keep its hands off of video memory while you're doing something full-screen, you could put it back when you're done.Bursary
Re: emulated VGA: perhaps less totally virtual than you might think. Modern PC video hardware apparently still have true hardware support for text mode, not slow trapping to SMM like I thought might be the case: Does modern PC video hardware support VGA text mode in HW, or does the BIOS emulate it (with System Management Mode)?Bursary

© 2022 - 2024 — McMap. All rights reserved.