/**************************************************************************** * * LibJpeg.Net * Copyright (c) 2008-2011, Bit Miracle * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the Bit Miracle nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS Software IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND Any EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BIT MIRACLE BE * LIABLE FOR Any DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON Any THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN Any WAY OUT OF THE USE OF THIS Software, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. ****************************************************************************/ using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Drawing; using System.Collections.ObjectModel; namespace BitMiracle.LibJpeg { #region JpegImage public sealed class JpegImage : IDisposable { private bool m_alreadyDisposed; private List m_rows = new List(); private int m_width; private int m_height; private byte m_bitsPerComponent; private byte m_componentsPerSample; private ColorSpace m_colorspace; private MemoryStream m_compressedData; private CompressionParameters m_compressionParameters; private MemoryStream m_decompressedData; private Bitmap m_bitmap; public JpegImage(System.Drawing.Bitmap bitmap) { createFromBitmap(bitmap); } public JpegImage(Stream imageData) { createFromStream(imageData); } public JpegImage(string fileName) { if (fileName == null) throw new ArgumentNullException("fileName"); using (FileStream input = new FileStream(fileName, FileMode.Open)) createFromStream(input); } public JpegImage(SampleRow[] sampleData, ColorSpace colorspace) { if (sampleData == null) throw new ArgumentNullException("sampleData"); if (sampleData.Length == 0) throw new ArgumentException("sampleData must not be empty"); if (colorspace == ColorSpace.Unknown) throw new ArgumentException("Unknown colorspace"); m_rows = new List(sampleData); SampleRow firstRow = m_rows[0]; m_width = firstRow.Length; m_height = m_rows.Count; Sample firstSample = firstRow[0]; m_bitsPerComponent = firstSample.BitsPerComponent; m_componentsPerSample = firstSample.ComponentCount; m_colorspace = colorspace; } public static JpegImage FromBitmap(Bitmap bitmap) { return new JpegImage(bitmap); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!m_alreadyDisposed) { if (disposing) { if (m_compressedData != null) m_compressedData.Dispose(); if (m_decompressedData != null) m_decompressedData.Dispose(); if (m_bitmap != null) m_bitmap.Dispose(); } m_compressionParameters = null; m_compressedData = null; m_decompressedData = null; m_bitmap = null; m_rows = null; m_alreadyDisposed = true; } } public int Width { get { return m_width; } internal set { m_width = value; } } public int Height { get { return m_height; } internal set { m_height = value; } } public byte ComponentsPerSample { get { return m_componentsPerSample; } internal set { m_componentsPerSample = value; } } public byte BitsPerComponent { get { return m_bitsPerComponent; } internal set { m_bitsPerComponent = value; } } public ColorSpace Colorspace { get { return m_colorspace; } internal set { m_colorspace = value; } } public SampleRow GetRow(int rowNumber) { return m_rows[rowNumber]; } public void WriteJpeg(Stream output) { WriteJpeg(output, new CompressionParameters()); } public void WriteJpeg(Stream output, CompressionParameters parameters) { compress(parameters); compressedData.WriteTo(output); } public void WriteBitmap(Stream output) { decompressedData.WriteTo(output); } public Bitmap ToBitmap() { return bitmap.Clone() as Bitmap; } private MemoryStream compressedData { get { if (m_compressedData == null) compress(new CompressionParameters()); return m_compressedData; } } private MemoryStream decompressedData { get { if (m_decompressedData == null) fillDecompressedData(); return m_decompressedData; } } private Bitmap bitmap { get { if (m_bitmap == null) { long position = compressedData.Position; m_bitmap = new Bitmap(compressedData); compressedData.Seek(position, SeekOrigin.Begin); } return m_bitmap; } } internal void addSampleRow(SampleRow row) { if (row == null) throw new ArgumentNullException("row"); m_rows.Add(row); } private static bool isCompressed(Stream imageData) { if (imageData == null) return false; if (imageData.Length <= 2) return false; imageData.Seek(0, SeekOrigin.Begin); int first = imageData.ReadByte(); int second = imageData.ReadByte(); return (first == 0xFF && second == (int)JpegMarkerType.SOI); } private void createFromBitmap(Bitmap bitmap) { initializeFromBitmap(bitmap); compress(new CompressionParameters()); } private void createFromStream(Stream imageData) { if (imageData == null) throw new ArgumentNullException("imageData"); if (isCompressed(imageData)) { m_compressedData = Utils.CopyStream(imageData); decompress(); } else { createFromBitmap(new Bitmap(imageData)); } } private void initializeFromBitmap(Bitmap bitmap) { if (bitmap == null) throw new ArgumentNullException("bitmap"); m_bitmap = bitmap; m_width = m_bitmap.Width; m_height = m_bitmap.Height; processPixelFormat(bitmap.PixelFormat); fillSamplesFromBitmap(); } private void compress(CompressionParameters parameters) { if (m_rows == null) throw new Exception("'m_rows' Cannot be null!"); if (m_rows.Count == 0) throw new ArgumentException("'m_rows' Cannot be empty!"); RawImage source = new RawImage(m_rows, m_colorspace); compress(source, parameters); } private void compress(IRawImage source, CompressionParameters parameters) { if (source == null) throw new ArgumentNullException("'source' Cannot be null!"); if (!needCompressWith(parameters)) return; m_compressedData = new MemoryStream(); m_compressionParameters = new CompressionParameters(parameters); Jpeg jpeg = new Jpeg(); jpeg.CompressionParameters = m_compressionParameters; jpeg.Compress(source, m_compressedData); } private bool needCompressWith(CompressionParameters parameters) { return m_compressedData == null || m_compressionParameters == null || !m_compressionParameters.Equals(parameters); } private void decompress() { Jpeg jpeg = new Jpeg(); jpeg.Decompress(compressedData, new DecompressorToJpegImage(this)); } private void fillDecompressedData() { if (m_decompressedData != null) throw new Exception("'m_decompressedData' Is not null!"); m_decompressedData = new MemoryStream(); BitmapDestination dest = new BitmapDestination(m_decompressedData); Jpeg jpeg = new Jpeg(); jpeg.Decompress(compressedData, dest); } private void processPixelFormat(System.Drawing.Imaging.PixelFormat pixelFormat) { if (pixelFormat == System.Drawing.Imaging.PixelFormat.Format16bppGrayScale) { m_bitsPerComponent = 16; m_componentsPerSample = 1; m_colorspace = ColorSpace.Grayscale; return; } byte formatIndexByte = (byte)((int)pixelFormat & 0x000000FF); byte pixelSizeByte = (byte)((int)pixelFormat & 0x0000FF00); if (pixelSizeByte == 32 && formatIndexByte == 15) { m_bitsPerComponent = 8; m_componentsPerSample = 4; m_colorspace = ColorSpace.CMYK; return; } m_bitsPerComponent = 8; m_componentsPerSample = 3; m_colorspace = ColorSpace.RGB; if (pixelSizeByte == 16) m_bitsPerComponent = 6; else if (pixelSizeByte == 24 || pixelSizeByte == 32) m_bitsPerComponent = 8; else if (pixelSizeByte == 48 || pixelSizeByte == 64) m_bitsPerComponent = 16; } private void fillSamplesFromBitmap() { if (m_bitmap == null) throw new Exception("Field 'm_bitmap' Cannot be null!"); for (int y = 0; y < Height; ++y) { short[] samples = new short[Width * 3]; for (int x = 0; x < Width; ++x) { Color color = m_bitmap.GetPixel(x, y); samples[x * 3] = color.R; samples[x * 3 + 1] = color.G; samples[x * 3 + 2] = color.B; } m_rows.Add(new SampleRow(samples, m_bitsPerComponent, m_componentsPerSample)); } } } #endregion #region BitmapDestination class BitmapDestination : IDecompressor { private Stream m_output; private byte[][] m_pixels; private int m_rowWidth; private int m_currentRow; private LoadedImageAttributes m_parameters; public BitmapDestination(Stream output) { m_output = output; } public override Stream Output { get { return m_output; } } public override void SetImageAttributes(LoadedImageAttributes parameters) { if (parameters == null) throw new ArgumentNullException("parameters"); m_parameters = parameters; } public override void BeginWrite() { m_rowWidth = m_parameters.Width * m_parameters.Components; while (m_rowWidth % 4 != 0) m_rowWidth++; m_pixels = new byte[m_rowWidth][]; for (int i = 0; i < m_rowWidth; i++) m_pixels[i] = new byte[m_parameters.Height]; m_currentRow = 0; } public override void ProcessPixelsRow(byte[] row) { if (m_parameters.Colorspace == ColorSpace.Grayscale || m_parameters.QuantizeColors) { putGrayRow(row); } else { if (m_parameters.Colorspace == ColorSpace.CMYK) putCmykRow(row); else putRgbRow(row); } ++m_currentRow; } public override void EndWrite() { writeHeader(); writePixels(); m_output.Flush(); } private void putGrayRow(byte[] row) { for (int i = 0; i < m_parameters.Width; ++i) m_pixels[i][m_currentRow] = row[i]; } private void putRgbRow(byte[] row) { for (int i = 0; i < m_parameters.Width; ++i) { int firstComponent = i * 3; byte red = row[firstComponent]; byte green = row[firstComponent + 1]; byte blue = row[firstComponent + 2]; m_pixels[firstComponent][m_currentRow] = blue; m_pixels[firstComponent + 1][m_currentRow] = green; m_pixels[firstComponent + 2][m_currentRow] = red; } } private void putCmykRow(byte[] row) { for (int i = 0; i < m_parameters.Width; ++i) { int firstComponent = i * 4; m_pixels[firstComponent][m_currentRow] = row[firstComponent + 2]; m_pixels[firstComponent + 1][m_currentRow] = row[firstComponent + 1]; m_pixels[firstComponent + 2][m_currentRow] = row[firstComponent + 0]; m_pixels[firstComponent + 3][m_currentRow] = row[firstComponent + 3]; } } private void writeHeader() { int bits_per_pixel; int cmap_entries; if (m_parameters.Colorspace == ColorSpace.Grayscale || m_parameters.QuantizeColors) { bits_per_pixel = 8; cmap_entries = 256; } else { cmap_entries = 0; if (m_parameters.Colorspace == ColorSpace.RGB) bits_per_pixel = 24; else if (m_parameters.Colorspace == ColorSpace.CMYK) bits_per_pixel = 32; else throw new InvalidOperationException(); } byte[] infoHeader = null; if (m_parameters.Colorspace == ColorSpace.RGB) infoHeader = createBitmapInfoHeader(bits_per_pixel, cmap_entries); else infoHeader = createBitmapV4InfoHeader(bits_per_pixel); const int fileHeaderSize = 14; int infoHeaderSize = infoHeader.Length; int paletteSize = cmap_entries * 4; int offsetToPixels = fileHeaderSize + infoHeaderSize + paletteSize; int fileSize = offsetToPixels + m_rowWidth * m_parameters.Height; byte[] fileHeader = createBitmapFileHeader(offsetToPixels, fileSize); m_output.Write(fileHeader, 0, fileHeader.Length); m_output.Write(infoHeader, 0, infoHeader.Length); if (cmap_entries > 0) writeColormap(cmap_entries, 4); } private static byte[] createBitmapFileHeader(int offsetToPixels, int fileSize) { byte[] bmpfileheader = new byte[14]; bmpfileheader[0] = 0x42; bmpfileheader[1] = 0x4D; PUT_4B(bmpfileheader, 2, fileSize); PUT_4B(bmpfileheader, 10, offsetToPixels); return bmpfileheader; } private byte[] createBitmapInfoHeader(int bits_per_pixel, int cmap_entries) { byte[] bmpinfoheader = new byte[40]; fillBitmapInfoHeader(bits_per_pixel, cmap_entries, bmpinfoheader); return bmpinfoheader; } private void fillBitmapInfoHeader(int bitsPerPixel, int cmap_entries, byte[] infoHeader) { PUT_2B(infoHeader, 0, infoHeader.Length); PUT_4B(infoHeader, 4, m_parameters.Width); PUT_4B(infoHeader, 8, m_parameters.Height); PUT_2B(infoHeader, 12, 1); PUT_2B(infoHeader, 14, bitsPerPixel); if (m_parameters.DensityUnit == DensityUnit.DotsCm) { PUT_4B(infoHeader, 24, m_parameters.DensityX * 100); PUT_4B(infoHeader, 28, m_parameters.DensityY * 100); } PUT_2B(infoHeader, 32, cmap_entries); } private byte[] createBitmapV4InfoHeader(int bitsPerPixel) { byte[] infoHeader = new byte[40 + 68]; fillBitmapInfoHeader(bitsPerPixel, 0, infoHeader); PUT_4B(infoHeader, 56, 0x02); return infoHeader; } private void writeColormap(int map_colors, int map_entry_size) { byte[][] colormap = m_parameters.Colormap; int num_colors = m_parameters.ActualNumberOfColors; int i = 0; if (colormap != null) { if (m_parameters.ComponentsPerSample == 3) { for (i = 0; i < num_colors; i++) { m_output.WriteByte(colormap[2][i]); m_output.WriteByte(colormap[1][i]); m_output.WriteByte(colormap[0][i]); if (map_entry_size == 4) m_output.WriteByte(0); } } else { for (i = 0; i < num_colors; i++) { m_output.WriteByte(colormap[0][i]); m_output.WriteByte(colormap[0][i]); m_output.WriteByte(colormap[0][i]); if (map_entry_size == 4) m_output.WriteByte(0); } } } else { for (i = 0; i < 256; i++) { m_output.WriteByte((byte)i); m_output.WriteByte((byte)i); m_output.WriteByte((byte)i); if (map_entry_size == 4) m_output.WriteByte(0); } } if (i > map_colors) throw new InvalidOperationException("Too many colors"); for (; i < map_colors; i++) { m_output.WriteByte(0); m_output.WriteByte(0); m_output.WriteByte(0); if (map_entry_size == 4) m_output.WriteByte(0); } } private void writePixels() { for (int row = m_parameters.Height - 1; row >= 0; --row) for (int col = 0; col < m_rowWidth; ++col) m_output.WriteByte(m_pixels[col][row]); } private static void PUT_2B(byte[] array, int offset, int value) { array[offset] = (byte)((value) & 0xFF); array[offset + 1] = (byte)(((value) >> 8) & 0xFF); } private static void PUT_4B(byte[] array, int offset, int value) { array[offset] = (byte)((value) & 0xFF); array[offset + 1] = (byte)(((value) >> 8) & 0xFF); array[offset + 2] = (byte)(((value) >> 16) & 0xFF); array[offset + 3] = (byte)(((value) >> 24) & 0xFF); } } #endregion #region BitStream class BitStream : IDisposable { private bool m_alreadyDisposed; private const int bitsInByte = 8; private Stream m_stream; private int m_positionInByte; private int m_size; public BitStream() { m_stream = new MemoryStream(); } public BitStream(byte[] buffer) { if (buffer == null) throw new ArgumentNullException("buffer"); m_stream = new MemoryStream(buffer); m_size = bitsAllocated(); } public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!m_alreadyDisposed) { if (disposing) { if (m_stream != null) m_stream.Dispose(); } m_stream = null; m_alreadyDisposed = true; } } public int Size() { return m_size; } public Stream UnderlyingStream { get { return m_stream; } } public virtual int Read(int bitCount) { if (Tell() + bitCount > bitsAllocated()) throw new ArgumentOutOfRangeException("bitCount"); return read(bitCount); } public int Write(int bitStorage, int bitCount) { if (bitCount == 0) return 0; const int maxBitsInStorage = sizeof(int) * bitsInByte; if (bitCount > maxBitsInStorage) throw new ArgumentOutOfRangeException("bitCount"); for (int i = 0; i < bitCount; ++i) { byte bit = (byte)((bitStorage << (maxBitsInStorage - (bitCount - i))) >> (maxBitsInStorage - 1)); if (!writeBit(bit)) return i; } return bitCount; } public void Seek(int pos, SeekOrigin mode) { switch (mode) { case SeekOrigin.Begin: seekSet(pos); break; case SeekOrigin.Current: seekCurrent(pos); break; case SeekOrigin.End: seekSet(Size() + pos); break; } } public int Tell() { return (int)m_stream.Position * bitsInByte + m_positionInByte; } private int bitsAllocated() { return (int)m_stream.Length * bitsInByte; } private int read(int bitsCount) { if (bitsCount < 0 || bitsCount > 32) throw new ArgumentOutOfRangeException("bitsCount"); if (bitsCount == 0) return 0; int bitsRead = 0; int result = 0; byte[] bt = new byte[1]; while (bitsRead == 0 || (bitsRead - m_positionInByte < bitsCount)) { m_stream.Read(bt, 0, 1); result = (result << bitsInByte); result += bt[0]; bitsRead += 8; } m_positionInByte = (m_positionInByte + bitsCount) % 8; if (m_positionInByte != 0) { result = (result >> (bitsInByte - m_positionInByte)); m_stream.Seek(-1, SeekOrigin.Current); } if (bitsCount < 32) { int mask = ((1 << bitsCount) - 1); result = result & mask; } return result; } private bool writeBit(byte bit) { if (m_stream.Position == m_stream.Length) { byte[] bt = { (byte)(bit << (bitsInByte - 1)) }; m_stream.Write(bt, 0, 1); m_stream.Seek(-1, SeekOrigin.Current); } else { byte[] bt = { 0 }; m_stream.Read(bt, 0, 1); m_stream.Seek(-1, SeekOrigin.Current); int shift = (bitsInByte - m_positionInByte - 1) % bitsInByte; byte maskByte = (byte)(bit << shift); bt[0] |= maskByte; m_stream.Write(bt, 0, 1); m_stream.Seek(-1, SeekOrigin.Current); } Seek(1, SeekOrigin.Current); int currentPosition = Tell(); if (currentPosition > m_size) m_size = currentPosition; return true; } private void seekSet(int pos) { if (pos < 0) throw new ArgumentOutOfRangeException("pos"); int byteDisplacement = pos / bitsInByte; m_stream.Seek(byteDisplacement, SeekOrigin.Begin); int shiftInByte = pos - byteDisplacement * bitsInByte; m_positionInByte = shiftInByte; } private void seekCurrent(int pos) { int result = Tell() + pos; if (result < 0 || result > bitsAllocated()) throw new ArgumentOutOfRangeException("pos"); seekSet(result); } } #endregion #region CoefControllerImpl class CoefControllerImpl : JpegCompressorCoefController { private BufferMode m_passModeSetByLastStartPass; private JpegCompressor m_cinfo; private int m_iMCU_row_num; private int m_mcu_ctr; private int m_MCU_vert_offset; private int m_MCU_rows_per_iMCU_row; private JpegBlock[][] m_MCU_buffer = new JpegBlock[JpegConstants.CompressorMaxBlocksInMCU][]; private JpegVirtualArray[] m_whole_image = new JpegVirtualArray[JpegConstants.MaxComponents]; public CoefControllerImpl(JpegCompressor cinfo, bool need_full_buffer) { m_cinfo = cinfo; if (need_full_buffer) { for (int ci = 0; ci < cinfo.m_num_components; ci++) { m_whole_image[ci] = JpegCommonBase.CreateBlocksArray( JpegUtils.jround_up(cinfo.Component_info[ci].Width_in_blocks, cinfo.Component_info[ci].H_samp_factor), JpegUtils.jround_up(cinfo.Component_info[ci].height_in_blocks, cinfo.Component_info[ci].V_samp_factor)); m_whole_image[ci].ErrorProcessor = cinfo; } } else { JpegBlock[] buffer = new JpegBlock[JpegConstants.CompressorMaxBlocksInMCU]; for (int i = 0; i < JpegConstants.CompressorMaxBlocksInMCU; i++) buffer[i] = new JpegBlock(); for (int i = 0; i < JpegConstants.CompressorMaxBlocksInMCU; i++) { m_MCU_buffer[i] = new JpegBlock[JpegConstants.CompressorMaxBlocksInMCU - i]; for (int j = i; j < JpegConstants.CompressorMaxBlocksInMCU; j++) m_MCU_buffer[i][j - i] = buffer[j]; } m_whole_image[0] = null; } } public virtual void start_pass(BufferMode pass_mode) { m_iMCU_row_num = 0; start_iMCU_row(); switch (pass_mode) { case BufferMode.PassThru: if (m_whole_image[0] != null) throw new Exception("Bogus buffer control mode"); break; case BufferMode.SaveAndPass: if (m_whole_image[0] == null) throw new Exception("Bogus buffer control mode"); break; case BufferMode.CrankDest: if (m_whole_image[0] == null) throw new Exception("Bogus buffer control mode"); break; default: throw new Exception("Bogus buffer control mode"); } m_passModeSetByLastStartPass = pass_mode; } public virtual bool compress_data(byte[][][] input_buf) { switch (m_passModeSetByLastStartPass) { case BufferMode.PassThru: return compressDataImpl(input_buf); case BufferMode.SaveAndPass: return compressFirstPass(input_buf); case BufferMode.CrankDest: return compressOutput(); } return false; } private bool compressDataImpl(byte[][][] input_buf) { int last_MCU_col = m_cinfo.m_MCUs_per_row - 1; int last_iMCU_row = m_cinfo.m_total_iMCU_rows - 1; for (int yoffset = m_MCU_vert_offset; yoffset < m_MCU_rows_per_iMCU_row; yoffset++) { for (int MCU_col_num = m_mcu_ctr; MCU_col_num <= last_MCU_col; MCU_col_num++) { int blkn = 0; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]]; int blockcnt = (MCU_col_num < last_MCU_col) ? componentInfo.MCU_width : componentInfo.last_col_width; int xpos = MCU_col_num * componentInfo.MCU_sample_width; int ypos = yoffset * JpegConstants.DCTSize; for (int yindex = 0; yindex < componentInfo.MCU_height; yindex++) { if (m_iMCU_row_num < last_iMCU_row || yoffset + yindex < componentInfo.last_row_height) { m_cinfo.m_fdct.forward_DCT(componentInfo.Quant_tbl_no, input_buf[componentInfo.Component_index], m_MCU_buffer[blkn], ypos, xpos, blockcnt); if (blockcnt < componentInfo.MCU_width) { for (int i = 0; i < (componentInfo.MCU_width - blockcnt); i++) Array.Clear(m_MCU_buffer[blkn + blockcnt][i].data, 0, m_MCU_buffer[blkn + blockcnt][i].data.Length); for (int bi = blockcnt; bi < componentInfo.MCU_width; bi++) m_MCU_buffer[blkn + bi][0][0] = m_MCU_buffer[blkn + bi - 1][0][0]; } } else { for (int i = 0; i < componentInfo.MCU_width; i++) Array.Clear(m_MCU_buffer[blkn][i].data, 0, m_MCU_buffer[blkn][i].data.Length); for (int bi = 0; bi < componentInfo.MCU_width; bi++) m_MCU_buffer[blkn + bi][0][0] = m_MCU_buffer[blkn - 1][0][0]; } blkn += componentInfo.MCU_width; ypos += JpegConstants.DCTSize; } } if (!m_cinfo.m_entropy.encode_mcu(m_MCU_buffer)) { m_MCU_vert_offset = yoffset; m_mcu_ctr = MCU_col_num; return false; } } m_mcu_ctr = 0; } m_iMCU_row_num++; start_iMCU_row(); return true; } private bool compressFirstPass(byte[][][] input_buf) { int last_iMCU_row = m_cinfo.m_total_iMCU_rows - 1; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[ci]; JpegBlock[][] buffer = m_whole_image[ci].Access(m_iMCU_row_num * componentInfo.V_samp_factor, componentInfo.V_samp_factor); int block_rows; if (m_iMCU_row_num < last_iMCU_row) { block_rows = componentInfo.V_samp_factor; } else { block_rows = componentInfo.height_in_blocks % componentInfo.V_samp_factor; if (block_rows == 0) block_rows = componentInfo.V_samp_factor; } int blocks_across = componentInfo.Width_in_blocks; int h_samp_factor = componentInfo.H_samp_factor; int ndummy = blocks_across % h_samp_factor; if (ndummy > 0) ndummy = h_samp_factor - ndummy; for (int block_row = 0; block_row < block_rows; block_row++) { m_cinfo.m_fdct.forward_DCT(componentInfo.Quant_tbl_no, input_buf[ci], buffer[block_row], block_row * JpegConstants.DCTSize, 0, blocks_across); if (ndummy > 0) { Array.Clear(buffer[block_row][blocks_across].data, 0, buffer[block_row][blocks_across].data.Length); short lastDC = buffer[block_row][blocks_across - 1][0]; for (int bi = 0; bi < ndummy; bi++) buffer[block_row][blocks_across + bi][0] = lastDC; } } if (m_iMCU_row_num == last_iMCU_row) { blocks_across += ndummy; int MCUs_across = blocks_across / h_samp_factor; for (int block_row = block_rows; block_row < componentInfo.V_samp_factor; block_row++) { for (int i = 0; i < blocks_across; i++) Array.Clear(buffer[block_row][i].data, 0, buffer[block_row][i].data.Length); int thisOffset = 0; int lastOffset = 0; for (int MCUindex = 0; MCUindex < MCUs_across; MCUindex++) { short lastDC = buffer[block_row - 1][lastOffset + h_samp_factor - 1][0]; for (int bi = 0; bi < h_samp_factor; bi++) buffer[block_row][thisOffset + bi][0] = lastDC; thisOffset += h_samp_factor; lastOffset += h_samp_factor; } } } } return compressOutput(); } private bool compressOutput() { JpegBlock[][][] buffer = new JpegBlock[JpegConstants.MaxComponentsInScan][][]; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]]; buffer[ci] = m_whole_image[componentInfo.Component_index].Access( m_iMCU_row_num * componentInfo.V_samp_factor, componentInfo.V_samp_factor); } for (int yoffset = m_MCU_vert_offset; yoffset < m_MCU_rows_per_iMCU_row; yoffset++) { for (int MCU_col_num = m_mcu_ctr; MCU_col_num < m_cinfo.m_MCUs_per_row; MCU_col_num++) { int blkn = 0; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]]; int start_col = MCU_col_num * componentInfo.MCU_width; for (int yindex = 0; yindex < componentInfo.MCU_height; yindex++) { for (int xindex = 0; xindex < componentInfo.MCU_width; xindex++) { int bufLength = buffer[ci][yindex + yoffset].Length; int start = start_col + xindex; m_MCU_buffer[blkn] = new JpegBlock[bufLength - start]; for (int j = start; j < bufLength; j++) m_MCU_buffer[blkn][j - start] = buffer[ci][yindex + yoffset][j]; blkn++; } } } if (!m_cinfo.m_entropy.encode_mcu(m_MCU_buffer)) { m_MCU_vert_offset = yoffset; m_mcu_ctr = MCU_col_num; return false; } } m_mcu_ctr = 0; } m_iMCU_row_num++; start_iMCU_row(); return true; } private void start_iMCU_row() { if (m_cinfo.m_comps_in_scan > 1) { m_MCU_rows_per_iMCU_row = 1; } else { if (m_iMCU_row_num < (m_cinfo.m_total_iMCU_rows - 1)) m_MCU_rows_per_iMCU_row = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[0]].V_samp_factor; else m_MCU_rows_per_iMCU_row = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[0]].last_row_height; } m_mcu_ctr = 0; m_MCU_vert_offset = 0; } } #endregion #region ColorConverter class ColorConverter { private const int SCALEBITS = 16; private const int CBCR_OFFSET = JpegConstants.MediumSampleValue << SCALEBITS; private const int ONE_HALF = 1 << (SCALEBITS - 1); private const int R_Y_OFF = 0; private const int G_Y_OFF = (1 * (JpegConstants.MaxSampleValue + 1)); private const int B_Y_OFF = (2 * (JpegConstants.MaxSampleValue + 1)); private const int R_CB_OFF = (3 * (JpegConstants.MaxSampleValue + 1)); private const int G_CB_OFF = (4 * (JpegConstants.MaxSampleValue + 1)); private const int B_CB_OFF = (5 * (JpegConstants.MaxSampleValue + 1)); private const int R_CR_OFF = B_CB_OFF; private const int G_CR_OFF = (6 * (JpegConstants.MaxSampleValue + 1)); private const int B_CR_OFF = (7 * (JpegConstants.MaxSampleValue + 1)); private const int TABLE_SIZE = (8 * (JpegConstants.MaxSampleValue + 1)); private JpegCompressor m_cinfo; private bool m_useNullStart; private bool m_useCmykYcckConvert; private bool m_useGrayscaleConvert; private bool m_useNullConvert; private bool m_useRgbGrayConvert; private bool m_useRgbYccConvert; private int[] m_rgb_ycc_tab; public ColorConverter(JpegCompressor cinfo) { m_cinfo = cinfo; m_useNullStart = true; switch (cinfo.m_in_color_space) { case ColorSpace.Grayscale: if (cinfo.m_input_components != 1) throw new Exception("Bogus input colorspace!"); break; case ColorSpace.RGB: case ColorSpace.YCbCr: if (cinfo.m_input_components != 3) throw new Exception("Bogus input colorspace!"); break; case ColorSpace.CMYK: case ColorSpace.YCCK: if (cinfo.m_input_components != 4) throw new Exception("Bogus input colorspace!"); break; default: if (cinfo.m_input_components < 1) throw new Exception("Bogus input colorspace!"); break; } clearConvertFlags(); switch (cinfo.m_jpeg_color_space) { case ColorSpace.Grayscale: { if (cinfo.m_num_components != 1) throw new Exception("Bogus Jpeg colorspace!"); if (cinfo.m_in_color_space == ColorSpace.Grayscale) { m_useGrayscaleConvert = true; } else if (cinfo.m_in_color_space == ColorSpace.RGB) { m_useNullStart = false; m_useRgbGrayConvert = true; } else if (cinfo.m_in_color_space == ColorSpace.YCbCr) { m_useGrayscaleConvert = true; } else { throw new Exception("Unsupported color conversion request."); } break; } case ColorSpace.RGB: { if (cinfo.m_num_components != 3) throw new Exception("Bogus Jpeg colorspace!"); if (cinfo.m_in_color_space == ColorSpace.RGB) { m_useNullConvert = true; } else { throw new Exception("Unsupported color conversion request."); } break; } case ColorSpace.YCbCr: { if (cinfo.m_num_components != 3) throw new Exception("Bogus Jpeg colorspace!"); if (cinfo.m_in_color_space == ColorSpace.RGB) { m_useNullStart = false; m_useRgbYccConvert = true; } else if (cinfo.m_in_color_space == ColorSpace.YCbCr) { m_useNullConvert = true; } else { throw new Exception("Unsupported color conversion request."); } break; } case ColorSpace.CMYK: { if (cinfo.m_num_components != 4) throw new Exception("Bogus Jpeg colorspace!"); if (cinfo.m_in_color_space == ColorSpace.CMYK) { m_useNullConvert = true; } else { throw new Exception("Unsupported color conversion request."); } break; } case ColorSpace.YCCK: { if (cinfo.m_num_components != 4) throw new Exception("Bogus Jpeg colorspace!"); if (cinfo.m_in_color_space == ColorSpace.CMYK) { m_useNullStart = false; m_useCmykYcckConvert = true; } else if (cinfo.m_in_color_space == ColorSpace.YCCK) { m_useNullConvert = true; } else { throw new Exception("Unsupported color conversion request."); } break; } default: if (cinfo.m_jpeg_color_space != cinfo.m_in_color_space || cinfo.m_num_components != cinfo.m_input_components) throw new Exception("Unsupported color conversion request."); m_useNullConvert = true; break; } } public void start_pass() { if (!m_useNullStart) rgb_ycc_start(); } public void color_convert(byte[][] input_buf, int input_row, byte[][][] output_buf, int output_row, int num_rows) { if (m_useCmykYcckConvert) cmyk_ycck_convert(input_buf, input_row, output_buf, output_row, num_rows); else if (m_useGrayscaleConvert) grayscale_convert(input_buf, input_row, output_buf, output_row, num_rows); else if (m_useRgbGrayConvert) rgb_gray_convert(input_buf, input_row, output_buf, output_row, num_rows); else if (m_useRgbYccConvert) rgb_ycc_convert(input_buf, input_row, output_buf, output_row, num_rows); else if (m_useNullConvert) null_convert(input_buf, input_row, output_buf, output_row, num_rows); else throw new Exception("Unsupported color conversion request."); } private void clearConvertFlags() { m_useCmykYcckConvert = false; m_useGrayscaleConvert = false; m_useNullConvert = false; m_useRgbGrayConvert = false; m_useRgbYccConvert = false; } private static int FIX(double x) { return (int)(x * (1L << SCALEBITS) + 0.5); } #region RGB to YCC private void rgb_ycc_start() { m_rgb_ycc_tab = new int[TABLE_SIZE]; for (int i = 0; i <= JpegConstants.MaxSampleValue; i++) { m_rgb_ycc_tab[i + R_Y_OFF] = FIX(0.29900) * i; m_rgb_ycc_tab[i + G_Y_OFF] = FIX(0.58700) * i; m_rgb_ycc_tab[i + B_Y_OFF] = FIX(0.11400) * i + ONE_HALF; m_rgb_ycc_tab[i + R_CB_OFF] = (-FIX(0.16874)) * i; m_rgb_ycc_tab[i + G_CB_OFF] = (-FIX(0.33126)) * i; m_rgb_ycc_tab[i + B_CB_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF - 1; m_rgb_ycc_tab[i + G_CR_OFF] = (-FIX(0.41869)) * i; m_rgb_ycc_tab[i + B_CR_OFF] = (-FIX(0.08131)) * i; } } private void rgb_ycc_convert(byte[][] input_buf, int input_row, byte[][][] output_buf, int output_row, int num_rows) { int num_cols = m_cinfo.m_image_width; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < num_cols; col++) { int r = input_buf[input_row + row][columnOffset + JpegConstants.Offset_RGB_Red]; int g = input_buf[input_row + row][columnOffset + JpegConstants.Offset_RGB_Green]; int b = input_buf[input_row + row][columnOffset + JpegConstants.Offset_RGB_Blue]; columnOffset += JpegConstants.RGB_PixelLength; output_buf[0][output_row][col] = (byte)((m_rgb_ycc_tab[r + R_Y_OFF] + m_rgb_ycc_tab[g + G_Y_OFF] + m_rgb_ycc_tab[b + B_Y_OFF]) >> SCALEBITS); output_buf[1][output_row][col] = (byte)((m_rgb_ycc_tab[r + R_CB_OFF] + m_rgb_ycc_tab[g + G_CB_OFF] + m_rgb_ycc_tab[b + B_CB_OFF]) >> SCALEBITS); output_buf[2][output_row][col] = (byte)((m_rgb_ycc_tab[r + R_CR_OFF] + m_rgb_ycc_tab[g + G_CR_OFF] + m_rgb_ycc_tab[b + B_CR_OFF]) >> SCALEBITS); } output_row++; } } #endregion #region RGB to Grayscale private void rgb_gray_convert(byte[][] input_buf, int input_row, byte[][][] output_buf, int output_row, int num_rows) { int num_cols = m_cinfo.m_image_width; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < num_cols; col++) { int r = input_buf[input_row + row][columnOffset + JpegConstants.Offset_RGB_Red]; int g = input_buf[input_row + row][columnOffset + JpegConstants.Offset_RGB_Green]; int b = input_buf[input_row + row][columnOffset + JpegConstants.Offset_RGB_Blue]; columnOffset += JpegConstants.RGB_PixelLength; output_buf[0][output_row][col] = (byte)((m_rgb_ycc_tab[r + R_Y_OFF] + m_rgb_ycc_tab[g + G_Y_OFF] + m_rgb_ycc_tab[b + B_Y_OFF]) >> SCALEBITS); } output_row++; } } #endregion #region CMYK to YCCK private void cmyk_ycck_convert(byte[][] input_buf, int input_row, byte[][][] output_buf, int output_row, int num_rows) { int num_cols = m_cinfo.m_image_width; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < num_cols; col++) { int r = JpegConstants.MaxSampleValue - input_buf[input_row + row][columnOffset]; int g = JpegConstants.MaxSampleValue - input_buf[input_row + row][columnOffset + 1]; int b = JpegConstants.MaxSampleValue - input_buf[input_row + row][columnOffset + 2]; output_buf[3][output_row][col] = input_buf[input_row + row][columnOffset + 3]; columnOffset += 4; output_buf[0][output_row][col] = (byte)((m_rgb_ycc_tab[r + R_Y_OFF] + m_rgb_ycc_tab[g + G_Y_OFF] + m_rgb_ycc_tab[b + B_Y_OFF]) >> SCALEBITS); output_buf[1][output_row][col] = (byte)((m_rgb_ycc_tab[r + R_CB_OFF] + m_rgb_ycc_tab[g + G_CB_OFF] + m_rgb_ycc_tab[b + B_CB_OFF]) >> SCALEBITS); output_buf[2][output_row][col] = (byte)((m_rgb_ycc_tab[r + R_CR_OFF] + m_rgb_ycc_tab[g + G_CR_OFF] + m_rgb_ycc_tab[b + B_CR_OFF]) >> SCALEBITS); } output_row++; } } #endregion #region Grayscale Conversion private void grayscale_convert(byte[][] input_buf, int input_row, byte[][][] output_buf, int output_row, int num_rows) { int num_cols = m_cinfo.m_image_width; int instride = m_cinfo.m_input_components; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < num_cols; col++) { output_buf[0][output_row][col] = input_buf[input_row + row][columnOffset]; columnOffset += instride; } output_row++; } } #endregion #region Null Conversion private void null_convert(byte[][] input_buf, int input_row, byte[][][] output_buf, int output_row, int num_rows) { int nc = m_cinfo.m_num_components; int num_cols = m_cinfo.m_image_width; for (int row = 0; row < num_rows; row++) { for (int ci = 0; ci < nc; ci++) { int columnOffset = 0; for (int col = 0; col < num_cols; col++) { output_buf[ci][output_row][col] = input_buf[input_row + row][columnOffset + ci]; columnOffset += nc; } } output_row++; } } #endregion } #endregion #region ColorDeconverter class ColorDeconverter { private const int SCALEBITS = 16; private const int ONE_HALF = 1 << (SCALEBITS - 1); private enum ColorConverter { grayscale_converter, ycc_rgb_converter, gray_rgb_converter, null_converter, ycck_cmyk_converter } private ColorConverter m_converter; private JpegDecompressor m_cinfo; private int[] m_perComponentOffsets; private int[] m_Cr_r_tab; private int[] m_Cb_b_tab; private int[] m_Cr_g_tab; private int[] m_Cb_g_tab; public ColorDeconverter(JpegDecompressor cinfo) { m_cinfo = cinfo; switch (cinfo.m_jpeg_color_space) { case ColorSpace.Grayscale: if (cinfo.m_num_components != 1) throw new Exception("Bogus Jpeg colorspace!"); break; case ColorSpace.RGB: case ColorSpace.YCbCr: if (cinfo.m_num_components != 3) throw new Exception("Bogus Jpeg colorspace!"); break; case ColorSpace.CMYK: case ColorSpace.YCCK: if (cinfo.m_num_components != 4) throw new Exception("Bogus Jpeg colorspace!"); break; default: if (cinfo.m_num_components < 1) throw new Exception("Bogus Jpeg colorspace!"); break; } switch (cinfo.m_out_color_space) { case ColorSpace.Grayscale: cinfo.m_out_color_components = 1; if (cinfo.m_jpeg_color_space == ColorSpace.Grayscale || cinfo.m_jpeg_color_space == ColorSpace.YCbCr) { m_converter = ColorConverter.grayscale_converter; for (int ci = 1; ci < cinfo.m_num_components; ci++) cinfo.Comp_info[ci].component_needed = false; } else throw new Exception("Unsupported color conversion request."); break; case ColorSpace.RGB: cinfo.m_out_color_components = JpegConstants.RGB_PixelLength; if (cinfo.m_jpeg_color_space == ColorSpace.YCbCr) { m_converter = ColorConverter.ycc_rgb_converter; build_ycc_rgb_table(); } else if (cinfo.m_jpeg_color_space == ColorSpace.Grayscale) m_converter = ColorConverter.gray_rgb_converter; else if (cinfo.m_jpeg_color_space == ColorSpace.RGB) m_converter = ColorConverter.null_converter; else throw new Exception("Unsupported color conversion request."); break; case ColorSpace.CMYK: cinfo.m_out_color_components = 4; if (cinfo.m_jpeg_color_space == ColorSpace.YCCK) { m_converter = ColorConverter.ycck_cmyk_converter; build_ycc_rgb_table(); } else if (cinfo.m_jpeg_color_space == ColorSpace.CMYK) m_converter = ColorConverter.null_converter; else throw new Exception("Unsupported color conversion request."); break; default: if (cinfo.m_out_color_space == cinfo.m_jpeg_color_space) { cinfo.m_out_color_components = cinfo.m_num_components; m_converter = ColorConverter.null_converter; } else { throw new Exception("Unsupported color conversion request."); } break; } if (cinfo.m_quantize_colors) cinfo.m_output_components = 1; else cinfo.m_output_components = cinfo.m_out_color_components; } public void color_convert(ComponentBuffer[] input_buf, int[] perComponentOffsets, int input_row, byte[][] output_buf, int output_row, int num_rows) { m_perComponentOffsets = perComponentOffsets; switch (m_converter) { case ColorConverter.grayscale_converter: grayscale_convert(input_buf, input_row, output_buf, output_row, num_rows); break; case ColorConverter.ycc_rgb_converter: ycc_rgb_convert(input_buf, input_row, output_buf, output_row, num_rows); break; case ColorConverter.gray_rgb_converter: gray_rgb_convert(input_buf, input_row, output_buf, output_row, num_rows); break; case ColorConverter.null_converter: null_convert(input_buf, input_row, output_buf, output_row, num_rows); break; case ColorConverter.ycck_cmyk_converter: ycck_cmyk_convert(input_buf, input_row, output_buf, output_row, num_rows); break; default: throw new Exception("Unsupported color conversion request."); } } private static int FIX(double x) { return (int)(x * (1L << SCALEBITS) + 0.5); } #region YCbCr to RGB private void build_ycc_rgb_table() { m_Cr_r_tab = new int[JpegConstants.MaxSampleValue + 1]; m_Cb_b_tab = new int[JpegConstants.MaxSampleValue + 1]; m_Cr_g_tab = new int[JpegConstants.MaxSampleValue + 1]; m_Cb_g_tab = new int[JpegConstants.MaxSampleValue + 1]; for (int i = 0, x = -JpegConstants.MediumSampleValue; i <= JpegConstants.MaxSampleValue; i++, x++) { m_Cr_r_tab[i] = JpegUtils.RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); m_Cb_b_tab[i] = JpegUtils.RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); m_Cr_g_tab[i] = (-FIX(0.71414)) * x; m_Cb_g_tab[i] = (-FIX(0.34414)) * x + ONE_HALF; } } private void ycc_rgb_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { int component0RowOffset = m_perComponentOffsets[0]; int component1RowOffset = m_perComponentOffsets[1]; int component2RowOffset = m_perComponentOffsets[2]; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < m_cinfo.m_output_width; col++) { int y = input_buf[0][input_row + component0RowOffset][col]; int cb = input_buf[1][input_row + component1RowOffset][col]; int cr = input_buf[2][input_row + component2RowOffset][col]; output_buf[output_row + row][columnOffset + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + m_Cr_r_tab[cr]]; output_buf[output_row + row][columnOffset + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS)]; output_buf[output_row + row][columnOffset + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + m_Cb_b_tab[cb]]; columnOffset += JpegConstants.RGB_PixelLength; } input_row++; } } #endregion #region YCCK to CMYK private void ycck_cmyk_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { int component0RowOffset = m_perComponentOffsets[0]; int component1RowOffset = m_perComponentOffsets[1]; int component2RowOffset = m_perComponentOffsets[2]; int component3RowOffset = m_perComponentOffsets[3]; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset; int num_cols = m_cinfo.m_output_width; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < num_cols; col++) { int y = input_buf[0][input_row + component0RowOffset][col]; int cb = input_buf[1][input_row + component1RowOffset][col]; int cr = input_buf[2][input_row + component2RowOffset][col]; output_buf[output_row + row][columnOffset] = limit[limitOffset + JpegConstants.MaxSampleValue - (y + m_Cr_r_tab[cr])]; output_buf[output_row + row][columnOffset + 1] = limit[limitOffset + JpegConstants.MaxSampleValue - (y + JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS))]; output_buf[output_row + row][columnOffset + 2] = limit[limitOffset + JpegConstants.MaxSampleValue - (y + m_Cb_b_tab[cb])]; output_buf[output_row + row][columnOffset + 3] = input_buf[3][input_row + component3RowOffset][col]; columnOffset += 4; } input_row++; } } #endregion #region Grayscale to RGB private void gray_rgb_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { int component0RowOffset = m_perComponentOffsets[0]; int component1RowOffset = m_perComponentOffsets[1]; int component2RowOffset = m_perComponentOffsets[2]; int num_cols = m_cinfo.m_output_width; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < num_cols; col++) { output_buf[output_row + row][columnOffset + JpegConstants.Offset_RGB_Red] = input_buf[0][input_row + component0RowOffset][col]; output_buf[output_row + row][columnOffset + JpegConstants.Offset_RGB_Green] = input_buf[0][input_row + component1RowOffset][col]; output_buf[output_row + row][columnOffset + JpegConstants.Offset_RGB_Blue] = input_buf[0][input_row + component2RowOffset][col]; columnOffset += JpegConstants.RGB_PixelLength; } input_row++; } } #endregion #region Grayscale Conversion private void grayscale_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { JpegUtils.jcopy_sample_rows(input_buf[0], input_row + m_perComponentOffsets[0], output_buf, output_row, num_rows, m_cinfo.m_output_width); } #endregion #region Null Conversion private void null_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { for (int row = 0; row < num_rows; row++) { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { int columnIndex = 0; int componentOffset = 0; int perComponentOffset = m_perComponentOffsets[ci]; for (int count = m_cinfo.m_output_width; count > 0; count--) { output_buf[output_row + row][ci + componentOffset] = input_buf[ci][input_row + perComponentOffset][columnIndex]; componentOffset += m_cinfo.m_num_components; columnIndex++; } } input_row++; } } #endregion } #endregion #region ColorQuantizer interface ColorQuantizer { void start_pass(bool is_pre_scan); void color_quantize(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows); void finish_pass(); void new_color_map(); } #endregion #region ComponentBuffer class ComponentBuffer { private byte[][] m_buffer; private int[] m_funnyIndices; private int m_funnyOffset; public ComponentBuffer() { } public ComponentBuffer(byte[][] buf, int[] funnyIndices, int funnyOffset) { SetBuffer(buf, funnyIndices, funnyOffset); } public void SetBuffer(byte[][] buf, int[] funnyIndices, int funnyOffset) { m_buffer = buf; m_funnyIndices = funnyIndices; m_funnyOffset = funnyOffset; } public byte[] this[int i] { get { if (m_funnyIndices == null) return m_buffer[i]; return m_buffer[m_funnyIndices[i + m_funnyOffset]]; } } } #endregion #region CompressionParameters public class CompressionParameters { private int m_quality = 75; private int m_smoothingFactor; private bool m_simpleProgressive; public CompressionParameters() { } internal CompressionParameters(CompressionParameters parameters) { if (parameters == null) throw new ArgumentNullException("parameters"); m_quality = parameters.m_quality; m_smoothingFactor = parameters.m_smoothingFactor; m_simpleProgressive = parameters.m_simpleProgressive; } public override bool Equals(object obj) { CompressionParameters parameters = obj as CompressionParameters; if (parameters == null) return false; return (m_quality == parameters.m_quality && m_smoothingFactor == parameters.m_smoothingFactor && m_simpleProgressive == parameters.m_simpleProgressive); } public override int GetHashCode() { return base.GetHashCode(); } public int Quality { get { return m_quality; } set { m_quality = value; } } public int SmoothingFactor { get { return m_smoothingFactor; } set { m_smoothingFactor = value; } } public bool SimpleProgressive { get { return m_simpleProgressive; } set { m_simpleProgressive = value; } } } #endregion #region DecompressionParameters class DecompressionParameters { private ColorSpace m_outColorspace = ColorSpace.Unknown; private int m_scaleNumerator = 1; private int m_scaleDenominator = 1; private bool m_bufferedImage; private bool m_rawDataOut; private DCTMethod m_dctMethod = (DCTMethod)JpegConstants.DefaultDCTMethod; private DitherMode m_ditherMode = DitherMode.FloydStein; private bool m_doFancyUpsampling = true; private bool m_doBlockSmoothing = true; private bool m_quantizeColors; private bool m_twoPassQuantize = true; private int m_desiredNumberOfColors = 256; private bool m_enableOnePassQuantizer; private bool m_enableExternalQuant; private bool m_enableTwoPassQuantizer; private int m_traceLevel; public int TraceLevel { get { return m_traceLevel; } set { m_traceLevel = value; } } public ColorSpace OutColorspace { get { return m_outColorspace; } set { m_outColorspace = value; } } public int ScaleNumerator { get { return m_scaleNumerator; } set { m_scaleNumerator = value; } } public int ScaleDenominator { get { return m_scaleDenominator; } set { m_scaleDenominator = value; } } public bool BufferedImage { get { return m_bufferedImage; } set { m_bufferedImage = value; } } public bool RawDataOut { get { return m_rawDataOut; } set { m_rawDataOut = value; } } public DCTMethod DCTMethod { get { return m_dctMethod; } set { m_dctMethod = value; } } public bool DoFancyUpsampling { get { return m_doFancyUpsampling; } set { m_doFancyUpsampling = value; } } public bool DoBlockSmoothing { get { return m_doBlockSmoothing; } set { m_doBlockSmoothing = value; } } public bool QuantizeColors { get { return m_quantizeColors; } set { m_quantizeColors = value; } } public DitherMode DitherMode { get { return m_ditherMode; } set { m_ditherMode = value; } } public bool TwoPassQuantize { get { return m_twoPassQuantize; } set { m_twoPassQuantize = value; } } public int DesiredNumberOfColors { get { return m_desiredNumberOfColors; } set { m_desiredNumberOfColors = value; } } public bool EnableOnePassQuantizer { get { return m_enableOnePassQuantizer; } set { m_enableOnePassQuantizer = value; } } public bool EnableExternalQuant { get { return m_enableExternalQuant; } set { m_enableExternalQuant = value; } } public bool EnableTwoPassQuantizer { get { return m_enableTwoPassQuantizer; } set { m_enableTwoPassQuantizer = value; } } } #endregion #region DecompressorToJpegImage class DecompressorToJpegImage : IDecompressor { private JpegImage m_jpegImage; internal DecompressorToJpegImage(JpegImage jpegImage) { m_jpegImage = jpegImage; } public override Stream Output { get { return null; } } public override void SetImageAttributes(LoadedImageAttributes parameters) { m_jpegImage.Width = parameters.Width; m_jpegImage.Height = parameters.Height; m_jpegImage.BitsPerComponent = 8; m_jpegImage.ComponentsPerSample = (byte)parameters.ComponentsPerSample; m_jpegImage.Colorspace = parameters.Colorspace; } public override void BeginWrite() { } public override void ProcessPixelsRow(byte[] row) { SampleRow samplesRow = new SampleRow(row, m_jpegImage.Width, m_jpegImage.BitsPerComponent, m_jpegImage.ComponentsPerSample); m_jpegImage.addSampleRow(samplesRow); } public override void EndWrite() { } } #endregion #region DerivedTable class DerivedTable { public int[] maxcode = new int[18]; public int[] valoffset = new int[17]; public JpegHuffmanTable pub; public int[] look_nbits = new int[1 << JpegConstants.HuffmanLookaheadDistance]; public byte[] look_sym = new byte[1 << JpegConstants.HuffmanLookaheadDistance]; } #endregion #region DestinationManager public abstract class DestinationManager { private byte[] m_buffer; private int m_position; private int m_free_in_buffer; public abstract void init_destination(); public abstract bool empty_output_buffer(); public abstract void term_destination(); public virtual bool emit_byte(int val) { m_buffer[m_position] = (byte)val; m_position++; if (--m_free_in_buffer == 0) { if (!empty_output_buffer()) return false; } return true; } protected void initInternalBuffer(byte[] buffer, int offset) { m_buffer = buffer; m_free_in_buffer = buffer.Length - offset; m_position = offset; } protected int freeInBuffer { get { return m_free_in_buffer; } } } #endregion #region DestinationManagerImpl class DestinationManagerImpl : DestinationManager { private const int OUTPUT_BUF_SIZE = 4096; private JpegCompressor m_cinfo; private Stream m_outfile; private byte[] m_buffer; public DestinationManagerImpl(JpegCompressor cinfo, Stream alreadyOpenFile) { m_cinfo = cinfo; m_outfile = alreadyOpenFile; } public override void init_destination() { m_buffer = new byte[OUTPUT_BUF_SIZE]; initInternalBuffer(m_buffer, 0); } public override bool empty_output_buffer() { writeBuffer(m_buffer.Length); initInternalBuffer(m_buffer, 0); return true; } public override void term_destination() { int datacount = m_buffer.Length - freeInBuffer; if (datacount > 0) writeBuffer(datacount); m_outfile.Flush(); } private void writeBuffer(int dataCount) { try { m_outfile.Write(m_buffer, 0, dataCount); } #pragma warning disable 168 catch (IOException e) { throw new Exception("Output file write error --- out of disk space?"); } catch (NotSupportedException e) { throw new Exception("Output file write error --- out of disk space?"); } catch (ObjectDisposedException e) { throw new Exception("Output file write error --- out of disk space?"); } #pragma warning restore 168 } } #endregion #region Enumerations enum BufferMode { PassThru, SaveSource, CrankDest, SaveAndPass } public enum DensityUnit { Unknown = 0, DotsInch = 1, DotsCm = 2 } public enum DitherMode { None, Ordered, FloydStein } public enum ReadResult { Suspended = 0, Header_Ok = 1, Header_Tables_Only = 2, Reached_SOS = 3, Reached_EOI = 4, Row_Completed = 5, Scan_Completed = 6 } public enum ColorSpace { Unknown, Grayscale, RGB, YCbCr, CMYK, YCCK } public enum DCTMethod { IntSlow, IntFast, Float } #endregion #region HuffEntropyDecoder class HuffEntropyDecoder : JpegEntropyDecoder { private class savable_state { public int[] last_dc_val = new int[JpegConstants.MaxComponentsInScan]; /* last DC coef for each component */ public void Assign(savable_state ss) { Buffer.BlockCopy(ss.last_dc_val, 0, last_dc_val, 0, last_dc_val.Length * sizeof(int)); } } private SavedBitreadState m_bitstate; private savable_state m_saved = new savable_state(); private int m_restarts_to_go; private DerivedTable[] m_dc_derived_tbls = new DerivedTable[JpegConstants.NumberOfHuffmanTables]; private DerivedTable[] m_ac_derived_tbls = new DerivedTable[JpegConstants.NumberOfHuffmanTables]; private DerivedTable[] m_dc_cur_tbls = new DerivedTable[JpegConstants.DecompressorMaxBlocksInMCU]; private DerivedTable[] m_ac_cur_tbls = new DerivedTable[JpegConstants.DecompressorMaxBlocksInMCU]; private bool[] m_dc_needed = new bool[JpegConstants.DecompressorMaxBlocksInMCU]; private bool[] m_ac_needed = new bool[JpegConstants.DecompressorMaxBlocksInMCU]; public HuffEntropyDecoder(JpegDecompressor cinfo) { m_cinfo = cinfo; for (int i = 0; i < JpegConstants.NumberOfHuffmanTables; i++) m_dc_derived_tbls[i] = m_ac_derived_tbls[i] = null; } public override void start_pass() { for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]]; int dctbl = componentInfo.Dc_tbl_no; int actbl = componentInfo.Ac_tbl_no; jpeg_make_d_derived_tbl(true, dctbl, ref m_dc_derived_tbls[dctbl]); jpeg_make_d_derived_tbl(false, actbl, ref m_ac_derived_tbls[actbl]); m_saved.last_dc_val[ci] = 0; } for (int blkn = 0; blkn < m_cinfo.m_blocks_in_MCU; blkn++) { int ci = m_cinfo.m_MCU_membership[blkn]; JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]]; m_dc_cur_tbls[blkn] = m_dc_derived_tbls[componentInfo.Dc_tbl_no]; m_ac_cur_tbls[blkn] = m_ac_derived_tbls[componentInfo.Ac_tbl_no]; if (componentInfo.component_needed) { m_dc_needed[blkn] = true; m_ac_needed[blkn] = (componentInfo.DCT_scaled_size > 1); } else { m_dc_needed[blkn] = m_ac_needed[blkn] = false; } } m_bitstate.bits_left = 0; m_bitstate.get_buffer = 0; m_insufficient_data = false; m_restarts_to_go = m_cinfo.m_restart_interval; } public override bool decode_mcu(JpegBlock[] MCU_data) { if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { if (!process_restart()) return false; } } if (!m_insufficient_data) { int get_buffer; int bits_left; WorkingBitreadState br_state = new WorkingBitreadState(); BITREAD_LOAD_STATE(m_bitstate, out get_buffer, out bits_left, ref br_state); savable_state state = new savable_state(); state.Assign(m_saved); for (int blkn = 0; blkn < m_cinfo.m_blocks_in_MCU; blkn++) { int s; if (!HUFF_DECODE(out s, ref br_state, m_dc_cur_tbls[blkn], ref get_buffer, ref bits_left)) return false; if (s != 0) { if (!CHECK_BIT_BUFFER(ref br_state, s, ref get_buffer, ref bits_left)) return false; int r = GET_BITS(s, get_buffer, ref bits_left); s = HUFF_EXTEND(r, s); } if (m_dc_needed[blkn]) { int ci = m_cinfo.m_MCU_membership[blkn]; s += state.last_dc_val[ci]; state.last_dc_val[ci] = s; MCU_data[blkn][0] = (short)s; } if (m_ac_needed[blkn]) { for (int k = 1; k < JpegConstants.DCTSize2; k++) { if (!HUFF_DECODE(out s, ref br_state, m_ac_cur_tbls[blkn], ref get_buffer, ref bits_left)) return false; int r = s >> 4; s &= 15; if (s != 0) { k += r; if (!CHECK_BIT_BUFFER(ref br_state, s, ref get_buffer, ref bits_left)) return false; r = GET_BITS(s, get_buffer, ref bits_left); s = HUFF_EXTEND(r, s); MCU_data[blkn][JpegUtils.jpeg_natural_order[k]] = (short)s; } else { if (r != 15) break; k += 15; } } } else { for (int k = 1; k < JpegConstants.DCTSize2; k++) { if (!HUFF_DECODE(out s, ref br_state, m_ac_cur_tbls[blkn], ref get_buffer, ref bits_left)) return false; int r = s >> 4; s &= 15; if (s != 0) { k += r; if (!CHECK_BIT_BUFFER(ref br_state, s, ref get_buffer, ref bits_left)) return false; DROP_BITS(s, ref bits_left); } else { if (r != 15) break; k += 15; } } } } BITREAD_SAVE_STATE(ref m_bitstate, get_buffer, bits_left); m_saved.Assign(state); } m_restarts_to_go--; return true; } private bool process_restart() { m_cinfo.m_marker.SkipBytes(m_bitstate.bits_left / 8); m_bitstate.bits_left = 0; if (!m_cinfo.m_marker.read_restart_marker()) return false; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) m_saved.last_dc_val[ci] = 0; m_restarts_to_go = m_cinfo.m_restart_interval; if (m_cinfo.m_unread_marker == 0) m_insufficient_data = false; return true; } } #endregion #region HuffEntropyEncoder class HuffEntropyEncoder : JpegEntropyEncoder { private class savable_state { public int put_buffer; public int put_bits; public int[] last_dc_val = new int[JpegConstants.MaxComponentsInScan]; } private bool m_gather_statistics; private savable_state m_saved = new savable_state(); private int m_restarts_to_go; private int m_next_restart_num; private c_derived_tbl[] m_dc_derived_tbls = new c_derived_tbl[JpegConstants.NumberOfHuffmanTables]; private c_derived_tbl[] m_ac_derived_tbls = new c_derived_tbl[JpegConstants.NumberOfHuffmanTables]; private long[][] m_dc_count_ptrs = new long[JpegConstants.NumberOfHuffmanTables][]; private long[][] m_ac_count_ptrs = new long[JpegConstants.NumberOfHuffmanTables][]; public HuffEntropyEncoder(JpegCompressor cinfo) { m_cinfo = cinfo; for (int i = 0; i < JpegConstants.NumberOfHuffmanTables; i++) { m_dc_derived_tbls[i] = m_ac_derived_tbls[i] = null; m_dc_count_ptrs[i] = m_ac_count_ptrs[i] = null; } } public override void start_pass(bool gather_statistics) { m_gather_statistics = gather_statistics; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { int dctbl = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]].Dc_tbl_no; int actbl = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]].Ac_tbl_no; if (m_gather_statistics) { if (dctbl < 0 || dctbl >= JpegConstants.NumberOfHuffmanTables) throw new Exception(String.Format("Huffman table 0x{0:X2} was not defined", dctbl)); if (actbl < 0 || actbl >= JpegConstants.NumberOfHuffmanTables) throw new Exception(String.Format("Huffman table 0x{0:X2} was not defined", actbl)); if (m_dc_count_ptrs[dctbl] == null) m_dc_count_ptrs[dctbl] = new long[257]; Array.Clear(m_dc_count_ptrs[dctbl], 0, m_dc_count_ptrs[dctbl].Length); if (m_ac_count_ptrs[actbl] == null) m_ac_count_ptrs[actbl] = new long[257]; Array.Clear(m_ac_count_ptrs[actbl], 0, m_ac_count_ptrs[actbl].Length); } else { jpeg_make_c_derived_tbl(true, dctbl, ref m_dc_derived_tbls[dctbl]); jpeg_make_c_derived_tbl(false, actbl, ref m_ac_derived_tbls[actbl]); } m_saved.last_dc_val[ci] = 0; } m_saved.put_buffer = 0; m_saved.put_bits = 0; m_restarts_to_go = m_cinfo.m_restart_interval; m_next_restart_num = 0; } public override bool encode_mcu(JpegBlock[][] MCU_data) { if (m_gather_statistics) return encode_mcu_gather(MCU_data); return encode_mcu_huff(MCU_data); } public override void finish_pass() { if (m_gather_statistics) finish_pass_gather(); else finish_pass_huff(); } private bool encode_mcu_huff(JpegBlock[][] MCU_data) { savable_state state; state = m_saved; if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { if (!emit_restart(state, m_next_restart_num)) return false; } } for (int blkn = 0; blkn < m_cinfo.m_blocks_in_MCU; blkn++) { int ci = m_cinfo.m_MCU_membership[blkn]; if (!encode_one_block(state, MCU_data[blkn][0].data, state.last_dc_val[ci], m_dc_derived_tbls[m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]].Dc_tbl_no], m_ac_derived_tbls[m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]].Ac_tbl_no])) { return false; } state.last_dc_val[ci] = MCU_data[blkn][0][0]; } m_saved = state; if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { m_restarts_to_go = m_cinfo.m_restart_interval; m_next_restart_num++; m_next_restart_num &= 7; } m_restarts_to_go--; } return true; } private void finish_pass_huff() { savable_state state; state = m_saved; if (!flush_bits(state)) throw new Exception("Suspension not allowed here!"); m_saved = state; } private bool encode_mcu_gather(JpegBlock[][] MCU_data) { if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) m_saved.last_dc_val[ci] = 0; m_restarts_to_go = m_cinfo.m_restart_interval; } m_restarts_to_go--; } for (int blkn = 0; blkn < m_cinfo.m_blocks_in_MCU; blkn++) { int ci = m_cinfo.m_MCU_membership[blkn]; htest_one_block(MCU_data[blkn][0].data, m_saved.last_dc_val[ci], m_dc_count_ptrs[m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]].Dc_tbl_no], m_ac_count_ptrs[m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]].Ac_tbl_no]); m_saved.last_dc_val[ci] = MCU_data[blkn][0][0]; } return true; } private void finish_pass_gather() { bool[] did_dc = new bool[JpegConstants.NumberOfHuffmanTables]; bool[] did_ac = new bool[JpegConstants.NumberOfHuffmanTables]; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { int dctbl = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]].Dc_tbl_no; if (!did_dc[dctbl]) { if (m_cinfo.m_dc_huff_tbl_ptrs[dctbl] == null) m_cinfo.m_dc_huff_tbl_ptrs[dctbl] = new JpegHuffmanTable(); jpeg_gen_optimal_table(m_cinfo.m_dc_huff_tbl_ptrs[dctbl], m_dc_count_ptrs[dctbl]); did_dc[dctbl] = true; } int actbl = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]].Ac_tbl_no; if (!did_ac[actbl]) { if (m_cinfo.m_ac_huff_tbl_ptrs[actbl] == null) m_cinfo.m_ac_huff_tbl_ptrs[actbl] = new JpegHuffmanTable(); jpeg_gen_optimal_table(m_cinfo.m_ac_huff_tbl_ptrs[actbl], m_ac_count_ptrs[actbl]); did_ac[actbl] = true; } } } private bool encode_one_block(savable_state state, short[] block, int last_dc_val, c_derived_tbl dctbl, c_derived_tbl actbl) { int temp = block[0] - last_dc_val; int temp2 = temp; if (temp < 0) { temp = -temp; temp2--; } int nbits = 0; while (temp != 0) { nbits++; temp >>= 1; } if (nbits > MAX_HUFFMAN_COEF_BITS + 1) throw new Exception("DCT coefficient is out of range!"); if (!emit_bits(state, dctbl.ehufco[nbits], dctbl.ehufsi[nbits])) return false; if (nbits != 0) { if (!emit_bits(state, temp2, nbits)) return false; } int r = 0; for (int k = 1; k < JpegConstants.DCTSize2; k++) { temp = block[JpegUtils.jpeg_natural_order[k]]; if (temp == 0) { r++; } else { while (r > 15) { if (!emit_bits(state, actbl.ehufco[0xF0], actbl.ehufsi[0xF0])) return false; r -= 16; } temp2 = temp; if (temp < 0) { temp = -temp; temp2--; } nbits = 1; while ((temp >>= 1) != 0) nbits++; if (nbits > MAX_HUFFMAN_COEF_BITS) throw new Exception("DCT coefficient is out of range!"); int i = (r << 4) + nbits; if (!emit_bits(state, actbl.ehufco[i], actbl.ehufsi[i])) return false; if (!emit_bits(state, temp2, nbits)) return false; r = 0; } } if (r > 0) { if (!emit_bits(state, actbl.ehufco[0], actbl.ehufsi[0])) return false; } return true; } private void htest_one_block(short[] block, int last_dc_val, long[] dc_counts, long[] ac_counts) { int temp = block[0] - last_dc_val; if (temp < 0) temp = -temp; int nbits = 0; while (temp != 0) { nbits++; temp >>= 1; } if (nbits > MAX_HUFFMAN_COEF_BITS + 1) throw new Exception("DCT coefficient is out of range!"); dc_counts[nbits]++; int r = 0; for (int k = 1; k < JpegConstants.DCTSize2; k++) { temp = block[JpegUtils.jpeg_natural_order[k]]; if (temp == 0) { r++; } else { while (r > 15) { ac_counts[0xF0]++; r -= 16; } if (temp < 0) temp = -temp; nbits = 1; while ((temp >>= 1) != 0) nbits++; if (nbits > MAX_HUFFMAN_COEF_BITS) throw new Exception("DCT coefficient is out of range!"); ac_counts[(r << 4) + nbits]++; r = 0; } } if (r > 0) ac_counts[0]++; } private bool emit_byte(int val) { return m_cinfo.m_dest.emit_byte(val); } private bool emit_bits(savable_state state, int code, int size) { int put_buffer = code; int put_bits = state.put_bits; if (size == 0) throw new Exception("Missing Huffman code table entry"); put_buffer &= (1 << size) - 1; put_bits += size; put_buffer <<= 24 - put_bits; put_buffer |= state.put_buffer; while (put_bits >= 8) { int c = (put_buffer >> 16) & 0xFF; if (!emit_byte(c)) return false; if (c == 0xFF) { if (!emit_byte(0)) return false; } put_buffer <<= 8; put_bits -= 8; } state.put_buffer = put_buffer; state.put_bits = put_bits; return true; } private bool flush_bits(savable_state state) { if (!emit_bits(state, 0x7F, 7)) return false; state.put_buffer = 0; state.put_bits = 0; return true; } private bool emit_restart(savable_state state, int restart_num) { if (!flush_bits(state)) return false; if (!emit_byte(0xFF)) return false; if (!emit_byte((int)(JpegMarkerType.RST0 + restart_num))) return false; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) state.last_dc_val[ci] = 0; return true; } } #endregion #region IDecompressDestination abstract class IDecompressor { public abstract Stream Output { get; } public abstract void SetImageAttributes(LoadedImageAttributes parameters); public abstract void BeginWrite(); public abstract void ProcessPixelsRow(byte[] row); public abstract void EndWrite(); } class LoadedImageAttributes { private ColorSpace m_colorspace; private bool m_quantizeColors; private int m_width; private int m_height; private int m_componentsPerSample; private int m_components; private int m_actualNumberOfColors; private byte[][] m_colormap; private DensityUnit m_densityUnit; private int m_densityX; private int m_densityY; public ColorSpace Colorspace { get { return m_colorspace; } internal set { m_colorspace = value; } } public bool QuantizeColors { get { return m_quantizeColors; } internal set { m_quantizeColors = value; } } public int Width { get { return m_width; } internal set { m_width = value; } } public int Height { get { return m_height; } internal set { m_height = value; } } public int ComponentsPerSample { get { return m_componentsPerSample; } internal set { m_componentsPerSample = value; } } public int Components { get { return m_components; } internal set { m_components = value; } } public int ActualNumberOfColors { get { return m_actualNumberOfColors; } internal set { m_actualNumberOfColors = value; } } public byte[][] Colormap { get { return m_colormap; } internal set { m_colormap = value; } } public DensityUnit DensityUnit { get { return m_densityUnit; } internal set { m_densityUnit = value; } } public int DensityX { get { return m_densityX; } internal set { m_densityX = value; } } public int DensityY { get { return m_densityY; } internal set { m_densityY = value; } } } #endregion #region IRawImage abstract class IRawImage { public abstract int Width { get; } public abstract int Height { get; } public abstract ColorSpace Colorspace { get; } public abstract int ComponentsPerPixel { get; } public abstract void BeginRead(); public abstract byte[] GetPixelRow(); public abstract void EndRead(); } #endregion #region Jpeg class Jpeg { private JpegCompressor m_compressor = new JpegCompressor(); private JpegDecompressor m_decompressor = new JpegDecompressor(); private CompressionParameters m_compressionParameters = new CompressionParameters(); private DecompressionParameters m_decompressionParameters = new DecompressionParameters(); public CompressionParameters CompressionParameters { get { return m_compressionParameters; } set { if (value == null) throw new ArgumentNullException("value"); m_compressionParameters = value; } } public DecompressionParameters DecompressionParameters { get { return m_decompressionParameters; } set { if (value == null) throw new ArgumentNullException("value"); m_decompressionParameters = value; } } public void Compress(IRawImage source, Stream output) { if (source == null) throw new ArgumentNullException("source"); if (output == null) throw new ArgumentNullException("output"); m_compressor.Image_width = source.Width; m_compressor.Image_height = source.Height; m_compressor.In_color_space = (ColorSpace)source.Colorspace; m_compressor.Input_components = source.ComponentsPerPixel; m_compressor.jpeg_set_defaults(); applyParameters(m_compressionParameters); m_compressor.jpeg_stdio_dest(output); m_compressor.jpeg_start_compress(true); source.BeginRead(); while (m_compressor.Next_scanline < m_compressor.Image_height) { byte[] row = source.GetPixelRow(); if (row == null) { throw new InvalidDataException("Row of pixels is null"); } byte[][] rowForDecompressor = new byte[1][]; rowForDecompressor[0] = row; m_compressor.jpeg_write_scanlines(rowForDecompressor, 1); } source.EndRead(); m_compressor.jpeg_finish_compress(); } public void Decompress(Stream jpeg, IDecompressor destination) { if (jpeg == null) throw new ArgumentNullException("jpeg"); if (destination == null) throw new ArgumentNullException("destination"); beforeDecompress(jpeg); m_decompressor.jpeg_start_decompress(); LoadedImageAttributes parameters = getImageParametersFromDecompressor(); destination.SetImageAttributes(parameters); destination.BeginWrite(); while (m_decompressor.Output_scanline < m_decompressor.Output_height) { byte[][] row = JpegCommonBase.AllocJpegSamples(m_decompressor.Output_width * m_decompressor.Output_components, 1); m_decompressor.jpeg_read_scanlines(row, 1); destination.ProcessPixelsRow(row[0]); } destination.EndWrite(); m_decompressor.jpeg_finish_decompress(); } private void beforeDecompress(Stream jpeg) { m_decompressor.jpeg_stdio_src(jpeg); m_decompressor.jpeg_read_header(true); applyParameters(m_decompressionParameters); m_decompressor.jpeg_calc_output_dimensions(); } private LoadedImageAttributes getImageParametersFromDecompressor() { LoadedImageAttributes result = new LoadedImageAttributes(); result.Colorspace = (ColorSpace)m_decompressor.Out_color_space; result.QuantizeColors = m_decompressor.Quantize_colors; result.Width = m_decompressor.Output_width; result.Height = m_decompressor.Output_height; result.ComponentsPerSample = m_decompressor.Out_color_components; result.Components = m_decompressor.Output_components; result.ActualNumberOfColors = m_decompressor.Actual_number_of_colors; result.Colormap = m_decompressor.Colormap; result.DensityUnit = m_decompressor.Density_unit; result.DensityX = m_decompressor.X_density; result.DensityY = m_decompressor.Y_density; return result; } public JpegCompressor ClassicCompressor { get { return m_compressor; } } public JpegDecompressor ClassicDecompressor { get { return m_decompressor; } } public delegate bool MarkerParser(Jpeg decompressor); public void SetMarkerProcessor(int markerCode, MarkerParser routine) { JpegDecompressor.jpeg_marker_parser_method f = delegate { return routine(this); }; m_decompressor.jpeg_set_marker_processor(markerCode, f); } private void applyParameters(DecompressionParameters parameters) { if (parameters == null) throw new ArgumentNullException("'parameters' Cannot be null!"); if (parameters.OutColorspace != ColorSpace.Unknown) m_decompressor.Out_color_space = (ColorSpace)parameters.OutColorspace; m_decompressor.Scale_num = parameters.ScaleNumerator; m_decompressor.Scale_denom = parameters.ScaleDenominator; m_decompressor.Buffered_image = parameters.BufferedImage; m_decompressor.Raw_data_out = parameters.RawDataOut; m_decompressor.Dct_method = (DCTMethod)parameters.DCTMethod; m_decompressor.Dither_mode = (DitherMode)parameters.DitherMode; m_decompressor.Do_fancy_upsampling = parameters.DoFancyUpsampling; m_decompressor.Do_block_smoothing = parameters.DoBlockSmoothing; m_decompressor.Quantize_colors = parameters.QuantizeColors; m_decompressor.Two_pass_quantize = parameters.TwoPassQuantize; m_decompressor.Desired_number_of_colors = parameters.DesiredNumberOfColors; m_decompressor.Enable_1pass_quant = parameters.EnableOnePassQuantizer; m_decompressor.Enable_external_quant = parameters.EnableExternalQuant; m_decompressor.Enable_2pass_quant = parameters.EnableTwoPassQuantizer; } private void applyParameters(CompressionParameters parameters) { if (parameters == null) throw new ArgumentNullException("'parameters' Cannot be null!"); m_compressor.Smoothing_factor = parameters.SmoothingFactor; m_compressor.jpeg_set_quality(parameters.Quality, true); if (parameters.SimpleProgressive) m_compressor.jpeg_simple_progression(); } } #endregion #region JpegBlock public class JpegBlock { internal short[] data = new short[JpegConstants.DCTSize2]; public short this[int index] { get { return data[index]; } set { data[index] = value; } } } #endregion #region JpegCompressor public class JpegCompressor : JpegCommonBase { private static int[] std_luminance_quant_tbl = { 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99 }; private static int[] std_chrominance_quant_tbl = { 17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99 }; private static byte[] bits_dc_luminance = { 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; private static byte[] val_dc_luminance = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; private static byte[] bits_dc_chrominance = { 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; private static byte[] val_dc_chrominance = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; private static byte[] bits_ac_luminance = { 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d }; private static byte[] val_ac_luminance = { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa }; private static byte[] bits_ac_chrominance = { 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; private static byte[] val_ac_chrominance = { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa }; internal DestinationManager m_dest; internal int m_image_width; internal int m_image_height; internal int m_input_components; internal ColorSpace m_in_color_space; internal int m_data_precision; internal int m_num_components; internal ColorSpace m_jpeg_color_space; private JpegComponent[] m_comp_info; internal JpegQuantizationTable[] m_quant_tbl_ptrs = new JpegQuantizationTable[JpegConstants.NumberOfQuantTables]; internal JpegHuffmanTable[] m_dc_huff_tbl_ptrs = new JpegHuffmanTable[JpegConstants.NumberOfHuffmanTables]; internal JpegHuffmanTable[] m_ac_huff_tbl_ptrs = new JpegHuffmanTable[JpegConstants.NumberOfHuffmanTables]; internal int m_num_scans; internal JpegScanInfo[] m_scan_info; internal bool m_raw_data_in; internal bool m_optimize_coding; internal bool m_CCIR601_sampling; internal int m_smoothing_factor; internal DCTMethod m_dct_method; internal int m_restart_interval; internal int m_restart_in_rows; internal bool m_write_JFIF_header; internal byte m_JFIF_major_version; internal byte m_JFIF_minor_version; internal DensityUnit m_density_unit; internal short m_X_density; internal short m_Y_density; internal bool m_write_Adobe_marker; internal int m_next_scanline; internal bool m_progressive_mode; internal int m_max_h_samp_factor; internal int m_max_v_samp_factor; internal int m_total_iMCU_rows; internal int m_comps_in_scan; internal int[] m_cur_comp_info = new int[JpegConstants.MaxComponentsInScan]; internal int m_MCUs_per_row; internal int m_MCU_rows_in_scan; internal int m_blocks_in_MCU; internal int[] m_MCU_membership = new int[JpegConstants.CompressorMaxBlocksInMCU]; internal int m_Ss; internal int m_Se; internal int m_Ah; internal int m_Al; internal JpegCompressorMaster m_master; internal JpegCompressorMainController m_main; internal JpegCompressorPrepController m_prep; internal JpegCompressorCoefController m_coef; internal JpegMarkerWriter m_marker; internal ColorConverter m_cconvert; internal JpegDownsampler m_downsample; internal JpegFowardDCT m_fdct; internal JpegEntropyEncoder m_entropy; internal JpegScanInfo[] m_script_space; internal int m_script_space_size; public JpegCompressor() : base() { initialize(); } public override bool IsDecompressor { get { return false; } } public LibJpeg.DestinationManager Dest { get { return m_dest; } set { m_dest = value; } } public int Image_width { get { return m_image_width; } set { m_image_width = value; } } public int Image_height { get { return m_image_height; } set { m_image_height = value; } } public int Input_components { get { return m_input_components; } set { m_input_components = value; } } public LibJpeg.ColorSpace In_color_space { get { return m_in_color_space; } set { m_in_color_space = value; } } public int Data_precision { get { return m_data_precision; } set { m_data_precision = value; } } public int Num_components { get { return m_num_components; } set { m_num_components = value; } } public ColorSpace Jpeg_color_space { get { return m_jpeg_color_space; } set { m_jpeg_color_space = value; } } public bool Raw_data_in { get { return m_raw_data_in; } set { m_raw_data_in = value; } } public bool Optimize_coding { get { return m_optimize_coding; } set { m_optimize_coding = value; } } public bool CCIR601_sampling { get { return m_CCIR601_sampling; } set { m_CCIR601_sampling = value; } } public int Smoothing_factor { get { return m_smoothing_factor; } set { m_smoothing_factor = value; } } public DCTMethod Dct_method { get { return m_dct_method; } set { m_dct_method = value; } } public int Restart_interval { get { return m_restart_interval; } set { m_restart_interval = value; } } public int Restart_in_rows { get { return m_restart_in_rows; } set { m_restart_in_rows = value; } } public bool Write_JFIF_header { get { return m_write_JFIF_header; } set { m_write_JFIF_header = value; } } public byte JFIF_major_version { get { return m_JFIF_major_version; } set { m_JFIF_major_version = value; } } public byte JFIF_minor_version { get { return m_JFIF_minor_version; } set { m_JFIF_minor_version = value; } } public DensityUnit Density_unit { get { return m_density_unit; } set { m_density_unit = value; } } public short X_density { get { return m_X_density; } set { m_X_density = value; } } public short Y_density { get { return m_Y_density; } set { m_Y_density = value; } } public bool Write_Adobe_marker { get { return m_write_Adobe_marker; } set { m_write_Adobe_marker = value; } } public int Max_v_samp_factor { get { return m_max_v_samp_factor; } } public JpegComponent[] Component_info { get { return m_comp_info; } } public JpegQuantizationTable[] Quant_tbl_ptrs { get { return m_quant_tbl_ptrs; } } public JpegHuffmanTable[] Dc_huff_tbl_ptrs { get { return m_dc_huff_tbl_ptrs; } } public JpegHuffmanTable[] Ac_huff_tbl_ptrs { get { return m_ac_huff_tbl_ptrs; } } public int Next_scanline { get { return m_next_scanline; } } public void jpeg_abort_compress() { jpeg_abort(); } public void jpeg_suppress_tables(bool suppress) { for (int i = 0; i < JpegConstants.NumberOfQuantTables; i++) { if (m_quant_tbl_ptrs[i] != null) m_quant_tbl_ptrs[i].Sent_table = suppress; } for (int i = 0; i < JpegConstants.NumberOfHuffmanTables; i++) { if (m_dc_huff_tbl_ptrs[i] != null) m_dc_huff_tbl_ptrs[i].Sent_table = suppress; if (m_ac_huff_tbl_ptrs[i] != null) m_ac_huff_tbl_ptrs[i].Sent_table = suppress; } } public void jpeg_finish_compress() { int iMCU_row; if (m_global_state == JpegState.CSTATE_SCANNING || m_global_state == JpegState.CSTATE_RAW_OK) { if (m_next_scanline < m_image_height) throw new Exception("Application transferred too few scanlines"); m_master.finish_pass(); } else if (m_global_state != JpegState.CSTATE_WRCOEFS) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); while (!m_master.IsLastPass()) { m_master.prepare_for_pass(); for (iMCU_row = 0; iMCU_row < m_total_iMCU_rows; iMCU_row++) { if (!m_coef.compress_data(null)) throw new Exception("Suspension not allowed here"); } m_master.finish_pass(); } m_marker.write_file_trailer(); m_dest.term_destination(); jpeg_abort(); } public void jpeg_write_marker(int marker, byte[] data) { if (m_next_scanline != 0 || (m_global_state != JpegState.CSTATE_SCANNING && m_global_state != JpegState.CSTATE_RAW_OK && m_global_state != JpegState.CSTATE_WRCOEFS)) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); m_marker.write_marker_header(marker, data.Length); for (int i = 0; i < data.Length; i++) m_marker.write_marker_byte(data[i]); } public void jpeg_write_m_header(int marker, int datalen) { if (m_next_scanline != 0 || (m_global_state != JpegState.CSTATE_SCANNING && m_global_state != JpegState.CSTATE_RAW_OK && m_global_state != JpegState.CSTATE_WRCOEFS)) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); m_marker.write_marker_header(marker, datalen); } public void jpeg_write_m_byte(byte val) { m_marker.write_marker_byte(val); } public void jpeg_write_tables() { if (m_global_state != JpegState.CSTATE_START) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); m_dest.init_destination(); m_marker = new JpegMarkerWriter(this); m_marker.write_tables_only(); m_dest.term_destination(); } public void jpeg_stdio_dest(Stream outfile) { m_dest = new DestinationManagerImpl(this, outfile); } public void jpeg_set_defaults() { if (m_global_state != JpegState.CSTATE_START) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); if (m_comp_info == null) { m_comp_info = JpegComponent.createArrayOfComponents(JpegConstants.MaxComponents); } m_data_precision = JpegConstants.BitsInSample; jpeg_set_quality(75, true); std_huff_tables(); m_scan_info = null; m_num_scans = 0; m_raw_data_in = false; m_optimize_coding = false; if (m_data_precision > 8) m_optimize_coding = true; m_CCIR601_sampling = false; m_smoothing_factor = 0; m_dct_method = JpegConstants.DefaultDCTMethod; m_restart_interval = 0; m_restart_in_rows = 0; m_JFIF_major_version = 1; m_JFIF_minor_version = 1; m_density_unit = DensityUnit.Unknown; m_X_density = 1; m_Y_density = 1; jpeg_default_colorspace(); } public void jpeg_set_colorspace(ColorSpace colorspace) { int ci; if (m_global_state != JpegState.CSTATE_START) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); m_jpeg_color_space = colorspace; m_write_JFIF_header = false; m_write_Adobe_marker = false; switch (colorspace) { case ColorSpace.Grayscale: m_write_JFIF_header = true; m_num_components = 1; jpeg_set_colorspace_SET_COMP(0, 1, 1, 1, 0, 0, 0); break; case ColorSpace.RGB: m_write_Adobe_marker = true; m_num_components = 3; jpeg_set_colorspace_SET_COMP(0, 0x52, 1, 1, 0, 0, 0); jpeg_set_colorspace_SET_COMP(1, 0x47, 1, 1, 0, 0, 0); jpeg_set_colorspace_SET_COMP(2, 0x42, 1, 1, 0, 0, 0); break; case ColorSpace.YCbCr: m_write_JFIF_header = true; m_num_components = 3; jpeg_set_colorspace_SET_COMP(0, 1, 2, 2, 0, 0, 0); jpeg_set_colorspace_SET_COMP(1, 2, 1, 1, 1, 1, 1); jpeg_set_colorspace_SET_COMP(2, 3, 1, 1, 1, 1, 1); break; case ColorSpace.CMYK: m_write_Adobe_marker = true; m_num_components = 4; jpeg_set_colorspace_SET_COMP(0, 0x43, 1, 1, 0, 0, 0); jpeg_set_colorspace_SET_COMP(1, 0x4D, 1, 1, 0, 0, 0); jpeg_set_colorspace_SET_COMP(2, 0x59, 1, 1, 0, 0, 0); jpeg_set_colorspace_SET_COMP(3, 0x4B, 1, 1, 0, 0, 0); break; case ColorSpace.YCCK: m_write_Adobe_marker = true; m_num_components = 4; jpeg_set_colorspace_SET_COMP(0, 1, 2, 2, 0, 0, 0); jpeg_set_colorspace_SET_COMP(1, 2, 1, 1, 1, 1, 1); jpeg_set_colorspace_SET_COMP(2, 3, 1, 1, 1, 1, 1); jpeg_set_colorspace_SET_COMP(3, 4, 2, 2, 0, 0, 0); break; case ColorSpace.Unknown: m_num_components = m_input_components; if (m_num_components < 1 || m_num_components > JpegConstants.MaxComponents) throw new Exception(String.Format("Too many color components: {0}, max {1}", m_num_components, JpegConstants.MaxComponents)); for (ci = 0; ci < m_num_components; ci++) { jpeg_set_colorspace_SET_COMP(ci, ci, 1, 1, 0, 0, 0); } break; default: throw new Exception("Bad Jpeg ColorSpace."); } } public void jpeg_default_colorspace() { switch (m_in_color_space) { case ColorSpace.Grayscale: jpeg_set_colorspace(ColorSpace.Grayscale); break; case ColorSpace.RGB: jpeg_set_colorspace(ColorSpace.YCbCr); break; case ColorSpace.YCbCr: jpeg_set_colorspace(ColorSpace.YCbCr); break; case ColorSpace.CMYK: jpeg_set_colorspace(ColorSpace.CMYK); break; case ColorSpace.YCCK: jpeg_set_colorspace(ColorSpace.YCCK); break; case ColorSpace.Unknown: jpeg_set_colorspace(ColorSpace.Unknown); break; default: throw new Exception("Bad input colorspace!"); } } public void jpeg_set_quality(int quality, bool force_baseline) { quality = jpeg_quality_scaling(quality); jpeg_set_linear_quality(quality, force_baseline); } public void jpeg_set_linear_quality(int scale_factor, bool force_baseline) { jpeg_add_quant_table(0, std_luminance_quant_tbl, scale_factor, force_baseline); jpeg_add_quant_table(1, std_chrominance_quant_tbl, scale_factor, force_baseline); } public void jpeg_add_quant_table(int which_tbl, int[] basic_table, int scale_factor, bool force_baseline) { if (m_global_state != JpegState.CSTATE_START) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); if (which_tbl < 0 || which_tbl >= JpegConstants.NumberOfQuantTables) throw new Exception(String.Format("Bogus DQT index {0}", which_tbl)); if (m_quant_tbl_ptrs[which_tbl] == null) m_quant_tbl_ptrs[which_tbl] = new JpegQuantizationTable(); for (int i = 0; i < JpegConstants.DCTSize2; i++) { int temp = (basic_table[i] * scale_factor + 50) / 100; if (temp <= 0) temp = 1; if (temp > 32767) temp = 32767; if (force_baseline && temp > 255) temp = 255; m_quant_tbl_ptrs[which_tbl].quantval[i] = (short)temp; } m_quant_tbl_ptrs[which_tbl].Sent_table = false; } public static int jpeg_quality_scaling(int quality) { if (quality <= 0) quality = 1; if (quality > 100) quality = 100; if (quality < 50) quality = 5000 / quality; else quality = 200 - quality * 2; return quality; } public void jpeg_simple_progression() { if (m_global_state != JpegState.CSTATE_START) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); int nscans; if (m_num_components == 3 && m_jpeg_color_space == ColorSpace.YCbCr) { nscans = 10; } else { if (m_num_components > JpegConstants.MaxComponentsInScan) { nscans = 6 * m_num_components; } else { nscans = 2 + 4 * m_num_components; } } if (m_script_space == null || m_script_space_size < nscans) { m_script_space_size = Math.Max(nscans, 10); m_script_space = new JpegScanInfo[m_script_space_size]; for (int i = 0; i < m_script_space_size; i++) m_script_space[i] = new JpegScanInfo(); } m_scan_info = m_script_space; m_num_scans = nscans; int scanIndex = 0; if (m_num_components == 3 && m_jpeg_color_space == ColorSpace.YCbCr) { fill_dc_scans(ref scanIndex, m_num_components, 0, 1); fill_a_scan(ref scanIndex, 0, 1, 5, 0, 2); fill_a_scan(ref scanIndex, 2, 1, 63, 0, 1); fill_a_scan(ref scanIndex, 1, 1, 63, 0, 1); fill_a_scan(ref scanIndex, 0, 6, 63, 0, 2); fill_a_scan(ref scanIndex, 0, 1, 63, 2, 1); fill_dc_scans(ref scanIndex, m_num_components, 1, 0); fill_a_scan(ref scanIndex, 2, 1, 63, 1, 0); fill_a_scan(ref scanIndex, 1, 1, 63, 1, 0); fill_a_scan(ref scanIndex, 0, 1, 63, 1, 0); } else { fill_dc_scans(ref scanIndex, m_num_components, 0, 1); fill_scans(ref scanIndex, m_num_components, 1, 5, 0, 2); fill_scans(ref scanIndex, m_num_components, 6, 63, 0, 2); fill_scans(ref scanIndex, m_num_components, 1, 63, 2, 1); fill_dc_scans(ref scanIndex, m_num_components, 1, 0); fill_scans(ref scanIndex, m_num_components, 1, 63, 1, 0); } } public void jpeg_start_compress(bool write_all_tables) { if (m_global_state != JpegState.CSTATE_START) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); if (write_all_tables) jpeg_suppress_tables(false); m_dest.init_destination(); jinit_compress_master(); m_master.prepare_for_pass(); m_next_scanline = 0; m_global_state = (m_raw_data_in ? JpegState.CSTATE_RAW_OK : JpegState.CSTATE_SCANNING); } public int jpeg_write_scanlines(byte[][] scanlines, int num_lines) { if (m_global_state != JpegState.CSTATE_SCANNING) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); if (m_master.MustCallPassStartup()) m_master.pass_startup(); int rows_left = m_image_height - m_next_scanline; if (num_lines > rows_left) num_lines = rows_left; int row_ctr = 0; m_main.process_data(scanlines, ref row_ctr, num_lines); m_next_scanline += row_ctr; return row_ctr; } public int jpeg_write_raw_data(byte[][][] data, int num_lines) { if (m_global_state != JpegState.CSTATE_RAW_OK) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); if (m_next_scanline >= m_image_height) { return 0; } if (m_master.MustCallPassStartup()) m_master.pass_startup(); int lines_per_iMCU_row = m_max_v_samp_factor * JpegConstants.DCTSize; if (num_lines < lines_per_iMCU_row) throw new Exception("Buffer passed to JPEG library is too small"); if (!m_coef.compress_data(data)) { return 0; } m_next_scanline += lines_per_iMCU_row; return lines_per_iMCU_row; } public void jpeg_write_coefficients(JpegVirtualArray[] coef_arrays) { if (m_global_state != JpegState.CSTATE_START) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); jpeg_suppress_tables(false); m_dest.init_destination(); transencode_master_selection(coef_arrays); m_next_scanline = 0; m_global_state = JpegState.CSTATE_WRCOEFS; } private void initialize() { m_dest = null; m_comp_info = null; for (int i = 0; i < JpegConstants.NumberOfQuantTables; i++) m_quant_tbl_ptrs[i] = null; for (int i = 0; i < JpegConstants.NumberOfHuffmanTables; i++) { m_dc_huff_tbl_ptrs[i] = null; m_ac_huff_tbl_ptrs[i] = null; } m_script_space = null; m_global_state = JpegState.CSTATE_START; } private void jinit_compress_master() { jinit_c_master_control(false); if (!m_raw_data_in) { m_cconvert = new ColorConverter(this); m_downsample = new JpegDownsampler(this); m_prep = new JpegCompressorPrepController(this); } m_fdct = new JpegFowardDCT(this); if (m_progressive_mode) m_entropy = new ProgressiveHuffmanEncoder(this); else m_entropy = new HuffEntropyEncoder(this); m_coef = new CoefControllerImpl(this, (bool)(m_num_scans > 1 || m_optimize_coding)); jinit_c_main_controller(false); m_marker = new JpegMarkerWriter(this); m_marker.write_file_header(); } private void jinit_c_master_control(bool transcode_only) { initial_setup(); if (m_scan_info != null) { validate_script(); } else { m_progressive_mode = false; m_num_scans = 1; } if (m_progressive_mode) m_optimize_coding = true; m_master = new JpegCompressorMaster(this, transcode_only); } private void jinit_c_main_controller(bool need_full_buffer) { if (m_raw_data_in) return; if (need_full_buffer) throw new Exception("Bogus buffer control mode"); else m_main = new JpegCompressorMainController(this); } private void transencode_master_selection(JpegVirtualArray[] coef_arrays) { m_input_components = 1; jinit_c_master_control(true); if (m_progressive_mode) m_entropy = new ProgressiveHuffmanEncoder(this); else m_entropy = new HuffEntropyEncoder(this); m_coef = new TransCoefControllerImpl(this, coef_arrays); m_marker = new JpegMarkerWriter(this); m_marker.write_file_header(); } private void initial_setup() { if (m_image_height <= 0 || m_image_width <= 0 || m_num_components <= 0 || m_input_components <= 0) throw new Exception("Empty JPEG image (DNL not supported)"); if (m_image_height > JpegConstants.JpegMaxDimention || m_image_width > JpegConstants.JpegMaxDimention) throw new Exception(String.Format("Maximum supported image dimension is {0} pixels", (int)JpegConstants.JpegMaxDimention)); long samplesperrow = m_image_width * m_input_components; int jd_samplesperrow = (int)samplesperrow; if ((long)jd_samplesperrow != samplesperrow) throw new Exception("Image too wide for this implementation"); if (m_data_precision != JpegConstants.BitsInSample) throw new Exception(String.Format("Unsupported JPEG data precision {0}", m_data_precision)); if (m_num_components > JpegConstants.MaxComponents) throw new Exception(String.Format("Too many color components: {0}, max {1}", m_num_components, JpegConstants.MaxComponents)); m_max_h_samp_factor = 1; m_max_v_samp_factor = 1; for (int ci = 0; ci < m_num_components; ci++) { if (m_comp_info[ci].H_samp_factor <= 0 || m_comp_info[ci].H_samp_factor > JpegConstants.MaxSamplingFactor || m_comp_info[ci].V_samp_factor <= 0 || m_comp_info[ci].V_samp_factor > JpegConstants.MaxSamplingFactor) { throw new Exception("Bogus sampling factors"); } m_max_h_samp_factor = Math.Max(m_max_h_samp_factor, m_comp_info[ci].H_samp_factor); m_max_v_samp_factor = Math.Max(m_max_v_samp_factor, m_comp_info[ci].V_samp_factor); } for (int ci = 0; ci < m_num_components; ci++) { m_comp_info[ci].Component_index = ci; m_comp_info[ci].DCT_scaled_size = JpegConstants.DCTSize; m_comp_info[ci].Width_in_blocks = JpegUtils.jdiv_round_up( m_image_width * m_comp_info[ci].H_samp_factor, m_max_h_samp_factor * JpegConstants.DCTSize); m_comp_info[ci].height_in_blocks = JpegUtils.jdiv_round_up( m_image_height * m_comp_info[ci].V_samp_factor, m_max_v_samp_factor * JpegConstants.DCTSize); m_comp_info[ci].downsampled_width = JpegUtils.jdiv_round_up( m_image_width * m_comp_info[ci].H_samp_factor, m_max_h_samp_factor); m_comp_info[ci].downsampled_height = JpegUtils.jdiv_round_up( m_image_height * m_comp_info[ci].V_samp_factor, m_max_v_samp_factor); m_comp_info[ci].component_needed = true; } m_total_iMCU_rows = JpegUtils.jdiv_round_up(m_image_height, m_max_v_samp_factor * JpegConstants.DCTSize); } private void validate_script() { if (m_num_scans <= 0) throw new Exception(String.Format("Invalid scan script at entry {0}", 0)); int[][] last_bitpos = new int[JpegConstants.MaxComponents][]; for (int i = 0; i < JpegConstants.MaxComponents; i++) last_bitpos[i] = new int[JpegConstants.DCTSize2]; bool[] component_sent = new bool[JpegConstants.MaxComponents]; if (m_scan_info[0].Ss != 0 || m_scan_info[0].Se != JpegConstants.DCTSize2 - 1) { m_progressive_mode = true; for (int ci = 0; ci < m_num_components; ci++) { for (int coefi = 0; coefi < JpegConstants.DCTSize2; coefi++) last_bitpos[ci][coefi] = -1; } } else { m_progressive_mode = false; for (int ci = 0; ci < m_num_components; ci++) component_sent[ci] = false; } for (int scanno = 1; scanno <= m_num_scans; scanno++) { JpegScanInfo scanInfo = m_scan_info[scanno - 1]; int ncomps = scanInfo.comps_in_scan; if (ncomps <= 0 || ncomps > JpegConstants.MaxComponentsInScan) throw new Exception(String.Format("Too many color components: {0}, max {1}", ncomps, JpegConstants.MaxComponentsInScan)); for (int ci = 0; ci < ncomps; ci++) { int thisi = scanInfo.component_index[ci]; if (thisi < 0 || thisi >= m_num_components) throw new Exception(String.Format("Invalid scan script at entry {0}", scanno)); if (ci > 0 && thisi <= scanInfo.component_index[ci - 1]) throw new Exception(String.Format("Invalid scan script at entry {0}", scanno)); } int Ss = scanInfo.Ss; int Se = scanInfo.Se; int Ah = scanInfo.Ah; int Al = scanInfo.Al; if (m_progressive_mode) { const int MAX_AH_AL = 10; if (Ss < 0 || Ss >= JpegConstants.DCTSize2 || Se < Ss || Se >= JpegConstants.DCTSize2 || Ah < 0 || Ah > MAX_AH_AL || Al < 0 || Al > MAX_AH_AL) { throw new Exception(String.Format("Invalid progressive parameters at scan script entry {0}", scanno)); } if (Ss == 0) { if (Se != 0) throw new Exception(String.Format("Invalid progressive parameters at scan script entry {0}", scanno)); } else { if (ncomps != 1) throw new Exception(String.Format("Invalid progressive parameters at scan script entry {0}", scanno)); } for (int ci = 0; ci < ncomps; ci++) { int lastBitComponentIndex = scanInfo.component_index[ci]; if (Ss != 0 && last_bitpos[lastBitComponentIndex][0] < 0) throw new Exception(String.Format("Invalid progressive parameters at scan script entry {0}", scanno)); for (int coefi = Ss; coefi <= Se; coefi++) { if (last_bitpos[lastBitComponentIndex][coefi] < 0) { if (Ah != 0) throw new Exception(String.Format("Invalid progressive parameters at scan script entry {0}", scanno)); } else { if (Ah != last_bitpos[lastBitComponentIndex][coefi] || Al != Ah - 1) throw new Exception(String.Format("Invalid progressive parameters at scan script entry {0}", scanno)); } last_bitpos[lastBitComponentIndex][coefi] = Al; } } } else { if (Ss != 0 || Se != JpegConstants.DCTSize2 - 1 || Ah != 0 || Al != 0) throw new Exception(String.Format("Invalid progressive parameters at scan script entry {0}", scanno)); for (int ci = 0; ci < ncomps; ci++) { int thisi = scanInfo.component_index[ci]; if (component_sent[thisi]) throw new Exception(String.Format("Invalid scan script at entry {0}", scanno)); component_sent[thisi] = true; } } } if (m_progressive_mode) { for (int ci = 0; ci < m_num_components; ci++) { if (last_bitpos[ci][0] < 0) throw new Exception("Scan script does not transmit all data"); } } else { for (int ci = 0; ci < m_num_components; ci++) { if (!component_sent[ci]) throw new Exception("Scan script does not transmit all data"); } } } private void std_huff_tables() { add_huff_table(ref m_dc_huff_tbl_ptrs[0], bits_dc_luminance, val_dc_luminance); add_huff_table(ref m_ac_huff_tbl_ptrs[0], bits_ac_luminance, val_ac_luminance); add_huff_table(ref m_dc_huff_tbl_ptrs[1], bits_dc_chrominance, val_dc_chrominance); add_huff_table(ref m_ac_huff_tbl_ptrs[1], bits_ac_chrominance, val_ac_chrominance); } private void add_huff_table(ref JpegHuffmanTable htblptr, byte[] bits, byte[] val) { if (htblptr == null) htblptr = new JpegHuffmanTable(); Buffer.BlockCopy(bits, 0, htblptr.Bits, 0, htblptr.Bits.Length); int nsymbols = 0; for (int len = 1; len <= 16; len++) nsymbols += bits[len]; if (nsymbols < 1 || nsymbols > 256) throw new Exception("Bogus Huffman table definition"); Buffer.BlockCopy(val, 0, htblptr.Huffval, 0, nsymbols); htblptr.Sent_table = false; } private void fill_a_scan(ref int scanIndex, int ci, int Ss, int Se, int Ah, int Al) { m_script_space[scanIndex].comps_in_scan = 1; m_script_space[scanIndex].component_index[0] = ci; m_script_space[scanIndex].Ss = Ss; m_script_space[scanIndex].Se = Se; m_script_space[scanIndex].Ah = Ah; m_script_space[scanIndex].Al = Al; scanIndex++; } private void fill_dc_scans(ref int scanIndex, int ncomps, int Ah, int Al) { if (ncomps <= JpegConstants.MaxComponentsInScan) { m_script_space[scanIndex].comps_in_scan = ncomps; for (int ci = 0; ci < ncomps; ci++) m_script_space[scanIndex].component_index[ci] = ci; m_script_space[scanIndex].Ss = 0; m_script_space[scanIndex].Se = 0; m_script_space[scanIndex].Ah = Ah; m_script_space[scanIndex].Al = Al; scanIndex++; } else { fill_scans(ref scanIndex, ncomps, 0, 0, Ah, Al); } } private void fill_scans(ref int scanIndex, int ncomps, int Ss, int Se, int Ah, int Al) { for (int ci = 0; ci < ncomps; ci++) { m_script_space[scanIndex].comps_in_scan = 1; m_script_space[scanIndex].component_index[0] = ci; m_script_space[scanIndex].Ss = Ss; m_script_space[scanIndex].Se = Se; m_script_space[scanIndex].Ah = Ah; m_script_space[scanIndex].Al = Al; scanIndex++; } } private void jpeg_set_colorspace_SET_COMP(int index, int id, int hsamp, int vsamp, int quant, int dctbl, int actbl) { m_comp_info[index].Component_id = id; m_comp_info[index].H_samp_factor = hsamp; m_comp_info[index].V_samp_factor = vsamp; m_comp_info[index].Quant_tbl_no = quant; m_comp_info[index].Dc_tbl_no = dctbl; m_comp_info[index].Ac_tbl_no = actbl; } } #endregion // STOPPED REMOVING COMMENTS HERE. #region JpegCompressorCoefController /// /// Coefficient buffer control /// interface JpegCompressorCoefController { void start_pass(BufferMode pass_mode); bool compress_data(byte[][][] input_buf); } #endregion #region JpegCompressorMainController /// /// Main buffer control (downsampled-data buffer) /// class JpegCompressorMainController { private JpegCompressor m_cinfo; private int m_cur_iMCU_row; /* number of current iMCU row */ private int m_rowgroup_ctr; /* counts row groups received in iMCU row */ private bool m_suspended; /* remember if we suspended output */ /* If using just a strip buffer, this points to the entire set of buffers * (we allocate one for each component). In the full-image case, this * points to the currently accessible strips of the virtual arrays. */ private byte[][][] m_buffer = new byte[JpegConstants.MaxComponents][][]; public JpegCompressorMainController(JpegCompressor cinfo) { m_cinfo = cinfo; /* Allocate a strip buffer for each component */ for (int ci = 0; ci < cinfo.m_num_components; ci++) { m_buffer[ci] = JpegCommonBase.AllocJpegSamples( cinfo.Component_info[ci].Width_in_blocks * JpegConstants.DCTSize, cinfo.Component_info[ci].V_samp_factor * JpegConstants.DCTSize); } } // Initialize for a processing pass. public void start_pass(BufferMode pass_mode) { /* Do nothing in raw-data mode. */ if (m_cinfo.m_raw_data_in) return; m_cur_iMCU_row = 0; /* initialize counters */ m_rowgroup_ctr = 0; m_suspended = false; if (pass_mode != BufferMode.PassThru) throw new Exception("Bogus buffer control mode!"); } /// /// Process some data. /// This routine handles the simple pass-through mode, /// where we have only a strip buffer. /// public void process_data(byte[][] input_buf, ref int in_row_ctr, int in_rows_avail) { while (m_cur_iMCU_row < m_cinfo.m_total_iMCU_rows) { /* Read input data if we haven't filled the main buffer yet */ if (m_rowgroup_ctr < JpegConstants.DCTSize) m_cinfo.m_prep.pre_process_data(input_buf, ref in_row_ctr, in_rows_avail, m_buffer, ref m_rowgroup_ctr, JpegConstants.DCTSize); /* If we don't have a full iMCU row buffered, return to application for * more data. Note that preprocessor will always pad to fill the iMCU row * at the bottom of the image. */ if (m_rowgroup_ctr != JpegConstants.DCTSize) return; /* Send the completed row to the compressor */ if (!m_cinfo.m_coef.compress_data(m_buffer)) { /* If compressor did not consume the whole row, then we must need to * suspend processing and return to the application. In this situation * we pretend we didn't yet consume the last input row; otherwise, if * it happened to be the last row of the image, the application would * think we were done. */ if (!m_suspended) { in_row_ctr--; m_suspended = true; } return; } /* We did finish the row. Undo our little suspension hack if a previous * call suspended; then mark the main buffer empty. */ if (m_suspended) { in_row_ctr++; m_suspended = false; } m_rowgroup_ctr = 0; m_cur_iMCU_row++; } } } #endregion #region JpegCompressorMaster /// /// Master control module /// class JpegCompressorMaster { private enum c_pass_type { main_pass, /* input data, also do first output step */ huff_opt_pass, /* Huffman code optimization pass */ output_pass /* data output pass */ } private JpegCompressor m_cinfo; private bool m_call_pass_startup; /* True if pass_startup must be called */ private bool m_is_last_pass; /* True during last pass */ private c_pass_type m_pass_type; /* the type of the current pass */ private int m_pass_number; /* # of passes completed */ private int m_total_passes; /* total # of passes needed */ private int m_scan_number; /* current index in scan_info[] */ public JpegCompressorMaster(JpegCompressor cinfo, bool transcode_only) { m_cinfo = cinfo; if (transcode_only) { /* no main pass in transcoding */ if (cinfo.m_optimize_coding) m_pass_type = c_pass_type.huff_opt_pass; else m_pass_type = c_pass_type.output_pass; } else { /* for normal compression, first pass is always this type: */ m_pass_type = c_pass_type.main_pass; } if (cinfo.m_optimize_coding) m_total_passes = cinfo.m_num_scans * 2; else m_total_passes = cinfo.m_num_scans; } /// /// Per-pass setup. /// /// This is called at the beginning of each pass. We determine which /// modules will be active during this pass and give them appropriate /// start_pass calls. /// We also set is_last_pass to indicate whether any more passes will /// be required. /// public void prepare_for_pass() { switch (m_pass_type) { case c_pass_type.main_pass: prepare_for_main_pass(); break; case c_pass_type.huff_opt_pass: if (!prepare_for_huff_opt_pass()) break; prepare_for_output_pass(); break; case c_pass_type.output_pass: prepare_for_output_pass(); break; } m_is_last_pass = (m_pass_number == m_total_passes - 1); } /// /// Special start-of-pass hook. /// /// This is called by jpeg_write_scanlines if call_pass_startup is true. /// In single-pass processing, we need this hook because we don't want to /// write frame/scan headers during jpeg_start_compress; we want to let the /// application write COM markers etc. between jpeg_start_compress and the /// jpeg_write_scanlines loop. /// In multi-pass processing, this routine is not used. /// public void pass_startup() { m_cinfo.m_master.m_call_pass_startup = false; /* reset flag so call only once */ m_cinfo.m_marker.write_frame_header(); m_cinfo.m_marker.write_scan_header(); } /// /// Finish up at end of pass. /// public void finish_pass() { /* The entropy coder always needs an end-of-pass call, * either to analyze statistics or to flush its output buffer. */ m_cinfo.m_entropy.finish_pass(); /* Update state for next pass */ switch (m_pass_type) { case c_pass_type.main_pass: /* next pass is either output of scan 0 (after optimization) * or output of scan 1 (if no optimization). */ m_pass_type = c_pass_type.output_pass; if (!m_cinfo.m_optimize_coding) m_scan_number++; break; case c_pass_type.huff_opt_pass: /* next pass is always output of current scan */ m_pass_type = c_pass_type.output_pass; break; case c_pass_type.output_pass: /* next pass is either optimization or output of next scan */ if (m_cinfo.m_optimize_coding) m_pass_type = c_pass_type.huff_opt_pass; m_scan_number++; break; } m_pass_number++; } public bool IsLastPass() { return m_is_last_pass; } public bool MustCallPassStartup() { return m_call_pass_startup; } private void prepare_for_main_pass() { /* Initial pass: will collect input data, and do either Huffman * optimization or data output for the first scan. */ select_scan_parameters(); per_scan_setup(); if (!m_cinfo.m_raw_data_in) { m_cinfo.m_cconvert.start_pass(); m_cinfo.m_prep.start_pass(BufferMode.PassThru); } m_cinfo.m_fdct.start_pass(); m_cinfo.m_entropy.start_pass(m_cinfo.m_optimize_coding); m_cinfo.m_coef.start_pass((m_total_passes > 1 ? BufferMode.SaveAndPass : BufferMode.PassThru)); m_cinfo.m_main.start_pass(BufferMode.PassThru); if (m_cinfo.m_optimize_coding) { /* No immediate data output; postpone writing frame/scan headers */ m_call_pass_startup = false; } else { /* Will write frame/scan headers at first jpeg_write_scanlines call */ m_call_pass_startup = true; } } private bool prepare_for_huff_opt_pass() { /* Do Huffman optimization for a scan after the first one. */ select_scan_parameters(); per_scan_setup(); if (m_cinfo.m_Ss != 0 || m_cinfo.m_Ah == 0) { m_cinfo.m_entropy.start_pass(true); m_cinfo.m_coef.start_pass(BufferMode.CrankDest); m_call_pass_startup = false; return false; } /* Special case: Huffman DC refinement scans need no Huffman table * and therefore we can skip the optimization pass for them. */ m_pass_type = c_pass_type.output_pass; m_pass_number++; return true; } private void prepare_for_output_pass() { /* Do a data-output pass. */ /* We need not repeat per-scan setup if prior optimization pass did it. */ if (!m_cinfo.m_optimize_coding) { select_scan_parameters(); per_scan_setup(); } m_cinfo.m_entropy.start_pass(false); m_cinfo.m_coef.start_pass(BufferMode.CrankDest); /* We emit frame/scan headers now */ if (m_scan_number == 0) m_cinfo.m_marker.write_frame_header(); m_cinfo.m_marker.write_scan_header(); m_call_pass_startup = false; } // Set up the scan parameters for the current scan private void select_scan_parameters() { if (m_cinfo.m_scan_info != null) { /* Prepare for current scan --- the script is already validated */ JpegScanInfo scanInfo = m_cinfo.m_scan_info[m_scan_number]; m_cinfo.m_comps_in_scan = scanInfo.comps_in_scan; for (int ci = 0; ci < scanInfo.comps_in_scan; ci++) m_cinfo.m_cur_comp_info[ci] = scanInfo.component_index[ci]; m_cinfo.m_Ss = scanInfo.Ss; m_cinfo.m_Se = scanInfo.Se; m_cinfo.m_Ah = scanInfo.Ah; m_cinfo.m_Al = scanInfo.Al; } else { /* Prepare for single sequential-JPEG scan containing all components */ if (m_cinfo.m_num_components > JpegConstants.MaxComponentsInScan) throw new Exception(String.Format("Too many color components: {0}, max {1}", m_cinfo.m_num_components, JpegConstants.MaxComponentsInScan)); m_cinfo.m_comps_in_scan = m_cinfo.m_num_components; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) m_cinfo.m_cur_comp_info[ci] = ci; m_cinfo.m_Ss = 0; m_cinfo.m_Se = JpegConstants.DCTSize2 - 1; m_cinfo.m_Ah = 0; m_cinfo.m_Al = 0; } } /// /// Do computations that are needed before processing a JPEG scan /// cinfo.comps_in_scan and cinfo.cur_comp_info[] are already set /// private void per_scan_setup() { if (m_cinfo.m_comps_in_scan == 1) { /* Non-interleaved (single-component) scan */ int compIndex = m_cinfo.m_cur_comp_info[0]; /* Overall image size in MCUs */ m_cinfo.m_MCUs_per_row = m_cinfo.Component_info[compIndex].Width_in_blocks; m_cinfo.m_MCU_rows_in_scan = m_cinfo.Component_info[compIndex].height_in_blocks; /* For non-interleaved scan, always one block per MCU */ m_cinfo.Component_info[compIndex].MCU_width = 1; m_cinfo.Component_info[compIndex].MCU_height = 1; m_cinfo.Component_info[compIndex].MCU_blocks = 1; m_cinfo.Component_info[compIndex].MCU_sample_width = JpegConstants.DCTSize; m_cinfo.Component_info[compIndex].last_col_width = 1; /* For non-interleaved scans, it is convenient to define last_row_height * as the number of block rows present in the last iMCU row. */ int tmp = m_cinfo.Component_info[compIndex].height_in_blocks % m_cinfo.Component_info[compIndex].V_samp_factor; if (tmp == 0) tmp = m_cinfo.Component_info[compIndex].V_samp_factor; m_cinfo.Component_info[compIndex].last_row_height = tmp; /* Prepare array describing MCU composition */ m_cinfo.m_blocks_in_MCU = 1; m_cinfo.m_MCU_membership[0] = 0; } else { /* Interleaved (multi-component) scan */ if (m_cinfo.m_comps_in_scan <= 0 || m_cinfo.m_comps_in_scan > JpegConstants.MaxComponentsInScan) throw new Exception(String.Format("Too many color components: {0}, max {1}", m_cinfo.m_comps_in_scan, JpegConstants.MaxComponentsInScan)); /* Overall image size in MCUs */ m_cinfo.m_MCUs_per_row = JpegUtils.jdiv_round_up( m_cinfo.m_image_width, m_cinfo.m_max_h_samp_factor * JpegConstants.DCTSize); m_cinfo.m_MCU_rows_in_scan = JpegUtils.jdiv_round_up(m_cinfo.m_image_height, m_cinfo.m_max_v_samp_factor * JpegConstants.DCTSize); m_cinfo.m_blocks_in_MCU = 0; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { int compIndex = m_cinfo.m_cur_comp_info[ci]; /* Sampling factors give # of blocks of component in each MCU */ m_cinfo.Component_info[compIndex].MCU_width = m_cinfo.Component_info[compIndex].H_samp_factor; m_cinfo.Component_info[compIndex].MCU_height = m_cinfo.Component_info[compIndex].V_samp_factor; m_cinfo.Component_info[compIndex].MCU_blocks = m_cinfo.Component_info[compIndex].MCU_width * m_cinfo.Component_info[compIndex].MCU_height; m_cinfo.Component_info[compIndex].MCU_sample_width = m_cinfo.Component_info[compIndex].MCU_width * JpegConstants.DCTSize; /* Figure number of non-dummy blocks in last MCU column & row */ int tmp = m_cinfo.Component_info[compIndex].Width_in_blocks % m_cinfo.Component_info[compIndex].MCU_width; if (tmp == 0) tmp = m_cinfo.Component_info[compIndex].MCU_width; m_cinfo.Component_info[compIndex].last_col_width = tmp; tmp = m_cinfo.Component_info[compIndex].height_in_blocks % m_cinfo.Component_info[compIndex].MCU_height; if (tmp == 0) tmp = m_cinfo.Component_info[compIndex].MCU_height; m_cinfo.Component_info[compIndex].last_row_height = tmp; /* Prepare array describing MCU composition */ int mcublks = m_cinfo.Component_info[compIndex].MCU_blocks; if (m_cinfo.m_blocks_in_MCU + mcublks > JpegConstants.CompressorMaxBlocksInMCU) throw new Exception("Sampling factors too large for interleaved scan"); while (mcublks-- > 0) m_cinfo.m_MCU_membership[m_cinfo.m_blocks_in_MCU++] = ci; } } /* Convert restart specified in rows to actual MCU count. */ /* Note that count must fit in 16 bits, so we provide limiting. */ if (m_cinfo.m_restart_in_rows > 0) { int nominal = m_cinfo.m_restart_in_rows * m_cinfo.m_MCUs_per_row; m_cinfo.m_restart_interval = Math.Min(nominal, 65535); } } } #endregion #region JpegCompressorPrepController /// /// Compression preprocessing (downsampling input buffer control). /// /// For the simple (no-context-row) case, we just need to buffer one /// row group's worth of pixels for the downsampling step. At the bottom of /// the image, we pad to a full row group by replicating the last pixel row. /// The downsampler's last output row is then replicated if needed to pad /// out to a full iMCU row. /// /// When providing context rows, we must buffer three row groups' worth of /// pixels. Three row groups are physically allocated, but the row pointer /// arrays are made five row groups high, with the extra pointers above and /// below "wrapping around" to point to the last and first real row groups. /// This allows the downsampler to access the proper context rows. /// At the top and bottom of the image, we create dummy context rows by /// copying the first or last real pixel row. This copying could be avoided /// by pointer hacking as is done in jdmainct.c, but it doesn't seem worth the /// trouble on the compression side. /// class JpegCompressorPrepController { private JpegCompressor m_cinfo; /* Downsampling input buffer. This buffer holds color-converted data * until we have enough to do a downsample step. */ private byte[][][] m_color_buf = new byte[JpegConstants.MaxComponents][][]; private int m_colorBufRowsOffset; private int m_rows_to_go; /* counts rows remaining in source image */ private int m_next_buf_row; /* index of next row to store in color_buf */ private int m_this_row_group; /* starting row index of group to process */ private int m_next_buf_stop; /* downsample when we reach this index */ public JpegCompressorPrepController(JpegCompressor cinfo) { m_cinfo = cinfo; /* Allocate the color conversion buffer. * We make the buffer wide enough to allow the downsampler to edge-expand * horizontally within the buffer, if it so chooses. */ if (cinfo.m_downsample.NeedContextRows()) { /* Set up to provide context rows */ create_context_buffer(); } else { /* No context, just make it tall enough for one row group */ for (int ci = 0; ci < cinfo.m_num_components; ci++) { m_colorBufRowsOffset = 0; m_color_buf[ci] = JpegCompressor.AllocJpegSamples( (cinfo.Component_info[ci].Width_in_blocks * JpegConstants.DCTSize * cinfo.m_max_h_samp_factor) / cinfo.Component_info[ci].H_samp_factor, cinfo.m_max_v_samp_factor); } } } /// /// Initialize for a processing pass. /// public void start_pass(BufferMode pass_mode) { if (pass_mode != BufferMode.PassThru) throw new Exception("Bogus buffer control mode!"); /* Initialize total-height counter for detecting bottom of image */ m_rows_to_go = m_cinfo.m_image_height; /* Mark the conversion buffer empty */ m_next_buf_row = 0; /* Preset additional state variables for context mode. * These aren't used in non-context mode, so we needn't test which mode. */ m_this_row_group = 0; /* Set next_buf_stop to stop after two row groups have been read in. */ m_next_buf_stop = 2 * m_cinfo.m_max_v_samp_factor; } public void pre_process_data(byte[][] input_buf, ref int in_row_ctr, int in_rows_avail, byte[][][] output_buf, ref int out_row_group_ctr, int out_row_groups_avail) { if (m_cinfo.m_downsample.NeedContextRows()) pre_process_context(input_buf, ref in_row_ctr, in_rows_avail, output_buf, ref out_row_group_ctr, out_row_groups_avail); else pre_process_WithoutContext(input_buf, ref in_row_ctr, in_rows_avail, output_buf, ref out_row_group_ctr, out_row_groups_avail); } /// /// Create the wrapped-around downsampling input buffer needed for context mode. /// private void create_context_buffer() { int rgroup_height = m_cinfo.m_max_v_samp_factor; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { int samplesPerRow = (m_cinfo.Component_info[ci].Width_in_blocks * JpegConstants.DCTSize * m_cinfo.m_max_h_samp_factor) / m_cinfo.Component_info[ci].H_samp_factor; byte[][] fake_buffer = new byte[5 * rgroup_height][]; for (int i = 1; i < 4 * rgroup_height; i++) fake_buffer[i] = new byte[samplesPerRow]; /* Allocate the actual buffer space (3 row groups) for this component. * We make the buffer wide enough to allow the downsampler to edge-expand * horizontally within the buffer, if it so chooses. */ byte[][] true_buffer = JpegCommonBase.AllocJpegSamples(samplesPerRow, 3 * rgroup_height); /* Copy true buffer row pointers into the middle of the fake row array */ for (int i = 0; i < 3 * rgroup_height; i++) fake_buffer[rgroup_height + i] = true_buffer[i]; /* Fill in the above and below wraparound pointers */ for (int i = 0; i < rgroup_height; i++) { fake_buffer[i] = true_buffer[2 * rgroup_height + i]; fake_buffer[4 * rgroup_height + i] = true_buffer[i]; } m_color_buf[ci] = fake_buffer; m_colorBufRowsOffset = rgroup_height; } } /// /// Process some data in the simple no-context case. /// /// Preprocessor output data is counted in "row groups". A row group /// is defined to be v_samp_factor sample rows of each component. /// Downsampling will produce this much data from each max_v_samp_factor /// input rows. /// private void pre_process_WithoutContext(byte[][] input_buf, ref int in_row_ctr, int in_rows_avail, byte[][][] output_buf, ref int out_row_group_ctr, int out_row_groups_avail) { while (in_row_ctr < in_rows_avail && out_row_group_ctr < out_row_groups_avail) { /* Do color conversion to fill the conversion buffer. */ int inrows = in_rows_avail - in_row_ctr; int numrows = m_cinfo.m_max_v_samp_factor - m_next_buf_row; numrows = Math.Min(numrows, inrows); m_cinfo.m_cconvert.color_convert(input_buf, in_row_ctr, m_color_buf, m_colorBufRowsOffset + m_next_buf_row, numrows); in_row_ctr += numrows; m_next_buf_row += numrows; m_rows_to_go -= numrows; /* If at bottom of image, pad to fill the conversion buffer. */ if (m_rows_to_go == 0 && m_next_buf_row < m_cinfo.m_max_v_samp_factor) { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) expand_bottom_edge(m_color_buf[ci], m_colorBufRowsOffset, m_cinfo.m_image_width, m_next_buf_row, m_cinfo.m_max_v_samp_factor); m_next_buf_row = m_cinfo.m_max_v_samp_factor; } /* If we've filled the conversion buffer, empty it. */ if (m_next_buf_row == m_cinfo.m_max_v_samp_factor) { m_cinfo.m_downsample.downsample(m_color_buf, m_colorBufRowsOffset, output_buf, out_row_group_ctr); m_next_buf_row = 0; out_row_group_ctr++; } /* If at bottom of image, pad the output to a full iMCU height. * Note we assume the caller is providing a one-iMCU-height output buffer! */ if (m_rows_to_go == 0 && out_row_group_ctr < out_row_groups_avail) { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[ci]; expand_bottom_edge(output_buf[ci], 0, componentInfo.Width_in_blocks * JpegConstants.DCTSize, out_row_group_ctr * componentInfo.V_samp_factor, out_row_groups_avail * componentInfo.V_samp_factor); } out_row_group_ctr = out_row_groups_avail; break; /* can exit outer loop without test */ } } } /// /// Process some data in the context case. /// private void pre_process_context(byte[][] input_buf, ref int in_row_ctr, int in_rows_avail, byte[][][] output_buf, ref int out_row_group_ctr, int out_row_groups_avail) { while (out_row_group_ctr < out_row_groups_avail) { if (in_row_ctr < in_rows_avail) { /* Do color conversion to fill the conversion buffer. */ int inrows = in_rows_avail - in_row_ctr; int numrows = m_next_buf_stop - m_next_buf_row; numrows = Math.Min(numrows, inrows); m_cinfo.m_cconvert.color_convert(input_buf, in_row_ctr, m_color_buf, m_colorBufRowsOffset + m_next_buf_row, numrows); /* Pad at top of image, if first time through */ if (m_rows_to_go == m_cinfo.m_image_height) { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { for (int row = 1; row <= m_cinfo.m_max_v_samp_factor; row++) JpegUtils.jcopy_sample_rows(m_color_buf[ci], m_colorBufRowsOffset, m_color_buf[ci], m_colorBufRowsOffset - row, 1, m_cinfo.m_image_width); } } in_row_ctr += numrows; m_next_buf_row += numrows; m_rows_to_go -= numrows; } else { /* Return for more data, unless we are at the bottom of the image. */ if (m_rows_to_go != 0) break; /* When at bottom of image, pad to fill the conversion buffer. */ if (m_next_buf_row < m_next_buf_stop) { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) expand_bottom_edge(m_color_buf[ci], m_colorBufRowsOffset, m_cinfo.m_image_width, m_next_buf_row, m_next_buf_stop); m_next_buf_row = m_next_buf_stop; } } /* If we've gotten enough data, downsample a row group. */ if (m_next_buf_row == m_next_buf_stop) { m_cinfo.m_downsample.downsample(m_color_buf, m_colorBufRowsOffset + m_this_row_group, output_buf, out_row_group_ctr); out_row_group_ctr++; /* Advance pointers with wraparound as necessary. */ m_this_row_group += m_cinfo.m_max_v_samp_factor; int buf_height = m_cinfo.m_max_v_samp_factor * 3; if (m_this_row_group >= buf_height) m_this_row_group = 0; if (m_next_buf_row >= buf_height) m_next_buf_row = 0; m_next_buf_stop = m_next_buf_row + m_cinfo.m_max_v_samp_factor; } } } /// /// Expand an image vertically from height input_rows to height output_rows, /// by duplicating the bottom row. /// private static void expand_bottom_edge(byte[][] image_data, int rowsOffset, int num_cols, int input_rows, int output_rows) { for (int row = input_rows; row < output_rows; row++) JpegUtils.jcopy_sample_rows(image_data, rowsOffset + input_rows - 1, image_data, row, 1, num_cols); } } #endregion #region JpegDecompressor /// /// JPEG decompression routine. /// /// public class JpegDecompressor : JpegCommonBase { /// /// The delegate for application-supplied marker processing methods. /// /// Decompressor. /// Return true to indicate success. false should be returned only /// if you are using a suspending data source and it tells you to suspend. /// /// Although the marker code is not explicitly passed, the routine can find it /// in the . At the time of call, /// the marker proper has been read from the data source module. The processor routine /// is responsible for reading the marker length word and the remaining parameter bytes, if any. /// public delegate bool jpeg_marker_parser_method(JpegDecompressor cinfo); /* Source of compressed data */ internal Jpeg_Source m_src; internal int m_image_width; /* nominal image width (from SOF marker) */ internal int m_image_height; /* nominal image height */ internal int m_num_components; /* # of color components in JPEG image */ internal ColorSpace m_jpeg_color_space; /* colorspace of JPEG image */ internal ColorSpace m_out_color_space; /* colorspace for output */ internal int m_scale_num; internal int m_scale_denom; /* fraction by which to scale image */ internal bool m_buffered_image; /* true=multiple output passes */ internal bool m_raw_data_out; /* true=downsampled data wanted */ internal DCTMethod m_dct_method; /* IDCT algorithm selector */ internal bool m_do_fancy_upsampling; /* true=apply fancy up-sampling */ internal bool m_do_block_smoothing; /* true=apply inter-block smoothing */ internal bool m_quantize_colors; /* true=colormapped output wanted */ internal DitherMode m_dither_mode; /* type of color dithering to use */ internal bool m_two_pass_quantize; /* true=use two-pass color quantization */ internal int m_desired_number_of_colors; /* max # colors to use in created colormap */ internal bool m_enable_1pass_quant; /* enable future use of 1-pass quantizer */ internal bool m_enable_external_quant;/* enable future use of external colormap */ internal bool m_enable_2pass_quant; /* enable future use of 2-pass quantizer */ internal int m_output_width; /* scaled image width */ internal int m_output_height; /* scaled image height */ internal int m_out_color_components; /* # of color components in out_color_space */ /* # of color components returned * output_components is 1 (a colormap index) when quantizing colors; * otherwise it equals out_color_components. */ internal int m_output_components; internal int m_rec_outbuf_height; /* min recommended height of scanline buffer */ internal int m_actual_number_of_colors; /* number of entries in use */ internal byte[][] m_colormap; /* The color map as a 2-D pixel array */ internal int m_output_scanline; /* 0 .. output_height-1 */ internal int m_input_scan_number; /* Number of SOS markers seen so far */ internal int m_input_iMCU_row; /* Number of iMCU rows completed */ internal int m_output_scan_number; /* Nominal scan number being displayed */ internal int m_output_iMCU_row; /* Number of iMCU rows read */ internal int[][] m_coef_bits; /* -1 or current Al value for each coef */ /* Internal JPEG parameters --- the application usually need not look at * these fields. Note that the decompressor output side may not use * any parameters that can change between scans. */ /* Quantization and Huffman tables are carried forward across input * data-streams when processing abbreviated JPEG data-streams. */ internal JpegQuantizationTable[] m_quant_tbl_ptrs = new JpegQuantizationTable[JpegConstants.NumberOfQuantTables]; /* ptrs to coefficient quantization tables, or null if not defined */ internal JpegHuffmanTable[] m_dc_huff_tbl_ptrs = new JpegHuffmanTable[JpegConstants.NumberOfHuffmanTables]; internal JpegHuffmanTable[] m_ac_huff_tbl_ptrs = new JpegHuffmanTable[JpegConstants.NumberOfHuffmanTables]; /* ptrs to Huffman coding tables, or null if not defined */ /* These parameters are never carried across data-streams, since they * are given in SOF/SOS markers or defined to be reset by SOI. */ internal int m_data_precision; /* bits of precision in image data */ /* m_comp_info[i] describes component that appears i'th in SOF */ private JpegComponent[] m_comp_info; internal bool m_progressive_mode; /* true if SOFn specifies progressive mode */ internal int m_restart_interval; /* MCUs per restart interval, or 0 for no restart */ /* These fields record data obtained from optional markers recognized by * the JPEG library. */ internal bool m_saw_JFIF_marker; /* true iff a JFIF APP0 marker was found */ /* Data copied from JFIF marker; only valid if saw_JFIF_marker is true: */ internal byte m_JFIF_major_version; /* JFIF version number */ internal byte m_JFIF_minor_version; internal DensityUnit m_density_unit; /* JFIF code for pixel size units */ internal short m_X_density; /* Horizontal pixel density */ internal short m_Y_density; /* Vertical pixel density */ internal bool m_saw_Adobe_marker; /* true iff an Adobe APP14 marker was found */ internal byte m_Adobe_transform; /* Color transform code from Adobe marker */ internal bool m_CCIR601_sampling; /* true=first samples are co-sited */ internal List m_marker_list; /* Head of list of saved markers */ /* Remaining fields are known throughout decompressor, but generally * should not be touched by a surrounding application. */ /* * These fields are computed during decompression startup */ internal int m_max_h_samp_factor; /* largest h_samp_factor */ internal int m_max_v_samp_factor; /* largest v_samp_factor */ internal int m_min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ internal int m_total_iMCU_rows; /* # of iMCU rows in image */ /* The coefficient controller's input and output progress is measured in * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows * in fully interleaved JPEG scans, but are used whether the scan is * interleaved or not. We define an iMCU row as v_samp_factor DCT block * rows of each component. Therefore, the IDCT output contains * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row. */ internal byte[] m_sample_range_limit; /* table for fast range-limiting */ internal int m_sampleRangeLimitOffset; /* * These fields are valid during any one scan. * They describe the components and MCUs actually appearing in the scan. * Note that the decompressor output side must not use these fields. */ internal int m_comps_in_scan; /* # of JPEG components in this scan */ internal int[] m_cur_comp_info = new int[JpegConstants.MaxComponentsInScan]; /* *cur_comp_info[i] describes component that appears i'th in SOS */ internal int m_MCUs_per_row; /* # of MCUs across the image */ internal int m_MCU_rows_in_scan; /* # of MCU rows in the image */ internal int m_blocks_in_MCU; /* # of DCT blocks per MCU */ internal int[] m_MCU_membership = new int[JpegConstants.DecompressorMaxBlocksInMCU]; /* MCU_membership[i] is index in cur_comp_info of component owning */ /* i'th block in an MCU */ /* progressive JPEG parameters for scan */ internal int m_Ss; internal int m_Se; internal int m_Ah; internal int m_Al; /* This field is shared between entropy decoder and marker parser. * It is either zero or the code of a JPEG marker that has been * read from the data source, but has not yet been processed. */ internal int m_unread_marker; /* * Links to decompression sub-objects (methods, private variables of modules) */ internal JpegDecompressorMaster m_master; internal JpegDecompressorMainController m_main; internal JpegDecompressorCoefController m_coef; internal JpegDecompressorPostController m_post; internal JpegInputController m_inputctl; internal JpegMarkerReader m_marker; internal JpegEntropyDecoder m_entropy; internal JpegInverseDCT m_idct; internal JpegUpsampler m_upsample; internal ColorDeconverter m_cconvert; internal ColorQuantizer m_cquantize; /// /// Initializes a new instance of the class. /// /// public JpegDecompressor() : base() { initialize(); } /// /// Retrieves true because this is a decompressor. /// /// true public override bool IsDecompressor { get { return true; } } /// /// Gets or sets the source for decompression. /// /// The source for decompression. public LibJpeg.Jpeg_Source Src { get { return m_src; } set { m_src = value; } } /* Basic description of image --- filled in by jpeg_read_header(). */ /* Application may inspect these values to decide how to process image. */ /// /// Gets the width of image, set by /// /// The width of image. /// Decompression parameter selection public int Image_width { get { return m_image_width; } } /// /// Gets the height of image, set by /// /// The height of image. /// Decompression parameter selection public int Image_height { get { return m_image_height; } } /// /// Gets the number of color components in JPEG image. /// /// The number of color components. /// Decompression parameter selection public int Num_components { get { return m_num_components; } } /// /// Gets or sets the colorspace of JPEG image. /// /// The colorspace of JPEG image. /// Decompression parameter selection public LibJpeg.ColorSpace Jpeg_color_space { get { return m_jpeg_color_space; } set { m_jpeg_color_space = value; } } /// /// Gets the list of loaded special markers. /// /// All the special markers in the file appear in this list, in order of /// their occurrence in the file (but omitting any markers of types you didn't ask for) /// /// The list of loaded special markers. /// Special markers public ReadOnlyCollection Marker_list { get { return m_marker_list.AsReadOnly(); } } /* Decompression processing parameters --- these fields must be set before * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes * them to default values. */ /// /// Gets or sets the output color space. /// /// The output color space. /// Decompression parameter selection public LibJpeg.ColorSpace Out_color_space { get { return m_out_color_space; } set { m_out_color_space = value; } } /// /// Gets or sets the numerator of the fraction of image scaling. /// /// Scale the image by the fraction Scale_num/Scale_denom. /// Default is 1/1, or no scaling. Currently, the only supported scaling ratios are 1/1, 1/2, 1/4, and 1/8. /// (The library design allows for arbitrary scaling ratios but this is not likely to be implemented any time soon.) /// /// Smaller scaling ratios permit significantly faster decoding since fewer pixels /// need to be processed and a simpler DCT method can be used. /// /// Decompression parameter selection public int Scale_num { get { return m_scale_num; } set { m_scale_num = value; } } /// /// Gets or sets the denominator of the fraction of image scaling. /// /// Scale the image by the fraction Scale_num/Scale_denom. /// Default is 1/1, or no scaling. Currently, the only supported scaling ratios are 1/1, 1/2, 1/4, and 1/8. /// (The library design allows for arbitrary scaling ratios but this is not likely to be implemented any time soon.) /// /// Smaller scaling ratios permit significantly faster decoding since fewer pixels /// need to be processed and a simpler DCT method can be used. /// /// Decompression parameter selection public int Scale_denom { get { return m_scale_denom; } set { m_scale_denom = value; } } /// /// Gets or sets a value indicating whether to use buffered-image mode. /// /// true if buffered-image mode is turned on; otherwise, false. /// Buffered-image mode public bool Buffered_image { get { return m_buffered_image; } set { m_buffered_image = value; } } /// /// Enable or disable raw data output. /// /// true if raw data output is enabled; otherwise, false. /// Default value: false
/// Set this to true before /// if you need to obtain raw data output. ///
/// public bool Raw_data_out { get { return m_raw_data_out; } set { m_raw_data_out = value; } } /// /// Gets or sets the algorithm used for the DCT step. /// /// The algorithm used for the DCT step. /// Decompression parameter selection public LibJpeg.DCTMethod Dct_method { get { return m_dct_method; } set { m_dct_method = value; } } /// /// Enable or disable up-sampling of chroma components. /// /// If true, do careful up-sampling of chroma components. /// If false, a faster but sloppier method is used. /// The visual impact of the sloppier method is often very small. /// /// Default value: true /// Decompression parameter selection public bool Do_fancy_upsampling { get { return m_do_fancy_upsampling; } set { m_do_fancy_upsampling = value; } } /// /// Apply inter-block smoothing in early stages of decoding progressive JPEG files. /// /// If true, inter-block smoothing is applied in early stages of decoding progressive JPEG files; /// if false, not. Early progression stages look "fuzzy" with smoothing, "blocky" without. /// Default value: true
/// In any case, block smoothing ceases to be applied after the first few AC coefficients are /// known to full accuracy, so it is relevant only when using /// buffered-image mode for progressive images. ///
/// Decompression parameter selection public bool Do_block_smoothing { get { return m_do_block_smoothing; } set { m_do_block_smoothing = value; } } /// /// Colors quantization. /// /// If set true, colormapped output will be delivered.
/// Default value: false, meaning that full-color output will be delivered. ///
/// Decompression parameter selection public bool Quantize_colors { get { return m_quantize_colors; } set { m_quantize_colors = value; } } /* the following are ignored if not quantize_colors: */ /// /// Selects color dithering method. /// /// Default value: . /// Ignored if is false.
/// At present, ordered dither is implemented only in the single-pass, standard-colormap case. /// If you ask for ordered dither when is true /// or when you supply an external color map, you'll get F-S dithering. ///
/// /// Decompression parameter selection public LibJpeg.DitherMode Dither_mode { get { return m_dither_mode; } set { m_dither_mode = value; } } /// /// Gets or sets a value indicating whether to use two-pass color quantization. /// /// If true, an extra pass over the image is made to select a custom color map for the image. /// This usually looks a lot better than the one-size-fits-all colormap that is used otherwise. /// Ignored when the application supplies its own color map.
/// /// Default value: true ///
/// Ignored if is false.
///
/// /// Decompression parameter selection public bool Two_pass_quantize { get { return m_two_pass_quantize; } set { m_two_pass_quantize = value; } } /// /// Maximum number of colors to use in generating a library-supplied color map. /// /// Default value: 256. /// Ignored if is false.
/// The actual number of colors is returned in a . ///
/// /// Decompression parameter selection public int Desired_number_of_colors { get { return m_desired_number_of_colors; } set { m_desired_number_of_colors = value; } } /* these are significant only in buffered-image mode: */ /// /// Enable future use of 1-pass quantizer. /// /// Default value: false /// Significant only in buffered-image mode. /// Buffered-image mode public bool Enable_1pass_quant { get { return m_enable_1pass_quant; } set { m_enable_1pass_quant = value; } } /// /// Enable future use of external colormap. /// /// Default value: false /// Significant only in buffered-image mode. /// Buffered-image mode public bool Enable_external_quant { get { return m_enable_external_quant; } set { m_enable_external_quant = value; } } /// /// Enable future use of 2-pass quantizer. /// /// Default value: false /// Significant only in buffered-image mode. /// Buffered-image mode public bool Enable_2pass_quant { get { return m_enable_2pass_quant; } set { m_enable_2pass_quant = value; } } /* Description of actual output image that will be returned to application. * These fields are computed by jpeg_start_decompress(). * You can also use jpeg_calc_output_dimensions() to determine these values * in advance of calling jpeg_start_decompress(). */ /// /// Gets the actual width of output image. /// /// The width of output image. /// Computed by . /// You can also use to determine this value /// in advance of calling . /// public int Output_width { get { return m_output_width; } } /// /// Gets the actual height of output image. /// /// The height of output image. /// Computed by . /// You can also use to determine this value /// in advance of calling . /// public int Output_height { get { return m_output_height; } } /// /// Gets the number of color components in . /// /// Computed by . /// You can also use to determine this value /// in advance of calling . /// The number of color components. /// /// Decompression parameter selection public int Out_color_components { get { return m_out_color_components; } } /// /// Gets the number of color components returned. /// /// Computed by . /// You can also use to determine this value /// in advance of calling . /// When quantizing colors, /// Output_components is 1, indicating a single color map index per pixel. /// Otherwise it equals to . /// /// /// Decompression parameter selection public int Output_components { get { return m_output_components; } } /// /// Gets the recommended height of scanline buffer. /// /// In high-quality modes, Rec_outbuf_height is always 1, but some faster, /// lower-quality modes set it to larger values (typically 2 to 4). /// Computed by . /// You can also use to determine this value /// in advance of calling .
/// /// Rec_outbuf_height is the recommended minimum height (in scanlines) /// of the buffer passed to . /// If the buffer is smaller, the library will still work, but time will be wasted due /// to unnecessary data copying. If you are going to ask for a high-speed processing mode, /// you may as well go to the trouble of honoring Rec_outbuf_height so as to avoid data copying. /// (An output buffer larger than Rec_outbuf_height lines is OK, but won't provide /// any material speed improvement over that height.) ///
/// Decompression parameter selection public int Rec_outbuf_height { get { return m_rec_outbuf_height; } } /* When quantizing colors, the output colormap is described by these fields. * The application can supply a colormap by setting colormap non-null before * calling jpeg_start_decompress; otherwise a colormap is created during * jpeg_start_decompress or jpeg_start_output. * The map has out_color_components rows and actual_number_of_colors columns. */ /// /// The number of colors in the color map. /// /// The number of colors in the color map. /// /// Decompression parameter selection public int Actual_number_of_colors { get { return m_actual_number_of_colors; } set { m_actual_number_of_colors = value; } } /// /// The color map, represented as a 2-D pixel array of rows /// and columns. /// /// Colormap is set to null by . /// The application can supply a color map by setting Colormap non-null and setting /// to the map size. /// /// Ignored if not quantizing.
/// Implementation restriction: at present, an externally supplied Colormap /// is only accepted for 3-component output color spaces. ///
/// /// /// Decompression parameter selection public byte[][] Colormap { get { return m_colormap; } set { m_colormap = value; } } /* State variables: these variables indicate the progress of decompression. * The application may examine these but must not modify them. */ /* Row index of next scanline to be read from jpeg_read_scanlines(). * Application may use this to control its processing loop, e.g., * "while (output_scanline < output_height)". */ /// /// Gets the number of scanlines returned so far. /// /// The output_scanline. /// Usually you can just use this variable as the loop counter, /// so that the loop test looks like /// while (cinfo.Output_scanline < cinfo.Output_height) /// Decompression details public int Output_scanline { get { return m_output_scanline; } } /* Current input scan number and number of iMCU rows completed in scan. * These indicate the progress of the decompressor input side. */ /// /// Gets the number of SOS markers seen so far. /// /// The number of SOS markers seen so far. /// Indicates the progress of the decompressor input side. public int Input_scan_number { get { return m_input_scan_number; } } /// /// Gets the number of iMCU rows completed. /// /// The number of iMCU rows completed. /// Indicates the progress of the decompressor input side. public int Input_iMCU_row { get { return m_input_iMCU_row; } } /* The "output scan number" is the notional scan being displayed by the * output side. The decompressor will not allow output scan/row number * to get ahead of input scan/row, but it can fall arbitrarily far behind. */ /// /// Gets the nominal scan number being displayed. /// /// The nominal scan number being displayed. public int Output_scan_number { get { return m_output_scan_number; } } /// /// Gets the number of iMCU rows read. /// /// The number of iMCU rows read. public int Output_iMCU_row { get { return m_output_iMCU_row; } } /* Current progression status. coef_bits[c][i] indicates the precision * with which component c's DCT coefficient i (in zigzag order) is known. * It is -1 when no data has yet been received, otherwise it is the point * transform (shift) value for the most recent scan of the coefficient * (thus, 0 at completion of the progression). * This is null when reading a non-progressive file. */ /// /// Gets the current progression status.. /// /// Coef_bits[c][i] indicates the precision with /// which component c's DCT coefficient i (in zigzag order) is known. /// It is -1 when no data has yet been received, otherwise /// it is the point transform (shift) value for the most recent scan of the coefficient /// (thus, 0 at completion of the progression). This is null when reading a non-progressive file. /// /// Progressive JPEG support public int[][] Coef_bits { get { return m_coef_bits; } } // These fields record data obtained from optional markers // recognized by the JPEG library. /// /// Gets the resolution information from JFIF marker. /// /// The information from JFIF marker. /// /// /// Decompression parameter selection public DensityUnit Density_unit { get { return m_density_unit; } } /// /// Gets the horizontal component of pixel ratio. /// /// The horizontal component of pixel ratio. /// /// public short X_density { get { return m_X_density; } } /// /// Gets the vertical component of pixel ratio. /// /// The vertical component of pixel ratio. /// /// public short Y_density { get { return m_Y_density; } } /// /// Gets the data precision. /// /// The data precision. public int Data_precision { get { return m_data_precision; } //set { m_data_precision = value; } } /// /// Gets the largest vertical sample factor. /// /// The largest vertical sample factor. public int Max_v_samp_factor { get { return m_max_v_samp_factor; } //set { m_max_v_samp_factor = value; } } /// /// Gets the last read and unprocessed JPEG marker. /// /// It is either zero or the code of a JPEG marker that has been /// read from the data source, but has not yet been processed. /// /// /// Special markers public int Unread_marker { get { return m_unread_marker; } } /// /// Comp_info[i] describes component that appears i'th in SOF /// /// The components in SOF. /// public JpegComponent[] Comp_info { get { return m_comp_info; } internal set { m_comp_info = value; } } /// /// Sets input stream. /// /// The input stream. /// /// The caller must have already opened the stream, and is responsible /// for closing it after finishing decompression. /// /// Decompression details public void jpeg_stdio_src(Stream infile) { /* The source object and input buffer are made permanent so that a series * of JPEG images can be read from the same file by calling jpeg_stdio_src * only before the first one. (If we discarded the buffer at the end of * one image, we'd likely lose the start of the next one.) * This makes it unsafe to use this manager and a different source * manager serially with the same JPEG object. Caveat programmer. */ if (m_src == null) { /* first time for this JPEG object? */ m_src = new SourceManagerImpl(this); } SourceManagerImpl m = m_src as SourceManagerImpl; if (m != null) m.Attach(infile); } /// /// Decompression startup: this will read the source datastream header markers, up to the beginning of the compressed data proper. /// /// Read a description of Return Value. /// /// If you pass require_image=true (normal case), you need not check for a /// return code; an abbreviated file will cause /// an error exit. is only possible if you use a data source /// module that can give a suspension return.

/// /// This method will read as far as the first SOS marker (ie, actual start of compressed data), /// and will save all tables and parameters in the JPEG object. It will also initialize the /// decompression parameters to default values, and finally return . /// On return, the application may adjust the decompression parameters and then call /// . (Or, if the application only wanted to /// determine the image parameters, the data need not be decompressed. In that case, call /// to release any temporary space.)

/// /// If an abbreviated (tables only) datastream is presented, the routine will return /// upon reaching EOI. The application may then re-use /// the JPEG object to read the abbreviated image datastream(s). It is unnecessary (but OK) to call /// jpeg_abort in this case. /// The return code only occurs if the data source module /// requests suspension of the decompressor. In this case the application should load more source /// data and then re-call jpeg_read_header to resume processing.

/// /// If a non-suspending data source is used and require_image is true, /// then the return code need not be inspected since only is possible. ///
/// Need only initialize JPEG object and supply a data source before calling.
/// On return, the image dimensions and other info have been stored in the JPEG object. /// The application may wish to consult this information before selecting decompression parameters.
/// This routine is now just a front end to , with some extra error checking. ///
/// Decompression details /// Decompression parameter selection public ReadResult jpeg_read_header(bool require_image) { if (m_global_state != JpegState.DSTATE_START && m_global_state != JpegState.DSTATE_INHEADER) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); ReadResult retcode = jpeg_consume_input(); switch (retcode) { case ReadResult.Reached_SOS: return ReadResult.Header_Ok; case ReadResult.Reached_EOI: if (require_image) /* Complain if application wanted an image */ throw new Exception("JPEG datastream contains no image"); /* Reset to start state; it would be safer to require the application to * call jpeg_abort, but we can't change it now for compatibility reasons. * A side effect is to free any temporary memory (there shouldn't be any). */ jpeg_abort(); /* sets state = DSTATE_START */ return ReadResult.Header_Tables_Only; case ReadResult.Suspended: /* no work */ break; } return ReadResult.Suspended; } ////////////////////////////////////////////////////////////////////////// // Main entry points for decompression /// /// Decompression initialization. /// /// Returns false if suspended. The return value need be inspected /// only if a suspending data source is used. /// /// jpeg_read_header must be completed before calling this.
/// /// If a multipass operating mode was selected, this will do all but the last pass, and thus may take a great deal of time. ///
/// /// Decompression details public bool jpeg_start_decompress() { if (m_global_state == JpegState.DSTATE_READY) { /* First call: initialize master control, select active modules */ m_master = new JpegDecompressorMaster(this); if (m_buffered_image) { /* No more work here; expecting jpeg_start_output next */ m_global_state = JpegState.DSTATE_BUFIMAGE; return true; } m_global_state = JpegState.DSTATE_PRELOAD; } if (m_global_state == JpegState.DSTATE_PRELOAD) { /* If file has multiple scans, absorb them all into the coef buffer */ if (m_inputctl.HasMultipleScans()) { for (; ; ) { ReadResult retcode; /* Absorb some more input */ retcode = m_inputctl.consume_input(); if (retcode == ReadResult.Suspended) return false; if (retcode == ReadResult.Reached_EOI) break; } } m_output_scan_number = m_input_scan_number; } else if (m_global_state != JpegState.DSTATE_PRESCAN) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); /* Perform any dummy output passes, and set up for the final pass */ return output_pass_setup(); } /// /// Read some scanlines of data from the JPEG decompressor. /// /// Buffer for filling. /// Required number of lines. /// The return value will be the number of lines actually read. /// This may be less than the number requested in several cases, including /// bottom of image, data source suspension, and operating modes that emit multiple scanlines at a time. /// /// We warn about excess calls to jpeg_read_scanlines since this likely signals an /// application programmer error. However, an oversize buffer (max_lines > scanlines remaining) /// is not an error. /// /// Decompression details public int jpeg_read_scanlines(byte[][] scanlines, int max_lines) { if (m_global_state != JpegState.DSTATE_SCANNING) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); if (m_output_scanline >= m_output_height) { return 0; } /* Process some data */ int row_ctr = 0; m_main.process_data(scanlines, ref row_ctr, max_lines); m_output_scanline += row_ctr; return row_ctr; } /// /// Finish JPEG decompression. /// /// Returns false if suspended. The return value need be inspected /// only if a suspending data source is used. /// /// This will normally just verify the file trailer and release temp storage. /// /// Decompression details public bool jpeg_finish_decompress() { if ((m_global_state == JpegState.DSTATE_SCANNING || m_global_state == JpegState.DSTATE_RAW_OK) && !m_buffered_image) { /* Terminate final pass of non-buffered mode */ if (m_output_scanline < m_output_height) throw new Exception("Application transferred too few scanlines"); m_master.finish_output_pass(); m_global_state = JpegState.DSTATE_STOPPING; } else if (m_global_state == JpegState.DSTATE_BUFIMAGE) { /* Finishing after a buffered-image operation */ m_global_state = JpegState.DSTATE_STOPPING; } else if (m_global_state != JpegState.DSTATE_STOPPING) { /* STOPPING = repeat call after a suspension, anything else is error */ throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); } /* Read until EOI */ while (!m_inputctl.EOIReached()) { if (m_inputctl.consume_input() == ReadResult.Suspended) { /* Suspend, come back later */ return false; } } /* Do final cleanup */ m_src.term_source(); /* We can use jpeg_abort to release memory and reset global_state */ jpeg_abort(); return true; } /// /// Alternate entry point to read raw data. /// /// The raw data. /// The number of scanlines for reading. /// The number of lines actually read. /// Replaces jpeg_read_scanlines /// when reading raw downsampled data. Processes exactly one iMCU row per call, unless suspended. /// public int jpeg_read_raw_data(byte[][][] data, int max_lines) { if (m_global_state != JpegState.DSTATE_RAW_OK) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); if (m_output_scanline >= m_output_height) { return 0; } /* Verify that at least one iMCU row can be returned. */ int lines_per_iMCU_row = m_max_v_samp_factor * m_min_DCT_scaled_size; if (max_lines < lines_per_iMCU_row) throw new Exception("Buffer passed to JPEG library is too small"); int componentCount = data.Length; // maybe we should use max_lines here ComponentBuffer[] cb = new ComponentBuffer[componentCount]; for (int i = 0; i < componentCount; i++) { cb[i] = new ComponentBuffer(); cb[i].SetBuffer(data[i], null, 0); } /* Decompress directly into user's buffer. */ if (m_coef.decompress_data(cb) == ReadResult.Suspended) { /* suspension forced, can do nothing more */ return 0; } /* OK, we processed one iMCU row. */ m_output_scanline += lines_per_iMCU_row; return lines_per_iMCU_row; } ////////////////////////////////////////////////////////////////////////// // Additional entry points for buffered-image mode. /// /// Is there more than one scan? /// /// true if image has more than one scan; otherwise, false /// If you are concerned about maximum performance on baseline JPEG files, /// you should use buffered-image mode only /// when the incoming file actually has multiple scans. This can be tested by calling this method. /// public bool jpeg_has_multiple_scans() { /* Only valid after jpeg_read_header completes */ if (m_global_state < JpegState.DSTATE_READY || m_global_state > JpegState.DSTATE_STOPPING) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); return m_inputctl.HasMultipleScans(); } /// /// Initialize for an output pass in buffered-image mode. /// /// Indicates which scan of the input file is to be displayed; /// the scans are numbered starting at 1 for this purpose. /// true if done; false if suspended /// /// Buffered-image mode public bool jpeg_start_output(int scan_number) { if (m_global_state != JpegState.DSTATE_BUFIMAGE && m_global_state != JpegState.DSTATE_PRESCAN) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); /* Limit scan number to valid range */ if (scan_number <= 0) scan_number = 1; if (m_inputctl.EOIReached() && scan_number > m_input_scan_number) scan_number = m_input_scan_number; m_output_scan_number = scan_number; /* Perform any dummy output passes, and set up for the real pass */ return output_pass_setup(); } /// /// Finish up after an output pass in buffered-image mode. /// /// Returns false if suspended. The return value need be inspected only if a suspending data source is used. /// /// Buffered-image mode public bool jpeg_finish_output() { if ((m_global_state == JpegState.DSTATE_SCANNING || m_global_state == JpegState.DSTATE_RAW_OK) && m_buffered_image) { /* Terminate this pass. */ /* We do not require the whole pass to have been completed. */ m_master.finish_output_pass(); m_global_state = JpegState.DSTATE_BUFPOST; } else if (m_global_state != JpegState.DSTATE_BUFPOST) { /* BUFPOST = repeat call after a suspension, anything else is error */ throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); } /* Read markers looking for SOS or EOI */ while (m_input_scan_number <= m_output_scan_number && !m_inputctl.EOIReached()) { if (m_inputctl.consume_input() == ReadResult.Suspended) { /* Suspend, come back later */ return false; } } m_global_state = JpegState.DSTATE_BUFIMAGE; return true; } /// /// Indicates if we have finished reading the input file. /// /// true if we have finished reading the input file. /// Buffered-image mode public bool jpeg_input_complete() { /* Check for valid jpeg object */ if (m_global_state < JpegState.DSTATE_START || m_global_state > JpegState.DSTATE_STOPPING) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); return m_inputctl.EOIReached(); } /// /// Consume data in advance of what the decompressor requires. /// /// The result of data consumption. /// This routine can be called at any time after initializing the JPEG object. /// It reads some additional data and returns when one of the indicated significant events /// occurs. If called after the EOI marker is reached, it will immediately return /// without attempting to read more data. public ReadResult jpeg_consume_input() { ReadResult retcode = ReadResult.Suspended; /* NB: every possible DSTATE value should be listed in this switch */ switch (m_global_state) { case JpegState.DSTATE_START: jpeg_consume_input_start(); retcode = jpeg_consume_input_inHeader(); break; case JpegState.DSTATE_INHEADER: retcode = jpeg_consume_input_inHeader(); break; case JpegState.DSTATE_READY: /* Can't advance past first SOS until start_decompress is called */ retcode = ReadResult.Reached_SOS; break; case JpegState.DSTATE_PRELOAD: case JpegState.DSTATE_PRESCAN: case JpegState.DSTATE_SCANNING: case JpegState.DSTATE_RAW_OK: case JpegState.DSTATE_BUFIMAGE: case JpegState.DSTATE_BUFPOST: case JpegState.DSTATE_STOPPING: retcode = m_inputctl.consume_input(); break; default: throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); } return retcode; } /// /// Pre-calculate output image dimensions and related values for current decompression parameters. /// /// This is allowed for possible use by application. Hence it mustn't do anything /// that can't be done twice. Also note that it may be called before the master module is initialized! /// public void jpeg_calc_output_dimensions() { // Do computations that are needed before master selection phase /* Prevent application from calling me at wrong times */ if (m_global_state != JpegState.DSTATE_READY) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); /* Compute actual output image dimensions and DCT scaling choices. */ if (m_scale_num * 8 <= m_scale_denom) { /* Provide 1/8 scaling */ m_output_width = JpegUtils.jdiv_round_up(m_image_width, 8); m_output_height = JpegUtils.jdiv_round_up(m_image_height, 8); m_min_DCT_scaled_size = 1; } else if (m_scale_num * 4 <= m_scale_denom) { /* Provide 1/4 scaling */ m_output_width = JpegUtils.jdiv_round_up(m_image_width, 4); m_output_height = JpegUtils.jdiv_round_up(m_image_height, 4); m_min_DCT_scaled_size = 2; } else if (m_scale_num * 2 <= m_scale_denom) { /* Provide 1/2 scaling */ m_output_width = JpegUtils.jdiv_round_up(m_image_width, 2); m_output_height = JpegUtils.jdiv_round_up(m_image_height, 2); m_min_DCT_scaled_size = 4; } else { /* Provide 1/1 scaling */ m_output_width = m_image_width; m_output_height = m_image_height; m_min_DCT_scaled_size = JpegConstants.DCTSize; } /* In selecting the actual DCT scaling for each component, we try to * scale up the chroma components via IDCT scaling rather than upsampling. * This saves time if the upsampler gets to use 1:1 scaling. * Note this code assumes that the supported DCT scalings are powers of 2. */ for (int ci = 0; ci < m_num_components; ci++) { int ssize = m_min_DCT_scaled_size; while (ssize < JpegConstants.DCTSize && (m_comp_info[ci].H_samp_factor * ssize * 2 <= m_max_h_samp_factor * m_min_DCT_scaled_size) && (m_comp_info[ci].V_samp_factor * ssize * 2 <= m_max_v_samp_factor * m_min_DCT_scaled_size)) { ssize = ssize * 2; } m_comp_info[ci].DCT_scaled_size = ssize; } /* Recompute downsampled dimensions of components; * application needs to know these if using raw downsampled data. */ for (int ci = 0; ci < m_num_components; ci++) { /* Size in samples, after IDCT scaling */ m_comp_info[ci].downsampled_width = JpegUtils.jdiv_round_up( m_image_width * m_comp_info[ci].H_samp_factor * m_comp_info[ci].DCT_scaled_size, m_max_h_samp_factor * JpegConstants.DCTSize); m_comp_info[ci].downsampled_height = JpegUtils.jdiv_round_up( m_image_height * m_comp_info[ci].V_samp_factor * m_comp_info[ci].DCT_scaled_size, m_max_v_samp_factor * JpegConstants.DCTSize); } /* Report number of components in selected colorspace. */ /* Probably this should be in the color conversion module... */ switch (m_out_color_space) { case ColorSpace.Grayscale: m_out_color_components = 1; break; case ColorSpace.RGB: case ColorSpace.YCbCr: m_out_color_components = 3; break; case ColorSpace.CMYK: case ColorSpace.YCCK: m_out_color_components = 4; break; default: /* else must be same colorspace as in file */ m_out_color_components = m_num_components; break; } m_output_components = (m_quantize_colors ? 1 : m_out_color_components); /* See if up-sampler will want to emit more than one row at a time */ if (use_merged_upsample()) m_rec_outbuf_height = m_max_v_samp_factor; else m_rec_outbuf_height = 1; } /// /// Read or write the raw DCT coefficient arrays from a JPEG file (useful for lossless transcoding). /// /// Returns null if suspended. This case need be checked only /// if a suspending data source is used. /// /// /// jpeg_read_header must be completed before calling this.
/// /// The entire image is read into a set of virtual coefficient-block arrays, one per component. /// The return value is an array of virtual-array descriptors.
/// /// An alternative usage is to simply obtain access to the coefficient arrays during a /// buffered-image mode decompression operation. This is allowed after any /// jpeg_finish_output call. The arrays can be accessed /// until jpeg_finish_decompress is called. /// Note that any call to the library may reposition the arrays, /// so don't rely on results to stay valid across library calls. ///
public JpegVirtualArray[] jpeg_read_coefficients() { if (m_global_state == JpegState.DSTATE_READY) { /* First call: initialize active modules */ transdecode_master_selection(); m_global_state = JpegState.DSTATE_RDCOEFS; } if (m_global_state == JpegState.DSTATE_RDCOEFS) { /* Absorb whole file into the coef buffer */ for (; ; ) { ReadResult retcode; /* Absorb some more input */ retcode = m_inputctl.consume_input(); if (retcode == ReadResult.Suspended) return null; if (retcode == ReadResult.Reached_EOI) break; } /* Set state so that jpeg_finish_decompress does the right thing */ m_global_state = JpegState.DSTATE_STOPPING; } /* At this point we should be in state DSTATE_STOPPING if being used * standalone, or in state DSTATE_BUFIMAGE if being invoked to get access * to the coefficients during a full buffered-image-mode decompression. */ if ((m_global_state == JpegState.DSTATE_STOPPING || m_global_state == JpegState.DSTATE_BUFIMAGE) && m_buffered_image) return m_coef.GetCoefArrays(); /* Oops, improper usage */ throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); } /// /// Initializes the compression object with default parameters, then copy from the source object /// all parameters needed for lossless transcoding. /// /// Target JPEG compression object. /// Parameters that can be varied without loss (such as scan script and /// Huffman optimization) are left in their default states. public void jpeg_copy_critical_parameters(JpegCompressor dstinfo) { /* Safety check to ensure start_compress not called yet. */ if (dstinfo.m_global_state != JpegState.CSTATE_START) throw new Exception(String.Format("Improper call to JPEG library in state {0}", (int)m_global_state)); /* Copy fundamental image dimensions */ dstinfo.m_image_width = m_image_width; dstinfo.m_image_height = m_image_height; dstinfo.m_input_components = m_num_components; dstinfo.m_in_color_space = m_jpeg_color_space; /* Initialize all parameters to default values */ dstinfo.jpeg_set_defaults(); /* jpeg_set_defaults may choose wrong colorspace, eg YCbCr if input is RGB. * Fix it to get the right header markers for the image colorspace. */ dstinfo.jpeg_set_colorspace(m_jpeg_color_space); dstinfo.m_data_precision = m_data_precision; dstinfo.m_CCIR601_sampling = m_CCIR601_sampling; /* Copy the source's quantization tables. */ for (int tblno = 0; tblno < JpegConstants.NumberOfQuantTables; tblno++) { if (m_quant_tbl_ptrs[tblno] != null) { if (dstinfo.m_quant_tbl_ptrs[tblno] == null) dstinfo.m_quant_tbl_ptrs[tblno] = new JpegQuantizationTable(); Buffer.BlockCopy(m_quant_tbl_ptrs[tblno].quantval, 0, dstinfo.m_quant_tbl_ptrs[tblno].quantval, 0, dstinfo.m_quant_tbl_ptrs[tblno].quantval.Length * sizeof(short)); dstinfo.m_quant_tbl_ptrs[tblno].Sent_table = false; } } /* Copy the source's per-component info. * Note we assume jpeg_set_defaults has allocated the dest comp_info array. */ dstinfo.m_num_components = m_num_components; if (dstinfo.m_num_components < 1 || dstinfo.m_num_components > JpegConstants.MaxComponents) throw new Exception(String.Format("Too many color components: {0}, max {1}", dstinfo.m_num_components, JpegConstants.MaxComponents)); for (int ci = 0; ci < dstinfo.m_num_components; ci++) { dstinfo.Component_info[ci].Component_id = m_comp_info[ci].Component_id; dstinfo.Component_info[ci].H_samp_factor = m_comp_info[ci].H_samp_factor; dstinfo.Component_info[ci].V_samp_factor = m_comp_info[ci].V_samp_factor; dstinfo.Component_info[ci].Quant_tbl_no = m_comp_info[ci].Quant_tbl_no; /* Make sure saved quantization table for component matches the qtable * slot. If not, the input file re-used this qtable slot. * IJG encoder currently cannot duplicate this. */ int tblno = dstinfo.Component_info[ci].Quant_tbl_no; if (tblno < 0 || tblno >= JpegConstants.NumberOfQuantTables || m_quant_tbl_ptrs[tblno] == null) throw new Exception(String.Format("Quantization table 0x{0:X2} was not defined", tblno)); JpegQuantizationTable c_quant = m_comp_info[ci].quant_table; if (c_quant != null) { JpegQuantizationTable slot_quant = m_quant_tbl_ptrs[tblno]; for (int coefi = 0; coefi < JpegConstants.DCTSize2; coefi++) { if (c_quant.quantval[coefi] != slot_quant.quantval[coefi]) throw new Exception(String.Format("Cannot transcode due to multiple use of quantization table {0}", tblno)); } } /* Note: we do not copy the source's Huffman table assignments; * instead we rely on jpeg_set_colorspace to have made a suitable choice. */ } /* Also copy JFIF version and resolution information, if available. * Strictly speaking this isn't "critical" info, but it's nearly * always appropriate to copy it if available. In particular, * if the application chooses to copy JFIF 1.02 extension markers from * the source file, we need to copy the version to make sure we don't * emit a file that has 1.02 extensions but a claimed version of 1.01. * We will *not*, however, copy version info from mislabeled "2.01" files. */ if (m_saw_JFIF_marker) { if (m_JFIF_major_version == 1) { dstinfo.m_JFIF_major_version = m_JFIF_major_version; dstinfo.m_JFIF_minor_version = m_JFIF_minor_version; } dstinfo.m_density_unit = m_density_unit; dstinfo.m_X_density = (short)m_X_density; dstinfo.m_Y_density = (short)m_Y_density; } } /// /// Aborts processing of a JPEG decompression operation. /// /// public void jpeg_abort_decompress() { jpeg_abort(); } /// /// Sets processor for special marker. /// /// The marker code. /// The processor. /// Allows you to supply your own routine to process /// COM and/or APPn markers on-the-fly as they are read. /// /// Special markers public void jpeg_set_marker_processor(int marker_code, jpeg_marker_parser_method routine) { m_marker.jpeg_set_marker_processor(marker_code, routine); } /// /// Control saving of COM and APPn markers into Marker_list. /// /// The marker type to save (see JpegMarkerType enumeration).
/// To arrange to save all the special marker types, you need to call this /// routine 17 times, for COM and APP0-APP15 markers. /// If the incoming marker is longer than length_limit data bytes, /// only length_limit bytes will be saved; this parameter allows you to avoid chewing up memory /// when you only need to see the first few bytes of a potentially large marker. If you want to save /// all the data, set length_limit to 0xFFFF; that is enough since marker lengths are only 16 bits. /// As a special case, setting length_limit to 0 prevents that marker type from being saved at all. /// (That is the default behavior, in fact.) /// /// /// Special markers public void jpeg_save_markers(int marker_code, int length_limit) { m_marker.jpeg_save_markers(marker_code, length_limit); } /// /// Determine whether merged upsample/color conversion should be used. /// CRUCIAL: this must match the actual capabilities of merged upsampler! /// internal bool use_merged_upsample() { /* Merging is the equivalent of plain box-filter upsampling */ if (m_do_fancy_upsampling || m_CCIR601_sampling) return false; /* UpsamplerImpl only supports YCC=>RGB color conversion */ if (m_jpeg_color_space != ColorSpace.YCbCr || m_num_components != 3 || m_out_color_space != ColorSpace.RGB || m_out_color_components != JpegConstants.RGB_PixelLength) { return false; } /* and it only handles 2h1v or 2h2v sampling ratios */ if (m_comp_info[0].H_samp_factor != 2 || m_comp_info[1].H_samp_factor != 1 || m_comp_info[2].H_samp_factor != 1 || m_comp_info[0].V_samp_factor > 2 || m_comp_info[1].V_samp_factor != 1 || m_comp_info[2].V_samp_factor != 1) { return false; } /* furthermore, it doesn't work if we've scaled the IDCTs differently */ if (m_comp_info[0].DCT_scaled_size != m_min_DCT_scaled_size || m_comp_info[1].DCT_scaled_size != m_min_DCT_scaled_size || m_comp_info[2].DCT_scaled_size != m_min_DCT_scaled_size) { return false; } /* ??? also need to test for upsample-time rescaling, when & if supported */ /* by golly, it'll work... */ return true; } /// /// Initialization of JPEG compression objects. /// The error manager must already be set up (in case memory manager fails). /// private void initialize() { /* Zero out pointers to permanent structures. */ m_src = null; for (int i = 0; i < JpegConstants.NumberOfQuantTables; i++) m_quant_tbl_ptrs[i] = null; for (int i = 0; i < JpegConstants.NumberOfHuffmanTables; i++) { m_dc_huff_tbl_ptrs[i] = null; m_ac_huff_tbl_ptrs[i] = null; } /* Initialize marker processor so application can override methods * for COM, APPn markers before calling jpeg_read_header. */ m_marker_list = new List(); m_marker = new JpegMarkerReader(this); /* And initialize the overall input controller. */ m_inputctl = new JpegInputController(this); /* OK, I'm ready */ m_global_state = JpegState.DSTATE_START; } /// /// Master selection of decompression modules for transcoding (that is, reading /// raw DCT coefficient arrays from an input JPEG file.) /// This substitutes for initialization of the full decompressor. /// private void transdecode_master_selection() { /* This is effectively a buffered-image operation. */ m_buffered_image = true; if (m_progressive_mode) m_entropy = new ProgressiveHuffmanDecoder(this); else m_entropy = new HuffEntropyDecoder(this); /* Always get a full-image coefficient buffer. */ m_coef = new JpegDecompressorCoefController(this, true); /* Initialize input side of decompressor to consume first scan. */ m_inputctl.start_input_pass(); } /// /// Set up for an output pass, and perform any dummy pass(es) needed. /// Common subroutine for jpeg_start_decompress and jpeg_start_output. /// Entry: global_state = DSTATE_PRESCAN only if previously suspended. /// Exit: If done, returns true and sets global_state for proper output mode. /// If suspended, returns false and sets global_state = DSTATE_PRESCAN. /// private bool output_pass_setup() { if (m_global_state != JpegState.DSTATE_PRESCAN) { /* First call: do pass setup */ m_master.prepare_for_output_pass(); m_output_scanline = 0; m_global_state = JpegState.DSTATE_PRESCAN; } /* Loop over any required dummy passes */ while (m_master.IsDummyPass()) { /* Crank through the dummy pass */ while (m_output_scanline < m_output_height) { int last_scanline; /* Process some data */ last_scanline = m_output_scanline; m_main.process_data(null, ref m_output_scanline, 0); if (m_output_scanline == last_scanline) { /* No progress made, must suspend */ return false; } } /* Finish up dummy pass, and set up for another one */ m_master.finish_output_pass(); m_master.prepare_for_output_pass(); m_output_scanline = 0; } /* Ready for application to drive output pass through * jpeg_read_scanlines or jpeg_read_raw_data. */ m_global_state = m_raw_data_out ? JpegState.DSTATE_RAW_OK : JpegState.DSTATE_SCANNING; return true; } /// /// Set default decompression parameters. /// private void default_decompress_parms() { /* Guess the input colorspace, and set output colorspace accordingly. */ /* (Wish JPEG committee had provided a real way to specify this...) */ /* Note application may override our guesses. */ switch (m_num_components) { case 1: m_jpeg_color_space = ColorSpace.Grayscale; m_out_color_space = ColorSpace.Grayscale; break; case 3: if (m_saw_JFIF_marker) { /* JFIF implies YCbCr */ m_jpeg_color_space = ColorSpace.YCbCr; } else if (m_saw_Adobe_marker) { switch (m_Adobe_transform) { case 0: m_jpeg_color_space = ColorSpace.RGB; break; case 1: m_jpeg_color_space = ColorSpace.YCbCr; break; default: m_jpeg_color_space = ColorSpace.YCbCr; /* assume it's YCbCr */ break; } } else { /* Saw no special markers, try to guess from the component IDs */ int cid0 = m_comp_info[0].Component_id; int cid1 = m_comp_info[1].Component_id; int cid2 = m_comp_info[2].Component_id; if (cid0 == 1 && cid1 == 2 && cid2 == 3) { /* assume JFIF w/out marker */ m_jpeg_color_space = ColorSpace.YCbCr; } else if (cid0 == 82 && cid1 == 71 && cid2 == 66) { /* ASCII 'R', 'G', 'B' */ m_jpeg_color_space = ColorSpace.RGB; } else { /* assume it's YCbCr */ m_jpeg_color_space = ColorSpace.YCbCr; } } /* Always guess RGB is proper output colorspace. */ m_out_color_space = ColorSpace.RGB; break; case 4: if (m_saw_Adobe_marker) { switch (m_Adobe_transform) { case 0: m_jpeg_color_space = ColorSpace.CMYK; break; case 2: m_jpeg_color_space = ColorSpace.YCCK; break; default: /* assume it's YCCK */ m_jpeg_color_space = ColorSpace.YCCK; break; } } else { /* No special markers, assume straight CMYK. */ m_jpeg_color_space = ColorSpace.CMYK; } m_out_color_space = ColorSpace.CMYK; break; default: m_jpeg_color_space = ColorSpace.Unknown; m_out_color_space = ColorSpace.Unknown; break; } /* Set defaults for other decompression parameters. */ m_scale_num = 1; /* 1:1 scaling */ m_scale_denom = 1; m_buffered_image = false; m_raw_data_out = false; m_dct_method = JpegConstants.DefaultDCTMethod; m_do_fancy_upsampling = true; m_do_block_smoothing = true; m_quantize_colors = false; /* We set these in case application only sets quantize_colors. */ m_dither_mode = DitherMode.FloydStein; m_two_pass_quantize = true; m_desired_number_of_colors = 256; m_colormap = null; /* Initialize for no mode change in buffered-image mode. */ m_enable_1pass_quant = false; m_enable_external_quant = false; m_enable_2pass_quant = false; } private void jpeg_consume_input_start() { /* Start-of-datastream actions: reset appropriate modules */ m_inputctl.reset_input_controller(); /* Initialize application's data source module */ m_src.init_source(); m_global_state = JpegState.DSTATE_INHEADER; } private ReadResult jpeg_consume_input_inHeader() { ReadResult retcode = m_inputctl.consume_input(); if (retcode == ReadResult.Reached_SOS) { /* Found SOS, prepare to decompress */ /* Set up default parameters based on header data */ default_decompress_parms(); /* Set global state: ready for start_decompress */ m_global_state = JpegState.DSTATE_READY; } return retcode; } } #endregion #region JpegDecompressorCoefController /// /// Coefficient buffer control /// /// This code applies interblock smoothing as described by section K.8 /// of the JPEG standard: the first 5 AC coefficients are estimated from /// the DC values of a DCT block and its 8 neighboring blocks. /// We apply smoothing only for progressive JPEG decoding, and only if /// the coefficients it can estimate are not yet known to full precision. /// class JpegDecompressorCoefController { private const int SAVED_COEFS = 6; /* we save coef_bits[0..5] */ /* Natural-order array positions of the first 5 zigzag-order coefficients */ private const int Q01_POS = 1; private const int Q10_POS = 8; private const int Q20_POS = 16; private const int Q11_POS = 9; private const int Q02_POS = 2; private enum DecompressorType { Ordinary, Smooth, OnePass } private JpegDecompressor m_cinfo; private bool m_useDummyConsumeData; private DecompressorType m_decompressor; /* These variables keep track of the current location of the input side. */ /* cinfo.input_iMCU_row is also used for this. */ private int m_MCU_ctr; /* counts MCUs processed in current row */ private int m_MCU_vert_offset; /* counts MCU rows within iMCU row */ private int m_MCU_rows_per_iMCU_row; /* number of such rows needed */ /* The output side's location is represented by cinfo.output_iMCU_row. */ /* In single-pass modes, it's sufficient to buffer just one MCU. * We allocate a workspace of DecompressorMaxBlocksInMCU coefficient blocks, * and let the entropy decoder write into that workspace each time. * (On 80x86, the workspace is FAR even though it's not really very big; * this is to keep the module interfaces unchanged when a large coefficient * buffer is necessary.) * In multi-pass modes, this array points to the current MCU's blocks * within the virtual arrays; it is used only by the input side. */ private JpegBlock[] m_MCU_buffer = new JpegBlock[JpegConstants.DecompressorMaxBlocksInMCU]; /* In multi-pass modes, we need a virtual block array for each component. */ private JpegVirtualArray[] m_whole_image = new JpegVirtualArray[JpegConstants.MaxComponents]; private JpegVirtualArray[] m_coef_arrays; /* When doing block smoothing, we latch coefficient Al values here */ private int[] m_coef_bits_latch; private int m_coef_bits_savedOffset; public JpegDecompressorCoefController(JpegDecompressor cinfo, bool need_full_buffer) { m_cinfo = cinfo; /* Create the coefficient buffer. */ if (need_full_buffer) { /* Allocate a full-image virtual array for each component, */ /* padded to a multiple of samp_factor DCT blocks in each direction. */ /* Note we ask for a pre-zeroed array. */ for (int ci = 0; ci < cinfo.m_num_components; ci++) { m_whole_image[ci] = JpegCommonBase.CreateBlocksArray( JpegUtils.jround_up(cinfo.Comp_info[ci].Width_in_blocks, cinfo.Comp_info[ci].H_samp_factor), JpegUtils.jround_up(cinfo.Comp_info[ci].height_in_blocks, cinfo.Comp_info[ci].V_samp_factor)); m_whole_image[ci].ErrorProcessor = cinfo; } m_useDummyConsumeData = false; m_decompressor = DecompressorType.Ordinary; m_coef_arrays = m_whole_image; /* link to virtual arrays */ } else { /* We only need a single-MCU buffer. */ JpegBlock[] buffer = new JpegBlock[JpegConstants.DecompressorMaxBlocksInMCU]; for (int i = 0; i < JpegConstants.DecompressorMaxBlocksInMCU; i++) { buffer[i] = new JpegBlock(); for (int ii = 0; ii < buffer[i].data.Length; ii++) buffer[i].data[ii] = -12851; m_MCU_buffer[i] = buffer[i]; } m_useDummyConsumeData = true; m_decompressor = DecompressorType.OnePass; m_coef_arrays = null; /* flag for no virtual arrays */ } } /// /// Initialize for an input processing pass. /// public void start_input_pass() { m_cinfo.m_input_iMCU_row = 0; start_iMCU_row(); } /// /// Consume input data and store it in the full-image coefficient buffer. /// We read as much as one fully interleaved MCU row ("iMCU" row) per call, /// ie, v_samp_factor block rows for each component in the scan. /// public ReadResult consume_data() { if (m_useDummyConsumeData) return ReadResult.Suspended; /* Always indicate nothing was done */ JpegBlock[][][] buffer = new JpegBlock[JpegConstants.MaxComponentsInScan][][]; /* Align the virtual buffers for the components used in this scan. */ for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]]; buffer[ci] = m_whole_image[componentInfo.Component_index].Access( m_cinfo.m_input_iMCU_row * componentInfo.V_samp_factor, componentInfo.V_samp_factor); /* Note: entropy decoder expects buffer to be zeroed, * but this is handled automatically by the memory manager * because we requested a pre-zeroed array. */ } /* Loop to process one whole iMCU row */ for (int yoffset = m_MCU_vert_offset; yoffset < m_MCU_rows_per_iMCU_row; yoffset++) { for (int MCU_col_num = m_MCU_ctr; MCU_col_num < m_cinfo.m_MCUs_per_row; MCU_col_num++) { /* Construct list of pointers to DCT blocks belonging to this MCU */ int blkn = 0; /* index of current DCT block within MCU */ for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]]; int start_col = MCU_col_num * componentInfo.MCU_width; for (int yindex = 0; yindex < componentInfo.MCU_height; yindex++) { for (int xindex = 0; xindex < componentInfo.MCU_width; xindex++) { m_MCU_buffer[blkn] = buffer[ci][yindex + yoffset][start_col + xindex]; blkn++; } } } /* Try to fetch the MCU. */ if (!m_cinfo.m_entropy.decode_mcu(m_MCU_buffer)) { /* Suspension forced; update state counters and exit */ m_MCU_vert_offset = yoffset; m_MCU_ctr = MCU_col_num; return ReadResult.Suspended; } } /* Completed an MCU row, but perhaps not an iMCU row */ m_MCU_ctr = 0; } /* Completed the iMCU row, advance counters for next one */ m_cinfo.m_input_iMCU_row++; if (m_cinfo.m_input_iMCU_row < m_cinfo.m_total_iMCU_rows) { start_iMCU_row(); return ReadResult.Row_Completed; } /* Completed the scan */ m_cinfo.m_inputctl.finish_input_pass(); return ReadResult.Scan_Completed; } /// /// Initialize for an output processing pass. /// public void start_output_pass() { /* If multipass, check to see whether to use block smoothing on this pass */ if (m_coef_arrays != null) { if (m_cinfo.m_do_block_smoothing && smoothing_ok()) m_decompressor = DecompressorType.Smooth; else m_decompressor = DecompressorType.Ordinary; } m_cinfo.m_output_iMCU_row = 0; } public ReadResult decompress_data(ComponentBuffer[] output_buf) { switch (m_decompressor) { case DecompressorType.Ordinary: return decompress_data_ordinary(output_buf); case DecompressorType.Smooth: return decompress_smooth_data(output_buf); case DecompressorType.OnePass: return decompress_onepass(output_buf); } throw new Exception("Not implemented yet"); } /* Pointer to array of coefficient virtual arrays, or null if none */ public JpegVirtualArray[] GetCoefArrays() { return m_coef_arrays; } /// /// Decompress and return some data in the single-pass case. /// Always attempts to emit one fully interleaved MCU row ("iMCU" row). /// Input and output must run in lockstep since we have only a one-MCU buffer. /// Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. /// /// NB: output_buf contains a plane for each component in image, /// which we index according to the component's SOF position. /// private ReadResult decompress_onepass(ComponentBuffer[] output_buf) { int last_MCU_col = m_cinfo.m_MCUs_per_row - 1; int last_iMCU_row = m_cinfo.m_total_iMCU_rows - 1; /* Loop to process as much as one whole iMCU row */ for (int yoffset = m_MCU_vert_offset; yoffset < m_MCU_rows_per_iMCU_row; yoffset++) { for (int MCU_col_num = m_MCU_ctr; MCU_col_num <= last_MCU_col; MCU_col_num++) { /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ for (int i = 0; i < m_cinfo.m_blocks_in_MCU; i++) Array.Clear(m_MCU_buffer[i].data, 0, m_MCU_buffer[i].data.Length); if (!m_cinfo.m_entropy.decode_mcu(m_MCU_buffer)) { /* Suspension forced; update state counters and exit */ m_MCU_vert_offset = yoffset; m_MCU_ctr = MCU_col_num; return ReadResult.Suspended; } /* Determine where data should go in output_buf and do the IDCT thing. * We skip dummy blocks at the right and bottom edges (but blkn gets * incremented past them!). Note the inner loop relies on having * allocated the MCU_buffer[] blocks sequentially. */ int blkn = 0; /* index of current DCT block within MCU */ for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]]; /* Don't bother to IDCT an uninteresting component. */ if (!componentInfo.component_needed) { blkn += componentInfo.MCU_blocks; continue; } int useful_width = (MCU_col_num < last_MCU_col) ? componentInfo.MCU_width : componentInfo.last_col_width; int outputIndex = yoffset * componentInfo.DCT_scaled_size; int start_col = MCU_col_num * componentInfo.MCU_sample_width; for (int yindex = 0; yindex < componentInfo.MCU_height; yindex++) { if (m_cinfo.m_input_iMCU_row < last_iMCU_row || yoffset + yindex < componentInfo.last_row_height) { int output_col = start_col; for (int xindex = 0; xindex < useful_width; xindex++) { m_cinfo.m_idct.inverse(componentInfo.Component_index, m_MCU_buffer[blkn + xindex].data, output_buf[componentInfo.Component_index], outputIndex, output_col); output_col += componentInfo.DCT_scaled_size; } } blkn += componentInfo.MCU_width; outputIndex += componentInfo.DCT_scaled_size; } } } /* Completed an MCU row, but perhaps not an iMCU row */ m_MCU_ctr = 0; } /* Completed the iMCU row, advance counters for next one */ m_cinfo.m_output_iMCU_row++; m_cinfo.m_input_iMCU_row++; if (m_cinfo.m_input_iMCU_row < m_cinfo.m_total_iMCU_rows) { start_iMCU_row(); return ReadResult.Row_Completed; } /* Completed the scan */ m_cinfo.m_inputctl.finish_input_pass(); return ReadResult.Scan_Completed; } /// /// Decompress and return some data in the multi-pass case. /// Always attempts to emit one fully interleaved MCU row ("iMCU" row). /// Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. /// /// NB: output_buf contains a plane for each component in image. /// private ReadResult decompress_data_ordinary(ComponentBuffer[] output_buf) { /* Force some input to be done if we are getting ahead of the input. */ while (m_cinfo.m_input_scan_number < m_cinfo.m_output_scan_number || (m_cinfo.m_input_scan_number == m_cinfo.m_output_scan_number && m_cinfo.m_input_iMCU_row <= m_cinfo.m_output_iMCU_row)) { if (m_cinfo.m_inputctl.consume_input() == ReadResult.Suspended) return ReadResult.Suspended; } int last_iMCU_row = m_cinfo.m_total_iMCU_rows - 1; /* OK, output from the virtual arrays. */ for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[ci]; /* Don't bother to IDCT an uninteresting component. */ if (!componentInfo.component_needed) continue; /* Align the virtual buffer for this component. */ JpegBlock[][] buffer = m_whole_image[ci].Access(m_cinfo.m_output_iMCU_row * componentInfo.V_samp_factor, componentInfo.V_samp_factor); /* Count non-dummy DCT block rows in this iMCU row. */ int block_rows; if (m_cinfo.m_output_iMCU_row < last_iMCU_row) block_rows = componentInfo.V_samp_factor; else { /* NB: can't use last_row_height here; it is input-side-dependent! */ block_rows = componentInfo.height_in_blocks % componentInfo.V_samp_factor; if (block_rows == 0) block_rows = componentInfo.V_samp_factor; } /* Loop over all DCT blocks to be processed. */ int rowIndex = 0; for (int block_row = 0; block_row < block_rows; block_row++) { int output_col = 0; for (int block_num = 0; block_num < componentInfo.Width_in_blocks; block_num++) { m_cinfo.m_idct.inverse(componentInfo.Component_index, buffer[block_row][block_num].data, output_buf[ci], rowIndex, output_col); output_col += componentInfo.DCT_scaled_size; } rowIndex += componentInfo.DCT_scaled_size; } } m_cinfo.m_output_iMCU_row++; if (m_cinfo.m_output_iMCU_row < m_cinfo.m_total_iMCU_rows) return ReadResult.Row_Completed; return ReadResult.Scan_Completed; } /// /// Variant of decompress_data for use when doing block smoothing. /// private ReadResult decompress_smooth_data(ComponentBuffer[] output_buf) { /* Force some input to be done if we are getting ahead of the input. */ while (m_cinfo.m_input_scan_number <= m_cinfo.m_output_scan_number && !m_cinfo.m_inputctl.EOIReached()) { if (m_cinfo.m_input_scan_number == m_cinfo.m_output_scan_number) { /* If input is working on current scan, we ordinarily want it to * have completed the current row. But if input scan is DC, * we want it to keep one row ahead so that next block row's DC * values are up to date. */ int delta = (m_cinfo.m_Ss == 0) ? 1 : 0; if (m_cinfo.m_input_iMCU_row > m_cinfo.m_output_iMCU_row + delta) break; } if (m_cinfo.m_inputctl.consume_input() == ReadResult.Suspended) return ReadResult.Suspended; } int last_iMCU_row = m_cinfo.m_total_iMCU_rows - 1; /* OK, output from the virtual arrays. */ for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[ci]; /* Don't bother to IDCT an uninteresting component. */ if (!componentInfo.component_needed) continue; int block_rows; int access_rows; bool last_row; /* Count non-dummy DCT block rows in this iMCU row. */ if (m_cinfo.m_output_iMCU_row < last_iMCU_row) { block_rows = componentInfo.V_samp_factor; access_rows = block_rows * 2; /* this and next iMCU row */ last_row = false; } else { /* NB: can't use last_row_height here; it is input-side-dependent! */ block_rows = componentInfo.height_in_blocks % componentInfo.V_samp_factor; if (block_rows == 0) block_rows = componentInfo.V_samp_factor; access_rows = block_rows; /* this iMCU row only */ last_row = true; } /* Align the virtual buffer for this component. */ JpegBlock[][] buffer = null; bool first_row; int bufferRowOffset = 0; if (m_cinfo.m_output_iMCU_row > 0) { access_rows += componentInfo.V_samp_factor; /* prior iMCU row too */ buffer = m_whole_image[ci].Access((m_cinfo.m_output_iMCU_row - 1) * componentInfo.V_samp_factor, access_rows); bufferRowOffset = componentInfo.V_samp_factor; /* point to current iMCU row */ first_row = false; } else { buffer = m_whole_image[ci].Access(0, access_rows); first_row = true; } /* Fetch component-dependent info */ int coefBitsOffset = ci * SAVED_COEFS; int Q00 = componentInfo.quant_table.quantval[0]; int Q01 = componentInfo.quant_table.quantval[Q01_POS]; int Q10 = componentInfo.quant_table.quantval[Q10_POS]; int Q20 = componentInfo.quant_table.quantval[Q20_POS]; int Q11 = componentInfo.quant_table.quantval[Q11_POS]; int Q02 = componentInfo.quant_table.quantval[Q02_POS]; int outputIndex = ci; /* Loop over all DCT blocks to be processed. */ for (int block_row = 0; block_row < block_rows; block_row++) { int bufferIndex = bufferRowOffset + block_row; int prev_block_row = 0; if (first_row && block_row == 0) prev_block_row = bufferIndex; else prev_block_row = bufferIndex - 1; int next_block_row = 0; if (last_row && block_row == block_rows - 1) next_block_row = bufferIndex; else next_block_row = bufferIndex + 1; /* We fetch the surrounding DC values using a sliding-register approach. * Initialize all nine here so as to do the right thing on narrow pics. */ int DC1 = buffer[prev_block_row][0][0]; int DC2 = DC1; int DC3 = DC1; int DC4 = buffer[bufferIndex][0][0]; int DC5 = DC4; int DC6 = DC4; int DC7 = buffer[next_block_row][0][0]; int DC8 = DC7; int DC9 = DC7; int output_col = 0; int last_block_column = componentInfo.Width_in_blocks - 1; for (int block_num = 0; block_num <= last_block_column; block_num++) { /* Fetch current DCT block into workspace so we can modify it. */ JpegBlock workspace = new JpegBlock(); Buffer.BlockCopy(buffer[bufferIndex][0].data, 0, workspace.data, 0, workspace.data.Length * sizeof(short)); /* Update DC values */ if (block_num < last_block_column) { DC3 = buffer[prev_block_row][1][0]; DC6 = buffer[bufferIndex][1][0]; DC9 = buffer[next_block_row][1][0]; } /* Compute coefficient estimates per K.8. * An estimate is applied only if coefficient is still zero, * and is not known to be fully accurate. */ /* AC01 */ int Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 1]; if (Al != 0 && workspace[1] == 0) { int pred; int num = 36 * Q00 * (DC4 - DC6); if (num >= 0) { pred = ((Q01 << 7) + num) / (Q01 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q01 << 7) - num) / (Q01 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[1] = (short)pred; } /* AC10 */ Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 2]; if (Al != 0 && workspace[8] == 0) { int pred; int num = 36 * Q00 * (DC2 - DC8); if (num >= 0) { pred = ((Q10 << 7) + num) / (Q10 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q10 << 7) - num) / (Q10 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[8] = (short)pred; } /* AC20 */ Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 3]; if (Al != 0 && workspace[16] == 0) { int pred; int num = 9 * Q00 * (DC2 + DC8 - 2 * DC5); if (num >= 0) { pred = ((Q20 << 7) + num) / (Q20 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q20 << 7) - num) / (Q20 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[16] = (short)pred; } /* AC11 */ Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 4]; if (Al != 0 && workspace[9] == 0) { int pred; int num = 5 * Q00 * (DC1 - DC3 - DC7 + DC9); if (num >= 0) { pred = ((Q11 << 7) + num) / (Q11 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q11 << 7) - num) / (Q11 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[9] = (short)pred; } /* AC02 */ Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 5]; if (Al != 0 && workspace[2] == 0) { int pred; int num = 9 * Q00 * (DC4 + DC6 - 2 * DC5); if (num >= 0) { pred = ((Q02 << 7) + num) / (Q02 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q02 << 7) - num) / (Q02 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[2] = (short)pred; } /* OK, do the IDCT */ m_cinfo.m_idct.inverse(componentInfo.Component_index, workspace.data, output_buf[outputIndex], 0, output_col); /* Advance for next column */ DC1 = DC2; DC2 = DC3; DC4 = DC5; DC5 = DC6; DC7 = DC8; DC8 = DC9; bufferIndex++; prev_block_row++; next_block_row++; output_col += componentInfo.DCT_scaled_size; } outputIndex += componentInfo.DCT_scaled_size; } } m_cinfo.m_output_iMCU_row++; if (m_cinfo.m_output_iMCU_row < m_cinfo.m_total_iMCU_rows) return ReadResult.Row_Completed; return ReadResult.Scan_Completed; } /// /// Determine whether block smoothing is applicable and safe. /// We also latch the current states of the coef_bits[] entries for the /// AC coefficients; otherwise, if the input side of the decompressor /// advances into a new scan, we might think the coefficients are known /// more accurately than they really are. /// private bool smoothing_ok() { if (!m_cinfo.m_progressive_mode || m_cinfo.m_coef_bits == null) return false; /* Allocate latch area if not already done */ if (m_coef_bits_latch == null) { m_coef_bits_latch = new int[m_cinfo.m_num_components * SAVED_COEFS]; m_coef_bits_savedOffset = 0; } bool smoothing_useful = false; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { /* All components' quantization values must already be latched. */ JpegQuantizationTable qtable = m_cinfo.Comp_info[ci].quant_table; if (qtable == null) return false; /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ if (qtable.quantval[0] == 0 || qtable.quantval[Q01_POS] == 0 || qtable.quantval[Q10_POS] == 0 || qtable.quantval[Q20_POS] == 0 || qtable.quantval[Q11_POS] == 0 || qtable.quantval[Q02_POS] == 0) { return false; } /* DC values must be at least partly known for all components. */ if (m_cinfo.m_coef_bits[ci][0] < 0) return false; /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ for (int coefi = 1; coefi <= 5; coefi++) { m_coef_bits_latch[m_coef_bits_savedOffset + coefi] = m_cinfo.m_coef_bits[ci][coefi]; if (m_cinfo.m_coef_bits[ci][coefi] != 0) smoothing_useful = true; } m_coef_bits_savedOffset += SAVED_COEFS; } return smoothing_useful; } /// /// Reset within-iMCU-row counters for a new row (input side) /// private void start_iMCU_row() { /* In an interleaved scan, an MCU row is the same as an iMCU row. * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. * But at the bottom of the image, process only what's left. */ if (m_cinfo.m_comps_in_scan > 1) { m_MCU_rows_per_iMCU_row = 1; } else { JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[0]]; if (m_cinfo.m_input_iMCU_row < (m_cinfo.m_total_iMCU_rows - 1)) m_MCU_rows_per_iMCU_row = componentInfo.V_samp_factor; else m_MCU_rows_per_iMCU_row = componentInfo.last_row_height; } m_MCU_ctr = 0; m_MCU_vert_offset = 0; } } #endregion #region JpegDecompressorMainController /// /// Main buffer control (downsampled-data buffer) /// /// In the current system design, the main buffer need never be a full-image /// buffer; any full-height buffers will be found inside the coefficient or /// postprocessing controllers. Nonetheless, the main controller is not /// trivial. Its responsibility is to provide context rows for upsampling/ /// rescaling, and doing this in an efficient fashion is a bit tricky. /// /// Postprocessor input data is counted in "row groups". A row group /// is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) /// sample rows of each component. (We require DCT_scaled_size values to be /// chosen such that these numbers are integers. In practice DCT_scaled_size /// values will likely be powers of two, so we actually have the stronger /// condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) /// Upsampling will typically produce max_v_samp_factor pixel rows from each /// row group (times any additional scale factor that the upsampler is /// applying). /// /// The coefficient controller will deliver data to us one iMCU row at a time; /// each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or /// exactly min_DCT_scaled_size row groups. (This amount of data corresponds /// to one row of MCUs when the image is fully interleaved.) Note that the /// number of sample rows varies across components, but the number of row /// groups does not. Some garbage sample rows may be included in the last iMCU /// row at the bottom of the image. /// /// Depending on the vertical scaling algorithm used, the upsampler may need /// access to the sample row(s) above and below its current input row group. /// The upsampler is required to set need_context_rows true at global selection /// time if so. When need_context_rows is false, this controller can simply /// obtain one iMCU row at a time from the coefficient controller and dole it /// out as row groups to the postprocessor. /// /// When need_context_rows is true, this controller guarantees that the buffer /// passed to postprocessing contains at least one row group's worth of samples /// above and below the row group(s) being processed. Note that the context /// rows "above" the first passed row group appear at negative row offsets in /// the passed buffer. At the top and bottom of the image, the required /// context rows are manufactured by duplicating the first or last real sample /// row; this avoids having special cases in the upsampling inner loops. /// /// The amount of context is fixed at one row group just because that's a /// convenient number for this controller to work with. The existing /// upsamplers really only need one sample row of context. An upsampler /// supporting arbitrary output rescaling might wish for more than one row /// group of context when shrinking the image; tough, we don't handle that. /// (This is justified by the assumption that downsizing will be handled mostly /// by adjusting the DCT_scaled_size values, so that the actual scale factor at /// the upsample step needn't be much less than one.) /// /// To provide the desired context, we have to retain the last two row groups /// of one iMCU row while reading in the next iMCU row. (The last row group /// can't be processed until we have another row group for its below-context, /// and so we have to save the next-to-last group too for its above-context.) /// We could do this most simply by copying data around in our buffer, but /// that'd be very slow. We can avoid copying any data by creating a rather /// strange pointer structure. Here's how it works. We allocate a workspace /// consisting of M+2 row groups (where M = min_DCT_scaled_size is the number /// of row groups per iMCU row). We create two sets of redundant pointers to /// the workspace. Labeling the physical row groups 0 to M+1, the synthesized /// pointer lists look like this: /// M+1 M-1 /// master pointer --> 0 master pointer --> 0 /// 1 1 /// ... ... /// M-3 M-3 /// M-2 M /// M-1 M+1 /// M M-2 /// M+1 M-1 /// 0 0 /// We read alternate iMCU rows using each master pointer; thus the last two /// row groups of the previous iMCU row remain un-overwritten in the workspace. /// The pointer lists are set up so that the required context rows appear to /// be adjacent to the proper places when we pass the pointer lists to the /// upsampler. /// /// The above pictures describe the normal state of the pointer lists. /// At top and bottom of the image, we diddle the pointer lists to duplicate /// the first or last sample row as necessary (this is cheaper than copying /// sample rows around). /// /// This scheme breaks down if M less than 2, ie, min_DCT_scaled_size is 1. In that /// situation each iMCU row provides only one row group so the buffering logic /// must be different (eg, we must read two iMCU rows before we can emit the /// first row group). For now, we simply do not support providing context /// rows when min_DCT_scaled_size is 1. That combination seems unlikely to /// be worth providing --- if someone wants a 1/8th-size preview, they probably /// want it quick and dirty, so a context-free upsampler is sufficient. /// class JpegDecompressorMainController { private enum DataProcessor { context_main, simple_main, crank_post } /* context_state values: */ private const int CTX_PREPARE_FOR_IMCU = 0; /* need to prepare for MCU row */ private const int CTX_PROCESS_IMCU = 1; /* feeding iMCU to postprocessor */ private const int CTX_POSTPONED_ROW = 2; /* feeding postponed row group */ private DataProcessor m_dataProcessor; private JpegDecompressor m_cinfo; /* Pointer to allocated workspace (M or M+2 row groups). */ private byte[][][] m_buffer = new byte[JpegConstants.MaxComponents][][]; private bool m_buffer_full; /* Have we gotten an iMCU row from decoder? */ private int m_rowgroup_ctr; /* counts row groups output to postprocessor */ /* Remaining fields are only used in the context case. */ private int[][][] m_funnyIndices = new int[2][][] { new int[JpegConstants.MaxComponents][], new int[JpegConstants.MaxComponents][] }; private int[] m_funnyOffsets = new int[JpegConstants.MaxComponents]; private int m_whichFunny; /* indicates which funny indices set is now in use */ private int m_context_state; /* process_data state machine status */ private int m_rowgroups_avail; /* row groups available to postprocessor */ private int m_iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ public JpegDecompressorMainController(JpegDecompressor cinfo) { m_cinfo = cinfo; /* Allocate the workspace. * ngroups is the number of row groups we need. */ int ngroups = cinfo.m_min_DCT_scaled_size; if (cinfo.m_upsample.NeedContextRows()) { if (cinfo.m_min_DCT_scaled_size < 2) /* unsupported, see comments above */ throw new Exception("Not implemented yet"); alloc_funny_pointers(); /* Alloc space for xbuffer[] lists */ ngroups = cinfo.m_min_DCT_scaled_size + 2; } for (int ci = 0; ci < cinfo.m_num_components; ci++) { /* height of a row group of component */ int rgroup = (cinfo.Comp_info[ci].V_samp_factor * cinfo.Comp_info[ci].DCT_scaled_size) / cinfo.m_min_DCT_scaled_size; m_buffer[ci] = JpegCommonBase.AllocJpegSamples( cinfo.Comp_info[ci].Width_in_blocks * cinfo.Comp_info[ci].DCT_scaled_size, rgroup * ngroups); } } /// /// Initialize for a processing pass. /// public void start_pass(BufferMode pass_mode) { switch (pass_mode) { case BufferMode.PassThru: if (m_cinfo.m_upsample.NeedContextRows()) { m_dataProcessor = DataProcessor.context_main; make_funny_pointers(); /* Create the xbuffer[] lists */ m_whichFunny = 0; /* Read first iMCU row into xbuffer[0] */ m_context_state = CTX_PREPARE_FOR_IMCU; m_iMCU_row_ctr = 0; } else { /* Simple case with no context needed */ m_dataProcessor = DataProcessor.simple_main; } m_buffer_full = false; /* Mark buffer empty */ m_rowgroup_ctr = 0; break; case BufferMode.CrankDest: /* For last pass of 2-pass quantization, just crank the postprocessor */ m_dataProcessor = DataProcessor.crank_post; break; default: throw new Exception("Bogus buffer control mode"); } } public void process_data(byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { switch (m_dataProcessor) { case DataProcessor.simple_main: process_data_simple_main(output_buf, ref out_row_ctr, out_rows_avail); break; case DataProcessor.context_main: process_data_context_main(output_buf, ref out_row_ctr, out_rows_avail); break; case DataProcessor.crank_post: process_data_crank_post(output_buf, ref out_row_ctr, out_rows_avail); break; default: throw new Exception("Not implemented yet"); } } /// /// Process some data. /// This handles the simple case where no context is required. /// private void process_data_simple_main(byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { ComponentBuffer[] cb = new ComponentBuffer[JpegConstants.MaxComponents]; for (int i = 0; i < JpegConstants.MaxComponents; i++) { cb[i] = new ComponentBuffer(); cb[i].SetBuffer(m_buffer[i], null, 0); } /* Read input data if we haven't filled the main buffer yet */ if (!m_buffer_full) { if (m_cinfo.m_coef.decompress_data(cb) == ReadResult.Suspended) { /* suspension forced, can do nothing more */ return; } /* OK, we have an iMCU row to work with */ m_buffer_full = true; } /* There are always min_DCT_scaled_size row groups in an iMCU row. */ int rowgroups_avail = m_cinfo.m_min_DCT_scaled_size; /* Note: at the bottom of the image, we may pass extra garbage row groups * to the postprocessor. The postprocessor has to check for bottom * of image anyway (at row resolution), so no point in us doing it too. */ /* Feed the postprocessor */ m_cinfo.m_post.post_process_data(cb, ref m_rowgroup_ctr, rowgroups_avail, output_buf, ref out_row_ctr, out_rows_avail); /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ if (m_rowgroup_ctr >= rowgroups_avail) { m_buffer_full = false; m_rowgroup_ctr = 0; } } /// /// Process some data. /// This handles the case where context rows must be provided. /// private void process_data_context_main(byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { ComponentBuffer[] cb = new ComponentBuffer[m_cinfo.m_num_components]; for (int i = 0; i < m_cinfo.m_num_components; i++) { cb[i] = new ComponentBuffer(); cb[i].SetBuffer(m_buffer[i], m_funnyIndices[m_whichFunny][i], m_funnyOffsets[i]); } /* Read input data if we haven't filled the main buffer yet */ if (!m_buffer_full) { if (m_cinfo.m_coef.decompress_data(cb) == ReadResult.Suspended) { /* suspension forced, can do nothing more */ return; } /* OK, we have an iMCU row to work with */ m_buffer_full = true; /* count rows received */ m_iMCU_row_ctr++; } /* Postprocessor typically will not swallow all the input data it is handed * in one call (due to filling the output buffer first). Must be prepared * to exit and restart. This switch lets us keep track of how far we got. * Note that each case falls through to the next on successful completion. */ if (m_context_state == CTX_POSTPONED_ROW) { /* Call postprocessor using previously set pointers for postponed row */ m_cinfo.m_post.post_process_data(cb, ref m_rowgroup_ctr, m_rowgroups_avail, output_buf, ref out_row_ctr, out_rows_avail); if (m_rowgroup_ctr < m_rowgroups_avail) { /* Need to suspend */ return; } m_context_state = CTX_PREPARE_FOR_IMCU; if (out_row_ctr >= out_rows_avail) { /* Postprocessor exactly filled output buf */ return; } } if (m_context_state == CTX_PREPARE_FOR_IMCU) { /* Prepare to process first M-1 row groups of this iMCU row */ m_rowgroup_ctr = 0; m_rowgroups_avail = m_cinfo.m_min_DCT_scaled_size - 1; /* Check for bottom of image: if so, tweak pointers to "duplicate" * the last sample row, and adjust rowgroups_avail to ignore padding rows. */ if (m_iMCU_row_ctr == m_cinfo.m_total_iMCU_rows) set_bottom_pointers(); m_context_state = CTX_PROCESS_IMCU; } if (m_context_state == CTX_PROCESS_IMCU) { /* Call postprocessor using previously set pointers */ m_cinfo.m_post.post_process_data(cb, ref m_rowgroup_ctr, m_rowgroups_avail, output_buf, ref out_row_ctr, out_rows_avail); if (m_rowgroup_ctr < m_rowgroups_avail) { /* Need to suspend */ return; } /* After the first iMCU, change wraparound pointers to normal state */ if (m_iMCU_row_ctr == 1) set_wraparound_pointers(); /* Prepare to load new iMCU row using other xbuffer list */ m_whichFunny ^= 1; /* 0=>1 or 1=>0 */ m_buffer_full = false; /* Still need to process last row group of this iMCU row, */ /* which is saved at index M+1 of the other xbuffer */ m_rowgroup_ctr = m_cinfo.m_min_DCT_scaled_size + 1; m_rowgroups_avail = m_cinfo.m_min_DCT_scaled_size + 2; m_context_state = CTX_POSTPONED_ROW; } } /// /// Process some data. /// Final pass of two-pass quantization: just call the postprocessor. /// Source data will be the postprocessor controller's internal buffer. /// private void process_data_crank_post(byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { int dummy = 0; m_cinfo.m_post.post_process_data(null, ref dummy, 0, output_buf, ref out_row_ctr, out_rows_avail); } /// /// Allocate space for the funny pointer lists. /// This is done only once, not once per pass. /// private void alloc_funny_pointers() { int M = m_cinfo.m_min_DCT_scaled_size; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { /* height of a row group of component */ int rgroup = (m_cinfo.Comp_info[ci].V_samp_factor * m_cinfo.Comp_info[ci].DCT_scaled_size) / m_cinfo.m_min_DCT_scaled_size; /* Get space for pointer lists --- M+4 row groups in each list. */ m_funnyIndices[0][ci] = new int[rgroup * (M + 4)]; m_funnyIndices[1][ci] = new int[rgroup * (M + 4)]; m_funnyOffsets[ci] = rgroup; } } /// /// Create the funny pointer lists discussed in the comments above. /// The actual workspace is already allocated (in main.buffer), /// and the space for the pointer lists is allocated too. /// This routine just fills in the curiously ordered lists. /// This will be repeated at the beginning of each pass. /// private void make_funny_pointers() { int M = m_cinfo.m_min_DCT_scaled_size; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { /* height of a row group of component */ int rgroup = (m_cinfo.Comp_info[ci].V_samp_factor * m_cinfo.Comp_info[ci].DCT_scaled_size) / m_cinfo.m_min_DCT_scaled_size; int[] ind0 = m_funnyIndices[0][ci]; int[] ind1 = m_funnyIndices[1][ci]; /* First copy the workspace pointers as-is */ for (int i = 0; i < rgroup * (M + 2); i++) { ind0[i + rgroup] = i; ind1[i + rgroup] = i; } /* In the second list, put the last four row groups in swapped order */ for (int i = 0; i < rgroup * 2; i++) { ind1[rgroup * (M - 1) + i] = rgroup * M + i; ind1[rgroup * (M + 1) + i] = rgroup * (M - 2) + i; } /* The wraparound pointers at top and bottom will be filled later * (see set_wraparound_pointers, below). Initially we want the "above" * pointers to duplicate the first actual data line. This only needs * to happen in xbuffer[0]. */ for (int i = 0; i < rgroup; i++) ind0[i] = ind0[rgroup]; } } /// /// Set up the "wraparound" pointers at top and bottom of the pointer lists. /// This changes the pointer list state from top-of-image to the normal state. /// private void set_wraparound_pointers() { int M = m_cinfo.m_min_DCT_scaled_size; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { /* height of a row group of component */ int rgroup = (m_cinfo.Comp_info[ci].V_samp_factor * m_cinfo.Comp_info[ci].DCT_scaled_size) / m_cinfo.m_min_DCT_scaled_size; int[] ind0 = m_funnyIndices[0][ci]; int[] ind1 = m_funnyIndices[1][ci]; for (int i = 0; i < rgroup; i++) { ind0[i] = ind0[rgroup * (M + 2) + i]; ind1[i] = ind1[rgroup * (M + 2) + i]; ind0[rgroup * (M + 3) + i] = ind0[i + rgroup]; ind1[rgroup * (M + 3) + i] = ind1[i + rgroup]; } } } /// /// Change the pointer lists to duplicate the last sample row at the bottom /// of the image. m_whichFunny indicates which m_funnyIndices holds the final iMCU row. /// Also sets rowgroups_avail to indicate number of nondummy row groups in row. /// private void set_bottom_pointers() { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { /* Count sample rows in one iMCU row and in one row group */ int iMCUheight = m_cinfo.Comp_info[ci].V_samp_factor * m_cinfo.Comp_info[ci].DCT_scaled_size; int rgroup = iMCUheight / m_cinfo.m_min_DCT_scaled_size; /* Count nondummy sample rows remaining for this component */ int rows_left = m_cinfo.Comp_info[ci].downsampled_height % iMCUheight; if (rows_left == 0) rows_left = iMCUheight; /* Count nondummy row groups. Should get same answer for each component, * so we need only do it once. */ if (ci == 0) m_rowgroups_avail = (rows_left - 1) / rgroup + 1; /* Duplicate the last real sample row rgroup*2 times; this pads out the * last partial rowgroup and ensures at least one full rowgroup of context. */ for (int i = 0; i < rgroup * 2; i++) m_funnyIndices[m_whichFunny][ci][rows_left + i + rgroup] = m_funnyIndices[m_whichFunny][ci][rows_left - 1 + rgroup]; } } } #endregion #region JpegDecompressorMaster /// /// Master control module /// class JpegDecompressorMaster { private JpegDecompressor m_cinfo; private int m_pass_number; /* # of passes completed */ private bool m_is_dummy_pass; /* True during 1st pass for 2-pass quant */ private bool m_using_merged_upsample; /* true if using merged upsample/cconvert */ /* Saved references to initialized quantizer modules, * in case we need to switch modes. */ private ColorQuantizer m_quantizer_1pass; private ColorQuantizer m_quantizer_2pass; public JpegDecompressorMaster(JpegDecompressor cinfo) { m_cinfo = cinfo; master_selection(); } /// /// Per-pass setup. /// This is called at the beginning of each output pass. We determine which /// modules will be active during this pass and give them appropriate /// start_pass calls. We also set is_dummy_pass to indicate whether this /// is a "real" output pass or a dummy pass for color quantization. /// (In the latter case, we will crank the pass to completion.) /// public void prepare_for_output_pass() { if (m_is_dummy_pass) { /* Final pass of 2-pass quantization */ m_is_dummy_pass = false; m_cinfo.m_cquantize.start_pass(false); m_cinfo.m_post.start_pass(BufferMode.CrankDest); m_cinfo.m_main.start_pass(BufferMode.CrankDest); } else { if (m_cinfo.m_quantize_colors && m_cinfo.m_colormap == null) { /* Select new quantization method */ if (m_cinfo.m_two_pass_quantize && m_cinfo.m_enable_2pass_quant) { m_cinfo.m_cquantize = m_quantizer_2pass; m_is_dummy_pass = true; } else if (m_cinfo.m_enable_1pass_quant) m_cinfo.m_cquantize = m_quantizer_1pass; else throw new Exception("Invalid color quantization mode change"); } m_cinfo.m_idct.start_pass(); m_cinfo.m_coef.start_output_pass(); if (!m_cinfo.m_raw_data_out) { m_cinfo.m_upsample.start_pass(); if (m_cinfo.m_quantize_colors) m_cinfo.m_cquantize.start_pass(m_is_dummy_pass); m_cinfo.m_post.start_pass((m_is_dummy_pass ? BufferMode.SaveAndPass : BufferMode.PassThru)); m_cinfo.m_main.start_pass(BufferMode.PassThru); } } } /// /// Finish up at end of an output pass. /// public void finish_output_pass() { if (m_cinfo.m_quantize_colors) m_cinfo.m_cquantize.finish_pass(); m_pass_number++; } public bool IsDummyPass() { return m_is_dummy_pass; } /// /// Master selection of decompression modules. /// This is done once at jpeg_start_decompress time. We determine /// which modules will be used and give them appropriate initialization calls. /// We also initialize the decompressor input side to begin consuming data. /// /// Since jpeg_read_header has finished, we know what is in the SOF /// and (first) SOS markers. We also have all the application parameter /// settings. /// private void master_selection() { /* Initialize dimensions and other stuff */ m_cinfo.jpeg_calc_output_dimensions(); prepare_range_limit_table(); /* Width of an output scanline must be representable as int. */ long samplesperrow = m_cinfo.m_output_width * m_cinfo.m_out_color_components; int jd_samplesperrow = (int)samplesperrow; if ((long)jd_samplesperrow != samplesperrow) throw new Exception("Image too wide for this implementation"); /* Initialize my private state */ m_pass_number = 0; m_using_merged_upsample = m_cinfo.use_merged_upsample(); /* Color quantizer selection */ m_quantizer_1pass = null; m_quantizer_2pass = null; /* No mode changes if not using buffered-image mode. */ if (!m_cinfo.m_quantize_colors || !m_cinfo.m_buffered_image) { m_cinfo.m_enable_1pass_quant = false; m_cinfo.m_enable_external_quant = false; m_cinfo.m_enable_2pass_quant = false; } if (m_cinfo.m_quantize_colors) { if (m_cinfo.m_raw_data_out) throw new Exception("Not implemented yet"); /* 2-pass quantizer only works in 3-component color space. */ if (m_cinfo.m_out_color_components != 3) { m_cinfo.m_enable_1pass_quant = true; m_cinfo.m_enable_external_quant = false; m_cinfo.m_enable_2pass_quant = false; m_cinfo.m_colormap = null; } else if (m_cinfo.m_colormap != null) m_cinfo.m_enable_external_quant = true; else if (m_cinfo.m_two_pass_quantize) m_cinfo.m_enable_2pass_quant = true; else m_cinfo.m_enable_1pass_quant = true; if (m_cinfo.m_enable_1pass_quant) { m_cinfo.m_cquantize = new Pass1ColorQuantizer(m_cinfo); m_quantizer_1pass = m_cinfo.m_cquantize; } /* We use the 2-pass code to map to external colormaps. */ if (m_cinfo.m_enable_2pass_quant || m_cinfo.m_enable_external_quant) { m_cinfo.m_cquantize = new Pass2ColorQuantizer(m_cinfo); m_quantizer_2pass = m_cinfo.m_cquantize; } /* If both quantizers are initialized, the 2-pass one is left active; * this is necessary for starting with quantization to an external map. */ } /* Post-processing: in particular, color conversion first */ if (!m_cinfo.m_raw_data_out) { if (m_using_merged_upsample) { /* does color conversion too */ m_cinfo.m_upsample = new MergedUpsampler(m_cinfo); } else { m_cinfo.m_cconvert = new ColorDeconverter(m_cinfo); m_cinfo.m_upsample = new UpsamplerImpl(m_cinfo); } m_cinfo.m_post = new JpegDecompressorPostController(m_cinfo, m_cinfo.m_enable_2pass_quant); } /* Inverse DCT */ m_cinfo.m_idct = new JpegInverseDCT(m_cinfo); if (m_cinfo.m_progressive_mode) m_cinfo.m_entropy = new ProgressiveHuffmanDecoder(m_cinfo); else m_cinfo.m_entropy = new HuffEntropyDecoder(m_cinfo); /* Initialize principal buffer controllers. */ bool use_c_buffer = m_cinfo.m_inputctl.HasMultipleScans() || m_cinfo.m_buffered_image; m_cinfo.m_coef = new JpegDecompressorCoefController(m_cinfo, use_c_buffer); if (!m_cinfo.m_raw_data_out) m_cinfo.m_main = new JpegDecompressorMainController(m_cinfo); /* Initialize input side of decompressor to consume first scan. */ m_cinfo.m_inputctl.start_input_pass(); } /// /// Allocate and fill in the sample_range_limit table. /// /// Several decompression processes need to range-limit values to the range /// 0..MaxSampleValue; the input value may fall somewhat outside this range /// due to noise introduced by quantization, roundoff error, etc. These /// processes are inner loops and need to be as fast as possible. On most /// machines, particularly CPUs with pipelines or instruction prefetch, /// a (subscript-check-less) C table lookup /// x = sample_range_limit[x]; /// is faster than explicit tests /// /// if (x & 0) /// x = 0; /// else if (x > MaxSampleValue) /// x = MaxSampleValue; /// /// These processes all use a common table prepared by the routine below. /// /// For most steps we can mathematically guarantee that the initial value /// of x is within MaxSampleValue + 1 of the legal range, so a table running from /// -(MaxSampleValue + 1) to 2 * MaxSampleValue + 1 is sufficient. But for the initial /// limiting step (just after the IDCT), a wildly out-of-range value is /// possible if the input data is corrupt. To avoid any chance of indexing /// off the end of memory and getting a bad-pointer trap, we perform the /// post-IDCT limiting thus: x = range_limit[x & Mask]; /// where Mask is 2 bits wider than legal sample data, ie 10 bits for 8-bit /// samples. Under normal circumstances this is more than enough range and /// a correct output will be generated; with bogus input data the mask will /// cause wraparound, and we will safely generate a bogus-but-in-range output. /// For the post-IDCT step, we want to convert the data from signed to unsigned /// representation by adding MediumSampleValue at the same time that we limit it. /// So the post-IDCT limiting table ends up looking like this: ///
        ///     MediumSampleValue, MediumSampleValue + 1, ..., MaxSampleValue,
        ///     MaxSampleValue (repeat 2 * (MaxSampleValue + 1) - MediumSampleValue times),
        ///     0          (repeat 2 * (MaxSampleValue + 1) - MediumSampleValue times),
        ///     0, 1, ..., MediumSampleValue - 1
        /// 
/// Negative inputs select values from the upper half of the table after /// masking. /// /// We can save some space by overlapping the start of the post-IDCT table /// with the simpler range limiting table. The post-IDCT table begins at /// sample_range_limit + MediumSampleValue. /// /// Note that the table is allocated in near data space on PCs; it's small /// enough and used often enough to justify this. ///
private void prepare_range_limit_table() { byte[] table = new byte[5 * (JpegConstants.MaxSampleValue + 1) + JpegConstants.MediumSampleValue]; /* allow negative subscripts of simple table */ int tableOffset = JpegConstants.MaxSampleValue + 1; m_cinfo.m_sample_range_limit = table; m_cinfo.m_sampleRangeLimitOffset = tableOffset; /* First segment of "simple" table: limit[x] = 0 for x < 0 */ Array.Clear(table, 0, JpegConstants.MaxSampleValue + 1); /* Main part of "simple" table: limit[x] = x */ for (int i = 0; i <= JpegConstants.MaxSampleValue; i++) table[tableOffset + i] = (byte)i; tableOffset += JpegConstants.MediumSampleValue; /* Point to where post-IDCT table starts */ /* End of simple table, rest of first half of post-IDCT table */ for (int i = JpegConstants.MediumSampleValue; i < 2 * (JpegConstants.MaxSampleValue + 1); i++) table[tableOffset + i] = JpegConstants.MaxSampleValue; /* Second half of post-IDCT table */ Array.Clear(table, tableOffset + 2 * (JpegConstants.MaxSampleValue + 1), 2 * (JpegConstants.MaxSampleValue + 1) - JpegConstants.MediumSampleValue); Buffer.BlockCopy(m_cinfo.m_sample_range_limit, 0, table, tableOffset + 4 * (JpegConstants.MaxSampleValue + 1) - JpegConstants.MediumSampleValue, JpegConstants.MediumSampleValue); } } #endregion #region JpegDecompressorPostController /// /// Decompression postprocessing (color quantization buffer control) /// class JpegDecompressorPostController { private enum ProcessorType { OnePass, PrePass, Upsample, SecondPass } private ProcessorType m_processor; private JpegDecompressor m_cinfo; /* Color quantization source buffer: this holds output data from * the upsample/color conversion step to be passed to the quantizer. * For two-pass color quantization, we need a full-image buffer; * for one-pass operation, a strip buffer is sufficient. */ private JpegVirtualArray m_whole_image; /* virtual array, or null if one-pass */ private byte[][] m_buffer; /* strip buffer, or current strip of virtual */ private int m_strip_height; /* buffer size in rows */ /* for two-pass mode only: */ private int m_starting_row; /* row # of first row in current strip */ private int m_next_row; /* index of next row to fill/empty in strip */ /// /// Initialize postprocessing controller. /// public JpegDecompressorPostController(JpegDecompressor cinfo, bool need_full_buffer) { m_cinfo = cinfo; /* Create the quantization buffer, if needed */ if (cinfo.m_quantize_colors) { /* The buffer strip height is max_v_samp_factor, which is typically * an efficient number of rows for upsampling to return. * (In the presence of output rescaling, we might want to be smarter?) */ m_strip_height = cinfo.m_max_v_samp_factor; if (need_full_buffer) { /* Two-pass color quantization: need full-image storage. */ /* We round up the number of rows to a multiple of the strip height. */ m_whole_image = JpegCommonBase.CreateSamplesArray( cinfo.m_output_width * cinfo.m_out_color_components, JpegUtils.jround_up(cinfo.m_output_height, m_strip_height)); m_whole_image.ErrorProcessor = cinfo; } else { /* One-pass color quantization: just make a strip buffer. */ m_buffer = JpegCommonBase.AllocJpegSamples( cinfo.m_output_width * cinfo.m_out_color_components, m_strip_height); } } } /// /// Initialize for a processing pass. /// public void start_pass(BufferMode pass_mode) { switch (pass_mode) { case BufferMode.PassThru: if (m_cinfo.m_quantize_colors) { /* Single-pass processing with color quantization. */ m_processor = ProcessorType.OnePass; /* We could be doing buffered-image output before starting a 2-pass * color quantization; in that case, jinit_d_post_controller did not * allocate a strip buffer. Use the virtual-array buffer as workspace. */ if (m_buffer == null) m_buffer = m_whole_image.Access(0, m_strip_height); } else { /* For single-pass processing without color quantization, * I have no work to do; just call the upsampler directly. */ m_processor = ProcessorType.Upsample; } break; case BufferMode.SaveAndPass: /* First pass of 2-pass quantization */ if (m_whole_image == null) throw new Exception("Bogus buffer control mode"); m_processor = ProcessorType.PrePass; break; case BufferMode.CrankDest: /* Second pass of 2-pass quantization */ if (m_whole_image == null) throw new Exception("Bogus buffer control mode"); m_processor = ProcessorType.SecondPass; break; default: throw new Exception("Bogus buffer control mode"); } m_starting_row = m_next_row = 0; } public void post_process_data(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { switch (m_processor) { case ProcessorType.OnePass: post_process_1pass(input_buf, ref in_row_group_ctr, in_row_groups_avail, output_buf, ref out_row_ctr, out_rows_avail); break; case ProcessorType.PrePass: post_process_prepass(input_buf, ref in_row_group_ctr, in_row_groups_avail, ref out_row_ctr); break; case ProcessorType.Upsample: m_cinfo.m_upsample.upsample(input_buf, ref in_row_group_ctr, in_row_groups_avail, output_buf, ref out_row_ctr, out_rows_avail); break; case ProcessorType.SecondPass: post_process_2pass(output_buf, ref out_row_ctr, out_rows_avail); break; default: throw new Exception("Not implemented yet"); } } /// /// Process some data in the one-pass (strip buffer) case. /// This is used for color precision reduction as well as one-pass quantization. /// private void post_process_1pass(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { /* Fill the buffer, but not more than what we can dump out in one go. */ /* Note we rely on the upsampler to detect bottom of image. */ int max_rows = out_rows_avail - out_row_ctr; if (max_rows > m_strip_height) max_rows = m_strip_height; int num_rows = 0; m_cinfo.m_upsample.upsample(input_buf, ref in_row_group_ctr, in_row_groups_avail, m_buffer, ref num_rows, max_rows); /* Quantize and emit data. */ m_cinfo.m_cquantize.color_quantize(m_buffer, 0, output_buf, out_row_ctr, num_rows); out_row_ctr += num_rows; } /// /// Process some data in the first pass of 2-pass quantization. /// private void post_process_prepass(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, ref int out_row_ctr) { int old_next_row, num_rows; /* Reposition virtual buffer if at start of strip. */ if (m_next_row == 0) m_buffer = m_whole_image.Access(m_starting_row, m_strip_height); /* Upsample some data (up to a strip height's worth). */ old_next_row = m_next_row; m_cinfo.m_upsample.upsample(input_buf, ref in_row_group_ctr, in_row_groups_avail, m_buffer, ref m_next_row, m_strip_height); /* Allow quantizer to scan new data. No data is emitted, */ /* but we advance out_row_ctr so outer loop can tell when we're done. */ if (m_next_row > old_next_row) { num_rows = m_next_row - old_next_row; m_cinfo.m_cquantize.color_quantize(m_buffer, old_next_row, null, 0, num_rows); out_row_ctr += num_rows; } /* Advance if we filled the strip. */ if (m_next_row >= m_strip_height) { m_starting_row += m_strip_height; m_next_row = 0; } } /// /// Process some data in the second pass of 2-pass quantization. /// private void post_process_2pass(byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { int num_rows, max_rows; /* Reposition virtual buffer if at start of strip. */ if (m_next_row == 0) m_buffer = m_whole_image.Access(m_starting_row, m_strip_height); /* Determine number of rows to emit. */ num_rows = m_strip_height - m_next_row; /* available in strip */ max_rows = out_rows_avail - out_row_ctr; /* available in output area */ if (num_rows > max_rows) num_rows = max_rows; /* We have to check bottom of image here, can't depend on upsampler. */ max_rows = m_cinfo.m_output_height - m_starting_row; if (num_rows > max_rows) num_rows = max_rows; /* Quantize and emit data. */ m_cinfo.m_cquantize.color_quantize(m_buffer, m_next_row, output_buf, out_row_ctr, num_rows); out_row_ctr += num_rows; /* Advance if we filled the strip. */ m_next_row += num_rows; if (m_next_row >= m_strip_height) { m_starting_row += m_strip_height; m_next_row = 0; } } } #endregion #region JpegCommonBase /// Base class for both JPEG compressor and decompresor. /// /// Routines that are to be used by both halves of the library are declared /// to receive an instance of this class. There are no actual instances of /// , only of /// and /// public abstract class JpegCommonBase { internal enum JpegState { DESTROYED = 0, CSTATE_START = 100, /* after create_compress */ CSTATE_SCANNING = 101, /* start_compress done, write_scanlines OK */ CSTATE_RAW_OK = 102, /* start_compress done, write_raw_data OK */ CSTATE_WRCOEFS = 103, /* jpeg_write_coefficients done */ DSTATE_START = 200, /* after create_decompress */ DSTATE_INHEADER = 201, /* reading header markers, no SOS yet */ DSTATE_READY = 202, /* found SOS, ready for start_decompress */ DSTATE_PRELOAD = 203, /* reading multi-scan file in start_decompress*/ DSTATE_PRESCAN = 204, /* performing dummy pass for 2-pass quant */ DSTATE_SCANNING = 205, /* start_decompress done, read_scanlines OK */ DSTATE_RAW_OK = 206, /* start_decompress done, read_raw_data OK */ DSTATE_BUFIMAGE = 207, /* expecting jpeg_start_output */ DSTATE_BUFPOST = 208, /* looking for SOS/EOI in jpeg_finish_output */ DSTATE_RDCOEFS = 209, /* reading file in jpeg_read_coefficients */ DSTATE_STOPPING = 210 /* looking for EOI in jpeg_finish_decompress */ } internal JpegState m_global_state; /* For checking call sequence validity */ /// /// Base constructor. /// /// /// public JpegCommonBase() { } /// /// Gets a value indicating whether this instance is Jpeg decompressor. /// /// /// true if this is Jpeg decompressor; otherwise, false. /// public abstract bool IsDecompressor { get; } /// /// Gets the version of LibJpeg. /// /// The version of LibJpeg. public static string Version { get { return "Special Compilation for Cosmos. Based on version 1.2.300.0 of LibJpeg.Net"; } } /// /// Gets the LibJpeg's copyright. /// /// The copyright. public static string Copyright { get { return "Copyright (C) 2008-2011, Bit Miracle"; } } /// /// Creates the array of samples. /// /// The number of samples in row. /// The number of rows. /// The array of samples. public static JpegVirtualArray CreateSamplesArray(int samplesPerRow, int numberOfRows) { return new JpegVirtualArray(samplesPerRow, numberOfRows, AllocJpegSamples); } /// /// Creates the array of blocks. /// /// The number of blocks in row. /// The number of rows. /// The array of blocks. /// public static JpegVirtualArray CreateBlocksArray(int blocksPerRow, int numberOfRows) { return new JpegVirtualArray(blocksPerRow, numberOfRows, allocJpegBlocks); } /// /// Creates 2-D sample array. /// /// The number of samples per row. /// The number of rows. /// The array of samples. public static byte[][] AllocJpegSamples(int samplesPerRow, int numberOfRows) { byte[][] result = new byte[numberOfRows][]; for (int i = 0; i < numberOfRows; ++i) result[i] = new byte[samplesPerRow]; return result; } // Creation of 2-D block arrays. private static JpegBlock[][] allocJpegBlocks(int blocksPerRow, int numberOfRows) { JpegBlock[][] result = new JpegBlock[numberOfRows][]; for (int i = 0; i < numberOfRows; ++i) { result[i] = new JpegBlock[blocksPerRow]; for (int j = 0; j < blocksPerRow; ++j) result[i][j] = new JpegBlock(); } return result; } // Generic versions of jpeg_abort and jpeg_destroy that work on either // flavor of JPEG object. These may be more convenient in some places. /// /// Abort processing of a JPEG compression or decompression operation, /// but don't destroy the object itself. /// /// Closing a data source or destination, if necessary, is the /// application's responsibility. /// public void jpeg_abort() { /* Reset overall state for possible reuse of object */ if (IsDecompressor) { m_global_state = JpegState.DSTATE_START; /* Try to keep application from accessing now-deleted marker list. * A bit kludgy to do it here, but this is the most central place. */ JpegDecompressor s = this as JpegDecompressor; if (s != null) s.m_marker_list = null; } else { m_global_state = JpegState.CSTATE_START; } } /// /// Destruction of a JPEG object. /// /// Closing a data source or destination, if necessary, is the /// application's responsibility. /// public void jpeg_destroy() { // mark it destroyed m_global_state = JpegState.DESTROYED; } } #endregion #region JpegComponent /// /// Basic info about one component (color channel). /// public class JpegComponent { /* These values are fixed over the whole image. */ /* For compression, they must be supplied by parameter setup; */ /* for decompression, they are read from the SOF marker. */ private int component_id; private int component_index; private int h_samp_factor; private int v_samp_factor; private int quant_tbl_no; /* These values may vary between scans. */ /* For compression, they must be supplied by parameter setup; */ /* for decompression, they are read from the SOS marker. */ /* The decompressor output side may not use these variables. */ private int dc_tbl_no; private int ac_tbl_no; /* Remaining fields should be treated as private by applications. */ /* These values are computed during compression or decompression startup: */ /* Component's size in DCT blocks. * Any dummy blocks added to complete an MCU are not counted; therefore * these values do not depend on whether a scan is interleaved or not. */ private int width_in_blocks; internal int height_in_blocks; /* Size of a DCT block in samples. Always DCTSize for compression. * For decompression this is the size of the output from one DCT block, * reflecting any scaling we choose to apply during the IDCT step. * Values of 1,2,4,8 are likely to be supported. Note that different * components may receive different IDCT scalings. */ internal int DCT_scaled_size; /* The downsampled dimensions are the component's actual, unpadded number * of samples at the main buffer (preprocessing/compression interface), thus * downsampled_width = ceil(image_width * Hi/Hmax) * and similarly for height. For decompression, IDCT scaling is included, so * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSize) */ internal int downsampled_width; /* actual width in samples */ internal int downsampled_height; /* actual height in samples */ /* This flag is used only for decompression. In cases where some of the * components will be ignored (eg grayscale output from YCbCr image), * we can skip most computations for the unused components. */ internal bool component_needed; /* do we need the value of this component? */ /* These values are computed before starting a scan of the component. */ /* The decompressor output side may not use these variables. */ internal int MCU_width; /* number of blocks per MCU, horizontally */ internal int MCU_height; /* number of blocks per MCU, vertically */ internal int MCU_blocks; /* MCU_width * MCU_height */ internal int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ internal int last_col_width; /* # of non-dummy blocks across in last MCU */ internal int last_row_height; /* # of non-dummy blocks down in last MCU */ /* Saved quantization table for component; null if none yet saved. * See JpegInputController comments about the need for this information. * This field is currently used only for decompression. */ internal JpegQuantizationTable quant_table; internal JpegComponent() { } internal void Assign(JpegComponent ci) { component_id = ci.component_id; component_index = ci.component_index; h_samp_factor = ci.h_samp_factor; v_samp_factor = ci.v_samp_factor; quant_tbl_no = ci.quant_tbl_no; dc_tbl_no = ci.dc_tbl_no; ac_tbl_no = ci.ac_tbl_no; width_in_blocks = ci.width_in_blocks; height_in_blocks = ci.height_in_blocks; DCT_scaled_size = ci.DCT_scaled_size; downsampled_width = ci.downsampled_width; downsampled_height = ci.downsampled_height; component_needed = ci.component_needed; MCU_width = ci.MCU_width; MCU_height = ci.MCU_height; MCU_blocks = ci.MCU_blocks; MCU_sample_width = ci.MCU_sample_width; last_col_width = ci.last_col_width; last_row_height = ci.last_row_height; quant_table = ci.quant_table; } /// /// Identifier for this component (0..255) /// /// The component ID. public int Component_id { get { return component_id; } set { component_id = value; } } /// /// Its index in SOF or . /// /// The component index. public int Component_index { get { return component_index; } set { component_index = value; } } /// /// Horizontal sampling factor (1..4) /// /// The horizontal sampling factor. public int H_samp_factor { get { return h_samp_factor; } set { h_samp_factor = value; } } /// /// Vertical sampling factor (1..4) /// /// The vertical sampling factor. public int V_samp_factor { get { return v_samp_factor; } set { v_samp_factor = value; } } /// /// Quantization table selector (0..3) /// /// The quantization table selector. public int Quant_tbl_no { get { return quant_tbl_no; } set { quant_tbl_no = value; } } /// /// DC entropy table selector (0..3) /// /// The DC entropy table selector. public int Dc_tbl_no { get { return dc_tbl_no; } set { dc_tbl_no = value; } } /// /// AC entropy table selector (0..3) /// /// The AC entropy table selector. public int Ac_tbl_no { get { return ac_tbl_no; } set { ac_tbl_no = value; } } /// /// Gets or sets the width in blocks. /// /// The width in blocks. public int Width_in_blocks { get { return width_in_blocks; } set { width_in_blocks = value; } } /// /// Gets the downsampled width. /// /// The downsampled width. public int Downsampled_width { get { return downsampled_width; } } internal static JpegComponent[] createArrayOfComponents(int length) { if (length < 0) throw new ArgumentOutOfRangeException("length"); JpegComponent[] result = new JpegComponent[length]; for (int i = 0; i < result.Length; ++i) result[i] = new JpegComponent(); return result; } } #endregion #region JpegConstants /// /// Defines some JPEG constants. /// public static class JpegConstants { ////////////////////////////////////////////////////////////////////////// // All of these are specified by the JPEG standard, so don't change them // if you want to be compatible. // /// /// The basic DCT block is 8x8 samples /// public const int DCTSize = 8; /// /// DCTSize squared; the number of elements in a block. /// public const int DCTSize2 = DCTSize * DCTSize; /// /// Quantization tables are numbered 0..3 /// public const int NumberOfQuantTables = 4; /// /// Huffman tables are numbered 0..3 /// public const int NumberOfHuffmanTables = 4; /// /// JPEG limit on the number of components in one scan. /// public const int MaxComponentsInScan = 4; // compressor's limit on blocks per MCU // // Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; // the PostScript DCT filter can emit files with many more than 10 blocks/MCU. // If you happen to run across such a file, you can up DecompressorMaxBlocksInMCU // to handle it. We even let you do this from the jconfig.h file. However, // we strongly discourage changing CompressorMaxBlocksInMCU; just because Adobe // sometimes emits noncompliant files doesn't mean you should too. /// /// Compressor's limit on blocks per MCU. /// public const int CompressorMaxBlocksInMCU = 10; /// /// Decompressor's limit on blocks per MCU. /// public const int DecompressorMaxBlocksInMCU = 10; /// /// JPEG limit on sampling factors. /// public const int MaxSamplingFactor = 4; ////////////////////////////////////////////////////////////////////////// // implementation-specific constants // // Maximum number of components (color channels) allowed in JPEG image. // To meet the letter of the JPEG spec, set this to 255. However, darn // few applications need more than 4 channels (maybe 5 for CMYK + alpha // mask). We recommend 10 as a reasonable compromise; use 4 if you are // really short on memory. (Each allowed component costs a hundred or so // bytes of storage, whether actually used in an image or not.) /// /// Maximum number of color channels allowed in JPEG image. /// public const int MaxComponents = 10; /// /// The size of sample. /// /// Is either: /// 8 - for 8-bit sample values (the usual setting)
/// 12 - for 12-bit sample values (not supported by this version)
/// Only 8 and 12 are legal data precisions for lossy JPEG according to the JPEG standard. /// Although original IJG code claims it supports 12 bit images, our code does not support /// anything except 8-bit images.
public const int BitsInSample = 8; /// /// DCT method used by default. /// public static DCTMethod DefaultDCTMethod = DCTMethod.IntSlow; /// /// Fastest DCT method. /// public static DCTMethod FastestDCTMethod = DCTMethod.IntFast; /// /// A tad under 64K to prevent overflows. /// public const int JpegMaxDimention = 65500; /// /// The maximum sample value. /// public const int MaxSampleValue = 255; /// /// The medium sample value. /// public const int MediumSampleValue = 128; // Ordering of RGB data in scanlines passed to or from the application. // RESTRICTIONS: // 1. These macros only affect RGB<=>YCbCr color conversion, so they are not // useful if you are using JPEG color spaces other than YCbCr or grayscale. // 2. The color quantizer modules will not behave desirably if RGB_PixelLength // is not 3 (they don't understand about dummy color components!). So you // can't use color quantization if you change that value. /// /// Offset of Red in an RGB scanline element. /// public const int Offset_RGB_Red = 0; /// /// Offset of Green in an RGB scanline element. /// public const int Offset_RGB_Green = 1; /// /// Offset of Blue in an RGB scanline element. /// public const int Offset_RGB_Blue = 2; /// /// Bytes per RGB scanline element. /// public const int RGB_PixelLength = 3; /// /// The number of bits of lookahead. /// public const int HuffmanLookaheadDistance = 8; } #endregion #region JpegDownsampler /// /// Downsampling /// class JpegDownsampler { private enum downSampleMethod { fullsize_smooth_downsampler, fullsize_downsampler, h2v1_downsampler, h2v2_smooth_downsampler, h2v2_downsampler, int_downsampler }; /* Downsamplers, one per component */ private downSampleMethod[] m_downSamplers = new downSampleMethod[JpegConstants.MaxComponents]; private JpegCompressor m_cinfo; private bool m_need_context_rows; /* true if need rows above & below */ public JpegDownsampler(JpegCompressor cinfo) { m_cinfo = cinfo; m_need_context_rows = false; if (cinfo.m_CCIR601_sampling) throw new Exception("CCIR601 sampling not implemented yet"); /* Verify we can handle the sampling factors, and set up method pointers */ for (int ci = 0; ci < cinfo.m_num_components; ci++) { JpegComponent componentInfo = cinfo.Component_info[ci]; if (componentInfo.H_samp_factor == cinfo.m_max_h_samp_factor && componentInfo.V_samp_factor == cinfo.m_max_v_samp_factor) { if (cinfo.m_smoothing_factor != 0) { m_downSamplers[ci] = downSampleMethod.fullsize_smooth_downsampler; m_need_context_rows = true; } else { m_downSamplers[ci] = downSampleMethod.fullsize_downsampler; } } else if (componentInfo.H_samp_factor * 2 == cinfo.m_max_h_samp_factor && componentInfo.V_samp_factor == cinfo.m_max_v_samp_factor) { m_downSamplers[ci] = downSampleMethod.h2v1_downsampler; } else if (componentInfo.H_samp_factor * 2 == cinfo.m_max_h_samp_factor && componentInfo.V_samp_factor * 2 == cinfo.m_max_v_samp_factor) { if (cinfo.m_smoothing_factor != 0) { m_downSamplers[ci] = downSampleMethod.h2v2_smooth_downsampler; m_need_context_rows = true; } else { m_downSamplers[ci] = downSampleMethod.h2v2_downsampler; } } else if ((cinfo.m_max_h_samp_factor % componentInfo.H_samp_factor) == 0 && (cinfo.m_max_v_samp_factor % componentInfo.V_samp_factor) == 0) { m_downSamplers[ci] = downSampleMethod.int_downsampler; } else throw new Exception("Fractional sampling not implemented yet"); } } /// /// Do downsampling for a whole row group (all components). /// /// In this version we simply downsample each component independently. /// public void downsample(byte[][][] input_buf, int in_row_index, byte[][][] output_buf, int out_row_group_index) { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { int outIndex = out_row_group_index * m_cinfo.Component_info[ci].V_samp_factor; switch (m_downSamplers[ci]) { case downSampleMethod.fullsize_smooth_downsampler: fullsize_smooth_downsample(ci, input_buf[ci], in_row_index, output_buf[ci], outIndex); break; case downSampleMethod.fullsize_downsampler: fullsize_downsample(ci, input_buf[ci], in_row_index, output_buf[ci], outIndex); break; case downSampleMethod.h2v1_downsampler: h2v1_downsample(ci, input_buf[ci], in_row_index, output_buf[ci], outIndex); break; case downSampleMethod.h2v2_smooth_downsampler: h2v2_smooth_downsample(ci, input_buf[ci], in_row_index, output_buf[ci], outIndex); break; case downSampleMethod.h2v2_downsampler: h2v2_downsample(ci, input_buf[ci], in_row_index, output_buf[ci], outIndex); break; case downSampleMethod.int_downsampler: int_downsample(ci, input_buf[ci], in_row_index, output_buf[ci], outIndex); break; }; } } public bool NeedContextRows() { return m_need_context_rows; } /// /// Downsample pixel values of a single component. /// One row group is processed per call. /// This version handles arbitrary integral sampling ratios, without smoothing. /// Note that this version is not actually used for customary sampling ratios. /// private void int_downsample(int componentIndex, byte[][] input_data, int startInputRow, byte[][] output_data, int startOutRow) { /* Expand input data enough to let all the output samples be generated * by the standard loop. Special-casing padded output would be more * efficient. */ int output_cols = m_cinfo.Component_info[componentIndex].Width_in_blocks * JpegConstants.DCTSize; int h_expand = m_cinfo.m_max_h_samp_factor / m_cinfo.Component_info[componentIndex].H_samp_factor; expand_right_edge(input_data, startInputRow, m_cinfo.m_max_v_samp_factor, m_cinfo.m_image_width, output_cols * h_expand); int v_expand = m_cinfo.m_max_v_samp_factor / m_cinfo.Component_info[componentIndex].V_samp_factor; int numpix = h_expand * v_expand; int numpix2 = numpix / 2; int inrow = 0; for (int outrow = 0; outrow < m_cinfo.Component_info[componentIndex].V_samp_factor; outrow++) { for (int outcol = 0, outcol_h = 0; outcol < output_cols; outcol++, outcol_h += h_expand) { int outvalue = 0; for (int v = 0; v < v_expand; v++) { for (int h = 0; h < h_expand; h++) outvalue += input_data[startInputRow + inrow + v][outcol_h + h]; } output_data[startOutRow + outrow][outcol] = (byte)((outvalue + numpix2) / numpix); } inrow += v_expand; } } /// /// Downsample pixel values of a single component. /// This version handles the special case of a full-size component, /// without smoothing. /// private void fullsize_downsample(int componentIndex, byte[][] input_data, int startInputRow, byte[][] output_data, int startOutRow) { /* Copy the data */ JpegUtils.jcopy_sample_rows(input_data, startInputRow, output_data, startOutRow, m_cinfo.m_max_v_samp_factor, m_cinfo.m_image_width); /* Edge-expand */ expand_right_edge(output_data, startOutRow, m_cinfo.m_max_v_samp_factor, m_cinfo.m_image_width, m_cinfo.Component_info[componentIndex].Width_in_blocks * JpegConstants.DCTSize); } /// /// Downsample pixel values of a single component. /// This version handles the common case of 2:1 horizontal and 1:1 vertical, /// without smoothing. /// /// A note about the "bias" calculations: when rounding fractional values to /// integer, we do not want to always round 0.5 up to the next integer. /// If we did that, we'd introduce a noticeable bias towards larger values. /// Instead, this code is arranged so that 0.5 will be rounded up or down at /// alternate pixel locations (a simple ordered dither pattern). /// private void h2v1_downsample(int componentIndex, byte[][] input_data, int startInputRow, byte[][] output_data, int startOutRow) { /* Expand input data enough to let all the output samples be generated * by the standard loop. Special-casing padded output would be more * efficient. */ int output_cols = m_cinfo.Component_info[componentIndex].Width_in_blocks * JpegConstants.DCTSize; expand_right_edge(input_data, startInputRow, m_cinfo.m_max_v_samp_factor, m_cinfo.m_image_width, output_cols * 2); for (int outrow = 0; outrow < m_cinfo.Component_info[componentIndex].V_samp_factor; outrow++) { /* bias = 0,1,0,1,... for successive samples */ int bias = 0; int inputColumn = 0; for (int outcol = 0; outcol < output_cols; outcol++) { output_data[startOutRow + outrow][outcol] = (byte)( ((int)input_data[startInputRow + outrow][inputColumn] + (int)input_data[startInputRow + outrow][inputColumn + 1] + bias) >> 1); bias ^= 1; /* 0=>1, 1=>0 */ inputColumn += 2; } } } /// /// Downsample pixel values of a single component. /// This version handles the standard case of 2:1 horizontal and 2:1 vertical, /// without smoothing. /// private void h2v2_downsample(int componentIndex, byte[][] input_data, int startInputRow, byte[][] output_data, int startOutRow) { /* Expand input data enough to let all the output samples be generated * by the standard loop. Special-casing padded output would be more * efficient. */ int output_cols = m_cinfo.Component_info[componentIndex].Width_in_blocks * JpegConstants.DCTSize; expand_right_edge(input_data, startInputRow, m_cinfo.m_max_v_samp_factor, m_cinfo.m_image_width, output_cols * 2); int inrow = 0; for (int outrow = 0; outrow < m_cinfo.Component_info[componentIndex].V_samp_factor; outrow++) { /* bias = 1,2,1,2,... for successive samples */ int bias = 1; int inputColumn = 0; for (int outcol = 0; outcol < output_cols; outcol++) { output_data[startOutRow + outrow][outcol] = (byte)(( (int)input_data[startInputRow + inrow][inputColumn] + (int)input_data[startInputRow + inrow][inputColumn + 1] + (int)input_data[startInputRow + inrow + 1][inputColumn] + (int)input_data[startInputRow + inrow + 1][inputColumn + 1] + bias) >> 2); bias ^= 3; /* 1=>2, 2=>1 */ inputColumn += 2; } inrow += 2; } } /// /// Downsample pixel values of a single component. /// This version handles the standard case of 2:1 horizontal and 2:1 vertical, /// with smoothing. One row of context is required. /// private void h2v2_smooth_downsample(int componentIndex, byte[][] input_data, int startInputRow, byte[][] output_data, int startOutRow) { /* Expand input data enough to let all the output samples be generated * by the standard loop. Special-casing padded output would be more * efficient. */ int output_cols = m_cinfo.Component_info[componentIndex].Width_in_blocks * JpegConstants.DCTSize; expand_right_edge(input_data, startInputRow - 1, m_cinfo.m_max_v_samp_factor + 2, m_cinfo.m_image_width, output_cols * 2); /* We don't bother to form the individual "smoothed" input pixel values; * we can directly compute the output which is the average of the four * smoothed values. Each of the four member pixels contributes a fraction * (1-8*SF) to its own smoothed image and a fraction SF to each of the three * other smoothed pixels, therefore a total fraction (1-5*SF)/4 to the final * output. The four corner-adjacent neighbor pixels contribute a fraction * SF to just one smoothed pixel, or SF/4 to the final output; while the * eight edge-adjacent neighbors contribute SF to each of two smoothed * pixels, or SF/2 overall. In order to use integer arithmetic, these * factors are scaled by 2^16 = 65536. * Also recall that SF = smoothing_factor / 1024. */ int memberscale = 16384 - m_cinfo.m_smoothing_factor * 80; /* scaled (1-5*SF)/4 */ int neighscale = m_cinfo.m_smoothing_factor * 16; /* scaled SF/4 */ for (int inrow = 0, outrow = 0; outrow < m_cinfo.Component_info[componentIndex].V_samp_factor; outrow++) { int outIndex = 0; int inIndex0 = 0; int inIndex1 = 0; int aboveIndex = 0; int belowIndex = 0; /* Special case for first column: pretend column -1 is same as column 0 */ int membersum = input_data[startInputRow + inrow][inIndex0] + input_data[startInputRow + inrow][inIndex0 + 1] + input_data[startInputRow + inrow + 1][inIndex1] + input_data[startInputRow + inrow + 1][inIndex1 + 1]; int neighsum = input_data[startInputRow + inrow - 1][aboveIndex] + input_data[startInputRow + inrow - 1][aboveIndex + 1] + input_data[startInputRow + inrow + 2][belowIndex] + input_data[startInputRow + inrow + 2][belowIndex + 1] + input_data[startInputRow + inrow][inIndex0] + input_data[startInputRow + inrow][inIndex0 + 2] + input_data[startInputRow + inrow + 1][inIndex1] + input_data[startInputRow + inrow + 1][inIndex1 + 2]; neighsum += neighsum; neighsum += input_data[startInputRow + inrow - 1][aboveIndex] + input_data[startInputRow + inrow - 1][aboveIndex + 2] + input_data[startInputRow + inrow + 2][belowIndex] + input_data[startInputRow + inrow + 2][belowIndex + 2]; membersum = membersum * memberscale + neighsum * neighscale; output_data[startOutRow + outrow][outIndex] = (byte)((membersum + 32768) >> 16); outIndex++; inIndex0 += 2; inIndex1 += 2; aboveIndex += 2; belowIndex += 2; for (int colctr = output_cols - 2; colctr > 0; colctr--) { /* sum of pixels directly mapped to this output element */ membersum = input_data[startInputRow + inrow][inIndex0] + input_data[startInputRow + inrow][inIndex0 + 1] + input_data[startInputRow + inrow + 1][inIndex1] + input_data[startInputRow + inrow + 1][inIndex1 + 1]; /* sum of edge-neighbor pixels */ neighsum = input_data[startInputRow + inrow - 1][aboveIndex] + input_data[startInputRow + inrow - 1][aboveIndex + 1] + input_data[startInputRow + inrow + 2][belowIndex] + input_data[startInputRow + inrow + 2][belowIndex + 1] + input_data[startInputRow + inrow][inIndex0 - 1] + input_data[startInputRow + inrow][inIndex0 + 2] + input_data[startInputRow + inrow + 1][inIndex1 - 1] + input_data[startInputRow + inrow + 1][inIndex1 + 2]; /* The edge-neighbors count twice as much as corner-neighbors */ neighsum += neighsum; /* Add in the corner-neighbors */ neighsum += input_data[startInputRow + inrow - 1][aboveIndex - 1] + input_data[startInputRow + inrow - 1][aboveIndex + 2] + input_data[startInputRow + inrow + 2][belowIndex - 1] + input_data[startInputRow + inrow + 2][belowIndex + 2]; /* form final output scaled up by 2^16 */ membersum = membersum * memberscale + neighsum * neighscale; /* round, descale and output it */ output_data[startOutRow + outrow][outIndex] = (byte)((membersum + 32768) >> 16); outIndex++; inIndex0 += 2; inIndex1 += 2; aboveIndex += 2; belowIndex += 2; } /* Special case for last column */ membersum = input_data[startInputRow + inrow][inIndex0] + input_data[startInputRow + inrow][inIndex0 + 1] + input_data[startInputRow + inrow + 1][inIndex1] + input_data[startInputRow + inrow + 1][inIndex1 + 1]; neighsum = input_data[startInputRow + inrow - 1][aboveIndex] + input_data[startInputRow + inrow - 1][aboveIndex + 1] + input_data[startInputRow + inrow + 2][belowIndex] + input_data[startInputRow + inrow + 2][belowIndex + 1] + input_data[startInputRow + inrow][inIndex0 - 1] + input_data[startInputRow + inrow][inIndex0 + 1] + input_data[startInputRow + inrow + 1][inIndex1 - 1] + input_data[startInputRow + inrow + 1][inIndex1 + 1]; neighsum += neighsum; neighsum += input_data[startInputRow + inrow - 1][aboveIndex - 1] + input_data[startInputRow + inrow - 1][aboveIndex + 1] + input_data[startInputRow + inrow + 2][belowIndex - 1] + input_data[startInputRow + inrow + 2][belowIndex + 1]; membersum = membersum * memberscale + neighsum * neighscale; output_data[startOutRow + outrow][outIndex] = (byte)((membersum + 32768) >> 16); inrow += 2; } } /// /// Downsample pixel values of a single component. /// This version handles the special case of a full-size component, /// with smoothing. One row of context is required. /// private void fullsize_smooth_downsample(int componentIndex, byte[][] input_data, int startInputRow, byte[][] output_data, int startOutRow) { /* Expand input data enough to let all the output samples be generated * by the standard loop. Special-casing padded output would be more * efficient. */ int output_cols = m_cinfo.Component_info[componentIndex].Width_in_blocks * JpegConstants.DCTSize; expand_right_edge(input_data, startInputRow - 1, m_cinfo.m_max_v_samp_factor + 2, m_cinfo.m_image_width, output_cols); /* Each of the eight neighbor pixels contributes a fraction SF to the * smoothed pixel, while the main pixel contributes (1-8*SF). In order * to use integer arithmetic, these factors are multiplied by 2^16 = 65536. * Also recall that SF = smoothing_factor / 1024. */ int memberscale = 65536 - m_cinfo.m_smoothing_factor * 512; /* scaled 1-8*SF */ int neighscale = m_cinfo.m_smoothing_factor * 64; /* scaled SF */ for (int outrow = 0; outrow < m_cinfo.Component_info[componentIndex].V_samp_factor; outrow++) { int outIndex = 0; int inIndex = 0; int aboveIndex = 0; int belowIndex = 0; /* Special case for first column */ int colsum = input_data[startInputRow + outrow - 1][aboveIndex] + input_data[startInputRow + outrow + 1][belowIndex] + input_data[startInputRow + outrow][inIndex]; aboveIndex++; belowIndex++; int membersum = input_data[startInputRow + outrow][inIndex]; inIndex++; int nextcolsum = input_data[startInputRow + outrow - 1][aboveIndex] + input_data[startInputRow + outrow + 1][belowIndex] + input_data[startInputRow + outrow][inIndex]; int neighsum = colsum + (colsum - membersum) + nextcolsum; membersum = membersum * memberscale + neighsum * neighscale; output_data[startOutRow + outrow][outIndex] = (byte)((membersum + 32768) >> 16); outIndex++; int lastcolsum = colsum; colsum = nextcolsum; for (int colctr = output_cols - 2; colctr > 0; colctr--) { membersum = input_data[startInputRow + outrow][inIndex]; inIndex++; aboveIndex++; belowIndex++; nextcolsum = input_data[startInputRow + outrow - 1][aboveIndex] + input_data[startInputRow + outrow + 1][belowIndex] + input_data[startInputRow + outrow][inIndex]; neighsum = lastcolsum + (colsum - membersum) + nextcolsum; membersum = membersum * memberscale + neighsum * neighscale; output_data[startOutRow + outrow][outIndex] = (byte)((membersum + 32768) >> 16); outIndex++; lastcolsum = colsum; colsum = nextcolsum; } /* Special case for last column */ membersum = input_data[startInputRow + outrow][inIndex]; neighsum = lastcolsum + (colsum - membersum) + colsum; membersum = membersum * memberscale + neighsum * neighscale; output_data[startOutRow + outrow][outIndex] = (byte)((membersum + 32768) >> 16); } } /// /// Expand a component horizontally from width input_cols to width output_cols, /// by duplicating the rightmost samples. /// private static void expand_right_edge(byte[][] image_data, int startInputRow, int num_rows, int input_cols, int output_cols) { int numcols = output_cols - input_cols; if (numcols > 0) { for (int row = startInputRow; row < (startInputRow + num_rows); row++) { /* don't need GETJSAMPLE() here */ byte pixval = image_data[row][input_cols - 1]; for (int count = 0; count < numcols; count++) image_data[row][input_cols + count] = pixval; } } } } #endregion #region JpegEntropyDecoder /// /// Entropy decoding /// abstract class JpegEntropyDecoder { // Figure F.12: extend sign bit. // entry n is 2**(n-1) private static int[] extend_test = { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; // entry n is (-1 << n) + 1 private static int[] extend_offset = { 0, (-1 << 1) + 1, (-1 << 2) + 1, (-1 << 3) + 1, (-1 << 4) + 1, (-1 << 5) + 1, (-1 << 6) + 1, (-1 << 7) + 1, (-1 << 8) + 1, (-1 << 9) + 1, (-1 << 10) + 1, (-1 << 11) + 1, (-1 << 12) + 1, (-1 << 13) + 1, (-1 << 14) + 1, (-1 << 15) + 1 }; /* Fetching the next N bits from the input stream is a time-critical operation * for the Huffman decoders. We implement it with a combination of inline * macros and out-of-line subroutines. Note that N (the number of bits * demanded at one time) never exceeds 15 for JPEG use. * * We read source bytes into get_buffer and dole out bits as needed. * If get_buffer already contains enough bits, they are fetched in-line * by the macros CHECK_BIT_BUFFER and GET_BITS. When there aren't enough * bits, jpeg_fill_bit_buffer is called; it will attempt to fill get_buffer * as full as possible (not just to the number of bits needed; this * prefetching reduces the overhead cost of calling jpeg_fill_bit_buffer). * Note that jpeg_fill_bit_buffer may return false to indicate suspension. * On true return, jpeg_fill_bit_buffer guarantees that get_buffer contains * at least the requested number of bits --- dummy zeroes are inserted if * necessary. */ protected const int BIT_BUF_SIZE = 32; /* size of buffer in bits */ /* * Out-of-line code for bit fetching (shared with jdphuff.c). * See jdhuff.h for info about usage. * Note: current values of get_buffer and bits_left are passed as parameters, * but are returned in the corresponding fields of the state struct. * * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width * of get_buffer to be used. (On machines with wider words, an even larger * buffer could be used.) However, on some machines 32-bit shifts are * quite slow and take time proportional to the number of places shifted. * (This is true with most PC compilers, for instance.) In this case it may * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. */ protected const int MIN_GET_BITS = BIT_BUF_SIZE - 7; protected JpegDecompressor m_cinfo; /* This is here to share code between baseline and progressive decoders; */ /* other modules probably should not use it */ protected bool m_insufficient_data; /* set true after emitting warning */ public abstract void start_pass(); public abstract bool decode_mcu(JpegBlock[] MCU_data); protected static int HUFF_EXTEND(int x, int s) { return ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)); } protected void BITREAD_LOAD_STATE(SavedBitreadState bitstate, out int get_buffer, out int bits_left, ref WorkingBitreadState br_state) { br_state.cinfo = m_cinfo; get_buffer = bitstate.get_buffer; bits_left = bitstate.bits_left; } protected static void BITREAD_SAVE_STATE(ref SavedBitreadState bitstate, int get_buffer, int bits_left) { bitstate.get_buffer = get_buffer; bitstate.bits_left = bits_left; } /// /// Expand a Huffman table definition into the derived format /// This routine also performs some validation checks on the table. /// protected void jpeg_make_d_derived_tbl(bool isDC, int tblno, ref DerivedTable dtbl) { /* Note that huffsize[] and huffcode[] are filled in code-length order, * paralleling the order of the symbols themselves in htbl.huffval[]. */ /* Find the input Huffman table */ if (tblno < 0 || tblno >= JpegConstants.NumberOfHuffmanTables) throw new Exception(String.Format("Huffman table 0x{0:X2} was not defined", tblno)); JpegHuffmanTable htbl = isDC ? m_cinfo.m_dc_huff_tbl_ptrs[tblno] : m_cinfo.m_ac_huff_tbl_ptrs[tblno]; if (htbl == null) throw new Exception(String.Format("Huffman table 0x{0:X2} was not defined", tblno)); /* Allocate a workspace if we haven't already done so. */ if (dtbl == null) dtbl = new DerivedTable(); dtbl.pub = htbl; /* fill in back link */ /* Figure C.1: make table of Huffman code length for each symbol */ int p = 0; char[] huffsize = new char[257]; for (int l = 1; l <= 16; l++) { int i = htbl.Bits[l]; if (i < 0 || p + i > 256) /* protect against table overrun */ throw new Exception("Bogus Huffman table definition"); while ((i--) != 0) huffsize[p++] = (char)l; } huffsize[p] = (char)0; int numsymbols = p; /* Figure C.2: generate the codes themselves */ /* We also validate that the counts represent a legal Huffman code tree. */ int code = 0; int si = huffsize[0]; int[] huffcode = new int[257]; p = 0; while (huffsize[p] != 0) { while (((int)huffsize[p]) == si) { huffcode[p++] = code; code++; } /* code is now 1 more than the last code used for code-length si; but * it must still fit in si bits, since no code is allowed to be all ones. */ if (code >= (1 << si)) throw new Exception("Bogus Huffman table definition"); code <<= 1; si++; } /* Figure F.15: generate decoding tables for bit-sequential decoding */ p = 0; for (int l = 1; l <= 16; l++) { if (htbl.Bits[l] != 0) { /* valoffset[l] = huffval[] index of 1st symbol of code length l, * minus the minimum code of length l */ dtbl.valoffset[l] = p - huffcode[p]; p += htbl.Bits[l]; dtbl.maxcode[l] = huffcode[p - 1]; /* maximum code of length l */ } else { /* -1 if no codes of this length */ dtbl.maxcode[l] = -1; } } dtbl.maxcode[17] = 0xFFFFF; /* ensures jpeg_huff_decode terminates */ /* Compute lookahead tables to speed up decoding. * First we set all the table entries to 0, indicating "too long"; * then we iterate through the Huffman codes that are short enough and * fill in all the entries that correspond to bit sequences starting * with that code. */ Array.Clear(dtbl.look_nbits, 0, dtbl.look_nbits.Length); p = 0; for (int l = 1; l <= JpegConstants.HuffmanLookaheadDistance; l++) { for (int i = 1; i <= htbl.Bits[l]; i++, p++) { /* l = current code's length, p = its index in huffcode[] & huffval[]. */ /* Generate left-justified code followed by all possible bit sequences */ int lookbits = huffcode[p] << (JpegConstants.HuffmanLookaheadDistance - l); for (int ctr = 1 << (JpegConstants.HuffmanLookaheadDistance - l); ctr > 0; ctr--) { dtbl.look_nbits[lookbits] = l; dtbl.look_sym[lookbits] = htbl.Huffval[p]; lookbits++; } } } /* Validate symbols as being reasonable. * For AC tables, we make no check, but accept all byte values 0..255. * For DC tables, we require the symbols to be in range 0..15. * (Tighter bounds could be applied depending on the data depth and mode, * but this is sufficient to ensure safe decoding.) */ if (isDC) { for (int i = 0; i < numsymbols; i++) { int sym = htbl.Huffval[i]; if (sym < 0 || sym > 15) throw new Exception("Bogus Huffman table definition"); } } } /* * These methods provide the in-line portion of bit fetching. * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer * before using GET_BITS, PEEK_BITS, or DROP_BITS. * The variables get_buffer and bits_left are assumed to be locals, * but the state struct might not be (jpeg_huff_decode needs this). * CHECK_BIT_BUFFER(state,n,action); * Ensure there are N bits in get_buffer; if suspend, take action. * val = GET_BITS(n); * Fetch next N bits. * val = PEEK_BITS(n); * Fetch next N bits without removing them from the buffer. * DROP_BITS(n); * Discard next N bits. * The value N should be a simple variable, not an expression, because it * is evaluated multiple times. */ protected static bool CHECK_BIT_BUFFER(ref WorkingBitreadState state, int nbits, ref int get_buffer, ref int bits_left) { if (bits_left < nbits) { if (!jpeg_fill_bit_buffer(ref state, get_buffer, bits_left, nbits)) return false; get_buffer = state.get_buffer; bits_left = state.bits_left; } return true; } protected static int GET_BITS(int nbits, int get_buffer, ref int bits_left) { return (((int)(get_buffer >> (bits_left -= nbits))) & ((1 << nbits) - 1)); } protected static int PEEK_BITS(int nbits, int get_buffer, int bits_left) { return (((int)(get_buffer >> (bits_left - nbits))) & ((1 << nbits) - 1)); } protected static void DROP_BITS(int nbits, ref int bits_left) { bits_left -= nbits; } /* Load up the bit buffer to a depth of at least nbits */ protected static bool jpeg_fill_bit_buffer(ref WorkingBitreadState state, int get_buffer, int bits_left, int nbits) { /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ /* (It is assumed that no request will be for more than that many bits.) */ /* We fail to do so only if we hit a marker or are forced to suspend. */ bool noMoreBytes = false; if (state.cinfo.m_unread_marker == 0) { /* cannot advance past a marker */ while (bits_left < MIN_GET_BITS) { int c; state.cinfo.m_src.GetByte(out c); /* If it's 0xFF, check and discard stuffed zero byte */ if (c == 0xFF) { /* Loop here to discard any padding FF's on terminating marker, * so that we can save a valid unread_marker value. NOTE: we will * accept multiple FF's followed by a 0 as meaning a single FF data * byte. This data pattern is not valid according to the standard. */ do { state.cinfo.m_src.GetByte(out c); } while (c == 0xFF); if (c == 0) { /* Found FF/00, which represents an FF data byte */ c = 0xFF; } else { /* Oops, it's actually a marker indicating end of compressed data. * Save the marker code for later use. * Fine point: it might appear that we should save the marker into * bitread working state, not straight into permanent state. But * once we have hit a marker, we cannot need to suspend within the * current MCU, because we will read no more bytes from the data * source. So it is OK to update permanent state right away. */ state.cinfo.m_unread_marker = c; /* See if we need to insert some fake zero bits. */ noMoreBytes = true; break; } } /* OK, load c into get_buffer */ get_buffer = (get_buffer << 8) | c; bits_left += 8; } /* end while */ } else noMoreBytes = true; if (noMoreBytes) { /* We get here if we've read the marker that terminates the compressed * data segment. There should be enough bits in the buffer register * to satisfy the request; if so, no problem. */ if (nbits > bits_left) { /* Uh-oh. Report corrupted data to user and stuff zeroes into * the data stream, so that we can produce some kind of image. * We use a nonvolatile flag to ensure that only one warning message * appears per data segment. */ if (!state.cinfo.m_entropy.m_insufficient_data) { state.cinfo.m_entropy.m_insufficient_data = true; } /* Fill the buffer with zero bits */ get_buffer <<= MIN_GET_BITS - bits_left; bits_left = MIN_GET_BITS; } } /* Unload the local registers */ state.get_buffer = get_buffer; state.bits_left = bits_left; return true; } /* * Code for extracting next Huffman-coded symbol from input bit stream. * Again, this is time-critical and we make the main paths be macros. * * We use a lookahead table to process codes of up to HuffmanLookaheadDistance bits * without looping. Usually, more than 95% of the Huffman codes will be 8 * or fewer bits long. The few overlength codes are handled with a loop, * which need not be inline code. * * Notes about the HUFF_DECODE macro: * 1. Near the end of the data segment, we may fail to get enough bits * for a lookahead. In that case, we do it the hard way. * 2. If the lookahead table contains no entry, the next code must be * more than HuffmanLookaheadDistance bits long. * 3. jpeg_huff_decode returns -1 if forced to suspend. */ protected static bool HUFF_DECODE(out int result, ref WorkingBitreadState state, DerivedTable htbl, ref int get_buffer, ref int bits_left) { int nb = 0; bool doSlow = false; if (bits_left < JpegConstants.HuffmanLookaheadDistance) { if (!jpeg_fill_bit_buffer(ref state, get_buffer, bits_left, 0)) { result = -1; return false; } get_buffer = state.get_buffer; bits_left = state.bits_left; if (bits_left < JpegConstants.HuffmanLookaheadDistance) { nb = 1; doSlow = true; } } if (!doSlow) { int look = PEEK_BITS(JpegConstants.HuffmanLookaheadDistance, get_buffer, bits_left); if ((nb = htbl.look_nbits[look]) != 0) { DROP_BITS(nb, ref bits_left); result = htbl.look_sym[look]; return true; } nb = JpegConstants.HuffmanLookaheadDistance + 1; } result = jpeg_huff_decode(ref state, get_buffer, bits_left, htbl, nb); if (result < 0) return false; get_buffer = state.get_buffer; bits_left = state.bits_left; return true; } /* Out-of-line case for Huffman code fetching */ protected static int jpeg_huff_decode(ref WorkingBitreadState state, int get_buffer, int bits_left, DerivedTable htbl, int min_bits) { /* HUFF_DECODE has determined that the code is at least min_bits */ /* bits long, so fetch that many bits in one swoop. */ int l = min_bits; if (!CHECK_BIT_BUFFER(ref state, l, ref get_buffer, ref bits_left)) return -1; int code = GET_BITS(l, get_buffer, ref bits_left); /* Collect the rest of the Huffman code one bit at a time. */ /* This is per Figure F.16 in the JPEG spec. */ while (code > htbl.maxcode[l]) { code <<= 1; if (!CHECK_BIT_BUFFER(ref state, 1, ref get_buffer, ref bits_left)) return -1; code |= GET_BITS(1, get_buffer, ref bits_left); l++; } /* Unload the local registers */ state.get_buffer = get_buffer; state.bits_left = bits_left; /* With garbage input we may reach the sentinel value l = 17. */ if (l > 16) { /* fake a zero as the safest result */ return 0; } return htbl.pub.Huffval[code + htbl.valoffset[l]]; } } #endregion #region JpegEntropyEncoder /// /// Entropy encoding /// abstract class JpegEntropyEncoder { /* Derived data constructed for each Huffman table */ protected class c_derived_tbl { public int[] ehufco = new int[256]; /* code for each symbol */ public char[] ehufsi = new char[256]; /* length of code for each symbol */ /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ } /* The legal range of a DCT coefficient is * -1024 .. +1023 for 8-bit data; * -16384 .. +16383 for 12-bit data. * Hence the magnitude should always fit in 10 or 14 bits respectively. */ protected static int MAX_HUFFMAN_COEF_BITS = 10; private static int MAX_CLEN = 32; /* assumed maximum initial code length */ protected JpegCompressor m_cinfo; public abstract void start_pass(bool gather_statistics); public abstract bool encode_mcu(JpegBlock[][] MCU_data); public abstract void finish_pass(); /// /// Expand a Huffman table definition into the derived format /// Compute the derived values for a Huffman table. /// This routine also performs some validation checks on the table. /// protected void jpeg_make_c_derived_tbl(bool isDC, int tblno, ref c_derived_tbl dtbl) { /* Note that huffsize[] and huffcode[] are filled in code-length order, * paralleling the order of the symbols themselves in htbl.huffval[]. */ /* Find the input Huffman table */ if (tblno < 0 || tblno >= JpegConstants.NumberOfHuffmanTables) throw new Exception(String.Format("Huffman table 0x{0:X2} was not defined", tblno)); JpegHuffmanTable htbl = isDC ? m_cinfo.m_dc_huff_tbl_ptrs[tblno] : m_cinfo.m_ac_huff_tbl_ptrs[tblno]; if (htbl == null) throw new Exception(String.Format("Huffman table 0x{0:X2} was not defined", tblno)); /* Allocate a workspace if we haven't already done so. */ if (dtbl == null) dtbl = new c_derived_tbl(); /* Figure C.1: make table of Huffman code length for each symbol */ int p = 0; char[] huffsize = new char[257]; for (int l = 1; l <= 16; l++) { int i = htbl.Bits[l]; if (i < 0 || p + i > 256) /* protect against table overrun */ throw new Exception("Bogus Huffman table definition"); while ((i--) != 0) huffsize[p++] = (char)l; } huffsize[p] = (char)0; int lastp = p; /* Figure C.2: generate the codes themselves */ /* We also validate that the counts represent a legal Huffman code tree. */ int code = 0; int si = huffsize[0]; p = 0; int[] huffcode = new int[257]; while (huffsize[p] != 0) { while (((int)huffsize[p]) == si) { huffcode[p++] = code; code++; } /* code is now 1 more than the last code used for codelength si; but * it must still fit in si bits, since no code is allowed to be all ones. */ if (code >= (1 << si)) throw new Exception("Bogus Huffman table definition"); code <<= 1; si++; } /* Figure C.3: generate encoding tables */ /* These are code and size indexed by symbol value */ /* Set all codeless symbols to have code length 0; * this lets us detect duplicate VAL entries here, and later * allows emit_bits to detect any attempt to emit such symbols. */ Array.Clear(dtbl.ehufsi, 0, dtbl.ehufsi.Length); /* This is also a convenient place to check for out-of-range * and duplicated VAL entries. We allow 0..255 for AC symbols * but only 0..15 for DC. (We could constrain them further * based on data depth and mode, but this seems enough.) */ int maxsymbol = isDC ? 15 : 255; for (p = 0; p < lastp; p++) { int i = htbl.Huffval[p]; if (i < 0 || i > maxsymbol || dtbl.ehufsi[i] != 0) throw new Exception("Bogus Huffman table definition"); dtbl.ehufco[i] = huffcode[p]; dtbl.ehufsi[i] = huffsize[p]; } } /// /// Generate the best Huffman code table for the given counts, fill htbl. /// /// The JPEG standard requires that no symbol be assigned a codeword of all /// one bits (so that padding bits added at the end of a compressed segment /// can't look like a valid code). Because of the canonical ordering of /// codewords, this just means that there must be an unused slot in the /// longest codeword length category. Section K.2 of the JPEG spec suggests /// reserving such a slot by pretending that symbol 256 is a valid symbol /// with count 1. In theory that's not optimal; giving it count zero but /// including it in the symbol set anyway should give a better Huffman code. /// But the theoretically better code actually seems to come out worse in /// practice, because it produces more all-ones bytes (which incur stuffed /// zero bytes in the final file). In any case the difference is tiny. /// /// The JPEG standard requires Huffman codes to be no more than 16 bits long. /// If some symbols have a very small but nonzero probability, the Huffman tree /// must be adjusted to meet the code length restriction. We currently use /// the adjustment method suggested in JPEG section K.2. This method is *not* /// optimal; it may not choose the best possible limited-length code. But /// typically only very-low-frequency symbols will be given less-than-optimal /// lengths, so the code is almost optimal. Experimental comparisons against /// an optimal limited-length-code algorithm indicate that the difference is /// microscopic --- usually less than a hundredth of a percent of total size. /// So the extra complexity of an optimal algorithm doesn't seem worthwhile. /// protected void jpeg_gen_optimal_table(JpegHuffmanTable htbl, long[] freq) { byte[] bits = new byte[MAX_CLEN + 1]; /* bits[k] = # of symbols with code length k */ int[] codesize = new int[257]; /* codesize[k] = code length of symbol k */ int[] others = new int[257]; /* next symbol in current branch of tree */ int c1, c2; int p, i, j; long v; /* This algorithm is explained in section K.2 of the JPEG standard */ for (i = 0; i < 257; i++) others[i] = -1; /* init links to empty */ freq[256] = 1; /* make sure 256 has a nonzero count */ /* Including the pseudo-symbol 256 in the Huffman procedure guarantees * that no real symbol is given code-value of all ones, because 256 * will be placed last in the largest codeword category. */ /* Huffman's basic algorithm to assign optimal code lengths to symbols */ for (; ; ) { /* Find the smallest nonzero frequency, set c1 = its symbol */ /* In case of ties, take the larger symbol number */ c1 = -1; v = 1000000000L; for (i = 0; i <= 256; i++) { if (freq[i] != 0 && freq[i] <= v) { v = freq[i]; c1 = i; } } /* Find the next smallest nonzero frequency, set c2 = its symbol */ /* In case of ties, take the larger symbol number */ c2 = -1; v = 1000000000L; for (i = 0; i <= 256; i++) { if (freq[i] != 0 && freq[i] <= v && i != c1) { v = freq[i]; c2 = i; } } /* Done if we've merged everything into one frequency */ if (c2 < 0) break; /* Else merge the two counts/trees */ freq[c1] += freq[c2]; freq[c2] = 0; /* Increment the codesize of everything in c1's tree branch */ codesize[c1]++; while (others[c1] >= 0) { c1 = others[c1]; codesize[c1]++; } others[c1] = c2; /* chain c2 onto c1's tree branch */ /* Increment the codesize of everything in c2's tree branch */ codesize[c2]++; while (others[c2] >= 0) { c2 = others[c2]; codesize[c2]++; } } /* Now count the number of symbols of each code length */ for (i = 0; i <= 256; i++) { if (codesize[i] != 0) { /* The JPEG standard seems to think that this can't happen, */ /* but I'm paranoid... */ if (codesize[i] > MAX_CLEN) throw new Exception("Huffman code size table overflow"); bits[codesize[i]]++; } } /* JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure * Huffman procedure assigned any such lengths, we must adjust the coding. * Here is what the JPEG spec says about how this next bit works: * Since symbols are paired for the longest Huffman code, the symbols are * removed from this length category two at a time. The prefix for the pair * (which is one bit shorter) is allocated to one of the pair; then, * skipping the BITS entry for that prefix length, a code word from the next * shortest nonzero BITS entry is converted into a prefix for two code words * one bit longer. */ for (i = MAX_CLEN; i > 16; i--) { while (bits[i] > 0) { j = i - 2; /* find length of new prefix to be used */ while (bits[j] == 0) j--; bits[i] -= 2; /* remove two symbols */ bits[i - 1]++; /* one goes in this length */ bits[j + 1] += 2; /* two new symbols in this length */ bits[j]--; /* symbol of this length is now a prefix */ } } /* Remove the count for the pseudo-symbol 256 from the largest codelength */ while (bits[i] == 0) /* find largest codelength still in use */ i--; bits[i]--; /* Return final symbol counts (only for lengths 0..16) */ Buffer.BlockCopy(bits, 0, htbl.Bits, 0, htbl.Bits.Length); /* Return a list of the symbols sorted by code length */ /* It's not real clear to me why we don't need to consider the codelength * changes made above, but the JPEG spec seems to think this works. */ p = 0; for (i = 1; i <= MAX_CLEN; i++) { for (j = 0; j <= 255; j++) { if (codesize[j] == i) { htbl.Huffval[p] = (byte)j; p++; } } } /* Set sent_table false so updated table will be written to JPEG file. */ htbl.Sent_table = false; } } #endregion #region JpegFowardDCT /// /// Forward DCT (also controls coefficient quantization) /// /// A forward DCT routine is given a pointer to a work area of type DCTELEM[]; /// the DCT is to be performed in-place in that buffer. Type DCTELEM is int /// for 8-bit samples, int for 12-bit samples. (NOTE: Floating-point DCT /// implementations use an array of type float, instead.) /// The DCT inputs are expected to be signed (range +-MediumSampleValue). /// The DCT outputs are returned scaled up by a factor of 8; they therefore /// have a range of +-8K for 8-bit data, +-128K for 12-bit data. This /// convention improves accuracy in integer implementations and saves some /// work in floating-point ones. /// /// Each IDCT routine has its own ideas about the best dct_table element type. /// class JpegFowardDCT { private const int FAST_INTEGER_CONST_BITS = 8; /* We use the following pre-calculated constants. * If you change FAST_INTEGER_CONST_BITS you may want to add appropriate values. * * Convert a positive real constant to an integer scaled by CONST_SCALE. * static int FAST_INTEGER_FIX(double x) *{ * return ((int) ((x) * (((int) 1) << FAST_INTEGER_CONST_BITS) + 0.5)); *} */ private const int FAST_INTEGER_FIX_0_382683433 = 98; /* FIX(0.382683433) */ private const int FAST_INTEGER_FIX_0_541196100 = 139; /* FIX(0.541196100) */ private const int FAST_INTEGER_FIX_0_707106781 = 181; /* FIX(0.707106781) */ private const int FAST_INTEGER_FIX_1_306562965 = 334; /* FIX(1.306562965) */ private const int SLOW_INTEGER_CONST_BITS = 13; private const int SLOW_INTEGER_PASS1_BITS = 2; /* We use the following pre-calculated constants. * If you change SLOW_INTEGER_CONST_BITS you may want to add appropriate values. * * Convert a positive real constant to an integer scaled by CONST_SCALE. * * static int SLOW_INTEGER_FIX(double x) * { * return ((int) ((x) * (((int) 1) << SLOW_INTEGER_CONST_BITS) + 0.5)); * } */ private const int SLOW_INTEGER_FIX_0_298631336 = 2446; /* FIX(0.298631336) */ private const int SLOW_INTEGER_FIX_0_390180644 = 3196; /* FIX(0.390180644) */ private const int SLOW_INTEGER_FIX_0_541196100 = 4433; /* FIX(0.541196100) */ private const int SLOW_INTEGER_FIX_0_765366865 = 6270; /* FIX(0.765366865) */ private const int SLOW_INTEGER_FIX_0_899976223 = 7373; /* FIX(0.899976223) */ private const int SLOW_INTEGER_FIX_1_175875602 = 9633; /* FIX(1.175875602) */ private const int SLOW_INTEGER_FIX_1_501321110 = 12299; /* FIX(1.501321110) */ private const int SLOW_INTEGER_FIX_1_847759065 = 15137; /* FIX(1.847759065) */ private const int SLOW_INTEGER_FIX_1_961570560 = 16069; /* FIX(1.961570560) */ private const int SLOW_INTEGER_FIX_2_053119869 = 16819; /* FIX(2.053119869) */ private const int SLOW_INTEGER_FIX_2_562915447 = 20995; /* FIX(2.562915447) */ private const int SLOW_INTEGER_FIX_3_072711026 = 25172; /* FIX(3.072711026) */ /* For AA&N IDCT method, divisors are equal to quantization * coefficients scaled by scalefactor[row]*scalefactor[col], where * scalefactor[0] = 1 * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 * We apply a further scale factor of 8. */ private const int CONST_BITS = 14; /* precomputed values scaled up by 14 bits */ private static short[] aanscales = { 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 }; /* For float AA&N IDCT method, divisors are equal to quantization * coefficients scaled by scalefactor[row]*scalefactor[col], where * scalefactor[0] = 1 * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 * We apply a further scale factor of 8. * What's actually stored is 1/divisor so that the inner loop can * use a multiplication rather than a division. */ private static double[] aanscalefactor = { 1.0, 1.387039845, 1.306562965, 1.175875602, 1.0, 0.785694958, 0.541196100, 0.275899379 }; private JpegCompressor m_cinfo; private bool m_useSlowMethod; private bool m_useFloatMethod; /* The actual post-DCT divisors --- not identical to the quant table * entries, because of scaling (especially for an unnormalized DCT). * Each table is given in normal array order. */ private int[][] m_divisors = new int[JpegConstants.NumberOfQuantTables][]; /* Same as above for the floating-point case. */ private float[][] m_float_divisors = new float[JpegConstants.NumberOfQuantTables][]; public JpegFowardDCT(JpegCompressor cinfo) { m_cinfo = cinfo; switch (cinfo.m_dct_method) { case DCTMethod.IntSlow: m_useFloatMethod = false; m_useSlowMethod = true; break; case DCTMethod.IntFast: m_useFloatMethod = false; m_useSlowMethod = false; break; case DCTMethod.Float: m_useFloatMethod = true; break; default: throw new Exception("Unknown dct method!"); } /* Mark divisor tables unallocated */ for (int i = 0; i < JpegConstants.NumberOfQuantTables; i++) { m_divisors[i] = null; m_float_divisors[i] = null; } } /// /// Initialize for a processing pass. /// Verify that all referenced Q-tables are present, and set up /// the divisor table for each one. /// In the current implementation, DCT of all components is done during /// the first pass, even if only some components will be output in the /// first scan. Hence all components should be examined here. /// public virtual void start_pass() { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { int qtblno = m_cinfo.Component_info[ci].Quant_tbl_no; /* Make sure specified quantization table is present */ if (qtblno < 0 || qtblno >= JpegConstants.NumberOfQuantTables || m_cinfo.m_quant_tbl_ptrs[qtblno] == null) throw new Exception(String.Format("Quantization table 0x{0:X2} was not defined", qtblno)); JpegQuantizationTable qtbl = m_cinfo.m_quant_tbl_ptrs[qtblno]; /* Compute divisors for this quant table */ /* We may do this more than once for same table, but it's not a big deal */ int i = 0; switch (m_cinfo.m_dct_method) { case DCTMethod.IntSlow: /* For LL&M IDCT method, divisors are equal to raw quantization * coefficients multiplied by 8 (to counteract scaling). */ if (m_divisors[qtblno] == null) m_divisors[qtblno] = new int[JpegConstants.DCTSize2]; for (i = 0; i < JpegConstants.DCTSize2; i++) m_divisors[qtblno][i] = ((int)qtbl.quantval[i]) << 3; break; case DCTMethod.IntFast: if (m_divisors[qtblno] == null) m_divisors[qtblno] = new int[JpegConstants.DCTSize2]; for (i = 0; i < JpegConstants.DCTSize2; i++) m_divisors[qtblno][i] = JpegUtils.DESCALE((int)qtbl.quantval[i] * (int)aanscales[i], CONST_BITS - 3); break; case DCTMethod.Float: if (m_float_divisors[qtblno] == null) m_float_divisors[qtblno] = new float[JpegConstants.DCTSize2]; float[] fdtbl = m_float_divisors[qtblno]; i = 0; for (int row = 0; row < JpegConstants.DCTSize; row++) { for (int col = 0; col < JpegConstants.DCTSize; col++) { fdtbl[i] = (float)(1.0 / (((double)qtbl.quantval[i] * aanscalefactor[row] * aanscalefactor[col] * 8.0))); i++; } } break; default: throw new Exception("Unknown dct method!"); } } } /// /// Perform forward DCT on one or more blocks of a component. /// /// The input samples are taken from the sample_data[] array starting at /// position start_row/start_col, and moving to the right for any additional /// blocks. The quantized coefficients are returned in coef_blocks[]. /// public virtual void forward_DCT(int quant_tbl_no, byte[][] sample_data, JpegBlock[] coef_blocks, int start_row, int start_col, int num_blocks) { if (m_useFloatMethod) forwardDCTFloatImpl(quant_tbl_no, sample_data, coef_blocks, start_row, start_col, num_blocks); else forwardDCTImpl(quant_tbl_no, sample_data, coef_blocks, start_row, start_col, num_blocks); } // This version is used for integer DCT implementations. private void forwardDCTImpl(int quant_tbl_no, byte[][] sample_data, JpegBlock[] coef_blocks, int start_row, int start_col, int num_blocks) { /* This routine is heavily used, so it's worth coding it tightly. */ int[] workspace = new int[JpegConstants.DCTSize2]; /* work area for FDCT subroutine */ for (int bi = 0; bi < num_blocks; bi++, start_col += JpegConstants.DCTSize) { /* Load data into workspace, applying unsigned->signed conversion */ int workspaceIndex = 0; for (int elemr = 0; elemr < JpegConstants.DCTSize; elemr++) { for (int column = 0; column < JpegConstants.DCTSize; column++) { workspace[workspaceIndex] = (int)sample_data[start_row + elemr][start_col + column] - JpegConstants.MediumSampleValue; workspaceIndex++; } } /* Perform the DCT */ if (m_useSlowMethod) jpeg_fdct_islow(workspace); else jpeg_fdct_ifast(workspace); /* Quantize/descale the coefficients, and store into coef_blocks[] */ for (int i = 0; i < JpegConstants.DCTSize2; i++) { int qval = m_divisors[quant_tbl_no][i]; int temp = workspace[i]; if (temp < 0) { temp = -temp; temp += qval >> 1; /* for rounding */ if (temp >= qval) temp /= qval; else temp = 0; temp = -temp; } else { temp += qval >> 1; /* for rounding */ if (temp >= qval) temp /= qval; else temp = 0; } coef_blocks[bi][i] = (short)temp; } } } // This version is used for floating-point DCT implementations. private void forwardDCTFloatImpl(int quant_tbl_no, byte[][] sample_data, JpegBlock[] coef_blocks, int start_row, int start_col, int num_blocks) { /* This routine is heavily used, so it's worth coding it tightly. */ float[] workspace = new float[JpegConstants.DCTSize2]; /* work area for FDCT subroutine */ for (int bi = 0; bi < num_blocks; bi++, start_col += JpegConstants.DCTSize) { /* Load data into workspace, applying unsigned->signed conversion */ int workspaceIndex = 0; for (int elemr = 0; elemr < JpegConstants.DCTSize; elemr++) { for (int column = 0; column < JpegConstants.DCTSize; column++) { workspace[workspaceIndex] = (float)((int)sample_data[start_row + elemr][start_col + column] - JpegConstants.MediumSampleValue); workspaceIndex++; } } /* Perform the DCT */ jpeg_fdct_float(workspace); /* Quantize/descale the coefficients, and store into coef_blocks[] */ for (int i = 0; i < JpegConstants.DCTSize2; i++) { /* Apply the quantization and scaling factor */ float temp = workspace[i] * m_float_divisors[quant_tbl_no][i]; /* Round to nearest integer. * Since C does not specify the direction of rounding for negative * quotients, we have to force the dividend positive for portability. * The maximum coefficient size is +-16K (for 12-bit data), so this * code should work for either 16-bit or 32-bit ints. */ coef_blocks[bi][i] = (short)((int)(temp + (float)16384.5) - 16384); } } } /// /// Perform the forward DCT on one block of samples. /// NOTE: this code only copes with 8x8 DCTs. /// /// A floating-point implementation of the /// forward DCT (Discrete Cosine Transform). /// /// This implementation should be more accurate than either of the integer /// DCT implementations. However, it may not give the same results on all /// machines because of differences in roundoff behavior. Speed will depend /// on the hardware's floating point capacity. /// /// A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT /// on each column. Direct algorithms are also available, but they are /// much more complex and seem not to be any faster when reduced to code. /// /// This implementation is based on Arai, Agui, and Nakajima's algorithm for /// scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in /// Japanese, but the algorithm is described in the Pennebaker & Mitchell /// JPEG textbook (see REFERENCES section in file README). The following code /// is based directly on figure 4-8 in P&M. /// While an 8-point DCT cannot be done in less than 11 multiplies, it is /// possible to arrange the computation so that many of the multiplies are /// simple scalings of the final outputs. These multiplies can then be /// folded into the multiplications or divisions by the JPEG quantization /// table entries. The AA&N method leaves only 5 multiplies and 29 adds /// to be done in the DCT itself. /// The primary disadvantage of this method is that with a fixed-point /// implementation, accuracy is lost due to imprecise representation of the /// scaled quantization values. However, that problem does not arise if /// we use floating point arithmetic. /// private static void jpeg_fdct_float(float[] data) { /* Pass 1: process rows. */ int dataIndex = 0; for (int ctr = JpegConstants.DCTSize - 1; ctr >= 0; ctr--) { float tmp0 = data[dataIndex + 0] + data[dataIndex + 7]; float tmp7 = data[dataIndex + 0] - data[dataIndex + 7]; float tmp1 = data[dataIndex + 1] + data[dataIndex + 6]; float tmp6 = data[dataIndex + 1] - data[dataIndex + 6]; float tmp2 = data[dataIndex + 2] + data[dataIndex + 5]; float tmp5 = data[dataIndex + 2] - data[dataIndex + 5]; float tmp3 = data[dataIndex + 3] + data[dataIndex + 4]; float tmp4 = data[dataIndex + 3] - data[dataIndex + 4]; /* Even part */ float tmp10 = tmp0 + tmp3; /* phase 2 */ float tmp13 = tmp0 - tmp3; float tmp11 = tmp1 + tmp2; float tmp12 = tmp1 - tmp2; data[dataIndex + 0] = tmp10 + tmp11; /* phase 3 */ data[dataIndex + 4] = tmp10 - tmp11; float z1 = (tmp12 + tmp13) * ((float)0.707106781); /* c4 */ data[dataIndex + 2] = tmp13 + z1; /* phase 5 */ data[dataIndex + 6] = tmp13 - z1; /* Odd part */ tmp10 = tmp4 + tmp5; /* phase 2 */ tmp11 = tmp5 + tmp6; tmp12 = tmp6 + tmp7; /* The rotator is modified from fig 4-8 to avoid extra negations. */ float z5 = (tmp10 - tmp12) * ((float)0.382683433); /* c6 */ float z2 = ((float)0.541196100) * tmp10 + z5; /* c2-c6 */ float z4 = ((float)1.306562965) * tmp12 + z5; /* c2+c6 */ float z3 = tmp11 * ((float)0.707106781); /* c4 */ float z11 = tmp7 + z3; /* phase 5 */ float z13 = tmp7 - z3; data[dataIndex + 5] = z13 + z2; /* phase 6 */ data[dataIndex + 3] = z13 - z2; data[dataIndex + 1] = z11 + z4; data[dataIndex + 7] = z11 - z4; dataIndex += JpegConstants.DCTSize; /* advance pointer to next row */ } /* Pass 2: process columns. */ dataIndex = 0; for (int ctr = JpegConstants.DCTSize - 1; ctr >= 0; ctr--) { float tmp0 = data[dataIndex + JpegConstants.DCTSize * 0] + data[dataIndex + JpegConstants.DCTSize * 7]; float tmp7 = data[dataIndex + JpegConstants.DCTSize * 0] - data[dataIndex + JpegConstants.DCTSize * 7]; float tmp1 = data[dataIndex + JpegConstants.DCTSize * 1] + data[dataIndex + JpegConstants.DCTSize * 6]; float tmp6 = data[dataIndex + JpegConstants.DCTSize * 1] - data[dataIndex + JpegConstants.DCTSize * 6]; float tmp2 = data[dataIndex + JpegConstants.DCTSize * 2] + data[dataIndex + JpegConstants.DCTSize * 5]; float tmp5 = data[dataIndex + JpegConstants.DCTSize * 2] - data[dataIndex + JpegConstants.DCTSize * 5]; float tmp3 = data[dataIndex + JpegConstants.DCTSize * 3] + data[dataIndex + JpegConstants.DCTSize * 4]; float tmp4 = data[dataIndex + JpegConstants.DCTSize * 3] - data[dataIndex + JpegConstants.DCTSize * 4]; /* Even part */ float tmp10 = tmp0 + tmp3; /* phase 2 */ float tmp13 = tmp0 - tmp3; float tmp11 = tmp1 + tmp2; float tmp12 = tmp1 - tmp2; data[dataIndex + JpegConstants.DCTSize * 0] = tmp10 + tmp11; /* phase 3 */ data[dataIndex + JpegConstants.DCTSize * 4] = tmp10 - tmp11; float z1 = (tmp12 + tmp13) * ((float)0.707106781); /* c4 */ data[dataIndex + JpegConstants.DCTSize * 2] = tmp13 + z1; /* phase 5 */ data[dataIndex + JpegConstants.DCTSize * 6] = tmp13 - z1; /* Odd part */ tmp10 = tmp4 + tmp5; /* phase 2 */ tmp11 = tmp5 + tmp6; tmp12 = tmp6 + tmp7; /* The rotator is modified from fig 4-8 to avoid extra negations. */ float z5 = (tmp10 - tmp12) * ((float)0.382683433); /* c6 */ float z2 = ((float)0.541196100) * tmp10 + z5; /* c2-c6 */ float z4 = ((float)1.306562965) * tmp12 + z5; /* c2+c6 */ float z3 = tmp11 * ((float)0.707106781); /* c4 */ float z11 = tmp7 + z3; /* phase 5 */ float z13 = tmp7 - z3; data[dataIndex + JpegConstants.DCTSize * 5] = z13 + z2; /* phase 6 */ data[dataIndex + JpegConstants.DCTSize * 3] = z13 - z2; data[dataIndex + JpegConstants.DCTSize * 1] = z11 + z4; data[dataIndex + JpegConstants.DCTSize * 7] = z11 - z4; dataIndex++; /* advance pointer to next column */ } } /// /// Perform the forward DCT on one block of samples. /// NOTE: this code only copes with 8x8 DCTs. /// This file contains a fast, not so accurate integer implementation of the /// forward DCT (Discrete Cosine Transform). /// /// A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT /// on each column. Direct algorithms are also available, but they are /// much more complex and seem not to be any faster when reduced to code. /// /// This implementation is based on Arai, Agui, and Nakajima's algorithm for /// scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in /// Japanese, but the algorithm is described in the Pennebaker & Mitchell /// JPEG textbook (see REFERENCES section in file README). The following code /// is based directly on figure 4-8 in P&M. /// While an 8-point DCT cannot be done in less than 11 multiplies, it is /// possible to arrange the computation so that many of the multiplies are /// simple scalings of the final outputs. These multiplies can then be /// folded into the multiplications or divisions by the JPEG quantization /// table entries. The AA&N method leaves only 5 multiplies and 29 adds /// to be done in the DCT itself. /// The primary disadvantage of this method is that with fixed-point math, /// accuracy is lost due to imprecise representation of the scaled /// quantization values. The smaller the quantization table entry, the less /// precise the scaled value, so this implementation does worse with high- /// quality-setting files than with low-quality ones. /// /// Scaling decisions are generally the same as in the LL&M algorithm; /// see jpeg_fdct_islow for more details. However, we choose to descale /// (right shift) multiplication products as soon as they are formed, /// rather than carrying additional fractional bits into subsequent additions. /// This compromises accuracy slightly, but it lets us save a few shifts. /// More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) /// everywhere except in the multiplications proper; this saves a good deal /// of work on 16-bit-int machines. /// /// Again to save a few shifts, the intermediate results between pass 1 and /// pass 2 are not upscaled, but are represented only to integral precision. /// /// A final compromise is to represent the multiplicative constants to only /// 8 fractional bits, rather than 13. This saves some shifting work on some /// machines, and may also reduce the cost of multiplication (since there /// are fewer one-bits in the constants). /// private static void jpeg_fdct_ifast(int[] data) { /* Pass 1: process rows. */ int dataIndex = 0; for (int ctr = JpegConstants.DCTSize - 1; ctr >= 0; ctr--) { int tmp0 = data[dataIndex + 0] + data[dataIndex + 7]; int tmp7 = data[dataIndex + 0] - data[dataIndex + 7]; int tmp1 = data[dataIndex + 1] + data[dataIndex + 6]; int tmp6 = data[dataIndex + 1] - data[dataIndex + 6]; int tmp2 = data[dataIndex + 2] + data[dataIndex + 5]; int tmp5 = data[dataIndex + 2] - data[dataIndex + 5]; int tmp3 = data[dataIndex + 3] + data[dataIndex + 4]; int tmp4 = data[dataIndex + 3] - data[dataIndex + 4]; /* Even part */ int tmp10 = tmp0 + tmp3; /* phase 2 */ int tmp13 = tmp0 - tmp3; int tmp11 = tmp1 + tmp2; int tmp12 = tmp1 - tmp2; data[dataIndex + 0] = tmp10 + tmp11; /* phase 3 */ data[dataIndex + 4] = tmp10 - tmp11; int z1 = FAST_INTEGER_MULTIPLY(tmp12 + tmp13, FAST_INTEGER_FIX_0_707106781); /* c4 */ data[dataIndex + 2] = tmp13 + z1; /* phase 5 */ data[dataIndex + 6] = tmp13 - z1; /* Odd part */ tmp10 = tmp4 + tmp5; /* phase 2 */ tmp11 = tmp5 + tmp6; tmp12 = tmp6 + tmp7; /* The rotator is modified from fig 4-8 to avoid extra negations. */ int z5 = FAST_INTEGER_MULTIPLY(tmp10 - tmp12, FAST_INTEGER_FIX_0_382683433); /* c6 */ int z2 = FAST_INTEGER_MULTIPLY(tmp10, FAST_INTEGER_FIX_0_541196100) + z5; /* c2-c6 */ int z4 = FAST_INTEGER_MULTIPLY(tmp12, FAST_INTEGER_FIX_1_306562965) + z5; /* c2+c6 */ int z3 = FAST_INTEGER_MULTIPLY(tmp11, FAST_INTEGER_FIX_0_707106781); /* c4 */ int z11 = tmp7 + z3; /* phase 5 */ int z13 = tmp7 - z3; data[dataIndex + 5] = z13 + z2; /* phase 6 */ data[dataIndex + 3] = z13 - z2; data[dataIndex + 1] = z11 + z4; data[dataIndex + 7] = z11 - z4; dataIndex += JpegConstants.DCTSize; /* advance pointer to next row */ } /* Pass 2: process columns. */ dataIndex = 0; for (int ctr = JpegConstants.DCTSize - 1; ctr >= 0; ctr--) { int tmp0 = data[dataIndex + JpegConstants.DCTSize * 0] + data[dataIndex + JpegConstants.DCTSize * 7]; int tmp7 = data[dataIndex + JpegConstants.DCTSize * 0] - data[dataIndex + JpegConstants.DCTSize * 7]; int tmp1 = data[dataIndex + JpegConstants.DCTSize * 1] + data[dataIndex + JpegConstants.DCTSize * 6]; int tmp6 = data[dataIndex + JpegConstants.DCTSize * 1] - data[dataIndex + JpegConstants.DCTSize * 6]; int tmp2 = data[dataIndex + JpegConstants.DCTSize * 2] + data[dataIndex + JpegConstants.DCTSize * 5]; int tmp5 = data[dataIndex + JpegConstants.DCTSize * 2] - data[dataIndex + JpegConstants.DCTSize * 5]; int tmp3 = data[dataIndex + JpegConstants.DCTSize * 3] + data[dataIndex + JpegConstants.DCTSize * 4]; int tmp4 = data[dataIndex + JpegConstants.DCTSize * 3] - data[dataIndex + JpegConstants.DCTSize * 4]; /* Even part */ int tmp10 = tmp0 + tmp3; /* phase 2 */ int tmp13 = tmp0 - tmp3; int tmp11 = tmp1 + tmp2; int tmp12 = tmp1 - tmp2; data[dataIndex + JpegConstants.DCTSize * 0] = tmp10 + tmp11; /* phase 3 */ data[dataIndex + JpegConstants.DCTSize * 4] = tmp10 - tmp11; int z1 = FAST_INTEGER_MULTIPLY(tmp12 + tmp13, FAST_INTEGER_FIX_0_707106781); /* c4 */ data[dataIndex + JpegConstants.DCTSize * 2] = tmp13 + z1; /* phase 5 */ data[dataIndex + JpegConstants.DCTSize * 6] = tmp13 - z1; /* Odd part */ tmp10 = tmp4 + tmp5; /* phase 2 */ tmp11 = tmp5 + tmp6; tmp12 = tmp6 + tmp7; /* The rotator is modified from fig 4-8 to avoid extra negations. */ int z5 = FAST_INTEGER_MULTIPLY(tmp10 - tmp12, FAST_INTEGER_FIX_0_382683433); /* c6 */ int z2 = FAST_INTEGER_MULTIPLY(tmp10, FAST_INTEGER_FIX_0_541196100) + z5; /* c2-c6 */ int z4 = FAST_INTEGER_MULTIPLY(tmp12, FAST_INTEGER_FIX_1_306562965) + z5; /* c2+c6 */ int z3 = FAST_INTEGER_MULTIPLY(tmp11, FAST_INTEGER_FIX_0_707106781); /* c4 */ int z11 = tmp7 + z3; /* phase 5 */ int z13 = tmp7 - z3; data[dataIndex + JpegConstants.DCTSize * 5] = z13 + z2; /* phase 6 */ data[dataIndex + JpegConstants.DCTSize * 3] = z13 - z2; data[dataIndex + JpegConstants.DCTSize * 1] = z11 + z4; data[dataIndex + JpegConstants.DCTSize * 7] = z11 - z4; dataIndex++; /* advance pointer to next column */ } } /// /// Perform the forward DCT on one block of samples. /// NOTE: this code only copes with 8x8 DCTs. /// /// A slow-but-accurate integer implementation of the /// forward DCT (Discrete Cosine Transform). /// /// A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT /// on each column. Direct algorithms are also available, but they are /// much more complex and seem not to be any faster when reduced to code. /// /// This implementation is based on an algorithm described in /// C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT /// Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, /// Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. /// The primary algorithm described there uses 11 multiplies and 29 adds. /// We use their alternate method with 12 multiplies and 32 adds. /// The advantage of this method is that no data path contains more than one /// multiplication; this allows a very simple and accurate implementation in /// scaled fixed-point arithmetic, with a minimal number of shifts. /// /// The poop on this scaling stuff is as follows: /// /// Each 1-D DCT step produces outputs which are a factor of sqrt(N) /// larger than the true DCT outputs. The final outputs are therefore /// a factor of N larger than desired; since N=8 this can be cured by /// a simple right shift at the end of the algorithm. The advantage of /// this arrangement is that we save two multiplications per 1-D DCT, /// because the y0 and y4 outputs need not be divided by sqrt(N). /// In the IJG code, this factor of 8 is removed by the quantization /// step, NOT here. /// /// We have to do addition and subtraction of the integer inputs, which /// is no problem, and multiplication by fractional constants, which is /// a problem to do in integer arithmetic. We multiply all the constants /// by CONST_SCALE and convert them to integer constants (thus retaining /// SLOW_INTEGER_CONST_BITS bits of precision in the constants). After doing a /// multiplication we have to divide the product by CONST_SCALE, with proper /// rounding, to produce the correct output. This division can be done /// cheaply as a right shift of SLOW_INTEGER_CONST_BITS bits. We postpone shifting /// as long as possible so that partial sums can be added together with /// full fractional precision. /// /// The outputs of the first pass are scaled up by SLOW_INTEGER_PASS1_BITS bits so that /// they are represented to better-than-integral precision. These outputs /// require BitsInSample + SLOW_INTEGER_PASS1_BITS + 3 bits; this fits in a 16-bit word /// with the recommended scaling. (For 12-bit sample data, the intermediate /// array is int anyway.) /// /// To avoid overflow of the 32-bit intermediate results in pass 2, we must /// have BitsInSample + SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS <= 26. Error analysis /// shows that the values given below are the most effective. /// private static void jpeg_fdct_islow(int[] data) { /* Pass 1: process rows. */ /* Note results are scaled up by sqrt(8) compared to a true DCT; */ /* furthermore, we scale the results by 2**SLOW_INTEGER_PASS1_BITS. */ int dataIndex = 0; for (int ctr = JpegConstants.DCTSize - 1; ctr >= 0; ctr--) { int tmp0 = data[dataIndex + 0] + data[dataIndex + 7]; int tmp7 = data[dataIndex + 0] - data[dataIndex + 7]; int tmp1 = data[dataIndex + 1] + data[dataIndex + 6]; int tmp6 = data[dataIndex + 1] - data[dataIndex + 6]; int tmp2 = data[dataIndex + 2] + data[dataIndex + 5]; int tmp5 = data[dataIndex + 2] - data[dataIndex + 5]; int tmp3 = data[dataIndex + 3] + data[dataIndex + 4]; int tmp4 = data[dataIndex + 3] - data[dataIndex + 4]; /* Even part per LL&M figure 1 --- note that published figure is faulty; * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". */ int tmp10 = tmp0 + tmp3; int tmp13 = tmp0 - tmp3; int tmp11 = tmp1 + tmp2; int tmp12 = tmp1 - tmp2; data[dataIndex + 0] = (tmp10 + tmp11) << SLOW_INTEGER_PASS1_BITS; data[dataIndex + 4] = (tmp10 - tmp11) << SLOW_INTEGER_PASS1_BITS; int z1 = (tmp12 + tmp13) * SLOW_INTEGER_FIX_0_541196100; data[dataIndex + 2] = JpegUtils.DESCALE(z1 + tmp13 * SLOW_INTEGER_FIX_0_765366865, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); data[dataIndex + 6] = JpegUtils.DESCALE(z1 + tmp12 * (-SLOW_INTEGER_FIX_1_847759065), SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); /* Odd part per figure 8 --- note paper omits factor of sqrt(2). * cK represents cos(K*pi/16). * i0..i3 in the paper are tmp4..tmp7 here. */ z1 = tmp4 + tmp7; int z2 = tmp5 + tmp6; int z3 = tmp4 + tmp6; int z4 = tmp5 + tmp7; int z5 = (z3 + z4) * SLOW_INTEGER_FIX_1_175875602; /* sqrt(2) * c3 */ tmp4 = tmp4 * SLOW_INTEGER_FIX_0_298631336; /* sqrt(2) * (-c1+c3+c5-c7) */ tmp5 = tmp5 * SLOW_INTEGER_FIX_2_053119869; /* sqrt(2) * ( c1+c3-c5+c7) */ tmp6 = tmp6 * SLOW_INTEGER_FIX_3_072711026; /* sqrt(2) * ( c1+c3+c5-c7) */ tmp7 = tmp7 * SLOW_INTEGER_FIX_1_501321110; /* sqrt(2) * ( c1+c3-c5-c7) */ z1 = z1 * (-SLOW_INTEGER_FIX_0_899976223); /* sqrt(2) * (c7-c3) */ z2 = z2 * (-SLOW_INTEGER_FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ z3 = z3 * (-SLOW_INTEGER_FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ z4 = z4 * (-SLOW_INTEGER_FIX_0_390180644); /* sqrt(2) * (c5-c3) */ z3 += z5; z4 += z5; data[dataIndex + 7] = JpegUtils.DESCALE(tmp4 + z1 + z3, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); data[dataIndex + 5] = JpegUtils.DESCALE(tmp5 + z2 + z4, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); data[dataIndex + 3] = JpegUtils.DESCALE(tmp6 + z2 + z3, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); data[dataIndex + 1] = JpegUtils.DESCALE(tmp7 + z1 + z4, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); dataIndex += JpegConstants.DCTSize; /* advance pointer to next row */ } /* Pass 2: process columns. * We remove the SLOW_INTEGER_PASS1_BITS scaling, but leave the results scaled up * by an overall factor of 8. */ dataIndex = 0; for (int ctr = JpegConstants.DCTSize - 1; ctr >= 0; ctr--) { int tmp0 = data[dataIndex + JpegConstants.DCTSize * 0] + data[dataIndex + JpegConstants.DCTSize * 7]; int tmp7 = data[dataIndex + JpegConstants.DCTSize * 0] - data[dataIndex + JpegConstants.DCTSize * 7]; int tmp1 = data[dataIndex + JpegConstants.DCTSize * 1] + data[dataIndex + JpegConstants.DCTSize * 6]; int tmp6 = data[dataIndex + JpegConstants.DCTSize * 1] - data[dataIndex + JpegConstants.DCTSize * 6]; int tmp2 = data[dataIndex + JpegConstants.DCTSize * 2] + data[dataIndex + JpegConstants.DCTSize * 5]; int tmp5 = data[dataIndex + JpegConstants.DCTSize * 2] - data[dataIndex + JpegConstants.DCTSize * 5]; int tmp3 = data[dataIndex + JpegConstants.DCTSize * 3] + data[dataIndex + JpegConstants.DCTSize * 4]; int tmp4 = data[dataIndex + JpegConstants.DCTSize * 3] - data[dataIndex + JpegConstants.DCTSize * 4]; /* Even part per LL&M figure 1 --- note that published figure is faulty; * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". */ int tmp10 = tmp0 + tmp3; int tmp13 = tmp0 - tmp3; int tmp11 = tmp1 + tmp2; int tmp12 = tmp1 - tmp2; data[dataIndex + JpegConstants.DCTSize * 0] = JpegUtils.DESCALE(tmp10 + tmp11, SLOW_INTEGER_PASS1_BITS); data[dataIndex + JpegConstants.DCTSize * 4] = JpegUtils.DESCALE(tmp10 - tmp11, SLOW_INTEGER_PASS1_BITS); int z1 = (tmp12 + tmp13) * SLOW_INTEGER_FIX_0_541196100; data[dataIndex + JpegConstants.DCTSize * 2] = JpegUtils.DESCALE(z1 + tmp13 * SLOW_INTEGER_FIX_0_765366865, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS); data[dataIndex + JpegConstants.DCTSize * 6] = JpegUtils.DESCALE(z1 + tmp12 * (-SLOW_INTEGER_FIX_1_847759065), SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS); /* Odd part per figure 8 --- note paper omits factor of sqrt(2). * cK represents cos(K*pi/16). * i0..i3 in the paper are tmp4..tmp7 here. */ z1 = tmp4 + tmp7; int z2 = tmp5 + tmp6; int z3 = tmp4 + tmp6; int z4 = tmp5 + tmp7; int z5 = (z3 + z4) * SLOW_INTEGER_FIX_1_175875602; /* sqrt(2) * c3 */ tmp4 = tmp4 * SLOW_INTEGER_FIX_0_298631336; /* sqrt(2) * (-c1+c3+c5-c7) */ tmp5 = tmp5 * SLOW_INTEGER_FIX_2_053119869; /* sqrt(2) * ( c1+c3-c5+c7) */ tmp6 = tmp6 * SLOW_INTEGER_FIX_3_072711026; /* sqrt(2) * ( c1+c3+c5-c7) */ tmp7 = tmp7 * SLOW_INTEGER_FIX_1_501321110; /* sqrt(2) * ( c1+c3-c5-c7) */ z1 = z1 * (-SLOW_INTEGER_FIX_0_899976223); /* sqrt(2) * (c7-c3) */ z2 = z2 * (-SLOW_INTEGER_FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ z3 = z3 * (-SLOW_INTEGER_FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ z4 = z4 * (-SLOW_INTEGER_FIX_0_390180644); /* sqrt(2) * (c5-c3) */ z3 += z5; z4 += z5; data[dataIndex + JpegConstants.DCTSize * 7] = JpegUtils.DESCALE(tmp4 + z1 + z3, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS); data[dataIndex + JpegConstants.DCTSize * 5] = JpegUtils.DESCALE(tmp5 + z2 + z4, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS); data[dataIndex + JpegConstants.DCTSize * 3] = JpegUtils.DESCALE(tmp6 + z2 + z3, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS); data[dataIndex + JpegConstants.DCTSize * 1] = JpegUtils.DESCALE(tmp7 + z1 + z4, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS); dataIndex++; /* advance pointer to next column */ } } /// /// Multiply a DCTELEM variable by an int constant, and immediately /// descale to yield a DCTELEM result. /// private static int FAST_INTEGER_MULTIPLY(int var, int c) { return (JpegUtils.DESCALE((var) * (c), FAST_INTEGER_CONST_BITS)); } } #endregion #region JpegHuffmanTable /// /// Huffman coding table. /// public class JpegHuffmanTable { /* These two fields directly represent the contents of a JPEG DHT marker */ private readonly byte[] m_bits = new byte[17]; /* bits[k] = # of symbols with codes of */ /* length k bits; bits[0] is unused */ private readonly byte[] m_huffval = new byte[256]; /* The symbols, in order of incr code length */ private bool m_sent_table; /* true when table has been output */ internal JpegHuffmanTable() { } internal byte[] Bits { get { return m_bits; } } internal byte[] Huffval { get { return m_huffval; } } /// /// Gets or sets a value indicating whether the table has been output to file. /// /// It's initialized false when the table is created, and set /// true when it's been output to the file. You could suppress output /// of a table by setting this to true. /// /// This property is used only during compression. It's initialized /// false when the table is created, and set true when it's been /// output to the file. You could suppress output of a table by setting this to /// true. (See jpeg_suppress_tables for an example.) /// public bool Sent_table { get { return m_sent_table; } set { m_sent_table = value; } } } #endregion #region JpegInputController /// /// Input control module /// class JpegInputController { private JpegDecompressor m_cinfo; private bool m_consumeData; private bool m_inheaders; /* true until first SOS is reached */ private bool m_has_multiple_scans; /* True if file has multiple scans */ private bool m_eoi_reached; /* True when EOI has been consumed */ /// /// Initialize the input controller module. /// This is called only once, when the decompression object is created. /// public JpegInputController(JpegDecompressor cinfo) { m_cinfo = cinfo; /* Initialize state: can't use reset_input_controller since we don't * want to try to reset other modules yet. */ m_inheaders = true; } public ReadResult consume_input() { if (m_consumeData) return m_cinfo.m_coef.consume_data(); return consume_markers(); } /// /// Reset state to begin a fresh datastream. /// public void reset_input_controller() { m_consumeData = false; m_has_multiple_scans = false; /* "unknown" would be better */ m_eoi_reached = false; m_inheaders = true; /* Reset other modules */ m_cinfo.m_marker.reset_marker_reader(); /* Reset progression state -- would be cleaner if entropy decoder did this */ m_cinfo.m_coef_bits = null; } /// /// Initialize the input modules to read a scan of compressed data. /// The first call to this is done after initializing /// the entire decompressor (during jpeg_start_decompress). /// Subsequent calls come from consume_markers, below. /// public void start_input_pass() { per_scan_setup(); latch_quant_tables(); m_cinfo.m_entropy.start_pass(); m_cinfo.m_coef.start_input_pass(); m_consumeData = true; } /// /// Finish up after inputting a compressed-data scan. /// This is called by the coefficient controller after it's read all /// the expected data of the scan. /// public void finish_input_pass() { m_consumeData = false; } public bool HasMultipleScans() { return m_has_multiple_scans; } public bool EOIReached() { return m_eoi_reached; } /// /// Read JPEG markers before, between, or after compressed-data scans. /// Change state as necessary when a new scan is reached. /// Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. /// /// The consume_input method pointer points either here or to the /// coefficient controller's consume_data routine, depending on whether /// we are reading a compressed data segment or inter-segment markers. /// private ReadResult consume_markers() { ReadResult val; if (m_eoi_reached) /* After hitting EOI, read no further */ return ReadResult.Reached_EOI; val = m_cinfo.m_marker.read_markers(); switch (val) { case ReadResult.Reached_SOS: /* Found SOS */ if (m_inheaders) { /* 1st SOS */ initial_setup(); m_inheaders = false; /* Note: start_input_pass must be called by JpegDecompressorMaster * before any more input can be consumed. */ } else { /* 2nd or later SOS marker */ if (!m_has_multiple_scans) { /* Oops, I wasn't expecting this! */ throw new Exception("Didn't expect more than one scan"); } m_cinfo.m_inputctl.start_input_pass(); } break; case ReadResult.Reached_EOI: /* Found EOI */ m_eoi_reached = true; if (m_inheaders) { /* Tables-only data-stream, apparently */ if (m_cinfo.m_marker.SawSOF()) throw new Exception("Invalid JPEG file structure: missing SOS marker"); } else { /* Prevent infinite loop in coef ctlr's decompress_data routine * if user set output_scan_number larger than number of scans. */ if (m_cinfo.m_output_scan_number > m_cinfo.m_input_scan_number) m_cinfo.m_output_scan_number = m_cinfo.m_input_scan_number; } break; case ReadResult.Suspended: break; } return val; } /// /// Routines to calculate various quantities related to the size of the image. /// Called once, when first SOS marker is reached /// private void initial_setup() { /* Make sure image isn't bigger than I can handle */ if (m_cinfo.m_image_height > JpegConstants.JpegMaxDimention || m_cinfo.m_image_width > JpegConstants.JpegMaxDimention) { throw new Exception(String.Format("Maximum supported image dimension is {0} pixels", (int)JpegConstants.JpegMaxDimention)); } /* For now, precision must match compiled-in value... */ if (m_cinfo.m_data_precision != JpegConstants.BitsInSample) throw new Exception(String.Format("Unsupported JPEG data precision {0}", m_cinfo.m_data_precision)); /* Check that number of components won't exceed internal array sizes */ if (m_cinfo.m_num_components > JpegConstants.MaxComponents) throw new Exception(String.Format("Too many color components: {0}, max {1}", m_cinfo.m_num_components, JpegConstants.MaxComponents)); /* Compute maximum sampling factors; check factor validity */ m_cinfo.m_max_h_samp_factor = 1; m_cinfo.m_max_v_samp_factor = 1; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { if (m_cinfo.Comp_info[ci].H_samp_factor <= 0 || m_cinfo.Comp_info[ci].H_samp_factor > JpegConstants.MaxSamplingFactor || m_cinfo.Comp_info[ci].V_samp_factor <= 0 || m_cinfo.Comp_info[ci].V_samp_factor > JpegConstants.MaxSamplingFactor) { throw new Exception("Bogus sampling factors"); } m_cinfo.m_max_h_samp_factor = Math.Max(m_cinfo.m_max_h_samp_factor, m_cinfo.Comp_info[ci].H_samp_factor); m_cinfo.m_max_v_samp_factor = Math.Max(m_cinfo.m_max_v_samp_factor, m_cinfo.Comp_info[ci].V_samp_factor); } /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSize. * In the full decompressor, this will be overridden JpegDecompressorMaster; * but in the transcoder, JpegDecompressorMaster is not used, so we must do it here. */ m_cinfo.m_min_DCT_scaled_size = JpegConstants.DCTSize; /* Compute dimensions of components */ for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { m_cinfo.Comp_info[ci].DCT_scaled_size = JpegConstants.DCTSize; /* Size in DCT blocks */ m_cinfo.Comp_info[ci].Width_in_blocks = JpegUtils.jdiv_round_up( m_cinfo.m_image_width * m_cinfo.Comp_info[ci].H_samp_factor, m_cinfo.m_max_h_samp_factor * JpegConstants.DCTSize); m_cinfo.Comp_info[ci].height_in_blocks = JpegUtils.jdiv_round_up( m_cinfo.m_image_height * m_cinfo.Comp_info[ci].V_samp_factor, m_cinfo.m_max_v_samp_factor * JpegConstants.DCTSize); /* downsampled_width and downsampled_height will also be overridden by * JpegDecompressorMaster if we are doing full decompression. The transcoder library * doesn't use these values, but the calling application might. */ /* Size in samples */ m_cinfo.Comp_info[ci].downsampled_width = JpegUtils.jdiv_round_up( m_cinfo.m_image_width * m_cinfo.Comp_info[ci].H_samp_factor, m_cinfo.m_max_h_samp_factor); m_cinfo.Comp_info[ci].downsampled_height = JpegUtils.jdiv_round_up( m_cinfo.m_image_height * m_cinfo.Comp_info[ci].V_samp_factor, m_cinfo.m_max_v_samp_factor); /* Mark component needed, until color conversion says otherwise */ m_cinfo.Comp_info[ci].component_needed = true; /* Mark no quantization table yet saved for component */ m_cinfo.Comp_info[ci].quant_table = null; } /* Compute number of fully interleaved MCU rows. */ m_cinfo.m_total_iMCU_rows = JpegUtils.jdiv_round_up( m_cinfo.m_image_height, m_cinfo.m_max_v_samp_factor * JpegConstants.DCTSize); /* Decide whether file contains multiple scans */ if (m_cinfo.m_comps_in_scan < m_cinfo.m_num_components || m_cinfo.m_progressive_mode) m_cinfo.m_inputctl.m_has_multiple_scans = true; else m_cinfo.m_inputctl.m_has_multiple_scans = false; } /// /// Save away a copy of the Q-table referenced by each component present /// in the current scan, unless already saved during a prior scan. /// /// In a multiple-scan JPEG file, the encoder could assign different components /// the same Q-table slot number, but change table definitions between scans /// so that each component uses a different Q-table. (The IJG encoder is not /// currently capable of doing this, but other encoders might.) Since we want /// to be able to de-quantize all the components at the end of the file, this /// means that we have to save away the table actually used for each component. /// We do this by copying the table at the start of the first scan containing /// the component. /// The JPEG spec prohibits the encoder from changing the contents of a Q-table /// slot between scans of a component using that slot. If the encoder does so /// anyway, this decoder will simply use the Q-table values that were current /// at the start of the first scan for the component. /// /// The decompressor output side looks only at the saved quant tables, /// not at the current Q-table slots. /// private void latch_quant_tables() { for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]]; /* No work if we already saved Q-table for this component */ if (componentInfo.quant_table != null) continue; /* Make sure specified quantization table is present */ int qtblno = componentInfo.Quant_tbl_no; if (qtblno < 0 || qtblno >= JpegConstants.NumberOfQuantTables || m_cinfo.m_quant_tbl_ptrs[qtblno] == null) throw new Exception(String.Format("Quantization table 0x{0:X2} was not defined", qtblno)); /* OK, save away the quantization table */ JpegQuantizationTable qtbl = new JpegQuantizationTable(); Buffer.BlockCopy(m_cinfo.m_quant_tbl_ptrs[qtblno].quantval, 0, qtbl.quantval, 0, qtbl.quantval.Length * sizeof(short)); qtbl.Sent_table = m_cinfo.m_quant_tbl_ptrs[qtblno].Sent_table; componentInfo.quant_table = qtbl; m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]] = componentInfo; } } /// /// Do computations that are needed before processing a JPEG scan /// cinfo.comps_in_scan and cinfo.cur_comp_info[] were set from SOS marker /// private void per_scan_setup() { if (m_cinfo.m_comps_in_scan == 1) { /* Non-interleaved (single-component) scan */ JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[0]]; /* Overall image size in MCUs */ m_cinfo.m_MCUs_per_row = componentInfo.Width_in_blocks; m_cinfo.m_MCU_rows_in_scan = componentInfo.height_in_blocks; /* For non-interleaved scan, always one block per MCU */ componentInfo.MCU_width = 1; componentInfo.MCU_height = 1; componentInfo.MCU_blocks = 1; componentInfo.MCU_sample_width = componentInfo.DCT_scaled_size; componentInfo.last_col_width = 1; /* For non-interleaved scans, it is convenient to define last_row_height * as the number of block rows present in the last iMCU row. */ int tmp = componentInfo.height_in_blocks % componentInfo.V_samp_factor; if (tmp == 0) tmp = componentInfo.V_samp_factor; componentInfo.last_row_height = tmp; m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[0]] = componentInfo; /* Prepare array describing MCU composition */ m_cinfo.m_blocks_in_MCU = 1; m_cinfo.m_MCU_membership[0] = 0; } else { /* Interleaved (multi-component) scan */ if (m_cinfo.m_comps_in_scan <= 0 || m_cinfo.m_comps_in_scan > JpegConstants.MaxComponentsInScan) throw new Exception(String.Format("Too many color components: {0}, max {1}", m_cinfo.m_comps_in_scan, JpegConstants.MaxComponentsInScan)); /* Overall image size in MCUs */ m_cinfo.m_MCUs_per_row = JpegUtils.jdiv_round_up( m_cinfo.m_image_width, m_cinfo.m_max_h_samp_factor * JpegConstants.DCTSize); m_cinfo.m_MCU_rows_in_scan = JpegUtils.jdiv_round_up( m_cinfo.m_image_height, m_cinfo.m_max_v_samp_factor * JpegConstants.DCTSize); m_cinfo.m_blocks_in_MCU = 0; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]]; /* Sampling factors give # of blocks of component in each MCU */ componentInfo.MCU_width = componentInfo.H_samp_factor; componentInfo.MCU_height = componentInfo.V_samp_factor; componentInfo.MCU_blocks = componentInfo.MCU_width * componentInfo.MCU_height; componentInfo.MCU_sample_width = componentInfo.MCU_width * componentInfo.DCT_scaled_size; /* Figure number of non-dummy blocks in last MCU column & row */ int tmp = componentInfo.Width_in_blocks % componentInfo.MCU_width; if (tmp == 0) tmp = componentInfo.MCU_width; componentInfo.last_col_width = tmp; tmp = componentInfo.height_in_blocks % componentInfo.MCU_height; if (tmp == 0) tmp = componentInfo.MCU_height; componentInfo.last_row_height = tmp; /* Prepare array describing MCU composition */ int mcublks = componentInfo.MCU_blocks; if (m_cinfo.m_blocks_in_MCU + mcublks > JpegConstants.DecompressorMaxBlocksInMCU) throw new Exception("Sampling factors too large for interleaved scan"); m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]] = componentInfo; while (mcublks-- > 0) m_cinfo.m_MCU_membership[m_cinfo.m_blocks_in_MCU++] = ci; } } } } #endregion #region JpegInverseDCT /// /// An inverse DCT routine is given a pointer to the input JpegBlock and a pointer /// to an output sample array. The routine must dequantize the input data as /// well as perform the IDCT; for dequantization, it uses the multiplier table /// pointed to by componentInfo.dct_table. The output data is to be placed into the /// sample array starting at a specified column. (Any row offset needed will /// be applied to the array pointer before it is passed to the IDCT code) /// Note that the number of samples emitted by the IDCT routine is /// DCT_scaled_size * DCT_scaled_size. /// /// Each IDCT routine has its own ideas about the best dct_table element type. /// /// The decompressor input side saves away the appropriate /// quantization table for each component at the start of the first scan /// involving that component. (This is necessary in order to correctly /// decode files that reuse Q-table slots.) /// When we are ready to make an output pass, the saved Q-table is converted /// to a multiplier table that will actually be used by the IDCT routine. /// The multiplier table contents are IDCT-method-dependent. To support /// application changes in IDCT method between scans, we can remake the /// multiplier tables if necessary. /// In buffered-image mode, the first output pass may occur before any data /// has been seen for some components, and thus before their Q-tables have /// been saved away. To handle this case, multiplier tables are preset /// to zeroes; the result of the IDCT will be a neutral gray level. /// class JpegInverseDCT { private const int IFAST_SCALE_BITS = 2; /* fractional bits in scale factors */ /* * Each IDCT routine is responsible for range-limiting its results and * converting them to unsigned form (0..MaxSampleValue). The raw outputs could * be quite far out of range if the input data is corrupt, so a bulletproof * range-limiting step is required. We use a mask-and-table-lookup method * to do the combined operations quickly. See the comments with * prepare_range_limit_table (in jdmaster.c) for more info. */ private const int RANGE_MASK = (JpegConstants.MaxSampleValue * 4 + 3); /* 2 bits wider than legal samples */ private const int SLOW_INTEGER_CONST_BITS = 13; private const int SLOW_INTEGER_PASS1_BITS = 2; /* We use the following pre-calculated constants. * If you change SLOW_INTEGER_CONST_BITS you may want to add appropriate values. * * Convert a positive real constant to an integer scaled by CONST_SCALE. * static int SLOW_INTEGER_FIX(double x) * { * return ((int) ((x) * (((int) 1) << SLOW_INTEGER_CONST_BITS) + 0.5)); * } */ private const int SLOW_INTEGER_FIX_0_298631336 = 2446; /* SLOW_INTEGER_FIX(0.298631336) */ private const int SLOW_INTEGER_FIX_0_390180644 = 3196; /* SLOW_INTEGER_FIX(0.390180644) */ private const int SLOW_INTEGER_FIX_0_541196100 = 4433; /* SLOW_INTEGER_FIX(0.541196100) */ private const int SLOW_INTEGER_FIX_0_765366865 = 6270; /* SLOW_INTEGER_FIX(0.765366865) */ private const int SLOW_INTEGER_FIX_0_899976223 = 7373; /* SLOW_INTEGER_FIX(0.899976223) */ private const int SLOW_INTEGER_FIX_1_175875602 = 9633; /* SLOW_INTEGER_FIX(1.175875602) */ private const int SLOW_INTEGER_FIX_1_501321110 = 12299; /* SLOW_INTEGER_FIX(1.501321110) */ private const int SLOW_INTEGER_FIX_1_847759065 = 15137; /* SLOW_INTEGER_FIX(1.847759065) */ private const int SLOW_INTEGER_FIX_1_961570560 = 16069; /* SLOW_INTEGER_FIX(1.961570560) */ private const int SLOW_INTEGER_FIX_2_053119869 = 16819; /* SLOW_INTEGER_FIX(2.053119869) */ private const int SLOW_INTEGER_FIX_2_562915447 = 20995; /* SLOW_INTEGER_FIX(2.562915447) */ private const int SLOW_INTEGER_FIX_3_072711026 = 25172; /* SLOW_INTEGER_FIX(3.072711026) */ private const int FAST_INTEGER_CONST_BITS = 8; private const int FAST_INTEGER_PASS1_BITS = 2; /* We use the following pre-calculated constants. * If you change FAST_INTEGER_CONST_BITS you may want to add appropriate values. */ private const int FAST_INTEGER_FIX_1_082392200 = 277; /* FAST_INTEGER_FIX(1.082392200) */ private const int FAST_INTEGER_FIX_1_414213562 = 362; /* FAST_INTEGER_FIX(1.414213562) */ private const int FAST_INTEGER_FIX_1_847759065 = 473; /* FAST_INTEGER_FIX(1.847759065) */ private const int FAST_INTEGER_FIX_2_613125930 = 669; /* FAST_INTEGER_FIX(2.613125930) */ private const int REDUCED_CONST_BITS = 13; private const int REDUCED_PASS1_BITS = 2; /* We use the following pre-calculated constants. * If you change REDUCED_CONST_BITS you may want to add appropriate values. * Convert a positive real constant to an integer scaled by CONST_SCALE. * static int REDUCED_FIX(double x) * { * return ((int) ((x) * (((int) 1) << REDUCED_CONST_BITS) + 0.5)); * } */ private const int REDUCED_FIX_0_211164243 = 1730; /* REDUCED_FIX(0.211164243) */ private const int REDUCED_FIX_0_509795579 = 4176; /* REDUCED_FIX(0.509795579) */ private const int REDUCED_FIX_0_601344887 = 4926; /* REDUCED_FIX(0.601344887) */ private const int REDUCED_FIX_0_720959822 = 5906; /* REDUCED_FIX(0.720959822) */ private const int REDUCED_FIX_0_765366865 = 6270; /* REDUCED_FIX(0.765366865) */ private const int REDUCED_FIX_0_850430095 = 6967; /* REDUCED_FIX(0.850430095) */ private const int REDUCED_FIX_0_899976223 = 7373; /* REDUCED_FIX(0.899976223) */ private const int REDUCED_FIX_1_061594337 = 8697; /* REDUCED_FIX(1.061594337) */ private const int REDUCED_FIX_1_272758580 = 10426; /* REDUCED_FIX(1.272758580) */ private const int REDUCED_FIX_1_451774981 = 11893; /* REDUCED_FIX(1.451774981) */ private const int REDUCED_FIX_1_847759065 = 15137; /* REDUCED_FIX(1.847759065) */ private const int REDUCED_FIX_2_172734803 = 17799; /* REDUCED_FIX(2.172734803) */ private const int REDUCED_FIX_2_562915447 = 20995; /* REDUCED_FIX(2.562915447) */ private const int REDUCED_FIX_3_624509785 = 29692; /* REDUCED_FIX(3.624509785) */ /* precomputed values scaled up by 14 bits */ private static short[] aanscales = { 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 }; private const int CONST_BITS = 14; private static double[] aanscalefactor = { 1.0, 1.387039845, 1.306562965, 1.175875602, 1.0, 0.785694958, 0.541196100, 0.275899379 }; private enum InverseMethod { Unknown, idct_1x1_method, idct_2x2_method, idct_4x4_method, idct_islow_method, idct_ifast_method, idct_float_method } /* It is useful to allow each component to have a separate IDCT method. */ private InverseMethod[] m_inverse_DCT_method = new InverseMethod[JpegConstants.MaxComponents]; /* Allocated multiplier tables: big enough for any supported variant */ private class multiplier_table { public int[] int_array = new int[JpegConstants.DCTSize2]; public float[] float_array = new float[JpegConstants.DCTSize2]; }; private multiplier_table[] m_dctTables; private JpegDecompressor m_cinfo; /* This array contains the IDCT method code that each multiplier table * is currently set up for, or -1 if it's not yet set up. * The actual multiplier tables are pointed to by dct_table in the * per-component comp_info structures. */ private int[] m_cur_method = new int[JpegConstants.MaxComponents]; private ComponentBuffer m_componentBuffer; public JpegInverseDCT(JpegDecompressor cinfo) { m_cinfo = cinfo; m_dctTables = new multiplier_table[cinfo.m_num_components]; for (int ci = 0; ci < cinfo.m_num_components; ci++) { /* Allocate and pre-zero a multiplier table for each component */ m_dctTables[ci] = new multiplier_table(); /* Mark multiplier table not yet set up for any method */ m_cur_method[ci] = -1; } } /// /// Prepare for an output pass. /// Here we select the proper IDCT routine for each component and build /// a matching multiplier table. /// public void start_pass() { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[ci]; InverseMethod im = InverseMethod.Unknown; int method = 0; /* Select the proper IDCT routine for this component's scaling */ switch (componentInfo.DCT_scaled_size) { case 1: im = InverseMethod.idct_1x1_method; method = (int)DCTMethod.IntSlow; /* jidctred uses islow-style table */ break; case 2: im = InverseMethod.idct_2x2_method; method = (int)DCTMethod.IntSlow; /* jidctred uses islow-style table */ break; case 4: im = InverseMethod.idct_4x4_method; method = (int)DCTMethod.IntSlow; /* jidctred uses islow-style table */ break; case JpegConstants.DCTSize: switch (m_cinfo.m_dct_method) { case DCTMethod.IntSlow: im = InverseMethod.idct_islow_method; method = (int)DCTMethod.IntSlow; break; case DCTMethod.IntFast: im = InverseMethod.idct_ifast_method; method = (int)DCTMethod.IntFast; break; case DCTMethod.Float: im = InverseMethod.idct_float_method; method = (int)DCTMethod.Float; break; default: throw new Exception("Unknown DCT Method!"); } break; default: throw new Exception(String.Format("IDCT output block size {0} not supported", componentInfo.DCT_scaled_size)); } m_inverse_DCT_method[ci] = im; /* Create multiplier table from quant table. * However, we can skip this if the component is uninteresting * or if we already built the table. Also, if no quant table * has yet been saved for the component, we leave the * multiplier table all-zero; we'll be reading zeroes from the * coefficient controller's buffer anyway. */ if (!componentInfo.component_needed || m_cur_method[ci] == method) continue; if (componentInfo.quant_table == null) { /* happens if no data yet for component */ continue; } m_cur_method[ci] = method; switch ((DCTMethod)method) { case DCTMethod.IntSlow: /* For LL&M IDCT method, multipliers are equal to raw quantization * coefficients, but are stored as ints to ensure access efficiency. */ int[] ismtbl = m_dctTables[ci].int_array; for (int i = 0; i < JpegConstants.DCTSize2; i++) ismtbl[i] = componentInfo.quant_table.quantval[i]; break; case DCTMethod.IntFast: /* For AA&N IDCT method, multipliers are equal to quantization * coefficients scaled by scalefactor[row]*scalefactor[col], where * scalefactor[0] = 1 * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 * For integer operation, the multiplier table is to be scaled by * IFAST_SCALE_BITS. */ int[] ifmtbl = m_dctTables[ci].int_array; for (int i = 0; i < JpegConstants.DCTSize2; i++) { ifmtbl[i] = JpegUtils.DESCALE((int)componentInfo.quant_table.quantval[i] * (int)aanscales[i], CONST_BITS - IFAST_SCALE_BITS); } break; case DCTMethod.Float: /* For float AA&N IDCT method, multipliers are equal to quantization * coefficients scaled by scalefactor[row]*scalefactor[col], where * scalefactor[0] = 1 * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 */ float[] fmtbl = m_dctTables[ci].float_array; int ii = 0; for (int row = 0; row < JpegConstants.DCTSize; row++) { for (int col = 0; col < JpegConstants.DCTSize; col++) { fmtbl[ii] = (float)((double)componentInfo.quant_table.quantval[ii] * aanscalefactor[row] * aanscalefactor[col]); ii++; } } break; default: throw new Exception("Unknown DCT Method!"); } } } /* Inverse DCT (also performs de-quantization) */ public void inverse(int component_index, short[] coef_block, ComponentBuffer output_buf, int output_row, int output_col) { m_componentBuffer = output_buf; switch (m_inverse_DCT_method[component_index]) { case InverseMethod.idct_1x1_method: jpeg_idct_1x1(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_2x2_method: jpeg_idct_2x2(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_4x4_method: jpeg_idct_4x4(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_islow_method: jpeg_idct_islow(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_ifast_method: jpeg_idct_ifast(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_float_method: jpeg_idct_float(component_index, coef_block, output_row, output_col); break; case InverseMethod.Unknown: default: throw new Exception("Unknown Inverse Method!"); } } /// /// Perform de-quantization and inverse DCT on one block of coefficients. /// NOTE: this code only copes with 8x8 DCTs. /// A slow-but-accurate integer implementation of the /// inverse DCT (Discrete Cosine Transform). In the IJG code, this routine /// must also perform de-quantization of the input coefficients. /// /// A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT /// on each row (or vice versa, but it's more convenient to emit a row at /// a time). Direct algorithms are also available, but they are much more /// complex and seem not to be any faster when reduced to code. /// /// This implementation is based on an algorithm described in /// C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT /// Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, /// Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. /// The primary algorithm described there uses 11 multiplies and 29 adds. /// We use their alternate method with 12 multiplies and 32 adds. /// The advantage of this method is that no data path contains more than one /// multiplication; this allows a very simple and accurate implementation in /// scaled fixed-point arithmetic, with a minimal number of shifts. /// /// The poop on this scaling stuff is as follows: /// /// Each 1-D IDCT step produces outputs which are a factor of sqrt(N) /// larger than the true IDCT outputs. The final outputs are therefore /// a factor of N larger than desired; since N=8 this can be cured by /// a simple right shift at the end of the algorithm. The advantage of /// this arrangement is that we save two multiplications per 1-D IDCT, /// because the y0 and y4 inputs need not be divided by sqrt(N). /// /// We have to do addition and subtraction of the integer inputs, which /// is no problem, and multiplication by fractional constants, which is /// a problem to do in integer arithmetic. We multiply all the constants /// by CONST_SCALE and convert them to integer constants (thus retaining /// SLOW_INTEGER_CONST_BITS bits of precision in the constants). After doing a /// multiplication we have to divide the product by CONST_SCALE, with proper /// rounding, to produce the correct output. This division can be done /// cheaply as a right shift of SLOW_INTEGER_CONST_BITS bits. We postpone shifting /// as long as possible so that partial sums can be added together with /// full fractional precision. /// /// The outputs of the first pass are scaled up by SLOW_INTEGER_PASS1_BITS bits so that /// they are represented to better-than-integral precision. These outputs /// require BitsInSample + SLOW_INTEGER_PASS1_BITS + 3 bits; this fits in a 16-bit word /// with the recommended scaling. (To scale up 12-bit sample data further, an /// intermediate int array would be needed.) /// /// To avoid overflow of the 32-bit intermediate results in pass 2, we must /// have BitsInSample + SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS <= 26. Error analysis /// shows that the values given below are the most effective. /// private void jpeg_idct_islow(int component_index, short[] coef_block, int output_row, int output_col) { /* buffers data between passes */ int[] workspace = new int[JpegConstants.DCTSize2]; /* Pass 1: process columns from input, store into work array. */ /* Note results are scaled up by sqrt(8) compared to a true IDCT; */ /* furthermore, we scale the results by 2**SLOW_INTEGER_PASS1_BITS. */ int coefBlockIndex = 0; int[] quantTable = m_dctTables[component_index].int_array; int quantTableIndex = 0; int workspaceIndex = 0; for (int ctr = JpegConstants.DCTSize; ctr > 0; ctr--) { /* Due to quantization, we will usually find that many of the input * coefficients are zero, especially the AC terms. We can exploit this * by short-circuiting the IDCT calculation for any column in which all * the AC terms are zero. In that case each output is equal to the * DC coefficient (with scale factor as needed). * With typical images and quantization tables, half or more of the * column DCT calculations can be simplified this way. */ if (coef_block[coefBlockIndex + JpegConstants.DCTSize * 1] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 2] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 3] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 4] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 5] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 6] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 7] == 0) { /* AC terms all zero */ int dcval = SLOW_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]) << SLOW_INTEGER_PASS1_BITS; workspace[workspaceIndex + JpegConstants.DCTSize * 0] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 1] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 2] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 3] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 4] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 5] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 6] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 7] = dcval; /* advance pointers to next column */ coefBlockIndex++; quantTableIndex++; workspaceIndex++; continue; } /* Even part: reverse the even part of the forward DCT. */ /* The rotator is sqrt(2)*c(-6). */ int z2 = SLOW_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 2], quantTable[quantTableIndex + JpegConstants.DCTSize * 2]); int z3 = SLOW_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 6], quantTable[quantTableIndex + JpegConstants.DCTSize * 6]); int z1 = (z2 + z3) * SLOW_INTEGER_FIX_0_541196100; int tmp2 = z1 + z3 * (-SLOW_INTEGER_FIX_1_847759065); int tmp3 = z1 + z2 * SLOW_INTEGER_FIX_0_765366865; z2 = SLOW_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]); z3 = SLOW_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 4], quantTable[quantTableIndex + JpegConstants.DCTSize * 4]); int tmp0 = (z2 + z3) << SLOW_INTEGER_CONST_BITS; int tmp1 = (z2 - z3) << SLOW_INTEGER_CONST_BITS; int tmp10 = tmp0 + tmp3; int tmp13 = tmp0 - tmp3; int tmp11 = tmp1 + tmp2; int tmp12 = tmp1 - tmp2; /* Odd part per figure 8; the matrix is unitary and hence its * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. */ tmp0 = SLOW_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 7], quantTable[quantTableIndex + JpegConstants.DCTSize * 7]); tmp1 = SLOW_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 5], quantTable[quantTableIndex + JpegConstants.DCTSize * 5]); tmp2 = SLOW_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 3], quantTable[quantTableIndex + JpegConstants.DCTSize * 3]); tmp3 = SLOW_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 1], quantTable[quantTableIndex + JpegConstants.DCTSize * 1]); z1 = tmp0 + tmp3; z2 = tmp1 + tmp2; z3 = tmp0 + tmp2; int z4 = tmp1 + tmp3; int z5 = (z3 + z4) * SLOW_INTEGER_FIX_1_175875602; /* sqrt(2) * c3 */ tmp0 = tmp0 * SLOW_INTEGER_FIX_0_298631336; /* sqrt(2) * (-c1+c3+c5-c7) */ tmp1 = tmp1 * SLOW_INTEGER_FIX_2_053119869; /* sqrt(2) * ( c1+c3-c5+c7) */ tmp2 = tmp2 * SLOW_INTEGER_FIX_3_072711026; /* sqrt(2) * ( c1+c3+c5-c7) */ tmp3 = tmp3 * SLOW_INTEGER_FIX_1_501321110; /* sqrt(2) * ( c1+c3-c5-c7) */ z1 = z1 * (-SLOW_INTEGER_FIX_0_899976223); /* sqrt(2) * (c7-c3) */ z2 = z2 * (-SLOW_INTEGER_FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ z3 = z3 * (-SLOW_INTEGER_FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ z4 = z4 * (-SLOW_INTEGER_FIX_0_390180644); /* sqrt(2) * (c5-c3) */ z3 += z5; z4 += z5; tmp0 += z1 + z3; tmp1 += z2 + z4; tmp2 += z2 + z3; tmp3 += z1 + z4; /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ workspace[workspaceIndex + JpegConstants.DCTSize * 0] = JpegUtils.DESCALE(tmp10 + tmp3, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); workspace[workspaceIndex + JpegConstants.DCTSize * 7] = JpegUtils.DESCALE(tmp10 - tmp3, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); workspace[workspaceIndex + JpegConstants.DCTSize * 1] = JpegUtils.DESCALE(tmp11 + tmp2, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); workspace[workspaceIndex + JpegConstants.DCTSize * 6] = JpegUtils.DESCALE(tmp11 - tmp2, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); workspace[workspaceIndex + JpegConstants.DCTSize * 2] = JpegUtils.DESCALE(tmp12 + tmp1, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); workspace[workspaceIndex + JpegConstants.DCTSize * 5] = JpegUtils.DESCALE(tmp12 - tmp1, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); workspace[workspaceIndex + JpegConstants.DCTSize * 3] = JpegUtils.DESCALE(tmp13 + tmp0, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); workspace[workspaceIndex + JpegConstants.DCTSize * 4] = JpegUtils.DESCALE(tmp13 - tmp0, SLOW_INTEGER_CONST_BITS - SLOW_INTEGER_PASS1_BITS); /* advance pointers to next column */ coefBlockIndex++; quantTableIndex++; workspaceIndex++; } /* Pass 2: process rows from work array, store into output array. */ /* Note that we must descale the results by a factor of 8 == 2**3, */ /* and also undo the SLOW_INTEGER_PASS1_BITS scaling. */ workspaceIndex = 0; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset + JpegConstants.MediumSampleValue; for (int ctr = 0; ctr < JpegConstants.DCTSize; ctr++) { /* Rows of zeroes can be exploited in the same way as we did with columns. * However, the column calculation has created many nonzero AC terms, so * the simplification applies less often (typically 5% to 10% of the time). * On machines with very fast multiplication, it's possible that the * test takes more time than it's worth. In that case this section * may be commented out. */ int currentOutRow = output_row + ctr; if (workspace[workspaceIndex + 1] == 0 && workspace[workspaceIndex + 2] == 0 && workspace[workspaceIndex + 3] == 0 && workspace[workspaceIndex + 4] == 0 && workspace[workspaceIndex + 5] == 0 && workspace[workspaceIndex + 6] == 0 && workspace[workspaceIndex + 7] == 0) { /* AC terms all zero */ byte dcval = limit[limitOffset + JpegUtils.DESCALE(workspace[workspaceIndex + 0], SLOW_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 0] = dcval; m_componentBuffer[currentOutRow][output_col + 1] = dcval; m_componentBuffer[currentOutRow][output_col + 2] = dcval; m_componentBuffer[currentOutRow][output_col + 3] = dcval; m_componentBuffer[currentOutRow][output_col + 4] = dcval; m_componentBuffer[currentOutRow][output_col + 5] = dcval; m_componentBuffer[currentOutRow][output_col + 6] = dcval; m_componentBuffer[currentOutRow][output_col + 7] = dcval; workspaceIndex += JpegConstants.DCTSize; /* advance pointer to next row */ continue; } /* Even part: reverse the even part of the forward DCT. */ /* The rotator is sqrt(2)*c(-6). */ int z2 = workspace[workspaceIndex + 2]; int z3 = workspace[workspaceIndex + 6]; int z1 = (z2 + z3) * SLOW_INTEGER_FIX_0_541196100; int tmp2 = z1 + z3 * (-SLOW_INTEGER_FIX_1_847759065); int tmp3 = z1 + z2 * SLOW_INTEGER_FIX_0_765366865; int tmp0 = (workspace[workspaceIndex + 0] + workspace[workspaceIndex + 4]) << SLOW_INTEGER_CONST_BITS; int tmp1 = (workspace[workspaceIndex + 0] - workspace[workspaceIndex + 4]) << SLOW_INTEGER_CONST_BITS; int tmp10 = tmp0 + tmp3; int tmp13 = tmp0 - tmp3; int tmp11 = tmp1 + tmp2; int tmp12 = tmp1 - tmp2; /* Odd part per figure 8; the matrix is unitary and hence its * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. */ tmp0 = workspace[workspaceIndex + 7]; tmp1 = workspace[workspaceIndex + 5]; tmp2 = workspace[workspaceIndex + 3]; tmp3 = workspace[workspaceIndex + 1]; z1 = tmp0 + tmp3; z2 = tmp1 + tmp2; z3 = tmp0 + tmp2; int z4 = tmp1 + tmp3; int z5 = (z3 + z4) * SLOW_INTEGER_FIX_1_175875602; /* sqrt(2) * c3 */ tmp0 = tmp0 * SLOW_INTEGER_FIX_0_298631336; /* sqrt(2) * (-c1+c3+c5-c7) */ tmp1 = tmp1 * SLOW_INTEGER_FIX_2_053119869; /* sqrt(2) * ( c1+c3-c5+c7) */ tmp2 = tmp2 * SLOW_INTEGER_FIX_3_072711026; /* sqrt(2) * ( c1+c3+c5-c7) */ tmp3 = tmp3 * SLOW_INTEGER_FIX_1_501321110; /* sqrt(2) * ( c1+c3-c5-c7) */ z1 = z1 * (-SLOW_INTEGER_FIX_0_899976223); /* sqrt(2) * (c7-c3) */ z2 = z2 * (-SLOW_INTEGER_FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ z3 = z3 * (-SLOW_INTEGER_FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ z4 = z4 * (-SLOW_INTEGER_FIX_0_390180644); /* sqrt(2) * (c5-c3) */ z3 += z5; z4 += z5; tmp0 += z1 + z3; tmp1 += z2 + z4; tmp2 += z2 + z3; tmp3 += z1 + z4; /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ m_componentBuffer[currentOutRow][output_col + 0] = limit[limitOffset + JpegUtils.DESCALE(tmp10 + tmp3, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 7] = limit[limitOffset + JpegUtils.DESCALE(tmp10 - tmp3, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 1] = limit[limitOffset + JpegUtils.DESCALE(tmp11 + tmp2, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 6] = limit[limitOffset + JpegUtils.DESCALE(tmp11 - tmp2, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 2] = limit[limitOffset + JpegUtils.DESCALE(tmp12 + tmp1, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 5] = limit[limitOffset + JpegUtils.DESCALE(tmp12 - tmp1, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 3] = limit[limitOffset + JpegUtils.DESCALE(tmp13 + tmp0, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 4] = limit[limitOffset + JpegUtils.DESCALE(tmp13 - tmp0, SLOW_INTEGER_CONST_BITS + SLOW_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; /* advance pointer to next row */ workspaceIndex += JpegConstants.DCTSize; } } /// /// Dequantize a coefficient by multiplying it by the multiplier-table /// entry; produce an int result. In this module, both inputs and result /// are 16 bits or less, so either int or short multiply will work. /// private static int SLOW_INTEGER_DEQUANTIZE(int coef, int quantval) { return (coef * quantval); } /// /// Perform dequantization and inverse DCT on one block of coefficients. /// NOTE: this code only copes with 8x8 DCTs. /// /// A fast, not so accurate integer implementation of the /// inverse DCT (Discrete Cosine Transform). In the IJG code, this routine /// must also perform dequantization of the input coefficients. /// /// A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT /// on each row (or vice versa, but it's more convenient to emit a row at /// a time). Direct algorithms are also available, but they are much more /// complex and seem not to be any faster when reduced to code. /// /// This implementation is based on Arai, Agui, and Nakajima's algorithm for /// scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in /// Japanese, but the algorithm is described in the Pennebaker & Mitchell /// JPEG textbook (see REFERENCES section in file README). The following code /// is based directly on figure 4-8 in P&M. /// While an 8-point DCT cannot be done in less than 11 multiplies, it is /// possible to arrange the computation so that many of the multiplies are /// simple scalings of the final outputs. These multiplies can then be /// folded into the multiplications or divisions by the JPEG quantization /// table entries. The AA&N method leaves only 5 multiplies and 29 adds /// to be done in the DCT itself. /// The primary disadvantage of this method is that with fixed-point math, /// accuracy is lost due to imprecise representation of the scaled /// quantization values. The smaller the quantization table entry, the less /// precise the scaled value, so this implementation does worse with high- /// quality-setting files than with low-quality ones. /// /// Scaling decisions are generally the same as in the LL&M algorithm; /// However, we choose to descale /// (right shift) multiplication products as soon as they are formed, /// rather than carrying additional fractional bits into subsequent additions. /// This compromises accuracy slightly, but it lets us save a few shifts. /// More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) /// everywhere except in the multiplications proper; this saves a good deal /// of work on 16-bit-int machines. /// /// The dequantized coefficients are not integers because the AA&N scaling /// factors have been incorporated. We represent them scaled up by FAST_INTEGER_PASS1_BITS, /// so that the first and second IDCT rounds have the same input scaling. /// For 8-bit JSAMPLEs, we choose IFAST_SCALE_BITS = FAST_INTEGER_PASS1_BITS so as to /// avoid a descaling shift; this compromises accuracy rather drastically /// for small quantization table entries, but it saves a lot of shifts. /// For 12-bit JSAMPLEs, there's no hope of using 16x16 multiplies anyway, /// so we use a much larger scaling factor to preserve accuracy. /// /// A final compromise is to represent the multiplicative constants to only /// 8 fractional bits, rather than 13. This saves some shifting work on some /// machines, and may also reduce the cost of multiplication (since there /// are fewer one-bits in the constants). /// private void jpeg_idct_ifast(int component_index, short[] coef_block, int output_row, int output_col) { /* buffers data between passes */ int[] workspace = new int[JpegConstants.DCTSize2]; /* Pass 1: process columns from input, store into work array. */ int coefBlockIndex = 0; int workspaceIndex = 0; int[] quantTable = m_dctTables[component_index].int_array; int quantTableIndex = 0; for (int ctr = JpegConstants.DCTSize; ctr > 0; ctr--) { /* Due to quantization, we will usually find that many of the input * coefficients are zero, especially the AC terms. We can exploit this * by short-circuiting the IDCT calculation for any column in which all * the AC terms are zero. In that case each output is equal to the * DC coefficient (with scale factor as needed). * With typical images and quantization tables, half or more of the * column DCT calculations can be simplified this way. */ if (coef_block[coefBlockIndex + JpegConstants.DCTSize * 1] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 2] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 3] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 4] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 5] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 6] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 7] == 0) { /* AC terms all zero */ int dcval = FAST_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]); workspace[workspaceIndex + JpegConstants.DCTSize * 0] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 1] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 2] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 3] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 4] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 5] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 6] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 7] = dcval; /* advance pointers to next column */ coefBlockIndex++; quantTableIndex++; workspaceIndex++; continue; } /* Even part */ int tmp0 = FAST_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]); int tmp1 = FAST_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 2], quantTable[quantTableIndex + JpegConstants.DCTSize * 2]); int tmp2 = FAST_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 4], quantTable[quantTableIndex + JpegConstants.DCTSize * 4]); int tmp3 = FAST_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 6], quantTable[quantTableIndex + JpegConstants.DCTSize * 6]); int tmp10 = tmp0 + tmp2; /* phase 3 */ int tmp11 = tmp0 - tmp2; int tmp13 = tmp1 + tmp3; /* phases 5-3 */ int tmp12 = FAST_INTEGER_MULTIPLY(tmp1 - tmp3, FAST_INTEGER_FIX_1_414213562) - tmp13; /* 2*c4 */ tmp0 = tmp10 + tmp13; /* phase 2 */ tmp3 = tmp10 - tmp13; tmp1 = tmp11 + tmp12; tmp2 = tmp11 - tmp12; /* Odd part */ int tmp4 = FAST_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 1], quantTable[quantTableIndex + JpegConstants.DCTSize * 1]); int tmp5 = FAST_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 3], quantTable[quantTableIndex + JpegConstants.DCTSize * 3]); int tmp6 = FAST_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 5], quantTable[quantTableIndex + JpegConstants.DCTSize * 5]); int tmp7 = FAST_INTEGER_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 7], quantTable[quantTableIndex + JpegConstants.DCTSize * 7]); int z13 = tmp6 + tmp5; /* phase 6 */ int z10 = tmp6 - tmp5; int z11 = tmp4 + tmp7; int z12 = tmp4 - tmp7; tmp7 = z11 + z13; /* phase 5 */ tmp11 = FAST_INTEGER_MULTIPLY(z11 - z13, FAST_INTEGER_FIX_1_414213562); /* 2*c4 */ int z5 = FAST_INTEGER_MULTIPLY(z10 + z12, FAST_INTEGER_FIX_1_847759065); /* 2*c2 */ tmp10 = FAST_INTEGER_MULTIPLY(z12, FAST_INTEGER_FIX_1_082392200) - z5; /* 2*(c2-c6) */ tmp12 = FAST_INTEGER_MULTIPLY(z10, -FAST_INTEGER_FIX_2_613125930) + z5; /* -2*(c2+c6) */ tmp6 = tmp12 - tmp7; /* phase 2 */ tmp5 = tmp11 - tmp6; tmp4 = tmp10 + tmp5; workspace[workspaceIndex + JpegConstants.DCTSize * 0] = tmp0 + tmp7; workspace[workspaceIndex + JpegConstants.DCTSize * 7] = tmp0 - tmp7; workspace[workspaceIndex + JpegConstants.DCTSize * 1] = tmp1 + tmp6; workspace[workspaceIndex + JpegConstants.DCTSize * 6] = tmp1 - tmp6; workspace[workspaceIndex + JpegConstants.DCTSize * 2] = tmp2 + tmp5; workspace[workspaceIndex + JpegConstants.DCTSize * 5] = tmp2 - tmp5; workspace[workspaceIndex + JpegConstants.DCTSize * 4] = tmp3 + tmp4; workspace[workspaceIndex + JpegConstants.DCTSize * 3] = tmp3 - tmp4; /* advance pointers to next column */ coefBlockIndex++; quantTableIndex++; workspaceIndex++; } /* Pass 2: process rows from work array, store into output array. */ /* Note that we must descale the results by a factor of 8 == 2**3, */ /* and also undo the FAST_INTEGER_PASS1_BITS scaling. */ workspaceIndex = 0; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset + JpegConstants.MediumSampleValue; for (int ctr = 0; ctr < JpegConstants.DCTSize; ctr++) { int currentOutRow = output_row + ctr; /* Rows of zeroes can be exploited in the same way as we did with columns. * However, the column calculation has created many nonzero AC terms, so * the simplification applies less often (typically 5% to 10% of the time). * On machines with very fast multiplication, it's possible that the * test takes more time than it's worth. In that case this section * may be commented out. */ if (workspace[workspaceIndex + 1] == 0 && workspace[workspaceIndex + 2] == 0 && workspace[workspaceIndex + 3] == 0 && workspace[workspaceIndex + 4] == 0 && workspace[workspaceIndex + 5] == 0 && workspace[workspaceIndex + 6] == 0 && workspace[workspaceIndex + 7] == 0) { /* AC terms all zero */ byte dcval = limit[limitOffset + FAST_INTEGER_IDESCALE(workspace[workspaceIndex + 0], FAST_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 0] = dcval; m_componentBuffer[currentOutRow][output_col + 1] = dcval; m_componentBuffer[currentOutRow][output_col + 2] = dcval; m_componentBuffer[currentOutRow][output_col + 3] = dcval; m_componentBuffer[currentOutRow][output_col + 4] = dcval; m_componentBuffer[currentOutRow][output_col + 5] = dcval; m_componentBuffer[currentOutRow][output_col + 6] = dcval; m_componentBuffer[currentOutRow][output_col + 7] = dcval; /* advance pointer to next row */ workspaceIndex += JpegConstants.DCTSize; continue; } /* Even part */ int tmp10 = workspace[workspaceIndex + 0] + workspace[workspaceIndex + 4]; int tmp11 = workspace[workspaceIndex + 0] - workspace[workspaceIndex + 4]; int tmp13 = workspace[workspaceIndex + 2] + workspace[workspaceIndex + 6]; int tmp12 = FAST_INTEGER_MULTIPLY(workspace[workspaceIndex + 2] - workspace[workspaceIndex + 6], FAST_INTEGER_FIX_1_414213562) - tmp13; int tmp0 = tmp10 + tmp13; int tmp3 = tmp10 - tmp13; int tmp1 = tmp11 + tmp12; int tmp2 = tmp11 - tmp12; /* Odd part */ int z13 = workspace[workspaceIndex + 5] + workspace[workspaceIndex + 3]; int z10 = workspace[workspaceIndex + 5] - workspace[workspaceIndex + 3]; int z11 = workspace[workspaceIndex + 1] + workspace[workspaceIndex + 7]; int z12 = workspace[workspaceIndex + 1] - workspace[workspaceIndex + 7]; int tmp7 = z11 + z13; /* phase 5 */ tmp11 = FAST_INTEGER_MULTIPLY(z11 - z13, FAST_INTEGER_FIX_1_414213562); /* 2*c4 */ int z5 = FAST_INTEGER_MULTIPLY(z10 + z12, FAST_INTEGER_FIX_1_847759065); /* 2*c2 */ tmp10 = FAST_INTEGER_MULTIPLY(z12, FAST_INTEGER_FIX_1_082392200) - z5; /* 2*(c2-c6) */ tmp12 = FAST_INTEGER_MULTIPLY(z10, -FAST_INTEGER_FIX_2_613125930) + z5; /* -2*(c2+c6) */ int tmp6 = tmp12 - tmp7; /* phase 2 */ int tmp5 = tmp11 - tmp6; int tmp4 = tmp10 + tmp5; /* Final output stage: scale down by a factor of 8 and range-limit */ m_componentBuffer[currentOutRow][output_col + 0] = limit[limitOffset + FAST_INTEGER_IDESCALE(tmp0 + tmp7, FAST_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 7] = limit[limitOffset + FAST_INTEGER_IDESCALE(tmp0 - tmp7, FAST_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 1] = limit[limitOffset + FAST_INTEGER_IDESCALE(tmp1 + tmp6, FAST_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 6] = limit[limitOffset + FAST_INTEGER_IDESCALE(tmp1 - tmp6, FAST_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 2] = limit[limitOffset + FAST_INTEGER_IDESCALE(tmp2 + tmp5, FAST_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 5] = limit[limitOffset + FAST_INTEGER_IDESCALE(tmp2 - tmp5, FAST_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 4] = limit[limitOffset + FAST_INTEGER_IDESCALE(tmp3 + tmp4, FAST_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 3] = limit[limitOffset + FAST_INTEGER_IDESCALE(tmp3 - tmp4, FAST_INTEGER_PASS1_BITS + 3) & RANGE_MASK]; /* advance pointer to next row */ workspaceIndex += JpegConstants.DCTSize; } } /// /// Multiply a DCTELEM variable by an int constant, and immediately /// descale to yield a DCTELEM result. /// private static int FAST_INTEGER_MULTIPLY(int var, int c) { return (JpegUtils.DESCALE(var * c, FAST_INTEGER_CONST_BITS)); } /// /// Dequantize a coefficient by multiplying it by the multiplier-table /// entry; produce a DCTELEM result. For 8-bit data a 16x16->16 /// multiplication will do. For 12-bit data, the multiplier table is /// declared int, so a 32-bit multiply will be used. /// private static int FAST_INTEGER_DEQUANTIZE(short coef, int quantval) { return ((int)coef * quantval); } /// /// Like DESCALE, but applies to a DCTELEM and produces an int. /// We assume that int right shift is unsigned if int right shift is. /// private static int FAST_INTEGER_IRIGHT_SHIFT(int x, int shft) { return (x >> shft); } private static int FAST_INTEGER_IDESCALE(int x, int n) { return (FAST_INTEGER_IRIGHT_SHIFT((x) + (1 << ((n) - 1)), n)); } /// /// Perform dequantization and inverse DCT on one block of coefficients. /// NOTE: this code only copes with 8x8 DCTs. /// /// A floating-point implementation of the /// inverse DCT (Discrete Cosine Transform). In the IJG code, this routine /// must also perform dequantization of the input coefficients. /// /// This implementation should be more accurate than either of the integer /// IDCT implementations. However, it may not give the same results on all /// machines because of differences in roundoff behavior. Speed will depend /// on the hardware's floating point capacity. /// /// A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT /// on each row (or vice versa, but it's more convenient to emit a row at /// a time). Direct algorithms are also available, but they are much more /// complex and seem not to be any faster when reduced to code. /// /// This implementation is based on Arai, Agui, and Nakajima's algorithm for /// scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in /// Japanese, but the algorithm is described in the Pennebaker & Mitchell /// JPEG textbook (see REFERENCES section in file README). The following code /// is based directly on figure 4-8 in P&M. /// While an 8-point DCT cannot be done in less than 11 multiplies, it is /// possible to arrange the computation so that many of the multiplies are /// simple scalings of the final outputs. These multiplies can then be /// folded into the multiplications or divisions by the JPEG quantization /// table entries. The AA&N method leaves only 5 multiplies and 29 adds /// to be done in the DCT itself. /// The primary disadvantage of this method is that with a fixed-point /// implementation, accuracy is lost due to imprecise representation of the /// scaled quantization values. However, that problem does not arise if /// we use floating point arithmetic. /// private void jpeg_idct_float(int component_index, short[] coef_block, int output_row, int output_col) { /* buffers data between passes */ float[] workspace = new float[JpegConstants.DCTSize2]; /* Pass 1: process columns from input, store into work array. */ int coefBlockIndex = 0; int workspaceIndex = 0; float[] quantTable = m_dctTables[component_index].float_array; int quantTableIndex = 0; for (int ctr = JpegConstants.DCTSize; ctr > 0; ctr--) { /* Due to quantization, we will usually find that many of the input * coefficients are zero, especially the AC terms. We can exploit this * by short-circuiting the IDCT calculation for any column in which all * the AC terms are zero. In that case each output is equal to the * DC coefficient (with scale factor as needed). * With typical images and quantization tables, half or more of the * column DCT calculations can be simplified this way. */ if (coef_block[coefBlockIndex + JpegConstants.DCTSize * 1] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 2] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 3] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 4] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 5] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 6] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 7] == 0) { /* AC terms all zero */ float dcval = FLOAT_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]); workspace[workspaceIndex + JpegConstants.DCTSize * 0] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 1] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 2] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 3] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 4] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 5] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 6] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 7] = dcval; coefBlockIndex++; /* advance pointers to next column */ quantTableIndex++; workspaceIndex++; continue; } /* Even part */ float tmp0 = FLOAT_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]); float tmp1 = FLOAT_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 2], quantTable[quantTableIndex + JpegConstants.DCTSize * 2]); float tmp2 = FLOAT_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 4], quantTable[quantTableIndex + JpegConstants.DCTSize * 4]); float tmp3 = FLOAT_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 6], quantTable[quantTableIndex + JpegConstants.DCTSize * 6]); float tmp10 = tmp0 + tmp2; /* phase 3 */ float tmp11 = tmp0 - tmp2; float tmp13 = tmp1 + tmp3; /* phases 5-3 */ float tmp12 = (tmp1 - tmp3) * 1.414213562f - tmp13; /* 2*c4 */ tmp0 = tmp10 + tmp13; /* phase 2 */ tmp3 = tmp10 - tmp13; tmp1 = tmp11 + tmp12; tmp2 = tmp11 - tmp12; /* Odd part */ float tmp4 = FLOAT_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 1], quantTable[quantTableIndex + JpegConstants.DCTSize * 1]); float tmp5 = FLOAT_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 3], quantTable[quantTableIndex + JpegConstants.DCTSize * 3]); float tmp6 = FLOAT_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 5], quantTable[quantTableIndex + JpegConstants.DCTSize * 5]); float tmp7 = FLOAT_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 7], quantTable[quantTableIndex + JpegConstants.DCTSize * 7]); float z13 = tmp6 + tmp5; /* phase 6 */ float z10 = tmp6 - tmp5; float z11 = tmp4 + tmp7; float z12 = tmp4 - tmp7; tmp7 = z11 + z13; /* phase 5 */ tmp11 = (z11 - z13) * 1.414213562f; /* 2*c4 */ float z5 = (z10 + z12) * 1.847759065f; /* 2*c2 */ tmp10 = 1.082392200f * z12 - z5; /* 2*(c2-c6) */ tmp12 = -2.613125930f * z10 + z5; /* -2*(c2+c6) */ tmp6 = tmp12 - tmp7; /* phase 2 */ tmp5 = tmp11 - tmp6; tmp4 = tmp10 + tmp5; workspace[workspaceIndex + JpegConstants.DCTSize * 0] = tmp0 + tmp7; workspace[workspaceIndex + JpegConstants.DCTSize * 7] = tmp0 - tmp7; workspace[workspaceIndex + JpegConstants.DCTSize * 1] = tmp1 + tmp6; workspace[workspaceIndex + JpegConstants.DCTSize * 6] = tmp1 - tmp6; workspace[workspaceIndex + JpegConstants.DCTSize * 2] = tmp2 + tmp5; workspace[workspaceIndex + JpegConstants.DCTSize * 5] = tmp2 - tmp5; workspace[workspaceIndex + JpegConstants.DCTSize * 4] = tmp3 + tmp4; workspace[workspaceIndex + JpegConstants.DCTSize * 3] = tmp3 - tmp4; coefBlockIndex++; /* advance pointers to next column */ quantTableIndex++; workspaceIndex++; } /* Pass 2: process rows from work array, store into output array. */ /* Note that we must descale the results by a factor of 8 == 2**3. */ workspaceIndex = 0; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset + JpegConstants.MediumSampleValue; for (int ctr = 0; ctr < JpegConstants.DCTSize; ctr++) { /* Rows of zeroes can be exploited in the same way as we did with columns. * However, the column calculation has created many nonzero AC terms, so * the simplification applies less often (typically 5% to 10% of the time). * And testing floats for zero is relatively expensive, so we don't bother. */ /* Even part */ float tmp10 = workspace[workspaceIndex + 0] + workspace[workspaceIndex + 4]; float tmp11 = workspace[workspaceIndex + 0] - workspace[workspaceIndex + 4]; float tmp13 = workspace[workspaceIndex + 2] + workspace[workspaceIndex + 6]; float tmp12 = (workspace[workspaceIndex + 2] - workspace[workspaceIndex + 6]) * 1.414213562f - tmp13; float tmp0 = tmp10 + tmp13; float tmp3 = tmp10 - tmp13; float tmp1 = tmp11 + tmp12; float tmp2 = tmp11 - tmp12; /* Odd part */ float z13 = workspace[workspaceIndex + 5] + workspace[workspaceIndex + 3]; float z10 = workspace[workspaceIndex + 5] - workspace[workspaceIndex + 3]; float z11 = workspace[workspaceIndex + 1] + workspace[workspaceIndex + 7]; float z12 = workspace[workspaceIndex + 1] - workspace[workspaceIndex + 7]; float tmp7 = z11 + z13; tmp11 = (z11 - z13) * 1.414213562f; float z5 = (z10 + z12) * 1.847759065f; /* 2*c2 */ tmp10 = 1.082392200f * z12 - z5; /* 2*(c2-c6) */ tmp12 = -2.613125930f * z10 + z5; /* -2*(c2+c6) */ float tmp6 = tmp12 - tmp7; float tmp5 = tmp11 - tmp6; float tmp4 = tmp10 + tmp5; /* Final output stage: scale down by a factor of 8 and range-limit */ int currentOutRow = output_row + ctr; m_componentBuffer[currentOutRow][output_col + 0] = limit[limitOffset + JpegUtils.DESCALE((int)(tmp0 + tmp7), 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 7] = limit[limitOffset + JpegUtils.DESCALE((int)(tmp0 - tmp7), 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 1] = limit[limitOffset + JpegUtils.DESCALE((int)(tmp1 + tmp6), 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 6] = limit[limitOffset + JpegUtils.DESCALE((int)(tmp1 - tmp6), 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 2] = limit[limitOffset + JpegUtils.DESCALE((int)(tmp2 + tmp5), 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 5] = limit[limitOffset + JpegUtils.DESCALE((int)(tmp2 - tmp5), 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 4] = limit[limitOffset + JpegUtils.DESCALE((int)(tmp3 + tmp4), 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 3] = limit[limitOffset + JpegUtils.DESCALE((int)(tmp3 - tmp4), 3) & RANGE_MASK]; workspaceIndex += JpegConstants.DCTSize; /* advance pointer to next row */ } } /// /// Dequantize a coefficient by multiplying it by the multiplier-table /// entry; produce a float result. /// private static float FLOAT_DEQUANTIZE(short coef, float quantval) { return (((float)(coef)) * (quantval)); } /// /// Inverse-DCT routines that produce reduced-size output: /// either 4x4, 2x2, or 1x1 pixels from an 8x8 DCT block. /// /// NOTE: this code only copes with 8x8 DCTs. /// /// The implementation is based on the Loeffler, Ligtenberg and Moschytz (LL&M) /// algorithm. We simply replace each 8-to-8 1-D IDCT step /// with an 8-to-4 step that produces the four averages of two adjacent outputs /// (or an 8-to-2 step producing two averages of four outputs, for 2x2 output). /// These steps were derived by computing the corresponding values at the end /// of the normal LL&M code, then simplifying as much as possible. /// /// 1x1 is trivial: just take the DC coefficient divided by 8. /// /// Perform dequantization and inverse DCT on one block of coefficients, /// producing a reduced-size 4x4 output block. /// private void jpeg_idct_4x4(int component_index, short[] coef_block, int output_row, int output_col) { /* buffers data between passes */ int[] workspace = new int[JpegConstants.DCTSize * 4]; /* Pass 1: process columns from input, store into work array. */ int coefBlockIndex = 0; int workspaceIndex = 0; int[] quantTable = m_dctTables[component_index].int_array; int quantTableIndex = 0; for (int ctr = JpegConstants.DCTSize; ctr > 0; coefBlockIndex++, quantTableIndex++, workspaceIndex++, ctr--) { /* Don't bother to process column 4, because second pass won't use it */ if (ctr == JpegConstants.DCTSize - 4) continue; if (coef_block[coefBlockIndex + JpegConstants.DCTSize * 1] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 2] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 3] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 5] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 6] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 7] == 0) { /* AC terms all zero; we need not examine term 4 for 4x4 output */ int dcval = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]) << REDUCED_PASS1_BITS; workspace[workspaceIndex + JpegConstants.DCTSize * 0] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 1] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 2] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 3] = dcval; continue; } /* Even part */ int tmp0 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]); tmp0 <<= (REDUCED_CONST_BITS + 1); int z2 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 2], quantTable[quantTableIndex + JpegConstants.DCTSize * 2]); int z3 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 6], quantTable[quantTableIndex + JpegConstants.DCTSize * 6]); int tmp2 = z2 * REDUCED_FIX_1_847759065 + z3 * (-REDUCED_FIX_0_765366865); int tmp10 = tmp0 + tmp2; int tmp12 = tmp0 - tmp2; /* Odd part */ int z1 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 7], quantTable[quantTableIndex + JpegConstants.DCTSize * 7]); z2 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 5], quantTable[quantTableIndex + JpegConstants.DCTSize * 5]); z3 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 3], quantTable[quantTableIndex + JpegConstants.DCTSize * 3]); int z4 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 1], quantTable[quantTableIndex + JpegConstants.DCTSize * 1]); tmp0 = z1 * (-REDUCED_FIX_0_211164243) /* sqrt(2) * (c3-c1) */ + z2 * REDUCED_FIX_1_451774981 /* sqrt(2) * (c3+c7) */ + z3 * (-REDUCED_FIX_2_172734803) /* sqrt(2) * (-c1-c5) */ + z4 * REDUCED_FIX_1_061594337; /* sqrt(2) * (c5+c7) */ tmp2 = z1 * (-REDUCED_FIX_0_509795579) /* sqrt(2) * (c7-c5) */ + z2 * (-REDUCED_FIX_0_601344887) /* sqrt(2) * (c5-c1) */ + z3 * REDUCED_FIX_0_899976223 /* sqrt(2) * (c3-c7) */ + z4 * REDUCED_FIX_2_562915447; /* sqrt(2) * (c1+c3) */ /* Final output stage */ workspace[workspaceIndex + JpegConstants.DCTSize * 0] = JpegUtils.DESCALE(tmp10 + tmp2, REDUCED_CONST_BITS - REDUCED_PASS1_BITS + 1); workspace[workspaceIndex + JpegConstants.DCTSize * 3] = JpegUtils.DESCALE(tmp10 - tmp2, REDUCED_CONST_BITS - REDUCED_PASS1_BITS + 1); workspace[workspaceIndex + JpegConstants.DCTSize * 1] = JpegUtils.DESCALE(tmp12 + tmp0, REDUCED_CONST_BITS - REDUCED_PASS1_BITS + 1); workspace[workspaceIndex + JpegConstants.DCTSize * 2] = JpegUtils.DESCALE(tmp12 - tmp0, REDUCED_CONST_BITS - REDUCED_PASS1_BITS + 1); } /* Pass 2: process 4 rows from work array, store into output array. */ byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset + JpegConstants.MediumSampleValue; workspaceIndex = 0; for (int ctr = 0; ctr < 4; ctr++) { int currentOutRow = output_row + ctr; /* It's not clear whether a zero row test is worthwhile here ... */ if (workspace[workspaceIndex + 1] == 0 && workspace[workspaceIndex + 2] == 0 && workspace[workspaceIndex + 3] == 0 && workspace[workspaceIndex + 5] == 0 && workspace[workspaceIndex + 6] == 0 && workspace[workspaceIndex + 7] == 0) { /* AC terms all zero */ byte dcval = limit[limitOffset + JpegUtils.DESCALE(workspace[workspaceIndex + 0], REDUCED_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 0] = dcval; m_componentBuffer[currentOutRow][output_col + 1] = dcval; m_componentBuffer[currentOutRow][output_col + 2] = dcval; m_componentBuffer[currentOutRow][output_col + 3] = dcval; workspaceIndex += JpegConstants.DCTSize; /* advance pointer to next row */ continue; } /* Even part */ int tmp0 = (workspace[workspaceIndex + 0]) << (REDUCED_CONST_BITS + 1); int tmp2 = workspace[workspaceIndex + 2] * REDUCED_FIX_1_847759065 + workspace[workspaceIndex + 6] * (-REDUCED_FIX_0_765366865); int tmp10 = tmp0 + tmp2; int tmp12 = tmp0 - tmp2; /* Odd part */ int z1 = workspace[workspaceIndex + 7]; int z2 = workspace[workspaceIndex + 5]; int z3 = workspace[workspaceIndex + 3]; int z4 = workspace[workspaceIndex + 1]; tmp0 = z1 * (-REDUCED_FIX_0_211164243) /* sqrt(2) * (c3-c1) */ + z2 * REDUCED_FIX_1_451774981 /* sqrt(2) * (c3+c7) */ + z3 * (-REDUCED_FIX_2_172734803) /* sqrt(2) * (-c1-c5) */ + z4 * REDUCED_FIX_1_061594337; /* sqrt(2) * (c5+c7) */ tmp2 = z1 * (-REDUCED_FIX_0_509795579) /* sqrt(2) * (c7-c5) */ + z2 * (-REDUCED_FIX_0_601344887) /* sqrt(2) * (c5-c1) */ + z3 * REDUCED_FIX_0_899976223 /* sqrt(2) * (c3-c7) */ + z4 * REDUCED_FIX_2_562915447; /* sqrt(2) * (c1+c3) */ /* Final output stage */ m_componentBuffer[currentOutRow][output_col + 0] = limit[limitOffset + JpegUtils.DESCALE(tmp10 + tmp2, REDUCED_CONST_BITS + REDUCED_PASS1_BITS + 3 + 1) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 3] = limit[limitOffset + JpegUtils.DESCALE(tmp10 - tmp2, REDUCED_CONST_BITS + REDUCED_PASS1_BITS + 3 + 1) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 1] = limit[limitOffset + JpegUtils.DESCALE(tmp12 + tmp0, REDUCED_CONST_BITS + REDUCED_PASS1_BITS + 3 + 1) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 2] = limit[limitOffset + JpegUtils.DESCALE(tmp12 - tmp0, REDUCED_CONST_BITS + REDUCED_PASS1_BITS + 3 + 1) & RANGE_MASK]; workspaceIndex += JpegConstants.DCTSize; /* advance pointer to next row */ } } /// /// Perform dequantization and inverse DCT on one block of coefficients, /// producing a reduced-size 2x2 output block. /// private void jpeg_idct_2x2(int component_index, short[] coef_block, int output_row, int output_col) { /* buffers data between passes */ int[] workspace = new int[JpegConstants.DCTSize * 2]; /* Pass 1: process columns from input, store into work array. */ int coefBlockIndex = 0; int workspaceIndex = 0; int[] quantTable = m_dctTables[component_index].int_array; int quantTableIndex = 0; for (int ctr = JpegConstants.DCTSize; ctr > 0; coefBlockIndex++, quantTableIndex++, workspaceIndex++, ctr--) { /* Don't bother to process columns 2,4,6 */ if (ctr == JpegConstants.DCTSize - 2 || ctr == JpegConstants.DCTSize - 4 || ctr == JpegConstants.DCTSize - 6) continue; if (coef_block[coefBlockIndex + JpegConstants.DCTSize * 1] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 3] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 5] == 0 && coef_block[coefBlockIndex + JpegConstants.DCTSize * 7] == 0) { /* AC terms all zero; we need not examine terms 2,4,6 for 2x2 output */ int dcval = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]) << REDUCED_PASS1_BITS; workspace[workspaceIndex + JpegConstants.DCTSize * 0] = dcval; workspace[workspaceIndex + JpegConstants.DCTSize * 1] = dcval; continue; } /* Even part */ int z1 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 0], quantTable[quantTableIndex + JpegConstants.DCTSize * 0]); int tmp10 = z1 << (REDUCED_CONST_BITS + 2); /* Odd part */ z1 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 7], quantTable[quantTableIndex + JpegConstants.DCTSize * 7]); int tmp0 = z1 * -REDUCED_FIX_0_720959822; /* sqrt(2) * (c7-c5+c3-c1) */ z1 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 5], quantTable[quantTableIndex + JpegConstants.DCTSize * 5]); tmp0 += z1 * REDUCED_FIX_0_850430095; /* sqrt(2) * (-c1+c3+c5+c7) */ z1 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 3], quantTable[quantTableIndex + JpegConstants.DCTSize * 3]); tmp0 += z1 * (-REDUCED_FIX_1_272758580); /* sqrt(2) * (-c1+c3-c5-c7) */ z1 = REDUCED_DEQUANTIZE(coef_block[coefBlockIndex + JpegConstants.DCTSize * 1], quantTable[quantTableIndex + JpegConstants.DCTSize * 1]); tmp0 += z1 * REDUCED_FIX_3_624509785; /* sqrt(2) * (c1+c3+c5+c7) */ /* Final output stage */ workspace[workspaceIndex + JpegConstants.DCTSize * 0] = JpegUtils.DESCALE(tmp10 + tmp0, REDUCED_CONST_BITS - REDUCED_PASS1_BITS + 2); workspace[workspaceIndex + JpegConstants.DCTSize * 1] = JpegUtils.DESCALE(tmp10 - tmp0, REDUCED_CONST_BITS - REDUCED_PASS1_BITS + 2); } /* Pass 2: process 2 rows from work array, store into output array. */ workspaceIndex = 0; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset + JpegConstants.MediumSampleValue; for (int ctr = 0; ctr < 2; ctr++) { int currentOutRow = output_row + ctr; /* It's not clear whether a zero row test is worthwhile here ... */ if (workspace[workspaceIndex + 1] == 0 && workspace[workspaceIndex + 3] == 0 && workspace[workspaceIndex + 5] == 0 && workspace[workspaceIndex + 7] == 0) { /* AC terms all zero */ byte dcval = limit[limitOffset + JpegUtils.DESCALE(workspace[workspaceIndex + 0], REDUCED_PASS1_BITS + 3) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 0] = dcval; m_componentBuffer[currentOutRow][output_col + 1] = dcval; workspaceIndex += JpegConstants.DCTSize; /* advance pointer to next row */ continue; } /* Even part */ int tmp10 = (workspace[workspaceIndex + 0]) << (REDUCED_CONST_BITS + 2); /* Odd part */ int tmp0 = workspace[workspaceIndex + 7] * (-REDUCED_FIX_0_720959822) /* sqrt(2) * (c7-c5+c3-c1) */ + workspace[workspaceIndex + 5] * REDUCED_FIX_0_850430095 /* sqrt(2) * (-c1+c3+c5+c7) */ + workspace[workspaceIndex + 3] * (-REDUCED_FIX_1_272758580) /* sqrt(2) * (-c1+c3-c5-c7) */ + workspace[workspaceIndex + 1] * REDUCED_FIX_3_624509785; /* sqrt(2) * (c1+c3+c5+c7) */ /* Final output stage */ m_componentBuffer[currentOutRow][output_col + 0] = limit[limitOffset + JpegUtils.DESCALE(tmp10 + tmp0, REDUCED_CONST_BITS + REDUCED_PASS1_BITS + 3 + 2) & RANGE_MASK]; m_componentBuffer[currentOutRow][output_col + 1] = limit[limitOffset + JpegUtils.DESCALE(tmp10 - tmp0, REDUCED_CONST_BITS + REDUCED_PASS1_BITS + 3 + 2) & RANGE_MASK]; workspaceIndex += JpegConstants.DCTSize; /* advance pointer to next row */ } } /// /// Perform dequantization and inverse DCT on one block of coefficients, /// producing a reduced-size 1x1 output block. /// private void jpeg_idct_1x1(int component_index, short[] coef_block, int output_row, int output_col) { /* We hardly need an inverse DCT routine for this: just take the * average pixel value, which is one-eighth of the DC coefficient. */ int[] quantptr = m_dctTables[component_index].int_array; int dcval = REDUCED_DEQUANTIZE(coef_block[0], quantptr[0]); dcval = JpegUtils.DESCALE(dcval, 3); byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset + JpegConstants.MediumSampleValue; m_componentBuffer[output_row + 0][output_col] = limit[limitOffset + dcval & RANGE_MASK]; } /// /// Dequantize a coefficient by multiplying it by the multiplier-table /// entry; produce an int result. In this module, both inputs and result /// are 16 bits or less, so either int or short multiply will work. /// private static int REDUCED_DEQUANTIZE(short coef, int quantval) { return ((int)coef * quantval); } } #endregion #region JpegMarker /// /// Representation of special JPEG marker. /// /// You can't create instance of this class manually. /// Concrete objects are instantiated by library and you can get them /// through Marker_list property. /// /// /// Special markers public class JpegMarker { /// /// marker code: JPEG_COM, or JPEG_APP0+n /// private byte m_marker; /// /// # bytes of data in the file /// private int m_originalLength; /// /// the data contained in the marker /// private byte[] m_data; internal JpegMarker(byte marker, int originalDataLength, int lengthLimit) { m_marker = marker; m_originalLength = originalDataLength; m_data = new byte[lengthLimit]; } /// /// Gets the special marker. /// /// The marker value. public byte Marker { get { return m_marker; } } /// /// Gets the full length of original data associated with the marker. /// /// The length of original data associated with the marker. /// This length excludes the marker length word, whereas the stored representation /// within the JPEG file includes it. (Hence the maximum data length is really only 65533.) /// public int OriginalLength { get { return m_originalLength; } } /// /// Gets the data associated with the marker. /// /// The data associated with the marker. /// The length of this array doesn't exceed length_limit for the particular marker type. /// Note that this length excludes the marker length word, whereas the stored representation /// within the JPEG file includes it. (Hence the maximum data length is really only 65533.) /// public byte[] Data { get { return m_data; } } } #endregion #region JpegMarkerReader /// /// Marker reading and parsing /// class JpegMarkerReader { private const int APP0_DATA_LEN = 14; /* Length of interesting data in APP0 */ private const int APP14_DATA_LEN = 12; /* Length of interesting data in APP14 */ private const int APPN_DATA_LEN = 14; /* Must be the largest of the above!! */ private JpegDecompressor m_cinfo; /* Application-overridable marker processing methods */ private JpegDecompressor.jpeg_marker_parser_method m_process_COM; private JpegDecompressor.jpeg_marker_parser_method[] m_process_APPn = new JpegDecompressor.jpeg_marker_parser_method[16]; /* Limit on marker data length to save for each marker type */ private int m_length_limit_COM; private int[] m_length_limit_APPn = new int[16]; private bool m_saw_SOI; /* found SOI? */ private bool m_saw_SOF; /* found SOF? */ private int m_next_restart_num; /* next restart number expected (0-7) */ private int m_discarded_bytes; /* # of bytes skipped looking for a marker */ /* Status of COM/APPn marker saving */ private JpegMarker m_cur_marker; /* null if not processing a marker */ private int m_bytes_read; /* data bytes read so far in marker */ /* Note: cur_marker is not linked into marker_list until it's all read. */ /// /// Initialize the marker reader module. /// This is called only once, when the decompression object is created. /// public JpegMarkerReader(JpegDecompressor cinfo) { m_cinfo = cinfo; /* Initialize COM/APPn processing. * By default, we examine and then discard APP0 and APP14, * but simply discard COM and all other APPn. */ m_process_COM = skip_variable; for (int i = 0; i < 16; i++) { m_process_APPn[i] = skip_variable; m_length_limit_APPn[i] = 0; } m_process_APPn[0] = get_interesting_appn; m_process_APPn[14] = get_interesting_appn; /* Reset marker processing state */ reset_marker_reader(); } /// /// Reset marker processing state to begin a fresh datastream. /// public void reset_marker_reader() { m_cinfo.Comp_info = null; /* until allocated by get_sof */ m_cinfo.m_input_scan_number = 0; /* no SOS seen yet */ m_cinfo.m_unread_marker = 0; /* no pending marker */ m_saw_SOI = false; /* set internal state too */ m_saw_SOF = false; m_discarded_bytes = 0; m_cur_marker = null; } /// /// Read markers until SOS or EOI. /// /// Returns same codes as are defined for jpeg_consume_input: /// JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. /// public ReadResult read_markers() { /* Outer loop repeats once for each marker. */ for (; ; ) { /* Collect the marker proper, unless we already did. */ /* NB: first_marker() enforces the requirement that SOI appear first. */ if (m_cinfo.m_unread_marker == 0) { if (!m_cinfo.m_marker.m_saw_SOI) { if (!first_marker()) return ReadResult.Suspended; } else { if (!next_marker()) return ReadResult.Suspended; } } /* At this point m_cinfo.unread_marker contains the marker code and the * input point is just past the marker proper, but before any parameters. * A suspension will cause us to return with this state still true. */ switch ((JpegMarkerType)m_cinfo.m_unread_marker) { case JpegMarkerType.SOI: if (!get_soi()) return ReadResult.Suspended; break; case JpegMarkerType.SOF0: /* Baseline */ case JpegMarkerType.SOF1: /* Extended sequential, Huffman */ if (!get_sof(false)) return ReadResult.Suspended; break; case JpegMarkerType.SOF2: /* Progressive, Huffman */ if (!get_sof(true)) return ReadResult.Suspended; break; /* Currently unsupported SOFn types */ case JpegMarkerType.SOF3: /* Lossless, Huffman */ case JpegMarkerType.SOF5: /* Differential sequential, Huffman */ case JpegMarkerType.SOF6: /* Differential progressive, Huffman */ case JpegMarkerType.SOF7: /* Differential lossless, Huffman */ case JpegMarkerType.SOF9: /* Extended sequential, arithmetic */ case JpegMarkerType.SOF10: /* Progressive, arithmetic */ case JpegMarkerType.JPG: /* Reserved for JPEG extensions */ case JpegMarkerType.SOF11: /* Lossless, arithmetic */ case JpegMarkerType.SOF13: /* Differential sequential, arithmetic */ case JpegMarkerType.SOF14: /* Differential progressive, arithmetic */ case JpegMarkerType.SOF15: /* Differential lossless, arithmetic */ throw new Exception(String.Format("Unsupported JPEG process: SOF type 0x{0:X2}", m_cinfo.m_unread_marker)); case JpegMarkerType.SOS: if (!get_sos()) return ReadResult.Suspended; m_cinfo.m_unread_marker = 0; /* processed the marker */ return ReadResult.Reached_SOS; case JpegMarkerType.EOI: m_cinfo.m_unread_marker = 0; /* processed the marker */ return ReadResult.Reached_EOI; case JpegMarkerType.DAC: if (!skip_variable(m_cinfo)) return ReadResult.Suspended; break; case JpegMarkerType.DHT: if (!get_dht()) return ReadResult.Suspended; break; case JpegMarkerType.DQT: if (!get_dqt()) return ReadResult.Suspended; break; case JpegMarkerType.DRI: if (!get_dri()) return ReadResult.Suspended; break; case JpegMarkerType.APP0: case JpegMarkerType.APP1: case JpegMarkerType.APP2: case JpegMarkerType.APP3: case JpegMarkerType.APP4: case JpegMarkerType.APP5: case JpegMarkerType.APP6: case JpegMarkerType.APP7: case JpegMarkerType.APP8: case JpegMarkerType.APP9: case JpegMarkerType.APP10: case JpegMarkerType.APP11: case JpegMarkerType.APP12: case JpegMarkerType.APP13: case JpegMarkerType.APP14: case JpegMarkerType.APP15: if (!m_cinfo.m_marker.m_process_APPn[m_cinfo.m_unread_marker - (int)JpegMarkerType.APP0](m_cinfo)) return ReadResult.Suspended; break; case JpegMarkerType.COM: if (!m_cinfo.m_marker.m_process_COM(m_cinfo)) return ReadResult.Suspended; break; /* these are all parameter-less */ case JpegMarkerType.RST0: case JpegMarkerType.RST1: case JpegMarkerType.RST2: case JpegMarkerType.RST3: case JpegMarkerType.RST4: case JpegMarkerType.RST5: case JpegMarkerType.RST6: case JpegMarkerType.RST7: case JpegMarkerType.TEM: break; case JpegMarkerType.DNL: /* Ignore DNL ... perhaps the wrong thing */ if (!skip_variable(m_cinfo)) return ReadResult.Suspended; break; default: /* must be DHP, EXP, JPGn, or RESn */ /* For now, we treat the reserved markers as fatal errors since they are * likely to be used to signal incompatible JPEG Part 3 extensions. * Once the JPEG 3 version-number marker is well defined, this code * ought to change! */ throw new Exception(String.Format("Unsupported marker type 0x{0:X2}", m_cinfo.m_unread_marker)); } /* Successfully processed marker, so reset state variable */ m_cinfo.m_unread_marker = 0; } /* end loop */ } /// /// Read a restart marker, which is expected to appear next in the data-stream; /// if the marker is not there, take appropriate recovery action. /// Returns false if suspension is required. /// /// Made public for use by entropy decoder only /// /// This is called by the entropy decoder after it has read an appropriate /// number of MCUs. cinfo.unread_marker may be nonzero if the entropy decoder /// has already read a marker from the data source. Under normal conditions /// cinfo.unread_marker will be reset to 0 before returning; if not reset, /// it holds a marker which the decoder will be unable to read past. /// public bool read_restart_marker() { /* Obtain a marker unless we already did. */ /* Note that next_marker will complain if it skips any data. */ if (m_cinfo.m_unread_marker == 0) { if (!next_marker()) return false; } if (m_cinfo.m_unread_marker == ((int)JpegMarkerType.RST0 + m_cinfo.m_marker.m_next_restart_num)) { /* Normal case --- swallow the marker and let entropy decoder continue */ m_cinfo.m_unread_marker = 0; } else { /* Uh-oh, the restart markers have been messed up. */ /* Let the data source manager determine how to re-sync. */ if (!m_cinfo.m_src.resync_to_restart(m_cinfo, m_cinfo.m_marker.m_next_restart_num)) return false; } /* Update next-restart state */ m_cinfo.m_marker.m_next_restart_num = (m_cinfo.m_marker.m_next_restart_num + 1) & 7; return true; } /// /// Find the next JPEG marker, save it in cinfo.unread_marker. /// Returns false if had to suspend before reaching a marker; /// in that case cinfo.unread_marker is unchanged. /// /// Note that the result might not be a valid marker code, /// but it will never be 0 or FF. /// public bool next_marker() { int c; for (; ; ) { if (!m_cinfo.m_src.GetByte(out c)) return false; /* Skip any non-FF bytes. * This may look a bit inefficient, but it will not occur in a valid file. * We sync after each discarded byte so that a suspending data source * can discard the byte from its buffer. */ while (c != 0xFF) { m_cinfo.m_marker.m_discarded_bytes++; if (!m_cinfo.m_src.GetByte(out c)) return false; } /* This loop swallows any duplicate FF bytes. Extra FFs are legal as * pad bytes, so don't count them in discarded_bytes. We assume there * will not be so many consecutive FF bytes as to overflow a suspending * data source's input buffer. */ do { if (!m_cinfo.m_src.GetByte(out c)) return false; } while (c == 0xFF); if (c != 0) { /* found a valid marker, exit loop */ break; } /* Reach here if we found a stuffed-zero data sequence (FF/00). * Discard it and loop back to try again. */ m_cinfo.m_marker.m_discarded_bytes += 2; } if (m_cinfo.m_marker.m_discarded_bytes != 0) { m_cinfo.m_marker.m_discarded_bytes = 0; } m_cinfo.m_unread_marker = c; return true; } /// /// Install a special processing method for COM or APPn markers. /// public void jpeg_set_marker_processor(int marker_code, JpegDecompressor.jpeg_marker_parser_method routine) { if (marker_code == (int)JpegMarkerType.COM) m_process_COM = routine; else if (marker_code >= (int)JpegMarkerType.APP0 && marker_code <= (int)JpegMarkerType.APP15) m_process_APPn[marker_code - (int)JpegMarkerType.APP0] = routine; else throw new Exception(String.Format("Unsupported marker type 0x{0:X2}", marker_code)); } public void jpeg_save_markers(int marker_code, int length_limit) { /* Choose processor routine to use. * APP0/APP14 have special requirements. */ JpegDecompressor.jpeg_marker_parser_method processor; if (length_limit != 0) { processor = save_marker; /* If saving APP0/APP14, save at least enough for our internal use. */ if (marker_code == (int)JpegMarkerType.APP0 && length_limit < APP0_DATA_LEN) length_limit = APP0_DATA_LEN; else if (marker_code == (int)JpegMarkerType.APP14 && length_limit < APP14_DATA_LEN) length_limit = APP14_DATA_LEN; } else { processor = skip_variable; /* If discarding APP0/APP14, use our regular on-the-fly processor. */ if (marker_code == (int)JpegMarkerType.APP0 || marker_code == (int)JpegMarkerType.APP14) processor = get_interesting_appn; } if (marker_code == (int)JpegMarkerType.COM) { m_process_COM = processor; m_length_limit_COM = length_limit; } else if (marker_code >= (int)JpegMarkerType.APP0 && marker_code <= (int)JpegMarkerType.APP15) { m_process_APPn[marker_code - (int)JpegMarkerType.APP0] = processor; m_length_limit_APPn[marker_code - (int)JpegMarkerType.APP0] = length_limit; } else throw new Exception(String.Format("Unsupported marker type 0x{0:X2}", marker_code)); } /* State of marker reader, applications * supplying COM or APPn handlers might like to know the state. */ public bool SawSOI() { return m_saw_SOI; } public bool SawSOF() { return m_saw_SOF; } public int NextRestartNumber() { return m_next_restart_num; } public int DiscardedByteCount() { return m_discarded_bytes; } public void SkipBytes(int count) { m_discarded_bytes += count; } /// /// Save an APPn or COM marker into the marker list /// private static bool save_marker(JpegDecompressor cinfo) { JpegMarker cur_marker = cinfo.m_marker.m_cur_marker; byte[] data = null; int length = 0; int bytes_read; int data_length; int dataOffset = 0; if (cur_marker == null) { /* begin reading a marker */ if (!cinfo.m_src.GetTwoBytes(out length)) return false; length -= 2; if (length >= 0) { /* watch out for bogus length word */ /* figure out how much we want to save */ int limit; if (cinfo.m_unread_marker == (int)JpegMarkerType.COM) limit = cinfo.m_marker.m_length_limit_COM; else limit = cinfo.m_marker.m_length_limit_APPn[cinfo.m_unread_marker - (int)JpegMarkerType.APP0]; if (length < limit) limit = length; /* allocate and initialize the marker item */ cur_marker = new JpegMarker((byte)cinfo.m_unread_marker, length, limit); /* data area is just beyond the JpegMarker */ data = cur_marker.Data; cinfo.m_marker.m_cur_marker = cur_marker; cinfo.m_marker.m_bytes_read = 0; bytes_read = 0; data_length = limit; } else { /* deal with bogus length word */ bytes_read = data_length = 0; data = null; } } else { /* resume reading a marker */ bytes_read = cinfo.m_marker.m_bytes_read; data_length = cur_marker.Data.Length; data = cur_marker.Data; dataOffset = bytes_read; } byte[] tempData = null; if (data_length != 0) tempData = new byte[data.Length]; while (bytes_read < data_length) { /* move the restart point to here */ cinfo.m_marker.m_bytes_read = bytes_read; /* If there's not at least one byte in buffer, suspend */ if (!cinfo.m_src.MakeByteAvailable()) return false; /* Copy bytes with reasonable rapidity */ int read = cinfo.m_src.GetBytes(tempData, data_length - bytes_read); Buffer.BlockCopy(tempData, 0, data, dataOffset, data_length - bytes_read); bytes_read += read; } /* Done reading what we want to read */ if (cur_marker != null) { /* will be null if bogus length word */ /* Add new marker to end of list */ cinfo.m_marker_list.Add(cur_marker); /* Reset pointer & calc remaining data length */ data = cur_marker.Data; dataOffset = 0; length = cur_marker.OriginalLength - data_length; } /* Reset to initial state for next marker */ cinfo.m_marker.m_cur_marker = null; JpegMarkerType currentMarker = (JpegMarkerType)cinfo.m_unread_marker; if (data_length != 0 && (currentMarker == JpegMarkerType.APP0 || currentMarker == JpegMarkerType.APP14)) { tempData = new byte[data.Length]; Buffer.BlockCopy(data, dataOffset, tempData, 0, data.Length - dataOffset); } /* Process the marker if interesting; else just make a generic trace msg */ switch ((JpegMarkerType)cinfo.m_unread_marker) { case JpegMarkerType.APP0: examine_app0(cinfo, tempData, data_length, length); break; case JpegMarkerType.APP14: examine_app14(cinfo, tempData, data_length, length); break; default: break; } /* skip any remaining data -- could be lots */ if (length > 0) cinfo.m_src.skip_input_data(length); return true; } /// /// Skip over an unknown or uninteresting variable-length marker /// private static bool skip_variable(JpegDecompressor cinfo) { int length; if (!cinfo.m_src.GetTwoBytes(out length)) return false; length -= 2; if (length > 0) cinfo.m_src.skip_input_data(length); return true; } /// /// Process an APP0 or APP14 marker without saving it /// private static bool get_interesting_appn(JpegDecompressor cinfo) { int length; if (!cinfo.m_src.GetTwoBytes(out length)) return false; length -= 2; /* get the interesting part of the marker data */ int numtoread = 0; if (length >= APPN_DATA_LEN) numtoread = APPN_DATA_LEN; else if (length > 0) numtoread = length; byte[] b = new byte[APPN_DATA_LEN]; for (int i = 0; i < numtoread; i++) { int temp = 0; if (!cinfo.m_src.GetByte(out temp)) return false; b[i] = (byte)temp; } length -= numtoread; /* process it */ switch ((JpegMarkerType)cinfo.m_unread_marker) { case JpegMarkerType.APP0: examine_app0(cinfo, b, numtoread, length); break; case JpegMarkerType.APP14: examine_app14(cinfo, b, numtoread, length); break; default: /* can't get here unless jpeg_save_markers chooses wrong processor */ throw new Exception(String.Format("Unsupported marker type 0x{0:X2}", cinfo.m_unread_marker)); } /* skip any remaining data -- could be lots */ if (length > 0) cinfo.m_src.skip_input_data(length); return true; } /* * Routines for processing APPn and COM markers. * These are either saved in memory or discarded, per application request. * APP0 and APP14 are specially checked to see if they are * JFIF and Adobe markers, respectively. */ /// /// Examine first few bytes from an APP0. /// Take appropriate action if it is a JFIF marker. /// datalen is # of bytes at data[], remaining is length of rest of marker data. /// private static void examine_app0(JpegDecompressor cinfo, byte[] data, int datalen, int remaining) { int totallen = datalen + remaining; if (datalen >= APP0_DATA_LEN && data[0] == 0x4A && data[1] == 0x46 && data[2] == 0x49 && data[3] == 0x46 && data[4] == 0) { /* Found JFIF APP0 marker: save info */ cinfo.m_saw_JFIF_marker = true; cinfo.m_JFIF_major_version = data[5]; cinfo.m_JFIF_minor_version = data[6]; cinfo.m_density_unit = (DensityUnit)data[7]; cinfo.m_X_density = (short)((data[8] << 8) + data[9]); cinfo.m_Y_density = (short)((data[10] << 8) + data[11]); } else if (datalen >= 6 && data[0] == 0x4A && data[1] == 0x46 && data[2] == 0x58 && data[3] == 0x58 && data[4] == 0) { /* Found JFIF "JFXX" extension APP0 marker */ /* The library doesn't actually do anything with these. */ } else { /* Start of APP0 does not match "JFIF" or "JFXX", or too short */ } } /// /// Examine first few bytes from an APP14. /// Take appropriate action if it is an Adobe marker. /// datalen is # of bytes at data[], remaining is length of rest of marker data. /// private static void examine_app14(JpegDecompressor cinfo, byte[] data, int datalen, int remaining) { if (datalen >= APP14_DATA_LEN && data[0] == 0x41 && data[1] == 0x64 && data[2] == 0x6F && data[3] == 0x62 && data[4] == 0x65) { /* Found Adobe APP14 marker */ int version = (data[5] << 8) + data[6]; int flags0 = (data[7] << 8) + data[8]; int flags1 = (data[9] << 8) + data[10]; int transform = data[11]; cinfo.m_saw_Adobe_marker = true; cinfo.m_Adobe_transform = (byte)transform; } else { /* Start of APP14 does not match "Adobe", or too short */ } } /* * Routines to process JPEG markers. * * Entry condition: JPEG marker itself has been read and its code saved * in cinfo.unread_marker; input restart point is just after the marker. * * Exit: if return true, have read and processed any parameters, and have * updated the restart point to point after the parameters. * If return false, was forced to suspend before reaching end of * marker parameters; restart point has not been moved. Same routine * will be called again after application supplies more input data. * * This approach to suspension assumes that all of a marker's parameters * can fit into a single input buffer-load. This should hold for "normal" * markers. Some COM/APPn markers might have large parameter segments * that might not fit. If we are simply dropping such a marker, we use * skip_input_data to get past it, and thereby put the problem on the * source manager's shoulders. If we are saving the marker's contents * into memory, we use a slightly different convention: when forced to * suspend, the marker processor updates the restart point to the end of * what it's consumed (ie, the end of the buffer) before returning false. * On resumption, cinfo.unread_marker still contains the marker code, * but the data source will point to the next chunk of marker data. * The marker processor must retain internal state to deal with this. * * Note that we don't bother to avoid duplicate trace messages if a * suspension occurs within marker parameters. Other side effects * require more care. */ /// /// Process an SOI marker /// private bool get_soi() { if (m_cinfo.m_marker.m_saw_SOI) throw new Exception("Invalid JPEG file structure: two SOI markers"); /* Reset all parameters that are defined to be reset by SOI */ m_cinfo.m_restart_interval = 0; /* Set initial assumptions for colorspace etc */ m_cinfo.m_jpeg_color_space = ColorSpace.Unknown; m_cinfo.m_CCIR601_sampling = false; /* Assume non-CCIR sampling??? */ m_cinfo.m_saw_JFIF_marker = false; m_cinfo.m_JFIF_major_version = 1; /* set default JFIF APP0 values */ m_cinfo.m_JFIF_minor_version = 1; m_cinfo.m_density_unit = DensityUnit.Unknown; m_cinfo.m_X_density = 1; m_cinfo.m_Y_density = 1; m_cinfo.m_saw_Adobe_marker = false; m_cinfo.m_Adobe_transform = 0; m_cinfo.m_marker.m_saw_SOI = true; return true; } /// /// Process a SOFn marker /// private bool get_sof(bool is_prog) { m_cinfo.m_progressive_mode = is_prog; int length; if (!m_cinfo.m_src.GetTwoBytes(out length)) return false; if (!m_cinfo.m_src.GetByte(out m_cinfo.m_data_precision)) return false; int temp = 0; if (!m_cinfo.m_src.GetTwoBytes(out temp)) return false; m_cinfo.m_image_height = temp; if (!m_cinfo.m_src.GetTwoBytes(out temp)) return false; m_cinfo.m_image_width = temp; if (!m_cinfo.m_src.GetByte(out m_cinfo.m_num_components)) return false; length -= 8; if (m_cinfo.m_marker.m_saw_SOF) throw new Exception("Invalid JPEG file structure: two SOI markers"); /* We don't support files in which the image height is initially specified */ /* as 0 and is later redefined by DNL. As long as we have to check that, */ /* might as well have a general sanity check. */ if (m_cinfo.m_image_height <= 0 || m_cinfo.m_image_width <= 0 || m_cinfo.m_num_components <= 0) throw new Exception("Empty JPEG image (DNL not supported)"); if (length != (m_cinfo.m_num_components * 3)) throw new Exception("Bogus marker length"); if (m_cinfo.Comp_info == null) { /* do only once, even if suspend */ m_cinfo.Comp_info = JpegComponent.createArrayOfComponents(m_cinfo.m_num_components); } for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { m_cinfo.Comp_info[ci].Component_index = ci; int component_id; if (!m_cinfo.m_src.GetByte(out component_id)) return false; m_cinfo.Comp_info[ci].Component_id = component_id; int c; if (!m_cinfo.m_src.GetByte(out c)) return false; m_cinfo.Comp_info[ci].H_samp_factor = (c >> 4) & 15; m_cinfo.Comp_info[ci].V_samp_factor = (c) & 15; int quant_tbl_no; if (!m_cinfo.m_src.GetByte(out quant_tbl_no)) return false; m_cinfo.Comp_info[ci].Quant_tbl_no = quant_tbl_no; } m_cinfo.m_marker.m_saw_SOF = true; return true; } /// /// Process a SOS marker /// private bool get_sos() { if (!m_cinfo.m_marker.m_saw_SOF) throw new Exception("Invalid JPEG file structure: SOS before SOF"); int length; if (!m_cinfo.m_src.GetTwoBytes(out length)) return false; /* Number of components */ int n; if (!m_cinfo.m_src.GetByte(out n)) return false; if (length != (n * 2 + 6) || n < 1 || n > JpegConstants.MaxComponentsInScan) throw new Exception("Bogus marker length"); m_cinfo.m_comps_in_scan = n; /* Collect the component-spec parameters */ for (int i = 0; i < n; i++) { int cc; if (!m_cinfo.m_src.GetByte(out cc)) return false; int c; if (!m_cinfo.m_src.GetByte(out c)) return false; bool idFound = false; int foundIndex = -1; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { if (cc == m_cinfo.Comp_info[ci].Component_id) { foundIndex = ci; idFound = true; break; } } if (!idFound) throw new Exception(String.Format("Invalid component ID {0} in SOS", cc)); m_cinfo.m_cur_comp_info[i] = foundIndex; m_cinfo.Comp_info[foundIndex].Dc_tbl_no = (c >> 4) & 15; m_cinfo.Comp_info[foundIndex].Ac_tbl_no = (c) & 15; } /* Collect the additional scan parameters Ss, Se, Ah/Al. */ int temp; if (!m_cinfo.m_src.GetByte(out temp)) return false; m_cinfo.m_Ss = temp; if (!m_cinfo.m_src.GetByte(out temp)) return false; m_cinfo.m_Se = temp; if (!m_cinfo.m_src.GetByte(out temp)) return false; m_cinfo.m_Ah = (temp >> 4) & 15; m_cinfo.m_Al = (temp) & 15; /* Prepare to scan data & restart markers */ m_cinfo.m_marker.m_next_restart_num = 0; /* Count another SOS marker */ m_cinfo.m_input_scan_number++; return true; } /// /// Process a DHT marker /// private bool get_dht() { int length; if (!m_cinfo.m_src.GetTwoBytes(out length)) return false; length -= 2; byte[] bits = new byte[17]; byte[] huffval = new byte[256]; while (length > 16) { int index; if (!m_cinfo.m_src.GetByte(out index)) return false; bits[0] = 0; int count = 0; for (int i = 1; i <= 16; i++) { int temp = 0; if (!m_cinfo.m_src.GetByte(out temp)) return false; bits[i] = (byte)temp; count += bits[i]; } length -= 1 + 16; /* Here we just do minimal validation of the counts to avoid walking * off the end of our table space. HuffEntropyDecoder will check more carefully. */ if (count > 256 || count > length) throw new Exception("Bogus Huffman table definition"); for (int i = 0; i < count; i++) { int temp = 0; if (!m_cinfo.m_src.GetByte(out temp)) return false; huffval[i] = (byte)temp; } length -= count; JpegHuffmanTable htblptr = null; if ((index & 0x10) != 0) { /* AC table definition */ index -= 0x10; if (m_cinfo.m_ac_huff_tbl_ptrs[index] == null) m_cinfo.m_ac_huff_tbl_ptrs[index] = new JpegHuffmanTable(); htblptr = m_cinfo.m_ac_huff_tbl_ptrs[index]; } else { /* DC table definition */ if (m_cinfo.m_dc_huff_tbl_ptrs[index] == null) m_cinfo.m_dc_huff_tbl_ptrs[index] = new JpegHuffmanTable(); htblptr = m_cinfo.m_dc_huff_tbl_ptrs[index]; } if (index < 0 || index >= JpegConstants.NumberOfHuffmanTables) throw new Exception(String.Format("Bogus DHT index {0}", index)); Buffer.BlockCopy(bits, 0, htblptr.Bits, 0, htblptr.Bits.Length); Buffer.BlockCopy(huffval, 0, htblptr.Huffval, 0, htblptr.Huffval.Length); } if (length != 0) throw new Exception("Bogus marker length"); return true; } /// /// Process a DQT marker /// private bool get_dqt() { int length; if (!m_cinfo.m_src.GetTwoBytes(out length)) return false; length -= 2; while (length > 0) { int n; if (!m_cinfo.m_src.GetByte(out n)) return false; int prec = n >> 4; n &= 0x0F; if (n >= JpegConstants.NumberOfQuantTables) throw new Exception(String.Format("Bogus DQT index {0}", n)); if (m_cinfo.m_quant_tbl_ptrs[n] == null) m_cinfo.m_quant_tbl_ptrs[n] = new JpegQuantizationTable(); JpegQuantizationTable quant_ptr = m_cinfo.m_quant_tbl_ptrs[n]; for (int i = 0; i < JpegConstants.DCTSize2; i++) { int tmp; if (prec != 0) { int temp = 0; if (!m_cinfo.m_src.GetTwoBytes(out temp)) return false; tmp = temp; } else { int temp = 0; if (!m_cinfo.m_src.GetByte(out temp)) return false; tmp = temp; } /* We convert the zigzag-order table to natural array order. */ quant_ptr.quantval[JpegUtils.jpeg_natural_order[i]] = (short)tmp; } length -= JpegConstants.DCTSize2 + 1; if (prec != 0) length -= JpegConstants.DCTSize2; } if (length != 0) throw new Exception("Bogus marker length"); return true; } /// /// Process a DRI marker /// private bool get_dri() { int length; if (!m_cinfo.m_src.GetTwoBytes(out length)) return false; if (length != 4) throw new Exception("Bogus marker length"); int temp = 0; if (!m_cinfo.m_src.GetTwoBytes(out temp)) return false; int tmp = temp; m_cinfo.m_restart_interval = tmp; return true; } /// /// Like next_marker, but used to obtain the initial SOI marker. /// For this marker, we do not allow preceding garbage or fill; otherwise, /// we might well scan an entire input file before realizing it's not JPEG. /// If an application wants to process non-JFIF files, it must seek to the /// SOI before calling the JPEG library. /// private bool first_marker() { int c; if (!m_cinfo.m_src.GetByte(out c)) return false; int c2; if (!m_cinfo.m_src.GetByte(out c2)) return false; if (c != 0xFF || c2 != (int)JpegMarkerType.SOI) throw new Exception(String.Format("Not a JPEG file: starts with 0x{0:X2} 0x{1:X2}", c, c2)); m_cinfo.m_unread_marker = c2; return true; } } #endregion #region JpegMarkerType /// /// JPEG marker codes. /// /// Special markers public enum JpegMarkerType { /// /// /// SOF0 = 0xc0, /// /// /// SOF1 = 0xc1, /// /// /// SOF2 = 0xc2, /// /// /// SOF3 = 0xc3, /// /// /// SOF5 = 0xc5, /// /// /// SOF6 = 0xc6, /// /// /// SOF7 = 0xc7, /// /// /// JPG = 0xc8, /// /// /// SOF9 = 0xc9, /// /// /// SOF10 = 0xca, /// /// /// SOF11 = 0xcb, /// /// /// SOF13 = 0xcd, /// /// /// SOF14 = 0xce, /// /// /// SOF15 = 0xcf, /// /// /// DHT = 0xc4, /// /// /// DAC = 0xcc, /// /// /// RST0 = 0xd0, /// /// /// RST1 = 0xd1, /// /// /// RST2 = 0xd2, /// /// /// RST3 = 0xd3, /// /// /// RST4 = 0xd4, /// /// /// RST5 = 0xd5, /// /// /// RST6 = 0xd6, /// /// /// RST7 = 0xd7, /// /// /// SOI = 0xd8, /// /// /// EOI = 0xd9, /// /// /// SOS = 0xda, /// /// /// DQT = 0xdb, /// /// /// DNL = 0xdc, /// /// /// DRI = 0xdd, /// /// /// DHP = 0xde, /// /// /// EXP = 0xdf, /// /// /// APP0 = 0xe0, /// /// /// APP1 = 0xe1, /// /// /// APP2 = 0xe2, /// /// /// APP3 = 0xe3, /// /// /// APP4 = 0xe4, /// /// /// APP5 = 0xe5, /// /// /// APP6 = 0xe6, /// /// /// APP7 = 0xe7, /// /// /// APP8 = 0xe8, /// /// /// APP9 = 0xe9, /// /// /// APP10 = 0xea, /// /// /// APP11 = 0xeb, /// /// /// APP12 = 0xec, /// /// /// APP13 = 0xed, /// /// /// APP14 = 0xee, /// /// /// APP15 = 0xef, /// /// /// JPG0 = 0xf0, /// /// /// JPG13 = 0xfd, /// /// /// COM = 0xfe, /// /// /// TEM = 0x01, /// /// /// ERROR = 0x100 } #endregion #region JpegMarkerWriter /// /// Marker writing /// class JpegMarkerWriter { private JpegCompressor m_cinfo; private int m_last_restart_interval; /* last DRI value emitted; 0 after SOI */ public JpegMarkerWriter(JpegCompressor cinfo) { m_cinfo = cinfo; } /// /// Write datastream header. /// This consists of an SOI and optional APPn markers. /// We recommend use of the JFIF marker, but not the Adobe marker, /// when using YCbCr or grayscale data. The JFIF marker should NOT /// be used for any other JPEG colorspace. The Adobe marker is helpful /// to distinguish RGB, CMYK, and YCCK colorspaces. /// Note that an application can write additional header markers after /// jpeg_start_compress returns. /// public void write_file_header() { emit_marker(JpegMarkerType.SOI); /* first the SOI */ /* SOI is defined to reset restart interval to 0 */ m_last_restart_interval = 0; if (m_cinfo.m_write_JFIF_header) /* next an optional JFIF APP0 */ emit_jfif_app0(); if (m_cinfo.m_write_Adobe_marker) /* next an optional Adobe APP14 */ emit_adobe_app14(); } /// /// Write frame header. /// This consists of DQT and SOFn markers. /// Note that we do not emit the SOF until we have emitted the DQT(s). /// This avoids compatibility problems with incorrect implementations that /// try to error-check the quant table numbers as soon as they see the SOF. /// public void write_frame_header() { /* Emit DQT for each quantization table. * Note that emit_dqt() suppresses any duplicate tables. */ int prec = 0; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) prec += emit_dqt(m_cinfo.Component_info[ci].Quant_tbl_no); /* now prec is nonzero iff there are any 16-bit quant tables. */ /* Check for a non-baseline specification. * Note we assume that Huffman table numbers won't be changed later. */ bool is_baseline; if (m_cinfo.m_progressive_mode || m_cinfo.m_data_precision != 8) { is_baseline = false; } else { is_baseline = true; for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { if (m_cinfo.Component_info[ci].Dc_tbl_no > 1 || m_cinfo.Component_info[ci].Ac_tbl_no > 1) is_baseline = false; } if (prec != 0 && is_baseline) { is_baseline = false; } } /* Emit the proper SOF marker */ if (m_cinfo.m_progressive_mode) emit_sof(JpegMarkerType.SOF2); /* SOF code for progressive Huffman */ else if (is_baseline) emit_sof(JpegMarkerType.SOF0); /* SOF code for baseline implementation */ else emit_sof(JpegMarkerType.SOF1); /* SOF code for non-baseline Huffman file */ } /// /// Write scan header. /// This consists of DHT or DAC markers, optional DRI, and SOS. /// Compressed data will be written following the SOS. /// public void write_scan_header() { /* Emit Huffman tables. * Note that emit_dht() suppresses any duplicate tables. */ for (int i = 0; i < m_cinfo.m_comps_in_scan; i++) { int ac_tbl_no = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[i]].Ac_tbl_no; int dc_tbl_no = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[i]].Dc_tbl_no; if (m_cinfo.m_progressive_mode) { /* Progressive mode: only DC or only AC tables are used in one scan */ if (m_cinfo.m_Ss == 0) { if (m_cinfo.m_Ah == 0) { /* DC needs no table for refinement scan */ emit_dht(dc_tbl_no, false); } } else { emit_dht(ac_tbl_no, true); } } else { /* Sequential mode: need both DC and AC tables */ emit_dht(dc_tbl_no, false); emit_dht(ac_tbl_no, true); } } /* Emit DRI if required --- note that DRI value could change for each scan. * We avoid wasting space with unnecessary DRIs, however. */ if (m_cinfo.m_restart_interval != m_last_restart_interval) { emit_dri(); m_last_restart_interval = m_cinfo.m_restart_interval; } emit_sos(); } /// /// Write datastream trailer. /// public void write_file_trailer() { emit_marker(JpegMarkerType.EOI); } /// /// Write an abbreviated table-specification datastream. /// This consists of SOI, DQT and DHT tables, and EOI. /// Any table that is defined and not marked sent_table = true will be /// emitted. Note that all tables will be marked sent_table = true at exit. /// public void write_tables_only() { emit_marker(JpegMarkerType.SOI); for (int i = 0; i < JpegConstants.NumberOfQuantTables; i++) { if (m_cinfo.m_quant_tbl_ptrs[i] != null) emit_dqt(i); } for (int i = 0; i < JpegConstants.NumberOfHuffmanTables; i++) { if (m_cinfo.m_dc_huff_tbl_ptrs[i] != null) emit_dht(i, false); if (m_cinfo.m_ac_huff_tbl_ptrs[i] != null) emit_dht(i, true); } emit_marker(JpegMarkerType.EOI); } ////////////////////////////////////////////////////////////////////////// // These routines allow writing an arbitrary marker with parameters. // The only intended use is to emit COM or APPn markers after calling // write_file_header and before calling write_frame_header. // Other uses are not guaranteed to produce desirable results. // Counting the parameter bytes properly is the caller's responsibility. /// /// Emit an arbitrary marker header /// public void write_marker_header(int marker, int datalen) { if (datalen > 65533) /* safety check */ throw new Exception("Bogus marker length"); emit_marker((JpegMarkerType)marker); emit_2bytes(datalen + 2); /* total length */ } /// /// Emit one byte of marker parameters following write_marker_header /// public void write_marker_byte(byte val) { emit_byte(val); } ////////////////////////////////////////////////////////////////////////// // Routines to write specific marker types. // /// /// Emit a SOS marker /// private void emit_sos() { emit_marker(JpegMarkerType.SOS); emit_2bytes(2 * m_cinfo.m_comps_in_scan + 2 + 1 + 3); /* length */ emit_byte(m_cinfo.m_comps_in_scan); for (int i = 0; i < m_cinfo.m_comps_in_scan; i++) { int componentIndex = m_cinfo.m_cur_comp_info[i]; emit_byte(m_cinfo.Component_info[componentIndex].Component_id); int td = m_cinfo.Component_info[componentIndex].Dc_tbl_no; int ta = m_cinfo.Component_info[componentIndex].Ac_tbl_no; if (m_cinfo.m_progressive_mode) { /* Progressive mode: only DC or only AC tables are used in one scan; * furthermore, Huffman coding of DC refinement uses no table at all. * We emit 0 for unused field(s); this is recommended by the P&M text * but does not seem to be specified in the standard. */ if (m_cinfo.m_Ss == 0) { /* DC scan */ ta = 0; if (m_cinfo.m_Ah != 0) { /* no DC table either */ td = 0; } } else { /* AC scan */ td = 0; } } emit_byte((td << 4) + ta); } emit_byte(m_cinfo.m_Ss); emit_byte(m_cinfo.m_Se); emit_byte((m_cinfo.m_Ah << 4) + m_cinfo.m_Al); } /// /// Emit a SOF marker /// private void emit_sof(JpegMarkerType code) { emit_marker(code); emit_2bytes(3 * m_cinfo.m_num_components + 2 + 5 + 1); /* length */ /* Make sure image isn't bigger than SOF field can handle */ if (m_cinfo.m_image_height > 65535 || m_cinfo.m_image_width > 65535) throw new Exception(String.Format("Maximum supported image dimension is {0} pixels", 65535)); emit_byte(m_cinfo.m_data_precision); emit_2bytes(m_cinfo.m_image_height); emit_2bytes(m_cinfo.m_image_width); emit_byte(m_cinfo.m_num_components); for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[ci]; emit_byte(componentInfo.Component_id); emit_byte((componentInfo.H_samp_factor << 4) + componentInfo.V_samp_factor); emit_byte(componentInfo.Quant_tbl_no); } } /// /// Emit an Adobe APP14 marker /// private void emit_adobe_app14() { /* * Length of APP14 block (2 bytes) * Block ID (5 bytes - ASCII "Adobe") * Version Number (2 bytes - currently 100) * Flags0 (2 bytes - currently 0) * Flags1 (2 bytes - currently 0) * Color transform (1 byte) * * Although Adobe TN 5116 mentions Version = 101, all the Adobe files * now in circulation seem to use Version = 100, so that's what we write. * * We write the color transform byte as 1 if the JPEG color space is * YCbCr, 2 if it's YCCK, 0 otherwise. Adobe's definition has to do with * whether the encoder performed a transformation, which is pretty useless. */ emit_marker(JpegMarkerType.APP14); emit_2bytes(2 + 5 + 2 + 2 + 2 + 1); /* length */ emit_byte(0x41); /* Identifier: ASCII "Adobe" */ emit_byte(0x64); emit_byte(0x6F); emit_byte(0x62); emit_byte(0x65); emit_2bytes(100); /* Version */ emit_2bytes(0); /* Flags0 */ emit_2bytes(0); /* Flags1 */ switch (m_cinfo.m_jpeg_color_space) { case ColorSpace.YCbCr: emit_byte(1); /* Color transform = 1 */ break; case ColorSpace.YCCK: emit_byte(2); /* Color transform = 2 */ break; default: emit_byte(0); /* Color transform = 0 */ break; } } /// /// Emit a DRI marker /// private void emit_dri() { emit_marker(JpegMarkerType.DRI); emit_2bytes(4); /* fixed length */ emit_2bytes(m_cinfo.m_restart_interval); } /// /// Emit a DHT marker /// private void emit_dht(int index, bool is_ac) { JpegHuffmanTable htbl = m_cinfo.m_dc_huff_tbl_ptrs[index]; if (is_ac) { htbl = m_cinfo.m_ac_huff_tbl_ptrs[index]; index += 0x10; /* output index has AC bit set */ } if (htbl == null) throw new Exception(String.Format("Huffman table 0x{0:X2} was not defined", index)); if (!htbl.Sent_table) { emit_marker(JpegMarkerType.DHT); int length = 0; for (int i = 1; i <= 16; i++) length += htbl.Bits[i]; emit_2bytes(length + 2 + 1 + 16); emit_byte(index); for (int i = 1; i <= 16; i++) emit_byte(htbl.Bits[i]); for (int i = 0; i < length; i++) emit_byte(htbl.Huffval[i]); htbl.Sent_table = true; } } /// /// Emit a DQT marker /// /// The index. /// the precision used (0 = 8bits, 1 = 16bits) for baseline checking private int emit_dqt(int index) { JpegQuantizationTable qtbl = m_cinfo.m_quant_tbl_ptrs[index]; if (qtbl == null) throw new Exception(String.Format("Quantization table 0x{0:X2} was not defined", index)); int prec = 0; for (int i = 0; i < JpegConstants.DCTSize2; i++) { if (qtbl.quantval[i] > 255) prec = 1; } if (!qtbl.Sent_table) { emit_marker(JpegMarkerType.DQT); emit_2bytes(prec != 0 ? JpegConstants.DCTSize2 * 2 + 1 + 2 : JpegConstants.DCTSize2 + 1 + 2); emit_byte(index + (prec << 4)); for (int i = 0; i < JpegConstants.DCTSize2; i++) { /* The table entries must be emitted in zigzag order. */ int qval = qtbl.quantval[JpegUtils.jpeg_natural_order[i]]; if (prec != 0) emit_byte(qval >> 8); emit_byte(qval & 0xFF); } qtbl.Sent_table = true; } return prec; } /// /// Emit a JFIF-compliant APP0 marker /// private void emit_jfif_app0() { /* * Length of APP0 block (2 bytes) * Block ID (4 bytes - ASCII "JFIF") * Zero byte (1 byte to terminate the ID string) * Version Major, Minor (2 bytes - major first) * Units (1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm) * Xdpu (2 bytes - dots per unit horizontal) * Ydpu (2 bytes - dots per unit vertical) * Thumbnail X size (1 byte) * Thumbnail Y size (1 byte) */ emit_marker(JpegMarkerType.APP0); emit_2bytes(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); /* length */ emit_byte(0x4A); /* Identifier: ASCII "JFIF" */ emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); emit_byte(0); emit_byte(m_cinfo.m_JFIF_major_version); /* Version fields */ emit_byte(m_cinfo.m_JFIF_minor_version); emit_byte((int)m_cinfo.m_density_unit); /* Pixel size information */ emit_2bytes(m_cinfo.m_X_density); emit_2bytes(m_cinfo.m_Y_density); emit_byte(0); /* No thumbnail image */ emit_byte(0); } ////////////////////////////////////////////////////////////////////////// // Basic output routines. // // Note that we do not support suspension while writing a marker. // Therefore, an application using suspension must ensure that there is // enough buffer space for the initial markers (typ. 600-700 bytes) before // calling jpeg_start_compress, and enough space to write the trailing EOI // (a few bytes) before calling jpeg_finish_compress. Multi-pass compression // modes are not supported at all with suspension, so those two are the only // points where markers will be written. /// /// Emit a marker code /// private void emit_marker(JpegMarkerType mark) { emit_byte(0xFF); emit_byte((int)mark); } /// /// Emit a 2-byte integer; these are always MSB first in JPEG files /// private void emit_2bytes(int value) { emit_byte((value >> 8) & 0xFF); emit_byte(value & 0xFF); } /// /// Emit a byte /// private void emit_byte(int val) { if (!m_cinfo.m_dest.emit_byte(val)) throw new Exception("Suspension not allowed here"); } } #endregion #region JpegQuantizationTable /// /// DCT coefficient quantization table. /// public class JpegQuantizationTable { /// /// This field is used only during compression. It's initialized false when /// the table is created, and set true when it's been output to the file. /// You could suppress output of a table by setting this to true. /// private bool m_sent_table; /// /// This array gives the coefficient quantizers in natural array order /// (not the zigzag order in which they are stored in a JPEG DQT marker). /// CAUTION: IJG versions prior to v6a kept this array in zigzag order. /// internal readonly short[] quantval = new short[JpegConstants.DCTSize2]; internal JpegQuantizationTable() { } /// /// Gets or sets a value indicating whether the table has been output to file. /// /// It's initialized false when the table is created, and set /// true when it's been output to the file. You could suppress output of a table by setting this to true. /// /// This property is used only during compression. /// public bool Sent_table { get { return m_sent_table; } set { m_sent_table = value; } } } #endregion #region JpegScanInfo /// /// The script for encoding a multiple-scan file is an array of these: /// class JpegScanInfo { public int comps_in_scan; /* number of components encoded in this scan */ public int[] component_index = new int[JpegConstants.MaxComponentsInScan]; /* their SOF/comp_info[] indexes */ public int Ss; public int Se; /* progressive JPEG spectral selection parms */ public int Ah; public int Al; /* progressive JPEG successive approx. parms */ } #endregion #region JpegSource /// /// Data source object for decompression. /// public abstract class Jpeg_Source { private byte[] m_next_input_byte; private int m_bytes_in_buffer; /* # of bytes remaining (unread) in buffer */ private int m_position; /// /// Initializes this instance. /// public abstract void init_source(); /// /// Fills input buffer /// /// true if operation succeed; otherwise, false public abstract bool fill_input_buffer(); /// /// Initializes the internal buffer. /// /// The buffer. /// The size. protected void initInternalBuffer(byte[] buffer, int size) { m_bytes_in_buffer = size; m_next_input_byte = buffer; m_position = 0; } /// /// Skip data - used to skip over a potentially large amount of /// uninteresting data (such as an APPn marker). /// /// The number of bytes to skip. /// Writers of suspendable-input applications must note that skip_input_data /// is not granted the right to give a suspension return. If the skip extends /// beyond the data currently in the buffer, the buffer can be marked empty so /// that the next read will cause a fill_input_buffer call that can suspend. /// Arranging for additional bytes to be discarded before reloading the input /// buffer is the application writer's problem. public virtual void skip_input_data(int num_bytes) { /* Just a dumb implementation for now. Could use fseek() except * it doesn't work on pipes. Not clear that being smart is worth * any trouble anyway --- large skips are infrequent. */ if (num_bytes > 0) { while (num_bytes > m_bytes_in_buffer) { num_bytes -= m_bytes_in_buffer; fill_input_buffer(); /* note we assume that fill_input_buffer will never return false, * so suspension need not be handled. */ } m_position += num_bytes; m_bytes_in_buffer -= num_bytes; } } /// /// This is the default resync_to_restart method for data source /// managers to use if they don't have any better approach. /// /// An instance of /// The desired /// false if suspension is required. /// That method assumes that no backtracking is possible. /// Some data source managers may be able to back up, or may have /// additional knowledge about the data which permits a more /// intelligent recovery strategy; such managers would /// presumably supply their own resync method.

/// /// read_restart_marker calls resync_to_restart if it finds a marker other than /// the restart marker it was expecting. (This code is *not* used unless /// a nonzero restart interval has been declared.) cinfo.unread_marker is /// the marker code actually found (might be anything, except 0 or FF). /// The desired restart marker number (0..7) is passed as a parameter.

/// /// This routine is supposed to apply whatever error recovery strategy seems /// appropriate in order to position the input stream to the next data segment. /// Note that cinfo.unread_marker is treated as a marker appearing before /// the current data-source input point; usually it should be reset to zero /// before returning.

/// /// This implementation is substantially constrained by wanting to treat the /// input as a data stream; this means we can't back up. Therefore, we have /// only the following actions to work with:
/// 1. Simply discard the marker and let the entropy decoder resume at next /// byte of file.
/// 2. Read forward until we find another marker, discarding intervening /// data. (In theory we could look ahead within the current bufferload, /// without having to discard data if we don't find the desired marker. /// This idea is not implemented here, in part because it makes behavior /// dependent on buffer size and chance buffer-boundary positions.)
/// 3. Leave the marker unread (by failing to zero cinfo.unread_marker). /// This will cause the entropy decoder to process an empty data segment, /// inserting dummy zeroes, and then we will reprocess the marker.
/// /// #2 is appropriate if we think the desired marker lies ahead, while #3 is /// appropriate if the found marker is a future restart marker (indicating /// that we have missed the desired restart marker, probably because it got /// corrupted).
/// We apply #2 or #3 if the found marker is a restart marker no more than /// two counts behind or ahead of the expected one. We also apply #2 if the /// found marker is not a legal JPEG marker code (it's certainly bogus data). /// If the found marker is a restart marker more than 2 counts away, we do #1 /// (too much risk that the marker is erroneous; with luck we will be able to /// resync at some future point).
/// For any valid non-restart JPEG marker, we apply #3. This keeps us from /// overrunning the end of a scan. An implementation limited to single-scan /// files might find it better to apply #2 for markers other than EOI, since /// any other marker would have to be bogus data in that case.
public virtual bool resync_to_restart(JpegDecompressor cinfo, int desired) { /* Outer loop handles repeated decision after scanning forward. */ int action = 1; for (; ; ) { if (cinfo.m_unread_marker < (int)JpegMarkerType.SOF0) { /* invalid marker */ action = 2; } else if (cinfo.m_unread_marker < (int)JpegMarkerType.RST0 || cinfo.m_unread_marker > (int)JpegMarkerType.RST7) { /* valid non-restart marker */ action = 3; } else { if (cinfo.m_unread_marker == ((int)JpegMarkerType.RST0 + ((desired + 1) & 7)) || cinfo.m_unread_marker == ((int)JpegMarkerType.RST0 + ((desired + 2) & 7))) { /* one of the next two expected restarts */ action = 3; } else if (cinfo.m_unread_marker == ((int)JpegMarkerType.RST0 + ((desired - 1) & 7)) || cinfo.m_unread_marker == ((int)JpegMarkerType.RST0 + ((desired - 2) & 7))) { /* a prior restart, so advance */ action = 2; } else { /* desired restart or too far away */ action = 1; } } switch (action) { case 1: /* Discard marker and let entropy decoder resume processing. */ cinfo.m_unread_marker = 0; return true; case 2: /* Scan to the next marker, and repeat the decision loop. */ if (!cinfo.m_marker.next_marker()) return false; break; case 3: /* Return without advancing past this marker. */ /* Entropy decoder will be forced to process an empty segment. */ return true; } } } /// /// Terminate source - called by jpeg_finish_decompress /// after all data has been read. Often a no-op. /// /// NB: not called by jpeg_abort or jpeg_destroy; surrounding /// application must deal with any cleanup that should happen even /// for error exit. public virtual void term_source() { } /// /// Reads two bytes interpreted as an unsigned 16-bit integer. /// /// The result. /// true if operation succeed; otherwise, false public virtual bool GetTwoBytes(out int V) { if (!MakeByteAvailable()) { V = 0; return false; } m_bytes_in_buffer--; V = m_next_input_byte[m_position] << 8; m_position++; if (!MakeByteAvailable()) return false; m_bytes_in_buffer--; V += m_next_input_byte[m_position]; m_position++; return true; } /// /// Read a byte into variable V. /// If must suspend, take the specified action (typically "return false"). /// /// The result. /// true if operation succeed; otherwise, false public virtual bool GetByte(out int V) { if (!MakeByteAvailable()) { V = 0; return false; } m_bytes_in_buffer--; V = m_next_input_byte[m_position]; m_position++; return true; } /// /// Gets the bytes. /// /// The destination. /// The amount. /// The number of available bytes. public virtual int GetBytes(byte[] dest, int amount) { int avail = amount; if (avail > m_bytes_in_buffer) avail = m_bytes_in_buffer; for (int i = 0; i < avail; i++) { dest[i] = m_next_input_byte[m_position]; m_position++; m_bytes_in_buffer--; } return avail; } /// /// Functions for fetching data from the data source module. /// /// true if operation succeed; otherwise, false /// At all times, cinfo.src.next_input_byte and .bytes_in_buffer reflect /// the current restart point; we update them only when we have reached a /// suitable place to restart if a suspension occurs. public virtual bool MakeByteAvailable() { if (m_bytes_in_buffer == 0) { if (!fill_input_buffer()) return false; } return true; } } #endregion #region JpegUpsampler /// /// Upsampling (note that upsampler must also call color converter) /// abstract class JpegUpsampler { protected bool m_need_context_rows; /* true if need rows above & below */ public abstract void start_pass(); public abstract void upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail); public bool NeedContextRows() { return m_need_context_rows; } } #endregion #region JpegUtils class JpegUtils { /* * jpeg_natural_order[i] is the natural-order position of the i'th element * of zigzag order. * * When reading corrupted data, the Huffman decoders could attempt * to reference an entry beyond the end of this array (if the decoded * zero run length reaches past the end of the block). To prevent * wild stores without adding an inner-loop test, we put some extra * "63"s after the real entries. This will cause the extra coefficient * to be stored in location 63 of the block, not somewhere random. * The worst case would be a run-length of 15, which means we need 16 * fake entries. */ public static int[] jpeg_natural_order = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ 63, 63, 63, 63, 63, 63, 63, 63 }; /* We assume that right shift corresponds to signed division by 2 with * rounding towards minus infinity. This is correct for typical "arithmetic * shift" instructions that shift in copies of the sign bit. * RIGHT_SHIFT provides a proper signed right shift of an int quantity. * It is only applied with constant shift counts. SHIFT_TEMPS must be * included in the variables of any routine using RIGHT_SHIFT. */ public static int RIGHT_SHIFT(int x, int shft) { return (x >> shft); } /* Descale and correctly round an int value that's scaled by N bits. * We assume RIGHT_SHIFT rounds towards minus infinity, so adding * the fudge factor is correct for either sign of X. */ public static int DESCALE(int x, int n) { return RIGHT_SHIFT(x + (1 << (n - 1)), n); } ////////////////////////////////////////////////////////////////////////// // Arithmetic utilities /// /// Compute a/b rounded up to next integer, ie, ceil(a/b) /// Assumes a >= 0, b > 0 /// public static int jdiv_round_up(int a, int b) { return (a + b - 1) / b; } /// /// Compute a rounded up to next multiple of b, ie, ceil(a/b)*b /// Assumes a >= 0, b > 0 /// public static int jround_up(int a, int b) { a += b - 1; return a - (a % b); } /// /// Copy some rows of samples from one place to another. /// num_rows rows are copied from input_array[source_row++] /// to output_array[dest_row++]; these areas may overlap for duplication. /// The source and destination arrays must be at least as wide as num_cols. /// public static void jcopy_sample_rows(ComponentBuffer input_array, int source_row, byte[][] output_array, int dest_row, int num_rows, int num_cols) { for (int row = 0; row < num_rows; row++) Buffer.BlockCopy(input_array[source_row + row], 0, output_array[dest_row + row], 0, num_cols); } public static void jcopy_sample_rows(ComponentBuffer input_array, int source_row, ComponentBuffer output_array, int dest_row, int num_rows, int num_cols) { for (int row = 0; row < num_rows; row++) Buffer.BlockCopy(input_array[source_row + row], 0, output_array[dest_row + row], 0, num_cols); } public static void jcopy_sample_rows(byte[][] input_array, int source_row, byte[][] output_array, int dest_row, int num_rows, int num_cols) { for (int row = 0; row < num_rows; row++) Buffer.BlockCopy(input_array[source_row++], 0, output_array[dest_row++], 0, num_cols); } } #endregion #region JpegVirtualArray /// /// JPEG virtual array. /// /// The type of array's elements. /// You can't create virtual array manually. For creation use methods /// and /// . /// public class JpegVirtualArray { internal delegate T[][] Allocator(int width, int height); private JpegCommonBase m_cinfo; /// /// the in-memory buffer /// private T[][] m_buffer; /// /// Request a virtual 2-D array /// /// Width of array /// Total virtual array height /// The allocator. internal JpegVirtualArray(int width, int height, Allocator allocator) { m_cinfo = null; m_buffer = allocator(width, height); if (m_buffer == null) throw new Exception("Filling of 'm_buffer' Failed!"); } /// /// Gets or sets the error processor. /// /// The error processor.
/// Default value: null ///
/// Uses only for calling /// JpegCommonBase.ERREXIT /// on error. public JpegCommonBase ErrorProcessor { get { return m_cinfo; } set { m_cinfo = value; } } /// /// Access the part of a virtual array. /// /// The first row in required block. /// The number of required rows. /// The required part of virtual array. public T[][] Access(int startRow, int numberOfRows) { /* debugging check */ if (startRow + numberOfRows > m_buffer.Length) { throw new InvalidOperationException("Bogus virtual array access"); } /* Return proper part of the buffer */ T[][] ret = new T[numberOfRows][]; for (int i = 0; i < numberOfRows; i++) ret[i] = m_buffer[startRow + i]; return ret; } } #endregion #region MergedUpsampler class MergedUpsampler : JpegUpsampler { private const int SCALEBITS = 16; /* speediest right-shift on some machines */ private const int ONE_HALF = 1 << (SCALEBITS - 1); private JpegDecompressor m_cinfo; private bool m_use_2v_upsample; /* Private state for YCC->RGB conversion */ private int[] m_Cr_r_tab; /* => table for Cr to R conversion */ private int[] m_Cb_b_tab; /* => table for Cb to B conversion */ private int[] m_Cr_g_tab; /* => table for Cr to G conversion */ private int[] m_Cb_g_tab; /* => table for Cb to G conversion */ /* For 2:1 vertical sampling, we produce two output rows at a time. * We need a "spare" row buffer to hold the second output row if the * application provides just a one-row buffer; we also use the spare * to discard the dummy last row if the image height is odd. */ private byte[] m_spare_row; private bool m_spare_full; /* T if spare buffer is occupied */ private int m_out_row_width; /* samples per output row */ private int m_rows_to_go; /* counts rows remaining in image */ public MergedUpsampler(JpegDecompressor cinfo) { m_cinfo = cinfo; m_need_context_rows = false; m_out_row_width = cinfo.m_output_width * cinfo.m_out_color_components; if (cinfo.m_max_v_samp_factor == 2) { m_use_2v_upsample = true; /* Allocate a spare row buffer */ m_spare_row = new byte[m_out_row_width]; } else { m_use_2v_upsample = false; } build_ycc_rgb_table(); } /// /// Initialize for an upsampling pass. /// public override void start_pass() { /* Mark the spare buffer empty */ m_spare_full = false; /* Initialize total-height counter for detecting bottom of image */ m_rows_to_go = m_cinfo.m_output_height; } public override void upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { if (m_use_2v_upsample) merged_2v_upsample(input_buf, ref in_row_group_ctr, output_buf, ref out_row_ctr, out_rows_avail); else merged_1v_upsample(input_buf, ref in_row_group_ctr, output_buf, ref out_row_ctr); } /// /// Control routine to do upsampling (and color conversion). /// The control routine just handles the row buffering considerations. /// 1:1 vertical sampling case: much easier, never need a spare row. /// private void merged_1v_upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, byte[][] output_buf, ref int out_row_ctr) { /* Just do the upsampling. */ h2v1_merged_upsample(input_buf, in_row_group_ctr, output_buf, out_row_ctr); /* Adjust counts */ out_row_ctr++; in_row_group_ctr++; } /// /// Control routine to do upsampling (and color conversion). /// The control routine just handles the row buffering considerations. /// 2:1 vertical sampling case: may need a spare row. /// private void merged_2v_upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { int num_rows; /* number of rows returned to caller */ if (m_spare_full) { /* If we have a spare row saved from a previous cycle, just return it. */ byte[][] temp = new byte[1][]; temp[0] = m_spare_row; JpegUtils.jcopy_sample_rows(temp, 0, output_buf, out_row_ctr, 1, m_out_row_width); num_rows = 1; m_spare_full = false; } else { /* Figure number of rows to return to caller. */ num_rows = 2; /* Not more than the distance to the end of the image. */ if (num_rows > m_rows_to_go) num_rows = m_rows_to_go; /* And not more than what the client can accept: */ out_rows_avail -= out_row_ctr; if (num_rows > out_rows_avail) num_rows = out_rows_avail; /* Create output pointer array for upsampler. */ byte[][] work_ptrs = new byte[2][]; work_ptrs[0] = output_buf[out_row_ctr]; if (num_rows > 1) { work_ptrs[1] = output_buf[out_row_ctr + 1]; } else { work_ptrs[1] = m_spare_row; m_spare_full = true; } /* Now do the upsampling. */ h2v2_merged_upsample(input_buf, in_row_group_ctr, work_ptrs); } /* Adjust counts */ out_row_ctr += num_rows; m_rows_to_go -= num_rows; /* When the buffer is emptied, declare this input row group consumed */ if (!m_spare_full) in_row_group_ctr++; } /* * These are the routines invoked by the control routines to do * the actual upsampling/conversion. One row group is processed per call. * * Note: since we may be writing directly into application-supplied buffers, * we have to be honest about the output width; we can't assume the buffer * has been rounded up to an even width. */ /// /// Upsample and color convert for the case of 2:1 horizontal and 1:1 vertical. /// private void h2v1_merged_upsample(ComponentBuffer[] input_buf, int in_row_group_ctr, byte[][] output_buf, int outRow) { int inputIndex0 = 0; int inputIndex1 = 0; int inputIndex2 = 0; int outputIndex = 0; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset; /* Loop for each pair of output pixels */ for (int col = m_cinfo.m_output_width >> 1; col > 0; col--) { /* Do the chroma part of the calculation */ int cb = input_buf[1][in_row_group_ctr][inputIndex1]; inputIndex1++; int cr = input_buf[2][in_row_group_ctr][inputIndex2]; inputIndex2++; int cred = m_Cr_r_tab[cr]; int cgreen = JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS); int cblue = m_Cb_b_tab[cb]; /* Fetch 2 Y values and emit 2 pixels */ int y = input_buf[0][in_row_group_ctr][inputIndex0]; inputIndex0++; output_buf[outRow][outputIndex + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + cred]; output_buf[outRow][outputIndex + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + cgreen]; output_buf[outRow][outputIndex + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + cblue]; outputIndex += JpegConstants.RGB_PixelLength; y = input_buf[0][in_row_group_ctr][inputIndex0]; inputIndex0++; output_buf[outRow][outputIndex + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + cred]; output_buf[outRow][outputIndex + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + cgreen]; output_buf[outRow][outputIndex + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + cblue]; outputIndex += JpegConstants.RGB_PixelLength; } /* If image width is odd, do the last output column separately */ if ((m_cinfo.m_output_width & 1) != 0) { int cb = input_buf[1][in_row_group_ctr][inputIndex1]; int cr = input_buf[2][in_row_group_ctr][inputIndex2]; int cred = m_Cr_r_tab[cr]; int cgreen = JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS); int cblue = m_Cb_b_tab[cb]; int y = input_buf[0][in_row_group_ctr][inputIndex0]; output_buf[outRow][outputIndex + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + cred]; output_buf[outRow][outputIndex + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + cgreen]; output_buf[outRow][outputIndex + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + cblue]; } } /// /// Upsample and color convert for the case of 2:1 horizontal and 2:1 vertical. /// private void h2v2_merged_upsample(ComponentBuffer[] input_buf, int in_row_group_ctr, byte[][] output_buf) { int inputRow00 = in_row_group_ctr * 2; int inputIndex00 = 0; int inputRow01 = in_row_group_ctr * 2 + 1; int inputIndex01 = 0; int inputIndex1 = 0; int inputIndex2 = 0; int outIndex0 = 0; int outIndex1 = 0; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset; /* Loop for each group of output pixels */ for (int col = m_cinfo.m_output_width >> 1; col > 0; col--) { /* Do the chroma part of the calculation */ int cb = input_buf[1][in_row_group_ctr][inputIndex1]; inputIndex1++; int cr = input_buf[2][in_row_group_ctr][inputIndex2]; inputIndex2++; int cred = m_Cr_r_tab[cr]; int cgreen = JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS); int cblue = m_Cb_b_tab[cb]; /* Fetch 4 Y values and emit 4 pixels */ int y = input_buf[0][inputRow00][inputIndex00]; inputIndex00++; output_buf[0][outIndex0 + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + cred]; output_buf[0][outIndex0 + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + cgreen]; output_buf[0][outIndex0 + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + cblue]; outIndex0 += JpegConstants.RGB_PixelLength; y = input_buf[0][inputRow00][inputIndex00]; inputIndex00++; output_buf[0][outIndex0 + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + cred]; output_buf[0][outIndex0 + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + cgreen]; output_buf[0][outIndex0 + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + cblue]; outIndex0 += JpegConstants.RGB_PixelLength; y = input_buf[0][inputRow01][inputIndex01]; inputIndex01++; output_buf[1][outIndex1 + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + cred]; output_buf[1][outIndex1 + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + cgreen]; output_buf[1][outIndex1 + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + cblue]; outIndex1 += JpegConstants.RGB_PixelLength; y = input_buf[0][inputRow01][inputIndex01]; inputIndex01++; output_buf[1][outIndex1 + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + cred]; output_buf[1][outIndex1 + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + cgreen]; output_buf[1][outIndex1 + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + cblue]; outIndex1 += JpegConstants.RGB_PixelLength; } /* If image width is odd, do the last output column separately */ if ((m_cinfo.m_output_width & 1) != 0) { int cb = input_buf[1][in_row_group_ctr][inputIndex1]; int cr = input_buf[2][in_row_group_ctr][inputIndex2]; int cred = m_Cr_r_tab[cr]; int cgreen = JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS); int cblue = m_Cb_b_tab[cb]; int y = input_buf[0][inputRow00][inputIndex00]; output_buf[0][outIndex0 + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + cred]; output_buf[0][outIndex0 + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + cgreen]; output_buf[0][outIndex0 + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + cblue]; y = input_buf[0][inputRow01][inputIndex01]; output_buf[1][outIndex1 + JpegConstants.Offset_RGB_Red] = limit[limitOffset + y + cred]; output_buf[1][outIndex1 + JpegConstants.Offset_RGB_Green] = limit[limitOffset + y + cgreen]; output_buf[1][outIndex1 + JpegConstants.Offset_RGB_Blue] = limit[limitOffset + y + cblue]; } } /// /// Initialize tables for YCC->RGB colorspace conversion. /// This is taken directly from ColorDeconverter; see that file for more info. /// private void build_ycc_rgb_table() { m_Cr_r_tab = new int[JpegConstants.MaxSampleValue + 1]; m_Cb_b_tab = new int[JpegConstants.MaxSampleValue + 1]; m_Cr_g_tab = new int[JpegConstants.MaxSampleValue + 1]; m_Cb_g_tab = new int[JpegConstants.MaxSampleValue + 1]; for (int i = 0, x = -JpegConstants.MediumSampleValue; i <= JpegConstants.MaxSampleValue; i++, x++) { /* i is the actual input pixel value, in the range 0..MaxSampleValue */ /* The Cb or Cr value we are thinking of is x = i - MediumSampleValue */ /* Cr=>R value is nearest int to 1.40200 * x */ m_Cr_r_tab[i] = JpegUtils.RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); /* Cb=>B value is nearest int to 1.77200 * x */ m_Cb_b_tab[i] = JpegUtils.RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); /* Cr=>G value is scaled-up -0.71414 * x */ m_Cr_g_tab[i] = (-FIX(0.71414)) * x; /* Cb=>G value is scaled-up -0.34414 * x */ /* We also add in ONE_HALF so that need not do it in inner loop */ m_Cb_g_tab[i] = (-FIX(0.34414)) * x + ONE_HALF; } } private static int FIX(double x) { return ((int)((x) * (1L << SCALEBITS) + 0.5)); } } #endregion #region Pass1ColorQuantizer /// /// The main purpose of 1-pass quantization is to provide a fast, if not very /// high quality, colormapped output capability. A 2-pass quantizer usually /// gives better visual quality; however, for quantized grayscale output this /// quantizer is perfectly adequate. Dithering is highly recommended with this /// quantizer, though you can turn it off if you really want to. /// /// In 1-pass quantization the colormap must be chosen in advance of seeing the /// image. We use a map consisting of all combinations of Ncolors[i] color /// values for the i'th component. The Ncolors[] values are chosen so that /// their product, the total number of colors, is no more than that requested. /// (In most cases, the product will be somewhat less.) /// /// Since the colormap is orthogonal, the representative value for each color /// component can be determined without considering the other components; /// then these indexes can be combined into a colormap index by a standard /// N-dimensional-array-subscript calculation. Most of the arithmetic involved /// can be precalculated and stored in the lookup table colorindex[]. /// colorindex[i][j] maps pixel value j in component i to the nearest /// representative value (grid plane) for that component; this index is /// multiplied by the array stride for component i, so that the /// index of the colormap entry closest to a given pixel value is just /// sum( colorindex[component-number][pixel-component-value] ) /// Aside from being fast, this scheme allows for variable spacing between /// representative values with no additional lookup cost. /// /// If gamma correction has been applied in color conversion, it might be wise /// to adjust the color grid spacing so that the representative colors are /// equidistant in linear space. At this writing, gamma correction is not /// implemented, so nothing is done here. /// /// /// Declarations for Floyd-Steinberg dithering. /// /// Errors are accumulated into the array fserrors[], at a resolution of /// 1/16th of a pixel count. The error at a given pixel is propagated /// to its not-yet-processed neighbors using the standard F-S fractions, /// ... (here) 7/16 /// 3/16 5/16 1/16 /// We work left-to-right on even rows, right-to-left on odd rows. /// /// We can get away with a single array (holding one row's worth of errors) /// by using it to store the current row's errors at pixel columns not yet /// processed, but the next row's errors at columns already processed. We /// need only a few extra variables to hold the errors immediately around the /// current column. (If we are lucky, those variables are in registers, but /// even if not, they're probably cheaper to access than array elements are.) /// /// The fserrors[] array is indexed [component#][position]. /// We provide (#columns + 2) entries per component; the extra entry at each /// end saves us from special-casing the first and last pixels. /// /// /// Declarations for ordered dithering. /// /// We use a standard 16x16 ordered dither array. The basic concept of ordered /// dithering is described in many references, for instance Dale Schumacher's /// chapter II.2 of Graphics Gems II (James Arvo, ed. Academic Press, 1991). /// In place of Schumacher's comparisons against a "threshold" value, we add a /// "dither" value to the input pixel and then round the result to the nearest /// output value. The dither value is equivalent to (0.5 - threshold) times /// the distance between output values. For ordered dithering, we assume that /// the output colors are equally spaced; if not, results will probably be /// worse, since the dither may be too much or too little at a given point. /// /// The normal calculation would be to form pixel value + dither, range-limit /// this to 0..MaxSampleValue, and then index into the colorindex table as usual. /// We can skip the separate range-limiting step by extending the colorindex /// table in both directions. /// class Pass1ColorQuantizer : ColorQuantizer { private enum QuantizerType { color_quantizer3, color_quantizer, quantize3_ord_dither_quantizer, quantize_ord_dither_quantizer, quantize_fs_dither_quantizer } private static int[] RGB_order = { JpegConstants.Offset_RGB_Green, JpegConstants.Offset_RGB_Red, JpegConstants.Offset_RGB_Blue }; private const int MAX_Q_COMPS = 4; /* max components I can handle */ private const int ODITHER_SIZE = 16; /* dimension of dither matrix */ /* NB: if ODITHER_SIZE is not a power of 2, ODITHER_MASK uses will break */ private const int ODITHER_CELLS = (ODITHER_SIZE * ODITHER_SIZE); /* # cells in matrix */ private const int ODITHER_MASK = (ODITHER_SIZE - 1); /* mask for wrapping around counters */ /* Bayer's order-4 dither array. Generated by the code given in * Stephen Hawley's article "Ordered Dithering" in Graphics Gems I. * The values in this array must range from 0 to ODITHER_CELLS-1. */ private static byte[][] base_dither_matrix = new byte[][] { new byte[] { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, new byte[] { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, new byte[] { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, new byte[] { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, new byte[] { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, new byte[] { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, new byte[] { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, new byte[] { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, new byte[] { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, new byte[] { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, new byte[] { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, new byte[] { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, new byte[] { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, new byte[] { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, new byte[] { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, new byte[] { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } }; private QuantizerType m_quantizer; private JpegDecompressor m_cinfo; /* Initially allocated colormap is saved here */ private byte[][] m_sv_colormap; /* The color map as a 2-D pixel array */ private int m_sv_actual; /* number of entries in use */ private byte[][] m_colorindex; /* Precomputed mapping for speed */ private int[] m_colorindexOffset; /* colorindex[i][j] = index of color closest to pixel value j in component i, * premultiplied as described above. Since colormap indexes must fit into * bytes, the entries of this array will too. */ private bool m_is_padded; /* is the colorindex padded for odither? */ private int[] m_Ncolors = new int[MAX_Q_COMPS]; /* # of values alloced to each component */ /* Variables for ordered dithering */ private int m_row_index; /* cur row's vertical index in dither matrix */ private int[][][] m_odither = new int[MAX_Q_COMPS][][]; /* one dither array per component */ /* Variables for Floyd-Steinberg dithering */ private short[][] m_fserrors = new short[MAX_Q_COMPS][]; /* accumulated errors */ private bool m_on_odd_row; /* flag to remember which row we are on */ /// /// Module initialization routine for 1-pass color quantization. /// /// The cinfo. public Pass1ColorQuantizer(JpegDecompressor cinfo) { m_cinfo = cinfo; m_fserrors[0] = null; /* Flag FS workspace not allocated */ m_odither[0] = null; /* Also flag odither arrays not allocated */ /* Make sure my internal arrays won't overflow */ if (cinfo.m_out_color_components > MAX_Q_COMPS) throw new Exception(String.Format("Cannot quantize more than {0} color components", MAX_Q_COMPS)); /* Make sure colormap indexes can be represented by JSAMPLEs */ if (cinfo.m_desired_number_of_colors > (JpegConstants.MaxSampleValue + 1)) throw new Exception(String.Format("Cannot quantize to more than {0} colors", JpegConstants.MaxSampleValue + 1)); /* Create the colormap and color index table. */ create_colormap(); create_colorindex(); /* Allocate Floyd-Steinberg workspace now if requested. * We do this now since it is FAR storage and may affect the memory * manager's space calculations. If the user changes to FS dither * mode in a later pass, we will allocate the space then, and will * possibly overrun the max_memory_to_use setting. */ if (cinfo.m_dither_mode == DitherMode.FloydStein) alloc_fs_workspace(); } /// /// Initialize for one-pass color quantization. /// public virtual void start_pass(bool is_pre_scan) { /* Install my colormap. */ m_cinfo.m_colormap = m_sv_colormap; m_cinfo.m_actual_number_of_colors = m_sv_actual; /* Initialize for desired dithering mode. */ switch (m_cinfo.m_dither_mode) { case DitherMode.None: if (m_cinfo.m_out_color_components == 3) m_quantizer = QuantizerType.color_quantizer3; else m_quantizer = QuantizerType.color_quantizer; break; case DitherMode.Ordered: if (m_cinfo.m_out_color_components == 3) m_quantizer = QuantizerType.quantize3_ord_dither_quantizer; else m_quantizer = QuantizerType.quantize3_ord_dither_quantizer; /* initialize state for ordered dither */ m_row_index = 0; /* If user changed to ordered dither from another mode, * we must recreate the color index table with padding. * This will cost extra space, but probably isn't very likely. */ if (!m_is_padded) create_colorindex(); /* Create ordered-dither tables if we didn't already. */ if (m_odither[0] == null) create_odither_tables(); break; case DitherMode.FloydStein: m_quantizer = QuantizerType.quantize_fs_dither_quantizer; /* initialize state for F-S dither */ m_on_odd_row = false; /* Allocate Floyd-Steinberg workspace if didn't already. */ if (m_fserrors[0] == null) alloc_fs_workspace(); /* Initialize the propagated errors to zero. */ int arraysize = m_cinfo.m_output_width + 2; for (int i = 0; i < m_cinfo.m_out_color_components; i++) Array.Clear(m_fserrors[i], 0, arraysize); break; default: throw new Exception("Unknown Dither Mode"); } } public virtual void color_quantize(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows) { switch (m_quantizer) { case QuantizerType.color_quantizer3: quantize3(input_buf, in_row, output_buf, out_row, num_rows); break; case QuantizerType.color_quantizer: quantize(input_buf, in_row, output_buf, out_row, num_rows); break; case QuantizerType.quantize3_ord_dither_quantizer: quantize3_ord_dither(input_buf, in_row, output_buf, out_row, num_rows); break; case QuantizerType.quantize_ord_dither_quantizer: quantize_ord_dither(input_buf, in_row, output_buf, out_row, num_rows); break; case QuantizerType.quantize_fs_dither_quantizer: quantize_fs_dither(input_buf, in_row, output_buf, out_row, num_rows); break; default: throw new Exception("Not implemented yet"); } } /// /// Finish up at the end of the pass. /// public virtual void finish_pass() { /* no work in 1-pass case */ } /// /// Switch to a new external colormap between output passes. /// Shouldn't get to this! /// public virtual void new_color_map() { throw new Exception("Invalid mode change during color quantization"); } /// /// Map some rows of pixels to the output colormapped representation. /// General case, no dithering. /// private void quantize(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows) { int nc = m_cinfo.m_out_color_components; for (int row = 0; row < num_rows; row++) { int inIndex = 0; int inRow = in_row + row; int outIndex = 0; int outRow = out_row + row; for (int col = m_cinfo.m_output_width; col > 0; col--) { int pixcode = 0; for (int ci = 0; ci < nc; ci++) { pixcode += m_colorindex[ci][m_colorindexOffset[ci] + input_buf[inRow][inIndex]]; inIndex++; } output_buf[outRow][outIndex] = (byte)pixcode; outIndex++; } } } /// /// Map some rows of pixels to the output colormapped representation. /// Fast path for out_color_components==3, no dithering /// private void quantize3(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows) { int width = m_cinfo.m_output_width; for (int row = 0; row < num_rows; row++) { int inIndex = 0; int inRow = in_row + row; int outIndex = 0; int outRow = out_row + row; for (int col = width; col > 0; col--) { int pixcode = m_colorindex[0][m_colorindexOffset[0] + input_buf[inRow][inIndex]]; inIndex++; pixcode += m_colorindex[1][m_colorindexOffset[1] + input_buf[inRow][inIndex]]; inIndex++; pixcode += m_colorindex[2][m_colorindexOffset[2] + input_buf[inRow][inIndex]]; inIndex++; output_buf[outRow][outIndex] = (byte)pixcode; outIndex++; } } } /// /// Map some rows of pixels to the output colormapped representation. /// General case, with ordered dithering. /// private void quantize_ord_dither(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows) { int nc = m_cinfo.m_out_color_components; int width = m_cinfo.m_output_width; for (int row = 0; row < num_rows; row++) { /* Initialize output values to 0 so can process components separately */ Array.Clear(output_buf[out_row + row], 0, width); int row_index = m_row_index; for (int ci = 0; ci < nc; ci++) { int inputIndex = ci; int outIndex = 0; int outRow = out_row + row; int col_index = 0; for (int col = width; col > 0; col--) { /* Form pixel value + dither, range-limit to 0..MaxSampleValue, * select output value, accumulate into output code for this pixel. * Range-limiting need not be done explicitly, as we have extended * the colorindex table to produce the right answers for out-of-range * inputs. The maximum dither is +- MaxSampleValue; this sets the * required amount of padding. */ output_buf[outRow][outIndex] += m_colorindex[ci][m_colorindexOffset[ci] + input_buf[in_row + row][inputIndex] + m_odither[ci][row_index][col_index]]; inputIndex += nc; outIndex++; col_index = (col_index + 1) & ODITHER_MASK; } } /* Advance row index for next row */ row_index = (row_index + 1) & ODITHER_MASK; m_row_index = row_index; } } /// /// Map some rows of pixels to the output colormapped representation. /// Fast path for out_color_components==3, with ordered dithering /// private void quantize3_ord_dither(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows) { int width = m_cinfo.m_output_width; for (int row = 0; row < num_rows; row++) { int row_index = m_row_index; int inRow = in_row + row; int inIndex = 0; int outIndex = 0; int outRow = out_row + row; int col_index = 0; for (int col = width; col > 0; col--) { int pixcode = m_colorindex[0][m_colorindexOffset[0] + input_buf[inRow][inIndex] + m_odither[0][row_index][col_index]]; inIndex++; pixcode += m_colorindex[1][m_colorindexOffset[1] + input_buf[inRow][inIndex] + m_odither[1][row_index][col_index]]; inIndex++; pixcode += m_colorindex[2][m_colorindexOffset[2] + input_buf[inRow][inIndex] + m_odither[2][row_index][col_index]]; inIndex++; output_buf[outRow][outIndex] = (byte)pixcode; outIndex++; col_index = (col_index + 1) & ODITHER_MASK; } row_index = (row_index + 1) & ODITHER_MASK; m_row_index = row_index; } } /// /// Map some rows of pixels to the output colormapped representation. /// General case, with Floyd-Steinberg dithering /// private void quantize_fs_dither(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows) { int nc = m_cinfo.m_out_color_components; int width = m_cinfo.m_output_width; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset; for (int row = 0; row < num_rows; row++) { /* Initialize output values to 0 so can process components separately */ Array.Clear(output_buf[out_row + row], 0, width); for (int ci = 0; ci < nc; ci++) { int inRow = in_row + row; int inIndex = ci; int outIndex = 0; int outRow = out_row + row; int errorIndex = 0; int dir; /* 1 for left-to-right, -1 for right-to-left */ if (m_on_odd_row) { /* work right to left in this row */ inIndex += (width - 1) * nc; /* so point to rightmost pixel */ outIndex += width - 1; dir = -1; errorIndex = width + 1; /* => entry after last column */ } else { /* work left to right in this row */ dir = 1; errorIndex = 0; /* => entry before first column */ } int dirnc = dir * nc; /* Preset error values: no error propagated to first pixel from left */ int cur = 0; /* and no error propagated to row below yet */ int belowerr = 0; int bpreverr = 0; for (int col = width; col > 0; col--) { /* cur holds the error propagated from the previous pixel on the * current line. Add the error propagated from the previous line * to form the complete error correction term for this pixel, and * round the error term (which is expressed * 16) to an integer. * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct * for either sign of the error value. * Note: errorIndex is for *previous* column's array entry. */ cur = JpegUtils.RIGHT_SHIFT(cur + m_fserrors[ci][errorIndex + dir] + 8, 4); /* Form pixel value + error, and range-limit to 0..MaxSampleValue. * The maximum error is +- MaxSampleValue; this sets the required size * of the range_limit array. */ cur += input_buf[inRow][inIndex]; cur = limit[limitOffset + cur]; /* Select output value, accumulate into output code for this pixel */ int pixcode = m_colorindex[ci][m_colorindexOffset[ci] + cur]; output_buf[outRow][outIndex] += (byte)pixcode; /* Compute actual representation error at this pixel */ /* Note: we can do this even though we don't have the final */ /* pixel code, because the colormap is orthogonal. */ cur -= m_sv_colormap[ci][pixcode]; /* Compute error fractions to be propagated to adjacent pixels. * Add these into the running sums, and simultaneously shift the * next-line error sums left by 1 column. */ int bnexterr = cur; int delta = cur * 2; cur += delta; /* form error * 3 */ m_fserrors[ci][errorIndex + 0] = (short)(bpreverr + cur); cur += delta; /* form error * 5 */ bpreverr = belowerr + cur; belowerr = bnexterr; cur += delta; /* form error * 7 */ /* At this point cur contains the 7/16 error value to be propagated * to the next pixel on the current line, and all the errors for the * next line have been shifted over. We are therefore ready to move on. */ inIndex += dirnc; /* advance input to next column */ outIndex += dir; /* advance output to next column */ errorIndex += dir; /* advance errorIndex to current column */ } /* Post-loop cleanup: we must unload the final error value into the * final fserrors[] entry. Note we need not unload belowerr because * it is for the dummy column before or after the actual array. */ m_fserrors[ci][errorIndex + 0] = (short)bpreverr; /* unload prev err into array */ } m_on_odd_row = (m_on_odd_row ? false : true); } } /// /// Create the colormap. /// private void create_colormap() { /* Select number of colors for each component */ int total_colors = select_ncolors(m_Ncolors); /* Allocate and fill in the colormap. */ /* The colors are ordered in the map in standard row-major order, */ /* i.e. rightmost (highest-indexed) color changes most rapidly. */ byte[][] colormap = JpegCommonBase.AllocJpegSamples(total_colors, m_cinfo.m_out_color_components); /* blksize is number of adjacent repeated entries for a component */ /* blkdist is distance between groups of identical entries for a component */ int blkdist = total_colors; for (int i = 0; i < m_cinfo.m_out_color_components; i++) { /* fill in colormap entries for i'th color component */ int nci = m_Ncolors[i]; /* # of distinct values for this color */ int blksize = blkdist / nci; for (int j = 0; j < nci; j++) { /* Compute j'th output value (out of nci) for component */ int val = output_value(j, nci - 1); /* Fill in all colormap entries that have this value of this component */ for (int ptr = j * blksize; ptr < total_colors; ptr += blkdist) { /* fill in blksize entries beginning at ptr */ for (int k = 0; k < blksize; k++) colormap[i][ptr + k] = (byte)val; } } /* blksize of this color is blkdist of next */ blkdist = blksize; } /* Save the colormap in private storage, * where it will survive color quantization mode changes. */ m_sv_colormap = colormap; m_sv_actual = total_colors; } /// /// Create the color index table. /// private void create_colorindex() { /* For ordered dither, we pad the color index tables by MaxSampleValue in * each direction (input index values can be -MaxSampleValue .. 2*MaxSampleValue). * This is not necessary in the other dithering modes. However, we * flag whether it was done in case user changes dithering mode. */ int pad; if (m_cinfo.m_dither_mode == DitherMode.Ordered) { pad = JpegConstants.MaxSampleValue * 2; m_is_padded = true; } else { pad = 0; m_is_padded = false; } m_colorindex = JpegCommonBase.AllocJpegSamples(JpegConstants.MaxSampleValue + 1 + pad, m_cinfo.m_out_color_components); m_colorindexOffset = new int[m_cinfo.m_out_color_components]; /* blksize is number of adjacent repeated entries for a component */ int blksize = m_sv_actual; for (int i = 0; i < m_cinfo.m_out_color_components; i++) { /* fill in colorindex entries for i'th color component */ int nci = m_Ncolors[i]; /* # of distinct values for this color */ blksize = blksize / nci; /* adjust colorindex pointers to provide padding at negative indexes. */ if (pad != 0) m_colorindexOffset[i] += JpegConstants.MaxSampleValue; /* in loop, val = index of current output value, */ /* and k = largest j that maps to current val */ int val = 0; int k = largest_input_value(0, nci - 1); for (int j = 0; j <= JpegConstants.MaxSampleValue; j++) { while (j > k) { /* advance val if past boundary */ k = largest_input_value(++val, nci - 1); } /* premultiply so that no multiplication needed in main processing */ m_colorindex[i][m_colorindexOffset[i] + j] = (byte)(val * blksize); } /* Pad at both ends if necessary */ if (pad != 0) { for (int j = 1; j <= JpegConstants.MaxSampleValue; j++) { m_colorindex[i][m_colorindexOffset[i] + -j] = m_colorindex[i][m_colorindexOffset[i]]; m_colorindex[i][m_colorindexOffset[i] + JpegConstants.MaxSampleValue + j] = m_colorindex[i][m_colorindexOffset[i] + JpegConstants.MaxSampleValue]; } } } } /// /// Create the ordered-dither tables. /// Components having the same number of representative colors may /// share a dither table. /// private void create_odither_tables() { for (int i = 0; i < m_cinfo.m_out_color_components; i++) { int nci = m_Ncolors[i]; /* # of distinct values for this color */ /* search for matching prior component */ int foundPos = -1; for (int j = 0; j < i; j++) { if (nci == m_Ncolors[j]) { foundPos = j; break; } } if (foundPos == -1) { /* need a new table? */ m_odither[i] = make_odither_array(nci); } else m_odither[i] = m_odither[foundPos]; } } /// /// Allocate workspace for Floyd-Steinberg errors. /// private void alloc_fs_workspace() { for (int i = 0; i < m_cinfo.m_out_color_components; i++) m_fserrors[i] = new short[m_cinfo.m_output_width + 2]; } /* * Policy-making subroutines for create_colormap and create_colorindex. * These routines determine the colormap to be used. The rest of the module * only assumes that the colormap is orthogonal. * * * select_ncolors decides how to divvy up the available colors * among the components. * * output_value defines the set of representative values for a component. * * largest_input_value defines the mapping from input values to * representative values for a component. * Note that the latter two routines may impose different policies for * different components, though this is not currently done. */ /// /// Return largest input value that should map to j'th output value /// Must have largest(j=0) >= 0, and largest(j=maxj) >= MaxSampleValue /// private static int largest_input_value(int j, int maxj) { /* Breakpoints are halfway between values returned by output_value */ return (int)(((2 * j + 1) * JpegConstants.MaxSampleValue + maxj) / (2 * maxj)); } /// /// Return j'th output value, where j will range from 0 to maxj /// The output values must fall in 0..MaxSampleValue in increasing order /// private static int output_value(int j, int maxj) { /* We always provide values 0 and MaxSampleValue for each component; * any additional values are equally spaced between these limits. * (Forcing the upper and lower values to the limits ensures that * dithering can't produce a color outside the selected gamut.) */ return (int)((j * JpegConstants.MaxSampleValue + maxj / 2) / maxj); } /// /// Determine allocation of desired colors to components, /// and fill in Ncolors[] array to indicate choice. /// Return value is total number of colors (product of Ncolors[] values). /// private int select_ncolors(int[] Ncolors) { int nc = m_cinfo.m_out_color_components; /* number of color components */ int max_colors = m_cinfo.m_desired_number_of_colors; /* We can allocate at least the nc'th root of max_colors per component. */ /* Compute floor(nc'th root of max_colors). */ int iroot = 1; long temp = 0; do { iroot++; temp = iroot; /* set temp = iroot ** nc */ for (int i = 1; i < nc; i++) temp *= iroot; } while (temp <= max_colors); /* repeat till iroot exceeds root */ /* now iroot = floor(root) */ iroot--; /* Must have at least 2 color values per component */ if (iroot < 2) throw new Exception(String.Format("Cannot quantize to fewer than {0} colors", (int)temp)); /* Initialize to iroot color values for each component */ int total_colors = 1; for (int i = 0; i < nc; i++) { Ncolors[i] = iroot; total_colors *= iroot; } /* We may be able to increment the count for one or more components without * exceeding max_colors, though we know not all can be incremented. * Sometimes, the first component can be incremented more than once! * (Example: for 16 colors, we start at 2*2*2, go to 3*2*2, then 4*2*2.) * In RGB colorspace, try to increment G first, then R, then B. */ bool changed = false; do { changed = false; for (int i = 0; i < nc; i++) { int j = (m_cinfo.m_out_color_space == ColorSpace.RGB ? RGB_order[i] : i); /* calculate new total_colors if Ncolors[j] is incremented */ temp = total_colors / Ncolors[j]; temp *= Ncolors[j] + 1; /* done in long arith to avoid oflo */ if (temp > max_colors) break; /* won't fit, done with this pass */ Ncolors[j]++; /* OK, apply the increment */ total_colors = (int)temp; changed = true; } } while (changed); return total_colors; } /// /// Create an ordered-dither array for a component having ncolors /// distinct output values. /// private static int[][] make_odither_array(int ncolors) { int[][] odither = new int[ODITHER_SIZE][]; for (int i = 0; i < ODITHER_SIZE; i++) odither[i] = new int[ODITHER_SIZE]; /* The inter-value distance for this color is MaxSampleValue/(ncolors-1). * Hence the dither value for the matrix cell with fill order f * (f=0..N-1) should be (N-1-2*f)/(2*N) * MaxSampleValue/(ncolors-1). * On 16-bit-int machine, be careful to avoid overflow. */ int den = 2 * ODITHER_CELLS * (ncolors - 1); for (int j = 0; j < ODITHER_SIZE; j++) { for (int k = 0; k < ODITHER_SIZE; k++) { int num = ((int)(ODITHER_CELLS - 1 - 2 * ((int)base_dither_matrix[j][k]))) * JpegConstants.MaxSampleValue; /* Ensure round towards zero despite C's lack of consistency * about rounding negative values in integer division... */ odither[j][k] = num < 0 ? -((-num) / den) : num / den; } } return odither; } } #endregion #region Pass2ColorQuantizer /// /// This module implements the well-known Heckbert paradigm for color /// quantization. Most of the ideas used here can be traced back to /// Heckbert's seminal paper /// Heckbert, Paul. "Color Image Quantization for Frame Buffer Display", /// Proc. SIGGRAPH '82, Computer Graphics v.16 #3 (July 1982), pp 297-304. /// /// In the first pass over the image, we accumulate a histogram showing the /// usage count of each possible color. To keep the histogram to a reasonable /// size, we reduce the precision of the input; typical practice is to retain /// 5 or 6 bits per color, so that 8 or 4 different input values are counted /// in the same histogram cell. /// /// Next, the color-selection step begins with a box representing the whole /// color space, and repeatedly splits the "largest" remaining box until we /// have as many boxes as desired colors. Then the mean color in each /// remaining box becomes one of the possible output colors. /// /// The second pass over the image maps each input pixel to the closest output /// color (optionally after applying a Floyd-Steinberg dithering correction). /// This mapping is logically trivial, but making it go fast enough requires /// considerable care. /// /// Heckbert-style quantizers vary a good deal in their policies for choosing /// the "largest" box and deciding where to cut it. The particular policies /// used here have proved out well in experimental comparisons, but better ones /// may yet be found. /// /// In earlier versions of the IJG code, this module quantized in YCbCr color /// space, processing the raw upsampled data without a color conversion step. /// This allowed the color conversion math to be done only once per colormap /// entry, not once per pixel. However, that optimization precluded other /// useful optimizations (such as merging color conversion with upsampling) /// and it also interfered with desired capabilities such as quantizing to an /// externally-supplied colormap. We have therefore abandoned that approach. /// The present code works in the post-conversion color space, typically RGB. /// /// To improve the visual quality of the results, we actually work in scaled /// RGB space, giving G distances more weight than R, and R in turn more than /// B. To do everything in integer math, we must use integer scale factors. /// The 2/3/1 scale factors used here correspond loosely to the relative /// weights of the colors in the NTSC grayscale equation. /// If you want to use this code to quantize a non-RGB color space, you'll /// probably need to change these scale factors. /// /// First we have the histogram data structure and routines for creating it. /// /// The number of bits of precision can be adjusted by changing these symbols. /// We recommend keeping 6 bits for G and 5 each for R and B. /// If you have plenty of memory and cycles, 6 bits all around gives marginally /// better results; if you are short of memory, 5 bits all around will save /// some space but degrade the results. /// To maintain a fully accurate histogram, we'd need to allocate a "long" /// (preferably unsigned long) for each cell. In practice this is overkill; /// we can get by with 16 bits per cell. Few of the cell counts will overflow, /// and clamping those that do overflow to the maximum value will give close- /// enough results. This reduces the recommended histogram size from 256Kb /// to 128Kb, which is a useful savings on PC-class machines. /// (In the second pass the histogram space is re-used for pixel mapping data; /// in that capacity, each cell must be able to store zero to the number of /// desired colors. 16 bits/cell is plenty for that too.) /// Since the JPEG code is intended to run in small memory model on 80x86 /// machines, we can't just allocate the histogram in one chunk. Instead /// of a true 3-D array, we use a row of pointers to 2-D arrays. Each /// pointer corresponds to a C0 value (typically 2^5 = 32 pointers) and /// each 2-D array has 2^6*2^5 = 2048 or 2^6*2^6 = 4096 entries. Note that /// on 80x86 machines, the pointer row is in near memory but the actual /// arrays are in far memory (same arrangement as we use for image arrays). /// /// /// Declarations for Floyd-Steinberg dithering. /// /// Errors are accumulated into the array fserrors[], at a resolution of /// 1/16th of a pixel count. The error at a given pixel is propagated /// to its not-yet-processed neighbors using the standard F-S fractions, /// ... (here) 7/16 /// 3/16 5/16 1/16 /// We work left-to-right on even rows, right-to-left on odd rows. /// /// We can get away with a single array (holding one row's worth of errors) /// by using it to store the current row's errors at pixel columns not yet /// processed, but the next row's errors at columns already processed. We /// need only a few extra variables to hold the errors immediately around the /// current column. (If we are lucky, those variables are in registers, but /// even if not, they're probably cheaper to access than array elements are.) /// /// The fserrors[] array has (#columns + 2) entries; the extra entry at /// each end saves us from special-casing the first and last pixels. /// Each entry is three values long, one value for each color component. /// class Pass2ColorQuantizer : ColorQuantizer { private struct box { /* The bounds of the box (inclusive); expressed as histogram indexes */ public int c0min; public int c0max; public int c1min; public int c1max; public int c2min; public int c2max; /* The volume (actually 2-norm) of the box */ public int volume; /* The number of nonzero histogram cells within this box */ public long colorcount; } private enum QuantizerType { prescan_quantizer, pass2_fs_dither_quantizer, pass2_no_dither_quantizer } private const int MAXNUMCOLORS = (JpegConstants.MaxSampleValue + 1); /* maximum size of colormap */ /* These will do the right thing for either R,G,B or B,G,R color order, * but you may not like the results for other color orders. */ private const int HIST_C0_BITS = 5; /* bits of precision in R/B histogram */ private const int HIST_C1_BITS = 6; /* bits of precision in G histogram */ private const int HIST_C2_BITS = 5; /* bits of precision in B/R histogram */ /* Number of elements along histogram axes. */ private const int HIST_C0_ELEMS = (1 << HIST_C0_BITS); private const int HIST_C1_ELEMS = (1 << HIST_C1_BITS); private const int HIST_C2_ELEMS = (1 << HIST_C2_BITS); /* These are the amounts to shift an input value to get a histogram index. */ private const int C0_SHIFT = (JpegConstants.BitsInSample - HIST_C0_BITS); private const int C1_SHIFT = (JpegConstants.BitsInSample - HIST_C1_BITS); private const int C2_SHIFT = (JpegConstants.BitsInSample - HIST_C2_BITS); private const int R_SCALE = 2; /* scale R distances by this much */ private const int G_SCALE = 3; /* scale G distances by this much */ private const int B_SCALE = 1; /* and B by this much */ /* log2(histogram cells in update box) for each axis; this can be adjusted */ private const int BOX_C0_LOG = (HIST_C0_BITS - 3); private const int BOX_C1_LOG = (HIST_C1_BITS - 3); private const int BOX_C2_LOG = (HIST_C2_BITS - 3); private const int BOX_C0_ELEMS = (1 << BOX_C0_LOG); /* # of hist cells in update box */ private const int BOX_C1_ELEMS = (1 << BOX_C1_LOG); private const int BOX_C2_ELEMS = (1 << BOX_C2_LOG); private const int BOX_C0_SHIFT = (C0_SHIFT + BOX_C0_LOG); private const int BOX_C1_SHIFT = (C1_SHIFT + BOX_C1_LOG); private const int BOX_C2_SHIFT = (C2_SHIFT + BOX_C2_LOG); private QuantizerType m_quantizer; private bool m_useFinishPass1; private JpegDecompressor m_cinfo; /* Space for the eventually created colormap is stashed here */ private byte[][] m_sv_colormap; /* colormap allocated at init time */ private int m_desired; /* desired # of colors = size of colormap */ /* Variables for accumulating image statistics */ private ushort[][] m_histogram; /* pointer to the histogram */ private bool m_needs_zeroed; /* true if next pass must zero histogram */ /* Variables for Floyd-Steinberg dithering */ private short[] m_fserrors; /* accumulated errors */ private bool m_on_odd_row; /* flag to remember which row we are on */ private int[] m_error_limiter; /* table for clamping the applied error */ /// /// Module initialization routine for 2-pass color quantization. /// public Pass2ColorQuantizer(JpegDecompressor cinfo) { m_cinfo = cinfo; /* Make sure jdmaster didn't give me a case I can't handle */ if (cinfo.m_out_color_components != 3) throw new Exception("Unable to handle anything other than 3 color components!"); /* Allocate the histogram/inverse colormap storage */ m_histogram = new ushort[HIST_C0_ELEMS][]; for (int i = 0; i < HIST_C0_ELEMS; i++) m_histogram[i] = new ushort[HIST_C1_ELEMS * HIST_C2_ELEMS]; m_needs_zeroed = true; /* histogram is garbage now */ /* Allocate storage for the completed colormap, if required. * We do this now since it is FAR storage and may affect * the memory manager's space calculations. */ if (cinfo.m_enable_2pass_quant) { /* Make sure color count is acceptable */ int desired_local = cinfo.m_desired_number_of_colors; /* Lower bound on # of colors ... somewhat arbitrary as long as > 0 */ if (desired_local < 8) throw new Exception("Cannot quantize to fewer than 8 colors"); /* Make sure colormap indexes can be represented by JSAMPLEs */ if (desired_local > MAXNUMCOLORS) throw new Exception(String.Format("Cannot quantize to more than {0} colors", MAXNUMCOLORS)); m_sv_colormap = JpegCommonBase.AllocJpegSamples(desired_local, 3); m_desired = desired_local; } /* Only F-S dithering or no dithering is supported. */ /* If user asks for ordered dither, give him F-S. */ if (cinfo.m_dither_mode != DitherMode.None) cinfo.m_dither_mode = DitherMode.FloydStein; /* Allocate Floyd-Steinberg workspace if necessary. * This isn't really needed until pass 2, but again it is FAR storage. * Although we will cope with a later change in dither_mode, * we do not promise to honor max_memory_to_use if dither_mode changes. */ if (cinfo.m_dither_mode == DitherMode.FloydStein) { m_fserrors = new short[(cinfo.m_output_width + 2) * 3]; /* Might as well create the error-limiting table too. */ init_error_limit(); } } /// /// Initialize for each processing pass. /// public virtual void start_pass(bool is_pre_scan) { /* Only F-S dithering or no dithering is supported. */ /* If user asks for ordered dither, give him F-S. */ if (m_cinfo.m_dither_mode != DitherMode.None) m_cinfo.m_dither_mode = DitherMode.FloydStein; if (is_pre_scan) { /* Set up method pointers */ m_quantizer = QuantizerType.prescan_quantizer; m_useFinishPass1 = true; m_needs_zeroed = true; /* Always zero histogram */ } else { /* Set up method pointers */ if (m_cinfo.m_dither_mode == DitherMode.FloydStein) m_quantizer = QuantizerType.pass2_fs_dither_quantizer; else m_quantizer = QuantizerType.pass2_no_dither_quantizer; m_useFinishPass1 = false; /* Make sure color count is acceptable */ int i = m_cinfo.m_actual_number_of_colors; if (i < 1) throw new Exception("Cannot quantize to less than 1 color"); if (i > MAXNUMCOLORS) throw new Exception(String.Format("Cannot quantize to more than {0} colors", MAXNUMCOLORS)); if (m_cinfo.m_dither_mode == DitherMode.FloydStein) { /* Allocate Floyd-Steinberg workspace if we didn't already. */ if (m_fserrors == null) { int arraysize = (m_cinfo.m_output_width + 2) * 3; m_fserrors = new short[arraysize]; } else { /* Initialize the propagated errors to zero. */ Array.Clear(m_fserrors, 0, m_fserrors.Length); } /* Make the error-limit table if we didn't already. */ if (m_error_limiter == null) init_error_limit(); m_on_odd_row = false; } } /* Zero the histogram or inverse color map, if necessary */ if (m_needs_zeroed) { for (int i = 0; i < HIST_C0_ELEMS; i++) Array.Clear(m_histogram[i], 0, m_histogram[i].Length); m_needs_zeroed = false; } } public virtual void color_quantize(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows) { switch (m_quantizer) { case QuantizerType.prescan_quantizer: prescan_quantize(input_buf, in_row, num_rows); break; case QuantizerType.pass2_fs_dither_quantizer: pass2_fs_dither(input_buf, in_row, output_buf, out_row, num_rows); break; case QuantizerType.pass2_no_dither_quantizer: pass2_no_dither(input_buf, in_row, output_buf, out_row, num_rows); break; default: throw new Exception("Specified Quantizer Type not implemented"); } } public virtual void finish_pass() { if (m_useFinishPass1) finish_pass1(); } /// /// Switch to a new external colormap between output passes. /// public virtual void new_color_map() { /* Reset the inverse color map */ m_needs_zeroed = true; } /// /// Prescan some rows of pixels. /// In this module the prescan simply updates the histogram, which has been /// initialized to zeroes by start_pass. /// An output_buf parameter is required by the method signature, but no data /// is actually output (in fact the buffer controller is probably passing a /// null pointer). /// private void prescan_quantize(byte[][] input_buf, int in_row, int num_rows) { for (int row = 0; row < num_rows; row++) { int inputIndex = 0; for (int col = m_cinfo.m_output_width; col > 0; col--) { int rowIndex = (int)input_buf[in_row + row][inputIndex] >> C0_SHIFT; int columnIndex = ((int)input_buf[in_row + row][inputIndex + 1] >> C1_SHIFT) * HIST_C2_ELEMS + ((int)input_buf[in_row + row][inputIndex + 2] >> C2_SHIFT); /* increment pixel value, check for overflow and undo increment if so. */ m_histogram[rowIndex][columnIndex]++; if (m_histogram[rowIndex][columnIndex] <= 0) m_histogram[rowIndex][columnIndex]--; inputIndex += 3; } } } /// /// Map some rows of pixels to the output colormapped representation. /// This version performs Floyd-Steinberg dithering /// private void pass2_fs_dither(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows) { byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset; for (int row = 0; row < num_rows; row++) { int inputPixelIndex = 0; int outputPixelIndex = 0; int errorIndex = 0; int dir; /* +1 or -1 depending on direction */ int dir3; /* 3*dir, for advancing inputIndex & errorIndex */ if (m_on_odd_row) { /* work right to left in this row */ inputPixelIndex += (m_cinfo.m_output_width - 1) * 3; /* so point to rightmost pixel */ outputPixelIndex += m_cinfo.m_output_width - 1; dir = -1; dir3 = -3; errorIndex = (m_cinfo.m_output_width + 1) * 3; /* => entry after last column */ m_on_odd_row = false; /* flip for next time */ } else { /* work left to right in this row */ dir = 1; dir3 = 3; errorIndex = 0; /* => entry before first real column */ m_on_odd_row = true; /* flip for next time */ } /* Preset error values: no error propagated to first pixel from left */ /* current error or pixel value */ int cur0 = 0; int cur1 = 0; int cur2 = 0; /* and no error propagated to row below yet */ /* error for pixel below cur */ int belowerr0 = 0; int belowerr1 = 0; int belowerr2 = 0; /* error for below/prev col */ int bpreverr0 = 0; int bpreverr1 = 0; int bpreverr2 = 0; for (int col = m_cinfo.m_output_width; col > 0; col--) { /* curN holds the error propagated from the previous pixel on the * current line. Add the error propagated from the previous line * to form the complete error correction term for this pixel, and * round the error term (which is expressed * 16) to an integer. * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct * for either sign of the error value. * Note: errorIndex is for *previous* column's array entry. */ cur0 = JpegUtils.RIGHT_SHIFT(cur0 + m_fserrors[errorIndex + dir3] + 8, 4); cur1 = JpegUtils.RIGHT_SHIFT(cur1 + m_fserrors[errorIndex + dir3 + 1] + 8, 4); cur2 = JpegUtils.RIGHT_SHIFT(cur2 + m_fserrors[errorIndex + dir3 + 2] + 8, 4); /* Limit the error using transfer function set by init_error_limit. * See comments with init_error_limit for rationale. */ cur0 = m_error_limiter[JpegConstants.MaxSampleValue + cur0]; cur1 = m_error_limiter[JpegConstants.MaxSampleValue + cur1]; cur2 = m_error_limiter[JpegConstants.MaxSampleValue + cur2]; /* Form pixel value + error, and range-limit to 0..MaxSampleValue. * The maximum error is +- MaxSampleValue (or less with error limiting); * this sets the required size of the range_limit array. */ cur0 += input_buf[in_row + row][inputPixelIndex]; cur1 += input_buf[in_row + row][inputPixelIndex + 1]; cur2 += input_buf[in_row + row][inputPixelIndex + 2]; cur0 = limit[limitOffset + cur0]; cur1 = limit[limitOffset + cur1]; cur2 = limit[limitOffset + cur2]; /* Index into the cache with adjusted pixel value */ int hRow = cur0 >> C0_SHIFT; int hColumn = (cur1 >> C1_SHIFT) * HIST_C2_ELEMS + (cur2 >> C2_SHIFT); /* If we have not seen this color before, find nearest colormap */ /* entry and update the cache */ if (m_histogram[hRow][hColumn] == 0) fill_inverse_cmap(cur0 >> C0_SHIFT, cur1 >> C1_SHIFT, cur2 >> C2_SHIFT); /* Now emit the colormap index for this cell */ int pixcode = m_histogram[hRow][hColumn] - 1; output_buf[out_row + row][outputPixelIndex] = (byte)pixcode; /* Compute representation error for this pixel */ cur0 -= m_cinfo.m_colormap[0][pixcode]; cur1 -= m_cinfo.m_colormap[1][pixcode]; cur2 -= m_cinfo.m_colormap[2][pixcode]; /* Compute error fractions to be propagated to adjacent pixels. * Add these into the running sums, and simultaneously shift the * next-line error sums left by 1 column. */ int bnexterr = cur0; /* Process component 0 */ int delta = cur0 * 2; cur0 += delta; /* form error * 3 */ m_fserrors[errorIndex] = (short)(bpreverr0 + cur0); cur0 += delta; /* form error * 5 */ bpreverr0 = belowerr0 + cur0; belowerr0 = bnexterr; cur0 += delta; /* form error * 7 */ bnexterr = cur1; /* Process component 1 */ delta = cur1 * 2; cur1 += delta; /* form error * 3 */ m_fserrors[errorIndex + 1] = (short)(bpreverr1 + cur1); cur1 += delta; /* form error * 5 */ bpreverr1 = belowerr1 + cur1; belowerr1 = bnexterr; cur1 += delta; /* form error * 7 */ bnexterr = cur2; /* Process component 2 */ delta = cur2 * 2; cur2 += delta; /* form error * 3 */ m_fserrors[errorIndex + 2] = (short)(bpreverr2 + cur2); cur2 += delta; /* form error * 5 */ bpreverr2 = belowerr2 + cur2; belowerr2 = bnexterr; cur2 += delta; /* form error * 7 */ /* At this point curN contains the 7/16 error value to be propagated * to the next pixel on the current line, and all the errors for the * next line have been shifted over. We are therefore ready to move on. */ inputPixelIndex += dir3; /* Advance pixel pointers to next column */ outputPixelIndex += dir; errorIndex += dir3; /* advance errorIndex to current column */ } /* Post-loop cleanup: we must unload the final error values into the * final fserrors[] entry. Note we need not unload belowerrN because * it is for the dummy column before or after the actual array. */ m_fserrors[errorIndex] = (short)bpreverr0; /* unload prev errs into array */ m_fserrors[errorIndex + 1] = (short)bpreverr1; m_fserrors[errorIndex + 2] = (short)bpreverr2; } } /// /// Map some rows of pixels to the output colormapped representation. /// This version performs no dithering /// private void pass2_no_dither(byte[][] input_buf, int in_row, byte[][] output_buf, int out_row, int num_rows) { for (int row = 0; row < num_rows; row++) { int inRow = row + in_row; int inIndex = 0; int outIndex = 0; int outRow = out_row + row; for (int col = m_cinfo.m_output_width; col > 0; col--) { /* get pixel value and index into the cache */ int c0 = (int)input_buf[inRow][inIndex] >> C0_SHIFT; inIndex++; int c1 = (int)input_buf[inRow][inIndex] >> C1_SHIFT; inIndex++; int c2 = (int)input_buf[inRow][inIndex] >> C2_SHIFT; inIndex++; int hRow = c0; int hColumn = c1 * HIST_C2_ELEMS + c2; /* If we have not seen this color before, find nearest colormap entry */ /* and update the cache */ if (m_histogram[hRow][hColumn] == 0) fill_inverse_cmap(c0, c1, c2); /* Now emit the colormap index for this cell */ output_buf[outRow][outIndex] = (byte)(m_histogram[hRow][hColumn] - 1); outIndex++; } } } /// /// Finish up at the end of each pass. /// private void finish_pass1() { /* Select the representative colors and fill in cinfo.colormap */ m_cinfo.m_colormap = m_sv_colormap; select_colors(m_desired); /* Force next pass to zero the color index table */ m_needs_zeroed = true; } /// /// Compute representative color for a box, put it in colormap[icolor] /// private void compute_color(box[] boxlist, int boxIndex, int icolor) { /* Current algorithm: mean weighted by pixels (not colors) */ /* Note it is important to get the rounding correct! */ long total = 0; long c0total = 0; long c1total = 0; long c2total = 0; box curBox = boxlist[boxIndex]; for (int c0 = curBox.c0min; c0 <= curBox.c0max; c0++) { for (int c1 = curBox.c1min; c1 <= curBox.c1max; c1++) { int histogramIndex = c1 * HIST_C2_ELEMS + curBox.c2min; for (int c2 = curBox.c2min; c2 <= curBox.c2max; c2++) { long count = m_histogram[c0][histogramIndex]; histogramIndex++; if (count != 0) { total += count; c0total += ((c0 << C0_SHIFT) + ((1 << C0_SHIFT) >> 1)) * count; c1total += ((c1 << C1_SHIFT) + ((1 << C1_SHIFT) >> 1)) * count; c2total += ((c2 << C2_SHIFT) + ((1 << C2_SHIFT) >> 1)) * count; } } } } m_cinfo.m_colormap[0][icolor] = (byte)((c0total + (total >> 1)) / total); m_cinfo.m_colormap[1][icolor] = (byte)((c1total + (total >> 1)) / total); m_cinfo.m_colormap[2][icolor] = (byte)((c2total + (total >> 1)) / total); } /// /// Master routine for color selection /// private void select_colors(int desired_colors) { /* Allocate workspace for box list */ box[] boxlist = new box[desired_colors]; /* Initialize one box containing whole space */ int numboxes = 1; boxlist[0].c0min = 0; boxlist[0].c0max = JpegConstants.MaxSampleValue >> C0_SHIFT; boxlist[0].c1min = 0; boxlist[0].c1max = JpegConstants.MaxSampleValue >> C1_SHIFT; boxlist[0].c2min = 0; boxlist[0].c2max = JpegConstants.MaxSampleValue >> C2_SHIFT; /* Shrink it to actually-used volume and set its statistics */ update_box(boxlist, 0); /* Perform median-cut to produce final box list */ numboxes = median_cut(boxlist, numboxes, desired_colors); /* Compute the representative color for each box, fill colormap */ for (int i = 0; i < numboxes; i++) compute_color(boxlist, i, i); m_cinfo.m_actual_number_of_colors = numboxes; } /// /// Repeatedly select and split the largest box until we have enough boxes /// private int median_cut(box[] boxlist, int numboxes, int desired_colors) { while (numboxes < desired_colors) { /* Select box to split. * Current algorithm: by population for first half, then by volume. */ int foundIndex; if (numboxes * 2 <= desired_colors) foundIndex = find_biggest_color_pop(boxlist, numboxes); else foundIndex = find_biggest_volume(boxlist, numboxes); if (foundIndex == -1) /* no splittable boxes left! */ break; /* Copy the color bounds to the new box. */ boxlist[numboxes].c0max = boxlist[foundIndex].c0max; boxlist[numboxes].c1max = boxlist[foundIndex].c1max; boxlist[numboxes].c2max = boxlist[foundIndex].c2max; boxlist[numboxes].c0min = boxlist[foundIndex].c0min; boxlist[numboxes].c1min = boxlist[foundIndex].c1min; boxlist[numboxes].c2min = boxlist[foundIndex].c2min; /* Choose which axis to split the box on. * Current algorithm: longest scaled axis. * See notes in update_box about scaling distances. */ int c0 = ((boxlist[foundIndex].c0max - boxlist[foundIndex].c0min) << C0_SHIFT) * R_SCALE; int c1 = ((boxlist[foundIndex].c1max - boxlist[foundIndex].c1min) << C1_SHIFT) * G_SCALE; int c2 = ((boxlist[foundIndex].c2max - boxlist[foundIndex].c2min) << C2_SHIFT) * B_SCALE; /* We want to break any ties in favor of green, then red, blue last. * This code does the right thing for R,G,B or B,G,R color orders only. */ int cmax = c1; int n = 1; if (c0 > cmax) { cmax = c0; n = 0; } if (c2 > cmax) { n = 2; } /* Choose split point along selected axis, and update box bounds. * Current algorithm: split at halfway point. * (Since the box has been shrunk to minimum volume, * any split will produce two nonempty subboxes.) * Note that lb value is max for lower box, so must be < old max. */ int lb; switch (n) { case 0: lb = (boxlist[foundIndex].c0max + boxlist[foundIndex].c0min) / 2; boxlist[foundIndex].c0max = lb; boxlist[numboxes].c0min = lb + 1; break; case 1: lb = (boxlist[foundIndex].c1max + boxlist[foundIndex].c1min) / 2; boxlist[foundIndex].c1max = lb; boxlist[numboxes].c1min = lb + 1; break; case 2: lb = (boxlist[foundIndex].c2max + boxlist[foundIndex].c2min) / 2; boxlist[foundIndex].c2max = lb; boxlist[numboxes].c2min = lb + 1; break; } /* Update stats for boxes */ update_box(boxlist, foundIndex); update_box(boxlist, numboxes); numboxes++; } return numboxes; } /* * Next we have the really interesting routines: selection of a colormap * given the completed histogram. * These routines work with a list of "boxes", each representing a rectangular * subset of the input color space (to histogram precision). */ /// /// Find the splittable box with the largest color population /// Returns null if no splittable boxes remain /// private static int find_biggest_color_pop(box[] boxlist, int numboxes) { long maxc = 0; int which = -1; for (int i = 0; i < numboxes; i++) { if (boxlist[i].colorcount > maxc && boxlist[i].volume > 0) { which = i; maxc = boxlist[i].colorcount; } } return which; } /// /// Find the splittable box with the largest (scaled) volume /// Returns null if no splittable boxes remain /// private static int find_biggest_volume(box[] boxlist, int numboxes) { int maxv = 0; int which = -1; for (int i = 0; i < numboxes; i++) { if (boxlist[i].volume > maxv) { which = i; maxv = boxlist[i].volume; } } return which; } /// /// Shrink the min/max bounds of a box to enclose only nonzero elements, /// and recompute its volume and population /// private void update_box(box[] boxlist, int boxIndex) { box curBox = boxlist[boxIndex]; bool have_c0min = false; if (curBox.c0max > curBox.c0min) { for (int c0 = curBox.c0min; c0 <= curBox.c0max; c0++) { for (int c1 = curBox.c1min; c1 <= curBox.c1max; c1++) { int histogramIndex = c1 * HIST_C2_ELEMS + curBox.c2min; for (int c2 = curBox.c2min; c2 <= curBox.c2max; c2++) { if (m_histogram[c0][histogramIndex++] != 0) { curBox.c0min = c0; have_c0min = true; break; } } if (have_c0min) break; } if (have_c0min) break; } } bool have_c0max = false; if (curBox.c0max > curBox.c0min) { for (int c0 = curBox.c0max; c0 >= curBox.c0min; c0--) { for (int c1 = curBox.c1min; c1 <= curBox.c1max; c1++) { int histogramIndex = c1 * HIST_C2_ELEMS + curBox.c2min; for (int c2 = curBox.c2min; c2 <= curBox.c2max; c2++) { if (m_histogram[c0][histogramIndex++] != 0) { curBox.c0max = c0; have_c0max = true; break; } } if (have_c0max) break; } if (have_c0max) break; } } bool have_c1min = false; if (curBox.c1max > curBox.c1min) { for (int c1 = curBox.c1min; c1 <= curBox.c1max; c1++) { for (int c0 = curBox.c0min; c0 <= curBox.c0max; c0++) { int histogramIndex = c1 * HIST_C2_ELEMS + curBox.c2min; for (int c2 = curBox.c2min; c2 <= curBox.c2max; c2++) { if (m_histogram[c0][histogramIndex++] != 0) { curBox.c1min = c1; have_c1min = true; break; } } if (have_c1min) break; } if (have_c1min) break; } } bool have_c1max = false; if (curBox.c1max > curBox.c1min) { for (int c1 = curBox.c1max; c1 >= curBox.c1min; c1--) { for (int c0 = curBox.c0min; c0 <= curBox.c0max; c0++) { int histogramIndex = c1 * HIST_C2_ELEMS + curBox.c2min; for (int c2 = curBox.c2min; c2 <= curBox.c2max; c2++) { if (m_histogram[c0][histogramIndex++] != 0) { curBox.c1max = c1; have_c1max = true; break; } } if (have_c1max) break; } if (have_c1max) break; } } bool have_c2min = false; if (curBox.c2max > curBox.c2min) { for (int c2 = curBox.c2min; c2 <= curBox.c2max; c2++) { for (int c0 = curBox.c0min; c0 <= curBox.c0max; c0++) { int histogramIndex = curBox.c1min * HIST_C2_ELEMS + c2; for (int c1 = curBox.c1min; c1 <= curBox.c1max; c1++, histogramIndex += HIST_C2_ELEMS) { if (m_histogram[c0][histogramIndex] != 0) { curBox.c2min = c2; have_c2min = true; break; } } if (have_c2min) break; } if (have_c2min) break; } } bool have_c2max = false; if (curBox.c2max > curBox.c2min) { for (int c2 = curBox.c2max; c2 >= curBox.c2min; c2--) { for (int c0 = curBox.c0min; c0 <= curBox.c0max; c0++) { int histogramIndex = curBox.c1min * HIST_C2_ELEMS + c2; for (int c1 = curBox.c1min; c1 <= curBox.c1max; c1++, histogramIndex += HIST_C2_ELEMS) { if (m_histogram[c0][histogramIndex] != 0) { curBox.c2max = c2; have_c2max = true; break; } } if (have_c2max) break; } if (have_c2max) break; } } /* Update box volume. * We use 2-norm rather than real volume here; this biases the method * against making long narrow boxes, and it has the side benefit that * a box is splittable iff norm > 0. * Since the differences are expressed in histogram-cell units, * we have to shift back to byte units to get consistent distances; * after which, we scale according to the selected distance scale factors. */ int dist0 = ((curBox.c0max - curBox.c0min) << C0_SHIFT) * R_SCALE; int dist1 = ((curBox.c1max - curBox.c1min) << C1_SHIFT) * G_SCALE; int dist2 = ((curBox.c2max - curBox.c2min) << C2_SHIFT) * B_SCALE; curBox.volume = dist0 * dist0 + dist1 * dist1 + dist2 * dist2; /* Now scan remaining volume of box and compute population */ long ccount = 0; for (int c0 = curBox.c0min; c0 <= curBox.c0max; c0++) { for (int c1 = curBox.c1min; c1 <= curBox.c1max; c1++) { int histogramIndex = c1 * HIST_C2_ELEMS + curBox.c2min; for (int c2 = curBox.c2min; c2 <= curBox.c2max; c2++, histogramIndex++) { if (m_histogram[c0][histogramIndex] != 0) ccount++; } } } curBox.colorcount = ccount; boxlist[boxIndex] = curBox; } /// /// Initialize the error-limiting transfer function (lookup table). /// The raw F-S error computation can potentially compute error values of up to /// +- MaxSampleValue. But we want the maximum correction applied to a pixel to be /// much less, otherwise obviously wrong pixels will be created. (Typical /// effects include weird fringes at color-area boundaries, isolated bright /// pixels in a dark area, etc.) The standard advice for avoiding this problem /// is to ensure that the "corners" of the color cube are allocated as output /// colors; then repeated errors in the same direction cannot cause cascading /// error buildup. However, that only prevents the error from getting /// completely out of hand; Aaron Giles reports that error limiting improves /// the results even with corner colors allocated. /// A simple clamping of the error values to about +- MaxSampleValue/8 works pretty /// well, but the smoother transfer function used below is even better. Thanks /// to Aaron Giles for this idea. /// private void init_error_limit() { m_error_limiter = new int[JpegConstants.MaxSampleValue * 2 + 1]; int tableOffset = JpegConstants.MaxSampleValue; const int STEPSIZE = ((JpegConstants.MaxSampleValue + 1) / 16); /* Map errors 1:1 up to +- MaxSampleValue/16 */ int output = 0; int input = 0; for (; input < STEPSIZE; input++, output++) { m_error_limiter[tableOffset + input] = output; m_error_limiter[tableOffset - input] = -output; } /* Map errors 1:2 up to +- 3*MaxSampleValue/16 */ for (; input < STEPSIZE * 3; input++) { m_error_limiter[tableOffset + input] = output; m_error_limiter[tableOffset - input] = -output; output += (input & 1) != 0 ? 1 : 0; } /* Clamp the rest to final output value (which is (MaxSampleValue+1)/8) */ for (; input <= JpegConstants.MaxSampleValue; input++) { m_error_limiter[tableOffset + input] = output; m_error_limiter[tableOffset - input] = -output; } } /* * These routines are concerned with the time-critical task of mapping input * colors to the nearest color in the selected colormap. * * We re-use the histogram space as an "inverse color map", essentially a * cache for the results of nearest-color searches. All colors within a * histogram cell will be mapped to the same colormap entry, namely the one * closest to the cell's center. This may not be quite the closest entry to * the actual input color, but it's almost as good. A zero in the cache * indicates we haven't found the nearest color for that cell yet; the array * is cleared to zeroes before starting the mapping pass. When we find the * nearest color for a cell, its colormap index plus one is recorded in the * cache for future use. The pass2 scanning routines call fill_inverse_cmap * when they need to use an unfilled entry in the cache. * * Our method of efficiently finding nearest colors is based on the "locally * sorted search" idea described by Heckbert and on the incremental distance * calculation described by Spencer W. Thomas in chapter III.1 of Graphics * Gems II (James Arvo, ed. Academic Press, 1991). Thomas points out that * the distances from a given colormap entry to each cell of the histogram can * be computed quickly using an incremental method: the differences between * distances to adjacent cells themselves differ by a constant. This allows a * fairly fast implementation of the "brute force" approach of computing the * distance from every colormap entry to every histogram cell. Unfortunately, * it needs a work array to hold the best-distance-so-far for each histogram * cell (because the inner loop has to be over cells, not colormap entries). * The work array elements have to be ints, so the work array would need * 256Kb at our recommended precision. This is not feasible in DOS machines. * * To get around these problems, we apply Thomas' method to compute the * nearest colors for only the cells within a small subbox of the histogram. * The work array need be only as big as the subbox, so the memory usage * problem is solved. Furthermore, we need not fill subboxes that are never * referenced in pass2; many images use only part of the color gamut, so a * fair amount of work is saved. An additional advantage of this * approach is that we can apply Heckbert's locality criterion to quickly * eliminate colormap entries that are far away from the subbox; typically * three-fourths of the colormap entries are rejected by Heckbert's criterion, * and we need not compute their distances to individual cells in the subbox. * The speed of this approach is heavily influenced by the subbox size: too * small means too much overhead, too big loses because Heckbert's criterion * can't eliminate as many colormap entries. Empirically the best subbox * size seems to be about 1/512th of the histogram (1/8th in each direction). * * Thomas' article also describes a refined method which is asymptotically * faster than the brute-force method, but it is also far more complex and * cannot efficiently be applied to small subboxes. It is therefore not * useful for programs intended to be portable to DOS machines. On machines * with plenty of memory, filling the whole histogram in one shot with Thomas' * refined method might be faster than the present code --- but then again, * it might not be any faster, and it's certainly more complicated. */ /* * The next three routines implement inverse colormap filling. They could * all be folded into one big routine, but splitting them up this way saves * some stack space (the mindist[] and bestdist[] arrays need not coexist) * and may allow some compilers to produce better code by registerizing more * inner-loop variables. */ /// /// Locate the colormap entries close enough to an update box to be candidates /// for the nearest entry to some cell(s) in the update box. The update box /// is specified by the center coordinates of its first cell. The number of /// candidate colormap entries is returned, and their colormap indexes are /// placed in colorlist[]. /// This routine uses Heckbert's "locally sorted search" criterion to select /// the colors that need further consideration. /// private int find_nearby_colors(int minc0, int minc1, int minc2, byte[] colorlist) { /* Compute true coordinates of update box's upper corner and center. * Actually we compute the coordinates of the center of the upper-corner * histogram cell, which are the upper bounds of the volume we care about. * Note that since ">>" rounds down, the "center" values may be closer to * min than to max; hence comparisons to them must be "<=", not "<". */ int maxc0 = minc0 + ((1 << BOX_C0_SHIFT) - (1 << C0_SHIFT)); int centerc0 = (minc0 + maxc0) >> 1; int maxc1 = minc1 + ((1 << BOX_C1_SHIFT) - (1 << C1_SHIFT)); int centerc1 = (minc1 + maxc1) >> 1; int maxc2 = minc2 + ((1 << BOX_C2_SHIFT) - (1 << C2_SHIFT)); int centerc2 = (minc2 + maxc2) >> 1; /* For each color in colormap, find: * 1. its minimum squared-distance to any point in the update box * (zero if color is within update box); * 2. its maximum squared-distance to any point in the update box. * Both of these can be found by considering only the corners of the box. * We save the minimum distance for each color in mindist[]; * only the smallest maximum distance is of interest. */ int minmaxdist = 0x7FFFFFFF; int[] mindist = new int[MAXNUMCOLORS]; /* min distance to colormap entry i */ for (int i = 0; i < m_cinfo.m_actual_number_of_colors; i++) { /* We compute the squared-c0-distance term, then add in the other two. */ int x = m_cinfo.m_colormap[0][i]; int min_dist; int max_dist; if (x < minc0) { int tdist = (x - minc0) * R_SCALE; min_dist = tdist * tdist; tdist = (x - maxc0) * R_SCALE; max_dist = tdist * tdist; } else if (x > maxc0) { int tdist = (x - maxc0) * R_SCALE; min_dist = tdist * tdist; tdist = (x - minc0) * R_SCALE; max_dist = tdist * tdist; } else { /* within cell range so no contribution to min_dist */ min_dist = 0; if (x <= centerc0) { int tdist = (x - maxc0) * R_SCALE; max_dist = tdist * tdist; } else { int tdist = (x - minc0) * R_SCALE; max_dist = tdist * tdist; } } x = m_cinfo.m_colormap[1][i]; if (x < minc1) { int tdist = (x - minc1) * G_SCALE; min_dist += tdist * tdist; tdist = (x - maxc1) * G_SCALE; max_dist += tdist * tdist; } else if (x > maxc1) { int tdist = (x - maxc1) * G_SCALE; min_dist += tdist * tdist; tdist = (x - minc1) * G_SCALE; max_dist += tdist * tdist; } else { /* within cell range so no contribution to min_dist */ if (x <= centerc1) { int tdist = (x - maxc1) * G_SCALE; max_dist += tdist * tdist; } else { int tdist = (x - minc1) * G_SCALE; max_dist += tdist * tdist; } } x = m_cinfo.m_colormap[2][i]; if (x < minc2) { int tdist = (x - minc2) * B_SCALE; min_dist += tdist * tdist; tdist = (x - maxc2) * B_SCALE; max_dist += tdist * tdist; } else if (x > maxc2) { int tdist = (x - maxc2) * B_SCALE; min_dist += tdist * tdist; tdist = (x - minc2) * B_SCALE; max_dist += tdist * tdist; } else { /* within cell range so no contribution to min_dist */ if (x <= centerc2) { int tdist = (x - maxc2) * B_SCALE; max_dist += tdist * tdist; } else { int tdist = (x - minc2) * B_SCALE; max_dist += tdist * tdist; } } mindist[i] = min_dist; /* save away the results */ if (max_dist < minmaxdist) minmaxdist = max_dist; } /* Now we know that no cell in the update box is more than minmaxdist * away from some colormap entry. Therefore, only colors that are * within minmaxdist of some part of the box need be considered. */ int ncolors = 0; for (int i = 0; i < m_cinfo.m_actual_number_of_colors; i++) { if (mindist[i] <= minmaxdist) colorlist[ncolors++] = (byte)i; } return ncolors; } /// /// Find the closest colormap entry for each cell in the update box, /// given the list of candidate colors prepared by find_nearby_colors. /// Return the indexes of the closest entries in the bestcolor[] array. /// This routine uses Thomas' incremental distance calculation method to /// find the distance from a colormap entry to successive cells in the box. /// private void find_best_colors(int minc0, int minc1, int minc2, int numcolors, byte[] colorlist, byte[] bestcolor) { /* Nominal steps between cell centers ("x" in Thomas article) */ const int STEP_C0 = ((1 << C0_SHIFT) * R_SCALE); const int STEP_C1 = ((1 << C1_SHIFT) * G_SCALE); const int STEP_C2 = ((1 << C2_SHIFT) * B_SCALE); /* This array holds the distance to the nearest-so-far color for each cell */ int[] bestdist = new int[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; /* Initialize best-distance for each cell of the update box */ int bestIndex = 0; for (int i = BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS - 1; i >= 0; i--) { bestdist[bestIndex] = 0x7FFFFFFF; bestIndex++; } /* For each color selected by find_nearby_colors, * compute its distance to the center of each cell in the box. * If that's less than best-so-far, update best distance and color number. */ for (int i = 0; i < numcolors; i++) { int icolor = colorlist[i]; /* Compute (square of) distance from minc0/c1/c2 to this color */ int inc0 = (minc0 - m_cinfo.m_colormap[0][icolor]) * R_SCALE; int dist0 = inc0 * inc0; int inc1 = (minc1 - m_cinfo.m_colormap[1][icolor]) * G_SCALE; dist0 += inc1 * inc1; int inc2 = (minc2 - m_cinfo.m_colormap[2][icolor]) * B_SCALE; dist0 += inc2 * inc2; /* Form the initial difference increments */ inc0 = inc0 * (2 * STEP_C0) + STEP_C0 * STEP_C0; inc1 = inc1 * (2 * STEP_C1) + STEP_C1 * STEP_C1; inc2 = inc2 * (2 * STEP_C2) + STEP_C2 * STEP_C2; /* Now loop over all cells in box, updating distance per Thomas method */ bestIndex = 0; int colorIndex = 0; int xx0 = inc0; for (int ic0 = BOX_C0_ELEMS - 1; ic0 >= 0; ic0--) { int dist1 = dist0; int xx1 = inc1; for (int ic1 = BOX_C1_ELEMS - 1; ic1 >= 0; ic1--) { int dist2 = dist1; int xx2 = inc2; for (int ic2 = BOX_C2_ELEMS - 1; ic2 >= 0; ic2--) { if (dist2 < bestdist[bestIndex]) { bestdist[bestIndex] = dist2; bestcolor[colorIndex] = (byte)icolor; } dist2 += xx2; xx2 += 2 * STEP_C2 * STEP_C2; bestIndex++; colorIndex++; } dist1 += xx1; xx1 += 2 * STEP_C1 * STEP_C1; } dist0 += xx0; xx0 += 2 * STEP_C0 * STEP_C0; } } } /// /// Fill the inverse-colormap entries in the update box that contains /// histogram cell c0/c1/c2. (Only that one cell MUST be filled, but /// we can fill as many others as we wish.) /// private void fill_inverse_cmap(int c0, int c1, int c2) { /* Convert cell coordinates to update box ID */ c0 >>= BOX_C0_LOG; c1 >>= BOX_C1_LOG; c2 >>= BOX_C2_LOG; /* Compute true coordinates of update box's origin corner. * Actually we compute the coordinates of the center of the corner * histogram cell, which are the lower bounds of the volume we care about. */ int minc0 = (c0 << BOX_C0_SHIFT) + ((1 << C0_SHIFT) >> 1); int minc1 = (c1 << BOX_C1_SHIFT) + ((1 << C1_SHIFT) >> 1); int minc2 = (c2 << BOX_C2_SHIFT) + ((1 << C2_SHIFT) >> 1); /* Determine which colormap entries are close enough to be candidates * for the nearest entry to some cell in the update box. */ /* This array lists the candidate colormap indexes. */ byte[] colorlist = new byte[MAXNUMCOLORS]; int numcolors = find_nearby_colors(minc0, minc1, minc2, colorlist); /* Determine the actually nearest colors. */ /* This array holds the actually closest colormap index for each cell. */ byte[] bestcolor = new byte[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; find_best_colors(minc0, minc1, minc2, numcolors, colorlist, bestcolor); /* Save the best color numbers (plus 1) in the main cache array */ c0 <<= BOX_C0_LOG; /* convert ID back to base cell indexes */ c1 <<= BOX_C1_LOG; c2 <<= BOX_C2_LOG; int bestcolorIndex = 0; for (int ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++) { for (int ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++) { int histogramIndex = (c1 + ic1) * HIST_C2_ELEMS + c2; for (int ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++) { m_histogram[c0 + ic0][histogramIndex] = (ushort)((int)bestcolor[bestcolorIndex] + 1); histogramIndex++; bestcolorIndex++; } } } } } #endregion #region ProgressiveHuffmanDecoder /// /// Expanded entropy decoder object for progressive Huffman decoding. /// /// The savable_state sub-record contains fields that change within an MCU, /// but must not be updated permanently until we complete the MCU. /// class ProgressiveHuffmanDecoder : JpegEntropyDecoder { private class savable_state { //savable_state operator=(savable_state src); public int EOBRUN; /* remaining EOBs in EOBRUN */ public int[] last_dc_val = new int[JpegConstants.MaxComponentsInScan]; /* last DC coef for each component */ public void Assign(savable_state ss) { EOBRUN = ss.EOBRUN; Buffer.BlockCopy(ss.last_dc_val, 0, last_dc_val, 0, last_dc_val.Length * sizeof(int)); } } private enum MCUDecoder { mcu_DC_first_decoder, mcu_AC_first_decoder, mcu_DC_refine_decoder, mcu_AC_refine_decoder } private MCUDecoder m_decoder; /* These fields are loaded into local variables at start of each MCU. * In case of suspension, we exit WITHOUT updating them. */ private SavedBitreadState m_bitstate; /* Bit buffer at start of MCU */ private savable_state m_saved = new savable_state(); /* Other state at start of MCU */ /* These fields are NOT loaded into local working state. */ private int m_restarts_to_go; /* MCUs left in this restart interval */ /* Pointers to derived tables (these workspaces have image lifespan) */ private DerivedTable[] m_derived_tbls = new DerivedTable[JpegConstants.NumberOfHuffmanTables]; private DerivedTable m_ac_derived_tbl; /* active table during an AC scan */ public ProgressiveHuffmanDecoder(JpegDecompressor cinfo) { m_cinfo = cinfo; /* Mark derived tables unallocated */ for (int i = 0; i < JpegConstants.NumberOfHuffmanTables; i++) m_derived_tbls[i] = null; /* Create progression status table */ cinfo.m_coef_bits = new int[cinfo.m_num_components][]; for (int i = 0; i < cinfo.m_num_components; i++) cinfo.m_coef_bits[i] = new int[JpegConstants.DCTSize2]; for (int ci = 0; ci < cinfo.m_num_components; ci++) { for (int i = 0; i < JpegConstants.DCTSize2; i++) cinfo.m_coef_bits[ci][i] = -1; } } /// /// Initialize for a Huffman-compressed scan. /// public override void start_pass() { /* Validate scan parameters */ bool bad = false; bool is_DC_band = (m_cinfo.m_Ss == 0); if (is_DC_band) { if (m_cinfo.m_Se != 0) bad = true; } else { /* need not check Ss/Se < 0 since they came from unsigned bytes */ if (m_cinfo.m_Ss > m_cinfo.m_Se || m_cinfo.m_Se >= JpegConstants.DCTSize2) bad = true; /* AC scans may have only one component */ if (m_cinfo.m_comps_in_scan != 1) bad = true; } if (m_cinfo.m_Ah != 0) { /* Successive approximation refinement scan: must have Al = Ah-1. */ if (m_cinfo.m_Al != m_cinfo.m_Ah - 1) bad = true; } if (m_cinfo.m_Al > 13) { /* need not check for < 0 */ bad = true; } /* Arguably the maximum Al value should be less than 13 for 8-bit precision, * but the spec doesn't say so, and we try to be liberal about what we * accept. Note: large Al values could result in out-of-range DC * coefficients during early scans, leading to bizarre displays due to * overflows in the IDCT math. But we won't crash. */ if (bad) throw new Exception(String.Format("Invalid progressive parameters Ss={0} Se={1} Ah={2} Al={3}", m_cinfo.m_Ss, m_cinfo.m_Se, m_cinfo.m_Ah, m_cinfo.m_Al)); /* Update progression status, and verify that scan order is legal. * Note that inter-scan inconsistencies are treated as warnings * not fatal errors ... not clear if this is right way to behave. */ for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { int cindex = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]].Component_index; for (int coefi = m_cinfo.m_Ss; coefi <= m_cinfo.m_Se; coefi++) { int expected = m_cinfo.m_coef_bits[cindex][coefi]; if (expected < 0) expected = 0; m_cinfo.m_coef_bits[cindex][coefi] = m_cinfo.m_Al; } } /* Select MCU decoding routine */ if (m_cinfo.m_Ah == 0) { if (is_DC_band) m_decoder = MCUDecoder.mcu_DC_first_decoder; else m_decoder = MCUDecoder.mcu_AC_first_decoder; } else { if (is_DC_band) m_decoder = MCUDecoder.mcu_DC_refine_decoder; else m_decoder = MCUDecoder.mcu_AC_refine_decoder; } for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]]; /* Make sure requested tables are present, and compute derived tables. * We may build same derived table more than once, but it's not expensive. */ if (is_DC_band) { if (m_cinfo.m_Ah == 0) { /* DC refinement needs no table */ jpeg_make_d_derived_tbl(true, componentInfo.Dc_tbl_no, ref m_derived_tbls[componentInfo.Dc_tbl_no]); } } else { jpeg_make_d_derived_tbl(false, componentInfo.Ac_tbl_no, ref m_derived_tbls[componentInfo.Ac_tbl_no]); /* remember the single active table */ m_ac_derived_tbl = m_derived_tbls[componentInfo.Ac_tbl_no]; } /* Initialize DC predictions to 0 */ m_saved.last_dc_val[ci] = 0; } /* Initialize bitread state variables */ m_bitstate.bits_left = 0; m_bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ m_insufficient_data = false; /* Initialize private state variables */ m_saved.EOBRUN = 0; /* Initialize restart counter */ m_restarts_to_go = m_cinfo.m_restart_interval; } public override bool decode_mcu(JpegBlock[] MCU_data) { switch (m_decoder) { case MCUDecoder.mcu_DC_first_decoder: return decode_mcu_DC_first(MCU_data); case MCUDecoder.mcu_AC_first_decoder: return decode_mcu_AC_first(MCU_data); case MCUDecoder.mcu_DC_refine_decoder: return decode_mcu_DC_refine(MCU_data); case MCUDecoder.mcu_AC_refine_decoder: return decode_mcu_AC_refine(MCU_data); } throw new Exception("The specified MCUDecoder is not implemented!"); } /* * Huffman MCU decoding. * Each of these routines decodes and returns one MCU's worth of * Huffman-compressed coefficients. * The coefficients are reordered from zigzag order into natural array order, * but are not de-quantized. * * The i'th block of the MCU is stored into the block pointed to by * MCU_data[i]. WE ASSUME THIS AREA IS INITIALLY ZEROED BY THE CALLER. * * We return false if data source requested suspension. In that case no * changes have been made to permanent state. (Exception: some output * coefficients may already have been assigned. This is harmless for * spectral selection, since we'll just re-assign them on the next call. * Successive approximation AC refinement has to be more careful, however.) */ /// /// MCU decoding for DC initial scan (either spectral selection, /// or first pass of successive approximation). /// private bool decode_mcu_DC_first(JpegBlock[] MCU_data) { /* Process restart marker if needed; may have to suspend */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { if (!process_restart()) return false; } } /* If we've run out of data, just leave the MCU set to zeroes. * This way, we return uniform gray for the remainder of the segment. */ if (!m_insufficient_data) { /* Load up working state */ int get_buffer; int bits_left; WorkingBitreadState br_state = new WorkingBitreadState(); BITREAD_LOAD_STATE(m_bitstate, out get_buffer, out bits_left, ref br_state); savable_state state = new savable_state(); state.Assign(m_saved); /* Outer loop handles each block in the MCU */ for (int blkn = 0; blkn < m_cinfo.m_blocks_in_MCU; blkn++) { int ci = m_cinfo.m_MCU_membership[blkn]; /* Decode a single block's worth of coefficients */ /* Section F.2.2.1: decode the DC coefficient difference */ int s; if (!HUFF_DECODE(out s, ref br_state, m_derived_tbls[m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]].Dc_tbl_no], ref get_buffer, ref bits_left)) return false; if (s != 0) { if (!CHECK_BIT_BUFFER(ref br_state, s, ref get_buffer, ref bits_left)) return false; int r = GET_BITS(s, get_buffer, ref bits_left); s = HUFF_EXTEND(r, s); } /* Convert DC difference to actual value, update last_dc_val */ s += state.last_dc_val[ci]; state.last_dc_val[ci] = s; /* Scale and output the coefficient (assumes jpeg_natural_order[0]=0) */ MCU_data[blkn][0] = (short)(s << m_cinfo.m_Al); } /* Completed MCU, so update state */ BITREAD_SAVE_STATE(ref m_bitstate, get_buffer, bits_left); m_saved.Assign(state); } /* Account for restart interval (no-op if not using restarts) */ m_restarts_to_go--; return true; } /// /// MCU decoding for AC initial scan (either spectral selection, /// or first pass of successive approximation). /// private bool decode_mcu_AC_first(JpegBlock[] MCU_data) { /* Process restart marker if needed; may have to suspend */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { if (!process_restart()) return false; } } /* If we've run out of data, just leave the MCU set to zeroes. * This way, we return uniform gray for the remainder of the segment. */ if (!m_insufficient_data) { /* Load up working state. * We can avoid loading/saving bitread state if in an EOB run. */ int EOBRUN = m_saved.EOBRUN; /* only part of saved state we need */ /* There is always only one block per MCU */ if (EOBRUN > 0) { /* if it's a band of zeroes... */ /* ...process it now (we do nothing) */ EOBRUN--; } else { int get_buffer; int bits_left; WorkingBitreadState br_state = new WorkingBitreadState(); BITREAD_LOAD_STATE(m_bitstate, out get_buffer, out bits_left, ref br_state); for (int k = m_cinfo.m_Ss; k <= m_cinfo.m_Se; k++) { int s; if (!HUFF_DECODE(out s, ref br_state, m_ac_derived_tbl, ref get_buffer, ref bits_left)) return false; int r = s >> 4; s &= 15; if (s != 0) { k += r; if (!CHECK_BIT_BUFFER(ref br_state, s, ref get_buffer, ref bits_left)) return false; r = GET_BITS(s, get_buffer, ref bits_left); s = HUFF_EXTEND(r, s); /* Scale and output coefficient in natural (dezigzagged) order */ MCU_data[0][JpegUtils.jpeg_natural_order[k]] = (short)(s << m_cinfo.m_Al); } else { if (r == 15) { /* ZRL */ k += 15; /* skip 15 zeroes in band */ } else { /* EOBr, run length is 2^r + appended bits */ EOBRUN = 1 << r; if (r != 0) { /* EOBr, r > 0 */ if (!CHECK_BIT_BUFFER(ref br_state, r, ref get_buffer, ref bits_left)) return false; r = GET_BITS(r, get_buffer, ref bits_left); EOBRUN += r; } EOBRUN--; /* this band is processed at this moment */ break; /* force end-of-band */ } } } BITREAD_SAVE_STATE(ref m_bitstate, get_buffer, bits_left); } /* Completed MCU, so update state */ m_saved.EOBRUN = EOBRUN; /* only part of saved state we need */ } /* Account for restart interval (no-op if not using restarts) */ m_restarts_to_go--; return true; } /// /// MCU decoding for DC successive approximation refinement scan. /// Note: we assume such scans can be multi-component, although the spec /// is not very clear on the point. /// private bool decode_mcu_DC_refine(JpegBlock[] MCU_data) { /* Process restart marker if needed; may have to suspend */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { if (!process_restart()) return false; } } /* Not worth the cycles to check insufficient_data here, * since we will not change the data anyway if we read zeroes. */ /* Load up working state */ int get_buffer; int bits_left; WorkingBitreadState br_state = new WorkingBitreadState(); BITREAD_LOAD_STATE(m_bitstate, out get_buffer, out bits_left, ref br_state); /* Outer loop handles each block in the MCU */ for (int blkn = 0; blkn < m_cinfo.m_blocks_in_MCU; blkn++) { /* Encoded data is simply the next bit of the two's-complement DC value */ if (!CHECK_BIT_BUFFER(ref br_state, 1, ref get_buffer, ref bits_left)) return false; if (GET_BITS(1, get_buffer, ref bits_left) != 0) { /* 1 in the bit position being coded */ MCU_data[blkn][0] |= (short)(1 << m_cinfo.m_Al); } /* Note: since we use |=, repeating the assignment later is safe */ } /* Completed MCU, so update state */ BITREAD_SAVE_STATE(ref m_bitstate, get_buffer, bits_left); /* Account for restart interval (no-op if not using restarts) */ m_restarts_to_go--; return true; } // There is always only one block per MCU private bool decode_mcu_AC_refine(JpegBlock[] MCU_data) { int p1 = 1 << m_cinfo.m_Al; /* 1 in the bit position being coded */ int m1 = -1 << m_cinfo.m_Al; /* -1 in the bit position being coded */ /* Process restart marker if needed; may have to suspend */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { if (!process_restart()) return false; } } /* If we've run out of data, don't modify the MCU. */ if (!m_insufficient_data) { /* Load up working state */ int get_buffer; int bits_left; WorkingBitreadState br_state = new WorkingBitreadState(); BITREAD_LOAD_STATE(m_bitstate, out get_buffer, out bits_left, ref br_state); int EOBRUN = m_saved.EOBRUN; /* only part of saved state we need */ /* If we are forced to suspend, we must undo the assignments to any newly * nonzero coefficients in the block, because otherwise we'd get confused * next time about which coefficients were already nonzero. * But we need not undo addition of bits to already-nonzero coefficients; * instead, we can test the current bit to see if we already did it. */ int num_newnz = 0; int[] newnz_pos = new int[JpegConstants.DCTSize2]; /* initialize coefficient loop counter to start of band */ int k = m_cinfo.m_Ss; if (EOBRUN == 0) { for (; k <= m_cinfo.m_Se; k++) { int s; if (!HUFF_DECODE(out s, ref br_state, m_ac_derived_tbl, ref get_buffer, ref bits_left)) { undo_decode_mcu_AC_refine(MCU_data, newnz_pos, num_newnz); return false; } int r = s >> 4; s &= 15; if (s != 0) { if (!CHECK_BIT_BUFFER(ref br_state, 1, ref get_buffer, ref bits_left)) { undo_decode_mcu_AC_refine(MCU_data, newnz_pos, num_newnz); return false; } if (GET_BITS(1, get_buffer, ref bits_left) != 0) { /* newly nonzero coef is positive */ s = p1; } else { /* newly nonzero coef is negative */ s = m1; } } else { if (r != 15) { EOBRUN = 1 << r; /* EOBr, run length is 2^r + appended bits */ if (r != 0) { if (!CHECK_BIT_BUFFER(ref br_state, r, ref get_buffer, ref bits_left)) { undo_decode_mcu_AC_refine(MCU_data, newnz_pos, num_newnz); return false; } r = GET_BITS(r, get_buffer, ref bits_left); EOBRUN += r; } break; /* rest of block is handled by EOB logic */ } /* note s = 0 for processing ZRL */ } /* Advance over already-nonzero coefs and r still-zero coefs, * appending correction bits to the nonzeroes. A correction bit is 1 * if the absolute value of the coefficient must be increased. */ do { int blockIndex = JpegUtils.jpeg_natural_order[k]; short thiscoef = MCU_data[0][blockIndex]; if (thiscoef != 0) { if (!CHECK_BIT_BUFFER(ref br_state, 1, ref get_buffer, ref bits_left)) { undo_decode_mcu_AC_refine(MCU_data, newnz_pos, num_newnz); return false; } if (GET_BITS(1, get_buffer, ref bits_left) != 0) { if ((thiscoef & p1) == 0) { /* do nothing if already set it */ if (thiscoef >= 0) MCU_data[0][blockIndex] += (short)p1; else MCU_data[0][blockIndex] += (short)m1; } } } else { if (--r < 0) break; /* reached target zero coefficient */ } k++; } while (k <= m_cinfo.m_Se); if (s != 0) { int pos = JpegUtils.jpeg_natural_order[k]; /* Output newly nonzero coefficient */ MCU_data[0][pos] = (short)s; /* Remember its position in case we have to suspend */ newnz_pos[num_newnz++] = pos; } } } if (EOBRUN > 0) { /* Scan any remaining coefficient positions after the end-of-band * (the last newly nonzero coefficient, if any). Append a correction * bit to each already-nonzero coefficient. A correction bit is 1 * if the absolute value of the coefficient must be increased. */ for (; k <= m_cinfo.m_Se; k++) { int blockIndex = JpegUtils.jpeg_natural_order[k]; short thiscoef = MCU_data[0][blockIndex]; if (thiscoef != 0) { if (!CHECK_BIT_BUFFER(ref br_state, 1, ref get_buffer, ref bits_left)) { //undo_decode_mcu_AC_refine(MCU_data[0], newnz_pos, num_newnz); undo_decode_mcu_AC_refine(MCU_data, newnz_pos, num_newnz); return false; } if (GET_BITS(1, get_buffer, ref bits_left) != 0) { if ((thiscoef & p1) == 0) { /* do nothing if already changed it */ if (thiscoef >= 0) MCU_data[0][blockIndex] += (short)p1; else MCU_data[0][blockIndex] += (short)m1; } } } } /* Count one block completed in EOB run */ EOBRUN--; } /* Completed MCU, so update state */ BITREAD_SAVE_STATE(ref m_bitstate, get_buffer, bits_left); m_saved.EOBRUN = EOBRUN; /* only part of saved state we need */ } /* Account for restart interval (no-op if not using restarts) */ m_restarts_to_go--; return true; } /// /// Check for a restart marker and resynchronize decoder. /// Returns false if must suspend. /// private bool process_restart() { /* Throw away any unused bits remaining in bit buffer; */ /* include any full bytes in next_marker's count of discarded bytes */ m_cinfo.m_marker.SkipBytes(m_bitstate.bits_left / 8); m_bitstate.bits_left = 0; /* Advance past the RSTn marker */ if (!m_cinfo.m_marker.read_restart_marker()) return false; /* Re-initialize DC predictions to 0 */ for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) m_saved.last_dc_val[ci] = 0; /* Re-init EOB run count, too */ m_saved.EOBRUN = 0; /* Reset restart counter */ m_restarts_to_go = m_cinfo.m_restart_interval; /* Reset out-of-data flag, unless read_restart_marker left us smack up * against a marker. In that case we will end up treating the next data * segment as empty, and we can avoid producing bogus output pixels by * leaving the flag set. */ if (m_cinfo.m_unread_marker == 0) m_insufficient_data = false; return true; } /// /// MCU decoding for AC successive approximation refinement scan. /// private static void undo_decode_mcu_AC_refine(JpegBlock[] block, int[] newnz_pos, int num_newnz) { /* Re-zero any output coefficients that we made newly nonzero */ while (num_newnz > 0) block[0][newnz_pos[--num_newnz]] = 0; } } #endregion #region ProgressiveHuffmanEncoder /// /// Expanded entropy encoder object for progressive Huffman encoding. /// class ProgressiveHuffmanEncoder : JpegEntropyEncoder { private enum MCUEncoder { mcu_DC_first_encoder, mcu_AC_first_encoder, mcu_DC_refine_encoder, mcu_AC_refine_encoder } /* MAX_CORR_BITS is the number of bits the AC refinement correction-bit * buffer can hold. Larger sizes may slightly improve compression, but * 1000 is already well into the realm of overkill. * The minimum safe size is 64 bits. */ private const int MAX_CORR_BITS = 1000; /* Max # of correction bits I can buffer */ private MCUEncoder m_MCUEncoder; /* Mode flag: true for optimization, false for actual data output */ private bool m_gather_statistics; // Bit-level coding status. private int m_put_buffer; /* current bit-accumulation buffer */ private int m_put_bits; /* # of bits now in it */ /* Coding status for DC components */ private int[] m_last_dc_val = new int[JpegConstants.MaxComponentsInScan]; /* last DC coef for each component */ /* Coding status for AC components */ private int m_ac_tbl_no; /* the table number of the single component */ private int m_EOBRUN; /* run length of EOBs */ private int m_BE; /* # of buffered correction bits before MCU */ private char[] m_bit_buffer; /* buffer for correction bits (1 per char) */ /* packing correction bits tightly would save some space but cost time... */ private int m_restarts_to_go; /* MCUs left in this restart interval */ private int m_next_restart_num; /* next restart number to write (0-7) */ /* Pointers to derived tables (these workspaces have image lifespan). * Since any one scan codes only DC or only AC, we only need one set * of tables, not one for DC and one for AC. */ private c_derived_tbl[] m_derived_tbls = new c_derived_tbl[JpegConstants.NumberOfHuffmanTables]; /* Statistics tables for optimization; again, one set is enough */ private long[][] m_count_ptrs = new long[JpegConstants.NumberOfHuffmanTables][]; public ProgressiveHuffmanEncoder(JpegCompressor cinfo) { m_cinfo = cinfo; /* Mark tables unallocated */ for (int i = 0; i < JpegConstants.NumberOfHuffmanTables; i++) { m_derived_tbls[i] = null; m_count_ptrs[i] = null; } } // Initialize for a Huffman-compressed scan using progressive JPEG. public override void start_pass(bool gather_statistics) { m_gather_statistics = gather_statistics; /* We assume the scan parameters are already validated. */ /* Select execution routines */ bool is_DC_band = (m_cinfo.m_Ss == 0); if (m_cinfo.m_Ah == 0) { if (is_DC_band) m_MCUEncoder = MCUEncoder.mcu_DC_first_encoder; else m_MCUEncoder = MCUEncoder.mcu_AC_first_encoder; } else { if (is_DC_band) { m_MCUEncoder = MCUEncoder.mcu_DC_refine_encoder; } else { m_MCUEncoder = MCUEncoder.mcu_AC_refine_encoder; /* AC refinement needs a correction bit buffer */ if (m_bit_buffer == null) m_bit_buffer = new char[MAX_CORR_BITS]; } } /* Only DC coefficients may be interleaved, so m_cinfo.comps_in_scan = 1 * for AC coefficients. */ for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]]; /* Initialize DC predictions to 0 */ m_last_dc_val[ci] = 0; /* Get table index */ int tbl; if (is_DC_band) { if (m_cinfo.m_Ah != 0) /* DC refinement needs no table */ continue; tbl = componentInfo.Dc_tbl_no; } else { m_ac_tbl_no = componentInfo.Ac_tbl_no; tbl = componentInfo.Ac_tbl_no; } if (m_gather_statistics) { /* Check for invalid table index */ /* (make_c_derived_tbl does this in the other path) */ if (tbl < 0 || tbl >= JpegConstants.NumberOfHuffmanTables) throw new Exception(String.Format("Huffman table 0x{0:X2} was not defined", tbl)); /* Allocate and zero the statistics tables */ /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ if (m_count_ptrs[tbl] == null) m_count_ptrs[tbl] = new long[257]; Array.Clear(m_count_ptrs[tbl], 0, 257); } else { /* Compute derived values for Huffman table */ /* We may do this more than once for a table, but it's not expensive */ jpeg_make_c_derived_tbl(is_DC_band, tbl, ref m_derived_tbls[tbl]); } } /* Initialize AC stuff */ m_EOBRUN = 0; m_BE = 0; /* Initialize bit buffer to empty */ m_put_buffer = 0; m_put_bits = 0; /* Initialize restart stuff */ m_restarts_to_go = m_cinfo.m_restart_interval; m_next_restart_num = 0; } public override bool encode_mcu(JpegBlock[][] MCU_data) { switch (m_MCUEncoder) { case MCUEncoder.mcu_DC_first_encoder: return encode_mcu_DC_first(MCU_data); case MCUEncoder.mcu_AC_first_encoder: return encode_mcu_AC_first(MCU_data); case MCUEncoder.mcu_DC_refine_encoder: return encode_mcu_DC_refine(MCU_data); case MCUEncoder.mcu_AC_refine_encoder: return encode_mcu_AC_refine(MCU_data); } throw new Exception("The specified MCUEncoder is not implemented."); } public override void finish_pass() { if (m_gather_statistics) finish_pass_gather_phuff(); else finish_pass_phuff(); } /// /// MCU encoding for DC initial scan (either spectral selection, /// or first pass of successive approximation). /// private bool encode_mcu_DC_first(JpegBlock[][] MCU_data) { /* Emit restart marker if needed */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) emit_restart(m_next_restart_num); } /* Encode the MCU data blocks */ for (int blkn = 0; blkn < m_cinfo.m_blocks_in_MCU; blkn++) { /* Compute the DC value after the required point transform by Al. * This is simply an arithmetic right shift. */ int temp2 = IRIGHT_SHIFT(MCU_data[blkn][0][0], m_cinfo.m_Al); /* DC differences are figured on the point-transformed values. */ int ci = m_cinfo.m_MCU_membership[blkn]; int temp = temp2 - m_last_dc_val[ci]; m_last_dc_val[ci] = temp2; /* Encode the DC coefficient difference per section G.1.2.1 */ temp2 = temp; if (temp < 0) { /* temp is abs value of input */ temp = -temp; /* For a negative input, want temp2 = bitwise complement of abs(input) */ /* This code assumes we are on a two's complement machine */ temp2--; } /* Find the number of bits needed for the magnitude of the coefficient */ int nbits = 0; while (temp != 0) { nbits++; temp >>= 1; } /* Check for out-of-range coefficient values. * Since we're encoding a difference, the range limit is twice as much. */ if (nbits > MAX_HUFFMAN_COEF_BITS + 1) throw new Exception("DCT coefficient out of range"); /* Count/emit the Huffman-coded symbol for the number of bits */ emit_symbol(m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]].Dc_tbl_no, nbits); /* Emit that number of bits of the value, if positive, */ /* or the complement of its magnitude, if negative. */ if (nbits != 0) { /* emit_bits rejects calls with size 0 */ emit_bits(temp2, nbits); } } /* Update restart-interval state too */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { m_restarts_to_go = m_cinfo.m_restart_interval; m_next_restart_num++; m_next_restart_num &= 7; } m_restarts_to_go--; } return true; } /// /// MCU encoding for AC initial scan (either spectral selection, /// or first pass of successive approximation). /// private bool encode_mcu_AC_first(JpegBlock[][] MCU_data) { /* Emit restart marker if needed */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) emit_restart(m_next_restart_num); } /* Encode the AC coefficients per section G.1.2.2, fig. G.3 */ /* r = run length of zeros */ int r = 0; for (int k = m_cinfo.m_Ss; k <= m_cinfo.m_Se; k++) { int temp = MCU_data[0][0][JpegUtils.jpeg_natural_order[k]]; if (temp == 0) { r++; continue; } /* We must apply the point transform by Al. For AC coefficients this * is an integer division with rounding towards 0. To do this portably * in C, we shift after obtaining the absolute value; so the code is * interwoven with finding the abs value (temp) and output bits (temp2). */ int temp2; if (temp < 0) { temp = -temp; /* temp is abs value of input */ temp >>= m_cinfo.m_Al; /* apply the point transform */ /* For a negative coef, want temp2 = bitwise complement of abs(coef) */ temp2 = ~temp; } else { temp >>= m_cinfo.m_Al; /* apply the point transform */ temp2 = temp; } /* Watch out for case that nonzero coef is zero after point transform */ if (temp == 0) { r++; continue; } /* Emit any pending EOBRUN */ if (m_EOBRUN > 0) emit_eobrun(); /* if run length > 15, must emit special run-length-16 codes (0xF0) */ while (r > 15) { emit_symbol(m_ac_tbl_no, 0xF0); r -= 16; } /* Find the number of bits needed for the magnitude of the coefficient */ int nbits = 1; /* there must be at least one 1 bit */ while ((temp >>= 1) != 0) nbits++; /* Check for out-of-range coefficient values */ if (nbits > MAX_HUFFMAN_COEF_BITS) throw new Exception("DCT coefficient out of range"); /* Count/emit Huffman symbol for run length / number of bits */ emit_symbol(m_ac_tbl_no, (r << 4) + nbits); /* Emit that number of bits of the value, if positive, */ /* or the complement of its magnitude, if negative. */ emit_bits(temp2, nbits); r = 0; /* reset zero run length */ } if (r > 0) { /* If there are trailing zeroes, */ m_EOBRUN++; /* count an EOB */ if (m_EOBRUN == 0x7FFF) emit_eobrun(); /* force it out to avoid overflow */ } /* Update restart-interval state too */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { m_restarts_to_go = m_cinfo.m_restart_interval; m_next_restart_num++; m_next_restart_num &= 7; } m_restarts_to_go--; } return true; } /// /// MCU encoding for DC successive approximation refinement scan. /// Note: we assume such scans can be multi-component, although the spec /// is not very clear on the point. /// private bool encode_mcu_DC_refine(JpegBlock[][] MCU_data) { /* Emit restart marker if needed */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) emit_restart(m_next_restart_num); } /* Encode the MCU data blocks */ for (int blkn = 0; blkn < m_cinfo.m_blocks_in_MCU; blkn++) { /* We simply emit the Al'th bit of the DC coefficient value. */ int temp = MCU_data[blkn][0][0]; emit_bits(temp >> m_cinfo.m_Al, 1); } /* Update restart-interval state too */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { m_restarts_to_go = m_cinfo.m_restart_interval; m_next_restart_num++; m_next_restart_num &= 7; } m_restarts_to_go--; } return true; } /// /// MCU encoding for AC successive approximation refinement scan. /// private bool encode_mcu_AC_refine(JpegBlock[][] MCU_data) { /* Emit restart marker if needed */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) emit_restart(m_next_restart_num); } /* Encode the MCU data block */ /* It is convenient to make a pre-pass to determine the transformed * coefficients' absolute values and the EOB position. */ int EOB = 0; int[] absvalues = new int[JpegConstants.DCTSize2]; for (int k = m_cinfo.m_Ss; k <= m_cinfo.m_Se; k++) { int temp = MCU_data[0][0][JpegUtils.jpeg_natural_order[k]]; /* We must apply the point transform by Al. For AC coefficients this * is an integer division with rounding towards 0. To do this portably * in C, we shift after obtaining the absolute value. */ if (temp < 0) temp = -temp; /* temp is abs value of input */ temp >>= m_cinfo.m_Al; /* apply the point transform */ absvalues[k] = temp; /* save abs value for main pass */ if (temp == 1) { /* EOB = index of last newly-nonzero coef */ EOB = k; } } /* Encode the AC coefficients per section G.1.2.3, fig. G.7 */ int r = 0; /* r = run length of zeros */ int BR = 0; /* BR = count of buffered bits added now */ int bitBufferOffset = m_BE; /* Append bits to buffer */ for (int k = m_cinfo.m_Ss; k <= m_cinfo.m_Se; k++) { int temp = absvalues[k]; if (temp == 0) { r++; continue; } /* Emit any required ZRLs, but not if they can be folded into EOB */ while (r > 15 && k <= EOB) { /* emit any pending EOBRUN and the BE correction bits */ emit_eobrun(); /* Emit ZRL */ emit_symbol(m_ac_tbl_no, 0xF0); r -= 16; /* Emit buffered correction bits that must be associated with ZRL */ emit_buffered_bits(bitBufferOffset, BR); bitBufferOffset = 0;/* BE bits are gone now */ BR = 0; } /* If the coef was previously nonzero, it only needs a correction bit. * NOTE: a straight translation of the spec's figure G.7 would suggest * that we also need to test r > 15. But if r > 15, we can only get here * if k > EOB, which implies that this coefficient is not 1. */ if (temp > 1) { /* The correction bit is the next bit of the absolute value. */ m_bit_buffer[bitBufferOffset + BR] = (char)(temp & 1); BR++; continue; } /* Emit any pending EOBRUN and the BE correction bits */ emit_eobrun(); /* Count/emit Huffman symbol for run length / number of bits */ emit_symbol(m_ac_tbl_no, (r << 4) + 1); /* Emit output bit for newly-nonzero coef */ temp = (MCU_data[0][0][JpegUtils.jpeg_natural_order[k]] < 0) ? 0 : 1; emit_bits(temp, 1); /* Emit buffered correction bits that must be associated with this code */ emit_buffered_bits(bitBufferOffset, BR); bitBufferOffset = 0;/* BE bits are gone now */ BR = 0; r = 0; /* reset zero run length */ } if (r > 0 || BR > 0) { /* If there are trailing zeroes, */ m_EOBRUN++; /* count an EOB */ m_BE += BR; /* concat my correction bits to older ones */ /* We force out the EOB if we risk either: * 1. overflow of the EOB counter; * 2. overflow of the correction bit buffer during the next MCU. */ if (m_EOBRUN == 0x7FFF || m_BE > (MAX_CORR_BITS - JpegConstants.DCTSize2 + 1)) emit_eobrun(); } /* Update restart-interval state too */ if (m_cinfo.m_restart_interval != 0) { if (m_restarts_to_go == 0) { m_restarts_to_go = m_cinfo.m_restart_interval; m_next_restart_num++; m_next_restart_num &= 7; } m_restarts_to_go--; } return true; } /// /// Finish up at the end of a Huffman-compressed progressive scan. /// private void finish_pass_phuff() { /* Flush out any buffered data */ emit_eobrun(); flush_bits(); } /// /// Finish up a statistics-gathering pass and create the new Huffman tables. /// private void finish_pass_gather_phuff() { /* Flush out buffered data (all we care about is counting the EOB symbol) */ emit_eobrun(); /* It's important not to apply jpeg_gen_optimal_table more than once * per table, because it clobbers the input frequency counts! */ bool[] did = new bool[JpegConstants.NumberOfHuffmanTables]; bool is_DC_band = (m_cinfo.m_Ss == 0); for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]]; int tbl = componentInfo.Ac_tbl_no; if (is_DC_band) { if (m_cinfo.m_Ah != 0) /* DC refinement needs no table */ continue; tbl = componentInfo.Dc_tbl_no; } if (!did[tbl]) { JpegHuffmanTable htblptr = null; if (is_DC_band) { if (m_cinfo.m_dc_huff_tbl_ptrs[tbl] == null) m_cinfo.m_dc_huff_tbl_ptrs[tbl] = new JpegHuffmanTable(); htblptr = m_cinfo.m_dc_huff_tbl_ptrs[tbl]; } else { if (m_cinfo.m_ac_huff_tbl_ptrs[tbl] == null) m_cinfo.m_ac_huff_tbl_ptrs[tbl] = new JpegHuffmanTable(); htblptr = m_cinfo.m_ac_huff_tbl_ptrs[tbl]; } jpeg_gen_optimal_table(htblptr, m_count_ptrs[tbl]); did[tbl] = true; } } } ////////////////////////////////////////////////////////////////////////// // Outputting bytes to the file. // NB: these must be called only when actually outputting, // that is, entropy.gather_statistics == false. // Emit a byte private void emit_byte(int val) { m_cinfo.m_dest.emit_byte(val); } /// /// Outputting bits to the file /// /// Only the right 24 bits of put_buffer are used; the valid bits are /// left-justified in this part. At most 16 bits can be passed to emit_bits /// in one call, and we never retain more than 7 bits in put_buffer /// between calls, so 24 bits are sufficient. /// private void emit_bits(int code, int size) { // Emit some bits, unless we are in gather mode /* This routine is heavily used, so it's worth coding tightly. */ int local_put_buffer = code; /* if size is 0, caller used an invalid Huffman table entry */ if (size == 0) throw new Exception("Missing Huffman code table entry"); if (m_gather_statistics) { /* do nothing if we're only getting stats */ return; } local_put_buffer &= (1 << size) - 1; /* mask off any extra bits in code */ m_put_bits += size; /* new number of bits in buffer */ local_put_buffer <<= 24 - m_put_bits; /* align incoming bits */ local_put_buffer |= m_put_buffer; /* and merge with old buffer contents */ while (m_put_bits >= 8) { int c = (local_put_buffer >> 16) & 0xFF; emit_byte(c); if (c == 0xFF) { /* need to stuff a zero byte? */ emit_byte(0); } local_put_buffer <<= 8; m_put_bits -= 8; } m_put_buffer = local_put_buffer; /* update variables */ } private void flush_bits() { emit_bits(0x7F, 7); /* fill any partial byte with ones */ m_put_buffer = 0; /* and reset bit-buffer to empty */ m_put_bits = 0; } // Emit (or just count) a Huffman symbol. private void emit_symbol(int tbl_no, int symbol) { if (m_gather_statistics) m_count_ptrs[tbl_no][symbol]++; else emit_bits(m_derived_tbls[tbl_no].ehufco[symbol], m_derived_tbls[tbl_no].ehufsi[symbol]); } // Emit bits from a correction bit buffer. private void emit_buffered_bits(int offset, int nbits) { if (m_gather_statistics) { /* no real work */ return; } for (int i = 0; i < nbits; i++) emit_bits(m_bit_buffer[offset + i], 1); } // Emit any pending EOBRUN symbol. private void emit_eobrun() { if (m_EOBRUN > 0) { /* if there is any pending EOBRUN */ int temp = m_EOBRUN; int nbits = 0; while ((temp >>= 1) != 0) nbits++; /* safety check: shouldn't happen given limited correction-bit buffer */ if (nbits > 14) throw new Exception("Missing Huffman code table entry"); emit_symbol(m_ac_tbl_no, nbits << 4); if (nbits != 0) emit_bits(m_EOBRUN, nbits); m_EOBRUN = 0; /* Emit any buffered correction bits */ emit_buffered_bits(0, m_BE); m_BE = 0; } } // Emit a restart marker & resynchronize predictions. private void emit_restart(int restart_num) { emit_eobrun(); if (!m_gather_statistics) { flush_bits(); emit_byte(0xFF); emit_byte((int)(JpegMarkerType.RST0 + restart_num)); } if (m_cinfo.m_Ss == 0) { /* Re-initialize DC predictions to 0 */ for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) m_last_dc_val[ci] = 0; } else { /* Re-initialize all AC-related fields to 0 */ m_EOBRUN = 0; m_BE = 0; } } /// /// IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than int. /// We assume that int right shift is unsigned if int right shift is, /// which should be safe. /// private static int IRIGHT_SHIFT(int x, int shft) { return (x >> shft); } } #endregion #region RawImage class RawImage : IRawImage { private List m_samples; private ColorSpace m_colorspace; private int m_currentRow = -1; internal RawImage(List samples, ColorSpace colorspace) { if (samples == null) throw new ArgumentNullException("'samples' Cannot be null!"); if (samples.Count <= 0) throw new Exception("No element specified in 'samples'"); if (colorspace == ColorSpace.Unknown) throw new Exception("Unknown Color Space!"); m_samples = samples; m_colorspace = colorspace; } public override int Width { get { return m_samples[0].Length; } } public override int Height { get { return m_samples.Count; } } public override ColorSpace Colorspace { get { return m_colorspace; } } public override int ComponentsPerPixel { get { return m_samples[0][0].ComponentCount; } } public override void BeginRead() { m_currentRow = 0; } public override byte[] GetPixelRow() { SampleRow row = m_samples[m_currentRow]; List result = new List(); for (int i = 0; i < row.Length; ++i) { Sample sample = row[i]; for (int j = 0; j < sample.ComponentCount; ++j) result.Add((byte)sample[j]); } ++m_currentRow; return result.ToArray(); } public override void EndRead() { } } #endregion #region Sample /// /// Represents a "sample" (you can understand it as a "pixel") of image. /// /// It's impossible to create an instance of this class directly, /// but you can use existing samples through collection. /// Usual scenario is to get row of samples from the method. /// public class Sample { private short[] m_components; private byte m_bitsPerComponent; internal Sample(BitStream bitStream, byte bitsPerComponent, byte componentCount) { if (bitStream == null) throw new ArgumentNullException("bitStream"); if (bitsPerComponent <= 0 || bitsPerComponent > 16) throw new ArgumentOutOfRangeException("bitsPerComponent"); if (componentCount <= 0 || componentCount > 5) throw new ArgumentOutOfRangeException("componentCount"); m_bitsPerComponent = bitsPerComponent; m_components = new short[componentCount]; for (short i = 0; i < componentCount; ++i) m_components[i] = (short)bitStream.Read(bitsPerComponent); } internal Sample(short[] components, byte bitsPerComponent) { if (components == null) throw new ArgumentNullException("components"); if (components.Length == 0 || components.Length > 5) throw new ArgumentException("components must be not empty and contain less than 5 elements"); if (bitsPerComponent <= 0 || bitsPerComponent > 16) throw new ArgumentOutOfRangeException("bitsPerComponent"); m_bitsPerComponent = bitsPerComponent; m_components = new short[components.Length]; Buffer.BlockCopy(components, 0, m_components, 0, components.Length * sizeof(short)); } /// /// Gets the number of bits per color component. /// /// The number of bits per color component. public byte BitsPerComponent { get { return m_bitsPerComponent; } } /// /// Gets the number of color components. /// /// The number of color components. public byte ComponentCount { get { return (byte)m_components.Length; } } /// /// Gets the color component at the specified index. /// /// The number of color component. /// Value of color component. public short this[int componentNumber] { get { return GetComponent(componentNumber); } } /// /// Gets the required color component. /// /// The number of color component. /// Value of color component. public short GetComponent(int componentNumber) { return m_components[componentNumber]; } } #endregion #region SampleRow /// /// Represents a row of image - collection of samples. /// public class SampleRow { private byte[] m_bytes; private Sample[] m_samples; /// /// Creates a row from raw samples data. /// /// Raw description of samples.
/// You can pass collection with more than sampleCount samples - only sampleCount samples /// will be parsed and all remaining bytes will be ignored. /// The number of samples in row. /// The number of bits per component. /// The number of components per sample. public SampleRow(byte[] row, int sampleCount, byte bitsPerComponent, byte componentsPerSample) { if (row == null) throw new ArgumentNullException("row"); if (row.Length == 0) throw new ArgumentException("row is empty"); if (sampleCount <= 0) throw new ArgumentOutOfRangeException("sampleCount"); if (bitsPerComponent <= 0 || bitsPerComponent > 16) throw new ArgumentOutOfRangeException("bitsPerComponent"); if (componentsPerSample <= 0 || componentsPerSample > 5) throw new ArgumentOutOfRangeException("componentsPerSample"); m_bytes = row; using (BitStream bitStream = new BitStream(row)) { m_samples = new Sample[sampleCount]; for (int i = 0; i < sampleCount; ++i) m_samples[i] = new Sample(bitStream, bitsPerComponent, componentsPerSample); } } /// /// Creates row from an array of components. /// /// Array of color components. /// The number of bits per component. /// The number of components per sample. /// The difference between this constructor and /// another one - /// this constructor accept an array of prepared color components whereas /// another constructor accept raw bytes and parse them. /// internal SampleRow(short[] sampleComponents, byte bitsPerComponent, byte componentsPerSample) { if (sampleComponents == null) throw new ArgumentNullException("sampleComponents"); if (sampleComponents.Length == 0) throw new ArgumentException("row is empty"); if (bitsPerComponent <= 0 || bitsPerComponent > 16) throw new ArgumentOutOfRangeException("bitsPerComponent"); if (componentsPerSample <= 0 || componentsPerSample > 5) throw new ArgumentOutOfRangeException("componentsPerSample"); int sampleCount = sampleComponents.Length / componentsPerSample; m_samples = new Sample[sampleCount]; for (int i = 0; i < sampleCount; ++i) { short[] components = new short[componentsPerSample]; Buffer.BlockCopy(sampleComponents, i * componentsPerSample * sizeof(short), components, 0, componentsPerSample * sizeof(short)); m_samples[i] = new Sample(components, bitsPerComponent); } using (BitStream bits = new BitStream()) { for (int i = 0; i < sampleCount; ++i) { for (int j = 0; j < componentsPerSample; ++j) bits.Write(sampleComponents[i * componentsPerSample + j], bitsPerComponent); } m_bytes = new byte[bits.UnderlyingStream.Length]; bits.UnderlyingStream.Seek(0, System.IO.SeekOrigin.Begin); bits.UnderlyingStream.Read(m_bytes, 0, (int)bits.UnderlyingStream.Length); } } /// /// Gets the number of samples in this row. /// /// The number of samples. public int Length { get { return m_samples.Length; } } /// /// Gets the sample at the specified index. /// /// The number of sample. /// The required sample. public Sample this[int sampleNumber] { get { return GetAt(sampleNumber); } } /// /// Gets the sample at the specified index. /// /// The number of sample. /// The required sample. public Sample GetAt(int sampleNumber) { return m_samples[sampleNumber]; } /// /// Serializes this row to raw bytes. /// /// The row representation as array of bytes public byte[] ToBytes() { return m_bytes; } } #endregion #region SavedBitreadState /// /// Bitreading state saved across MCUs /// struct SavedBitreadState { public int get_buffer; /* current bit-extraction buffer */ public int bits_left; /* # of unused bits in it */ } #endregion #region SourceManagerImpl /// /// Expanded data source object for stdio input /// class SourceManagerImpl : Jpeg_Source { private const int INPUT_BUF_SIZE = 4096; private JpegDecompressor m_cinfo; private Stream m_infile; /* source stream */ private byte[] m_buffer; /* start of buffer */ private bool m_start_of_file; /* have we gotten any data yet? */ /// /// Initialize source - called by jpeg_read_header /// before any data is actually read. /// public SourceManagerImpl(JpegDecompressor cinfo) { m_cinfo = cinfo; m_buffer = new byte[INPUT_BUF_SIZE]; } public void Attach(Stream infile) { m_infile = infile; m_infile.Seek(0, SeekOrigin.Begin); initInternalBuffer(null, 0); } public override void init_source() { /* We reset the empty-input-file flag for each image, * but we don't clear the input buffer. * This is correct behavior for reading a series of images from one source. */ m_start_of_file = true; } /// /// Fill the input buffer - called whenever buffer is emptied. /// /// In typical applications, this should read fresh data into the buffer /// (ignoring the current state of next_input_byte and bytes_in_buffer), /// reset the pointer and count to the start of the buffer, and return true /// indicating that the buffer has been reloaded. It is not necessary to /// fill the buffer entirely, only to obtain at least one more byte. /// /// There is no such thing as an EOF return. If the end of the file has been /// reached, the routine has a choice of ERREXIT() or inserting fake data into /// the buffer. In most cases, generating a warning message and inserting a /// fake EOI marker is the best course of action --- this will allow the /// decompressor to output however much of the image is there. However, /// the resulting error message is misleading if the real problem is an empty /// input file, so we handle that case specially. /// /// In applications that need to be able to suspend compression due to input /// not being available yet, a false return indicates that no more data can be /// obtained right now, but more may be forthcoming later. In this situation, /// the decompressor will return to its caller (with an indication of the /// number of scanlines it has read, if any). The application should resume /// decompression after it has loaded more data into the input buffer. Note /// that there are substantial restrictions on the use of suspension --- see /// the documentation. /// /// When suspending, the decompressor will back up to a convenient restart point /// (typically the start of the current MCU). next_input_byte and bytes_in_buffer /// indicate where the restart point will be if the current call returns false. /// Data beyond this point must be rescanned after resumption, so move it to /// the front of the buffer rather than discarding it. /// public override bool fill_input_buffer() { int nbytes = m_infile.Read(m_buffer, 0, INPUT_BUF_SIZE); if (nbytes <= 0) { if (m_start_of_file) /* Treat empty input file as fatal error */ throw new Exception("The input file is empty!"); /* Insert a fake EOI marker */ m_buffer[0] = (byte)0xFF; m_buffer[1] = (byte)JpegMarkerType.EOI; nbytes = 2; } initInternalBuffer(m_buffer, nbytes); m_start_of_file = false; return true; } } #endregion #region TransCoefControllerImpl /// /// This is a special implementation of the coefficient /// buffer controller. This is similar to jccoefct.c, but it handles only /// output from presupplied virtual arrays. Furthermore, we generate any /// dummy padding blocks on-the-fly rather than expecting them to be present /// in the arrays. /// class TransCoefControllerImpl : JpegCompressorCoefController { private JpegCompressor m_cinfo; private int m_iMCU_row_num; /* iMCU row # within image */ private int m_mcu_ctr; /* counts MCUs processed in current row */ private int m_MCU_vert_offset; /* counts MCU rows within iMCU row */ private int m_MCU_rows_per_iMCU_row; /* number of such rows needed */ /* Virtual block array for each component. */ private JpegVirtualArray[] m_whole_image; /* Workspace for constructing dummy blocks at right/bottom edges. */ private JpegBlock[][] m_dummy_buffer = new JpegBlock[JpegConstants.CompressorMaxBlocksInMCU][]; /// /// Initialize coefficient buffer controller. /// /// Each passed coefficient array must be the right size for that /// coefficient: width_in_blocks wide and height_in_blocks high, /// with unit height at least v_samp_factor. /// public TransCoefControllerImpl(JpegCompressor cinfo, JpegVirtualArray[] coef_arrays) { m_cinfo = cinfo; /* Save pointer to virtual arrays */ m_whole_image = coef_arrays; /* Allocate and pre-zero space for dummy DCT blocks. */ JpegBlock[] buffer = new JpegBlock[JpegConstants.CompressorMaxBlocksInMCU]; for (int i = 0; i < JpegConstants.CompressorMaxBlocksInMCU; i++) buffer[i] = new JpegBlock(); for (int i = 0; i < JpegConstants.CompressorMaxBlocksInMCU; i++) { m_dummy_buffer[i] = new JpegBlock[JpegConstants.CompressorMaxBlocksInMCU - i]; for (int j = i; j < JpegConstants.CompressorMaxBlocksInMCU; j++) m_dummy_buffer[i][j - i] = buffer[j]; } } /// /// Initialize for a processing pass. /// public virtual void start_pass(BufferMode pass_mode) { if (pass_mode != BufferMode.CrankDest) throw new Exception("Bogus buffer control mode"); m_iMCU_row_num = 0; start_iMCU_row(); } /// /// Process some data. /// We process the equivalent of one fully interleaved MCU row ("iMCU" row) /// per call, ie, v_samp_factor block rows for each component in the scan. /// The data is obtained from the virtual arrays and fed to the entropy coder. /// Returns true if the iMCU row is completed, false if suspended. /// /// NB: input_buf is ignored; it is likely to be a null pointer. /// public virtual bool compress_data(byte[][][] input_buf) { /* Align the virtual buffers for the components used in this scan. */ JpegBlock[][][] buffer = new JpegBlock[JpegConstants.MaxComponentsInScan][][]; for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]]; buffer[ci] = m_whole_image[componentInfo.Component_index].Access( m_iMCU_row_num * componentInfo.V_samp_factor, componentInfo.V_samp_factor); } /* Loop to process one whole iMCU row */ int last_MCU_col = m_cinfo.m_MCUs_per_row - 1; int last_iMCU_row = m_cinfo.m_total_iMCU_rows - 1; JpegBlock[][] MCU_buffer = new JpegBlock[JpegConstants.CompressorMaxBlocksInMCU][]; for (int yoffset = m_MCU_vert_offset; yoffset < m_MCU_rows_per_iMCU_row; yoffset++) { for (int MCU_col_num = m_mcu_ctr; MCU_col_num < m_cinfo.m_MCUs_per_row; MCU_col_num++) { /* Construct list of pointers to DCT blocks belonging to this MCU */ int blkn = 0; /* index of current DCT block within MCU */ for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { JpegComponent componentInfo = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[ci]]; int start_col = MCU_col_num * componentInfo.MCU_width; int blockcnt = (MCU_col_num < last_MCU_col) ? componentInfo.MCU_width : componentInfo.last_col_width; for (int yindex = 0; yindex < componentInfo.MCU_height; yindex++) { int xindex = 0; if (m_iMCU_row_num < last_iMCU_row || yindex + yoffset < componentInfo.last_row_height) { /* Fill in pointers to real blocks in this row */ for (xindex = 0; xindex < blockcnt; xindex++) { int bufLength = buffer[ci][yindex + yoffset].Length; int start = start_col + xindex; MCU_buffer[blkn] = new JpegBlock[bufLength - start]; for (int j = start; j < bufLength; j++) MCU_buffer[blkn][j - start] = buffer[ci][yindex + yoffset][j]; blkn++; } } else { /* At bottom of image, need a whole row of dummy blocks */ xindex = 0; } /* Fill in any dummy blocks needed in this row. * Dummy blocks are filled in the same way as in jccoefct.c: * all zeroes in the AC entries, DC entries equal to previous * block's DC value. The init routine has already zeroed the * AC entries, so we need only set the DC entries correctly. */ for (; xindex < componentInfo.MCU_width; xindex++) { MCU_buffer[blkn] = m_dummy_buffer[blkn]; MCU_buffer[blkn][0][0] = MCU_buffer[blkn - 1][0][0]; blkn++; } } } /* Try to write the MCU. */ if (!m_cinfo.m_entropy.encode_mcu(MCU_buffer)) { /* Suspension forced; update state counters and exit */ m_MCU_vert_offset = yoffset; m_mcu_ctr = MCU_col_num; return false; } } /* Completed an MCU row, but perhaps not an iMCU row */ m_mcu_ctr = 0; } /* Completed the iMCU row, advance counters for next one */ m_iMCU_row_num++; start_iMCU_row(); return true; } /// /// Reset within-iMCU-row counters for a new row /// private void start_iMCU_row() { /* In an interleaved scan, an MCU row is the same as an iMCU row. * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. * But at the bottom of the image, process only what's left. */ if (m_cinfo.m_comps_in_scan > 1) { m_MCU_rows_per_iMCU_row = 1; } else { if (m_iMCU_row_num < (m_cinfo.m_total_iMCU_rows - 1)) m_MCU_rows_per_iMCU_row = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[0]].V_samp_factor; else m_MCU_rows_per_iMCU_row = m_cinfo.Component_info[m_cinfo.m_cur_comp_info[0]].last_row_height; } m_mcu_ctr = 0; m_MCU_vert_offset = 0; } } #endregion #region UpsamplerImpl class UpsamplerImpl : JpegUpsampler { private enum ComponentUpsampler { noop_upsampler, fullsize_upsampler, h2v1_fancy_upsampler, h2v1_upsampler, h2v2_fancy_upsampler, h2v2_upsampler, int_upsampler } private JpegDecompressor m_cinfo; /* Color conversion buffer. When using separate upsampling and color * conversion steps, this buffer holds one upsampled row group until it * has been color converted and output. * Note: we do not allocate any storage for component(s) which are full-size, * ie do not need rescaling. The corresponding entry of color_buf[] is * simply set to point to the input data array, thereby avoiding copying. */ private ComponentBuffer[] m_color_buf = new ComponentBuffer[JpegConstants.MaxComponents]; // used only for fullsize_upsampler mode private int[] m_perComponentOffsets = new int[JpegConstants.MaxComponents]; /* Per-component upsampling method pointers */ private ComponentUpsampler[] m_upsampleMethods = new ComponentUpsampler[JpegConstants.MaxComponents]; private int m_currentComponent; // component being upsampled private int m_upsampleRowOffset; private int m_next_row_out; /* counts rows emitted from color_buf */ private int m_rows_to_go; /* counts rows remaining in image */ /* Height of an input row group for each component. */ private int[] m_rowgroup_height = new int[JpegConstants.MaxComponents]; /* These arrays save pixel expansion factors so that int_expand need not * re-compute them each time. They are unused for other up-sampling methods. */ private byte[] m_h_expand = new byte[JpegConstants.MaxComponents]; private byte[] m_v_expand = new byte[JpegConstants.MaxComponents]; public UpsamplerImpl(JpegDecompressor cinfo) { m_cinfo = cinfo; m_need_context_rows = false; /* until we find out differently */ if (cinfo.m_CCIR601_sampling) /* this isn't supported */ throw new Exception("CCIR601 sampling not implemented yet"); /* JpegDecompressorMainController doesn't support context rows when min_DCT_scaled_size = 1, * so don't ask for it. */ bool do_fancy = cinfo.m_do_fancy_upsampling && cinfo.m_min_DCT_scaled_size > 1; /* Verify we can handle the sampling factors, select per-component methods, * and create storage as needed. */ for (int ci = 0; ci < cinfo.m_num_components; ci++) { JpegComponent componentInfo = cinfo.Comp_info[ci]; /* Compute size of an "input group" after IDCT scaling. This many samples * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. */ int h_in_group = (componentInfo.H_samp_factor * componentInfo.DCT_scaled_size) / cinfo.m_min_DCT_scaled_size; int v_in_group = (componentInfo.V_samp_factor * componentInfo.DCT_scaled_size) / cinfo.m_min_DCT_scaled_size; int h_out_group = cinfo.m_max_h_samp_factor; int v_out_group = cinfo.m_max_v_samp_factor; /* save for use later */ m_rowgroup_height[ci] = v_in_group; bool need_buffer = true; if (!componentInfo.component_needed) { /* Don't bother to upsample an uninteresting component. */ m_upsampleMethods[ci] = ComponentUpsampler.noop_upsampler; need_buffer = false; } else if (h_in_group == h_out_group && v_in_group == v_out_group) { /* Fullsize components can be processed without any work. */ m_upsampleMethods[ci] = ComponentUpsampler.fullsize_upsampler; need_buffer = false; } else if (h_in_group * 2 == h_out_group && v_in_group == v_out_group) { /* Special cases for 2h1v upsampling */ if (do_fancy && componentInfo.downsampled_width > 2) m_upsampleMethods[ci] = ComponentUpsampler.h2v1_fancy_upsampler; else m_upsampleMethods[ci] = ComponentUpsampler.h2v1_upsampler; } else if (h_in_group * 2 == h_out_group && v_in_group * 2 == v_out_group) { /* Special cases for 2h2v upsampling */ if (do_fancy && componentInfo.downsampled_width > 2) { m_upsampleMethods[ci] = ComponentUpsampler.h2v2_fancy_upsampler; m_need_context_rows = true; } else { m_upsampleMethods[ci] = ComponentUpsampler.h2v2_upsampler; } } else if ((h_out_group % h_in_group) == 0 && (v_out_group % v_in_group) == 0) { /* Generic integral-factors up-sampling method */ m_upsampleMethods[ci] = ComponentUpsampler.int_upsampler; m_h_expand[ci] = (byte)(h_out_group / h_in_group); m_v_expand[ci] = (byte)(v_out_group / v_in_group); } else throw new Exception("Fractional sampling not implemented yet"); if (need_buffer) { ComponentBuffer cb = new ComponentBuffer(); cb.SetBuffer(JpegCommonBase.AllocJpegSamples(JpegUtils.jround_up(cinfo.m_output_width, cinfo.m_max_h_samp_factor), cinfo.m_max_v_samp_factor), null, 0); m_color_buf[ci] = cb; } } } /// /// Initialize for an upsampling pass. /// public override void start_pass() { /* Mark the conversion buffer empty */ m_next_row_out = m_cinfo.m_max_v_samp_factor; /* Initialize total-height counter for detecting bottom of image */ m_rows_to_go = m_cinfo.m_output_height; } /// /// Control routine to do upsampling (and color conversion). /// /// In this version we upsample each component independently. /// We upsample one row group into the conversion buffer, then apply /// color conversion a row at a time. /// public override void upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { /* Fill the conversion buffer, if it's empty */ if (m_next_row_out >= m_cinfo.m_max_v_samp_factor) { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { m_perComponentOffsets[ci] = 0; /* Invoke per-component upsample method.*/ m_currentComponent = ci; m_upsampleRowOffset = in_row_group_ctr * m_rowgroup_height[ci]; upsampleComponent(ref input_buf[ci]); } m_next_row_out = 0; } /* Color-convert and emit rows */ /* How many we have in the buffer: */ int num_rows = m_cinfo.m_max_v_samp_factor - m_next_row_out; /* Not more than the distance to the end of the image. Need this test * in case the image height is not a multiple of max_v_samp_factor: */ if (num_rows > m_rows_to_go) num_rows = m_rows_to_go; /* And not more than what the client can accept: */ out_rows_avail -= out_row_ctr; if (num_rows > out_rows_avail) num_rows = out_rows_avail; m_cinfo.m_cconvert.color_convert(m_color_buf, m_perComponentOffsets, m_next_row_out, output_buf, out_row_ctr, num_rows); /* Adjust counts */ out_row_ctr += num_rows; m_rows_to_go -= num_rows; m_next_row_out += num_rows; /* When the buffer is emptied, declare this input row group consumed */ if (m_next_row_out >= m_cinfo.m_max_v_samp_factor) in_row_group_ctr++; } private void upsampleComponent(ref ComponentBuffer input_data) { switch (m_upsampleMethods[m_currentComponent]) { case ComponentUpsampler.noop_upsampler: noop_upsample(); break; case ComponentUpsampler.fullsize_upsampler: fullsize_upsample(ref input_data); break; case ComponentUpsampler.h2v1_fancy_upsampler: h2v1_fancy_upsample(m_cinfo.Comp_info[m_currentComponent].downsampled_width, ref input_data); break; case ComponentUpsampler.h2v1_upsampler: h2v1_upsample(ref input_data); break; case ComponentUpsampler.h2v2_fancy_upsampler: h2v2_fancy_upsample(m_cinfo.Comp_info[m_currentComponent].downsampled_width, ref input_data); break; case ComponentUpsampler.h2v2_upsampler: h2v2_upsample(ref input_data); break; case ComponentUpsampler.int_upsampler: int_upsample(ref input_data); break; default: throw new Exception("The specified Component Up-sampler isn't implemented."); } } /* * These are the routines invoked to upsample pixel values * of a single component. One row group is processed per call. */ /// /// This is a no-op version used for "uninteresting" components. /// These components will not be referenced by color conversion. /// private static void noop_upsample() { // do nothing } /// /// For full-size components, we just make color_buf[ci] point at the /// input buffer, and thus avoid copying any data. Note that this is /// safe only because sep_upsample doesn't declare the input row group /// "consumed" until we are done color converting and emitting it. /// private void fullsize_upsample(ref ComponentBuffer input_data) { m_color_buf[m_currentComponent] = input_data; m_perComponentOffsets[m_currentComponent] = m_upsampleRowOffset; } /// /// Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. /// /// The upsampling algorithm is linear interpolation between pixel centers, /// also known as a "triangle filter". This is a good compromise between /// speed and visual quality. The centers of the output pixels are 1/4 and 3/4 /// of the way between input pixel centers. /// /// A note about the "bias" calculations: when rounding fractional values to /// integer, we do not want to always round 0.5 up to the next integer. /// If we did that, we'd introduce a noticeable bias towards larger values. /// Instead, this code is arranged so that 0.5 will be rounded up or down at /// alternate pixel locations (a simple ordered dither pattern). /// private void h2v1_fancy_upsample(int downsampled_width, ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; for (int inrow = 0; inrow < m_cinfo.m_max_v_samp_factor; inrow++) { int row = m_upsampleRowOffset + inrow; int inIndex = 0; int outIndex = 0; /* Special case for first column */ int invalue = input_data[row][inIndex]; inIndex++; output_data[inrow][outIndex] = (byte)invalue; outIndex++; output_data[inrow][outIndex] = (byte)((invalue * 3 + (int)input_data[row][inIndex] + 2) >> 2); outIndex++; for (int colctr = downsampled_width - 2; colctr > 0; colctr--) { /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ invalue = (int)input_data[row][inIndex] * 3; inIndex++; output_data[inrow][outIndex] = (byte)((invalue + (int)input_data[row][inIndex - 2] + 1) >> 2); outIndex++; output_data[inrow][outIndex] = (byte)((invalue + (int)input_data[row][inIndex] + 2) >> 2); outIndex++; } /* Special case for last column */ invalue = input_data[row][inIndex]; output_data[inrow][outIndex] = (byte)((invalue * 3 + (int)input_data[row][inIndex - 1] + 1) >> 2); outIndex++; output_data[inrow][outIndex] = (byte)invalue; outIndex++; } } /// /// Fast processing for the common case of 2:1 horizontal and 1:1 vertical. /// It's still a box filter. /// private void h2v1_upsample(ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; for (int inrow = 0; inrow < m_cinfo.m_max_v_samp_factor; inrow++) { int row = m_upsampleRowOffset + inrow; int outIndex = 0; for (int col = 0; col < m_cinfo.m_output_width; col++) { byte invalue = input_data[row][col]; /* don't need GETJSAMPLE() here */ output_data[inrow][outIndex] = invalue; outIndex++; output_data[inrow][outIndex] = invalue; outIndex++; } } } /// /// Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. /// Again a triangle filter; see comments for h2v1 case, above. /// /// It is OK for us to reference the adjacent input rows because we demanded /// context from the main buffer controller (see initialization code). /// private void h2v2_fancy_upsample(int downsampled_width, ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; int inrow = m_upsampleRowOffset; int outrow = 0; while (outrow < m_cinfo.m_max_v_samp_factor) { for (int v = 0; v < 2; v++) { // nearest input row index int inIndex0 = 0; //next nearest input row index int inIndex1 = 0; int inRow1 = -1; if (v == 0) { /* next nearest is row above */ inRow1 = inrow - 1; } else { /* next nearest is row below */ inRow1 = inrow + 1; } int row = outrow; int outIndex = 0; outrow++; /* Special case for first column */ int thiscolsum = (int)input_data[inrow][inIndex0] * 3 + (int)input_data[inRow1][inIndex1]; inIndex0++; inIndex1++; int nextcolsum = (int)input_data[inrow][inIndex0] * 3 + (int)input_data[inRow1][inIndex1]; inIndex0++; inIndex1++; output_data[row][outIndex] = (byte)((thiscolsum * 4 + 8) >> 4); outIndex++; output_data[row][outIndex] = (byte)((thiscolsum * 3 + nextcolsum + 7) >> 4); outIndex++; int lastcolsum = thiscolsum; thiscolsum = nextcolsum; for (int colctr = downsampled_width - 2; colctr > 0; colctr--) { /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ nextcolsum = (int)input_data[inrow][inIndex0] * 3 + (int)input_data[inRow1][inIndex1]; inIndex0++; inIndex1++; output_data[row][outIndex] = (byte)((thiscolsum * 3 + lastcolsum + 8) >> 4); outIndex++; output_data[row][outIndex] = (byte)((thiscolsum * 3 + nextcolsum + 7) >> 4); outIndex++; lastcolsum = thiscolsum; thiscolsum = nextcolsum; } /* Special case for last column */ output_data[row][outIndex] = (byte)((thiscolsum * 3 + lastcolsum + 8) >> 4); outIndex++; output_data[row][outIndex] = (byte)((thiscolsum * 4 + 7) >> 4); outIndex++; } inrow++; } } /// /// Fast processing for the common case of 2:1 horizontal and 2:1 vertical. /// It's still a box filter. /// private void h2v2_upsample(ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; int inrow = 0; int outrow = 0; while (outrow < m_cinfo.m_max_v_samp_factor) { int row = m_upsampleRowOffset + inrow; int outIndex = 0; for (int col = 0; col < m_cinfo.m_output_width; col++) { byte invalue = input_data[row][col]; /* don't need GETJSAMPLE() here */ output_data[outrow][outIndex] = invalue; outIndex++; output_data[outrow][outIndex] = invalue; outIndex++; } JpegUtils.jcopy_sample_rows(output_data, outrow, output_data, outrow + 1, 1, m_cinfo.m_output_width); inrow++; outrow += 2; } } /// /// This version handles any integral sampling ratios. /// This is not used for typical JPEG files, so it need not be fast. /// Nor, for that matter, is it particularly accurate: the algorithm is /// simple replication of the input pixel onto the corresponding output /// pixels. The hi-falutin sampling literature refers to this as a /// "box filter". A box filter tends to introduce visible artifacts, /// so if you are actually going to use 3:1 or 4:1 sampling ratios /// you would be well advised to improve this code. /// private void int_upsample(ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; int h_expand = m_h_expand[m_currentComponent]; int v_expand = m_v_expand[m_currentComponent]; int inrow = 0; int outrow = 0; while (outrow < m_cinfo.m_max_v_samp_factor) { /* Generate one output row with proper horizontal expansion */ int row = m_upsampleRowOffset + inrow; for (int col = 0; col < m_cinfo.m_output_width; col++) { byte invalue = input_data[row][col]; /* don't need GETJSAMPLE() here */ int outIndex = 0; for (int h = h_expand; h > 0; h--) { output_data[outrow][outIndex] = invalue; outIndex++; } } /* Generate any additional output rows by duplicating the first one */ if (v_expand > 1) { JpegUtils.jcopy_sample_rows(output_data, outrow, output_data, outrow + 1, v_expand - 1, m_cinfo.m_output_width); } inrow++; outrow += v_expand; } } } #endregion #region Utils class Utils { public static MemoryStream CopyStream(Stream stream) { if (stream == null) throw new ArgumentNullException("stream"); long positionBefore = stream.Position; stream.Seek(0, SeekOrigin.Begin); MemoryStream result = new MemoryStream((int)stream.Length); byte[] block = new byte[2048]; for (; ; ) { int bytesRead = stream.Read(block, 0, 2048); result.Write(block, 0, bytesRead); if (bytesRead < 2048) break; } stream.Seek(positionBefore, SeekOrigin.Begin); return result; } public static void CMYK2RGB(byte c, byte m, byte y, byte k, out byte red, out byte green, out byte blue) { float C, M, Y, K; C = c / 255.0f; M = m / 255.0f; Y = y / 255.0f; K = k / 255.0f; float R, G, B; R = C * (1.0f - K) + K; G = M * (1.0f - K) + K; B = Y * (1.0f - K) + K; R = (1.0f - R) * 255.0f + 0.5f; G = (1.0f - G) * 255.0f + 0.5f; B = (1.0f - B) * 255.0f + 0.5f; red = (byte)(R * 255); green = (byte)(G * 255); blue = (byte)(B * 255); } } #endregion #region WorkingBitreadState struct WorkingBitreadState { public int get_buffer; public int bits_left; public JpegDecompressor cinfo; } #endregion }