Как изменить форму массива в С#

У меня есть трехмерный массив байтов в С#, который я прочитал из растрового изображения:

byte[w, h, 3]

Каков самый простой и эффективный способ преобразования этого массива в двумерную (линейную) форму?

byte[w*h, 3]

Другими словами, я хочу сохранить количество каналов (функций), но в линейной форме (а не в квадратной форме)

Позвольте мне проиллюстрировать ввод и желаемый результат:

ввод:

|(r1,g1,b1)    (r2,g2,b2)    (r3,g3,b3)|
|(r4,g4,b4)    (r5,g5,b5)    (r6,g6,b6)|
|(r7,g7,b7)    (r8,g8,b8)    (r9,g9,b9)|

заметим, что arr [0, 0, 0] = r1, arr [0, 0, 1] = g1, arr [0, 0, 2] = b1 и т.д.

и вывод:

|(r1,g1,b1)    (r2,g2,b2)    (r3,g3,b3)    (r4,g4,b4)    (r5,g5,b5)    (r6,g6,b6) ...|

Ответ 1

Кажется, что это нормально, потому что массив уже находится в правильной форме в памяти:

var a = new byte[2,  2, 2] { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } };
var b = new byte[2 * 2, 2];

//sizeof(byte) is obviously 1 here, but I put it there for documentation
Buffer.BlockCopy(a, 0, b, 0, a.Length * sizeof(byte));

Для интересующихся: что делать, если вы действительно хотите перенести 2D-массив в 1D:

byte[,] a = {
    {1, 2},
    {3, 4},
    {5, 6},
};
var b = new byte[a.GetLength(1) * a.GetLength(0)]; //Transpose

const int R_STRIDE1 = 8; //Tune this for your CPU
const int C_STRIDE1 = 8; //Tune this for your CPU

//You should hoist the calls to GetLength() out of the loop unlike what I do here
for (int r1 = 0; r1 < a.GetLength(0); r1 += R_STRIDE1)
for (int c1 = 0; c1 < a.GetLength(1); c1 += C_STRIDE1)
    for (int r2 = 0; r2 < R_STRIDE1; r2++)
    for (int c2 = 0; c2 < C_STRIDE1; c2++)
    {
        var r = r1 + r2;
        var c = c1 + c2;
        if (r < a.GetLength(0) && c < a.GetLength(1))
            b[c * a.GetLength(0) + r] = a[r, c];
    }

Это должно использовать кеширование в CPU. У меня только выполнено ограниченное тестирование на этом - он все равно может быть медленным. Попробуйте настроить его, если он есть.
Вы можете (несколько нетривиально) распространить это на 3D-массив.

Ответ 2

Buffer.BlockCopy сделает это. По крайней мере, он работает в этом простом тесте.

byte[, ,] src = new byte[10, 10, 3];
byte[,] dest = new byte[100, 3];

List<byte> srcList = new List<byte>();
Random rnd = new Random();
for (int i = 0; i < 10; ++i)
{
    for (int j = 0; j < 10; ++j)
    {
        for (int k = 0; k < 3; ++k)
        {
            byte b = (byte)rnd.Next();
            src[i, j, k] = b;
            srcList.Add(b);
        }
    }
}

Buffer.BlockCopy(src, 0, dest, 0, 300);

List<byte> destList = new List<byte>();
for (int i = 0; i < 100; ++i)
{
    for (int j = 0; j < 3; ++j)
    {
        destList.Add(dest[i, j]);
    }
}

// See if they're in the same order
for (int i = 0; i < srcList.Count; ++i)
{
    Console.WriteLine("{0,3:N0} - {1,3:N0}", srcList[i], destList[i]);
    if (srcList[i] != destList[i])
    {
        Console.WriteLine("ERROR!");
    }
}

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

Я бы предложил написать явный цикл.