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
///