Как транспонировать растровое изображение на месте в C++

Я пытаюсь создать функцию для транспонирования на месте растрового изображения. Но пока что результат, который я получаю, весь перепутан, и я не могу найти, что я делаю не так.

Исходные растровые изображения представляют собой массив пикселей размером 1d в формате ARGB.

void transpose(uint8_t* buffer, const uint32_t width, const uint32_t height)
{
    const size_t stride = width * sizeof(uint32_t);

    for (uint32_t i = 0; i < height; i++)
    {
        uint32_t* row = (uint32_t*)(buffer + (stride * i));
        uint8_t* section = buffer + (i * sizeof(uint32_t));

        for (uint32_t j = i + 1; j < height; j++)
        {
            const uint32_t tmp = row[j];
            row[j] = *((uint32_t*)(section + (stride * j)));
            *((uint32_t*)(section + (stride * j))) = tmp;
        }
    }
}

введите здесь описание изображения

ОБНОВЛЕНИЕ:

Чтобы уточнить и избежать путаницы, поскольку некоторые люди думают, что это просто вопрос о повороте изображения. Транспонирование изображения состоит из двух преобразований: 1) перевернуть по горизонтали 2) повернуть на 90° против часовой стрелки. (Как показано в примере изображения, см. направления стрелок)


person PerracoLabs    schedule 16.10.2018    source источник
comment
в вашем примере есть height != widht, поэтому не совсем понятно, что вы имеете в виду под "на месте"   -  person 463035818_is_not_a_number    schedule 16.10.2018
comment
Под «на месте» я подразумеваю отсутствие копирования результата в другое растровое изображение и выделение временного рабочего буфера с теми же размерами, что и у источника. Итак, для преобразования непосредственно входного растрового буфера.   -  person PerracoLabs    schedule 16.10.2018
comment
было бы лучше выделить вычисление адреса для каждого пикселя в небольшую встроенную функцию, чтобы уменьшить объем выполняемой математики.   -  person    schedule 16.10.2018
comment
Я беру свой предыдущий комментарий о шаге назад, вы меня смутили, переходя туда и обратно между uint32_t* и uint8_t*   -  person    schedule 16.10.2018
comment
теперь он правильно работает для квадратных растровых изображений?   -  person    schedule 16.10.2018
comment
Нет, потому что реализация, которую я делаю, не имеет ограничений по соотношению сторон. Таким образом, квадрат растрового изображения или нет, не будет иметь никакого значения.   -  person PerracoLabs    schedule 16.10.2018
comment
Я бы предложил а) сделать однократное приведение к указателю uint32_t - это значительно упростит ваш код. б) найдите и прочитайте Как отлаживать небольшие программы Эрика Липперта в) пошаговый алгоритм для простого растрового изображения 2x3 - либо в отладчике, либо на бумаге.   -  person Martin Bonner supports Monica    schedule 16.10.2018
comment
Может быть, нам нужен какой-то дополнительный контекст, есть ли у растрового буфера какие-то собственные размеры? Или он соответствует размерам растрового изображения? После перелистывания содержимого вы оставляете ширину и высоту как были или тоже меняете местами?   -  person    schedule 16.10.2018
comment
@jakub_d, Размеры буфера указаны в параметрах функции, ширина и высота, и они совпадают с ним. Когда преобразование завершено, вызывающая функция знает, что их нужно поменять местами. Как вы можете видеть на примере изображения, в результате правильно поменялись местами размеры.   -  person PerracoLabs    schedule 16.10.2018
comment
Итак... на выходе высота и ширина поменялись местами, поэтому вычисление адреса для пикселя [i][j] отличается для ввода и вывода?   -  person    schedule 16.10.2018
comment
К изображению никогда не обращаются как к массиву 2d (пиксель [i] [j]), вход всегда представляет собой массив пикселей 1d.   -  person PerracoLabs    schedule 16.10.2018
comment
если вы представляете себе доступ к нему как 2d, как мой пример с адресом   -  person    schedule 16.10.2018
comment
comment
@ Алан не повторяется. Транспонирование - это зеркальное отражение по горизонтали + поворот на 90 против часовой стрелки, предоставленная ссылка не дает решения.   -  person PerracoLabs    schedule 16.10.2018
comment
В принятом ответе упоминается транспонирование, а затем зеркало, ссылка на статью в Википедии дает вам требуемый алгоритм: ="nofollow noreferrer">en.wikipedia.org/wiki/   -  person Alan Birtles    schedule 16.10.2018
comment
@Алан. Спасибо за ссылки. Просто поясню, что транспонирование — это преобразование, производимое как зеркалом, так и вращением. Мой вопрос не о том, как повернуть изображение, а о том, чтобы выполнить транспонирование на месте, которое подразумевает оба действия, а не только вращение. Но спасибо за ссылки, посмотрю.   -  person PerracoLabs    schedule 16.10.2018


Ответы (3)


Я думаю, что проблема более сложна, чем вы думаете, и это не просто случай замены пикселей в x, y на пиксели в y, x. Если вы рассматриваете изображение размером 3 * 7 пикселей, в котором я пометил пиксели a-u:

abcdefg
hijklmn
opqrstu

Вращение этого изображения дает:

aho
bip
cjq
dkr
els
fmt
gnu

Превращение обоих изображений в одномерный массив дает:

abcdefghijklmnopqrstu

ahobipcjqdkrelsfmtgnu

Обратите внимание, что b переместилось на позицию d, но было заменено на h.

Переосмыслите свой алгоритм, нарисуйте его для небольшого изображения и убедитесь, что он работает, прежде чем пытаться его реализовать.

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

person Alan Birtles    schedule 16.10.2018
comment
это также не просто замена элемента в x,y элементом в какой-то другой позиции a,b. На самом деле я не знал, что транспонировать матрицу так сложно - person 463035818_is_not_a_number; 16.10.2018

В основном эквивалентный код, который должно быть легче отлаживать:

inline uint32_t * addr(uint8_t* buffer, const uint32_t width, uint32_t i, uint32_t j) {
    uint32_t * tmp = buffer;
    return tmp+i*width+j;
}

void transpose(uint8_t* buffer, const uint32_t width, const uint32_t height) {
    for (uint32_t i = 0; i < min(width,height); i++) {
        for (uint32_t j = 0; j < i; j++) {
            uint32_t * a = addr(buffer, width, i, j);
            uint32_t * b = addr(buffer, width, j, i);
            const uint32_t tmp = *a;
            *a = *b;
            *b = tmp;
        }
    }
}

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

person Community    schedule 16.10.2018
comment
кажется, из комментариев, что OP пытается одновременно изменить размеры растрового изображения, так что это не сработает - person ; 16.10.2018
comment
Алгоритм не пытается изменить размеры. Это массив 1d пикселей. Транспонирование изображения: 1) перевернуть по горизонтали, 2) повернуть на 90° против часовой стрелки. Так как вход представляет собой массив из 1d пикселей, вызывающие функции знают, что после транспонирования и ширина, и высота должны рассматриваться как переставленные из-за поворота. - person PerracoLabs; 16.10.2018
comment
Если вы возьмете результат, рассмотрите вертикальную линию с использованием адресации высота * ширина, какие координаты соответствуют этим адресам памяти в источнике с использованием перевернутой адресации ширина * высота? Вы получаете угловую линию, а не вертикальную. Это затруднит определение того, что должно быть заменено на что. - person ; 16.10.2018

Обратите внимание, что перенос матрицы на место не является тривиальным, когда N!=M. Подробнее см., например, здесь.

Причина в том, что при N=M можно просто перебрать половину матрицы и поменять местами элементы. Когда N!=M это не так.

Для иллюстрации рассмотрим более простой случай:

Сначала 2D-просмотр 1D-данных:

struct my2dview {
    std::vector<int>& data;
    int width,height;
    my2dview(std::vector<int>& data,int width,int height):data(data),width(width),height(height){}
    int operator()(int x,int y) const { return data[x*width + y]; }
    int& operator()(int x,int y){ return data[x*width + y]; }
    my2dview get_transposed() { return my2dview(data,height,width);}
};


std::ostream& operator<<(std::ostream& out, const my2dview& x){
    for (int h=0;h<x.height;++h){
        for (int w=0;w<x.width;++w){
            out << x(h,w) << " ";
        }
        out << "\n";
    }
    return out;
}

Теперь транспонирование, которое будет работать для N=M:

my2dview broken_transpose(my2dview x){
    auto res = x.get_transposed();
    for (int i=0;i<x.height;++i){
        for (int j=0;j<x.width;++j){
            res(j,i) = x(i,j);
        }
    }
    return res;
}

Использование его для небольшой матрицы

int main() {
    std::vector<int> x{1,2,3,4,5,6};
    auto v = my2dview(x,2,3);
    std::cout << v << '\n';
    std::cout << v.get_transposed() << '\n';
    auto v2 = broken_transpose(v);
    std::cout << v2;
}

отпечатки

1 2
3 4
5 6

1 2 3
4 5 6

1 3 2
2 2 6

Вывод: наивный подход с заменой элементов не работает для неквадратных матриц.

На самом деле этот ответ просто перефразирует ответ @Alan Birtles. Я чувствовал вызов его

Из-за сложности задачи может оказаться, что быстрее создать временный [...]

просто чтобы прийти к такому же выводу ;).

person 463035818_is_not_a_number    schedule 16.10.2018