00001 /* 00002 * Filtered filter_Image Rescaling 00003 * 00004 * by Dale Schumacher 00005 * 00006 */ 00007 00008 /* 00009 Additional changes by Ray Gardener, Daylon Graphics Ltd. 00010 December 4, 1999 00011 00012 Extreme modification to this to make it usable with SDL_Surfaces -Dave Olsen 1/2006 00013 and compatible with c++ compilers.... namely VC++2005 Express edition. 00014 It's a major hack-job. If anyone cleans this up, please let me know! 00015 I'm sure it can be made more efficient. (It's lots faster in release than in debug) 00016 00017 Summary: 00018 00019 - Horizontal filter contributions are calculated on the fly, 00020 as each column is mapped from src to dst image. This lets 00021 us omit having to allocate a temporary full horizontal stretch 00022 of the src image. 00023 00024 - If none of the src pixels within a sampling region differ, 00025 then the output pixel is forced to equal (any of) the source pixel. 00026 This ensures that filters do not corrupt areas of constant color. 00027 00028 - Filter weight contribution results, after summing, are 00029 rounded to the nearest pixel color value instead of 00030 being casted to Pixel (usually an int or char). Otherwise, 00031 artifacting occurs. 00032 00033 - All memory allocations checked for failure; zoom() returns 00034 error code. filter_new_image() returns NULL if unable to allocate 00035 pixel storage, even if filter_Image struct can be allocated. 00036 Some assertions added. 00037 */ 00038 00039 00040 // "Public Domain 1991 by Dale Schumacher. Mods by Ray Gardener"; 00041 // further mods by ME! (David Olsen) 00042 // and even more by Kevin Baragona, to make it valid C 00043 // and a few more to make it valid C89, and return NULL when needed (David Olsen) 00044 00045 00046 //It would be fantastic if someone would eventually modify these routines to make use 00047 //of native SDL image and pixel formats during the resize process... but, whatever. 00048 00049 #include <stdlib.h> 00050 #include <math.h> 00051 #include <SDL/SDL.h> 00052 00053 /* clamp the input to the specified range */ 00054 #define CLAMP(v,l,h) ((v)<(l) ? (l) : (v) > (h) ? (h) : v) 00055 #ifndef M_PI 00056 #define M_PI 3.14159265359 00057 #endif 00058 00059 typedef Uint8 Pixel; 00060 typedef struct 00061 { 00062 int xsize; /* horizontal size of the image in Pixels */ 00063 int ysize; /* vertical size of the image in Pixels */ 00064 Pixel * data; /* pointer to first scanline of image */ 00065 int span; /* byte offset between two scanlines */ 00066 } filter_Image; 00067 typedef struct 00068 { 00069 int pixel; 00070 double weight; 00071 } CONTRIB; 00072 typedef struct 00073 { 00074 int n; /* number of contributors */ 00075 CONTRIB *p; /* pointer to list of contributions */ 00076 } CLIST; 00077 00078 SDL_Surface* SDL_ResizeFactor(SDL_Surface *image, float scalefactor, int filter); 00079 SDL_Surface* SDL_ResizeXY(SDL_Surface *image, int new_w, int new_h, int filter); 00080 00081 static SDL_Surface *filter_resizexy(SDL_Surface* source,int new_w, int new_h, int filter); 00082 00083 static Uint32 filter_GetPixel(SDL_Surface *surface, int x, int y) 00084 { 00085 int bpp = surface->format->BytesPerPixel; 00086 /* Here p is the address to the pixel we want to retrieve */ 00087 Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; 00088 00089 switch(bpp) 00090 { 00091 case 1: return *p; 00092 case 2: return *(Uint16 *)p; 00093 case 3: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) 00094 return p[0] << 16 | p[1] << 8 | p[2]; 00095 else 00096 return p[0] | p[1] << 8 | p[2] << 16; 00097 case 4: return *(Uint32 *)p; 00098 default: return 0; /* shouldn't happen, but avoids warnings */ 00099 } 00100 } 00101 00102 static void filter_PutPixel(SDL_Surface *surface, int x, int y, Uint32 pixel) 00103 { 00104 int bpp = surface->format->BytesPerPixel; 00105 /* Here p is the address to the pixel we want to set */ 00106 Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; 00107 00108 switch(bpp) 00109 { 00110 case 1: *p = pixel; 00111 break; 00112 case 2: *(Uint16 *)p = pixel; 00113 break; 00114 case 3: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) 00115 { 00116 p[0] = (pixel >> 16) & 0xff; 00117 p[1] = (pixel >> 8) & 0xff; 00118 p[2] = pixel & 0xff; 00119 } 00120 else 00121 { 00122 p[0] = pixel & 0xff; 00123 p[1] = (pixel >> 8) & 0xff; 00124 p[2] = (pixel >> 16) & 0xff; 00125 } 00126 break; 00127 case 4: *(Uint32 *)p = pixel; 00128 break; 00129 } 00130 } 00131 00132 #ifdef __cplusplus 00133 SDL_Surface* SDL_Resize(SDL_Surface *image, float scalefactor, int filter) 00134 { 00135 return SDL_ResizeFactor(image, scalefactor, filter); 00136 } 00137 #endif 00138 SDL_Surface* SDL_ResizeFactor(SDL_Surface *image, float scalefactor, int filter) 00139 { 00140 int neww, newh; 00141 SDL_Surface * r; 00142 if (!image) return NULL; //invalid image passed in. 00143 if (scalefactor > 100.0f) scalefactor = 100.0f; //let's be reasonable... 00144 neww = (int)((float)image->w*scalefactor); 00145 newh = (int)((float)image->h*scalefactor); 00146 if (neww<1) neww = 1; 00147 if (newh<1) newh = 1; 00148 r = SDL_ResizeXY(image, neww, newh, filter); 00149 return r; 00150 } 00151 00152 #ifdef __cplusplus 00153 SDL_Surface* SDL_Resize(SDL_Surface *image, int new_w, int new_h, int filter) 00154 { 00155 return SDL_ResizeXY(image, new_w, new_h, filter); 00156 } 00157 #endif 00158 SDL_Surface* SDL_ResizeXY(SDL_Surface *image, int new_w, int new_h, int filter) 00159 { 00160 SDL_Surface *dest = NULL; 00161 Uint8 alpha, r, g, b; 00162 char usealpha; 00163 int cx; 00164 if (!image) return NULL; //invalid image passed in 00165 00166 if ((new_w != image->w) || (new_h != image->h)) 00167 dest = filter_resizexy(image, new_w, new_h, filter); 00168 else 00169 { 00170 SDL_FreeSurface(dest); 00171 dest = image; 00172 } 00173 00174 //check for alpha content of the image... like for buttons... 00175 00176 if SDL_MUSTLOCK(dest) SDL_LockSurface(dest); 00177 alpha = 0; r = 0; g = 0; b = 0; 00178 usealpha = 0; 00179 cx = 0; 00180 for (; cx < dest->w; cx++) 00181 { //check the whole image for any occurance of alpha 00182 int cy = 0; 00183 for (; cy < dest->h; cy++) 00184 { 00185 SDL_GetRGBA(filter_GetPixel(dest, cx, cy), dest->format, &r, &g, &b, &alpha); 00186 if (alpha != SDL_ALPHA_OPAQUE) {usealpha = 1; cx=dest->w; break;} 00187 } 00188 } 00189 if SDL_MUSTLOCK(dest) SDL_UnlockSurface(dest); 00190 00191 if (!usealpha) // no alpha component 00192 { 00193 image = SDL_DisplayFormat(dest); 00194 SDL_SetAlpha(image, SDL_RLEACCEL, 0); 00195 } 00196 else // it does have alpha 00197 { 00198 image = SDL_DisplayFormatAlpha(dest); 00199 SDL_SetAlpha(image, SDL_RLEACCEL | SDL_SRCALPHA, 0); 00200 } 00201 SDL_FreeSurface(dest); 00202 return image; 00203 } 00204 00205 static Pixel filter_get_pixel2(SDL_Surface *image, int x, int y, int which) 00206 { 00207 static Uint8 r=0, g=0, b=0, a=0; 00208 static int xx=-1, yy=-1; 00209 Pixel p = 0; 00210 00211 if((x < 0) || (x >= image->w) || (y < 0) || (y >= image->h)) 00212 return(0); 00213 00214 if ((xx!=x) || (yy!=y)) 00215 { 00216 Uint32 fullpixel; 00217 xx = x; yy = y; //this way it only calls the Getpixel,RGBA once per pixel... 00218 fullpixel = filter_GetPixel(image,x,y); 00219 SDL_GetRGBA(fullpixel,image->format,&r,&g,&b,&a); 00220 } 00221 00222 switch (which) 00223 { 00224 case 0 : p = r; break; 00225 case 1 : p = g; break; 00226 case 2 : p = b; break; 00227 case 3 : p = a; break; 00228 default: p = r; break; //not really needed... 00229 } 00230 return(p); 00231 } 00232 00233 static char filter_put_pixel2(SDL_Surface *image, int x, int y, Uint8 c[4]) 00234 { 00235 if((x < 0) || (x >= image->w) || (y < 0) || (y >= image->h)) 00236 return 0; 00237 filter_PutPixel(image,x,y,SDL_MapRGBA(image->format,c[0],c[1],c[2],c[3])); 00238 return 1; 00239 } 00240 00241 static double filter_hermite_interp(double t) 00242 { 00243 /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ 00244 if(t < 0.0) t = -t; 00245 if(t < 1.0) return((2.0 * t - 3.0) * t * t + 1.0); 00246 return(0.0); 00247 } 00248 00249 static double filter_box_interp(double t) 00250 { 00251 if((t > -0.5) && (t <= 0.5)) return(1.0); 00252 return(0.0); 00253 } 00254 00255 static double filter_triangle_interp(double t) 00256 { 00257 if(t < 0.0) t = -t; 00258 if(t < 1.0) return(1.0 - t); 00259 return(0.0); 00260 } 00261 00262 static double filter_bell_interp(double t) /* box (*) box (*) box */ 00263 { 00264 if(t < 0) t = -t; 00265 if(t < .5) return(.75 - (t * t)); 00266 if(t < 1.5) { 00267 t = (t - 1.5); 00268 return(.5 * (t * t)); 00269 } 00270 return(0.0); 00271 } 00272 00273 static double filter_B_spline_interp(double t) /* box (*) box (*) box (*) box */ 00274 { 00275 double tt; 00276 00277 if(t < 0) t = -t; 00278 if(t < 1) { 00279 tt = t * t; 00280 return((.5 * tt * t) - tt + (2.0 / 3.0)); 00281 } else if(t < 2) { 00282 t = 2 - t; 00283 return((1.0 / 6.0) * (t * t * t)); 00284 } 00285 return(0.0); 00286 } 00287 00288 static double filter_sinc(double x) 00289 { 00290 x *= M_PI; 00291 if(x != 0) return(sin(x) / x); 00292 return(1.0); 00293 } 00294 00295 static double filter_Lanczos3_interp(double t) 00296 { 00297 if(t < 0) t = -t; 00298 if(t < 3.0) return(filter_sinc(t) * filter_sinc(t/3.0)); 00299 return(0.0); 00300 } 00301 00302 static double filter_Mitchell_interp(double t) 00303 { 00304 static double B = (1.0 / 3.0); 00305 static double C = (1.0 / 3.0); 00306 double tt; 00307 00308 tt = t * t; 00309 if(t < 0) t = -t; 00310 if(t < 1.0) { 00311 t = (((12.0 - 9.0 * B - 6.0 * C) * (t * tt)) 00312 + ((-18.0 + 12.0 * B + 6.0 * C) * tt) 00313 + (6.0 - 2 * B)); 00314 return(t / 6.0); 00315 } else if(t < 2.0) { 00316 t = (((-1.0 * B - 6.0 * C) * (t * tt)) 00317 + ((6.0 * B + 30.0 * C) * tt) 00318 + ((-12.0 * B - 48.0 * C) * t) 00319 + (8.0 * B + 24 * C)); 00320 return(t / 6.0); 00321 } 00322 return(0.0); 00323 } 00324 00325 static int filter_roundcloser(double d) 00326 { 00327 /* Untested potential one-liner, but smacks of call overhead */ 00328 /* return fabs(ceil(d)-d) <= 0.5 ? ceil(d) : floor(d); */ 00329 00330 /* Untested potential optimized ceil() usage */ 00331 /* double cd = ceil(d); 00332 int ncd = (int)cd; 00333 if(fabs(cd - d) > 0.5) 00334 ncd--; 00335 return ncd; 00336 */ 00337 00338 /* Version that uses no function calls at all. */ 00339 int n = (int) d; 00340 double diff = d - (double)n; 00341 if(diff < 0) 00342 diff = -diff; 00343 if(diff >= 0.5) 00344 { 00345 if(d < 0) 00346 n--; 00347 else 00348 n++; 00349 } 00350 return n; 00351 } /* filter_roundcloser */ 00352 00353 static int filter_calc_x_contrib(CLIST *contribX, double xscale, double fwidth, 00354 int dstwidth, int srcwidth, double (*filterf)(double), int i) 00355 { 00356 double width; 00357 double fscale; 00358 double center, left, right; 00359 double weight; 00360 int j, k, n; 00361 00362 if(xscale < 1.0) 00363 { 00364 /* Shrinking image */ 00365 width = fwidth / xscale; 00366 fscale = 1.0 / xscale; 00367 00368 contribX->n = 0; 00369 contribX->p = (CONTRIB *)calloc((int) (width * 2 + 1), 00370 sizeof(CONTRIB)); 00371 if(contribX->p == NULL) 00372 return -1; 00373 00374 center = (double) i / xscale; 00375 left = ceil(center - width); 00376 right = floor(center + width); 00377 for(j = (int)left; j <= right; ++j) 00378 { 00379 weight = center - (double) j; 00380 weight = ((*filterf)(weight / fscale)) / fscale; 00381 if(j < 0) 00382 n = -j; 00383 else if(j >= srcwidth) 00384 n = (srcwidth - j) + srcwidth - 1; 00385 else 00386 n = j; 00387 00388 k = contribX->n++; 00389 contribX->p[k].pixel = n; 00390 contribX->p[k].weight = weight; 00391 } 00392 00393 } 00394 else 00395 { 00396 /* Expanding image */ 00397 contribX->n = 0; 00398 contribX->p = (CONTRIB *)calloc((int) (fwidth * 2 + 1), 00399 sizeof(CONTRIB)); 00400 if(contribX->p == NULL) 00401 return -1; 00402 center = (double) i / xscale; 00403 left = ceil(center - fwidth); 00404 right = floor(center + fwidth); 00405 00406 for(j = (int)left; j <= right; ++j) 00407 { 00408 weight = center - (double) j; 00409 weight = (*filterf)(weight); 00410 if(j < 0) { 00411 n = -j; 00412 } else if(j >= srcwidth) { 00413 n = (srcwidth - j) + srcwidth - 1; 00414 } else { 00415 n = j; 00416 } 00417 k = contribX->n++; 00418 contribX->p[k].pixel = n; 00419 contribX->p[k].weight = weight; 00420 } 00421 } 00422 return 0; 00423 } /* filter_calc_x_contrib */ 00424 00425 static int filter_zoom2(SDL_Surface *dst, SDL_Surface *src, double (*filterf)(double), double fwidth) 00426 { 00427 Pixel* tmp; 00428 double xscale, yscale; /* zoom scale factors */ 00429 int xx; 00430 int i, j, k; /* loop variables */ 00431 int n; /* pixel number */ 00432 double center, left, right; /* filter calculation variables */ 00433 double width, fscale, weight; /* filter calculation variables */ 00434 Uint8 weightedcolor[4]; //reconstruct the pixel out of these! 00435 Pixel pel, pel2; 00436 int bPelDelta; 00437 CLIST *contribY; /* array of contribution lists */ 00438 CLIST contribX; 00439 int nRet = -1; 00440 00441 /* create intermediate column to hold horizontal dst column zoom */ 00442 tmp = (Pixel*)malloc(src->h * sizeof(Pixel) * 4); 00443 if(tmp == NULL) 00444 return 0; 00445 00446 xscale = (double) dst->w / (double) src->w; 00447 00448 /* Build y weights */ 00449 /* pre-calculate filter contributions for a column */ 00450 contribY = (CLIST *)calloc(dst->h, sizeof(CLIST)); 00451 if(contribY == NULL) 00452 { 00453 free(tmp); 00454 return -1; 00455 } 00456 00457 yscale = (double) dst->h / (double) src->h; 00458 00459 if(yscale < 1.0) 00460 { 00461 width = fwidth / yscale; 00462 fscale = 1.0 / yscale; 00463 for(i = 0; i < dst->h; ++i) 00464 { 00465 contribY[i].n = 0; 00466 contribY[i].p = (CONTRIB *)calloc((int) (width * 2 + 1), 00467 sizeof(CONTRIB)); 00468 if(contribY[i].p == NULL) 00469 { 00470 free(tmp); 00471 free(contribY); 00472 return -1; 00473 } 00474 center = (double) i / yscale; 00475 left = ceil(center - width); 00476 right = floor(center + width); 00477 for(j = (int)left; j <= right; ++j) { 00478 weight = center - (double) j; 00479 weight = (*filterf)(weight / fscale) / fscale; 00480 if(j < 0) { 00481 n = -j; 00482 } else if(j >= src->h) { 00483 n = (src->h - j) + src->h - 1; 00484 } else { 00485 n = j; 00486 } 00487 k = contribY[i].n++; 00488 contribY[i].p[k].pixel = n; 00489 contribY[i].p[k].weight = weight; 00490 } 00491 } 00492 } else { 00493 for(i = 0; i < dst->h; ++i) { 00494 contribY[i].n = 0; 00495 contribY[i].p = (CONTRIB *)calloc((int) (fwidth * 2 + 1), 00496 sizeof(CONTRIB)); 00497 if(contribY[i].p == NULL) 00498 { 00499 free(tmp); 00500 free(contribY); 00501 return -1; 00502 } 00503 center = (double) i / yscale; 00504 left = ceil(center - fwidth); 00505 right = floor(center + fwidth); 00506 for(j = (int)left; j <= right; ++j) { 00507 weight = center - (double) j; 00508 weight = (*filterf)(weight); 00509 if(j < 0) { 00510 n = -j; 00511 } else if(j >= src->h) { 00512 n = (src->h - j) + src->h - 1; 00513 } else { 00514 n = j; 00515 } 00516 k = contribY[i].n++; 00517 contribY[i].p[k].pixel = n; 00518 contribY[i].p[k].weight = weight; 00519 } 00520 } 00521 } 00522 00523 00524 00525 for(xx = 0; xx < dst->w; xx++) 00526 { 00527 if(0 != filter_calc_x_contrib(&contribX, xscale, fwidth, 00528 dst->w, src->w, filterf, xx)) 00529 { 00530 goto __zoom_cleanup; 00531 } 00532 /* Apply horz filter to make dst column in tmp. */ 00533 for(k = 0; k < src->h; ++k) 00534 { 00535 //mine!!!! 00536 int w=0; 00537 for (; w<4; w++) { 00538 weight = 0.0; 00539 bPelDelta = 0; 00540 pel = filter_get_pixel2(src, contribX.p[0].pixel, k, w); 00541 for(j = 0; j < contribX.n; ++j) 00542 { 00543 pel2 = filter_get_pixel2(src, contribX.p[j].pixel, k, w); 00544 if(pel2 != pel) 00545 bPelDelta = 1; 00546 weight += pel2 * contribX.p[j].weight; 00547 } 00548 weight = bPelDelta ? filter_roundcloser(weight) : pel; 00549 00550 tmp[k+w*src->h] = (Pixel)CLAMP(weight, 0, 255); // keep it Uint8 00551 } //cycle through each color... 00552 } /* next row in temp column */ 00553 00554 free(contribX.p); 00555 00556 00557 /* The temp column has been built. Now stretch it 00558 vertically into dst column. */ 00559 for(i = 0; i < dst->h; ++i) 00560 { 00561 int w=0; 00562 for (; w<4; w++) { 00563 weight = 0.0; 00564 bPelDelta = 0; 00565 pel = tmp[contribY[i].p[0].pixel+w*src->h]; 00566 00567 for(j = 0; j < contribY[i].n; ++j) 00568 { 00569 pel2 = tmp[contribY[i].p[j].pixel+w*src->h]; 00570 if(pel2 != pel) 00571 bPelDelta = 1; 00572 weight += pel2 * contribY[i].p[j].weight; 00573 } 00574 weight = bPelDelta ? filter_roundcloser(weight) : pel; 00575 weightedcolor[w] = (Uint8)CLAMP(weight, 0, 255); //get all 4 "colors" this way 00576 } 00577 filter_put_pixel2(dst, xx, i, weightedcolor ); //keep it Uint8 00578 } /* next dst row */ 00579 00580 } /* next dst column */ 00581 nRet = 0; /* success */ 00582 00583 __zoom_cleanup: 00584 free(tmp); 00585 00586 /* free the memory allocated for vertical filter weights */ 00587 for(i = 0; i < dst->h; ++i) 00588 free(contribY[i].p); 00589 free(contribY); 00590 00591 return nRet; 00592 } /* zoom */ 00593 00594 static SDL_Surface *filter_resizexy(SDL_Surface* source,int new_w, int new_h, int filter) 00595 { 00596 //f and s need to be complementary... one as filter, one as support. 00597 double (*f)(double) ; //function pointer 00598 double s; //support 00599 SDL_Surface *temp, *dest; 00600 00601 const double box_support = 0.5, 00602 triangle_support = 1.0, 00603 bell_support = 1.5, 00604 B_spline_support = 2.0, 00605 hermite_support = 1.0, 00606 Mitchell_support = 2.0, 00607 Lanczos3_support = 3.0; 00608 00609 switch (filter) 00610 { 00611 case 1 : f=filter_box_interp; s=box_support; break; 00612 case 2 : f=filter_triangle_interp; s=triangle_support; break; 00613 case 3 : f=filter_bell_interp; s=bell_support; break; 00614 case 4 : f=filter_hermite_interp; s=hermite_support; break; 00615 case 5 : f=filter_B_spline_interp; s=B_spline_support; break; 00616 case 6 : f=filter_Mitchell_interp; s=Mitchell_support; break; 00617 case 7 : f=filter_Lanczos3_interp; s=Lanczos3_support; break; 00618 default: f=filter_Lanczos3_interp; s=Lanczos3_support; break; 00619 } 00620 00621 //Make new surface and send it in to the real filter 00622 temp = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_SRCALPHA, 00623 new_w, new_h, 32,0,0,0,0) , 00624 dest = SDL_DisplayFormatAlpha(temp); 00625 00626 SDL_FreeSurface(temp); 00627 00628 if SDL_MUSTLOCK(source) SDL_LockSurface(source); 00629 if SDL_MUSTLOCK(dest) SDL_LockSurface(dest); 00630 00631 filter_zoom2(dest, source, f, s ); 00632 00633 if SDL_MUSTLOCK(dest) SDL_UnlockSurface(dest); 00634 if SDL_MUSTLOCK(source) SDL_UnlockSurface(source); 00635 00636 SDL_FreeSurface(source); 00637 //should be all cleaned up! 00638 00639 return dest; 00640 }