Browse Source

support solid interiors

David Rose 22 years ago
parent
commit
1b97d7e178

+ 1 - 0
panda/src/pnmtext/config_pnmtext.cxx

@@ -30,6 +30,7 @@ ConfigureFn(config_pnmtext) {
 const float text_point_size = config_pnmtext.GetFloat("text-point-size", 10.0f);
 const float text_pixels_per_unit = config_pnmtext.GetFloat("text-pixels-per-unit", 30.0f);
 const float text_scale_factor = config_pnmtext.GetFloat("text-scale-factor", 2.0f);
+const bool text_native_antialias = config_pnmtext.GetBool("text-native-antialias", true);
 
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libpnmtext

+ 1 - 0
panda/src/pnmtext/config_pnmtext.h

@@ -29,6 +29,7 @@ NotifyCategoryDecl(pnmtext, EXPCL_PANDA, EXPTP_PANDA);
 extern const float text_point_size;
 extern const float text_pixels_per_unit;
 extern const float text_scale_factor;
+extern const bool text_native_antialias;
 
 extern EXPCL_PANDA void init_libpnmtext();
 

+ 31 - 0
panda/src/pnmtext/freetypeFont.I

@@ -143,6 +143,37 @@ get_scale_factor() const {
   return _scale_factor;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: FreetypeFont::set_native_antialias
+//       Access: Public
+//  Description: Sets whether the Freetype library's built-in
+//               antialias mode is enabled.  There are two unrelated
+//               ways to achieve antialiasing: with Freetype's native
+//               antialias mode, and with the use of a scale_factor
+//               greater than one.  By default, both modes are
+//               enabled.
+//
+//               At low resolutions, some fonts may do better with one
+//               mode or the other.  In general, Freetype's native
+//               antialiasing will produce less blurry results, but
+//               may introduce more artifacts.
+////////////////////////////////////////////////////////////////////
+INLINE void FreetypeFont::
+set_native_antialias(bool native_antialias) {
+  _native_antialias = native_antialias;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FreetypeFont::get_native_antialias
+//       Access: Public
+//  Description: Returns whether Freetype's built-in antialias mode is
+//               enabled.  See set_native_antialias().
+////////////////////////////////////////////////////////////////////
+INLINE bool FreetypeFont::
+get_native_antialias() const {
+  return _native_antialias;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: FreetypeFont::get_line_height
 //       Access: Public

+ 24 - 0
panda/src/pnmtext/freetypeFont.cxx

@@ -49,6 +49,7 @@ FreetypeFont() {
   _point_size = text_point_size;
   _tex_pixels_per_unit = text_pixels_per_unit;
   _scale_factor = text_scale_factor;
+  _native_antialias = text_native_antialias;
 
   _line_height = 1.0f;
   _space_advance = 0.25f;
@@ -168,6 +169,29 @@ unload_font() {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: FreetypeFont::load_glyph
+//       Access: Protected
+//  Description: Invokes Freetype to load and render the indicated
+//               glyph into a bitmap.  Returns true if successful,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool FreetypeFont::
+load_glyph(int glyph_index) {
+  int flags = FT_LOAD_RENDER;
+  if (!_native_antialias) { 
+    flags |= FT_LOAD_MONOCHROME;
+  }
+
+  int error = FT_Load_Glyph(_face, glyph_index, flags);
+  if (error) {
+    pnmtext_cat.error()
+      << "Unable to render glyph " << glyph_index << "\n";
+    return false;
+  }
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: FreetypeFont::copy_bitmap_to_pnmimage
 //       Access: Protected

+ 5 - 0
panda/src/pnmtext/freetypeFont.h

@@ -64,6 +64,9 @@ public:
   INLINE bool set_scale_factor(float scale_factor);
   INLINE float get_scale_factor() const;
 
+  INLINE void set_native_antialias(bool native_antialias);
+  INLINE bool get_native_antialias() const;
+
   INLINE float get_line_height() const;
   INLINE float get_space_advance() const;
 
@@ -71,6 +74,7 @@ public:
   INLINE static float get_points_per_inch();
 
 protected:
+  bool load_glyph(int glyph_index);
   void copy_bitmap_to_pnmimage(const FT_Bitmap &bitmap, PNMImage &image);
 
 private:
@@ -82,6 +86,7 @@ protected:
   float _point_size;
   float _tex_pixels_per_unit;
   float _scale_factor;
+  bool _native_antialias;
   float _font_pixels_per_unit;
 
   float _line_height;

+ 19 - 1
panda/src/pnmtext/pnmTextGlyph.I

@@ -105,5 +105,23 @@ INLINE double PNMTextGlyph::
 get_value(int x, int y) const {
   nassertr(x >= 0 && x < get_width() &&
            y >= 0 && y < get_height(), 0.0);
-  return _image.get_gray(x, y);
+  // By convention, the "value" attribute is stored in the blue
+  // component.
+  return _image.get_blue(x, y);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextGlyph::get_interior_flag
+//       Access: Public
+//  Description: Returns true if the indicated pixel represents a
+//               pixel in the interior of a hollow font, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool PNMTextGlyph::
+get_interior_flag(int x, int y) const {
+  nassertr(x >= 0 && x < get_width() &&
+           y >= 0 && y < get_height(), false);
+  // By convention, the "interior_value" attribute is stored in the red
+  // component.
+  return _image.get_red_val(x, y) != 0;
 }

+ 239 - 54
panda/src/pnmtext/pnmTextGlyph.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "pnmTextGlyph.h"
+#include "indent.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PNMTextGlyph::Constructor
@@ -42,8 +43,243 @@ PNMTextGlyph::
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PNMTextGlyph::rescale
+//     Function: PNMTextGlyph::place
+//       Access: Public
+//  Description: Copies the glyph to the indicated destination image
+//               at the indicated origin.  It colors the glyph pixels
+//               the indicated foreground color, blends antialiased
+//               pixels with the appropriate amount of the foreground
+//               color and the existing background color, and leaves
+//               other pixels alone.
+////////////////////////////////////////////////////////////////////
+void PNMTextGlyph::
+place(PNMImage &dest_image, int xp, int yp, const Colorf &fg) {
+  if (!_image.is_valid()) {
+    // If we have no image, do nothing.
+    return;
+  }
+  RGBColord fg_rgb(fg[0], fg[1], fg[2]);
+  double fg_alpha = fg[3];
+
+  int left = xp + _left;
+  int top = yp - _top;
+  int right = left + _image.get_x_size();
+  int bottom = top + _image.get_y_size();
+
+  // Clip the glyph to the destination image.
+  int cleft = max(left, 0);
+  int ctop = max(top, 0);
+  int cright = min(right, dest_image.get_x_size());
+  int cbottom = min(bottom, dest_image.get_y_size());
+
+  for (int y = ctop; y < cbottom; y++) {
+    for (int x = cleft; x < cright; x++) {
+      double gval = get_value(x - left, y - top);
+      if (gval == 1.0) {
+        dest_image.set_xel(x, y, fg_rgb);
+        if (dest_image.has_alpha()) {
+          dest_image.set_alpha(x, y, fg_alpha);
+        }
+
+      } else if (gval > 0.0) {
+        RGBColord bg_rgb = dest_image.get_xel(x, y);
+        dest_image.set_xel(x, y, fg_rgb * gval + bg_rgb * (1.0 - gval));
+        if (dest_image.has_alpha()) {
+          double bg_alpha = dest_image.get_alpha(x, y);
+          dest_image.set_alpha(x, y, fg_alpha * gval + bg_alpha * (1.0 - gval));
+        }
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextGlyph::place
 //       Access: Public
+//  Description: This flavor of place() also fills in the interior
+//               color.  This requires that determine_interior was
+//               called earlier.
+////////////////////////////////////////////////////////////////////
+void PNMTextGlyph::
+place(PNMImage &dest_image, int xp, int yp, const Colorf &fg, 
+      const Colorf &interior) {
+  if (!_image.is_valid()) {
+    // If we have no image, do nothing.
+    return;
+  }
+  RGBColord fg_rgb(fg[0], fg[1], fg[2]);
+  double fg_alpha = fg[3];
+  RGBColord interior_rgb(interior[0], interior[1], interior[2]);
+  double interior_alpha = interior[3];
+
+  int left = xp + _left;
+  int top = yp - _top;
+  int right = left + _image.get_x_size();
+  int bottom = top + _image.get_y_size();
+
+  // Clip the glyph to the destination image.
+  int cleft = max(left, 0);
+  int ctop = max(top, 0);
+  int cright = min(right, dest_image.get_x_size());
+  int cbottom = min(bottom, dest_image.get_y_size());
+
+  for (int y = ctop; y < cbottom; y++) {
+    for (int x = cleft; x < cright; x++) {
+      double gval = get_value(x - left, y - top);
+      if (gval == 1.0) {
+        dest_image.set_xel(x, y, fg_rgb);
+        if (dest_image.has_alpha()) {
+          dest_image.set_alpha(x, y, fg_alpha);
+        }
+
+      } else if (gval > 0.0) {
+        bool is_interior = get_interior_flag(x - left, y - top);
+        RGBColord bg_rgb;
+        if (is_interior) {
+          bg_rgb = interior_rgb;
+        } else {
+          bg_rgb = dest_image.get_xel(x, y);
+        }
+
+        dest_image.set_xel(x, y, fg_rgb * gval + bg_rgb * (1.0 - gval));
+        if (dest_image.has_alpha()) {
+          double bg_alpha;
+
+          if (is_interior) {
+            bg_alpha = interior_alpha;
+          } else {
+            bg_alpha = dest_image.get_alpha(x, y);
+          }
+          dest_image.set_alpha(x, y, fg_alpha * gval + bg_alpha * (1.0 - gval));
+        }
+      } else { // gval == 0.0
+        bool is_interior = get_interior_flag(x - left, y - top);
+        if (is_interior) {
+          dest_image.set_xel(x, y, interior_rgb);
+          if (dest_image.has_alpha()) {
+            dest_image.set_alpha(x, y, interior_alpha);
+          }
+        }
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextGlyph::determine_interior
+//       Access: Private
+//  Description: Once the glyph has been generated, but before it has
+//               been scaled down by _scale_factor, walk through the
+//               glyph and try to determine which parts represent the
+//               interior portions of a hollow font, and mark them so
+//               they may be properly colored.
+////////////////////////////////////////////////////////////////////
+void PNMTextGlyph::
+determine_interior() {
+  // We will use the red component as a working buffer.  First, we
+  // fill the whole thing to maxval.
+  int x_size = _image.get_x_size();
+  int y_size = _image.get_y_size();
+  xelval maxval = _image.get_maxval();
+  for (int yi = 0; yi < y_size; yi++) {
+    for (int xi = 0; xi < x_size; xi++) {
+      _image.set_red_val(xi, yi, maxval);
+    }
+  }
+
+  // Now we recursively analyze the image to determine the number of
+  // walls between each pixel and any edge.  All outer edge pixels
+  // have a value of 0; all dark pixels adjacent to those pixels have
+  // a value of 1, and light pixels adjacent to those have a value of
+  // 2, and so on.
+  _scan_interior_points.clear();
+  for (int yi = 0; yi < y_size; yi++) {
+    scan_interior(0, yi, 0, false, 0);
+    scan_interior(x_size - 1, yi, 0, false, 0);
+  }
+  for (int xi = 0; xi < x_size; xi++) {
+    scan_interior(xi, 0, 0, false, 0);
+    scan_interior(xi, y_size - 1, 0, false, 0);
+  }
+
+  // Pick up any points that we couldn't visit recursively because of
+  // the lame stack limit on Windows.
+  while (!_scan_interior_points.empty()) {
+    int index = _scan_interior_points.back();
+    _scan_interior_points.pop_back();
+    int y = index / _image.get_x_size();
+    int x = index % _image.get_x_size();
+    xelval new_code = _image.get_red_val(x, y);
+    bool this_dark = (_image.get_blue_val(x, y) > 0);
+
+    scan_interior(x - 1, y, new_code, this_dark, 0);
+    scan_interior(x, y - 1, new_code, this_dark, 0);
+    scan_interior(x + 1, y, new_code, this_dark, 0);
+    scan_interior(x, y + 1, new_code, this_dark, 0);
+  }
+  _scan_interior_points.clear();
+
+  // Finally, go back and set any pixel whose red value is two more
+  // than a multiple of 4 to dark.  This indicates the interior part
+  // of a hollow font.
+  for (int yi = 0; yi < y_size; yi++) {
+    for (int xi = 0; xi < x_size; xi++) {
+      xelval code = _image.get_red_val(xi, yi);
+      if (((code + 2) & 0x3) == 0) {
+        _image.set_red_val(xi, yi, maxval);
+      } else {
+        _image.set_red_val(xi, yi, 0);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextGlyph::scan_interior
+//       Access: Private
+//  Description: Recursively scans the image for interior pixels.  On
+//               completion, the image's red channel will be filled
+//               with 0, 1, 2, etc., representing the number of edges
+//               between each pixel and the border.
+////////////////////////////////////////////////////////////////////
+void PNMTextGlyph::
+scan_interior(int x, int y, xelval new_code, bool neighbor_dark, 
+              int recurse_level) {
+  if (x < 0 || y < 0 || x >= _image.get_x_size() || y >= _image.get_y_size()) {
+    return;
+  }
+  bool this_dark = (_image.get_blue_val(x, y) > 0);
+  if (this_dark != neighbor_dark) {
+    // If we just crossed an edge, we have to increment the code.
+    if (new_code < _image.get_maxval()) {
+      new_code++;
+    }
+    nassertv(new_code > 0);
+  }
+
+  if (new_code < _image.get_red_val(x, y)) {
+    _image.set_red_val(x, y, new_code);
+    recurse_level++;
+    if (recurse_level > 1024) {
+      // To cobble around a lame Windows limitation on the length of
+      // the stack, we must prevent the recursion from going too deep.
+      // But we still need to remember this pixel so we can come back
+      // to it later.
+      int index = y * _image.get_x_size() + x;
+      _scan_interior_points.push_back(index);
+
+    } else {
+      scan_interior(x - 1, y, new_code, this_dark, recurse_level);
+      scan_interior(x, y - 1, new_code, this_dark, recurse_level);
+      scan_interior(x + 1, y, new_code, this_dark, recurse_level);
+      scan_interior(x, y + 1, new_code, this_dark, recurse_level);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextGlyph::rescale
+//       Access: Private
 //  Description: After the image has been rendered large by FreeType,
 //               scales it small again for placing.
 ////////////////////////////////////////////////////////////////////
@@ -90,64 +326,13 @@ rescale(double scale_factor) {
     // These shouldn't go negative.
     nassertv(extra_pad + pad_left >= 0 && extra_pad + pad_top >= 0);
 
-    PNMImage enlarged(old_x_size, old_y_size, 1);
+    PNMImage enlarged(old_x_size, old_y_size, _image.get_num_channels());
     enlarged.copy_sub_image(_image, pad_left + extra_pad, pad_top + extra_pad);
 
-    _image.clear(new_x_size, new_y_size, 1);
+    _image.clear(new_x_size, new_y_size, _image.get_num_channels());
     _image.quick_filter_from(enlarged);
 
     _left = new_left;
     _top = new_top;
   }
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: PNMTextGlyph::place
-//       Access: Public
-//  Description: Copies the glyph to the indicated destination image
-//               at the indicated origin.  It colors the glyph pixels
-//               the indicated foreground color, blends antialiased
-//               pixels with the appropriate amount of the foreground
-//               color and the existing background color, and leaves
-//               other pixels alone.
-////////////////////////////////////////////////////////////////////
-void PNMTextGlyph::
-place(PNMImage &dest_image, int xp, int yp, const Colorf &fg) {
-  if (!_image.is_valid()) {
-    // If we have no image, do nothing.
-    return;
-  }
-  RGBColord fg_rgb(fg[0], fg[1], fg[2]);
-  double fg_alpha = fg[3];
-
-  int left = xp + _left;
-  int top = yp - _top;
-  int right = left + _image.get_x_size();
-  int bottom = top + _image.get_y_size();
-
-  // Clip the glyph to the destination image.
-  int cleft = max(left, 0);
-  int ctop = max(top, 0);
-  int cright = min(right, dest_image.get_x_size());
-  int cbottom = min(bottom, dest_image.get_y_size());
-
-  for (int y = ctop; y < cbottom; y++) {
-    for (int x = cleft; x < cright; x++) {
-      double gval = get_value(x - left, y - top);
-      if (gval == 1.0) {
-        dest_image.set_xel(x, y, fg_rgb);
-        if (dest_image.has_alpha()) {
-          dest_image.set_alpha(x, y, fg_alpha);
-        }
-
-      } else if (gval > 0.0) {
-        RGBColord bg_rgb = dest_image.get_xel(x, y);
-        dest_image.set_xel(x, y, fg_rgb * gval + bg_rgb * (1.0 - gval));
-        if (dest_image.has_alpha()) {
-          double bg_alpha = dest_image.get_alpha(x, y);
-          dest_image.set_alpha(x, y, fg_alpha * gval + bg_alpha * (1.0 - gval));
-        }
-      }
-    }
-  }
-}

+ 11 - 2
panda/src/pnmtext/pnmTextGlyph.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 
 #include "pnmImage.h"
+#include "vector_int.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : PNMTextGlyph
@@ -32,11 +33,12 @@ public:
   PNMTextGlyph(double advance);
   ~PNMTextGlyph();
 
-  void rescale(double scale_factor);
   INLINE int get_advance() const;
 
   void place(PNMImage &dest_image, int xp, int yp, 
-             const Colorf &fg = Colorf(0.0f, 0.0f, 0.0f, 1.0f));
+             const Colorf &fg);
+  void place(PNMImage &dest_image, int xp, int yp, 
+             const Colorf &fg, const Colorf &interior);
 
   INLINE int get_left() const;
   INLINE int get_right() const;
@@ -46,13 +48,20 @@ public:
   INLINE int get_height() const;
   INLINE int get_width() const;
   INLINE double get_value(int x, int y) const;
+  INLINE bool get_interior_flag(int x, int y) const;
 
 private:
+  void determine_interior();
+  void scan_interior(int x, int y, xelval new_code, bool neighbor_dark,
+                     int recurse_level);
+  void rescale(double scale_factor);
+
   PNMImage _image;
   int _top;
   int _left;
   double _advance;
   int _int_advance;
+  vector_int _scan_interior_points;
 
   friend class PNMTextMaker;
 };

+ 81 - 3
panda/src/pnmtext/pnmTextMaker.I

@@ -35,9 +35,7 @@ is_valid() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void PNMTextMaker::
 set_align(PNMTextMaker::Alignment align_type) {
-  if (_align != align_type) {
-    _align = align_type;
-  }
+  _align = align_type;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -50,6 +48,86 @@ get_align() const {
   return _align;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextMaker::set_interior_flag
+//       Access: Published
+//  Description: Sets the flag that indicates whether the interior of
+//               hollow fonts is identified as a preprocess as each
+//               glyph is loaded.  If this flag is true, you may
+//               specify an interior color along with a fg and bg
+//               color when you place text; if the flag is false, the
+//               interior color is ignored.
+//
+//               It is generally best to set_native_antialias(0) when
+//               using this feature.  Also, this works best when the
+//               pixel size is not very small.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMTextMaker::
+set_interior_flag(bool interior_flag) {
+  if (_interior_flag != interior_flag) {
+    _interior_flag = interior_flag;
+    empty_cache();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextMaker::get_interior_flag
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool PNMTextMaker::
+get_interior_flag() const {
+  return _interior_flag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextMaker::set_fg
+//       Access: Published
+//  Description: Sets the foreground color of text that will be
+//               generated by future calls to generate_into().  This
+//               is the color that all of the "on" pixels in the font
+//               will show as.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMTextMaker::
+set_fg(const Colorf &fg) {
+  _fg = fg;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextMaker::get_fg
+//       Access: Published
+//  Description: Returns the foreground color of text that will be
+//               generated by future calls to generate_into().
+////////////////////////////////////////////////////////////////////
+INLINE const Colorf &PNMTextMaker::
+get_fg() const {
+  return _fg;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextMaker::set_interior
+//       Access: Published
+//  Description: Sets the color that will be used to render the
+//               interior portions of hollow fonts in future calls to
+//               generate_into().  This is respected only if
+//               interior_flag is true.
+////////////////////////////////////////////////////////////////////
+INLINE void PNMTextMaker::
+set_interior(const Colorf &interior) {
+  _interior = interior;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMTextMaker::get_interior
+//       Access: Published
+//  Description: Returns the color that will be used to render the
+//               interior portions of hollow fonts.
+////////////////////////////////////////////////////////////////////
+INLINE const Colorf &PNMTextMaker::
+get_interior() const {
+  return _interior;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PNMTextMaker::generate_into
 //       Access: Public

+ 13 - 5
panda/src/pnmtext/pnmTextMaker.cxx

@@ -95,7 +95,11 @@ generate_into(const wstring &text, PNMImage &dest_image, int x, int y) {
   for (ti = text.begin(); ti != text.end(); ++ti) {
     int ch = (*ti);
     PNMTextGlyph *glyph = get_glyph(ch);
-    glyph->place(dest_image, xp, yp);
+    if (_interior_flag) {
+      glyph->place(dest_image, xp, yp, _fg, _interior);
+    } else {
+      glyph->place(dest_image, xp, yp, _fg);
+    }
     xp += glyph->get_advance();
   }
 }
@@ -130,6 +134,9 @@ get_glyph(int character) {
 void PNMTextMaker::
 initialize() {
   _align = A_left;
+  _interior_flag = false;
+  _fg.set(0.0f, 0.0f, 0.0f, 1.0f);
+  _interior.set(0.5f, 0.5f, 0.5f, 1.0f);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -140,9 +147,7 @@ initialize() {
 ////////////////////////////////////////////////////////////////////
 PNMTextGlyph *PNMTextMaker::
 make_glyph(int glyph_index) {
-  int error = FT_Load_Glyph(_face, glyph_index, FT_LOAD_RENDER);
-  if (error) {
-    nout << "Unable to render glyph " << glyph_index << "\n";
+  if (!load_glyph(glyph_index)) {
     return (PNMTextGlyph *)NULL;
   }
 
@@ -160,12 +165,15 @@ make_glyph(int glyph_index) {
   } else {
     PNMTextGlyph *glyph = new PNMTextGlyph(advance);
     PNMImage &glyph_image = glyph->_image;
-    glyph_image.clear(bitmap.width, bitmap.rows, 1);
+    glyph_image.clear(bitmap.width, bitmap.rows, 3);
     copy_bitmap_to_pnmimage(bitmap, glyph_image);
 
     glyph->_top = slot->bitmap_top;
     glyph->_left = slot->bitmap_left;
 
+    if (_interior_flag) {
+      glyph->determine_interior();
+    }
     glyph->rescale(_scale_factor);
     return glyph;
   }

+ 12 - 0
panda/src/pnmtext/pnmTextMaker.h

@@ -57,6 +57,15 @@ public:
   INLINE void set_align(Alignment align_type);
   INLINE Alignment get_align() const;
 
+  INLINE void set_interior_flag(bool interior_flag);
+  INLINE bool get_interior_flag() const;
+
+  INLINE void set_fg(const Colorf &fg);
+  INLINE const Colorf &get_fg() const;
+
+  INLINE void set_interior(const Colorf &interior);
+  INLINE const Colorf &get_interior() const;
+
   INLINE void generate_into(const string &text,
                             PNMImage &dest_image, int x, int y);
   void generate_into(const wstring &text,
@@ -75,6 +84,9 @@ private:
   Glyphs _glyphs;
 
   Alignment _align;
+  bool _interior_flag;
+  Colorf _fg;
+  Colorf _interior;
 };
 
 #include "pnmTextMaker.I"

+ 43 - 10
pandatool/src/egg-mkfont/eggMakeFont.cxx

@@ -81,6 +81,15 @@ EggMakeFont() : EggWriter(true, false) {
      "will not include an alpha component.",
      &EggMakeFont::dispatch_color, NULL, &_bg[0]);
 
+  add_option
+    ("interior", "r,g,b[,a]", 0,
+     "Specifies the color to render the interior part of a hollow font.  "
+     "This is a special effect that involves analysis of the bitmap after "
+     "the font has been rendered, and so is more effective when the pixel "
+     "size is large.  It also implies -noaa (but you can use a scale "
+     "factor with -sf to achieve antialiasing).",
+     &EggMakeFont::dispatch_color, &_got_interior, &_interior[0]);
+
   add_option
     ("chars", "range", 0,
      "Specifies the characters of the font that are used.  The range "
@@ -110,13 +119,14 @@ EggMakeFont() : EggWriter(true, false) {
   add_option
     ("bp", "n", 0,
      "The number of extra pixels around a single character in the "
-     "generated polygon. [1.0]",
+     "generated polygon.  This may be a floating-point number.",
      &EggMakeFont::dispatch_double, NULL, &_poly_margin);
 
   add_option
     ("bt", "n", 0,
-     "The number of extra pixels around each character in the texture map.",
-     &EggMakeFont::dispatch_double, NULL, &_tex_margin);
+     "The number of extra pixels around each character in the texture map.  "
+     "This may only be an integer.",
+     &EggMakeFont::dispatch_int, NULL, &_tex_margin);
 
   add_option
     ("sf", "factor", 0,
@@ -128,6 +138,14 @@ EggMakeFont() : EggWriter(true, false) {
      "matching font to the desired pixel size.",
      &EggMakeFont::dispatch_double, NULL, &_scale_factor);
 
+  add_option
+    ("noaa", "", 0,
+     "Disable low-level antialiasing by the Freetype library.  "
+     "This is unrelated to the antialiasing that is applied due to the "
+     "scale factor specified by -sf; you may have either one, neither, or "
+     "both kinds of antialiasing enabled.",
+     &EggMakeFont::dispatch_none, &_no_native_aa);
+
   add_option
     ("face", "index", 0,
      "Specify the face index of the particular face within the font file "
@@ -137,10 +155,11 @@ EggMakeFont() : EggWriter(true, false) {
 
   _fg.set(1.0, 1.0, 1.0, 1.0);
   _bg.set(1.0, 1.0, 1.0, 0.0);
+  _interior.set(1.0, 1.0, 1.0, 0.0);
   _pixels_per_unit = 30.0;
   _point_size = 10.0;
   _poly_margin = 1.0;
-  _tex_margin = 2.0;
+  _tex_margin = 2;
   _scale_factor = 2.0;
   _face_index = 0;
 
@@ -183,6 +202,8 @@ run() {
   }
   _text_maker->set_point_size(_point_size);
   _text_maker->set_scale_factor(_scale_factor);
+  _text_maker->set_native_antialias(!_no_native_aa && !_got_interior);
+  _text_maker->set_interior_flag(_got_interior);
   _text_maker->set_pixels_per_unit(_pixels_per_unit);
 
   if (_range.is_empty()) {
@@ -192,20 +213,27 @@ run() {
   }
   if (_output_image_pattern.empty()) {
     // Create a default texture filename pattern.
-    _output_image_pattern = _input_font_filename.get_fullpath_wo_extension() + "%03d.rgb";
+    _output_image_pattern = get_output_filename().get_fullpath_wo_extension() + "%03d.rgb";
   }
 
   // Figure out how many channels we need based on the foreground and
   // background colors.
-  bool needs_alpha = (_fg[3] != 1.0 || _bg[3] != 1.0);
+  bool needs_alpha = (_fg[3] != 1.0 || _bg[3] != 1.0 || _interior[3] != 1.0);
   bool needs_color = (_fg[0] != _fg[1] || _fg[1] != _fg[2] ||
-                      _bg[0] != _bg[1] || _bg[1] != _bg[2]);
+                      _bg[0] != _bg[1] || _bg[1] != _bg[2] ||
+                      _interior[0] != _interior[1] || _interior[1] != _interior[2]);
+  cerr << "needs_alpha = " << needs_alpha << "\n"
+       << "needs_color = " << needs_color << "\n"
+       << "fg = " << _fg << "\n"
+       << "bg = " << _bg << "\n"
+       << "interior = " << _interior << "\n";
+
   if (needs_alpha) {
     if (needs_color) {
       _num_channels = 4;
       _format = EggTexture::F_rgba;
     } else {
-      if (_fg[0] == 1.0 && _bg[0] == 1.0) {
+      if (_fg[0] == 1.0 && _bg[0] == 1.0 && _interior[0] == 1.0) {
         // A special case: we only need an alpha channel.  Copy the
         // alpha data into the color channels so we can write out a
         // one-channel image.
@@ -404,8 +432,13 @@ make_tref(PNMTextGlyph *glyph, int character) {
   if (image.has_alpha()) {
     image.alpha_fill(_bg[3]);
   }
-  glyph->place(image, -glyph->get_left() + _tex_margin, 
-               glyph->get_top() + _tex_margin, _fg);
+  if (_got_interior) {
+    glyph->place(image, -glyph->get_left() + _tex_margin, 
+                 glyph->get_top() + _tex_margin, _fg, _interior);
+  } else {
+    glyph->place(image, -glyph->get_left() + _tex_margin, 
+                 glyph->get_top() + _tex_margin, _fg);
+  }
 
   if (!image.write(texture_filename)) {
     nout << "Unable to write " << texture_filename << "\n";

+ 4 - 2
pandatool/src/egg-mkfont/eggMakeFont.h

@@ -60,13 +60,15 @@ private:
   EggTexture *make_tref(PNMTextGlyph *glyph, int character);
 
 private:
-  Colorf _fg, _bg;
+  Colorf _fg, _bg, _interior;
+  bool _got_interior;
   RangeDescription _range;
   double _pixels_per_unit;
   double _point_size;
   double _poly_margin;
-  double _tex_margin;
+  int _tex_margin;
   double _scale_factor;
+  bool _no_native_aa;
 
   Filename _input_font_filename;
   int _face_index;