Initial commit
This commit is contained in:
commit
262601edb8
2025 files changed
+218754
No files matched your search
@@ -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) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61dacbe123502832e83e23426d871c08
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c1aeeb7f1096784c802febffe0f3607
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa79d8e79060782859dbee00fe1d56f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user