Point operations are the simplest image processing transformations. An output pixel value depends on the input pixel value only. Examples of such transformations are tone adjustments for brightness and contrast, thresholding, color space transformation, image compositing, image arithmetic or logic, etc.
Mathematically, a point operator can be written as
h(x, y) = g(f(x, y))
or
h = g ∘ f
Various basic algorithms are suitable for point operations within
nGI. The most common algorithm is transform() which comes in
multiple incarnations: with one, two or three source views.
Writing Point Operations
There are many ways to write and use point functions in nGI. First,
point functions can be written in the form of ordinary functions or
ordinary template functions. Second, point functions can be written in
the form of function objects (i.e. structures or classes that define
operator ()). Third, point functions can be written in the form of
lambda expressions. Finally, one of the many predefined point functors
of nGI can be used.
The following examples are all taken from the point_operations sample.
Point Operations Using Functions
Ordinary C++ functions can be used as point operations with the
transform() algorithm. Here is a very simple example of pixel
negation:
// ordinary function usable for unary point operation
inline unsigned char negate_mono(unsigned char const& a)
{
return ~a;
}
and here is the code fragment that you would need to actually perform the operation:
transform(source.view(), destination.view(), negate_mono);
Of course, you can also use binary or ternary functions with the respective versions of the transform algorithm to implement point operations between two or more images.
In addition, you can use template functions to make them more generic and independent of the respective data type.
// template function usable for unary point operation
template<typename T>
inline T negate(T const& a)
{
return ~a;
}
When you use a template function, you must specify the type to the point function:
transform(source.get_view(), destination.get_view(), negate<ungsigned char>);
Point Operations Using Function Objects
C++ function objects provide more power than ordinary functions, and can be used equally well as point operations with the transform algorithm. Here is the same simple example of pixel negation as above, but written with a function object:
// function object usable for unary point operations
struct negate {
unsigned char operator()(unsigned char const& a) {
return ~a;
}
};
...
transform(source.get_view(), destination.get_view(), negate());
Of course, you can also use binary or ternary function objects with the respective versions of the transform algorithm to implement point operations between two or more images.
In addition, you can use template function objects to make them more generic and independent of the respective data type.
Point Operations Using Lambda Functions
Lambda function objects provide even more convenience, since they avoid the manual creation of functions or function objects altogether. Instead, special lambda expression syntax is used to write the function on-the-fly. Here is the same simple example of pixel negation as above, but written with a lambda expression:
transform(source.get_view(),destination.get_view(), ~_1);
Of course, you can also use binary or ternary lambda expressions with the respective versions of the transform algorithm to implement point operations between two or more images.
Classification of Point Operations
There are various ways to classify point operations. One way to classify them is by the number of arguments. Unary point operations take one argument, binary point operations take two arguments and tertiary point arguments take three arguments. Another way is to separate them into arithmetic, logical, and other operations. The table shows this classification for the operations implemented within nGI.
| arithmetic | logic | other | |
| unary | negate, absolute, increment, decrement | not | color space transformation |
| binary | add, difference, subtract | and, or, xor | maximum, minimum, compare_bigger, compare_bigger_or_equal, compare_equal, compare_smaller, compare_smaller_or_equal, compositing, |
| tertiary | compositing |
The code samples in this chapter use the following input images:



absolute
Absolute takes the absolute value of its arguments.
f(x) = |x|
It is defined for signed types only. You can use the function
absolute() or the function object pt_absolute_1, but note that the
former may be optimized for performance.
absolute(source.get_view(), destination.get_view());
add
Add adds its arguments. The version implemented here avoids overflow and saturates (i.e. for 8 bits: sum > 255 will be saturated to 255).
$$f(x,y)= \{ \begin{matrix} x+y & \mid & x+y \< max \\ max & \mid & x+y \geqslant max \end{matrix}$$
You can use the function add() or the function object pt_add_2, but
note that the former may be optimized for performance.
add(source1.get_view(), source2.get_view(), destination.get_view());

The function add() is overloaded and allows you to add a constant
value (y is constant) to a view.
add(source.get_view(), 100, destination.get_view());

and
And logically bitwise ANDs its arguments.
f(x, y) = x ∧ y
You can use the function and() or the function object pt_and_2, but
note that the former may be optimized for performance.
and(source1.get_view(), source2.get_view(), destination.get_view());

The function and() is overloaded and allows you to AND another image
view or a constant value (y is constant) with a view.
and(source.get_view(), 100, destination.get_view());

blend
Blend blends its arguments.
f(x, y, α) = x ⋅ (1 − α) + y ⋅ α
You can use the function blend() or the function object pt_blend_3,
but note that the former may be optimized for performance.
blend(source1.get_view(), source2.get_view(), alpha.get_view(), destination.get_view());

The function blend() is overloaded and allows you to blend a view with
a constant value (y is constant).
blend(source.get_view(), 100, alpha.get_view(), destination.get_view());

Another overload of blend() allows you to blend two views with a
constant blend factor (α is constant).
blend_constant(source.get_view(), source2.get_view(), 100, destination.get_view());

Another overload of blend() allows you to blend a view and a constant
with a constant blend factor (x and α are constant).
blend_constant(source.get_view(), 100, 100, destination.get_view());

compare_bigger
Compare bigger compares its arguments.
$$f(x)= \{ \begin{matrix} max & \mid & x > y\\ min & \mid & x \leqslant y \end{matrix}$$
You can use the function compare_bigger() or the function object
pt_compare_bigger_2, but note that the former may be optimized for
performance.
compare_bigger(source1.get_view(), source2.get_view(), destination.get_view());

The function compare_bigger() is overloaded and allows you to compare
a constant value (y is constant) and a view.
compare_bigger(source.get_view(), 100, destination.get_view());

compare_bigger_or_equal
Compare bigger_or_equal compares its arguments.
$$f(x)= \{ \begin{matrix} max & \mid & x \geqslant y\\ min & \mid & x \< y \end{matrix}$$
You can use the function compare_bigger_or_equal() or the function
object pt_compare_bigger_or_equal_2, but note that the former may be
optimized for performance.
compare_bigger_or_equal(source1.get_view(), source2.get_view(), destination.get_view());

The function compare_bigger_or_equal() is overloaded and allows you to
compare a constant value (y is constant) and a view.
compare_bigger_or_eqal(source.get_view(), 100, destination.get_view());

compare_equal
Compare equal compares its arguments.
$$f(x)= \{ \begin{matrix} max & \mid & x = y\\ min & \mid & x \neq y \end{matrix}$$
You can use the function compare_equal() or the function object
pt_compare_equal_2, but note that the former may be optimized for
performance.
compare_equal(source1.get_view(), source2.get_view(), destination.get_view());

The function compare_equal() is overloaded and allows you to compare a
constant value (y is constant) and a view.
compare_eqal(source.get_view(), 100, destination.get_view());

compare_smaller
Compare smaller compares its arguments.
$$f(x)= \{ \begin{matrix} max & \mid & x \< y\\ min & \mid & x \geqslant y \end{matrix}$$
You can use the function compare_smaller() or the function object
pt_compare_smaller_2, but note that the former may be optimized for
performance.
compare_smaller(source1.get_view(), source2.get_view(), destination.get_view());

The function compare_smaller() is overloaded and allows you to compare
a constant value (y is constant) and a view.
compare_eqal(source.get_view(), 100, destination.get_view());

compare_smaller_or_equal
Compare smaller_or_equal compares its arguments.
$$f(x)= \{ \begin{matrix} max & \mid & x \leqslant y\\ min & \mid & x > y \end{matrix}$$
You can use the function compare_smaller_or_equal() or the function
object pt_compare_smaller_or_equal_2, but note that the former may be
optimized for performance.
compare_smaller_or_equal(source1.get_view(), source2.get_view(), destination.get_view());

The function compare_smaller_or_equal() is overloaded and allows you
to compare a constant value (y is constant) and a view.
compare_eqal(source.get_view(), 100, destination.get_view());

compositing
Compositing is about composing a new image from two or more other images. The foundations of that have been laid down by Thomas Porter and Tom Duff in 1984 while working at Lucasfilm Ltd. Despite the age it is still worthwhile to read their original paper (http://keithp.com/~keithp/porterduff/p253-porter.pdf).
The following chapters show the results of compositing operations defined by the original Porter/Duff paper, as well as the code used to create them.
src
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_src_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


dst
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_dst_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


src-over
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_src_over_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


src-in
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_src_in_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


src-out
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_src_out_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


src-atop
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_src_atop_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


dst-over
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_dst_over_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


dst-in
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_dst_in_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


dst-out
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_dst_out_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


dst-atop
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_dst_atop_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


xor
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_xor_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


plus
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_plus_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


multiply
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_multiply_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


screen
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_screen_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


overlay
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_overlay_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


darken
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_darken_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


lighten
transform(rgba1.get_view(), rgba2.get_view(), rgba.get_view(),
pt_composite_lighten_2<color_rgba<unsigned char>, color_rgba<unsigned char> >());


decrement
Decrement decreases its argument by one. The version implemented avoids underflow and saturates (i.e. for 8 bits: 0 decremented will be saturated to 0).
$$f(x)= \{ \begin{matrix} x-1 & \mid & x > min\\ x & \mid & x = min \end{matrix}$$
You can use the function decrement() or the function object
pt_decrement_1, but note that the former may be optimized for
performance.
decrement(source.get_view(), destination.get_view());

difference
Difference calculates the absolute difference between its arguments.
f(x, y) = |x − y|
You can use the function difference() or the function object
pt_difference_2, but note that the former may be optimized for
performance.
difference(source1.get_view(), source2.get_view(), destination.get_view());

The function difference() is overloaded and allows you to add a
constant value (y is constant) to a view.
difference(source.get_view(), 100, destination.get_view());

divide
Divide divides its arguments. The version implemented here avoids overflow by scaling.
$$f(x,y)= \frac{x}{y} \cdot max$$
For performance reasons we use a version of this formula, where max is not the maximum value usable for the type, but one more, so that the multiplication can be implemented with a shift operation.
You can use the function divide() or the function object
pt_divide_2, but note that the former may be optimized for
performance.
divide(source1.get_view(), source2.get_view(), destination.get_view());

The function divide() is overloaded and allows you to divide a view
with a constant value (y is constant).
divide(source.get_view(), 100, destination.get_view());

Another overload of divide() allows to divide a constant value (x is
constant) with a view.
divide(source.get_view(), 100, destination.get_view());

increment
Increment increases its argument by one. The version implemented here avoids overflow and saturates (i.e. for 8 bits: 255 incremented will be saturated to 255).
$$f(x)= \{ \begin{matrix} x+1 & \mid & x \< max\\ x & \mid & x = max \end{matrix}$$
You can use the function increment() or the function object
pt_increment_1, but note that the former may be optimized for
performance.
increment(source.get_view(), destination.get_view());

maximum
Maximum calculates the maximum of its arguments.
$$f(x)= \{ \begin{matrix} x & \mid & x > y\\ y & \mid & x \leqslant y \end{matrix}$$
You can use the function maximum() or the function object
pt_maximum_2, but note that the former may be optimized for
performance.
maximum(source1.get_view(), source2.get_view(), destination.get_view());

The function maximum() is overloaded and allows you to calculate the
maximum of a constant value (y is constant) and a view.
maximum(source.get_view(), 100, destination.get_view());

minimum
Minimum calculates the minimum of its arguments.
$$f(x)= \{ \begin{matrix} x & \mid & x \< y\\ y & \mid & x \geqslant y \end{matrix}$$
You can use the function minimum() or the function object
pt_minimum_2, but note that the former may be optimized for
performance.
minimum(source1.get_view(), source2.get_view(), destination.get_view());

The function minimum() is overloaded and allows you to calculate the
maximum of a constant value (y is constant) and a view.
minimum(source.get_view(), 100, destination.get_view());

multiply
Multiply multiplies its arguments. The version implemented here avoids overflow by scaling.
$$f(x,y)= x \cdot \frac{y}{max}$$
For performance reasons we use a version of this formula, where max is not the maximum value usable for the type, but one more, so that the division can be implemented with a shift operation.
You can use the function multiply() or the function object
pt_multiply_2, but note that the former may be optimized for
performance.
multiply(source1.get_view(), source2.get_view(), destination.get_view());

The function multiply() is overloaded and allows you to multiply a
constant value (y is constant) to a view.
multiply(source.get_view(), 100, destination.get_view());

negate
Negate negates its argument using two’s complement.
f(x) = − x
It is defined for signed types only. You can use the function negate()
or the function object pt_negate_1, but note that the former may be
optimized for performance.
negate(source.get_view(), destination.get_view());

If you are working with an unsigned type, not is probably what you are
looking for.
not
Not negates its argument using one’s complement. Not performs a bit-wise negation of all the bits of its argument.
f(x) = ¬x
This can be used to create an inverse of an image. Dark will be turned to bright and vice versa, just like a photographic negative.
It is defined for integral types only. It has no meaning with floating
point types. You can use the function not() or the function object
pt_not_1, but note that the former may be optimized for performance.
not(source.get_view(), destination.get_view());

If you are working with a floating point type, negate is probably what
you are looking for.
or
Or logically bitwise ORs its arguments.
f(x, y) = x ∨ y
You can use the function or() or the function object pt_or_2, but
note that the former may be optimized for performance.
or(source1.get_view(), source2.get_view(), destination.get_view());

The function or() is overloaded and allows you to OR an image view or
a constant value (y is constant) with a view.
or(source.get_view(), 100, destination.get_view());

subtract
Subtract subtracts its arguments. The version implemented here avoids overflow and saturates (i.e. for 8 bits: difference \< 0 will be saturated to 0).
$$f(x,y)= \{ \begin{matrix} x-y & \mid & x-y > min\\ max & \mid & x-y leqslant min \end{matrix}$$
You can use the function subtract() or the function object
pt_subtract_2, but note that the former may be optimized for
performance.
subtract(source1.get_view(), source2.get_view(), destination.get_view());

The function subtract() is overloaded and allows you to subtract a
constant value (y is constant) from a view.
subtract(source.get_view(), 100, destination.get_view());

Another overload of subtract() allows to subtract a view from a
constant value (x is constant).
subtract(source.get_view(), 100, destination.get_view());

xor
Xor logically bitwise XORs its arguments.
$$f(x, y)=x \underbar{\vee} y$$
You can use the function xor() or the function object pt_xor_2, but
note that the former may be optimized for performance.
xor(source1.get_view(), source2.get_view(), destination.get_view());

The function xor() is overloaded and allows you to XOR an image view
or a constant value (y is constant) with a view.
xor(source.get_view(), 100, destination.get_view());
