⛏️ index : haiku.git

/*
 * Copyright 2003-2014 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 *	Authors:
 *		Stephan Aßmus, superstippi@gmx.de
 *		Stefano Ceccherini, burton666@libero.it
 */


#include <Region.h>

#include <stdlib.h>
#include <string.h>

#include <Debug.h>

#include "clipping.h"
#include "RegionSupport.h"


const static int32 kDataBlockSize = 8;


BRegion::BRegion()
	:
	fCount(0),
	fDataSize(0),
	fBounds((clipping_rect){ 0, 0, 0, 0 }),
	fData(NULL)
{
	_SetSize(kDataBlockSize);
}


BRegion::BRegion(const BRegion& other)
	:
	fCount(0),
	fDataSize(0),
	fBounds((clipping_rect){ 0, 0, 0, 0 }),
	fData(NULL)
{
	*this = other;
}


BRegion::BRegion(const BRect rect)
	:
	fCount(0),
	fDataSize(1),
	fBounds((clipping_rect){ 0, 0, 0, 0 }),
	fData(&fBounds)
{
	if (!rect.IsValid())
		return;

	fBounds = _ConvertToInternal(rect);
	fCount = 1;
}


#if defined(__cplusplus) && __cplusplus >= 201103L
BRegion::BRegion(BRegion&& other)
	:
	fCount(0),
	fDataSize(0),
	fBounds((clipping_rect){ 0, 0, 0, 0 }),
	fData(NULL)
{
	MoveFrom(other);
}
#endif


// NOTE: private constructor
BRegion::BRegion(const clipping_rect& clipping)
	:
	fCount(1),
	fDataSize(1),
	fBounds(clipping),
	fData(&fBounds)
{
}


BRegion::~BRegion()
{
	if (fData != &fBounds)
		free(fData);
}


BRegion&
BRegion::operator=(const BRegion& other)
{
	if (&other == this)
		return *this;

	// handle reallocation if we're too small to contain the other's data
	if (_SetSize(other.fDataSize)) {
		memcpy(fData, other.fData, other.fCount * sizeof(clipping_rect));

		fBounds = other.fBounds;
		fCount = other.fCount;
	}

	return *this;
}


#if defined(__cplusplus) && __cplusplus >= 201103L
BRegion&
BRegion::operator=(BRegion&& other)
{
	MoveFrom(other);

	return *this;
}
#endif


bool
BRegion::operator==(const BRegion& other) const
{
	if (&other == this)
		return true;

	if (fCount != other.fCount)
		return false;

	return memcmp(fData, other.fData, fCount * sizeof(clipping_rect)) == 0;
}


void
BRegion::Set(BRect rect)
{
	Set(_Convert(rect));
}


void
BRegion::Set(clipping_rect clipping)
{
	_SetSize(1);

	if (valid_rect(clipping) && fData != NULL) {
		fCount = 1;
		fData[0] = fBounds = _ConvertToInternal(clipping);
	} else
		MakeEmpty();
}


void
BRegion::MoveFrom(BRegion& other)
{
	if (other.CountRects() <= 0) {
		MakeEmpty();
		return;
	}
	if (other.CountRects() == 1) {
		Set(other.FrameInt());
		other.MakeEmpty();
		return;
	}
	fCount = other.fCount;
	fDataSize = other.fDataSize;
	fBounds = other.fBounds;
	fData = other.fData;

	other.fCount = 0;
	other.fDataSize = 0;
	other.fBounds = (clipping_rect){ 0, 0, 0, 0 };
	other.fData = NULL;
}


BRect
BRegion::Frame() const
{
	return BRect(fBounds.left, fBounds.top,
		fBounds.right - 1, fBounds.bottom - 1);
}


clipping_rect
BRegion::FrameInt() const
{
	return (clipping_rect){ fBounds.left, fBounds.top,
		fBounds.right - 1, fBounds.bottom - 1 };
}


BRect
BRegion::RectAt(int32 index)
{
	return const_cast<const BRegion*>(this)->RectAt(index);
}


BRect
BRegion::RectAt(int32 index) const
{
	if (index >= 0 && index < fCount) {
		const clipping_rect& r = fData[index];
		return BRect(r.left, r.top, r.right - 1, r.bottom - 1);
	}

	return BRect();
		// an invalid BRect
}


clipping_rect
BRegion::RectAtInt(int32 index)
{
	return const_cast<const BRegion*>(this)->RectAtInt(index);
}


clipping_rect
BRegion::RectAtInt(int32 index) const
{
	if (index >= 0 && index < fCount) {
		const clipping_rect& r = fData[index];
		return (clipping_rect){ r.left, r.top, r.right - 1, r.bottom - 1 };
	}

	return (clipping_rect){ 1, 1, 0, 0 };
		// an invalid clipping_rect
}


int32
BRegion::CountRects()
{
	return fCount;
}


int32
BRegion::CountRects() const
{
	return fCount;
}


bool
BRegion::Intersects(BRect rect) const
{
	return Intersects(_Convert(rect));
}


bool
BRegion::Intersects(clipping_rect clipping) const
{
	clipping = _ConvertToInternal(clipping);

	int result = Support::XRectInRegion(this, clipping);

	return result > Support::RectangleOut;
}


bool
BRegion::Contains(BPoint point) const
{
	return Support::XPointInRegion(this, (int)point.x, (int)point.y);
}


bool
BRegion::Contains(int32 x, int32 y)
{
	return Support::XPointInRegion(this, x, y);
}


bool
BRegion::Contains(int32 x, int32 y) const
{
	return Support::XPointInRegion(this, x, y);
}


// Prints the BRegion to stdout.
void
BRegion::PrintToStream() const
{
	Frame().PrintToStream();

	for (int32 i = 0; i < fCount; i++) {
		clipping_rect *rect = &fData[i];
		printf("data[%" B_PRId32 "] = BRect(l:%" B_PRId32 ".0, t:%" B_PRId32
			".0, r:%" B_PRId32 ".0, b:%" B_PRId32 ".0)\n",
			i, rect->left, rect->top, rect->right - 1, rect->bottom - 1);
	}
}


void
BRegion::OffsetBy(const BPoint& point)
{
	OffsetBy(point.x, point.y);
}


void
BRegion::OffsetBy(int32 x, int32 y)
{
	if (x == 0 && y == 0)
		return;

	if (fCount > 0) {
		if (fData != &fBounds) {
			for (int32 i = 0; i < fCount; i++)
				offset_rect(fData[i], x, y);
		}

		offset_rect(fBounds, x, y);
	}
}


void
BRegion::ScaleBy(BSize scale)
{
	ScaleBy(scale.Width(), scale.Height());
}


void
BRegion::ScaleBy(float x, float y)
{
	if (x == 1.0 && y == 1.0)
		return;

	if (fCount > 0) {
		if (fData != &fBounds) {
			for (int32 i = 0; i < fCount; i++)
				scale_rect(fData[i], x, y);
		}

		scale_rect(fBounds, x, y);
	}
}


void
BRegion::MakeEmpty()
{
	fBounds = (clipping_rect){ 0, 0, 0, 0 };
	fCount = 0;
}


void
BRegion::Include(BRect rect)
{
	Include(_Convert(rect));
}


void
BRegion::Include(clipping_rect clipping)
{
	if (!valid_rect(clipping))
		return;

	// convert to internal clipping format
	clipping.right++;
	clipping.bottom++;

	// use private clipping_rect constructor which avoids malloc()
	BRegion temp(clipping);

	BRegion result;
	Support::XUnionRegion(this, &temp, &result);

	_AdoptRegionData(result);
}


void
BRegion::Include(const BRegion* region)
{
	BRegion result;
	Support::XUnionRegion(this, region, &result);

	_AdoptRegionData(result);
}


void
BRegion::Exclude(BRect rect)
{
	Exclude(_Convert(rect));
}


void
BRegion::Exclude(clipping_rect clipping)
{
	if (!valid_rect(clipping))
		return;

	// convert to internal clipping format
	clipping.right++;
	clipping.bottom++;

	// use private clipping_rect constructor which avoids malloc()
	BRegion temp(clipping);

	BRegion result;
	Support::XSubtractRegion(this, &temp, &result);

	_AdoptRegionData(result);
}


void
BRegion::Exclude(const BRegion* region)
{
	BRegion result;
	Support::XSubtractRegion(this, region, &result);

	_AdoptRegionData(result);
}


void
BRegion::IntersectWith(const BRegion* region)
{
	BRegion result;
	Support::XIntersectRegion(this, region, &result);

	_AdoptRegionData(result);
}


void
BRegion::ExclusiveInclude(const BRegion* region)
{
	BRegion result;
	Support::XXorRegion(this, region, &result);

	_AdoptRegionData(result);
}


//	#pragma mark - BRegion private methods


/*!
	\fn void BRegion::_AdoptRegionData(BRegion& region)
	\brief Takes over the data of \a region and empties it.

	\param region The \a region to adopt data from.
*/
void
BRegion::_AdoptRegionData(BRegion& region)
{
	fCount = region.fCount;
	fDataSize = region.fDataSize;
	fBounds = region.fBounds;
	if (fData != &fBounds)
		free(fData);
	if (region.fData != &region.fBounds)
		fData = region.fData;
	else
		fData = &fBounds;

	// NOTE: MakeEmpty() is not called since _AdoptRegionData is only
	// called with internally allocated regions, so they don't need to
	// be left in a valid state.
	region.fData = NULL;
//	region.MakeEmpty();
}


/*!
	\fn bool BRegion::_SetSize(int32 newSize)
	\brief Reallocate the memory in the region.

	\param newSize The amount of rectangles that the region should be
		able to hold.
*/
bool
BRegion::_SetSize(int32 newSize)
{
	// we never shrink the size
	newSize = max_c(fDataSize, newSize);
		// The amount of rectangles that the region should be able to hold.
	if (newSize == fDataSize)
		return true;

	// align newSize to multiple of kDataBlockSize
	newSize = ((newSize + kDataBlockSize - 1) / kDataBlockSize) * kDataBlockSize;

	if (newSize > 0) {
		if (fData == &fBounds) {
			fData = (clipping_rect*)malloc(newSize * sizeof(clipping_rect));
			fData[0] = fBounds;
		} else if (fData) {
			clipping_rect* resizedData = (clipping_rect*)realloc(fData,
				newSize * sizeof(clipping_rect));
			if (!resizedData) {
				// failed to resize, but we cannot keep the
				// previous state of the object
				free(fData);
				fData = NULL;
			} else
				fData = resizedData;
		} else
			fData = (clipping_rect*)malloc(newSize * sizeof(clipping_rect));
	} else {
		// just an empty region, but no error
		MakeEmpty();
		return true;
	}

	if (!fData) {
		// allocation actually failed
		fDataSize = 0;
		MakeEmpty();
		return false;
	}

	fDataSize = newSize;
	return true;
}


clipping_rect
BRegion::_Convert(const BRect& rect) const
{
	return (clipping_rect){ (int)floorf(rect.left), (int)floorf(rect.top),
		(int)ceilf(rect.right), (int)ceilf(rect.bottom) };
}


clipping_rect
BRegion::_ConvertToInternal(const BRect& rect) const
{
	return (clipping_rect){ (int)floorf(rect.left), (int)floorf(rect.top),
		(int)ceilf(rect.right) + 1, (int)ceilf(rect.bottom) + 1 };
}


clipping_rect
BRegion::_ConvertToInternal(const clipping_rect& rect) const
{
	return (clipping_rect){ rect.left, rect.top,
		rect.right + 1, rect.bottom + 1 };
}