diff options
Diffstat (limited to 'helium/trace.cpp')
-rw-r--r-- | helium/trace.cpp | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/helium/trace.cpp b/helium/trace.cpp new file mode 100644 index 0000000..2218143 --- /dev/null +++ b/helium/trace.cpp @@ -0,0 +1,281 @@ +// Copyright 2006 Google Inc. +// All Rights Reserved. +// Author: <renn@google.com> (Marius Renn) +// +#include "debugging.h" +#include "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 +} |