public static CompressedImage Load(string path, int DCTSize) { var result = new CompressedImage(); using (var sr = new FileStream(path, FileMode.Open)) { var buffer = new byte[4]; sr.Read(buffer, 0, 4); result.Height = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.Width = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.CompressionLevel = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); var blockSize = result.FrequencesPerBlock = BitConverter.ToInt32(buffer, 0); var blocksCount = result.Height*result.Width/(DCTSize*DCTSize); result.Frequences = new List<double>(blocksCount*result.FrequencesPerBlock); for (var blockNum = 0; blockNum < blocksCount; blockNum++) { for (var freqNum = 0; freqNum < blockSize; freqNum++) { sr.Read(buffer, 0, 2); result.Frequences.Add(BitConverter.ToInt16(buffer, 0)); } } } return result; }
public static double[,] ParallelUncompressWithDCT(this CompressedImage image, Options options) { var result = new double[image.Height, image.Width]; var blocksCount = image.Width * image.Height / (options.DCTSize * options.DCTSize); var freqsCount = Enumerable.Range(1, image.CompressionLevel).Sum(); Parallel.For(0, blocksCount, blockIndex => { var y = blockIndex / (image.Width / options.DCTSize); var x = blockIndex % (image.Width / options.DCTSize); var channelFreqs = new double[options.DCTSize, options.DCTSize]; var freqNum = blockIndex * freqsCount; for (var i = 0; i < options.DCTSize; i++) { for (var j = 0; j < options.DCTSize; j++) { if (i + j < image.CompressionLevel) { channelFreqs[i, j] = image.Frequences[freqNum++]; } } } var processedSubmatrix = DCTHelper.IDCT2D(channelFreqs); processedSubmatrix.ShiftMatrixValues(128); result.SetSubmatrix(processedSubmatrix, y * options.DCTSize, x * options.DCTSize); }); return(result); }
public static double[,] UncompressWithDCT(this CompressedImage image, Options options) { var result = new double[image.Height, image.Width]; var DCTSize = options.DCTSize; var freqNum = 0; for (var y = 0; y < image.Height; y += DCTSize) { for (var x = 0; x < image.Width; x += DCTSize) { var channelFreqs = new double[DCTSize, DCTSize]; for (var i = 0; i < DCTSize; i++) { for (var j = 0; j < DCTSize; j++) { if (i + j < image.CompressionLevel) { channelFreqs[i, j] = image.Frequences[freqNum++]; } } } var processedSubmatrix = DCTHelper.IDCT2D(channelFreqs); processedSubmatrix.ShiftMatrixValues(128); result.SetSubmatrix(processedSubmatrix, y, x); } } return(result); }
public static CompressedImage Load(string path, int DCTSize) { var result = new CompressedImage(); using (var sr = new FileStream(path, FileMode.Open)) { byte[] buffer = new byte[4]; sr.Read(buffer, 0, 4); result.Height = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.Width = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.CompressionLevel = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); var blockSize = result.FrequencesPerBlock = BitConverter.ToInt32(buffer, 0); var blocksCount = result.Height * result.Width / (DCTSize * DCTSize); result.Frequences = new List<double>(blocksCount * result.FrequencesPerBlock); for (int blockNum = 0; blockNum < blocksCount; blockNum++) { for (int freqNum = 0; freqNum < blockSize; freqNum++) { sr.Read(buffer, 0, 2); result.Frequences.Add(BitConverter.ToInt16(buffer, 0)); } } } return result; }
static void Main(string[] args) { Console.WriteLine(IntPtr.Size == 8 ? "64-bit version" : "32-bit version"); var sw = Stopwatch.StartNew(); const string fileName = @"earth.bmp"; // var fileName = "Big_Black_River_Railroad_Bridge.bmp"; var compressedFileName = fileName + ".compressed." + CompressionQuality; var uncompressedFileName = fileName + ".uncompressed." + CompressionQuality + ".bmp"; using (var fileStream = File.OpenRead(fileName)) using (var bmp = (Bitmap)Image.FromStream(fileStream, false, false)) { var imageMatrix = (Matrix)bmp; sw.Stop(); Console.WriteLine($"{bmp.Width}x{bmp.Height} - {fileStream.Length / (1024.0 * 1024):F2} MB"); sw.Start(); var compressionResult = Compress(imageMatrix, CompressionQuality); compressionResult.Save(compressedFileName); } sw.Stop(); GC.Collect(); Console.WriteLine("Compression: " + sw.Elapsed); sw.Restart(); var compressedImage = CompressedImage.Load(compressedFileName); var uncompressedImage = Uncompress(compressedImage); var resultBmp = (Bitmap)uncompressedImage; resultBmp.Save(uncompressedFileName, ImageFormat.Bmp); Console.WriteLine("Decompression: " + sw.Elapsed); Console.WriteLine($"Peak commit size: {MemoryMeter.PeakPrivateBytes() / (1024.0*1024):F2} MB"); Console.WriteLine($"Peak working set: {MemoryMeter.PeakWorkingSet() / (1024.0*1024):F2} MB"); }
public static void Main(string[] args) { try { Console.WriteLine(IntPtr.Size == 8 ? "64-bit version" : "32-bit version"); var sw = Stopwatch.StartNew(); //var fileName = @"sample.bmp"; //var fileName = @"MARBLES.BMP"; //var fileName = @"earth.bmp"; var fileName = @"kot.jpg"; var compressedFileName = fileName + ".compressed." + CompressionQuality; var uncompressedFileName = fileName + ".uncompressed." + CompressionQuality + ".bmp"; var imageMatrix = Matrix.FromFile(fileName, out var length); sw.Stop(); Console.WriteLine($"{imageMatrix.Width}x{imageMatrix.Height} - {length / (1024.0 * 1024):F2} MB"); sw.Start(); var compressionResult = Compress(imageMatrix); compressionResult.Save(compressedFileName); sw.Stop(); Console.WriteLine("Compression: " + sw.Elapsed); sw.Restart(); var compressedImage = CompressedImage.Load(compressedFileName); var uncompressedImage = Uncompress(compressedImage); uncompressedImage.SaveToFile(uncompressedFileName); Console.WriteLine("Decompression: " + sw.Elapsed); Console.WriteLine($"Peak commit size: {MemoryMeter.PeakPrivateBytes() / (1024.0*1024):F2} MB"); Console.WriteLine($"Peak working set: {MemoryMeter.PeakWorkingSet() / (1024.0*1024):F2} MB"); } catch (Exception e) { Console.WriteLine(e); } }
private static Matrix Uncompress(CompressedImage image) { var result = new Matrix(image.Height, image.Width); using (var allQuantizedBytes = new MemoryStream(HuffmanCodec.Decode(image.CompressedBytes, image.DecodeTable, image.BitsCount))) { for (var y = 0; y < image.Height; y += DCTSize) { for (var x = 0; x < image.Width; x += DCTSize) { var _y = new double[DCTSize, DCTSize]; var cb = new double[DCTSize, DCTSize]; var cr = new double[DCTSize, DCTSize]; foreach (var channel in new [] { _y, cb, cr }) { var quantizedBytes = new byte[DCTSize * DCTSize]; allQuantizedBytes.ReadAsync(quantizedBytes, 0, quantizedBytes.Length).Wait(); var quantizedFreqs = ZigZagUnScan(quantizedBytes); var channelFreqs = DeQuantize(quantizedFreqs, image.Quality); DCT.IDCT2D(channelFreqs, channel); ShiftMatrixValues(channel, 128); } SetPixels(result, _y, cb, cr, PixelFormat.YCbCr, y, x); } } } return(result); }
public void Decompress() { var compressedImage = CompressedImage.Load(compressedFileName); var uncompressedImage = Decompress(compressedImage); var resultBmp = (Bitmap)uncompressedImage; resultBmp.Save(uncompressedFileName, ImageFormat.Bmp); }
public static CompressedImage Load(string path) { var result = new CompressedImage(); using (var sr = new FileStream(path, FileMode.Open)) { var buffer = new byte[8]; sr.Read(buffer, 0, 4); result.RealWidth = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.RealHeight = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.Width = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.Height = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.Quality = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); var decodeTableSize = BitConverter.ToInt32(buffer, 0); result.DecodeTable = new Dictionary <BitsWithLength, byte>(decodeTableSize, new BitsWithLength.Comparer()); for (var i = 0; i < decodeTableSize; i++) { sr.Read(buffer, 0, 4); var bits = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); var bitsCount = BitConverter.ToInt32(buffer, 0); var mappedByte = (byte)sr.ReadByte(); result.DecodeTable[new BitsWithLength { Bits = bits, BitsCount = bitsCount }] = mappedByte; } sr.Read(buffer, 0, 8); result.BitsCount = BitConverter.ToInt64(buffer, 0); sr.Read(buffer, 0, 4); var compressedBytesCount = BitConverter.ToInt32(buffer, 0); result.CompressedBytes = new byte[compressedBytesCount]; var totalRead = 0; while (totalRead < compressedBytesCount) { totalRead += sr.Read(result.CompressedBytes, totalRead, compressedBytesCount - totalRead); } } return(result); }
public static CompressedImage Load(string path, int dctSize) { var result = new CompressedImage(); using (var sr = new FileStream(path, FileMode.Open)) { var buffer = new byte[4]; sr.Read(buffer, 0, 4); result.Height = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.Width = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.PixelFormat = (PixelFormat)BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.ThinIndex = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.CompressionLevel = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); result.NumberDataBytes = BitConverter.ToInt32(buffer, 0); var byteList = new List <byte>(); for (var blockNum = 0; blockNum < result.NumberDataBytes; blockNum++) { sr.Read(buffer, 0, 1); byteList.Add(buffer[0]); } result.DataBytes = byteList.ToArray(); sr.Read(buffer, 0, 4); result.BitsCount = BitConverter.ToInt32(buffer, 0); sr.Read(buffer, 0, 4); var numberBytesDecodeTable = BitConverter.ToInt32(buffer, 0); var bigBuffer = new byte[numberBytesDecodeTable]; sr.Read(bigBuffer, 0, numberBytesDecodeTable); var mStream = new MemoryStream(); var binFormatter = new BinaryFormatter(); mStream.Write(bigBuffer, 0, bigBuffer.Length); mStream.Position = 0; var myObject = binFormatter.Deserialize(mStream) as Dictionary <BitsWithLength, byte>; result.DecodeTable = myObject; } return(result); }
private static void Main() { Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)3; Console.WriteLine(IntPtr.Size == 8 ? "64-bit version" : "32-bit version"); var fileName = "sample.bmp"; //var fileName = "earth.bmp"; //var fileName = "marbles.bmp"; //var fileName = "marbles2.bmp"; fileName = fileName.Insert(0, @"Images\"); var compressedFileName = fileName + ".compressed." + CompressionQuality; var uncompressedFileName = fileName + ".uncompressed." + CompressionQuality + ".bmp"; var sw = Stopwatch.StartNew(); using (var fileStream = File.OpenRead(fileName)) using (var bmp = (Bitmap)Image.FromStream(fileStream, false, false)) { var imageMatrix = (Matrix)bmp; sw.Stop(); Console.WriteLine("getPixel: {0}", sw.ElapsedMilliseconds); Console.WriteLine($"{bmp.Width}x{bmp.Height} - {fileStream.Length / (1024.0 * 1024):F2} MB"); sw.Start(); var compressionResult = Compress(imageMatrix); sw.Stop(); Console.WriteLine("Compression: " + sw.Elapsed); compressionResult.Save(compressedFileName); } var compressedImage = CompressedImage.Load(compressedFileName); sw.Restart(); var uncompressedImage = Uncompress(compressedImage); Console.WriteLine("Decompression: " + sw.Elapsed); sw.Restart(); var resultBmp = (Bitmap)uncompressedImage; Console.WriteLine("setPixel: {0}", sw.ElapsedMilliseconds); resultBmp.Save(uncompressedFileName, ImageFormat.Bmp); Console.WriteLine($"Peak commit size: {MemoryMeter.PeakPrivateBytes() / (1024.0 * 1024):F2} MB"); Console.WriteLine($"Peak working set: {MemoryMeter.PeakWorkingSet() / (1024.0 * 1024):F2} MB"); }
private static Matrix Uncompress(CompressedImage image) { var result = new Matrix(image.Height, image.Width, image.RealHeight, image.RealWidth); using (var allQuantizedBytes = new MemoryStream(HuffmanCodec.Decode(image.CompressedBytes, image.DecodeTable, image.BitsCount))) { Parallel.ForEach(Enumerable.Range(0, image.Height / DCTSize).Select(y => y * DCTSize), y => { for (var x = 0; x < image.Width; x += DCTSize) { var _y = new double[DCTSize, DCTSize]; var cb = new double[DCTSize, DCTSize]; var cr = new double[DCTSize, DCTSize]; for (var i = 0; i < 3; i++) { var quantizedBytes = new byte[DCTSize * DCTSize]; allQuantizedBytes.Position = (x * 8 + y * image.Width) * 3 + 64 * i; allQuantizedBytes.ReadAsync(quantizedBytes, 0, quantizedBytes.Length).Wait(); var quantizedFreqs = ZigZagUnScan(quantizedBytes); var channelFreqs = DeQuantize(quantizedFreqs, image.Quality); DCT.IDCT2D(channelFreqs); ShiftMatrixValues(channelFreqs, 128); switch (i) { case 0: _y = channelFreqs; break; case 1: cb = channelFreqs; break; default: cr = channelFreqs; break; } } SetPixels(result, _y, cb, cr, PixelFormat.YCbCr, y, x); } }); } return(result); }
private static void Decode(Options options) { var uncompressedFileName = options.PathToEncoded + ".uncompressed." + options.DCTSize + "." + CalcCompressionLevel(options) + ".bmp"; var data = File.ReadAllBytes(options.PathToEncoded); var encodedDataLength = BitConverter.ToInt32(data, 0); var encodedData = new byte[encodedDataLength]; Buffer.BlockCopy(data, 4, encodedData, 0, encodedDataLength); var decodeTable = DeserializeDecodeTable(data, 4 + encodedDataLength); var decodedHuffman = HuffmanCodec.Decode(encodedData, decodeTable, options); var compressedImg = CompressedImage.LoadFromBytesArray(decodedHuffman, options.DCTSize); compressedImg .ParallelUncompressWithDCT(options) .GrayscaleMatrixToBitmap() .Save(uncompressedFileName); }
private static CbCrImage Uncompress(CompressedImage image) { var result = new CbCrImage(image.Height, image.Width); var DCTcoefficients = DCT.GetCoefficientsMatrix(DCTSize, DCTSize); var transponseDCTcoefficients = DCT.Transpose(DCTcoefficients); var quantizationMatrix = GetQuantizationMatrix(image.Quality); using (var allQuantizedBytes = new MemoryStream(HuffmanCodec.Decode(image.CompressedBytes, image.DecodeTable, image.BitsCount))) { Parallel.For(0, image.Height / DCTSize, ky => { var y = ky; Parallel.For(0, image.Width / DCTSize, _x => { var x = _x; var _y = new double[DCTSize, DCTSize]; var cb = new double[DCTSize, DCTSize]; var cr = new double[DCTSize, DCTSize]; var c = 0; foreach (var channel in new[] { _y, cb, cr }) { var quantizedBytes = new byte[DCTSize * DCTSize]; lock (allQuantizedBytes) { allQuantizedBytes.Seek((y * (image.Width / 8) + x) * 8 * 8 * 3 + 8 * 8 * c, SeekOrigin.Begin); allQuantizedBytes.ReadAsync(quantizedBytes, 0, quantizedBytes.Length).Wait(); } c++; var quantizedFreqs = ZigZagUnScan(quantizedBytes); var channelFreqs = DeQuantize(quantizedFreqs, quantizationMatrix); DCT.IDCT2D(channelFreqs, channel, DCTcoefficients, transponseDCTcoefficients); } SetPixels(result, _y, cb, cr, y * DCTSize, x * DCTSize, 128); }); }); } return(result); }
static void Main(string[] args) { try { var sw = Stopwatch.StartNew(); var fileName = @"..\..\sample.bmp"; var compressedFileName = fileName + ".compressed." + CompressionQuality; var uncompressedFileName = fileName + ".uncompressed." + CompressionQuality + ".bmp"; using (var fileStream = File.OpenRead(fileName)) using (var bmp = (Bitmap)Image.FromStream(fileStream, false, false)) { var imageMatrix = (Matrix)bmp; sw.Stop(); Console.WriteLine($"{bmp.Width}x{bmp.Height} - {fileStream.Length / (1024.0 * 1024):F2} MB"); sw.Start(); var compressionResult = Compress(imageMatrix, CompressionQuality); compressionResult.Save(compressedFileName); } sw.Stop(); Console.WriteLine("Compression: " + sw.Elapsed); sw.Restart(); var compressedImage = CompressedImage.Load(compressedFileName); var uncompressedImage = Uncompress(compressedImage); var resultBmp = (Bitmap)uncompressedImage; resultBmp.Save(uncompressedFileName, ImageFormat.Bmp); Console.WriteLine("Decompression: " + sw.Elapsed); Console.WriteLine($"Peak commit size: {MemoryMeter.PeakPrivateBytes() / (1024.0 * 1024):F2} MB"); Console.WriteLine($"Peak working set: {MemoryMeter.PeakWorkingSet() / (1024.0 * 1024):F2} MB"); } catch (Exception e) { Console.WriteLine(e); } }
private static void Main(string[] args) { ApplicationOptions options; if (!TryArgsParse(args, out options)) { return; } //try //{ SetMaxThread(options.MaxThreads); const int thinIndex = 2; var countSaveFrequence = PercentToCountFrequence(options.PercentCompress, options.Dct); var applicationModule = new ApplicationModule(options.Dct, countSaveFrequence, thinIndex); var applicationKernel = new StandardKernel(applicationModule); if (options.PathCompressFile != null) { var jpegCompressor = applicationKernel.Get <ICompressor>(); var bmp = (Bitmap)Image.FromFile(options.PathSourceFile); var compressedImage = jpegCompressor.Compress(bmp); compressedImage.Save(options.PathCompressFile); } if (options.PathDecompressFile != null) { var jpegDecompressor = applicationKernel.Get <IDecompressor>(); var compressedImage = CompressedImage.Load(options.PathSourceFile, options.Dct); var decompressedImage = jpegDecompressor.Decompress(compressedImage); decompressedImage.Save(options.PathDecompressFile, ImageFormat.Bmp); } //} //catch (Exception e) //{ // Console.WriteLine(e); //} }
private Matrix Decompress(CompressedImage image) { if (image.Quality != compressionQuality) { quantizationMatrix = GetQuantizationMatrix(image.Quality); } var matrix = new Matrix(image.Height, image.Width, PixelFormat.YCbCr); var allQuantizedBytes = HuffmanCodec.Decode(image.CompressedBytes, image.DecodeTable, image.BitsCount); var heigth = image.Height / DCTSize; var width = image.Width / DCTSize; var dct = new DCT(DCTSize); Parallel.For(0, heigth, h => { Parallel.For(0, width, w => { var y = new double[DCTSize, DCTSize]; var cb = new double[DCTSize, DCTSize]; var cr = new double[DCTSize, DCTSize]; var channels = new[] { y, cb, cr }; var blockLength = DCTSize * DCTSize * channels.Length; var readPos = h * blockLength * width + w * blockLength; for (var i = 0; i < channels.Length; i++) { var channel = channels[i]; var channelReadPos = readPos + i * DCTSize * DCTSize; var quantizedBytes = allQuantizedBytes.ReadFrom(channelReadPos, DCTSize * DCTSize); var quantizedFreqs = ZigZagUnScan(quantizedBytes); var channelFreqs = DeQuantize(quantizedFreqs); dct.IDCT2D(channelFreqs, channel); channel.ShiftValues(128); } matrix.SetPixels(y, cb, cr, h * DCTSize, w * DCTSize); }); }); return(matrix); }
public static Matrix Uncompress(CompressedImage image) { var result = new Matrix(image.Height, image.Width); var container = new byte[image.Height * image.Width * 3]; using (var allQuantizedBytes = new MemoryStream(HuffmanCodec.Decode(image.CompressedBytes, image.DecodeTable, image.BitsCount))) allQuantizedBytes.Read(container); var qm = GetQuantizationMatrix(image.Quality); Parallel.For(0, image.Height / 8, i => { var y = i * 8; for (var x = 0; x < image.Width; x += DCTSize) { var channels = new[] { new double[DCTSize, DCTSize], new double[DCTSize, DCTSize], new double[DCTSize, DCTSize] }; for (var index = 0; index < 3; index++) { var channel = channels[index]; var quantizedBytes = new byte[DCTSize * DCTSize]; Array.Copy(container, (index + 3 * i * image.Width / 8 + 3 * x / 8) * 64, quantizedBytes, 0, 64); var quantizedFreqs = ZigZagUnScan(quantizedBytes); var channelFreqs = DeQuantize(quantizedFreqs, qm); DCT.IDCT2D(channelFreqs, channel); ShiftMatrixValues(channel, 128); } SetPixels(result, channels[0], channels[1], channels[2], PixelFormat.YCbCr, y, x); } }); return(result); }
private static Matrix Uncompress(CompressedImage image) { var result = new Matrix(image.Height, image.Width); // using (var allQuantizedBytes = // new MemoryStream(HuffmanCodec.Decode(image.CompressedBytes, image.DecodeTable, image.BitsCount))) // { // for (var y = 0; y < image.Height; y += DCTSize) // for (var x = 0; x < image.Width; x += DCTSize) // { // var _y = new double[DCTSize, DCTSize]; // var cb = new double[DCTSize, DCTSize]; // var cr = new double[DCTSize, DCTSize]; // foreach (var channel in new[] {_y, cb, cr}) // { // var quantizedBytes = new byte[DCTSize * DCTSize]; // allQuantizedBytes.ReadAsync(quantizedBytes, 0, quantizedBytes.Length).Wait(); // var quantizedFreqs = ZigZagUnScan(quantizedBytes); // var channelFreqs = DeQuantize(quantizedFreqs, image.Quality); // DCT.IDCT2D(channelFreqs, channel); // ShiftMatrixValues(channel, 128); // } // // SetPixels(result, _y, cb, cr, PixelFormat.YCbCr, y, x); // } // } var decodedBytesArray = HuffmanCodec.Decode(image.CompressedBytes, image.DecodeTable, image.BitsCount); var tuples = new List <(int, int)>(); for (var y = 0; y < image.Height; y += DCTSize) { for (var x = 0; x < image.Width; x += DCTSize) { tuples.Add((x, y)); } } // var allQuantizedBytes = new List<byte>[tuples.Count]; Parallel.For(0, tuples.Count, i => { var(x, y) = tuples[i]; var _y = new double[DCTSize, DCTSize]; var cb = new double[DCTSize, DCTSize]; var cr = new double[DCTSize, DCTSize]; var offset = 0; foreach (var channel in new[] { _y, cb, cr }) { var quantizedBytes = new byte[DCTSize * DCTSize]; Buffer.BlockCopy(decodedBytesArray, quantizedBytes.Length * (i * 3 + offset), quantizedBytes, 0, quantizedBytes.Length); var quantizedFreqs = ZigZagUnScan(quantizedBytes); var channelFreqs = DeQuantize(quantizedFreqs, image.Quality); DCT.IDCT2D(channelFreqs, channel); ShiftMatrixValues(channel, 128); offset++; } SetPixels(result, _y, cb, cr, PixelFormat.YCbCr, y, x); }); return(result); }