From 1b97d7e178ddfeed2682596d7366b70e352bdc9a Mon Sep 17 00:00:00 2001 From: David Rose Date: Wed, 10 Sep 2003 16:30:38 +0000 Subject: [PATCH] support solid interiors --- panda/src/pnmtext/config_pnmtext.cxx | 1 + panda/src/pnmtext/config_pnmtext.h | 1 + panda/src/pnmtext/freetypeFont.I | 31 +++ panda/src/pnmtext/freetypeFont.cxx | 24 ++ panda/src/pnmtext/freetypeFont.h | 5 + panda/src/pnmtext/pnmTextGlyph.I | 20 +- panda/src/pnmtext/pnmTextGlyph.cxx | 305 ++++++++++++++++++----- panda/src/pnmtext/pnmTextGlyph.h | 13 +- panda/src/pnmtext/pnmTextMaker.I | 84 ++++++- panda/src/pnmtext/pnmTextMaker.cxx | 18 +- panda/src/pnmtext/pnmTextMaker.h | 12 + pandatool/src/egg-mkfont/eggMakeFont.cxx | 53 +++- pandatool/src/egg-mkfont/eggMakeFont.h | 6 +- 13 files changed, 490 insertions(+), 83 deletions(-) diff --git a/panda/src/pnmtext/config_pnmtext.cxx b/panda/src/pnmtext/config_pnmtext.cxx index 3488956cc0..f8728004a3 100644 --- a/panda/src/pnmtext/config_pnmtext.cxx +++ b/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 diff --git a/panda/src/pnmtext/config_pnmtext.h b/panda/src/pnmtext/config_pnmtext.h index 9515ce559c..cd82d177fc 100644 --- a/panda/src/pnmtext/config_pnmtext.h +++ b/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(); diff --git a/panda/src/pnmtext/freetypeFont.I b/panda/src/pnmtext/freetypeFont.I index 9ec4d3e1ad..5b87cccae4 100644 --- a/panda/src/pnmtext/freetypeFont.I +++ b/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 diff --git a/panda/src/pnmtext/freetypeFont.cxx b/panda/src/pnmtext/freetypeFont.cxx index 01d0dd0bb2..fd96700ea0 100644 --- a/panda/src/pnmtext/freetypeFont.cxx +++ b/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 diff --git a/panda/src/pnmtext/freetypeFont.h b/panda/src/pnmtext/freetypeFont.h index a7ee254d48..4700a17150 100644 --- a/panda/src/pnmtext/freetypeFont.h +++ b/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; diff --git a/panda/src/pnmtext/pnmTextGlyph.I b/panda/src/pnmtext/pnmTextGlyph.I index 87bdd44865..27909e18a4 100644 --- a/panda/src/pnmtext/pnmTextGlyph.I +++ b/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; } diff --git a/panda/src/pnmtext/pnmTextGlyph.cxx b/panda/src/pnmtext/pnmTextGlyph.cxx index 1d65a90c94..c77ed31746 100644 --- a/panda/src/pnmtext/pnmTextGlyph.cxx +++ b/panda/src/pnmtext/pnmTextGlyph.cxx @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////// #include "pnmTextGlyph.h" +#include "indent.h" //////////////////////////////////////////////////////////////////// // Function: PNMTextGlyph::Constructor @@ -41,66 +42,6 @@ PNMTextGlyph:: ~PNMTextGlyph() { } -//////////////////////////////////////////////////////////////////// -// Function: PNMTextGlyph::rescale -// Access: Public -// Description: After the image has been rendered large by FreeType, -// scales it small again for placing. -//////////////////////////////////////////////////////////////////// -void PNMTextGlyph:: -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); - - if (_image.is_valid()) { - int orig_x_size = _image.get_x_size(); - int orig_y_size = _image.get_y_size(); - int orig_left = _left; - int orig_top = _top; - - // Pad the image by a few pixels all around to allow for - // antialiasing at the edges. - 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; - - // Now compute the reduced size. - int new_x_size = (int)ceil(orig_x_size / scale_factor); - int new_y_size = (int)ceil(orig_y_size / scale_factor); - int new_left = (int)floor(orig_left / scale_factor); - int new_top = (int)ceil(orig_top / scale_factor); - - // And scale those back up so we can determine the amount of - // additional padding we need to make the pixels remain in the - // right place after the integer reduction. - int old_x_size = (int)(new_x_size * scale_factor + 0.5); - int old_y_size = (int)(new_y_size * scale_factor + 0.5); - int old_left = (int)(new_left * scale_factor + 0.5); - int old_top = (int)(new_top * scale_factor + 0.5); - - int pad_left = orig_left - old_left; - int pad_top = old_top - orig_top; - - // 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); - enlarged.copy_sub_image(_image, pad_left + extra_pad, pad_top + extra_pad); - - _image.clear(new_x_size, new_y_size, 1); - _image.quick_filter_from(enlarged); - - _left = new_left; - _top = new_top; - } -} - //////////////////////////////////////////////////////////////////// // Function: PNMTextGlyph::place // Access: Public @@ -151,3 +92,247 @@ place(PNMImage &dest_image, int xp, int yp, const Colorf &fg) { } } } + +//////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////// +void PNMTextGlyph:: +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); + + if (_image.is_valid()) { + int orig_x_size = _image.get_x_size(); + int orig_y_size = _image.get_y_size(); + int orig_left = _left; + int orig_top = _top; + + // Pad the image by a few pixels all around to allow for + // antialiasing at the edges. + 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; + + // Now compute the reduced size. + int new_x_size = (int)ceil(orig_x_size / scale_factor); + int new_y_size = (int)ceil(orig_y_size / scale_factor); + int new_left = (int)floor(orig_left / scale_factor); + int new_top = (int)ceil(orig_top / scale_factor); + + // And scale those back up so we can determine the amount of + // additional padding we need to make the pixels remain in the + // right place after the integer reduction. + int old_x_size = (int)(new_x_size * scale_factor + 0.5); + int old_y_size = (int)(new_y_size * scale_factor + 0.5); + int old_left = (int)(new_left * scale_factor + 0.5); + int old_top = (int)(new_top * scale_factor + 0.5); + + int pad_left = orig_left - old_left; + int pad_top = old_top - orig_top; + + // These shouldn't go negative. + nassertv(extra_pad + pad_left >= 0 && extra_pad + pad_top >= 0); + + 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, _image.get_num_channels()); + _image.quick_filter_from(enlarged); + + _left = new_left; + _top = new_top; + } +} diff --git a/panda/src/pnmtext/pnmTextGlyph.h b/panda/src/pnmtext/pnmTextGlyph.h index c7dd7835a9..c98237ac3a 100644 --- a/panda/src/pnmtext/pnmTextGlyph.h +++ b/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; }; diff --git a/panda/src/pnmtext/pnmTextMaker.I b/panda/src/pnmtext/pnmTextMaker.I index f2db23f745..03a00e3dbf 100644 --- a/panda/src/pnmtext/pnmTextMaker.I +++ b/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 diff --git a/panda/src/pnmtext/pnmTextMaker.cxx b/panda/src/pnmtext/pnmTextMaker.cxx index b09373093c..de477f2568 100644 --- a/panda/src/pnmtext/pnmTextMaker.cxx +++ b/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; } diff --git a/panda/src/pnmtext/pnmTextMaker.h b/panda/src/pnmtext/pnmTextMaker.h index f96aeac0c4..7b0f99776f 100644 --- a/panda/src/pnmtext/pnmTextMaker.h +++ b/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" diff --git a/pandatool/src/egg-mkfont/eggMakeFont.cxx b/pandatool/src/egg-mkfont/eggMakeFont.cxx index 5c309c9a25..648d9a5cab 100644 --- a/pandatool/src/egg-mkfont/eggMakeFont.cxx +++ b/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"; diff --git a/pandatool/src/egg-mkfont/eggMakeFont.h b/pandatool/src/egg-mkfont/eggMakeFont.h index 9eda436c7c..8952c772f9 100644 --- a/pandatool/src/egg-mkfont/eggMakeFont.h +++ b/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;