eLynx SDK
v3.3.0 C++ image processing API reference |
The idea is very simple: defining image as array width x height of pixels we can write generic image processing algorithms based on pixels. We implement only one generic algorithm to cover all image types.
We used to use:
We define all requested pixel format in eLynx::Image::EPixelFormat.
We use five different resolutions but not for all types:
We classify pixels by mode, eLynx::Image::EPixelMode:
The most used formats are:
Pixel templates are basic struct without any virtual method, so no virtual table.
They derive from eLynx::Image::PixelBase that defines pixel operators.
Template type < T > is the resolution.
We define ten pixel template structs:
template<typename T> struct PixelL; template<typename T> struct PixelLA; template<typename T> struct PixelComplex; template<typename T> struct PixelRGB; template<typename T> struct PixelHLS; template<typename T> struct PixelXYZ; template<typename T> struct PixelLuv; template<typename T> struct PixelLab; template<typename T> struct PixelLch; template<typename T> struct PixelHLab; template<typename T> struct PixelRGBA;
We define 35 pixel structs from pixel templates:
typedef PixelL<ubyte> PixelLub; typedef PixelL<ushort> PixelLus; typedef PixelL<int> PixelLi; typedef PixelL<float> PixelLf; typedef PixelL<double> PixelLd; typedef PixelLA<ubyte> PixelLAub; typedef PixelLA<ushort> PixelLAus; typedef PixelLA<int> PixelLAi; typedef PixelLA<float> PixelLAf; typedef PixelLA<double> PixelLAd; typedef PixelComplex<int> PixelComplexi; typedef PixelComplex<float> PixelComplexf; typedef PixelComplex<double> PixelComplexd; typedef PixelRGB<ubyte> PixelRGBub; typedef PixelRGB<ushort> PixelRGBus; typedef PixelRGB<int> PixelRGBi; typedef PixelRGB<float> PixelRGBf; typedef PixelRGB<double> PixelRGBd; typedef PixelHLS<float> PixelHLSf; typedef PixelHLS<double> PixelHLSd; typedef PixelXYZ<float> PixelXYZf; typedef PixelXYZ<double> PixelXYZd; typedef PixelLab<float> PixelLabf; typedef PixelLab<double> PixelLabd; typedef PixelLuv<float> PixelLuvf; typedef PixelLuv<double> PixelLuvd; typedef PixelLab<float> PixelLchf; typedef PixelLab<double> PixelLchd; typedef PixelLab<float> PixelHLabf; typedef PixelLab<double> PixelHLabd; typedef PixelRGBA<ubyte> PixelRGBAub; typedef PixelRGBA<ushort> PixelRGBAus; typedef PixelRGBA<int> PixelRGBAi; typedef PixelRGBA<float> PixelRGBAf; typedef PixelRGBA<double> PixelRGBAd;
Each pixel template is defined as a array of channels of template resolution < T >.
For explanations we take PixelRGB as concrete example.
template<typename T> struct PixelRGB : public PixelBase< PixelRGB<T> > { typedef T type; union { type _channel[3]; struct { type _red; type _green; type _blue; }; }; static const uint GetChannelCount() { return 3; } static const EResolution GetResolution() { return ResolutionTypeTraits< T >::_Resolution; } ... };
We use union to have both generic and specific sample naming.
Here _channel[0] is _red, _channel[1] is _green and _channel[2] is _blue.
Writing code with generic pixel we MUST only use _channel access:
template <class Pixel> Pixel elxMin(const Pixel& iPixel1, const Pixel& iPixel2) { Pixel tmp; const uint nChannel = Pixel::GetChannelCount(); for (uint c=0; c<nChannel; c++) { tmp._channel[c] = (iPixel1._channel[c] < iPixel2._channel[c] ) ? iPixel1._channel[c] : iPixel2._channel[c]; } return tmp; }
With specialized pixel we can use specific sample naming.
Writing code with specialized pixel with generic resolution:
template <typename T> void TracePixelRGB(const PixelRGB<T>& iPixel) { cout << "template RGB" << " red=" << iPixel._red << " green=" << iPixel._green << " blue=" << iPixel._blue << endl; }
Writing code with specialized pixel with resolution specialization:
template <> void TracePixelRGB(const PixelRGB<ubyte>& iPixel) { cout << "template RGB in ubyte" << " red=" << int(iPixel._red) << " green=" << int(iPixel._green ) << " blue=" << int(iPixel._blue) << endl; }
Writing code with full specialized pixel:
void TracePixelRGBub(const PixelRGBub& iPixel) { cout << "specialized RGBub" << " red=" << int(iPixel._red) << " green=" << int(iPixel._green ) << " blue=" << int(iPixel._blue) << endl; }
Using all together:
PixelRGBub p1(10, 50, 233), p2(25, 255, 0); TracePixelRGB(p1); TracePixelRGB(p2); PixelRGBub p3 = elxMin(p1, p2); TracePixelRGBub(p3);
At run time we get:
template RGB in ubyte red=10 green=50 blue=233
template RGB in ubyte red=25 green=255 blue=0
specialized RGBub red=10 green=50 blue=0
PixelRGBd p1(0.5, 1.0, 0.33), p2(0.99, 0.1, 0.2); TracePixelRGB(p1); TracePixelRGB(p2); PixelRGBd p3 = elxMin(p1, p2); TracePixelRGB(p3);
At run time we get:
template RGB red=0.5 green=1 blue=0.33
template RGB red=0.99 green=0.1 blue=0.2
template RGB red=0.5 green=0.1 blue=0.2
It's simple because ImageImpl is defined as a < Pixel > template.
You access pixel with GetPixel(), GetPixelEnd() and GetPixel(x,y):
template <class Pixel> bool Process(ImageImpl<Pixel>& ioImage) { if (!ioImage.IsValid()) return false; // get first pixel of image Pixel * prCurrent = ioImage.GetPixel(); // get last pixel of image Pixel * prEnd = ioImage.GetPixelEnd(); // process all pixels do { // do something with prCurrent pixel } while (++prCurrent != prEnd); // get pixel at position x=10 and y=20 prCurrent = ioImage.GetPixel(10, 20); return true; }
We only know pixel format at runtime with AbstractImage. So it's not a good idea to access pixels from this class. But as AbstractImage can be used in pixel template methods, we write PixelIteror to access template image's pixels. But be aware that it means that AbstractImage has < Pixel > type.
If AbstractImage is of another pixel type an exception is thrown.
You can access to pixel with PixelIterator:
template <typename Pixel> bool Process(AbstractImage& ioImage) { if (!ioImage.IsValid()) return false; // get first pixel of image PixelIterator<Pixel> prCurrent = elxDowncast<Pixel>(ioImage.Begin()); // get last pixel of image PixelIterator<Pixel> prEnd = elxDowncast<Pixel>(ioImage.End()); // process all pixels do { // do something with prCurrent pixel } while (++prCurrent != prEnd); return true; }
We can run process because image1 is of PixelRGBf type:
ImageRGBf imageRGBf(40, 30); AbstractImage& image1 = imageRGBf; Process<PixelRGBf>(image1);
But next code will generate an exception because image2 is PixelRGBd
type and it's processed as PixelRGBf type:
ImageRGBd imageRGBd(30, 40); AbstractImage& image2 = imageRGBd; Process<PixelRGBf>(image2);
We can also use PixelIterator from const AbstractImage:
template <typename Pixel> boost::shared_ptr< ImageImpl<Pixel> > Copy(const AbstractImage& iImage) { if (!iImage.IsValid()) return boost::shared_ptr< ImageImpl<Pixel> >(); const uint w = iImage.GetWidth(); const uint h = iImage.GetHeight(); // create the new image boost::shared_ptr< ImageImpl<Pixel> > spImage( new ImageImpl<Pixel>(w,h) ); if (!elxUseable(spImage.get())) return boost::shared_ptr< ImageImpl<Pixel> >(); // get first pixel of input image PixelIterator<const Pixel> prCurrent = elxConstDowncast<const Pixel>(iImage.Begin()); // get first pixel of output image Pixel * prDst = spImage->GetPixel(); // get last pixel of output image Pixel * prEnd = spImage->GetPixelEnd(); // copy all pixels do { *prDst = *prCurrent++; } while (++prDst != prEnd); return spImage; }
Using const AbstractImage:
ImageRGBus imageRGBus(100, 10); AbstractImage& image3 = imageRGBus; boost::shared_ptr< ImageRGBus > spImage = Copy<PixelRGBus>(image3);
TODO