720 lines
24 KiB
C++
720 lines
24 KiB
C++
// Filename: indexImage.cxx
|
|
// Created by: drose (03Apr02)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://www.panda3d.org/license.txt .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d@yahoogroups.com .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "indexImage.h"
|
|
#include "rollDirectory.h"
|
|
#include "photo.h"
|
|
#include "textMaker.h"
|
|
#include "indexParameters.h"
|
|
#include "pnmImage.h"
|
|
|
|
#include "string_utils.h"
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
IndexImage::
|
|
IndexImage(RollDirectory *dir, int index) :
|
|
_dir(dir),
|
|
_index(index)
|
|
{
|
|
_name = _dir->get_basename() + "_" + format_string(index + 1);
|
|
_index_x_size = 0;
|
|
_index_y_size = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::Destructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
IndexImage::
|
|
~IndexImage() {
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::add_photo
|
|
// Access: Public
|
|
// Description: Adds the nth photo in the roll to the index image.
|
|
// Returns true if the photo is added, or false if there
|
|
// was no more room.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool IndexImage::
|
|
add_photo(int photo_index) {
|
|
int count = _photos.size();
|
|
if (count >= max_thumbs) {
|
|
return false;
|
|
}
|
|
|
|
int yi = count / thumb_count_x;
|
|
int xi = count - (yi * thumb_count_x);
|
|
|
|
int x = thumb_x_space + xi * (thumb_width + thumb_x_space);
|
|
int y = thumb_y_space + yi * (thumb_width + thumb_caption_height + thumb_x_space);
|
|
|
|
PhotoInfo pinfo;
|
|
pinfo._photo_index = photo_index;
|
|
pinfo._x_place = x;
|
|
pinfo._y_place = y;
|
|
_photos.push_back(pinfo);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::generate_images
|
|
// Access: Public
|
|
// Description: Computes the thumbnail image file and writes it to
|
|
// the indicated directory; generates all reduced images
|
|
// associated with these photos into the appropriate
|
|
// subdirectory. Returns true on success, false on
|
|
// failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool IndexImage::
|
|
generate_images(const Filename &archive_dir, TextMaker *text_maker) {
|
|
int count = _photos.size();
|
|
int num_rows = (count + thumb_count_x - 1) / thumb_count_x;
|
|
int actual_index_height = thumb_y_space + num_rows * (thumb_height + thumb_caption_height + thumb_y_space);
|
|
|
|
PNMImage index_image;
|
|
|
|
Filename reduced_dir(archive_dir, "reduced/" + _dir->get_basename());
|
|
|
|
Filename output_filename(archive_dir, _name);
|
|
output_filename.set_extension("jpg");
|
|
|
|
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 (!dummy_mode && !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 (!dummy_mode &&
|
|
(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 (dummy_mode) {
|
|
// In dummy mode, we may or may not actually have a reduced
|
|
// image. In either case, ignore the file and compute its
|
|
// appropriate size from the source image.
|
|
compute_reduction(photo_image, reduced_image, reduced_width, reduced_height);
|
|
photo->_reduced_x_size = reduced_image.get_x_size();
|
|
photo->_reduced_y_size = reduced_image.get_y_size();
|
|
|
|
} else 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.
|
|
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_interior_width, thumb_interior_height);
|
|
|
|
if (dummy_mode) {
|
|
draw_box(thumbnail_image);
|
|
} else {
|
|
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;
|
|
|
|
if (draw_frames) {
|
|
draw_frame(index_image,
|
|
pinfo._x_place, pinfo._y_place,
|
|
thumb_width, thumb_height,
|
|
pinfo._x_place + x_center, pinfo._y_place + y_center,
|
|
thumbnail_image.get_x_size(), thumbnail_image.get_y_size());
|
|
}
|
|
|
|
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_frame_number(), 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();
|
|
_index_y_size = index_image.get_y_size();
|
|
_index_basename = output_filename.get_basename();
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::generate_html
|
|
// Access: Public
|
|
// Description: Generates all HTML files associated with reduced
|
|
// images into the appropriate subdirectory, and
|
|
// generates root-level HTML code to access the
|
|
// thumbnails on the index image into the indicated
|
|
// stream.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool IndexImage::
|
|
generate_html(ostream &root_html, const Filename &archive_dir,
|
|
const Filename &roll_dir_root) {
|
|
root_html
|
|
<< "<a name=\"" << _name << "\">\n"
|
|
<< "<img src=\"" << _index_basename
|
|
<< "\" width=" << _index_x_size << " height=" << _index_y_size
|
|
<< " ismap usemap=\"#" << _name << "\">\n"
|
|
<< "<map name=\"" << _name << "\"><br>\n";
|
|
|
|
Photos::const_iterator pi;
|
|
for (pi = _photos.begin(); pi != _photos.end(); ++pi) {
|
|
const PhotoInfo &pinfo = (*pi);
|
|
int photo_index = pinfo._photo_index;
|
|
Photo *photo = _dir->get_photo(pinfo._photo_index);
|
|
Filename html_relname("html/" + _dir->get_basename(),
|
|
photo->get_basename());
|
|
html_relname.set_extension("htm");
|
|
|
|
Filename html_filename(archive_dir, html_relname);
|
|
html_filename.make_dir();
|
|
html_filename.set_text();
|
|
ofstream reduced_html;
|
|
if (!html_filename.open_write(reduced_html)) {
|
|
nout << "Unable to write to " << html_filename << "\n";
|
|
return false;
|
|
}
|
|
if (!generate_reduced_html(reduced_html, photo, photo_index,
|
|
pi - _photos.begin(), roll_dir_root)) {
|
|
return false;
|
|
}
|
|
|
|
root_html
|
|
<< " <area shape=\"RECT\" href=\"" << html_relname
|
|
<< "\" coords=\""
|
|
<< pinfo._x_place << "," << pinfo._y_place << ","
|
|
<< pinfo._x_place + thumb_width << ","
|
|
<< pinfo._y_place + thumb_height + thumb_caption_height
|
|
<< "\">\n";
|
|
}
|
|
|
|
root_html
|
|
<< "</map>\n";
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::output
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void IndexImage::
|
|
output(ostream &out) const {
|
|
out << _name;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::write
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void IndexImage::
|
|
write(ostream &out, int indent_level) const {
|
|
indent(out, indent_level)
|
|
<< _name << "\n";
|
|
Photos::const_iterator pi;
|
|
for (pi = _photos.begin(); pi != _photos.end(); ++pi) {
|
|
const PhotoInfo &pinfo = (*pi);
|
|
indent(out, indent_level + 2)
|
|
<< *_dir->get_photo(pinfo._photo_index) << "\n";
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::generate_reduced_html
|
|
// Access: Public
|
|
// Description: Generates HTML code for rendering the indicated
|
|
// reduced-image photo into the given ostream.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool IndexImage::
|
|
generate_reduced_html(ostream &html, Photo *photo, int photo_index, int pi,
|
|
const Filename &roll_dir_root) {
|
|
Filename full_dir =
|
|
compose_href("../..", roll_dir_root, _dir->get_basename());
|
|
Filename full(full_dir, photo->get_basename());
|
|
|
|
Filename reduced_dir("../../reduced", _dir->get_basename());
|
|
Filename reduced(reduced_dir, photo->get_basename());
|
|
|
|
Filename root_html("../..", "index.htm");
|
|
string up_href = root_html.get_fullpath() + "#" + _name;
|
|
|
|
Filename prev_photo_filename;
|
|
Filename next_photo_filename;
|
|
|
|
if (pi != 0) {
|
|
// If this is not the first photo on the index, "back" will take
|
|
// us to the previous photo on the same index.
|
|
Photo *prev_photo = _dir->get_photo(_photos[pi - 1]._photo_index);
|
|
prev_photo_filename = Filename(prev_photo->get_basename());
|
|
prev_photo_filename.set_extension("htm");
|
|
|
|
} else {
|
|
// If this is the first photo on the index, "back" will take us to
|
|
// the last photo on the previous index. Or even to the last
|
|
// photo on the previous roll.
|
|
RollDirectory *roll_dir = _dir;
|
|
int index = _index - 1;
|
|
while (index < 0 && roll_dir->_prev != (RollDirectory *)NULL) {
|
|
roll_dir = roll_dir->_prev;
|
|
index = roll_dir->get_num_index_images() - 1;
|
|
}
|
|
|
|
if (index >= 0) {
|
|
IndexImage *prev_index = roll_dir->get_index_image(index);
|
|
nassertr(!prev_index->_photos.empty(), false);
|
|
Photo *prev_photo =
|
|
roll_dir->get_photo(prev_index->_photos.back()._photo_index);
|
|
Filename other_dir("..", roll_dir->get_basename());
|
|
prev_photo_filename = Filename(other_dir, prev_photo->get_basename());
|
|
prev_photo_filename.set_extension("htm");
|
|
}
|
|
}
|
|
|
|
if (pi + 1 < (int)_photos.size()) {
|
|
// If this is not the last photo, "next" will take us to the next
|
|
// photo on the same index.
|
|
Photo *next_photo = _dir->get_photo(_photos[pi + 1]._photo_index);
|
|
next_photo_filename = Filename(next_photo->get_basename());
|
|
next_photo_filename.set_extension("htm");
|
|
|
|
} else {
|
|
// If this is the last photo on the index, "next" will take us to
|
|
// the first photo on the next index. Or even to the first photo
|
|
// on the next roll.
|
|
|
|
RollDirectory *roll_dir = _dir;
|
|
int index = _index + 1;
|
|
while (index >= roll_dir->get_num_index_images() &&
|
|
roll_dir->_next != (RollDirectory *)NULL) {
|
|
roll_dir = roll_dir->_next;
|
|
index = 0;
|
|
}
|
|
|
|
if (index < roll_dir->get_num_index_images()) {
|
|
IndexImage *next_index = roll_dir->get_index_image(index);
|
|
nassertr(!next_index->_photos.empty(), false);
|
|
Photo *next_photo =
|
|
roll_dir->get_photo(next_index->_photos.front()._photo_index);
|
|
Filename other_dir("..", roll_dir->get_basename());
|
|
next_photo_filename = Filename(other_dir, next_photo->get_basename());
|
|
next_photo_filename.set_extension("htm");
|
|
}
|
|
}
|
|
|
|
html
|
|
<< "<html>\n"
|
|
<< "<head>\n"
|
|
<< "<title>" << photo->get_name() << " (" << photo_index + 1 << " of "
|
|
<< _dir->get_num_photos() << " on " << _dir->get_basename()
|
|
<< ")</title>\n"
|
|
<< "</head>\n"
|
|
<< "<body>\n"
|
|
<< "<h1>" << photo->get_name() << " (" << photo_index + 1 << " of "
|
|
<< _dir->get_num_photos() << ")</h1>";
|
|
|
|
generate_nav_buttons(html, prev_photo_filename, next_photo_filename,
|
|
up_href);
|
|
|
|
Filename cm_filename(_dir->get_dir(), photo->get_basename());
|
|
cm_filename.set_extension("cm");
|
|
if (cm_filename.exists()) {
|
|
// If a comment file for the photo exists, insert its contents
|
|
// here, right above the photo.
|
|
if (!RollDirectory::insert_html_comment(html, cm_filename)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
html
|
|
<< "<p><a href=\"" << reduced << "\">"
|
|
<< "<img src=\"" << reduced << "\" width=" << photo->_reduced_x_size
|
|
<< " height=" << photo->_reduced_y_size << " alt=\"" << photo->get_name()
|
|
<< "\"></a></p>\n";
|
|
|
|
html
|
|
<< "<p><a href=\"" << full << "\">View full size image ("
|
|
<< photo->_full_x_size << " x " << photo->_full_y_size << ")</a></p>";
|
|
|
|
generate_nav_buttons(html, prev_photo_filename, next_photo_filename,
|
|
up_href);
|
|
|
|
html
|
|
<< "</body>\n"
|
|
<< "</html>\n";
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::generate_nav_buttons
|
|
// Access: Private, Static
|
|
// Description: Outputs the HTML code to generate the next, prev,
|
|
// up buttons when viewing each reduced image.
|
|
////////////////////////////////////////////////////////////////////
|
|
void IndexImage::
|
|
generate_nav_buttons(ostream &html, const Filename &prev_photo_filename,
|
|
const Filename &next_photo_filename,
|
|
const string &up_href) {
|
|
html << "<p>\n";
|
|
|
|
bool first_icons = false;
|
|
if (!prev_icon.empty() && !next_icon.empty()) {
|
|
first_icons = true;
|
|
// Use icons to go forward and back.
|
|
Filename prev_icon_href = compose_href("../..", prev_icon);
|
|
if (prev_photo_filename.empty()) {
|
|
html << "<img src=\"" << prev_icon_href << "\" alt=\"No previous photo\">\n";
|
|
} else {
|
|
html << "<a href=\"" << prev_photo_filename
|
|
<< "\"><img src=\"" << prev_icon_href << "\" alt=\"previous\"></a>\n";
|
|
}
|
|
|
|
Filename next_icon_href = compose_href("../..", next_icon);
|
|
if (next_photo_filename.empty()) {
|
|
html << "<img src=\"" << next_icon_href << "\" alt=\"No next photo\">\n";
|
|
} else {
|
|
html << "<a href=\"" << next_photo_filename
|
|
<< "\"><img src=\"" << next_icon_href << "\" alt=\"next\"></a>\n";
|
|
}
|
|
|
|
} else {
|
|
// No prev/next icons; use text to go forward and back.
|
|
if (prev_photo_filename.empty()) {
|
|
html << "(This is the first photo.)\n";
|
|
} else {
|
|
html << "<a href=\"" << prev_photo_filename
|
|
<< "\">Back to previous photo</a>\n";
|
|
}
|
|
|
|
if (next_photo_filename.empty()) {
|
|
html << "<br>(This is the last photo.)\n";
|
|
} else {
|
|
html << "<br><a href=\"" << next_photo_filename
|
|
<< "\">On to next photo</a>\n";
|
|
}
|
|
}
|
|
|
|
if (!up_href.empty()) {
|
|
if (!up_icon.empty()) {
|
|
// Use an icon to go up.
|
|
if (!first_icons) {
|
|
html << "<br>";
|
|
} else {
|
|
html << " ";
|
|
}
|
|
Filename up_icon_href = compose_href("../..", up_icon);
|
|
html << "<a href=\"" << up_href
|
|
<< "\"><img src=\"" << up_icon_href << "\" alt=\"return to index\"></a>\n";
|
|
} else {
|
|
// No up icon; use text to go up.
|
|
html << "<br><a href=\"" << up_href
|
|
<< "\">Return to index</a>\n";
|
|
}
|
|
}
|
|
html << "</p>\n";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::compose_href
|
|
// Access: Private
|
|
// Description: Combines a user-supplied prefix with a relative
|
|
// directory to generate the appropriate href to the
|
|
// file.
|
|
//
|
|
// rel_dir is the relative path to archive_dir.
|
|
// user_prefix is the string the user indicated as the
|
|
// relative or absolute path to the file's parent
|
|
// directory from archive_dir. basename is the name
|
|
// of the file, or empty if the filename is part of
|
|
// user_prefix.
|
|
////////////////////////////////////////////////////////////////////
|
|
Filename IndexImage::
|
|
compose_href(const Filename &rel_dir, const Filename &user_prefix,
|
|
const Filename &basename) {
|
|
Filename result;
|
|
|
|
if (user_prefix.empty()) {
|
|
result = rel_dir;
|
|
|
|
} else {
|
|
// Check to see if the user prefix begins with a URL designator,
|
|
// like http:// or ftp://.
|
|
size_t ui = 0;
|
|
while (ui < user_prefix.length() && isalpha(user_prefix[ui])) {
|
|
ui++;
|
|
}
|
|
bool is_url = (user_prefix.get_fullpath().substr(ui, 3) == "://");
|
|
|
|
if (!is_url && user_prefix.is_local()) {
|
|
Filename rel_user_dir(rel_dir, user_prefix);
|
|
result = rel_user_dir;
|
|
result.standardize();
|
|
|
|
} else {
|
|
result = user_prefix;
|
|
}
|
|
}
|
|
|
|
if (basename.empty()) {
|
|
return result;
|
|
} else {
|
|
return Filename(result, basename);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::compute_reduction
|
|
// Access: Private, Static
|
|
// Description: Computes the amount by which the source image must be
|
|
// scaled to fit within the indicated box, and clears
|
|
// the dest_image to the computed size.
|
|
////////////////////////////////////////////////////////////////////
|
|
void IndexImage::
|
|
compute_reduction(const PNMImageHeader &source_image, PNMImage &dest_image,
|
|
int x_size, int y_size) {
|
|
double x_scale = (double)x_size / (double)source_image.get_x_size();
|
|
double y_scale = (double)y_size / (double)source_image.get_y_size();
|
|
double scale = min(x_scale, y_scale);
|
|
|
|
// Don't ever enlarge an image to fit the rectangle; if the image is
|
|
// smaller than the rectangle, just leave it small.
|
|
scale = min(scale, 1.0);
|
|
|
|
int new_x_size = (int)(source_image.get_x_size() * scale + 0.5);
|
|
int new_y_size = (int)(source_image.get_y_size() * scale + 0.5);
|
|
|
|
dest_image.clear(new_x_size, new_y_size,
|
|
source_image.get_num_channels(),
|
|
source_image.get_maxval());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::draw_box
|
|
// Access: Private, Static
|
|
// Description: Called in dummy mode to draw a little box in black
|
|
// around the border of the empty thumbnail image.
|
|
////////////////////////////////////////////////////////////////////
|
|
void IndexImage::
|
|
draw_box(PNMImage &image) {
|
|
// First, fill it in white.
|
|
image.fill(1, 1, 1);
|
|
|
|
if (!draw_frames) {
|
|
// Now make the border pixel black. We only need to do this if we
|
|
// aren't drawing frames, since the frames will reveal the shape
|
|
// of the image too.
|
|
int x_size = image.get_x_size();
|
|
int y_size = image.get_y_size();
|
|
for (int xi = 0; xi < x_size; xi++) {
|
|
image.set_xel(xi, 0, 0, 0, 0);
|
|
image.set_xel(xi, y_size - 1, 0, 0, 0);
|
|
}
|
|
|
|
for (int yi = 1; yi < y_size - 1; yi++) {
|
|
image.set_xel(0, yi, 0, 0, 0);
|
|
image.set_xel(x_size - 1, yi, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: IndexImage::draw_frame
|
|
// Access: Private, Static
|
|
// Description: Called in draw_frames mode to draw a slide mount in
|
|
// gray on the index image before drawing the thumbnail
|
|
// image in the center.
|
|
////////////////////////////////////////////////////////////////////
|
|
void IndexImage::
|
|
draw_frame(PNMImage &image,
|
|
int frame_left, int frame_top, int frame_width, int frame_height,
|
|
int hole_left, int hole_top, int hole_width, int hole_height) {
|
|
// Gray levels.
|
|
static const RGBColord mid(0.5, 0.5, 0.5);
|
|
static const RGBColord light(0.7, 0.7, 0.7);
|
|
static const RGBColord lighter(0.9, 0.9, 0.9);
|
|
static const RGBColord dark(0.3, 0.3, 0.3);
|
|
static const RGBColord darker(0.1, 0.1, 0.1);
|
|
|
|
// First, fill in the whole rectangle in gray.
|
|
int xi, yi;
|
|
for (yi = 0; yi < frame_height; yi++) {
|
|
for (xi = 0; xi < frame_width; xi++) {
|
|
image.set_xel(xi + frame_left, yi + frame_top, mid);
|
|
}
|
|
}
|
|
|
|
// Now draw the bevel.
|
|
for (xi = 0; xi < frame_outer_bevel; xi++) {
|
|
for (yi = xi; yi < frame_height - xi; yi++) {
|
|
// Left edge.
|
|
image.set_xel(xi + frame_left, yi + frame_top, light);
|
|
// Right edge.
|
|
image.set_xel(frame_width - 1 - xi + frame_left, yi + frame_top, dark);
|
|
}
|
|
}
|
|
for (yi = 0; yi < frame_outer_bevel; yi++) {
|
|
for (xi = yi; xi < frame_width - yi; xi++) {
|
|
// Top edge.
|
|
image.set_xel(xi + frame_left, yi + frame_top, lighter);
|
|
// Bottom edge.
|
|
image.set_xel(xi + frame_left, frame_height - 1 - yi + frame_top, darker);
|
|
}
|
|
}
|
|
|
|
// Interior bevel.
|
|
for (xi = -1; xi >= -frame_inner_bevel; xi--) {
|
|
for (yi = xi; yi < hole_height - xi; yi++) {
|
|
// Left edge.
|
|
image.set_xel(xi + hole_left, yi + hole_top, dark);
|
|
// Right edge.
|
|
image.set_xel(hole_width - 1 - xi + hole_left, yi + hole_top, light);
|
|
}
|
|
}
|
|
for (yi = -1; yi >= -frame_inner_bevel; yi--) {
|
|
for (xi = yi; xi < hole_width - yi; xi++) {
|
|
// Top edge.
|
|
image.set_xel(xi + hole_left, yi + hole_top, darker);
|
|
// Bottom edge.
|
|
image.set_xel(xi + hole_left, hole_height - 1 - yi + hole_top, lighter);
|
|
}
|
|
}
|
|
|
|
// We don't have to cut out the hole, since the thumbnail image will
|
|
// do that when it is placed.
|
|
}
|