mxwcore-legion/dep/g3dlite/include/G3D/Map2D.h

681 lines
20 KiB
C++

/**
@file Map2D.h
More flexible support than provided by G3D::GImage.
@maintainer Morgan McGuire, morgan@cs.brown.edu
@created 2004-10-10
@edited 2009-03-24
*/
#ifndef G3D_Map2D_h
#define G3D_Map2D_h
#include "G3D/platform.h"
#include "G3D/g3dmath.h"
#include "G3D/Array.h"
#include "G3D/vectorMath.h"
#include "G3D/Vector2int16.h"
#include "G3D/ReferenceCount.h"
#include "G3D/AtomicInt32.h"
#include "G3D/GThread.h"
#include "G3D/Rect2D.h"
#include "G3D/WrapMode.h"
#include <string>
namespace G3D {
namespace _internal {
/** The default compute type for a type is the type itself. */
template<typename Storage> class _GetComputeType {
public:
typedef Storage Type;
};
} // _internal
} // G3D
// This weird syntax is needed to support VC6, which doesn't
// properly implement template overloading.
#define DECLARE_COMPUTE_TYPE(StorageType, ComputeType) \
namespace G3D { \
namespace _internal { \
template<> class _GetComputeType < StorageType > { \
public: \
typedef ComputeType Type; \
}; \
} \
}
DECLARE_COMPUTE_TYPE( float32, float64)
DECLARE_COMPUTE_TYPE( float64, float64)
DECLARE_COMPUTE_TYPE( int8, float32)
DECLARE_COMPUTE_TYPE( int16, float32)
DECLARE_COMPUTE_TYPE( int32, float64)
DECLARE_COMPUTE_TYPE( int64, float64)
DECLARE_COMPUTE_TYPE( uint8, float32)
DECLARE_COMPUTE_TYPE( uint16, float32)
DECLARE_COMPUTE_TYPE( uint32, float64)
DECLARE_COMPUTE_TYPE( uint64, float64)
DECLARE_COMPUTE_TYPE( Vector2, Vector2)
DECLARE_COMPUTE_TYPE( Vector2int16, Vector2)
DECLARE_COMPUTE_TYPE( Vector3, Vector3)
DECLARE_COMPUTE_TYPE( Vector3int16, Vector3)
DECLARE_COMPUTE_TYPE( Vector4, Vector4)
DECLARE_COMPUTE_TYPE( Color3, Color3)
DECLARE_COMPUTE_TYPE( Color3uint8, Color3)
DECLARE_COMPUTE_TYPE( Color4, Color4)
DECLARE_COMPUTE_TYPE( Color4uint8, Color4)
#undef DECLARE_COMPUTE_TYPE
namespace G3D {
/**
Map of values across a discrete 2D plane. Can be thought of as a generic class for 2D images,
allowing flexibility as to pixel format and convenient methods.
In fact, the "pixels" can be any values
on a grid that can be sensibly interpolated--RGB colors, scalars, 4D vectors, and so on.
Other "image" classes in G3D:
G3D::GImage - Supports file formats, fast, Color3uint8 and Color4uint8 formats. No interpolation.
G3D::shared_ptr<Texture> - Represents image on the graphics card (not directly readable on the CPU). Supports 2D, 3D, and a variety of interpolation methods, loads file formats.
G3D::Image3 - A subclass of Map2D<Color3> that supports image loading and saving and conversion to Texture.
G3D::Image4 - A subclass of Map2D<Color4> that supports image loading and saving and conversion to Texture.
G3D::Image3uint8 - A subclass of Map2D<Color3uint8> that supports image loading and saving and conversion to Texture.
G3D::Image4uint8 - A subclass of Map2D<Color4uint8> that supports image loading and saving and conversion to Texture.
There are two type parameters-- the first (@ Storage) is the type
used to store the "pixel" values efficiently and
the second (@a Compute) is
the type operated on by computation. The Compute::Compute(Storage&) constructor
is used to convert between storage and computation types.
@a Storage is often an integer version of @a Compute, for example
<code>Map2D<double, uint8></code>. By default, the computation type is:
<pre>
Storage Computation
uint8 float32
uint16 float32
uint32 float64
uint64 float64
int8 float32
int16 float32
int32 float64
int64 float64
float32 float64
float64 float64
Vector2 Vector2
Vector2int16 Vector2
Vector3 Vector3
Vector3int16 Vector3
Vector4 Vector4
Color3 Color3
Color3uint8 Color3
Color4 Color4
Color4uint8 Color4
</pre>
Any other storage type defaults to itself as the computation type.
The computation type can be any that
supports lerp, +, -, *, /, and an empty constructor.
Assign value:
<code>im->set(x, y, 7);</code> or
<code>im->get(x, y) = 7;</code>
Read value:
<code>int c = im(x, y);</code>
Can also sample with nearest neighbor, bilinear, and bicubic
interpolation.
Sampling follows OpenGL conventions, where
pixel values represent grid points and (0.5, 0.5) is half-way
between two vertical and two horizontal grid points.
To draw an image of dimensions w x h with nearest neighbor
sampling, render pixels from [0, 0] to [w - 1, h - 1].
Under the WrapMode::CLAMP wrap mode, the value of bilinear interpolation
becomes constant outside [1, w - 2] horizontally. Nearest neighbor
interpolation is constant outside [0, w - 1] and bicubic outside
[3, w - 4]. The class does not offer quadratic interpolation because
the interpolation filter could not center over a pixel.
@author Morgan McGuire, http://graphics.cs.williams.edu
*/
template< typename Storage,
typename Compute = typename G3D::_internal::_GetComputeType<Storage>::Type>
class Map2D : public ReferenceCountedObject {
//
// It doesn't make sense to automatically convert from Compute back to Storage
// because the rounding rule (and scaling) is application dependent.
// Thus the interpolation methods all return type Compute.
//
public:
typedef Storage StorageType;
typedef Compute ComputeType;
typedef Map2D<Storage, Compute> Type;
typedef shared_ptr<Map2D> Ref;
protected:
Storage ZERO;
/** Width, in pixels. */
uint32 w;
/** Height, in pixels. */
uint32 h;
WrapMode _wrapMode;
/** 0 if no mutating method has been invoked
since the last call to setChanged(); */
AtomicInt32 m_changed;
Array<Storage> data;
/** Handles the exceptional cases from get */
const Storage& slowGet(int x, int y, WrapMode wrap) {
switch (wrap) {
case WrapMode::CLAMP:
return fastGet(iClamp(x, 0, w - 1), iClamp(y, 0, h - 1));
case WrapMode::TILE:
return fastGet(iWrap(x, w), iWrap(y, h));
case WrapMode::ZERO:
return ZERO;
case WrapMode::ERROR:
alwaysAssertM(((uint32)x < w) && ((uint32)y < h),
format("Index out of bounds: (%d, %d), w = %d, h = %d",
x, y, w, h));
// intentionally fall through
case WrapMode::IGNORE:
// intentionally fall through
default:
{
static Storage temp;
return temp;
}
}
}
public:
/** Unsafe access to the underlying data structure with no wrapping support; requires that (x, y) is in bounds. */
inline const Storage& fastGet(int x, int y) const {
debugAssert(((uint32)x < w) && ((uint32)y < h));
return data[x + y * w];
}
/** Unsafe access to the underlying data structure with no wrapping support; requires that (x, y) is in bounds. */
inline void fastSet(int x, int y, const Storage& v) {
debugAssert(((uint32)x < w) && ((uint32)y < h));
data[x + y * w] = v;
}
protected:
/** Given four control points and a value on the range [0, 1)
evaluates the Catmull-rom spline between the times of the
middle two control points */
Compute bicubic(const Compute* ctrl, double s) const {
// f = B * S * ctrl'
// B matrix: Catmull-Rom spline basis
static const double B[4][4] = {
{ 0.0, -0.5, 1.0, -0.5},
{ 1.0, 0.0, -2.5, 1.5},
{ 0.0, 0.5, 2.0, -1.5},
{ 0.0, 0.0, -0.5, 0.5}};
// S: Powers of the fraction
double S[4];
double s2 = s * s;
S[0] = 1.0;
S[1] = s;
S[2] = s2;
S[3] = s2 * s;
Compute sum(ZERO);
for (int c = 0; c < 4; ++c) {
double coeff = 0.0;
for (int power = 0; power < 4; ++power) {
coeff += B[c][power] * S[power];
}
sum += ctrl[c] * coeff;
}
return sum;
}
Map2D(int w, int h, WrapMode wrap) : w(0), h(0), _wrapMode(wrap), m_changed(1) {
ZERO = Storage(Compute(Storage()) * 0);
resize(w, h);
}
public:
/**
Although Map2D is not threadsafe (except for the setChanged() method),
you can use this mutex to create your own threadsafe access to a Map2D.
Not used by the default implementation.
*/
GMutex mutex;
static Ref create(int w = 0, int h = 0, WrapMode wrap = WrapMode::ERROR) {
return Ref(new Map2D(w, h, wrap));
}
/** Resizes without clearing, leaving garbage.
*/
void resize(uint32 newW, uint32 newH) {
if ((newW != w) || (newH != h)) {
w = newW;
h = newH;
data.resize(w * h);
setChanged(true);
}
}
/**
Returns true if this map has been written to since the last call to setChanged(false).
This is useful if you are caching a texture map other value that must be recomputed
whenever this changes.
*/
bool changed() {
return m_changed.value() != 0;
}
/** Set/unset the changed flag. */
void setChanged(bool c) {
m_changed = c ? 1 : 0;
}
/** Returns a pointer to the underlying row-major data. There is no padding at the end of the row.
Be careful--this will be reallocated during a resize. You should call setChanged(true) if you mutate the array.*/
Storage* getCArray() {
return data.getCArray();
}
const Storage* getCArray() const {
return data.getCArray();
}
/** Row-major array. You should call setChanged(true) if you mutate the array. */
Array<Storage>& getArray() {
return data;
}
const Array<Storage>& getArray() const {
return data;
}
/** is (x, y) strictly within the image bounds, or will it trigger some kind of wrap mode */
inline bool inBounds(int x, int y) const {
return (((uint32)x < w) && ((uint32)y < h));
}
/** is (x, y) strictly within the image bounds, or will it trigger some kind of wrap mode */
inline bool inBounds(const Vector2int16& v) const {
return inBounds(v.x, v.y);
}
/** Get the value at (x, y).
Note that the type of image->get(x, y) is
the storage type, not the computation
type. If the constructor promoting Storage to Compute rescales values
(as, for example Color3(Color3uint8&) does), this will not match the value
returned by Map2D::nearest.
*/
inline const Storage& get(int x, int y, WrapMode wrap) const {
if (((uint32)x < w) && ((uint32)y < h)) {
return data[x + y * w];
} else {
// Remove the const to allow a slowGet on this object
// (we're returning a const reference so this is ok)
return const_cast<Type*>(this)->slowGet(x, y, wrap);
}
# ifndef G3D_WINDOWS
// gcc gives a useless warning that the above code might reach the end of the function;
// we use this line to supress the warning.
return ZERO;
# endif
}
inline const Storage& get(int x, int y) const {
return get(x, y, _wrapMode);
}
inline const Storage& get(const Vector2int16& p) const {
return get(p.x, p.y, _wrapMode);
}
inline const Storage& get(const Vector2int16& p, WrapMode wrap) const {
return get(p.x, p.y, wrap);
}
inline Storage& get(int x, int y, WrapMode wrap) {
return const_cast<Storage&>(const_cast<const Type*>(this)->get(x, y, wrap));
# ifndef G3D_WINDOWS
// gcc gives a useless warning that the above code might reach the end of the function;
// we use this line to supress the warning.
return ZERO;
# endif
}
inline Storage& get(int x, int y) {
return const_cast<Storage&>(const_cast<const Type*>(this)->get(x, y));
# ifndef G3D_WINDOWS
// gcc gives a useless warning that the above code might reach the end of the function;
// we use this line to supress the warning.
return ZERO;
# endif
}
inline Storage& get(const Vector2int16& p) {
return get(p.x, p.y);
}
/** Sets the changed flag to true */
inline void set(const Vector2int16& p, const Storage& v) {
set(p.x, p.y, v);
}
/** Sets the changed flag to true */
void set(int x, int y, const Storage& v, WrapMode wrap) {
setChanged(true);
if (((uint32)x < w) && ((uint32)y < h)) {
// In bounds, wrapping isn't an issue.
data[x + y * w] = v;
} else {
const_cast<Storage&>(slowGet(x, y, wrap)) = v;
}
}
void set(int x, int y, const Storage& v) {
set(x, y, v, _wrapMode);
}
void setAll(const Storage& v) {
for(int i = 0; i < data.size(); ++i) {
data[i] = v;
}
setChanged(true);
}
/** Copy values from \a src, which must have the same size */
template<class T>
void set(const shared_ptr<Map2D<Storage, T> >& src) {
debugAssert(src->width() == width());
debugAssert(src->height() == height());
const Array<Storage>& s = src->data;
int N = w * h;
for (int i = 0; i < N; ++i) {
data[i] = s[i];
}
setChanged(true);
}
/** flips if @a flip is true*/
void maybeFlipVertical(bool flip) {
if (flip) {
flipVertical();
}
}
virtual void flipVertical() {
int halfHeight = h/2;
Storage* d = data.getCArray();
for (int y = 0; y < halfHeight; ++y) {
int o1 = y * w;
int o2 = (h - y - 1) * w;
for (int x = 0; x < (int)w; ++x) {
int i1 = o1 + x;
int i2 = o2 + x;
Storage temp = d[i1];
d[i1] = d[i2];
d[i2] = temp;
}
}
setChanged(true);
}
virtual void flipHorizontal() {
int halfWidth = w / 2;
Storage* d = data.getCArray();
for (int x = 0; x < halfWidth; ++x) {
for (int y = 0; y < (int)h; ++y) {
int i1 = y * w + x;
int i2 = y * w + (w - x - 1);
Storage temp = d[i1];
d[i1] = d[i2];
d[i2] = temp;
}
}
setChanged(true);
}
/**
Crops this map so that it only contains pixels between (x, y) and (x + w - 1, y + h - 1) inclusive.
*/
virtual void crop(int newX, int newY, int newW, int newH) {
alwaysAssertM(newX + newW <= (int)w, "Cannot grow when cropping");
alwaysAssertM(newY + newH <= (int)h, "Cannot grow when cropping");
alwaysAssertM(newX >= 0 && newY >= 0, "Origin out of bounds.");
// Always safe to copy towards the upper left, provided
// that we're iterating towards the lower right. This lets us avoid
// reallocating the underlying array.
for (int y = 0; y < newH; ++y) {
for (int x = 0; x < newW; ++x) {
data[x + y * newW] = data[(x + newX) + (y + newY) * w];
}
}
resize(newW, newH);
}
/** iRounds to the nearest x0 and y0. */
virtual void crop(const Rect2D& rect) {
crop(iRound(rect.x0()), iRound(rect.y0()), iRound(rect.x1()) - iRound(rect.x0()), iRound(rect.y1()) - iRound(rect.y0()));
}
/** Returns the nearest neighbor. Pixel values are considered
to be at the upper left corner, so <code>image->nearest(x, y) == image(x, y)</code>
*/
inline Compute nearest(float x, float y, WrapMode wrap) const {
int ix = iRound(x);
int iy = iRound(y);
return Compute(get(ix, iy, wrap));
}
inline Compute nearest(float x, float y) const {
return nearest(x, y, _wrapMode);
}
inline Compute nearest(const Vector2& p) const {
return nearest(p.x, p.y);
}
/** Returns the average value of all elements of the map */
Compute average() const {
if ((w == 0) || (h == 0)) {
return ZERO;
}
// To avoid overflows, compute the average of row averages
Compute rowSum = ZERO;
for (unsigned int y = 0; y < h; ++y) {
Compute sum = ZERO;
int offset = y * w;
for (unsigned int x = 0; x < w; ++x) {
sum += Compute(data[offset + x]);
}
rowSum += sum * (1.0f / w);
}
return rowSum * (1.0f / h);
}
/**
Needs to access elements from (floor(x), floor(y))
to (floor(x) + 1, floor(y) + 1) and will use
the wrap mode appropriately (possibly generating
out of bounds errors).
Guaranteed to match nearest(x, y) at integers. */
Compute bilinear(float x, float y, WrapMode wrap) const {
const int i = iFloor(x);
const int j = iFloor(y);
const float fX = x - i;
const float fY = y - j;
// Horizontal interpolation, first row
const Compute& t0 = get(i, j, wrap);
const Compute& t1 = get(i + 1, j, wrap);
// Horizontal interpolation, second row
const Compute& t2 = get(i, j + 1, wrap);
const Compute& t3 = get(i + 1, j + 1, wrap);
const Compute& A = lerp(t0, t1, fX);
const Compute& B = lerp(t2, t3, fX);
// Vertical interpolation
return lerp(A, B, fY);
}
Compute bilinear(float x, float y) const {
return bilinear(x, y, _wrapMode);
}
inline Compute bilinear(const Vector2& p) const {
return bilinear(p.x, p.y, _wrapMode);
}
inline Compute bilinear(const Vector2& p, WrapMode wrap) const {
return bilinear(p.x, p.y, wrap);
}
/**
Uses Catmull-Rom splines to interpolate between grid
values. Guaranteed to match nearest(x, y) at integers.
*/
Compute bicubic(float x, float y, WrapMode wrap) const {
int i = iFloor(x);
int j = iFloor(y);
float fX = x - i;
float fY = y - j;
Compute vsample[4];
for (int v = 0; v < 4; ++v) {
// Horizontal interpolation
Compute hsample[4];
for (int u = 0; u < 4; ++u) {
hsample[u] = Compute(get(i + u - 1, j + v - 1, wrap));
}
vsample[v] = bicubic(hsample, fX);
}
// Vertical interpolation
return bicubic(vsample, fY);
}
Compute bicubic(float x, float y) const {
return bicubic(x, y, _wrapMode);
}
inline Compute bicubic(const Vector2& p, WrapMode wrap) const {
return bicubic(p.x, p.y, wrap);
}
inline Compute bicubic(const Vector2& p) const {
return bicubic(p.x, p.y, _wrapMode);
}
/** Pixel width */
inline int32 width() const {
return (int32)w;
}
/** Pixel height */
inline int32 height() const {
return (int32)h;
}
/** Dimensions in pixels */
Vector2int16 size() const {
return Vector2int16(w, h);
}
/** Rectangle from (0, 0) to (w, h) */
Rect2D rect2DBounds() const {
return Rect2D::xywh(0, 0, w, h);
}
/** Number of bytes occupied by the image data and this structure */
size_t sizeInMemory() const {
return data.size() * sizeof(Storage) + sizeof(*this);
}
WrapMode wrapMode() const {
return _wrapMode;
}
void setWrapMode(WrapMode m) {
_wrapMode = m;
}
};
}
#endif // G3D_IMAGE_H