具有通用uint8_t数据存储的图像类的迭代器

Iterator ofor an image class with generic uint8_t data storage

本文关键字:存储 图像 迭代器 数据 uint8      更新时间:2023-10-16

我有一个图像类,可以存储任何深度+任何精度(例如浮点、双精度)像素。我的数据存储在std::vector<uint8_t> m_pixels;中,然后我根据通道数量和精度(以字节为单位)对像素进行迭代,即,为了迭代RGB浮动图像中的所有像素,我会进行

float *ptr = reinterpret_cast<float*>(m_pixels.data());
int stride = numberOfChannels * sizeof(float);
for (int i = 0; i < imgWidth * imgHeight; i += stride) { 
  float& pixel = ptr[i];
  ...
}

(请暂时忘记优化代码,例如移动指针等)

现在我正在尝试创建一个基于std::iterator的迭代器。让我们称之为ImageIter。我有以下问题:如果我把它作为一个模板迭代器,我总是会有这样的代码:

if (image.isFloat()) {
  ImageIter<float> i(image);
  ...
} else if (image.isDouble()) {
  ImageIter<double> i(image);
  ...
}

有没有什么方法可以摆脱这种情况,或者让它成为一个更简单的代码,而不必总是依赖于在图像上迭代的链式if?

假设Image是一个模板结构,其中定义了迭代器类型:

template <class T>
struct Image {
    using iterator = ImageIter<T>;
    //...
};

如果你想让迭代变得通用,只需使用模板化的函数,比如:

template <class T>
void iterateIf(const Image<T> &cit) {
    // do sth if image has T
}
int main() {
    Image<float> floatImage;
    iterateIf(floatImage); //will execute templated iterateIf
}

现在,如果你想在图像包含替身时进行一些特定的操作,只需创建iterateIf重载:

template <class T>
void iterateIf(const Image<T> &cit) {
    // do sth if image has Ts
}
void iterateIf(const Image<double> &cid) {
    // do something only when parameter is Image<double>
}
int main() {
    Image<float> floatImage;
    iterateIf(floatImage); //will execute templated iterateIf
    Image<double> doubleImage;
    iterateIf(doubleImage); //will execute iterateIf(const Image<double>&)
}

我建议不要采用那种设计。

违反了最小惊奇原则:您的图像类是元素类型不可知的,但迭代器不是。

你有各种选择,都有缺点:

1.使图像类本身成为模板

并提供它们之间的分配/转换

template <typename TElement> class Image 
{
  public:
   ...
   template <typename TOtherElement>
   Image(Image<TotherElement const & rhs);
   ...
};

这会将if (image.is(...))向上移动一级。不一定更好,但(以一种好的方式)不那么令人惊讶。

您可以在所有这些之上实现用于图像操作的共享接口:

class IImageManipulation
{
  public:
    virtual void FlipH() = 0;
    virtual void FlipV() = 0;
    virtual void Rotate(float angleDegrees) = 0;
    ...
    virtual void ~IImageManipulation() {}
};
template <typename TElement>
class Image : public IImageManipulation
{
   // ... also implement the abstract methods
}

[edit]re"
我不能将其作为模板图像类,因为我需要从任何深度和任何像素数的文件中导入图像"
:

unique_ptr<IImageManipulation> LoadImage(....)
{
   // inspect stream what depth and pixel format you use
   ..
   // create instance
   unique_ptr<IImageManipulation> img = CreateImage(pixeltype);
   // ... and load
   img->Load(file);
}
// Image factory function:
unique_ptr<IImgManipulation> CreateImage(PixelType type)
{
   switch (pixeltype) 
   {  
     case ptFloat: return unique_ptr<IImageManipulation>(new Image<float>()); 
     ...
   }
}

您的容器将存储unique_ptr<IImageManipulation>

这个想法是将所有通用的图像操作移到界面上。这对于需要在Image模板中或使用该模板实现的像素级操作来说是不好的。然而,对于图像级别的操作来说,这是很好的。

2.使迭代器类型不可知

即迭代器不是模板化的,而是在可以处理所有不同像素格式的"Pixel"类型上进行迭代。

理想情况下,迭代器将实现像素格式转换:

Image imgf = MakeImage<float>(...);  // an image storing floats
for(Image::iterator it = imgf.begin(); ...) 
{
   Pixel & p = *it;  // type of the iterator elements
   float f = p.asFloat();  // read as float
   int i = p.asInt32();    // read as int, convert in the fly
   p = Pixel.FromRGB(17, 23, 432); // set as int, convert on the fly
   p = Pixel.FromFloat(0.23); 
}

请注意,转换可以是隐式的,但如果您有不同的像素格式和不同的基本类型(例如32位RGBA与ABGR),这可能会成为一个问题

在内部循环中使这种转换高效可能并非易事。