00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
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
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
00752
00753
00754
00755
00756
00757 #endif // APPMEDIA_APP_SLIDESHOW_C_DEFINED