Singularity/Library/PackageCache/com.unity.2d.psdimporter@6.0.7/Editor/PSDPlugin/PsdFile/PsdFile.cs
2024-05-06 11:45:45 -07:00

712 lines
23 KiB
C#

/////////////////////////////////////////////////////////////////////////////////
//
// Photoshop PSD FileType Plugin for Paint.NET
// http://psdplugin.codeplex.com/
//
// This software is provided under the MIT License:
// Copyright (c) 2006-2007 Frank Blumenberg
// Copyright (c) 2010-2016 Tao Yue
//
// Portions of this file are provided under the BSD 3-clause License:
// Copyright (c) 2006, Jonas Beckeman
//
// See LICENSE.txt for complete licensing and attribution information.
//
/////////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Diagnostics;
using PDNWrapper;
using System.IO;
using System.Linq;
using System.Text;
using PaintDotNet.Data.PhotoshopFileType;
namespace PhotoshopFile
{
[Flags]
internal enum ELoadFlag
{
Header = 1,
ColorMode = Header | 1 << 2,
ImageData = ColorMode | 1 << 3,
All = Header | ColorMode | ImageData
}
internal enum PsdColorMode
{
Bitmap = 0,
Grayscale = 1,
Indexed = 2,
RGB = 3,
CMYK = 4,
Multichannel = 7,
Duotone = 8,
Lab = 9
};
internal enum PsdFileVersion : short
{
Psd = 1,
PsbLargeDocument = 2
}
internal class PsdFile
{
#region Constructors
ELoadFlag m_LoadFlag;
public PsdFile(PsdFileVersion version = PsdFileVersion.Psd)
{
Version = version;
BaseLayer = new Layer(this);
ImageResources = new ImageResources();
Layers = new List<Layer>();
AdditionalInfo = new List<LayerInfo>();
}
public PsdFile(string filename, LoadContext loadContext, ELoadFlag loadFlag = ELoadFlag.All)
: this()
{
using (var stream = new FileStream(filename, FileMode.Open))
{
Load(stream, loadContext, loadFlag);
}
}
public PsdFile(Stream stream, LoadContext loadContext, ELoadFlag loadFlag = ELoadFlag.All)
: this()
{
Load(stream, loadContext, loadFlag);
}
#endregion
#region Load and save
internal LoadContext LoadContext { get; private set; }
private void Load(Stream stream, LoadContext loadContext, ELoadFlag loadFlag)
{
LoadContext = loadContext;
var reader = new PsdBinaryReader(stream, loadContext.Encoding);
if ((loadFlag & ELoadFlag.Header) == ELoadFlag.Header)
LoadHeader(reader);
if ((loadFlag & ELoadFlag.ColorMode) == ELoadFlag.ColorMode)
LoadColorModeData(reader);
if ((loadFlag & ELoadFlag.ImageData) == ELoadFlag.ImageData)
{
LoadImageResources(reader);
LoadLayerAndMaskInfo(reader);
LoadImage(reader);
DecompressImages();
}
}
#endregion
#region Header
/// <summary>
/// Photoshop file format version.
/// </summary>
public PsdFileVersion Version { get; private set; }
public bool IsLargeDocument
{
get { return Version == PsdFileVersion.PsbLargeDocument; }
}
private Int16 channelCount;
/// <summary>
/// The number of channels in the image, including any alpha channels.
/// </summary>
public Int16 ChannelCount
{
get { return channelCount; }
set
{
if (value < 1 || value > 56)
throw new ArgumentException("Number of channels must be from 1 to 56.");
channelCount = value;
}
}
private void CheckDimension(int dimension)
{
if (dimension < 1)
{
throw new ArgumentException("Image dimension must be at least 1.");
}
if ((Version == PsdFileVersion.Psd) && (dimension > 30000))
{
throw new ArgumentException("PSD image dimension cannot exceed 30000.");
}
if ((Version == PsdFileVersion.PsbLargeDocument) && (dimension > 300000))
{
throw new ArgumentException("PSB image dimension cannot exceed 300000.");
}
}
/// <summary>
/// The height of the image in pixels.
/// </summary>
public int RowCount
{
get { return this.BaseLayer.Rect.Height; }
set
{
CheckDimension(value);
BaseLayer.Rect = new Rectangle(0, 0, BaseLayer.Rect.Width, value);
}
}
/// <summary>
/// The width of the image in pixels.
/// </summary>
public int ColumnCount
{
get { return this.BaseLayer.Rect.Width; }
set
{
CheckDimension(value);
BaseLayer.Rect = new Rectangle(0, 0, value, BaseLayer.Rect.Height);
}
}
private int bitDepth;
/// <summary>
/// The number of bits per channel. Supported values are 1, 8, 16, and 32.
/// </summary>
public int BitDepth
{
get { return bitDepth; }
set
{
switch (value)
{
case 1:
case 8:
case 16:
case 32:
bitDepth = value;
break;
default:
throw new NotImplementedException("Invalid bit depth.");
}
}
}
/// <summary>
/// The color mode of the file.
/// </summary>
public PsdColorMode ColorMode { get; set; }
///////////////////////////////////////////////////////////////////////////
private void LoadHeader(PsdBinaryReader reader)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, File header");
var signature = reader.ReadAsciiChars(4);
if (signature != "8BPS")
throw new PsdInvalidException("The given stream is not a valid PSD file");
Version = (PsdFileVersion)reader.ReadInt16();
Util.DebugMessage(reader.BaseStream, "Load, Info, Version {0}", (int)Version);
if ((Version != PsdFileVersion.Psd)
&& (Version != PsdFileVersion.PsbLargeDocument))
{
throw new PsdInvalidException("The PSD file has an unknown version");
}
//6 bytes reserved
reader.BaseStream.Position += 6;
this.ChannelCount = reader.ReadInt16();
this.RowCount = reader.ReadInt32();
this.ColumnCount = reader.ReadInt32();
BitDepth = reader.ReadInt16();
ColorMode = (PsdColorMode)reader.ReadInt16();
Util.DebugMessage(reader.BaseStream, "Load, End, File header");
}
#endregion
///////////////////////////////////////////////////////////////////////////
#region ColorModeData
/// <summary>
/// If ColorMode is ColorModes.Indexed, the following 768 bytes will contain
/// a 256-color palette. If the ColorMode is ColorModes.Duotone, the data
/// following presumably consists of screen parameters and other related information.
/// Unfortunately, it is intentionally not documented by Adobe, and non-Photoshop
/// readers are advised to treat duotone images as gray-scale images.
/// </summary>
public byte[] ColorModeData = new byte[0];
private void LoadColorModeData(PsdBinaryReader reader)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, ColorModeData");
var paletteLength = reader.ReadUInt32();
if (paletteLength > 0)
{
ColorModeData = reader.ReadBytes((int)paletteLength);
}
Util.DebugMessage(reader.BaseStream, "Load, End, ColorModeData");
}
#endregion
///////////////////////////////////////////////////////////////////////////
#region ImageResources
/// <summary>
/// The Image resource blocks for the file
/// </summary>
public ImageResources ImageResources { get; set; }
public ResolutionInfo Resolution
{
get
{
return (ResolutionInfo)ImageResources.Get(ResourceID.ResolutionInfo);
}
set
{
ImageResources.Set(value);
}
}
///////////////////////////////////////////////////////////////////////////
private void LoadImageResources(PsdBinaryReader reader)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, ImageResources");
var imageResourcesLength = reader.ReadUInt32();
if (imageResourcesLength <= 0)
return;
var startPosition = reader.BaseStream.Position;
var endPosition = startPosition + imageResourcesLength;
while (reader.BaseStream.Position < endPosition)
{
var imageResource = ImageResourceFactory.CreateImageResource(reader);
ImageResources.Add(imageResource);
}
Util.DebugMessage(reader.BaseStream, "Load, End, ImageResources");
//-----------------------------------------------------------------------
// make sure we are not on a wrong offset, so set the stream position
// manually
reader.BaseStream.Position = startPosition + imageResourcesLength;
}
#endregion
///////////////////////////////////////////////////////////////////////////
#region LayerAndMaskInfo
public List<Layer> Layers { get; private set; }
public List<LayerInfo> AdditionalInfo { get; private set; }
public bool AbsoluteAlpha { get; set; }
///////////////////////////////////////////////////////////////////////////
private void LoadLayerAndMaskInfo(PsdBinaryReader reader)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, Layer and mask info");
var layersAndMaskLength = IsLargeDocument
? reader.ReadInt64()
: reader.ReadUInt32();
if (layersAndMaskLength <= 0)
return;
var startPosition = reader.BaseStream.Position;
var endPosition = startPosition + layersAndMaskLength;
LoadLayers(reader, true);
LoadGlobalLayerMask(reader);
//-----------------------------------------------------------------------
// Load Additional Layer Information
while (reader.BaseStream.Position < endPosition)
{
var info = LayerInfoFactory.Load(reader, this, true, endPosition);
AdditionalInfo.Add(info);
if (info is RawLayerInfo)
{
var layerInfo = (RawLayerInfo)info;
switch (info.Key)
{
case "LMsk":
m_GlobalLayerMaskData = layerInfo.Data;
break;
}
}
}
Util.DebugMessage(reader.BaseStream, "Load, End, Layer and mask info");
//-----------------------------------------------------------------------
// make sure we are not on a wrong offset, so set the stream position
// manually
reader.BaseStream.Position = startPosition + layersAndMaskLength;
}
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Load Layers Info section, including image data.
/// </summary>
/// <param name="reader">PSD reader.</param>
/// <param name="hasHeader">Whether the Layers Info section has a length header.</param>
internal void LoadLayers(PsdBinaryReader reader, bool hasHeader)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, Layers Info section");
long sectionLength = 0;
if (hasHeader)
{
sectionLength = IsLargeDocument
? reader.ReadInt64()
: reader.ReadUInt32();
if (sectionLength <= 0)
{
// The callback may take action when there are 0 layers, so it must
// be called even though the Layers Info section is empty.
LoadContext.OnLoadLayersHeader(this);
Util.DebugMessage(reader.BaseStream, "Load, End, Layers Info section");
return;
}
}
var startPosition = reader.BaseStream.Position;
var numLayers = reader.ReadInt16();
// If numLayers < 0, then number of layers is absolute value,
// and the first alpha channel contains the transparency data for
// the merged result.
if (numLayers < 0)
{
AbsoluteAlpha = true;
numLayers = Math.Abs(numLayers);
}
for (int i = 0; i < numLayers; i++)
{
var layer = new Layer(reader, this);
Layers.Add(layer);
}
// Header is complete just before loading pixel data
LoadContext.OnLoadLayersHeader(this);
//-----------------------------------------------------------------------
// Load image data for all channels.
foreach (var layer in Layers)
{
Util.DebugMessage(reader.BaseStream,
"Load, Begin, Layer image, layer.Name");
foreach (var channel in layer.Channels)
{
channel.LoadPixelData(reader);
}
Util.DebugMessage(reader.BaseStream,
"Load, End, Layer image, layer.Name");
}
// Length is set to 0 when called on higher bitdepth layers.
if (sectionLength > 0)
{
// Layers Info section is documented to be even-padded, but Photoshop
// actually pads to 4 bytes.
var endPosition = startPosition + sectionLength;
var positionOffset = reader.BaseStream.Position - endPosition;
Debug.Assert(positionOffset > -4,
"LoadLayers did not read the full length of the Layers Info section.");
Debug.Assert(positionOffset <= 0,
"LoadLayers read past the end of the Layers Info section.");
if (reader.BaseStream.Position < endPosition)
reader.BaseStream.Position = endPosition;
}
Util.DebugMessage(reader.BaseStream, "Load, End, Layers");
}
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Cleanup
/// </summary>
public void Cleanup()
{
var layersAndComposite = Layers.Concat(new[] { BaseLayer });
foreach (var lac in layersAndComposite)
{
foreach (var c in lac.Channels)
{
c.Cleanup();
}
}
}
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Decompress the document image data and all the layers' image data, in parallel.
/// </summary>
private void DecompressImages()
{
var layersAndComposite = Layers.Concat(new[] { BaseLayer });
//var channels = layersAndComposite.SelectMany(x => x.Channels);
//Parallel.ForEach(channels, channel =>
//{
// channel.DecodeImageData();
//});
foreach (var lac in layersAndComposite)
{
foreach (var c in lac.Channels)
{
c.DecodeImageData();
}
}
foreach (var layer in Layers)
{
foreach (var channel in layer.Channels)
{
if (channel.ID == -2)
layer.Masks.LayerMask.ImageData = channel.ImageData;
else if (channel.ID == -3)
layer.Masks.UserMask.ImageData = channel.ImageData;
}
}
}
/// <summary>
/// Verifies that any Additional Info layers are consistent.
/// </summary>
private void VerifyInfoLayers()
{
var infoLayersCount = AdditionalInfo.Count(x => x is InfoLayers);
if (infoLayersCount > 1)
{
throw new PsdInvalidException(
"Cannot have more than one InfoLayers in a PSD file.");
}
if ((infoLayersCount > 0) && (Layers.Count == 0))
{
throw new PsdInvalidException(
"InfoLayers cannot exist when there are 0 layers.");
}
}
/// <summary>
/// Verify validity of layer sections. Each start marker should have a
/// matching end marker.
/// </summary>
internal void VerifyLayerSections()
{
int depth = 0;
foreach (var layer in Enumerable.Reverse(Layers))
{
var layerSectionInfo = layer.AdditionalInfo.SingleOrDefault(
x => x is LayerSectionInfo);
if (layerSectionInfo == null)
continue;
var sectionInfo = (LayerSectionInfo)layerSectionInfo;
switch (sectionInfo.SectionType)
{
case LayerSectionType.OpenFolder:
case LayerSectionType.ClosedFolder:
depth++;
break;
case LayerSectionType.SectionDivider:
depth--;
if (depth < 0)
throw new PsdInvalidException("Layer section ended without matching start marker.");
break;
case LayerSectionType.Layer: // Nothing to do here yet.
break;
default:
throw new PsdInvalidException("Unrecognized layer section type.");
}
}
if (depth != 0)
throw new PsdInvalidException("Layer section not closed by end marker.");
}
/// <summary>
/// Set the VersionInfo resource on the file.
/// </summary>
public void SetVersionInfo()
{
var versionInfo = (VersionInfo)ImageResources.Get(ResourceID.VersionInfo);
if (versionInfo == null)
{
versionInfo = new VersionInfo();
ImageResources.Set(versionInfo);
// Get the version string. We don't use the fourth part (revision).
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var version = assembly.GetName().Version;
var versionString = version.Major + "." + version.Minor + "." + version.Build;
// Strings are not localized since they are not shown to the user.
versionInfo.Version = 1;
versionInfo.HasRealMergedData = true;
versionInfo.ReaderName = "Paint.NET PSD Plugin";
versionInfo.WriterName = "Paint.NET PSD Plugin " + versionString;
versionInfo.FileVersion = 1;
}
}
///////////////////////////////////////////////////////////////////////////
byte[] m_GlobalLayerMaskData;
private void LoadGlobalLayerMask(PsdBinaryReader reader)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, GlobalLayerMask");
var maskLength = reader.ReadUInt32();
if (maskLength <= 0)
{
Util.DebugMessage(reader.BaseStream, "Load, End, GlobalLayerMask");
return;
}
m_GlobalLayerMaskData = reader.ReadBytes((int)maskLength);
Util.DebugMessage(reader.BaseStream, "Load, End, GlobalLayerMask");
}
public byte[] globalLayerMaskData
{
get { return m_GlobalLayerMaskData; }
}
#endregion
///////////////////////////////////////////////////////////////////////////
#region Composite image
/// <summary>
/// Represents the composite image.
/// </summary>
public Layer BaseLayer { get; set; }
public ImageCompression ImageCompression { get; set; }
private void LoadImage(PsdBinaryReader reader)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, Composite image");
ImageCompression = (ImageCompression)reader.ReadInt16();
// Create channels
for (Int16 i = 0; i < ChannelCount; i++)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, Channel image data");
var channel = new Channel(i, this.BaseLayer);
channel.ImageCompression = ImageCompression;
channel.Length = this.RowCount
* Util.BytesPerRow(BaseLayer.Rect.Size, BitDepth);
// The composite image stores all RLE headers up-front, rather than
// with each channel.
if (ImageCompression == ImageCompression.Rle)
{
channel.RleRowLengths = new RleRowLengths(reader, RowCount, IsLargeDocument);
channel.Length = channel.RleRowLengths.Total;
}
BaseLayer.Channels.Add(channel);
Util.DebugMessage(reader.BaseStream, "Load, End, Channel image data");
}
foreach (var channel in this.BaseLayer.Channels)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, Channel image data");
Util.CheckByteArrayLength(channel.Length);
channel.ImageDataRaw = reader.ReadBytes((int)channel.Length);
Util.DebugMessage(reader.BaseStream, "Load, End, Channel image data");
}
// If there is exactly one more channel than we need, then it is the
// alpha channel.
if ((ColorMode != PsdColorMode.Multichannel)
&& (ChannelCount == ColorMode.MinChannelCount() + 1))
{
var alphaChannel = BaseLayer.Channels.Last();
alphaChannel.ID = -1;
}
Util.DebugMessage(reader.BaseStream, "Load, End, Composite image");
}
#endregion
}
/// <summary>
/// The possible Compression methods.
/// </summary>
internal enum ImageCompression
{
/// <summary>
/// Raw data
/// </summary>
Raw = 0,
/// <summary>
/// RLE compressed
/// </summary>
Rle = 1,
/// <summary>
/// ZIP without prediction.
/// </summary>
Zip = 2,
/// <summary>
/// ZIP with prediction.
/// </summary>
ZipPrediction = 3
}
}