/// <summary>
        /// Losslessly transform the JPEG image associated with this transformer instance into one or more JPEG images
        /// stored in the given destination buffers. Lossless transforms work by moving the raw coefficients from one
        /// JPEG image structure to another without altering the values of the coefficients. While this is typically
        /// faster than decompressing the image, transforming it, and re-compressing it, lossless transforms are not
        /// free. Each lossless transform requires reading and performing Huffman decoding on all of the coefficients
        /// in the source image, regardless of the size of the destination image, thus this method provides a means of
        /// generating multiple transformed images from the same source or of applying multiple transformations
        /// simultaneously, in order to eliminate the need to read the source coefficients multiple times.
        /// </summary>
        /// <param name="destBuffers">
        /// An array of image buffers. <paramref name="destBuffers" />[i] will receive a JPEG image that has been
        /// transformed using the parameters in <paramref name="transforms" />[i].  Use
        /// <see cref="NativeMethods.bufSize" /> to determine the maximum size for each buffer based on the
        /// transformed or cropped width and height and the chroma subsampling used in the source image.
        /// </param>
        /// <param name="transforms">An array of <seealso cref="TurboJpegTransform" /> instances, each of which
        /// specifies the transform parameters and/or cropping region for the corresponding transformed output image.
        /// </param>
        /// <param name="options">The flags to use for the transforms.</param>
        public void Transform(byte[][] destBuffers, TurboJpegTransform[] transforms, TurboJpegFlags options)
        {
            Contract.Requires(destBuffers != null);
            Contract.Requires(transforms != null);
            Contract.Requires(transforms.Length == destBuffers.Length);
            Contract.Requires(Enum.IsDefined(typeof(TransformOptions), options));
            Contract.Ensures(Contract.ForAll(destBuffers, _ => _ != null));

            Contract.Assume(this.JpegBuffer != null, "JPEG buffer not initialized");

            var count = destBuffers.Length;
            var destBufferPtrs = new IntPtr[count];
            this.transformedSizes = new int[count];

            if (NativeMethods.transform(this.Handle,
                                        this.JpegBuffer,
                                        this.JpegSize,
                                        count,
                                        ref destBufferPtrs,
                                        ref this.transformedSizes,
                                        transforms,
                                        options) != 0)
            {
                throw new Exception(TurboJpegInterop.GetLastError());
            }

            // we now have the result in buffers on the unmanaged heap
            for (var i = 0; i < count; ++i)
            {
                destBuffers[i] = new byte[this.transformedSizes[i]];
                Marshal.Copy(destBufferPtrs[i], destBuffers[i], 0, this.transformedSizes[i]);
            }
        }
        /// <summary>
        /// Decompress the JPEG source image associated with this decompressor instance and output a YUV planar image to the given
        /// destination buffer. This method performs JPEG decompression but leaves out the colour conversion step, so a planar YUV
        /// image is generated instead of an RGB image.  The padding of the planes in this image is the same as in the images
        /// generated by <seealso cref="TurboJpegCompressor#EncodeYuv(byte[], int)" />.
        /// <para>
        /// NOTE: Technically, the JPEG format uses the YCbCr colourspace, but per the convention of the digital video
        /// community, the TurboJPEG API uses "YUV" to refer to an image format consisting of Y, Cb, and Cr image planes.
        /// </para>
        /// </summary>
        /// <param name="flags">The <see cref="TurboJpegFlags" /> controlling decompression.</param>
        public TurboJpegBuffer DecompressToYuv(TurboJpegFlags flags)
        {
            Contract.Requires(Enum.IsDefined(typeof(TurboJpegFlags), flags));
            Contract.Ensures(Contract.Result <TurboJpegBuffer>()
                             .BufferSize > 0,
                             "output buffer must have non-zero size");

            Contract.Assume(this.jpegBuffer != null, "No JPEG image is associated with this instance");

            var bufferSize = NativeMethods.bufSizeYUV(this.jpegWidth, 4, this.jpegHeight, this.jpegSubsampling);

            using (var buffer = new TurboJpegSafeHandle(NativeMethods.alloc(bufferSize)))
            {
                var ptr = buffer.DangerousGetHandle();
                if (NativeMethods.decompressToYUV(this.Handle,
                                                  this.jpegBuffer,
                                                  this.jpegBuffer.Length,
                                                  ref ptr,
                                                  this.jpegWidth,
                                                  4,
                                                  this.jpegHeight,
                                                  flags) != 0)
                {
                    throw new Exception(TurboJpegInterop.GetLastError());
                }

                // we now have the result in a buffer on the unmanaged heap.
                return(new TurboJpegBuffer(ptr, bufferSize));
            }
        }
Example #3
0
        /// <summary>
        /// Losslessly transform the JPEG image associated with this transformer instance into one or more JPEG images
        /// stored in the given destination buffers. Lossless transforms work by moving the raw coefficients from one
        /// JPEG image structure to another without altering the values of the coefficients. While this is typically
        /// faster than decompressing the image, transforming it, and re-compressing it, lossless transforms are not
        /// free. Each lossless transform requires reading and performing Huffman decoding on all of the coefficients
        /// in the source image, regardless of the size of the destination image, thus this method provides a means of
        /// generating multiple transformed images from the same source or of applying multiple transformations
        /// simultaneously, in order to eliminate the need to read the source coefficients multiple times.
        /// </summary>
        /// <param name="destBuffers">
        /// An array of image buffers. <paramref name="destBuffers" />[i] will receive a JPEG image that has been
        /// transformed using the parameters in <paramref name="transforms" />[i].  Use
        /// <see cref="NativeMethods.bufSize" /> to determine the maximum size for each buffer based on the
        /// transformed or cropped width and height and the chroma subsampling used in the source image.
        /// </param>
        /// <param name="transforms">An array of <seealso cref="TurboJpegTransform" /> instances, each of which
        /// specifies the transform parameters and/or cropping region for the corresponding transformed output image.
        /// </param>
        /// <param name="options">The flags to use for the transforms.</param>
        public void Transform(byte[][] destBuffers, TurboJpegTransform[] transforms, TurboJpegFlags options)
        {
            Contract.Requires(destBuffers != null);
            Contract.Requires(transforms != null);
            Contract.Requires(transforms.Length == destBuffers.Length);
            Contract.Requires(Enum.IsDefined(typeof(TransformOptions), options));
            Contract.Ensures(Contract.ForAll(destBuffers, _ => _ != null));

            Contract.Assume(this.JpegBuffer != null, "JPEG buffer not initialized");

            var count          = destBuffers.Length;
            var destBufferPtrs = new IntPtr[count];

            this.transformedSizes = new int[count];

            if (NativeMethods.transform(this.Handle,
                                        this.JpegBuffer,
                                        this.JpegSize,
                                        count,
                                        ref destBufferPtrs,
                                        ref this.transformedSizes,
                                        transforms,
                                        options) != 0)
            {
                throw new Exception(TurboJpegInterop.GetLastError());
            }

            // we now have the result in buffers on the unmanaged heap
            for (var i = 0; i < count; ++i)
            {
                destBuffers[i] = new byte[this.transformedSizes[i]];
                Marshal.Copy(destBufferPtrs[i], destBuffers[i], 0, this.transformedSizes[i]);
            }
        }
 internal static extern int decompressToYUV(IntPtr handle,
                                            byte[] sourceBuffer,
                                            int size,
                                            ref IntPtr destinationBuffer,
                                            int width,
                                            int pad,
                                            int height,
                                            [MarshalAs(UnmanagedType.I4)] TurboJpegFlags flags);
 internal static extern int transform(IntPtr handle,
                                      byte[] sourceBuffer,
                                      int sourceSize,
                                      int count,
                                      ref IntPtr[] destinationBuffers,
                                      ref int[] sizes,
                                      TurboJpegTransform[] transforms,
                                      [MarshalAs(UnmanagedType.I4)] TurboJpegFlags flags);
 internal static extern int encodeYUV(IntPtr handle,
                                      byte[] sourceBuffer,
                                      int width,
                                      int pitch,
                                      int height,
                                      [MarshalAs(UnmanagedType.I4)] PixelFormat pixelFormat,
                                      byte[] destinationBuffer,
                                      [MarshalAs(UnmanagedType.I4)] Subsampling subsamp,
                                      [MarshalAs(UnmanagedType.I4)] TurboJpegFlags flags);
 internal static extern int decompress(IntPtr handle,
                                       [In][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1)]
                                       byte[] sourceBuffer,
                                       int size,
                                       IntPtr destinationBuffer,
                                       int desiredWidth,
                                       int pitch,
                                       int desiredHeight,
                                       [MarshalAs(UnmanagedType.I4)] PixelFormat pixelFormat,
                                       [MarshalAs(UnmanagedType.I4)] TurboJpegFlags flags);
 internal static extern int compress(IntPtr handle,
                                     byte[] sourceBuffer,
                                     int width,
                                     int pitch,
                                     int height,
                                     [MarshalAs(UnmanagedType.I4)] PixelFormat pixelFormat,
                                     ref IntPtr destinationBuffer,
                                     ref ulong bufferSize,
                                     [MarshalAs(UnmanagedType.I4)] Subsampling jpegSubsamp,
                                     int jpegQual,
                                     [MarshalAs(UnmanagedType.I4)] TurboJpegFlags flags);
        /// <summary>
        /// Decompress the JPEG source image associated with this decompressor instance and output a decompressed image to
        /// the given destination buffer.
        /// </summary>
        /// <param name="desiredWidth">The desired width (in pixels) of the decompressed image.  If the desired image
        /// dimensions are different than the dimensions of the JPEG image being decompressed, then TurboJPEG will use
        /// scaling in the JPEG decompressor to generate the largest possible image that will fit within the desired
        /// dimensions. Setting this to 0 is the same as setting it to the width of the JPEG image; in other words the
        /// width will not be considered when determining the scaled image size).
        /// </param>
        /// <param name="pitch">The number of bytes per line of the destination image.  Normally, this should be set to
        /// <code>scaledWidth * <see cref="TurboJpegUtilities.GetPixelSize" />(pixelFormat)</code> if the decompressed
        /// image is unpadded, but you can use this to, for instance, pad each line of the decompressed image to a
        /// 4-byte boundary or to decompress the JPEG image into a region of a larger image. NOTE: <code>scaledWidth</code>
        /// can be determined by calling
        /// <code>scalingFactor.<seealso cref="TurboJpegScalingFactor#getScaled getScaled" />(jpegWidth)</code> or by
        /// calling <seealso cref="GetScaledWidth" />. Setting this parameter to 0 is the equivalent of setting it to
        /// <code>scaledWidth * <see cref="TurboJpegUtilities.GetPixelSize" />(pixelFormat)</code>.</param>
        /// <param name="desiredHeight">
        /// The desired Height (in pixels) of the decompressed image (or image region.)  If the desired
        /// image dimensions are different than the dimensions of the JPEG image being decompressed, then TurboJPEG will use
        /// scaling in the JPEG decompressor to generate the largest possible image that will fit within the desired dimensions.
        /// Setting this to 0 is the same as setting it to the Height of the JPEG image (in other words, the Height will not be
        /// considered when determining the scaled image size.)
        /// </param>
        /// <param name="pixelFormat">
        /// pixel format of the decompressed/decoded image (one of
        /// <seealso cref="TurboJpegUtilities#Rgb TurboJpegUtilities.PF_*" />)
        /// </param>
        /// <param name="flags">
        /// the bitwise OR of one or more of
        /// <seealso cref="TurboJpegUtilities#BottomUp TurboJpegUtilities.FLAG_*" />
        /// </param>
        public TurboJpegBuffer Decompress(int desiredWidth,
                                          int pitch,
                                          int desiredHeight,
                                          PixelFormat pixelFormat,
                                          TurboJpegFlags flags)
        {
            Contract.Requires(desiredWidth >= 0, "desiredWidth must be non-negative");
            Contract.Requires(desiredHeight >= 0, "desiredHeight must be non-negative");
            Contract.Requires(pitch >= 0, "pitch must be non-negative");
            Contract.Requires(Enum.IsDefined(typeof(TurboJpegFlags), flags));
            Contract.Ensures(Contract.Result <TurboJpegBuffer>()
                             .BufferSize > 0,
                             "output buffer must have non-zero size");

            Contract.Assume(this.jpegBuffer != null, "No JPEG image is associated with this instance");

            var pixelSize    = TurboJpegUtilities.GetPixelSize(pixelFormat);
            var scaledWidth  = this.GetScaledWidth(desiredWidth, desiredHeight);
            var scaledHeight = this.GetScaledHeight(desiredWidth, desiredHeight);

            if (pitch == 0)
            {
                pitch = scaledWidth * pixelSize;
            }

            var bufferSize = pitch * scaledHeight;

            // having allocated memory with the libjpeg-turbo allocator, we must ensure that we release it with the
            // matching deallocator lest Bad Things happen. Unlike compress, where the initial buffer size is a best
            // guess, we know the dimensions of the uncompressed image and the number of bits per pixel and so sizing
            // it appropriately is trivial and the buffer will never be reallocated
            using (var buffer = new TurboJpegSafeHandle(NativeMethods.alloc(bufferSize)))
            {
                var ptr = buffer.DangerousGetHandle();
                if (NativeMethods.decompress(this.Handle,
                                             this.jpegBuffer,
                                             this.jpegBuffer.Length,
                                             ptr,
                                             desiredWidth,
                                             pitch,
                                             desiredHeight,
                                             pixelFormat,
                                             flags) != 0)
                {
                    throw new Exception(TurboJpegInterop.GetLastError());
                }

                // we now have the result in a buffer on the unmanaged heap.
                return(new TurboJpegBuffer(ptr, bufferSize));
            }
        }
Example #10
0
        /// <summary>
        /// Encode the uncompressed source image associated with this compressor instance and return a buffer containing a
        /// YUV planar image. See <see cref="EncodeYuv(byte[], TurboJpegFlags)" /> for more detail.
        /// </summary>
        /// <param name="compressionOptions">Set of flags controlling compression.</param>
        /// <returns> a buffer containing a YUV planar image </returns>
        public byte[] EncodeYuv(TurboJpegFlags compressionOptions)
        {
            Contract.Ensures(Contract.Result <byte[]>().Length != 0, "output buffer must not be zero bytes long");
            Contract.Assume(this.sourceWidth > 0 && this.sourceHeight > 0,
                            "No source image is associated with this instance");

            var buf =
                new byte[
                    NativeMethods.bufSizeYUV(this.sourceWidth,
                                             this.sourcePitch,
                                             this.sourceHeight,
                                             this.Subsampling)];

            this.EncodeYuv(buf, compressionOptions);
            return(buf);
        }
Example #11
0
        /// <summary>
        /// Compress the uncompressed source image associated with this compressor instance and output a
        /// <see cref="TurboJpegBuffer" /> pointing to a JPEG image in memory.
        /// </summary>
        /// <param name="compressionOptions">Flags controlling compression.</param>
        /// <returns>A <see cref="TurboJpegBuffer"/> pointing to a JPEG image.</returns>
        /// <exception cref="System.InvalidOperationException">The source image has not been set.</exception>
        public TurboJpegBuffer Compress(TurboJpegFlags compressionOptions)
        {
            Contract.Requires(Enum.IsDefined(typeof(TurboJpegFlags), compressionOptions));
            Contract.Assume(this.sourceBuffer != null, "No source image is associated with this instance");
            Contract.Assume(this.jpegQuality >= 0, "JPEG quality not set");

            var bufferSize =
                (ulong)NativeMethods.bufSize(this.sourceWidth, this.sourceHeight, this.Subsampling);

            // having allocated memory with the libjpeg-turbo allocator, we must ensure that we release it with the
            // matching deallocator lest Bad Things happen
            var buffer = NativeMethods.alloc((int)bufferSize);

            try
            {
                if (this.sourceX >= 0 || this.sourceY >= 0)
                {
                    if (NativeMethods.compress(this.Handle,
                                               this.sourceBuffer,
                                               this.sourceWidth,
                                               this.sourcePitch,
                                               this.sourceHeight,
                                               this.sourcePixelFormat,
                                               ref buffer,
                                               ref bufferSize,
                                               this.Subsampling,
                                               this.jpegQuality,
                                               compressionOptions) != 0)
                    {
                        throw new Exception(Marshal.PtrToStringAnsi(NativeMethods.getErrorMessage()));
                    }
                }

                // we now have the result in a buffer on the unmanaged heap. It may have moved from the original,
                // but libjpeg-turbo has handled the reallocation for us
                return(new TurboJpegBuffer(buffer, (int)bufferSize));
            }
            catch
            {
                NativeMethods.free(buffer);
                throw;
            }
        }
Example #12
0
        /// <summary>
        /// Encode the uncompressed source image associated with this compressor instance and output a YUV planar
        /// image to the given destination buffer. This method uses the accelerated colour conversion routines in
        /// TurboJPEG's underlying codec to produce a planar YUV image that is suitable for direct video display.
        /// Specifically, if the chroma components are subsampled along the horizontal dimension, then the width of
        /// the luma plane is padded to the nearest multiple of 2 in the output image (same goes for the height of the
        /// luma plane, if the chroma components are subsampled along the vertical dimension.) Also, each line of each
        /// plane in the output image is padded to 4 bytes. Although this will work with any subsampling option, it is
        /// really only useful in combination with <see cref="P:Subsampling.Chroma420" />, which produces an image
        /// compatible with the I420 (AKA "YUV420P") format.
        /// <para>
        /// NOTE: Technically, the JPEG format uses the YCbCr colourspace but following the convention of the digital
        /// videocommunity (who, it's broadly admitted, didn't know anywhere near enough about video when they
        /// invented digital video), the TurboJPEG API uses "YUV" to refer to an image format consisting of Y, Cb,
        /// and Cr image planes.
        /// </para>
        /// </summary>
        /// <param name="destinationBuffer">A buffer that will receive the YUV planar image. Use
        /// <see cref="TurboJpegInterop.bufSizeYUV" /> to determine the appropriate size for this buffer based on the
        /// image width, height and chroma subsampling.</param>
        /// <param name="compressionOptions">Set of flags controlling compression.</param>
        public void EncodeYuv(byte[] destinationBuffer, TurboJpegFlags compressionOptions)
        {
            Contract.Requires(destinationBuffer != null, "destinationBuffer must not be null");
            Contract.Assume(this.sourceBuffer != null, "No source image is associated with this instance");

            NativeMethods.encodeYUV(this.Handle,
                                    this.sourceBuffer,
                                    this.sourceWidth,
                                    this.sourcePitch,
                                    this.sourceHeight,
                                    this.sourcePixelFormat,
                                    destinationBuffer,
                                    this.Subsampling,
                                    compressionOptions);
            this.CompressedSize = NativeMethods.bufSizeYUV(this.sourceWidth,
                                                           this.sourcePitch,
                                                           this.sourceHeight,
                                                           this.Subsampling);
        }
        /// <summary>
        /// Decompress the JPEG source image associated with this decompressor instance and output a decompressed image to
        /// the given destination buffer.
        /// </summary>
        /// <param name="desiredWidth">The desired width (in pixels) of the decompressed image.  If the desired image
        /// dimensions are different than the dimensions of the JPEG image being decompressed, then TurboJPEG will use
        /// scaling in the JPEG decompressor to generate the largest possible image that will fit within the desired
        /// dimensions. Setting this to 0 is the same as setting it to the width of the JPEG image; in other words the
        /// width will not be considered when determining the scaled image size).
        /// </param>
        /// <param name="pitch">The number of bytes per line of the destination image.  Normally, this should be set to
        /// <code>scaledWidth * <see cref="TurboJpegUtilities.GetPixelSize" />(pixelFormat)</code> if the decompressed
        /// image is unpadded, but you can use this to, for instance, pad each line of the decompressed image to a
        /// 4-byte boundary or to decompress the JPEG image into a region of a larger image. NOTE: <code>scaledWidth</code>
        /// can be determined by calling
        /// <code>scalingFactor.<seealso cref="TurboJpegScalingFactor#getScaled getScaled" />(jpegWidth)</code> or by
        /// calling <seealso cref="GetScaledWidth" />. Setting this parameter to 0 is the equivalent of setting it to
        /// <code>scaledWidth * <see cref="TurboJpegUtilities.GetPixelSize" />(pixelFormat)</code>.</param>
        /// <param name="desiredHeight">
        /// The desired Height (in pixels) of the decompressed image (or image region.)  If the desired
        /// image dimensions are different than the dimensions of the JPEG image being decompressed, then TurboJPEG will use
        /// scaling in the JPEG decompressor to generate the largest possible image that will fit within the desired dimensions.
        /// Setting this to 0 is the same as setting it to the Height of the JPEG image (in other words, the Height will not be
        /// considered when determining the scaled image size.)
        /// </param>
        /// <param name="pixelFormat">
        /// pixel format of the decompressed/decoded image (one of
        /// <seealso cref="TurboJpegUtilities#Rgb TurboJpegUtilities.PF_*" />)
        /// </param>
        /// <param name="flags">
        /// the bitwise OR of one or more of
        /// <seealso cref="TurboJpegUtilities#BottomUp TurboJpegUtilities.FLAG_*" />
        /// </param>
        public TurboJpegBuffer Decompress(int desiredWidth,
                                          int pitch,
                                          int desiredHeight,
                                          PixelFormat pixelFormat,
                                          TurboJpegFlags flags)
        {
            Contract.Requires(desiredWidth >= 0, "desiredWidth must be non-negative");
            Contract.Requires(desiredHeight >= 0, "desiredHeight must be non-negative");
            Contract.Requires(pitch >= 0, "pitch must be non-negative");
            Contract.Requires(Enum.IsDefined(typeof(TurboJpegFlags), flags));
            Contract.Ensures(Contract.Result<TurboJpegBuffer>()
                                 .BufferSize > 0,
                             "output buffer must have non-zero size");

            Contract.Assume(this.jpegBuffer != null, "No JPEG image is associated with this instance");

            var pixelSize = TurboJpegUtilities.GetPixelSize(pixelFormat);
            var scaledWidth = this.GetScaledWidth(desiredWidth, desiredHeight);
            var scaledHeight = this.GetScaledHeight(desiredWidth, desiredHeight);
            if (pitch == 0)
            {
                pitch = scaledWidth * pixelSize;
            }

            var bufferSize = pitch * scaledHeight;

            // having allocated memory with the libjpeg-turbo allocator, we must ensure that we release it with the 
            // matching deallocator lest Bad Things happen. Unlike compress, where the initial buffer size is a best 
            // guess, we know the dimensions of the uncompressed image and the number of bits per pixel and so sizing 
            // it appropriately is trivial and the buffer will never be reallocated
            using(var buffer = new TurboJpegSafeHandle(NativeMethods.alloc(bufferSize)))
            {
                var ptr = buffer.DangerousGetHandle();
                if (NativeMethods.decompress(this.Handle,
                                                this.jpegBuffer,
                                                this.jpegBuffer.Length,
                                                ptr,
                                                desiredWidth,
                                                pitch,
                                                desiredHeight,
                                                pixelFormat,
                                                flags) != 0)
                {
                    throw new Exception(TurboJpegInterop.GetLastError());
                }

                // we now have the result in a buffer on the unmanaged heap. 
                return new TurboJpegBuffer(ptr, bufferSize);
            }
        }
Example #14
0
        /// <summary>
        /// Losslessly transform the JPEG image associated with this transformer instance and return an array of
        /// <seealso cref="TurboJpegDecompressor" /> instances, each of which has a transformed JPEG image associated
        /// with it.
        /// </summary>
        /// <param name="transforms">An array of <see cref="TurboJpegTransform" /> instances, each of which specifies
        /// the transform parameters and/or cropping region for the corresponding transformed output image.</param>
        /// <param name="flags">The flags to use when transforming.</param>
        /// <param name="preallocateBuffers">If set to <c>true</c>, preallocate buffers.</param>
        /// <returns>
        /// An array of <seealso cref="TurboJpegDecompressor" /> instances, each of which has a transformed
        /// JPEG image associated with it.
        /// </returns>
        public TurboJpegDecompressor[] Transform(TurboJpegTransform[] transforms, TurboJpegFlags flags, bool preallocateBuffers)
        {
            Contract.Requires(transforms != null);
            Contract.Requires(Enum.IsDefined(typeof(TransformOptions), flags));
            Contract.Ensures(Contract.Result <TurboJpegDecompressor[]>() != null);
            Contract.Ensures(Contract.Result <TurboJpegDecompressor[]>().Length == transforms.Length);
            Contract.Ensures(Contract.ForAll(Contract.Result <TurboJpegDecompressor[]>(), _ => _ != null));

            var count = transforms.Length;
            var destinationBuffers = new byte[count][];

            Contract.Assume(this.Width > 0, "JPEG buffer not initialized");
            Contract.Assume(this.Height > 0, "JPEG buffer not initialized");

            var decompressors = new TurboJpegDecompressor[count];

            if (preallocateBuffers)
            {
                for (var i = 0; i < count; i++)
                {
                    var width  = this.Width;
                    var height = this.Height;
                    if ((transforms[i].Options & TransformOptions.Crop) != 0)
                    {
                        if (transforms[i].Width != 0)
                        {
                            width = transforms[i].Width;
                        }

                        if (transforms[i].Height != 0)
                        {
                            height = transforms[i].Height;
                        }
                    }

                    destinationBuffers[i] = new byte[NativeMethods.bufSize(width, height, this.Subsampling)];
                }

                this.Transform(destinationBuffers, transforms, flags);
                for (var i = 0; i < count; i++)
                {
                    decompressors[i] = new TurboJpegDecompressor(destinationBuffers[i]);
                }
            }
            else
            {
                var destBufferPtrs = new IntPtr[count];
                this.transformedSizes = new int[count];

                if (NativeMethods.transform(this.Handle,
                                            this.JpegBuffer,
                                            this.JpegSize,
                                            count,
                                            ref destBufferPtrs,
                                            ref this.transformedSizes,
                                            transforms,
                                            flags) != 0)
                {
                    throw new Exception(TurboJpegInterop.GetLastError());
                }

                // we now have the result in buffers on the unmanaged heap
                for (var i = 0; i < count; ++i)
                {
                    decompressors[i] = new TurboJpegDecompressor();
                    decompressors[i].SetJpegImage(destBufferPtrs[i]);
                }
            }

            return(decompressors);
        }
        /// <summary>
        /// Encode the uncompressed source image associated with this compressor instance and return a buffer containing a
        /// YUV planar image. See <see cref="EncodeYuv(byte[], TurboJpegFlags)" /> for more detail.
        /// </summary>
        /// <param name="compressionOptions">Set of flags controlling compression.</param>
        /// <returns> a buffer containing a YUV planar image </returns>
        public byte[] EncodeYuv(TurboJpegFlags compressionOptions)
        {
            Contract.Ensures(Contract.Result<byte[]>().Length != 0, "output buffer must not be zero bytes long");
            Contract.Assume(this.sourceWidth > 0 && this.sourceHeight > 0,
                            "No source image is associated with this instance");

            var buf =
                new byte[
                    NativeMethods.bufSizeYUV(this.sourceWidth,
                                                this.sourcePitch,
                                                this.sourceHeight,
                                                this.Subsampling)];
            this.EncodeYuv(buf, compressionOptions);
            return buf;
        }
        /// <summary>
        /// Encode the uncompressed source image associated with this compressor instance and output a YUV planar 
        /// image to the given destination buffer. This method uses the accelerated colour conversion routines in 
        /// TurboJPEG's underlying codec to produce a planar YUV image that is suitable for direct video display.  
        /// Specifically, if the chroma components are subsampled along the horizontal dimension, then the width of 
        /// the luma plane is padded to the nearest multiple of 2 in the output image (same goes for the height of the 
        /// luma plane, if the chroma components are subsampled along the vertical dimension.) Also, each line of each 
        /// plane in the output image is padded to 4 bytes. Although this will work with any subsampling option, it is 
        /// really only useful in combination with <see cref="P:Subsampling.Chroma420" />, which produces an image 
        /// compatible with the I420 (AKA "YUV420P") format.
        /// <para>
        /// NOTE: Technically, the JPEG format uses the YCbCr colourspace but following the convention of the digital 
        /// videocommunity (who, it's broadly admitted, didn't know anywhere near enough about video when they 
        /// invented digital video), the TurboJPEG API uses "YUV" to refer to an image format consisting of Y, Cb, 
        /// and Cr image planes.
        /// </para>
        /// </summary>
        /// <param name="destinationBuffer">A buffer that will receive the YUV planar image. Use
        /// <see cref="TurboJpegInterop.bufSizeYUV" /> to determine the appropriate size for this buffer based on the 
        /// image width, height and chroma subsampling.</param>
        /// <param name="compressionOptions">Set of flags controlling compression.</param>
        public void EncodeYuv(byte[] destinationBuffer, TurboJpegFlags compressionOptions)
        {
            Contract.Requires(destinationBuffer != null, "destinationBuffer must not be null");
            Contract.Assume(this.sourceBuffer != null, "No source image is associated with this instance");

            NativeMethods.encodeYUV(this.Handle,
                                       this.sourceBuffer,
                                       this.sourceWidth,
                                       this.sourcePitch,
                                       this.sourceHeight,
                                       this.sourcePixelFormat,
                                       destinationBuffer,
                                       this.Subsampling,
                                       compressionOptions);
            this.CompressedSize = NativeMethods.bufSizeYUV(this.sourceWidth,
                                                              this.sourcePitch,
                                                              this.sourceHeight,
                                                              this.Subsampling);
        }
        /// <summary>
        /// Compress the uncompressed source image associated with this compressor instance and output a
        /// <see cref="TurboJpegBuffer" /> pointing to a JPEG image in memory.
        /// </summary>
        /// <param name="compressionOptions">Flags controlling compression.</param>
        /// <returns>A <see cref="TurboJpegBuffer"/> pointing to a JPEG image.</returns>
        /// <exception cref="System.InvalidOperationException">The source image has not been set.</exception>
        public TurboJpegBuffer Compress(TurboJpegFlags compressionOptions)
        {
            Contract.Requires(Enum.IsDefined(typeof(TurboJpegFlags), compressionOptions));
            Contract.Assume(this.sourceBuffer != null, "No source image is associated with this instance");
            Contract.Assume(this.jpegQuality >= 0, "JPEG quality not set");

            var bufferSize =
                (ulong) NativeMethods.bufSize(this.sourceWidth, this.sourceHeight, this.Subsampling);
                
            // having allocated memory with the libjpeg-turbo allocator, we must ensure that we release it with the 
            // matching deallocator lest Bad Things happen
            var buffer = NativeMethods.alloc((int) bufferSize);
            try
            {
                if (this.sourceX >= 0 || this.sourceY >= 0)
                {
                    if (NativeMethods.compress(this.Handle,
                                                  this.sourceBuffer,
                                                  this.sourceWidth,
                                                  this.sourcePitch,
                                                  this.sourceHeight,
                                                  this.sourcePixelFormat,
                                                  ref buffer,
                                                  ref bufferSize,
                                                  this.Subsampling,
                                                  this.jpegQuality,
                                                  compressionOptions) != 0)
                    {
                        throw new Exception(Marshal.PtrToStringAnsi(NativeMethods.getErrorMessage()));
                    }
                }

                // we now have the result in a buffer on the unmanaged heap. It may have moved from the original, 
                // but libjpeg-turbo has handled the reallocation for us
                return new TurboJpegBuffer(buffer, (int)bufferSize);
            }
            catch
            {
                NativeMethods.free(buffer);
                throw;
            }
        }
        /// <summary>
        /// Decompress the JPEG source image associated with this decompressor instance and output a YUV planar image to the given
        /// destination buffer. This method performs JPEG decompression but leaves out the colour conversion step, so a planar YUV
        /// image is generated instead of an RGB image.  The padding of the planes in this image is the same as in the images
        /// generated by <seealso cref="TurboJpegCompressor#EncodeYuv(byte[], int)" />.
        /// <para>
        /// NOTE: Technically, the JPEG format uses the YCbCr colourspace, but per the convention of the digital video
        /// community, the TurboJPEG API uses "YUV" to refer to an image format consisting of Y, Cb, and Cr image planes.
        /// </para>
        /// </summary>
        /// <param name="flags">The <see cref="TurboJpegFlags" /> controlling decompression.</param>
        public TurboJpegBuffer DecompressToYuv(TurboJpegFlags flags)
        {
            Contract.Requires(Enum.IsDefined(typeof(TurboJpegFlags), flags));
            Contract.Ensures(Contract.Result<TurboJpegBuffer>()
                                 .BufferSize > 0,
                             "output buffer must have non-zero size");

            Contract.Assume(this.jpegBuffer != null, "No JPEG image is associated with this instance");

            var bufferSize = NativeMethods.bufSizeYUV(this.jpegWidth, 4, this.jpegHeight, this.jpegSubsampling);
            using(var buffer = new TurboJpegSafeHandle(NativeMethods.alloc(bufferSize)))
            {
                var ptr = buffer.DangerousGetHandle();
                if (NativeMethods.decompressToYUV(this.Handle,
                                                     this.jpegBuffer,
                                                     this.jpegBuffer.Length,
                                                     ref ptr,
                                                     this.jpegWidth,
                                                     4,
                                                     this.jpegHeight,
                                                     flags) != 0)
                {
                    throw new Exception(TurboJpegInterop.GetLastError());
                }

                // we now have the result in a buffer on the unmanaged heap. 
                return new TurboJpegBuffer(ptr, bufferSize);
            }
        }
        /// <summary>
        /// Losslessly transform the JPEG image associated with this transformer instance and return an array of
        /// <seealso cref="TurboJpegDecompressor" /> instances, each of which has a transformed JPEG image associated
        /// with it.
        /// </summary>
        /// <param name="transforms">An array of <see cref="TurboJpegTransform" /> instances, each of which specifies
        /// the transform parameters and/or cropping region for the corresponding transformed output image.</param>
        /// <param name="flags">The flags to use when transforming.</param>
        /// <param name="preallocateBuffers">If set to <c>true</c>, preallocate buffers.</param>
        /// <returns>
        /// An array of <seealso cref="TurboJpegDecompressor" /> instances, each of which has a transformed
        /// JPEG image associated with it.
        /// </returns>
        public TurboJpegDecompressor[] Transform(TurboJpegTransform[] transforms, TurboJpegFlags flags, bool preallocateBuffers)
        {
            Contract.Requires(transforms != null);
            Contract.Requires(Enum.IsDefined(typeof(TransformOptions), flags));
            Contract.Ensures(Contract.Result<TurboJpegDecompressor[]>() != null);
            Contract.Ensures(Contract.Result<TurboJpegDecompressor[]>().Length == transforms.Length);
            Contract.Ensures(Contract.ForAll(Contract.Result<TurboJpegDecompressor[]>(), _ => _ != null));

            var count = transforms.Length;
            var destinationBuffers = new byte[count][];
            Contract.Assume(this.Width > 0, "JPEG buffer not initialized");
            Contract.Assume(this.Height > 0, "JPEG buffer not initialized");

            var decompressors = new TurboJpegDecompressor[count];

            if (preallocateBuffers)
            {
                for (var i = 0; i < count; i++)
                {
                    var width = this.Width;
                    var height = this.Height;
                    if ((transforms[i].Options & TransformOptions.Crop) != 0)
                    {
                        if (transforms[i].Width != 0)
                        {
                            width = transforms[i].Width;
                        }

                        if (transforms[i].Height != 0)
                        {
                            height = transforms[i].Height;
                        }
                    }

                    destinationBuffers[i] = new byte[NativeMethods.bufSize(width, height, this.Subsampling)];
                }

                this.Transform(destinationBuffers, transforms, flags);
                for (var i = 0; i < count; i++)
                {
                    decompressors[i] = new TurboJpegDecompressor(destinationBuffers[i]);
                }
            }
            else
            {
                var destBufferPtrs = new IntPtr[count];
                this.transformedSizes = new int[count];

                if (NativeMethods.transform(this.Handle,
                                            this.JpegBuffer,
                                            this.JpegSize,
                                            count,
                                            ref destBufferPtrs,
                                            ref this.transformedSizes,
                                            transforms,
                                            flags) != 0)
                {
                    throw new Exception(TurboJpegInterop.GetLastError());
                }

                // we now have the result in buffers on the unmanaged heap
                for (var i = 0; i < count; ++i)
                {
                    decompressors[i] = new TurboJpegDecompressor();
                    decompressors[i].SetJpegImage(destBufferPtrs[i]);
                }
            }

            return decompressors;
        }