Как элегантно извлечь 2D прямоугольную область из вектора C ++ - PullRequest
1 голос
/ 23 апреля 2009

Проблема довольно простая. (Я озадачен, почему поиск ничего не нашел)

У меня есть прямоугольная «картинка», в которой хранится цветовая линия пикселя. после строки в std :: vector

Я хочу скопировать прямоугольную область из этого изображения.

Как бы я элегантно закодировал это в c ++?

Моя первая попытка:

 template <class  T>    std::vector<T> copyRectFromVector(const std::vector<T>& vec, std::size_t startx,  std::size_t starty, std::size_t endx, std::size_t endy, std::size_t fieldWidth, std::size_t fieldHeight)
    {
     using namespace std;
    vector<T> ret((endx-startx)*(endy-starty)+10);  // 10: chickenfactor

    // checks if the given parameters make sense:
    if (vec.size() < fieldWidth*endy)
    {
        cerr << "Error: CopyRectFromVector: vector to small to contain rectangular region!" << std::endl;
        return ret;
    }

    // do the copying line by line:
    vector<T>::const_iterator vecIt = vec.begin();
    vector<T>::forward_iterator retIt = ret.end();

    vecIt += startx + (starty*fieldWidth);
     for(int i=starty; i < endy; ++i)
    {
            std::copy(vecIt, vecIt + endx - startx, retIt);
        }
        return ret;
}

даже не компилируется .....

Addit: Пояснение: Я знаю, как это сделать "от руки". Это не проблема как таковая. Но мне бы хотелось немного магии итераторов c ++ stl, которая делает то же самое, но быстрее и ... более стильно в c ++.

Дополнение: я даю алгоритму pictureDataVector, ширину и высоту картинки и прямоугольник, обозначающий область, которую я хочу скопировать из картинки. Возвращаемое значение должно быть новым вектором с содержимым прямоугольника.

Думайте об этом как об открытии вашего любимого графического редактора и скопируйте из него прямоугольную область. Изображение хранится в виде длинного 1D-массива (вектора) пикселей.

Ответы [ 4 ]

3 голосов
/ 24 апреля 2009

По сути, та же идея, за исключением того, что она компилируется и немного более итеративна:

#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>

template <typename I, typename O> 
void copyRectFromBiggerRect(
    I input,
    O output,
    std::size_t startx,  
    std::size_t cols, 
    std::size_t starty, 
    std::size_t rows, 
    std::size_t stride
) {
    std::advance(input, starty*stride + startx);
    while(rows--) {
        std::copy(input, input+cols, output);
        std::advance(input, stride);
    }
}

template<typename T>
std::vector<T> copyRectFromVector (
    const std::vector<T> &vec, 
    std::size_t startx,  
    std::size_t starty, 
    std::size_t endx, 
    std::size_t endy, 
    std::size_t stride
) {
    // parameter-checking omitted: you could also check endx > startx etc.

    const std::size_t cols = endx - startx;
    const std::size_t rows = endy - starty;

    std::vector<T> ret;
    ret.reserve(rows*cols);
    std::back_insert_iterator<std::vector<T> > output(ret);

    typename std::vector<T>::const_iterator input = vec.begin();
    copyRectFromBiggerRect(input,output,startx,cols,starty,rows,stride);
    return ret;
}

int main() {
    std::vector<int> v(20);
    for (int i = 0; i < 20; ++i) v[i] = i;
    std::vector<int> v2 = copyRectFromVector(v, 0, 0, 1, 2, 4);
    std::copy(v2.begin(), v2.end(), std::ostream_iterator<int>(std::cout, "\n"));
}

Я бы не ожидал, что это будет быстрее, чем копирование двух циклов по индексу. Возможно, даже медленнее, хотя в основном это гонка между издержками vector :: push_back и усилением std :: copy по циклу.

Однако это может быть более гибким, если ваш другой код шаблона предназначен для работы с итераторами в целом, а не с вектором в качестве отдельного контейнера. copyRectFromBiggerRect может использовать массив, деку или даже список в качестве входных данных так же легко, как вектор, хотя в настоящее время он не оптимален для итераторов, которые не имеют произвольного доступа, поскольку в настоящее время он перемещается по каждой скопированной строке дважды.

Для других способов сделать это более похожим на другой код C ++, рассмотрите boost :: multi_array для многомерных массивов (в этом случае реализация в любом случае будет полностью отличаться от этого), и избегайте возврата коллекций, таких как vector by value ( во-первых, это может быть неэффективно, если вы не получите оптимизацию возвращаемого значения, и, во-вторых, так, чтобы контроль над тем, какие ресурсы были выделены, оставался на максимально возможном уровне).

3 голосов
/ 23 апреля 2009
for (int r = startRow; r < endRow; r++)
    for (int c = startCol; c < endCol; c++)
        rect[r-startRow][c-startCol] = source[r*rowWidth+c];
2 голосов
/ 24 апреля 2009

Ваш вопрос требует C ++ способа копирования прямоугольного поля элементов в некотором контейнере. У вас есть достаточно близкий пример, и вы получите больше ответов. Давайте обобщим, однако:

Вам нужен итератор, который перемещает прямоугольный диапазон элементов по некоторому диапазону элементов. Итак, как насчет написать своего рода адаптер, который находится в любом контейнере и предоставляет этот специальный итератор.

Я собираюсь пойти широкими штрихами с кодом здесь:

vector<pixels> my_picture;
point selTopLeft(10,10), selBotRight(40, 50);
int picWidth(640), picHeight(480);
rectangular_selection<vector<pixels> > selection1(my_picture.begin(),
  my_picture.end(), picWidth, picHeight, selTopLeft, selBotRight);

// Now you can use stl algorithms on your rectangular range
vector<pixels> rect_copy = std::copy(selection1.begin(), selection1.end());
// or maybe you don't want to copy, you want 
// to modify the selection in place
std::for_each (selection1.begin(), selection1.end(), invert_color);

Я уверен, что это вполне выполнимо, но мне неудобно кодировать шаблоны в стиле stl. Если у меня есть время, и вам интересно, я могу позже отредактировать черновик, поскольку это интересная концепция.

См. ТАК ответ на вопрос для вдохновения.

2 голосов
/ 24 апреля 2009

Хороший код C ++ должен сначала быть легким для чтения и понимания (как любой код), объектно-ориентированным (как любой код в объектно-ориентированном языке), а затем должен использовать языковые средства для упрощения реализации.

Я бы не стал беспокоиться об использовании алгоритмов STL для того, чтобы он выглядел более C ++, лучше было бы начать упрощать юзабилити (интерфейс) объектно-ориентированным способом. Не используйте простые векторы для представления ваших изображений. Укажите уровень abstraction : создайте класс, представляющий изображение, и предоставьте необходимую вам функциональность. Это улучшит удобство использования за счет инкапсуляции деталей от обычного использования (объект 2D-области может знать его размеры, пользователю не нужно передавать их в качестве аргументов). И это сделает код более надежным , так как пользователь может совершать меньше ошибок.

Даже если вы используете контейнеры STL, всегда сначала учитывайте удобочитаемость . Если его проще реализовать с помощью регулярного цикла for и его будет труднее читать с помощью алгоритмов STL, забудьте о них: сделайте свой код простым и понятным.

Это должно быть вашим фокусом: создание лучшего, более простого и удобочитаемого кода. Используйте языковые функции для улучшения вашего кода, а не код для тренировки или демонстрации возможностей языка. Он окупится, если вам понадобится сохранить этот код через два месяца.

Примечание. Использование большего количества STL не сделает ваш код более идиоматичным в C ++, и я считаю, что это один из таких случаев. Злоупотребление STL может сделать код на самом деле хуже.

...