From 0580d974eea7f34e811d0c74bad8749e452bce39 Mon Sep 17 00:00:00 2001 From: David Rose Date: Sat, 6 Apr 2002 22:34:50 +0000 Subject: [PATCH] more improvements --- pandaapp/src/indexify/indexImage.cxx | 198 +++++++++++++++------- pandaapp/src/indexify/indexParameters.cxx | 3 + pandaapp/src/indexify/indexParameters.h | 3 + pandaapp/src/indexify/indexify.cxx | 127 ++++++++++---- pandaapp/src/indexify/rollDirectory.cxx | 165 ++++++++++++++++-- pandaapp/src/indexify/rollDirectory.h | 8 + 6 files changed, 401 insertions(+), 103 deletions(-) diff --git a/pandaapp/src/indexify/indexImage.cxx b/pandaapp/src/indexify/indexImage.cxx index 61d9f5cb12..d593ba0691 100644 --- a/pandaapp/src/indexify/indexImage.cxx +++ b/pandaapp/src/indexify/indexImage.cxx @@ -94,69 +94,153 @@ generate_images(const Filename &archive_dir, TextMaker *text_maker) { int actual_index_height = thumb_y_space + num_rows * (thumb_height + thumb_caption_height + thumb_y_space); PNMImage index_image; - index_image.clear(actual_index_width, actual_index_height); - index_image.fill(1.0, 1.0, 1.0); Filename reduced_dir(archive_dir, "reduced/" + _dir->get_basename()); - Photos::const_iterator pi; - for (pi = _photos.begin(); pi != _photos.end(); ++pi) { - const PhotoInfo &pinfo = (*pi); - Photo *photo = _dir->get_photo(pinfo._photo_index); - Filename photo_filename(_dir->get_dir(), photo->get_basename()); - nout << "Reading " << photo_filename << "\n"; - - PNMImage photo_image; - if (!photo_image.read(photo_filename)) { - nout << "Unable to read.\n"; - return false; - } - - photo->_full_x_size = photo_image.get_x_size(); - photo->_full_y_size = photo_image.get_y_size(); - - // Generate a reduced image for the photo. - PNMImage reduced_image; - compute_reduction(photo_image, reduced_image, reduced_width, reduced_height); - reduced_image.quick_filter_from(photo_image); - Filename reduced_filename(reduced_dir, photo->get_basename()); - reduced_filename.make_dir(); - nout << "Writing " << reduced_filename << "\n"; - if (!reduced_image.write(reduced_filename)) { - nout << "Unable to write.\n"; - return false; - } - - photo->_reduced_x_size = reduced_image.get_x_size(); - photo->_reduced_y_size = reduced_image.get_y_size(); - - // Generate a thumbnail image for the photo. - PNMImage thumbnail_image; - compute_reduction(reduced_image, thumbnail_image, thumb_width, thumb_height); - - thumbnail_image.quick_filter_from(reduced_image); - // Center the thumbnail image within its box. - int x_center = (thumb_width - thumbnail_image.get_x_size()) / 2; - int y_center = (thumb_height - thumbnail_image.get_y_size()) / 2; - - index_image.copy_sub_image(thumbnail_image, - pinfo._x_place + x_center, - pinfo._y_place + y_center); - - if (text_maker != (TextMaker *)NULL) { - text_maker->generate_into(photo->get_name(), index_image, - pinfo._x_place + thumb_width / 2, - pinfo._y_place + thumb_height + thumb_caption_height); - } - } - Filename output_filename(archive_dir, _name); output_filename.set_extension("jpg"); - nout << "Writing " << output_filename << "\n"; - if (!index_image.write(output_filename)) { - nout << "Unable to write.\n"; - return false; + Photos::const_iterator pi; + + // First, scan the image files to see if we can avoid regenerating + // the index image. + bool generate_index_image = true; + if (!force_regenerate && output_filename.exists()) { + // Maybe we don't need to renegerated the index. + generate_index_image = false; + + const Filename &newest_contributing_filename = + _dir->get_newest_contributing_filename(); + if (!newest_contributing_filename.empty()) { + generate_index_image = + (output_filename.compare_timestamps(newest_contributing_filename) < 0); + } + + for (pi = _photos.begin(); + pi != _photos.end() && !generate_index_image; + ++pi) { + const PhotoInfo &pinfo = (*pi); + Photo *photo = _dir->get_photo(pinfo._photo_index); + Filename photo_filename(_dir->get_dir(), photo->get_basename()); + + // If any of the source photos are newer than the index image, + // we must regenerate it. + generate_index_image = + (output_filename.compare_timestamps(photo_filename) < 0); + } + } + + if (generate_index_image) { + index_image.clear(actual_index_width, actual_index_height); + index_image.fill(1.0, 1.0, 1.0); + } + + // Now go back through and read whichever images are necessary, or + // just read the headers if we can get away with it. + for (pi = _photos.begin(); pi != _photos.end(); ++pi) { + const PhotoInfo &pinfo = (*pi); + Photo *photo = _dir->get_photo(pinfo._photo_index); + Filename photo_filename(_dir->get_dir(), photo->get_basename()); + Filename reduced_filename(reduced_dir, photo->get_basename()); + PNMImage reduced_image; + + if (force_regenerate || + reduced_filename.compare_timestamps(photo_filename) < 0) { + // If the reduced filename does not exist or is older than the + // source filename, we must read the complete source filename to + // generate the reduced image. + nout << "Reading " << photo_filename << "\n"; + + PNMImage photo_image; + if (!photo_image.read(photo_filename)) { + nout << "Unable to read.\n"; + return false; + } + + photo->_full_x_size = photo_image.get_x_size(); + photo->_full_y_size = photo_image.get_y_size(); + + // Generate a reduced image for the photo. + compute_reduction(photo_image, reduced_image, reduced_width, reduced_height); + reduced_image.quick_filter_from(photo_image); + reduced_filename.make_dir(); + nout << "Writing " << reduced_filename << "\n"; + if (!reduced_image.write(reduced_filename)) { + nout << "Unable to write.\n"; + return false; + } + + photo->_reduced_x_size = reduced_image.get_x_size(); + photo->_reduced_y_size = reduced_image.get_y_size(); + + } else { + // If the reduced image already exists and is newer than the + // source image, use it. + + // We still read the image header to determine its size. + PNMImageHeader photo_image; + if (!photo_image.read_header(photo_filename)) { + nout << "Unable to read " << photo_filename << "\n"; + return false; + } + + photo->_full_x_size = photo_image.get_x_size(); + photo->_full_y_size = photo_image.get_y_size(); + + if (generate_index_image) { + // Now read the reduced image from disk, so we can put it on + // the index image. + nout << "Reading " << reduced_filename << "\n"; + + if (!reduced_image.read(reduced_filename)) { + nout << "Unable to read.\n"; + return false; + } + + photo->_reduced_x_size = reduced_image.get_x_size(); + photo->_reduced_y_size = reduced_image.get_y_size(); + + } else { + // If we're not generating an index image, we don't even need + // the reduced image--just scan its header to get its size. + PNMImageHeader reduced_image; + if (!reduced_image.read_header(reduced_filename)) { + nout << "Unable to read " << reduced_filename << "\n"; + return false; + } + photo->_reduced_x_size = reduced_image.get_x_size(); + photo->_reduced_y_size = reduced_image.get_y_size(); + } + } + + if (generate_index_image) { + // Generate a thumbnail image for the photo. + PNMImage thumbnail_image; + compute_reduction(reduced_image, thumbnail_image, thumb_width, thumb_height); + + thumbnail_image.quick_filter_from(reduced_image); + // Center the thumbnail image within its box. + int x_center = (thumb_width - thumbnail_image.get_x_size()) / 2; + int y_center = (thumb_height - thumbnail_image.get_y_size()) / 2; + + index_image.copy_sub_image(thumbnail_image, + pinfo._x_place + x_center, + pinfo._y_place + y_center); + + if (text_maker != (TextMaker *)NULL) { + text_maker->generate_into(photo->get_name(), index_image, + pinfo._x_place + thumb_width / 2, + pinfo._y_place + thumb_height + thumb_caption_height); + } + } + } + + if (generate_index_image) { + nout << "Writing " << output_filename << "\n"; + if (!index_image.write(output_filename)) { + nout << "Unable to write.\n"; + return false; + } } _index_x_size = index_image.get_x_size(); diff --git a/pandaapp/src/indexify/indexParameters.cxx b/pandaapp/src/indexify/indexParameters.cxx index ad1804c1a9..c849fa6ce1 100644 --- a/pandaapp/src/indexify/indexParameters.cxx +++ b/pandaapp/src/indexify/indexParameters.cxx @@ -37,6 +37,9 @@ Filename prev_icon; Filename next_icon; Filename up_icon; +bool force_regenerate = false; +bool format_rose = false; + // Computed parameters int thumb_count_x; int thumb_count_y; diff --git a/pandaapp/src/indexify/indexParameters.h b/pandaapp/src/indexify/indexParameters.h index 845174dcfb..711ab75430 100644 --- a/pandaapp/src/indexify/indexParameters.h +++ b/pandaapp/src/indexify/indexParameters.h @@ -44,6 +44,9 @@ extern Filename prev_icon; extern Filename next_icon; extern Filename up_icon; +extern bool force_regenerate; +extern bool format_rose; + void finalize_parameters(); // The following parameters are all computed based on the above. diff --git a/pandaapp/src/indexify/indexify.cxx b/pandaapp/src/indexify/indexify.cxx index 5ec8db0cda..4d13124275 100644 --- a/pandaapp/src/indexify/indexify.cxx +++ b/pandaapp/src/indexify/indexify.cxx @@ -41,7 +41,33 @@ Indexify() { "archives (typically JPEG files), and will generate a number of " "thumbnail images and a series of HTML pages to browse them. It is " "especially useful in preparation for burning the photo archives to " - "CD."); + "CD.\n\n" + + "A number of directories is named on the command line; each " + "directory must contain a number of image files, and all directories " + "should be within the same parent directory. Each directory is " + "considered a \"roll\", which may or may not correspond to a physical " + "roll of film, and the photos within each directory are grouped " + "correspondingly on the generated HTML pages.\n\n" + + "If a file exists by the same name as an image file but with the " + "extension \"cm\", that file is taken to be a HTML comment about that " + "particular image and is inserted the HTML page for that image. " + "Similarly, if there is a file within a roll directory with the same " + "name as the directory itself (but with the extension \"cm\"), that file " + "is inserted into the front page to introduce that particular roll.\n\n" + + "Normally, all image files with the specified extension (normally " + "\"jpg\") within a roll directory are included in the index, and sorted " + "into alphabetical (or numeric) order. If you wish to specify a " + "different order, or use only a subset of the images in a directory, " + "create a file in the roll directory with the same name as the " + "directory itself, and the extension \"ls\". This file should " + "simply list the filenames (with or without extension) within the " + "roll directory in the order they should be listed. If the ls " + "file exists but is empty, it indicates that the files should be " + "listed in reverse order, as from a camera that loads its film " + "upside-down."); add_option ("t", "title", 0, @@ -63,6 +89,21 @@ Indexify() { "directories, from the directory named by -a.", &Indexify::dispatch_filename, NULL, &_roll_dir_root); + add_option + ("f", "", 0, + "Forces the regeneration of all reduced and thumbnail images, even if " + "image files already exist that seem to be newer than the source " + "image files.", + &Indexify::dispatch_none, &force_regenerate); + + add_option + ("r", "", 0, + "Specifies that roll directory names are encoded using the Rose " + "convention of six digits: mmyyss, where mm and yy are the month and " + "year, and ss is a sequence number of the roll within the month. This " + "name will be reformatted to m-yy/s for output.", + &Indexify::dispatch_none, &format_rose); + add_option ("e", "extension", 0, "Specifies the filename extension (without a leading dot) to identify " @@ -170,18 +211,23 @@ handle_args(ProgramBase::Args &args) { Filename filename = Filename::from_os_specific(*ai); filename.standardize(); if (filename.is_directory()) { - RollDirectory *roll_dir = new RollDirectory(filename); - if (prev_roll_dir != (RollDirectory *)NULL) { - roll_dir->_prev = prev_roll_dir; - prev_roll_dir->_next = roll_dir; + string basename = filename.get_basename(); + if (basename == "icons" || basename == "html" || basename == "reduced") { + nout << "Ignoring " << filename << "; indexify-generated directory.\n"; + + } else { + RollDirectory *roll_dir = new RollDirectory(filename); + if (prev_roll_dir != (RollDirectory *)NULL) { + roll_dir->_prev = prev_roll_dir; + prev_roll_dir->_next = roll_dir; + } + + _roll_dirs.push_back(roll_dir); + prev_roll_dir = roll_dir; } - _roll_dirs.push_back(roll_dir); - prev_roll_dir = roll_dir; - } else if (filename.exists()) { - nout << filename << " is not a directory name.\n"; - return false; + nout << "Ignoring " << filename << "; not a directory.\n"; } else { nout << filename << " does not exist.\n"; @@ -225,9 +271,9 @@ post_command_line() { if (_front_title.empty()) { // Supply a default title. if (_roll_dirs.size() == 1) { - _front_title = _roll_dirs.front()->get_basename(); + _front_title = _roll_dirs.front()->get_name(); } else { - _front_title = _roll_dirs.front()->get_basename() + " to " + _roll_dirs.back()->get_basename(); + _front_title = _roll_dirs.front()->get_name() + " to " + _roll_dirs.back()->get_name(); } } @@ -257,41 +303,51 @@ post_command_line() { if (prev_icon.empty()) { prev_icon = Filename("icons", default_left_icon_filename); Filename icon_filename(_archive_dir, prev_icon); - icon_filename.make_dir(); - icon_filename.set_binary(); - ofstream output; - if (!icon_filename.open_write(output)) { - nout << "Unable to write to " << icon_filename << "\n"; - exit(1); + if (force_regenerate || !icon_filename.exists()) { + nout << "Generating " << icon_filename << "\n"; + icon_filename.make_dir(); + icon_filename.set_binary(); + + ofstream output; + if (!icon_filename.open_write(output)) { + nout << "Unable to write to " << icon_filename << "\n"; + exit(1); + } + output.write(default_left_icon, default_left_icon_size); } - output.write(default_left_icon, default_left_icon_size); } if (next_icon.empty()) { next_icon = Filename("icons", default_right_icon_filename); Filename icon_filename(_archive_dir, next_icon); - icon_filename.make_dir(); - icon_filename.set_binary(); - - ofstream output; - if (!icon_filename.open_write(output)) { - nout << "Unable to write to " << icon_filename << "\n"; - exit(1); + if (force_regenerate || !icon_filename.exists()) { + nout << "Generating " << icon_filename << "\n"; + icon_filename.make_dir(); + icon_filename.set_binary(); + + ofstream output; + if (!icon_filename.open_write(output)) { + nout << "Unable to write to " << icon_filename << "\n"; + exit(1); + } + output.write(default_right_icon, default_right_icon_size); } - output.write(default_right_icon, default_right_icon_size); } if (up_icon.empty()) { up_icon = Filename("icons", default_up_icon_filename); Filename icon_filename(_archive_dir, up_icon); - icon_filename.make_dir(); - icon_filename.set_binary(); - - ofstream output; - if (!icon_filename.open_write(output)) { - nout << "Unable to write to " << icon_filename << "\n"; - exit(1); + if (force_regenerate || !icon_filename.exists()) { + nout << "Generating " << icon_filename << "\n"; + icon_filename.make_dir(); + icon_filename.set_binary(); + + ofstream output; + if (!icon_filename.open_write(output)) { + nout << "Unable to write to " << icon_filename << "\n"; + exit(1); + } + output.write(default_up_icon, default_up_icon_size); } - output.write(default_up_icon, default_up_icon_size); } } @@ -339,6 +395,7 @@ run() { // Then go back and generate the HTML. Filename html_filename(_archive_dir, "index.htm"); + nout << "Generating " << html_filename << "\n"; html_filename.set_text(); ofstream root_html; if (!html_filename.open_write(root_html)) { diff --git a/pandaapp/src/indexify/rollDirectory.cxx b/pandaapp/src/indexify/rollDirectory.cxx index 2955bc0651..d3c4d6ff34 100644 --- a/pandaapp/src/indexify/rollDirectory.cxx +++ b/pandaapp/src/indexify/rollDirectory.cxx @@ -22,6 +22,10 @@ #include "indent.h" #include "notify.h" #include "string_utils.h" +#include "indexParameters.h" + +#include +#include //////////////////////////////////////////////////////////////////// // Function: RollDirectory::Constructor @@ -33,6 +37,11 @@ RollDirectory(const Filename &dir) : _dir(dir) { _basename = _dir.get_basename(); + if (format_rose) { + _name = format_basename(_basename); + } else { + _name = _basename; + } _prev = (RollDirectory *)NULL; _next = (RollDirectory *)NULL; } @@ -77,6 +86,21 @@ get_basename() const { return _basename; } +//////////////////////////////////////////////////////////////////// +// Function: RollDirectory::get_name +// Access: Public +// Description: Returns the formatted name of the directory. This is +// often the same as the basename, but if -r is +// specified on the command line it might be a somewhat +// different, reformatted name. In any case, it is the +// name of the roll as it should be presented to the +// user. +//////////////////////////////////////////////////////////////////// +const string &RollDirectory:: +get_name() const { + return _name; +} + //////////////////////////////////////////////////////////////////// // Function: RollDirectory::scan // Access: Public @@ -84,17 +108,71 @@ get_basename() const { //////////////////////////////////////////////////////////////////// bool RollDirectory:: scan(const string &extension) { - vector_string contents; - if (!_dir.scan_directory(contents)) { - nout << "Could not read " << _dir << "\n"; - return false; + bool reverse_order = false; + bool explicit_list = false; + + // Check for an .ls file in the roll directory, which may give an + // explicit ordering, or if empty, it specifies reverse ordering. + Filename ls_filename(_dir, _basename + ".ls"); + if (ls_filename.exists()) { + add_contributing_filename(ls_filename); + ls_filename.set_text(); + ifstream ls; + if (!ls_filename.open_read(ls)) { + nout << "Could not read " << ls_filename << "\n"; + } else { + bool any_words = false; + string word; + ls >> word; + while (!ls.eof() && !ls.fail()) { + any_words = true; + Filename try_filename(_dir, word); + if (!try_filename.exists()) { + try_filename = Filename(_dir, word + "." + extension); + } + if (!try_filename.exists()) { + try_filename = Filename(_dir, _basename + word + "." + extension); + } + if (!try_filename.exists()) { + try_filename = Filename(_dir, _basename + "0" + word + "." + extension); + } + if (try_filename.exists()) { + _photos.push_back(new Photo(this, try_filename.get_basename())); + } else { + nout << "Frame " << word << " not found in " << _name << "\n"; + } + ls >> word; + } + + if (!any_words) { + // An empty .ls file just means reverse the order. + reverse_order = true; + } else { + // A non-empty .ls file has listed all the files we need; no + // need to scan the directory. + explicit_list = true; + } + } } - vector_string::iterator ci; - for (ci = contents.begin(); ci != contents.end(); ++ci) { - Filename basename = (*ci); - if (basename.get_extension() == extension) { - _photos.push_back(new Photo(this, basename)); + if (!explicit_list) { + vector_string contents; + + if (!_dir.scan_directory(contents)) { + nout << "Could not read " << _dir << "\n"; + return false; + } + + if (reverse_order) { + reverse(contents.begin(), contents.end()); + } + + vector_string::iterator ci; + for (ci = contents.begin(); ci != contents.end(); ++ci) { + Filename basename = (*ci); + if (basename.get_extension() == extension) { + _photos.push_back(new Photo(this, basename)); + } } } @@ -131,6 +209,20 @@ collect_index_images() { } } +//////////////////////////////////////////////////////////////////// +// Function: RollDirectory::get_newest_contributing_filename +// Access: Public +// Description: Returns the Filename with the newest timestamp that +// contributes to the contents of the index images in +// this directory, if any (other than the images +// themselves). This is used by the IndexImage code to +// determine if it needs to regenerate the index image. +//////////////////////////////////////////////////////////////////// +const Filename &RollDirectory:: +get_newest_contributing_filename() const { + return _newest_contributing_filename; +} + //////////////////////////////////////////////////////////////////// // Function: RollDirectory::get_num_photos // Access: Public @@ -218,9 +310,12 @@ generate_html(ostream &root_html, const Filename &archive_dir, } else { root_html - << "

" << _basename << "

\n"; + << "

" << _name << "

\n"; } + nout << "Generating " << Filename(archive_dir, "html/") + << _basename << "/*\n"; + IndexImages::iterator ii; for (ii = _index_images.begin(); ii != _index_images.end(); ++ii) { IndexImage *index_image = (*ii); @@ -239,7 +334,7 @@ generate_html(ostream &root_html, const Filename &archive_dir, //////////////////////////////////////////////////////////////////// void RollDirectory:: output(ostream &out) const { - out << _basename; + out << _name; } //////////////////////////////////////////////////////////////////// @@ -300,6 +395,7 @@ insert_html_comment(ostream &html, Filename cm_filename) { } // We didn't find it. Insert the whole file. + cm.clear(); cm.seekg(0); int ch = cm.get(); while (ch != EOF) { @@ -310,6 +406,20 @@ insert_html_comment(ostream &html, Filename cm_filename) { return true; } +//////////////////////////////////////////////////////////////////// +// Function: RollDirectory::add_contributing_filename +// Access: Private +// Description: Specifies an additional filename that contributes to +// the index image. +//////////////////////////////////////////////////////////////////// +void RollDirectory:: +add_contributing_filename(const Filename &filename) { + if (_newest_contributing_filename.empty() || + _newest_contributing_filename.compare_timestamps(filename) < 0) { + _newest_contributing_filename = filename; + } +} + //////////////////////////////////////////////////////////////////// // Function: RollDirectory::insert_html_comment_body // Access: Private, Static @@ -334,3 +444,36 @@ insert_html_comment_body(ostream &html, istream &cm) { // Reached end of file; good enough. return true; } + +//////////////////////////////////////////////////////////////////// +// Function: RollDirectory::format_basename +// Access: Private, Static +// Description: Reformats the roll directory into a user-friendly +// name based on its encoded directory name. +//////////////////////////////////////////////////////////////////// +string RollDirectory:: +format_basename(const string &basename) { + if (basename.length() <= 4) { + return basename; + } + + // The first four characters must be digits. + for (size_t i = 0; i < 4; i++) { + if (!isdigit(basename[i])) { + return basename; + } + } + + string mm = basename.substr(0, 2); + string yy = basename.substr(2, 2); + string ss = basename.substr(4); + + if (mm[0] == '0') { + mm = mm[1]; + } + while (ss.length() > 1 && ss[0] == '0') { + ss = ss.substr(1); + } + + return mm + "-" + yy + "/" + ss; +} diff --git a/pandaapp/src/indexify/rollDirectory.h b/pandaapp/src/indexify/rollDirectory.h index bf3cb3e4be..3960612fcc 100644 --- a/pandaapp/src/indexify/rollDirectory.h +++ b/pandaapp/src/indexify/rollDirectory.h @@ -40,9 +40,12 @@ public: const Filename &get_dir() const; const string &get_basename() const; + const string &get_name() const; bool scan(const string &extension); void collect_index_images(); + const Filename &get_newest_contributing_filename() const; + int get_num_photos() const; Photo *get_photo(int n) const; @@ -59,7 +62,9 @@ public: static bool insert_html_comment(ostream &html, Filename cm_filename); private: + void add_contributing_filename(const Filename &filename); static bool insert_html_comment_body(ostream &html, istream &cm); + static string format_basename(const string &basename); public: RollDirectory *_prev; @@ -68,8 +73,11 @@ public: private: Filename _dir; string _basename; + string _name; typedef pvector Photos; Photos _photos; + + Filename _newest_contributing_filename; typedef pvector IndexImages; IndexImages _index_images;