diff --git a/pandaapp/src/indexify/indexImage.cxx b/pandaapp/src/indexify/indexImage.cxx index d593ba0691..2dd52eb2b7 100644 --- a/pandaapp/src/indexify/indexImage.cxx +++ b/pandaapp/src/indexify/indexImage.cxx @@ -105,7 +105,7 @@ generate_images(const Filename &archive_dir, TextMaker *text_maker) { // 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()) { + if (!dummy_mode && !force_regenerate && output_filename.exists()) { // Maybe we don't need to renegerated the index. generate_index_image = false; @@ -144,8 +144,9 @@ generate_images(const Filename &archive_dir, TextMaker *text_maker) { Filename reduced_filename(reduced_dir, photo->get_basename()); PNMImage reduced_image; - if (force_regenerate || - reduced_filename.compare_timestamps(photo_filename) < 0) { + 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. @@ -187,7 +188,7 @@ generate_images(const Filename &archive_dir, TextMaker *text_maker) { photo->_full_x_size = photo_image.get_x_size(); photo->_full_y_size = photo_image.get_y_size(); - if (generate_index_image) { + if (!dummy_mode && generate_index_image) { // Now read the reduced image from disk, so we can put it on // the index image. nout << "Reading " << reduced_filename << "\n"; @@ -203,7 +204,6 @@ generate_images(const Filename &archive_dir, TextMaker *text_maker) { } 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; @@ -216,12 +216,25 @@ generate_images(const Filename &archive_dir, TextMaker *text_maker) { 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); + 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, @@ -590,6 +603,8 @@ compute_reduction(const PNMImage &source_image, PNMImage &dest_image, 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); @@ -599,3 +614,98 @@ compute_reduction(const PNMImage &source_image, PNMImage &dest_image, 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. +} diff --git a/pandaapp/src/indexify/indexImage.h b/pandaapp/src/indexify/indexImage.h index 0b9fd19a3b..a47f7de04d 100644 --- a/pandaapp/src/indexify/indexImage.h +++ b/pandaapp/src/indexify/indexImage.h @@ -61,6 +61,13 @@ private: static void compute_reduction(const PNMImage &source_image, PNMImage &dest_image, int x_size, int y_size); + static void draw_box(PNMImage &image); + + static void 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); private: RollDirectory *_dir; diff --git a/pandaapp/src/indexify/indexParameters.cxx b/pandaapp/src/indexify/indexParameters.cxx index c849fa6ce1..08efb8dae0 100644 --- a/pandaapp/src/indexify/indexParameters.cxx +++ b/pandaapp/src/indexify/indexParameters.cxx @@ -24,11 +24,16 @@ int max_index_height = 700; int thumb_width = 100; int thumb_height = 100; -int thumb_caption_height = 16; -int thumb_x_space = 16; -int thumb_y_space = 16; -int caption_font_size = 14; +int thumb_caption_height = 12; +int caption_font_size = 12; + +int thumb_x_space = 14; +int thumb_y_space = 14; + +double frame_reduction_factor = 0.75; +int frame_outer_bevel = 2; +int frame_inner_bevel = 1; int reduced_width = 800; int reduced_height = 700; @@ -39,6 +44,8 @@ Filename up_icon; bool force_regenerate = false; bool format_rose = false; +bool dummy_mode = false; +bool draw_frames = false; // Computed parameters int thumb_count_x; @@ -46,6 +53,9 @@ int thumb_count_y; int max_thumbs; int actual_index_width; +int thumb_interior_width; +int thumb_interior_height; + //////////////////////////////////////////////////////////////////// // Function: finalize_parameters // Description: This is called after all user parameters have been @@ -63,4 +73,12 @@ finalize_parameters() { max_thumbs = thumb_count_x * thumb_count_y; actual_index_width = thumb_x_space + thumb_count_x * (thumb_width + thumb_x_space); + + if (draw_frames) { + thumb_interior_width = (int)(thumb_width * frame_reduction_factor + 0.5); + thumb_interior_height = (int)(thumb_height * frame_reduction_factor + 0.5); + } else { + thumb_interior_width = thumb_width; + thumb_interior_height = thumb_height; + } } diff --git a/pandaapp/src/indexify/indexParameters.h b/pandaapp/src/indexify/indexParameters.h index 711ab75430..0ba047b867 100644 --- a/pandaapp/src/indexify/indexParameters.h +++ b/pandaapp/src/indexify/indexParameters.h @@ -26,36 +26,92 @@ // Some of these constants may be modified by command-line parameters // from the user. +// The maximum size of the index image. It will shrink vertically to +// fit the images it contains, and it will shrink horizontally to fit +// a complete row of images (even if it does not contain a complete +// row). It will never be larger than this. extern int max_index_width; extern int max_index_height; +// The size of the individual thumbnail images, including the frames +// (if present). Thumbnail images are scaled to fit within this box. extern int thumb_width; extern int thumb_height; + +// The total number of pixels reserved for the caption under each +// thumbnail image. This is the caption_font_size plus whatever +// spacing should be included between the caption and the image. extern int thumb_caption_height; + +// The size in pixels of the caption font. This depends on the point +// size of the font as reported by FreeType, so the actual height of +// the letters might be slightly lower or higher than this, depending +// on the font. +extern int caption_font_size; + +// The amount of space, in pixels, between each two neighboring +// thumbnail images, and around the overall index image. extern int thumb_x_space; extern int thumb_y_space; -extern int caption_font_size; +// The ratio by which the thumbnail images are reduced further when +// frames are drawn, to allow room for a frame that resembles a slide +// mount. +extern double frame_reduction_factor; +// The number of pixels of thickness to draw for the frames' outer +// bevels and inner bevels, respectively. +extern int frame_outer_bevel; +extern int frame_inner_bevel; + +// The size of the reduced images on the individual image pages. The +// source image will be scaled to fit within this rectangle. extern int reduced_width; extern int reduced_height; +// The filenames (or URLS) to the icon images for navigating the +// individual image pages. extern Filename prev_icon; extern Filename next_icon; extern Filename up_icon; +// True to regenerate every image, whether it appears to need it or +// not. extern bool force_regenerate; + +// True to use the Rose formatting convention for roll directory names. extern bool format_rose; +// True to place dummy thumbnails instead of loading actual images. +extern bool dummy_mode; + +// True to draw frames (slide mounts) around each thumbnail image. +extern bool draw_frames; + + void finalize_parameters(); + + // The following parameters are all computed based on the above. +// The number of thumbnail images that fit across an index image, +// horizontally and vertically. extern int thumb_count_x; extern int thumb_count_y; + +// The total number of thumbnail images within each index image. extern int max_thumbs; + +// The number of pixels wide each index image will actually be, based +// on thumb_count_x. extern int actual_index_width; +// The actual size of the rectangle each thumbnail image must be +// scaled into, accounting for the presence of a frame. +extern int thumb_interior_width; +extern int thumb_interior_height; + #endif diff --git a/pandaapp/src/indexify/indexify.cxx b/pandaapp/src/indexify/indexify.cxx index 4d13124275..aed8e85b50 100644 --- a/pandaapp/src/indexify/indexify.cxx +++ b/pandaapp/src/indexify/indexify.cxx @@ -23,6 +23,7 @@ #include "default_font.h" #include "default_index_icons.h" #include "indexParameters.h" +#include "string_utils.h" #include @@ -104,6 +105,17 @@ Indexify() { "name will be reformatted to m-yy/s for output.", &Indexify::dispatch_none, &format_rose); + add_option + ("d", "", 0, + "Run in \"dummy\" mode; don't load any images, but instead just " + "draw an empty box indicating where the thumbnails will be.", + &Indexify::dispatch_none, &dummy_mode); + + add_option + ("fr", "", 0, + "Draw a frame, like a slide mount, around each thumbnail image.", + &Indexify::dispatch_none, &draw_frames); + add_option ("e", "extension", 0, "Specifies the filename extension (without a leading dot) to identify " @@ -119,9 +131,21 @@ Indexify() { &Indexify::dispatch_none, &_generate_icons); add_option - ("caption", "size", 0, - "Specifies the font size in pixels of the thumbnail captions.", - &Indexify::dispatch_int, NULL, &caption_font_size); + ("caption", "size[,spacing]", 0, + "Specifies the font size in pixels of the thumbnail captions. If the " + "optional spacing parameter is included, it is the number of pixels " + "below each thumbnail that the caption should be placed. Specify " + "-caption 0 to disable thumbnail captions.", + &Indexify::dispatch_caption, NULL); + + add_option + ("fontaa", "factor", 0, + "Specifies a scale factor to apply to the fonts used for captioning " + "when generating text for the purpose of antialiasing the fonts a " + "little better than FreeType can do by itself. The letters are " + "generated large and then scaled to their proper size. Normally this " + "should be a number in the range 3 to 4 for best effect.", + &Indexify::dispatch_double, NULL, &_font_aa_factor); add_option ("font", "fontname", 0, @@ -170,6 +194,7 @@ Indexify() { _photo_extension = "jpg"; _text_maker = (TextMaker *)NULL; + _font_aa_factor = 4.0; } //////////////////////////////////////////////////////////////////// @@ -277,26 +302,27 @@ post_command_line() { } } - - if (!_font_filename.empty()) { - _text_maker = new TextMaker(_font_filename, 0); - if (!_text_maker->is_valid()) { - delete _text_maker; - _text_maker = (TextMaker *)NULL; + if (caption_font_size != 0) { + if (!_font_filename.empty()) { + _text_maker = new TextMaker(_font_filename, 0); + if (!_text_maker->is_valid()) { + delete _text_maker; + _text_maker = (TextMaker *)NULL; + } } - } - - if (_text_maker == (TextMaker *)NULL) { - _text_maker = new TextMaker(default_font, default_font_size, 0); - if (!_text_maker->is_valid()) { - nout << "Unable to open default font.\n"; - delete _text_maker; - _text_maker = (TextMaker *)NULL; + + if (_text_maker == (TextMaker *)NULL) { + _text_maker = new TextMaker(default_font, default_font_size, 0); + if (!_text_maker->is_valid()) { + nout << "Unable to open default font.\n"; + delete _text_maker; + _text_maker = (TextMaker *)NULL; + } + } + + if (_text_maker != (TextMaker *)NULL) { + _text_maker->set_pixel_size(caption_font_size, _font_aa_factor); } - } - - if (_text_maker != (TextMaker *)NULL) { - _text_maker->set_pixel_size(caption_font_size, 4.0); } if (_generate_icons) { @@ -351,15 +377,50 @@ post_command_line() { } } - // Provide a little bit of whitespace above the captions. - thumb_caption_height = (int)ceil(caption_font_size * 8.0 / 7.0); - finalize_parameters(); return ProgramBase::post_command_line(); } + +//////////////////////////////////////////////////////////////////// +// Function: Indexify::dispatch_caption +// Access: Protected, Static +// Description: Dispatch function for the -caption parameter, which +// takes either one or two numbers separated by a comma, +// representing the caption font size and the optional +// pixel spacing of the caption under the image. +//////////////////////////////////////////////////////////////////// +bool Indexify:: +dispatch_caption(const string &opt, const string &arg, void *) { + vector_string words; + tokenize(arg, words, ","); + + int caption_spacing = 0; + + bool okflag = false; + if (words.size() == 1) { + okflag = + string_to_int(words[0], caption_font_size); + + } else if (words.size() == 2) { + okflag = + string_to_int(words[0], caption_font_size) && + string_to_int(words[1], caption_spacing); + } + + if (!okflag) { + nout << "-" << opt + << " requires one or two integers separated by a comma.\n"; + return false; + } + + thumb_caption_height = caption_font_size + caption_spacing; + + return true; +} + //////////////////////////////////////////////////////////////////// // Function: Indexify::run // Access: Public diff --git a/pandaapp/src/indexify/indexify.h b/pandaapp/src/indexify/indexify.h index bee95739d1..58a4b2ffcf 100644 --- a/pandaapp/src/indexify/indexify.h +++ b/pandaapp/src/indexify/indexify.h @@ -41,6 +41,8 @@ protected: virtual bool handle_args(Args &args); virtual bool post_command_line(); + static bool dispatch_caption(const string &opt, const string &arg, void *var); + public: void run(); @@ -50,6 +52,7 @@ public: string _photo_extension; Filename _font_filename; bool _generate_icons; + double _font_aa_factor; typedef pvector RollDirs; RollDirs _roll_dirs; diff --git a/pandaapp/src/indexify/textGlyph.cxx b/pandaapp/src/indexify/textGlyph.cxx index fb08d236b0..1aae065010 100644 --- a/pandaapp/src/indexify/textGlyph.cxx +++ b/pandaapp/src/indexify/textGlyph.cxx @@ -49,6 +49,9 @@ TextGlyph:: //////////////////////////////////////////////////////////////////// void TextGlyph:: rescale(double scale_factor) { + if (scale_factor == 1.0) { + return; + } nassertv(scale_factor != 0.0); _advance /= scale_factor; _int_advance = (int)floor(_advance + 0.5); @@ -64,8 +67,8 @@ rescale(double scale_factor) { int extra_pad = (int)ceil(scale_factor); orig_x_size += 2*extra_pad; orig_y_size += 2*extra_pad; - orig_left += extra_pad; - orig_top -= extra_pad; + orig_left -= extra_pad; + orig_top += extra_pad; // Now compute the reduced size. int new_x_size = (int)ceil(orig_x_size / scale_factor); @@ -85,7 +88,7 @@ rescale(double scale_factor) { int pad_top = old_top - orig_top; // These shouldn't go negative. - nassertv(pad_left >= 0 && pad_top >= 0); + nassertv(extra_pad + pad_left >= 0 && extra_pad + pad_top >= 0); PNMImage enlarged(old_x_size, old_y_size, 1); enlarged.fill(1, 1, 1); diff --git a/pandaapp/src/indexify/textMaker.cxx b/pandaapp/src/indexify/textMaker.cxx index f6a177671c..fef9f0c2ee 100644 --- a/pandaapp/src/indexify/textMaker.cxx +++ b/pandaapp/src/indexify/textMaker.cxx @@ -70,6 +70,9 @@ TextMaker(const Filename &font_filename, int face_index) { } set_name(name); + // Maybe we don't care about enforcing this. It doesn't work + // with older versions of FreeType anyway. + /* error = FT_Select_Charmap(_face, ft_encoding_unicode); if (error) { error = FT_Select_Charmap(_face, ft_encoding_latin_2); @@ -78,7 +81,8 @@ TextMaker(const Filename &font_filename, int face_index) { nout << "Unable to select ISO encoding for " << get_name() << ".\n"; FT_Done_Face(_face); - } else { + } else */ + { nout << "Loaded font " << get_name() << "\n"; _is_valid = true; _scale_factor = 0.0;