blob: b1f842ee580460e77bd961017652fd5f24900567 [file] [log] [blame]
// Copyright 2006 Google Inc.
// All Rights Reserved.
// Author: <renn@google.com> (Marius Renn)
//
#include "debugging.h"
#include "helium_image.h"
#include "trace.h"
using namespace helium;
// Neighborhood map 0, 1, 2, 3, 4, 5, 6, 7
const int8 kNeighborX[8] = { 1, 1, 0, -1, -1, -1, 0, 1 };
const int8 kNeighborY[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };
const int kMaxInt = ~(0x01 << (sizeof(int)*8 - 1));
const uint8 kMark = 0x02;
inline bool MovingDown(uint8 dir, bool old_flag) {
if ((dir >= 1) && (dir <= 3))
return true;
else if ((dir >= 5) && (dir <= 7))
return false;
else
return old_flag;
}
inline bool VDirectionChanged(uint8 dir, bool down_flag) {
return ((dir >= 1) && (dir <= 3) && !down_flag)
|| ((dir >= 5) && (dir <= 7) && down_flag);
}
Trace::Trace()
: Array<uint8>(16),
type_(0),
bounds_(0, 0, 0, 0),
start_(0, 0) {
}
Trace::Trace(const Trace& other)
: Array<uint8>(other),
type_(other.type_),
bounds_(other.bounds_),
start_(other.start_) {
}
Trace* Trace::Copy() const {
return new Trace(*this);
}
void Trace::CloseAt(Point p) {
int min_x = kMaxInt;
int min_y = kMaxInt;
int max_x = 0;
int max_y = 0;
Point orig_p = p;
for (unsigned i = 0; i < size(); i++) {
if (p.x < min_x) min_x = p.x;
if (p.y < min_y) min_y = p.y;
if (p.x > max_x) max_x = p.x;
if (p.y > max_y) max_y = p.y;
p.x += kNeighborX[ValueAt(i)];
p.y += kNeighborY[ValueAt(i)];
}
// Bug: we don't need to subtract 1 from min_x,min_y
// min_x--;
// min_y--;
ASSERT((max_x >= min_x) && (max_y >= min_y));
Point origin(min_x, min_y);
bounds_.set_origin(origin);
bounds_.set_width(max_x - min_x + 1);
bounds_.set_height(max_y - min_y + 1);
start_ = p;
ASSERT(p.x == orig_p.x && p.y == orig_p.y);
}
void Trace::PlotOnto(Mask& mask) const {
unsigned w = mask.width();
int neighbor[8] = { 1, w + 1, w, w - 1, -1, -w - 1, -w , -w + 1 };
bool* mask_ptr = mask.Access(start_);
for (unsigned i = 0; i < size(); i++) {
(*mask_ptr) = 1;
mask_ptr += neighbor[ValueAt(i)];
}
(*mask_ptr) = 1;
}
bool Trace::GetFinalVerticalMove() const {
for (int i = size() - 1; i >= 0; i--) {
uint8 dir = ValueAt(i);
if ((dir >= 5) && (dir <= 7))
return false;
else if ((dir >= 1) && (dir <= 3))
return true;
}
return false;
}
// This method marks the left and right edges of the trace to simplify the
// process of extracting pixels from within the trace. The marks are written
// to the alpha channel of the given Image, and must be plotted in such a way
// that every mark signals entering or exiting the inside of the Trace.
// Scanning the inner pixels then becomes trivial, as one must only scan the
// area of the trace in the image, line by line, and at every mark
// invert a flag that specifies whether to extract the next pixel or not.
//
// To find these trace borders, the algorithm follows the trace marking the
// points it has visited, and paying attention to the following special cases:
//
// 1) Horizontal moves
//
// |
// +------------
//
// We do not mark the current location as a boundry if the last move was
// horizontal. (In the upper example, the moves markes with '-' will not get
// marked.
//
// 2) Change of vertical direction
//
// | |
// +-------------+
//
// However, in the this example, we need to mark the second '+', even though
// the last move was horizontal. This is due to the change in the vertical
// direction, that makes this point critical to the trace. Therefore, if there
// is a change in the vertical direction, we mark the point, at which the
// direction changes as well.
//
// 3) Spikes
//
// +-------------+
// | |
// | | |
// +---/ \-------+
//
// In this special case, we must not mark the tip of the spike, as this would
// suggest that we have left the interior of the trace. Therefore, if we
// detect a spike of this manner, we do not mark the coordinates at the tip of
// such a spike.
void Trace::MarkHorizontalBoundries(Image& image) const {
unsigned w = image.width();
int neighbor[8] = { 1, w + 1, w, w - 1, -1, -w - 1, -w , -w + 1 };
Color* image_ptr = image.Access(start_);
uint8 dir, last_dir = ValueAt(size() - 1);
bool moving_down = MovingDown(last_dir, GetFinalVerticalMove());
for (unsigned i = 0; i < size(); i++) {
dir = ValueAt(i);
bool spike_up = (last_dir >= 5) && (last_dir <= 7)
&& (dir >= 1) && (dir <= 3);
bool spike_dn = (last_dir >= 1) && (last_dir <= 3)
&& (dir >= 5) && (dir <= 7);
if (!spike_up && !spike_dn) { // Ignore spikes
if ((last_dir != 0) && (last_dir != 4)) // Ignore horizontal moves, ...
SetAlphaAt(image_ptr, kMark);
else if (VDirectionChanged(dir, moving_down)) // ...unless they lead to
SetAlphaAt(image_ptr, kMark); // a change in vertical
} // direction.
// Move in image
image_ptr += neighbor[dir];
moving_down = MovingDown(dir, moving_down);
last_dir = dir;
}
}
void Trace::RemoveMarks(Image& image) const {
unsigned w = image.width();
int neighbor[8] = { 1, w + 1, w, w - 1, -1, -w - 1, -w , -w + 1 };
Color* image_ptr = image.Access(start_);
for (unsigned i = 0; i < size(); i++) {
SetAlphaAt(image_ptr, 0x00);
image_ptr += neighbor[ValueAt(i)];
}
}
Histogram Trace::ExtractHistogramFrom(Image& image) const {
// Is this Trace valid (has it been closed)?
ASSERT((bounds_.width() > 0) && (bounds_.height() > 0));
// Bounds check
ASSERT((bounds_.left() >= 0) && (bounds_.right() < image.width()));
ASSERT((bounds_.top() >= 0) && (bounds_.bottom() < image.height()));
// Mark the horizontal boundries
MarkHorizontalBoundries(image);
// Scan pixels
Point p;
Color* image_ptr = image.Access(bounds_.origin());
unsigned step_v = image.width() - bounds_.width();
Histogram histogram;
for (p.y = 0; p.y < bounds_.height(); p.y++) {
bool pen = false;
for (p.x = 0; p.x < bounds_.width(); p.x++) {
if (Alpha(*image_ptr) == kMark)
pen = !pen;
else if (pen) // exclude marker point since they are on boundary
histogram.AddColor(*image_ptr);
image_ptr++;
}
image_ptr += step_v;
}
// Remove marks again NOTE: Is this the best way to do it???
RemoveMarks(image);
return histogram;
}
void Trace::FillTraceOnto(Image& image, Color color) const {
// Is this Trace valid (has it been closed)?
ASSERT((bounds_.width() > 0) && (bounds_.height() > 0));
// Bounds check
ASSERT((bounds_.left() >= 0) && (bounds_.right() < image.width()));
ASSERT((bounds_.top() >= 0) && (bounds_.bottom() < image.height()));
// Mark the horizontal boundries
MarkHorizontalBoundries(image);
// Render pixels
Point p;
Color* image_ptr = image.Access(bounds_.origin());
unsigned step_v = image.width() - bounds_.width();
for (p.y = 0; p.y < bounds_.height(); p.y++) {
bool pen = false;
for (p.x = 0; p.x < bounds_.width(); p.x++) {
if (Alpha(*image_ptr) == kMark)
pen = !pen;
else if (pen) // exclude marker point since they are on boundary
*image_ptr = color;
image_ptr++;
}
image_ptr += step_v;
}
// Remove marks again NOTE: Is this the best way to do it???
RemoveMarks(image);
}
// In order to test whether two non-intersecting contours A contains B, we
// need the actual coordinates of boundary points, not just directions.
// The algorithm is due to Ray Smith. Since the traces are non-intersecting
// and closed, we simply need to test if any point (x,y) in B is inside or
// outside A. This is achieved by counting the number of times points on
// A crosses the horizontal line at y to the right of x. The traces must
// have been 'Closed' first.
bool Trace::Contains(const Trace& trace) const {
// First do a quick test using bounding box
if (!Surrounds(bounds_, trace.bounds_))
return false;
int x = trace.start_.x;
int y = trace.start_.y;
int ncross = 0;
Point p(start_.x, start_.y);
for (int i = 0; i < size(); ++i) {
uint8 dir = ValueAt(i);
p.x += kNeighborX[dir];
p.y += kNeighborY[dir];
if (p.x <= x) continue; // consider only one side of x
if (p.y == y && dir >= 1 && dir <= 3)
ncross++; // increment count if coming down to the horizon
if (p.y == y+1 && dir >= 5 && dir <= 7)
ncross--; // decrement count if rising from the horizon
}
return (ncross % 2); // If A contains B, must have odd crossings
}