more improvements

This commit is contained in:
David Rose 2002-04-06 22:34:50 +00:00
parent ce32c8c748
commit 0580d974ee
6 changed files with 401 additions and 103 deletions

View File

@ -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();

View File

@ -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;

View File

@ -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.

View File

@ -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)) {

View File

@ -22,6 +22,10 @@
#include "indent.h"
#include "notify.h"
#include "string_utils.h"
#include "indexParameters.h"
#include <ctype.h>
#include <algorithm>
////////////////////////////////////////////////////////////////////
// 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
<< "<h2>" << _basename << "</h2>\n";
<< "<h2>" << _name << "</h2>\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;
}

View File

@ -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<Photo *> Photos;
Photos _photos;
Filename _newest_contributing_filename;
typedef pvector<IndexImage *> IndexImages;
IndexImages _index_images;