00001 /* 00002 File: ImgView.cc 00003 00004 Function: Simple Image Viewer 00005 00006 Author: Andrew Willmott 00007 00008 Notes: 00009 */ 00010 00011 #define GCL_WATCH_FILE 00012 00013 #include "gcl/XGraphicsSystem.h" 00014 #include "gcl/VecUtil.h" 00015 #include "cl/Timer.h" 00016 00017 #include <stdio.h> 00018 #include <stdlib.h> 00019 #include <unistd.h> 00020 #include "cl/ArgParse.h" 00021 00022 enum IPCmd 00023 { 00024 ipNone, 00025 ipReload, 00026 ipNext, 00027 ipPrev 00028 }; 00029 00030 class ImagePane : public XBackedPane 00031 { 00032 public: 00033 ImagePane() : XBackedPane(), command(ipNone), offscreen(0), closed(false) 00034 {}; 00035 00036 Void HandleKey(Char c); 00037 Void PrintKeys(); 00038 Void PrintInfo(); 00039 Void CheckFile(); 00040 Int Load(StrConst filename); 00041 00042 IPCmd command; 00043 FileName imageFile; 00044 RGBAImage image; 00045 RGBEImage radImage; 00046 Long timeStamp; 00047 Int offWidth, offHeight; 00048 Bool closed; 00049 Bool loaded; 00050 Double alpha; 00051 XOffscreenPane *offscreen; 00052 00053 static XGraphicsSystem *sXgs; 00054 }; 00055 00056 XGraphicsSystem *ImagePane::sXgs = 0; 00057 00058 typedef ImagePane *ImagePanePtr; 00059 00060 // XXX image stuff -- quick hack 00061 00062 struct ImageStats 00063 { 00064 Double avgLum; 00065 Double avgLogLum; 00066 Double minLum; 00067 Double maxLum; 00068 Colour avgClr; 00069 Colour minClr; 00070 Colour maxClr; 00071 Int numSamples; 00072 }; 00073 00074 Void FindImageStats(Image &img, ImageStats &stats) 00075 { 00076 Int x, y; 00077 Colour c; 00078 Double lum; 00079 00080 stats.numSamples = 0; 00081 stats.avgLogLum = 0.0; 00082 stats.avgLum = 0.0; 00083 stats.minLum = vl_inf; 00084 stats.maxLum = -vl_inf; 00085 stats.avgClr.MakeZero(); 00086 stats.minClr.MakeBlock(vl_inf); 00087 stats.maxClr.MakeBlock(-vl_inf); 00088 00089 for (y = 0; y < img.Height(); y++) 00090 for (x = 0; x < img.Width(); x++) 00091 { 00092 c = img.GetPixel(x, y); 00093 lum = dot(cRGBToLum, c); 00094 00095 if (lum > 0.0) 00096 { 00097 stats.avgClr += c; 00098 stats.avgLum += lum; 00099 stats.avgLogLum += log(lum); 00100 FindMinCmpts(c, stats.minClr, stats.minClr); 00101 FindMaxCmpts(c, stats.maxClr, stats.maxClr); 00102 stats.minLum = Min(lum, stats.minLum); 00103 stats.maxLum = Max(lum, stats.maxLum); 00104 stats.numSamples++; 00105 } 00106 } 00107 00108 stats.avgLum /= stats.numSamples; 00109 stats.avgLogLum /= stats.numSamples; 00110 stats.avgClr /= stats.numSamples; 00111 } 00112 00113 Void LinearMapImage(const Image &img, Double start, Double slope, Image &imgOut) 00114 { 00115 Int x, y; 00116 Colour c, cStart; 00117 00118 imgOut.SetSize(img.Width(), img.Height()); 00119 cStart.MakeBlock(start); 00120 00121 for (y = 0; y < img.Height(); y++) 00122 for (x = 0; x < img.Width(); x++) 00123 { 00124 c = img.GetPixel(x, y); 00125 c -= cStart; 00126 c *= slope; 00127 imgOut.SetPixel(x, y, ClipColour(c)); 00128 } 00129 } 00130 00131 Void DoToneMap(Image &radImage, Image &image) 00132 { 00133 ImageStats stats; 00134 Double scale, EV; 00135 00136 FindImageStats(radImage, stats); 00137 00138 // roughly approximate exposure value for ISO 100 film 00139 EV = stats.avgLogLum / log(2) - 1; 00140 // crop arbitrarily to "sensible" values 00141 EV = Clip(EV, 6.0, 17.0); 00142 // use this to scale irradiances so average level is 0.18, 00143 // a la most light meters 00144 scale = 0.18 * exp(-(EV + 1) * log(2)); 00145 LinearMapImage(radImage, 0.0, scale, image); 00146 } 00147 00148 // ---- 00149 00150 Int ImagePane::Load(StrConst filename) 00151 { 00152 Int err; 00153 Bool justCreated = false; 00154 00155 loaded = false; 00156 imageFile.SetPath(filename); 00157 00158 if (imageFile.GetExtension() == "pic") 00159 { 00160 err = radImage.LoadPIC(imageFile.GetPath()); 00161 if (!err) 00162 DoToneMap(radImage, image); 00163 } 00164 else 00165 err = image.Load(imageFile); 00166 00167 if (err != 0) 00168 { 00169 cerr << "image read failed." << endl; 00170 return(err); 00171 } 00172 00173 if (!xgs) 00174 { 00175 sXgs->CreateWindow(this, imageFile.GetFile(), image.Width(), image.Height()); 00176 justCreated = true; 00177 } 00178 00179 if (!offscreen || image.Width() > offWidth || image.Height() > offHeight) 00180 { 00181 if (offscreen) 00182 delete offscreen; 00183 00184 offscreen = new XOffscreenPane; 00185 xgs->CreateOffscreen(offscreen, image.Width(), image.Height()); 00186 offWidth = image.Width(); 00187 offHeight = image.Height(); 00188 Attach(offscreen); 00189 } 00190 offscreen->PutImage(image); 00191 00192 if (!justCreated) 00193 { 00194 SetTitle(imageFile.GetFile()); 00195 Resize(image.Width(), image.Height()); 00196 } 00197 HandleExpose(); 00198 00199 timeStamp = imageFile.GetTimeStamp(); 00200 command = ipNone; 00201 loaded = true; 00202 00203 return(0); 00204 } 00205 00206 Void ImagePane::CheckFile() 00207 { 00208 if (loaded && imageFile.GetTimeStamp() > timeStamp) 00209 Load(imageFile.GetPath()); 00210 } 00211 00212 Void ImagePane::HandleKey(Char c) 00213 { 00214 switch (c) 00215 { 00216 case 'h': 00217 case '?': 00218 PrintKeys(); 00219 break; 00220 case 'i': 00221 PrintInfo(); 00222 break; 00223 case 'd': 00224 { 00225 FileName outFile; 00226 outFile = imageFile; 00227 outFile.SetExtension("tif"); 00228 image.Save(outFile); 00229 } 00230 break; 00231 case 'r': 00232 Load(imageFile.GetPath()); 00233 break; 00234 case '1': 00235 image.Transform(RGBSaturate(alpha)); 00236 offscreen->PutImage(image); 00237 HandleExpose(); 00238 break; 00239 case '2': 00240 image.Transform(RGBSaturate(2.0 - alpha)); 00241 offscreen->PutImage(image); 00242 HandleExpose(); 00243 break; 00244 case '3': 00245 image.Transform(RGBHueRotate(alpha)); 00246 offscreen->PutImage(image); 00247 HandleExpose(); 00248 break; 00249 case '4': 00250 image.Clear(cRed); 00251 offscreen->PutImage(image); 00252 HandleExpose(); 00253 break; 00254 case ',': 00255 case 0x1D: 00256 command = ipPrev; 00257 xgs->SignalDone(); 00258 break; 00259 case ' ': 00260 case '.': 00261 case 0x1C: 00262 command = ipNext; 00263 xgs->SignalDone(); 00264 break; 00265 case 'w': 00266 Hide(); 00267 closed = true; 00268 break; 00269 case 'q': 00270 case 0x1B: 00271 closed = true; 00272 xgs->SignalDone(); 00273 break; 00274 00275 default: 00276 XBackedPane::HandleKey(c); 00277 } 00278 } 00279 00280 Void ImagePane::PrintKeys() 00281 { 00282 cout << "Viewer control:" << endl 00283 << " r - reload image" << endl 00284 << " d - dump tiff image" << endl 00285 << " . or <space> - move on to next image" << endl 00286 << " , - move back to previous image" << endl 00287 << " i - print image information" << endl 00288 << " q or <esc> - quit" << endl 00289 << " w - close window" << endl 00290 << endl; 00291 } 00292 00293 Void ImagePane::PrintInfo() 00294 { 00295 cout << String().Printf("Image %s is %d x %d", imageFile.GetPath().CString(), 00296 image.Width(), image.Height()) << endl; 00297 } 00298 00299 class ImgViewApp 00300 { 00301 public: 00302 ImgViewApp(); 00303 00304 Void SetOptions(Int argc, Char **argv); 00305 Int Run(); 00306 00307 // options 00308 static Void GetFileArgs(Int argc, Char *argv[]); 00309 00310 static Char **files; 00311 static Int numFiles; 00312 Int noFork; 00313 Int all; 00314 00315 Double alpha; 00316 Int doSat; 00317 }; 00318 00319 Char **ImgViewApp::files = 0; 00320 Int ImgViewApp::numFiles = 0; 00321 00322 ImgViewApp::ImgViewApp() 00323 { 00324 } 00325 00326 Void ImgViewApp::GetFileArgs(Int argc, Char *argv[]) 00327 { 00328 files = argv; 00329 numFiles = argc; 00330 } 00331 00332 Void ImgViewApp::SetOptions(int argc, char **argv) 00333 { 00334 ArgForm *arg_format; 00335 Int formats; 00336 00337 alpha = 1.0; 00338 00339 arg_format = arg_to_form(0, 00340 "", 00341 "Usage: imgview file1 file2 ... [options]", 00342 "", ARG_SUBR(GetFileArgs), 00343 "", 00344 "-noFork", ARG_FLAG(&noFork), 00345 "Don't fork into background.", 00346 "-all", ARG_FLAG(&all), 00347 "Open all images at once.", 00348 "-alpha %F", &alpha, 00349 "Set alpha for image operation", 00350 "-sat", ARG_FLAG(&doSat), 00351 "Change saturation of the image", 00352 "-formats", ARG_FLAG(&formats), 00353 "List supported file formats", 00354 0); 00355 00356 if (argc == 1) 00357 { 00358 arg_form_print(arg_format); 00359 cout << endl; 00360 exit(0); 00361 } 00362 00363 if (arg_parse_argv(argc, argv, arg_format) < 0) 00364 exit(1); 00365 00366 #ifdef DEBUG 00367 noFork = true; 00368 #endif 00369 00370 if (formats) 00371 { 00372 Image::PrintSupportedFormats(cout); 00373 exit(0); 00374 } 00375 } 00376 00377 Int ImgViewApp::Run() 00378 { 00379 Int i; 00380 00381 if (numFiles < 1) 00382 return(1); 00383 00384 if (!noFork && fork()) 00385 return(0); 00386 00387 ImagePane::sXgs = new XGraphicsSystem; 00388 00389 if (all) 00390 { 00391 ImagePanePtr *panes = new ImagePanePtr[numFiles]; 00392 Int closed = 0; 00393 00394 for (i = 0; i < numFiles; i++) 00395 { 00396 panes[i] = new ImagePane; 00397 panes[i]->alpha = alpha; 00398 panes[i]->Load(files[i]); 00399 } 00400 00401 while (closed < numFiles) 00402 { 00403 if (ImagePane::sXgs->RunFor(1.0)) 00404 break; 00405 for (i = 0; i < numFiles; i++) 00406 if (panes[i]) 00407 { 00408 if (panes[i]->closed) 00409 { 00410 delete panes[i]; 00411 panes[i] = 0; 00412 closed++; 00413 } 00414 else 00415 panes[i]->CheckFile(); 00416 } 00417 } 00418 } 00419 else 00420 { 00421 Int curImage = 0; 00422 ImagePane *imagePane = new ImagePane; 00423 00424 imagePane->Load(files[0]); 00425 imagePane->alpha = alpha; 00426 00427 do 00428 { 00429 ImagePane::sXgs->RunFor(1.0); 00430 00431 imagePane->CheckFile(); 00432 00433 switch (imagePane->command) 00434 { 00435 case ipPrev: 00436 if (curImage > 0) 00437 curImage--; 00438 else 00439 curImage = numFiles - 1; 00440 imagePane->Load(files[curImage]); 00441 break; 00442 case ipNext: 00443 curImage++; 00444 if (curImage == numFiles) 00445 curImage = 0; 00446 imagePane->Load(files[curImage]); 00447 break; 00448 } 00449 } 00450 while (!imagePane->closed); 00451 00452 delete imagePane; 00453 } 00454 00455 return(0); 00456 } 00457 00458 int main(int argc, char **argv) 00459 { 00460 ImgViewApp app; 00461 00462 app.SetOptions(argc, argv); 00463 00464 return(app.Run()); 00465 }