support solid interiors

This commit is contained in:
David Rose 2003-09-10 16:30:38 +00:00
parent 17e4330c0c
commit 1b97d7e178
13 changed files with 490 additions and 83 deletions

View File

@ -30,6 +30,7 @@ ConfigureFn(config_pnmtext) {
const float text_point_size = config_pnmtext.GetFloat("text-point-size", 10.0f); 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_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 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 // Function: init_libpnmtext

View File

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

View File

@ -143,6 +143,37 @@ get_scale_factor() const {
return _scale_factor; 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 // Function: FreetypeFont::get_line_height
// Access: Public // Access: Public

View File

@ -49,6 +49,7 @@ FreetypeFont() {
_point_size = text_point_size; _point_size = text_point_size;
_tex_pixels_per_unit = text_pixels_per_unit; _tex_pixels_per_unit = text_pixels_per_unit;
_scale_factor = text_scale_factor; _scale_factor = text_scale_factor;
_native_antialias = text_native_antialias;
_line_height = 1.0f; _line_height = 1.0f;
_space_advance = 0.25f; _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 // Function: FreetypeFont::copy_bitmap_to_pnmimage
// Access: Protected // Access: Protected

View File

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

View File

@ -105,5 +105,23 @@ INLINE double PNMTextGlyph::
get_value(int x, int y) const { get_value(int x, int y) const {
nassertr(x >= 0 && x < get_width() && nassertr(x >= 0 && x < get_width() &&
y >= 0 && y < get_height(), 0.0); 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;
} }

View File

@ -17,6 +17,7 @@
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
#include "pnmTextGlyph.h" #include "pnmTextGlyph.h"
#include "indent.h"
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: PNMTextGlyph::Constructor // Function: PNMTextGlyph::Constructor
@ -41,66 +42,6 @@ PNMTextGlyph::
~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 // Function: PNMTextGlyph::place
// Access: Public // 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;
}
}

View File

@ -22,6 +22,7 @@
#include "pandabase.h" #include "pandabase.h"
#include "pnmImage.h" #include "pnmImage.h"
#include "vector_int.h"
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Class : PNMTextGlyph // Class : PNMTextGlyph
@ -32,11 +33,12 @@ public:
PNMTextGlyph(double advance); PNMTextGlyph(double advance);
~PNMTextGlyph(); ~PNMTextGlyph();
void rescale(double scale_factor);
INLINE int get_advance() const; INLINE int get_advance() const;
void place(PNMImage &dest_image, int xp, int yp, 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_left() const;
INLINE int get_right() const; INLINE int get_right() const;
@ -46,13 +48,20 @@ public:
INLINE int get_height() const; INLINE int get_height() const;
INLINE int get_width() const; INLINE int get_width() const;
INLINE double get_value(int x, int y) const; INLINE double get_value(int x, int y) const;
INLINE bool get_interior_flag(int x, int y) const;
private: 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; PNMImage _image;
int _top; int _top;
int _left; int _left;
double _advance; double _advance;
int _int_advance; int _int_advance;
vector_int _scan_interior_points;
friend class PNMTextMaker; friend class PNMTextMaker;
}; };

View File

@ -35,9 +35,7 @@ is_valid() const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
INLINE void PNMTextMaker:: INLINE void PNMTextMaker::
set_align(PNMTextMaker::Alignment align_type) { 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; 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 // Function: PNMTextMaker::generate_into
// Access: Public // Access: Public

View File

@ -95,7 +95,11 @@ generate_into(const wstring &text, PNMImage &dest_image, int x, int y) {
for (ti = text.begin(); ti != text.end(); ++ti) { for (ti = text.begin(); ti != text.end(); ++ti) {
int ch = (*ti); int ch = (*ti);
PNMTextGlyph *glyph = get_glyph(ch); 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(); xp += glyph->get_advance();
} }
} }
@ -130,6 +134,9 @@ get_glyph(int character) {
void PNMTextMaker:: void PNMTextMaker::
initialize() { initialize() {
_align = A_left; _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:: PNMTextGlyph *PNMTextMaker::
make_glyph(int glyph_index) { make_glyph(int glyph_index) {
int error = FT_Load_Glyph(_face, glyph_index, FT_LOAD_RENDER); if (!load_glyph(glyph_index)) {
if (error) {
nout << "Unable to render glyph " << glyph_index << "\n";
return (PNMTextGlyph *)NULL; return (PNMTextGlyph *)NULL;
} }
@ -160,12 +165,15 @@ make_glyph(int glyph_index) {
} else { } else {
PNMTextGlyph *glyph = new PNMTextGlyph(advance); PNMTextGlyph *glyph = new PNMTextGlyph(advance);
PNMImage &glyph_image = glyph->_image; 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); copy_bitmap_to_pnmimage(bitmap, glyph_image);
glyph->_top = slot->bitmap_top; glyph->_top = slot->bitmap_top;
glyph->_left = slot->bitmap_left; glyph->_left = slot->bitmap_left;
if (_interior_flag) {
glyph->determine_interior();
}
glyph->rescale(_scale_factor); glyph->rescale(_scale_factor);
return glyph; return glyph;
} }

View File

@ -57,6 +57,15 @@ public:
INLINE void set_align(Alignment align_type); INLINE void set_align(Alignment align_type);
INLINE Alignment get_align() const; 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, INLINE void generate_into(const string &text,
PNMImage &dest_image, int x, int y); PNMImage &dest_image, int x, int y);
void generate_into(const wstring &text, void generate_into(const wstring &text,
@ -75,6 +84,9 @@ private:
Glyphs _glyphs; Glyphs _glyphs;
Alignment _align; Alignment _align;
bool _interior_flag;
Colorf _fg;
Colorf _interior;
}; };
#include "pnmTextMaker.I" #include "pnmTextMaker.I"

View File

@ -81,6 +81,15 @@ EggMakeFont() : EggWriter(true, false) {
"will not include an alpha component.", "will not include an alpha component.",
&EggMakeFont::dispatch_color, NULL, &_bg[0]); &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 add_option
("chars", "range", 0, ("chars", "range", 0,
"Specifies the characters of the font that are used. The range " "Specifies the characters of the font that are used. The range "
@ -110,13 +119,14 @@ EggMakeFont() : EggWriter(true, false) {
add_option add_option
("bp", "n", 0, ("bp", "n", 0,
"The number of extra pixels around a single character in the " "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); &EggMakeFont::dispatch_double, NULL, &_poly_margin);
add_option add_option
("bt", "n", 0, ("bt", "n", 0,
"The number of extra pixels around each character in the texture map.", "The number of extra pixels around each character in the texture map. "
&EggMakeFont::dispatch_double, NULL, &_tex_margin); "This may only be an integer.",
&EggMakeFont::dispatch_int, NULL, &_tex_margin);
add_option add_option
("sf", "factor", 0, ("sf", "factor", 0,
@ -128,6 +138,14 @@ EggMakeFont() : EggWriter(true, false) {
"matching font to the desired pixel size.", "matching font to the desired pixel size.",
&EggMakeFont::dispatch_double, NULL, &_scale_factor); &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 add_option
("face", "index", 0, ("face", "index", 0,
"Specify the face index of the particular face within the font file " "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); _fg.set(1.0, 1.0, 1.0, 1.0);
_bg.set(1.0, 1.0, 1.0, 0.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; _pixels_per_unit = 30.0;
_point_size = 10.0; _point_size = 10.0;
_poly_margin = 1.0; _poly_margin = 1.0;
_tex_margin = 2.0; _tex_margin = 2;
_scale_factor = 2.0; _scale_factor = 2.0;
_face_index = 0; _face_index = 0;
@ -183,6 +202,8 @@ run() {
} }
_text_maker->set_point_size(_point_size); _text_maker->set_point_size(_point_size);
_text_maker->set_scale_factor(_scale_factor); _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); _text_maker->set_pixels_per_unit(_pixels_per_unit);
if (_range.is_empty()) { if (_range.is_empty()) {
@ -192,20 +213,27 @@ run() {
} }
if (_output_image_pattern.empty()) { if (_output_image_pattern.empty()) {
// Create a default texture filename pattern. // 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 // Figure out how many channels we need based on the foreground and
// background colors. // 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] || 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_alpha) {
if (needs_color) { if (needs_color) {
_num_channels = 4; _num_channels = 4;
_format = EggTexture::F_rgba; _format = EggTexture::F_rgba;
} else { } 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 // A special case: we only need an alpha channel. Copy the
// alpha data into the color channels so we can write out a // alpha data into the color channels so we can write out a
// one-channel image. // one-channel image.
@ -404,8 +432,13 @@ make_tref(PNMTextGlyph *glyph, int character) {
if (image.has_alpha()) { if (image.has_alpha()) {
image.alpha_fill(_bg[3]); image.alpha_fill(_bg[3]);
} }
glyph->place(image, -glyph->get_left() + _tex_margin, if (_got_interior) {
glyph->get_top() + _tex_margin, _fg); 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)) { if (!image.write(texture_filename)) {
nout << "Unable to write " << texture_filename << "\n"; nout << "Unable to write " << texture_filename << "\n";

View File

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