Initial commit

This commit is contained in:
Thastertyn committed 2024-06-07 00:47:07 +02:00
commit 262601edb8
2025 files changed
+218754

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
//
// Codec.cs:
//
// Author:
// Mike Gemuende (mike@gemuende.be)
//
// Copyright (C) 2010 Mike Gemuende
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License version
// 2.1 as published by the Free Software Foundation.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
//
namespace TagLib.Png
{
/// <summary>
/// A Png photo codec. Contains basic photo details.
/// </summary>
public class Codec : Image.Codec
{
/// <summary>
/// Gets a text description of the media represented by the
/// current instance.
/// </summary>
/// <value>
/// A <see cref="string" /> object containing a description
/// of the media represented by the current instance.
/// </value>
public override string Description { get { return "PNG File"; } }
/// <summary>
/// Constructs a new <see cref="Codec" /> with the given width
/// and height.
/// </summary>
/// <param name="width">
/// The width of the photo.
/// </param>
/// <param name="height">
/// The height of the photo.
/// </param>
/// <returns>
/// A new <see cref="Codec" /> instance.
/// </returns>
public Codec (int width, int height)
: base (width, height) { }
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 61dacbe123502832e83e23426d871c08
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+957
View File
@@ -0,0 +1,957 @@
//
// File.cs: Provides tagging for PNG files
//
// Author:
// Mike Gemuende (mike@gemuende.be)
//
// Copyright (C) 2010 Mike Gemuende
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License version
// 2.1 as published by the Free Software Foundation.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
//
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using TagLib.Image;
using TagLib.Xmp;
using System.Text;
namespace TagLib.Png
{
/// <summary>
/// This class extends <see cref="TagLib.Image.ImageBlockFile" /> to provide tagging
/// for PNG image files.
/// </summary>
/// <remarks>
/// This implementation is based on http://www.w3.org/TR/PNG
/// </remarks>
[SupportedMimeType ("taglib/png", "png")]
[SupportedMimeType ("image/png")]
public class File : ImageBlockFile
{
#region GIF specific constants
/// <summary>
/// The PNG Header every png file starts with.
/// </summary>
readonly byte[] HEADER = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
/// <summary>
/// byte sequence to indicate a IHDR Chunk
/// </summary>
readonly byte[] IHDR_CHUNK_TYPE = new byte[] { 73, 72, 68, 82 };
/// <summary>
/// byte sequence to indicate a IEND Chunk
/// </summary>
readonly byte[] IEND_CHUNK_TYPE = new byte[] { 73, 69, 78, 68 };
/// <summary>
/// byte sequence to indicate a iTXt Chunk
/// </summary>
readonly byte[] iTXt_CHUNK_TYPE = new byte[] { 105, 84, 88, 116 };
/// <summary>
/// byte sequence to indicate a tEXt Chunk
/// </summary>
readonly byte[] tEXt_CHUNK_TYPE = new byte[] { 116, 69, 88, 116 };
/// <summary>
/// byte sequence to indicate a zTXt Chunk
/// </summary>
readonly byte[] zTXt_CHUNK_TYPE = new byte[] { 122, 84, 88, 116 };
/// <summary>
/// header of a iTXt which contains XMP data.
/// </summary>
readonly byte[] XMP_CHUNK_HEADER = new byte[] {
// Keyword ("XML:com.adobe.xmp")
0x58, 0x4D, 0x4C, 0x3A, 0x63, 0x6F, 0x6D, 0x2E, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x2E, 0x78, 0x6D, 0x70,
// Null Separator
0x00,
// Compression Flag
0x00,
// Compression Method
0x00,
// Language Tag Null Separator
0x00,
// Translated Keyword Null Separator
0x00
};
#endregion
#region private fields
/// <summary>
/// The height of the image
/// </summary>
int height;
/// <summary>
/// The width of the image
/// </summary>
int width;
/// <summary>
/// The Properties of the image
/// </summary>
Properties properties;
#endregion
private class RawProfile
{
public RawProfile ()
{
Data = new List<string> ();
}
public string Name { get; set; }
public string LengthText { get; set; }
public List<string> Data { get; set; }
public override string ToString ()
{
return "\n" + Name + "\n" + LengthText + "\n" + string.Join ("\n", Data.ToArray ());
}
}
#region public Properties
/// <summary>
/// Gets the media properties of the file represented by the
/// current instance.
/// </summary>
/// <value>
/// A <see cref="TagLib.Properties" /> object containing the
/// media properties of the file represented by the current
/// instance.
/// </value>
public override Properties Properties {
get { return properties; }
}
#endregion
#region constructors
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="File" /> for a specified path in the local file
/// system and specified read style.
/// </summary>
/// <param name="path">
/// A <see cref="string" /> object containing the path of the
/// file to use in the new instance.
/// </param>
/// <param name="propertiesStyle">
/// A <see cref="ReadStyle" /> value specifying at what level
/// of accuracy to read the media properties, or <see
/// cref="ReadStyle.None" /> to ignore the properties.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="path" /> is <see langword="null" />.
/// </exception>
public File (string path, ReadStyle propertiesStyle)
: this (new LocalFileAbstraction (path), propertiesStyle)
{
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="File" /> for a specified path in the local file
/// system.
/// </summary>
/// <param name="path">
/// A <see cref="string" /> object containing the path of the
/// file to use in the new instance.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="path" /> is <see langword="null" />.
/// </exception>
public File (string path) : this (path, ReadStyle.Average)
{
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="File" /> for a specified file abstraction and
/// specified read style.
/// </summary>
/// <param name="abstraction">
/// A <see cref="TagLib.File.IFileAbstraction" /> object to use when
/// reading from and writing to the file.
/// </param>
/// <param name="propertiesStyle">
/// A <see cref="ReadStyle" /> value specifying at what level
/// of accuracy to read the media properties, or <see
/// cref="ReadStyle.None" /> to ignore the properties.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="abstraction" /> is <see langword="null"
/// />.
/// </exception>
public File (IFileAbstraction abstraction, ReadStyle propertiesStyle)
: base (abstraction)
{
Read (propertiesStyle);
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="File" /> for a specified file abstraction.
/// </summary>
/// <param name="abstraction">
/// A <see cref="TagLib.File.IFileAbstraction" /> object to use when
/// reading from and writing to the file.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="abstraction" /> is <see langword="null"
/// />.
/// </exception>
protected File (IFileAbstraction abstraction)
: this (abstraction, ReadStyle.Average)
{
}
#endregion
#region Public Methods
/// <summary>
/// Saves the changes made in the current instance to the
/// file it represents.
/// </summary>
public override void Save ()
{
// Boilerplate
PreSave ();
Mode = AccessMode.Write;
try {
SaveMetadata ();
TagTypesOnDisk = TagTypes;
} finally {
Mode = AccessMode.Closed;
}
}
#endregion
#region private methods
/// <summary>
/// Reads the information from file with a specified read style.
/// </summary>
/// <param name="propertiesStyle">
/// A <see cref="ReadStyle" /> value specifying at what level
/// of accuracy to read the media properties, or <see
/// cref="ReadStyle.None" /> to ignore the properties.
/// </param>
void Read (ReadStyle propertiesStyle)
{
Mode = AccessMode.Read;
try {
ImageTag = new CombinedImageTag (TagTypes.XMP | TagTypes.Png);
ValidateHeader ();
ReadMetadata ();
TagTypesOnDisk = TagTypes;
if ((propertiesStyle & ReadStyle.Average) != 0)
properties = ExtractProperties ();
} finally {
Mode = AccessMode.Closed;
}
}
/// <summary>
/// Validates the header of a PNG file. Therfore, the current position to
/// read must be the start of the file.
/// </summary>
void ValidateHeader ()
{
ByteVector data = ReadBlock (8);
if (data.Count != 8)
throw new CorruptFileException ("Unexpected end of header");
if (!data.Equals (new ByteVector (HEADER)))
throw new CorruptFileException ("PNG Header was expected");
}
/// <summary>
/// Reads the length of data of a chunk from the current position
/// </summary>
/// <returns>
/// A <see cref="System.Int32"/> with the length of data.
/// </returns>
/// <remarks>
/// The length is stored in a 4-byte unsigned integer in the file,
/// but due to the PNG specification this value does not exceed
/// 2^31-1 and can therfore be safely returned as an signed integer.
/// This prevents unsafe casts for using the length as parameter
/// for other methods.
/// </remarks>
int ReadChunkLength ()
{
ByteVector data = ReadBlock (4);
if (data.Count != 4)
throw new CorruptFileException ("Unexpected end of Chunk Length");
uint length = data.ToUInt (true);
if (length > int.MaxValue)
throw new CorruptFileException ("PNG limits the Chunk Length to 2^31-1");
return (int)length;
}
/// <summary>
/// Reads the type of a chunk from the current position.
/// </summary>
/// <returns>
/// A <see cref="ByteVector"/> with 4 bytes containing the type of
/// the Chunk.
/// </returns>
ByteVector ReadChunkType ()
{
ByteVector data = ReadBlock (4);
if (data.Count != 4)
throw new CorruptFileException ("Unexpected end of Chunk Type");
return data;
}
/// <summary>
/// Reads the CRC value for a chunk from the current position.
/// </summary>
/// <returns>
/// A <see cref="ByteVector"/> with 4 bytes with the CRC value.
/// </returns>
ByteVector ReadCRC ()
{
ByteVector data = ReadBlock (4);
if (data.Count != 4)
throw new CorruptFileException ("Unexpected end of CRC");
return data;
}
/// <summary>
/// Reads the whole Chunk data starting from the current position.
/// </summary>
/// <param name="data_length">
/// A <see cref="System.Int32"/> with the length of the Chunk Data.
/// </param>
/// <returns>
/// A <see cref="ByteVector"/> with the Chunk Data which is read.
/// </returns>
ByteVector ReadChunkData (int data_length)
{
ByteVector data = ReadBlock (data_length);
if (data.Count != data_length)
throw new CorruptFileException ($"Chunk Data of Length {data_length} expected");
return data;
}
/// <summary>
/// Reads a null terminated string from the given data from given position.
/// </summary>
/// <param name="data">
/// A <see cref="ByteVector"/> with teh data to read the string from
/// </param>
/// <param name="start_index">
/// A <see cref="System.Int32"/> with the index to start reading
/// </param>
/// <param name="terminator_index">
/// A <see cref="System.Int32"/> with the index of the null byte
/// </param>
/// <returns>
/// A <see cref="System.String"/> with the read string. The null byte
/// is not included.
/// </returns>
string ReadTerminatedString (ByteVector data, int start_index, out int terminator_index)
{
if (start_index >= data.Count)
throw new CorruptFileException ("Unexpected End of Data");
terminator_index = data.Find ("\0", start_index);
if (terminator_index < 0)
throw new CorruptFileException ("Cannot find string terminator");
return data.Mid (start_index, terminator_index - start_index).ToString ();
}
/// <summary>
/// Reads a null terminated keyword from he given data from given position.
/// </summary>
/// <param name="data">
/// A <see cref="ByteVector"/> with teh data to read the string from
/// </param>
/// <param name="start_index">
/// A <see cref="System.Int32"/> with the index to start reading
/// </param>
/// <param name="terminator_index">
/// A <see cref="System.Int32"/> with the index of the null byte
/// </param>
/// <returns>
/// A <see cref="System.String"/> with the read keyword. The null byte
/// is not included.
/// </returns>
string ReadKeyword (ByteVector data, int start_index, out int terminator_index)
{
string keyword = ReadTerminatedString (data, start_index, out terminator_index);
if (string.IsNullOrEmpty (keyword))
throw new CorruptFileException ("Keyword cannot be empty");
return keyword;
}
/// <summary>
/// Skips the Chunk Data and CRC Data. The read position must be at the
/// beginning of the Chunk data.
/// </summary>
/// <param name="data_size">
/// A <see cref="System.Int32"/> with the length of the chunk data read
/// before.
/// </param>
void SkipChunkData (int data_size)
{
long position = Tell;
if (position + data_size >= Length)
throw new CorruptFileException ($"Chunk Data of Length {data_size} expected");
Seek (Tell + data_size);
ReadCRC ();
}
/// <summary>
/// Reads the whole metadata from file. The current position must be set to
/// the first Chunk which is contained in the file.
/// </summary>
void ReadMetadata ()
{
int data_length = ReadChunkLength ();
ByteVector type = ReadChunkType ();
// File should start with a header chunk
if (!type.StartsWith (IHDR_CHUNK_TYPE))
throw new CorruptFileException ($"IHDR Chunk was expected, but Chunk {type} was found");
ReadIHDRChunk (data_length);
// Read all following chunks
while (true) {
data_length = ReadChunkLength ();
type = ReadChunkType ();
if (type.StartsWith (IEND_CHUNK_TYPE))
return;
else if (type.StartsWith (iTXt_CHUNK_TYPE))
ReadiTXtChunk (data_length);
else if (type.StartsWith (tEXt_CHUNK_TYPE))
ReadtEXtChunk (data_length);
else if (type.StartsWith (zTXt_CHUNK_TYPE))
ReadzTXtChunk (data_length);
else
SkipChunkData (data_length);
}
}
/// <summary>
/// Reads the IHDR Chunk from file and extracts some image information
/// like width and height. The current position must be set to the start
/// of the Chunk Data.
/// </summary>
/// <param name="data_length">
/// A <see cref="System.Int32"/> with the length of the Chunk Data.
/// </param>
void ReadIHDRChunk (int data_length)
{
// IHDR Chunk
//
// 4 Bytes Width
// 4 Bytes Height
// 1 Byte Bit depth
// 1 Byte Colour type
// 1 Byte Compression method
// 1 Byte Filter method
// 1 Byte Interlace method
//
// Followed by 4 Bytes CRC data
if (data_length != 13)
throw new CorruptFileException ("IHDR chunk data length must be 13");
ByteVector data = ReadChunkData (data_length);
CheckCRC (IHDR_CHUNK_TYPE, data, ReadCRC ());
// The PNG specification limits the size of 4-byte unsigned integers to 2^31-1.
// That allows us to safely cast them to an signed integer.
uint width = data.Mid (0, 4).ToUInt (true);
uint height = data.Mid (4, 4).ToUInt (true);
if (width > int.MaxValue || height > int.MaxValue)
throw new CorruptFileException ("PNG limits width and heigth to 2^31-1");
this.width = (int)width;
this.height = (int)height;
}
/// <summary>
/// Reads an iTXt Chunk from file. The current position must be set
/// to the start of the Chunk Data. Such a Chunk may contain XMP data
/// or translated keywords.
/// </summary>
/// <param name="data_length">
/// A <see cref="System.Int32"/> with the length of the Chunk Data.
/// </param>
void ReadiTXtChunk (int data_length)
{
long position = Tell;
// iTXt Chunk
//
// N Bytes Keyword
// 1 Byte Null Separator
// 1 Byte Compression Flag (0 for uncompressed data)
// 1 Byte Compression Method
// N Bytes Language Tag
// 1 Byte Null Separator
// N Bytes Translated Keyword
// 1 Byte Null Terminator
// N Bytes Txt
//
// Followed by 4 Bytes CRC data
ByteVector data = ReadChunkData (data_length);
CheckCRC (iTXt_CHUNK_TYPE, data, ReadCRC ());
// handle XMP, which has a fixed header
if (data.StartsWith (XMP_CHUNK_HEADER)) {
ImageTag.AddTag (new XmpTag (data.Mid (XMP_CHUNK_HEADER.Length).ToString (StringType.UTF8), this));
AddMetadataBlock (position - 8, data_length + 8 + 4);
return;
}
string keyword = ReadKeyword (data, 0, out var terminator_index);
if (terminator_index + 2 >= data_length)
throw new CorruptFileException ("Compression Flag and Compression Method byte expected");
byte compression_flag = data[terminator_index + 1];
byte compression_method = data[terminator_index + 2];
//string language = ReadTerminatedString (data, terminator_index + 3, out terminator_index);
//string translated_keyword = ReadTerminatedString (data, terminator_index + 1, out terminator_index);
ByteVector txt_data = data.Mid (terminator_index + 1);
if (compression_flag != 0x00) {
txt_data = Decompress (compression_method, txt_data);
// ignore unknown compression methods
if (txt_data == null)
return;
}
string value = txt_data.ToString ();
var png_tag = GetTag (TagTypes.Png, true) as PngTag;
if (png_tag.GetKeyword (keyword) == null)
png_tag.SetKeyword (keyword, value);
AddMetadataBlock (position - 8, data_length + 8 + 4);
}
/// <summary>
/// Reads an tEXt Chunk from file. The current position must be set
/// to the start of the Chunk Data. Such a Chunk contains plain
/// keywords.
/// </summary>
/// <param name="data_length">
/// A <see cref="System.Int32"/> with the length of the Chunk Data.
/// </param>
void ReadtEXtChunk (int data_length)
{
long position = Tell;
// tEXt Chunk
//
// N Bytes Keyword
// 1 Byte Null Separator
// N Bytes Txt
//
// Followed by 4 Bytes CRC data
ByteVector data = ReadChunkData (data_length);
CheckCRC (tEXt_CHUNK_TYPE, data, ReadCRC ());
string keyword = ReadKeyword (data, 0, out var keyword_terminator);
string value = data.Mid (keyword_terminator + 1).ToString ();
var png_tag = GetTag (TagTypes.Png, true) as PngTag;
if (png_tag.GetKeyword (keyword) == null)
png_tag.SetKeyword (keyword, value);
AddMetadataBlock (position - 8, data_length + 8 + 4);
}
private static RawProfile ProcessRawProfile (string value)
{
// ImageMagick formats 'raw profiles' as
// '\n<name>\n<length>(%8lu)\n<hex payload>\n'.
var parts = value.Split (new[] { '\n' });
if (parts.Length < 4)
return null;
var profile = new RawProfile ();
profile.Name = parts [1];
profile.LengthText = parts [2];
for (int i = 3; i < parts.Length; i++) {
var buffer = new Byte[parts [i].Length];
int iBuffer = 0;
for (int j = 0; j < parts [i].Length - 1; j += 2) {
var subString = parts [i].Substring (j, 2);
buffer [iBuffer++] = Convert.ToByte (subString, 16);
}
// raw profile data often has many trailing nulls in most lines, which must be removed
// for XML processing as XMP data. Are there other raw profiles that need them?
profile.Data.Add (Encoding.UTF8.GetString (buffer).Trim (new char[] {'\0'}));
}
return profile;
}
/// <summary>
/// Reads an zTXt Chunk from file. The current position must be set
/// to the start of the Chunk Data. Such a Chunk contains compressed
/// keywords.
/// </summary>
/// <param name="data_length">
/// A <see cref="System.Int32"/> with the length of the Chunk Data.
/// </param>
/// <remarks>
/// The Chunk may also contain compressed Exif data which is written
/// by other tools. But, since the PNG specification does not support
/// Exif data, we ignore it here.
/// </remarks>
void ReadzTXtChunk (int data_length)
{
long position = Tell;
// zTXt Chunk
//
// N Bytes Keyword
// 1 Byte Null Separator
// 1 Byte Compression Method
// N Bytes Txt
//
// Followed by 4 Bytes CRC data
ByteVector data = ReadChunkData (data_length);
CheckCRC (zTXt_CHUNK_TYPE, data, ReadCRC ());
string keyword = ReadKeyword (data, 0, out var terminator_index);
if (terminator_index + 1 >= data_length)
throw new CorruptFileException ("Compression Method byte expected");
byte compression_method = data[terminator_index + 1];
ByteVector plain_data = Decompress (compression_method, data.Mid (terminator_index + 2));
// ignore unknown compression methods
if (plain_data == null)
return;
string value = plain_data.ToString ();
RawProfile rawProfile = null;
if (keyword.StartsWith ("Raw profile type")) {
rawProfile = ProcessRawProfile (value);
value = rawProfile.ToString ();
}
// handle XMP, which has a fixed header
if (keyword == "xmp" || rawProfile != null && string.Compare (rawProfile.Name, "xmp", StringComparison.InvariantCultureIgnoreCase) == 0) {
ImageTag.AddTag (new XmpTag (string.Join("", rawProfile.Data.ToArray()), this));
} else {
var png_tag = GetTag (TagTypes.Png, true) as PngTag;
if (png_tag.GetKeyword (keyword) == null)
png_tag.SetKeyword (keyword, value);
}
AddMetadataBlock (position - 8, data_length + 8 + 4);
}
/// <summary>
/// Save the metadata to file.
/// </summary>
void SaveMetadata ()
{
var metadata_chunks = new ByteVector {
RenderXMPChunk (),
RenderKeywordChunks ()
};
// Metadata is stored after the PNG header and the IDHR chunk.
SaveMetadata (metadata_chunks, HEADER.Length + 13 + 4 + 4 + 4);
}
/// <summary>
/// Creates a Chunk containing the XMP data.
/// </summary>
/// <returns>
/// A <see cref="ByteVector"/> with the XMP data chunk
/// or <see langword="null" /> if no XMP data is contained.
/// </returns>
ByteVector RenderXMPChunk ()
{
// Check, if XmpTag is contained
var xmp = ImageTag.Xmp;
if (xmp == null)
return null;
var chunk = new ByteVector ();
// render the XMP data itself
ByteVector xmp_data = xmp.Render ();
// TODO check uint size.
chunk.Add (ByteVector.FromUInt ((uint)xmp_data.Count + (uint)XMP_CHUNK_HEADER.Length));
chunk.Add (iTXt_CHUNK_TYPE);
chunk.Add (XMP_CHUNK_HEADER);
chunk.Add (xmp_data);
chunk.Add (ComputeCRC (iTXt_CHUNK_TYPE, XMP_CHUNK_HEADER, xmp_data));
return chunk;
}
/// <summary>
/// Creates a list of Chunks containing the PNG keywords
/// </summary>
/// <returns>
/// A <see cref="ByteVector"/> with the list of chunks, or
/// or <see langword="null" /> if no PNG Keywords are contained.
/// </returns>
ByteVector RenderKeywordChunks ()
{
// Check, if PngTag is contained
if (!(GetTag (TagTypes.Png, true) is PngTag png_tag))
return null;
var chunks = new ByteVector ();
foreach (KeyValuePair<string, string> keyword in png_tag) {
var data = new ByteVector {
keyword.Key,
"\0",
keyword.Value
};
chunks.Add (ByteVector.FromUInt ((uint)data.Count));
chunks.Add (tEXt_CHUNK_TYPE);
chunks.Add (data);
chunks.Add (ComputeCRC (tEXt_CHUNK_TYPE, data));
}
return chunks;
}
/// <summary>
/// Attempts to extract the media properties of the main
/// photo.
/// </summary>
/// <returns>
/// A <see cref="Properties" /> object with a best effort guess
/// at the right values. When no guess at all can be made,
/// <see langword="null" /> is returned.
/// </returns>
Properties ExtractProperties ()
{
if (width > 0 && height > 0)
return new Properties (TimeSpan.Zero, new Codec (width, height));
return null;
}
#endregion
#region Utility Stuff
/// <summary>
/// Checks the CRC for a Chunk.
/// </summary>
/// <param name="chunk_type">
/// A <see cref="ByteVector"/> whith the Chunk type
/// </param>
/// <param name="chunk_data">
/// A <see cref="ByteVector"/> with the Chunk data.
/// </param>
/// <param name="crc_data">
/// A <see cref="ByteVector"/> with the read CRC data.
/// </param>
static void CheckCRC (ByteVector chunk_type, ByteVector chunk_data, ByteVector crc_data)
{
ByteVector computed_crc = ComputeCRC (chunk_type, chunk_data);
if (computed_crc != crc_data)
throw new CorruptFileException (
$"CRC check failed for {chunk_type} Chunk (expected: 0x{computed_crc.ToUInt ():X4}, read: 0x{crc_data.ToUInt ():X4}");
}
/// <summary>
/// Computes a 32bit CRC for the given data.
/// </summary>
/// <param name="datas">
/// A <see cref="T:ByteVector[]"/> with data to compute
/// the CRC for.
/// </param>
/// <returns>
/// A <see cref="ByteVector"/> with 4 bytes (32bit) containing the CRC.
/// </returns>
static ByteVector ComputeCRC (params ByteVector[] datas)
{
uint crc = 0xFFFFFFFF;
if (crc_table == null)
BuildCRCTable ();
foreach (var data in datas) {
foreach (byte b in data) {
crc = crc_table[(crc ^ b) & 0xFF] ^ (crc >> 8);
}
}
// Invert
return ByteVector.FromUInt (crc ^ 0xFFFFFFFF);
}
/// <summary>
/// Table for faster computation of CRC.
/// </summary>
static uint[] crc_table;
/// <summary>
/// Initializes the CRC Table.
/// </summary>
static void BuildCRCTable ()
{
uint polynom = 0xEDB88320;
crc_table = new uint[256];
for (int i = 0; i < 256; i++) {
uint c = (uint)i;
for (int k = 0; k < 8; k++) {
if ((c & 0x00000001) != 0x00)
c = polynom ^ (c >> 1);
else
c = c >> 1;
}
crc_table[i] = c;
}
}
static ByteVector Inflate (ByteVector data)
{
using (var out_stream = new MemoryStream ())
using (var input = new MemoryStream (data.Data)) {
input.Seek (2, SeekOrigin.Begin); // First 2 bytes are properties deflate does not need (or handle)
using (var zipstream = new DeflateStream (input, CompressionMode.Decompress)) {
//zipstream.CopyTo (out_stream); Cleaner with .NET 4
byte[] buffer = new byte[1024];
int written_bytes;
while ((written_bytes = zipstream.Read (buffer, 0, 1024)) > 0)
out_stream.Write (buffer, 0, written_bytes);
return new ByteVector (out_stream.ToArray ());
}
}
}
static ByteVector Decompress (byte compression_method, ByteVector compressed_data)
{
// there is currently just one compression method specified
// for PNG.
switch (compression_method) {
case 0:
return Inflate (compressed_data);
default:
return null;
}
}
#endregion
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c1aeeb7f1096784c802febffe0f3607
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+302
View File
@@ -0,0 +1,302 @@
//
// PngTag.cs:
//
// Author:
// Mike Gemuende (mike@gemuende.de)
//
// Copyright (C) 2010 Mike Gemuende
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License version
// 2.1 as published by the Free Software Foundation.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
//
using System;
using System.Collections;
using System.Collections.Generic;
using TagLib.Image;
namespace TagLib.Png
{
/// <summary>
/// Native Png Keywords
/// </summary>
public class PngTag : ImageTag, IEnumerable
{
#region defined PNG keywords
/// <summary>
/// Short (one line) title or caption for image
/// </summary>
public static readonly string TITLE = "Title";
/// <summary>
/// Name of image's creator
/// </summary>
public static readonly string AUTHOR = "Author";
/// <summary>
/// Description of image (possibly long)
/// </summary>
public static readonly string DESCRIPTION = "Description";
/// <summary>
/// Copyright notice
/// </summary>
public static readonly string COPYRIGHT = "Copyright";
/// <summary>
/// Time of original image creation
/// </summary>
public static readonly string CREATION_TIME = "Creation Time";
/// <summary>
/// Software used to create the image
/// </summary>
public static readonly string SOFTWARE = "Software";
/// <summary>
/// Legal disclaimer
/// </summary>
public static readonly string DISCLAIMER = "Disclaimer";
/// <summary>
/// Warning of nature of content
/// </summary>
public static readonly string WARNING = "Warning";
/// <summary>
/// Device used to create the image
/// </summary>
public static readonly string SOURCE = "Source";
/// <summary>
/// Miscellaneous comment
/// </summary>
public static readonly string COMMENT = "Comment";
#endregion
#region Private Fieds
/// <summary>
/// Store the keywords with their values
/// </summary>
readonly Dictionary<string, string> keyword_store = new Dictionary<string, string> ();
#endregion
#region Constructors
/// <summary>
/// Constructor.
/// </summary>
public PngTag ()
{
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the comment for the image described
/// by the current instance.
/// </summary>
/// <value>
/// A <see cref="string" /> containing the comment of the
/// current instace.
/// </value>
/// <remarks>
/// We use here both keywords Description and Comment of the
/// PNG specification to store the comment.
/// </remarks>
public override string Comment {
get {
string description = GetKeyword (DESCRIPTION);
if (!string.IsNullOrEmpty (description))
return description;
return GetKeyword (COMMENT);
}
set {
SetKeyword (DESCRIPTION, value);
SetKeyword (COMMENT, value);
}
}
/// <summary>
/// Gets and sets the title for the media described by the
/// current instance.
/// </summary>
/// <value>
/// A <see cref="string" /> object containing the title for
/// the media described by the current instance or <see
/// langword="null" /> if no value is present.
/// </value>
public override string Title {
get { return GetKeyword (TITLE); }
set { SetKeyword (TITLE, value); }
}
/// <summary>
/// Gets or sets the creator of the image.
/// </summary>
/// <value>
/// A <see cref="string" /> with the name of the creator.
/// </value>
public override string Creator {
get { return GetKeyword (AUTHOR); }
set { SetKeyword (AUTHOR, value); }
}
/// <summary>
/// Gets and sets the copyright information for the media
/// represented by the current instance.
/// </summary>
/// <value>
/// A <see cref="string" /> object containing the copyright
/// information for the media represented by the current
/// instance or <see langword="null" /> if no value present.
/// </value>
public override string Copyright {
get { return GetKeyword (COPYRIGHT); }
set { SetKeyword (COPYRIGHT, value); }
}
/// <summary>
/// Gets or sets the software the image, the current instance
/// belongs to, was created with.
/// </summary>
/// <value>
/// A <see cref="string" /> containing the name of the
/// software the current instace was created with.
/// </value>
public override string Software {
get { return GetKeyword (SOFTWARE); }
set { SetKeyword (SOFTWARE, value); }
}
/// <summary>
/// Gets or sets the time when the image, the current instance
/// belongs to, was taken.
/// </summary>
/// <value>
/// A <see cref="System.Nullable"/> with the time the image was taken.
/// </value>
public override DateTime? DateTime {
get {
string date = GetKeyword (CREATION_TIME);
if (System.DateTime.TryParse (date, out var ret))
return ret;
return null;
}
set {
string date = null;
if (value != null) {
// Creation Date is stored in RFC 822 for PNG
date = value.Value.ToString ("R");
}
SetKeyword (CREATION_TIME, date);
}
}
#endregion
#region Public Methods
/// <summary>
/// Sets a keyword of to the given value.
/// </summary>
/// <param name="keyword">
/// A <see cref="System.String"/> with the keyword to set.
/// </param>
/// <param name="value">
/// A <see cref="System.String"/> with the value.
/// </param>
public void SetKeyword (string keyword, string value)
{
if (string.IsNullOrEmpty (keyword))
throw new ArgumentException ("keyword is null or empty");
keyword_store.Remove (keyword);
if (value != null) {
keyword_store.Add (keyword, value);
}
}
/// <summary>
/// Gets a value of a keyword.
/// </summary>
/// <param name="keyword">
/// A <see cref="System.String"/> with the keyword to get the value for.
/// </param>
/// <returns>
/// A <see cref="System.String"/> with the value or <see langword="null" />
/// if the keyword is not contained.
/// </returns>
public string GetKeyword (string keyword)
{
keyword_store.TryGetValue (keyword, out var ret);
return ret;
}
/// <summary>
/// Gets the tag types contained in the current instance.
/// </summary>
/// <value>
/// Always <see cref="TagTypes.Png" />.
/// </value>
public override TagTypes TagTypes {
get { return TagTypes.Png; }
}
/// <summary>
/// Clears the values stored in the current instance.
/// </summary>
public override void Clear ()
{
keyword_store.Clear ();
}
/// <summary>
/// Returns an enumerator to enumerate all keywords.
/// </summary>
/// <returns>
/// A <see cref="System.Collections.IEnumerator"/> to enumerate
/// the keywords.
/// </returns>
public IEnumerator GetEnumerator ()
{
return keyword_store.GetEnumerator ();
}
#endregion
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa79d8e79060782859dbee00fe1d56f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: