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.
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.
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.
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)]
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.
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
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.