Question

I'm working with Vis. Std 2010 Cpp and I've been trying to figure out how to create a bitmap from two existing bitmaps.

I don't really want to use two gl_drawbitmap functions, one for each, but rather create a third bitmap and gl_drawbitmap THAT new one out onto my window. I also am attempting to get the second bitmap(let's call it b2) to be placed in the middle of the first(b1), so the pseudo code would look kind of like: (assuming b2 is lets say 80 by 60)

place b2 on b1 stretched from 
(width-wise middle of b1 -40, heightwise middle of b1-30) 
to (widthwise middle of b1+40, heightwise middle of b1+30)

I know of glutGet(GLUT_WINDOW_WIDTH/HEIGHT) to find a current WINDOW's specific measurements, but I don't know how to find it for a bitmap. I haven't been able to find proof that they are viable, but would b1.getHeight() and b1.getWidth() do the trick?

I'm just overall having plenty of trouble figuring out how to do the actual COMBINATION of the two.

void drawbitmap(bitmap* b1, bitmap* b2)
{
...
}

Function related, it's what I'm trying to put the code in.

Was it helpful?

Solution

Whatever function you have that loads the image, should give you two things: One is the image info, such as width and height. Another is the image data, which is an array of numbers.

Let's say you get images b1 and b2, b1 and b2 being arrays of GLubyte. And let's say b1_w, b1_h, b2_w and b2_h are the width and heights of b1 and b2 (which should be provided to you by the bitmap load function).

What you want to do consists of two parts, first, you need to stretch b1. Second, you need to write it in the middle of b2. In fact you can do both at the same time, but for now, I will keep them separated.

Stretching

To stretch an image, you need to be able to do some simple math. Before getting to the map though, I will tell you the basic idea in image transformation (rotation, stretch etc). The simplest thing you can do is to go through the pixels of the target image, transform them back to the original picture, and decide what color to take from the original picture and assign to that pixel.

For example, if you want to stretch an image from width and height w1 and h1 to w2 and h2, here's the transformation:

xt = w2/w1*xs
yt = h2/h1*ys

where xt and yt are coordinates in the target image and xs and ys are coordinates in the source image. The inverse transformation is:

xs = w1/w2*xt
ys = h1/h2*yt

Before continuing, let's define a macro to make life easier. Since the array of data is a 1d array, but we use x and y indices, we write a macro to do the conversion for us.

#define INDEX(x, y, width) (((y)*(width)+(x))*3)

(Sidenote: Note that I assumed the image is stored in RGB (or BGR), that is it has three values per pixel. Also note that if your image width is not a multiple of 4, then there would be padding and each row width will not be width*3 bytes, but would be width*3+something_to_make_it_divisible_by_4. In the second case, you could write the macro like this:

#define INDEX(x, y, row_bytes) ((y)*(row_bytes)+(x)*3) // note the parentheses

)

So your code would look like this (all the width/height and x/y values are float unless otherwise specified)

for (unsigned int yt = 0; yt < h2; ++yt)
    for (unsigned int xt = 0; xt < w2; ++xt)
    {
        float xs = w1/w2*xt;
        float ys = h1/h2*yt;
        target[INDEX(xt, yt, w2)] = // COMPUTED IN THE NEXT STEP
        target[INDEX(xt, yt, w2)+1] = // COMPUTED IN THE NEXT STEP
        target[INDEX(xt, yt, w2)+2] = // COMPUTED IN THE NEXT STEP
    }

There are three value assignments, because there are three color components (R, G and B). If you have alpha two, make it 4. If you are working in grayscale, keep it 1 (you get the idea)

Next step is the actual assignment of the pixel color. This is where different stretching algorithms vary and the quality of your stretching is dependent on this step.

So, here's the idea. Imagine xs and ys to turn out to be perfect rounded integers. Well that's easy, you just pick the color of that pixel in the source image and put it in the target. BUT, what if xs and ys are not integers? Then which color would you take for the target pixel? Here are two common methods.

Nearest Neighbor

This one is easy. It says, choose the color of the pixel that is closest to the location you computed. For example, if (xs, ys) was (12.78, 98.2), you choose the color of the point (13, 98). That is in your code you have:

unsigned int xs_int = (unsigned int)round(xs);
unsigned int ys_int = (unsigned int)round(ys);
target[INDEX(xt, yt, w2)] = source[INDEX(xs_int, ys_int, w1)]

Linearization

This method needs a bit of math. What it says is, you take the linearized value in between the four pixels around (xs, ys) and select that as the target pixel's color. How is that? I'll show you. Imagine these four pixels and the computed (xs, ys)

v_tl         v_tr
+------------+
|            |
|  .(xs, ys) |
|            |
|            |
|            |
+------------+
v_bl         v_br

The values of the four surrounding pixels are v_{t,b}{l,r} (abbreviations of top, bottom, left and right) as shown in the picture. To linearize the value for position (xs, ys), you could either get the linearized value of (floor(xs), ys) and (ceil(xs), ys) and again linearize for that, or do the same first over y then x which is the same. So you get:

unsigned int x_bl = (unsigned int)floor(xs);
unsigned int y_bl = (unsigned int)floor(ys);
float v_l = v_bl+(v_tl-v_bl)*(ys-y_bl); // (floor(xs), ys)
    // note that in the end, there must be a (x_tl-x_bl) but this value is 1
float v_r = v_br+(v_tr-v_br)*(ys-y_bl); // (ceil(xs), ys)
float v = v_l+(v_r-v_l)*(xs-x_bl);

Finally, you get

target[INDEX(xt, yt, w2)] = v;

Note that you should do this linearization for every channel of the image (R, G and B for example) and put the appropriate v in the appropriate index (INDEX(...), INDEX(...)+1 and INDEX(...)+2 for example).

Note that in both methods you MUST check for the computed indices in the source image so that you wouldn't go out of the array bounds. If you do, you can assume a color of black/white/midgray for those pixels that go outside the array bounds. Or, you can check for those and if the pixel was out of the array, you change your method to only work with the ones that are inside the source image.

Writing over the other image

This part is simple. You have the image stretched_b2 and you want to write it on b1. If the bottom left position where you want to write stretched_b2 in b1 is (x_start, y_start), you simply write:

for (unsigned int y = 0; y < h2_stretched; ++y)
    for (unsigned int x = 0; x < w2_stretched; ++x)
    {
        b1[INDEX(x_start+x, y_start+y, w1)] =
            stretched_b2[INDEX(x, y, w2_stretched)];
        b1[INDEX(x_start+x, y_start+y, w1)+1] =
            stretched_b2[INDEX(x, y, w2_stretched)+1];
        b1[INDEX(x_start+x, y_start+y, w1)+2] =
            stretched_b2[INDEX(x, y, w2_stretched)+2];
    }

You want to place the image here:

(width-wise middle of b1 -40, heightwise middle of b1-30) to (widthwise middle of b1+40, heightwise middle of b1+30)

so easily, you have

w2_stretched = 80        // or +1 if you want to be really precise,
                         // but for now let's say 80 to keep it a multiple of 4
h2_stretched = 60
x_start = w1/2-40
y_start = h1/2-30

Combining the two parts

Now remember in the stretching section, the part where you had

for (unsigned int yt = 0; yt < h2; ++yt)
    for (unsigned int xt = 0; xt < w2; ++xt)

and in the placing section that you had

for (unsigned int y = 0; y < h2_stretched; ++y)
    for (unsigned int x = 0; x < w2_stretched; ++x)

Well in the stretching section h2 and w2 were the height and width of the stretched target image, remember? That's what I called h2_stretched and w2_stretched in the placing section. So as you can see, these for loops iterate the same thing. Now in the stretching section, you compute the value of the target image, and in the placing section you copy it over b1. Well, instead of keeping it in a temporary target image, you can directly compute the value and write it over b1, so you'd have:

for (unsigned int yt = 0; yt < h2; ++yt)
    for (unsigned int xt = 0; xt < w2; ++xt)
    {
        float xs = w1/w2*xt;
        float ys = h1/h2*yt;
        b1[INDEX(x_start+xt, y_start+yt, w1)] = compute_with_mentioned_methods;
        b1[INDEX(x_start+xt, y_start+yt, w1)+1] = compute_with_mentioned_methods;
        b1[INDEX(x_start+xt, y_start+yt, w1)+2] = compute_with_mentioned_methods;
    }

Final note: Image manipulation is although trivial when you say it, has a lot of index checking and it's hard to get it right the first time. Be ready for segmentation faults and don't feel upset if it took you a while to debug it.

Post-final note: I explained these for you to learn, but with OpenGL, if I were you I would turn both images into textures, draw QUADS in appropriate positions and have OpenGL stretch the image for you.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top