testss
This commit is contained in:
156
Library/PackageCache/com.unity.2d.psdimporter@5.0.1/CHANGELOG.md
Normal file
156
Library/PackageCache/com.unity.2d.psdimporter@5.0.1/CHANGELOG.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# Changelog
|
||||
|
||||
## [5.0.1] - 2021-03-19
|
||||
### Changed
|
||||
- Updated package dependencies
|
||||
|
||||
## [5.0.0] - 2021-03-17
|
||||
### Changed
|
||||
- Update version for release
|
||||
|
||||
## [5.0.0-pre.3] - 2021-03-15
|
||||
### Changed
|
||||
- Updated documentation
|
||||
|
||||
## [5.0.0-pre.2] - 2021-01-16
|
||||
### Changed
|
||||
- Update license file
|
||||
|
||||
### Fixed
|
||||
- Fixed case 1291323 where upgrading from PSDImporter v2 causes Sprite to be missing
|
||||
|
||||
## [5.0.0-pre.1] - 2020-11-02
|
||||
### Added
|
||||
- Added bone sharing from other PSDImporter file
|
||||
|
||||
## [4.0.2] - 2020-08-31
|
||||
### Fixed
|
||||
- Fixed importing files with vector layers generates textures incorrectly (case 1266986)
|
||||
- Fixed Sprite Editor Window doesn't show the Sprite when the Inspector is locked and the Sprite is not selected in the Project window
|
||||
|
||||
## [4.0.1] - 2020-07-07
|
||||
### Fixed
|
||||
- Fixed ArgumentException thrown when 2D Game Kit is imported for the first time (case 1244287)
|
||||
- Updated to use non-experimental AssetImporter (case 1254380)
|
||||
|
||||
## [4.0.0] - 2020-05-11
|
||||
### Changed
|
||||
- Version bump for Unity 2020.2
|
||||
|
||||
## [3.1.4] - 2020-04-09
|
||||
### Fixed
|
||||
- Fix PSD import issues with PSD file without unique layer id
|
||||
- Fix crash on importing huge PSD files
|
||||
- Fix metafile not updated when reimporting
|
||||
- Fix error when importing PSB files with 32-bit color
|
||||
|
||||
### Changed
|
||||
- Improve PSD file import performance
|
||||
|
||||
## [3.1.3] - 2020-03-20
|
||||
### Changed
|
||||
- Update 2D Animation dependency
|
||||
|
||||
## [4.0.0] - 2020-03-11
|
||||
### Changed
|
||||
- Version bump for Unity 2020.2
|
||||
|
||||
## [3.1.2] - 2020-02-27
|
||||
### Fixed
|
||||
- Fixed broken documentation links in inspectors
|
||||
- Fixed empty GameObjects created in certain cases
|
||||
|
||||
## [3.1.1] - 2020-01-09
|
||||
### Fixed
|
||||
- Fix wrong dependency version
|
||||
|
||||
## [3.1.0] - 2019-12-16
|
||||
### Added
|
||||
- Expose PSDImporter class to be accessible via scripting
|
||||
- Added example in manual to show how to set PSDImporter as default importer for PSD files.
|
||||
|
||||
## [3.0.0] - 2019-11-06
|
||||
### Changed
|
||||
- Update version number for Unity 2020.1
|
||||
- Update documentation
|
||||
|
||||
## [2.0.6] - 2019-10-18
|
||||
### Fixed
|
||||
- Fixed SpriteRect name clash when Photoshop layer is renamed to the same name as an exisiting user created SpriteRect
|
||||
|
||||
## [2.0.5] - 2019-08-06
|
||||
### Fixed
|
||||
- Physics Shape not saved into Sprite when importing with AssetDatabase V2
|
||||
|
||||
### Added
|
||||
- Experimental feature to have Sprites with same name generated from source file
|
||||
- Support for providing Layer and Group order to Animation Skinning Module
|
||||
|
||||
## [2.0.4] - 2019-08-09
|
||||
### Added
|
||||
- Add related test packages
|
||||
- Add support Secondary Texture Module in Sprite Editor Window
|
||||
|
||||
### Fixed
|
||||
- Texture and SpriteLibraryAsset subassets in PSDImporter now follows the main asset's name.
|
||||
|
||||
## [2.0.3] - 2019-07-20
|
||||
### Changed
|
||||
- Update 2D Animation dependency
|
||||
|
||||
## [2.0.2] - 2019-07-13
|
||||
### Changed
|
||||
- Mark package to support Unity 2019.3.0a10 onwards.
|
||||
|
||||
## [2.0.1] - 2019-06-12
|
||||
### Changed
|
||||
- Update 2D Animation dependency
|
||||
|
||||
## [2.0.0] - 2019-06-17
|
||||
### Changed
|
||||
- Remove preview tag
|
||||
- Remove experimental namespace
|
||||
|
||||
## [1.2.0-preview.2] - 2019-06-07
|
||||
### Added
|
||||
- Change API to internal access
|
||||
- Only generate Sprite Library Asset if there is entry
|
||||
- Do not reset Reslice checkbox after Inspector apply
|
||||
|
||||
## [1.2.0-preview.1] - 2019-03-15
|
||||
### Added
|
||||
- Update support for 2019.2
|
||||
- Integrate with 2D Animation Sprite Library
|
||||
- Integrate with new 2D Animation Character Group
|
||||
- Fix asset name conflict
|
||||
|
||||
## [1.1.0-preview.2] - 2019-04-23
|
||||
### Added
|
||||
- Fix potential name clashing issues with ScriptedImporter
|
||||
- Fix Prefab asset using wrong name. Note this will break Prefab references if upgrading from previous versions.
|
||||
|
||||
## [1.1.0-preview.1] - 2019-02-19
|
||||
### Added
|
||||
- Update dependency for 2019.1 support
|
||||
|
||||
## [1.0.0-preview.3] - 2019-02-19
|
||||
### Added
|
||||
- Fix compilation error in .NET 3.5
|
||||
|
||||
## [1.0.0-preview.2] - 2019-01-25
|
||||
### Added
|
||||
- Fix unable to rig Sprites created manually
|
||||
- Remove legacy packing tag
|
||||
- Default Texture Type is changed to 'Sprite (2D and UI)'
|
||||
- Default Sprite Mode is changed to 'Multiple'
|
||||
|
||||
## [1.0.0-preview.1] - 2018-11-20
|
||||
### Added
|
||||
- New release
|
||||
- ScriptedImporter for importing Adobe Photoshop file
|
||||
- Supports handling of Adobe Photoshop layers
|
||||
- Creates Sprites from individual layers
|
||||
- Handles include or exclude hidden layers
|
||||
- Supports Prefab generation that reconstruct generated Sprites to original art asset layout
|
||||
- Prefab generation supports GameObject grouping based on Adobe Photoshop layer grouping
|
||||
- Supports 2D Animation v2 single character with multiple Sprites workflow
|
@@ -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": []
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
com.unity.2d.psdimporter copyright © 2020 Unity Technologies ApS
|
||||
|
||||
Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license).
|
||||
|
||||
Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
|
@@ -0,0 +1,9 @@
|
||||
PSB Importer
|
||||
|
||||
ScriptedImporter to import Adobe Photoshop PSB file format
|
||||
|
||||
Feature
|
||||
- Generate texture and sprite by mosaicing layers
|
||||
- Option to generate Prefab to reconstuct the image from generated Sprites
|
||||
- Option to import hidden layers
|
||||
|
@@ -0,0 +1,14 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace UnityEditor.Experimental.U2D.PSDImporter.Tests
|
||||
{
|
||||
internal class Placeholder
|
||||
{
|
||||
[Test]
|
||||
public void PlaceHolderTest()
|
||||
{
|
||||
// Use the Assert class to test conditions.
|
||||
Assert.Pass("This is a placeholder to ensure we have at least one test.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "Unity.2D.Psdimporter.EditorTests",
|
||||
"references": [
|
||||
"Unity.2D.Common.Editor",
|
||||
"Unity.2D.Psdimporter.Editor",
|
||||
"Unity.2D.Animation.Editor",
|
||||
"Unity.2D.Animation.Runtime",
|
||||
"Unity.2D.Sprite.Editor"
|
||||
],
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": []
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
This package contains third-party software components governed by the license(s) indicated below:
|
||||
---------
|
||||
|
||||
Component Name: Photoshop PSD FileType Plugin for Paint.NET
|
||||
|
||||
License Type: MIT, BSD
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Photoshop PSD FileType Plugin for Paint.NET
|
||||
http://psdplugin.codeplex.com/
|
||||
|
||||
Copyright (c) 2006-2007 Frank Blumenberg
|
||||
Copyright (c) 2010-2016 Tao Yue
|
||||
|
||||
MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Portions of the software have been adapted from Yet Another PSD Parser:
|
||||
http://www.codeproject.com/KB/graphics/PSDParser.aspx
|
||||
|
||||
These portions are provided under the BSD License:
|
||||
http://www.opensource.org/licenses/BSD-3-Clause
|
||||
|
||||
----
|
||||
|
||||
Copyright (c) 2006, Jonas Beckeman
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Jonas Beckeman nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY JONAS BECKEMAN AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL JONAS BECKEMAN AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "com.unity.2d.psdimporter",
|
||||
"version": "5.0.1",
|
||||
"unity": "2021.1",
|
||||
"displayName": "2D PSD Importer",
|
||||
"description": "A ScriptedImporter for importing Adobe Photoshop PSB (Photoshop Big) file format. The ScriptedImporter is currently targeted for users who wants to create multi Sprite character animation using Unity 2D Animation Package.\n\nDocumentation for v5.0 is currently being updated.",
|
||||
"keywords": [
|
||||
"2d",
|
||||
"psdimporter",
|
||||
"assetimporter"
|
||||
],
|
||||
"category": "2D",
|
||||
"dependencies": {
|
||||
"com.unity.2d.common": "5.0.0",
|
||||
"com.unity.2d.animation": "6.0.1",
|
||||
"com.unity.2d.sprite": "1.0.0"
|
||||
},
|
||||
"relatedPackages": {
|
||||
"com.unity.2d.psdimporter.tests": "5.0.1"
|
||||
},
|
||||
"upmCi": {
|
||||
"footprint": "ea1429bf4ae47afb9cfb1359c557b9c825a670bc"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.cds.internal.unity3d.com/unity/2d.git",
|
||||
"type": "git",
|
||||
"revision": "1de540a091021db4de462f30f9a458120ea465ae"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user