📷 A modern, cross-platform, 2D Graphics library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

360 lines
11 KiB

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using BitMiracle.LibJpeg.Classic;
namespace BitMiracle.LibJpeg
{
/// <summary>
/// Main class for work with JPEG images.
/// </summary>
#if EXPOSE_LIBJPEG
public
#endif
sealed class JpegImage : IDisposable
{
private bool m_alreadyDisposed;
/// <summary>
/// Description of image pixels (samples)
/// </summary>
private List<SampleRow> m_rows = new List<SampleRow>();
private int m_width;
private int m_height;
private byte m_bitsPerComponent;
private byte m_componentsPerSample;
private Colorspace m_colorspace;
// Fields below (m_compressedData, m_decompressedData, m_bitmap) are not initialized in constructors necessarily.
// Instead direct access to these field you should use corresponding properties (compressedData, decompressedData, bitmap)
// Such agreement allows to load required data (e.g. compress image) only by request.
/// <summary>
/// Bytes of jpeg image. Refreshed when m_compressionParameters changed.
/// </summary>
private MemoryStream m_compressedData;
/// <summary>
/// Current compression parameters corresponding with compressed data.
/// </summary>
private CompressionParameters m_compressionParameters;
/// <summary>
/// Bytes of decompressed image (bitmap)
/// </summary>
private MemoryStream m_decompressedData;
/// <summary>
/// Creates <see cref="JpegImage"/> from stream with an arbitrary image data
/// </summary>
/// <param name="imageData">Stream containing bytes of image in
/// arbitrary format (BMP, Jpeg, GIF, PNG, TIFF, e.t.c)</param>
public JpegImage(Stream imageData)
{
createFromStream(imageData);
}
/// <summary>
/// Creates <see cref="JpegImage"/> from pixels
/// </summary>
/// <param name="sampleData">Description of pixels.</param>
/// <param name="colorspace">Colorspace of image.</param>
/// <seealso cref="SampleRow"/>
public JpegImage(SampleRow[] sampleData, Colorspace colorspace)
{
if (sampleData == null)
throw new ArgumentNullException("sampleData");
if (sampleData.Length == 0)
throw new ArgumentException("sampleData must be no empty");
if (colorspace == Colorspace.Unknown)
throw new ArgumentException("Unknown colorspace");
m_rows = new List<SampleRow>(sampleData);
SampleRow firstRow = m_rows[0];
m_width = firstRow.Length;
m_height = m_rows.Count;
Sample firstSample = firstRow[0];
m_bitsPerComponent = firstSample.BitsPerComponent;
m_componentsPerSample = firstSample.ComponentCount;
m_colorspace = colorspace;
}
/// <summary>
/// Frees and releases all resources allocated by this <see cref="JpegImage"/>
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!m_alreadyDisposed)
{
if (disposing)
{
// dispose managed resources
if (m_compressedData != null)
m_compressedData.Dispose();
if (m_decompressedData != null)
m_decompressedData.Dispose();
}
// free native resources
m_compressionParameters = null;
m_compressedData = null;
m_decompressedData = null;
m_rows = null;
m_alreadyDisposed = true;
}
}
/// <summary>
/// Gets the width of image in <see cref="Sample">samples</see>.
/// </summary>
/// <value>The width of image.</value>
public int Width
{
get
{
return m_width;
}
internal set
{
m_width = value;
}
}
/// <summary>
/// Gets the height of image in <see cref="Sample">samples</see>.
/// </summary>
/// <value>The height of image.</value>
public int Height
{
get
{
return m_height;
}
internal set
{
m_height = value;
}
}
/// <summary>
/// Gets the number of color components per <see cref="Sample">sample</see>.
/// </summary>
/// <value>The number of color components per sample.</value>
public byte ComponentsPerSample
{
get
{
return m_componentsPerSample;
}
internal set
{
m_componentsPerSample = value;
}
}
/// <summary>
/// Gets the number of bits per color component of <see cref="Sample">sample</see>.
/// </summary>
/// <value>The number of bits per color component.</value>
public byte BitsPerComponent
{
get
{
return m_bitsPerComponent;
}
internal set
{
m_bitsPerComponent = value;
}
}
/// <summary>
/// Gets the colorspace of image.
/// </summary>
/// <value>The colorspace of image.</value>
public Colorspace Colorspace
{
get
{
return m_colorspace;
}
internal set
{
m_colorspace = value;
}
}
/// <summary>
/// Retrieves the required row of image.
/// </summary>
/// <param name="rowNumber">The number of row.</param>
/// <returns>Image row of samples.</returns>
public SampleRow GetRow(int rowNumber)
{
return m_rows[rowNumber];
}
/// <summary>
/// Writes compressed JPEG image to stream.
/// </summary>
/// <param name="output">Output stream.</param>
public void WriteJpeg(Stream output)
{
WriteJpeg(output, new CompressionParameters());
}
/// <summary>
/// Compresses image to JPEG with given parameters and writes it to stream.
/// </summary>
/// <param name="output">Output stream.</param>
/// <param name="parameters">The parameters of compression.</param>
public void WriteJpeg(Stream output, CompressionParameters parameters)
{
compress(parameters);
compressedData.WriteTo(output);
}
/// <summary>
/// Writes decompressed image data as bitmap to stream.
/// </summary>
/// <param name="output">Output stream.</param>
public void WriteBitmap(Stream output)
{
decompressedData.WriteTo(output);
}
private MemoryStream compressedData
{
get
{
if (m_compressedData == null)
compress(new CompressionParameters());
Debug.Assert(m_compressedData != null);
Debug.Assert(m_compressedData.Length != 0);
return m_compressedData;
}
}
private MemoryStream decompressedData
{
get
{
if (m_decompressedData == null)
fillDecompressedData();
Debug.Assert(m_decompressedData != null);
return m_decompressedData;
}
}
/// <summary>
/// Needs for DecompressorToJpegImage class
/// </summary>
internal void addSampleRow(SampleRow row)
{
if (row == null)
throw new ArgumentNullException("row");
m_rows.Add(row);
}
/// <summary>
/// Checks if imageData contains jpeg image
/// </summary>
private static bool isCompressed(Stream imageData)
{
if (imageData == null)
return false;
if (imageData.Length <= 2)
return false;
imageData.Seek(0, SeekOrigin.Begin);
int first = imageData.ReadByte();
int second = imageData.ReadByte();
return (first == 0xFF && second == (int)JPEG_MARKER.SOI);
}
private void createFromStream(Stream imageData)
{
if (imageData == null)
throw new ArgumentNullException("imageData");
if (isCompressed(imageData))
{
m_compressedData = Utils.CopyStream(imageData);
decompress();
}
else
{
throw new NotImplementedException("JpegImage.createFromStream(Stream)");
}
}
private void compress(CompressionParameters parameters)
{
Debug.Assert(m_rows != null);
Debug.Assert(m_rows.Count != 0);
RawImage source = new RawImage(m_rows, m_colorspace);
compress(source, parameters);
}
private void compress(IRawImage source, CompressionParameters parameters)
{
Debug.Assert(source != null);
if (!needCompressWith(parameters))
return;
m_compressedData = new MemoryStream();
m_compressionParameters = new CompressionParameters(parameters);
Jpeg jpeg = new Jpeg();
jpeg.CompressionParameters = m_compressionParameters;
jpeg.Compress(source, m_compressedData);
}
private bool needCompressWith(CompressionParameters parameters)
{
return m_compressedData == null ||
m_compressionParameters == null ||
!m_compressionParameters.Equals(parameters);
}
private void decompress()
{
Jpeg jpeg = new Jpeg();
jpeg.Decompress(compressedData, new DecompressorToJpegImage(this));
}
private void fillDecompressedData()
{
Debug.Assert(m_decompressedData == null);
m_decompressedData = new MemoryStream();
BitmapDestination dest = new BitmapDestination(m_decompressedData);
Jpeg jpeg = new Jpeg();
jpeg.Decompress(compressedData, dest);
}
}
}