diff --git a/LibJpegWrapper/Quamotion.TurboJpegWrapper.csproj b/LibJpegWrapper/Quamotion.TurboJpegWrapper.csproj index fe4dd03..e7e2e98 100644 --- a/LibJpegWrapper/Quamotion.TurboJpegWrapper.csproj +++ b/LibJpegWrapper/Quamotion.TurboJpegWrapper.csproj @@ -12,8 +12,8 @@ Quamotion git https://github.com/qmfrederik/AS.TurboJpegWrapper - true - 1.0.3 + false + 1.0.4 diff --git a/LibJpegWrapper/TJCompressor.cs b/LibJpegWrapper/TJCompressor.cs index 76549b8..efc1520 100644 --- a/LibJpegWrapper/TJCompressor.cs +++ b/LibJpegWrapper/TJCompressor.cs @@ -14,7 +14,7 @@ public class TJCompressor : IDisposable { private IntPtr _compressorHandle; private bool _isDisposed; - private readonly object _lock = new object(); + private readonly object _lock; /// /// Creates new instance of @@ -25,6 +25,7 @@ public class TJCompressor : IDisposable public TJCompressor() { _compressorHandle = TurboJpegImport.tjInitCompress(); + _lock = new object(); if (_compressorHandle == IntPtr.Zero) { @@ -62,14 +63,13 @@ public byte[] Compress(Bitmap srcImage, TJSubsamplingOptions subSamp, int qualit var height = srcImage.Height; // ReSharper disable once ExceptionNotDocumented var srcData = srcImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, pixelFormat); - - var stride = srcData.Stride; - var srcPtr = srcData.Scan0; + // BitmapData.Stride may be negative if the bitmap is bottom up. In this case, BitmapData.Scan0 is the last scan line -> recalculate start of unmanaged memory + var stride = Math.Abs(srcData.Stride); + var srcPtr = srcData.Stride > 0 ? srcData.Scan0 : srcData.Scan0 - ((srcData.Height - 1) * stride); try { return Compress(srcPtr, stride, width, height, pixelFormat, subSamp, quality, flags); - } finally { @@ -79,6 +79,63 @@ public byte[] Compress(Bitmap srcImage, TJSubsamplingOptions subSamp, int qualit } + /// + /// Compresses input image to the jpeg format with specified quality + /// + /// Pointer to an image buffer containing RGB, grayscale, or CMYK pixels to be compressed. + /// This buffer is not modified. + /// Bytes per line in the source image. + /// Normally, this should be width * BytesPerPixel if the image is unpadded, + /// or TJPAD(width * BytesPerPixel if each line of the image + /// is padded to the nearest 32-bit boundary, as is the case for Windows bitmaps. + /// You can also be clever and use this parameter to skip lines, etc. + /// Setting this parameter to 0 is the equivalent of setting it to + /// width * BytesPerPixel. + /// Width (in pixels) of the source image + /// Height (in pixels) of the source image + /// Pixel format of the source image (see "Pixel formats") + /// The level of chrominance subsampling to be used when + /// generating the JPEG image (see "Chrominance subsampling options".) + /// The image quality of the generated JPEG image (1 = worst, 100 = best) + /// The bitwise OR of one or more of the "flags" + /// Size of the returned unmanaged buffer (in bytes). + /// + /// this + /// Throws if compress function failed + /// Object is disposed and can not be used anymore + /// Some parameters' values are incompatible: + /// Subsampling not equals to and pixel format + public TJUnmanagedMemory Compress(IntPtr srcPtr, int stride, int width, int height, PixelFormat pixelFormat, TJSubsamplingOptions subSamp, int quality, TJFlags flags, out ulong bufSize) + { + if (_isDisposed) + throw new ObjectDisposedException("this"); + + var tjPixelFormat = TJUtils.ConvertPixelFormat(pixelFormat); + CheckOptionsCompatibilityAndThrow(subSamp, tjPixelFormat); + + var buf = IntPtr.Zero; + bufSize = 0; + var result = TurboJpegImport.tjCompress2( + _compressorHandle, + srcPtr, + width, + stride, + height, + (int)tjPixelFormat, + ref buf, + ref bufSize, + (int)subSamp, + quality, + (int)flags); + + if (result == -1) + { + TJUtils.GetErrorAndThrow(); + } + + return new TJUnmanagedMemory(buf, bufSize); + } + /// /// Compresses input image to the jpeg format with specified quality /// @@ -114,43 +171,8 @@ public byte[] Compress(Bitmap srcImage, TJSubsamplingOptions subSamp, int qualit /// public byte[] Compress(IntPtr srcPtr, int stride, int width, int height, PixelFormat pixelFormat, TJSubsamplingOptions subSamp, int quality, TJFlags flags) { - if (_isDisposed) - throw new ObjectDisposedException("this"); - - var tjPixelFormat = TJUtils.ConvertPixelFormat(pixelFormat); - CheckOptionsCompatibilityAndThrow(subSamp, tjPixelFormat); - - var buf = IntPtr.Zero; - ulong bufSize = 0; - try - { - var result = TurboJpegImport.tjCompress2( - _compressorHandle, - srcPtr, - width, - stride, - height, - (int)tjPixelFormat, - ref buf, - ref bufSize, - (int)subSamp, - quality, - (int)flags); - - if (result == -1) - { - TJUtils.GetErrorAndThrow(); - } - - var jpegBuf = new byte[bufSize]; - // ReSharper disable once ExceptionNotDocumentedOptional - Marshal.Copy(buf, jpegBuf, 0, (int)bufSize); - return jpegBuf; - } - finally - { - TurboJpegImport.tjFree(buf); - } + ulong bufSize; + return Compress(srcPtr, stride, width, height, pixelFormat, subSamp, quality, flags, out bufSize); } /// @@ -188,47 +210,13 @@ public byte[] Compress(IntPtr srcPtr, int stride, int width, int height, PixelFo /// Subsampling not equals to and pixel format /// /// - public unsafe byte[] Compress(byte[] srcBuf, int stride, int width, int height, PixelFormat pixelFormat, TJSubsamplingOptions subSamp, int quality, TJFlags flags) + public byte[] Compress(byte[] srcBuf, int stride, int width, int height, PixelFormat pixelFormat, TJSubsamplingOptions subSamp, int quality, TJFlags flags) { if (_isDisposed) throw new ObjectDisposedException("this"); - var tjPixelFormat = TJUtils.ConvertPixelFormat(pixelFormat); - CheckOptionsCompatibilityAndThrow(subSamp, tjPixelFormat); - - var buf = IntPtr.Zero; - ulong bufSize = 0; - try - { - fixed (byte* srcBufPtr = srcBuf) - { - var result = TurboJpegImport.tjCompress2( - _compressorHandle, - (IntPtr)srcBufPtr, - width, - stride, - height, - (int)tjPixelFormat, - ref buf, - ref bufSize, - (int)subSamp, - quality, - (int)flags); - if (result == -1) - { - TJUtils.GetErrorAndThrow(); - } - } - - var jpegBuf = new byte[bufSize]; - // ReSharper disable once ExceptionNotDocumentedOptional - Marshal.Copy(buf, jpegBuf, 0, (int)bufSize); - return jpegBuf; - } - finally - { - TurboJpegImport.tjFree(buf); - } + using (var srcPtr = new TJUnmanagedMemory(srcBuf)) + return Compress((IntPtr)srcPtr, stride, width, height, pixelFormat, subSamp, quality, flags); } /// @@ -293,5 +281,4 @@ private static void CheckOptionsCompatibilityAndThrow(TJSubsamplingOptions subSa $"Subsampling differ from {TJSubsamplingOptions.TJSAMP_GRAY} for pixel format {TJPixelFormats.TJPF_GRAY} is not supported"); } } - } diff --git a/LibJpegWrapper/TJDecompressor.cs b/LibJpegWrapper/TJDecompressor.cs index b17adf9..4c55c77 100644 --- a/LibJpegWrapper/TJDecompressor.cs +++ b/LibJpegWrapper/TJDecompressor.cs @@ -11,9 +11,23 @@ namespace TurboJpegWrapper /// public class TJDecompressor : IDisposable { - private IntPtr _decompressorHandle = IntPtr.Zero; + private static ColorPalette _grayscalePalette; + private IntPtr _decompressorHandle; private bool _isDisposed; - private readonly object _lock = new object(); + private readonly object _lock; + + /// + /// Static constructor to create grayscale palette object + /// + static TJDecompressor() + { + using (var bitmap = new Bitmap(1, 1, PixelFormat.Format8bppIndexed)) // a ColorPalette cannot be created. Therefore, create a dummy Bitmap to get a template from + { + _grayscalePalette = bitmap.Palette; + for (var index = 0; index < _grayscalePalette.Entries.Length; ++index) // set the Color entries to grayscale + _grayscalePalette.Entries[index] = Color.FromArgb(index, index, index); + } + } /// /// Creates new instance of @@ -23,6 +37,7 @@ public class TJDecompressor : IDisposable /// public TJDecompressor() { + _lock = new object(); _decompressorHandle = TurboJpegImport.tjInitDecompress(); if (_decompressorHandle == IntPtr.Zero) @@ -44,46 +59,72 @@ public TJDecompressor() /// Raw pixel data of specified format /// Throws if underlying decompress function failed /// Object is disposed and can not be used anymore - public unsafe byte[] Decompress(IntPtr jpegBuf, ulong jpegBufSize, TJPixelFormats destPixelFormat, TJFlags flags, out int width, out int height, out int stride) + public byte[] Decompress(IntPtr jpegBuf, ulong jpegBufSize, TJPixelFormats destPixelFormat, TJFlags flags, out int width, out int height, out int stride) { - int outBufSize; - this.GetImageInfo(jpegBuf, jpegBufSize, destPixelFormat, out width, out height, out stride, out outBufSize); + if (_isDisposed) + throw new ObjectDisposedException("this"); - var buf = new byte[outBufSize]; + int outBufSize; + GetImageInfo(jpegBuf, jpegBufSize, destPixelFormat, out width, out height, out stride, out outBufSize); - fixed (byte* bufPtr = buf) + using (var outBuf = new TJUnmanagedMemory(outBufSize)) { - this.Decompress(jpegBuf, jpegBufSize, (IntPtr)bufPtr, outBufSize, destPixelFormat, flags, out width, out height, out stride); + DecompressInternal(jpegBuf, jpegBufSize, outBuf, outBufSize, destPixelFormat, flags, width, height, stride); + return outBuf; } - - return buf; } - public unsafe void Decompress(IntPtr jpegBuf, ulong jpegBufSize, IntPtr outBuf, int outBufSize, TJPixelFormats destPixelFormat, TJFlags flags, out int width, out int height, out int stride) + /// + /// Decompress a JPEG image to an RGB, grayscale, or CMYK image. + /// + /// Pointer to a buffer containing the JPEG image to decompress. This buffer is not modified + /// Size of the JPEG image (in bytes) + /// Buffer of unmanaged memory to hold the decompressed image + /// Size of the output buffer (in bytes) + /// Pixel format of the destination image (see "Pixel formats".) + /// The bitwise OR of one or more of the "flags" + /// Width of image in pixels + /// Height of image in pixels + /// Bytes per line in the destination image + /// Raw pixel data of specified format + /// Throws if underlying decompress function failed + /// Object is disposed and can not be used anymore + public void Decompress(IntPtr jpegBuf, ulong jpegBufSize, IntPtr outBuf, int outBufSize, TJPixelFormats destPixelFormat, TJFlags flags, out int width, out int height, out int stride) { if (_isDisposed) throw new ObjectDisposedException("this"); - int subsampl; - int colorspace; - var funcResult = TurboJpegImport.tjDecompressHeader(_decompressorHandle, jpegBuf, jpegBufSize, - out width, out height, out subsampl, out colorspace); - - if (funcResult == -1) - { - TJUtils.GetErrorAndThrow(); - } - - var targetFormat = destPixelFormat; - stride = TurboJpegImport.TJPAD(width * TurboJpegImport.PixelSizes[targetFormat]); - var bufSize = stride * height; + int bufSize; + GetImageInfo(jpegBuf, jpegBufSize, destPixelFormat, out width, out height, out stride, out bufSize); if (outBufSize < bufSize) { throw new ArgumentOutOfRangeException(nameof(outBufSize)); } - funcResult = TurboJpegImport.tjDecompress( + DecompressInternal(jpegBuf, jpegBufSize, outBuf, outBufSize, destPixelFormat, flags, width, height, stride); + } + + /// + /// Internal method to decompress a JPEG image to an RGB, grayscale, or CMYK image. + /// + /// Pointer to a buffer containing the JPEG image to decompress. This buffer is not modified + /// Size of the JPEG image (in bytes) + /// Buffer of unmanaged memory to hold the decompressed image + /// Size of the output buffer (in bytes) + /// Pixel format of the destination image (see "Pixel formats".) + /// The bitwise OR of one or more of the "flags" + /// Width of image in pixels + /// Height of image in pixels + /// Bytes per line in the destination image + /// Object is disposed and can not be used anymore + /// Throws if underlying decompress function failed + private void DecompressInternal(IntPtr jpegBuf, ulong jpegBufSize, IntPtr outBuf, int outBufSize, TJPixelFormats destPixelFormat, TJFlags flags, int width, int height, int stride) + { + if (_isDisposed) + throw new ObjectDisposedException("this"); + + var funcResult = TurboJpegImport.tjDecompress( _decompressorHandle, jpegBuf, jpegBufSize, @@ -91,7 +132,7 @@ public unsafe void Decompress(IntPtr jpegBuf, ulong jpegBufSize, IntPtr outBuf, width, stride, height, - (int)targetFormat, + (int)destPixelFormat, (int)flags); if (funcResult == -1) @@ -112,16 +153,14 @@ public unsafe void Decompress(IntPtr jpegBuf, ulong jpegBufSize, IntPtr outBuf, /// Raw pixel data of specified format /// Throws if underlying decompress function failed /// Object is disposed and can not be used anymore - public unsafe byte[] Decompress(byte[] jpegBuf, TJPixelFormats destPixelFormat, TJFlags flags, out int width, out int height, out int stride) + public byte[] Decompress(byte[] jpegBuf, TJPixelFormats destPixelFormat, TJFlags flags, out int width, out int height, out int stride) { if (_isDisposed) throw new ObjectDisposedException("this"); var jpegBufSize = (ulong)jpegBuf.Length; - fixed (byte* jpegPtr = jpegBuf) - { - return Decompress((IntPtr)jpegPtr, jpegBufSize, destPixelFormat, flags, out width, out height, out stride); - } + using (var jpegBufPtr = new TJUnmanagedMemory(jpegBuf)) + return Decompress(jpegBufPtr, jpegBufSize, destPixelFormat, flags, out width, out height, out stride); } /// @@ -135,7 +174,7 @@ public unsafe byte[] Decompress(byte[] jpegBuf, TJPixelFormats destPixelFormat, /// Throws if underlying decompress function failed /// Object is disposed and can not be used anymore /// Convertion to the requested pixel format can not be performed - public unsafe Bitmap Decompress(IntPtr jpegBuf, ulong jpegBufSize, PixelFormat destPixelFormat, TJFlags flags) + public Bitmap Decompress(IntPtr jpegBuf, ulong jpegBufSize, PixelFormat destPixelFormat, TJFlags flags) { if (_isDisposed) throw new ObjectDisposedException("this"); @@ -144,15 +183,68 @@ public unsafe Bitmap Decompress(IntPtr jpegBuf, ulong jpegBufSize, PixelFormat d int width; int height; int stride; - var buffer = Decompress(jpegBuf, jpegBufSize, targetFormat, flags, out width, out height, out stride); - Bitmap result; - fixed (byte* bufferPtr = buffer) + int outBufSize; + GetImageInfo(jpegBuf, jpegBufSize, targetFormat, out width, out height, out stride, out outBufSize); + + var result = new Bitmap(width, height, destPixelFormat); + var dstData = result.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, destPixelFormat); + try + { + // BitmapData.Stride may be negative if the bitmap is bottom up. In this case, BitmapData.Scan0 is the last scan line -> recalculate start of unmanaged memory + stride = Math.Abs(dstData.Stride); // use actual stride from GDI Bitmap + var dstPtr = dstData.Stride > 0 ? dstData.Scan0 : dstData.Scan0 - ((dstData.Height - 1) * stride); + DecompressInternal(jpegBuf, jpegBufSize, dstPtr, stride * height, targetFormat, flags, width, height, stride); + } + finally + { + result.UnlockBits(dstData); + } + if (destPixelFormat == PixelFormat.Format8bppIndexed) + { + result.Palette = _grayscalePalette; + } + return result; + } + + /// + /// Decompress a JPEG image to an RGB, grayscale, or CMYK image. + /// + /// Pointer to a buffer containing the JPEG image to decompress. This buffer is not modified + /// Size of the JPEG image (in bytes) + /// Decompressed image of specified format + /// Throws if underlying decompress function failed + /// Object is disposed and can not be used anymore + /// Convertion to the requested pixel format can not be performed + public Bitmap Decompress(IntPtr jpegBuf, ulong jpegBufSize) + { + if (_isDisposed) + throw new ObjectDisposedException("this"); + + TJPixelFormats targetFormat; + var flags = TJFlags.BOTTOMUP; + int width; + int height; + int stride; + int outBufSize; + GetImageInfo(jpegBuf, jpegBufSize, out targetFormat, out width, out height, out stride, out outBufSize); + + var destPixelFormat = TJUtils.ConvertPixelFormat(targetFormat); + var result = new Bitmap(width, height, destPixelFormat); + var dstData = result.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, destPixelFormat); + try { - result = new Bitmap(width, height, stride, destPixelFormat, (IntPtr)bufferPtr); - if (destPixelFormat == PixelFormat.Format8bppIndexed) - { - result.Palette = FixPaletteToGrayscale(result.Palette); - } + // BitmapData.Stride may be negative if the bitmap is bottom up. In this case, BitmapData.Scan0 is the last scan line -> recalculate start of unmanaged memory + stride = Math.Abs(dstData.Stride); // use actual stride from GDI Bitmap + var dstPtr = dstData.Stride > 0 ? dstData.Scan0 : dstData.Scan0 - ((dstData.Height - 1) * stride); + DecompressInternal(jpegBuf, jpegBufSize, dstPtr, stride * height, targetFormat, flags, width, height, stride); + } + finally + { + result.UnlockBits(dstData); + } + if (destPixelFormat == PixelFormat.Format8bppIndexed) + { + result.Palette = _grayscalePalette; } return result; } @@ -167,16 +259,60 @@ public unsafe Bitmap Decompress(IntPtr jpegBuf, ulong jpegBufSize, PixelFormat d /// Throws if underlying decompress function failed /// Object is disposed and can not be used anymore /// Convertion to the requested pixel format can not be performed - public unsafe Bitmap Decompress(byte[] jpegBuf, PixelFormat destPixelFormat, TJFlags flags) + public Bitmap Decompress(byte[] jpegBuf, PixelFormat destPixelFormat, TJFlags flags) { if (_isDisposed) throw new ObjectDisposedException("this"); var jpegBufSize = (ulong)jpegBuf.Length; - fixed (byte* jpegPtr = jpegBuf) - { - return Decompress((IntPtr)jpegPtr, jpegBufSize, destPixelFormat, flags); - } + using (var jpegBufPtr = new TJUnmanagedMemory(jpegBuf)) + return Decompress(jpegBufPtr, jpegBufSize, destPixelFormat, flags); + } + + /// + /// Decompress a JPEG image to an RGB, grayscale, or CMYK image. + /// + /// A buffer containing the JPEG image to decompress. This buffer is not modified + /// Decompressed image of specified format + /// Throws if underlying decompress function failed + /// Object is disposed and can not be used anymore + /// Convertion to the requested pixel format can not be performed + public Bitmap Decompress(byte[] jpegBuf) + { + if (_isDisposed) + throw new ObjectDisposedException("this"); + + var jpegBufSize = (ulong)jpegBuf.Length; + using (var jpegBufPtr = new TJUnmanagedMemory(jpegBuf)) + return Decompress(jpegBufPtr, jpegBufSize); + } + + /// + /// Retrieve information about a JPEG image without decompressing it. + /// + /// + /// Buffer containing a JPEG image. This buffer is not modified. + /// + /// + /// The pixel format of the uncompressed image. + /// + /// + /// Pointer to an integer variable that will receive the width (in pixels) of the JPEG image + /// + /// + /// Pointer to an integer variable that will receive the height (in pixels) of the JPEG image + /// + /// + /// Pointer to an integer variable that will receive the stride (in bytes) of the JPEG image. + /// + /// + /// The size of a buffer that can receive the uncompressed JPEG image. + /// + public void GetImageInfo(byte[] jpegBuf, TJPixelFormats destPixelFormat, out int width, out int height, out int stride, out int bufSize) + { + var jpegBufSize = (ulong)jpegBuf.Length; + using (var jpegBufPtr = new TJUnmanagedMemory(jpegBuf)) + GetImageInfo(jpegBufPtr, jpegBufSize, destPixelFormat, out width, out height, out stride, out bufSize); } /// @@ -211,6 +347,82 @@ public void GetImageInfo(IntPtr jpegBuf, ulong jpegBufSize, TJPixelFormats destP var funcResult = TurboJpegImport.tjDecompressHeader(_decompressorHandle, jpegBuf, jpegBufSize, out width, out height, out subsampl, out colorspace); + if (funcResult == -1) + { + TJUtils.GetErrorAndThrow(); + } + + stride = TurboJpegImport.TJPAD(width * TurboJpegImport.PixelSizes[destPixelFormat]); + bufSize = stride * height; + } + + /// + /// Retrieve information about a JPEG image without decompressing it. + /// + /// + /// Buffer containing a JPEG image. This buffer is not modified. + /// + /// + /// Pointer to a variable that will receive the pixel format of the uncompressed image. + /// + /// + /// Pointer to an integer variable that will receive the width (in pixels) of the JPEG image + /// + /// + /// Pointer to an integer variable that will receive the height (in pixels) of the JPEG image + /// + /// + /// Pointer to an integer variable that will receive the stride (in bytes) of the JPEG image. + /// + /// + /// The size of a buffer that can receive the uncompressed JPEG image. + /// + public void GetImageInfo(byte[] jpegBuf, out TJPixelFormats destPixelFormat, out int width, out int height, out int stride, out int bufSize) + { + var jpegBufSize = (ulong)jpegBuf.Length; + using (var jpegBufPtr = new TJUnmanagedMemory(jpegBuf)) + GetImageInfo(jpegBufPtr, jpegBufSize, out destPixelFormat, out width, out height, out stride, out bufSize); + } + + /// + /// Retrieve information about a JPEG image without decompressing it. + /// + /// + /// Pointer to a buffer containing a JPEG image. This buffer is not modified. + /// + /// + /// Size of the JPEG image (in bytes) + /// + /// + /// Pointer to a variable that will receive the pixel format of the uncompressed image. + /// + /// + /// Pointer to an integer variable that will receive the width (in pixels) of the JPEG image + /// + /// + /// Pointer to an integer variable that will receive the height (in pixels) of the JPEG image + /// + /// + /// Pointer to an integer variable that will receive the stride (in bytes) of the JPEG image. + /// + /// + /// The size of a buffer that can receive the uncompressed JPEG image. + /// + public void GetImageInfo(IntPtr jpegBuf, ulong jpegBufSize, out TJPixelFormats destPixelFormat, out int width, out int height, out int stride, out int bufSize) + { + int subsampl; + int colorspace; + + var funcResult = TurboJpegImport.tjDecompressHeader(_decompressorHandle, jpegBuf, jpegBufSize, + out width, out height, out subsampl, out colorspace); + + if (funcResult == -1) + { + TJUtils.GetErrorAndThrow(); + } + + // Since JPEG can only be 8 or 24 bit, select TJPF_GRAY when both colorspace and subsampling say that there is no color information, otherwise use normal windows TJPF_BGR format + destPixelFormat = (TJColorSpaces)colorspace == TJColorSpaces.TJCS_GRAY && (TJSubsamplingOptions)subsampl == TJSubsamplingOptions.TJSAMP_GRAY ? TJPixelFormats.TJPF_GRAY : TJPixelFormats.TJPF_BGR; stride = TurboJpegImport.TJPAD(width * TurboJpegImport.PixelSizes[destPixelFormat]); bufSize = stride * height; } @@ -236,15 +448,6 @@ public int GetBufferSize(int height, int width, TJPixelFormats destPixelFormat) return stride * height; } - private ColorPalette FixPaletteToGrayscale(ColorPalette palette) - { - for (var index = 0; index < palette.Entries.Length; ++index) - { - palette.Entries[index] = Color.FromArgb(index, index, index); - } - return palette; - } - /// /// Releases resources /// @@ -291,7 +494,5 @@ private void Dispose(bool callFromUserCode) { Dispose(false); } - } - } diff --git a/LibJpegWrapper/TJTransformer.cs b/LibJpegWrapper/TJTransformer.cs index c5b19fa..2ef8922 100644 --- a/LibJpegWrapper/TJTransformer.cs +++ b/LibJpegWrapper/TJTransformer.cs @@ -97,7 +97,7 @@ public byte[][] Transform(IntPtr jpegBuf, ulong jpegBufSize, TJTransformDescript customFilter = transforms[i].CustomFilter }; } - var transformsPtr = TJUtils.StructArrayToIntPtr(tjTransforms); + var transformsPtr = TJUtils.StructArrayToIntPtr(tjTransforms); try { funcResult = TurboJpegImport.tjTransform(_transformHandle, jpegBuf, jpegBufSize, count, destBufs, @@ -169,17 +169,15 @@ private static int CorrectRegionSize(int desiredCoordinate, int realCoordinate, /// is . /// Transforms can not be empty /// Throws if low level turbo jpeg function fails - public unsafe byte[][] Transform(byte[] jpegBuf, TJTransformDescription[] transforms, TJFlags flags) + public byte[][] Transform(byte[] jpegBuf, TJTransformDescription[] transforms, TJFlags flags) { if (transforms == null) throw new ArgumentNullException("transforms"); if (transforms.Length == 0) throw new ArgumentException("Transforms can not be empty", "transforms"); - fixed (byte* jpegPtr = jpegBuf) - { - return Transform((IntPtr)jpegPtr, (ulong)jpegBuf.Length, transforms, flags); - } + using (var jpegPtr = new TJUnmanagedMemory(jpegBuf)) + return Transform(jpegPtr, (ulong)jpegBuf.Length, transforms, flags); } /// diff --git a/LibJpegWrapper/TJUnmanagedMemory.cs b/LibJpegWrapper/TJUnmanagedMemory.cs new file mode 100644 index 0000000..75779c6 --- /dev/null +++ b/LibJpegWrapper/TJUnmanagedMemory.cs @@ -0,0 +1,128 @@ +using System; +using System.Runtime.InteropServices; + +namespace TurboJpegWrapper +{ + /// + /// This class wraps a block of unmanaged memory allocated using + /// ensuring that the memory is freed using when it is manually + /// disposed, goes out of context or is garbage collected. An instance of this class can be implicitly converted + /// to either an or a . + /// + /// + public sealed class TJUnmanagedMemory : IDisposable + { + private bool _isDisposed; + private IntPtr _buffer; + private ulong _size; + + /// + /// Initializes a new instance of the class and allocates the + /// requested amount of unmanaged memory using . + /// + /// The number of bytes of unmanaged memory to allocate. + public TJUnmanagedMemory(int size) + { + if (size < 0) + throw new ArgumentOutOfRangeException(nameof(size)); + _buffer = TurboJpegImport.tjAlloc(size); + if (_buffer == IntPtr.Zero) + throw new OutOfMemoryException("Could not allocate memory for buffer"); + _size = (ulong)size; + } + + /// + /// Initializes a new instance of the class without allocating + /// new memory. + /// + /// The buffer to wrap (must be allocated using or returned from a native TJ method). + /// The size of the buffer (in bytes). + internal TJUnmanagedMemory(IntPtr buffer, ulong size) + { + _buffer = buffer; + _size = size; + } + + /// + /// Initializes a new instance of the class, allocating memory for the bytes + /// int the providied array and copying them to the unmanaged memory using . + /// + /// The buffer which to convert to unmanaged memory. + internal TJUnmanagedMemory(byte[] buffer) + :this(buffer.Length) + { + Marshal.Copy(buffer, 0, _buffer, (int)_size); + } + + /// + /// Gets the size of the allocated native memory block (in bytes). + /// + /// + /// The size. + /// + public int Size { get { return (int)_size; } } + + /// + /// Performs an implicit conversion from to . + /// + /// The source object to convert. + /// + /// The result of the conversion. + /// + /// source + /// this + public static implicit operator IntPtr(TJUnmanagedMemory source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (source._isDisposed) + throw new ObjectDisposedException("this"); + return source._buffer; + } + + /// + /// Performs an implicit conversion from to . + /// + /// The source object to convert. + /// + /// The result of the conversion. + /// + /// source + /// this + public static implicit operator byte[](TJUnmanagedMemory source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (source._isDisposed) + throw new ObjectDisposedException("this"); + var buffer = new byte[source._size]; + Marshal.Copy(source._buffer, buffer, 0, (int)source._size); + return buffer; + } + + #region IDisposable Support + private void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (_buffer != IntPtr.Zero) + TurboJpegImport.tjFree(_buffer); + _isDisposed = true; + _buffer = IntPtr.Zero; + _size = 0; + } + } + + ~TJUnmanagedMemory() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/LibJpegWrapper/TJUtils.cs b/LibJpegWrapper/TJUtils.cs index 708efe9..b208363 100644 --- a/LibJpegWrapper/TJUtils.cs +++ b/LibJpegWrapper/TJUtils.cs @@ -6,7 +6,7 @@ namespace TurboJpegWrapper { // ReSharper disable once InconsistentNaming - static class TJUtils + public static class TJUtils { /// /// Retrieves last error from underlying turbo-jpeg library and throws exception @@ -16,6 +16,7 @@ public static void GetErrorAndThrow() var error = TurboJpegImport.tjGetErrorStr(); throw new TJException(error); } + /// /// Converts pixel format from to /// @@ -37,6 +38,27 @@ public static TJPixelFormats ConvertPixelFormat(PixelFormat pixelFormat) } } + /// + /// Converts pixel format from to + /// + /// Pixel format to convert + /// Converted value of pixel format or exception if convertion is impossible + /// Convertion can not be performed + public static PixelFormat ConvertPixelFormat(TJPixelFormats pixelFormat) + { + switch (pixelFormat) + { + case TJPixelFormats.TJPF_BGRA: + return PixelFormat.Format32bppArgb; + case TJPixelFormats.TJPF_BGR: + return PixelFormat.Format24bppRgb; + case TJPixelFormats.TJPF_GRAY: + return PixelFormat.Format8bppIndexed; + default: + throw new NotSupportedException($"Provided pixel format \"{pixelFormat}\" is not supported"); + } + } + /// /// Returns actual platform name depending on pointer size ///