mirror of https://github.com/SixLabors/ImageSharp
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.
241 lines
7.6 KiB
241 lines
7.6 KiB
// Copyright (c) Six Labors and contributors.
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
#if !NETSTANDARD1_1
|
|
using System.Security.Cryptography;
|
|
#endif
|
|
|
|
namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
|
|
{
|
|
/// <summary>
|
|
/// Represents an ICC profile
|
|
/// </summary>
|
|
public sealed class IccProfile
|
|
{
|
|
/// <summary>
|
|
/// The byte array to read the ICC profile from
|
|
/// </summary>
|
|
private byte[] data;
|
|
|
|
/// <summary>
|
|
/// The backing file for the <see cref="Entries"/> property
|
|
/// </summary>
|
|
private List<IccTagDataEntry> entries;
|
|
|
|
/// <summary>
|
|
/// ICC profile header
|
|
/// </summary>
|
|
private IccProfileHeader header;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="IccProfile"/> class.
|
|
/// </summary>
|
|
public IccProfile()
|
|
: this((byte[])null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="IccProfile"/> class.
|
|
/// </summary>
|
|
/// <param name="data">The raw ICC profile data</param>
|
|
public IccProfile(byte[] data)
|
|
{
|
|
this.data = data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="IccProfile"/> class
|
|
/// by making a copy from another ICC profile.
|
|
/// </summary>
|
|
/// <param name="other">The other ICC profile, where the clone should be made from.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>>
|
|
public IccProfile(IccProfile other)
|
|
{
|
|
Guard.NotNull(other, nameof(other));
|
|
|
|
this.data = other.ToByteArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="IccProfile"/> class.
|
|
/// </summary>
|
|
/// <param name="header">The profile header</param>
|
|
/// <param name="entries">The actual profile data</param>
|
|
internal IccProfile(IccProfileHeader header, IEnumerable<IccTagDataEntry> entries)
|
|
{
|
|
Guard.NotNull(header, nameof(header));
|
|
Guard.NotNull(entries, nameof(entries));
|
|
|
|
this.header = header;
|
|
this.entries = new List<IccTagDataEntry>(entries);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the profile header
|
|
/// </summary>
|
|
public IccProfileHeader Header
|
|
{
|
|
get
|
|
{
|
|
this.InitializeHeader();
|
|
return this.header;
|
|
}
|
|
|
|
set => this.header = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the actual profile data
|
|
/// </summary>
|
|
public List<IccTagDataEntry> Entries
|
|
{
|
|
get
|
|
{
|
|
this.InitializeEntries();
|
|
return this.entries;
|
|
}
|
|
}
|
|
|
|
#if !NETSTANDARD1_1
|
|
|
|
/// <summary>
|
|
/// Calculates the MD5 hash value of an ICC profile
|
|
/// </summary>
|
|
/// <param name="data">The data of which to calculate the hash value</param>
|
|
/// <returns>The calculated hash</returns>
|
|
public static IccProfileId CalculateHash(byte[] data)
|
|
{
|
|
Guard.NotNull(data, nameof(data));
|
|
Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header");
|
|
|
|
const int profileFlagPos = 44;
|
|
const int renderingIntentPos = 64;
|
|
const int profileIdPos = 84;
|
|
|
|
// need to copy some values because they need to be zero for the hashing
|
|
byte[] temp = new byte[24];
|
|
Buffer.BlockCopy(data, profileFlagPos, temp, 0, 4);
|
|
Buffer.BlockCopy(data, renderingIntentPos, temp, 4, 4);
|
|
Buffer.BlockCopy(data, profileIdPos, temp, 8, 16);
|
|
|
|
using (var md5 = MD5.Create())
|
|
{
|
|
try
|
|
{
|
|
// Zero out some values
|
|
Array.Clear(data, profileFlagPos, 4);
|
|
Array.Clear(data, renderingIntentPos, 4);
|
|
Array.Clear(data, profileIdPos, 16);
|
|
|
|
// Calculate hash
|
|
byte[] hash = md5.ComputeHash(data);
|
|
|
|
// Read values from hash
|
|
var reader = new IccDataReader(hash);
|
|
return reader.ReadProfileId();
|
|
}
|
|
finally
|
|
{
|
|
Buffer.BlockCopy(temp, 0, data, profileFlagPos, 4);
|
|
Buffer.BlockCopy(temp, 4, data, renderingIntentPos, 4);
|
|
Buffer.BlockCopy(temp, 8, data, profileIdPos, 16);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Extends the profile with additional data.
|
|
/// </summary>
|
|
/// <param name="bytes">The array containing addition profile data.</param>
|
|
public void Extend(byte[] bytes)
|
|
{
|
|
int currentLength = this.data.Length;
|
|
Array.Resize(ref this.data, currentLength + bytes.Length);
|
|
Buffer.BlockCopy(bytes, 0, this.data, currentLength, bytes.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks for signs of a corrupt profile.
|
|
/// </summary>
|
|
/// <remarks>This is not an absolute proof of validity but should weed out most corrupt data.</remarks>
|
|
/// <returns>True if the profile is valid; False otherwise</returns>
|
|
public bool CheckIsValid()
|
|
{
|
|
const int minSize = 128;
|
|
const int maxSize = 50_000_000; // it's unlikely there is a profile bigger than 50MB
|
|
|
|
bool arrayValid = true;
|
|
if (this.data != null)
|
|
{
|
|
arrayValid = this.data.Length >= minSize &&
|
|
this.data.Length >= this.Header.Size;
|
|
}
|
|
|
|
return arrayValid &&
|
|
Enum.IsDefined(typeof(IccColorSpaceType), this.Header.DataColorSpace) &&
|
|
Enum.IsDefined(typeof(IccColorSpaceType), this.Header.ProfileConnectionSpace) &&
|
|
Enum.IsDefined(typeof(IccRenderingIntent), this.Header.RenderingIntent) &&
|
|
this.Header.Size >= minSize &&
|
|
this.Header.Size < maxSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts this instance to a byte array.
|
|
/// </summary>
|
|
/// <returns>The <see cref="T:byte[]"/></returns>
|
|
public byte[] ToByteArray()
|
|
{
|
|
if (this.data != null)
|
|
{
|
|
byte[] copy = new byte[this.data.Length];
|
|
Buffer.BlockCopy(this.data, 0, copy, 0, copy.Length);
|
|
return copy;
|
|
}
|
|
else
|
|
{
|
|
var writer = new IccWriter();
|
|
return writer.Write(this);
|
|
}
|
|
}
|
|
|
|
private void InitializeHeader()
|
|
{
|
|
if (this.header != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.data == null)
|
|
{
|
|
this.header = new IccProfileHeader();
|
|
return;
|
|
}
|
|
|
|
var reader = new IccReader();
|
|
this.header = reader.ReadHeader(this.data);
|
|
}
|
|
|
|
private void InitializeEntries()
|
|
{
|
|
if (this.entries != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.data == null)
|
|
{
|
|
this.entries = new List<IccTagDataEntry>();
|
|
return;
|
|
}
|
|
|
|
var reader = new IccReader();
|
|
this.entries = new List<IccTagDataEntry>(reader.ReadTagData(this.data));
|
|
}
|
|
}
|
|
}
|
|
|