Linear Methods for Image Interpolation
|
00001 00020 #include <math.h> 00021 #include <string.h> 00022 #include <ctype.h> 00023 00024 #include "imageio.h" 00025 #include "strutil.h" 00026 00027 #define VERBOSE 0 00028 00030 #define NUMSTDS 4 00031 00032 00034 typedef struct 00035 { 00037 uint32_t *Data; 00039 int Width; 00041 int Height; 00042 } image; 00043 00044 typedef enum 00045 { 00046 BOUNDARY_CONSTANT = 0, 00047 BOUNDARY_HSYMMETRIC = 1, 00048 BOUNDARY_WSYMMETRIC = 2, 00049 BOUNDARY_PERIODIC = 3 00050 } boundaryhandling; 00051 00053 typedef struct 00054 { 00056 char *InputFile; 00058 char *OutputFile; 00060 int JpegQuality; 00062 int CenteredGrid; 00064 boundaryhandling Boundary; 00066 char *ScaleStr; 00068 double ScaleX; 00070 double ScaleY; 00072 int CoarseWidth; 00074 int CoarseHeight; 00076 float PsfSigma; 00077 } programparams; 00078 00079 00080 int ParseParams(programparams *Param, int argc, char *argv[]); 00081 static int ParseScaling(programparams *Param, int InputWidth, int InputHeight); 00082 int Coarsen(image v, image u, programparams Param); 00083 00085 void PrintHelpMessage() 00086 { 00087 printf("Image coarsening utility, P. Getreuer 2010-2011\n\n"); 00088 printf("Usage: imcoarsen [options] <input file> <output file>\n\n" 00089 "Only " READIMAGE_FORMATS_SUPPORTED " images are supported.\n\n"); 00090 printf("Options:\n"); 00091 printf(" -x <number> coarsening factor (>= 1.0, may be non-integer)\n"); 00092 printf(" -x <x-scale>,<y-scale> set horizontal and vertical coarsening factors\n"); 00093 printf(" -x <width>x<height> set maximum coarsened size in pixels, \n"); 00094 printf(" preserves aspect ratio\n"); 00095 printf(" -x <width>x<height>^ set minimum coarsened size in pixels, \n"); 00096 printf(" preserves aspect ratio\n"); 00097 printf(" -x <width>x<height>! set actual coarsened size in pixels, \n"); 00098 printf(" ignores aspect ratio\n\n"); 00099 00100 printf(" -p <number> sigma_h, the blur size of the Gaussian point spread function\n" 00101 " in units of output pixels.\n"); 00102 printf(" -b <ext> extension to use for boundary handling, choices for <ext> are\n"); 00103 printf(" const constant extension\n"); 00104 printf(" hsym half-sample symmetric (default)\n"); 00105 printf(" wsym whole-sample symmetric\n"); 00106 printf(" per periodic\n"); 00107 printf(" -g <grid> grid to use for resampling, choices for <grid> are\n" 00108 " centered grid with centered alignment (default)\n" 00109 " topleft the top-left anchored grid\n\n"); 00110 #ifdef LIBJPEG_SUPPORT 00111 printf(" -q <number> quality for saving JPEG images (0 to 100)\n\n"); 00112 #endif 00113 printf("Example: coarsen by factor 2.5\n" 00114 " imcoarsen -x 2.5 -p 0.35 frog.bmp coarse.bmp\n"); 00115 } 00116 00117 00118 int main(int argc, char *argv[]) 00119 { 00120 programparams Param; 00121 image u = {NULL, 0, 0}, v = {NULL, 0, 0}; 00122 int Status = 1; 00123 00124 00125 if(!ParseParams(&Param, argc, argv)) 00126 return 0; 00127 00128 /* Read the input image */ 00129 if(!(u.Data = (uint32_t *)ReadImage(&u.Width, &u.Height, Param.InputFile, 00130 IMAGEIO_U8 | IMAGEIO_RGBA))) 00131 goto Catch; 00132 00133 if(!ParseScaling(&Param, u.Width, u.Height)) 00134 goto Catch; 00135 00136 if(Param.ScaleX >= u.Width || Param.ScaleY >= u.Height) 00137 { 00138 ErrorMessage("Image is too small for scale factor.\n"); 00139 goto Catch; 00140 } 00141 00142 /* Allocate the output image */ 00143 v.Width = Param.CoarseWidth; 00144 v.Height = Param.CoarseHeight; 00145 00146 #if VERBOSE > 0 00147 printf("%dx%d input -> %dx%d output\n", u.Width, u.Height, v.Width, v.Height); 00148 #endif 00149 00150 if(!(v.Data = (uint32_t *)Malloc(sizeof(uint32_t)* 00151 ((long int)v.Width)*((long int)v.Height)))) 00152 goto Catch; 00153 00154 /* Convolution followed by downsampling */ 00155 if(!Coarsen(v, u, Param)) 00156 goto Catch; 00157 00158 /* Write the output image */ 00159 if(!WriteImage(v.Data, v.Width, v.Height, Param.OutputFile, 00160 IMAGEIO_U8 | IMAGEIO_RGBA, Param.JpegQuality)) 00161 goto Catch; 00162 #if VERBOSE > 0 00163 else 00164 printf("Output written to \"%s\".\n", Param.OutputFile); 00165 #endif 00166 00167 Status = 0; /* Finished successfully, set exit status to zero. */ 00168 00169 Catch: 00170 Free(v.Data); 00171 Free(u.Data); 00172 return Status; 00173 } 00174 00175 00176 float Sqr(float x) 00177 { 00178 return x*x; 00179 } 00180 00187 static int ConstExtension(int N, int i) 00188 { 00189 if(i < 0) 00190 return 0; 00191 else if(i >= N) 00192 return N - 1; 00193 else 00194 return i; 00195 } 00196 00197 00204 static int HSymExtension(int N, int i) 00205 { 00206 while(1) 00207 { 00208 if(i < 0) 00209 i = -1 - i; 00210 else if(i >= N) 00211 i = (2*N - 1) - i; 00212 else 00213 return i; 00214 } 00215 } 00216 00217 00224 static int WSymExtension(int N, int i) 00225 { 00226 while(1) 00227 { 00228 if(i < 0) 00229 i = -i; 00230 else if(i >= N) 00231 i = (2*N - 2) - i; 00232 else 00233 return i; 00234 } 00235 } 00236 00237 00244 static int PerExtension(int N, int i) 00245 { 00246 while(1) 00247 { 00248 if(i < 0) 00249 i += N; 00250 else if(i >= N) 00251 i -= N; 00252 else 00253 return i; 00254 } 00255 } 00256 00257 00258 int (*ExtensionMethod[4])(int, int) = 00259 {ConstExtension, HSymExtension, WSymExtension, PerExtension}; 00260 00261 00262 int Coarsen(image v, image u, programparams Param) 00263 { 00264 int (*Extension)(int, int) = ExtensionMethod[Param.Boundary]; 00265 const float PsfRadiusX = NUMSTDS*Param.PsfSigma*Param.ScaleX; 00266 const float PsfRadiusY = NUMSTDS*Param.PsfSigma*Param.ScaleY; 00267 const int PsfWidth = 1 + ((PsfRadiusX == 0) ? 1 : (int)ceil(2*PsfRadiusX)); 00268 const int PsfHeight = 1 + ((PsfRadiusY == 0) ? 1 : (int)ceil(2*PsfRadiusY)); 00269 float *Temp = NULL, *PsfBuf = NULL; 00270 float ExpDenomX, ExpDenomY, Weight, Sum[4], DenomSum; 00271 float XStart, YStart, X, Y; 00272 uint32_t Pixel; 00273 int IndexX0, IndexY0, SrcOffset, DestOffset; 00274 int x, y, n, c, Success = 0; 00275 00276 00277 if(!(Temp = (float *)Malloc(sizeof(float)*4*v.Width*u.Height)) 00278 || !(PsfBuf = (float *)Malloc(sizeof(float) 00279 *((PsfWidth >= PsfHeight) ? PsfWidth : PsfHeight)))) 00280 goto Catch; 00281 00282 ExpDenomX = 2 * Sqr(Param.PsfSigma*Param.ScaleX); 00283 ExpDenomY = 2 * Sqr(Param.PsfSigma*Param.ScaleY); 00284 00285 if(Param.CenteredGrid) 00286 { 00287 XStart = (1/Param.ScaleX - 1)/2; 00288 YStart = (1/Param.ScaleY - 1)/2; 00289 } 00290 else 00291 XStart = YStart = 0; 00292 00293 for(x = 0; x < v.Width; x++) 00294 { 00295 X = (-XStart + x)*Param.ScaleX; 00296 IndexX0 = (int)ceil(X - PsfRadiusX - 0.5f); 00297 DenomSum = 0; 00298 00299 /* Evaluate the PSF */ 00300 for(n = 0; n < PsfWidth; n++) 00301 { 00302 PsfBuf[n] = Sqr(X - (IndexX0 + n)); 00303 00304 if(!n || PsfBuf[n] < DenomSum) 00305 DenomSum = PsfBuf[n]; 00306 } 00307 00308 if(ExpDenomX > 0) 00309 for(n = 0; n < PsfWidth; n++) 00310 PsfBuf[n] = (float)exp((DenomSum - PsfBuf[n]) / ExpDenomX); 00311 else if(IndexX0 == (int)floor(X - PsfRadiusX + 0.5f)) 00312 { /* If PsfSigma = 0, sample the nearest neighbor */ 00313 PsfBuf[0] = 1; 00314 PsfBuf[1] = 0; 00315 } 00316 else /* At a half integer, average the two nearest neighbors */ 00317 PsfBuf[0] = PsfBuf[1] = 0.5f; 00318 00319 DenomSum = 0; 00320 00321 for(n = 0; n < PsfWidth; n++) 00322 DenomSum += PsfBuf[n]; 00323 00324 for(y = 0, SrcOffset = 0, DestOffset = x; y < u.Height; 00325 y++, SrcOffset += u.Width, DestOffset += v.Width) 00326 { 00327 Sum[0] = Sum[1] = Sum[2] = Sum[3] = 0; 00328 00329 for(n = 0; n < PsfWidth; n++) 00330 { 00331 Weight = PsfBuf[n]; 00332 Pixel = u.Data[Extension(u.Width, IndexX0 + n) + SrcOffset]; 00333 00334 for(c = 0; c < 4; c++) 00335 Sum[c] += (float)((uint8_t *)&Pixel)[c] * Weight; 00336 } 00337 00338 for(c = 0; c < 4; c++) 00339 Temp[4*DestOffset + c] = Sum[c] / DenomSum; 00340 } 00341 } 00342 00343 for(y = 0; y < v.Height; y++, v.Data += v.Width) 00344 { 00345 Y = (-YStart + y)*Param.ScaleY; 00346 IndexY0 = (int)ceil(Y - PsfRadiusY - 0.5f); 00347 DenomSum = 0; 00348 00349 /* Evaluate the PSF */ 00350 for(n = 0; n < PsfHeight; n++) 00351 { 00352 PsfBuf[n] = Sqr(Y - (IndexY0 + n)); 00353 00354 if(!n || PsfBuf[n] < DenomSum) 00355 DenomSum = PsfBuf[n]; 00356 } 00357 00358 if(ExpDenomY > 0) 00359 for(n = 0; n < PsfHeight; n++) 00360 PsfBuf[n] = (float)exp((DenomSum - PsfBuf[n]) / ExpDenomY); 00361 else if(IndexY0 == (int)floor(Y - PsfRadiusY + 0.5f)) 00362 { /* If PsfSigma = 0, sample the nearest neighbor */ 00363 PsfBuf[0] = 1; 00364 PsfBuf[1] = 0; 00365 } 00366 else /* At a half integer, average the two nearest neighbors */ 00367 PsfBuf[0] = PsfBuf[1] = 0.5f; 00368 00369 DenomSum = 0; 00370 00371 for(n = 0; n < PsfHeight; n++) 00372 DenomSum += PsfBuf[n]; 00373 00374 for(x = 0; x < v.Width; x++) 00375 { 00376 Sum[0] = Sum[1] = Sum[2] = Sum[3] = 0; 00377 00378 for(n = 0; n < PsfHeight; n++) 00379 { 00380 SrcOffset = x + v.Width*Extension(u.Height, IndexY0 + n); 00381 Weight = PsfBuf[n]; 00382 00383 for(c = 0; c < 4; c++) 00384 Sum[c] += Temp[4*SrcOffset + c] * Weight; 00385 } 00386 00387 for(c = 0; c < 4; c++) 00388 ((uint8_t *)&Pixel)[c] = (int)(Sum[c] / DenomSum + 0.5f); 00389 00390 v.Data[x] = Pixel; 00391 } 00392 } 00393 00394 Success = 1; 00395 Catch: 00396 Free(PsfBuf); 00397 Free(Temp); 00398 return Success; 00399 } 00400 00401 00402 /* 00403 * Parse the scaling option string 00404 * 00405 * Syntax 00406 * <scale> Scale by factor <scale> 00407 * <scalex>,<scaley> Scale width and height individually 00408 * <width>x<height> Max size given, aspect ratio preserved 00409 * <width>x<height>^ Min size given, aspect ratio preserved 00410 * <width>x<height>! Actual size given, aspect ratio ignored 00411 */ 00412 static int ParseScaling(programparams *Param, int InputWidth, int InputHeight) 00413 { 00414 const char *StrPtr = Param->ScaleStr; 00415 double Number; 00416 00417 if(!ParseNumber(&Number, &StrPtr, 1)) 00418 goto Catch; 00419 00420 if(!EatWhitespace(&StrPtr)) 00421 { /* Syntax <scale> */ 00422 Param->ScaleX = Param->ScaleY = Number; 00423 Param->CoarseWidth = (int)ceil(InputWidth/Param->ScaleX); 00424 Param->CoarseHeight = (int)ceil(InputHeight/Param->ScaleY); 00425 } 00426 else if(*StrPtr == ',') 00427 { /* Syntax <scalex>,<scaley> */ 00428 StrPtr++; 00429 Param->ScaleX = Number; 00430 00431 if(!ParseNumber(&Number, &StrPtr, 1)) 00432 goto Catch; 00433 00434 Param->ScaleY = Number; 00435 Param->CoarseWidth = (int)ceil(InputWidth/Param->ScaleX); 00436 Param->CoarseHeight = (int)ceil(InputHeight/Param->ScaleY); 00437 } 00438 else if(*StrPtr == 'x' || *StrPtr == 'X') 00439 { /* Syntax <width>x<height>... */ 00440 StrPtr = Param->ScaleStr; 00441 00442 /* Reparse as integer */ 00443 if(!ParseNumber(&Number, &StrPtr, 0) 00444 || !(*StrPtr == 'x' || *StrPtr == 'X')) 00445 goto Catch; 00446 00447 StrPtr++; 00448 Param->CoarseWidth = (int)floor(Number + 0.5); 00449 00450 if(!ParseNumber(&Number, &StrPtr, 0)) 00451 goto Catch; 00452 00453 Param->CoarseHeight = (int)floor(Number + 0.5); 00454 Param->ScaleX = ((double)InputWidth) / ((double)Param->CoarseWidth); 00455 Param->ScaleY = ((double)InputHeight) / ((double)Param->CoarseHeight); 00456 EatWhitespace(&StrPtr); 00457 00458 switch(*StrPtr) 00459 { 00460 case '\0': /* <width>x<height> Max size given, preserve aspect ratio */ 00461 if(InputHeight*Param->CoarseWidth 00462 <= InputWidth*Param->CoarseHeight) 00463 { 00464 Param->ScaleY = Param->ScaleX; 00465 Param->CoarseHeight = 00466 (int)floor(InputHeight/Param->ScaleY + 0.5); 00467 } 00468 else 00469 { 00470 Param->ScaleX = Param->ScaleY; 00471 Param->CoarseWidth = 00472 (int)floor(InputWidth/Param->ScaleX + 0.5); 00473 } 00474 break; 00475 case '^': /* <width>x<height>^ Min size given, preserve aspect ratio */ 00476 if(InputHeight*Param->CoarseWidth 00477 >= InputWidth*Param->CoarseHeight) 00478 { 00479 Param->ScaleY = Param->ScaleX; 00480 Param->CoarseHeight = 00481 (int)floor(InputHeight/Param->ScaleY + 0.5); 00482 } 00483 else 00484 { 00485 Param->ScaleX = Param->ScaleY; 00486 Param->CoarseWidth = 00487 (int)floor(InputWidth/Param->ScaleX + 0.5); 00488 } 00489 break; 00490 case '!': /* <width>x<height>! Actual size given */ 00491 break; 00492 default: 00493 goto Catch; 00494 } 00495 } 00496 else 00497 goto Catch; 00498 00499 return 1; 00500 Catch: 00501 ErrorMessage("Invalid scaling option \"%s\".\n", Param->ScaleStr); 00502 return 0; 00503 } 00504 00505 00506 int ParseParams(programparams *Param, int argc, char *argv[]) 00507 { 00508 static char *DefaultOutputFile = (char *)"out.bmp"; 00509 static char *DefaultScaleStr = (char *)"1"; 00510 char *OptionString; 00511 char OptionChar; 00512 int i; 00513 00514 00515 if(argc < 2) 00516 { 00517 PrintHelpMessage(); 00518 return 0; 00519 } 00520 00521 /* Set parameter defaults */ 00522 Param->InputFile = 0; 00523 Param->OutputFile = DefaultOutputFile; 00524 Param->JpegQuality = 99; 00525 Param->ScaleStr = DefaultScaleStr; 00526 Param->PsfSigma = 0.35f; 00527 Param->CenteredGrid = 1; 00528 Param->Boundary = BOUNDARY_HSYMMETRIC; 00529 00530 for(i = 1; i < argc;) 00531 { 00532 if(argv[i] && argv[i][0] == '-') 00533 { 00534 if((OptionChar = argv[i][1]) == 0) 00535 { 00536 ErrorMessage("Invalid parameter format.\n"); 00537 return 0; 00538 } 00539 00540 if(argv[i][2]) 00541 OptionString = &argv[i][2]; 00542 else if(++i < argc) 00543 OptionString = argv[i]; 00544 else 00545 { 00546 ErrorMessage("Invalid parameter format.\n"); 00547 return 0; 00548 } 00549 00550 switch(OptionChar) 00551 { 00552 case 'x': 00553 Param->ScaleStr = OptionString; 00554 break; 00555 case 'p': 00556 Param->PsfSigma = (float)atof(OptionString); 00557 00558 if(Param->PsfSigma < 0.0) 00559 { 00560 ErrorMessage("Point spread blur size must be nonnegative.\n"); 00561 return 0; 00562 } 00563 break; 00564 case 'b': 00565 if(!strcmp(OptionString, "const")) 00566 Param->Boundary = BOUNDARY_CONSTANT; 00567 else if(!strcmp(OptionString, "hsym")) 00568 Param->Boundary = BOUNDARY_HSYMMETRIC; 00569 else if(!strcmp(OptionString, "wsym")) 00570 Param->Boundary = BOUNDARY_WSYMMETRIC; 00571 else if(!strcmp(OptionString, "per")) 00572 Param->Boundary = BOUNDARY_PERIODIC; 00573 else 00574 { 00575 ErrorMessage("Boundary extension must be either \"const\", \"hsym\", or \"wsym\".\n"); 00576 return 0; 00577 } 00578 break; 00579 case 'g': 00580 if(!strcmp(OptionString, "centered") 00581 || !strcmp(OptionString, "center")) 00582 Param->CenteredGrid = 1; 00583 else if(!strcmp(OptionString, "topleft") 00584 || !strcmp(OptionString, "top-left")) 00585 Param->CenteredGrid = 0; 00586 else 00587 { 00588 ErrorMessage("Grid must be either \"centered\" or \"topleft\".\n"); 00589 return 0; 00590 } 00591 break; 00592 00593 #ifdef LIBJPEG_SUPPORT 00594 case 'q': 00595 Param->JpegQuality = atoi(OptionString); 00596 00597 if(Param->JpegQuality <= 0 || Param->JpegQuality > 100) 00598 { 00599 ErrorMessage("JPEG quality must be between 0 and 100.\n"); 00600 return 0; 00601 } 00602 break; 00603 #endif 00604 case '-': 00605 PrintHelpMessage(); 00606 return 0; 00607 default: 00608 if(isprint(OptionChar)) 00609 ErrorMessage("Unknown option \"-%c\".\n", OptionChar); 00610 else 00611 ErrorMessage("Unknown option.\n"); 00612 00613 return 0; 00614 } 00615 00616 i++; 00617 } 00618 else 00619 { 00620 if(!Param->InputFile) 00621 Param->InputFile = argv[i]; 00622 else 00623 Param->OutputFile = argv[i]; 00624 00625 i++; 00626 } 00627 } 00628 00629 if(!Param->InputFile) 00630 { 00631 PrintHelpMessage(); 00632 return 0; 00633 } 00634 00635 return 1; 00636 }