[SDL] Creating new custom surfaces

Started by
8 comments, last by Kylotan 16 years, 2 months ago
I'm trying to resize an SDL surface after loading it with a bitmap. I do this by creating new pixel data by a resize function and then calling SDL_CreateRGBSurfaceFrom() with that pixel data. It works fine for resizing only the height of the image. If the width is resized(irrespective of height), the resulting image shows some weird color pattern mildly resembling the original image. I suspect it has something to do with the pitch, but I didn't find anything wrong there. I would appreciate any help, I'm really at my wit's end after spending two days on this. I wouldn't say no to a lecture on pitch either, I've found very little on the net about that. This is the code, its for 16-bit data only. Thanks, (for reading till here at least)

/* ... */

#define BPP_BMAP 16		    /* bits per pixel */
typedef unsigned short word;	    /* 16 bit integer */

#define align4(X) (((X) + 3) & ~3)  /* return alignment at next multiple of 4 */

#define pixcopy(DEST, DOFF, SRC, SOFF)        (*(word*)((DEST) + (DOFF)) = *(word*)((SRC) + (SOFF)))


/* resizes a bitmap by width or height or both */
void* resizebmap(void **base, int w, int h, float scalew, float scaleh)
{
  int x, y, neww, newh;
  void *newbase;

  neww = (int) (w * scalew);
  newh = (int) (h * scaleh);
  neww = align4(neww);

  /* allocate memory for bitmap data for resized image */
  if ((newbase = malloc(neww * newh * BPP_BMAP)) == NULL)
      return NULL;

  /* resize by expanding each pixel */
  for (y = 0; y < newh; y++){
      for (x = 0; x < neww; x++){
          pixcopy(newbase, y * neww + x, *base, ((int)(y/scaleh))*w +   ((int)(x/scalew)));
      }
  }
  return newbase;
}


main()
{
  float xc, yc;
  int p;

  SDL_Surface *screen, *bmp, *bmp1;
  void *pix;

  if ((bmp1 = SDL_LoadBMP("test.bmp")) == NULL){
       printf("Couldn't load bitmap\n");
       exit(1);
  }
  if ((bmp = SDL_ConvertSurface(bmp1, screen->format, 0)) == NULL){
      printf("Couldn't load bitmap\n");
      exit(1);
  }

  xc = 1, yc = 1;
  pix = resizebmap(&bmp->pixels, bmp->pitch, bmp->h, xc, yc);

  p = bmp->pitch * xc;
  p = align4(p);
  bmp1 = SDL_CreateRGBSurfaceFrom (pix, bmp->w * xc, bmp->h * yc, bmp->format->BitsPerPixel, p, bmp->format->Rmask, bmp->format->Gmask, bmp->format->Bmask, bmp->format->Amask);

  SDL_BlitSurface(bmp1, NULL, screen, NULL);

  /* ... */
}




[Edited by - manu1001 on February 6, 2008 8:09:41 AM]
Advertisement
It would probably just be simpler to use something like SDL_gfx's surface zooming than writing your own.
The problem here is not with resizing, but in creating SDL surfaces with custom pixel data.
I wish to use this for other image transformations as well.
You could try creating a new surface and then blitting from the old surface to the new surface...
Pitch is the physical surface width, whereas width is the logical surface width. Pitch can often be bigger than width, leaving unused space on the side of the image. So if you want to skip down a row, you add on pitch*pixelSize. But for reading within a row, you only want values 0-(pitch-1). So you can't just use them interchangeably, and you can't pass in the image pitch as the width.
[in reply to samuraicrow]

Yes, I am creating a new surface (using SDL_CreateRGBSurfaceFrom()) but the pixel data for it is not in another surface so that i can blit, rather I'm generating that using resizebmap() function.

I somehow need to associate the data returned by resizebmap() with the new surface.

Again, the method used in the code above works for surfaces of same pitch(scanline), which led me to suspect that pitch is the culprit here.
[in reply to Kylotan]

I've tried as you said and now pass both width and pitch as parameters. It isn't working this way either. In fact there's an additional problem now, it displays only half the image always. No clue why.

Here are the changed parts of the code

Function:

/* resizes a bitmap by width or height or both */void* resizebmap(void **base, int w, int h, int p, float scalew, float scaleh){  int x, y, neww, newh, newp;  void *newbase;  neww = (int) (w * scalew);  newh = (int) (h * scaleh);  newp = (neww * BPP_BMAP);  newp = align4(newp);  /* allocate memory for bitmap data for resized image */  if ((newbase = malloc(newp * newh)) == NULL)      return NULL;  /* resize by expanding each pixel */  for (y = 0; y < newh; y++){      for (x = 0; x < neww; x++){          pixcopy(newbase, y*newp + x, *base, ((int)(y/scaleh))*p + ((int)(x/scalew)));      }  }  return newbase;}


call:

pix = resizebmap(&bmp->pixels, bmp->w, bmp->h, bmp->pitch, xc, yc);

If you're allocating a lump of memory, you don't need to worry about pitch. Your pitch will always equal width. It's when the API allocates memory and you want to read or amend it that you must concern yourself with the pitch that it chooses, otherwise you can't tell where each row starts.

You seem to be mixing your units here, measuring pitch in bytes and width in pixels. Not recommended, mostly because that will get things wrong by a factor of 2 when you have 2 bytes per pixel, obviously. Remove newp, and change 'y*newp + x' to 'y*neww + x' and see how it goes.

[Edited by - Kylotan on February 8, 2008 4:59:48 AM]
The Final Solution!

Finally got it (whew). You're right Kylotan, I did get my units mixed up. The pixcopy function copied word elements, but I was sending byte arguments(even that was incorrect actually, I was sending y as byte and x as word). Here's the modified version with pixcopy accessing byte elements. It works perfectly(for now).

There are a few other small "tunings", for the resize function to work properly.

Thanks everyone for your help.

And I would like to give a precise definition of pitch here. Is it fine?

Pitch = number of bytes in a single row of a surface + number of bytes required to make the width of the row word aligned.


/* ... */#define BPP_BMAP 2		    /* bytes per pixel */typedef unsigned short word;	    /* 16 bit integer */#define align4(X) (((X) + 3) & ~3)  /* return alignment at next multiple of 4 */#define pixcopy(DEST, DOFF, SRC, SOFF)        (*((byte*)(DEST) + (DOFF) + 0) = *((byte*)(SRC) + (SOFF) + 0)),  (*((byte*)(DEST) + (DOFF) + 1) = *((byte*)(SRC) + (SOFF) + 1))/* resizes a bitmap by width or height or both */void* resizebmap(void *base, int w, int h, int p, float scalew, float scaleh){  int x, y, neww, newh, newp;  void *newbase;  neww = ((int)(w * scalew)) + 1;  newh = ((int)(h * scaleh)) + 1;  newp = neww * BPP_BMAP;  newp = align4(newp);  /* allocate memory for bitmap data for resized image */  if ((newbase = malloc(newp* newh)) == NULL)      return NULL;  /* resize by expanding each pixel */  for (y = 0; y < newh; y++){      for (x = 0; x < neww; x++){          pixcopy(newbase, y*newp + x*BPP_BMAP, base, ((int)(y/scaleh))*p + ((int)(x/scalew)*BPP_BMAP));      }  }  return newbase;}main(){  float xc, yc;  int p;  SDL_Surface *screen, *bmp, *bmp1;  void *pix;  if ((bmp1 = SDL_LoadBMP("test.bmp")) == NULL){       printf("Couldn't load bitmap\n");       exit(1);  }  if ((bmp = SDL_ConvertSurface(bmp1, screen->format, 0)) == NULL){      printf("Couldn't load bitmap\n");      exit(1);  }  xc = 1.5, yc = 1.5;  pix = resizebmap(bmp->pixels, bmp->w, bmp->h, bmp->pitch, xc, yc);          p = (int)(bmp->w * xc + 1) * bmp->format->BytesPerPixel;  p = align4(p);  bmp1 = SDL_CreateRGBSurfaceFrom (pix, bmp->w * xc + 1, bmp->h * yc + 1, bmp->format->BitsPerPixel, p,                                     bmp->format->Rmask, bmp->format->Gmask, bmp->format->Bmask, bmp->format->Amask);  SDL_BlitSurface(bmp1, NULL, screen, NULL);  /* ... */}
No, pitch could be any number at all that is equal to or larger than the width of the surface. It won't just be rounded up to the nearest word alignment - it might be rounded up to the nearest 16, or the nearest power of 2, or some other arbitrary maximum. It allows graphics cards to store the memory in the most speed-efficient way, by perhaps wasting a little memory.

This topic is closed to new replies.

Advertisement