Panda3D
pnmTextGlyph.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file pnmTextGlyph.cxx
10  * @author drose
11  * @date 2002-04-03
12  */
13 
14 #include "pnmTextGlyph.h"
15 #include "indent.h"
16 
17 using std::max;
18 using std::min;
19 
20 /**
21  *
22  */
23 PNMTextGlyph::
24 PNMTextGlyph(double advance) :
25  _advance(advance)
26 {
27  _left = 0;
28  _top = 0;
29  _int_advance = (int)floor(_advance + 0.5);
30 }
31 
32 /**
33  *
34  */
35 PNMTextGlyph::
36 ~PNMTextGlyph() {
37 }
38 
39 /**
40  * Copies the glyph to the indicated destination image at the indicated
41  * origin. It colors the glyph pixels the indicated foreground color, blends
42  * antialiased pixels with the appropriate amount of the foreground color and
43  * the existing background color, and leaves other pixels alone.
44  */
45 void PNMTextGlyph::
46 place(PNMImage &dest_image, int xp, int yp, const LColor &fg) {
47  if (!_image.is_valid()) {
48  // If we have no image, do nothing.
49  return;
50  }
51 
52  LColorf fgf = LCAST(float, fg);
53 
54  int left = xp + _left;
55  int top = yp - _top;
56  int right = left + _image.get_x_size();
57  int bottom = top + _image.get_y_size();
58 
59  // Clip the glyph to the destination image.
60  int cleft = max(left, 0);
61  int ctop = max(top, 0);
62  int cright = min(right, dest_image.get_x_size());
63  int cbottom = min(bottom, dest_image.get_y_size());
64 
65  for (int y = ctop; y < cbottom; y++) {
66  for (int x = cleft; x < cright; x++) {
67  double gval = get_value(x - left, y - top);
68  if (gval == 1.0) {
69  dest_image.set_xel_a(x, y, fgf);
70 
71  } else if (gval > 0.0) {
72  LColorf bg = dest_image.get_xel_a(x, y);
73  dest_image.set_xel_a(x, y, fgf * gval + bg * (1.0 - gval));
74  }
75  }
76  }
77 }
78 
79 /**
80  * This flavor of place() also fills in the interior color. This requires
81  * that determine_interior was called earlier.
82  */
83 void PNMTextGlyph::
84 place(PNMImage &dest_image, int xp, int yp, const LColor &fg,
85  const LColor &interior) {
86  if (!_image.is_valid()) {
87  // If we have no image, do nothing.
88  return;
89  }
90 
91  LColorf fgf = LCAST(float, fg);
92  LColorf interiorf = LCAST(float, interior);
93 
94  int left = xp + _left;
95  int top = yp - _top;
96  int right = left + _image.get_x_size();
97  int bottom = top + _image.get_y_size();
98 
99  // Clip the glyph to the destination image.
100  int cleft = max(left, 0);
101  int ctop = max(top, 0);
102  int cright = min(right, dest_image.get_x_size());
103  int cbottom = min(bottom, dest_image.get_y_size());
104 
105  for (int y = ctop; y < cbottom; y++) {
106  for (int x = cleft; x < cright; x++) {
107  double gval = get_value(x - left, y - top);
108  if (gval == 1.0) {
109  dest_image.set_xel_a(x, y, fgf);
110 
111  } else if (gval > 0.0) {
112  bool is_interior = get_interior_flag(x - left, y - top);
113  LColorf bg;
114  if (is_interior) {
115  bg = interiorf;
116  } else {
117  bg = dest_image.get_xel_a(x, y);
118  }
119 
120  dest_image.set_xel_a(x, y, fgf * gval + bg * (1.0 - gval));
121  } else { // gval == 0.0
122  bool is_interior = get_interior_flag(x - left, y - top);
123  if (is_interior) {
124  dest_image.set_xel_a(x, y, interiorf);
125  }
126  }
127  }
128  }
129 }
130 
131 /**
132  * Once the glyph has been generated, but before it has been scaled down by
133  * _scale_factor, walk through the glyph and try to determine which parts
134  * represent the interior portions of a hollow font, and mark them so they may
135  * be properly colored.
136  */
137 void PNMTextGlyph::
138 determine_interior() {
139  // We will use the red component as a working buffer. First, we fill the
140  // whole thing to maxval.
141  int x_size = _image.get_x_size();
142  int y_size = _image.get_y_size();
143  xelval maxval = _image.get_maxval();
144  for (int yi = 0; yi < y_size; yi++) {
145  for (int xi = 0; xi < x_size; xi++) {
146  _image.set_red_val(xi, yi, maxval);
147  }
148  }
149 
150  // Now we recursively analyze the image to determine the number of walls
151  // between each pixel and any edge. All outer edge pixels have a value of
152  // 0; all dark pixels adjacent to those pixels have a value of 1, and light
153  // pixels adjacent to those have a value of 2, and so on.
154  _scan_interior_points.clear();
155  for (int yi = 0; yi < y_size; yi++) {
156  scan_interior(0, yi, 0, false, 0);
157  scan_interior(x_size - 1, yi, 0, false, 0);
158  }
159  for (int xi = 0; xi < x_size; xi++) {
160  scan_interior(xi, 0, 0, false, 0);
161  scan_interior(xi, y_size - 1, 0, false, 0);
162  }
163 
164  // Pick up any points that we couldn't visit recursively because of the lame
165  // stack limit on Windows.
166  while (!_scan_interior_points.empty()) {
167  int index = _scan_interior_points.back();
168  _scan_interior_points.pop_back();
169  int y = index / _image.get_x_size();
170  int x = index % _image.get_x_size();
171  xelval new_code = _image.get_red_val(x, y);
172  bool this_dark = (_image.get_blue_val(x, y) > 0);
173 
174  scan_interior(x - 1, y, new_code, this_dark, 0);
175  scan_interior(x, y - 1, new_code, this_dark, 0);
176  scan_interior(x + 1, y, new_code, this_dark, 0);
177  scan_interior(x, y + 1, new_code, this_dark, 0);
178  }
179  _scan_interior_points.clear();
180 
181  // Finally, go back and set any pixel whose red value is two more than a
182  // multiple of 4 to dark. This indicates the interior part of a hollow
183  // font.
184  for (int yi = 0; yi < y_size; yi++) {
185  for (int xi = 0; xi < x_size; xi++) {
186  xelval code = _image.get_red_val(xi, yi);
187  if (((code + 2) & 0x3) == 0) {
188  _image.set_red_val(xi, yi, maxval);
189  } else {
190  _image.set_red_val(xi, yi, 0);
191  }
192  }
193  }
194 }
195 
196 /**
197  * Recursively scans the image for interior pixels. On completion, the
198  * image's red channel will be filled with 0, 1, 2, etc., representing the
199  * number of edges between each pixel and the border.
200  */
201 void PNMTextGlyph::
202 scan_interior(int x, int y, xelval new_code, bool neighbor_dark,
203  int recurse_level) {
204  if (x < 0 || y < 0 || x >= _image.get_x_size() || y >= _image.get_y_size()) {
205  return;
206  }
207  bool this_dark = (_image.get_blue_val(x, y) > 0);
208  if (this_dark != neighbor_dark) {
209  // If we just crossed an edge, we have to increment the code.
210  if (new_code < _image.get_maxval()) {
211  new_code++;
212  }
213  nassertv(new_code > 0);
214  }
215 
216  if (new_code < _image.get_red_val(x, y)) {
217  _image.set_red_val(x, y, new_code);
218  recurse_level++;
219  if (recurse_level > 1024) {
220  // To cobble around a lame Windows limitation on the length of the
221  // stack, we must prevent the recursion from going too deep. But we
222  // still need to remember this pixel so we can come back to it later.
223  int index = y * _image.get_x_size() + x;
224  _scan_interior_points.push_back(index);
225 
226  } else {
227  scan_interior(x - 1, y, new_code, this_dark, recurse_level);
228  scan_interior(x, y - 1, new_code, this_dark, recurse_level);
229  scan_interior(x + 1, y, new_code, this_dark, recurse_level);
230  scan_interior(x, y + 1, new_code, this_dark, recurse_level);
231  }
232  }
233 }
234 
235 /**
236  * After the image has been rendered large by FreeType, scales it small again
237  * for placing.
238  */
239 void PNMTextGlyph::
240 rescale(double scale_factor) {
241  if (scale_factor == 1.0) {
242  return;
243  }
244  nassertv(scale_factor != 0.0);
245  _advance /= scale_factor;
246  _int_advance = (int)floor(_advance + 0.5);
247 
248  if (_image.is_valid()) {
249  int orig_x_size = _image.get_x_size();
250  int orig_y_size = _image.get_y_size();
251  int orig_left = _left;
252  int orig_top = _top;
253 
254  // Pad the image by a few pixels all around to allow for antialiasing at
255  // the edges.
256  int extra_pad = (int)ceil(scale_factor);
257  orig_x_size += 2*extra_pad;
258  orig_y_size += 2*extra_pad;
259  orig_left -= extra_pad;
260  orig_top += extra_pad;
261 
262  // Now compute the reduced size.
263  int new_x_size = (int)ceil(orig_x_size / scale_factor);
264  int new_y_size = (int)ceil(orig_y_size / scale_factor);
265  int new_left = (int)floor(orig_left / scale_factor);
266  int new_top = (int)ceil(orig_top / scale_factor);
267 
268  // And scale those back up so we can determine the amount of additional
269  // padding we need to make the pixels remain in the right place after the
270  // integer reduction.
271  int old_x_size = (int)(new_x_size * scale_factor + 0.5);
272  int old_y_size = (int)(new_y_size * scale_factor + 0.5);
273  int old_left = (int)(new_left * scale_factor + 0.5);
274  int old_top = (int)(new_top * scale_factor + 0.5);
275 
276  int pad_left = orig_left - old_left;
277  int pad_top = old_top - orig_top;
278 
279  // These shouldn't go negative.
280  nassertv(extra_pad + pad_left >= 0 && extra_pad + pad_top >= 0);
281 
282  PNMImage enlarged(old_x_size, old_y_size, _image.get_num_channels());
283  enlarged.copy_sub_image(_image, pad_left + extra_pad, pad_top + extra_pad);
284 
285  _image.clear(new_x_size, new_y_size, _image.get_num_channels());
286  _image.quick_filter_from(enlarged);
287 
288  _left = new_left;
289  _top = new_top;
290  }
291 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void copy_sub_image(const PNMImage &copy, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1)
Copies a rectangular area of another image into a rectangular area of this image.
Definition: pnmImage.cxx:1077
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
get_num_channels
Returns the number of channels in the image.
bool get_interior_flag(int x, int y) const
Returns true if the indicated pixel represents a pixel in the interior of a hollow font,...
Definition: pnmTextGlyph.I:89
int get_y_size() const
Returns the number of pixels in the Y direction.
void quick_filter_from(const PNMImage &copy, int xborder=0, int yborder=0)
Resizes from the given image, with a fixed radius of 0.5.
int get_x_size() const
Returns the number of pixels in the X direction.
bool is_valid() const
Returns true if the image has been read in or correctly initialized with a height and width.
Definition: pnmImage.I:253
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition: pnmImage.I:594
void place(PNMImage &dest_image, int xp, int yp, const LColor &fg)
Copies the glyph to the indicated destination image at the indicated origin.
xelval get_blue_val(int x, int y) const
Returns the blue component color at the indicated pixel.
Definition: pnmImage.I:383
void set_xel_a(int x, int y, const LColorf &value)
Changes the RGBA color at the indicated pixel.
Definition: pnmImage.I:661
double get_value(int x, int y) const
Returns the value of the indicated pixel of the glyph.
Definition: pnmTextGlyph.I:77
void set_red_val(int x, int y, xelval r)
Sets the red component color only at the indicated pixel.
Definition: pnmImage.I:417
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition: pnmImage.cxx:48
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
xelval get_red_val(int x, int y) const
Returns the red component color at the indicated pixel.
Definition: pnmImage.I:363