aboutsummaryrefslogtreecommitdiff
path: root/externals/g3dlite/G3D/Map2D.h
blob: 9af9f7242c1470b5411a733625e99ed15afa3058 (plain)
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
/**
 @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::Texture::Ref - 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 ReferenceCountedPointer<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 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_WIN32
            // 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_WIN32
            // 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_WIN32
            // 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);
    }

    /** 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