testss
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using PhotoshopFile;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Analytics;
|
||||
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
[Serializable]
|
||||
internal struct PSDApplyEvent
|
||||
{
|
||||
public int instance_id;
|
||||
public int texture_type;
|
||||
public int sprite_mode;
|
||||
public bool mosaic_layer;
|
||||
public bool import_hidden_layer;
|
||||
public bool character_mode;
|
||||
public bool generate_go_hierarchy;
|
||||
public bool reslice_from_layer;
|
||||
public bool is_character_rigged;
|
||||
public SpriteAlignment character_alignment;
|
||||
public bool is_psd;
|
||||
public PsdColorMode color_mode;
|
||||
|
||||
}
|
||||
|
||||
internal interface IAnalytics
|
||||
{
|
||||
AnalyticsResult SendApplyEvent(PSDApplyEvent evt);
|
||||
}
|
||||
|
||||
internal static class AnalyticFactory
|
||||
{
|
||||
static IAnalytics s_Analytics;
|
||||
static public IAnalytics analytics
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_Analytics == null)
|
||||
s_Analytics = new Analytics();
|
||||
return s_Analytics;
|
||||
}
|
||||
set { s_Analytics = value; }
|
||||
}
|
||||
}
|
||||
|
||||
[InitializeOnLoad]
|
||||
internal class Analytics : IAnalytics
|
||||
{
|
||||
const int k_MaxEventsPerHour = 100;
|
||||
const int k_MaxNumberOfElements = 1000;
|
||||
const string k_VendorKey = "unity.2d.psdimporter";
|
||||
const int k_Version = 1;
|
||||
|
||||
static Analytics()
|
||||
{
|
||||
EditorAnalytics.RegisterEventWithLimit("psdImporterApply", k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey, k_Version);
|
||||
}
|
||||
|
||||
public AnalyticsResult SendApplyEvent(PSDApplyEvent evt)
|
||||
{
|
||||
return EditorAnalytics.SendEventWithLimit("psdImporterApply", evt, k_Version);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Psdimporter.Tests.EditorTests")]
|
@@ -0,0 +1,85 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.U2D;
|
||||
using UnityEditor.U2D.Sprites;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
internal class PSDImportPostProcessor : AssetPostprocessor
|
||||
{
|
||||
private static string s_CurrentApplyAssetPath = null;
|
||||
|
||||
void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
|
||||
{
|
||||
var dataProviderFactories = new SpriteDataProviderFactories();
|
||||
dataProviderFactories.Init();
|
||||
PSDImporter psd = AssetImporter.GetAtPath(assetPath) as PSDImporter;
|
||||
if (psd == null)
|
||||
return;
|
||||
ISpriteEditorDataProvider importer = dataProviderFactories.GetSpriteEditorDataProviderFromObject(psd);
|
||||
if (importer != null)
|
||||
{
|
||||
importer.InitSpriteEditorDataProvider();
|
||||
var physicsOutlineDataProvider = importer.GetDataProvider<ISpritePhysicsOutlineDataProvider>();
|
||||
var textureDataProvider = importer.GetDataProvider<ITextureDataProvider>();
|
||||
int actualWidth = 0, actualHeight = 0;
|
||||
textureDataProvider.GetTextureActualWidthAndHeight(out actualWidth, out actualHeight);
|
||||
float definitionScaleW = (float)texture.width / actualWidth;
|
||||
float definitionScaleH = (float)texture.height / actualHeight;
|
||||
float definitionScale = Mathf.Min(definitionScaleW, definitionScaleH);
|
||||
foreach (var sprite in sprites)
|
||||
{
|
||||
var guid = sprite.GetSpriteID();
|
||||
var outline = physicsOutlineDataProvider.GetOutlines(guid);
|
||||
var outlineOffset = sprite.rect.size / 2;
|
||||
if (outline != null && outline.Count > 0)
|
||||
{
|
||||
// Ensure that outlines are all valid.
|
||||
int validOutlineCount = 0;
|
||||
for (int i = 0; i < outline.Count; ++i)
|
||||
validOutlineCount = validOutlineCount + ( (outline[i].Length > 2) ? 1 : 0 );
|
||||
|
||||
int index = 0;
|
||||
var convertedOutline = new Vector2[validOutlineCount][];
|
||||
for (int i = 0; i < outline.Count; ++i)
|
||||
{
|
||||
if (outline[i].Length > 2)
|
||||
{
|
||||
convertedOutline[index] = new Vector2[outline[i].Length];
|
||||
for (int j = 0; j < outline[i].Length; ++j)
|
||||
{
|
||||
convertedOutline[index][j] = outline[i][j] * definitionScale + outlineOffset;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
sprite.OverridePhysicsShape(convertedOutline);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string currentApplyAssetPath
|
||||
{
|
||||
set { s_CurrentApplyAssetPath = value; }
|
||||
}
|
||||
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromPath)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(s_CurrentApplyAssetPath))
|
||||
{
|
||||
foreach (var asset in importedAssets)
|
||||
{
|
||||
if (asset == s_CurrentApplyAssetPath)
|
||||
{
|
||||
var obj = AssetDatabase.LoadMainAssetAtPath(asset);
|
||||
Selection.activeObject = obj;
|
||||
Unsupported.SceneTrackerFlushDirty();
|
||||
s_CurrentApplyAssetPath = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,294 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEditor.U2D.Common;
|
||||
using UnityEditor.U2D.Animation;
|
||||
using System;
|
||||
using UnityEditor.U2D.Sprites;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
internal abstract class PSDDataProvider
|
||||
{
|
||||
public PSDImporter dataProvider;
|
||||
}
|
||||
|
||||
internal class SpriteBoneDataProvider : PSDDataProvider, ISpriteBoneDataProvider
|
||||
{
|
||||
public List<SpriteBone> GetBones(GUID guid)
|
||||
{
|
||||
var sprite = ((SpriteMetaData)dataProvider.GetSpriteData(guid));
|
||||
Assert.IsNotNull(sprite, string.Format("Sprite not found for GUID:{0}", guid.ToString()));
|
||||
return sprite.spriteBone != null ? sprite.spriteBone.ToList() : new List<SpriteBone>();
|
||||
}
|
||||
|
||||
public void SetBones(GUID guid, List<SpriteBone> bones)
|
||||
{
|
||||
var sprite = dataProvider.GetSpriteDataFromAllMode(guid);
|
||||
if (sprite != null)
|
||||
((SpriteMetaData)sprite).spriteBone = bones;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TextureDataProvider : PSDDataProvider, ITextureDataProvider
|
||||
{
|
||||
Texture2D m_ReadableTexture;
|
||||
Texture2D m_OriginalTexture;
|
||||
|
||||
PSDImporter textureImporter { get { return (PSDImporter)dataProvider.targetObject; } }
|
||||
|
||||
public Texture2D texture
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_OriginalTexture == null)
|
||||
m_OriginalTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(textureImporter.assetPath);
|
||||
return m_OriginalTexture;
|
||||
}
|
||||
}
|
||||
|
||||
public Texture2D previewTexture
|
||||
{
|
||||
get { return texture; }
|
||||
}
|
||||
|
||||
public Texture2D GetReadableTexture2D()
|
||||
{
|
||||
if (m_ReadableTexture == null)
|
||||
{
|
||||
m_ReadableTexture = InternalEditorBridge.CreateTemporaryDuplicate(texture, texture.width, texture.height);
|
||||
if (m_ReadableTexture != null)
|
||||
m_ReadableTexture.filterMode = texture.filterMode;
|
||||
}
|
||||
return m_ReadableTexture;
|
||||
}
|
||||
|
||||
public void GetTextureActualWidthAndHeight(out int width, out int height)
|
||||
{
|
||||
width = dataProvider.textureActualWidth;
|
||||
height = dataProvider.textureActualHeight;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SecondaryTextureDataProvider : PSDDataProvider, ISecondaryTextureDataProvider
|
||||
{
|
||||
public SecondarySpriteTexture[] textures
|
||||
{
|
||||
get { return dataProvider.secondaryTextures; }
|
||||
set { dataProvider.secondaryTextures = value; }
|
||||
}
|
||||
}
|
||||
|
||||
internal class SpriteOutlineDataProvider : PSDDataProvider, ISpriteOutlineDataProvider
|
||||
{
|
||||
public List<Vector2[]> GetOutlines(GUID guid)
|
||||
{
|
||||
var sprite = ((SpriteMetaData)dataProvider.GetSpriteData(guid));
|
||||
Assert.IsNotNull(sprite, string.Format("Sprite not found for GUID:{0}", guid.ToString()));
|
||||
|
||||
var outline = sprite.spriteOutline;
|
||||
if (outline != null)
|
||||
return outline.Select(x => x.outline).ToList();
|
||||
return new List<Vector2[]>();
|
||||
}
|
||||
|
||||
public void SetOutlines(GUID guid, List<Vector2[]> data)
|
||||
{
|
||||
var sprite = dataProvider.GetSpriteDataFromAllMode(guid);
|
||||
if (sprite != null)
|
||||
((SpriteMetaData)sprite).spriteOutline = data.Select(x => new SpriteOutline() {outline = x}).ToList();
|
||||
}
|
||||
|
||||
public float GetTessellationDetail(GUID guid)
|
||||
{
|
||||
return ((SpriteMetaData)dataProvider.GetSpriteData(guid)).tessellationDetail;
|
||||
}
|
||||
|
||||
public void SetTessellationDetail(GUID guid, float value)
|
||||
{
|
||||
var sprite = dataProvider.GetSpriteDataFromAllMode(guid);
|
||||
if (sprite != null)
|
||||
((SpriteMetaData)sprite).tessellationDetail = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SpritePhysicsOutlineProvider : PSDDataProvider, ISpritePhysicsOutlineDataProvider
|
||||
{
|
||||
public List<Vector2[]> GetOutlines(GUID guid)
|
||||
{
|
||||
var sprite = ((SpriteMetaData)dataProvider.GetSpriteData(guid));
|
||||
Assert.IsNotNull(sprite, string.Format("Sprite not found for GUID:{0}", guid.ToString()));
|
||||
var outline = sprite.spritePhysicsOutline;
|
||||
if (outline != null)
|
||||
return outline.Select(x => x.outline).ToList();
|
||||
|
||||
return new List<Vector2[]>();
|
||||
}
|
||||
|
||||
public void SetOutlines(GUID guid, List<Vector2[]> data)
|
||||
{
|
||||
var sprite = dataProvider.GetSpriteDataFromAllMode(guid);
|
||||
if (sprite != null)
|
||||
((SpriteMetaData)sprite).spritePhysicsOutline = data.Select(x => new SpriteOutline() { outline = x }).ToList();
|
||||
}
|
||||
|
||||
public float GetTessellationDetail(GUID guid)
|
||||
{
|
||||
return ((SpriteMetaData)dataProvider.GetSpriteData(guid)).tessellationDetail;
|
||||
}
|
||||
|
||||
public void SetTessellationDetail(GUID guid, float value)
|
||||
{
|
||||
var sprite = dataProvider.GetSpriteDataFromAllMode(guid);
|
||||
if (sprite != null)
|
||||
((SpriteMetaData)sprite).tessellationDetail = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SpriteMeshDataProvider : PSDDataProvider, ISpriteMeshDataProvider
|
||||
{
|
||||
public Vertex2DMetaData[] GetVertices(GUID guid)
|
||||
{
|
||||
var sprite = ((SpriteMetaData)dataProvider.GetSpriteData(guid));
|
||||
Assert.IsNotNull(sprite, string.Format("Sprite not found for GUID:{0}", guid.ToString()));
|
||||
var v = sprite.vertices;
|
||||
if (v != null)
|
||||
return v.ToArray();
|
||||
|
||||
return new Vertex2DMetaData[0];
|
||||
}
|
||||
|
||||
public void SetVertices(GUID guid, Vertex2DMetaData[] vertices)
|
||||
{
|
||||
var sprite = dataProvider.GetSpriteDataFromAllMode(guid);
|
||||
if (sprite != null)
|
||||
((SpriteMetaData)sprite).vertices = vertices.ToList();
|
||||
}
|
||||
|
||||
public int[] GetIndices(GUID guid)
|
||||
{
|
||||
var sprite = ((SpriteMetaData)dataProvider.GetSpriteData(guid));
|
||||
Assert.IsNotNull(sprite, string.Format("Sprite not found for GUID:{0}", guid.ToString()));
|
||||
var v = sprite.indices;
|
||||
if (v != null)
|
||||
return v;
|
||||
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
public void SetIndices(GUID guid, int[] indices)
|
||||
{
|
||||
var sprite = dataProvider.GetSpriteDataFromAllMode(guid);
|
||||
if (sprite != null)
|
||||
((SpriteMetaData)sprite).indices = indices;
|
||||
}
|
||||
|
||||
public Vector2Int[] GetEdges(GUID guid)
|
||||
{
|
||||
var sprite = ((SpriteMetaData)dataProvider.GetSpriteData(guid));
|
||||
Assert.IsNotNull(sprite, string.Format("Sprite not found for GUID:{0}", guid.ToString()));
|
||||
var v = sprite.edges;
|
||||
if (v != null)
|
||||
return v;
|
||||
|
||||
return new Vector2Int[0];
|
||||
}
|
||||
|
||||
public void SetEdges(GUID guid, Vector2Int[] edges)
|
||||
{
|
||||
var sprite = dataProvider.GetSpriteDataFromAllMode(guid);
|
||||
if (sprite != null)
|
||||
((SpriteMetaData)sprite).edges = edges;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class CharacterDataProvider : PSDDataProvider, ICharacterDataProvider
|
||||
{
|
||||
int ParentGroupInFlatten(int parentIndex, List<PSDLayer> psdLayers)
|
||||
{
|
||||
int group = -1;
|
||||
for (int i = 0; i <= parentIndex; ++i)
|
||||
if (psdLayers[i].isGroup)
|
||||
++group;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
public CharacterData GetCharacterData()
|
||||
{
|
||||
var psdLayers = dataProvider.GetPSDLayers();
|
||||
var groups = new List<CharacterGroup>();
|
||||
for (int i = 0; i < psdLayers.Count; ++i)
|
||||
{
|
||||
if (psdLayers[i].isGroup)
|
||||
{
|
||||
groups.Add(new CharacterGroup()
|
||||
{
|
||||
name = psdLayers[i].name,
|
||||
parentGroup = ParentGroupInFlatten(psdLayers[i].parentIndex, psdLayers),
|
||||
order = i
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var cd = dataProvider.characterData;
|
||||
|
||||
var parts = cd.parts == null ? new List<CharacterPart>() : cd.parts.ToList();
|
||||
var spriteRects = dataProvider.GetSpriteMetaData();
|
||||
parts.RemoveAll(x => Array.FindIndex(spriteRects, y => y.spriteID == new GUID(x.spriteId)) == -1);
|
||||
foreach (var spriteMetaData in spriteRects)
|
||||
{
|
||||
var srIndex = parts.FindIndex(x => new GUID(x.spriteId) == spriteMetaData.spriteID);
|
||||
CharacterPart cp = srIndex == -1 ? new CharacterPart() : parts[srIndex];
|
||||
cp.spriteId = spriteMetaData.spriteID.ToString();
|
||||
cp.order = psdLayers.FindIndex(l => l.spriteID == spriteMetaData.spriteID);
|
||||
cp.spritePosition = new RectInt();
|
||||
var uvTransform = spriteMetaData.uvTransform;
|
||||
var outlineOffset = new Vector2(spriteMetaData.rect.x - uvTransform.x, spriteMetaData.rect.y - uvTransform.y);
|
||||
cp.spritePosition.position = new Vector2Int((int)outlineOffset.x, (int)outlineOffset.y);
|
||||
cp.spritePosition.size = new Vector2Int((int)spriteMetaData.rect.width, (int)spriteMetaData.rect.height);
|
||||
cp.parentGroup = -1;
|
||||
//Find group
|
||||
var spritePSDLayer = psdLayers.FirstOrDefault(x => x.spriteID == spriteMetaData.spriteID);
|
||||
if (spritePSDLayer != null)
|
||||
{
|
||||
cp.parentGroup = ParentGroupInFlatten(spritePSDLayer.parentIndex, psdLayers);
|
||||
}
|
||||
|
||||
|
||||
if (srIndex == -1)
|
||||
parts.Add(cp);
|
||||
else
|
||||
parts[srIndex] = cp;
|
||||
}
|
||||
|
||||
parts.Sort((x, y) =>
|
||||
{
|
||||
return x.order.CompareTo(y.order);
|
||||
});
|
||||
|
||||
parts.Reverse();
|
||||
cd.parts = parts.ToArray();
|
||||
cd.dimension = dataProvider.documentSize;
|
||||
cd.characterGroups = groups.ToArray();
|
||||
return cd;
|
||||
}
|
||||
|
||||
public void SetCharacterData(CharacterData characterData)
|
||||
{
|
||||
characterData.parts = characterData.parts.Reverse().ToArray();
|
||||
dataProvider.characterData = characterData;
|
||||
}
|
||||
}
|
||||
|
||||
internal class MainSkeletonDataProvider : PSDDataProvider, IMainSkeletonDataProvider
|
||||
{
|
||||
public MainSkeletonData GetMainSkeletonData()
|
||||
{
|
||||
return new MainSkeletonData { bones = dataProvider.mainSkeletonBones };
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
[Serializable]
|
||||
class PSDLayer
|
||||
{
|
||||
[SerializeField]
|
||||
string m_Name;
|
||||
[SerializeField]
|
||||
string m_SpriteName;
|
||||
[SerializeField]
|
||||
bool m_IsGroup;
|
||||
[SerializeField]
|
||||
int m_ParentIndex;
|
||||
[SerializeField]
|
||||
string m_SpriteID;
|
||||
[SerializeField]
|
||||
int m_LayerID;
|
||||
[SerializeField]
|
||||
Vector2Int m_MosaicPosition;
|
||||
|
||||
[NonSerialized]
|
||||
GameObject m_GameObject;
|
||||
|
||||
public PSDLayer(NativeArray<Color32> tex, int parent, bool group, string layerName, int width, int height, int id)
|
||||
{
|
||||
isGroup = group;
|
||||
parentIndex = parent;
|
||||
texture = tex;
|
||||
name = layerName;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
layerID = id;
|
||||
}
|
||||
|
||||
public int layerID { get { return m_LayerID; } private set { m_LayerID = value; } }
|
||||
|
||||
public string name { get { return m_Name; } private set { m_Name = value; } }
|
||||
public string spriteName { get { return m_SpriteName; } set { m_SpriteName = value; } }
|
||||
public bool isGroup { get { return m_IsGroup; } private set { m_IsGroup = value; } }
|
||||
public int parentIndex { get { return m_ParentIndex; } private set { m_ParentIndex = value; } }
|
||||
public Vector2Int mosaicPosition { get { return m_MosaicPosition; } set { m_MosaicPosition = value; } }
|
||||
public GUID spriteID { get { return new GUID(m_SpriteID); } set { m_SpriteID = value.ToString(); } }
|
||||
public GameObject gameObject { get { return m_GameObject; } set { m_GameObject = value; } }
|
||||
|
||||
public NativeArray<Color32> texture { get; set; }
|
||||
public int width { get; private set; }
|
||||
public int height { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (texture.IsCreated)
|
||||
texture.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.2D.PsdImporter.Editor")]
|
@@ -0,0 +1,50 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PDNWrapper
|
||||
{
|
||||
internal static class Layer
|
||||
{
|
||||
public static BitmapLayer CreateBackgroundLayer(int w, int h)
|
||||
{
|
||||
return new BitmapLayer(w, h);
|
||||
}
|
||||
}
|
||||
|
||||
internal class BitmapLayer
|
||||
{
|
||||
int width, height;
|
||||
|
||||
public Rectangle Bounds
|
||||
{
|
||||
get {return new Rectangle(0, 0, width, height); }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Surface.Dispose();
|
||||
foreach (var layer in ChildLayer)
|
||||
layer.Dispose();
|
||||
}
|
||||
|
||||
public BitmapLayer(int w, int h)
|
||||
{
|
||||
Surface = new Surface(w, h);
|
||||
width = w;
|
||||
height = h;
|
||||
ChildLayer = new List<BitmapLayer>();
|
||||
IsGroup = false;
|
||||
}
|
||||
public int LayerID { get; set; }
|
||||
|
||||
public bool IsGroup {get; set; }
|
||||
public BitmapLayer ParentLayer {get; set; }
|
||||
public List<BitmapLayer> ChildLayer { get; set; }
|
||||
public string Name { get; set; }
|
||||
public byte Opacity { get; set; }
|
||||
public bool Visible { get; set; }
|
||||
public LayerBlendMode BlendMode { get; set; }
|
||||
|
||||
public Surface Surface { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PDNWrapper
|
||||
{
|
||||
internal class Document
|
||||
{
|
||||
public int width, height;
|
||||
|
||||
public Document(int w, int h)
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
Layers = new List<BitmapLayer>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var layer in Layers)
|
||||
layer.Dispose();
|
||||
}
|
||||
|
||||
public List<BitmapLayer> Layers { get; set; }
|
||||
|
||||
public MeasurementUnit DpuUnit { get; set; }
|
||||
|
||||
public double DpuX { get; set; }
|
||||
public double DpuY { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
namespace PDNWrapper
|
||||
{
|
||||
internal enum MeasurementUnit
|
||||
{
|
||||
Pixel = 1,
|
||||
Inch = 2,
|
||||
Centimeter = 3
|
||||
}
|
||||
|
||||
internal enum LayerBlendMode
|
||||
{
|
||||
Normal = 0,
|
||||
Multiply = 1,
|
||||
Additive = 2,
|
||||
ColorBurn = 3,
|
||||
ColorDodge = 4,
|
||||
Reflect = 5,
|
||||
Glow = 6,
|
||||
Overlay = 7,
|
||||
Difference = 8,
|
||||
Negation = 9,
|
||||
Lighten = 10,
|
||||
Darken = 11,
|
||||
Screen = 12,
|
||||
Xor = 13
|
||||
}
|
||||
}
|
@@ -0,0 +1,624 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using System;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace PaintDotNet.Data.PhotoshopFileType
|
||||
{
|
||||
|
||||
#region PDNDecodeJob
|
||||
|
||||
internal struct PDNDecoderData
|
||||
{
|
||||
// Inputs.
|
||||
public PDNWrapper.Rectangle Rect;
|
||||
public PDNWrapper.Rectangle LayerRect;
|
||||
public PDNWrapper.Rectangle ClippedRect;
|
||||
public int SurfaceWidth;
|
||||
public int SurfaceHeight;
|
||||
public int SurfaceByteDepth;
|
||||
public DecodeType DecoderType;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
[ReadOnly]
|
||||
public NativeArray<byte> ColorChannel0;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
[ReadOnly]
|
||||
public NativeArray<byte> ColorChannel1;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
[ReadOnly]
|
||||
public NativeArray<byte> ColorChannel2;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
[ReadOnly]
|
||||
public NativeArray<byte> ColorChannel3;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
[ReadOnly]
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<byte> ColorModeData;
|
||||
|
||||
// Outputs
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<Color32> DecodedImage;
|
||||
|
||||
}
|
||||
|
||||
internal struct PDNDecoderJob : IJobParallelFor
|
||||
{
|
||||
|
||||
public PDNDecoderData Data;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
|
||||
int idx = Data.Rect.Top + index;
|
||||
{
|
||||
// Calculate index into ImageData source from row and column.
|
||||
int idxSrcPixel = (idx - Data.LayerRect.Top) * Data.LayerRect.Width + (Data.Rect.Left - Data.LayerRect.Left);
|
||||
int idxSrcBytes = idxSrcPixel * Data.SurfaceByteDepth;
|
||||
|
||||
// Calculate pointers to destination Surface.
|
||||
var idxDstStart = idx * Data.SurfaceWidth + Data.ClippedRect.Left;
|
||||
var idxDstStops = idx * Data.SurfaceWidth + Data.ClippedRect.Right;
|
||||
|
||||
// For 16-bit images, take the higher-order byte from the image data, which is now in little-endian order.
|
||||
if (Data.SurfaceByteDepth == 2)
|
||||
{
|
||||
idxSrcBytes++;
|
||||
}
|
||||
|
||||
switch (Data.DecoderType)
|
||||
{
|
||||
case DecodeType.RGB32:
|
||||
{
|
||||
SetPDNRowRgb32(idxDstStart, idxDstStops, idxSrcBytes);
|
||||
}
|
||||
break;
|
||||
case DecodeType.Grayscale32:
|
||||
{
|
||||
SetPDNRowGrayscale32(idxDstStart, idxDstStops, idxSrcBytes);
|
||||
}
|
||||
break;
|
||||
case DecodeType.RGB:
|
||||
{
|
||||
SetPDNRowRgb(idxDstStart, idxDstStops, idxSrcBytes);
|
||||
}
|
||||
break;
|
||||
case DecodeType.CMYK:
|
||||
{
|
||||
SetPDNRowCmyk(idxDstStart, idxDstStops, idxSrcBytes);
|
||||
}
|
||||
break;
|
||||
case DecodeType.Bitmap:
|
||||
{
|
||||
SetPDNRowBitmap(idxDstStart, idxDstStops, idxSrcBytes);
|
||||
}
|
||||
break;
|
||||
case DecodeType.Grayscale:
|
||||
{
|
||||
SetPDNRowGrayscale(idxDstStart, idxDstStops, idxSrcBytes);
|
||||
}
|
||||
break;
|
||||
case DecodeType.Indexed:
|
||||
{
|
||||
SetPDNRowIndexed(idxDstStart, idxDstStops, idxSrcBytes);
|
||||
}
|
||||
break;
|
||||
case DecodeType.Lab:
|
||||
{
|
||||
SetPDNRowLab(idxDstStart, idxDstStops, idxSrcBytes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Case 0:
|
||||
private void SetPDNRowRgb32(int dstStart, int dstStops, int idxSrc)
|
||||
{
|
||||
NativeArray<float> cR = Data.ColorChannel0.Reinterpret<float>(1);
|
||||
NativeArray<float> cG = Data.ColorChannel1.Reinterpret<float>(1);
|
||||
NativeArray<float> cB = Data.ColorChannel2.Reinterpret<float>(1);
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
c.r = ImageDecoderPdn.RGBByteFromHDRFloat(cR[idxSrc / 4]);
|
||||
c.g = ImageDecoderPdn.RGBByteFromHDRFloat(cG[idxSrc / 4]);
|
||||
c.b = ImageDecoderPdn.RGBByteFromHDRFloat(cB[idxSrc / 4]);
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
idxSrc += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 1:
|
||||
private void SetPDNRowGrayscale32(int dstStart, int dstStops, int idxSrc)
|
||||
{
|
||||
NativeArray<float> channel = Data.ColorChannel0.Reinterpret<float>(1);
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
byte rgbValue = ImageDecoderPdn.RGBByteFromHDRFloat(channel[idxSrc / 4]);
|
||||
c.r = rgbValue;
|
||||
c.g = rgbValue;
|
||||
c.b = rgbValue;
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
idxSrc += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2:
|
||||
private void SetPDNRowRgb(int dstStart, int dstStops, int idxSrc)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
c.r = Data.ColorChannel0[idxSrc];
|
||||
c.g = Data.ColorChannel1[idxSrc];
|
||||
c.b = Data.ColorChannel2[idxSrc];
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
idxSrc += Data.SurfaceByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 3:
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// The color-conversion formulas come from the Colour Space Conversions FAQ:
|
||||
// http://www.poynton.com/PDFs/coloureq.pdf
|
||||
//
|
||||
// RGB --> CMYK CMYK --> RGB
|
||||
// --------------------------------------- --------------------------------------------
|
||||
// Black = minimum(1-Red,1-Green,1-Blue) Red = 1-minimum(1,Cyan*(1-Black)+Black)
|
||||
// Cyan = (1-Red-Black)/(1-Black) Green = 1-minimum(1,Magenta*(1-Black)+Black)
|
||||
// Magenta = (1-Green-Black)/(1-Black) Blue = 1-minimum(1,Yellow*(1-Black)+Black)
|
||||
// Yellow = (1-Blue-Black)/(1-Black)
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
private void SetPDNRowCmyk(int dstStart, int dstStops, int idxSrc)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
// CMYK values are stored as complements, presumably to allow for some
|
||||
// measure of compatibility with RGB-only applications.
|
||||
var C = 255 - Data.ColorChannel0[idxSrc];
|
||||
var M = 255 - Data.ColorChannel1[idxSrc];
|
||||
var Y = 255 - Data.ColorChannel2[idxSrc];
|
||||
var K = 255 - Data.ColorChannel3[idxSrc];
|
||||
|
||||
int R = 255 - Math.Min(255, C * (255 - K) / 255 + K);
|
||||
int G = 255 - Math.Min(255, M * (255 - K) / 255 + K);
|
||||
int B = 255 - Math.Min(255, Y * (255 - K) / 255 + K);
|
||||
|
||||
c.r = (byte)R;
|
||||
c.g = (byte)G;
|
||||
c.b = (byte)B;
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
idxSrc += Data.SurfaceByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 4:
|
||||
private void SetPDNRowBitmap(int dstStart, int dstStops, int idxSrc)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
byte mask = (byte)(0x80 >> (idxSrc % 8));
|
||||
byte bwValue = (byte)(Data.ColorChannel0[idxSrc / 8] & mask);
|
||||
bwValue = (bwValue == 0) ? (byte)255 : (byte)0;
|
||||
|
||||
c.r = bwValue;
|
||||
c.g = bwValue;
|
||||
c.b = bwValue;
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
idxSrc += Data.SurfaceByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 5:
|
||||
private void SetPDNRowGrayscale(int dstStart, int dstStops, int idxSrc)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
|
||||
c.r = Data.ColorChannel0[idxSrc];
|
||||
c.g = Data.ColorChannel0[idxSrc];
|
||||
c.b = Data.ColorChannel0[idxSrc];
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
idxSrc += Data.SurfaceByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 6:
|
||||
private void SetPDNRowIndexed(int dstStart, int dstStops, int idxSrc)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
int index = (int)Data.ColorChannel0[idxSrc];
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
c.r = Data.ColorModeData[index];
|
||||
c.g = Data.ColorModeData[index + 256];
|
||||
c.b = Data.ColorModeData[index + 2 * 256];
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
idxSrc += Data.SurfaceByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 7:
|
||||
private void SetPDNRowLab(int dstStart, int dstStops, int idxSrc)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
double exL, exA, exB;
|
||||
exL = (double)Data.ColorChannel0[idxSrc];
|
||||
exA = (double)Data.ColorChannel1[idxSrc];
|
||||
exB = (double)Data.ColorChannel2[idxSrc];
|
||||
|
||||
int L = (int)(exL / 2.55);
|
||||
int a = (int)(exA - 127.5);
|
||||
int b = (int)(exB - 127.5);
|
||||
|
||||
// First, convert from Lab to XYZ.
|
||||
// Standards used Observer = 2, Illuminant = D65
|
||||
|
||||
const double ref_X = 95.047;
|
||||
const double ref_Y = 100.000;
|
||||
const double ref_Z = 108.883;
|
||||
|
||||
double var_Y = ((double)L + 16.0) / 116.0;
|
||||
double var_X = (double)a / 500.0 + var_Y;
|
||||
double var_Z = var_Y - (double)b / 200.0;
|
||||
|
||||
double var_X3 = var_X * var_X * var_X;
|
||||
double var_Y3 = var_Y * var_Y * var_Y;
|
||||
double var_Z3 = var_Z * var_Z * var_Z;
|
||||
|
||||
if (var_Y3 > 0.008856)
|
||||
var_Y = var_Y3;
|
||||
else
|
||||
var_Y = (var_Y - 16 / 116) / 7.787;
|
||||
|
||||
if (var_X3 > 0.008856)
|
||||
var_X = var_X3;
|
||||
else
|
||||
var_X = (var_X - 16 / 116) / 7.787;
|
||||
|
||||
if (var_Z3 > 0.008856)
|
||||
var_Z = var_Z3;
|
||||
else
|
||||
var_Z = (var_Z - 16 / 116) / 7.787;
|
||||
|
||||
double X = ref_X * var_X;
|
||||
double Y = ref_Y * var_Y;
|
||||
double Z = ref_Z * var_Z;
|
||||
|
||||
// Then, convert from XYZ to RGB.
|
||||
// Standards used Observer = 2, Illuminant = D65
|
||||
// ref_X = 95.047, ref_Y = 100.000, ref_Z = 108.883
|
||||
|
||||
double var_R = X * 0.032406 + Y * (-0.015372) + Z * (-0.004986);
|
||||
double var_G = X * (-0.009689) + Y * 0.018758 + Z * 0.000415;
|
||||
double var_B = X * 0.000557 + Y * (-0.002040) + Z * 0.010570;
|
||||
|
||||
if (var_R > 0.0031308)
|
||||
var_R = 1.055 * (Math.Pow(var_R, 1 / 2.4)) - 0.055;
|
||||
else
|
||||
var_R = 12.92 * var_R;
|
||||
|
||||
if (var_G > 0.0031308)
|
||||
var_G = 1.055 * (Math.Pow(var_G, 1 / 2.4)) - 0.055;
|
||||
else
|
||||
var_G = 12.92 * var_G;
|
||||
|
||||
if (var_B > 0.0031308)
|
||||
var_B = 1.055 * (Math.Pow(var_B, 1 / 2.4)) - 0.055;
|
||||
else
|
||||
var_B = 12.92 * var_B;
|
||||
|
||||
int nRed = (int)(var_R * 256.0);
|
||||
int nGreen = (int)(var_G * 256.0);
|
||||
int nBlue = (int)(var_B * 256.0);
|
||||
|
||||
if (nRed < 0)
|
||||
nRed = 0;
|
||||
else if (nRed > 255)
|
||||
nRed = 255;
|
||||
if (nGreen < 0)
|
||||
nGreen = 0;
|
||||
else if (nGreen > 255)
|
||||
nGreen = 255;
|
||||
if (nBlue < 0)
|
||||
nBlue = 0;
|
||||
else if (nBlue > 255)
|
||||
nBlue = 255;
|
||||
|
||||
c.r = (byte)nRed;
|
||||
c.g = (byte)nGreen;
|
||||
c.b = (byte)nBlue;
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
idxSrc += Data.SurfaceByteDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AlphaDecodeJob
|
||||
|
||||
internal struct PDNAlphaMaskData
|
||||
{
|
||||
// Inputs.
|
||||
public PDNWrapper.Rectangle Rect;
|
||||
public PDNWrapper.Rectangle LayerRect;
|
||||
public PDNWrapper.Rectangle ClippedRect;
|
||||
public int SurfaceWidth;
|
||||
public int SurfaceHeight;
|
||||
public int SurfaceByteDepth;
|
||||
|
||||
public int HasAlphaChannel;
|
||||
public int HasUserAlphaMask;
|
||||
public int UserMaskInvertOnBlend;
|
||||
public PDNWrapper.Rectangle UserMaskRect;
|
||||
public PDNWrapper.Rectangle UserMaskContextRect;
|
||||
public int HasLayerAlphaMask;
|
||||
public int LayerMaskInvertOnBlend;
|
||||
public PDNWrapper.Rectangle LayerMaskRect;
|
||||
public PDNWrapper.Rectangle LayerMaskContextRect;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
[ReadOnly]
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<byte> AlphaChannel0;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
[ReadOnly]
|
||||
public NativeArray<byte> UserMask;
|
||||
|
||||
[DeallocateOnJobCompletion]
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<byte> UserAlphaMask;
|
||||
|
||||
[DeallocateOnJobCompletion]
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<byte> UserAlphaMaskEmpty;
|
||||
|
||||
[NativeDisableParallelForRestriction]
|
||||
[ReadOnly]
|
||||
public NativeArray<byte> LayerMask;
|
||||
|
||||
[DeallocateOnJobCompletion]
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<byte> LayerAlphaMask;
|
||||
|
||||
[DeallocateOnJobCompletion]
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<byte> LayerAlphaMaskEmpty;
|
||||
|
||||
// Outputs
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<Color32> DecodedImage;
|
||||
|
||||
// Colors.
|
||||
public byte UserMaskBackgroundColor;
|
||||
public byte LayerMaskBackgroundColor;
|
||||
}
|
||||
|
||||
internal struct PDNAlphaMaskJob : IJob
|
||||
{
|
||||
|
||||
public PDNAlphaMaskData Data;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
|
||||
for (int idx = Data.Rect.Top; idx < Data.Rect.Bottom; idx++)
|
||||
{
|
||||
// Calculate index into ImageData source from row and column.
|
||||
int idxSrcPixel = (idx - Data.LayerRect.Top) * Data.LayerRect.Width + (Data.Rect.Left - Data.LayerRect.Left);
|
||||
int idxSrcBytes = idxSrcPixel * Data.SurfaceByteDepth;
|
||||
|
||||
// Calculate pointers to destination Surface.
|
||||
var idxDstStart = idx * Data.SurfaceWidth + Data.ClippedRect.Left;
|
||||
var idxDstStops = idx * Data.SurfaceWidth + Data.ClippedRect.Right;
|
||||
|
||||
// For 16-bit images, take the higher-order byte from the image data, which is now in little-endian order.
|
||||
if (Data.SurfaceByteDepth == 2)
|
||||
{
|
||||
idxSrcBytes++;
|
||||
}
|
||||
|
||||
SetPDNAlphaRow(idxDstStart, idxDstStops, idxSrcBytes);
|
||||
if (0 != Data.HasLayerAlphaMask)
|
||||
{
|
||||
GetMaskAlphaRow(idx, Data.LayerAlphaMask, Data.LayerAlphaMaskEmpty, Data.LayerMask, Data.LayerMaskInvertOnBlend, Data.LayerMaskBackgroundColor, Data.LayerMaskContextRect, Data.LayerMaskRect);
|
||||
}
|
||||
if (0 != Data.HasUserAlphaMask)
|
||||
{
|
||||
GetMaskAlphaRow(idx, Data.UserAlphaMask, Data.UserAlphaMaskEmpty, Data.UserMask, Data.UserMaskInvertOnBlend, Data.UserMaskBackgroundColor, Data.UserMaskContextRect, Data.UserMaskRect);
|
||||
}
|
||||
ApplyPDNMask(idxDstStart, idxDstStops);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPDNAlphaRow(int dstStart, int dstStops, int idxSrc)
|
||||
{
|
||||
// Set alpha to fully-opaque if there is no alpha channel
|
||||
if (0 == Data.HasAlphaChannel)
|
||||
{
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
c.a = 255;
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
dstStart++;
|
||||
}
|
||||
}
|
||||
// Set the alpha channel data
|
||||
else
|
||||
{
|
||||
NativeArray<float> srcAlphaChannel = Data.AlphaChannel0.Reinterpret<float>(1);
|
||||
{
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
c.a = (Data.SurfaceByteDepth < 4) ? Data.AlphaChannel0[idxSrc] : ImageDecoderPdn.RGBByteFromHDRFloat(srcAlphaChannel[idxSrc / 4]);
|
||||
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
dstStart++;
|
||||
idxSrc += Data.SurfaceByteDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyPDNMask(int dstStart, int dstStops)
|
||||
{
|
||||
// Do nothing if there are no masks
|
||||
if (0 == Data.HasLayerAlphaMask && 0 == Data.HasUserAlphaMask)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply one mask
|
||||
else if (0 == Data.HasLayerAlphaMask || 0 == Data.HasUserAlphaMask)
|
||||
{
|
||||
var maskAlpha = (0 == Data.HasLayerAlphaMask) ? Data.UserAlphaMask : Data.LayerAlphaMask;
|
||||
var maskStart = 0;
|
||||
{
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
c.a = (byte)(Data.DecodedImage[dstStart].a * maskAlpha[maskStart] / 255);
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
maskStart++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Apply both masks in one pass, to minimize rounding error
|
||||
else
|
||||
{
|
||||
var maskStart = 0;
|
||||
{
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
var c = Data.DecodedImage[dstStart];
|
||||
var alphaFactor = (Data.LayerAlphaMask[maskStart]) * (Data.UserAlphaMask[maskStart]);
|
||||
c.a = (byte)(Data.DecodedImage[dstStart].a * alphaFactor / 65025);
|
||||
Data.DecodedImage[dstStart] = c;
|
||||
|
||||
dstStart++;
|
||||
maskStart++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DecodeMaskAlphaRow32(NativeArray<byte> Alpha, int dstStart, int dstStops, NativeArray<byte> Mask, int maskStart)
|
||||
{
|
||||
NativeArray<float> floatArray = Mask.Reinterpret<float>(1);
|
||||
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
Alpha[dstStart] = ImageDecoderPdn.RGBByteFromHDRFloat(floatArray[maskStart / 4]);
|
||||
|
||||
dstStart++;
|
||||
maskStart += 4;
|
||||
}
|
||||
}
|
||||
|
||||
private void DecodeMaskAlphaRow(NativeArray<byte> Alpha, int dstStart, int dstStops, NativeArray<byte> Mask, int maskStart, int byteDepth)
|
||||
{
|
||||
while (dstStart < dstStops)
|
||||
{
|
||||
Alpha[dstStart] = Mask[maskStart];
|
||||
|
||||
dstStart++;
|
||||
maskStart += byteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void GetMaskAlphaRow(int idxSrc, NativeArray<byte> alphaBuffer, NativeArray<byte> alphaBufferEmpty, NativeArray<byte> maskChannel, int MaskInvertOnBlend, byte MaskBackgroundColor, PDNWrapper.Rectangle MaskContextRect, PDNWrapper.Rectangle MaskRect)
|
||||
{
|
||||
//////////////////////////////////////
|
||||
// Transfer mask into the alpha array
|
||||
// Background color for areas not covered by the mask
|
||||
byte backgroundColor = (0 != MaskInvertOnBlend) ? (byte)(255 - MaskBackgroundColor) : MaskBackgroundColor;
|
||||
{
|
||||
var alphaBufferPtr = NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(alphaBuffer);
|
||||
UnsafeUtility.MemSet(alphaBufferPtr, backgroundColor, alphaBuffer.Length);
|
||||
}
|
||||
// Only process if not Empty.
|
||||
if (alphaBufferEmpty[idxSrc] == 0)
|
||||
{
|
||||
// Get pointers to starting positions
|
||||
int alphaColumn = MaskContextRect.X;
|
||||
// It's possible that the layer's rect is larger than the clip and it's offset.
|
||||
// Since we only copy out the alpha based on the MaskContext size
|
||||
// The copy will start from where the MaskContextRect is
|
||||
if(Data.LayerRect.X > 0)
|
||||
alphaColumn = MaskContextRect.X - Data.LayerRect.X;
|
||||
var pAlpha = alphaColumn;
|
||||
var pAlphaEnd = pAlpha + MaskContextRect.Width;
|
||||
|
||||
int maskRow = idxSrc - MaskRect.Y;
|
||||
int maskColumn = MaskContextRect.X - MaskRect.X;
|
||||
int idxMaskPixel = (maskRow * MaskRect.Width) + maskColumn;
|
||||
var pMask = idxMaskPixel * Data.SurfaceByteDepth;
|
||||
|
||||
// Take the high-order byte if values are 16-bit (little-endian)
|
||||
if (Data.SurfaceByteDepth == 2)
|
||||
{
|
||||
pMask++;
|
||||
}
|
||||
|
||||
// Decode mask into the alpha array.
|
||||
if (Data.SurfaceByteDepth == 4)
|
||||
{
|
||||
DecodeMaskAlphaRow32(alphaBuffer, pAlpha, pAlphaEnd, maskChannel, pMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeMaskAlphaRow(alphaBuffer, pAlpha, pAlphaEnd, maskChannel, pMask, Data.SurfaceByteDepth);
|
||||
}
|
||||
|
||||
// Obsolete since Photoshop CS6, but retained for compatibility with older versions. Note that the background has already been inverted.
|
||||
if (0 != MaskInvertOnBlend)
|
||||
{
|
||||
PhotoshopFile.Util.Invert(alphaBuffer, pAlpha, pAlphaEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
|
||||
namespace PDNWrapper
|
||||
{
|
||||
// Mimics System.Drawing.Rectangle
|
||||
internal struct Rectangle
|
||||
{
|
||||
public static readonly Rectangle Empty = new Rectangle();
|
||||
|
||||
private int x;
|
||||
private int y;
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public Rectangle(int x, int y, int width, int height)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public static Rectangle FromLTRB(int left, int top, int right, int bottom)
|
||||
{
|
||||
return new Rectangle(left,
|
||||
top,
|
||||
right - left,
|
||||
bottom - top);
|
||||
}
|
||||
|
||||
public Size Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Size(Width, Height);
|
||||
}
|
||||
set
|
||||
{
|
||||
this.Width = value.Width;
|
||||
this.Height = value.Height;
|
||||
}
|
||||
}
|
||||
|
||||
public int X
|
||||
{
|
||||
get
|
||||
{
|
||||
return x;
|
||||
}
|
||||
set
|
||||
{
|
||||
x = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Y
|
||||
{
|
||||
get
|
||||
{
|
||||
return y;
|
||||
}
|
||||
set
|
||||
{
|
||||
y = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Width
|
||||
{
|
||||
get
|
||||
{
|
||||
return width;
|
||||
}
|
||||
set
|
||||
{
|
||||
width = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Height
|
||||
{
|
||||
get
|
||||
{
|
||||
return height;
|
||||
}
|
||||
set
|
||||
{
|
||||
height = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Left
|
||||
{
|
||||
get
|
||||
{
|
||||
return X;
|
||||
}
|
||||
}
|
||||
|
||||
public int Top
|
||||
{
|
||||
get
|
||||
{
|
||||
return Y;
|
||||
}
|
||||
}
|
||||
|
||||
public int Right
|
||||
{
|
||||
get
|
||||
{
|
||||
return X + Width;
|
||||
}
|
||||
}
|
||||
|
||||
public int Bottom
|
||||
{
|
||||
get
|
||||
{
|
||||
return Y + Height;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return height == 0 && width == 0 && x == 0 && y == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is Rectangle))
|
||||
return false;
|
||||
|
||||
Rectangle comp = (Rectangle)obj;
|
||||
|
||||
return (comp.X == this.X) &&
|
||||
(comp.Y == this.Y) &&
|
||||
(comp.Width == this.Width) &&
|
||||
(comp.Height == this.Height);
|
||||
}
|
||||
|
||||
public static bool operator==(Rectangle left, Rectangle right)
|
||||
{
|
||||
return (left.X == right.X
|
||||
&& left.Y == right.Y
|
||||
&& left.Width == right.Width
|
||||
&& left.Height == right.Height);
|
||||
}
|
||||
|
||||
public static bool operator!=(Rectangle left, Rectangle right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public bool Contains(int x, int y)
|
||||
{
|
||||
return this.X <= x &&
|
||||
x < this.X + this.Width &&
|
||||
this.Y <= y &&
|
||||
y < this.Y + this.Height;
|
||||
}
|
||||
|
||||
public bool Contains(Rectangle rect)
|
||||
{
|
||||
return (this.X <= rect.X) &&
|
||||
((rect.X + rect.Width) <= (this.X + this.Width)) &&
|
||||
(this.Y <= rect.Y) &&
|
||||
((rect.Y + rect.Height) <= (this.Y + this.Height));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (int)((UInt32)X ^
|
||||
(((UInt32)Y << 13) | ((UInt32)Y >> 19)) ^
|
||||
(((UInt32)Width << 26) | ((UInt32)Width >> 6)) ^
|
||||
(((UInt32)Height << 7) | ((UInt32)Height >> 25)));
|
||||
}
|
||||
|
||||
public void Inflate(int width, int height)
|
||||
{
|
||||
this.X -= width;
|
||||
this.Y -= height;
|
||||
this.Width += 2 * width;
|
||||
this.Height += 2 * height;
|
||||
}
|
||||
|
||||
public void Inflate(Size size)
|
||||
{
|
||||
Inflate(size.Width, size.Height);
|
||||
}
|
||||
|
||||
public static Rectangle Inflate(Rectangle rect, int x, int y)
|
||||
{
|
||||
Rectangle r = rect;
|
||||
r.Inflate(x, y);
|
||||
return r;
|
||||
}
|
||||
|
||||
public void Intersect(Rectangle rect)
|
||||
{
|
||||
Rectangle result = Rectangle.Intersect(rect, this);
|
||||
|
||||
this.X = result.X;
|
||||
this.Y = result.Y;
|
||||
this.Width = result.Width;
|
||||
this.Height = result.Height;
|
||||
}
|
||||
|
||||
public static Rectangle Intersect(Rectangle a, Rectangle b)
|
||||
{
|
||||
int x1 = Math.Max(a.X, b.X);
|
||||
int x2 = Math.Min(a.X + a.Width, b.X + b.Width);
|
||||
int y1 = Math.Max(a.Y, b.Y);
|
||||
int y2 = Math.Min(a.Y + a.Height, b.Y + b.Height);
|
||||
|
||||
if (x2 >= x1
|
||||
&& y2 >= y1)
|
||||
{
|
||||
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
|
||||
public bool IntersectsWith(Rectangle rect)
|
||||
{
|
||||
return (rect.X < this.X + this.Width) &&
|
||||
(this.X < (rect.X + rect.Width)) &&
|
||||
(rect.Y < this.Y + this.Height) &&
|
||||
(this.Y < rect.Y + rect.Height);
|
||||
}
|
||||
|
||||
public static Rectangle Union(Rectangle a, Rectangle b)
|
||||
{
|
||||
int x1 = Math.Min(a.X, b.X);
|
||||
int x2 = Math.Max(a.X + a.Width, b.X + b.Width);
|
||||
int y1 = Math.Min(a.Y, b.Y);
|
||||
int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height);
|
||||
|
||||
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
|
||||
public void Offset(int x, int y)
|
||||
{
|
||||
this.X += x;
|
||||
this.Y += y;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{X=" + X.ToString() + ",Y=" + Y.ToString() +
|
||||
",Width=" + Width.ToString() +
|
||||
",Height=" + Height.ToString() + "}";
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
namespace PDNWrapper
|
||||
{
|
||||
// Mimics System.Drawing.Size
|
||||
internal struct Size
|
||||
{
|
||||
public static readonly Size Empty = new Size();
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public Size(int width, int height)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return width == 0 && height == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int Width
|
||||
{
|
||||
get
|
||||
{
|
||||
return width;
|
||||
}
|
||||
set
|
||||
{
|
||||
width = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Height
|
||||
{
|
||||
get
|
||||
{
|
||||
return height;
|
||||
}
|
||||
set
|
||||
{
|
||||
height = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace PDNWrapper
|
||||
{
|
||||
internal class Surface
|
||||
{
|
||||
NativeArray<Color32> m_Color;
|
||||
public Surface(int w, int h)
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
m_Color = new NativeArray<Color32>(width * height, Allocator.Persistent);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_Color.Dispose();
|
||||
}
|
||||
|
||||
public NativeArray<Color32> color
|
||||
{
|
||||
get { return m_Color; }
|
||||
}
|
||||
|
||||
public int width { get; private set; }
|
||||
public int height { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2014 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using PaintDotNet;
|
||||
using PhotoshopFile;
|
||||
using PDNWrapper;
|
||||
|
||||
namespace PaintDotNet.Data.PhotoshopFileType
|
||||
{
|
||||
internal static class BlendModeMapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert between Paint.NET and Photoshop blend modes.
|
||||
/// </summary>
|
||||
public static string ToPsdBlendMode(this LayerBlendMode pdnBlendMode)
|
||||
{
|
||||
switch (pdnBlendMode)
|
||||
{
|
||||
case LayerBlendMode.Normal:
|
||||
return PsdBlendMode.Normal;
|
||||
|
||||
case LayerBlendMode.Multiply:
|
||||
return PsdBlendMode.Multiply;
|
||||
case LayerBlendMode.Additive:
|
||||
return PsdBlendMode.LinearDodge;
|
||||
case LayerBlendMode.ColorBurn:
|
||||
return PsdBlendMode.ColorBurn;
|
||||
case LayerBlendMode.ColorDodge:
|
||||
return PsdBlendMode.ColorDodge;
|
||||
case LayerBlendMode.Overlay:
|
||||
return PsdBlendMode.Overlay;
|
||||
case LayerBlendMode.Difference:
|
||||
return PsdBlendMode.Difference;
|
||||
case LayerBlendMode.Lighten:
|
||||
return PsdBlendMode.Lighten;
|
||||
case LayerBlendMode.Darken:
|
||||
return PsdBlendMode.Darken;
|
||||
case LayerBlendMode.Screen:
|
||||
return PsdBlendMode.Screen;
|
||||
|
||||
// Paint.NET blend modes without a Photoshop equivalent are saved
|
||||
// as Normal.
|
||||
case LayerBlendMode.Glow:
|
||||
case LayerBlendMode.Negation:
|
||||
case LayerBlendMode.Reflect:
|
||||
case LayerBlendMode.Xor:
|
||||
return PsdBlendMode.Normal;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unknown Paint.NET blend mode.");
|
||||
return PsdBlendMode.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a Photoshop blend mode to a Paint.NET BlendOp.
|
||||
/// </summary>
|
||||
public static LayerBlendMode FromPsdBlendMode(string blendModeKey)
|
||||
{
|
||||
switch (blendModeKey)
|
||||
{
|
||||
case PsdBlendMode.Normal:
|
||||
return LayerBlendMode.Normal;
|
||||
|
||||
case PsdBlendMode.Multiply:
|
||||
return LayerBlendMode.Multiply;
|
||||
case PsdBlendMode.LinearDodge:
|
||||
return LayerBlendMode.Additive;
|
||||
case PsdBlendMode.ColorBurn:
|
||||
return LayerBlendMode.ColorBurn;
|
||||
case PsdBlendMode.ColorDodge:
|
||||
return LayerBlendMode.ColorDodge;
|
||||
case PsdBlendMode.Overlay:
|
||||
return LayerBlendMode.Overlay;
|
||||
case PsdBlendMode.Difference:
|
||||
return LayerBlendMode.Difference;
|
||||
case PsdBlendMode.Lighten:
|
||||
return LayerBlendMode.Lighten;
|
||||
case PsdBlendMode.Darken:
|
||||
return LayerBlendMode.Darken;
|
||||
case PsdBlendMode.Screen:
|
||||
return LayerBlendMode.Screen;
|
||||
|
||||
// Photoshop blend modes without a Paint.NET equivalent are loaded
|
||||
// as Normal.
|
||||
default:
|
||||
return LayerBlendMode.Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using PhotoshopFile;
|
||||
|
||||
namespace PaintDotNet.Data.PhotoshopFileType
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the loading of a PSD file into a Paint.NET Document.
|
||||
/// </summary>
|
||||
internal class DocumentLoadContext : LoadContext
|
||||
{
|
||||
public DocumentLoadContext() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnLoadLayersHeader(PsdFile psdFile)
|
||||
{
|
||||
PsdLoad.CheckSufficientMemory(psdFile);
|
||||
}
|
||||
|
||||
public override void OnLoadLayerHeader(PhotoshopFile.Layer layer)
|
||||
{
|
||||
var psdFile = layer.PsdFile;
|
||||
if (psdFile.ColorMode == PsdColorMode.Multichannel)
|
||||
{
|
||||
PsdLoad.CheckSufficientMemory(psdFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,807 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2014 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using PaintDotNet;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using PDNWrapper;
|
||||
using System.Text;
|
||||
|
||||
using PhotoshopFile;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace PaintDotNet.Data.PhotoshopFileType
|
||||
{
|
||||
|
||||
internal enum DecodeType
|
||||
{
|
||||
RGB32 = 0,
|
||||
Grayscale32 = 1,
|
||||
RGB = 2,
|
||||
CMYK = 3,
|
||||
Bitmap = 4,
|
||||
Grayscale = 5,
|
||||
Indexed = 6,
|
||||
Lab = 7
|
||||
};
|
||||
|
||||
internal static class ImageDecoderPdn
|
||||
{
|
||||
private static double rgbExponent = 1 / 2.19921875;
|
||||
|
||||
private class DecodeContext
|
||||
{
|
||||
public PhotoshopFile.Layer Layer { get; private set; }
|
||||
public int ByteDepth { get; private set; }
|
||||
public int HasAlphaChannel { get; private set; }
|
||||
public Channel[] Channels { get; private set; }
|
||||
public NativeArray<byte> AlphaChannel { get; private set; }
|
||||
public PsdColorMode ColorMode { get; private set; }
|
||||
public NativeArray<byte> ColorModeData { get; private set; }
|
||||
|
||||
public Rectangle Rectangle { get; private set; }
|
||||
public MaskDecodeContext LayerMaskContext { get; private set; }
|
||||
public MaskDecodeContext UserMaskContext { get; private set; }
|
||||
|
||||
public DecodeContext(PhotoshopFile.Layer layer, Rectangle bounds)
|
||||
{
|
||||
Layer = layer;
|
||||
ByteDepth = Util.BytesFromBitDepth(layer.PsdFile.BitDepth);
|
||||
HasAlphaChannel = 0;
|
||||
Channels = layer.Channels.ToIdArray();
|
||||
|
||||
var alphaSize = 4;
|
||||
if (layer.AlphaChannel != null && layer.AlphaChannel.ImageData.Length > 0)
|
||||
{
|
||||
HasAlphaChannel = 1;
|
||||
alphaSize = layer.AlphaChannel.ImageData.Length;
|
||||
alphaSize = (alphaSize / 4) + (alphaSize % 4 > 0 ? 1 : 0);
|
||||
alphaSize = alphaSize * 4;
|
||||
}
|
||||
AlphaChannel = new NativeArray<byte>(alphaSize, Allocator.TempJob);
|
||||
if (HasAlphaChannel > 0)
|
||||
NativeArray<byte>.Copy(layer.AlphaChannel.ImageData, AlphaChannel, layer.AlphaChannel.ImageData.Length);
|
||||
ColorMode = layer.PsdFile.ColorMode;
|
||||
ColorModeData = new NativeArray<byte>(layer.PsdFile.ColorModeData, Allocator.TempJob);
|
||||
|
||||
// Clip the layer to the specified bounds
|
||||
Rectangle = Layer.Rect.IntersectWith(bounds);
|
||||
|
||||
if (layer.Masks != null)
|
||||
{
|
||||
LayerMaskContext = GetMaskContext(layer.Masks.LayerMask);
|
||||
UserMaskContext = GetMaskContext(layer.Masks.UserMask);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Cleanup()
|
||||
{
|
||||
AlphaChannel.Dispose();
|
||||
ColorModeData.Dispose();
|
||||
}
|
||||
|
||||
private MaskDecodeContext GetMaskContext(Mask mask)
|
||||
{
|
||||
if ((mask == null) || (mask.Disabled))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new MaskDecodeContext(mask, this);
|
||||
}
|
||||
}
|
||||
|
||||
private class MaskDecodeContext
|
||||
{
|
||||
public Mask Mask { get; private set; }
|
||||
public Rectangle Rectangle { get; private set; }
|
||||
public MaskDecodeContext(Mask mask, DecodeContext layerContext)
|
||||
{
|
||||
Mask = mask;
|
||||
|
||||
// The PositionVsLayer flag is documented to indicate a position
|
||||
// relative to the layer, but Photoshop treats the position as
|
||||
// absolute. So that's what we do, too.
|
||||
Rectangle = mask.Rect.IntersectWith(layerContext.Rectangle);
|
||||
}
|
||||
|
||||
public bool IsRowEmpty(int row)
|
||||
{
|
||||
return (Mask.ImageData == null)
|
||||
|| (Mask.ImageData.Length == 0)
|
||||
|| (Rectangle.Size.IsEmpty)
|
||||
|| (row < Rectangle.Top)
|
||||
|| (row >= Rectangle.Bottom);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
internal static byte RGBByteFromHDRFloat(float ptr)
|
||||
{
|
||||
var result = (byte)(255 * Math.Pow(ptr, rgbExponent));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DecodeDelegate GetDecodeDelegate(PsdColorMode psdColorMode, ref DecodeType decoderType)
|
||||
{
|
||||
switch (psdColorMode)
|
||||
{
|
||||
case PsdColorMode.Bitmap:
|
||||
decoderType = DecodeType.Bitmap;
|
||||
return SetPDNRowBitmap;
|
||||
case PsdColorMode.Grayscale:
|
||||
case PsdColorMode.Duotone:
|
||||
decoderType = DecodeType.Grayscale;
|
||||
return SetPDNRowGrayscale;
|
||||
case PsdColorMode.Indexed:
|
||||
decoderType = DecodeType.Indexed;
|
||||
return SetPDNRowIndexed;
|
||||
case PsdColorMode.RGB:
|
||||
decoderType = DecodeType.RGB;
|
||||
return SetPDNRowRgb;
|
||||
case PsdColorMode.CMYK:
|
||||
decoderType = DecodeType.CMYK;
|
||||
return SetPDNRowCmyk;
|
||||
case PsdColorMode.Lab:
|
||||
decoderType = DecodeType.Lab;
|
||||
return SetPDNRowLab;
|
||||
case PsdColorMode.Multichannel:
|
||||
throw new Exception("Cannot decode multichannel.");
|
||||
default:
|
||||
throw new Exception("Unknown color mode.");
|
||||
}
|
||||
}
|
||||
|
||||
private static DecodeDelegate GetDecodeDelegate32(PsdColorMode psdColorMode, ref DecodeType decoderType)
|
||||
{
|
||||
switch (psdColorMode)
|
||||
{
|
||||
case PsdColorMode.Grayscale:
|
||||
decoderType = DecodeType.Grayscale32;
|
||||
return SetPDNRowGrayscale32;
|
||||
case PsdColorMode.RGB:
|
||||
decoderType = DecodeType.RGB32;
|
||||
return SetPDNRowRgb32;
|
||||
default:
|
||||
throw new PsdInvalidException(
|
||||
"32-bit HDR images must be either RGB or grayscale.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode image from Photoshop's channel-separated formats to BGRA.
|
||||
/// </summary>
|
||||
public static JobHandle DecodeImage(BitmapLayer pdnLayer, PhotoshopFile.Layer psdLayer, JobHandle inputDeps)
|
||||
{
|
||||
UnityEngine.Profiling.Profiler.BeginSample("DecodeImage");
|
||||
var decodeContext = new DecodeContext(psdLayer, pdnLayer.Bounds);
|
||||
DecodeDelegate decoder = null;
|
||||
DecodeType decoderType = 0;
|
||||
|
||||
if (decodeContext.ByteDepth == 4)
|
||||
decoder = GetDecodeDelegate32(decodeContext.ColorMode, ref decoderType);
|
||||
else
|
||||
decoder = GetDecodeDelegate(decodeContext.ColorMode, ref decoderType);
|
||||
|
||||
JobHandle jobHandle = DecodeImage(pdnLayer, decodeContext, decoderType, inputDeps);
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
return jobHandle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode image from Photoshop's channel-separated formats to BGRA,
|
||||
/// using the specified decode delegate on each row.
|
||||
/// </summary>
|
||||
private static JobHandle DecodeImage(BitmapLayer pdnLayer, DecodeContext decodeContext, DecodeType decoderType, JobHandle inputDeps)
|
||||
{
|
||||
|
||||
var psdLayer = decodeContext.Layer;
|
||||
var surface = pdnLayer.Surface;
|
||||
var rect = decodeContext.Rectangle;
|
||||
|
||||
// Convert rows from the Photoshop representation, writing the
|
||||
// resulting ARGB values to to the Paint.NET Surface.
|
||||
|
||||
int jobCount = Unity.Jobs.LowLevel.Unsafe.JobsUtility.JobWorkerMaximumCount;
|
||||
int execCount = (rect.Bottom - rect.Top);
|
||||
int sliceCount = execCount / jobCount;
|
||||
PDNDecoderJob decoderJob = new PDNDecoderJob();
|
||||
|
||||
decoderJob.Data.Rect = rect;
|
||||
decoderJob.Data.LayerRect = psdLayer.Rect;
|
||||
decoderJob.Data.ClippedRect = rect;
|
||||
decoderJob.Data.SurfaceWidth = surface.width;
|
||||
decoderJob.Data.SurfaceHeight = surface.height;
|
||||
decoderJob.Data.SurfaceByteDepth = decodeContext.ByteDepth;
|
||||
decoderJob.Data.DecoderType = decoderType;
|
||||
|
||||
decoderJob.Data.ColorChannel0 = decodeContext.Channels[0].ImageData;
|
||||
decoderJob.Data.ColorChannel1 = decodeContext.Channels.Length > 1 ? decodeContext.Channels[1].ImageData : decodeContext.Channels[0].ImageData;
|
||||
decoderJob.Data.ColorChannel2 = decodeContext.Channels.Length > 2 ? decodeContext.Channels[2].ImageData : decodeContext.Channels[0].ImageData;
|
||||
decoderJob.Data.ColorChannel3 = decodeContext.Channels.Length > 3 ? decodeContext.Channels[3].ImageData : decodeContext.Channels[0].ImageData;
|
||||
decoderJob.Data.ColorModeData = decodeContext.ColorModeData;
|
||||
decoderJob.Data.DecodedImage = surface.color;
|
||||
|
||||
// Schedule the job, returns the JobHandle which can be waited upon later on
|
||||
JobHandle jobHandle = decoderJob.Schedule(execCount, sliceCount, inputDeps);
|
||||
|
||||
// Mask and Alpha.
|
||||
int userMaskContextSize = decodeContext.UserMaskContext != null ? decodeContext.Rectangle.Width : 1;
|
||||
int layerMaskContextSize = decodeContext.LayerMaskContext != null ? decodeContext.Rectangle.Width : 1;
|
||||
var userAlphaMask = new NativeArray<byte>(userMaskContextSize, Allocator.TempJob);
|
||||
var layerAlphaMask = new NativeArray<byte>(layerMaskContextSize, Allocator.TempJob);
|
||||
var userAlphaMaskEmpty = new NativeArray<byte>(rect.Bottom, Allocator.TempJob);
|
||||
var layerAlphaMaskEmpty = new NativeArray<byte>(rect.Bottom, Allocator.TempJob);
|
||||
|
||||
PDNAlphaMaskJob alphaMaskJob = new PDNAlphaMaskJob();
|
||||
|
||||
for (int y = rect.Top; y < rect.Bottom; ++y)
|
||||
{
|
||||
if (decodeContext.UserMaskContext != null)
|
||||
userAlphaMaskEmpty[y] = decodeContext.UserMaskContext.IsRowEmpty(y) ? (byte)1 : (byte)0;
|
||||
if (decodeContext.LayerMaskContext != null)
|
||||
layerAlphaMaskEmpty[y] = decodeContext.LayerMaskContext.IsRowEmpty(y) ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
alphaMaskJob.Data.Rect = rect;
|
||||
alphaMaskJob.Data.LayerRect = psdLayer.Rect;
|
||||
alphaMaskJob.Data.ClippedRect = rect;
|
||||
alphaMaskJob.Data.SurfaceWidth = surface.width;
|
||||
alphaMaskJob.Data.SurfaceHeight = surface.height;
|
||||
alphaMaskJob.Data.SurfaceByteDepth = decodeContext.ByteDepth;
|
||||
alphaMaskJob.Data.HasAlphaChannel = decodeContext.HasAlphaChannel;
|
||||
|
||||
alphaMaskJob.Data.HasUserAlphaMask = decodeContext.UserMaskContext != null ? 1 : 0;
|
||||
alphaMaskJob.Data.UserMaskInvertOnBlend = decodeContext.UserMaskContext != null ? (decodeContext.UserMaskContext.Mask.InvertOnBlend ? 1 : 0) : 0;
|
||||
alphaMaskJob.Data.UserMaskRect = decodeContext.UserMaskContext != null ? decodeContext.UserMaskContext.Mask.Rect : rect;
|
||||
alphaMaskJob.Data.UserMaskContextRect = decodeContext.UserMaskContext != null ? decodeContext.UserMaskContext.Rectangle : rect;
|
||||
alphaMaskJob.Data.HasLayerAlphaMask = decodeContext.LayerMaskContext != null ? 1 : 0;
|
||||
alphaMaskJob.Data.LayerMaskInvertOnBlend = decodeContext.LayerMaskContext != null ? (decodeContext.LayerMaskContext.Mask.InvertOnBlend ? 1 : 0) : 0;
|
||||
alphaMaskJob.Data.LayerMaskRect = decodeContext.LayerMaskContext != null ? decodeContext.LayerMaskContext.Mask.Rect : rect;
|
||||
alphaMaskJob.Data.LayerMaskContextRect = decodeContext.LayerMaskContext != null ? decodeContext.LayerMaskContext.Rectangle : rect;
|
||||
|
||||
alphaMaskJob.Data.AlphaChannel0 = decodeContext.AlphaChannel;
|
||||
alphaMaskJob.Data.UserMask = decodeContext.UserMaskContext != null ? decodeContext.UserMaskContext.Mask.ImageData : decodeContext.AlphaChannel;
|
||||
alphaMaskJob.Data.UserAlphaMask = userAlphaMask;
|
||||
alphaMaskJob.Data.UserAlphaMaskEmpty = userAlphaMaskEmpty;
|
||||
alphaMaskJob.Data.LayerMask = decodeContext.LayerMaskContext != null ? decodeContext.LayerMaskContext.Mask.ImageData : decodeContext.AlphaChannel;
|
||||
alphaMaskJob.Data.LayerAlphaMask = layerAlphaMask;
|
||||
alphaMaskJob.Data.LayerAlphaMaskEmpty = layerAlphaMaskEmpty;
|
||||
alphaMaskJob.Data.DecodedImage = surface.color;
|
||||
alphaMaskJob.Data.UserMaskBackgroundColor = decodeContext.UserMaskContext != null ? decodeContext.UserMaskContext.Mask.BackgroundColor : (byte)0;
|
||||
alphaMaskJob.Data.LayerMaskBackgroundColor = decodeContext.LayerMaskContext != null ? decodeContext.LayerMaskContext.Mask.BackgroundColor : (byte)0;
|
||||
|
||||
jobHandle = alphaMaskJob.Schedule(jobHandle);
|
||||
return jobHandle;
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
/// SINGLE THREADED - KEPT FOR REFERENCE
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Decode image from Photoshop's channel-separated formats to BGRA.
|
||||
/// </summary>
|
||||
public static void DecodeImage(BitmapLayer pdnLayer, PhotoshopFile.Layer psdLayer)
|
||||
{
|
||||
UnityEngine.Profiling.Profiler.BeginSample("DecodeImage");
|
||||
var decodeContext = new DecodeContext(psdLayer, pdnLayer.Bounds);
|
||||
DecodeDelegate decoder = null;
|
||||
DecodeType decoderType = 0;
|
||||
|
||||
if (decodeContext.ByteDepth == 4)
|
||||
decoder = GetDecodeDelegate32(decodeContext.ColorMode, ref decoderType);
|
||||
else
|
||||
decoder = GetDecodeDelegate(decodeContext.ColorMode, ref decoderType);
|
||||
|
||||
DecodeImage(pdnLayer, decodeContext, decoder);
|
||||
decodeContext.Cleanup();
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
}
|
||||
|
||||
private delegate void DecodeDelegate(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, DecodeContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Decode image from Photoshop's channel-separated formats to BGRA,
|
||||
/// using the specified decode delegate on each row.
|
||||
/// </summary>
|
||||
private static void DecodeImage(BitmapLayer pdnLayer, DecodeContext decodeContext, DecodeDelegate decoder)
|
||||
{
|
||||
|
||||
var psdLayer = decodeContext.Layer;
|
||||
var surface = pdnLayer.Surface;
|
||||
var rect = decodeContext.Rectangle;
|
||||
|
||||
// Convert rows from the Photoshop representation, writing the
|
||||
// resulting ARGB values to to the Paint.NET Surface.
|
||||
for (int y = rect.Top; y < rect.Bottom; y++)
|
||||
{
|
||||
// Calculate index into ImageData source from row and column.
|
||||
int idxSrcPixel = (y - psdLayer.Rect.Top) * psdLayer.Rect.Width
|
||||
+ (rect.Left - psdLayer.Rect.Left);
|
||||
int idxSrcByte = idxSrcPixel * decodeContext.ByteDepth;
|
||||
|
||||
// Calculate pointers to destination Surface.
|
||||
//var pDestRow = surface.GetRowAddress(y);
|
||||
//var pDestStart = pDestRow + decodeContext.Rectangle.Left;
|
||||
//var pDestEnd = pDestRow + decodeContext.Rectangle.Right;
|
||||
var pDestStart = y * surface.width + decodeContext.Rectangle.Left;
|
||||
var pDestEnd = y * surface.width + decodeContext.Rectangle.Right;
|
||||
|
||||
// For 16-bit images, take the higher-order byte from the image
|
||||
// data, which is now in little-endian order.
|
||||
if (decodeContext.ByteDepth == 2)
|
||||
idxSrcByte++;
|
||||
|
||||
// Decode the color and alpha channels
|
||||
decoder(pDestStart, pDestEnd, surface.width, surface.color, idxSrcByte, decodeContext);
|
||||
}
|
||||
|
||||
// Mask and Alpha.
|
||||
int userMaskContextSize = decodeContext.UserMaskContext != null ? decodeContext.Rectangle.Width : 1;
|
||||
int layerMaskContextSize = decodeContext.LayerMaskContext != null ? decodeContext.Rectangle.Width : 1;
|
||||
var userAlphaMask = new NativeArray<byte>(userMaskContextSize, Allocator.TempJob);
|
||||
var layerAlphaMask = new NativeArray<byte>(layerMaskContextSize, Allocator.TempJob);
|
||||
|
||||
for (int y = rect.Top; y < rect.Bottom; y++)
|
||||
{
|
||||
// Calculate index into ImageData source from row and column.
|
||||
int idxSrcPixel = (y - psdLayer.Rect.Top) * psdLayer.Rect.Width + (rect.Left - psdLayer.Rect.Left);
|
||||
int idxSrcByte = idxSrcPixel * decodeContext.ByteDepth;
|
||||
|
||||
// Calculate pointers to destination Surface.
|
||||
//var pDestRow = surface.GetRowAddress(y);
|
||||
//var pDestStart = pDestRow + decodeContext.Rectangle.Left;
|
||||
//var pDestEnd = pDestRow + decodeContext.Rectangle.Right;
|
||||
var pDestStart = y * surface.width + decodeContext.Rectangle.Left;
|
||||
var pDestEnd = y * surface.width + decodeContext.Rectangle.Right;
|
||||
|
||||
// For 16-bit images, take the higher-order byte from the image
|
||||
// data, which is now in little-endian order.
|
||||
if (decodeContext.ByteDepth == 2)
|
||||
idxSrcByte++;
|
||||
|
||||
// Decode the color and alpha channels
|
||||
SetPDNAlphaRow(pDestStart, pDestEnd, surface.width, surface.color, idxSrcByte, decodeContext.ByteDepth, decodeContext.HasAlphaChannel, decodeContext.AlphaChannel);
|
||||
// Apply layer masks(s) to the alpha channel
|
||||
GetMaskAlphaRow(y, decodeContext, decodeContext.LayerMaskContext, ref layerAlphaMask);
|
||||
GetMaskAlphaRow(y, decodeContext, decodeContext.UserMaskContext, ref userAlphaMask);
|
||||
ApplyPDNMask(pDestStart, pDestEnd, surface.width, surface.color, layerAlphaMask, userAlphaMask);
|
||||
}
|
||||
userAlphaMask.Dispose();
|
||||
layerAlphaMask.Dispose();
|
||||
}
|
||||
|
||||
private static unsafe void GetMaskAlphaRow(int y, DecodeContext layerContext, MaskDecodeContext maskContext, ref NativeArray<byte> alphaBuffer)
|
||||
{
|
||||
if (maskContext == null)
|
||||
return;
|
||||
var mask = maskContext.Mask;
|
||||
|
||||
// Background color for areas not covered by the mask
|
||||
byte backgroundColor = mask.InvertOnBlend
|
||||
? (byte)(255 - mask.BackgroundColor)
|
||||
: mask.BackgroundColor;
|
||||
{
|
||||
var alphaBufferPtr = NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(alphaBuffer);
|
||||
UnsafeUtility.MemSet(alphaBufferPtr, backgroundColor, alphaBuffer.Length);
|
||||
}
|
||||
if (maskContext.IsRowEmpty(y))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// Transfer mask into the alpha array
|
||||
var pMaskData = mask.ImageData;
|
||||
{
|
||||
// Get pointers to starting positions
|
||||
int alphaColumn = maskContext.Rectangle.X - layerContext.Rectangle.X;
|
||||
var pAlpha = alphaColumn;
|
||||
var pAlphaEnd = pAlpha + maskContext.Rectangle.Width;
|
||||
|
||||
int maskRow = y - mask.Rect.Y;
|
||||
int maskColumn = maskContext.Rectangle.X - mask.Rect.X;
|
||||
int idxMaskPixel = (maskRow * mask.Rect.Width) + maskColumn;
|
||||
var pMask = idxMaskPixel * layerContext.ByteDepth;
|
||||
|
||||
// Take the high-order byte if values are 16-bit (little-endian)
|
||||
if (layerContext.ByteDepth == 2)
|
||||
pMask++;
|
||||
|
||||
// Decode mask into the alpha array.
|
||||
if (layerContext.ByteDepth == 4)
|
||||
{
|
||||
DecodeMaskAlphaRow32(alphaBuffer, pAlpha, pAlphaEnd, pMaskData, pMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeMaskAlphaRow(alphaBuffer, pAlpha, pAlphaEnd, pMaskData, pMask, layerContext.ByteDepth);
|
||||
}
|
||||
|
||||
// Obsolete since Photoshop CS6, but retained for compatibility with
|
||||
// older versions. Note that the background has already been inverted.
|
||||
if (mask.InvertOnBlend)
|
||||
{
|
||||
Util.Invert(alphaBuffer, pAlpha, pAlphaEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPDNAlphaRow(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, int byteDepth, int hasAlphaChannel, NativeArray<byte> alphaChannel)
|
||||
{
|
||||
// Set alpha to fully-opaque if there is no alpha channel
|
||||
if (0 == hasAlphaChannel)
|
||||
{
|
||||
var pDest = pDestStart;
|
||||
while (pDest < pDestEnd)
|
||||
{
|
||||
var c = color[pDest];
|
||||
c.a = 255;
|
||||
color[pDest] = c;
|
||||
pDest++;
|
||||
}
|
||||
}
|
||||
// Set the alpha channel data
|
||||
else
|
||||
{
|
||||
NativeArray<float> srcAlphaChannel = alphaChannel.Reinterpret<float>(1);
|
||||
{
|
||||
var pDest = pDestStart;
|
||||
while (pDest < pDestEnd)
|
||||
{
|
||||
var c = color[pDest];
|
||||
c.a = (byteDepth < 4) ? alphaChannel[idxSrc] : RGBByteFromHDRFloat(srcAlphaChannel[idxSrc / 4]);
|
||||
|
||||
color[pDest] = c;
|
||||
pDest++;
|
||||
idxSrc += byteDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeMaskAlphaRow32(NativeArray<byte> pAlpha, int pAlphaStart, int pAlphaEnd, NativeArray<byte> pMask, int pMaskStart)
|
||||
{
|
||||
|
||||
NativeArray<float> floatArray = pMask.Reinterpret<float>(1);
|
||||
|
||||
while (pAlphaStart < pAlphaEnd)
|
||||
{
|
||||
pAlpha[pAlphaStart] = RGBByteFromHDRFloat(floatArray[pMaskStart * 4]);
|
||||
|
||||
pAlphaStart++;
|
||||
pMaskStart += 4;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeMaskAlphaRow(NativeArray<byte> pAlpha, int pAlphaStart, int pAlphaEnd, NativeArray<byte> pMask, int pMaskStart, int byteDepth)
|
||||
{
|
||||
while (pAlphaStart < pAlphaEnd)
|
||||
{
|
||||
pAlpha[pAlphaStart] = pMask[pMaskStart];
|
||||
|
||||
pAlphaStart++;
|
||||
pMaskStart += byteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyPDNMask(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, NativeArray<byte> layerMaskAlpha, NativeArray<byte> userMaskAlpha)
|
||||
{
|
||||
// Do nothing if there are no masks
|
||||
if ((layerMaskAlpha.Length <= 1) && (userMaskAlpha.Length <= 1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Apply one mask
|
||||
else if ((layerMaskAlpha.Length <= 1) || (userMaskAlpha.Length <= 1))
|
||||
{
|
||||
var maskAlpha = (layerMaskAlpha.Length <= 1) ? userMaskAlpha : layerMaskAlpha;
|
||||
var maskStart = 0;
|
||||
{
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
var c = color[pDestStart];
|
||||
c.a = (byte)(color[pDestStart].a * maskAlpha[maskStart] / 255);
|
||||
color[pDestStart] = c;
|
||||
pDestStart++;
|
||||
maskStart++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Apply both masks in one pass, to minimize rounding error
|
||||
else
|
||||
{
|
||||
//fixed (byte* pLayerMaskAlpha = &layerMaskAlpha[0],
|
||||
// pUserMaskAlpha = &userMaskAlpha[0])
|
||||
{
|
||||
var maskStart = 0;
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
var alphaFactor = (layerMaskAlpha[maskStart]) * (userMaskAlpha[maskStart]);
|
||||
var c = color[pDestStart];
|
||||
c.a = (byte)(color[pDestStart].a * alphaFactor / 65025);
|
||||
color[pDestStart] = c;
|
||||
|
||||
pDestStart++;
|
||||
maskStart++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#region Decode 32-bit HDR channels
|
||||
|
||||
private static void SetPDNRowRgb32(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, DecodeContext context)
|
||||
{
|
||||
NativeArray<float> redChannel = context.Channels[0].ImageData.Reinterpret<float>(1);
|
||||
NativeArray<float> greenChannel = context.Channels[1].ImageData.Reinterpret<float>(1);
|
||||
NativeArray<float> blueChannel = context.Channels[2].ImageData.Reinterpret<float>(1);
|
||||
|
||||
{
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
var c = color[pDestStart];
|
||||
c.r = RGBByteFromHDRFloat(redChannel[idxSrc / 4]);
|
||||
c.g = RGBByteFromHDRFloat(greenChannel[idxSrc / 4]);
|
||||
c.b = RGBByteFromHDRFloat(blueChannel[idxSrc / 4]);
|
||||
color[pDestStart] = c;
|
||||
pDestStart++;
|
||||
idxSrc += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPDNRowGrayscale32(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, DecodeContext context)
|
||||
{
|
||||
NativeArray<float> channel = context.Channels[0].ImageData.Reinterpret<float>(1);
|
||||
{
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
byte rgbValue = RGBByteFromHDRFloat(channel[idxSrc / 4]);
|
||||
var c = color[pDestStart];
|
||||
c.r = rgbValue;
|
||||
c.g = rgbValue;
|
||||
c.b = rgbValue;
|
||||
color[pDestStart] = c;
|
||||
|
||||
pDestStart++;
|
||||
idxSrc += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#region Decode 8-bit and 16-bit channels
|
||||
|
||||
private static void SetPDNRowRgb(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, DecodeContext context)
|
||||
{
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
var c = color[pDestStart];
|
||||
c.r = context.Channels[0].ImageData[idxSrc];
|
||||
c.g = context.Channels[1].ImageData[idxSrc];
|
||||
c.b = context.Channels[2].ImageData[idxSrc];
|
||||
color[pDestStart] = c;
|
||||
|
||||
pDestStart++;
|
||||
idxSrc += context.ByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// The color-conversion formulas come from the Colour Space Conversions FAQ:
|
||||
// http://www.poynton.com/PDFs/coloureq.pdf
|
||||
//
|
||||
// RGB --> CMYK CMYK --> RGB
|
||||
// --------------------------------------- --------------------------------------------
|
||||
// Black = minimum(1-Red,1-Green,1-Blue) Red = 1-minimum(1,Cyan*(1-Black)+Black)
|
||||
// Cyan = (1-Red-Black)/(1-Black) Green = 1-minimum(1,Magenta*(1-Black)+Black)
|
||||
// Magenta = (1-Green-Black)/(1-Black) Blue = 1-minimum(1,Yellow*(1-Black)+Black)
|
||||
// Yellow = (1-Blue-Black)/(1-Black)
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static void SetPDNRowCmyk(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, DecodeContext context)
|
||||
{
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
// CMYK values are stored as complements, presumably to allow for some
|
||||
// measure of compatibility with RGB-only applications.
|
||||
var C = 255 - context.Channels[0].ImageData[idxSrc];
|
||||
var M = 255 - context.Channels[1].ImageData[idxSrc];
|
||||
var Y = 255 - context.Channels[2].ImageData[idxSrc];
|
||||
var K = 255 - context.Channels[3].ImageData[idxSrc];
|
||||
|
||||
int nRed = 255 - Math.Min(255, C * (255 - K) / 255 + K);
|
||||
int nGreen = 255 - Math.Min(255, M * (255 - K) / 255 + K);
|
||||
int nBlue = 255 - Math.Min(255, Y * (255 - K) / 255 + K);
|
||||
|
||||
var c = color[pDestStart];
|
||||
c.r = (byte)nRed;
|
||||
c.g = (byte)nGreen;
|
||||
c.b = (byte)nBlue;
|
||||
color[pDestStart] = c;
|
||||
|
||||
pDestStart++;
|
||||
idxSrc += context.ByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPDNRowBitmap(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, DecodeContext context)
|
||||
{
|
||||
var bitmap = context.Channels[0].ImageData;
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
byte mask = (byte)(0x80 >> (idxSrc % 8));
|
||||
byte bwValue = (byte)(bitmap[idxSrc / 8] & mask);
|
||||
bwValue = (bwValue == 0) ? (byte)255 : (byte)0;
|
||||
|
||||
var c = color[pDestStart];
|
||||
c.r = bwValue;
|
||||
c.g = bwValue;
|
||||
c.b = bwValue;
|
||||
color[pDestStart] = c;
|
||||
|
||||
pDestStart++;
|
||||
idxSrc += context.ByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPDNRowGrayscale(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, DecodeContext context)
|
||||
{
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
var level = context.Channels[0].ImageData[idxSrc];
|
||||
var c = color[pDestStart];
|
||||
c.r = level;
|
||||
c.g = level;
|
||||
c.b = level;
|
||||
color[pDestStart] = c;
|
||||
|
||||
pDestStart++;
|
||||
idxSrc += context.ByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPDNRowIndexed(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, DecodeContext context)
|
||||
{
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
int index = (int)context.Channels[0].ImageData[idxSrc];
|
||||
var c = color[pDestStart];
|
||||
c.r = (byte)context.ColorModeData[index];
|
||||
c.g = context.ColorModeData[index + 256];
|
||||
c.b = context.ColorModeData[index + 2 * 256];
|
||||
color[pDestStart] = c;
|
||||
|
||||
pDestStart++;
|
||||
idxSrc += context.ByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPDNRowLab(int pDestStart, int pDestEnd, int width, NativeArray<Color32> color, int idxSrc, DecodeContext context)
|
||||
{
|
||||
while (pDestStart < pDestEnd)
|
||||
{
|
||||
double exL, exA, exB;
|
||||
exL = (double)context.Channels[0].ImageData[idxSrc];
|
||||
exA = (double)context.Channels[1].ImageData[idxSrc];
|
||||
exB = (double)context.Channels[2].ImageData[idxSrc];
|
||||
|
||||
int L = (int)(exL / 2.55);
|
||||
int a = (int)(exA - 127.5);
|
||||
int b = (int)(exB - 127.5);
|
||||
|
||||
// First, convert from Lab to XYZ.
|
||||
// Standards used Observer = 2, Illuminant = D65
|
||||
|
||||
const double ref_X = 95.047;
|
||||
const double ref_Y = 100.000;
|
||||
const double ref_Z = 108.883;
|
||||
|
||||
double var_Y = ((double)L + 16.0) / 116.0;
|
||||
double var_X = (double)a / 500.0 + var_Y;
|
||||
double var_Z = var_Y - (double)b / 200.0;
|
||||
|
||||
double var_X3 = var_X * var_X * var_X;
|
||||
double var_Y3 = var_Y * var_Y * var_Y;
|
||||
double var_Z3 = var_Z * var_Z * var_Z;
|
||||
|
||||
if (var_Y3 > 0.008856)
|
||||
var_Y = var_Y3;
|
||||
else
|
||||
var_Y = (var_Y - 16 / 116) / 7.787;
|
||||
|
||||
if (var_X3 > 0.008856)
|
||||
var_X = var_X3;
|
||||
else
|
||||
var_X = (var_X - 16 / 116) / 7.787;
|
||||
|
||||
if (var_Z3 > 0.008856)
|
||||
var_Z = var_Z3;
|
||||
else
|
||||
var_Z = (var_Z - 16 / 116) / 7.787;
|
||||
|
||||
double X = ref_X * var_X;
|
||||
double Y = ref_Y * var_Y;
|
||||
double Z = ref_Z * var_Z;
|
||||
|
||||
// Then, convert from XYZ to RGB.
|
||||
// Standards used Observer = 2, Illuminant = D65
|
||||
// ref_X = 95.047, ref_Y = 100.000, ref_Z = 108.883
|
||||
|
||||
double var_R = X * 0.032406 + Y * (-0.015372) + Z * (-0.004986);
|
||||
double var_G = X * (-0.009689) + Y * 0.018758 + Z * 0.000415;
|
||||
double var_B = X * 0.000557 + Y * (-0.002040) + Z * 0.010570;
|
||||
|
||||
if (var_R > 0.0031308)
|
||||
var_R = 1.055 * (Math.Pow(var_R, 1 / 2.4)) - 0.055;
|
||||
else
|
||||
var_R = 12.92 * var_R;
|
||||
|
||||
if (var_G > 0.0031308)
|
||||
var_G = 1.055 * (Math.Pow(var_G, 1 / 2.4)) - 0.055;
|
||||
else
|
||||
var_G = 12.92 * var_G;
|
||||
|
||||
if (var_B > 0.0031308)
|
||||
var_B = 1.055 * (Math.Pow(var_B, 1 / 2.4)) - 0.055;
|
||||
else
|
||||
var_B = 12.92 * var_B;
|
||||
|
||||
int nRed = (int)(var_R * 256.0);
|
||||
int nGreen = (int)(var_G * 256.0);
|
||||
int nBlue = (int)(var_B * 256.0);
|
||||
|
||||
if (nRed < 0)
|
||||
nRed = 0;
|
||||
else if (nRed > 255)
|
||||
nRed = 255;
|
||||
if (nGreen < 0)
|
||||
nGreen = 0;
|
||||
else if (nGreen > 255)
|
||||
nGreen = 255;
|
||||
if (nBlue < 0)
|
||||
nBlue = 0;
|
||||
else if (nBlue > 255)
|
||||
nBlue = 255;
|
||||
|
||||
var c = color[pDestStart];
|
||||
c.r = (byte)nRed;
|
||||
c.g = (byte)nGreen;
|
||||
c.b = (byte)nBlue;
|
||||
color[pDestStart] = c;
|
||||
|
||||
pDestStart++;
|
||||
idxSrc += context.ByteDepth;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,301 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using PhotoshopFile;
|
||||
using UnityEngine;
|
||||
using PDNWrapper;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace PaintDotNet.Data.PhotoshopFileType
|
||||
{
|
||||
internal static class PsdLoad
|
||||
{
|
||||
public static PsdFile Load(System.IO.Stream input, ELoadFlag loadFlag)
|
||||
{
|
||||
var loadContext = new DocumentLoadContext();
|
||||
return new PsdFile(input, loadContext, loadFlag);
|
||||
}
|
||||
|
||||
public static Document Load(System.IO.Stream input)
|
||||
{
|
||||
// Load and decompress Photoshop file structures
|
||||
var loadContext = new DocumentLoadContext();
|
||||
var psdFile = new PsdFile(input, loadContext);
|
||||
|
||||
// Multichannel images are loaded by processing each channel as a
|
||||
// grayscale layer.
|
||||
if (psdFile.ColorMode == PsdColorMode.Multichannel)
|
||||
{
|
||||
CreateLayersFromChannels(psdFile);
|
||||
psdFile.ColorMode = PsdColorMode.Grayscale;
|
||||
}
|
||||
|
||||
// Convert into Paint.NET internal representation
|
||||
var document = new Document(psdFile.ColumnCount, psdFile.RowCount);
|
||||
|
||||
if (psdFile.Layers.Count == 0)
|
||||
{
|
||||
psdFile.BaseLayer.CreateMissingChannels();
|
||||
var layer = PDNWrapper.Layer.CreateBackgroundLayer(psdFile.ColumnCount, psdFile.RowCount);
|
||||
ImageDecoderPdn.DecodeImage(layer, psdFile.BaseLayer);
|
||||
layer.Name = String.IsNullOrEmpty(psdFile.BaseLayer.Name)? "Background" : psdFile.BaseLayer.Name;
|
||||
layer.Opacity = psdFile.BaseLayer.Opacity;
|
||||
layer.Visible = psdFile.BaseLayer.Visible;
|
||||
layer.IsGroup = psdFile.BaseLayer.IsGroup;
|
||||
layer.LayerID = psdFile.BaseLayer.LayerID;
|
||||
layer.BlendMode = BlendModeMapping.FromPsdBlendMode(psdFile.BaseLayer.BlendModeKey);
|
||||
document.Layers.Add(layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
psdFile.VerifyLayerSections();
|
||||
ApplyLayerSections(psdFile.Layers);
|
||||
|
||||
//var pdnLayers = psdFile.Layers.AsParallel().AsOrdered()
|
||||
// .Select(psdLayer => psdLayer.DecodeToPdnLayer())
|
||||
// .ToList();
|
||||
//document.Layers.AddRange(pdnLayers);
|
||||
/*
|
||||
foreach (var l in psdFile.Layers)
|
||||
{
|
||||
document.Layers.Add(l.DecodeToPdnLayer());
|
||||
}
|
||||
*/
|
||||
BitmapLayer parent = null;
|
||||
JobHandle jobHandle = default(JobHandle);
|
||||
foreach (var l in Enumerable.Reverse(psdFile.Layers))
|
||||
{
|
||||
if (l.IsEndGroupMarker)
|
||||
{
|
||||
parent = parent != null ? parent.ParentLayer : null;
|
||||
continue;
|
||||
}
|
||||
BitmapLayer b = null;
|
||||
jobHandle = l.DecodeToPdnLayer(jobHandle, out b);
|
||||
b.ParentLayer = parent;
|
||||
if (parent != null)
|
||||
parent.ChildLayer.Add(b);
|
||||
else
|
||||
document.Layers.Add(b);
|
||||
|
||||
if (b.IsGroup)
|
||||
parent = b;
|
||||
}
|
||||
jobHandle.Complete();
|
||||
}
|
||||
SetPdnResolutionInfo(psdFile, document);
|
||||
psdFile.Cleanup();
|
||||
return document;
|
||||
}
|
||||
|
||||
internal static JobHandle DecodeToPdnLayer(this PhotoshopFile.Layer psdLayer, JobHandle inputDeps, out BitmapLayer pdnLayer)
|
||||
{
|
||||
var psdFile = psdLayer.PsdFile;
|
||||
psdLayer.CreateMissingChannels();
|
||||
|
||||
pdnLayer = new BitmapLayer(psdFile.ColumnCount, psdFile.RowCount);
|
||||
pdnLayer.Name = psdLayer.Name;
|
||||
pdnLayer.Opacity = psdLayer.Opacity;
|
||||
pdnLayer.Visible = psdLayer.Visible;
|
||||
pdnLayer.IsGroup = psdLayer.IsGroup;
|
||||
pdnLayer.LayerID = psdLayer.LayerID;
|
||||
pdnLayer.BlendMode = BlendModeMapping.FromPsdBlendMode(psdLayer.BlendModeKey);
|
||||
return ImageDecoderPdn.DecodeImage(pdnLayer, psdLayer, inputDeps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a layer for each channel in a multichannel image.
|
||||
/// </summary>
|
||||
private static void CreateLayersFromChannels(PsdFile psdFile)
|
||||
{
|
||||
if (psdFile.ColorMode != PsdColorMode.Multichannel)
|
||||
throw new Exception("Not a multichannel image.");
|
||||
if (psdFile.Layers.Count > 0)
|
||||
throw new PsdInvalidException("Multichannel image should not have layers.");
|
||||
|
||||
// Get alpha channel names, preferably in Unicode.
|
||||
var alphaChannelNames = (AlphaChannelNames)psdFile.ImageResources
|
||||
.Get(ResourceID.AlphaChannelNames);
|
||||
var unicodeAlphaNames = (UnicodeAlphaNames)psdFile.ImageResources
|
||||
.Get(ResourceID.UnicodeAlphaNames);
|
||||
if ((alphaChannelNames == null) && (unicodeAlphaNames == null))
|
||||
throw new PsdInvalidException("No channel names found.");
|
||||
|
||||
var channelNames = (unicodeAlphaNames != null)
|
||||
? unicodeAlphaNames.ChannelNames
|
||||
: alphaChannelNames.ChannelNames;
|
||||
var channels = psdFile.BaseLayer.Channels;
|
||||
if (channels.Count > channelNames.Count)
|
||||
throw new PsdInvalidException("More channels than channel names.");
|
||||
|
||||
// Channels are stored from top to bottom, but layers are stored from
|
||||
// bottom to top.
|
||||
for (int i = channels.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var channel = channels[i];
|
||||
var channelName = channelNames[i];
|
||||
|
||||
// Copy metadata over from base layer
|
||||
var layer = new PhotoshopFile.Layer(psdFile);
|
||||
layer.Rect = psdFile.BaseLayer.Rect;
|
||||
layer.Visible = true;
|
||||
layer.Masks = new MaskInfo();
|
||||
layer.BlendingRangesData = new BlendingRanges(layer);
|
||||
|
||||
// We do not attempt to reconstruct the appearance of the image, but
|
||||
// only to provide access to the channels image data.
|
||||
layer.Name = channelName;
|
||||
layer.BlendModeKey = PsdBlendMode.Darken;
|
||||
layer.Opacity = 255;
|
||||
|
||||
// Copy channel image data into the new grayscale layer
|
||||
var layerChannel = new Channel(0, layer);
|
||||
layerChannel.ImageCompression = channel.ImageCompression;
|
||||
layerChannel.ImageData = new NativeArray<byte>(channel.ImageData, Allocator.Persistent);
|
||||
layer.Channels.Add(layerChannel);
|
||||
|
||||
psdFile.Layers.Add(layer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform Photoshop's layer tree to Paint.NET's flat layer list.
|
||||
/// Indicate where layer sections begin and end, and hide all layers within
|
||||
/// hidden layer sections.
|
||||
/// </summary>
|
||||
private static void ApplyLayerSections(List<PhotoshopFile.Layer> layers)
|
||||
{
|
||||
// BUG: PsdPluginResources.GetString will always return English resource,
|
||||
// because Paint.NET does not set the CurrentUICulture when OnLoad is
|
||||
// called. This situation should be resolved with Paint.NET 4.0, which
|
||||
// will provide an alternative mechanism to retrieve the UI language.
|
||||
|
||||
// Track the depth of the topmost hidden section. Any nested sections
|
||||
// will be hidden, whether or not they themselves have the flag set.
|
||||
int topHiddenSectionDepth = Int32.MaxValue;
|
||||
var layerSectionNames = new Stack<string>();
|
||||
|
||||
// Layers are stored bottom-to-top, but layer sections are specified
|
||||
// top-to-bottom.
|
||||
foreach (var layer in Enumerable.Reverse(layers))
|
||||
{
|
||||
// Leo: Since we are importing, we don't care if the group is collapsed
|
||||
// Apply to all layers within the layer section, as well as the
|
||||
// closing layer.
|
||||
//if (layerSectionNames.Count > topHiddenSectionDepth)
|
||||
// layer.Visible = false;
|
||||
|
||||
var sectionInfo = (LayerSectionInfo)layer.AdditionalInfo
|
||||
.SingleOrDefault(x => x is LayerSectionInfo);
|
||||
if (sectionInfo == null)
|
||||
continue;
|
||||
|
||||
switch (sectionInfo.SectionType)
|
||||
{
|
||||
case LayerSectionType.OpenFolder:
|
||||
case LayerSectionType.ClosedFolder:
|
||||
// Start a new layer section
|
||||
if ((!layer.Visible) && (topHiddenSectionDepth == Int32.MaxValue))
|
||||
topHiddenSectionDepth = layerSectionNames.Count;
|
||||
layerSectionNames.Push(layer.Name);
|
||||
layer.IsGroup = true;
|
||||
//layer.Name = String.Format(beginSectionWrapper, layer.Name);
|
||||
break;
|
||||
|
||||
case LayerSectionType.SectionDivider:
|
||||
// End the current layer section
|
||||
//var layerSectionName = layerSectionNames.Pop ();
|
||||
if (layerSectionNames.Count == topHiddenSectionDepth)
|
||||
topHiddenSectionDepth = Int32.MaxValue;
|
||||
layer.IsEndGroupMarker = true;
|
||||
//layer.Name = String.Format(endSectionWrapper, layerSectionName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the resolution on the Paint.NET Document to match the PSD file.
|
||||
/// </summary>
|
||||
private static void SetPdnResolutionInfo(PsdFile psdFile, Document document)
|
||||
{
|
||||
if (psdFile.Resolution != null)
|
||||
{
|
||||
// PSD files always specify the resolution in DPI. When loading and
|
||||
// saving cm, we will have to round-trip the conversion, but doubles
|
||||
// have plenty of precision to spare vs. PSD's 16/16 fixed-point.
|
||||
|
||||
if ((psdFile.Resolution.HResDisplayUnit == ResolutionInfo.ResUnit.PxPerCm)
|
||||
&& (psdFile.Resolution.VResDisplayUnit == ResolutionInfo.ResUnit.PxPerCm))
|
||||
{
|
||||
document.DpuUnit = MeasurementUnit.Centimeter;
|
||||
|
||||
// HACK: Paint.NET truncates DpuX and DpuY to three decimal places,
|
||||
// so add 0.0005 to get a rounded value instead.
|
||||
document.DpuX = psdFile.Resolution.HDpi / 2.54 + 0.0005;
|
||||
document.DpuY = psdFile.Resolution.VDpi / 2.54 + 0.0005;
|
||||
}
|
||||
else
|
||||
{
|
||||
document.DpuUnit = MeasurementUnit.Inch;
|
||||
document.DpuX = psdFile.Resolution.HDpi;
|
||||
document.DpuY = psdFile.Resolution.VDpi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the PSD file will fit into physical memory once loaded
|
||||
/// and converted to Paint.NET format.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This check is necessary because layers in Paint.NET have the same
|
||||
/// dimensions as the canvas. Thus, PSD files that contain lots of
|
||||
/// tiny adjustment layers may blow up in size by several
|
||||
/// orders of magnitude.
|
||||
/// </remarks>
|
||||
internal static void CheckSufficientMemory(PsdFile psdFile)
|
||||
{
|
||||
// Multichannel images have channels converted to layers
|
||||
var numLayers = (psdFile.ColorMode == PsdColorMode.Multichannel)
|
||||
? psdFile.BaseLayer.Channels.Count
|
||||
: Math.Max(psdFile.Layers.Count, 1);
|
||||
|
||||
// Paint.NET also requires a scratch layer and composite layer
|
||||
numLayers += 2;
|
||||
|
||||
long numPixels = (long)psdFile.ColumnCount * psdFile.RowCount;
|
||||
ulong bytesRequired = (ulong)(checked(4 * numPixels * numLayers));
|
||||
|
||||
// Check that the file will fit entirely into physical memory, so that we
|
||||
// do not thrash and make the Paint.NET UI nonresponsive. We also have
|
||||
// to check against virtual memory address space because 32-bit processes
|
||||
// cannot access all 4 GB.
|
||||
//var computerInfo = new Microsoft.VisualBasic.Devices.ComputerInfo();
|
||||
//var accessibleMemory = Math.Min(computerInfo.TotalPhysicalMemory,
|
||||
// computerInfo.TotalVirtualMemory);
|
||||
var accessibleMemory = (ulong)SystemInfo.systemMemorySize * 1024 * 1024;
|
||||
if (bytesRequired > accessibleMemory)
|
||||
{
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
|
||||
namespace PhotoshopFile.Compression
|
||||
{
|
||||
internal class EndianReverser : ImageData
|
||||
{
|
||||
private ImageData imageData;
|
||||
|
||||
protected override bool AltersWrittenData
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public EndianReverser(ImageData imageData)
|
||||
: base(imageData.Size, imageData.BitDepth)
|
||||
{
|
||||
this.imageData = imageData;
|
||||
}
|
||||
|
||||
internal override void Read(byte[] buffer)
|
||||
{
|
||||
imageData.Read(buffer);
|
||||
|
||||
var numPixels = Size.Width * Size.Height;
|
||||
if (numPixels == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Util.SwapByteArray(BitDepth, buffer, 0, numPixels);
|
||||
}
|
||||
|
||||
public override byte[] ReadCompressed()
|
||||
{
|
||||
return imageData.ReadCompressed();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using PDNWrapper;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace PhotoshopFile.Compression
|
||||
{
|
||||
internal abstract class ImageData
|
||||
{
|
||||
public int BitDepth { get; private set; }
|
||||
|
||||
public int BytesPerRow { get; private set; }
|
||||
|
||||
public Size Size { get; private set; }
|
||||
|
||||
protected abstract bool AltersWrittenData { get; }
|
||||
|
||||
protected ImageData(Size size, int bitDepth)
|
||||
{
|
||||
Size = size;
|
||||
BitDepth = bitDepth;
|
||||
BytesPerRow = Util.BytesPerRow(size, bitDepth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads decompressed image data.
|
||||
/// </summary>
|
||||
public virtual byte[] Read()
|
||||
{
|
||||
var imageLongLength = (long)BytesPerRow * Size.Height;
|
||||
Util.CheckByteArrayLength(imageLongLength);
|
||||
|
||||
var buffer = new byte[imageLongLength];
|
||||
Read(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
internal abstract void Read(byte[] buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Reads compressed image data.
|
||||
/// </summary>
|
||||
public abstract byte[] ReadCompressed();
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.IO.Compression;
|
||||
using PDNWrapper;
|
||||
|
||||
namespace PhotoshopFile.Compression
|
||||
{
|
||||
internal static class ImageDataFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an ImageData object to compress or decompress image data.
|
||||
/// </summary>
|
||||
/// <param name="channel">The Channel associated with the image data.</param>
|
||||
/// <param name="data">The image data to be decompressed, or null if
|
||||
/// image data is to be compressed.</param>
|
||||
public static ImageData Create(Channel channel, byte[] data)
|
||||
{
|
||||
var bitDepth = channel.Layer.PsdFile.BitDepth;
|
||||
ImageData imageData;
|
||||
switch (channel.ImageCompression)
|
||||
{
|
||||
case ImageCompression.Raw:
|
||||
imageData = new RawImage(data, channel.Rect.Size, bitDepth);
|
||||
break;
|
||||
|
||||
case ImageCompression.Rle:
|
||||
imageData = new RleImage(data, channel.RleRowLengths,
|
||||
channel.Rect.Size, bitDepth);
|
||||
break;
|
||||
|
||||
case ImageCompression.Zip:
|
||||
// Photoshop treats 32-bit Zip as 32-bit ZipPrediction
|
||||
imageData = (bitDepth == 32)
|
||||
? CreateZipPredict(data, channel.Rect.Size, bitDepth)
|
||||
: new ZipImage(data, channel.Rect.Size, bitDepth);
|
||||
break;
|
||||
|
||||
case ImageCompression.ZipPrediction:
|
||||
imageData = CreateZipPredict(data, channel.Rect.Size, bitDepth);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new PsdInvalidException("Unknown image compression method.");
|
||||
}
|
||||
|
||||
// Reverse endianness of multi-byte image data
|
||||
imageData = WrapEndianness(imageData);
|
||||
|
||||
return imageData;
|
||||
}
|
||||
|
||||
private static ImageData CreateZipPredict(byte[] data, Size size,
|
||||
int bitDepth)
|
||||
{
|
||||
switch (bitDepth)
|
||||
{
|
||||
case 16:
|
||||
return new ZipPredict16Image(data, size);
|
||||
case 32:
|
||||
return new ZipPredict32Image(data, size);
|
||||
default:
|
||||
throw new PsdInvalidException(
|
||||
"ZIP with prediction is only available for 16 and 32 bit depths.");
|
||||
}
|
||||
}
|
||||
|
||||
private static ImageData WrapEndianness(ImageData imageData)
|
||||
{
|
||||
// Single-byte image does not require endianness reversal
|
||||
if (imageData.BitDepth <= 8)
|
||||
{
|
||||
return imageData;
|
||||
}
|
||||
|
||||
// Bytes will be reordered by the compressor, so no wrapper is needed
|
||||
if ((imageData is ZipPredict16Image) || (imageData is ZipPredict32Image))
|
||||
{
|
||||
return imageData;
|
||||
}
|
||||
|
||||
return new EndianReverser(imageData);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using PDNWrapper;
|
||||
|
||||
namespace PhotoshopFile.Compression
|
||||
{
|
||||
internal class RawImage : ImageData
|
||||
{
|
||||
private byte[] data;
|
||||
|
||||
protected override bool AltersWrittenData
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public RawImage(byte[] data, Size size, int bitDepth)
|
||||
: base(size, bitDepth)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
internal override void Read(byte[] buffer)
|
||||
{
|
||||
Array.Copy(data, buffer, data.Length);
|
||||
}
|
||||
|
||||
public override byte[] ReadCompressed()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Photoshop PSD FileType Plugin for Paint.NET
|
||||
// http://psdplugin.codeplex.com/
|
||||
//
|
||||
// This software is ptortorovided under the MIT License:
|
||||
// Copyright (c) 2006-2007 Frank Blumenberg
|
||||
// Copyright (c) 2010-2016 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using PDNWrapper;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace PhotoshopFile.Compression
|
||||
{
|
||||
internal class RleImage : ImageData
|
||||
{
|
||||
private byte[] rleData;
|
||||
private RleRowLengths rleRowLengths;
|
||||
|
||||
protected override bool AltersWrittenData
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public RleImage(byte[] rleData, RleRowLengths rleRowLengths,
|
||||
Size size, int bitDepth)
|
||||
: base(size, bitDepth)
|
||||
{
|
||||
this.rleData = rleData;
|
||||
this.rleRowLengths = rleRowLengths;
|
||||
}
|
||||
|
||||
internal override void Read(byte[] buffer)
|
||||
{
|
||||
var rleStream = new MemoryStream(rleData);
|
||||
var rleReader = new RleReader(rleStream);
|
||||
var bufferIndex = 0;
|
||||
for (int i = 0; i < Size.Height; i++)
|
||||
{
|
||||
var bytesRead = rleReader.Read(buffer, bufferIndex, BytesPerRow);
|
||||
if (bytesRead != BytesPerRow)
|
||||
{
|
||||
throw new Exception("RLE row decompressed to unexpected length.");
|
||||
}
|
||||
bufferIndex += bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
public override byte[] ReadCompressed()
|
||||
{
|
||||
return rleData;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using PDNWrapper;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace PhotoshopFile.Compression
|
||||
{
|
||||
internal class ZipImage : ImageData
|
||||
{
|
||||
private MemoryStream zipDataStream;
|
||||
private DeflateStream zipStream;
|
||||
|
||||
protected override bool AltersWrittenData
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public ZipImage(byte[] data, Size size, int bitDepth)
|
||||
: base(size, bitDepth)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
InitCompress();
|
||||
}
|
||||
else
|
||||
{
|
||||
InitDecompress(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitCompress()
|
||||
{
|
||||
zipDataStream = new MemoryStream();
|
||||
|
||||
// Write 2-byte zlib (RFC 1950) header
|
||||
//
|
||||
// CMF Compression Method and flags:
|
||||
// CM 0:3 = 8 = deflate
|
||||
// CINFO 4:7 = 4 = undefined, RFC 1950 only defines CINFO = 8
|
||||
//
|
||||
// FLG Flags:
|
||||
// FCHECK 0:4 = 9 = check bits for CMF and FLG
|
||||
// FDICT 5 = 0 = no preset dictionary
|
||||
// FLEVEL 6:7 = 2 = default compression level
|
||||
|
||||
zipDataStream.WriteByte(0x48);
|
||||
zipDataStream.WriteByte(0x89);
|
||||
zipStream = new DeflateStream(zipDataStream, CompressionMode.Compress,
|
||||
true);
|
||||
}
|
||||
|
||||
private void InitDecompress(byte[] data)
|
||||
{
|
||||
zipDataStream = new MemoryStream(data);
|
||||
|
||||
// .NET implements Deflate (RFC 1951) but not zlib (RFC 1950),
|
||||
// so we have to skip the first two bytes.
|
||||
zipDataStream.ReadByte();
|
||||
zipDataStream.ReadByte();
|
||||
zipStream = new DeflateStream(zipDataStream, CompressionMode.Decompress,
|
||||
true);
|
||||
}
|
||||
|
||||
internal override void Read(byte[] buffer)
|
||||
{
|
||||
var bytesToRead = (long)Size.Height * BytesPerRow;
|
||||
Util.CheckByteArrayLength(bytesToRead);
|
||||
|
||||
var bytesRead = zipStream.Read(buffer, 0, (int)bytesToRead);
|
||||
if (bytesRead != bytesToRead)
|
||||
{
|
||||
throw new Exception("ZIP stream was not fully decompressed.");
|
||||
}
|
||||
}
|
||||
|
||||
public override byte[] ReadCompressed()
|
||||
{
|
||||
// Write out the last block. (Flush leaves the last block open.)
|
||||
zipStream.Close();
|
||||
|
||||
// Do not write the zlib header when the image data is empty
|
||||
var result = (zipDataStream.Length == 2)
|
||||
? new byte[0]
|
||||
: zipDataStream.ToArray();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using PDNWrapper;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace PhotoshopFile.Compression
|
||||
{
|
||||
internal class ZipPredict16Image : ImageData
|
||||
{
|
||||
private ImageData zipImage;
|
||||
|
||||
protected override bool AltersWrittenData
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public ZipPredict16Image(byte[] zipData, Size size)
|
||||
: base(size, 16)
|
||||
{
|
||||
// 16-bitdepth images are delta-encoded word-by-word. The deltas
|
||||
// are thus big-endian and must be reversed for further processing.
|
||||
var zipRawImage = new ZipImage(zipData, size, 16);
|
||||
zipImage = new EndianReverser(zipRawImage);
|
||||
}
|
||||
|
||||
internal override void Read(byte[] buffer)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
zipImage.Read(buffer);
|
||||
|
||||
{
|
||||
{
|
||||
Unpredict(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte[] ReadCompressed()
|
||||
{
|
||||
return zipImage.ReadCompressed();
|
||||
}
|
||||
|
||||
private void Predict(/*UInt16**/ byte[] ptrData)
|
||||
{
|
||||
int size = sizeof(UInt16);
|
||||
// Delta-encode each row
|
||||
for (int i = 0; i < Size.Height; i++)
|
||||
{
|
||||
int rowOffset = Size.Width * i * size;
|
||||
//UInt16* ptrDataRow = ptrData;
|
||||
var ptrDataRowEnd = Size.Width - 1;
|
||||
|
||||
// Start with the last column in the row
|
||||
while (ptrDataRowEnd > 0)
|
||||
{
|
||||
var v = BitConverter.ToUInt16(ptrData, ptrDataRowEnd * size + rowOffset);
|
||||
var v1 = BitConverter.ToUInt16(ptrData, (ptrDataRowEnd - 1) * size + rowOffset);
|
||||
v -= v1;
|
||||
var b = BitConverter.GetBytes(v);
|
||||
for (int c = 0; c < b.Length; ++c)
|
||||
{
|
||||
ptrData[ptrDataRowEnd * size + rowOffset + c] = b[c];
|
||||
}
|
||||
ptrDataRowEnd--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpredicts the decompressed, native-endian image data.
|
||||
/// </summary>
|
||||
private void Unpredict(byte[] ptrData)
|
||||
{
|
||||
int size = sizeof(UInt16);
|
||||
// Delta-decode each row
|
||||
for (int i = 0; i < Size.Height; i++)
|
||||
{
|
||||
//UInt16* ptrDataRowEnd = ptrData + Size.Width;
|
||||
int rowOffset = Size.Width * i * size;
|
||||
// Start with column index 1 on each row
|
||||
int start = 1;
|
||||
while (start < Size.Width)
|
||||
{
|
||||
var v = BitConverter.ToUInt16(ptrData, start * size + rowOffset);
|
||||
var v1 = BitConverter.ToUInt16(ptrData, (start - 1) * size + rowOffset);
|
||||
v += v1;
|
||||
var b = BitConverter.GetBytes(v);
|
||||
for (int c = 0; c < b.Length; ++c)
|
||||
{
|
||||
ptrData[start * size + rowOffset + c] = b[c];
|
||||
}
|
||||
start++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,175 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using PDNWrapper;
|
||||
|
||||
namespace PhotoshopFile.Compression
|
||||
{
|
||||
internal class ZipPredict32Image : ImageData
|
||||
{
|
||||
private ZipImage zipImage;
|
||||
|
||||
protected override bool AltersWrittenData
|
||||
{
|
||||
// Prediction will pack the data into a temporary buffer, so the
|
||||
// original data will remain unchanged.
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public ZipPredict32Image(byte[] zipData, Size size)
|
||||
: base(size, 32)
|
||||
{
|
||||
zipImage = new ZipImage(zipData, size, 32);
|
||||
}
|
||||
|
||||
internal override void Read(byte[] buffer)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var predictedData = new byte[buffer.Length];
|
||||
zipImage.Read(predictedData);
|
||||
|
||||
|
||||
{
|
||||
//fixed (byte* ptrData = &predictedData[0])
|
||||
//fixed (byte* ptrOutput = &buffer[0])
|
||||
{
|
||||
Unpredict(predictedData, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte[] ReadCompressed()
|
||||
{
|
||||
return zipImage.ReadCompressed();
|
||||
}
|
||||
|
||||
private void Predict(byte[] ptrData, byte[] ptrOutput /*Int32* ptrData, byte* ptrOutput*/)
|
||||
{
|
||||
int size = sizeof(Int32);
|
||||
int inputIndex = 0;
|
||||
int outputIndex = 0;
|
||||
for (int i = 0; i < Size.Height; i++)
|
||||
{
|
||||
// Pack together the individual bytes of the 32-bit words, high-order
|
||||
// bytes before low-order bytes.
|
||||
int offset1 = Size.Width;
|
||||
int offset2 = 2 * offset1;
|
||||
int offset3 = 3 * offset1;
|
||||
|
||||
//Int32* ptrDataRow = ptrData;
|
||||
//Int32* ptrDataRowEnd = ptrDataRow + Size.Width;
|
||||
int start = 0, end = Size.Width;
|
||||
//while (ptrData < ptrDataRowEnd)
|
||||
while (start < end)
|
||||
{
|
||||
Int32 data = BitConverter.ToInt32(ptrData, inputIndex);
|
||||
ptrOutput[start + outputIndex] = (byte)(data >> 24);
|
||||
ptrOutput[start + outputIndex + offset1] = (byte)(data >> 16);
|
||||
ptrOutput[start + outputIndex + offset2] = (byte)(data >> 8);
|
||||
ptrOutput[start + outputIndex + offset3] = (byte)(data);
|
||||
|
||||
//ptrData++;
|
||||
//ptrOutput++;
|
||||
start++;
|
||||
inputIndex += size;
|
||||
}
|
||||
|
||||
// Delta-encode the row
|
||||
//byte* ptrOutputRow = ptrOutput;
|
||||
//byte* ptrOutputRowEnd = ptrOutputRow + BytesPerRow;
|
||||
|
||||
//ptrOutput = ptrOutputRowEnd - 1;
|
||||
start = BytesPerRow - 1;
|
||||
while (start > 0)
|
||||
{
|
||||
ptrOutput[start + outputIndex] -= ptrOutput[start + outputIndex - 1];
|
||||
start--;
|
||||
}
|
||||
outputIndex += BytesPerRow;
|
||||
// Advance pointer to next row
|
||||
//ptrOutput = ptrOutputRowEnd;
|
||||
//Debug.Assert(ptrData == ptrDataRowEnd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpredicts the raw decompressed image data into a 32-bpp bitmap with
|
||||
/// native endianness.
|
||||
/// </summary>
|
||||
private void Unpredict(byte[] ptrData, byte[] ptrOutput /*byte* ptrData, Int32* ptrOutput*/)
|
||||
{
|
||||
int inputIndex = 0;
|
||||
int outputIndex = 0;
|
||||
for (int i = 0; i < Size.Height; i++)
|
||||
{
|
||||
//byte* ptrDataRow = ptrData;
|
||||
//byte* ptrDataRowEnd = ptrDataRow + BytesPerRow;
|
||||
|
||||
// Delta-decode each row
|
||||
//ptrData++;
|
||||
//while (ptrData < ptrDataRowEnd)
|
||||
int startIndex = 1;
|
||||
while (startIndex < BytesPerRow)
|
||||
{
|
||||
//*ptrData += *(ptrData - 1);
|
||||
//ptrData++;
|
||||
ptrData[inputIndex + startIndex] += ptrData[inputIndex + startIndex - 1];
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Within each row, the individual bytes of the 32-bit words are
|
||||
// packed together, high-order bytes before low-order bytes.
|
||||
// We now unpack them into words.
|
||||
int offset1 = Size.Width;
|
||||
int offset2 = 2 * offset1;
|
||||
int offset3 = 3 * offset1;
|
||||
|
||||
//ptrData = ptrDataRow;
|
||||
//Int32* ptrOutputRowEnd = ptrOutput + Size.Width;
|
||||
//while (ptrOutput < ptrOutputRowEnd)
|
||||
startIndex = 0;
|
||||
while (startIndex < Size.Width)
|
||||
{
|
||||
Int32 pp = (Int32)ptrData[inputIndex + startIndex] << 24;
|
||||
pp |= (Int32)ptrData[inputIndex + startIndex + offset1] << 16;
|
||||
pp |= (Int32)ptrData[inputIndex + startIndex + offset2] << 8;
|
||||
pp |= (Int32)ptrData[inputIndex + startIndex + offset3];
|
||||
byte[] rr = BitConverter.GetBytes(pp);
|
||||
for (int k = 0; k < rr.Length; ++k)
|
||||
{
|
||||
ptrOutput[outputIndex] = rr[k];
|
||||
outputIndex++;
|
||||
}
|
||||
startIndex++;
|
||||
//*ptrOutput = *(ptrData) << 24
|
||||
// | *(ptrData + offset1) << 16
|
||||
// | *(ptrData + offset2) << 8
|
||||
// | *(ptrData + offset3);
|
||||
|
||||
//ptrData++;
|
||||
//ptrOutput++;
|
||||
}
|
||||
|
||||
// Advance pointer to next row
|
||||
//ptrData = ptrDataRowEnd;
|
||||
//Debug.Assert(ptrOutput == ptrOutputRowEnd);
|
||||
inputIndex += BytesPerRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Photoshop PSD FileType Plugin for Paint.NET
|
||||
// http://psdplugin.codeplex.com/
|
||||
//
|
||||
// This software is perovided under the MIT License:
|
||||
// Copyright (c) 2006-2007 Frank Blumenberg
|
||||
// Copyright (c) 2010-2012 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using PDNWrapper;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
[Serializable]
|
||||
internal class PsdInvalidException : Exception
|
||||
{
|
||||
public PsdInvalidException()
|
||||
{
|
||||
}
|
||||
|
||||
public PsdInvalidException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class RleException : Exception
|
||||
{
|
||||
public RleException() {}
|
||||
|
||||
public RleException(string message) : base(message) {}
|
||||
}
|
||||
}
|
@@ -0,0 +1,234 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2015 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 System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal enum ResourceID
|
||||
{
|
||||
Undefined = 0,
|
||||
MacPrintInfo = 1001,
|
||||
ResolutionInfo = 1005,
|
||||
AlphaChannelNames = 1006,
|
||||
DisplayInfo = 1007,
|
||||
Caption = 1008,
|
||||
BorderInfo = 1009,
|
||||
BackgroundColor = 1010,
|
||||
PrintFlags = 1011,
|
||||
MultichannelHalftoneInfo = 1012,
|
||||
ColorHalftoneInfo = 1013,
|
||||
DuotoneHalftoneInfo = 1014,
|
||||
MultichannelTransferFunctions = 1015,
|
||||
ColorTransferFunctions = 1016,
|
||||
DuotoneTransferFunctions = 1017,
|
||||
DuotoneImageInfo = 1018,
|
||||
BlackWhiteRange = 1019,
|
||||
EpsOptions = 1021,
|
||||
QuickMaskInfo = 1022,
|
||||
LayerStateInfo = 1024,
|
||||
WorkingPathUnsaved = 1025,
|
||||
LayersGroupInfo = 1026,
|
||||
IptcNaa = 1028,
|
||||
RawFormatImageMode = 1029,
|
||||
JpegQuality = 1030,
|
||||
GridGuidesInfo = 1032,
|
||||
ThumbnailBgr = 1033,
|
||||
CopyrightInfo = 1034,
|
||||
Url = 1035,
|
||||
ThumbnailRgb = 1036,
|
||||
GlobalAngle = 1037,
|
||||
ColorSamplersObsolete = 1038,
|
||||
IccProfile = 1039,
|
||||
Watermark = 1040,
|
||||
IccUntagged = 1041,
|
||||
EffectsVisible = 1042,
|
||||
SpotHalftone = 1043,
|
||||
DocumentSpecific = 1044,
|
||||
UnicodeAlphaNames = 1045,
|
||||
IndexedColorTableCount = 1046,
|
||||
TransparentIndex = 1047,
|
||||
GlobalAltitude = 1049,
|
||||
Slices = 1050,
|
||||
WorkflowUrl = 1051,
|
||||
JumpToXpep = 1052,
|
||||
AlphaIdentifiers = 1053,
|
||||
UrlList = 1054,
|
||||
VersionInfo = 1057,
|
||||
ExifData1 = 1058,
|
||||
ExifData3 = 1059,
|
||||
XmpMetadata = 1060,
|
||||
CaptionDigest = 1061,
|
||||
PrintScale = 1062,
|
||||
PixelAspectRatio = 1064,
|
||||
LayerComps = 1065,
|
||||
AlternateDuotoneColors = 1066,
|
||||
AlternateSpotColors = 1067,
|
||||
LayerSelectionIDs = 1069,
|
||||
HdrToningInfo = 1070,
|
||||
PrintInfo = 1071,
|
||||
LayerGroupsEnabled = 1072,
|
||||
ColorSamplers = 1073,
|
||||
MeasurementScale = 1074,
|
||||
TimelineInfo = 1075,
|
||||
SheetDisclosure = 1076,
|
||||
FloatDisplayInfo = 1077,
|
||||
OnionSkins = 1078,
|
||||
CountInfo = 1080,
|
||||
PrintSettingsInfo = 1082,
|
||||
PrintStyle = 1083,
|
||||
MacNSPrintInfo = 1084,
|
||||
WinDevMode = 1085,
|
||||
AutoSaveFilePath = 1086,
|
||||
AutoSaveFormat = 1087,
|
||||
PathInfo = 2000, // 2000-2999: Path Information
|
||||
ClippingPathName = 2999,
|
||||
LightroomWorkflow = 8000,
|
||||
PrintFlagsInfo = 10000
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class for Image Resources
|
||||
/// </summary>
|
||||
internal abstract class ImageResource
|
||||
{
|
||||
private string signature;
|
||||
public string Signature
|
||||
{
|
||||
get { return signature; }
|
||||
set
|
||||
{
|
||||
if (value.Length != 4)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Signature must be 4 characters in length.");
|
||||
}
|
||||
signature = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public abstract ResourceID ID { get; }
|
||||
|
||||
protected ImageResource(string name)
|
||||
{
|
||||
Signature = "8BIM";
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the appropriate subclass of ImageResource.
|
||||
/// </summary>
|
||||
internal static class ImageResourceFactory
|
||||
{
|
||||
public static ImageResource CreateImageResource(PsdBinaryReader reader)
|
||||
{
|
||||
Util.DebugMessage(reader.BaseStream, "Load, Begin, ImageResource");
|
||||
|
||||
var signature = reader.ReadAsciiChars(4);
|
||||
var resourceIdInt = reader.ReadUInt16();
|
||||
var name = reader.ReadPascalString(2);
|
||||
var dataLength = (int)reader.ReadUInt32();
|
||||
|
||||
var dataPaddedLength = Util.RoundUp(dataLength, 2);
|
||||
var endPosition = reader.BaseStream.Position + dataPaddedLength;
|
||||
|
||||
ImageResource resource = null;
|
||||
var resourceId = (ResourceID)resourceIdInt;
|
||||
switch (resourceId)
|
||||
{
|
||||
case ResourceID.ResolutionInfo:
|
||||
resource = new ResolutionInfo(reader, name);
|
||||
break;
|
||||
case ResourceID.ThumbnailRgb:
|
||||
case ResourceID.ThumbnailBgr:
|
||||
resource = new Thumbnail(reader, resourceId, name, dataLength);
|
||||
break;
|
||||
case ResourceID.AlphaChannelNames:
|
||||
resource = new AlphaChannelNames(reader, name, dataLength);
|
||||
break;
|
||||
case ResourceID.UnicodeAlphaNames:
|
||||
resource = new UnicodeAlphaNames(reader, name, dataLength);
|
||||
break;
|
||||
case ResourceID.VersionInfo:
|
||||
resource = new VersionInfo(reader, name);
|
||||
break;
|
||||
default:
|
||||
resource = new RawImageResource(reader, signature, resourceId, name, dataLength);
|
||||
break;
|
||||
}
|
||||
|
||||
Util.DebugMessage(reader.BaseStream, "Load, End, ImageResource, {0}",
|
||||
resourceId);
|
||||
|
||||
// Reposition the reader if we do not consume the full resource block.
|
||||
// This takes care of the even-padding, and also preserves forward-
|
||||
// compatibility in case a resource block is later extended with
|
||||
// additional properties.
|
||||
if (reader.BaseStream.Position < endPosition)
|
||||
reader.BaseStream.Position = endPosition;
|
||||
|
||||
// However, overruns are definitely an error.
|
||||
if (reader.BaseStream.Position > endPosition)
|
||||
throw new PsdInvalidException("Corruption detected in resource.");
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ImageResources : List<ImageResource>
|
||||
{
|
||||
public ImageResources() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public ImageResource Get(ResourceID id)
|
||||
{
|
||||
return Find(x => x.ID == id);
|
||||
}
|
||||
|
||||
public void Set(ImageResource resource)
|
||||
{
|
||||
Predicate<ImageResource> matchId = delegate(ImageResource res)
|
||||
{
|
||||
return res.ID == resource.ID;
|
||||
};
|
||||
var itemIdx = this.FindIndex(matchId);
|
||||
var lastItemIdx = this.FindLastIndex(matchId);
|
||||
|
||||
if (itemIdx == -1)
|
||||
{
|
||||
Add(resource);
|
||||
}
|
||||
else if (itemIdx != lastItemIdx)
|
||||
{
|
||||
RemoveAll(matchId);
|
||||
Insert(itemIdx, resource);
|
||||
}
|
||||
else
|
||||
{
|
||||
this[itemIdx] = resource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2013 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
/// <summary>
|
||||
/// The names of the alpha channels
|
||||
/// </summary>
|
||||
internal class AlphaChannelNames : ImageResource
|
||||
{
|
||||
public override ResourceID ID
|
||||
{
|
||||
get { return ResourceID.AlphaChannelNames; }
|
||||
}
|
||||
|
||||
private List<string> channelNames = new List<string>();
|
||||
public List<string> ChannelNames
|
||||
{
|
||||
get { return channelNames; }
|
||||
}
|
||||
|
||||
public AlphaChannelNames() : base(String.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public AlphaChannelNames(PsdBinaryReader reader, string name, int resourceDataLength)
|
||||
: base(name)
|
||||
{
|
||||
var endPosition = reader.BaseStream.Position + resourceDataLength;
|
||||
|
||||
// Alpha channel names are Pascal strings, with no padding in-between.
|
||||
while (reader.BaseStream.Position < endPosition)
|
||||
{
|
||||
var channelName = reader.ReadPascalString(1);
|
||||
ChannelNames.Add(channelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2013 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using PDNWrapper;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the raw data for unimplemented image resource types.
|
||||
/// </summary>
|
||||
internal class RawImageResource : ImageResource
|
||||
{
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
private ResourceID id;
|
||||
public override ResourceID ID
|
||||
{
|
||||
get { return id; }
|
||||
}
|
||||
|
||||
public RawImageResource(ResourceID resourceId, string name)
|
||||
: base(name)
|
||||
{
|
||||
this.id = resourceId;
|
||||
}
|
||||
|
||||
public RawImageResource(PsdBinaryReader reader, string signature,
|
||||
ResourceID resourceId, string name, int numBytes)
|
||||
: base(name)
|
||||
{
|
||||
this.Signature = signature;
|
||||
this.id = resourceId;
|
||||
Data = reader.ReadBytes(numBytes);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2012 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;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for ResolutionInfo.
|
||||
/// </summary>
|
||||
internal class ResolutionInfo : ImageResource
|
||||
{
|
||||
public override ResourceID ID
|
||||
{
|
||||
get { return ResourceID.ResolutionInfo; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal DPI.
|
||||
/// </summary>
|
||||
public UFixed16_16 HDpi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Vertical DPI.
|
||||
/// </summary>
|
||||
public UFixed16_16 VDpi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 1 = pixels per inch, 2 = pixels per centimeter
|
||||
/// </summary>
|
||||
internal enum ResUnit
|
||||
{
|
||||
PxPerInch = 1,
|
||||
PxPerCm = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display units for horizontal resolution. This only affects the
|
||||
/// user interface; the resolution is still stored in the PSD file
|
||||
/// as pixels/inch.
|
||||
/// </summary>
|
||||
public ResUnit HResDisplayUnit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Display units for vertical resolution.
|
||||
/// </summary>
|
||||
public ResUnit VResDisplayUnit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical units.
|
||||
/// </summary>
|
||||
internal enum Unit
|
||||
{
|
||||
Inches = 1,
|
||||
Centimeters = 2,
|
||||
Points = 3,
|
||||
Picas = 4,
|
||||
Columns = 5
|
||||
}
|
||||
|
||||
public Unit WidthDisplayUnit { get; set; }
|
||||
|
||||
public Unit HeightDisplayUnit { get; set; }
|
||||
|
||||
public ResolutionInfo() : base(String.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public ResolutionInfo(PsdBinaryReader reader, string name)
|
||||
: base(name)
|
||||
{
|
||||
this.HDpi = new UFixed16_16(reader.ReadUInt32());
|
||||
this.HResDisplayUnit = (ResUnit)reader.ReadInt16();
|
||||
this.WidthDisplayUnit = (Unit)reader.ReadInt16();
|
||||
|
||||
this.VDpi = new UFixed16_16(reader.ReadUInt32());
|
||||
this.VResDisplayUnit = (ResUnit)reader.ReadInt16();
|
||||
this.HeightDisplayUnit = (Unit)reader.ReadInt16();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2013 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.IO;
|
||||
using System.Diagnostics;
|
||||
using PDNWrapper;
|
||||
//using PDNWrapper.Imaging;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for Thumbnail.
|
||||
/// </summary>
|
||||
internal class Thumbnail : RawImageResource
|
||||
{
|
||||
public Thumbnail(ResourceID id, string name)
|
||||
: base(id, name)
|
||||
{
|
||||
}
|
||||
|
||||
public Thumbnail(PsdBinaryReader psdReader, ResourceID id, string name, int numBytes)
|
||||
: base(psdReader, "8BIM", id, name, numBytes)
|
||||
{
|
||||
using (var memoryStream = new MemoryStream(Data))
|
||||
using (var reader = new PsdBinaryReader(memoryStream, psdReader))
|
||||
{
|
||||
const int HEADER_LENGTH = 28;
|
||||
var format = reader.ReadUInt32();
|
||||
//var width = reader.ReadUInt32();
|
||||
//var height = reader.ReadUInt32();
|
||||
//var widthBytes = reader.ReadUInt32();
|
||||
//var size = reader.ReadUInt32();
|
||||
//var compressedSize = reader.ReadUInt32();
|
||||
//var bitPerPixel = reader.ReadUInt16();
|
||||
//var planes = reader.ReadUInt16();
|
||||
|
||||
// Raw RGB bitmap
|
||||
if (format == 0)
|
||||
{
|
||||
//Image = new Bitmap((int)width, (int)height, PixelFormat.Format24bppRgb);
|
||||
}
|
||||
// JPEG bitmap
|
||||
else if (format == 1)
|
||||
{
|
||||
byte[] imgData = reader.ReadBytes(numBytes - HEADER_LENGTH);
|
||||
using (MemoryStream stream = new MemoryStream(imgData))
|
||||
{
|
||||
//var bitmap = new Bitmap(stream);
|
||||
//Image = (Bitmap)bitmap.Clone();
|
||||
}
|
||||
|
||||
// Reverse BGR pixels from old thumbnail format
|
||||
if (id == ResourceID.ThumbnailBgr)
|
||||
{
|
||||
//for(int y=0;y<m_thumbnailImage.Height;y++)
|
||||
// for (int x = 0; x < m_thumbnailImage.Width; x++)
|
||||
// {
|
||||
// Color c=m_thumbnailImage.GetPixel(x,y);
|
||||
// Color c2=Color.FromArgb(c.B, c.G, c.R);
|
||||
// m_thumbnailImage.SetPixel(x, y, c);
|
||||
// }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PsdInvalidException("Unknown thumbnail format.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2014 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
/// <summary>
|
||||
/// The names of the alpha channels.
|
||||
/// </summary>
|
||||
internal class UnicodeAlphaNames : ImageResource
|
||||
{
|
||||
public override ResourceID ID
|
||||
{
|
||||
get { return ResourceID.UnicodeAlphaNames; }
|
||||
}
|
||||
|
||||
private List<string> channelNames = new List<string>();
|
||||
public List<string> ChannelNames
|
||||
{
|
||||
get { return channelNames; }
|
||||
}
|
||||
|
||||
public UnicodeAlphaNames()
|
||||
: base(String.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public UnicodeAlphaNames(PsdBinaryReader reader, string name, int resourceDataLength)
|
||||
: base(name)
|
||||
{
|
||||
var endPosition = reader.BaseStream.Position + resourceDataLength;
|
||||
|
||||
while (reader.BaseStream.Position < endPosition)
|
||||
{
|
||||
var channelName = reader.ReadUnicodeString();
|
||||
|
||||
// Photoshop writes out a null terminator for Unicode alpha names.
|
||||
// There is no null terminator on other Unicode strings in PSD files.
|
||||
if (channelName.EndsWith("\0"))
|
||||
{
|
||||
channelName = channelName.Substring(0, channelName.Length - 1);
|
||||
}
|
||||
ChannelNames.Add(channelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2012 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal class VersionInfo : ImageResource
|
||||
{
|
||||
public override ResourceID ID
|
||||
{
|
||||
get { return ResourceID.VersionInfo; }
|
||||
}
|
||||
|
||||
public UInt32 Version { get; set; }
|
||||
|
||||
public bool HasRealMergedData { get; set; }
|
||||
|
||||
public string ReaderName { get; set; }
|
||||
|
||||
public string WriterName { get; set; }
|
||||
|
||||
public UInt32 FileVersion { get; set; }
|
||||
|
||||
|
||||
public VersionInfo() : base(String.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public VersionInfo(PsdBinaryReader reader, string name)
|
||||
: base(name)
|
||||
{
|
||||
Version = reader.ReadUInt32();
|
||||
HasRealMergedData = reader.ReadBoolean();
|
||||
ReaderName = reader.ReadUnicodeString();
|
||||
WriterName = reader.ReadUnicodeString();
|
||||
FileVersion = reader.ReadUInt32();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2014 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.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal class BlendingRanges
|
||||
{
|
||||
/// <summary>
|
||||
/// The layer to which this channel belongs
|
||||
/// </summary>
|
||||
public Layer Layer { get; private set; }
|
||||
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public BlendingRanges(Layer layer)
|
||||
{
|
||||
Layer = layer;
|
||||
Data = new byte[0];
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public BlendingRanges(PsdBinaryReader reader, Layer layer)
|
||||
{
|
||||
Util.DebugMessage(reader.BaseStream, "Load, Begin, BlendingRanges");
|
||||
|
||||
Layer = layer;
|
||||
var dataLength = reader.ReadInt32();
|
||||
if (dataLength <= 0)
|
||||
return;
|
||||
|
||||
Data = reader.ReadBytes(dataLength);
|
||||
|
||||
Util.DebugMessage(reader.BaseStream, "Load, End, BlendingRanges");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,232 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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.Linq;
|
||||
using Unity.Collections;
|
||||
using PhotoshopFile.Compression;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal class ChannelList : List<Channel>
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns channels with nonnegative IDs as an array, so that accessing
|
||||
/// a channel by Id can be optimized into pointer arithmetic rather than
|
||||
/// being implemented as a List scan.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This optimization is crucial for blitting lots of pixels back and
|
||||
/// forth between Photoshop's per-channel representation, and Paint.NET's
|
||||
/// per-pixel BGRA representation.
|
||||
/// </remarks>
|
||||
public Channel[] ToIdArray()
|
||||
{
|
||||
var maxId = this.Max(x => x.ID);
|
||||
var idArray = new Channel[maxId + 1];
|
||||
foreach (var channel in this)
|
||||
{
|
||||
if (channel.ID >= 0)
|
||||
idArray[channel.ID] = channel;
|
||||
}
|
||||
return idArray;
|
||||
}
|
||||
|
||||
public ChannelList()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public Channel GetId(int id)
|
||||
{
|
||||
return this.Single(x => x.ID == id);
|
||||
}
|
||||
|
||||
public bool ContainsId(int id)
|
||||
{
|
||||
return this.Exists(x => x.ID == id);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
[DebuggerDisplay("ID = {ID}")]
|
||||
internal class Channel
|
||||
{
|
||||
/// <summary>
|
||||
/// The layer to which this channel belongs
|
||||
/// </summary>
|
||||
public Layer Layer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Channel ID.
|
||||
/// <list type="bullet">
|
||||
/// <item>-1 = transparency mask</item>
|
||||
/// <item>-2 = user-supplied layer mask, or vector mask</item>
|
||||
/// <item>-3 = user-supplied layer mask, if channel -2 contains a vector mask</item>
|
||||
/// <item>
|
||||
/// Nonnegative channel IDs give the actual image channels, in the
|
||||
/// order defined by the colormode. For example, 0, 1, 2 = R, G, B.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public short ID { get; set; }
|
||||
|
||||
public Rectangle Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (ID)
|
||||
{
|
||||
case -2:
|
||||
return Layer.Masks.LayerMask.Rect;
|
||||
case -3:
|
||||
return Layer.Masks.UserMask.Rect;
|
||||
default:
|
||||
return Layer.Rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Total length of the channel data, including compression headers.
|
||||
/// </summary>
|
||||
public long Length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Raw image data for this color channel, in compressed on-disk format.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If null, the ImageData will be automatically compressed during save.
|
||||
/// </remarks>
|
||||
public byte[] ImageDataRaw { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decompressed image data for this color channel.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When making changes to the ImageData, set ImageDataRaw to null so that
|
||||
/// the correct data will be compressed during save.
|
||||
/// </remarks>
|
||||
public NativeArray<byte> ImageData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Image compression method used.
|
||||
/// </summary>
|
||||
public ImageCompression ImageCompression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RLE-compressed length of each row.
|
||||
/// </summary>
|
||||
public RleRowLengths RleRowLengths { get; set; }
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
internal Channel(short id, Layer layer)
|
||||
{
|
||||
ID = id;
|
||||
Layer = layer;
|
||||
}
|
||||
|
||||
internal Channel(PsdBinaryReader reader, Layer layer)
|
||||
{
|
||||
Util.DebugMessage(reader.BaseStream, "Load, Begin, Channel");
|
||||
|
||||
ID = reader.ReadInt16();
|
||||
Length = (layer.PsdFile.IsLargeDocument)
|
||||
? reader.ReadInt64()
|
||||
: reader.ReadInt32();
|
||||
Layer = layer;
|
||||
|
||||
Util.DebugMessage(reader.BaseStream, "Load, End, Channel, {0}", ID);
|
||||
}
|
||||
|
||||
internal void Cleanup()
|
||||
{
|
||||
if (ImageData.IsCreated)
|
||||
ImageData.Dispose();
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
internal void LoadPixelData(PsdBinaryReader reader)
|
||||
{
|
||||
Util.DebugMessage(reader.BaseStream, "Load, Begin, Channel image");
|
||||
|
||||
if (Length == 0)
|
||||
{
|
||||
ImageCompression = ImageCompression.Raw;
|
||||
ImageDataRaw = new byte[0];
|
||||
return;
|
||||
}
|
||||
|
||||
var endPosition = reader.BaseStream.Position + this.Length;
|
||||
ImageCompression = (ImageCompression)reader.ReadInt16();
|
||||
var longDataLength = this.Length - 2;
|
||||
Util.CheckByteArrayLength(longDataLength);
|
||||
var dataLength = (int)longDataLength;
|
||||
|
||||
switch (ImageCompression)
|
||||
{
|
||||
case ImageCompression.Raw:
|
||||
ImageDataRaw = reader.ReadBytes(dataLength);
|
||||
break;
|
||||
case ImageCompression.Rle:
|
||||
// RLE row lengths
|
||||
RleRowLengths = new RleRowLengths(reader, Rect.Height, Layer.PsdFile.IsLargeDocument);
|
||||
var rleDataLength = (int)(endPosition - reader.BaseStream.Position);
|
||||
Debug.Assert(rleDataLength == RleRowLengths.Total,
|
||||
"RLE row lengths do not sum to length of channel image data.");
|
||||
|
||||
// The PSD specification states that rows are padded to even sizes.
|
||||
// However, Photoshop doesn't actually do this. RLE rows can have
|
||||
// odd lengths in the header, and there is no padding between rows.
|
||||
ImageDataRaw = reader.ReadBytes(rleDataLength);
|
||||
break;
|
||||
case ImageCompression.Zip:
|
||||
case ImageCompression.ZipPrediction:
|
||||
ImageDataRaw = reader.ReadBytes(dataLength);
|
||||
break;
|
||||
}
|
||||
|
||||
Util.DebugMessage(reader.BaseStream, "Load, End, Channel image, {0}", ID, Layer.Name);
|
||||
Debug.Assert(reader.BaseStream.Position == endPosition, "Pixel data was not fully read in.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the raw image data from the compressed on-disk format into
|
||||
/// an uncompressed bitmap, in native byte order.
|
||||
/// </summary>
|
||||
public void DecodeImageData()
|
||||
{
|
||||
if ((ImageCompression == ImageCompression.Raw) && (Layer.PsdFile.BitDepth <= 8))
|
||||
{
|
||||
ImageData = new NativeArray<byte>(ImageDataRaw, Allocator.TempJob);
|
||||
return;
|
||||
}
|
||||
|
||||
var image = ImageDataFactory.Create(this, ImageDataRaw);
|
||||
var longLength = (long)image.BytesPerRow * Rect.Height;
|
||||
Util.CheckByteArrayLength(longLength);
|
||||
var LocalImageData = new byte[longLength];
|
||||
image.Read(LocalImageData);
|
||||
ImageData = new NativeArray<byte>(LocalImageData, Allocator.TempJob);
|
||||
ImageDataRaw = null; // no longer needed.
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,240 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using PDNWrapper;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
[DebuggerDisplay("Name = {Name}")]
|
||||
internal class Layer
|
||||
{
|
||||
internal PsdFile PsdFile { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rectangle containing the contents of the layer.
|
||||
/// </summary>
|
||||
public Rectangle Rect { get; set; }
|
||||
|
||||
public bool IsGroup { get; set; }
|
||||
public bool IsEndGroupMarker { get; set; }
|
||||
public Layer ParentLayer {get; set; }
|
||||
// ID from Key "lyid"
|
||||
public int LayerID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Image channels.
|
||||
/// </summary>
|
||||
public ChannelList Channels { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns alpha channel if it exists, otherwise null.
|
||||
/// </summary>
|
||||
public Channel AlphaChannel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Channels.ContainsId(-1))
|
||||
return Channels.GetId(-1);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string blendModeKey;
|
||||
/// <summary>
|
||||
/// Photoshop blend mode key for the layer
|
||||
/// </summary>
|
||||
public string BlendModeKey
|
||||
{
|
||||
get { return blendModeKey; }
|
||||
set
|
||||
{
|
||||
if (value.Length != 4)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"BlendModeKey must be 4 characters in length.");
|
||||
}
|
||||
blendModeKey = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 0 = transparent ... 255 = opaque
|
||||
/// </summary>
|
||||
public byte Opacity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// false = base, true = non-base
|
||||
/// </summary>
|
||||
public bool Clipping { get; set; }
|
||||
|
||||
private static int protectTransBit = BitVector32.CreateMask();
|
||||
private static int visibleBit = BitVector32.CreateMask(protectTransBit);
|
||||
BitVector32 flags = new BitVector32();
|
||||
|
||||
/// <summary>
|
||||
/// If true, the layer is visible.
|
||||
/// </summary>
|
||||
public bool Visible
|
||||
{
|
||||
get { return !flags[visibleBit]; }
|
||||
set { flags[visibleBit] = !value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protect the transparency
|
||||
/// </summary>
|
||||
public bool ProtectTrans
|
||||
{
|
||||
get { return flags[protectTransBit]; }
|
||||
set { flags[protectTransBit] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The descriptive layer name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
public BlendingRanges BlendingRangesData { get; set; }
|
||||
|
||||
public MaskInfo Masks { get; set; }
|
||||
|
||||
public List<LayerInfo> AdditionalInfo { get; set; }
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Layer(PsdFile psdFile)
|
||||
{
|
||||
PsdFile = psdFile;
|
||||
Rect = Rectangle.Empty;
|
||||
Channels = new ChannelList();
|
||||
BlendModeKey = PsdBlendMode.Normal;
|
||||
AdditionalInfo = new List<LayerInfo>();
|
||||
IsGroup = false;
|
||||
ParentLayer = null;
|
||||
IsEndGroupMarker = false;
|
||||
}
|
||||
|
||||
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, this.PsdFile, false, adjustmentLayerEndPos);
|
||||
AdditionalInfo.Add(layerInfo);
|
||||
}
|
||||
|
||||
foreach (var adjustmentInfo in AdditionalInfo)
|
||||
{
|
||||
switch (adjustmentInfo.Key)
|
||||
{
|
||||
case "luni":
|
||||
Name = ((LayerUnicodeName)adjustmentInfo).Name;
|
||||
break;
|
||||
case "lyid":
|
||||
LayerID = ((LayerId)adjustmentInfo).ID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Util.DebugMessage(reader.BaseStream, "Load, End, Layer, {0}", Name);
|
||||
|
||||
PsdFile.LoadContext.OnLoadLayerHeader(this);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Create ImageData for any missing channels.
|
||||
/// </summary>
|
||||
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 NativeArray<byte>(size, Allocator.TempJob);
|
||||
unsafe
|
||||
{
|
||||
UnsafeUtility.MemSet(ch.ImageData.GetUnsafePtr(), (byte)255, size);
|
||||
}
|
||||
this.Channels.Add(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,168 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal static class LayerInfoFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads the next LayerInfo record.
|
||||
/// </summary>
|
||||
/// <param name="reader">The file reader</param>
|
||||
/// <param name="psdFile">The PSD file.</param>
|
||||
/// <param name="globalLayerInfo">True if the LayerInfo record is being
|
||||
/// loaded from the end of the Layer and Mask Information section;
|
||||
/// false if it is being loaded from the end of a Layer record.</param>
|
||||
public static LayerInfo Load(PsdBinaryReader reader, PsdFile psdFile,
|
||||
bool globalLayerInfo, long fileEndPos)
|
||||
{
|
||||
Util.DebugMessage(reader.BaseStream, "Load, Begin, LayerInfo");
|
||||
|
||||
// Some keys use a signature of 8B64, but the identity of these keys
|
||||
// is undocumented. We will therefore accept either signature.
|
||||
var signature = reader.ReadAsciiChars(4);
|
||||
if ((signature != "8BIM") && (signature != "8B64"))
|
||||
{
|
||||
throw new PsdInvalidException(
|
||||
"LayerInfo signature invalid, must be 8BIM or 8B64.");
|
||||
}
|
||||
|
||||
var key = reader.ReadAsciiChars(4);
|
||||
var hasLongLength = LayerInfoUtil.HasLongLength(key, psdFile.IsLargeDocument);
|
||||
LayerInfo result = new RawLayerInfo("dummy");
|
||||
bool breakFromLoop = false;
|
||||
while (!breakFromLoop)
|
||||
{
|
||||
var baseStartPosition = reader.BaseStream.Position;
|
||||
var length = hasLongLength
|
||||
? reader.ReadInt64()
|
||||
: reader.ReadInt32();
|
||||
var startPosition = reader.BaseStream.Position;
|
||||
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case "Layr":
|
||||
case "Lr16":
|
||||
case "Lr32":
|
||||
result = new InfoLayers(reader, psdFile, key, length);
|
||||
break;
|
||||
case "lsct":
|
||||
case "lsdk":
|
||||
result = new LayerSectionInfo(reader, key, (int)length);
|
||||
break;
|
||||
case "luni":
|
||||
result = new LayerUnicodeName(reader);
|
||||
break;
|
||||
case "lyid":
|
||||
result = new LayerId(reader, key, length);
|
||||
break;
|
||||
default:
|
||||
result = new RawLayerInfo(reader, signature, key, length);
|
||||
break;
|
||||
}
|
||||
|
||||
// May have additional padding applied.
|
||||
var endPosition = startPosition + length;
|
||||
if (reader.BaseStream.Position < endPosition)
|
||||
reader.BaseStream.Position = endPosition;
|
||||
|
||||
// Documentation states that the length is even-padded. Actually:
|
||||
// 1. Most keys have 4-padded lengths.
|
||||
// 2. However, some keys (LMsk) have even-padded lengths.
|
||||
// 3. Other keys (Txt2, Lr16, Lr32) have unpadded lengths.
|
||||
//
|
||||
// Photoshop writes data that is always 4-padded, even when the stated
|
||||
// length is not a multiple of 4. The length mismatch seems to occur
|
||||
// only on global layer info. We do not read extra padding in other
|
||||
// cases because third-party programs are likely to follow the spec.
|
||||
|
||||
if (globalLayerInfo)
|
||||
{
|
||||
reader.ReadPadding(startPosition, 4);
|
||||
}
|
||||
|
||||
//try if we can read the next signature
|
||||
if (reader.BaseStream.Position < fileEndPos)
|
||||
{
|
||||
var nowPosition = reader.BaseStream.Position;
|
||||
signature = reader.ReadAsciiChars(4);
|
||||
if ((signature != "8BIM") && (signature != "8B64"))
|
||||
{
|
||||
hasLongLength = true;
|
||||
reader.BaseStream.Position = baseStartPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.BaseStream.Position = nowPosition;
|
||||
breakFromLoop = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
breakFromLoop = true;
|
||||
}
|
||||
|
||||
|
||||
Util.DebugMessage(reader.BaseStream, "Load, End, LayerInfo, {0}, {1}",
|
||||
result.Signature, result.Key);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class LayerInfoUtil
|
||||
{
|
||||
internal static bool HasLongLength(string key, bool isLargeDocument)
|
||||
{
|
||||
if (!isLargeDocument)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//return false;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case "LMsk":
|
||||
case "Lr16":
|
||||
case "Lr32":
|
||||
case "Layr":
|
||||
case "Mt16":
|
||||
case "Mt32":
|
||||
case "Mtrn":
|
||||
case "Alph":
|
||||
case "FMsk":
|
||||
case "lnk2":
|
||||
case "FEid":
|
||||
case "FXid":
|
||||
case "PxSD":
|
||||
case "lnkE": // Undocumented
|
||||
case "extn": // Undocumented
|
||||
case "cinf": // Undocumented
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class LayerInfo
|
||||
{
|
||||
public abstract string Signature { get; }
|
||||
|
||||
public abstract string Key { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Layers that are stored as Additional Info, rather than in the main
|
||||
/// Layers section of the PSD file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Photoshop stores layers in the Additional Info section for 16-bit and
|
||||
/// 32-bit depth images. The Layers section in the PSD file is left empty.
|
||||
///
|
||||
/// This appears to be for backward-compatibility purposes, but it is not
|
||||
/// required. Photoshop will successfully load a high-bitdepth image that
|
||||
/// puts the layers in the Layers section.
|
||||
/// </remarks>
|
||||
internal class InfoLayers : LayerInfo
|
||||
{
|
||||
public override string Signature
|
||||
{
|
||||
get { return "8BIM"; }
|
||||
}
|
||||
|
||||
private string key;
|
||||
public override string Key
|
||||
{
|
||||
get { return key; }
|
||||
}
|
||||
|
||||
public PsdFile PsdFile { get; set; }
|
||||
|
||||
public InfoLayers(PsdFile psdFile, string key)
|
||||
{
|
||||
PsdFile = psdFile;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
// The key does not have to match the bit depth, but it does have to
|
||||
// be one of the known values.
|
||||
case "Layr":
|
||||
case "Lr16":
|
||||
case "Lr32":
|
||||
this.key = key;
|
||||
break;
|
||||
default:
|
||||
throw new PsdInvalidException(
|
||||
"InfoLayers key must be Layr, Lr16, or Lr32.");
|
||||
}
|
||||
}
|
||||
|
||||
public InfoLayers(PsdBinaryReader reader, PsdFile psdFile,
|
||||
string key, long dataLength)
|
||||
: this(psdFile, key)
|
||||
{
|
||||
if (psdFile.Layers.Count > 0)
|
||||
{
|
||||
throw new PsdInvalidException(
|
||||
"Cannot have both regular layers and Additional Info layers");
|
||||
}
|
||||
|
||||
var endPosition = reader.BaseStream.Position + dataLength;
|
||||
psdFile.LoadLayers(reader, false);
|
||||
|
||||
if (reader.BaseStream.Position != endPosition)
|
||||
{
|
||||
throw new PsdInvalidException(
|
||||
"Incorrect length for InfoLayers.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// Author : leoyaik@unity3d.com
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Layers that are stored as Additional Info, rather than in the main
|
||||
/// Layers section of the PSD file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Photoshop stores layers in the Additional Info section for 16-bit and
|
||||
/// 32-bit depth images. The Layers section in the PSD file is left empty.
|
||||
///
|
||||
/// This appears to be for backward-compatibility purposes, but it is not
|
||||
/// required. Photoshop will successfully load a high-bitdepth image that
|
||||
/// puts the layers in the Layers section.
|
||||
/// </remarks>
|
||||
internal class LayerId : LayerInfo
|
||||
{
|
||||
public override string Signature
|
||||
{
|
||||
get { return "8BIM"; }
|
||||
}
|
||||
|
||||
private string key;
|
||||
public override string Key
|
||||
{
|
||||
get { return key; }
|
||||
}
|
||||
|
||||
private int id = 0;
|
||||
public int ID
|
||||
{
|
||||
get { return id; }
|
||||
}
|
||||
|
||||
|
||||
public LayerId(string key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
// The key does not have to match the bit depth, but it does have to
|
||||
// be one of the known values.
|
||||
case "lyid":
|
||||
case "lnsr":
|
||||
this.key = key;
|
||||
break;
|
||||
default:
|
||||
throw new PsdInvalidException(
|
||||
"LayerId key should be lyid or lnsr");
|
||||
}
|
||||
}
|
||||
|
||||
public LayerId(PsdBinaryReader reader,
|
||||
string key, long dataLength)
|
||||
: this(key)
|
||||
{
|
||||
if (dataLength == 4)
|
||||
id = reader.ReadInt32();
|
||||
else
|
||||
throw new PsdInvalidException("LayerId data length should be 4");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2015 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal enum LayerSectionType
|
||||
{
|
||||
Layer = 0,
|
||||
OpenFolder = 1,
|
||||
ClosedFolder = 2,
|
||||
SectionDivider = 3
|
||||
}
|
||||
|
||||
internal enum LayerSectionSubtype
|
||||
{
|
||||
Normal = 0,
|
||||
SceneGroup = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer sections are known as Groups in the Photoshop UI.
|
||||
/// </summary>
|
||||
internal class LayerSectionInfo : LayerInfo
|
||||
{
|
||||
public override string Signature
|
||||
{
|
||||
get { return "8BIM"; }
|
||||
}
|
||||
|
||||
private string key;
|
||||
public override string Key
|
||||
{
|
||||
get { return key; }
|
||||
}
|
||||
|
||||
public LayerSectionType SectionType { get; set; }
|
||||
|
||||
private LayerSectionSubtype? subtype;
|
||||
public LayerSectionSubtype Subtype
|
||||
{
|
||||
get { return subtype ?? LayerSectionSubtype.Normal; }
|
||||
set { subtype = value; }
|
||||
}
|
||||
|
||||
private string blendModeKey;
|
||||
public string BlendModeKey
|
||||
{
|
||||
get { return blendModeKey; }
|
||||
set
|
||||
{
|
||||
if (value.Length != 4)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"BlendModeKey must be 4 characters in length.");
|
||||
}
|
||||
blendModeKey = value;
|
||||
}
|
||||
}
|
||||
|
||||
public LayerSectionInfo(PsdBinaryReader reader, string key, int dataLength)
|
||||
{
|
||||
// The key for layer section info is documented to be "lsct". However,
|
||||
// some Photoshop files use the undocumented key "lsdk", with apparently
|
||||
// the same data format.
|
||||
this.key = key;
|
||||
|
||||
SectionType = (LayerSectionType)reader.ReadInt32();
|
||||
if (dataLength >= 12)
|
||||
{
|
||||
var signature = reader.ReadAsciiChars(4);
|
||||
if (signature != "8BIM")
|
||||
throw new PsdInvalidException("Invalid section divider signature.");
|
||||
|
||||
BlendModeKey = reader.ReadAsciiChars(4);
|
||||
if (dataLength >= 16)
|
||||
{
|
||||
Subtype = (LayerSectionSubtype)reader.ReadInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2014 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal class LayerUnicodeName : LayerInfo
|
||||
{
|
||||
public override string Signature
|
||||
{
|
||||
get { return "8BIM"; }
|
||||
}
|
||||
|
||||
public override string Key
|
||||
{
|
||||
get { return "luni"; }
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public LayerUnicodeName(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public LayerUnicodeName(PsdBinaryReader reader)
|
||||
{
|
||||
Name = reader.ReadUnicodeString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2014 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
[DebuggerDisplay("Layer Info: { key }")]
|
||||
internal class RawLayerInfo : LayerInfo
|
||||
{
|
||||
private string signature;
|
||||
public override string Signature
|
||||
{
|
||||
get { return signature; }
|
||||
}
|
||||
|
||||
private string key;
|
||||
public override string Key
|
||||
{
|
||||
get { return key; }
|
||||
}
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
public RawLayerInfo(string key, string signature = "8BIM")
|
||||
{
|
||||
this.signature = signature;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public RawLayerInfo(PsdBinaryReader reader, string signature, string key,
|
||||
long dataLength)
|
||||
{
|
||||
this.signature = signature;
|
||||
this.key = key;
|
||||
|
||||
Util.CheckByteArrayLength(dataLength);
|
||||
Data = reader.ReadBytes((int)dataLength);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,149 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2014 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.Specialized;
|
||||
using System.Diagnostics;
|
||||
using PDNWrapper;
|
||||
using System.Globalization;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal class Mask
|
||||
{
|
||||
/// <summary>
|
||||
/// The layer to which this mask belongs
|
||||
/// </summary>
|
||||
public Layer Layer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rectangle enclosing the mask.
|
||||
/// </summary>
|
||||
public Rectangle Rect { get; set; }
|
||||
|
||||
private byte backgroundColor;
|
||||
public byte BackgroundColor
|
||||
{
|
||||
get { return backgroundColor; }
|
||||
set
|
||||
{
|
||||
if ((value != 0) && (value != 255))
|
||||
throw new PsdInvalidException("Mask background must be fully-opaque or fully-transparent.");
|
||||
backgroundColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static int positionVsLayerBit = BitVector32.CreateMask();
|
||||
private static int disabledBit = BitVector32.CreateMask(positionVsLayerBit);
|
||||
private static int invertOnBlendBit = BitVector32.CreateMask(disabledBit);
|
||||
|
||||
private BitVector32 flags;
|
||||
public BitVector32 Flags { get { return flags; } }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the position of the mask is relative to the layer.
|
||||
/// </summary>
|
||||
public bool PositionVsLayer
|
||||
{
|
||||
get { return flags[positionVsLayerBit]; }
|
||||
set { flags[positionVsLayerBit] = value; }
|
||||
}
|
||||
|
||||
public bool Disabled
|
||||
{
|
||||
get { return flags[disabledBit]; }
|
||||
set { flags[disabledBit] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// if true, invert the mask when blending.
|
||||
/// </summary>
|
||||
public bool InvertOnBlend
|
||||
{
|
||||
get { return flags[invertOnBlendBit]; }
|
||||
set { flags[invertOnBlendBit] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mask image data.
|
||||
/// </summary>
|
||||
public NativeArray<byte> ImageData { get; set; }
|
||||
|
||||
public Mask(Layer layer)
|
||||
{
|
||||
Layer = layer;
|
||||
this.flags = new BitVector32();
|
||||
}
|
||||
|
||||
public Mask(Layer layer, Rectangle rect, byte color, BitVector32 flags)
|
||||
{
|
||||
Layer = layer;
|
||||
Rect = rect;
|
||||
BackgroundColor = color;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mask info for a layer. Contains both the layer and user masks.
|
||||
/// </summary>
|
||||
internal class MaskInfo
|
||||
{
|
||||
public Mask LayerMask { get; set; }
|
||||
|
||||
public Mask UserMask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct MaskInfo with null masks.
|
||||
/// </summary>
|
||||
public MaskInfo()
|
||||
{
|
||||
}
|
||||
|
||||
public MaskInfo(PsdBinaryReader reader, Layer layer)
|
||||
{
|
||||
Util.DebugMessage(reader.BaseStream, "Load, Begin, MaskInfo");
|
||||
|
||||
var maskLength = reader.ReadUInt32();
|
||||
if (maskLength <= 0)
|
||||
return;
|
||||
|
||||
var startPosition = reader.BaseStream.Position;
|
||||
var endPosition = startPosition + maskLength;
|
||||
|
||||
// Read layer mask
|
||||
var rectangle = reader.ReadRectangle();
|
||||
var backgroundColor = reader.ReadByte();
|
||||
var flagsByte = reader.ReadByte();
|
||||
LayerMask = new Mask(layer, rectangle, backgroundColor, new BitVector32(flagsByte));
|
||||
|
||||
// User mask is supplied separately when there is also a vector mask.
|
||||
if (maskLength == 36)
|
||||
{
|
||||
var userFlagsByte = reader.ReadByte();
|
||||
var userBackgroundColor = reader.ReadByte();
|
||||
var userRectangle = reader.ReadRectangle();
|
||||
UserMask = new Mask(layer, userRectangle, userBackgroundColor, new BitVector32(userFlagsByte));
|
||||
}
|
||||
|
||||
// 20-byte mask data will end with padding.
|
||||
reader.BaseStream.Position = endPosition;
|
||||
|
||||
Util.DebugMessage(reader.BaseStream, "Load, End, MaskInfo");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains settings and callbacks that affect the loading of a PSD file.
|
||||
/// </summary>
|
||||
internal class LoadContext
|
||||
{
|
||||
public Encoding Encoding { get; set; }
|
||||
|
||||
public LoadContext()
|
||||
{
|
||||
Encoding = Encoding.Default;
|
||||
}
|
||||
|
||||
public virtual void OnLoadLayersHeader(PsdFile psdFile)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnLoadLayerHeader(Layer layer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,229 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2013 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 PDNWrapper;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads PSD data types in big-endian byte order.
|
||||
/// </summary>
|
||||
internal class PsdBinaryReader : IDisposable
|
||||
{
|
||||
private BinaryReader reader;
|
||||
private Encoding encoding;
|
||||
|
||||
public Stream BaseStream
|
||||
{
|
||||
get { return reader.BaseStream; }
|
||||
}
|
||||
|
||||
public PsdBinaryReader(Stream stream, PsdBinaryReader reader)
|
||||
: this(stream, reader.encoding)
|
||||
{
|
||||
}
|
||||
|
||||
public PsdBinaryReader(Stream stream, Encoding encoding)
|
||||
{
|
||||
this.encoding = encoding;
|
||||
|
||||
// ReadPascalString and ReadUnicodeString handle encoding explicitly.
|
||||
// BinaryReader.ReadString() is never called, so it is constructed with
|
||||
// ASCII encoding to make accidental usage obvious.
|
||||
reader = new BinaryReader(stream, Encoding.ASCII);
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
return reader.ReadByte();
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(int count)
|
||||
{
|
||||
return reader.ReadBytes(count);
|
||||
}
|
||||
|
||||
public bool ReadBoolean()
|
||||
{
|
||||
return reader.ReadBoolean();
|
||||
}
|
||||
|
||||
public Int16 ReadInt16()
|
||||
{
|
||||
var val = reader.ReadInt16();
|
||||
byte[] b = BitConverter.GetBytes(val);
|
||||
{
|
||||
Util.SwapBytes(b, 0, 2);
|
||||
}
|
||||
val = BitConverter.ToInt16(b, 0);
|
||||
return val;
|
||||
}
|
||||
|
||||
public Int32 ReadInt32()
|
||||
{
|
||||
var val = reader.ReadInt32();
|
||||
byte[] b = BitConverter.GetBytes(val);
|
||||
{
|
||||
Util.SwapBytes(b, 0, 4);
|
||||
}
|
||||
val = BitConverter.ToInt32(b, 0);
|
||||
return val;
|
||||
}
|
||||
|
||||
public Int64 ReadInt64()
|
||||
{
|
||||
var val = reader.ReadInt64();
|
||||
var b = BitConverter.GetBytes(val);
|
||||
{
|
||||
Util.SwapBytes(b, 0, 8);
|
||||
}
|
||||
val = BitConverter.ToInt64(b, 0);
|
||||
return val;
|
||||
}
|
||||
|
||||
public UInt16 ReadUInt16()
|
||||
{
|
||||
var val = reader.ReadUInt16();
|
||||
var b = BitConverter.GetBytes(val);
|
||||
{
|
||||
Util.SwapBytes(b, 0, 2);
|
||||
}
|
||||
val = BitConverter.ToUInt16(b, 0);
|
||||
return val;
|
||||
}
|
||||
|
||||
public UInt32 ReadUInt32()
|
||||
{
|
||||
var val = reader.ReadUInt32();
|
||||
var b = BitConverter.GetBytes(val);
|
||||
{
|
||||
Util.SwapBytes(b, 0, 4);
|
||||
}
|
||||
val = BitConverter.ToUInt32(b, 0);
|
||||
return val;
|
||||
}
|
||||
|
||||
public UInt64 ReadUInt64()
|
||||
{
|
||||
var val = reader.ReadUInt64();
|
||||
var b = BitConverter.GetBytes(val);
|
||||
{
|
||||
Util.SwapBytes(b, 0, 8);
|
||||
}
|
||||
val = BitConverter.ToUInt64(b, 0);
|
||||
return val;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Read padding to get to the byte multiple for the block.
|
||||
/// </summary>
|
||||
/// <param name="startPosition">Starting position of the padded block.</param>
|
||||
/// <param name="padMultiple">Byte multiple that the block is padded to.</param>
|
||||
public void ReadPadding(long startPosition, int padMultiple)
|
||||
{
|
||||
// Pad to specified byte multiple
|
||||
var totalLength = reader.BaseStream.Position - startPosition;
|
||||
var padBytes = Util.GetPadding((int)totalLength, padMultiple);
|
||||
ReadBytes(padBytes);
|
||||
}
|
||||
|
||||
public Rectangle ReadRectangle()
|
||||
{
|
||||
var rect = new Rectangle();
|
||||
rect.Y = ReadInt32();
|
||||
rect.X = ReadInt32();
|
||||
rect.Height = ReadInt32() - rect.Y;
|
||||
rect.Width = ReadInt32() - rect.X;
|
||||
return rect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a fixed-length ASCII string.
|
||||
/// </summary>
|
||||
public string ReadAsciiChars(int count)
|
||||
{
|
||||
var bytes = reader.ReadBytes(count);
|
||||
var s = Encoding.ASCII.GetString(bytes);
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Pascal string using the specified encoding.
|
||||
/// </summary>
|
||||
/// <param name="padMultiple">Byte multiple that the Pascal string is padded to.</param>
|
||||
public string ReadPascalString(int padMultiple)
|
||||
{
|
||||
var startPosition = reader.BaseStream.Position;
|
||||
|
||||
byte stringLength = ReadByte();
|
||||
var bytes = ReadBytes(stringLength);
|
||||
ReadPadding(startPosition, padMultiple);
|
||||
|
||||
// Default decoder uses best-fit fallback, so it will not throw any
|
||||
// exceptions if unknown characters are encountered.
|
||||
var str = encoding.GetString(bytes);
|
||||
return str;
|
||||
}
|
||||
|
||||
public string ReadUnicodeString()
|
||||
{
|
||||
var numChars = ReadInt32();
|
||||
var length = 2 * numChars;
|
||||
var data = ReadBytes(length);
|
||||
var str = Encoding.BigEndianUnicode.GetString(data, 0, length);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
#region IDisposable
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
// Check to see if Dispose has already been called.
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (reader != null)
|
||||
{
|
||||
// BinaryReader.Dispose() is protected.
|
||||
reader.Close();
|
||||
reader = null;
|
||||
}
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2012 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal static class PsdBlendMode
|
||||
{
|
||||
public const string Normal = "norm";
|
||||
public const string Darken = "dark";
|
||||
public const string Lighten = "lite";
|
||||
public const string Hue = "hue ";
|
||||
public const string Saturation = "sat ";
|
||||
public const string Color = "colr";
|
||||
public const string Luminosity = "lum ";
|
||||
public const string Multiply = "mul ";
|
||||
public const string Screen = "scrn";
|
||||
public const string Dissolve = "diss";
|
||||
public const string Overlay = "over";
|
||||
public const string HardLight = "hLit";
|
||||
public const string SoftLight = "sLit";
|
||||
public const string Difference = "diff";
|
||||
public const string Exclusion = "smud";
|
||||
public const string ColorDodge = "div ";
|
||||
public const string ColorBurn = "idiv";
|
||||
public const string LinearBurn = "lbrn";
|
||||
public const string LinearDodge = "lddg";
|
||||
public const string VividLight = "vLit";
|
||||
public const string LinearLight = "lLit";
|
||||
public const string PinLight = "pLit";
|
||||
public const string HardMix = "hMix";
|
||||
public const string PassThrough = "pass";
|
||||
public const string DarkerColor = "dkCl";
|
||||
public const string LighterColor = "lgCl";
|
||||
public const string Subtract = "fsub";
|
||||
public const string Divide = "fdiv";
|
||||
}
|
||||
}
|
@@ -0,0 +1,708 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2013 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.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal class RleReader
|
||||
{
|
||||
private Stream stream;
|
||||
|
||||
public RleReader(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a PackBits RLE stream.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Output buffer for decoded data.</param>
|
||||
/// <param name="offset">Offset at which to begin writing.</param>
|
||||
/// <param name="count">Number of bytes to decode from the stream.</param>
|
||||
public int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (!Util.CheckBufferBounds(buffer, offset, count))
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
// Pin the entire buffer now, so that we don't keep pinning and unpinning
|
||||
// for each RLE packet.
|
||||
var ptrBuffer = buffer;
|
||||
//fixed (byte* ptrBuffer = &buffer[0])
|
||||
{
|
||||
int bytesLeft = count;
|
||||
int bufferIdx = offset;
|
||||
while (bytesLeft > 0)
|
||||
{
|
||||
// ReadByte returns an unsigned byte, but we want a signed byte.
|
||||
var flagCounter = unchecked((sbyte)stream.ReadByte());
|
||||
|
||||
// Raw packet
|
||||
if (flagCounter > 0)
|
||||
{
|
||||
var readLength = flagCounter + 1;
|
||||
if (bytesLeft < readLength)
|
||||
throw new RleException("Raw packet overruns the decode window.");
|
||||
|
||||
stream.Read(buffer, bufferIdx, readLength);
|
||||
|
||||
bufferIdx += readLength;
|
||||
bytesLeft -= readLength;
|
||||
}
|
||||
// RLE packet
|
||||
else if (flagCounter > -128)
|
||||
{
|
||||
var runLength = 1 - flagCounter;
|
||||
var byteValue = (byte)stream.ReadByte();
|
||||
if (runLength > bytesLeft)
|
||||
throw new RleException("RLE packet overruns the decode window.");
|
||||
|
||||
//byte* ptr = ptrBuffer + bufferIdx;
|
||||
//byte* ptrEnd = ptr + runLength;
|
||||
//while (ptr < ptrEnd)
|
||||
//{
|
||||
// *ptr = byteValue;
|
||||
// ptr++;
|
||||
//}
|
||||
|
||||
int start = 0;
|
||||
while (start < runLength)
|
||||
{
|
||||
ptrBuffer[bufferIdx + start] = byteValue;
|
||||
start++;
|
||||
}
|
||||
|
||||
bufferIdx += runLength;
|
||||
bytesLeft -= runLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The canonical PackBits algorithm will never emit 0x80 (-128), but
|
||||
// some programs do. Simply skip over the byte.
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Assert(bytesLeft == 0);
|
||||
return count - bytesLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2014 Tao Yue
|
||||
//
|
||||
// See LICENSE.txt for complete licensing and attribution information.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal class RleRowLengths
|
||||
{
|
||||
public int[] Values { get; private set; }
|
||||
|
||||
public long Total
|
||||
{
|
||||
get { return Values.Sum(x => (long)x); }
|
||||
}
|
||||
|
||||
public int this[int i]
|
||||
{
|
||||
get { return Values[i]; }
|
||||
set { Values[i] = value; }
|
||||
}
|
||||
|
||||
public RleRowLengths(int rowCount)
|
||||
{
|
||||
Values = new int[rowCount];
|
||||
}
|
||||
|
||||
public RleRowLengths(PsdBinaryReader reader, int rowCount, bool isLargeDocument)
|
||||
: this(rowCount)
|
||||
{
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
Values[i] = isLargeDocument
|
||||
? reader.ReadInt32()
|
||||
: reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,230 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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-2015 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.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal class RleWriter
|
||||
{
|
||||
private int maxPacketLength = 128;
|
||||
|
||||
// Current task
|
||||
private object rleLock;
|
||||
private Stream stream;
|
||||
private byte[] data;
|
||||
private int offset;
|
||||
|
||||
// Current packet
|
||||
private bool isRepeatPacket;
|
||||
private int idxPacketStart;
|
||||
private int packetLength;
|
||||
|
||||
private byte runValue;
|
||||
private int runLength;
|
||||
|
||||
public RleWriter(Stream stream)
|
||||
{
|
||||
rleLock = new object();
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes byte data using PackBits RLE compression.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw data to be encoded.</param>
|
||||
/// <param name="offset">Offset at which to begin transferring data.</param>
|
||||
/// <param name="count">Number of bytes of data to transfer.</param>
|
||||
/// <returns>Number of compressed bytes written to the stream.</returns>
|
||||
/// <remarks>
|
||||
/// There are multiple ways to encode two-byte runs:
|
||||
/// 1. Apple PackBits only encodes three-byte runs as repeats.
|
||||
/// 2. Adobe Photoshop encodes two-byte runs as repeats, unless preceded
|
||||
/// by literals.
|
||||
/// 3. TIFF PackBits recommends that two-byte runs be encoded as repeats,
|
||||
/// unless preceded *and* followed by literals.
|
||||
///
|
||||
/// This class adopts the Photoshop behavior, as it has slightly better
|
||||
/// compression efficiency than Apple PackBits, and is easier to implement
|
||||
/// than TIFF PackBits.
|
||||
/// </remarks>
|
||||
public int Write(byte[] data, int offset, int count)
|
||||
{
|
||||
if (!Util.CheckBufferBounds(data, offset, count))
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
// We cannot encode a count of 0, because the PackBits flag-counter byte
|
||||
// uses 0 to indicate a length of 1.
|
||||
if (count == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("count");
|
||||
}
|
||||
|
||||
lock (rleLock)
|
||||
{
|
||||
var startPosition = stream.Position;
|
||||
|
||||
this.data = data;
|
||||
this.offset = offset;
|
||||
//fixed (byte* ptrData = &data[0])
|
||||
{
|
||||
//byte* ptr = ptrData + offset;
|
||||
//byte* ptrEnd = ptr + count;
|
||||
//var bytesEncoded = EncodeToStream(ptr, ptrEnd);
|
||||
//Debug.Assert(bytesEncoded == count, "Encoded byte count should match the argument.");
|
||||
var bytesEncoded = EncodeToStream(data, offset, offset + count);
|
||||
Assert.AreEqual(bytesEncoded, count, "Encoded byte count should match the argument.");
|
||||
}
|
||||
|
||||
return (int)(stream.Position - startPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearPacket()
|
||||
{
|
||||
this.isRepeatPacket = false;
|
||||
this.packetLength = 0;
|
||||
}
|
||||
|
||||
private void WriteRepeatPacket(int length)
|
||||
{
|
||||
var header = unchecked((byte)(1 - length));
|
||||
stream.WriteByte(header);
|
||||
stream.WriteByte(runValue);
|
||||
}
|
||||
|
||||
private void WriteLiteralPacket(int length)
|
||||
{
|
||||
var header = unchecked((byte)(length - 1));
|
||||
stream.WriteByte(header);
|
||||
stream.Write(data, idxPacketStart, length);
|
||||
}
|
||||
|
||||
private void WritePacket()
|
||||
{
|
||||
if (isRepeatPacket)
|
||||
WriteRepeatPacket(packetLength);
|
||||
else
|
||||
WriteLiteralPacket(packetLength);
|
||||
}
|
||||
|
||||
private void StartPacket(int count,
|
||||
bool isRepeatPacket, int runLength, byte value)
|
||||
{
|
||||
this.isRepeatPacket = isRepeatPacket;
|
||||
|
||||
this.packetLength = runLength;
|
||||
this.runLength = runLength;
|
||||
this.runValue = value;
|
||||
|
||||
this.idxPacketStart = offset + count;
|
||||
}
|
||||
|
||||
private void ExtendPacketAndRun(byte value)
|
||||
{
|
||||
packetLength++;
|
||||
runLength++;
|
||||
}
|
||||
|
||||
private void ExtendPacketStartNewRun(byte value)
|
||||
{
|
||||
packetLength++;
|
||||
runLength = 1;
|
||||
runValue = value;
|
||||
}
|
||||
|
||||
private int EncodeToStream(byte[] ptr, int start, int end /*byte* ptr, byte* ptrEnd*/)
|
||||
{
|
||||
// Begin the first packet.
|
||||
StartPacket(0, false, 1, ptr[start]);
|
||||
int numBytesEncoded = 1;
|
||||
start++;
|
||||
|
||||
// Loop invariant: Packet is never empty.
|
||||
while (start < end)
|
||||
{
|
||||
var value = ptr[start];
|
||||
|
||||
if (packetLength == 1)
|
||||
{
|
||||
isRepeatPacket = (value == runValue);
|
||||
if (isRepeatPacket)
|
||||
ExtendPacketAndRun(value);
|
||||
else
|
||||
ExtendPacketStartNewRun(value);
|
||||
}
|
||||
else if (packetLength == maxPacketLength)
|
||||
{
|
||||
// Packet is full, so write it out and start a new one.
|
||||
WritePacket();
|
||||
StartPacket(numBytesEncoded, false, 1, value);
|
||||
}
|
||||
else if (isRepeatPacket)
|
||||
{
|
||||
// Decide whether to continue the repeat packet.
|
||||
if (value == runValue)
|
||||
ExtendPacketAndRun(value);
|
||||
else
|
||||
{
|
||||
// Different color, so terminate the run and start a new packet.
|
||||
WriteRepeatPacket(packetLength);
|
||||
StartPacket(numBytesEncoded, false, 1, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Decide whether to continue the literal packet.
|
||||
if (value == runValue)
|
||||
{
|
||||
ExtendPacketAndRun(value);
|
||||
|
||||
// A 3-byte run terminates the literal and starts a new repeat
|
||||
// packet. That's because the 3-byte run can be encoded as a
|
||||
// 2-byte repeat. So even if the run ends at 3, we've already
|
||||
// paid for the next flag-counter byte.
|
||||
if (runLength == 3)
|
||||
{
|
||||
// The 3-byte run can come in the middle of a literal packet,
|
||||
// but not at the beginning. The first 2 bytes of the run
|
||||
// should've triggered a repeat packet.
|
||||
Debug.Assert(packetLength > 3);
|
||||
|
||||
// -2 because numBytesEncoded has not yet been incremented
|
||||
WriteLiteralPacket(packetLength - 3);
|
||||
StartPacket(numBytesEncoded - 2, true, 3, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtendPacketStartNewRun(value);
|
||||
}
|
||||
}
|
||||
|
||||
start++;
|
||||
numBytesEncoded++;
|
||||
}
|
||||
|
||||
// Loop terminates with a non-empty packet waiting to be written out.
|
||||
WritePacket();
|
||||
ClearPacket();
|
||||
|
||||
return numBytesEncoded;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,390 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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 Unity.Collections;
|
||||
|
||||
namespace PhotoshopFile
|
||||
{
|
||||
internal static class Util
|
||||
{
|
||||
[DebuggerDisplay("Top = {Top}, Bottom = {Bottom}, Left = {Left}, Right = {Right}")]
|
||||
internal struct RectanglePosition
|
||||
{
|
||||
public int Top { get; set; }
|
||||
public int Bottom { get; set; }
|
||||
public int Left { get; set; }
|
||||
public int Right { get; set; }
|
||||
}
|
||||
|
||||
public static Rectangle IntersectWith(
|
||||
this Rectangle thisRect, Rectangle rect)
|
||||
{
|
||||
thisRect.Intersect(rect);
|
||||
return thisRect;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Fills a buffer with a byte value.
|
||||
/// </summary>
|
||||
static public void Fill(byte[] ptr, int start, int end, byte value)
|
||||
{
|
||||
while (start < end)
|
||||
{
|
||||
ptr[start] = value;
|
||||
start++;
|
||||
}
|
||||
}
|
||||
|
||||
static public void Invert(byte[] ptr, int ptrStart, int ptrEnd)
|
||||
{
|
||||
while (ptrStart < ptrEnd)
|
||||
{
|
||||
ptr[ptrStart] = (byte)(ptr[ptrStart] ^ 0xff);
|
||||
ptrStart++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a buffer with a byte value.
|
||||
/// </summary>
|
||||
static public void Fill(NativeArray<byte> ptr, int start, int end, byte value)
|
||||
{
|
||||
while (start < end)
|
||||
{
|
||||
ptr[start] = value;
|
||||
start++;
|
||||
}
|
||||
}
|
||||
|
||||
static public void Invert(NativeArray<byte> ptr, int ptrStart, int ptrEnd)
|
||||
{
|
||||
while (ptrStart < ptrEnd)
|
||||
{
|
||||
ptr[ptrStart] = (byte)(ptr[ptrStart] ^ 0xff);
|
||||
ptrStart++;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the endianness of a 2-byte word.
|
||||
/// </summary>
|
||||
static public void SwapBytes2(byte[] ptr, int start)
|
||||
{
|
||||
byte byte0 = ptr[start];
|
||||
ptr[start] = ptr[start + 1];
|
||||
ptr[start + 1] = byte0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the endianness of a 4-byte word.
|
||||
/// </summary>
|
||||
static public void SwapBytes4(byte[] ptr, int start)
|
||||
{
|
||||
byte byte0 = ptr[start];
|
||||
byte byte1 = ptr[start + 1];
|
||||
|
||||
ptr[start] = ptr[start + 3];
|
||||
ptr[start + 1] = ptr[start + 2];
|
||||
ptr[start + 2] = byte1;
|
||||
ptr[start + 3] = byte0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the endianness of a word of arbitrary length.
|
||||
/// </summary>
|
||||
static public void SwapBytes(byte[] ptr, int start, int nLength)
|
||||
{
|
||||
for (long i = 0; i < nLength / 2; ++i)
|
||||
{
|
||||
byte t = ptr[start + i];
|
||||
ptr[start + i] = ptr[start + nLength - i - 1];
|
||||
ptr[start + nLength - i - 1] = t;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static void SwapByteArray(int bitDepth,
|
||||
byte[] byteArray, int startIdx, int count)
|
||||
{
|
||||
switch (bitDepth)
|
||||
{
|
||||
case 1:
|
||||
case 8:
|
||||
break;
|
||||
|
||||
case 16:
|
||||
SwapByteArray2(byteArray, startIdx, count);
|
||||
break;
|
||||
|
||||
case 32:
|
||||
SwapByteArray4(byteArray, startIdx, count);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception(
|
||||
"Byte-swapping implemented only for 16-bit and 32-bit depths.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the endianness of 2-byte words in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="byteArray">Byte array containing the sequence on which to swap endianness</param>
|
||||
/// <param name="startIdx">Byte index of the first word to swap</param>
|
||||
/// <param name="count">Number of words to swap</param>
|
||||
public static void SwapByteArray2(byte[] byteArray, int startIdx, int count)
|
||||
{
|
||||
int endIdx = startIdx + count * 2;
|
||||
if (byteArray.Length < endIdx)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
|
||||
{
|
||||
//fixed (byte* arrayPtr = &byteArray[0])
|
||||
{
|
||||
//byte* ptr = arrayPtr + startIdx;
|
||||
//byte* endPtr = arrayPtr + endIdx;
|
||||
while (startIdx < endIdx)
|
||||
{
|
||||
SwapBytes2(byteArray, startIdx);
|
||||
startIdx += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the endianness of 4-byte words in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="byteArray">Byte array containing the sequence on which to swap endianness</param>
|
||||
/// <param name="startIdx">Byte index of the first word to swap</param>
|
||||
/// <param name="count">Number of words to swap</param>
|
||||
public static void SwapByteArray4(byte[] byteArray, int startIdx, int count)
|
||||
{
|
||||
int endIdx = startIdx + count * 4;
|
||||
if (byteArray.Length < endIdx)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
|
||||
{
|
||||
//fixed (byte* arrayPtr = &byteArray[0])
|
||||
{
|
||||
//byte* ptr = arrayPtr + startIdx;
|
||||
//byte* endPtr = arrayPtr + endIdx;
|
||||
while (startIdx < endIdx)
|
||||
{
|
||||
SwapBytes4(byteArray, startIdx);
|
||||
startIdx += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the number of bytes required to store a row of an image
|
||||
/// with the specified bit depth.
|
||||
/// </summary>
|
||||
/// <param name="size">The size of the image in pixels.</param>
|
||||
/// <param name="bitDepth">The bit depth of the image.</param>
|
||||
/// <returns>The number of bytes needed to store a row of the image.</returns>
|
||||
public static int BytesPerRow(Size size, int bitDepth)
|
||||
{
|
||||
switch (bitDepth)
|
||||
{
|
||||
case 1:
|
||||
return (size.Width + 7) / 8;
|
||||
default:
|
||||
return size.Width * BytesFromBitDepth(bitDepth);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Round the integer to a multiple.
|
||||
/// </summary>
|
||||
public static int RoundUp(int value, int multiple)
|
||||
{
|
||||
if (value == 0)
|
||||
return 0;
|
||||
|
||||
if (Math.Sign(value) != Math.Sign(multiple))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"value and multiple cannot have opposite signs.");
|
||||
}
|
||||
|
||||
var remainder = value % multiple;
|
||||
if (remainder > 0)
|
||||
{
|
||||
value += (multiple - remainder);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get number of bytes required to pad to the specified multiple.
|
||||
/// </summary>
|
||||
public static int GetPadding(int length, int padMultiple)
|
||||
{
|
||||
if ((length < 0) || (padMultiple < 0))
|
||||
throw new ArgumentException();
|
||||
|
||||
var remainder = length % padMultiple;
|
||||
if (remainder == 0)
|
||||
return 0;
|
||||
|
||||
var padding = padMultiple - remainder;
|
||||
return padding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of bytes needed to store a single pixel of the
|
||||
/// specified bit depth.
|
||||
/// </summary>
|
||||
public static int BytesFromBitDepth(int depth)
|
||||
{
|
||||
switch (depth)
|
||||
{
|
||||
case 1:
|
||||
case 8:
|
||||
return 1;
|
||||
case 16:
|
||||
return 2;
|
||||
case 32:
|
||||
return 4;
|
||||
default:
|
||||
throw new ArgumentException("Invalid bit depth.");
|
||||
}
|
||||
}
|
||||
|
||||
public static short MinChannelCount(this PsdColorMode colorMode)
|
||||
{
|
||||
switch (colorMode)
|
||||
{
|
||||
case PsdColorMode.Bitmap:
|
||||
case PsdColorMode.Duotone:
|
||||
case PsdColorMode.Grayscale:
|
||||
case PsdColorMode.Indexed:
|
||||
case PsdColorMode.Multichannel:
|
||||
return 1;
|
||||
case PsdColorMode.Lab:
|
||||
case PsdColorMode.RGB:
|
||||
return 3;
|
||||
case PsdColorMode.CMYK:
|
||||
return 4;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unknown color mode.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the offset and count will remain within the bounds of the
|
||||
/// buffer.
|
||||
/// </summary>
|
||||
/// <returns>True if in bounds, false if out of bounds.</returns>
|
||||
public static bool CheckBufferBounds(byte[] data, int offset, int count)
|
||||
{
|
||||
if (offset < 0)
|
||||
return false;
|
||||
if (count < 0)
|
||||
return false;
|
||||
if (offset + count > data.Length)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CheckByteArrayLength(long length)
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
throw new Exception("Byte array cannot have a negative length.");
|
||||
}
|
||||
if (length > 0x7fffffc7)
|
||||
{
|
||||
throw new OutOfMemoryException(
|
||||
"Byte array cannot exceed 2,147,483,591 in length.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a message to the debug console, indicating the current position
|
||||
/// in the stream in both decimal and hexadecimal formats.
|
||||
/// </summary>
|
||||
[Conditional("DEBUG")]
|
||||
public static void DebugMessage(Stream stream, string message,
|
||||
params object[] args)
|
||||
{
|
||||
//var formattedMessage = String.Format(message, args);
|
||||
//Debug.WriteLine("0x{0:x}, {0}, {1}",
|
||||
//stream.Position, formattedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fixed-point decimal, with 16-bit integer and 16-bit fraction.
|
||||
/// </summary>
|
||||
internal class UFixed16_16
|
||||
{
|
||||
public UInt16 Integer { get; set; }
|
||||
public UInt16 Fraction { get; set; }
|
||||
|
||||
public UFixed16_16(UInt16 integer, UInt16 fraction)
|
||||
{
|
||||
Integer = integer;
|
||||
Fraction = fraction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split the high and low words of a 32-bit unsigned integer into a
|
||||
/// fixed-point number.
|
||||
/// </summary>
|
||||
public UFixed16_16(UInt32 value)
|
||||
{
|
||||
Integer = (UInt16)(value >> 16);
|
||||
Fraction = (UInt16)(value & 0x0000ffff);
|
||||
}
|
||||
|
||||
public UFixed16_16(double value)
|
||||
{
|
||||
if (value >= 65536.0) throw new OverflowException();
|
||||
if (value < 0) throw new OverflowException();
|
||||
|
||||
Integer = (UInt16)value;
|
||||
|
||||
// Round instead of truncate, because doubles may not represent the
|
||||
// fraction exactly.
|
||||
Fraction = (UInt16)((value - Integer) * 65536 + 0.5);
|
||||
}
|
||||
|
||||
public static implicit operator double(UFixed16_16 value)
|
||||
{
|
||||
return (double)value.Integer + value.Fraction / 65536.0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "PsdPlugin",
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.U2D.Sprites;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
[Serializable]
|
||||
internal class SpriteMetaData : SpriteRect
|
||||
{
|
||||
public List<SpriteBone> spriteBone;
|
||||
public List<SpriteOutline> spriteOutline;
|
||||
public List<Vertex2DMetaData> vertices;
|
||||
public List<SpriteOutline> spritePhysicsOutline;
|
||||
public int[] indices;
|
||||
public Vector2Int[] edges;
|
||||
public float tessellationDetail;
|
||||
public int parentGroupIndex = -1;
|
||||
public Vector2Int uvTransform = Vector2Int.zero;
|
||||
|
||||
public SpriteMetaData() {}
|
||||
|
||||
public SpriteMetaData(SpriteRect sr)
|
||||
{
|
||||
alignment = sr.alignment;
|
||||
border = sr.border;
|
||||
name = sr.name;
|
||||
pivot = GetPivotValue(sr.alignment, sr.pivot);
|
||||
rect = sr.rect;
|
||||
spriteID = sr.spriteID;
|
||||
}
|
||||
|
||||
public static GUID GetGUIDFromSerializedProperty(SerializedProperty sp)
|
||||
{
|
||||
return new GUID(sp.FindPropertyRelative("m_SpriteID").stringValue);
|
||||
}
|
||||
|
||||
public static Vector2 GetPivotValue(SpriteAlignment alignment, Vector2 customOffset)
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case SpriteAlignment.BottomLeft:
|
||||
return new Vector2(0f, 0f);
|
||||
case SpriteAlignment.BottomCenter:
|
||||
return new Vector2(0.5f, 0f);
|
||||
case SpriteAlignment.BottomRight:
|
||||
return new Vector2(1f, 0f);
|
||||
|
||||
case SpriteAlignment.LeftCenter:
|
||||
return new Vector2(0f, 0.5f);
|
||||
case SpriteAlignment.Center:
|
||||
return new Vector2(0.5f, 0.5f);
|
||||
case SpriteAlignment.RightCenter:
|
||||
return new Vector2(1f, 0.5f);
|
||||
|
||||
case SpriteAlignment.TopLeft:
|
||||
return new Vector2(0f, 1f);
|
||||
case SpriteAlignment.TopCenter:
|
||||
return new Vector2(0.5f, 1f);
|
||||
case SpriteAlignment.TopRight:
|
||||
return new Vector2(1f, 1f);
|
||||
|
||||
case SpriteAlignment.Custom:
|
||||
return customOffset;
|
||||
}
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
public static implicit operator UnityEditor.AssetImporters.SpriteImportData(SpriteMetaData value)
|
||||
{
|
||||
var output = new UnityEditor.AssetImporters.SpriteImportData();
|
||||
output.name = value.name;
|
||||
output.alignment = value.alignment;
|
||||
output.rect = value.rect;
|
||||
output.border = value.border;
|
||||
output.pivot = value.pivot;
|
||||
output.tessellationDetail = value.tessellationDetail;
|
||||
output.spriteID = value.spriteID.ToString();
|
||||
if (value.spriteOutline != null)
|
||||
output.outline = value.spriteOutline.Select(x => x.outline).ToList();
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class SpriteOutline
|
||||
{
|
||||
[SerializeField]
|
||||
public Vector2[] outline;
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PDNWrapper;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
class ExtractLayerTask
|
||||
{
|
||||
struct ConvertBufferJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly]
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<IntPtr> original;
|
||||
[ReadOnly]
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<int> width;
|
||||
[ReadOnly]
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<int> height;
|
||||
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<IntPtr> output;
|
||||
public unsafe void Execute(int index)
|
||||
{
|
||||
Color32* originalColor = (Color32*)original[index];
|
||||
Color32* otuputColor = (Color32*)output[index];
|
||||
for (int i = 0; i < height[index]; ++i)
|
||||
{
|
||||
int originalYOffset = i * width[index];
|
||||
int outputYOffset = (height[index] - i - 1) * width[index];
|
||||
for (int j = 0; j < width[index]; ++j)
|
||||
{
|
||||
otuputColor[j + outputYOffset] = originalColor[j + originalYOffset];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe void Execute(List<PSDLayer> extractedLayer, List<BitmapLayer> layers, bool importHiddenLayer)
|
||||
{
|
||||
UnityEngine.Profiling.Profiler.BeginSample("ExtractLayer_PrepareJob");
|
||||
var tempExtractLayer = new List<PSDLayer>();
|
||||
int layerWithBuffer = ExtractLayer(tempExtractLayer, layers, importHiddenLayer);
|
||||
if (layerWithBuffer == 0)
|
||||
return;
|
||||
var job = new ConvertBufferJob();
|
||||
job.original = new NativeArray<IntPtr>(layerWithBuffer, Allocator.TempJob);
|
||||
job.output = new NativeArray<IntPtr>(layerWithBuffer, Allocator.TempJob);
|
||||
job.width = new NativeArray<int>(layerWithBuffer, Allocator.TempJob);
|
||||
job.height = new NativeArray<int>(layerWithBuffer, Allocator.TempJob);
|
||||
for (int i = 0, jobIndex = 0; i < tempExtractLayer.Count; ++i)
|
||||
{
|
||||
var el = tempExtractLayer[i];
|
||||
if (el.texture.Length == 0 || el.width == 0 || el.height == 0)
|
||||
{
|
||||
extractedLayer.Add(el);
|
||||
continue;
|
||||
}
|
||||
|
||||
job.original[jobIndex] = new IntPtr(el.texture.GetUnsafeReadOnlyPtr());
|
||||
el.texture = new NativeArray<Color32>(el.texture.Length, Allocator.Persistent);
|
||||
extractedLayer.Add(el);
|
||||
job.output[jobIndex] = new IntPtr(el.texture.GetUnsafePtr());
|
||||
job.width[jobIndex] = el.width;
|
||||
job.height[jobIndex] = el.height;
|
||||
++jobIndex;
|
||||
}
|
||||
|
||||
var jobsPerThread = layerWithBuffer / (SystemInfo.processorCount == 0 ? 8 : SystemInfo.processorCount);
|
||||
jobsPerThread = Mathf.Max(jobsPerThread, 1);
|
||||
var handle = job.Schedule(layerWithBuffer, jobsPerThread);
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
handle.Complete();
|
||||
}
|
||||
|
||||
static int ExtractLayer(List<PSDLayer> extractedLayer, List<BitmapLayer> layers, bool importHiddenLayer)
|
||||
{
|
||||
// parent is the previous element in extracedLayer
|
||||
int parentGroupIndex = extractedLayer.Count - 1;
|
||||
int actualLayerWithBuffer = 0;
|
||||
foreach (var l in layers)
|
||||
{
|
||||
if (!importHiddenLayer && !l.Visible)
|
||||
continue;
|
||||
if (l.IsGroup)
|
||||
{
|
||||
extractedLayer.Add(new PSDLayer(new NativeArray<Color32>(0, Allocator.Persistent), parentGroupIndex, l.IsGroup, l.Name, 0, 0, l.LayerID));
|
||||
actualLayerWithBuffer += ExtractLayer(extractedLayer, l.ChildLayer, importHiddenLayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
extractedLayer.Add(new PSDLayer(l.Surface.color, parentGroupIndex, l.IsGroup, l.Name, l.Surface.width, l.Surface.height, l.LayerID));
|
||||
++actualLayerWithBuffer;
|
||||
}
|
||||
}
|
||||
return actualLayerWithBuffer;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PDNWrapper;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
static class FlattenImageTask
|
||||
{
|
||||
static unsafe public void Execute(List<BitmapLayer> layer, bool importHiddenLayer, int width, int height, NativeArray<Color32> output)
|
||||
{
|
||||
UnityEngine.Profiling.Profiler.BeginSample("FlattenImage");
|
||||
List<IntPtr> buffers = new List<IntPtr>();
|
||||
for (int i = layer.Count - 1; i >= 0; --i)
|
||||
{
|
||||
GetBuffersToMergeFromLayer(layer[i], importHiddenLayer, buffers);
|
||||
}
|
||||
|
||||
if (buffers.Count == 0)
|
||||
return;
|
||||
|
||||
var layersPerJob = buffers.Count / (SystemInfo.processorCount == 0 ? 8 : SystemInfo.processorCount);
|
||||
layersPerJob = Mathf.Max(layersPerJob, 1);
|
||||
|
||||
var job = new FlattenImageInternalJob();
|
||||
var combineJob = new FlattenImageInternalJob();
|
||||
|
||||
job.buffers = new NativeArray<IntPtr>(buffers.ToArray(), Allocator.TempJob);
|
||||
for (int i = 0; i < buffers.Count; ++i)
|
||||
job.buffers[i] = buffers[i];
|
||||
|
||||
combineJob.width = job.width = width;
|
||||
combineJob.height = job.height = height;
|
||||
|
||||
job.layersPerJob = layersPerJob;
|
||||
job.flipY = false;
|
||||
combineJob.flipY = true;
|
||||
|
||||
int jobCount = buffers.Count / layersPerJob + (buffers.Count % layersPerJob > 0 ? 1 : 0);
|
||||
combineJob.layersPerJob = jobCount;
|
||||
|
||||
NativeArray<byte>[] premergedBuffer = new NativeArray<byte>[jobCount];
|
||||
job.output = new NativeArray<IntPtr>(jobCount, Allocator.TempJob);
|
||||
combineJob.buffers = new NativeArray<IntPtr>(jobCount, Allocator.TempJob);
|
||||
|
||||
for (int i = 0; i < jobCount; ++i)
|
||||
{
|
||||
premergedBuffer[i] = new NativeArray<byte>(width * height * 4, Allocator.TempJob);
|
||||
job.output[i] = new IntPtr(premergedBuffer[i].GetUnsafePtr());
|
||||
combineJob.buffers[i] = new IntPtr(premergedBuffer[i].GetUnsafeReadOnlyPtr());
|
||||
}
|
||||
combineJob.output = new NativeArray<IntPtr>(new[] {new IntPtr(output.GetUnsafePtr()), }, Allocator.TempJob);
|
||||
|
||||
var handle = job.Schedule(jobCount, 1);
|
||||
combineJob.Schedule(1, 1, handle).Complete();
|
||||
foreach (var b in premergedBuffer)
|
||||
{
|
||||
if (b.IsCreated)
|
||||
b.Dispose();
|
||||
}
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
}
|
||||
|
||||
static unsafe void GetBuffersToMergeFromLayer(BitmapLayer layer, bool importHiddenLayer, List<IntPtr> buffers)
|
||||
{
|
||||
if (!layer.Visible && importHiddenLayer == false)
|
||||
return;
|
||||
if (layer.IsGroup)
|
||||
{
|
||||
for (int i = layer.ChildLayer.Count - 1; i >= 0; --i)
|
||||
GetBuffersToMergeFromLayer(layer.ChildLayer[i], importHiddenLayer, buffers);
|
||||
}
|
||||
if (layer.Surface != null)
|
||||
buffers.Add(new IntPtr(layer.Surface.color.GetUnsafeReadOnlyPtr()));
|
||||
else
|
||||
Debug.LogWarning(string.Format("Layer {0} has no color buffer", layer.Name));
|
||||
}
|
||||
|
||||
struct FlattenImageInternalJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly]
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<IntPtr> buffers;
|
||||
[ReadOnly]
|
||||
public int layersPerJob;
|
||||
[ReadOnly]
|
||||
public int width;
|
||||
[ReadOnly]
|
||||
public int height;
|
||||
[ReadOnly]
|
||||
public bool flipY;
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<IntPtr> output;
|
||||
|
||||
public unsafe void Execute(int index)
|
||||
{
|
||||
var premerge = (Color32*)output[index].ToPointer();
|
||||
for (int layerIndex = index * layersPerJob; layerIndex < (index * layersPerJob) + layersPerJob; ++layerIndex)
|
||||
{
|
||||
if (buffers.Length <= layerIndex)
|
||||
break;
|
||||
var buffer = (Color32*)buffers[layerIndex].ToPointer();
|
||||
for (int i = 0; i < height; ++i)
|
||||
{
|
||||
int sourceYIndex = i * width;
|
||||
int destYIndex = flipY ? (height - 1 - i) * width : sourceYIndex;
|
||||
for (int j = 0; j < width; ++j)
|
||||
{
|
||||
int sourceIndex = sourceYIndex + j;
|
||||
int destIndex = destYIndex + j;
|
||||
float alpha = buffer[sourceIndex].a / 255.0f;
|
||||
premerge[destIndex].r = (byte)(alpha * (float)(buffer[sourceIndex].r) + (float)((1.0f - alpha) * (float)premerge[destIndex].r));
|
||||
premerge[destIndex].g = (byte)(alpha * (float)(buffer[sourceIndex].g) + (float)((1.0f - alpha) * (float)premerge[destIndex].g));
|
||||
premerge[destIndex].b = (byte)(alpha * (float)(buffer[sourceIndex].b) + (float)((1.0f - alpha) * (float)premerge[destIndex].b));
|
||||
premerge[destIndex].a = (byte)(alpha * (float)(buffer[sourceIndex].a) + (float)((1.0f - alpha) * (float)premerge[destIndex].a));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,254 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
internal class TexturePlatformSettingsController
|
||||
{
|
||||
public bool HandleDefaultSettings(List<TextureImporterPlatformSettings> platformSettings, TexturePlatformSettingsView view)
|
||||
{
|
||||
Assert.IsTrue(platformSettings.Count > 0, "At least 1 platform setting is needed to display the texture platform setting UI.");
|
||||
|
||||
int allSize = platformSettings[0].maxTextureSize;
|
||||
TextureImporterCompression allCompression = platformSettings[0].textureCompression;
|
||||
bool allUseCrunchedCompression = platformSettings[0].crunchedCompression;
|
||||
int allCompressionQuality = platformSettings[0].compressionQuality;
|
||||
TextureResizeAlgorithm allResizeAlgorithm = platformSettings[0].resizeAlgorithm;
|
||||
|
||||
var newSize = allSize;
|
||||
var newCompression = allCompression;
|
||||
var newUseCrunchedCompression = allUseCrunchedCompression;
|
||||
var newCompressionQuality = allCompressionQuality;
|
||||
var newResizeAlgorithm = allResizeAlgorithm;
|
||||
|
||||
bool mixedSize = false;
|
||||
bool mixedCompression = false;
|
||||
bool mixedUseCrunchedCompression = false;
|
||||
bool mixedCompressionQuality = false;
|
||||
bool mixedResizeAlgorithm = false;
|
||||
|
||||
bool sizeChanged = false;
|
||||
bool compressionChanged = false;
|
||||
bool useCrunchedCompressionChanged = false;
|
||||
bool compressionQualityChanged = false;
|
||||
bool resizedChanged = false;
|
||||
|
||||
for (var i = 1; i < platformSettings.Count; ++i)
|
||||
{
|
||||
var settings = platformSettings[i];
|
||||
if (settings.maxTextureSize != allSize)
|
||||
mixedSize = true;
|
||||
if (settings.textureCompression != allCompression)
|
||||
mixedCompression = true;
|
||||
if (settings.crunchedCompression != allUseCrunchedCompression)
|
||||
mixedUseCrunchedCompression = true;
|
||||
if (settings.compressionQuality != allCompressionQuality)
|
||||
mixedCompressionQuality = true;
|
||||
if (settings.resizeAlgorithm != allResizeAlgorithm)
|
||||
mixedResizeAlgorithm = true;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
newSize = view.DrawMaxSize(allSize, mixedSize, false, out sizeChanged);
|
||||
newResizeAlgorithm = view.DrawResizeAlgorithm(allResizeAlgorithm, mixedResizeAlgorithm, false, out resizedChanged);
|
||||
newCompression = view.DrawCompression(allCompression, mixedCompression, out compressionChanged);
|
||||
if (!mixedCompression && allCompression != TextureImporterCompression.Uncompressed)
|
||||
{
|
||||
newUseCrunchedCompression = view.DrawUseCrunchedCompression(allUseCrunchedCompression, mixedUseCrunchedCompression, out useCrunchedCompressionChanged);
|
||||
|
||||
if (!mixedUseCrunchedCompression && allUseCrunchedCompression)
|
||||
{
|
||||
newCompressionQuality = view.DrawCompressionQualitySlider(allCompressionQuality, mixedCompressionQuality, out compressionQualityChanged);
|
||||
}
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
if (sizeChanged || compressionChanged || useCrunchedCompressionChanged || compressionQualityChanged || resizedChanged)
|
||||
{
|
||||
for (var i = 0; i < platformSettings.Count; ++i)
|
||||
{
|
||||
if (sizeChanged)
|
||||
platformSettings[i].maxTextureSize = newSize;
|
||||
if (compressionChanged)
|
||||
platformSettings[i].textureCompression = newCompression;
|
||||
if (useCrunchedCompressionChanged)
|
||||
platformSettings[i].crunchedCompression = newUseCrunchedCompression;
|
||||
if (compressionQualityChanged)
|
||||
platformSettings[i].compressionQuality = newCompressionQuality;
|
||||
if (resizedChanged)
|
||||
platformSettings[i].resizeAlgorithm = newResizeAlgorithm;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HandlePlatformSettings(BuildTarget buildTarget, List<TextureImporterPlatformSettings> platformSettings, TexturePlatformSettingsView view)
|
||||
{
|
||||
if (buildTarget == BuildTarget.NoTarget)
|
||||
{
|
||||
return HandleDefaultSettings(platformSettings, view);
|
||||
}
|
||||
Assert.IsTrue(platformSettings.Count > 0, "At least 1 platform setting is needed to display the texture platform setting UI.");
|
||||
|
||||
bool allOverride = platformSettings[0].overridden;
|
||||
int allSize = platformSettings[0].maxTextureSize;
|
||||
TextureImporterFormat allFormat = platformSettings[0].format;
|
||||
int allCompressionQuality = platformSettings[0].compressionQuality;
|
||||
TextureResizeAlgorithm allResizeAlgorithm = platformSettings[0].resizeAlgorithm;
|
||||
var newResizeAlgorithm = allResizeAlgorithm;
|
||||
|
||||
var newOverride = allOverride;
|
||||
var newSize = allSize;
|
||||
var newFormat = allFormat;
|
||||
var newCompressionQuality = allCompressionQuality;
|
||||
|
||||
bool mixedOverride = false;
|
||||
bool mixedSize = false;
|
||||
bool mixedFormat = false;
|
||||
bool mixedCompression = false;
|
||||
bool mixedResizeAlgorithm = false;
|
||||
|
||||
bool overrideChanged = false;
|
||||
bool sizeChanged = false;
|
||||
bool formatChanged = false;
|
||||
bool compressionChanged = false;
|
||||
bool resizedChanged = false;
|
||||
|
||||
for (var i = 1; i < platformSettings.Count; ++i)
|
||||
{
|
||||
var settings = platformSettings[i];
|
||||
if (settings.overridden != allOverride)
|
||||
mixedOverride = true;
|
||||
if (settings.maxTextureSize != allSize)
|
||||
mixedSize = true;
|
||||
if (settings.format != allFormat)
|
||||
mixedFormat = true;
|
||||
if (settings.compressionQuality != allCompressionQuality)
|
||||
mixedCompression = true;
|
||||
if (settings.resizeAlgorithm != allResizeAlgorithm)
|
||||
mixedResizeAlgorithm = true;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
newOverride = view.DrawOverride(allOverride, mixedOverride, out overrideChanged);
|
||||
newResizeAlgorithm = view.DrawResizeAlgorithm(allResizeAlgorithm, mixedResizeAlgorithm, mixedOverride || !allOverride, out resizedChanged);
|
||||
newSize = view.DrawMaxSize(allSize, mixedSize, mixedOverride || !allOverride, out sizeChanged);
|
||||
|
||||
int[] formatValues = null;
|
||||
string[] formatStrings = null;
|
||||
AcquireTextureFormatValuesAndStrings(buildTarget, out formatValues, out formatStrings);
|
||||
|
||||
newFormat = view.DrawFormat(allFormat, formatValues, formatStrings, mixedFormat, mixedOverride || !allOverride, out formatChanged);
|
||||
|
||||
|
||||
if (!mixedFormat && !mixedOverride && allOverride && IsFormatRequireCompressionSetting(allFormat))
|
||||
{
|
||||
bool showAsEnum =
|
||||
buildTarget == BuildTarget.iOS ||
|
||||
buildTarget == BuildTarget.tvOS ||
|
||||
buildTarget == BuildTarget.Android
|
||||
;
|
||||
|
||||
if (showAsEnum)
|
||||
{
|
||||
int compressionMode = 1;
|
||||
if (allCompressionQuality == (int)TextureCompressionQuality.Fast)
|
||||
compressionMode = 0;
|
||||
else if (allCompressionQuality == (int)TextureCompressionQuality.Best)
|
||||
compressionMode = 2;
|
||||
|
||||
var returnValue = view.DrawCompressionQualityPopup(compressionMode, mixedCompression, out compressionChanged);
|
||||
|
||||
if (compressionChanged)
|
||||
{
|
||||
switch (returnValue)
|
||||
{
|
||||
case 0:
|
||||
newCompressionQuality = (int)TextureCompressionQuality.Fast;
|
||||
break;
|
||||
case 1:
|
||||
newCompressionQuality = (int)TextureCompressionQuality.Normal;
|
||||
break;
|
||||
case 2:
|
||||
newCompressionQuality = (int)TextureCompressionQuality.Best;
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.IsTrue(false, "ITexturePlatformSettingsView.DrawCompressionQualityPopup should never return compression option value that's not 0, 1 or 2.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newCompressionQuality = view.DrawCompressionQualitySlider(allCompressionQuality, mixedCompression, out compressionChanged);
|
||||
}
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
if (overrideChanged || sizeChanged || formatChanged || compressionChanged || resizedChanged)
|
||||
{
|
||||
for (var i = 0; i < platformSettings.Count; ++i)
|
||||
{
|
||||
if (overrideChanged)
|
||||
platformSettings[i].overridden = newOverride;
|
||||
if (sizeChanged)
|
||||
platformSettings[i].maxTextureSize = newSize;
|
||||
if (formatChanged)
|
||||
platformSettings[i].format = newFormat;
|
||||
if (compressionChanged)
|
||||
platformSettings[i].compressionQuality = newCompressionQuality;
|
||||
if (resizedChanged)
|
||||
platformSettings[i].resizeAlgorithm = newResizeAlgorithm;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AcquireTextureFormatValuesAndStrings(BuildTarget buildTarget, out int[] formatValues, out string[] formatStrings)
|
||||
{
|
||||
if (IsGLESMobileTargetPlatform(buildTarget))
|
||||
{
|
||||
if (buildTarget == BuildTarget.iOS || buildTarget == BuildTarget.tvOS)
|
||||
{
|
||||
formatValues = TexturePlatformSettingsModal.kTextureFormatsValueApplePVR;
|
||||
formatStrings = TexturePlatformSettingsModal.s_TextureFormatStringsApplePVR;
|
||||
}
|
||||
else
|
||||
{
|
||||
formatValues = TexturePlatformSettingsModal.kTextureFormatsValueAndroid;
|
||||
formatStrings = TexturePlatformSettingsModal.s_TextureFormatStringsAndroid;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buildTarget == BuildTarget.WebGL)
|
||||
{
|
||||
formatValues = TexturePlatformSettingsModal.kTextureFormatsValueWebGL;
|
||||
formatStrings = TexturePlatformSettingsModal.s_TextureFormatStringsWebGL;
|
||||
}
|
||||
else
|
||||
{
|
||||
formatValues = TexturePlatformSettingsModal.kTextureFormatsValueDefault;
|
||||
formatStrings = TexturePlatformSettingsModal.s_TextureFormatStringsDefault;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsFormatRequireCompressionSetting(TextureImporterFormat format)
|
||||
{
|
||||
return ArrayUtility.Contains<TextureImporterFormat>(TexturePlatformSettingsModal.kFormatsWithCompressionSettings, format);
|
||||
}
|
||||
|
||||
internal static bool IsGLESMobileTargetPlatform(BuildTarget target)
|
||||
{
|
||||
return target == BuildTarget.iOS || target == BuildTarget.tvOS || target == BuildTarget.Android;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,380 @@
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
internal static class TexturePlatformSettingsModal
|
||||
{
|
||||
public static readonly TextureImporterFormat[] kFormatsWithCompressionSettings =
|
||||
{
|
||||
TextureImporterFormat.PVRTC_RGB2,
|
||||
TextureImporterFormat.PVRTC_RGB4,
|
||||
TextureImporterFormat.PVRTC_RGBA2,
|
||||
TextureImporterFormat.PVRTC_RGBA4,
|
||||
TextureImporterFormat.ETC_RGB4,
|
||||
TextureImporterFormat.ETC2_RGBA8,
|
||||
TextureImporterFormat.ETC_RGB4,
|
||||
TextureImporterFormat.ETC2_RGB4,
|
||||
TextureImporterFormat.ETC2_RGB4_PUNCHTHROUGH_ALPHA,
|
||||
TextureImporterFormat.ETC2_RGBA8,
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
TextureImporterFormat.ASTC_4x4,
|
||||
TextureImporterFormat.ASTC_5x5,
|
||||
TextureImporterFormat.ASTC_6x6,
|
||||
TextureImporterFormat.ASTC_8x8,
|
||||
TextureImporterFormat.ASTC_10x10,
|
||||
TextureImporterFormat.ASTC_12x12,
|
||||
#else
|
||||
TextureImporterFormat.ASTC_RGB_4x4,
|
||||
TextureImporterFormat.ASTC_RGB_5x5,
|
||||
TextureImporterFormat.ASTC_RGB_6x6,
|
||||
TextureImporterFormat.ASTC_RGB_8x8,
|
||||
TextureImporterFormat.ASTC_RGB_10x10,
|
||||
TextureImporterFormat.ASTC_RGB_12x12,
|
||||
TextureImporterFormat.ASTC_RGBA_4x4,
|
||||
TextureImporterFormat.ASTC_RGBA_5x5,
|
||||
TextureImporterFormat.ASTC_RGBA_6x6,
|
||||
TextureImporterFormat.ASTC_RGBA_8x8,
|
||||
TextureImporterFormat.ASTC_RGBA_10x10,
|
||||
TextureImporterFormat.ASTC_RGBA_12x12
|
||||
#endif
|
||||
};
|
||||
|
||||
public struct BuildPlatformData
|
||||
{
|
||||
public string buildTargetName;
|
||||
public TextureImporterFormat defaultTextureFormat;
|
||||
public BuildTarget[] buildTarget;
|
||||
}
|
||||
|
||||
// Add new platforms here
|
||||
public static readonly BuildPlatformData[] kValidBuildPlatform = new BuildPlatformData[]
|
||||
{
|
||||
new BuildPlatformData()
|
||||
{
|
||||
buildTargetName = "Default",
|
||||
defaultTextureFormat = TextureImporterFormat.Automatic,
|
||||
buildTarget = new[]
|
||||
{
|
||||
BuildTarget.NoTarget
|
||||
}
|
||||
},
|
||||
|
||||
new BuildPlatformData()
|
||||
{
|
||||
buildTargetName = "PC, Mac & Linux Standalone",
|
||||
defaultTextureFormat = TextureImporterFormat.RGBA32,
|
||||
buildTarget = new[]
|
||||
{
|
||||
BuildTarget.StandaloneWindows,
|
||||
BuildTarget.StandaloneWindows64,
|
||||
BuildTarget.StandaloneLinux64,
|
||||
BuildTarget.StandaloneOSX,
|
||||
}
|
||||
},
|
||||
new BuildPlatformData()
|
||||
{
|
||||
buildTargetName = "iOS",
|
||||
defaultTextureFormat = TextureImporterFormat.RGBA32,
|
||||
buildTarget = new[] { BuildTarget.iOS }
|
||||
},
|
||||
new BuildPlatformData()
|
||||
{
|
||||
buildTargetName = "tvOS",
|
||||
defaultTextureFormat = TextureImporterFormat.RGBA32,
|
||||
buildTarget = new[] { BuildTarget.tvOS }
|
||||
},
|
||||
new BuildPlatformData()
|
||||
{
|
||||
buildTargetName = "Android",
|
||||
defaultTextureFormat = TextureImporterFormat.RGBA32,
|
||||
buildTarget = new[] { BuildTarget.Android }
|
||||
},
|
||||
new BuildPlatformData()
|
||||
{
|
||||
buildTargetName = "Universal Windows Platform",
|
||||
defaultTextureFormat = TextureImporterFormat.RGBA32,
|
||||
buildTarget = new[] { BuildTarget.WSAPlayer }
|
||||
},
|
||||
};
|
||||
|
||||
public static readonly int[] kTextureFormatsValueApplePVR =
|
||||
{
|
||||
(int)TextureImporterFormat.PVRTC_RGB2,
|
||||
(int)TextureImporterFormat.PVRTC_RGBA2,
|
||||
(int)TextureImporterFormat.PVRTC_RGB4,
|
||||
(int)TextureImporterFormat.PVRTC_RGBA4,
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
(int)TextureImporterFormat.ASTC_4x4,
|
||||
(int)TextureImporterFormat.ASTC_5x5,
|
||||
(int)TextureImporterFormat.ASTC_6x6,
|
||||
(int)TextureImporterFormat.ASTC_8x8,
|
||||
(int)TextureImporterFormat.ASTC_10x10,
|
||||
(int)TextureImporterFormat.ASTC_12x12,
|
||||
#else
|
||||
(int)TextureImporterFormat.ASTC_RGB_4x4,
|
||||
(int)TextureImporterFormat.ASTC_RGB_5x5,
|
||||
(int)TextureImporterFormat.ASTC_RGB_6x6,
|
||||
(int)TextureImporterFormat.ASTC_RGB_8x8,
|
||||
(int)TextureImporterFormat.ASTC_RGB_10x10,
|
||||
(int)TextureImporterFormat.ASTC_RGB_12x12,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_4x4,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_5x5,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_6x6,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_8x8,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_10x10,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_12x12,
|
||||
#endif
|
||||
|
||||
(int)TextureImporterFormat.RGB16,
|
||||
(int)TextureImporterFormat.RGB24,
|
||||
(int)TextureImporterFormat.Alpha8,
|
||||
(int)TextureImporterFormat.RGBA16,
|
||||
(int)TextureImporterFormat.RGBA32
|
||||
};
|
||||
|
||||
public static readonly int[] kTextureFormatsValueAndroid =
|
||||
{
|
||||
(int)TextureImporterFormat.DXT1,
|
||||
(int)TextureImporterFormat.DXT5,
|
||||
#if ENABLE_CRUNCH_TEXTURE_COMPRESSION
|
||||
(int)TextureImporterFormat.DXT1Crunched,
|
||||
(int)TextureImporterFormat.DXT5Crunched,
|
||||
#endif
|
||||
|
||||
(int)TextureImporterFormat.ETC_RGB4,
|
||||
|
||||
(int)TextureImporterFormat.ETC2_RGB4,
|
||||
(int)TextureImporterFormat.ETC2_RGB4_PUNCHTHROUGH_ALPHA,
|
||||
(int)TextureImporterFormat.ETC2_RGBA8,
|
||||
|
||||
|
||||
(int)TextureImporterFormat.PVRTC_RGB2,
|
||||
(int)TextureImporterFormat.PVRTC_RGBA2,
|
||||
(int)TextureImporterFormat.PVRTC_RGB4,
|
||||
(int)TextureImporterFormat.PVRTC_RGBA4,
|
||||
|
||||
(int)TextureImporterFormat.ETC_RGB4,
|
||||
(int)TextureImporterFormat.ETC2_RGBA8,
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
(int)TextureImporterFormat.ASTC_4x4,
|
||||
(int)TextureImporterFormat.ASTC_5x5,
|
||||
(int)TextureImporterFormat.ASTC_6x6,
|
||||
(int)TextureImporterFormat.ASTC_8x8,
|
||||
(int)TextureImporterFormat.ASTC_10x10,
|
||||
(int)TextureImporterFormat.ASTC_12x12,
|
||||
#else
|
||||
(int)TextureImporterFormat.ASTC_RGB_4x4,
|
||||
(int)TextureImporterFormat.ASTC_RGB_5x5,
|
||||
(int)TextureImporterFormat.ASTC_RGB_6x6,
|
||||
(int)TextureImporterFormat.ASTC_RGB_8x8,
|
||||
(int)TextureImporterFormat.ASTC_RGB_10x10,
|
||||
(int)TextureImporterFormat.ASTC_RGB_12x12,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_4x4,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_5x5,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_6x6,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_8x8,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_10x10,
|
||||
(int)TextureImporterFormat.ASTC_RGBA_12x12,
|
||||
#endif
|
||||
|
||||
(int)TextureImporterFormat.RGB16,
|
||||
(int)TextureImporterFormat.RGB24,
|
||||
(int)TextureImporterFormat.Alpha8,
|
||||
(int)TextureImporterFormat.RGBA16,
|
||||
(int)TextureImporterFormat.RGBA32
|
||||
};
|
||||
|
||||
public static readonly int[] kTextureFormatsValueSTV =
|
||||
{
|
||||
(int)TextureImporterFormat.ETC_RGB4,
|
||||
|
||||
(int)TextureImporterFormat.RGB16,
|
||||
(int)TextureImporterFormat.RGB24,
|
||||
(int)TextureImporterFormat.Alpha8,
|
||||
(int)TextureImporterFormat.RGBA16,
|
||||
(int)TextureImporterFormat.RGBA32,
|
||||
};
|
||||
|
||||
public static readonly int[] kTextureFormatsValueWebGL =
|
||||
{
|
||||
(int)TextureImporterFormat.DXT1,
|
||||
(int)TextureImporterFormat.DXT5,
|
||||
#if ENABLE_CRUNCH_TEXTURE_COMPRESSION
|
||||
(int)TextureImporterFormat.DXT1Crunched,
|
||||
(int)TextureImporterFormat.DXT5Crunched,
|
||||
#endif
|
||||
(int)TextureImporterFormat.RGB16,
|
||||
(int)TextureImporterFormat.RGB24,
|
||||
(int)TextureImporterFormat.Alpha8,
|
||||
(int)TextureImporterFormat.ARGB16,
|
||||
(int)TextureImporterFormat.RGBA32
|
||||
};
|
||||
|
||||
public static readonly int[] kNormalFormatsValueDefault =
|
||||
{
|
||||
(int)TextureImporterFormat.BC5,
|
||||
(int)TextureImporterFormat.BC7,
|
||||
(int)TextureImporterFormat.DXT5,
|
||||
#if ENABLE_CRUNCH_TEXTURE_COMPRESSION
|
||||
(int)TextureImporterFormat.DXT5Crunched,
|
||||
#endif
|
||||
(int)TextureImporterFormat.ARGB16,
|
||||
(int)TextureImporterFormat.RGBA32,
|
||||
};
|
||||
public static readonly int[] kTextureFormatsValueDefault =
|
||||
{
|
||||
(int)TextureImporterFormat.DXT1,
|
||||
(int)TextureImporterFormat.DXT5,
|
||||
#if ENABLE_CRUNCH_TEXTURE_COMPRESSION
|
||||
(int)TextureImporterFormat.DXT1Crunched,
|
||||
(int)TextureImporterFormat.DXT5Crunched,
|
||||
#endif
|
||||
(int)TextureImporterFormat.RGB16,
|
||||
(int)TextureImporterFormat.RGB24,
|
||||
(int)TextureImporterFormat.Alpha8,
|
||||
(int)TextureImporterFormat.ARGB16,
|
||||
(int)TextureImporterFormat.RGBA32,
|
||||
(int)TextureImporterFormat.RGBAHalf,
|
||||
(int)TextureImporterFormat.BC4,
|
||||
(int)TextureImporterFormat.BC5,
|
||||
(int)TextureImporterFormat.BC6H,
|
||||
(int)TextureImporterFormat.BC7,
|
||||
};
|
||||
public static readonly int[] kTextureFormatsValueSingleChannel =
|
||||
{
|
||||
(int)TextureImporterFormat.Alpha8,
|
||||
(int)TextureImporterFormat.BC4,
|
||||
};
|
||||
|
||||
internal static string[] s_TextureFormatStringsWebGL;
|
||||
internal static string[] s_TextureFormatStringsApplePVR;
|
||||
internal static string[] s_TextureFormatStringsAndroid;
|
||||
internal static string[] s_TextureFormatStringsSTV;
|
||||
internal static string[] s_TextureFormatStringsSingleChannel;
|
||||
internal static string[] s_TextureFormatStringsDefault;
|
||||
|
||||
static TexturePlatformSettingsModal()
|
||||
{
|
||||
s_TextureFormatStringsApplePVR = BuildTextureStrings(kTextureFormatsValueApplePVR);
|
||||
s_TextureFormatStringsAndroid = BuildTextureStrings(kTextureFormatsValueAndroid);
|
||||
s_TextureFormatStringsSTV = BuildTextureStrings(kTextureFormatsValueSTV);
|
||||
s_TextureFormatStringsWebGL = BuildTextureStrings(kTextureFormatsValueWebGL);
|
||||
s_TextureFormatStringsDefault = BuildTextureStrings(kTextureFormatsValueDefault);
|
||||
s_TextureFormatStringsSingleChannel = BuildTextureStrings(kTextureFormatsValueSingleChannel);
|
||||
}
|
||||
|
||||
internal static string[] BuildTextureStrings(int[] texFormatValues)
|
||||
{
|
||||
string[] retval = new string[texFormatValues.Length];
|
||||
for (int i = 0; i < texFormatValues.Length; i++)
|
||||
{
|
||||
int val = texFormatValues[i];
|
||||
retval[i] = GetTextureFormatString((TextureImporterFormat)val);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static string GetTextureFormatString(TextureImporterFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TextureImporterFormat.DXT1:
|
||||
return "RGB Crunched DTX1";
|
||||
case TextureImporterFormat.DXT5:
|
||||
return "RGB Crunched DTX5";
|
||||
case TextureImporterFormat.RGB16:
|
||||
return "RGB 16 bit";
|
||||
case TextureImporterFormat.RGB24:
|
||||
return "RGB 24 bit";
|
||||
case TextureImporterFormat.Alpha8:
|
||||
return "Alpha 8";
|
||||
case TextureImporterFormat.ARGB16:
|
||||
return "ARGB 16 bit";
|
||||
case TextureImporterFormat.RGBA32:
|
||||
return "RGBA 32 bit";
|
||||
case TextureImporterFormat.ARGB32:
|
||||
return "ARGB 32 bit";
|
||||
case TextureImporterFormat.RGBA16:
|
||||
return "RGBA 16 bit";
|
||||
case TextureImporterFormat.RGBAHalf:
|
||||
return "RGBA Half";
|
||||
|
||||
case TextureImporterFormat.BC4:
|
||||
return "R Compressed BC4";
|
||||
case TextureImporterFormat.BC5:
|
||||
return "RG Compressed BC5";
|
||||
case TextureImporterFormat.BC6H:
|
||||
return "RGB HDR Compressed BC6H";
|
||||
case TextureImporterFormat.BC7:
|
||||
return "RGB(A) Compressed BC7";
|
||||
case TextureImporterFormat.PVRTC_RGB2:
|
||||
return "RGB Compressed PVRTC 2 bits";
|
||||
case TextureImporterFormat.PVRTC_RGBA2:
|
||||
return "RGBA Compressed PVRTC 2 bits";
|
||||
case TextureImporterFormat.PVRTC_RGB4:
|
||||
return "RGB Compressed PVRTC 4 bits";
|
||||
case TextureImporterFormat.PVRTC_RGBA4:
|
||||
return "RGBA Compressed PVRTC 4 bits";
|
||||
case TextureImporterFormat.ETC_RGB4:
|
||||
return "RGB Compressed ETC 4 bits";
|
||||
|
||||
case TextureImporterFormat.EAC_R:
|
||||
return "11-bit R Compressed EAC 4 bit";
|
||||
case TextureImporterFormat.EAC_R_SIGNED:
|
||||
return "11-bit signed R Compressed EAC 4 bit";
|
||||
case TextureImporterFormat.EAC_RG:
|
||||
return "11-bit RG Compressed EAC 8 bit";
|
||||
case TextureImporterFormat.EAC_RG_SIGNED:
|
||||
return "11-bit signed RG Compressed EAC 8 bit";
|
||||
|
||||
case TextureImporterFormat.ETC2_RGB4:
|
||||
return "RGB Compressed ETC2 4 bits";
|
||||
case TextureImporterFormat.ETC2_RGB4_PUNCHTHROUGH_ALPHA:
|
||||
return "RGB + 1-bit Alpha Compressed ETC2 4 bits";
|
||||
case TextureImporterFormat.ETC2_RGBA8:
|
||||
return "RGBA Compressed ETC2 8 bits";
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
case TextureImporterFormat.ASTC_4x4:
|
||||
return "RGB(A) Compressed ASTC 4 x 4 block";
|
||||
case TextureImporterFormat.ASTC_5x5:
|
||||
return "RGB(A) Compressed ASTC 5 x 5 block";
|
||||
case TextureImporterFormat.ASTC_6x6:
|
||||
return "RGB(A) Compressed ASTC 6 x 6 block";
|
||||
case TextureImporterFormat.ASTC_8x8:
|
||||
return "RGB(A) Compressed ASTC 8 x 8 block";
|
||||
case TextureImporterFormat.ASTC_10x10:
|
||||
return "RGB(A) Compressed ASTC 10 x 10 block";
|
||||
case TextureImporterFormat.ASTC_12x12:
|
||||
return "RGB(A) Compressed ASTC 12 x 12 block";
|
||||
#else
|
||||
case TextureImporterFormat.ASTC_RGB_4x4:
|
||||
return "RGB Compressed ASTC 4 x 4 block";
|
||||
case TextureImporterFormat.ASTC_RGB_5x5:
|
||||
return "RGB Compressed ASTC 5 x 5 block";
|
||||
case TextureImporterFormat.ASTC_RGB_6x6:
|
||||
return "RGB Compressed ASTC 6 x 6 block";
|
||||
case TextureImporterFormat.ASTC_RGB_8x8:
|
||||
return "RGB Compressed ASTC 8 x 8 block";
|
||||
case TextureImporterFormat.ASTC_RGB_10x10:
|
||||
return "RGB Compressed ASTC 10 x 10 block";
|
||||
case TextureImporterFormat.ASTC_RGB_12x12:
|
||||
return "RGB Compressed ASTC 12 x 12 block";
|
||||
case TextureImporterFormat.ASTC_RGBA_4x4:
|
||||
return "RGBA Compressed ASTC 4 x 4 block";
|
||||
case TextureImporterFormat.ASTC_RGBA_5x5:
|
||||
return "RGBA Compressed ASTC 5 x 5 block";
|
||||
case TextureImporterFormat.ASTC_RGBA_6x6:
|
||||
return "RGBA Compressed ASTC 6 x 6 block";
|
||||
case TextureImporterFormat.ASTC_RGBA_8x8:
|
||||
return "RGBA Compressed ASTC 8 x 8 block";
|
||||
case TextureImporterFormat.ASTC_RGBA_10x10:
|
||||
return "RGBA Compressed ASTC 10 x 10 block";
|
||||
case TextureImporterFormat.ASTC_RGBA_12x12:
|
||||
return "RGBA Compressed ASTC 12 x 12 block";
|
||||
#endif
|
||||
}
|
||||
return "Unsupported";
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,155 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.PSD
|
||||
{
|
||||
internal class TexturePlatformSettingsView
|
||||
{
|
||||
class Styles
|
||||
{
|
||||
public readonly GUIContent textureFormatLabel = new GUIContent("Format");
|
||||
public readonly GUIContent maxTextureSizeLabel = new GUIContent("Max Texture Size", "Maximum size of the packed texture.");
|
||||
public readonly GUIContent compressionLabel = new GUIContent("Compression", "How will this texture be compressed?");
|
||||
public readonly GUIContent resizeAlgorithmLabel = new GUIContent("Resize", "Algorithm to use when resizing texture");
|
||||
public readonly GUIContent useCrunchedCompressionLabel = new GUIContent("Use Crunch Compression", "Texture is crunch-compressed to save space on disk when applicable.");
|
||||
public readonly GUIContent compressionQualityLabel = new GUIContent("Compressor Quality");
|
||||
public readonly GUIContent compressionQualitySliderLabel = new GUIContent("Compressor Quality", "Use the slider to adjust compression quality from 0 (Fastest) to 100 (Best)");
|
||||
|
||||
public readonly int[] kMaxTextureSizeValues = { 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192 };
|
||||
public readonly GUIContent[] kMaxTextureSizeStrings;
|
||||
|
||||
public readonly GUIContent[] kTextureCompressionOptions =
|
||||
{
|
||||
new GUIContent("None", "Texture is not compressed."),
|
||||
new GUIContent("Low Quality", "Texture compressed with low quality but high performance, high compression format."),
|
||||
new GUIContent("Normal Quality", "Texture is compressed with a standard format."),
|
||||
new GUIContent("High Quality", "Texture compressed with a high quality format."),
|
||||
};
|
||||
|
||||
public readonly GUIContent[] kResizeAlgoritmOptions =
|
||||
{
|
||||
new GUIContent(TextureResizeAlgorithm.Mitchell.ToString()),
|
||||
new GUIContent(TextureResizeAlgorithm.Bilinear.ToString()),
|
||||
};
|
||||
|
||||
public readonly int[] kTextureCompressionValues =
|
||||
{
|
||||
(int)TextureImporterCompression.Uncompressed,
|
||||
(int)TextureImporterCompression.CompressedLQ,
|
||||
(int)TextureImporterCompression.Compressed,
|
||||
(int)TextureImporterCompression.CompressedHQ
|
||||
};
|
||||
|
||||
public readonly GUIContent[] kMobileCompressionQualityOptions =
|
||||
{
|
||||
new GUIContent("Fast"),
|
||||
new GUIContent("Normal"),
|
||||
new GUIContent("Best")
|
||||
};
|
||||
|
||||
public Styles()
|
||||
{
|
||||
kMaxTextureSizeStrings = new GUIContent[kMaxTextureSizeValues.Length];
|
||||
for (var i = 0; i < kMaxTextureSizeValues.Length; ++i)
|
||||
kMaxTextureSizeStrings[i] = new GUIContent(string.Format("{0}", kMaxTextureSizeValues[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private static Styles s_Styles;
|
||||
|
||||
public string buildPlatformTitle { get; set; }
|
||||
|
||||
internal TexturePlatformSettingsView()
|
||||
{
|
||||
s_Styles = s_Styles ?? new Styles();
|
||||
}
|
||||
|
||||
public virtual TextureResizeAlgorithm DrawResizeAlgorithm(TextureResizeAlgorithm defaultValue, bool isMixedValue, bool isDisabled, out bool changed)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(isDisabled))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = isMixedValue;
|
||||
defaultValue = (TextureResizeAlgorithm)EditorGUILayout.EnumPopup(s_Styles.resizeAlgorithmLabel, defaultValue);
|
||||
EditorGUI.showMixedValue = false;
|
||||
changed = EditorGUI.EndChangeCheck();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public virtual TextureImporterCompression DrawCompression(TextureImporterCompression defaultValue, bool isMixedValue, out bool changed)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = isMixedValue;
|
||||
defaultValue = (TextureImporterCompression)EditorGUILayout.IntPopup(s_Styles.compressionLabel, (int)defaultValue, s_Styles.kTextureCompressionOptions, s_Styles.kTextureCompressionValues);
|
||||
EditorGUI.showMixedValue = false;
|
||||
changed = EditorGUI.EndChangeCheck();
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public virtual bool DrawUseCrunchedCompression(bool defaultValue, bool isMixedValue, out bool changed)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = isMixedValue;
|
||||
defaultValue = EditorGUILayout.Toggle(s_Styles.useCrunchedCompressionLabel, defaultValue);
|
||||
EditorGUI.showMixedValue = false;
|
||||
changed = EditorGUI.EndChangeCheck();
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public virtual bool DrawOverride(bool defaultValue, bool isMixedValue, out bool changed)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = isMixedValue;
|
||||
defaultValue = EditorGUILayout.ToggleLeft(new GUIContent("Override"), defaultValue);
|
||||
EditorGUI.showMixedValue = false;
|
||||
changed = EditorGUI.EndChangeCheck();
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public virtual int DrawMaxSize(int defaultValue, bool isMixedValue, bool isDisabled, out bool changed)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(isDisabled))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = isMixedValue;
|
||||
defaultValue = EditorGUILayout.IntPopup(s_Styles.maxTextureSizeLabel, defaultValue, s_Styles.kMaxTextureSizeStrings, s_Styles.kMaxTextureSizeValues);
|
||||
EditorGUI.showMixedValue = false;
|
||||
changed = EditorGUI.EndChangeCheck();
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual TextureImporterFormat DrawFormat(TextureImporterFormat defaultValue, int[] displayValues, string[] displayStrings, bool isMixedValue, bool isDisabled, out bool changed)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(isDisabled))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = isMixedValue;
|
||||
defaultValue = (TextureImporterFormat)EditorGUILayout.IntPopup(s_Styles.textureFormatLabel.text, (int)defaultValue, displayStrings, displayValues);
|
||||
EditorGUI.showMixedValue = false;
|
||||
changed = EditorGUI.EndChangeCheck();
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int DrawCompressionQualityPopup(int defaultValue, bool isMixedValue, out bool changed)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = isMixedValue;
|
||||
defaultValue = EditorGUILayout.Popup(s_Styles.compressionQualityLabel, defaultValue, s_Styles.kMobileCompressionQualityOptions);
|
||||
EditorGUI.showMixedValue = false;
|
||||
changed = EditorGUI.EndChangeCheck();
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public virtual int DrawCompressionQualitySlider(int defaultValue, bool isMixedValue, out bool changed)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = isMixedValue;
|
||||
defaultValue = EditorGUILayout.IntSlider(s_Styles.compressionQualitySliderLabel, defaultValue, 0, 100);
|
||||
EditorGUI.showMixedValue = false;
|
||||
changed = EditorGUI.EndChangeCheck();
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,383 @@
|
||||
//using System;
|
||||
//using System.Collections;
|
||||
//using System.Collections.Generic;
|
||||
//using UnityEditor;
|
||||
//using UnityEngine;
|
||||
|
||||
//namespace UnityEditor.U2D
|
||||
//{
|
||||
// public class TextureSettingsGUI
|
||||
// {
|
||||
// public SerializedProperty colorTexture;
|
||||
// public SerializedProperty readable;
|
||||
// public SerializedProperty npotScale;
|
||||
// public SerializedProperty filterMode;
|
||||
// public SerializedProperty aniso;
|
||||
// public SerializedProperty enablePostProcessor;
|
||||
|
||||
// readonly int[] m_FilterModeOptions = (int[])(Enum.GetValues(typeof(FilterMode)));
|
||||
// public TextureSettingsGUI(SerializedProperty sp)
|
||||
// {
|
||||
// colorTexture = sp.FindPropertyRelative("m_ColorTexture");
|
||||
// readable = sp.FindPropertyRelative("m_Readable");
|
||||
// npotScale = sp.FindPropertyRelative("m_NPOTScale");
|
||||
// filterMode = sp.FindPropertyRelative("m_FilterMode");
|
||||
// aniso = sp.FindPropertyRelative("m_Aniso");
|
||||
// enablePostProcessor = sp.FindPropertyRelative("m_EnablePostProcessor");
|
||||
// }
|
||||
|
||||
// public void OnInspectorGUI(bool isPOT, bool isNormalMap, bool hasMipMap, bool isCubeMap, bool hasMipmapFadeout)
|
||||
// {
|
||||
|
||||
// TextureSettingsGUIUtils.ToggleFromInt(colorTexture, TextureSettingsGUIUtils.s_Styles.sRGBTexture);
|
||||
// TextureSettingsGUIUtils.ToggleFromInt(readable, TextureSettingsGUIUtils.s_Styles.readWrite);
|
||||
// using (new EditorGUI.DisabledScope(isPOT))
|
||||
// {
|
||||
// TextureSettingsGUIUtils.EnumPopup(npotScale, typeof(TextureImporterNPOTScale), TextureSettingsGUIUtils.s_Styles.npot);
|
||||
// }
|
||||
|
||||
// EditorGUI.BeginChangeCheck();
|
||||
// // Filter mode
|
||||
// EditorGUI.showMixedValue = filterMode.hasMultipleDifferentValues;
|
||||
// FilterMode filter = (FilterMode)filterMode.intValue;
|
||||
// if ((int)filter == -1)
|
||||
// {
|
||||
// if (hasMipmapFadeout || isNormalMap)
|
||||
// filter = FilterMode.Trilinear;
|
||||
// else
|
||||
// filter = FilterMode.Bilinear;
|
||||
// }
|
||||
// filter = (FilterMode)EditorGUILayout.IntPopup(TextureSettingsGUIUtils.s_Styles.filterMode, (int)filter, TextureSettingsGUIUtils.s_Styles.filterModeOptions, m_FilterModeOptions);
|
||||
// EditorGUI.showMixedValue = false;
|
||||
// if (EditorGUI.EndChangeCheck())
|
||||
// filterMode.intValue = (int)filter;
|
||||
|
||||
// // Aniso
|
||||
// bool showAniso = (FilterMode)filter != FilterMode.Point && hasMipMap && isCubeMap;
|
||||
// using (new EditorGUI.DisabledScope(!showAniso))
|
||||
// {
|
||||
// EditorGUI.BeginChangeCheck();
|
||||
// EditorGUI.showMixedValue = aniso.hasMultipleDifferentValues;
|
||||
// int anisoValue = aniso.intValue;
|
||||
// if (anisoValue == -1)
|
||||
// anisoValue = 1;
|
||||
// //aniso = EditorGUILayout.IntSlider("Aniso Level", aniso, 0, 16);
|
||||
// EditorGUI.showMixedValue = false;
|
||||
// if (EditorGUI.EndChangeCheck())
|
||||
// aniso.intValue = anisoValue;
|
||||
|
||||
// if (anisoValue > 1)
|
||||
// {
|
||||
// if (QualitySettings.anisotropicFiltering == AnisotropicFiltering.Disable)
|
||||
// EditorGUILayout.HelpBox("Anisotropic filtering is disabled for all textures in Quality Settings.", MessageType.Info);
|
||||
// else if (QualitySettings.anisotropicFiltering == AnisotropicFiltering.ForceEnable)
|
||||
// EditorGUILayout.HelpBox("Anisotropic filtering is enabled for all textures in Quality Settings.", MessageType.Info);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// public class TextureSpriteSettingsGUI
|
||||
// {
|
||||
// public SerializedProperty packingTag;
|
||||
// public SerializedProperty ppu;
|
||||
// public SerializedProperty meshType;
|
||||
// public SerializedProperty extrudeEdges;
|
||||
|
||||
// public TextureSpriteSettingsGUI(SerializedProperty sp)
|
||||
// {
|
||||
// packingTag = sp.FindPropertyRelative("m_PackingTag");
|
||||
// ppu = sp.FindPropertyRelative("m_PixelsPerUnit");
|
||||
// meshType = sp.FindPropertyRelative("m_MeshType");
|
||||
// extrudeEdges = sp.FindPropertyRelative("m_ExtrudeEdges");
|
||||
// }
|
||||
|
||||
// public void OnInspectorGUI()
|
||||
// {
|
||||
// //// Show generic attributes
|
||||
// //if (m_SpriteMode.intValue != 0)
|
||||
// //{
|
||||
// // EditorGUILayout.PropertyField(m_SpritePackingTag, s_Styles.spritePackingTag);
|
||||
// // EditorGUILayout.PropertyField(m_SpritePixelsToUnits, s_Styles.spritePixelsPerUnit);
|
||||
|
||||
// // if (m_SpriteMode.intValue != (int)SpriteImportMode.Polygon && !m_SpriteMode.hasMultipleDifferentValues)
|
||||
// // {
|
||||
// // EditorGUILayout.IntPopup(m_SpriteMeshType, s_Styles.spriteMeshTypeOptions, new[] { 0, 1 }, s_Styles.spriteMeshType);
|
||||
// // }
|
||||
// // EditorGUILayout.EndFadeGroup();
|
||||
|
||||
// // EditorGUILayout.IntSlider(m_SpriteExtrude, 0, 32, s_Styles.spriteExtrude);
|
||||
|
||||
// // if (m_SpriteMode.intValue == 1)
|
||||
// // {
|
||||
// // EditorGUILayout.IntPopup(m_Alignment, s_Styles.spriteAlignmentOptions, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, s_Styles.spriteAlignment);
|
||||
|
||||
// // if (m_Alignment.intValue == (int)SpriteAlignment.Custom)
|
||||
// // {
|
||||
// // GUILayout.BeginHorizontal();
|
||||
// // EditorGUILayout.PropertyField(m_SpritePivot, new GUIContent());
|
||||
// // GUILayout.EndHorizontal();
|
||||
// // }
|
||||
// // }
|
||||
// //}
|
||||
// }
|
||||
// }
|
||||
|
||||
// public class TextureWrapSettingsGUI
|
||||
// {
|
||||
// public SerializedProperty wrapMode;
|
||||
// public SerializedProperty wrapModeU;
|
||||
// public SerializedProperty wrapModeV;
|
||||
// public SerializedProperty wrapModeW;
|
||||
|
||||
// public TextureWrapSettingsGUI(SerializedProperty sp)
|
||||
// {
|
||||
// wrapMode = sp.FindPropertyRelative("m_WrapMode");
|
||||
// wrapModeU = sp.FindPropertyRelative("m_WrapModeU");
|
||||
// wrapModeV = sp.FindPropertyRelative("m_WrapModeV");
|
||||
// wrapModeW = sp.FindPropertyRelative("m_WrapModeW");
|
||||
// }
|
||||
|
||||
// public void OnInspectorGUI()
|
||||
// {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// public class TextureAlphaSettingsGUI
|
||||
// {
|
||||
// public SerializedProperty tolerance;
|
||||
// public SerializedProperty source;
|
||||
|
||||
// public TextureAlphaSettingsGUI(SerializedProperty sp)
|
||||
// {
|
||||
// tolerance = sp.FindPropertyRelative("m_AlphaTolerance");
|
||||
// source = sp.FindPropertyRelative("m_AlphaSource");
|
||||
// }
|
||||
|
||||
// public void OnInspectorGUI()
|
||||
// {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// public class TextureMipmapSettingsGUI
|
||||
// {
|
||||
// public SerializedProperty filter;
|
||||
// public SerializedProperty borderMipmap;
|
||||
// public SerializedProperty fadeout;
|
||||
// public SerializedProperty preserveCoverage;
|
||||
// public SerializedProperty fadeDistanceStart;
|
||||
// public SerializedProperty fadeDistanceEnd;
|
||||
|
||||
// public TextureMipmapSettingsGUI(SerializedProperty sp)
|
||||
// {
|
||||
// filter = sp.FindPropertyRelative("m_Filter");
|
||||
// borderMipmap = sp.FindPropertyRelative("m_BorderMipmap");
|
||||
// fadeout = sp.FindPropertyRelative("m_Fadeout");
|
||||
// preserveCoverage = sp.FindPropertyRelative("m_PreserveCoverage");
|
||||
// fadeDistanceStart = sp.FindPropertyRelative("m_FadeDistanceStart");
|
||||
// fadeDistanceEnd = sp.FindPropertyRelative("m_FadeDistanceEnd");
|
||||
// }
|
||||
|
||||
// public void OnInspectorGUI()
|
||||
// {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// public class TextureNormalSettingsGUI
|
||||
// {
|
||||
// public SerializedProperty filter;
|
||||
// public SerializedProperty generateFromGrayScale;
|
||||
// public SerializedProperty bumpiness;
|
||||
|
||||
// public TextureNormalSettingsGUI(SerializedProperty sp)
|
||||
// {
|
||||
// filter = sp.FindPropertyRelative("m_Filter");
|
||||
// generateFromGrayScale = sp.FindPropertyRelative("m_GenerateFromGrayScale");
|
||||
// bumpiness = sp.FindPropertyRelative("m_Bumpiness");
|
||||
// }
|
||||
|
||||
// public void OnInspectorGUI()
|
||||
// {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// public class TextureCubemapSettingsGUI
|
||||
// {
|
||||
// public SerializedProperty convolution;
|
||||
// public SerializedProperty mode;
|
||||
// public SerializedProperty seamless;
|
||||
|
||||
// public TextureCubemapSettingsGUI(SerializedProperty sp)
|
||||
// {
|
||||
// convolution = sp.FindPropertyRelative("m_Convolution");
|
||||
// mode = sp.FindPropertyRelative("m_Mode");
|
||||
// seamless = sp.FindPropertyRelative("m_Seamless");
|
||||
// }
|
||||
|
||||
// public void OnInspectorGUI()
|
||||
// {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// static class TextureSettingsGUIUtils
|
||||
// {
|
||||
// public static void ToggleFromInt(SerializedProperty property, GUIContent label)
|
||||
// {
|
||||
// EditorGUI.BeginChangeCheck();
|
||||
// EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
|
||||
// int value = EditorGUILayout.Toggle(label, property.intValue > 0) ? 1 : 0;
|
||||
// EditorGUI.showMixedValue = false;
|
||||
// if (EditorGUI.EndChangeCheck())
|
||||
// property.intValue = value;
|
||||
// }
|
||||
|
||||
// public static void EnumPopup(SerializedProperty property, System.Type type, GUIContent label)
|
||||
// {
|
||||
// EditorGUILayout.IntPopup(label.text, property.intValue,
|
||||
// System.Enum.GetNames(type),
|
||||
// System.Enum.GetValues(type) as int[]);
|
||||
// }
|
||||
|
||||
// internal class Styles
|
||||
// {
|
||||
// public readonly GUIContent textureTypeTitle = new GUIContent("Texture Type", "What will this texture be used for?");
|
||||
// public readonly GUIContent[] textureTypeOptions =
|
||||
// {
|
||||
// new GUIContent("Default", "Texture is a normal image such as a diffuse texture or other."),
|
||||
// new GUIContent("Sprite (2D and UI)", "Texture is used for a sprite."),
|
||||
// };
|
||||
// public readonly int[] textureTypeValues =
|
||||
// {
|
||||
// (int)TextureImporterType.Default,
|
||||
// (int)TextureImporterType.Sprite,
|
||||
// };
|
||||
|
||||
// public readonly GUIContent textureShape = new GUIContent("Texture Shape", "What shape is this texture?");
|
||||
// private readonly GUIContent textureShape2D = new GUIContent("2D, Texture is 2D.");
|
||||
// private readonly GUIContent textureShapeCube = new GUIContent("Cube", "Texture is a Cubemap.");
|
||||
// public readonly Dictionary<TextureImporterShape, GUIContent[]> textureShapeOptionsDictionnary = new Dictionary<TextureImporterShape, GUIContent[]>();
|
||||
// public readonly Dictionary<TextureImporterShape, int[]> textureShapeValuesDictionnary = new Dictionary<TextureImporterShape, int[]>();
|
||||
|
||||
|
||||
// public readonly GUIContent filterMode = new GUIContent("Filter Mode");
|
||||
// public readonly GUIContent[] filterModeOptions =
|
||||
// {
|
||||
// new GUIContent("Point (no filter)"),
|
||||
// new GUIContent("Bilinear"),
|
||||
// new GUIContent("Trilinear")
|
||||
// };
|
||||
|
||||
// public readonly GUIContent textureFormat = new GUIContent("Format");
|
||||
|
||||
// public readonly GUIContent defaultPlatform = new GUIContent("Default");
|
||||
// public readonly GUIContent mipmapFadeOutToggle = new GUIContent("Fadeout Mip Maps");
|
||||
// public readonly GUIContent mipmapFadeOut = new GUIContent("Fade Range");
|
||||
// public readonly GUIContent readWrite = new GUIContent("Read/Write Enabled", "Enable to be able to access the raw pixel data from code.");
|
||||
|
||||
// public readonly GUIContent alphaSource = new GUIContent("Alpha Source", "How is the alpha generated for the imported texture.");
|
||||
// public readonly GUIContent[] alphaSourceOptions =
|
||||
// {
|
||||
// new GUIContent("None", "No Alpha will be used."),
|
||||
// new GUIContent("Input Texture Alpha", "Use Alpha from the input texture if one is provided."),
|
||||
// new GUIContent("From Gray Scale", "Generate Alpha from image gray scale."),
|
||||
// };
|
||||
// public readonly int[] alphaSourceValues =
|
||||
// {
|
||||
// (int)TextureImporterAlphaSource.None,
|
||||
// (int)TextureImporterAlphaSource.FromInput,
|
||||
// (int)TextureImporterAlphaSource.FromGrayScale,
|
||||
// };
|
||||
|
||||
// public readonly GUIContent generateMipMaps = new GUIContent("Generate Mip Maps");
|
||||
// public readonly GUIContent sRGBTexture = new GUIContent("sRGB (Color Texture)", "Texture content is stored in gamma space. Non-HDR color textures should enable this flag (except if used for IMGUI).");
|
||||
// public readonly GUIContent borderMipMaps = new GUIContent("Border Mip Maps");
|
||||
// public readonly GUIContent mipMapsPreserveCoverage = new GUIContent("Mip Maps Preserve Coverage", "The alpha channel of generated Mip Maps will preserve coverage during the alpha test.");
|
||||
// public readonly GUIContent alphaTestReferenceValue = new GUIContent("Alpha Cutoff Value", "The reference value used during the alpha test. Controls Mip Map coverage.");
|
||||
// public readonly GUIContent mipMapFilter = new GUIContent("Mip Map Filtering");
|
||||
// public readonly GUIContent[] mipMapFilterOptions =
|
||||
// {
|
||||
// new GUIContent("Box"),
|
||||
// new GUIContent("Kaiser"),
|
||||
// };
|
||||
// public readonly GUIContent npot = new GUIContent("Non Power of 2", "How non-power-of-two textures are scaled on import.");
|
||||
|
||||
// public readonly GUIContent compressionQuality = new GUIContent("Compressor Quality");
|
||||
// public readonly GUIContent compressionQualitySlider = new GUIContent("Compressor Quality", "Use the slider to adjust compression quality from 0 (Fastest) to 100 (Best)");
|
||||
// public readonly GUIContent[] mobileCompressionQualityOptions =
|
||||
// {
|
||||
// new GUIContent("Fast"),
|
||||
// new GUIContent("Normal"),
|
||||
// new GUIContent("Best")
|
||||
// };
|
||||
|
||||
// public readonly GUIContent spriteMode = new GUIContent("Sprite Mode");
|
||||
// public readonly GUIContent[] spriteModeOptions =
|
||||
// {
|
||||
// new GUIContent("Single"),
|
||||
// new GUIContent("Multiple"),
|
||||
// new GUIContent("Polygon"),
|
||||
// };
|
||||
// public readonly GUIContent[] spriteMeshTypeOptions =
|
||||
// {
|
||||
// new GUIContent("Full Rect"),
|
||||
// new GUIContent("Tight"),
|
||||
// };
|
||||
|
||||
// public readonly GUIContent spritePackingTag = new GUIContent("Packing Tag", "Tag for the Sprite Packing system.");
|
||||
// public readonly GUIContent spritePixelsPerUnit = new GUIContent("Pixels Per Unit", "How many pixels in the sprite correspond to one unit in the world.");
|
||||
// public readonly GUIContent spriteExtrude = new GUIContent("Extrude Edges", "How much empty area to leave around the sprite in the generated mesh.");
|
||||
// public readonly GUIContent spriteMeshType = new GUIContent("Mesh Type", "Type of sprite mesh to generate.");
|
||||
// public readonly GUIContent spriteAlignment = new GUIContent("Pivot", "Sprite pivot point in its localspace. May be used for syncing animation frames of different sizes.");
|
||||
// public readonly GUIContent[] spriteAlignmentOptions =
|
||||
// {
|
||||
// new GUIContent("Center"),
|
||||
// new GUIContent("Top Left"),
|
||||
// new GUIContent("Top"),
|
||||
// new GUIContent("Top Right"),
|
||||
// new GUIContent("Left"),
|
||||
// new GUIContent("Right"),
|
||||
// new GUIContent("Bottom Left"),
|
||||
// new GUIContent("Bottom"),
|
||||
// new GUIContent("Bottom Right"),
|
||||
// new GUIContent("Custom"),
|
||||
// };
|
||||
|
||||
// public readonly GUIContent alphaIsTransparency = new GUIContent("Alpha Is Transparency", "If the provided alpha channel is transparency, enable this to pre-filter the color to avoid texture filtering artifacts. This is not supported for HDR textures.");
|
||||
// public readonly GUIContent etc1Compression = new GUIContent("Compress using ETC1 (split alpha channel)|Alpha for this texture will be preserved by splitting the alpha channel to another texture, and both resulting textures will be compressed using ETC1.");
|
||||
|
||||
// public readonly GUIContent crunchedCompression = new GUIContent("Use Crunch Compression", "Texture is crunch-compressed to save space on disk when applicable.");
|
||||
|
||||
// public readonly GUIContent showAdvanced = new GUIContent("Advanced", "Show advanced settings.");
|
||||
|
||||
// public Styles()
|
||||
// {
|
||||
// // This is far from ideal, but it's better than having tons of logic in the GUI code itself.
|
||||
// // The combination should not grow too much anyway since only Texture3D will be added later.
|
||||
// GUIContent[] s2D_Options = { textureShape2D };
|
||||
// GUIContent[] sCube_Options = { textureShapeCube };
|
||||
// GUIContent[] s2D_Cube_Options = { textureShape2D, textureShapeCube };
|
||||
// textureShapeOptionsDictionnary.Add(TextureImporterShape.Texture2D, s2D_Options);
|
||||
// textureShapeOptionsDictionnary.Add(TextureImporterShape.TextureCube, sCube_Options);
|
||||
// textureShapeOptionsDictionnary.Add(TextureImporterShape.Texture2D | TextureImporterShape.TextureCube, s2D_Cube_Options);
|
||||
|
||||
// int[] s2D_Values = { (int)TextureImporterShape.Texture2D };
|
||||
// int[] sCube_Values = { (int)TextureImporterShape.TextureCube };
|
||||
// int[] s2D_Cube_Values = { (int)TextureImporterShape.Texture2D, (int)TextureImporterShape.TextureCube };
|
||||
// textureShapeValuesDictionnary.Add(TextureImporterShape.Texture2D, s2D_Values);
|
||||
// textureShapeValuesDictionnary.Add(TextureImporterShape.TextureCube, sCube_Values);
|
||||
// textureShapeValuesDictionnary.Add(TextureImporterShape.Texture2D | TextureImporterShape.TextureCube, s2D_Cube_Values);
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// internal static Styles s_Styles;
|
||||
// }
|
||||
//}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "Unity.2D.Psdimporter.Editor",
|
||||
"references": [
|
||||
"PsdPlugin",
|
||||
"Unity.InternalAPIEditorBridge.001",
|
||||
"Unity.2D.Common.Editor",
|
||||
"Unity.2D.Animation.Editor",
|
||||
"Unity.2D.Animation.Runtime",
|
||||
"Unity.2D.Sprite.Editor"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": []
|
||||
}
|
Reference in New Issue
Block a user