public static void SaveJpeg(string aFilename, int aQuality, Bitmap aImage)
{
if (aImage.PixelFormat != System.Drawing.Imaging.PixelFormat.Format24bppRgb)
{
throw new ArgumentException("Only three channel color images are supported.");
}
if (aImage.Width % 16 != 0 || aImage.Height % 16 != 0)
{
throw new ArgumentException("The provided bitmap must have a height and width of a multiple of 16.");
}
JPEGCompression compression = new JPEGCompression();
NPPImage_8uC3 src = new NPPImage_8uC3(aImage.Width, aImage.Height);
NPPImage_8uC1 srcY = new NPPImage_8uC1(aImage.Width, aImage.Height);
NPPImage_8uC1 srcCb = new NPPImage_8uC1(aImage.Width / 2, aImage.Height / 2);
NPPImage_8uC1 srcCr = new NPPImage_8uC1(aImage.Width / 2, aImage.Height / 2);
src.CopyToDevice(aImage);
//System.Drawing.Bitmap is ordered BGR not RGB
//The NPP routine BGR to YCbCR outputs the values in clamped range, following the YCbCr standard.
//But JPEG uses unclamped values ranging all from [0..255], thus use our own color matrix:
float[,] BgrToYCbCr = new float[3, 4]
{{0.114f, 0.587f, 0.299f, 0},
{0.5f, -0.33126f, -0.16874f, 128},
{-0.08131f, -0.41869f, 0.5f, 128}};
src.ColorTwist(BgrToYCbCr);
//Reduce size of of Cb and Cr channel
src.Copy(srcY, 2);
srcY.Resize(srcCr, 0.5, 0.5, InterpolationMode.SuperSampling);
src.Copy(srcY, 1);
srcY.Resize(srcCb, 0.5, 0.5, InterpolationMode.SuperSampling);
src.Copy(srcY, 0);
FrameHeader oFrameHeader = new FrameHeader();
oFrameHeader.nComponents = 3;
oFrameHeader.nHeight = (ushort)aImage.Height;
oFrameHeader.nSamplePrecision = 8;
oFrameHeader.nWidth = (ushort)aImage.Width;
oFrameHeader.aComponentIdentifier = new byte[] { 1, 2, 3 };
oFrameHeader.aSamplingFactors = new byte[] { 34, 17, 17 }; //Y channel is twice the sice of Cb/Cr channel
oFrameHeader.aQuantizationTableSelector = new byte[] { 0, 1, 1 };
//Get quantization tables from JPEG standard with quality scaling
QuantizationTable[] aQuantizationTables = new QuantizationTable[2];
aQuantizationTables[0] = new QuantizationTable(QuantizationTable.QuantizationType.Luminance, aQuality);
aQuantizationTables[1] = new QuantizationTable(QuantizationTable.QuantizationType.Chroma, aQuality);
CudaDeviceVariable<byte>[] pdQuantizationTables = new CudaDeviceVariable<byte>[2];
pdQuantizationTables[0] = aQuantizationTables[0].aTable;
pdQuantizationTables[1] = aQuantizationTables[1].aTable;
//Get Huffman tables from JPEG standard
HuffmanTable[] aHuffmanTables = new HuffmanTable[4];
aHuffmanTables[0] = new HuffmanTable(HuffmanTable.HuffmanType.LuminanceDC);
aHuffmanTables[1] = new HuffmanTable(HuffmanTable.HuffmanType.ChromaDC);
aHuffmanTables[2] = new HuffmanTable(HuffmanTable.HuffmanType.LuminanceAC);
aHuffmanTables[3] = new HuffmanTable(HuffmanTable.HuffmanType.ChromaAC);
//Set header
ScanHeader oScanHeader = new ScanHeader();
oScanHeader.nA = 0;
oScanHeader.nComponents = 3;
oScanHeader.nSe = 63;
oScanHeader.nSs = 0;
oScanHeader.aComponentSelector = new byte[] { 1, 2, 3 };
oScanHeader.aHuffmanTablesSelector = new byte[] { 0, 17, 17 };
NPPImage_16sC1[] apdDCT = new NPPImage_16sC1[3];
NPPImage_8uC1[] apDstImage = new NPPImage_8uC1[3];
NppiSize[] aDstSize = new NppiSize[3];
aDstSize[0] = new NppiSize(srcY.Width, srcY.Height);
aDstSize[1] = new NppiSize(srcCb.Width, srcCb.Height);
aDstSize[2] = new NppiSize(srcCr.Width, srcCr.Height);
// Compute channel sizes as stored in the output JPEG (8x8 blocks & MCU block layout)
NppiSize oDstImageSize = new NppiSize();
float frameWidth = (float)Math.Floor((float)oFrameHeader.nWidth);
float frameHeight = (float)Math.Floor((float)oFrameHeader.nHeight);
oDstImageSize.width = (int)Math.Max(1.0f, frameWidth);
oDstImageSize.height = (int)Math.Max(1.0f, frameHeight);
//Console.WriteLine("Output Size: " + oDstImageSize.width + "x" + oDstImageSize.height + "x" + (int)(oFrameHeader.nComponents));
apDstImage[0] = srcY;
apDstImage[1] = srcCb;
apDstImage[2] = srcCr;
int nMCUBlocksH = 0;
int nMCUBlocksV = 0;
// Compute channel sizes as stored in the JPEG (8x8 blocks & MCU block layout)
for (int i = 0; i < oFrameHeader.nComponents; ++i)
{
nMCUBlocksV = Math.Max(nMCUBlocksV, oFrameHeader.aSamplingFactors[i] >> 4);
nMCUBlocksH = Math.Max(nMCUBlocksH, oFrameHeader.aSamplingFactors[i] & 0x0f);
}
for (int i = 0; i < oFrameHeader.nComponents; ++i)
{
NppiSize oBlocks = new NppiSize();
NppiSize oBlocksPerMCU = new NppiSize(oFrameHeader.aSamplingFactors[i] & 0x0f, oFrameHeader.aSamplingFactors[i] >> 4);
oBlocks.width = (int)Math.Ceiling((oFrameHeader.nWidth + 7) / 8 *
(float)(oBlocksPerMCU.width) / nMCUBlocksH);
oBlocks.width = DivUp(oBlocks.width, oBlocksPerMCU.width) * oBlocksPerMCU.width;
oBlocks.height = (int)Math.Ceiling((oFrameHeader.nHeight + 7) / 8 *
(float)(oBlocksPerMCU.height) / nMCUBlocksV);
oBlocks.height = DivUp(oBlocks.height, oBlocksPerMCU.height) * oBlocksPerMCU.height;
// Allocate Memory
apdDCT[i] = new NPPImage_16sC1(oBlocks.width * 64, oBlocks.height);
}
/***************************
*
* Output
*
***************************/
// Forward DCT
for (int i = 0; i < 3; ++i)
{
compression.DCTQuantFwd8x8LS(apDstImage[i], apdDCT[i], aDstSize[i], pdQuantizationTables[oFrameHeader.aQuantizationTableSelector[i]]);
}
// Huffman Encoding
CudaDeviceVariable<byte> pdScan = new CudaDeviceVariable<byte>(BUFFER_SIZE);
int nScanLength = 0;
int nTempSize = JPEGCompression.EncodeHuffmanGetSize(aDstSize[0], 3);
CudaDeviceVariable<byte> pJpegEncoderTemp = new CudaDeviceVariable<byte>(nTempSize);
NppiEncodeHuffmanSpec[] apHuffmanDCTableEnc = new NppiEncodeHuffmanSpec[3];
NppiEncodeHuffmanSpec[] apHuffmanACTableEnc = new NppiEncodeHuffmanSpec[3];
for (int i = 0; i < 3; ++i)
{
apHuffmanDCTableEnc[i] = JPEGCompression.EncodeHuffmanSpecInitAlloc(aHuffmanTables[(oScanHeader.aHuffmanTablesSelector[i] >> 4)].aCodes, NppiHuffmanTableType.nppiDCTable);
apHuffmanACTableEnc[i] = JPEGCompression.EncodeHuffmanSpecInitAlloc(aHuffmanTables[(oScanHeader.aHuffmanTablesSelector[i] & 0x0f) + 2].aCodes, NppiHuffmanTableType.nppiACTable);
}
JPEGCompression.EncodeHuffmanScan(apdDCT, 0, oScanHeader.nSs, oScanHeader.nSe, oScanHeader.nA >> 4, oScanHeader.nA & 0x0f, pdScan, ref nScanLength, apHuffmanDCTableEnc, apHuffmanACTableEnc, aDstSize, pJpegEncoderTemp);
for (int i = 0; i < 3; ++i)
{
JPEGCompression.EncodeHuffmanSpecFree(apHuffmanDCTableEnc[i]);
JPEGCompression.EncodeHuffmanSpecFree(apHuffmanACTableEnc[i]);
}
// Write JPEG to byte array, as in original sample code
byte[] pDstOutput = new byte[BUFFER_SIZE];
int pos = 0;
oFrameHeader.nWidth = (ushort)oDstImageSize.width;
oFrameHeader.nHeight = (ushort)oDstImageSize.height;
writeMarker(0x0D8, pDstOutput, ref pos);
writeJFIFTag(pDstOutput, ref pos);
writeQuantizationTable(aQuantizationTables[0], pDstOutput, ref pos);
writeQuantizationTable(aQuantizationTables[1], pDstOutput, ref pos);
writeFrameHeader(oFrameHeader, pDstOutput, ref pos);
writeHuffmanTable(aHuffmanTables[0], pDstOutput, ref pos);
writeHuffmanTable(aHuffmanTables[1], pDstOutput, ref pos);
writeHuffmanTable(aHuffmanTables[2], pDstOutput, ref pos);
writeHuffmanTable(aHuffmanTables[3], pDstOutput, ref pos);
writeScanHeader(oScanHeader, pDstOutput, ref pos);
pdScan.CopyToHost(pDstOutput, 0, pos, nScanLength);
pos += nScanLength;
writeMarker(0x0D9, pDstOutput, ref pos);
FileStream fs = new FileStream(aFilename, FileMode.Create, FileAccess.Write);
fs.Write(pDstOutput, 0, pos);
fs.Close();
//cleanup:
fs.Dispose();
pJpegEncoderTemp.Dispose();
pdScan.Dispose();
apdDCT[2].Dispose();
apdDCT[1].Dispose();
apdDCT[0].Dispose();
pdQuantizationTables[1].Dispose();
pdQuantizationTables[0].Dispose();
srcCr.Dispose();
srcCb.Dispose();
srcY.Dispose();
src.Dispose();
compression.Dispose();
}