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?

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
neeh
  • 2,251
  • 2
  • 21
  • 31
  • Display as in "draw an image" or display as in "print contents of array in a descriptive manner"? –  Feb 09 '14 at 15:16
  • 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. – rullof Feb 09 '14 at 15:17
  • Also, what have you tried... –  Feb 09 '14 at 15:17
  • 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... – neeh Feb 09 '14 at 15:23
  • What operating system are you working with? – user1118321 Feb 09 '14 at 16:03
  • look at [directX](http://en.wikipedia.org/wiki/DirectX) – joey rohan Feb 09 '14 at 16:21
  • 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 ... – Spektre Feb 11 '14 at 09:28
  • 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... – neeh Feb 13 '14 at 14:26
  • 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 onpaint – Spektre Feb 13 '14 at 17:11

1 Answers1

5

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

On Windows you can forget about this also... Here is a small example:

        //==============================================================================
        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

 <!-- language:cpp -->

    //==============================================================================
    //=== 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

 <!-- language: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

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

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Spektre
  • 41,942
  • 8
  • 91
  • 312
  • 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? – neeh Feb 13 '14 at 14:35
  • 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 ...) – Spektre Feb 13 '14 at 17:14
  • 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 ... – Spektre Feb 13 '14 at 17:16