3rdParty/boost/1.78.0/libs/gil/doc/html/design/examples.html
Here are some operations you can do with pixel values, pixel pointers and pixel references:
rgb8\_pixel\_tp1(255,0,0);// make a red RGB pixelbgr8\_pixel\_tp2=p1;// RGB and BGR are compatible and the channels will be properly mapped.assert(p1==p2);// p2 will also be red.assert(p2[0]!=p1[0]);// operator[] gives physical channel order (as laid down in memory)assert(semantic\_at\_c\<0\>(p1)==semantic\_at\_c\<0\>(p2));// this is how to compare the two red channelsget\_color(p1,green\_t())=get\_color(p2,blue\_t());// channels can also be accessed by nameconstunsignedchar\*r;constunsignedchar\*g;constunsignedchar\*b;rgb8c\_planar\_ptr\_tptr(r,g,b);// constructing const planar pointer from const pointers to each planergb8c\_planar\_ref\_tref=\*ptr;// just like built-in reference, dereferencing a planar pointer returns a planar referencep2=ref;p2=p1;p2=ptr[7];p2=rgb8\_pixel\_t(1,2,3);// planar/interleaved references and values to RGB/BGR can be freely mixed//rgb8\_planar\_ref\_t ref2; // compile error: References have no default constructors//ref2=\*ptr; // compile error: Cannot construct non-const reference by dereferencing const pointer//ptr[3]=p1; // compile error: Cannot set the fourth pixel through a const pointer//p1 = pixel\<float, rgb\_layout\_t\>();// compile error: Incompatible channel depth//p1 = pixel\<bits8, rgb\_layout\_t\>();// compile error: Incompatible color space (even though it has the same number of channels)//p1 = pixel\<bits8,rgba\_layout\_t\>();// compile error: Incompatible color space (even though it contains red, green and blue channels)
Here is how to use pixels in generic code:
template\<typenameGrayPixel,typenameRGBPixel\>voidgray\_to\_rgb(constGrayPixel&src,RGBPixel&dst){gil\_function\_requires\<PixelConcept\<GrayPixel\>\>();gil\_function\_requires\<MutableHomogeneousPixelConcept\<RGBPixel\>\>();typedeftypenamecolor\_space\_type\<GrayPixel\>::typegray\_cs\_t;static\_assert(boost::is\_same\<gray\_cs\_t,gray\_t\>::value,"");typedeftypenamecolor\_space\_type\<RGBPixel\>::typergb\_cs\_t;static\_assert(boost::is\_same\<rgb\_cs\_t,rgb\_t\>::value,"");typedeftypenamechannel\_type\<GrayPixel\>::typegray\_channel\_t;typedeftypenamechannel\_type\<RGBPixel\>::typergb\_channel\_t;gray\_channel\_tgray=get\_color(src,gray\_color\_t());static\_fill(dst,channel\_convert\<rgb\_channel\_t\>(gray));}// example use patterns:// converting gray l-value to RGB and storing at (5,5) in a 16-bit BGR interleaved image:bgr16\_view\_tb16(...);gray\_to\_rgb(gray8\_pixel\_t(33),b16(5,5));// storing the first pixel of an 8-bit grayscale image as the 5-th pixel of 32-bit planar RGB image:rgb32f\_planar\_view\_trpv32;gray8\_view\_tgv8(...);gray\_to\_rgb(\*gv8.begin(),rpv32[5]);
As the example shows, both the source and the destination can be references or values, planar or interleaved, as long as they model PixelConcept and MutablePixelConcept respectively.
Resizing an image canvas means adding a buffer of pixels around existing pixels. Size of canvas of an image can never be smaller than the image itself.
Suppose we want to convolve an image with multiple kernels, the largest of which is 2K+1 x 2K+1 pixels. It may be worth creating a margin of K pixels around the image borders. Here is how to do it:
template\<typenameSrcView,// Models ImageViewConcept (the source view)typenameDstImage\>// Models ImageConcept (the returned image)voidcreate\_with\_margin(constSrcView&src,intk,DstImage&result){gil\_function\_requires\<ImageViewConcept\<SrcView\>\>();gil\_function\_requires\<ImageConcept\<DstImage\>\>();gil\_function\_requires\<ViewsCompatibleConcept\<SrcView,typenameDstImage::view\_t\>\>();result=DstImage(src.width()+2\*k,src.height()+2\*k);typenameDstImage::view\_tcenterImg=subimage\_view(view(result),k,k,src.width(),src.height());std::copy(src.begin(),src.end(),centerImg.begin());}
We allocated a larger image, then we used subimage_view to create a shallow image of its center area of top left corner at (k,k) and of identical size as src, and finally we copied src into that center image. If the margin needs initialization, we could have done it with fill_pixels. Here is how to simplify this code using the copy_pixels algorithm:
template\<typenameSrcView,typenameDstImage\>voidcreate\_with\_margin(constSrcView&src,intk,DstImage&result){result.recreate(src.width()+2\*k,src.height()+2\*k);copy\_pixels(src,subimage\_view(view(result),k,k,src.width(),src.height()));}
(Note also that image::recreate is more efficient than operator=, as the latter will do an unnecessary copy construction). Not only does the above example work for planar and interleaved images of any color space and pixel depth; it is also optimized. GIL overrides std::copy - when called on two identical interleaved images with no padding at the end of rows, it simply does a memmove. For planar images it does memmove for each channel. If one of the images has padding, (as in our case) it will try to do memmove for each row. When an image has no padding, it will use its lightweight horizontal iterator (as opposed to the more complex 1D image iterator that has to check for the end of rows). It choses the fastest method, taking into account both static and run-time parameters.
The histogram can be computed by counting the number of pixel values that fall in each bin. The following method takes a grayscale (one-dimensional) image view, since only grayscale pixels are convertible to integers:
template\<typenameGrayView,typenameR\>voidgrayimage\_histogram(constGrayView&img,R&hist){for(typenameGrayView::iteratorit=img.begin();it!=img.end();++it)++hist[\*it];}
Using boost::lambda and GIL’s for_each_pixel algorithm, we can write this more compactly:
template\<typenameGrayView,typenameR\>voidgrayimage\_histogram(constGrayView&v,R&hist){for\_each\_pixel(v,++var(hist)[\_1]);}
Where for_each_pixel invokes std::for_each and var and _1 are boost::lambda constructs. To compute the luminosity histogram, we call the above method using the grayscale view of an image:
template\<typenameView,typenameR\>voidluminosity\_histogram(constView&v,R&hist){grayimage\_histogram(color\_converted\_view\<gray8\_pixel\_t\>(v),hist);}
This is how to invoke it:
unsignedcharhist[256];std::fill(hist,hist+256,0);luminosity\_histogram(my\_view,hist);
If we want to view the histogram of the second channel of the image in the top left 100x100 area, we call:
grayimage\_histogram(nth\_channel\_view(subimage\_view(img,0,0,100,100),1),hist);
No pixels are copied and no extra memory is allocated - the code operates directly on the source pixels, which could be in any supported color space and channel depth. They could be either planar or interleaved.
The following code illustrates the power of using image views:
jpeg\_read\_image("monkey.jpg",img);step1=view(img);step2=subimage\_view(step1,200,300,150,150);step3=color\_converted\_view\<rgb8\_view\_t,gray8\_pixel\_t\>(step2);step4=rotated180\_view(step3);step5=subsampled\_view(step4,2,1);jpeg\_write\_view("monkey\_transform.jpg",step5);
The intermediate images are shown here:
Notice that no pixels are ever copied. All the work is done inside jpeg_write_view. If we call our luminosity_histogram with step5 it will do the right thing.