![]()
|
for(..)
instructions, but it also proposes interesting extensions to classical loops. Below is a list of all existing loop macros, classified in four different categories :cimg_library::CImg
image. Two macros are defined for this purpose :
img
, using a pointer T* ptr
, starting from the end of the buffer (last pixel) till the beginning of the buffer (first pixel).img
must be a (non empty) cimg_library::CImg
image of pixels T
.ptr
is a pointer of type T*
. This kind of loop should not appear a lot in your own source code, since this is a low-level loop and many functions of the CImg class may be used instead. Here is an example of use : CImg<float> img(320,200); cimg_for(img,ptr,float) { *ptr=0; } // Equivalent to 'img.fill(0);'
img
, using an offset
, starting from the beginning of the buffer (first pixel, off=0
) till the end of the buffer (last pixel value, off = img.size()-1
).img
must be a (non empty) cimg_library::CImg<T> image of pixels T
.off
is an inner-loop variable, only defined inside the scope of the loop.Here is an example of use :
CImg<float> img(320,200);
cimg_foroff(img,off) { img[off]=0; } // Equivalent to 'img.fill(0);'
for (int x=0; x<img.dimx(); x++)
.for (int y=0; y<img.dimy(); y++)
.for (int z=0; z<img.dimz(); z++)
.for (int v=0; v<img.dimv(); v++)
.Combinations of these macros are also defined as other loop macros, allowing to loop directly over 2D, 3D or 4D images :
cimg_forY(img,y)
cimg_forX(img,x)
.cimg_forZ(img,z)
cimg_forX(img,x)
.cimg_forZ(img,z)
cimg_forY(img,y)
.cimg_forV(img,v)
cimg_forX(img,x)
.cimg_forV(img,v)
cimg_forY(img,y)
.cimg_forV(img,v)
cimg_forZ(img,z)
.cimg_forZ(img,z)
cimg_forXY(img,x,y)
.cimg_forV(img,v)
cimg_forXY(img,x,y)
.cimg_forV(img,v)
cimg_forXZ(img,x,z)
.cimg_forV(img,v)
cimg_forYZ(img,y,z)
.cimg_forV(img,v)
cimg_forXYZ(img,x,y,z)
.
x
,y
,z
and v
are inner-defined variables only visible inside the scope of the loop. They don't have to be defined before the call of the macro.img
must be a (non empty) cimg_library::CImg image.Here is an example of use that creates an image with a smooth color gradient :
CImg<unsigned char> img(256,256,1,3); // Define a 256x256 color image cimg_forXYV(img,x,y,v) { img(x,y,v) = (x+y)*(v+1)/6; } img.display("Color gradient");
n
pixels wide.n
pixels wide.n
pixels wide.n
pixels wide.n
pixels wide.n
pixels wide.And also :
n
pixels wide.n
pixels wide.n
pixels wide.n
pixels wide.n
pixels wide.n
pixels wide.
x
,y
,z
and v
are inner-defined variables only visible inside the scope of the loop. They don't have to be defined before the call of the macro.img
must be a (non empty) cimg_library::CImg image.n
stands for the size of the border.Here is an example of use, to create a 2d grayscale image with two different intensity gradients :
CImg<> img(256,256); cimg_for_insideXY(img,x,y,50) img(x,y) = x+y; cimg_for_borderXY(img,x,y,50) img(x,y) = x-y; img.display();
For all these loops, x
and y
are inner-defined variables only visible inside the scope of the loop. They don't have to be defined before the call of the macro. img
is a non empty CImg<T> image. z
and v
are constants that define on which image slice and vector channel the loop must apply (usually both 0 for grayscale 2D images). Finally, I
is the 2x2, 3x3, 4x4 or 5x5 neighborhood that will be updated with the correct pixel values during the loop (see Defining neighborhoods).
For all these loops, x
, y
and z
are inner-defined variables only visible inside the scope of the loop. They don't have to be defined before the call of the macro. img
is a non empty CImg<T> image. v
is a constant that defines on which image channel the loop must apply (usually 0 for grayscale 3D images). Finally, I
is the 2x2x2 or 3x3x3 neighborhood that will be updated with the correct pixel values during the loop (see Defining neighborhoods).
I
, of type type
.I
, of type type
.I
, of type type
.I
, of type type
.I
, of type type
.I
, of type type
.
Actually, I
is a generic name for the neighborhood. In fact, these macros declare a set of new variables. For instance, defining a 3x3 neighborhood CImg_3x3(I,float)
declares 9 different float variables Ipp
,Icp
,Inp
,Ipc
,Icc
,Inc
,Ipn
,Icn
,Inn
which correspond to each pixel value of a 3x3 neighborhood. Variable indices are p
,c
or n
, and stand respectively for 'previous', 'current' and 'next'. First indice denotes the x-axis
, second indice denotes the y-axis
. Then, the names of the variables are directly related to the position of the corresponding pixels in the neighborhood. For 3D neighborhoods, a third indice denotes the z-axis
. Then, inside a neighborhood loop, you will have the following equivalence :
Ipp = img(x-1,y-1)
Icn = img(x,y+1)
Inp = img(x+1,y-1)
Inpc = img(x+1,y-1,z)
Ippn = img(x-1,y-1,z+1)
For bigger neighborhoods, such as 4x4 or 5x5 neighborhoods, two additionnal indices are introduced : a
(stands for 'after') and b
(stands for 'before'), so that :
Ibb = img(x-2,y-2)
Ina = img(x+1,y+2)
The value of a neighborhood pixel outside the image range (image border problem) is automatically set to the same values than the nearest valid pixel in the image (this is also called the Neumann border condition).
_ref
to the macro names used for the neighborhood definition :
I
, of type type
, as a reference to tab
.I
, of type type
, as a reference to tab
.I
, of type type
, as a reference to tab
.I
, of type type
, as a reference to tab
.I
, of type type
, as a reference to tab
.I
, of type type
, as a reference to tab
.
tab
can be a one-dimensionnal C-style array, or a non empty CImg<T>
image. Both objects must have same sizes as the considered neighborhoods.
cimg_for3x3x3()
loop macro :
CImg<float> volume("IRM.hdr"); // Load an IRM volume from an Analyze7.5 file CImg_3x3x3(I,float); // Define a 3x3x3 neighborhood CImg<float> gradnorm(volume); // Create an image with same size as 'volume' cimg_for3x3x3(volume,x,y,z,0,I) { // Loop over the volume, using the neighborhood I const float ix = 0.5f*(Incc-Ipcc); // Compute the derivative along the x-axis. const float iy = 0.5f*(Icnc-Icpc); // Compute the derivative along the y-axis. const float iz = 0.5f*(Iccn-Iccp); // Compute the derivative along the z-axis. gradnorm(x,y,z) = std::sqrt(ix*ix+iy*iy+iz*iz); // Set the gradient norm in the destination image } gradnorm.display("Gradient norm");
And the following example shows how to deal with neighborhood references to blur a color image by averaging pixel values on a 5x5 neighborhood.
CImg<unsigned char> src("image_color.jpg"), dest(src,false), neighbor(5,5); // Image definitions. typedef unsigned char uchar; // Avoid space in the second parameter of the macro CImg_5x5x1 below. CImg_5x5_ref(N,uchar,neighbor); // Define a 5x5 neighborhood as a reference to the 5x5 image neighbor. cimg_forV(src,k) // Standard loop on color channels cimg_for5x5(src,x,y,0,k,N) // 5x5 neighborhood loop. dest(x,y,k) = neighbor.sum()/(5*5); // Averaging pixels to filter the color image. CImgList<unsigned char> visu(src,dest); visu.display("Original + Filtered"); // Display both original and filtered image.
Note that in this example, we didn't use directly the variables Nbb,Nbp,..,Ncc,... since there are only references to the neighborhood image neighbor
. We rather used a member function of neighbor
.
As you can see, explaining the use of the CImg neighborhood macros is actually more difficult than using them !