Files
libremetaverse/LibreMetaverse/Imaging/J2KReader.cs
Cinder 3c76a98f8e Second shot OpenJPEG native lib replacement
Mostly working, could use some fine tuning for unsupported images
2021-07-04 18:12:27 -05:00

260 lines
7.9 KiB
C#

/**
* Copyright (c) 2021, Sjofn LLC.
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.co nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using OpenJpegDotNet;
namespace LibreMetaverse.Imaging
{
public sealed class J2KReader : IDisposable
{
#region Fields
private readonly J2KBuffer _Buffer;
private readonly IntPtr _UserData;
private readonly DelegateHandler<StreamRead> _ReadCallback;
private readonly DelegateHandler<StreamSeek> _SeekCallback;
private readonly DelegateHandler<StreamSkip> _SkipCallback;
private Codec _Codec;
private DecompressionParameters _DecompressionParameters;
private OpenJpegDotNet.Image _Image;
private readonly Stream _Stream;
#endregion
#region Constructors
public J2KReader(byte[] data)
{
this._Buffer = new J2KBuffer
{
Data = Marshal.AllocHGlobal(data.Length),
Length = data.Length,
Position = 0
};
Marshal.Copy(data, 0, this._Buffer.Data, this._Buffer.Length);
var size = Marshal.SizeOf(this._Buffer);
this._UserData = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(this._Buffer, this._UserData, false);
this._ReadCallback = new DelegateHandler<StreamRead>(Read);
this._SeekCallback = new DelegateHandler<StreamSeek>(Seek);
this._SkipCallback = new DelegateHandler<StreamSkip>(Skip);
this._Stream = OpenJpeg.StreamDefaultCreate(true);
OpenJpeg.StreamSetUserData(this._Stream, this._UserData);
OpenJpeg.StreamSetUserDataLength(this._Stream, this._Buffer.Length);
OpenJpeg.StreamSetReadFunction(this._Stream, this._ReadCallback);
OpenJpeg.StreamSetSeekFunction(this._Stream, this._SeekCallback);
OpenJpeg.StreamSetSkipFunction(this._Stream, this._SkipCallback);
}
#endregion
#region Properties
public int Height
{
get;
private set;
}
/// <summary>
/// Gets a value indicating whether this instance has been disposed.
/// </summary>
/// <returns>true if this instance has been disposed; otherwise, false.</returns>
public bool IsDisposed
{
get;
private set;
}
public int Width
{
get;
private set;
}
#endregion
#region Methods
public bool ReadHeader()
{
this._Codec?.Dispose();
this._DecompressionParameters?.Dispose();
this._Image?.Dispose();
this._Codec = null;
this._DecompressionParameters = null;
this._Image = null;
this._Codec = OpenJpeg.CreateDecompress(CodecFormat.J2k);
this._DecompressionParameters = new DecompressionParameters();
OpenJpeg.SetDefaultDecoderParameters(this._DecompressionParameters);
if (!OpenJpeg.SetupDecoder(this._Codec, this._DecompressionParameters))
return false;
if (!OpenJpeg.ReadHeader(this._Stream, this._Codec, out var image))
return false;
this.Width = (int)(image.X1 - image.X0);
this.Height = (int)(image.Y1 - image.Y0);
this._Image = image;
return true;
}
public OpenJpegDotNet.Image Decode()
{
if (this._Image == null || this._Image.IsDisposed)
throw new InvalidOperationException();
if (!OpenJpeg.Decode(this._Codec, this._Stream, this._Image))
throw new InvalidOperationException();
return this._Image;
}
public Bitmap DecodeToBitmap()
{
if (this._Image == null || this._Image.IsDisposed)
throw new InvalidOperationException();
if (!OpenJpeg.Decode(this._Codec, this._Stream, this._Image))
throw new InvalidOperationException();
return this._Image.ToBitmap();
}
#region Event Handlers
private static ulong Read(IntPtr buffer, ulong bytes, IntPtr userData)
{
unsafe
{
var buf = (J2KBuffer*)userData;
var bytesToRead = (int)Math.Min((ulong)(buf->Length - buf->Position), bytes);
if (bytesToRead > 0)
{
NativeMethods.cstd_memcpy(buffer, IntPtr.Add(buf->Data, buf->Position), bytesToRead);
buf->Position += bytesToRead;
return (ulong)bytesToRead;
}
else
{
return unchecked((ulong)-1);
}
}
}
private static int Seek(ulong bytes, IntPtr userData)
{
unsafe
{
var buf = (J2KBuffer*)userData;
var position = Math.Min((ulong)buf->Length, bytes);
buf->Position = (int)position;
return 1;
}
}
private static long Skip(ulong bytes, IntPtr userData)
{
unsafe
{
var buf = (J2KBuffer*)userData;
var bytesToSkip = (int)Math.Min((ulong)buf->Length, bytes);
if (bytesToSkip > 0)
{
buf->Position += bytesToSkip;
return bytesToSkip;
}
else
{
return unchecked(-1);
}
}
}
#endregion
#endregion
#region IDisposable Members
/// <summary>
/// Releases all resources used by this <see cref="Reader"/>.
/// </summary>
public void Dispose()
{
this.Dispose(true);
//GC.SuppressFinalize(this);
}
/// <summary>
/// Releases all resources used by this <see cref="Reader"/>.
/// </summary>
/// <param name="disposing">Indicate value whether <see cref="IDisposable.Dispose"/> method was called.</param>
private void Dispose(bool disposing)
{
if (this.IsDisposed)
{
return;
}
this.IsDisposed = true;
if (disposing)
{
this._Codec?.Dispose();
this._DecompressionParameters?.Dispose();
this._Stream.Dispose();
Marshal.FreeHGlobal(this._Buffer.Data);
Marshal.FreeHGlobal(this._UserData);
}
}
#endregion
}
}