3
\$\begingroup\$

I am very interested in inventing the best way possible for rendering bitmaps on-screen using the base drawing functions (putpixel(x, y), lineto(x, y), moveto(x, y), setpen(r, g, b, transp, size) )


You don't need to use graphic library to have them. You can call BIOS interrupt or initialize registers manually to set the video mode to 13h (for example) for 320x200 canvas field and 256-color mode and write from offset segment A000h


I know there are ways to make the rendering faster.

  • If loop iterations are reduced
  • If loop body computations are reduced
  • If the function is smart enough to figure out where to use lineto or larger pen

C89, no std libs, implementation of putpixel/setpen functions (from graphics.h, sdl, cairo etc)

#define OFFSET_OF_ID        (0x0)
#define OFFSET_OF_SIZE      (0x2)
#define OFFSET_OF_PIXELS    (0xA)
#define OFFSET_OF_NDIB      (0xE)
#define OFFSET_OF_WIDTH     (0x12)
#define OFFSET_OF_HEIGHT    (0x16)
#define OFFSET_OF_BPP       (0x1C)
#define OFFSET_OF_NRAW      (0x22)

#define loop(x, y, z) int x; for(; y; z)

typedef const volatile unsigned long data, *pdata;
typedef unsigned char pixel [0x3], (*byte_buffer);

void bmp_render (byte_buffer image)
{
    data image_width            = *(pdata)&image[OFFSET_OF_WIDTH];
    data image_height           = *(pdata)&image[OFFSET_OF_HEIGHT];
    data image_offset_pixels    = *(pdata)&image[OFFSET_OF_PIXELS];
    data image_raw_size         = *(pdata)&image[OFFSET_OF_NRAW];
    data image_row              = image_width * sizeof(pixel);

    unsigned h = 0, w = 0, pad = 0, padding = image_width % 4;

    loop(i = image_offset_pixels, i < image_raw_size, i += sizeof(pixel))
    {
        if(i == image_offset_pixels + (image_row * ((h + 1)) + (pad * h)))
        {
            pad = padding;
            i += pad;
            h += 1;
            w = !w;
        }

        setpen(image[i + 2], image[i + 1], image[i], 0, 1); // setpen(r, g, b, transp, size)
        putpixel(w++, image_height - h); // putpixel(x, y) of the canvas
    }
}
\$\endgroup\$
  • \$\begingroup\$ What's the difference between pad and padding? pad is initially 0, but it's not ever used before it's reset to padding. \$\endgroup\$ – Snowbody Apr 12 '15 at 1:57
  • \$\begingroup\$ It is part of the mechanics. The first if check must find pad to be 0 as the first line of the image has been encountered. This is at least what I was thinking when inventing this way of raster scan long ago. If it makes no difference.. people will mention it in the review anyway. \$\endgroup\$ – Edenia Apr 12 '15 at 2:01
3
\$\begingroup\$

If loop iterations are reduced

Loop iterations can be only lowered in occasions in which you use lineto or/and larger than 1 pixel pen size.

If loop body computations are reduced

The padding computation can be omitted from each iteration. Videlicet the modulo % operator slows down tremendously. Nevertheless with your current approach of raster scan I believe you are combining minimum resources during the loop.

If the function is smart enough to figure out where to use lineto or larger pen

Relates to the first idea for speculative optimization. Idea, that can be achieved along the second idea for optimization. And that is to give RLE-compressed (figuratively) image instead of raw image, which compression determines where to use line, where to use larger pen size or a combination of these two.


I've been through similar cases where optimizing is precious and with that consideration and in this particular circumstance I am only capable of confirming these "theories" as well as share your opinion about how useful that could be. Quite frequently people's first game projects are based on drawing from scratch.. and they have a hard time rendering images with the desired quality.

\$\endgroup\$
2
\$\begingroup\$

This looks like a fun project! Here are some suggestions:

Use Types

I'm looking at the macros you're using at the beginning of the code, and I'm just curious - why don't you create a data type for them? Instead of treating the data as an array of bytes, you could directly access the values you want:

typedef struct bitmap_header {
    short id;
    long long size; // Size of what? In what units?
    long pixel_offset;
    long ndib; // might want a more descriptive name
    long width;
    long height;
    long long bits_per_pixel; // Or whatever "bpp" means
    long raw_size;
} bitmap_header_t;

Then you can do something like this:

void bmp_render (byte_buffer image)
{
    bitmap_header_t* image_header = (bitmap_header_t*)(image);
    data image_width            = image_header->width;
    data image_height           = image_header->height;
    data image_offset_pixels    = image_header->pixel_offset;
    data image_raw_size         = image_header->raw_size;
    data image_row              = image_width * sizeof(pixel);

It will probably also be useful to create an actual type for your pixel data. Something like:

typedef struct pixel {
    unsigned char red;
    unsigned char green;
    unsigned char blue;
} pixel_t;

If you do that, then this line:

setpen(image[i + 2], image[i + 1], image[i], 0, 1);

gets a lot easier to read:

setpen(pixels->red, pixels->green, pixels->blue, 0, 1);

(Note that you'll have to do something like pixel_t* pixels = (pixel_t*)(image + image_offset_pixels);.)

Use Descriptive Names

When I see something like this:

typedef ... unsigned long data ...

I cringe a little. Anything in a computer program could be called data. In this case, I don't understand what it gets you. The various things that you've marked as being of type data don't have anything in common other than that they are all const unsigned longs. Personally, I don't think they warrant a type to themselves. If they did have something in common, it would be good to come up with a name that described what was common to them all.

Don't Be Overly Clever

It appears that in the inner loop, every time you increment h, you want to reset w to 0. It's not immediately obvious to someone reading the code that w = !w will do that. It probably doesn't compile to anything faster than w = 0 (though you could always profile to be sure, or even just examine the assembly output of the compiler).

What does your loop macro do for you that using a regular for loop doesn't? It seems like the x, y, and z parts are the same things you'd have to write in a normal for loop.

Optimizations

One suggestion for an optimization - the right side of the comparison in the if could be calculated only once per line. Basically, before the loop starts, calculate the end of the first line. Then your comparison becomes:

if (i == end_of_next_line)
{
    // ... do other stuff
    end_of_next_line = ... calculate end of next line ...
}

Anyway, I hope that's helpful. As I said, this looks really interesting!

\$\endgroup\$
  • \$\begingroup\$ Thanks for the suggestions. I already finished the entire project (a graphic library) But I remember I also used another trick. The entire area is filled with one call of putpixel but with a larger pen with the color dominant (the color that occurs the most in a bitmap, that is given on buffering) and the loop skips drawing if it gets to such pixel. \$\endgroup\$ – Edenia Jun 26 '15 at 23:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.