1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
|
/**
@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
|