GP2X Sdl Bitmap Scaler


Joined
Dec 1, 2006
Messages
948
Location
Scotland
Website
Visit site
I wrote this for my GBAX 2007 entry and thought it might be useful to someone.

It takes a bitmap and scales it to a user specified size.
The function prototype looks like this:
CODE
void bmscaler( int top_left_x, int top_left_y,
int bottom_right_x, int bottom_right_y,
int source_width, int source_height,
SDL_Surface *source, SDL_Surface *dest);


The top left x/y and bottom right x/y specify the size and position you want the bitmap scaled to. So if you specified 0,0,10,10 you would get a 10x10 pixel version of your original bitmap at position 0,0 on the destination SDL surface. Specifying 20,20,30,30 would give you a 10x10 at 20,20.

Be aware it doesn't clip the scaled bitmap so if it is outside the SDL surface you'll probably get a lock-up or crash.

source_width and source_height specify the size of the source in pixels.

SDL_Surface *source and SDL_Surface *dest and the source and destination.

So a call of bmscaler(0,0,200,200,100,100,pacman_graphic,screen) will upscale.
Conversely bmscaler(0,0,50,50,100,100,pacman_graphic,screen) will downscale.

Black is transparent.

Heres the full routine. Sorry in advance for the not very logical varible names - I tried originally a different way of scaling and those vars a legacy from that failed attempt (well not failed - just very slow)

CODE
void bmscaler(
int top_left_x, int top_left_y,
int bottom_right_x, int bottom_right_y,
int source_width, int source_height,
SDL_Surface *source, SDL_Surface *dest)
{
int destination_width,destination_height;

destination_width=bottom_right_x-top_left_x;
destination_height=bottom_right_y-top_left_y;
int screen_x=top_left_x;
int screen_y=top_left_y;

SDL_LockSurface(source);
SDL_LockSurface(dest);

Uint32 pixel_full;

pixel_full=(Uint32)source->pixels;
int source_pitch=source->pitch;

int dest_pitch=dest->pitch;
int dest_pixels=(int)dest->pixels + screen_y * dest_pitch + screen_x * 2;

int old_dest=dest_pixels;
Uint32 old_source=pixel_full;

Uint32 xratio= ((Uint32)source_width << 16) /(bottom_right_x-top_left_x);
Uint32 yratio= ((Uint32)source_height << 16)/(bottom_right_y-top_left_y);

SDL_UnlockSurface(dest);
SDL_UnlockSurface(source);

Uint32 ypos=0;
for(int i=screen_y;i<screen_y+destination_height;i++)
{
Uint32 xpos=0, yoff=(ypos>>16)*source_pitch;
for(int j=screen_x;j<screen_x+destination_width;j++)
{
Uint16 a=*(Uint16 *)(pixel_full+((xpos>>15) & 65534) + yoff);
if (a!=0) *(Uint16 *)(dest_pixels)=a;
xpos+=xratio;
dest_pixels++;dest_pixels++;
}
dest_pixels=old_dest; dest_pixels+=dest_pitch; old_dest=dest_pixels;
ypos+=yratio;
}
}


Its reasonably fast but could probably be optimized quite a bit.
Feel free to use it in your projects.
 
Had a look at SDL_gfx rotozoom and Im sure this is faster if you just want to scale a bitmap.

The rotozoom is nice for pre-rendering. This is intended for realtime use.
 
You should really move the SDL_UnlockSurface() calls to the end of the function as you are still modifying the surface. Reading from / writing to the bitmap needs to be inside the Lock to prevent other threads and the blitter from reading/writing at the same time.
 
CODE
void bmscaler(
int top_left_x, int top_left_y,
int bottom_right_x, int bottom_right_y,
int source_width, int source_height,
SDL_Surface *source, SDL_Surface *dest)
{
int destination_width,destination_height;

destination_width=bottom_right_x-top_left_x;
destination_height=bottom_right_y-top_left_y;
int screen_x=top_left_x;
int screen_y=top_left_y;

SDL_LockSurface(source);
SDL_LockSurface(dest);

Uint32 pixel_full;

pixel_full=(Uint32)source->pixels;
int source_pitch=source->pitch;

int dest_pitch=dest->pitch;
int dest_pixels=(int)dest->pixels + screen_y * dest_pitch + screen_x * 2;

int old_dest=dest_pixels;
Uint32 old_source=pixel_full;

Uint32 xratio= ((Uint32)source_width << 16) /(bottom_right_x-top_left_x);
Uint32 yratio= ((Uint32)source_height << 16)/(bottom_right_y-top_left_y);

Uint32 ypos=0;
for(int i=screen_y;i<screen_y+destination_height;i++)
{
Uint32 xpos=0, yoff=(ypos>>16)*source_pitch;
for(int j=screen_x;j<screen_x+destination_width;j++)
{
Uint16 a=*(Uint16 *)(pixel_full+((xpos>>15) & 65534) + yoff);
if (a!=0) *(Uint16 *)(dest_pixels)=a;
xpos+=xratio;
dest_pixels++;dest_pixels++;
}
dest_pixels=old_dest; dest_pixels+=dest_pitch; old_dest=dest_pixels;
ypos+=yratio;
}

SDL_UnlockSurface(dest);
SDL_UnlockSurface(source);
}
 
Ill leave you to work out why this version is a bit more flexible.

Ill post a full explanation later.

CODE
void bmscaler(int top_left_x, int top_left_y,
int bottom_right_x, int bottom_right_y,
int top_left_x_source, int top_left_y_source,
int bottom_right_x_source, int bottom_right_y_source,
SDL_Surface *source, SDL_Surface *dest)
{
int destination_width=bottom_right_x-top_left_x;
int destination_height=bottom_right_y-top_left_y;

int source_width=bottom_right_x_source-top_left_x_source;
int source_height=bottom_right_y_source-top_left_y_source;

SDL_LockSurface(source);
SDL_LockSurface(dest);

int source_pitch=source->pitch;
int dest_pitch=dest->pitch;

Uint32 pixel_full=(Uint32)source->pixels + top_left_y_source * source_pitch + top_left_x_source * 2;
int dest_pixels=(int)dest->pixels + top_left_y * dest_pitch + top_left_x * 2;

int old_dest=dest_pixels;
Uint32 old_source=pixel_full;

Uint32 xratio= ((Uint32)source_width << 16) /(bottom_right_x-top_left_x);
Uint32 yratio= ((Uint32)source_height << 16)/(bottom_right_y-top_left_y);

Uint32 ypos=0;
for(int i=top_left_y;i<top_left_y+destination_height;i++)
{
Uint32 xpos=0, yoff=(ypos>>16)*source_pitch;
for(int j=top_left_x;j<top_left_x+destination_width;j++)
{
Uint16 a=*(Uint16 *)(pixel_full+((xpos>>15) & 65534) + yoff);
if (a!=0) *(Uint16 *)(dest_pixels)=a;
xpos+=xratio;
dest_pixels++;dest_pixels++;
}
dest_pixels=old_dest; dest_pixels+=dest_pitch; old_dest=dest_pixels;
ypos+=yratio;
}

SDL_UnlockSurface(dest);
SDL_UnlockSurface(source);

}
 
will that work on any surface size rather than the entire screen? i don't know i just kinda skimmed. but you're already only drawing to part of the screen, so i dunno.
 
@roc : yup, any surface size.

@Goobers : again yes. Instead of source_width & source height you specify the top left x,y and bottom right x,y of the section of the source you want to scale.

BTW Goobers hows the mode 7 scaler coming along? Im asking because if your going to do this and release it to the community Im going to try writing a voxel engine instead of doing my own mode 7 scaler. Ill release the voxel engine to the community if I ever finish it.
 
Thanks! I snatched it! I will try it as soon as I need some scaling! Why not change the parameter more SDL styl e with two SDL_Rect for dest and source? The Kids here would love that! Thank you for sharing anway, that's the spirit! Keep up the good work!
 
So, here it is. Now it looks more like SDL, I removed some un-needed vars and added a bunch of asserts. I also made most things const hoping the compiler can optimize the shit out of it. Feel free to add more!

CODE

#include <SDL/SDL.h>
#include <assert.h>

blitSurfaceScaled(SDL_Surface* src, SDL_Rect* srcRect, SDL_Surface* dest, SDL_Rect* destRec )
{
// debgug; we don't like crashes
assert(src);
assert(srcRect);
assert(dest);
assert(destRec);
assert(destRec->w > 0);
assert(destRec->h > 0);

SDL_LockSurface(src);
SDL_LockSurface(dest);

const int destination_width = destRec->w;
const int destination_height = destRec->h;
const int top_left_x = destRec->x;
const int top_left_y = destRec->y;
const int bottom_right_x = top_left_x + destination_width;
const int bottom_right_y = top_left_y + destination_height;

const int source_width = srcRect->w;
const int source_height = srcRect->h;
const int top_left_y_source = srcRect->y;
const int top_left_x_source = srcRect->x;

const Uint32 xratio = ((Uint32)source_width << 16) /(bottom_right_x-top_left_x);
const Uint32 yratio = ((Uint32)source_height << 16)/(bottom_right_y-top_left_y);

const int source_pitch = src->pitch;
const int dest_pitch = dest->pitch;

const Uint32 pixel_full = (Uint32)src->pixels + top_left_y_source * source_pitch + top_left_x_source * 2;

int dest_pixels = (int)dest->pixels + top_left_y * dest_pitch + top_left_x * 2;
int old_dest = dest_pixels;
Uint32 ypos = 0;

for(int i=top_left_y;i<top_left_y+destination_height;i++)
{
Uint32 xpos=0, yoff=(ypos>>16)*source_pitch;
for(int j=top_left_x;j<top_left_x+destination_width;j++)
{
Uint16 a=*(Uint16 *)(pixel_full+((xpos>>15) & 65534) + yoff);
if (a!=0) *(Uint16 *)(dest_pixels)=a;
xpos+=xratio;
dest_pixels++;dest_pixels++;
}
dest_pixels=old_dest; dest_pixels+=dest_pitch; old_dest=dest_pixels;
ypos+=yratio;
}

SDL_UnlockSurface(dest);
SDL_UnlockSurface(src);
}
 
Unfathomable Depths said:
@roc : yup, any surface size.

@goobers : again yes. Instead of source_width & source height you specify the top left x,y and bottom right x,y of the section of the source you want to scale.

BTW Goobers hows the mode 7 scaler coming along? Im asking because if your going to do this and release it to the community Im going to try writing a voxel engine instead of doing my own mode 7 scaler. Ill release the voxel engine to the community if I ever finish it.

I'm still working on it at the moment... I'm looking at some different ways of implementing it to see which is fastest, but please, by all means don't let that stop you :) I'm not sure if my version will end up being very good, but i'll definitely release the source.

At the moment, I'm using a lot of trig to work out the angles, but I think i've seen someone do it with vectors instead so there is less trig needed, so I think that might be a better option. I'll have to see- my maths brain is a little rusty since I did my A-level. If it's possible, I feel slightly dumber after 2 years of uni!

Good luck with the voxel engine, I shall be very interested to see the results. I really like your idea of blending the two.
 
Last edited by a moderator:
What's up with the & 65534? Why isn't it 65535 (or, for legibility, 0xFFFF); for that matter, why does it exist at all? It looks like you're intentionally clearing the least significant bit but why?

EDIT: This is combined with the previous shift to do a multiply by 2, right? I think it'd be clearer if you just used a Uint16 * target and let the compiler adjust it.
 
arm doesnt like unaligned access, 65534 = fffe, rounding the address.
thats my understanding from the quick superficial look I took.
 
Exophase said:
What's up with the & 65534? Why isn't it 65535 (or, for legibility, 0xFFFF); for that matter, why does it exist at all? It looks like you're intentionally clearing the least significant bit but why?

EDIT: This is combined with the previous shift to do a multiply by 2, right? I think it'd be clearer if you just used a Uint16 * target and let the compiler adjust it.
No. Its there instead of getting the integer from the fixed point then multiplying by 2.

Try taking it out :lol:

Goobers said:
Unfathomable Depths said:
@roc : yup, any surface size.

@goobers : again yes. Instead of source_width & source height you specify the top left x,y and bottom right x,y of the section of the source you want to scale.

BTW Goobers hows the mode 7 scaler coming along? Im asking because if your going to do this and release it to the community Im going to try writing a voxel engine instead of doing my own mode 7 scaler. Ill release the voxel engine to the community if I ever finish it.

I'm still working on it at the moment... I'm looking at some different ways of implementing it to see which is fastest, but please, by all means don't let that stop you :) I'm not sure if my version will end up being very good, but i'll definitely release the source.

At the moment, I'm using a lot of trig to work out the angles, but I think i've seen someone do it with vectors instead so there is less trig needed, so I think that might be a better option. I'll have to see- my maths brain is a little rusty since I did my A-level. If it's possible, I feel slightly dumber after 2 years of uni!

Good luck with the voxel engine, I shall be very interested to see the results. I really like your idea of blending the two.


Good news that your still working on it. I was going to use the the scaler code and progressively scale up back to front with offsets for each scanline using a similar technique to the way xratio & yratio are done in the scaler code. Just my initial thoughts - dont have any actual code.

Not started the voxel engine yet. Ill keep you posted though.
 
Last edited by a moderator:
Unfathomable Depths said:
No. Its there instead of getting the integer from the fixed point then multiplying by 2.
Yeah, I realized that (see my edit).. I didn't see at first that it was >> 15 instead of 16.

If you're doing it as an optimization then you ought to try getting that 0xFFFE in a register (maybe a variable with the register keyword) because otherwise it probably isn't going to work out very well for you. I doubt it'll work that well anyway because like this the compiler probably won't fold the base addition into the memory address (but knowing GCC it might not anyway)..

If you're not trying to optimize this then you should really use a Uint16 * pointer instead. If you are trying to optimize it then you're probably best off using assembly language because GCC's ARM output isn't any good. This function is small and not complex to do in ARM ASM.
 
Last edited by a moderator:
Yea.. Im kind of new to C and havent even looked at arm assembler yet.

I know its not as optimized as it could be - just thought it would be useful to someone.

BTW I did see the edit - it just never registered - sry.
 
Back
Top