- Note:
- Some of this documentation was originally written at the time that we converted the Image class to have a ref-counted interface and moved functionality from member functions to free functions; this is the source of references to "new" and "old" styles.
Dims represents the width and height dimensions: struct Dims
{
int const ww;
int const hh;
};
ArrayData encapsulates the management of a 2-D data array template <class T>
class ArrayData
{
long itsRefCount;
Dims const itsDims;
T* const itsData;
public:
const T* data() const { return itsData; }
T* dataw() { return itsData; }
ArrayData* clone() const { return new ArrayData(*this); }
void incrRefCount() const { ++itsRefCount; }
long decrRefCount() const { return (--itsRefCount); }
bool isShared() const { return (itsRefCount > 1); }
};
- Since copying is cheap, an Image<T> can be the return value of a function...
- ...and chaining operations is easier:
template <class T>
void foo(const Image<T>& x)
{
Image<T> tmpResult, coolResult;
coolFilter1(x, tmpResult);
coolFilter2(tmpResult, coolResult);
Image<T> hotResult = hotFilter2(hotFilter1(x));
}
- We only have to deep copy the data when absolutely necessary. For instance, if we are converting an Image<T> of unknown type to Image<byte>:
- Since memory management is encapsulated within ArrayData and ArrayHandle, "ordinary" functions should not have to call
new[]/delete[] to manage temporary storage
- if an exception is thrown while filling in
buf in coolDraw(), then we leak memory
- but if an exception is thrown while filling in
tmp in hotDraw(), then ~Image() will clean up tmp for us
- non-const functions must check uniqueness each time they are called
- in particular, it is not especially efficient to call Image::setVal() in the middle of some inner loop, since
- therefore, in time-critical loops, instead of this:
- try this if you like
while loops:
- or this if you like
for loops:
- Iterators act like pointers
- All standard library containers provide iterators
#include <vector>
#include <iostream>
void foo()
{
std::vector<int> myvec;
for (int i = 0; i < 10; ++i)
myvec.push_back(i);
for (std::vector<int>::const_iterator
itr = myvec.begin(),
stop = myvec.end();
itr != stop;
++itr)
{
std::cout << *itr << std::endl;
}
}
- Iterators can be used with generic algorithms:
template <class Iterator>
void printContents(Iterator itr, Iterator stop)
{
while (itr != stop)
std::cout << *itr++ << std::endl;
}
void foo()
{
std::vector<int> myvec;
printContents(myvec.begin(), myvec.end());
}
- Normal (non-debug) iterators are just pointers:
- Debug iterators are objects that act like pointers, but check that the pointer is in bounds every time it is dereferenced.
- To use debug iterators, first
make clean, then re-run ./configure with --enable-mem-debug, then re-run make all.
template <class T>
class CheckedIterator
{
TT* ptr;
TT* start;
TT* stop;
TT* operator->() const
{
CheckedIteratorAux::ck_range_helper(ptr, start, stop);
return ptr;
}
TT& operator*() const
{
CheckedIteratorAux::ck_range_helper(ptr, start, stop);
return *ptr;
}
TT& operator[](diff_t d) const
{
CheckedIteratorAux::ck_range_helper(ptr+d, start, stop);
return ptr[d];
}
};
template <class T>
class Image
{
public:
#ifndef INVT_MEM_DEBUG
#else
typedef CheckedIterator<T> iterator;
typedef CheckedIterator<const T> const_iterator;
const_iterator begin() const
{ return const_iterator(impl().data(), impl().end()); }
const_iterator end() const
{ return const_iterator(impl().end(), impl().end()); }
iterator beginw() { return iterator(uniq().dataw(), uniq().endw()); }
iterator endw() { return iterator(uniq().endw(), uniq().endw()); }
#endif
}
- Iterators in use with handmade loops:
- Iterators in use with STL algorithms:
#include <algorithm>
#include <numeric>
template <class T>
int emptyArea(const Image<T>& img)
{
return std::count(img.begin(), img.end(), T());
}
template<class T>
double getMean(const Image<T>& img)
{
const double sum = std::accumulate(img.begin(), img.end(), 0.0);
return sum / double(img.getSize());
}
- Almost all image-related functions are free functions, rather than member functions, defined in separate
.H files. Why do this?
- The best example is to see the Image class in analogy to the std::vector class. The std::vector class defines only the complete but minimal set of operations needed to use std::vector as a container. Any functions for application-specific uses of the std::vector class would be written as free functions. Likewise, the Image class provides the complete but minimal set of operations needed to use Image as a two-dimensional array of values; any domain-specific uses of the Image class (such as filtering, rescaling, concatenating, arithmetic) are written as free functions.
- Since all operations can be coded in terms of iterators, most Image algorithms do not need access to the
private parts of Image.
- In some cases free functions offer a more natural mathematical syntax than member functions.
- Keeping unrelated functions in separate files improves logical encapsulation and decreases compile-time dependencies.
| File name | Contents |
Image/All.H | #includes all other Image-related files |
Image/ColorOps.H | color operations: get/set components, luminance, RGB-YIQ conversions, etc. |
Image/FilterOps.H | lowPass*(), convolve*(), sepFilter*(), etc. |
Image/MathOps.H | absDiff(), average(), takeMax(), RMSerr(), etc. |
Image/Omni.H | all omni-directional related operations |
Image/ShapeOps.H | rescale(), downSize(), concat*(), dec*(), int*(), crop() |
Image/Transforms.H | segmentObject(), dct(), infoFFT(), etc. |
- old:
#define BYTEMIN 0
#define BYTEMAX 255
#define INT16MIN (-32768)
#define INT16MAX 32767
#define INT32MIN (-2147483647)
#define INT32MAX 2147483647
#define FLOATMIN -3.40e+38F
#define FLOATMAX 3.40282347e+38F
void foo(Image<float>& x)
{
x.normalize(BYTEMIN, BYTEMAX);
}
- Why? It's standard, it's portable, it offers more
- can be specialized for user-defined types
- get the number of bits used to represent the type
- get the range of the exponent for floating-point types
#include <limits>
template <class T>
void foo(const Image<T>& x)
{
if (std::numeric_limits<T>::is_signed)
{
}
else
{
}
}
template <class T>
void bar(const Image<T>& x)
{
if (std::numeric_limits<T>::is_integer)
{
}
else
{
}
}