//восстановить линейность изображения, обойденного зигзагом. public static T[] fromZigzag <T>(T[] values, T[] output = null) { if (output == null) { output = new T[values.Length]; } int[] zigzag_indexes = ZigZag.getIndexes((int)Math.Sqrt(values.Length / 3)); for (int i = 0; i < values.Length / 3; ++i) { for (int channel = 0; channel < 3; ++channel) { output[zigzag_indexes[i] * 3 + channel] = values[i * 3 + channel]; } } return(output); }
//сжать изображение в формат .compressed (подробнее в Readme.md) public static byte[] compressImageValues( float[] imageValues, int width, int height, string colorScheme, string method, Tuple <float, float, float> quality, int blockSize, string travelMode, bool crossMerge ) { if (imageValues == null) { return(null); } //узнаем размер остатка long remainderSize = getRemainderSize(width, height, blockSize); //бьем изображение на блоки var blocks = splitImageValuesIntoBlocks(imageValues, width, height, blockSize); int pixelsInBlock = blockSize * blockSize; var elementsAmountInOneBlock = pixelsInBlock * 3; var elementsAmountInAllBlocks = elementsAmountInOneBlock * blocks.Length; //применяем прямое преобразоваение к каждому блоку switch (method) { case "FHT": MathUtils.mapFunctionToEachBlock(MathUtils.fht_2d, blocks); break; case "DCT": MathUtils.mapFunctionToEachBlock(MathUtils.dct_2d, blocks); break; } // Обнуляем малозначимые коэффициенты // узнаем, сколько элементов должно быть обнулено int[] q = new int[3]; q[0] = (int)MathUtils.MapInterval(100 - quality.Item1, 0, 100, 0, pixelsInBlock); q[1] = (int)MathUtils.MapInterval(100 - quality.Item2, 0, 100, 0, pixelsInBlock); q[2] = (int)MathUtils.MapInterval(100 - quality.Item3, 0, 100, 0, pixelsInBlock); switch (method) { case "FHT": { Parallel.For(0, blocks.Length, i => { //В преобразовни Хаара малозначащие коэффициенты - это те, //которые близки к нулю. //Поэтому для каждого канала создадим список пар вида (индекс, значение) //отсортируем по модулю значения //и оставим только индексы //и обнулим необходимое количество элементов по первым q[j] из этих индексов float[][] channels = new float[3][]; for (int j = 0; j < 3; ++j) { channels[j] = new float[pixelsInBlock]; for (int k = 0; k < pixelsInBlock; ++k) { channels[j][k] = blocks[i][k * 3 + j]; } } int[][] indexes = new int[3][]; for (int j = 0; j < 3; ++j) { indexes[j] = channels[j].Select((elem, ind) => new { Index = ind, Value = elem }) .OrderBy(x => Math.Abs(x.Value)) .Take(q[j]) .Select(x => x.Index) .ToArray(); } for (int j = 0; j < 3; ++j) { for (int k = 0; k < q[j]; ++k) { blocks[i][indexes[j][k] * 3 + j] = 0; } } }); break; } case "DCT": { //малозначимые коэффициенты скапливаются в правом нижнем углу, //так что обнуляем начиная оттуда и пока не обнулим столько, сколько надо int[] indexes = ZigZag.getIndexes(blockSize); Parallel.For(0, blocks.Length, i => { for (int channel = 0; channel < 3; ++channel) { for (int j = 0; j < q[channel]; ++j) { blocks[i][indexes[pixelsInBlock - 1 - j] * 3 + channel] = 0; } } }); break; } } //если нужно - обходим блоки зиг-загом if (travelMode == "Зиг-заг") { ZigZag.precompute(blockSize); Parallel.For(0, blocks.Length, i => { blocks[i] = toZigzag(blocks[i]); }); } float[] values = new float[elementsAmountInAllBlocks]; //укладываем блоки if (crossMerge) //с кросс-мерджем { crossMergeBlocks(blocks, values); } else //без кросс-мерджа { Parallel.For(0, blocks.Length, block_i => { for (int i = 0; i < elementsAmountInOneBlock; ++i) { values[block_i * elementsAmountInOneBlock + i] = blocks[block_i][i]; } }); } // если есть остаток if (remainderSize != 0) { //то получаем его float[] remainder = getRemainder(imageValues, width, height, blockSize); //увеличиваем values, что бы он влез Array.Resize(ref values, values.Length + remainder.Length); //дописываем его в конец for (int i = 0; i < remainder.Length; ++i) { values[elementsAmountInAllBlocks + i] = remainder[i]; } } //сжимаем byte[] compressedBytes = compressBytes(floatArrayAsByteArray(values)); //сюда запишем итоговый .compresed (то есть заголовок и сжатые байты(уложенные блоки + остаток)) byte[] resultBytes = new byte[Constants.HeaderSize + compressedBytes.Length + remainderSize * 4]; //записываем заголовок writeHeader_v1(resultBytes, width, height, colorScheme, method, blockSize, travelMode, crossMerge); //дописываем сжатые данные for (int i = 0; i < compressedBytes.Length; ++i) { resultBytes[Constants.HeaderSize + i] = compressedBytes[i]; } return(resultBytes); }