/////////////////////////////////////////////////////////////////////////////////
//
// 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-2019 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.Collections.Specialized;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
namespace PhotoshopFile
{
[DebuggerDisplay("Name = {Name}")]
public class Layer
{
internal PsdFile PsdFile { get; private set; }
///
/// The rectangle containing the contents of the layer.
///
public Rectangle Rect { get; set; }
///
/// Image channels.
///
public ChannelList Channels { get; private set; }
///
/// Returns alpha channel if it exists, otherwise null.
///
public Channel AlphaChannel => Channels.SingleOrDefault(x => x.ID == -1);
private string blendModeKey;
///
/// Photoshop blend mode key for the layer
///
public string BlendModeKey
{
get => blendModeKey;
set
{
if (value.Length != 4)
{
throw new ArgumentException(
$"{nameof(BlendModeKey)} must be 4 characters in length.");
}
blendModeKey = value;
}
}
///
/// 0 = transparent ... 255 = opaque
///
public byte Opacity { get; set; }
///
/// false = base, true = non-base
///
public bool Clipping { get; set; }
private static int protectTransBit = BitVector32.CreateMask();
private static int visibleBit = BitVector32.CreateMask(protectTransBit);
BitVector32 flags = new BitVector32();
///
/// If true, the layer is visible.
///
public bool Visible
{
get => !flags[visibleBit];
set => flags[visibleBit] = !value;
}
///
/// Protect the transparency
///
public bool ProtectTrans
{
get => flags[protectTransBit];
set => flags[protectTransBit] = value;
}
///
/// The descriptive layer name
///
public string Name { get; set; }
public BlendingRanges BlendingRangesData { get; set; }
public MaskInfo Masks { get; set; }
public List AdditionalInfo { get; set; }
///////////////////////////////////////////////////////////////////////////
public Layer(PsdFile psdFile)
{
PsdFile = psdFile;
Rect = Rectangle.Empty;
Channels = new ChannelList();
BlendModeKey = PsdBlendMode.Normal;
AdditionalInfo = new List();
}
public Layer(PsdBinaryReader reader, PsdFile psdFile)
: this(psdFile)
{
Util.DebugMessage(reader.BaseStream, "Load, Begin, Layer");
Rect = reader.ReadRectangle();
//-----------------------------------------------------------------------
// Read channel headers. Image data comes later, after the layer header.
int numberOfChannels = reader.ReadUInt16();
for (int channel = 0; channel < numberOfChannels; channel++)
{
var ch = new Channel(reader, this);
Channels.Add(ch);
}
//-----------------------------------------------------------------------
//
var signature = reader.ReadAsciiChars(4);
if (signature != "8BIM")
throw (new PsdInvalidException("Invalid signature in layer header."));
BlendModeKey = reader.ReadAsciiChars(4);
Opacity = reader.ReadByte();
Clipping = reader.ReadBoolean();
var flagsByte = reader.ReadByte();
flags = new BitVector32(flagsByte);
reader.ReadByte(); //padding
//-----------------------------------------------------------------------
// This is the total size of the MaskData, the BlendingRangesData, the
// Name and the AdjustmentLayerInfo.
var extraDataSize = reader.ReadUInt32();
var extraDataStartPosition = reader.BaseStream.Position;
Masks = new MaskInfo(reader, this);
BlendingRangesData = new BlendingRanges(reader, this);
Name = reader.ReadPascalString(4);
//-----------------------------------------------------------------------
// Process Additional Layer Information
long adjustmentLayerEndPos = extraDataStartPosition + extraDataSize;
while (reader.BaseStream.Position < adjustmentLayerEndPos)
{
var layerInfo = LayerInfoFactory.Load(reader,
psdFile: this.PsdFile,
globalLayerInfo: false);
AdditionalInfo.Add(layerInfo);
}
foreach (var adjustmentInfo in AdditionalInfo)
{
switch (adjustmentInfo.Key)
{
case "luni":
Name = ((LayerUnicodeName)adjustmentInfo).Name;
break;
}
}
Util.DebugMessage(reader.BaseStream, $"Load, End, Layer, {Name}");
PsdFile.LoadContext.OnLoadLayerHeader(this);
}
///////////////////////////////////////////////////////////////////////////
///
/// Create ImageData for any missing channels.
///
public void CreateMissingChannels()
{
var channelCount = this.PsdFile.ColorMode.MinChannelCount();
for (short id = 0; id < channelCount; id++)
{
if (!this.Channels.ContainsId(id))
{
var size = this.Rect.Height * this.Rect.Width;
var ch = new Channel(id, this);
ch.ImageData = new byte[size];
if (size > 0)
{
unsafe
{
fixed (byte* ptr = &ch.ImageData[0])
{
Util.Fill(ptr, ptr + size, (byte)255);
}
}
}
this.Channels.Add(ch);
}
}
}
///////////////////////////////////////////////////////////////////////////
public void PrepareSave()
{
foreach (var ch in Channels)
{
ch.CompressImageData();
}
// Create or update the Unicode layer name to be consistent with the
// ANSI layer name.
var layerUnicodeNames = AdditionalInfo.Where(x => x is LayerUnicodeName);
if (layerUnicodeNames.Count() > 1)
{
throw new PsdInvalidException(
$"{nameof(Layer)} can only have one {nameof(LayerUnicodeName)}.");
}
var layerUnicodeName = (LayerUnicodeName) layerUnicodeNames.FirstOrDefault();
if (layerUnicodeName == null)
{
layerUnicodeName = new LayerUnicodeName(Name);
AdditionalInfo.Add(layerUnicodeName);
}
else if (layerUnicodeName.Name != Name)
{
layerUnicodeName.Name = Name;
}
}
public void Save(PsdBinaryWriter writer)
{
Util.DebugMessage(writer.BaseStream, "Save, Begin, Layer");
writer.Write(Rect);
//-----------------------------------------------------------------------
writer.Write((short)Channels.Count);
foreach (var ch in Channels)
ch.Save(writer);
//-----------------------------------------------------------------------
writer.WriteAsciiChars("8BIM");
writer.WriteAsciiChars(BlendModeKey);
writer.Write(Opacity);
writer.Write(Clipping);
writer.Write((byte)flags.Data);
writer.Write((byte)0);
//-----------------------------------------------------------------------
using (new PsdBlockLengthWriter(writer))
{
Masks.Save(writer);
BlendingRangesData.Save(writer);
var namePosition = writer.BaseStream.Position;
// Legacy layer name is limited to 31 bytes. Unicode layer name
// can be much longer.
writer.WritePascalString(Name, 4, 31);
foreach (LayerInfo info in AdditionalInfo)
{
info.Save(writer,
globalLayerInfo: false,
isLargeDocument: PsdFile.IsLargeDocument);
}
}
Util.DebugMessage(writer.BaseStream, $"Save, End, Layer, {Name}");
}
}
}