`

【翻译】泛型图片库(GIL)教程(上)

 
阅读更多

原文引用自

http://stlab.adobe.com/gil/html/giltutorial.html

 

翻译引用自(注:修改并重新翻译)

http://www.cppprog.com/boost_doc/libs/gil/doc/html/giltutorial.html

 

此教程的原文即英文版属于boost和adobe

http://www.boost.org/

http://www.adobe.com/

 

(翻译均为个人观点,请以原文为准)

 

Generic Image Library Tutorial

 

【翻译】泛型图片库(GIL)教程

 

Author:

Lubomir Bourdev (lbourdev@adobe.com) and Hailin Jin (hljin@adobe.com) 

Adobe Systems Incorporated

 

作者:

Lubomir Bourdev (lbourdev@adobe.com) and Hailin Jin (hljin@adobe.com) 

Adobe系统公司

 

Version:

2.1

 

版本:

2.1

 

Date:

September 15, 2007

 

日期:

2007年9月15日

 

The Generic Image Library (GIL) is a C++ library that abstracts image representations from algorithms and allows writing code that can work on a variety of images with performance similar to hand-writing for a specific image type.

 

泛型图片库(GIL)是算法上的抽象图片表示的C++函数库,允许书写代码,操纵一些不同的图片,而性能近似于针对特定图片类型的手工代码。

 

This document will give you a jump-start in using GIL. It does not discuss the underlying design of the library and does not cover all aspects of it. You can find a detailed library design document on the main GIL web page at http://opensource.adobe.com/gil

 

本文档将给你使用GIL的快速起点。这里不讨论库的底层设计,也不会涵盖它的所有方面。你可以GIL的主网页http://stlab.adobe.com/gil找到更详细的库设计文档。

 

Installation

Example - Computing the Image Gradient

Interface and Glue Code

First Implementation

Using Locators

Creating a Generic Version of GIL Algorithms

Image View Transformations

1D pixel iterators

STL Equivalent Algorithms

Color Conversion

Image

Virtual Image Views

Run-Time Specified Images and Image Views

Conclusion

Appendix

Naming convention for GIL concrete types

 

安装

示例 - 计算图片梯度

接口和粘合代码

第一个实现

使用定位器

创建GIL算法的泛型版本

图像视图变换

一维像素迭代器

STL对应算法

颜色变换

图片

虚拟图片视图

运行时图片和图片视图

结论

附录

GIL实际类型的命名约定

 

Installation

 

安装

 

The latest version of GIL can be downloaded from GIL's web page, at http://opensource.adobe.com/gil. GIL is approved for integration into Boost and in the future will be installed simply by installing Boost from http://www.boost.org. GIL consists of header files only and does not require any libraries to link against. It does not require Boost to be built. Including boost/gil/gil_all.hpp will be sufficient for most projects.

 

GIL的最新版本可以从GIL的网页下载,http://opensource.adobe.com/gil。 GIL的被批准集成进Boost,将来可以从http://www.boost.org通过安装Boost来简单安装。GIL只包含头文件,不要求链接任何库。它不需要编译Boost。包含boost/gil/gil_all.hpp将足以应对大多数项目。

 

Example - Computing the Image Gradient

 

示例 - 计算图片梯度

 

This tutorial will walk through an example of using GIL to compute the image gradients. We will start with some very simple and non-generic code and make it more generic as we go along. Let us start with a horizontal gradient and use the simplest possible approximation to a gradient - central difference. The gradient at pixel x can be approximated with the half-difference of its two neighboring pixels: D[x] = (I[x-1] - I[x+1]) / 2

 

本教程将把使用GIL计算图像梯度的示例贯穿始终。我们将从一些非常简单和非泛型的代码开始,使之随着我们的步伐更泛型。让我们从水平梯度开始,使用梯度的最简近似算法 - 中心差。水平像素x的梯度可以用它的两个邻接像素的半差分近似计算:D[x] = (I[x-1] - I[x+1]) / 2

 

For simplicity, we will also ignore the boundary cases - the pixels along the edges of the image for which one of the neighbors is not defined. The focus of this document is how to use GIL, not how to create a good gradient generation algorithm.

 

简单起见,我们也将忽略边界情况 - 图片边界像素的一个邻接像素没有定义。本文档的焦点是如何使用GIL,而不是如何创建一个好的梯度生成算法.

 

Interface and Glue Code

 

接口和粘合代码

 

Let us first start with 8-bit unsigned grayscale image as the input and 8-bit signed grayscale image as the output. Here is how the interface to our algorithm looks like:

 

让我们首先从输入是8位无符号灰度图而输出是8位有符号灰度图开始。这里我们的算法接口看起来就像这样子:

 

#include <boost/gil/gil_all.hpp>

using namespace boost::gil;

void x_gradient(const gray8c_view_t& src, const gray8s_view_t& dst) {

    assert(src.dimensions() == dst.dimensions());

    ...    // compute the gradient //计算梯度

}

 

gray8c_view_t is the type of the source image view - an 8-bit grayscale view, whose pixels are read-only (denoted by the "c"). The output is a grayscale view with a 8-bit signed (denoted by the "s") integer channel type. See Appendix 1 for the complete convension GIL uses to name concrete types.

 

gray8c_view_t是源图片视图的类型——一个8位灰度视图,它的像素只读(用“c”表明),输出是8位有符号(用“s”表示)整型通道类型。附录1提供命名GIL实际类型的完整约定(注:约定的正式英文应该是convention)。

 

GIL makes a distinction between an image and an image view. A GIL image view, is a shallow, lightweight view of a rectangular grid of pixels. It provides access to the pixels but does not own the pixels. Copy-constructing a view does not deep-copy the pixels. Image views do not propagate their constness to the pixels and should always be taken by a const reference. Whether a view is mutable or read-only (immutable) is a property of the view type.

 

GIL区分图片和图片视图。一个GIL图片视图, 是一个像素矩形格的平薄的、轻量级的视图。它提供像素的读写能力,但并不拥有像素。视图的复制构造并不会深度复制像素。图片视图不会把它们的常量性传播到图片像素,而总是应该以常量引用来持有它们(注:这里可能指持有图片视图而非持有图片像素。个人认为,对C++的引用型参数都加上const可避免类型转换)。不管视图可变还是只读(不变),它都是视图类型的一个属性。

 

A GIL image, on the other hand, is a view with associated ownership. It is a container of pixels; its constructor/destructor allocates/deallocates the pixels, its copy-constructor performs deep-copy of the pixels and its operator== performs deep-compare of the pixels. Images also propagate their constness to their pixels - a constant reference to an image will not allow for modifying its pixels.

 

另一方面,GIL图片是一种拥有相关所有权的视图。它是像素的容器;它的构造或析构函数分配或释放像素。它的复制构造函数执行像素的深度复制,而操作符==会执行像素值的深度比较。图像也会把它们的常量性传播给它们的像素——一个指向图片的常量引用将不允许用于修改它的像素。

 

Most GIL algorithms operate on image views; images are rarely needed. GIL's design is very similar to that of the STL. The STL equivalent of GIL's image is a container, like std::vector, whereas GIL's image view corresponds to STL's range, which is often represented with a pair of iterators. STL algorithms operate on ranges, just like GIL algorithms operate on image views.

 

大多数GIL算法是在图片视图上操作的;极少需要用图片。GIL的设计非常近似于STL的设计。GIL图片的STL等价物是容器,像std::vector,而GIL's的图片视图对应STL中常表现为一对迭代器的区间(注:这里可能指begin()和end()这两个迭代器)。操作在区间的STL算法,正像操作在图片视图的GIL算法那样.

 

GIL's image views can be constructed from raw data - the dimensions, the number of bytes per row and the pixels, which for chunky views are represented with one pointer. Here is how to provide the glue between your code and GIL:

 

GIL的图片视图可以从原生数据中构造——尺寸(注:其实就是size,图片大小)、每行(注:图片横向的像素线)和每个像素的字节数,对于一小块视图可以用一个指针表示。这里介绍如何把你的代码和GIL粘合起来(注:就是说,如果你只有裸数据指针,你必须用下面的代码把它转成视图,否则GIL算法函数x_gradient无法使用):

 

void ComputeXGradientGray8(const unsigned char* src_pixels, ptrdiff_t src_row_bytes, int w, int h,

                                   signed char* dst_pixels, ptrdiff_t dst_row_bytes) {

    gray8c_view_t src = interleaved_view(w, h, (const gray8_pixel_t*)src_pixels,src_row_bytes);

    gray8s_view_t dst = interleaved_view(w, h, (     gray8s_pixel_t*)dst_pixels,dst_row_bytes);

    x_gradient(src,dst);

}

 

This glue code is very fast and views are lightweight - in the above example the views have a size of 16 bytes. They consist of a pointer to the top left pixel and three integers - the width, height, and number of bytes per row.

 

这段粘合代码非常块,而且视图都是轻量级的 - 上面的示例中每个视图只占16个字节。它们包括一个指向左上角像素的指针和三个整数——宽,高以及每行字节数。(注:像素是单字节,可能已经反映在实际类型中,所以不需要专门保存)

 

First Implementation

 

第一个实现

 

Focusing on simplicity at the expense of speed, we can compute the horizontal gradient like this:

 

把焦点放在简单而非速度上面,我们可以像这样计算水平梯度:

 

void x_gradient(const gray8c_view_t& src, const gray8s_view_t& dst) {

    for (int y=0; y<src.height(); ++y)

        for (int x=1; x<src.width()-1; ++x)

            dst(x,y) = (src(x-1,y) - src(x+1,y)) / 2;

}

 

We use image view's operator(x,y) to get a reference to the pixel at a given location and we set it to the half-difference of its left and right neighbors. operator() returns a reference to a grayscale pixel. A grayscale pixel is convertible to its channel type (unsigned char for src) and it can be copy-constructed from a channel. (This is only true for grayscale pixels). While the above code is easy to read, it is not very fast, because the binary operator() computes the location of the pixel in a 2D grid, which involves addition and multiplication. Here is a faster version of the above:

 

我们用视图的operator(x,y)(注:operator是C++表示运算符重载的关键字)获得给定位置的像素引用,并且把它设置为它的左右邻接像素的半差分。operator()返回一个灰度像素的引用。灰度像素值可以转换为它的通道类型(对于src是unsigned char),而它可以从通道中复制构造(这只对灰度像素有效)(注:严格来说不是复制构造,而是基本类型的值传递)。虽然上面的代码很容易理解,但它并不快,因为二元操作符operator()在一个二维网格中计算像素的位置(注:这句话有点暧昧,是说它用一维数组表示二维信息吗?),这会涉及加法和乘法。这里是一个比上面更快的版本:

 

void x_gradient(const gray8c_view_t& src, const gray8s_view_t& dst) {

    for (int y=0; y<src.height(); ++y) {

        gray8c_view_t::x_iterator src_it = src.row_begin(y);

        gray8s_view_t::x_iterator dst_it = dst.row_begin(y);

 

        for (int x=1; x<src.width()-1; ++x)

            dst_it[x] = (src_it[x-1] - src_it[x+1]) / 2;

    }

}

 

We use pixel iterators initialized at the beginning of each row. GIL's iterators are Random Access Traversal iterators. If you are not familiar with random access iterators, think of them as if they were pointers. In fact, in the above example the two iterator types are raw C pointers and their operator[] is a fast pointer indexing operator.

 

我们使用每行像素的开头初始化像素迭代器。GIL迭代器是随机访问遍历迭代器。如果你对随机访问迭代器不熟悉,那么你可以认为它们就是指针。事实上,在上面的例子中两个指针的类型都是裸的C指针,而它们的操作符operator[]是快速指针索引运算符。(注:至少比operator()快)

 

The code to compute gradient in the vertical direction is very similar:

 

计算垂直方向梯度的代码非常类似:

 

void y_gradient(const gray8c_view_t& src, const gray8s_view_t& dst) {

    for (int x=0; x<src.width(); ++x) {

        gray8c_view_t::y_iterator src_it = src.col_begin(x);

        gray8s_view_t::y_iterator dst_it = dst.col_begin(x);

 

        for (int y=1; y<src.height()-1; ++y)

            dst_it[y] = (src_it[y-1] - src_it[y+1])/2;

    }

}

 

Instead of looping over the rows, we loop over each column and create a y_iterator, an iterator moving vertically. In this case a simple pointer cannot be used because the distance between two adjacent pixels equals the number of bytes in each row of the image. GIL uses here a special step iterator class whose size is 8 bytes - it contains a raw C pointer and a step. Its operator[] multiplies the index by its step.

 

不是行循环,我们逐列循环,并且创建一个y_iterator,一个垂直移动的迭代器。这种情况下,不能使用一个简单指针,因为两个相邻像素之间的距离是图片每行的字节数。GIL使用一个8字节大小的特殊步进迭代器——包含一个裸指针和一个步长。它的操作符operator[]把索引乘上步长。

 

The above version of y_gradient, however, is much slower (easily an order of magnitude slower) than x_gradient because of the memory access pattern; traversing an image vertically results in lots of cache misses. A much more efficient and cache-friendly version will iterate over the columns in the inner loop:

 

然而,因为内存访问模式,上面的y_iterator版本比x_iterator版本慢得多(很容易慢一个数量级);垂直遍历一个图片会导致大量缓存丢失。更高效的和缓存友好的版本是在内循环中迭代列:(注:这里不同的是,使用遍历y而非遍历x,使用行迭代器而非列迭代器)

 

void y_gradient(const gray8c_view_t& src, const gray8s_view_t& dst) {

    for (int y=1; y<src.height()-1; ++y) {

        gray8c_view_t::x_iterator src1_it = src.row_begin(y-1);

        gray8c_view_t::x_iterator src2_it = src.row_begin(y+1);

        gray8s_view_t::x_iterator dst_it = dst.row_begin(y);

 

        for (int x=0; x<src.width(); ++x) {

            *dst_it = ((*src1_it) - (*src2_it))/2;

            ++dst_it;

            ++src1_it;

            ++src2_it;

        }

    }

}

 

This sample code also shows an alternative way of using pixel iterators - instead of operator[] one could use increments and dereferences.

 

这个示例代码还展示,使用像素迭代器的可替换方式——不使用operator[],我们也可以使用递增和取值。(注:这里指++和*运算符)

 

Using Locators

 

使用定位器

 

Unfortunately this cache-friendly version requires the extra hassle of maintaining two separate iterators in the source view. For every pixel, we want to access its neighbors above and below it. Such relative access can be done with GIL locators:

 

不幸的是,这个缓存友好版本需要维护源视图的两个单独迭代器的额外争议。 对于每个像素,我们想访问在它之上和之下的邻接像素。这种相对访问可以通过GIL定位器实现:(注:简单说就是把两个有关系的迭代器合并成一个定位器)

 

void y_gradient(const gray8c_view_t& src, const gray8s_view_t& dst) {

    gray8c_view_t::xy_locator src_loc = src.xy_at(0,1);

    for (int y=1; y<src.height()-1; ++y) {

        gray8s_view_t::x_iterator dst_it  = dst.row_begin(y);

 

        for (int x=0; x<src.width(); ++x) {

            (*dst_it) = (src_loc(0,-1) - src_loc(0,1)) / 2;

            ++dst_it;

            ++src_loc.x();                  // each dimension can be advanced separately // 每个维可以单独增加(注:这里指++x()会导致横向移动一个像素)

        }

        src_loc+=point2<std::ptrdiff_t>(-src.width(),1);    // carriage return  // 回车(注:向左移到最左,向下移一行)

    }

}

 

The first line creates a locator pointing to the first pixel of the second row of the source view. A GIL pixel locator is very similar to an iterator, except that it can move both horizontally and vertically. src_loc.x() and src_loc.y() return references to a horizontal and a vertical iterator respectively, which can be used to move the locator along the desired dimension, as shown above. Additionally, the locator can be advanced in both dimensions simultaneously using its operator+= and operator-=. Similar to image views, locators provide binary operator() which returns a reference to a pixel with a relative offset to the current locator position. For example, src_loc(0,1) returns a reference to the neighbor below the current pixel. Locators are very lightweight objects - in the above example the locator has a size of 8 bytes - it consists of a raw pointer to the current pixel and an int indicating the number of bytes from one row to the next (which is the step when moving vertically). The call to ++src_loc.x() corresponds to a single C pointer increment. However, the example above performs more computations than necessary. The code src_loc(0,1) has to compute the offset of the pixel in two dimensions, which is slow. Notice though that the offset of the two neighbors is the same, regardless of the pixel location. To improve the performance, GIL can cache and reuse this offset:

 

第一行代码创建了一个定位器,指向源视图的第二行第一个像素。GIL像素定位器很类似于一个迭代器,除了它垂直和水平都能移动。src_loc.x()和src_loc.y()返回一个垂直和水平方向的迭代器,它们可以沿着希望的维移动,如同上面展示的那样。另外,定位器可以使用它的operator+=和operator-=同时在两个维上前进。和图片视图相似,定位器提供二元操作符operator(),返回当前定位器相对偏移的像素引用。例如,src_loc(0,1)返回当前像素的下面邻接像素的引用。定位器是非常轻量级的对象——在上面的例子中定位器有8字节大小——它包含一个对当前像素的裸指针以及一个指示一行到另一行的字节数的整型变量(即垂直移动的步长)。++src_loc.x()的调用对应于单一C指针的递增。然而,上面的例子执行得过于复杂。代码src_loc(0,1)必须在二维上计算偏移,会很慢。注意到两个邻接像素的偏移是相同的,与像素位置无关。为了提升性能,GIL可以缓存和重用这个偏移:

 

void y_gradient(const gray8c_view_t& src, const gray8s_view_t& dst) {

    gray8c_view_t::xy_locator src_loc = src.xy_at(0,1);

    gray8c_view_t::xy_locator::cached_location_t above = src_loc.cache_location(0,-1);

    gray8c_view_t::xy_locator::cached_location_t below = src_loc.cache_location(0, 1);

 

    for (int y=1; y<src.height()-1; ++y) {

        gray8s_view_t::x_iterator dst_it = dst.row_begin(y);

 

        for (int x=0; x<src.width(); ++x) {

            (*dst_it) = (src_loc[above] - src_loc[below])/2;

            ++dst_it;

            ++src_loc.x();             

        }

        src_loc+=point2<std::ptrdiff_t>(-src.width(),1);

    }

}

 

In this example "src_loc[above]" corresponds to a fast pointer indexing operation and the code is efficient.

 

在例子中“src_loc[above]”对应快速的指针索引操作,这段代码是高效的。(注:很容易发现这里用了一个新类型cached_location_t)

 

Creating a Generic Version of GIL Algorithms

 

创建GIL算法的泛型版本

 

Let us make our x_gradient more generic. It should work with any image views, as long as they have the same number of channels. The gradient operation is to be computed for each channel independently. Here is how the new interface looks like:

 

让我们把x_gradient变得更加泛型。算法应该适用于任意图片视图,只要它们有相同的通道数(注:这里是指源和目标图片有相同个数的通道?)。对每个通道单独执行梯度操作。新接口看起来就像这样:(注:下面的代码貌似诊断了通道数相同)

 

template <typename SrcView, typename DstView>

void x_gradient(const SrcView& src, const DstView& dst) {

    gil_function_requires<ImageViewConcept<SrcView> >();

    gil_function_requires<MutableImageViewConcept<DstView> >();

    gil_function_requires<ColorSpacesCompatibleConcept<

                                typename color_space_type<SrcView>::type, 

                                typename color_space_type<DstView>::type> >();

 

    ... // compute the gradient // 计算梯度

}

 

 

The new algorithm now takes the types of the input and output image views as template parameters. That allows using both built-in GIL image views, as well as any user-defined image view classes. The first three lines are optional; they use boost::concept_check to ensure that the two arguments are valid GIL image views, that the second one is mutable and that their color spaces are compatible (i.e. have the same set of channels).

 

现在新算法把输入图片和输出图片的视图类型都当作模板参数。那样,既允许使用内建的GIL图片视图类型,也允许用户定义的任意图片视图类型。头三行是可选的;它们使用boost::concept_check确保这两个参数是合法的GIL图片视图,而第二个参数是可变的(注:这里指图片的像素可修改),它们的颜色空间也是兼容的(即具有相同的通道数)。

 

GIL does not require using its own built-in constructs. You are free to use your own channels, color spaces, iterators, locators, views and images. However, to work with the rest of GIL they have to satisfy a set of requirements; in other words, they have to model the corresponding GIL concept. GIL's concepts are defined in the user guide.

 

GIL不要求使用GIL的内建构造类型。你可以使用自己定义的颜色通道、颜色空间、迭代器、定位器、图片视图和图片。但是要想适用于其它GIL,它们必须满足一组条件;换句话说,它们必须建模出相应的GIL概念。GIL的概念在用户手册中定义。

 

One of the biggest drawbacks of using templates and generic programming in C++ is that compile errors can be very difficult to comprehend. This is a side-effect of the lack of early type checking - a generic argument may not satisfy the requirements of a function, but the incompatibility may be triggered deep into a nested call, in code unfamiliar and hardly related to the problem. GIL uses boost::concept_check to mitigate this problem. The above three lines of code check whether the template parameters are valid models of their corresponding concepts. If a model is incorrect, the compile error will be inside gil_function_requires, which is much closer to the problem and easier to track. Furthermore, such checks get compiled out and have zero performance overhead. The disadvantage of using concept checks is the sometimes severe impact they have on compile time. This is why GIL performs concept checks only in debug mode, and only if BOOST_GIL_USE_CONCEPT_CHECK is defined (off by default).

 

使用C++模板和泛型编程的最大弊端之一就是编译错误可能很难理解。这是缺乏早期类型检查的副作用——一个泛型参数(注:这里可能指模板参数)可能不满足函数的要求,但这种不兼容性可能在很深的嵌套调用中被触发,在那些并不熟悉和与问题有很小关系的代码中出现。GIL使用boost::concept_check缓和这种问题。上面的这三行代码检查模板参数是否是它们相应概念的合法模型。如果模型是错误的,编译错误就出现在gil_function_requires中,更靠近于问题且易于跟踪。更进一步,这种检查不会被编译也不会有性能开销。使用概念检查的缺点是有时会严重影响它们花在编译上的时间。这就是为什么GIL只会在调试模式和BOOST_GIL_USE_CONCEPT_CHECK被定义(默认关闭)下才执行概念检查。

 

 

The body of the generic function is very similar to that of the concrete one. The biggest difference is that we need to loop over the channels of the pixel and compute the gradient for each channel:

 

泛型函数的主体与非泛型版本非常类似的。最大的区别是我们需要对像素的颜色通道进行循环且计算每个通道的梯度:

 

template <typename SrcView, typename DstView>

void x_gradient(const SrcView& src, const DstView& dst) {

    for (int y=0; y<src.height(); ++y) {

        typename SrcView::x_iterator src_it = src.row_begin(y);

        typename DstView::x_iterator dst_it = dst.row_begin(y);

 

        for (int x=1; x<src.width()-1; ++x)

            for (int c=0; c<num_channels<SrcView>::value; ++c)

                dst_it[x][c] = (src_it[x-1][c]- src_it[x+1][c])/2;

    }

}

 

Having an explicit loop for each channel could be a performance problem. GIL allows us to abstract out such per-channel operations:

 

对每个通道都有显式循环可能有性能问题,GIL允许我们抽象这种针对各个通道的操作:(注:下面你会发现,循环被模板强行展开成非循环了)

 

template <typename Out>

struct halfdiff_cast_channels {

    template <typename T> Out operator()(const T& in1, const T& in2) const {

        return Out((in1-in2)/2);

    }

};

 

template <typename SrcView, typename DstView>

void x_gradient(const SrcView& src, const DstView& dst) {

    typedef typename channel_type<DstView>::type dst_channel_t;

 

    for (int y=0; y<src.height(); ++y) {

        typename SrcView::x_iterator src_it = src.row_begin(y);

        typename DstView::x_iterator dst_it = dst.row_begin(y);

 

        for (int x=1; x<src.width()-1; ++x)

            static_transform(src_it[x-1], src_it[x+1], dst_it[x], 

                               halfdiff_cast_channels<dst_channel_t>());

    }

}

 

static_transform is an example of a channel-level GIL algorithm. Other such algorithms are static_generate, static_fill and static_for_each. They are the channel-level equivalents of STL's generate, transform, fill and for_each respectively. GIL channel algorithms use static recursion to unroll the loops; they never loop over the channels explicitly. Note that sometimes modern compilers (at least Visual Studio 8) already unroll channel-level loops, such as the one above. However, another advantage of using GIL's channel-level algorithms is that they pair the channels semantically, not based on their order in memory. For example, the above example will properly match an RGB source with a BGR destination.

 

static_transform 是一个通道级GIL算法。其他算法还有static_generate,static_fill以及static_for_each。它们的STL等价物是generate,transform,fill和for_each。GIL通道算法使用静态递归展开循环;它们不会对通道进行显式循环。然而有时候现代的编译器(至少是Visual Studio 8)已经展开通道级的循环,和上面的一样。但是使用GIL通道级算法的另外一个好处是:它们在语义上把通道配对,而不是基于它们在内存的顺序。(注:即源和目标可能在通道顺序上不相同)例如, 上面的代码可以把RGB源正确匹配到BGR目标。

 

Here is how we can use our generic version with images of different types:

 

这里展示我们如何把我们的泛型版用在不同类型的图片上:(注:这里都是直接处理像素指针的)

 

// Calling with 16-bit grayscale data

// 调用16位灰度数据

void XGradientGray16_Gray32(const unsigned short* src_pixels, ptrdiff_t src_row_bytes, int w, int h,

                                  signed int* dst_pixels, ptrdiff_t dst_row_bytes) {

    gray16c_view_t src=interleaved_view(w,h,(const gray16_pixel_t*)src_pixels,src_row_bytes);

    gray32s_view_t dst=interleaved_view(w,h,(     gray32s_pixel_t*)dst_pixels,dst_row_bytes);

    x_gradient(src,dst);

}

 

// Calling with 8-bit RGB data into 16-bit BGR

// 调用8位RGB输出到16位BGR

void XGradientRGB8_BGR16(const unsigned char* src_pixels, ptrdiff_t src_row_bytes, int w, int h,

                                 signed short* dst_pixels, ptrdiff_t dst_row_bytes) {

    rgb8c_view_t  src = interleaved_view(w,h,(const rgb8_pixel_t*)src_pixels,src_row_bytes);

    rgb16s_view_t dst = interleaved_view(w,h,(    rgb16s_pixel_t*)dst_pixels,dst_row_bytes);

    x_gradient(src,dst);

}

 

// Either or both the source and the destination could be planar - the gradient code does not change

// 源和目标可以其中之一或都是平面的(注:“平面”指同一通道的像素在内存上是连续的) - 梯度代码没有改变

void XGradientPlanarRGB8_RGB32(

           const unsigned short* src_r, const unsigned short* src_g, const unsigned short* src_b, 

           ptrdiff_t src_row_bytes, int w, int h,

           signed int* dst_pixels, ptrdiff_t dst_row_bytes) {

    rgb16c_planar_view_t src=planar_rgb_view (w,h, src_r,src_g,src_b,         src_row_bytes);

    rgb32s_view_t        dst=interleaved_view(w,h,(rgb32s_pixel_t*)dst_pixels,dst_row_bytes);

    x_gradient(src,dst);

}

 

As these examples illustrate, both the source and the destination can be interleaved or planar, of any channel depth (assuming the destination channel is assignable to the source), and of any compatible color spaces.

 

正如这些例子所示,源和目标的图像类型可以是交错或平面的(注:同一通道的值在内存上不连续或连续),任意通道深度(假设目标类型对于源来说是可赋值的),和任意兼容的颜色空间。

 

GIL 2.1 can also natively represent images whose channels are not byte-aligned, such as 6-bit RGB222 image or a 1-bit Gray1 image. GIL algorithms apply to these images natively. See the design guide or sample files for more on using such images.

 

GIL 2.1还原生支持通道不是字节对齐的图片类型,诸如6位RGB222图片或1位的Gray1图片。GIL算法原生地使用这些图片。参考设计指南或实例文件获取使用这些图片的更多信息。

 

Image View Transformations

 

图片视图变换

 

One way to compute the y-gradient is to rotate the image by 90 degrees, compute the x-gradient and rotate the result back. Here is how to do this in GIL:

 

计算垂直梯度的一种方法是把图片旋转90度,计算水平梯度,然后再把结果旋转回来。这里展示怎样做到这一点的:

 

template <typename SrcView, typename DstView>

void y_gradient(const SrcView& src, const DstView& dst) {

    x_gradient(rotated90ccw_view(src), rotated90ccw_view(dst));

}

 

rotated90ccw_view takes an image view and returns an image view representing 90-degrees counter-clockwise rotation of its input. It is an example of a GIL view transformation function. GIL provides a variety of transformation functions that can perform any axis-aligned rotation, transpose the view, flip it vertically or horizontally, extract a rectangular subimage, perform color conversion, subsample view, etc. The view transformation functions are fast and shallow - they don't copy the pixels, they just change the "coordinate system" of accessing the pixels. rotated90cw_view, for example, returns a view whose horizontal iterators are the vertical iterators of the original view. The above code to compute y_gradient is slow because of the memory access pattern; using rotated90cw_view does not make it any slower.

 

rotated90ccw_view拿到图片视图并且返回一个逆时针旋转90度的图片视图。这是GIL视图变换函数的一个例子。GIL提供一些变换函数,可以执行任意坐标轴对齐的旋转,视图颠倒,垂直或水平翻转,截取矩形子视图,颜色变换,二次采样等等。视图变换函数快速而且浅层 - 它们不复制像素,它们只是改变访问像素的“坐标系统”。例如rotated90cw_view,返回一个视图,它的水平迭代器是原来视图的垂直迭代器。上面计算y_gradient的代码慢是因为内存访问模式;而使用rotated90cw_view不会让它变慢。(注:我很怀疑GIL特别针对横向遍历做算法上的优化,导致横向与纵向遍历的性能有差异)

 

Another example: suppose we want to compute the gradient of the N-th channel of a color image. Here is how to do that:

 

还有一个例子: 假如我们需要计算一个颜色图片的第N个通道的梯度。这里我们这样做:

 

template <typename SrcView, typename DstView>

void nth_channel_x_gradient(const SrcView& src, int n, const DstView& dst) {

    x_gradient(nth_channel_view(src, n), dst);

}

 

nth_channel_view is a view transformation function that takes any view and returns a single-channel (grayscale) view of its N-th channel. For interleaved RGB view, for example, the returned view is a step view - a view whose horizontal iterator skips over two channels when incremented. If applied on a planar RGB view, the returned type is a simple grayscale view whose horizontal iterator is a C pointer. Image view transformation functions can be piped together. For example, to compute the y gradient of the second channel of the even pixels in the view, use:

 

nth_channel_view是视图变换函数,取一个视图并返回它第N个通道的单通道(灰度)视图。例如对于RGB间隔视图来说,返回的结果就是一个步长视图——其水平迭代器在递增时跳过两个通道。如果应用在平面RGB视图上,返回类型是一个简单灰度视图,它的水平迭代器就是一个C指针(注:因为平面图片的相同通道像素在内存中是连续的)图片视图变换函数可以连起来(注:管道起来)使用。例如,为了计算视图中偶数像素的第二个通道的梯度值,使用这样的代码:

 

y_gradient(subsampled_view(nth_channel_view(src, 1), 2,2), dst);

 

GIL can sometimes simplify piped views. For example, two nested subsampled views (views that skip over pixels in X and in Y) can be represented as a single subsampled view whose step is the product of the steps of the two views.

 

GIL有时候可以简化管道视图。例如两个嵌套的二次采样视图(在X和Y方向上跳过一些像素的视图)可以表现为单一的二次采样视图,其步长是两个视图的步长乘积。(注:即采样率相乘)

 

1D pixel iterators

 

一维像素迭代器

 

Let's go back to x_gradient one more time. Many image view algorithms apply the same operation for each pixel and GIL provides an abstraction to handle them. However, our algorithm has an unusual access pattern, as it skips the first and the last column. It would be nice and instructional to see how we can rewrite it in canonical form. The way to do that in GIL is to write a version that works for every pixel, but apply it only on the subimage that excludes the first and last column:

 

让我们再次回到函数x_gradient。很多图片视图算法对每个像素应用相同的操作,GIL提供一种处理它们的抽象。然而,我们的算法有一个不寻常的访问模式,它跳过第一列和最后一列。我们可以以典型的方式重写,让它看起来美观和便于教学。在GIL中方法是写一个版本,可以工作于每个像素,但只把它应用在不包括第一列和最后列的子图片上:

 

void x_gradient_unguarded(const gray8c_view_t& src, const gray8s_view_t& dst) {

    for (int y=0; y<src.height(); ++y) {

        gray8c_view_t::x_iterator src_it = src.row_begin(y);

        gray8s_view_t::x_iterator dst_it = dst.row_begin(y);

 

        for (int x=0; x<src.width(); ++x)

            dst_it[x] = (src_it[x-1] - src_it[x+1]) / 2;

    }

}

 

void x_gradient(const gray8c_view_t& src, const gray8s_view_t& dst) {

    assert(src.width()>=2);

    x_gradient_unguarded(subimage_view(src, 1, 0, src.width()-2, src.height()),

                         subimage_view(dst, 1, 0, src.width()-2, src.height()));

}

 

subimage_view is another example of a GIL view transformation function. It takes a source view and a rectangular region (in this case, defined as x_min,y_min,width,height) and returns a view operating on that region of the source view. The above implementation has no measurable performance degradation from the version that operates on the original views.

 

subimage_view是GIL视图变换的另一个例子。它取一个图像视图和一个矩形区域(这个情形下被定义为x_min,y_min,width,height),而返回一个截取源视图那个区域的视图。上面的实现与操作在原始视图相比,没有可度量的性能下降。

 

Now that x_gradient_unguarded operates on every pixel, we can rewrite it more compactly:

 

现在x_gradient_unguarded操作于每个像素,我们可以更兼容地重写它:

 

void x_gradient_unguarded(const gray8c_view_t& src, const gray8s_view_t& dst) {

    gray8c_view_t::iterator src_it = src.begin();

    for (gray8s_view_t::iterator dst_it = dst.begin(); dst_it!=dst.end(); ++dst_it, ++src_it)

        *dst_it = (src_it.x()[-1] - src_it.x()[1]) / 2;

}

 

 

GIL image views provide begin() and end() methods that return one dimensional pixel iterators which iterate over each pixel in the view, left to right and top to bottom. They do a proper "carriage return" - they skip any unused bytes at the end of a row. As such, they are slightly suboptimal, because they need to keep track of their current position with respect to the end of the row. Their increment operator performs one extra check (are we at the end of the row?), a check that is avoided if two nested loops are used instead. These iterators have a method x() which returns the more lightweight horizontal iterator that we used previously. Horizontal iterators have no notion of the end of rows. In this case, the horizontal iterators are raw C pointers. In our example, we must use the horizontal iterators to access the two neighbors properly, since they could reside outside the image view.

 

GIL图片视图提供了begin()和end()方法返回一维像素迭代器,迭代视图的每个像素,从左到右,从上而下。它们会做正确的“回车”——它们跳过行结束处任何无用的字节。所以,它们有点不够优化,因为它们需要针对行尾来同步它们的当前位置。它们的递增操作执行一个额外的检查(我是否在行尾?),如果改为使用两个循环可以避免这个检查。这些迭代器有一个方法x()返回更加轻量级的水平迭代器,我们之前已经使用过。水平迭代器没有行尾的概念。在这种情况下,水平迭代器就是一个裸C指针。在我们的例子里,我们必须使用水平迭代器正确地访问两个邻接像素,因为它们可能位于图片视图外面。

 

STL Equivalent Algorithms

 

STL等价算法

 

GIL provides STL equivalents of many algorithms. For example, std::transform is an STL algorithm that sets each element in a destination range the result of a generic function taking the corresponding element of the source range. In our example, we want to assign to each destination pixel the value of the half-difference of the horizontal neighbors of the corresponding source pixel. If we abstract that operation in a function object, we can use GIL's transform_pixel_positions to do that:

 

GIL提供很多算法的STL等价物。例如,std::transform是一个STL算法,把目标区间每个元素的值设置为一个以源区间相应元素为参数的泛型函数(注:一般是函数对象)的结果。在我们的例子里,我们想把相应源像素的水平邻接像素的半差分赋给每个目标像素。

如果我们把那个操作抽象成函数对象,我们可以使用GIL的transform_pixel_positions做到那一点:

 

struct half_x_difference {

    int operator()(const gray8c_loc_t& src_loc) const {

        return (src_loc.x()[-1] - src_loc.x()[1]) / 2;

    }

};

 

void x_gradient_unguarded(const gray8c_view_t& src, const gray8s_view_t& dst) {

    transform_pixel_positions(src, dst, half_x_difference());

}

 

GIL provides the algorithms for_each_pixel and transform_pixels which are image view equivalents of STL's std::for_each and std::transform. It also provides for_each_pixel_position and transform_pixel_positions, which instead of references to pixels, pass to the generic function pixel locators. This allows for more powerful functions that can use the pixel neighbors through the passed locators. GIL algorithms iterate through the pixels using the more efficient two nested loops (as opposed to the single loop using 1-D iterators)

 

GIL提供算法for_each_pixel和transform_pixels,它们分别是STL的std::for_each和std::transform的等价物。还提供了for_each_pixel_position和transform_pixel_positions,不是像素的引用,而是向泛型函数传递像素定位器。这允许更强大的函数,可以通过传进来的定位器使用相邻的像素。GIL算法使用更高效的双层嵌套循环来迭代像素(而非使用一维迭代器的单循环)。

 

Color Conversion

 

颜色变换

 

Instead of computing the gradient of each color plane of an image, we often want to compute the gradient of the luminosity. In other words, we want to convert the color image to grayscale and compute the gradient of the result. Here how to compute the luminosity gradient of a 32-bit float RGB image:

 

我们常常不是计算图片每个颜色平面的梯度,而是想计算亮度的梯度。换句话说,我们想把有色图片转换成灰度图,并且计算结果的梯度。这里展示我们如何计算32位RGB图片的亮度梯度:

 

void x_gradient_rgb_luminosity(const rgb32fc_view_t& src, const gray8s_view_t& dst) {

    x_gradient(color_converted_view<gray8_pixel_t>(src), dst);

}

 

color_converted_view is a GIL view transformation function that takes any image view and returns a view in a target color space and channel depth (specified as template parameters). In our example, it constructs an 8-bit integer grayscale view over 32-bit float RGB pixels. Like all other view transformation functions, color_converted_view is very fast and shallow. It doesn't copy the data or perform any color conversion. Instead it returns a view that performs color conversion every time its pixels are accessed.

 

color_converted_view是一个GIL视图变换函数,取任意的图像视图类型,返回一个目标颜色空间和通道深度的视图(由模板参数指定)。在我们的例子里,它在32位浮点RGB像素上构造出一个8位整型灰度视图。好像所有其他视图变换函数那样,color_converted_view非常快而且轻型。它不会复制数据或执行任何颜色转换。取而代之,它返回一个视图,每次它的像素被访问时才执行颜色变换。

 

In the generic version of this algorithm we might like to convert the color space to grayscale, but keep the channel depth the same. We do that by constructing the type of a GIL grayscale pixel with the same channel as the source, and color convert to that pixel type:

 

在这个算法的泛型版本中,我们可能喜欢把颜色空间转换为灰度,但保持相同的通道深度。我们通过构造出一种类型,以相同通道(注:这里指8位通道深度?)的GIL灰度像素类型作为源,以及转换成那个像素类型的颜色为模板参数,来做到那一点:

 

template <typename SrcView, typename DstView>

void x_luminosity_gradient(const SrcView& src, const DstView& dst) {

    typedef pixel<typename channel_type<SrcView>::type, gray_layout_t> gray_pixel_t;

    x_gradient(color_converted_view<gray_pixel_t>(src), dst);

}

 

 

When the destination color space and channel type happens to be the same as the source one, color conversion is unnecessary. GIL detects this case and avoids calling the color conversion code at all - i.e. color_converted_view returns back the source view unchanged.

 

如果目标颜色空间与通道类型和源视图的相同,颜色变换就没有必要。GIL能够检测到这一情况并且完全避免颜色变换的代码——即color_converted_view返回不变的源视图。

 

Image

 

图片

 

The above example has a performance problem - x_gradient dereferences most source pixels twice, which will cause the above code to perform color conversion twice. Sometimes it may be more efficient to copy the color converted image into a temporary buffer and use it to compute the gradient - that way color conversion is invoked once per pixel. Using our non-generic version we can do it like this:

 

上面例子有一个性能问题——函数x_gradient对大多数像素取值(注:解除引用,指*运算符操作)两次,将导致上面的代码执行两次变换。有时候更高效的做法是,复制颜色转换后的图片到一个临时缓冲区,并且使用它计算梯度值——这种方式每个像素的颜色转换只调用一次。使用我们的非泛型版本,我们可以像这样子做到这一点:

 

void x_luminosity_gradient(const rgb32fc_view_t& src, const gray8s_view_t& dst) {

    gray8_image_t ccv_image(src.dimensions());

    copy_pixels(color_converted_view<gray8_pixel_t>(src), view(ccv_image));

 

    x_gradient(const_view(ccv_image), dst);

}

 

First we construct an 8-bit grayscale image with the same dimensions as our source. Then we copy a color-converted view of the source into the temporary image. Finally we use a read-only view of the temporary image in our x_gradient algorithm. As the example shows, GIL provides global functions view and const_view that take an image and return a mutable or an immutable view of its pixels.

 

首先我们构造一个具有相同尺寸的8位灰度图像作为源。然后我们把颜色转换后的视图复制进临时缓冲区。最后我们在x_gradient算法中使用临时图片的只读视图。正如例子所示,GIL提供全局函数view和const_view取一个图片并返回一个像素可变和像素不可变的视图。

 

 

Creating a generic version of the above is a bit trickier:

 

创建上面算法的泛型版本有点棘手:

 

template <typename SrcView, typename DstView>

void x_luminosity_gradient(const SrcView& src, const DstView& dst) {

    typedef typename channel_type<DstView>::type d_channel_t;

    typedef typename channel_convert_to_unsigned<d_channel_t>::type channel_t;

    typedef pixel<channel_t, gray_layout_t>  gray_pixel_t;

    typedef image<gray_pixel_t, false>       gray_image_t;

 

    gray_image_t ccv_image(src.dimensions());

    copy_pixels(color_converted_view<gray_pixel_t>(src), view(ccv_image));

    x_gradient(const_view(ccv_image), dst);

}

 

 

First we use the channel_type metafunction to get the channel type of the destination view. A metafunction is a function operating on types. In GIL metafunctions are structs which take their parameters as template parameters and return their result in a nested typedef called type. In this case, channel_type is a unary metafunction which in this example is called with the type of an image view and returns the type of the channel associated with that image view.

 

首先我们使用元函数(注:下面会解释,即编译期从一种类型得到其相关类型的模板)channel_type得到目标视图的通道类型。元函数是指操作在类型上的函数。在GIL中,元函数是一些结构体,以它们的参数作为模板参数,并且在typedef的嵌套下,调用type的中返回结构,返回它们的结果。在这种情况下,channel_type是一个一元元函数,在这个例子中,使用图片视图来调用且返回与图片视图相关的通道类型。

 

GIL constructs that have an associated pixel type, such as pixels, pixel iterators, locators, views and images, all model PixelBasedConcept, which means that they provide a set of metafunctions to query the pixel properties, such as channel_type, color_space_type, channel_mapping_type, and num_channels.

 

GIL可以对这些东西构造一个相关像素类型,诸如像素, 像素迭代器,定位器,视图和图片,所有建模自PixelBasedConcept的东西,这意味它们都提供了一系列元函数用于查询像素属性,诸如channel_type,color_space_type,channel_mapping_type,和num_channels.

 

After we get the channel type of the destination view, we use another metafunction to remove its sign (if it is a signed integral type) and then use it to generate the type of a grayscale pixel. From the pixel type we create the image type. GIL's image class is templated over the pixel type and a boolean indicating whether the image should be planar or interleaved. Single-channel (grayscale) images in GIL must always be interleaved. There are multiple ways of constructing types in GIL. Instead of instantiating the classes directly we could have used type factory metafunctions. The following code is equivalent:

 

在我们得到目标视图的通道类型后,我们使用另一个元函数删除它的符号(如果它是一个有符号整数类型)然后用它生成灰度像素的类型。从像素类型中我们创建出图片类型。GIL的图片类是以像素类型和一个指示图片是平面还是间隔的布尔值为参数的模板生成的。GIL的单通道(灰度)图片必须总是间隔的。GIL中有多种构建类型的方式。不需要直接实例化类,我们可以使用类工厂元函数。下面的代码是等价物:

 

template <typename SrcView, typename DstView>

void x_luminosity_gradient(const SrcView& src, const DstView& dst) {

    typedef typename channel_type<DstView>::type d_channel_t;

    typedef typename channel_convert_to_unsigned<d_channel_t>::type channel_t;

    typedef typename image_type<channel_t, gray_layout_t>::type gray_image_t;

    typedef typename gray_image_t::value_type gray_pixel_t;

 

    gray_image_t ccv_image(src.dimensions());

    copy_and_convert_pixels(src, view(ccv_image));

    x_gradient(const_view(ccv_image), dst);

}

 

 

GIL provides a set of metafunctions that generate GIL types - image_type is one such meta-function that constructs the type of an image from a given channel type, color layout, and planar/interleaved option (the default is interleaved). There are also similar meta-functions to construct the types of pixel references, iterators, locators and image views. GIL also has metafunctions derived_pixel_reference_type, derived_iterator_type, derived_view_type and derived_image_type that construct the type of a GIL construct from a given source one by changing one or more properties of the type and keeping the rest.

 

GIL 提供了一组元函数来生成GIL类型——image_type是这样的元函数之一,它通过给定的通道类型,颜色布局,平面/间隔选项(默认的是间隔)来构造图片类型。还有一些类似的元函数,用来构造像素引用、迭代器、定位器和图片视图。GIL还有元函数derived_pixel_reference_type,derived_iterator_type,derived_view_type和derived_image_type,通过改变一个或多个类型属性而且保持其它属性不变,而从一个给定的源中构建出GIL类型。

 

From the image type we can use the nested typedef value_type to obtain the type of a pixel. GIL images, image views and locators have nested typedefs value_type and reference to obtain the type of the pixel and a reference to the pixel. If you have a pixel iterator, you can get these types from its iterator_traits. Note also the algorithm copy_and_convert_pixels, which is an abbreviated version of copy_pixels with a color converted source view.

 

从图片类型中我们可以使用套有typedef的value_type获得像素类型。GIL图片、图片视图和定位器有套有typedefs的value_type和reference以获得像素和像素引用的类型。如果你有一个像素迭代器,你可以从它的iterator_traits中得到这些类型。还要注意算法copy_and_convert_pixels,它是带有颜色转换后的源视图的copy_pixels的简略版本。

 

Virtual Image Views

 

虚拟图片视图

 

So far we have been dealing with images that have pixels stored in memory. GIL allows you to create an image view of an arbitrary image, including a synthetic function. To demonstrate this, let us create a view of the Mandelbrot set. First, we need to create a function object that computes the value of the Mandelbrot set at a given location (x,y) in the image:

 

到现在为止,我们已经能够处理像素存储在内存的图片。GIL允许你创建任意图片的图片视图,包括一个合成函数(注:即非自然,人造的图片)。为了演示这一点,让我们创建一个曼德博集合的视图。首先,我们需要创建一个函数对象,以在图片中给定位置(x,y)处计算曼德博集合的值:

 

// models PixelDereferenceAdaptorConcept

// 建模自PixelDereferenceAdaptorConcept

struct mandelbrot_fn {

    typedef point2<ptrdiff_t>   point_t;

 

    typedef mandelbrot_fn       const_t;

    typedef gray8_pixel_t       value_type;

    typedef value_type          reference;

    typedef value_type          const_reference;

    typedef point_t             argument_type;

    typedef reference           result_type;

    BOOST_STATIC_CONSTANT(bool, is_mutable=false);

 

    mandelbrot_fn() {}

    mandelbrot_fn(const point_t& sz) : _img_size(sz) {}

 

    result_type operator()(const point_t& p) const {

        // normalize the coords to (-2..1, -1.5..1.5)

        // 把坐标值归一化到区间 (-2..1, -1.5..1.5)

        double t=get_num_iter(point2<double>(p.x/(double)_img_size.x*3-2, p.y/(double)_img_size.y*3-1.5f));

        return value_type((bits8)(pow(t,0.2)*255));   // raise to power suitable for viewing // 提升到适合视图的幂

    }

private:

    point_t _img_size;

 

    double get_num_iter(const point2<double>& p) const {

        point2<double> Z(0,0);

        for (int i=0; i<100; ++i) {     // 100 iterations // 100次迭代

            Z = point2<double>(Z.x*Z.x - Z.y*Z.y + p.x, 2*Z.x*Z.y + p.y);

            if (Z.x*Z.x + Z.y*Z.y > 4)

                return i/(double)100;

        }

        return 0;

    }

};

 

 

We can now use GIL's virtual_2d_locator with this function object to construct a Mandelbrot view of size 200x200 pixels:

 

现在我们可以使用GIL的virtual_2d_locator传入这个函数对象以创建一个大小为200x200像素的曼德博视图:

 

typedef mandelbrot_fn::point_t point_t;

typedef virtual_2d_locator<mandelbrot_fn,false> locator_t;

typedef image_view<locator_t> my_virt_view_t;

 

point_t dims(200,200);

 

// Construct a Mandelbrot view with a locator, taking top-left corner (0,0) and step (1,1)

// 用一个定位器构造一个曼德博视图,左上角为(0,0),步长为(1,1)

my_virt_view_t mandel(dims, locator_t(point_t(0,0), point_t(1,1), mandelbrot_fn(dims)));

 

 

We can treat the synthetic view just like a real one. For example, let's invoke our x_gradient algorithm to compute the gradient of the 90-degree rotated view of the Mandelbrot set and save the original and the result:

 

我们可以把合成视图看成一个真实视图。例如,让我们调用我们的x_gradient算法去计算曼德博集旋转90视图的梯度,并且保存原始和结果视图:

 

gray8s_image_t img(dims);

x_gradient(rotated90cw_view(mandel), view(img));

 

// Save the Mandelbrot set and its 90-degree rotated gradient (jpeg cannot save signed char; must convert to unsigned char)

// 保存曼德博集和它的90度旋转梯度(jpeg不能保存signed char,必须转换为unsigned char)

jpeg_write_view("mandel.jpg",mandel);

jpeg_write_view("mandel_grad.jpg",color_converted_view<gray8_pixel_t>(const_view(img)));

 

Here is what the two files look like:

 

这是两个文件看起来的样子:

 

(图略)

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics