681 lines
20 KiB
C
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
|