fill_distance_inside(), fill_distance_outside()

This commit is contained in:
David Rose 2013-03-06 22:09:59 +00:00
parent 1f309fede5
commit 531c5907fc
2 changed files with 255 additions and 40 deletions

View File

@ -1199,29 +1199,57 @@ lighten_sub_image(const PNMImage &copy, int xto, int yto,
// s = select_image.get_channel(x, y). Set this image's // s = select_image.get_channel(x, y). Set this image's
// (x, y) to: // (x, y) to:
// //
// lt.get_xel(x, y) if s <= threshold, or // lt.get_xel(x, y) if s < threshold, or
// //
// ge.get_xel(x, y) if s > threshold // ge.get_xel(x, y) if s >= threshold
// //
// Any of select_image, lt, or ge may be the same // Any of select_image, lt, or ge may be the same
// PNMImge object as this image, or the same as each // PNMImge object as this image, or the same as each
// other; or they may all be different. All images must // other; or they may all be different. All images must
// be the same size. // be the same size. As a special case, lt and ge may
// both be 1x1 images instead of the source image size.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void PNMImage:: void PNMImage::
threshold(const PNMImage &select_image, int channel, double threshold, threshold(const PNMImage &select_image, int channel, double threshold,
const PNMImage &lt, const PNMImage &ge) { const PNMImage &lt, const PNMImage &ge) {
nassertv(get_x_size() <= select_image.get_x_size() && get_y_size() <= select_image.get_y_size()); nassertv(get_x_size() <= select_image.get_x_size() && get_y_size() <= select_image.get_y_size());
nassertv(get_x_size() <= lt.get_x_size() && get_y_size() <= lt.get_y_size());
nassertv(get_x_size() <= ge.get_x_size() && get_y_size() <= ge.get_y_size());
nassertv(channel >= 0 && channel < select_image.get_num_channels()); nassertv(channel >= 0 && channel < select_image.get_num_channels());
xelval threshold_val = (xelval)(select_image.get_maxval() * threshold + 0.5); xelval threshold_val = (xelval)(select_image.get_maxval() * threshold + 0.5);
if (get_maxval() == lt.get_maxval() && get_maxval() == ge.get_maxval()) { if (lt.get_x_size() == 1 && lt.get_y_size() == 1 &&
// Simple case: the maxvals are all equal. Copy by integer value. ge.get_x_size() == 1 && ge.get_y_size() == 1) {
int x, y; // 1x1 source images.
xel lt_val = lt.get_xel_val(0, 0);
xelval lt_alpha = 0;
if (lt.has_alpha()) {
lt_alpha = lt.get_alpha_val(0, 0);
}
if (lt.get_maxval() != get_maxval()) {
double scale = (double)get_maxval() / (double)lt.get_maxval();
PPM_ASSIGN(lt_val,
(xelval)(PPM_GETR(lt_val) * scale + 0.5),
(xelval)(PPM_GETG(lt_val) * scale + 0.5),
(xelval)(PPM_GETB(lt_val) * scale + 0.5));
lt_alpha = (xelval)(lt_alpha * scale + 0.5);
}
xel ge_val = ge.get_xel_val(0, 0);
xelval ge_alpha = 0;
if (ge.has_alpha()) {
ge_alpha = ge.get_alpha_val(0, 0);
}
if (ge.get_maxval() != get_maxval()) {
double scale = (double)get_maxval() / (double)ge.get_maxval();
PPM_ASSIGN(ge_val,
(xelval)(PPM_GETR(ge_val) * scale + 0.5),
(xelval)(PPM_GETG(ge_val) * scale + 0.5),
(xelval)(PPM_GETB(ge_val) * scale + 0.5));
ge_alpha = (xelval)(ge_alpha * scale + 0.5);
}
int x, y;
if (channel == 3) { if (channel == 3) {
// Further special case: the alpha channel. // Further special case: the alpha channel.
if (has_alpha() && lt.has_alpha() && ge.has_alpha()) { if (has_alpha() && lt.has_alpha() && ge.has_alpha()) {
@ -1229,23 +1257,23 @@ threshold(const PNMImage &select_image, int channel, double threshold,
for (y = 0; y < get_y_size(); y++) { for (y = 0; y < get_y_size(); y++) {
for (x = 0; x < get_x_size(); x++) { for (x = 0; x < get_x_size(); x++) {
if (select_image.get_alpha_val(x, y) < threshold_val) { if (select_image.get_alpha_val(x, y) < threshold_val) {
set_xel_val(x, y, lt.get_xel_val(x, y)); set_xel_val(x, y, lt_val);
set_alpha_val(x, y, lt.get_alpha_val(x, y)); set_alpha_val(x, y, lt_alpha);
} else { } else {
set_xel_val(x, y, ge.get_xel_val(x, y)); set_xel_val(x, y, ge_val);
set_alpha_val(x, y, ge.get_alpha_val(x, y)); set_alpha_val(x, y, ge_alpha);
} }
} }
} }
} else { } else {
// Don't copy alpha channel. // Don't copy alpha channel.
for (y = 0; y < get_y_size(); y++) { for (y = 0; y < get_y_size(); y++) {
for (x = 0; x < get_x_size(); x++) { for (x = 0; x < get_x_size(); x++) {
if (select_image.get_alpha_val(x, y) < threshold_val) { if (select_image.get_alpha_val(x, y) < threshold_val) {
set_xel_val(x, y, lt.get_xel_val(x, y)); set_xel_val(x, y, lt_val);
} else { } else {
set_xel_val(x, y, ge.get_xel_val(x, y)); set_xel_val(x, y, ge_val);
} }
} }
} }
@ -1257,52 +1285,120 @@ threshold(const PNMImage &select_image, int channel, double threshold,
for (y = 0; y < get_y_size(); y++) { for (y = 0; y < get_y_size(); y++) {
for (x = 0; x < get_x_size(); x++) { for (x = 0; x < get_x_size(); x++) {
if (select_image.get_channel_val(x, y, channel) < threshold_val) { if (select_image.get_channel_val(x, y, channel) < threshold_val) {
set_xel_val(x, y, lt.get_xel_val(x, y)); set_xel_val(x, y, lt_val);
set_alpha_val(x, y, lt.get_alpha_val(x, y)); set_alpha_val(x, y, lt_alpha);
} else { } else {
set_xel_val(x, y, ge.get_xel_val(x, y)); set_xel_val(x, y, ge_val);
set_alpha_val(x, y, ge.get_alpha_val(x, y)); set_alpha_val(x, y, ge_alpha);
} }
} }
} }
} else { } else {
// Don't copy alpha channel. // Don't copy alpha channel.
for (y = 0; y < get_y_size(); y++) { for (y = 0; y < get_y_size(); y++) {
for (x = 0; x < get_x_size(); x++) { for (x = 0; x < get_x_size(); x++) {
if (select_image.get_channel_val(x, y, channel) < threshold_val) { if (select_image.get_channel_val(x, y, channel) < threshold_val) {
set_xel_val(x, y, lt.get_xel_val(x, y)); set_xel_val(x, y, lt_val);
} else { } else {
set_xel_val(x, y, ge.get_xel_val(x, y)); set_xel_val(x, y, ge_val);
} }
} }
} }
} }
} }
} else { } else {
// General case: the maxvals are different. Copy by floating-point value. // Same-sized source images.
int x, y; nassertv(get_x_size() <= lt.get_x_size() && get_y_size() <= lt.get_y_size());
nassertv(get_x_size() <= ge.get_x_size() && get_y_size() <= ge.get_y_size());
if (has_alpha() && lt.has_alpha() && ge.has_alpha()) { if (get_maxval() == lt.get_maxval() && get_maxval() == ge.get_maxval()) {
for (y = 0; y < get_y_size(); y++) { // Simple case: the maxvals are all equal. Copy by integer value.
for (x = 0; x < get_x_size(); x++) { int x, y;
if (select_image.get_channel_val(x, y, channel) < threshold_val) {
set_xel(x, y, lt.get_xel(x, y)); if (channel == 3) {
set_alpha(x, y, lt.get_alpha(x, y)); // Further special case: the alpha channel.
} else { if (has_alpha() && lt.has_alpha() && ge.has_alpha()) {
set_xel(x, y, ge.get_xel(x, y)); // Copy alpha channel too.
set_alpha(x, y, ge.get_alpha(x, y)); for (y = 0; y < get_y_size(); y++) {
for (x = 0; x < get_x_size(); x++) {
if (select_image.get_alpha_val(x, y) < threshold_val) {
set_xel_val(x, y, lt.get_xel_val(x, y));
set_alpha_val(x, y, lt.get_alpha_val(x, y));
} else {
set_xel_val(x, y, ge.get_xel_val(x, y));
set_alpha_val(x, y, ge.get_alpha_val(x, y));
}
}
}
} else {
// Don't copy alpha channel.
for (y = 0; y < get_y_size(); y++) {
for (x = 0; x < get_x_size(); x++) {
if (select_image.get_alpha_val(x, y) < threshold_val) {
set_xel_val(x, y, lt.get_xel_val(x, y));
} else {
set_xel_val(x, y, ge.get_xel_val(x, y));
}
}
}
}
} else {
// Any generic channel.
if (has_alpha() && lt.has_alpha() && ge.has_alpha()) {
// Copy alpha channel too.
for (y = 0; y < get_y_size(); y++) {
for (x = 0; x < get_x_size(); x++) {
if (select_image.get_channel_val(x, y, channel) < threshold_val) {
set_xel_val(x, y, lt.get_xel_val(x, y));
set_alpha_val(x, y, lt.get_alpha_val(x, y));
} else {
set_xel_val(x, y, ge.get_xel_val(x, y));
set_alpha_val(x, y, ge.get_alpha_val(x, y));
}
}
}
} else {
// Don't copy alpha channel.
for (y = 0; y < get_y_size(); y++) {
for (x = 0; x < get_x_size(); x++) {
if (select_image.get_channel_val(x, y, channel) < threshold_val) {
set_xel_val(x, y, lt.get_xel_val(x, y));
} else {
set_xel_val(x, y, ge.get_xel_val(x, y));
}
}
} }
} }
} }
} else { } else {
for (y = 0; y < get_y_size(); y++) { // General case: the maxvals are different. Copy by floating-point value.
for (x = 0; x < get_x_size(); x++) { int x, y;
if (select_image.get_channel_val(x, y, channel) < threshold_val) {
set_xel(x, y, lt.get_xel(x, y)); if (has_alpha() && lt.has_alpha() && ge.has_alpha()) {
} else { for (y = 0; y < get_y_size(); y++) {
set_xel(x, y, ge.get_xel(x, y)); for (x = 0; x < get_x_size(); x++) {
if (select_image.get_channel_val(x, y, channel) < threshold_val) {
set_xel(x, y, lt.get_xel(x, y));
set_alpha(x, y, lt.get_alpha(x, y));
} else {
set_xel(x, y, ge.get_xel(x, y));
set_alpha(x, y, ge.get_alpha(x, y));
}
}
}
} else {
for (y = 0; y < get_y_size(); y++) {
for (x = 0; x < get_x_size(); x++) {
if (select_image.get_channel_val(x, y, channel) < threshold_val) {
set_xel(x, y, lt.get_xel(x, y));
} else {
set_xel(x, y, ge.get_xel(x, y));
}
} }
} }
} }
@ -1310,6 +1406,98 @@ threshold(const PNMImage &select_image, int channel, double threshold,
} }
} }
////////////////////////////////////////////////////////////////////
// Function: PNMImage::fill_distance_inside
// Access: Published
// Description: Replaces this image with a grayscale image whose gray
// channel represents the linear Manhattan distance from
// the nearest dark pixel in the given mask image, up to
// the specified radius value (which also becomes the
// new maxval). radius may range from 0 to maxmaxval;
// smaller values will compute faster. A dark pixel is
// defined as one whose pixel value is < threshold.
//
// If shrink_from_border is true, then the mask image is
// considered to be surrounded by a border of dark
// pixels; otherwise, the border isn't considered.
//
// This can be used, in conjunction with threshold, to
// shrink a mask image inwards by a certain number of
// pixels.
//
// The mask image may be the same image as this one, in
// which case it is destructively modified by this
// process.
////////////////////////////////////////////////////////////////////
void PNMImage::
fill_distance_inside(const PNMImage &mask, double threshold, int radius, bool shrink_from_border) {
nassertv(radius <= PNM_MAXMAXVAL);
PNMImage dist(mask.get_x_size(), mask.get_y_size(), 1, radius);
dist.fill_val(radius);
xelval threshold_val = (xelval)(mask.get_maxval() * threshold + 0.5);
for (int yi = 0; yi < mask.get_y_size(); ++yi) {
for (int xi = 0; xi < mask.get_x_size(); ++xi) {
if (mask.get_gray_val(xi, yi) < threshold_val) {
dist.do_fill_distance(xi, yi, 0);
}
}
}
if (shrink_from_border) {
// Also measure from the image border.
for (int yi = 0; yi < mask.get_y_size(); ++yi) {
dist.do_fill_distance(0, yi, 1);
dist.do_fill_distance(mask.get_x_size() - 1, yi, 1);
}
for (int xi = 0; xi < mask.get_x_size(); ++xi) {
dist.do_fill_distance(xi, 0, 1);
dist.do_fill_distance(xi, mask.get_y_size() - 1, 1);
}
}
take_from(dist);
}
////////////////////////////////////////////////////////////////////
// Function: PNMImage::fill_distance_outside
// Access: Published
// Description: Replaces this image with a grayscale image whose gray
// channel represents the linear Manhattan distance from
// the nearest white pixel in the given mask image, up to
// the specified radius value (which also becomes the
// new maxval). radius may range from 0 to maxmaxval;
// smaller values will compute faster. A white pixel is
// defined as one whose pixel value is >= threshold.
//
// This can be used, in conjunction with threshold, to
// grow a mask image outwards by a certain number of
// pixels.
//
// The mask image may be the same image as this one, in
// which case it is destructively modified by this
// process.
////////////////////////////////////////////////////////////////////
void PNMImage::
fill_distance_outside(const PNMImage &mask, double threshold, int radius) {
nassertv(radius <= PNM_MAXMAXVAL);
PNMImage dist(mask.get_x_size(), mask.get_y_size(), 1, radius);
dist.fill_val(radius);
xelval threshold_val = (xelval)(mask.get_maxval() * threshold + 0.5);
for (int yi = 0; yi < mask.get_y_size(); ++yi) {
for (int xi = 0; xi < mask.get_x_size(); ++xi) {
if (mask.get_gray_val(xi, yi) >= threshold_val) {
dist.do_fill_distance(xi, yi, 0);
}
}
}
take_from(dist);
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: PNMImage::rescale // Function: PNMImage::rescale
// Access: Published // Access: Published
@ -1714,6 +1902,29 @@ setup_rc() {
} }
} }
////////////////////////////////////////////////////////////////////
// Function: PNMImage::do_fill_distance
// Access: Private
// Description: Recursively fills in the minimum distance measured
// from a certain set of points into the gray channel.
////////////////////////////////////////////////////////////////////
void PNMImage::
do_fill_distance(int xi, int yi, int d) {
if (xi < 0 || xi >= get_x_size() ||
yi < 0 || yi >= get_y_size()) {
return;
}
if (get_gray_val(xi, yi) <= d) {
return;
}
set_gray_val(xi, yi, d);
do_fill_distance(xi + 1, yi, d + 1);
do_fill_distance(xi - 1, yi, d + 1);
do_fill_distance(xi, yi + 1, d + 1);
do_fill_distance(xi, yi - 1, d + 1);
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: PNMImage::get_average_xel // Function: PNMImage::get_average_xel
// Access: Published // Access: Published

View File

@ -218,6 +218,8 @@ PUBLISHED:
double pixel_scale = 1.0); double pixel_scale = 1.0);
void threshold(const PNMImage &select_image, int channel, double threshold, void threshold(const PNMImage &select_image, int channel, double threshold,
const PNMImage &lt, const PNMImage &ge); const PNMImage &lt, const PNMImage &ge);
BLOCKING void fill_distance_inside(const PNMImage &mask, double threshold, int radius, bool shrink_from_border);
BLOCKING void fill_distance_outside(const PNMImage &mask, double threshold, int radius);
void rescale(double min_val, double max_val); void rescale(double min_val, double max_val);
@ -259,6 +261,8 @@ PUBLISHED:
LColord get_average_xel_a() const; LColord get_average_xel_a() const;
double get_average_gray() const; double get_average_gray() const;
void do_fill_distance(int xi, int yi, int d);
public: public:
// Know what you are doing if you access the underlying data arrays // Know what you are doing if you access the underlying data arrays
// directly. // directly.