00001 /*!@file AppMedia/app-slideshow.C */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00005 // by the University of Southern California (USC) and the iLab at USC. // 00006 // See http://iLab.usc.edu for information about this project. // 00007 // //////////////////////////////////////////////////////////////////// // 00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00010 // in Visual Environments, and Applications'' by Christof Koch and // 00011 // Laurent Itti, California Institute of Technology, 2001 (patent // 00012 // pending; application number 09/912,225 filed July 23, 2001; see // 00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00016 // // 00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00018 // redistribute it and/or modify it under the terms of the GNU General // 00019 // Public License as published by the Free Software Foundation; either // 00020 // version 2 of the License, or (at your option) any later version. // 00021 // // 00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00025 // PURPOSE. See the GNU General Public License for more details. // 00026 // // 00027 // You should have received a copy of the GNU General Public License // 00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00030 // Boston, MA 02111-1307 USA. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // 00033 // Primary maintainer for this file: Rob Peters <rjpeters at usc dot edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/AppMedia/app-slideshow.C $ 00035 // $Id: app-slideshow.C 9412 2008-03-10 23:10:15Z farhan $ 00036 // 00037 00038 #ifndef APPMEDIA_APP_SLIDESHOW_C_DEFINED 00039 #define APPMEDIA_APP_SLIDESHOW_C_DEFINED 00040 00041 #include "GUI/XWinManaged.H" 00042 #include "Image/CutPaste.H" 00043 #include "Image/DrawOps.H" 00044 #include "Image/Image.H" 00045 #include "Image/MatrixOps.H" 00046 #include "Image/Pixels.H" 00047 #include "Image/ShapeOps.H" 00048 #include "Image/SimpleFont.H" 00049 #include "Raster/Raster.H" 00050 #include "Util/FileUtil.H" 00051 #include "rutz/time.h" 00052 #include "rutz/unixcall.h" 00053 00054 #include <cstdio> 00055 #include <cstdlib> 00056 #include <fstream> 00057 #include <string> 00058 #include <time.h> 00059 00060 #include <sys/types.h> 00061 #include <sys/stat.h> 00062 #include <X11/keysym.h> 00063 00064 enum TransformType 00065 { 00066 TXTYPE_NONE, 00067 TXTYPE_TRANSPOSE, 00068 TXTYPE_BEST_FIT 00069 }; 00070 00071 std::string convertToString(const TransformType txtype) 00072 { 00073 switch (txtype) 00074 { 00075 case TXTYPE_NONE: return "none"; 00076 case TXTYPE_TRANSPOSE: return "transpose"; 00077 case TXTYPE_BEST_FIT: return "bestfit"; 00078 } 00079 00080 // default: 00081 return "invalid"; 00082 } 00083 00084 class random_sequence 00085 { 00086 double m_current; 00087 double m_next; 00088 00089 static int irange(double rval, int min, int max) 00090 { 00091 return int(rval * (max - min) + min); 00092 } 00093 00094 public: 00095 random_sequence() 00096 { 00097 srand48(time(NULL) / 2); 00098 00099 m_current = drand48(); 00100 m_next = drand48(); 00101 } 00102 00103 int inext(int min, int max) 00104 { 00105 m_current = m_next; 00106 while (m_current == m_next) 00107 m_next = drand48(); 00108 00109 return irange(m_current, min, max); 00110 } 00111 00112 int ipeek(int min, int max) const 00113 { 00114 return irange(m_next, min, max); 00115 } 00116 }; 00117 00118 template <class T> 00119 Image<T> myDownSize(const Image<T>& src, const Dims& new_dims) 00120 { 00121 if (src.getDims() == new_dims) return src; 00122 00123 ASSERT(new_dims.isNonEmpty()); 00124 00125 Image<T> result = src; 00126 00127 while (result.getWidth() > new_dims.w() * 2 && 00128 result.getHeight() > new_dims.h() * 2) 00129 { 00130 result = decY(decX(quickLocalAvg2x2(result))); 00131 } 00132 00133 return rescale(result, new_dims); 00134 } 00135 00136 struct ImageInfo 00137 { 00138 ImageInfo() {} 00139 00140 ImageInfo(const Image<PixRGB<byte> >& raw, const Dims& dsize, 00141 const TransformType ttype, 00142 const RescaleType rtype) 00143 : 00144 rawdims(raw.getDims()) 00145 { 00146 switch (ttype) 00147 { 00148 case TXTYPE_NONE: img = raw; break; 00149 case TXTYPE_TRANSPOSE: img = transpose(raw); break; 00150 case TXTYPE_BEST_FIT: 00151 if ( (dsize.w() >= dsize.h()) != (raw.getWidth() >= raw.getHeight()) ) 00152 img = transpose(raw); 00153 else 00154 img = raw; 00155 break; 00156 } 00157 const Dims ssize = img.getDims(); 00158 00159 const double wratio = double(dsize.w()) / double(ssize.w()); 00160 const double hratio = double(dsize.h()) / double(ssize.h()); 00161 this->ratio = std::min(wratio, hratio); 00162 00163 img = rescale(img, Dims(std::min(dsize.w(), int(ssize.w() * this->ratio)), 00164 std::min(dsize.h(), int(ssize.h() * this->ratio))), 00165 rtype); 00166 } 00167 00168 Dims rawdims; 00169 Image<PixRGB<byte> > img; 00170 double ratio; 00171 }; 00172 00173 namespace aux 00174 { 00175 void msg(const std::string& tag, const std::string& content) 00176 { 00177 fprintf(stderr, "%20s: %s\n", tag.c_str(), content.c_str()); 00178 } 00179 00180 bool file_exists(const std::string& fname) 00181 { 00182 struct stat statbuf; 00183 int res = ::stat(fname.c_str(), &statbuf); 00184 return (res == 0); 00185 } 00186 00187 bool image_file_exists(const std::string& fname) 00188 { 00189 struct stat statbuf; 00190 int res = ::stat(fname.c_str(), &statbuf); 00191 if (res != 0) 00192 return false; 00193 00194 if (statbuf.st_size == 0) 00195 { 00196 aux::msg("empty file", fname); 00197 return false; 00198 } 00199 00200 return true; 00201 } 00202 00203 ImageInfo build_scaled_pixmap(const std::string& fname, const Dims& dsize, 00204 const TransformType ttype, 00205 const RescaleType rtype) 00206 { 00207 const Image<PixRGB<byte> > raw = Raster::ReadRGB(fname); 00208 return ImageInfo(raw, dsize, ttype, rtype); 00209 } 00210 00211 bool is_img_file(const std::string& fname) 00212 { 00213 return (hasExtension(fname, ".jpg") 00214 || hasExtension(fname, ".jpeg") 00215 || hasExtension(fname, ".gif") 00216 || hasExtension(fname, ".pnm") 00217 || hasExtension(fname, ".png")); 00218 } 00219 } 00220 00221 class playlist 00222 { 00223 public: 00224 enum play_mode 00225 { 00226 SPINNING, 00227 JUMPING, 00228 }; 00229 00230 private: 00231 00232 const std::string m_list_file; 00233 std::vector<std::string> m_list; 00234 int m_idx; 00235 int m_guess_next; 00236 const rutz::shared_ptr<XWinManaged> m_widget; 00237 ImageInfo m_pixmap; 00238 std::map<std::string, ImageInfo> m_pixmap_cache; 00239 std::vector<std::string> m_purge_list; 00240 play_mode m_mode; 00241 int m_ndeleted; 00242 int m_nshown; 00243 int m_nmissed; 00244 int m_last_spin; 00245 rutz::time m_last_show_time; 00246 TransformType m_txtype; 00247 RescaleType m_rtype; 00248 bool m_didcache; 00249 bool m_looping; 00250 int m_loop_delay_power; 00251 bool m_show_overlay; 00252 random_sequence m_rseq; 00253 00254 public: 00255 playlist(const std::string& fname, rutz::shared_ptr<XWinManaged> widget) 00256 : 00257 m_list_file(fname), 00258 m_idx(0), 00259 m_guess_next(1), 00260 m_widget(widget), 00261 m_mode(SPINNING), 00262 m_ndeleted(0), 00263 m_nshown(0), 00264 m_nmissed(0), 00265 m_last_spin(1), 00266 m_txtype(TXTYPE_NONE), 00267 m_rtype(RESCALE_SIMPLE_BILINEAR), 00268 m_didcache(false), 00269 m_looping(false), 00270 m_loop_delay_power(0), 00271 m_show_overlay(true), 00272 m_rseq() 00273 { 00274 std::ifstream ifs(m_list_file.c_str()); 00275 if (!ifs.is_open()) 00276 LFATAL("couldn't open %s for reading", m_list_file.c_str()); 00277 std::string line; 00278 00279 while (std::getline(ifs, line)) 00280 m_list.push_back(line); 00281 } 00282 00283 void save() 00284 { 00285 aux::msg("write playlist", m_list_file); 00286 if (aux::file_exists(sformat("%s.bkp", m_list_file.c_str()))) 00287 rutz::unixcall::remove(sformat("%s.bkp", m_list_file.c_str()).c_str()); 00288 if (aux::file_exists(m_list_file)) 00289 rutz::unixcall::rename(m_list_file.c_str(), 00290 sformat("%s.bkp", m_list_file.c_str()).c_str()); 00291 00292 std::ofstream ofs(m_list_file.c_str()); 00293 if (!ofs.is_open()) 00294 LFATAL("couldn't open %s for writing", m_list_file.c_str()); 00295 for (size_t i = 0; i < m_list.size(); ++i) 00296 ofs << m_list.at(i) << '\n'; 00297 ofs.close(); 00298 } 00299 00300 void spin(int step) 00301 { 00302 m_mode = SPINNING; 00303 00304 if (m_list.size() == 0) 00305 { 00306 m_idx = 0; 00307 m_last_spin = 0; 00308 m_guess_next = 0; 00309 } 00310 else 00311 { 00312 ASSERT(m_list.size() > 0); 00313 00314 m_idx += step; 00315 while (m_idx < 0) m_idx += int(m_list.size()); 00316 while (m_idx >= int(m_list.size())) m_idx -= int(m_list.size()); 00317 m_last_spin = step; 00318 00319 int guess_step = step; 00320 if (guess_step == 0) { guess_step = 1; } 00321 00322 m_guess_next = m_idx + guess_step; 00323 while (m_guess_next < 0) m_guess_next += int(m_list.size()); 00324 while (m_guess_next >= int(m_list.size())) m_guess_next -= int(m_list.size()); 00325 } 00326 } 00327 00328 void jump(int oldlength = -1, int adjust = 0) 00329 { 00330 m_mode = JUMPING; 00331 00332 if (m_list.size() == 0) 00333 { 00334 m_idx = 0; 00335 m_guess_next = 0; 00336 } 00337 else 00338 { 00339 if (oldlength == -1) 00340 oldlength = m_list.size(); 00341 00342 ASSERT(m_list.size() >= 1); 00343 00344 m_idx = m_rseq.inext(0, oldlength) + adjust; 00345 if (m_idx < 0) m_idx = 0; 00346 else if (size_t(m_idx) >= m_list.size()) m_idx = m_list.size() - 1; 00347 m_guess_next = m_rseq.ipeek(0, m_list.size()); 00348 } 00349 } 00350 00351 std::string filename() const 00352 { 00353 if (m_list.size() == 0) 00354 return "(none)"; 00355 00356 ASSERT(size_t(m_idx) < m_list.size()); 00357 return m_list.at(m_idx); 00358 } 00359 00360 std::string status() const 00361 { 00362 if (m_list.size() == 0) 00363 return "(empty)"; 00364 00365 return sformat("(%d of %"ZU") %s", m_idx + 1, m_list.size(), 00366 this->filename().c_str()); 00367 } 00368 00369 void mode(const play_mode m) 00370 { 00371 m_mode = m; 00372 } 00373 00374 void remove_helper(bool do_purge) 00375 { 00376 if (m_list.size() == 0) 00377 return; 00378 00379 ASSERT(size_t(m_idx) < m_list.size()); 00380 std::string target = m_list.at(m_idx); 00381 aux::msg(sformat("hide file[%d]", m_idx), target.c_str()); 00382 if (do_purge) 00383 m_purge_list.push_back(target); 00384 00385 const size_t oldlength = m_list.size(); 00386 00387 m_list.erase(m_list.begin() + m_idx); 00388 00389 switch (m_mode) 00390 { 00391 case JUMPING: 00392 if (m_idx < m_guess_next) 00393 { 00394 aux::msg("jump offset", "1"); 00395 this->jump(oldlength, -1); 00396 } 00397 else 00398 { 00399 aux::msg("jump offset", "0"); 00400 this->jump(oldlength, 0); 00401 } 00402 break; 00403 00404 case SPINNING: 00405 default: 00406 if (m_last_spin <= 0) 00407 this->spin(m_last_spin); 00408 else 00409 this->spin(m_last_spin - 1); 00410 break; 00411 } 00412 } 00413 00414 void remove() { this->remove_helper(true); } 00415 void remove_no_purge() { this->remove_helper(false); } 00416 00417 void purge() 00418 { 00419 const size_t N = m_purge_list.size(); 00420 size_t n = 0; 00421 00422 while (!m_purge_list.empty()) 00423 { 00424 const std::string f = m_purge_list.back(); 00425 m_purge_list.pop_back(); 00426 ++n; 00427 aux::msg("purging", sformat("%"ZU" of %"ZU, n, N)); 00428 aux::msg("delete file", f); 00429 00430 std::string dirname, tail; 00431 splitPath(f, dirname, tail); 00432 const std::string stubfile = 00433 sformat("%s/.%s.deleted", dirname.c_str(), tail.c_str()); 00434 00435 std::ofstream ofs(stubfile.c_str()); 00436 ofs.close(); 00437 00438 try { 00439 rutz::unixcall::remove(f.c_str()); 00440 ++m_ndeleted; 00441 } 00442 catch (std::exception& e) { 00443 aux::msg("error during deletion", 00444 sformat("%s (%s)", f.c_str(), e.what())); 00445 } 00446 00447 this->redraw(false); 00448 } 00449 00450 m_purge_list.resize(0); 00451 this->save(); 00452 aux::msg("files deleted", sformat("%d", m_ndeleted)); 00453 aux::msg("files shown", sformat("%d", m_nshown)); 00454 aux::msg("cache misses", sformat("%d", m_nmissed)); 00455 aux::msg("percent kept", 00456 sformat("%.2f%%", 100.0 * (1.0 - double(m_ndeleted) 00457 / m_nshown))); 00458 } 00459 00460 void cachenext() 00461 { 00462 if (m_list.size() == 0) 00463 return; 00464 00465 if (m_guess_next < 0) m_guess_next = 0; 00466 if (size_t(m_guess_next) >= m_list.size()) m_guess_next = m_list.size() - 1; 00467 int i = m_guess_next; 00468 std::string f = m_list.at(i); 00469 while (!aux::image_file_exists(f)) 00470 { 00471 aux::msg(sformat("no such file[%d]", i), f); 00472 m_list.erase(m_list.begin() + i); 00473 i = i % m_list.size(); 00474 f = m_list.at(i); 00475 } 00476 if (m_pixmap_cache.find(f) == m_pixmap_cache.end()) 00477 { 00478 m_pixmap_cache[f] = 00479 aux::build_scaled_pixmap(f, m_widget->getDims(), m_txtype, m_rtype); 00480 00481 aux::msg(sformat("cache insert[%d]", i), f); 00482 } 00483 else 00484 { 00485 const Image<PixRGB<byte> > img = m_pixmap_cache[f].img; 00486 aux::msg(sformat("cache exists[%d]", i), 00487 sformat("%dx%d %s", img.getWidth(), img.getHeight(), 00488 f.c_str())); 00489 } 00490 } 00491 00492 double loop_delay() const 00493 { 00494 return 250.0 * pow(2.0, 0.5 * m_loop_delay_power); 00495 } 00496 00497 void redraw(bool show_image) 00498 { 00499 Image<PixRGB<byte> > img(m_widget->getDims(), ZEROS); 00500 00501 if (m_list.size() > 0 && show_image) 00502 inplacePaste(img, m_pixmap.img, 00503 Point2D<int>((img.getWidth() - m_pixmap.img.getWidth()) / 2, 00504 (img.getHeight() - m_pixmap.img.getHeight()) / 2)); 00505 00506 if (m_show_overlay) 00507 { 00508 const SimpleFont font = SimpleFont::FIXED(7); 00509 00510 struct stat statbuf; 00511 std::string mtime; 00512 if (0 == stat(this->filename().c_str(), &statbuf)) 00513 { 00514 char buf[32]; 00515 ctime_r(&statbuf.st_mtime, &buf[0]); 00516 mtime = buf; 00517 } 00518 00519 const std::string msgs[] = 00520 { 00521 sformat("#%d:%s", m_idx, this->filename().c_str()), 00522 sformat(" %s", mtime.c_str()), 00523 sformat(" %dx%d @ %d%%", m_pixmap.rawdims.w(), m_pixmap.rawdims.h(), int(0.5 + m_pixmap.ratio * 100.0)), 00524 m_looping ? sformat("loop:%.2fms", this->loop_delay()) : std::string("loop:off"), 00525 std::string(m_mode == JUMPING ? "mode:jumping" : "mode:spinning"), 00526 sformat("tx:%s", convertToString(m_txtype).c_str()), 00527 sformat("rs:%s", convertToString(m_rtype).c_str()), 00528 sformat("c:%"ZU, m_list.size()), 00529 sformat("p:%"ZU, m_purge_list.size()), 00530 sformat("d:%d", m_ndeleted), 00531 sformat("s:%d", m_nshown), 00532 sformat("m:%d", m_nmissed), 00533 }; 00534 00535 const int nmsg = sizeof(msgs) / sizeof(msgs[0]); 00536 00537 for (int i = 0; i < nmsg; ++i) 00538 writeText(img, Point2D<int>(1,1+i*font.h()), msgs[i].c_str(), 00539 PixRGB<byte>(255, 160, 0), 00540 PixRGB<byte>(0, 0, 0), 00541 font, 00542 true); 00543 } 00544 00545 m_widget->drawImage(img); 00546 } 00547 00548 void show() 00549 { 00550 if (m_list.size() > 0) 00551 { 00552 std::string f = this->filename(); 00553 00554 while (!aux::image_file_exists(f)) 00555 { 00556 aux::msg(sformat("no such file(%d)", m_idx), f); 00557 ASSERT(size_t(m_idx) < m_list.size()); 00558 m_list.erase(m_list.begin() + m_idx); 00559 m_idx = m_idx % m_list.size(); 00560 f = this->filename(); 00561 } 00562 00563 aux::msg("index", sformat("%d of %"ZU, m_idx, 00564 m_list.size())); 00565 aux::msg(sformat("show file[%d]", m_idx), f); 00566 00567 if (m_pixmap_cache.find(f) != m_pixmap_cache.end()) 00568 { 00569 m_pixmap = m_pixmap_cache[f]; 00570 m_pixmap_cache.erase(m_pixmap_cache.find(f)); 00571 aux::msg(sformat("cache hit[%d]", m_idx), f); 00572 } 00573 else 00574 { 00575 aux::msg(sformat("cache miss[%d]", m_idx), f); 00576 ++m_nmissed; 00577 m_pixmap = 00578 aux::build_scaled_pixmap(f, m_widget->getDims(), m_txtype, m_rtype); 00579 } 00580 } 00581 00582 this->redraw(true); 00583 ++m_nshown; 00584 00585 m_last_show_time = rutz::time::wall_clock_now(); 00586 } 00587 00588 rutz::time last_show_time() const { return m_last_show_time; } 00589 00590 void cycle_txtype() 00591 { 00592 switch (m_txtype) 00593 { 00594 case TXTYPE_NONE: m_txtype = TXTYPE_TRANSPOSE; break; 00595 case TXTYPE_TRANSPOSE: m_txtype = TXTYPE_BEST_FIT; break; 00596 case TXTYPE_BEST_FIT: m_txtype = TXTYPE_NONE; break; 00597 } 00598 m_pixmap_cache.clear(); 00599 } 00600 00601 void cycle_rtype() 00602 { 00603 switch (m_rtype) 00604 { 00605 case RESCALE_SIMPLE_NOINTERP: m_rtype = RESCALE_SIMPLE_BILINEAR; break; 00606 case RESCALE_SIMPLE_BILINEAR: m_rtype = RESCALE_FILTER_BSPLINE; break; 00607 case RESCALE_FILTER_BSPLINE: m_rtype = RESCALE_FILTER_LANCZOS3; break; 00608 default: m_rtype = RESCALE_SIMPLE_NOINTERP; break; 00609 } 00610 m_pixmap_cache.clear(); 00611 } 00612 00613 int run() 00614 { 00615 this->show(); 00616 00617 while (1) 00618 { 00619 KeySym ks = m_widget->getLastKeySym(); 00620 switch (ks) 00621 { 00622 case NoSymbol: 00623 if (!m_didcache) 00624 this->cachenext(); 00625 m_didcache = true; 00626 if (m_looping 00627 && 00628 (rutz::time::wall_clock_now() - this->last_show_time()).msec() 00629 >= this->loop_delay()) 00630 { 00631 if (JUMPING == m_mode) 00632 this->jump(); 00633 else 00634 this->spin(1); 00635 this->show(); 00636 m_didcache = false; 00637 } 00638 else 00639 usleep(10000); 00640 break; 00641 00642 case XK_Left: 00643 this->spin(-1); 00644 this->show(); 00645 m_didcache = false; 00646 break; 00647 00648 case XK_Right: 00649 this->spin(1); 00650 this->show(); 00651 m_didcache = false; 00652 break; 00653 00654 case XK_Up: 00655 this->jump(); 00656 this->show(); 00657 m_didcache = false; 00658 break; 00659 00660 case XK_Down: 00661 this->remove(); 00662 this->show(); 00663 m_didcache = false; 00664 break; 00665 00666 case XK_e: 00667 case XK_0: 00668 case XK_KP_0: 00669 this->remove_no_purge(); 00670 this->show(); 00671 m_didcache = false; 00672 break; 00673 00674 case XK_Return: 00675 this->save(); 00676 break; 00677 00678 case XK_x: 00679 this->purge(); 00680 this->redraw(true); 00681 break; 00682 00683 case XK_Escape: 00684 this->redraw(false); 00685 this->purge(); 00686 return 0; 00687 00688 case XK_l: 00689 m_looping = !m_looping; 00690 this->show(); 00691 break; 00692 00693 case XK_m: 00694 if (SPINNING == m_mode) m_mode = JUMPING; 00695 else if (JUMPING == m_mode) m_mode = SPINNING; 00696 this->show(); 00697 m_didcache = false; 00698 break; 00699 00700 case XK_o: 00701 m_show_overlay = !m_show_overlay; 00702 this->show(); 00703 break; 00704 00705 case XK_comma: 00706 ++m_loop_delay_power; 00707 break; 00708 00709 case XK_period: 00710 --m_loop_delay_power; 00711 break; 00712 00713 case XK_t: 00714 this->cycle_txtype(); 00715 this->show(); 00716 m_didcache = false; 00717 break; 00718 00719 case XK_r: 00720 this->cycle_rtype(); 00721 this->show(); 00722 m_didcache = false; 00723 break; 00724 } 00725 } 00726 } 00727 }; 00728 00729 int main(int argc, const char** argv) 00730 { 00731 Dims dims(800, 800); 00732 00733 if (argc != 2 && argc != 3) 00734 { 00735 fprintf(stderr, "usage: %s playlist ?WxH?\n", argv[0]); 00736 return 1; 00737 } 00738 00739 if (argc >= 3) 00740 convertFromString(argv[2], dims); 00741 00742 rutz::shared_ptr<XWinManaged> window(new XWinManaged(dims, -1, -1, 00743 argv[1])); 00744 00745 playlist pl(argv[1], window); 00746 00747 return pl.run(); 00748 } 00749 00750 // ###################################################################### 00751 /* So things look consistent in everyone's emacs... */ 00752 /* Local Variables: */ 00753 /* mode: c++ */ 00754 /* indent-tabs-mode: nil */ 00755 /* End: */ 00756 00757 #endif // APPMEDIA_APP_SLIDESHOW_C_DEFINED