//Разжать изображение из формата .compressed (подробнее в Readme.md) static public float[] decompressImageBytes( byte[] compressedBytes, out int width, out int height, out string colorScheme, out string method, out int blockSize, out string travelMode, out bool crossMerge ) { //считваем заголовок, что бы узнать информацию об изображении readHeader_v1( compressedBytes, out width, out height, out colorScheme, out method, out blockSize, out travelMode, out crossMerge ); int blocksAmount = getTotalBlocksAmount(blockSize, width, height); int elementsAmountInOneBlock = blockSize * blockSize * 3; //разжимаем основную часть (уложенне блоки и остаток) float[] values = byteArrayAsFloatArray(decompressBytes(compressedBytes, Constants.HeaderSize)); //восстанавливаем блоки float[][] blocks = new float[blocksAmount][]; if (crossMerge) //с кросс-мерджем { restoreCrossMergedBlocks(values, blocksAmount, elementsAmountInOneBlock, blocks); } else //без кросс-мерджа { Parallel.For(0, blocks.Length, i => { float[] temp = new float[elementsAmountInOneBlock]; for (int j = 0; j < elementsAmountInOneBlock; ++j) { temp[j] = values[i * elementsAmountInOneBlock + j]; } blocks[i] = temp; }); } //если надо - восстанавливаем линейность из зигзага if (travelMode == "Зиг-заг") { ZigZag.precompute(blockSize); Parallel.For(0, blocks.Length, i => { blocks[i] = fromZigzag(blocks[i]); }); } //к каждому блоку применяем обратное преобразование switch (method) { case "FHT": MathUtils.mapFunctionToEachBlock(MathUtils.inv_fht_2d, blocks); break; case "DCT": MathUtils.mapFunctionToEachBlock(MathUtils.inv_dct_2d, blocks); break; } float[] resultValues = mergeBlocksToImageValues(blocks, width, height, blockSize); //записываем остаток, если он был long remainderSize = getRemainderSize(width, height, blockSize); if (remainderSize != 0) { float[] remainder = new float[remainderSize]; for (int i = 0; i < remainderSize; ++i) { remainder[i] = values[blocks.Length * elementsAmountInOneBlock + i]; } writeRemainderIntoImageValues(remainder, resultValues, width, height, blockSize); } return(resultValues); }
//сжать изображение в формат .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); }