diff --git a/Default.aspx b/Default.aspx new file mode 100755 index 0000000..5e50d74 --- /dev/null +++ b/Default.aspx @@ -0,0 +1,19 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="GetPsdLayers.Default" %> + + + + + + + + +
+
+ + +

Your layers will appear here as PNGs

+ +
+
+ + diff --git a/Web.config b/Web.config new file mode 100755 index 0000000..8f044cd --- /dev/null +++ b/Web.config @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bin/App_Code/1bpp.cs b/bin/App_Code/1bpp.cs new file mode 100755 index 0000000..6bb4f97 --- /dev/null +++ b/bin/App_Code/1bpp.cs @@ -0,0 +1,227 @@ +using System; + + +namespace PhotoshopFiles +{ + internal class ImageBitChanger + { + //static void Main(string[] args) + //{ + // System.Drawing.Bitmap b = new System.Drawing.Bitmap("test.jpg"); + // SplashImage(b, 0, 0); + // // + // DateTime dtFaq = DateTime.Now; + // System.Drawing.Bitmap b0 = CopyToBpp(b, 1); + // TimeSpan tsFaq = DateTime.Now - dtFaq; + // Console.WriteLine("GDI conversion time: " + tsFaq.ToString()); + // SplashImage(b0, 200, 100); + // // + // DateTime dtLu = DateTime.Now; + // System.Drawing.Bitmap b1 = FaqCopyTo1bpp(b); + // TimeSpan tsLu = DateTime.Now - dtLu; + // Console.WriteLine("FAQ conversion time: " + tsLu.ToString()); + // SplashImage(b1, 400, 200); + // // + // System.Threading.Thread.Sleep(1000); + // InvalidateRect(IntPtr.Zero, IntPtr.Zero, 1); + //} + + + /// + /// Copies a bitmap into a 1bpp/8bpp bitmap of the same dimensions, fast + /// + /// original bitmap + /// 1 or 8, target bpp + /// a 1bpp copy of the bitmap + internal static System.Drawing.Bitmap CopyToBpp(System.Drawing.Bitmap b, int bpp) + { + //System.Drawing.Bitmap b = new System.Drawing.Bitmap(img); + + if (bpp != 1 && bpp != 8) throw new System.ArgumentException("1 or 8", "bpp"); + + // Plan: built into Windows GDI is the ability to convert + // bitmaps from one format to another. Most of the time, this + // job is actually done by the graphics hardware accelerator card + // and so is extremely fast. The rest of the time, the job is done by + // very fast native code. + // We will call into this GDI functionality from C#. Our plan: + // (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed) + // (2) Create a GDI monochrome hbitmap + // (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above) + // (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed) + + int w = b.Width, h = b.Height; + IntPtr hbm = b.GetHbitmap(); // this is step (1) + // + // Step (2): create the monochrome bitmap. + // "BITMAPINFO" is an interop-struct which we define below. + // In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs + BITMAPINFO bmi = new BITMAPINFO(); + bmi.biSize = 40; // the size of the BITMAPHEADERINFO struct + bmi.biWidth = w; + bmi.biHeight = h; + bmi.biPlanes = 1; // "planes" are confusing. We always use just 1. Read MSDN for more info. + bmi.biBitCount = (short)bpp; // ie. 1bpp or 8bpp + bmi.biCompression = BI_RGB; // ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes + bmi.biSizeImage = (uint)(((w + 7) & 0xFFFFFFF8) * h / 8); + bmi.biXPelsPerMeter = 1000000; // not really important + bmi.biYPelsPerMeter = 1000000; // not really important + // Now for the colour table. + uint ncols = (uint)1 << bpp; // 2 colours for 1bpp; 256 colours for 8bpp + bmi.biClrUsed = ncols; + bmi.biClrImportant = ncols; + bmi.cols = new uint[256]; // The structure always has fixed size 256, even if we end up using fewer colours + if (bpp == 1) { bmi.cols[0] = MAKERGB(0, 0, 0); bmi.cols[1] = MAKERGB(255, 255, 255); } + else { for (int i = 0; i < ncols; i++) bmi.cols[i] = MAKERGB(i, i, i); } + // For 8bpp we've created an palette with just greyscale colours. + // You can set up any palette you want here. Here are some possibilities: + // greyscale: for (int i=0; i<256; i++) bmi.cols[i]=MAKERGB(i,i,i); + // rainbow: bmi.biClrUsed=216; bmi.biClrImportant=216; int[] colv=new int[6]{0,51,102,153,204,255}; + // for (int i=0; i<216; i++) bmi.cols[i]=MAKERGB(colv[i/36],colv[(i/6)%6],colv[i%6]); + // optimal: a difficult topic: http://en.wikipedia.org/wiki/Color_quantization + // + // Now create the indexed bitmap "hbm0" + IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap. + IntPtr hbm0 = CreateDIBSection(IntPtr.Zero, ref bmi, DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0); + // + // Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap + // GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC". + IntPtr sdc = GetDC(IntPtr.Zero); // First we obtain the DC for the screen + // Next, create a DC for the original hbitmap + IntPtr hdc = CreateCompatibleDC(sdc); SelectObject(hdc, hbm); + // and create a DC for the monochrome hbitmap + IntPtr hdc0 = CreateCompatibleDC(sdc); SelectObject(hdc0, hbm0); + // Now we can do the BitBlt: + BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, SRCCOPY); + // Step (4): convert this monochrome hbitmap back into a Bitmap: + System.Drawing.Bitmap b0 = System.Drawing.Bitmap.FromHbitmap(hbm0); + // + // Finally some cleanup. + DeleteDC(hdc); + DeleteDC(hdc0); + ReleaseDC(IntPtr.Zero, sdc); + DeleteObject(hbm); + DeleteObject(hbm0); + // + return b0; + } + + /// + /// Draws a bitmap onto the screen. Note: this will be overpainted + /// by other windows when they come to draw themselves. Only use it + /// if you want to draw something quickly and can't be bothered with forms. + /// + /// the bitmap to draw on the screen + /// x screen coordinate + /// y screen coordinate + static void SplashImage(System.Drawing.Bitmap b, int x, int y) + { // Drawing onto the screen is supported by GDI, but not by the Bitmap/Graphics class. + // So we use interop: + // (1) Copy the Bitmap into a GDI hbitmap + IntPtr hbm = b.GetHbitmap(); + // (2) obtain the GDI equivalent of a "Graphics" for the screen + IntPtr sdc = GetDC(IntPtr.Zero); + // (3) obtain the GDI equivalent of a "Graphics" for the hbitmap + IntPtr hdc = CreateCompatibleDC(sdc); + SelectObject(hdc, hbm); + // (4) Draw from the hbitmap's "Graphics" onto the screen's "Graphics" + BitBlt(sdc, x, y, b.Width, b.Height, hdc, 0, 0, SRCCOPY); + // and do boring GDI cleanup: + DeleteDC(hdc); + ReleaseDC(IntPtr.Zero, sdc); + DeleteObject(hbm); + } + + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern bool DeleteObject(IntPtr hObject); + + [System.Runtime.InteropServices.DllImport("user32.dll")] + public static extern int InvalidateRect(IntPtr hwnd, IntPtr rect, int bErase); + + [System.Runtime.InteropServices.DllImport("user32.dll")] + public static extern IntPtr GetDC(IntPtr hwnd); + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + + [System.Runtime.InteropServices.DllImport("user32.dll")] + public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc); + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern int DeleteDC(IntPtr hdc); + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern int BitBlt(IntPtr hdcDst, int xDst, int yDst, int w, int h, IntPtr hdcSrc, int xSrc, int ySrc, int rop); + static int SRCCOPY = 0x00CC0020; + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO bmi, uint Usage, out IntPtr bits, IntPtr hSection, uint dwOffset); + static uint BI_RGB = 0; + static uint DIB_RGB_COLORS = 0; + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] + public struct BITMAPINFO + { + public uint biSize; + public int biWidth, biHeight; + public short biPlanes, biBitCount; + public uint biCompression, biSizeImage; + public int biXPelsPerMeter, biYPelsPerMeter; + public uint biClrUsed, biClrImportant; + [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 256)] + public uint[] cols; + } + + static uint MAKERGB(int r, int g, int b) + { + return ((uint)(b & 255)) | ((uint)((r & 255) << 8)) | ((uint)((g & 255) << 16)); + } + + + /// + /// Copies a bitmap into a 1bpp bitmap of the same dimensions, slowly, using code from Bob Powell's GDI+ faq http://www.bobpowell.net/onebit.htm + /// + /// original bitmap + /// a 1bpp copy of the bitmap + static System.Drawing.Bitmap FaqCopyTo1bpp(System.Drawing.Bitmap b) + { + int w = b.Width, h = b.Height; System.Drawing.Rectangle r = new System.Drawing.Rectangle(0, 0, w, h); + if (b.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppPArgb) + { + System.Drawing.Bitmap temp = new System.Drawing.Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); + System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(temp); + g.DrawImage(b, r, 0, 0, w, h, System.Drawing.GraphicsUnit.Pixel); + g.Dispose(); b = temp; + } + System.Drawing.Imaging.BitmapData bdat = b.LockBits(r, System.Drawing.Imaging.ImageLockMode.ReadOnly, b.PixelFormat); + System.Drawing.Bitmap b0 = new System.Drawing.Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format1bppIndexed); + System.Drawing.Imaging.BitmapData b0dat = b0.LockBits(r, System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format1bppIndexed); + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int index = y * bdat.Stride + (x * 4); + if (System.Drawing.Color.FromArgb(System.Runtime.InteropServices.Marshal.ReadByte(bdat.Scan0, index + 2), System.Runtime.InteropServices.Marshal.ReadByte(bdat.Scan0, index + 1), System.Runtime.InteropServices.Marshal.ReadByte(bdat.Scan0, index)).GetBrightness() > 0.5f) + { + int index0 = y * b0dat.Stride + (x >> 3); + byte p = System.Runtime.InteropServices.Marshal.ReadByte(b0dat.Scan0, index0); + byte mask = (byte)(0x80 >> (x & 0x7)); + System.Runtime.InteropServices.Marshal.WriteByte(b0dat.Scan0, index0, (byte)(p | mask)); + } + } + } + b0.UnlockBits(b0dat); + b.UnlockBits(bdat); + return b0; + } + + + //internal static void CopyToBpp(string p, int p_2) + //{ + // CopyToBpp(p, p_2); + //} + } +} \ No newline at end of file diff --git a/bin/App_Code/AlphaChannels.cs b/bin/App_Code/AlphaChannels.cs new file mode 100755 index 0000000..82a4273 --- /dev/null +++ b/bin/App_Code/AlphaChannels.cs @@ -0,0 +1,78 @@ +///////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2006, Frank Blumenberg +// +// See License.txt for complete licensing and attribution information. +// 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. +// +///////////////////////////////////////////////////////////////////////////////// +using System; +using System.Collections.Generic; + +namespace PhotoshopFiles +{ + /// + /// The names of the alpha channels + /// + public class AlphaChannels : ImageResource + { + private List m_channelNames = new List(); + public List ChannelNames + { + get { return m_channelNames; } + } + + public AlphaChannels() + : base((short)ResourceIDs.AlphaChannelNames) + { + } + + public AlphaChannels(ImageResource imgRes) + : base(imgRes) + { + + BinaryReverseReader reader = imgRes.DataReader; + // the names are pascal strings without padding!!! + while ((reader.BaseStream.Length - reader.BaseStream.Position) > 0) + { + byte stringLength = reader.ReadByte(); + string s = new string(reader.ReadChars(stringLength)); + if (s.Length > 0) + m_channelNames.Add(s); + } + reader.Close(); + } + + protected override void StoreData() + { + System.IO.MemoryStream stream = new System.IO.MemoryStream(); + BinaryReverseWriter writer = new BinaryReverseWriter(stream); + + foreach (string name in m_channelNames) + { + writer.Write((byte)name.Length); + writer.Write(name.ToCharArray()); + } + + writer.Close(); + stream.Close(); + + Data = stream.ToArray(); + } + } +} \ No newline at end of file diff --git a/bin/App_Code/BinaryReverseReader.cs b/bin/App_Code/BinaryReverseReader.cs new file mode 100755 index 0000000..ae1ca40 --- /dev/null +++ b/bin/App_Code/BinaryReverseReader.cs @@ -0,0 +1,293 @@ +///////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2006, Frank Blumenberg +// +// See License.txt for complete licensing and attribution information. +// 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. +// +///////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////////// +// +// This code is adapted from code in the Endogine sprite engine by Jonas Beckeman. +// http://www.endogine.com/CS/ +// +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.IO; + +namespace PhotoshopFiles +{ + /// + /// Reads primitive data types as binary values in in big-endian format + /// + public class BinaryReverseReader : BinaryReader + { + public BinaryReverseReader(Stream a_stream) + : base(a_stream) + { + } + + public override short ReadInt16() + { + short val = base.ReadInt16(); + unsafe + { + this.SwapBytes((byte*)&val, 2); + } + return val; + } + public override int ReadInt32() + { + int val = base.ReadInt32(); + unsafe + { + this.SwapBytes((byte*)&val, 4); + } + return val; + } + public override long ReadInt64() + { + long val = base.ReadInt64(); + unsafe + { + this.SwapBytes((byte*)&val, 8); + } + return val; + } + + public override ushort ReadUInt16() + { + ushort val = base.ReadUInt16(); + unsafe + { + this.SwapBytes((byte*)&val, 2); + } + return val; + } + + public override uint ReadUInt32() + { + uint val = base.ReadUInt32(); + unsafe + { + this.SwapBytes((byte*)&val, 4); + } + return val; + } + + public override ulong ReadUInt64() + { + ulong val = base.ReadUInt64(); + unsafe + { + this.SwapBytes((byte*)&val, 8); + } + return val; + } + + ////////////////////////////////////////////////////////////////// + + public string ReadPascalString() + { + byte stringLength = base.ReadByte(); + + char[] c = base.ReadChars(stringLength); + + if ((stringLength % 2) == 0) + base.ReadByte(); + + return new string(c); + } + + ////////////////////////////////////////////////////////////////// + unsafe protected void SwapBytes(byte* ptr, int nLength) + { + for (long i = 0; i < nLength / 2; ++i) + { + byte t = *(ptr + i); + *(ptr + i) = *(ptr + nLength - i - 1); + *(ptr + nLength - i - 1) = t; + } + } + } + + ////////////////////////////////////////////////////////////////// + + /// + /// Writes primitive data types as binary values in in big-endian format + /// + public class BinaryReverseWriter : BinaryWriter + { + public BinaryReverseWriter(Stream a_stream) + : base(a_stream) + { + } + + public bool AutoFlush; + + public void WritePascalString(string s) + { + char[] c; + if (s.Length > 255) + c = s.Substring(0, 255).ToCharArray(); + else + c = s.ToCharArray(); + + base.Write((byte)c.Length); + base.Write(c); + + int realLength = c.Length + 1; + + if ((realLength % 2) == 0) + return; + + for (int i = 0; i < (2 - (realLength % 2)); i++) + base.Write((byte)0); + + if (AutoFlush) + Flush(); + } + + public override void Write(short val) + { + unsafe + { + this.SwapBytes((byte*)&val, 2); + } + base.Write(val); + + if (AutoFlush) + Flush(); + } + public override void Write(int val) + { + unsafe + { + this.SwapBytes((byte*)&val, 4); + } + base.Write(val); + + if (AutoFlush) + Flush(); + } + public override void Write(long val) + { + unsafe + { + this.SwapBytes((byte*)&val, 8); + } + base.Write(val); + + if (AutoFlush) + Flush(); + } + + public override void Write(ushort val) + { + unsafe + { + this.SwapBytes((byte*)&val, 2); + } + base.Write(val); + + if (AutoFlush) + Flush(); + } + + public override void Write(uint val) + { + unsafe + { + this.SwapBytes((byte*)&val, 4); + } + base.Write(val); + + if (AutoFlush) + Flush(); + } + + public override void Write(ulong val) + { + unsafe + { + this.SwapBytes((byte*)&val, 8); + } + base.Write(val); + + if (AutoFlush) + Flush(); + } + + ////////////////////////////////////////////////////////////////// + + unsafe protected void SwapBytes(byte* ptr, int nLength) + { + for (long i = 0; i < nLength / 2; ++i) + { + byte t = *(ptr + i); + *(ptr + i) = *(ptr + nLength - i - 1); + *(ptr + nLength - i - 1) = t; + } + } + } + + + class LengthWriter : IDisposable + { + long m_lengthPosition = long.MinValue; + long m_startPosition; + BinaryReverseWriter m_writer; + + public LengthWriter(BinaryReverseWriter writer) + { + m_writer = writer; + + // we will write the correct length later, so remember + // the position + m_lengthPosition = m_writer.BaseStream.Position; + m_writer.Write((uint)0xFEEDFEED); + + // remember the start position for calculation Image + // resources length + m_startPosition = m_writer.BaseStream.Position; + } + + public void Write() + { + if (m_lengthPosition != long.MinValue) + { + long endPosition = m_writer.BaseStream.Position; + + m_writer.BaseStream.Position = m_lengthPosition; + long length = endPosition - m_startPosition; + m_writer.Write((uint)length); + m_writer.BaseStream.Position = endPosition; + + m_lengthPosition = long.MinValue; + } + } + + public void Dispose() + { + Write(); + } + } + +} \ No newline at end of file diff --git a/bin/App_Code/ImageDecoderBitmaps.cs b/bin/App_Code/ImageDecoderBitmaps.cs new file mode 100755 index 0000000..442337d --- /dev/null +++ b/bin/App_Code/ImageDecoderBitmaps.cs @@ -0,0 +1,509 @@ +///////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2006, Frank Blumenberg +// +// See License.txt for complete licensing and attribution information. +// 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. +// +///////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////////// +// +// This code contains code from SimplePsd class library by Igor Tolmachev. +// http://www.codeproject.com/csharp/simplepsd.asp +// +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.Drawing.Imaging; + +namespace PhotoshopFiles +{ + public class ImageDecoder + { + public ImageDecoder() + { + + } + + /////////////////////////////////////////////////////////////////////////// + +#if !TEST + private struct PixelData + { + public byte Blue; + public byte Green; + public byte Red; + public byte Alpha; + } +#endif + + /////////////////////////////////////////////////////////////////////////// + + public static Bitmap DecodeImage(PsdFile psdFile) + { + Bitmap bitmap = new Bitmap(psdFile.Columns, psdFile.Rows, PixelFormat.Format32bppArgb); + +#if TEST + for (int y = 0; y < psdFile.Rows; y++) + { + int rowIndex = y * psdFile.Columns; + + for (int x = 0; x < psdFile.Columns; x++) + { + int pos = rowIndex + x; + + Color pixelColor=GetColor(psdFile,pos); + + bitmap.SetPixel(x, y, pixelColor); + } + } + +#else + + Rectangle r = new Rectangle(0, 0, bitmap.Width, bitmap.Height); + BitmapData bd = bitmap.LockBits(r, ImageLockMode.ReadWrite, bitmap.PixelFormat); + + unsafe + { + byte* pCurrRowPixel = (byte*)bd.Scan0.ToPointer(); + + for (int y = 0; y < psdFile.Rows; y++) + { + int rowIndex = y * psdFile.Columns; + PixelData* pCurrPixel = (PixelData*)pCurrRowPixel; + for (int x = 0; x < psdFile.Columns; x++) + { + int pos = rowIndex + x; + + Color pixelColor = GetColor(psdFile, pos); + + pCurrPixel->Alpha = 255; + pCurrPixel->Red = pixelColor.R; + pCurrPixel->Green = pixelColor.G; + pCurrPixel->Blue = pixelColor.B; + + pCurrPixel += 1; + } + pCurrRowPixel += bd.Stride; + } + } + + bitmap.UnlockBits(bd); +#endif + + return bitmap; + } + + /////////////////////////////////////////////////////////////////////////// + + private static Color GetColor(PsdFile psdFile, int pos) + { + Color c = Color.White; + + switch (psdFile.ColorMode) + { + case PsdFile.ColorModes.RGB: + c = Color.FromArgb(psdFile.ImageData[0][pos], + psdFile.ImageData[1][pos], + psdFile.ImageData[2][pos]); + break; + case PsdFile.ColorModes.CMYK: + c = CMYKToRGB(psdFile.ImageData[0][pos], + psdFile.ImageData[1][pos], + psdFile.ImageData[2][pos], + psdFile.ImageData[3][pos]); + break; + case PsdFile.ColorModes.Multichannel: + c = CMYKToRGB(psdFile.ImageData[0][pos], + psdFile.ImageData[1][pos], + psdFile.ImageData[2][pos], + 0); + break; + case PsdFile.ColorModes.Grayscale: + case PsdFile.ColorModes.Duotone: + c = Color.FromArgb(psdFile.ImageData[0][pos], + psdFile.ImageData[0][pos], + psdFile.ImageData[0][pos]); + break; + case PsdFile.ColorModes.Indexed: + { + int index = (int)psdFile.ImageData[0][pos]; + c = Color.FromArgb((int)psdFile.ColorModeData[index], + psdFile.ColorModeData[index + 256], + psdFile.ColorModeData[index + 2 * 256]); + } + break; + case PsdFile.ColorModes.Lab: + { + c = LabToRGB(psdFile.ImageData[0][pos], + psdFile.ImageData[1][pos], + psdFile.ImageData[2][pos]); + } + break; + } + + return c; + } + + /////////////////////////////////////////////////////////////////////////// + + public static Bitmap DecodeImage(Layer layer) + { + if (layer.Rect.Width == 0 || layer.Rect.Height == 0) + { + return null; + } + + Bitmap bitmap = new Bitmap(layer.Rect.Width, layer.Rect.Height, PixelFormat.Format32bppArgb); + +#if TEST + for (int y = 0; y < layer.Rect.Height; y++) + { + int rowIndex = y * layer.Rect.Width; + + for (int x = 0; x < layer.Rect.Width; x++) + { + int pos = rowIndex + x; + + //Color pixelColor=GetColor(psdFile,pos); + Color pixelColor = Color.FromArgb(x % 255, Color.ForestGreen);// 255, 128, 0); + + bitmap.SetPixel(x, y, pixelColor); + } + } + +#else + + Rectangle r = new Rectangle(0, 0, bitmap.Width, bitmap.Height); + BitmapData bd = bitmap.LockBits(r, ImageLockMode.ReadWrite, bitmap.PixelFormat); + + unsafe + { + byte* pCurrRowPixel = (byte*)bd.Scan0.ToPointer(); + + for (int y = 0; y < layer.Rect.Height; y++) + { + int rowIndex = y * layer.Rect.Width; + PixelData* pCurrPixel = (PixelData*)pCurrRowPixel; + for (int x = 0; x < layer.Rect.Width; x++) + { + int pos = rowIndex + x; + + Color pixelColor = GetColor(layer, pos); + + if (layer.SortedChannels.ContainsKey(-2)) + { + int maskAlpha = GetColor(layer.MaskData, x, y); + int oldAlpha = pixelColor.A; + + int newAlpha = (oldAlpha * maskAlpha) / 255; + pixelColor = Color.FromArgb(newAlpha, pixelColor); + } + + pCurrPixel->Alpha = pixelColor.A; + pCurrPixel->Red = pixelColor.R; + pCurrPixel->Green = pixelColor.G; + pCurrPixel->Blue = pixelColor.B; + + pCurrPixel += 1; + } + pCurrRowPixel += bd.Stride; + } + } + + bitmap.UnlockBits(bd); +#endif + + return bitmap; + } + + /////////////////////////////////////////////////////////////////////////// + + private static Color GetColor(Layer layer, int pos) + { + Color c = Color.White; + + switch (layer.PsdFile.ColorMode) + { + case PsdFile.ColorModes.RGB: + c = Color.FromArgb(layer.SortedChannels[0].ImageData[pos], + layer.SortedChannels[1].ImageData[pos], + layer.SortedChannels[2].ImageData[pos]); + break; + case PsdFile.ColorModes.CMYK: + c = CMYKToRGB(layer.SortedChannels[0].ImageData[pos], + layer.SortedChannels[1].ImageData[pos], + layer.SortedChannels[2].ImageData[pos], + layer.SortedChannels[3].ImageData[pos]); + break; + case PsdFile.ColorModes.Multichannel: + c = CMYKToRGB(layer.SortedChannels[0].ImageData[pos], + layer.SortedChannels[1].ImageData[pos], + layer.SortedChannels[2].ImageData[pos], + 0); + break; + case PsdFile.ColorModes.Grayscale: + case PsdFile.ColorModes.Duotone: + c = Color.FromArgb(layer.SortedChannels[0].ImageData[pos], + layer.SortedChannels[0].ImageData[pos], + layer.SortedChannels[0].ImageData[pos]); + break; + case PsdFile.ColorModes.Indexed: + { + int index = (int)layer.SortedChannels[0].ImageData[pos]; + c = Color.FromArgb((int)layer.PsdFile.ColorModeData[index], + layer.PsdFile.ColorModeData[index + 256], + layer.PsdFile.ColorModeData[index + 2 * 256]); + } + break; + case PsdFile.ColorModes.Lab: + { + c = LabToRGB(layer.SortedChannels[0].ImageData[pos], + layer.SortedChannels[1].ImageData[pos], + layer.SortedChannels[2].ImageData[pos]); + } + break; + } + + if (layer.SortedChannels.ContainsKey(-1)) + c = Color.FromArgb(layer.SortedChannels[-1].ImageData[pos], c); + + return c; + } + + /////////////////////////////////////////////////////////////////////////// + + private static int GetColor(Layer.Mask mask, int x, int y) + { + int c = 255; + + if (mask.PositionIsRelative) + { + x -= mask.Rect.X; + y -= mask.Rect.Y; + } + else + { + x = (x + mask.Layer.Rect.X) - mask.Rect.X; + y = (y + mask.Layer.Rect.Y) - mask.Rect.Y; + } + + if (y >= 0 && y < mask.Rect.Height && + x >= 0 && x < mask.Rect.Width) + { + int pos = y * mask.Rect.Width + x; + if (pos < mask.ImageData.Length) + c = mask.ImageData[pos]; + else + c = 255; + } + + return c; + } + + /////////////////////////////////////////////////////////////////////////// + + public static Bitmap DecodeImage(Layer.Mask mask) + { + Layer layer = mask.Layer; + + if (mask.Rect.Width == 0 || mask.Rect.Height == 0) + { + return null; + } + + Bitmap bitmap = new Bitmap(mask.Rect.Width, mask.Rect.Height, PixelFormat.Format32bppArgb); + + Rectangle r = new Rectangle(0, 0, bitmap.Width, bitmap.Height); + BitmapData bd = bitmap.LockBits(r, ImageLockMode.ReadWrite, bitmap.PixelFormat); + + unsafe + { + byte* pCurrRowPixel = (byte*)bd.Scan0.ToPointer(); + + for (int y = 0; y < mask.Rect.Height; y++) + { + int rowIndex = y * mask.Rect.Width; + PixelData* pCurrPixel = (PixelData*)pCurrRowPixel; + for (int x = 0; x < mask.Rect.Width; x++) + { + int pos = rowIndex + x; + + Color pixelColor = Color.FromArgb(mask.ImageData[pos], mask.ImageData[pos], mask.ImageData[pos]); + + pCurrPixel->Alpha = 255; + pCurrPixel->Red = pixelColor.R; + pCurrPixel->Green = pixelColor.G; + pCurrPixel->Blue = pixelColor.B; + + pCurrPixel += 1; + } + pCurrRowPixel += bd.Stride; + } + } + + bitmap.UnlockBits(bd); + + return bitmap; + } + + /////////////////////////////////////////////////////////////////////////// + + private static Color LabToRGB(byte lb, byte ab, byte bb) + { + double exL, exA, exB; + + exL = (double)lb; + exA = (double)ab; + exB = (double)bb; + + double L_coef, a_coef, b_coef; + L_coef = 256.0 / 100.0; + a_coef = 256.0 / 256.0; + b_coef = 256.0 / 256.0; + + int L = (int)(exL / L_coef); + int a = (int)(exA / a_coef - 128.0); + int b = (int)(exB / b_coef - 128.0); + + // For the conversion we first convert values to XYZ and then to RGB + // 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; + + if (Math.Pow(var_Y, 3) > 0.008856) + var_Y = Math.Pow(var_Y, 3); + else + var_Y = (var_Y - 16 / 116) / 7.787; + + if (Math.Pow(var_X, 3) > 0.008856) + var_X = Math.Pow(var_X, 3); + else + var_X = (var_X - 16 / 116) / 7.787; + + if (Math.Pow(var_Z, 3) > 0.008856) + var_Z = Math.Pow(var_Z, 3); + 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; + + return XYZToRGB(X, Y, Z); + } + + //////////////////////////////////////////////////////////////////////////// + + private static Color XYZToRGB(double X, double Y, double Z) + { + // Standards used Observer = 2, Illuminant = D65 + // ref_X = 95.047, ref_Y = 100.000, ref_Z = 108.883 + + double var_X = X / 100.0; + double var_Y = Y / 100.0; + double var_Z = Z / 100.0; + + double var_R = var_X * 3.2406 + var_Y * (-1.5372) + var_Z * (-0.4986); + double var_G = var_X * (-0.9689) + var_Y * 1.8758 + var_Z * 0.0415; + double var_B = var_X * 0.0557 + var_Y * (-0.2040) + var_Z * 1.0570; + + 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; + + return Color.FromArgb(nRed, nGreen, nBlue); + } + + /////////////////////////////////////////////////////////////////////////////// + // + // The algorithms for these routines were taken from: + // http://www.neuro.sfc.keio.ac.jp/~aly/polygon/info/color-space-faq.html + // + // 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 Color CMYKToRGB(byte c, byte m, byte y, byte k) + { + double C, M, Y, K; + + double exC, exM, exY, exK; + double dMaxColours = Math.Pow(2, 8); + + exC = (double)c; + exM = (double)m; + exY = (double)y; + exK = (double)k; + + C = (1.0 - exC / dMaxColours); + M = (1.0 - exM / dMaxColours); + Y = (1.0 - exY / dMaxColours); + K = (1.0 - exK / dMaxColours); + + int nRed = (int)((1.0 - (C * (1 - K) + K)) * 255); + int nGreen = (int)((1.0 - (M * (1 - K) + K)) * 255); + int nBlue = (int)((1.0 - (Y * (1 - K) + K)) * 255); + + 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; + + return Color.FromArgb(nRed, nGreen, nBlue); + } + } +} \ No newline at end of file diff --git a/bin/App_Code/ImageResource.cs b/bin/App_Code/ImageResource.cs new file mode 100755 index 0000000..c01c425 --- /dev/null +++ b/bin/App_Code/ImageResource.cs @@ -0,0 +1,219 @@ +///////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2006, Frank Blumenberg +// +// See License.txt for complete licensing and attribution information. +// 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. +// +///////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////////// +// +// This code is adapted from code in the Endogine sprite engine by Jonas Beckeman. +// http://www.endogine.com/CS/ +// +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; + +namespace PhotoshopFiles +{ + + public enum ResourceIDs + { + Undefined = 0, + MacPrintInfo = 1001, + ResolutionInfo = 1005, + AlphaChannelNames = 1006, + DisplayInfo = 1007, + Caption = 1008, + BorderInfo = 1009, + BgColor = 1010, + PrintFlags = 1011, + MultiChannelHalftoneInfo = 1012, + ColorHalftoneInfo = 1013, + DuotoneHalftoneInfo = 1014, + MultiChannelTransferFunctions = 1015, + ColorTransferFunctions = 1016, + DuotoneTransferFunctions = 1017, + DuotoneImageInfo = 1018, + BlackWhiteRange = 1019, + EPSOptions = 1021, + QuickMaskInfo = 1022, //2 bytes containing Quick Mask channel ID, 1 byte boolean indicating whether the mask was initially empty. + LayerStateInfo = 1024, //2 bytes containing the index of target layer. 0=bottom layer. + WorkingPathUnsaved = 1025, + LayersGroupInfo = 1026, //2 bytes per layer containing a group ID for the dragging groups. Layers in a group have the same group ID. + IPTC_NAA = 1028, + RawFormatImageMode = 1029, + JPEGQuality = 1030, + GridGuidesInfo = 1032, + Thumbnail1 = 1033, + CopyrightInfo = 1034, + URL = 1035, + Thumbnail2 = 1036, + GlobalAngle = 1037, + ColorSamplers = 1038, + ICCProfile = 1039, //The raw bytes of an ICC format profile, see the ICC34.pdf and ICC34.h files from the Internation Color Consortium located in the documentation section + Watermark = 1040, + ICCUntagged = 1041, //1 byte that disables any assumed profile handling when opening the file. 1 = intentionally untagged. + EffectsVisible = 1042, //1 byte global flag to show/hide all the effects layer. Only present when they are hidden. + SpotHalftone = 1043, // 4 bytes for version, 4 bytes for length, and the variable length data. + DocumentSpecific = 1044, + UnicodeAlphaNames = 1045, // 4 bytes for length and the string as a unicode string + IndexedColorTableCount = 1046, // 2 bytes for the number of colors in table that are actually defined + TransparentIndex = 1047, + GlobalAltitude = 1049, // 4 byte entry for altitude + Slices = 1050, + WorkflowURL = 1051, //Unicode string, 4 bytes of length followed by unicode string + JumpToXPEP = 1052, //2 bytes major version, 2 bytes minor version, + //4 bytes count. Following is repeated for count: 4 bytes block size, + //4 bytes key, if key = 'jtDd' then next is a Boolean for the dirty flag + //otherwise it’s a 4 byte entry for the mod date + AlphaIdentifiers = 1053, //4 bytes of length, followed by 4 bytes each for every alpha identifier. + URLList = 1054, //4 byte count of URLs, followed by 4 byte long, 4 byte ID, and unicode string for each count. + VersionInfo = 1057, //4 byte version, 1 byte HasRealMergedData, unicode string of writer name, unicode string of reader name, 4 bytes of file version. + Unknown4 = 1058, //pretty long, 302 bytes in one file. Holds creation date, maybe Photoshop license number + XMLInfo = 1060, //some kind of XML definition of file. The xpacket tag seems to hold binary data + Unknown = 1061, //seems to be common! + Unknown2 = 1062, //seems to be common! + Unknown3 = 1064, //seems to be common! + PathInfo = 2000, //2000-2999 actually I think? + ClippingPathName = 2999, + PrintFlagsInfo = 10000 + } + + + /// + /// Summary description for ImageResource. + /// + public class ImageResource + { + private short m_id; + public short ID + { + get { return m_id; } + set { m_id = value; } + } + + private string m_name = String.Empty; + public string Name + { + get { return m_name; } + set { m_name = value; } + } + + private byte[] m_data; + public byte[] Data + { + get { return m_data; } + set { m_data = value; } + } + + private string m_osType = String.Empty; + + public string OSType + { + get { return m_osType; } + set { m_osType = value; } + } + + public ImageResource() + { + } + + public ImageResource(short id) + { + m_id = id; + } + + public ImageResource(ImageResource imgRes) + { + m_id = imgRes.m_id; + m_name = imgRes.m_name; + + m_data = new byte[imgRes.m_data.Length]; + imgRes.m_data.CopyTo(m_data, 0); + } + + ////////////////////////////////////////////////////////////////// + + public ImageResource(BinaryReverseReader reader) + { + m_osType = new string(reader.ReadChars(4)); + if (m_osType != "8BIM" && m_osType != "MeSa") + { + throw new InvalidOperationException("Could not read an image resource"); + } + + m_id = reader.ReadInt16(); + m_name = reader.ReadPascalString(); + + uint settingLength = reader.ReadUInt32(); + m_data = reader.ReadBytes((int)settingLength); + + if (reader.BaseStream.Position % 2 == 1) + reader.ReadByte(); + } + + ////////////////////////////////////////////////////////////////// + + public void Save(BinaryReverseWriter writer) + { + StoreData(); + + if (m_osType == String.Empty) + m_osType = "8BIM"; + + writer.Write(m_osType.ToCharArray()); + writer.Write(m_id); + + writer.WritePascalString(m_name); + + writer.Write((int)m_data.Length); + writer.Write(m_data); + + if (writer.BaseStream.Position % 2 == 1) + writer.Write((byte)0); + } + + ////////////////////////////////////////////////////////////////// + + protected virtual void StoreData() + { + + } + + ////////////////////////////////////////////////////////////////// + + public BinaryReverseReader DataReader + { + get + { + return new BinaryReverseReader(new System.IO.MemoryStream(this.m_data)); + } + } + + ////////////////////////////////////////////////////////////////// + + public override string ToString() + { + return String.Format("{0} {1}", (ResourceIDs)m_id, m_name); + } + } +} \ No newline at end of file diff --git a/bin/App_Code/Layer.cs b/bin/App_Code/Layer.cs new file mode 100755 index 0000000..a7d7cd2 --- /dev/null +++ b/bin/App_Code/Layer.cs @@ -0,0 +1,968 @@ +///////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2006, Frank Blumenberg +// +// See License.txt for complete licensing and attribution information. +// 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. +// +///////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////////// +// +// This code contains code from the Endogine sprite engine by Jonas Beckeman. +// http://www.endogine.com/CS/ +// +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; +using System.Drawing; +using System.IO; +using System.Diagnostics; + +namespace PhotoshopFiles +{ + public class Layer + { + /////////////////////////////////////////////////////////////////////////// + + public class Channel + { + private Layer m_layer; + /// + /// The layer to which this channel belongs + /// + public Layer Layer + { + get { return m_layer; } + } + + + private short m_id; + /// + /// 0 = red, 1 = green, etc. + /// –1 = transparency mask + /// –2 = user supplied layer mask + /// + public short ID + { + get { return m_id; } + set { m_id = value; } + } + + /// + /// The length of the compressed channel data. + /// + public int Length; + + private byte[] m_data; + /// + /// The compressed raw channel data + /// + public byte[] Data + { + get { return m_data; } + set { m_data = value; } + } + + /// + /// The raw image data from the channel. + /// + public byte[] m_imageData; + + public byte[] ImageData + { + get { return m_imageData; } + set { m_imageData = value; } + } + + private ImageCompression m_imageCompression; + public ImageCompression ImageCompression + { + get { return m_imageCompression; } + set { m_imageCompression = value; } + } + + ////////////////////////////////////////////////////////////////// + + internal Channel(short id, Layer layer) + { + m_id = id; + m_layer = layer; + m_layer.Channels.Add(this); + m_layer.SortedChannels.Add(this.ID, this); + } + + internal Channel(BinaryReverseReader reader, Layer layer) + { + Debug.WriteLine("Channel started at " + reader.BaseStream.Position.ToString()); + + m_id = reader.ReadInt16(); + Length = reader.ReadInt32(); + + m_layer = layer; + } + + internal void Save(BinaryReverseWriter writer) + { + Debug.WriteLine("Channel Save started at " + writer.BaseStream.Position.ToString()); + + writer.Write(m_id); + + CompressImageData(); + + writer.Write(Data.Length + 2); // 2 bytes for the image compression + } + + ////////////////////////////////////////////////////////////////// + + internal void LoadPixelData(BinaryReverseReader reader) + { + Debug.WriteLine("Channel.LoadPixelData started at " + reader.BaseStream.Position.ToString()); + + m_data = reader.ReadBytes((int)Length); + + using (BinaryReverseReader readerImg = DataReader) + { + m_imageCompression = (ImageCompression)readerImg.ReadInt16(); + + int bytesPerRow = 0; + + switch (m_layer.PsdFile.Depth) + { + case 1: + bytesPerRow = m_layer.m_rect.Width;//NOT Shure + break; + case 8: + bytesPerRow = m_layer.m_rect.Width; + break; + case 16: + bytesPerRow = m_layer.m_rect.Width * 2; + break; + } + + m_imageData = new byte[m_layer.m_rect.Height * bytesPerRow]; + + switch (m_imageCompression) + { + case ImageCompression.Raw: + readerImg.Read(m_imageData, 0, m_imageData.Length); + break; + case ImageCompression.Rle: + { + int[] rowLenghtList = new int[m_layer.m_rect.Height]; + for (int i = 0; i < rowLenghtList.Length; i++) + rowLenghtList[i] = readerImg.ReadInt16(); + + for (int i = 0; i < m_layer.m_rect.Height; i++) + { + int rowIndex = i * m_layer.m_rect.Width; + RleHelper.DecodedRow(readerImg.BaseStream, m_imageData, rowIndex, bytesPerRow); + + //if (rowLenghtList[i] % 2 == 1) + // readerImg.ReadByte(); + } + } + break; + default: + break; + } + } + } + + private void CompressImageData() + { + if (m_imageCompression == ImageCompression.Rle) + { + MemoryStream dataStream = new MemoryStream(); + BinaryReverseWriter writer = new BinaryReverseWriter(dataStream); + + // we will write the correct lengths later, so remember + // the position + long lengthPosition = writer.BaseStream.Position; + + int[] rleRowLenghs = new int[m_layer.m_rect.Height]; + + if (m_imageCompression == ImageCompression.Rle) + { + for (int i = 0; i < rleRowLenghs.Length; i++) + { + writer.Write((short)0x1234); + } + } + + //--------------------------------------------------------------- + + int bytesPerRow = 0; + + switch (m_layer.PsdFile.Depth) + { + case 1: + bytesPerRow = m_layer.m_rect.Width;//NOT Shure + break; + case 8: + bytesPerRow = m_layer.m_rect.Width; + break; + case 16: + bytesPerRow = m_layer.m_rect.Width * 2; + break; + } + + //--------------------------------------------------------------- + + for (int row = 0; row < m_layer.m_rect.Height; row++) + { + int rowIndex = row * m_layer.m_rect.Width; + rleRowLenghs[row] = RleHelper.EncodedRow(writer.BaseStream, m_imageData, rowIndex, bytesPerRow); + } + + //--------------------------------------------------------------- + + long endPosition = writer.BaseStream.Position; + + writer.BaseStream.Position = lengthPosition; + + for (int i = 0; i < rleRowLenghs.Length; i++) + { + writer.Write((short)rleRowLenghs[i]); + } + + writer.BaseStream.Position = endPosition; + + dataStream.Close(); + + m_data = dataStream.ToArray(); + + dataStream.Dispose(); + + } + else + { + m_data = (byte[])m_imageData.Clone(); + } + } + + internal void SavePixelData(BinaryReverseWriter writer) + { + Debug.WriteLine("Channel SavePixelData started at " + writer.BaseStream.Position.ToString()); + + writer.Write((short)m_imageCompression); + writer.Write(m_imageData); + } + + ////////////////////////////////////////////////////////////////// + + public BinaryReverseReader DataReader + { + get + { + if (m_data == null) + return null; + + return new BinaryReverseReader(new System.IO.MemoryStream(this.m_data)); + } + } + } + + /////////////////////////////////////////////////////////////////////////// + + public class Mask + { + private Layer m_layer; + /// + /// The layer to which this mask belongs + /// + public Layer Layer + { + get { return m_layer; } + } + + private Rectangle m_rect = Rectangle.Empty; + /// + /// The rectangle enclosing the mask. + /// + public Rectangle Rect + { + get { return m_rect; } + set { m_rect = value; } + } + + private byte m_defaultColor; + public byte DefaultColor + { + get { return m_defaultColor; } + set { m_defaultColor = value; } + } + + + private static int m_positionIsRelativeBit = BitVector32.CreateMask(); + private static int m_disabledBit = BitVector32.CreateMask(m_positionIsRelativeBit); + private static int m_invertOnBlendBit = BitVector32.CreateMask(m_disabledBit); + + private BitVector32 m_flags = new BitVector32(); + /// + /// If true, the position of the mask is relative to the layer. + /// + public bool PositionIsRelative + { + get + { + return m_flags[m_positionIsRelativeBit]; + } + set + { + m_flags[m_positionIsRelativeBit] = value; + } + } + + public bool Disabled + { + get { return m_flags[m_disabledBit]; } + set { m_flags[m_disabledBit] = value; } + } + + /// + /// if true, invert the mask when blending. + /// + public bool InvertOnBlendBit + { + get { return m_flags[m_invertOnBlendBit]; } + set { m_flags[m_invertOnBlendBit] = value; } + } + + /////////////////////////////////////////////////////////////////////////// + + internal Mask(Layer layer) + { + m_layer = layer; + m_layer.MaskData = this; + } + + /////////////////////////////////////////////////////////////////////////// + + internal Mask(BinaryReverseReader reader, Layer layer) + { + Debug.WriteLine("Mask started at " + reader.BaseStream.Position.ToString()); + + m_layer = layer; + + uint maskLength = reader.ReadUInt32(); + + if (maskLength <= 0) + return; + + long startPosition = reader.BaseStream.Position; + + //----------------------------------------------------------------------- + + m_rect = new Rectangle(); + m_rect.Y = reader.ReadInt32(); + m_rect.X = reader.ReadInt32(); + m_rect.Height = reader.ReadInt32() - m_rect.Y; + m_rect.Width = reader.ReadInt32() - m_rect.X; + + m_defaultColor = reader.ReadByte(); + + //----------------------------------------------------------------------- + + byte flags = reader.ReadByte(); + m_flags = new BitVector32(flags); + + //----------------------------------------------------------------------- + + if (maskLength == 36) + { + BitVector32 realFlags = new BitVector32(reader.ReadByte()); + + byte realUserMaskBackground = reader.ReadByte(); + + Rectangle rect = new Rectangle(); + rect.Y = reader.ReadInt32(); + rect.X = reader.ReadInt32(); + rect.Height = reader.ReadInt32() - m_rect.Y; + rect.Width = reader.ReadInt32() - m_rect.X; + } + + + // there is other stuff following, but we will ignore this. + reader.BaseStream.Position = startPosition + maskLength; + } + + /////////////////////////////////////////////////////////////////////////// + + public void Save(BinaryReverseWriter writer) + { + Debug.WriteLine("Mask Save started at " + writer.BaseStream.Position.ToString()); + + if (m_rect.IsEmpty) + { + writer.Write((uint)0); + return; + } + + using (new LengthWriter(writer)) + { + writer.Write(m_rect.Top); + writer.Write(m_rect.Left); + writer.Write(m_rect.Bottom); + writer.Write(m_rect.Right); + + writer.Write(m_defaultColor); + + writer.Write((byte)m_flags.Data); + + // padding 2 bytes so that size is 20 + writer.Write((int)0); + } + } + + ////////////////////////////////////////////////////////////////// + + /// + /// The raw image data from the channel. + /// + public byte[] m_imageData; + + public byte[] ImageData + { + get { return m_imageData; } + set { m_imageData = value; } + } + + internal void LoadPixelData(BinaryReverseReader reader) + { + Debug.WriteLine("Mask.LoadPixelData started at " + reader.BaseStream.Position.ToString()); + + if (m_rect.IsEmpty || m_layer.SortedChannels.ContainsKey(-2) == false) + return; + + Channel maskChannel = m_layer.SortedChannels[-2]; + + + maskChannel.Data = reader.ReadBytes((int)maskChannel.Length); + + + using (BinaryReverseReader readerImg = maskChannel.DataReader) + { + maskChannel.ImageCompression = (ImageCompression)readerImg.ReadInt16(); + + int bytesPerRow = 0; + + switch (m_layer.PsdFile.Depth) + { + case 1: + bytesPerRow = m_rect.Width;//NOT Shure + break; + case 8: + bytesPerRow = m_rect.Width; + break; + case 16: + bytesPerRow = m_rect.Width * 2; + break; + } + + maskChannel.ImageData = new byte[m_rect.Height * bytesPerRow]; + // Fill Array + for (int i = 0; i < maskChannel.ImageData.Length; i++) + { + maskChannel.ImageData[i] = 0xAB; + } + + m_imageData = (byte[])maskChannel.ImageData.Clone(); + + switch (maskChannel.ImageCompression) + { + case ImageCompression.Raw: + readerImg.Read(maskChannel.ImageData, 0, maskChannel.ImageData.Length); + break; + case ImageCompression.Rle: + { + int[] rowLenghtList = new int[m_rect.Height]; + + for (int i = 0; i < rowLenghtList.Length; i++) + rowLenghtList[i] = readerImg.ReadInt16(); + + for (int i = 0; i < m_rect.Height; i++) + { + int rowIndex = i * m_rect.Width; + RleHelper.DecodedRow(readerImg.BaseStream, maskChannel.ImageData, rowIndex, bytesPerRow); + } + } + break; + default: + break; + } + + m_imageData = (byte[])maskChannel.ImageData.Clone(); + + } + } + + internal void SavePixelData(BinaryReverseWriter writer) + { + //writer.Write(m_data); + } + + + /////////////////////////////////////////////////////////////////////////// + + } + + + /////////////////////////////////////////////////////////////////////////// + + public class BlendingRanges + { + private Layer m_layer; + /// + /// The layer to which this channel belongs + /// + public Layer Layer + { + get { return m_layer; } + } + + private byte[] m_data = new byte[0]; + + public byte[] Data + { + get { return m_data; } + set { m_data = value; } + } + + /////////////////////////////////////////////////////////////////////////// + + public BlendingRanges(Layer layer) + { + m_layer = layer; + m_layer.BlendingRangesData = this; + } + + /////////////////////////////////////////////////////////////////////////// + + public BlendingRanges(BinaryReverseReader reader, Layer layer) + { + Debug.WriteLine("BlendingRanges started at " + reader.BaseStream.Position.ToString()); + + m_layer = layer; + int dataLength = reader.ReadInt32(); + if (dataLength <= 0) + return; + + m_data = reader.ReadBytes(dataLength); + } + + /////////////////////////////////////////////////////////////////////////// + + public void Save(BinaryReverseWriter writer) + { + Debug.WriteLine("BlendingRanges Save started at " + writer.BaseStream.Position.ToString()); + + writer.Write((uint)m_data.Length); + writer.Write(m_data); + } + } + + /////////////////////////////////////////////////////////////////////////// + + public class AdjusmentLayerInfo + { + private Layer m_layer; + /// + /// The layer to which this info belongs + /// + internal Layer Layer + { + get { return m_layer; } + } + + private string m_key; + public string Key + { + get { return m_key; } + set { m_key = value; } + } + + private byte[] m_data; + public byte[] Data + { + get { return m_data; } + set { m_data = value; } + } + + public AdjusmentLayerInfo(string key, Layer layer) + { + m_key = key; + m_layer = layer; + m_layer.AdjustmentInfo.Add(this); + } + + public AdjusmentLayerInfo(BinaryReverseReader reader, Layer layer) + { + Debug.WriteLine("AdjusmentLayerInfo started at " + reader.BaseStream.Position.ToString()); + + m_layer = layer; + + string signature = new string(reader.ReadChars(4)); + if (signature != "8BIM") + { + throw new IOException("Could not read an image resource"); + } + + m_key = new string(reader.ReadChars(4)); + + uint dataLength = reader.ReadUInt32(); + m_data = reader.ReadBytes((int)dataLength); + } + + public void Save(BinaryReverseWriter writer) + { + Debug.WriteLine("AdjusmentLayerInfo Save started at " + writer.BaseStream.Position.ToString()); + + string signature = "8BIM"; + + writer.Write(signature.ToCharArray()); + writer.Write(m_key.ToCharArray()); + writer.Write((uint)m_data.Length); + writer.Write(m_data); + } + + ////////////////////////////////////////////////////////////////// + + public BinaryReverseReader DataReader + { + get + { + return new BinaryReverseReader(new System.IO.MemoryStream(this.m_data)); + } + } + } + + + /////////////////////////////////////////////////////////////////////////// + + private PsdFile m_psdFile; + internal PsdFile PsdFile + { + get { return m_psdFile; } + } + + private Rectangle m_rect = Rectangle.Empty; + /// + /// The rectangle containing the contents of the layer. + /// + public Rectangle Rect + { + get { return m_rect; } + set { m_rect = value; } + } + + + /// + /// Channel information. + /// + private List m_channels = new List(); + + public List Channels + { + get { return m_channels; } + } + + private SortedList m_sortedChannels = new SortedList(); + public SortedList SortedChannels + { + get + { + return m_sortedChannels; + } + } + + private string m_blendModeKey = "norm"; + /// + /// The blend mode key for the layer + /// + /// + /// + /// + /// normnormal + /// darkdarken + /// litelighten + /// hue hue + /// sat saturation + /// colrcolor + /// lum luminosity + /// mul multiply + /// scrnscreen + /// dissdissolve + /// overoverlay + /// hLithard light + /// sLitsoft light + /// diffdifference + /// smudexlusion + /// div color dodge + /// idivcolor burn + /// + /// + public string BlendModeKey + { + get { return m_blendModeKey; } + set + { + if (value.Length != 4) throw new ArgumentException("Key length must be 4"); + } + } + + + private byte m_opacity; + /// + /// 0 = transparent ... 255 = opaque + /// + public byte Opacity + { + get { return m_opacity; } + set { m_opacity = value; } + } + + + private bool m_clipping; + /// + /// false = base, true = non–base + /// + public bool Clipping + { + get { return m_clipping; } + set { m_clipping = value; } + } + + private static int m_protectTransBit = BitVector32.CreateMask(); + private static int m_visibleBit = BitVector32.CreateMask(m_protectTransBit); + + BitVector32 m_flags = new BitVector32(); + + /// + /// If true, the layer is visible. + /// + public bool Visible + { + get { return !m_flags[m_visibleBit]; } + set { m_flags[m_visibleBit] = !value; } + } + + + /// + /// Protect the transparency + /// + public bool ProtectTrans + { + get { return m_flags[m_protectTransBit]; } + set { m_flags[m_protectTransBit] = value; } + } + + + private string m_name; + /// + /// The descriptive layer name + /// + public string Name + { + get { return m_name; } + set { m_name = value; } + } + + private BlendingRanges m_blendingRangesData; + public Layer.BlendingRanges BlendingRangesData + { + get { return m_blendingRangesData; } + set { m_blendingRangesData = value; } + } + + private Mask m_maskData; + public Layer.Mask MaskData + { + get { return m_maskData; } + set { m_maskData = value; } + } + + private List m_adjustmentInfo = new List(); + public List AdjustmentInfo + { + get { return m_adjustmentInfo; } + set { m_adjustmentInfo = value; } + } + + /////////////////////////////////////////////////////////////////////////// + + public Layer(PsdFile psdFile) + { + m_psdFile = psdFile; + m_psdFile.Layers.Add(this); + } + + public Layer(BinaryReverseReader reader, PsdFile psdFile) + { + Debug.WriteLine("Layer started at " + reader.BaseStream.Position.ToString()); + + m_psdFile = psdFile; + m_rect = new Rectangle(); + m_rect.Y = reader.ReadInt32(); + m_rect.X = reader.ReadInt32(); + m_rect.Height = reader.ReadInt32() - m_rect.Y; + m_rect.Width = reader.ReadInt32() - m_rect.X; + + //----------------------------------------------------------------------- + + int numberOfChannels = reader.ReadUInt16(); + this.m_channels.Clear(); + for (int channel = 0; channel < numberOfChannels; channel++) + { + Channel ch = new Channel(reader, this); + m_channels.Add(ch); + m_sortedChannels.Add(ch.ID, ch); + } + + //----------------------------------------------------------------------- + + string signature = new string(reader.ReadChars(4)); + if (signature != "8BIM") + throw (new IOException("Layer Channelheader error!")); + + m_blendModeKey = new string(reader.ReadChars(4)); + m_opacity = reader.ReadByte(); + + m_clipping = reader.ReadByte() > 0; + + //----------------------------------------------------------------------- + + byte flags = reader.ReadByte(); + m_flags = new BitVector32(flags); + + //----------------------------------------------------------------------- + + reader.ReadByte(); //padding + + //----------------------------------------------------------------------- + + Debug.WriteLine("Layer extraDataSize started at " + reader.BaseStream.Position.ToString()); + + // this is the total size of the MaskData, the BlendingRangesData, the + // Name and the AdjustmenLayerInfo + uint extraDataSize = reader.ReadUInt32(); + + + + // remember the start position for calculation of the + // AdjustmenLayerInfo size + long extraDataStartPosition = reader.BaseStream.Position; + + m_maskData = new Mask(reader, this); + m_blendingRangesData = new BlendingRanges(reader, this); + + //----------------------------------------------------------------------- + + long namePosition = reader.BaseStream.Position; + + m_name = reader.ReadPascalString(); + + int paddingBytes = (int)((reader.BaseStream.Position - namePosition) % 4); + + Debug.Print("Layer {0} padding bytes after name", paddingBytes); + reader.ReadBytes(paddingBytes); + + //----------------------------------------------------------------------- + + m_adjustmentInfo.Clear(); + + long adjustmenLayerEndPos = extraDataStartPosition + extraDataSize; + while (reader.BaseStream.Position < adjustmenLayerEndPos) + { + try + { + m_adjustmentInfo.Add(new AdjusmentLayerInfo(reader, this)); + } + catch + { + reader.BaseStream.Position = adjustmenLayerEndPos; + } + } + + + //----------------------------------------------------------------------- + // make shure we are not on a wrong offset, so set the stream position + // manually + reader.BaseStream.Position = adjustmenLayerEndPos; + } + + /////////////////////////////////////////////////////////////////////////// + + public void Save(BinaryReverseWriter writer) + { + Debug.WriteLine("Layer Save started at " + writer.BaseStream.Position.ToString()); + + writer.Write(m_rect.Top); + writer.Write(m_rect.Left); + writer.Write(m_rect.Bottom); + writer.Write(m_rect.Right); + + //----------------------------------------------------------------------- + + writer.Write((short)m_channels.Count); + foreach (Channel ch in m_channels) + ch.Save(writer); + + //----------------------------------------------------------------------- + + string signature = "8BIM"; + writer.Write(signature.ToCharArray()); + writer.Write(m_blendModeKey.ToCharArray()); + writer.Write(m_opacity); + writer.Write((byte)(m_clipping ? 1 : 0)); + + writer.Write((byte)m_flags.Data); + + //----------------------------------------------------------------------- + + writer.Write((byte)0); + + //----------------------------------------------------------------------- + + using (new LengthWriter(writer)) + { + m_maskData.Save(writer); + m_blendingRangesData.Save(writer); + + long namePosition = writer.BaseStream.Position; + + writer.WritePascalString(m_name); + + int paddingBytes = (int)((writer.BaseStream.Position - namePosition) % 4); + Debug.Print("Layer {0} write padding bytes after name", paddingBytes); + + for (int i = 0; i < paddingBytes; i++) + writer.Write((byte)0); + + foreach (AdjusmentLayerInfo info in m_adjustmentInfo) + { + info.Save(writer); + } + } + } + + } +} \ No newline at end of file diff --git a/bin/App_Code/PSDFile.cs b/bin/App_Code/PSDFile.cs new file mode 100755 index 0000000..1182f71 --- /dev/null +++ b/bin/App_Code/PSDFile.cs @@ -0,0 +1,622 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Diagnostics; + +namespace PhotoshopFiles +{ + public class PsdFile + { + public enum ColorModes + { + Bitmap = 0, Grayscale = 1, Indexed = 2, RGB = 3, CMYK = 4, Multichannel = 7, Duotone = 8, Lab = 9 + }; + + #region "Properties and Variables" + + /// + /// 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. + /// + public byte[] ColorModeData = new byte[0]; + + //Masking data for the PSD + byte[] GlobalLayerMaskData = new byte[0]; + + /// + /// Always equal to 1. + /// + private short m_version = 1; + public short Version + { + get { return m_version; } + } + + private short m_channels; + /// + /// The number of channels in the image, including any alpha channels. + /// Supported range is 1 to 24. + /// + public short Channels + { + get { return m_channels; } + set + { + if (value < 1 || value > 24) + throw new ArgumentException("Supported range is 1 to 24"); + m_channels = value; + } + } + + + private int m_rows; + /// + /// The height of the image in pixels. + /// + public int Rows + { + get { return m_rows; } + set + { + if (value < 0 || value > 30000) + throw new ArgumentException("Supported range is 1 to 30000."); + m_rows = value; + } + } + + + private int m_columns; + /// + /// The width of the image in pixels. + /// + public int Columns + { + get { return m_columns; } + set + { + if (value < 0 || value > 30000) + throw new ArgumentException("Supported range is 1 to 30000."); + m_columns = value; + } + } + + + private int m_depth; + /// + /// The number of bits per channel. Supported values are 1, 8, and 16. + /// + public int Depth + { + get { return m_depth; } + set + { + if (value == 1 || value == 8 || value == 16) + m_depth = value; + else + throw new ArgumentException("Supported values are 1, 8, and 16."); + } + } + + private ColorModes m_colorMode; + /// + /// The color mode of the file. + /// + public ColorModes ColorMode + { + get { return m_colorMode; } + set { m_colorMode = value; } + } + + + private List m_imageResources = new List(); + /// + /// The Image resource blocks for the file + /// + /// + public List ImageResources + { + get { return m_imageResources; } + } + + public ResolutionInfo Resolution + { + get + { + return (ResolutionInfo)m_imageResources.Find((ImageResource x) => x.ID == (int)ResourceIDs.ResolutionInfo); + } + + set + { + ImageResource oldValue = m_imageResources.Find((ImageResource x) => x.ID == (int)ResourceIDs.ResolutionInfo); + if (oldValue != null) + m_imageResources.Remove(oldValue); + + m_imageResources.Add(value); + } + } + + List m_layers = new List(); + public List Layers + { + get + { + return m_layers; + } + } + + private bool m_absoluteAlpha; + public bool AbsoluteAlpha + { + get { return m_absoluteAlpha; } + set { m_absoluteAlpha = value; } + } + + /// + /// The raw image data from the file, seperated by the channels. + /// + public byte[][] m_imageData; + + public byte[][] ImageData + { + get { return m_imageData; } + set { m_imageData = value; } + } + + + private ImageCompression m_imageCompression; + public ImageCompression ImageCompression + { + get { return m_imageCompression; } + set { m_imageCompression = value; } + } + #endregion //End Properties + + public void Load(string filename) + { + using (FileStream stream = new FileStream(filename, FileMode.Open)) + { + + //binary reverse reader reads data types in big-endian format. + BinaryReverseReader reader = new BinaryReverseReader(stream); + + #region "Headers" + //The headers area is used to check for a valid PSD file + Debug.WriteLine("LoadHeader started at " + reader.BaseStream.Position.ToString()); + + string signature = new string(reader.ReadChars(4)); + if (signature != "8BPS") + throw new IOException("Bad or invalid file stream supplied"); + + //get the version number, should be 1 always + if ((m_version = reader.ReadInt16()) != 1) + throw new IOException("Invalid version number supplied"); + + //get rid of the 6 bytes reserverd in PSD format + reader.BaseStream.Position += 6; + + //get the rest of the information from the PSD file. + //Everytime ReadInt16() is called, it reads 2 bytes. + //Everytime ReadInt32() is called, it reads 4 bytes. + m_channels = reader.ReadInt16(); + m_rows = reader.ReadInt32(); + m_columns = reader.ReadInt32(); + m_depth = reader.ReadInt16(); + m_colorMode = (ColorModes)reader.ReadInt16(); + + //by end of headers, the reader has read 26 bytes into the file. + #endregion //End Headers + + #region "ColorModeData" + /// + /// 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. + /// + Debug.WriteLine("LoadColorModeData started at " + reader.BaseStream.Position.ToString()); + + uint paletteLength = reader.ReadUInt32(); //readUint32() advances the reader 4 bytes. + if (paletteLength > 0) + { + ColorModeData = reader.ReadBytes((int)paletteLength); + } + #endregion //End ColorModeData + + + #region "Loading Image Resources" + //This part takes extensive use of classes that I didn't write therefore + //I can't document much on what they do. + + Debug.WriteLine("LoadingImageResources started at " + reader.BaseStream.Position.ToString()); + + m_imageResources.Clear(); + + uint imgResLength = reader.ReadUInt32(); + if (imgResLength <= 0) + return; + + long startPosition = reader.BaseStream.Position; + + while ((reader.BaseStream.Position - startPosition) < imgResLength) + { + ImageResource imgRes = new ImageResource(reader); + + ResourceIDs resID = (ResourceIDs)imgRes.ID; + switch (resID) + { + case ResourceIDs.ResolutionInfo: + imgRes = new ResolutionInfo(imgRes); + break; + case ResourceIDs.Thumbnail1: + case ResourceIDs.Thumbnail2: + imgRes = new Thumbnail(imgRes); + break; + case ResourceIDs.AlphaChannelNames: + imgRes = new AlphaChannels(imgRes); + break; + } + + m_imageResources.Add(imgRes); + + } + // make sure we are not on a wrong offset, so set the stream position + // manually + reader.BaseStream.Position = startPosition + imgResLength; + + #endregion //End LoadingImageResources + + + #region "Layer and Mask Info" + //We are gonna load up all the layers and masking of the PSD now. + Debug.WriteLine("LoadLayerAndMaskInfo - Part1 started at " + reader.BaseStream.Position.ToString()); + uint layersAndMaskLength = reader.ReadUInt32(); + + if (layersAndMaskLength <= 0) + return; + + //new start position + startPosition = reader.BaseStream.Position; + + //Lets start by loading up all the layers + LoadLayers(reader); + //we are done the layers, load up the masks + LoadGlobalLayerMask(reader); + + // make sure we are not on a wrong offset, so set the stream position + // manually + reader.BaseStream.Position = startPosition + layersAndMaskLength; + #endregion //End Layer and Mask info + + #region "Loading Final Image" + + //we have loaded up all the information from the PSD file + //into variables we can use later on. + + //lets finish loading the raw data that defines the image + //in the picture. + + Debug.WriteLine("LoadImage started at " + reader.BaseStream.Position.ToString()); + + m_imageCompression = (ImageCompression)reader.ReadInt16(); + + m_imageData = new byte[m_channels][]; + + //--------------------------------------------------------------- + + if (m_imageCompression == ImageCompression.Rle) + { + // The RLE-compressed data is proceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + reader.BaseStream.Position += m_rows * m_channels * 2; + } + + //--------------------------------------------------------------- + + int bytesPerRow = 0; + + switch (m_depth) + { + case 1: + bytesPerRow = m_columns;//NOT Shure + break; + case 8: + bytesPerRow = m_columns; + break; + case 16: + bytesPerRow = m_columns * 2; + break; + } + + //--------------------------------------------------------------- + + for (int ch = 0; ch < m_channels; ch++) + { + m_imageData[ch] = new byte[m_rows * bytesPerRow]; + + switch (m_imageCompression) + { + case ImageCompression.Raw: + reader.Read(m_imageData[ch], 0, m_imageData[ch].Length); + break; + case ImageCompression.Rle: + { + for (int i = 0; i < m_rows; i++) + { + int rowIndex = i * m_columns; + RleHelper.DecodedRow(reader.BaseStream, m_imageData[ch], rowIndex, bytesPerRow); + } + } + break; + default: + break; + } + } + + #endregion //End LoadingFinalImage + } + } //end Load() + + + + + + /// + /// Loads up the Layers of the supplied PSD file + /// + private void LoadLayers(BinaryReverseReader reader) + { + Debug.WriteLine("LoadLayers started at " + reader.BaseStream.Position.ToString()); + + uint layersInfoSectionLength = reader.ReadUInt32(); + + if (layersInfoSectionLength <= 0) + return; + + long startPosition = reader.BaseStream.Position; + + short numberOfLayers = reader.ReadInt16(); + + // If <0, then number of layers is absolute value, + // and the first alpha channel contains the transparency data for + // the merged result. + if (numberOfLayers < 0) + { + AbsoluteAlpha = true; + numberOfLayers = Math.Abs(numberOfLayers); + } + + m_layers.Clear(); + + if (numberOfLayers == 0) + return; + + for (int i = 0; i < numberOfLayers; i++) + { + m_layers.Add(new Layer(reader, this)); + } + + foreach (Layer layer in m_layers) + { + foreach (Layer.Channel channel in layer.Channels) + { + if (channel.ID != -2) + channel.LoadPixelData(reader); + } + layer.MaskData.LoadPixelData(reader); + } + + + if (reader.BaseStream.Position % 2 == 1) + reader.ReadByte(); + + // make sure we are not on a wrong offset, so set the stream position + // manually + reader.BaseStream.Position = startPosition + layersInfoSectionLength; + } + + /// + /// Load up the masking information of the supplied PSD + /// + private void LoadGlobalLayerMask(BinaryReverseReader reader) + { + Debug.WriteLine("LoadGlobalLayerMask started at " + reader.BaseStream.Position.ToString()); + + uint maskLength = reader.ReadUInt32(); + + if (maskLength <= 0) + return; + + GlobalLayerMaskData = reader.ReadBytes((int)maskLength); + } + + } + + public enum ImageCompression + { + /// + /// Raw data + /// + Raw = 0, + /// + /// RLE compressed + /// + Rle = 1, + /// + /// ZIP without prediction. + /// + /// This is currently not supported since it is ot documented. + /// Loading will result in an image where all channels are set to zero. + /// + /// + Zip = 2, + /// + /// ZIP with prediction. + /// + /// This is currently not supported since it is ot documented. + /// Loading will result in an image where all channels are set to zero. + /// + /// + ZipPrediction = 3 + } + + + class RleHelper + { + //////////////////////////////////////////////////////////////////////// + + private class RlePacketStateMachine + { + private bool m_rlePacket = false; + private byte[] m_packetValues = new byte[128]; + private int packetLength; + private Stream m_stream; + + internal void Flush() + { + byte header; + if (m_rlePacket) + { + header = (byte)(-(packetLength - 1)); + } + else + { + header = (byte)(packetLength - 1); + } + + m_stream.WriteByte(header); + + int length = (m_rlePacket ? 1 : packetLength); + + m_stream.Write(m_packetValues, 0, length); + + packetLength = 0; + } + + internal void Push(byte color) + { + if (packetLength == 0) + { + // Starting a fresh packet. + m_rlePacket = false; + m_packetValues[0] = color; + packetLength = 1; + } + else if (packetLength == 1) + { + // 2nd byte of this packet... decide RLE or non-RLE. + m_rlePacket = (color == m_packetValues[0]); + m_packetValues[1] = color; + packetLength = 2; + } + else if (packetLength == m_packetValues.Length) + { + // Packet is full. Start a new one. + Flush(); + Push(color); + } + else if (packetLength >= 2 && m_rlePacket && color != m_packetValues[packetLength - 1]) + { + // We were filling in an RLE packet, and we got a non-repeated color. + // Emit the current packet and start a new one. + Flush(); + Push(color); + } + else if (packetLength >= 2 && m_rlePacket && color == m_packetValues[packetLength - 1]) + { + // We are filling in an RLE packet, and we got another repeated color. + // Add the new color to the current packet. + ++packetLength; + m_packetValues[packetLength - 1] = color; + } + else if (packetLength >= 2 && !m_rlePacket && color != m_packetValues[packetLength - 1]) + { + // We are filling in a raw packet, and we got another random color. + // Add the new color to the current packet. + ++packetLength; + m_packetValues[packetLength - 1] = color; + } + else if (packetLength >= 2 && !m_rlePacket && color == m_packetValues[packetLength - 1]) + { + // We were filling in a raw packet, but we got a repeated color. + // Emit the current packet without its last color, and start a + // new RLE packet that starts with a length of 2. + --packetLength; + Flush(); + Push(color); + Push(color); + } + } + + internal RlePacketStateMachine(Stream stream) + { + m_stream = stream; + } + } + + //////////////////////////////////////////////////////////////////////// + + public static int EncodedRow(Stream stream, byte[] imgData, int startIdx, int columns) + { + long startPosition = stream.Position; + + RlePacketStateMachine machine = new RlePacketStateMachine(stream); + + for (int x = 0; x < columns; ++x) + machine.Push(imgData[x + startIdx]); + + machine.Flush(); + + return (int)(stream.Position - startPosition); + } + + //////////////////////////////////////////////////////////////////////// + + public static void DecodedRow(Stream stream, byte[] imgData, int startIdx, int columns) + { + int count = 0; + while (count < columns) + { + byte byteValue = (byte)stream.ReadByte(); + + int len = (int)byteValue; + if (len < 128) + { + len++; + while (len != 0 && (startIdx + count) < imgData.Length) + { + byteValue = (byte)stream.ReadByte(); + + imgData[startIdx + count] = byteValue; + count++; + len--; + } + } + else if (len > 128) + { + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + byteValue = (byte)stream.ReadByte(); + + while (len != 0 && (startIdx + count) < imgData.Length) + { + imgData[startIdx + count] = byteValue; + count++; + len--; + } + } + else if (128 == len) + { + // Do nothing + } + } + + } + } +} diff --git a/bin/App_Code/ResolutionInfo.cs b/bin/App_Code/ResolutionInfo.cs new file mode 100755 index 0000000..35d4f4a --- /dev/null +++ b/bin/App_Code/ResolutionInfo.cs @@ -0,0 +1,152 @@ +///////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2006, Frank Blumenberg +// +// See License.txt for complete licensing and attribution information. +// 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. +// +///////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////////// +// +// This code is adapted from code in the Endogine sprite engine by Jonas Beckeman. +// http://www.endogine.com/CS/ +// +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PhotoshopFiles +{ + /// + /// Summary description for ResolutionInfo. + /// + public class ResolutionInfo : ImageResource + { + /// + /// Fixed-point number: pixels per inch + /// + private short m_hRes; + public short HRes + { + get { return m_hRes; } + set { m_hRes = value; } + } + + /// + /// Fixed-point number: pixels per inch + /// + private short m_vRes; + public short VRes + { + get { return m_vRes; } + set { m_vRes = value; } + } + + /// + /// 1=pixels per inch, 2=pixels per centimeter + /// + public enum ResUnit + { + PxPerInch = 1, + PxPerCent = 2 + } + + private ResUnit m_hResUnit; + public ResUnit HResUnit + { + get { return m_hResUnit; } + set { m_hResUnit = value; } + } + + private ResUnit m_vResUnit; + public ResUnit VResUnit + { + get { return m_vResUnit; } + set { m_vResUnit = value; } + } + + /// + /// 1=in, 2=cm, 3=pt, 4=picas, 5=columns + /// + public enum Unit + { + In = 1, + Cm = 2, + Pt = 3, + Picas = 4, + Columns = 5 + } + private Unit m_widthUnit; + + public Unit WidthUnit + { + get { return m_widthUnit; } + set { m_widthUnit = value; } + } + + private Unit m_heightUnit; + + public Unit HeightUnit + { + get { return m_heightUnit; } + set { m_heightUnit = value; } + } + + public ResolutionInfo() + : base() + { + base.ID = (short)ResourceIDs.ResolutionInfo; + } + public ResolutionInfo(ImageResource imgRes) + : base(imgRes) + { + BinaryReverseReader reader = imgRes.DataReader; + + this.m_hRes = reader.ReadInt16(); + this.m_hResUnit = (ResUnit)reader.ReadInt32(); + this.m_widthUnit = (Unit)reader.ReadInt16(); + + this.m_vRes = reader.ReadInt16(); + this.m_vResUnit = (ResUnit)reader.ReadInt32(); + this.m_heightUnit = (Unit)reader.ReadInt16(); + + reader.Close(); + } + + protected override void StoreData() + { + System.IO.MemoryStream stream = new System.IO.MemoryStream(); + BinaryReverseWriter writer = new BinaryReverseWriter(stream); + + writer.Write((Int16)m_hRes); + writer.Write((Int32)m_hResUnit); + writer.Write((Int16)m_widthUnit); + + writer.Write((Int16)m_vRes); + writer.Write((Int32)m_vResUnit); + writer.Write((Int16)m_heightUnit); + + writer.Close(); + stream.Close(); + + Data = stream.ToArray(); + } + + } +} \ No newline at end of file diff --git a/bin/App_Code/Thumbnail.cs b/bin/App_Code/Thumbnail.cs new file mode 100755 index 0000000..7c4b28b --- /dev/null +++ b/bin/App_Code/Thumbnail.cs @@ -0,0 +1,94 @@ +///////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2006, Frank Blumenberg +// +// See License.txt for complete licensing and attribution information. +// 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. +// +///////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////////// +// +// This code is adapted from code in the Endogine sprite engine by Jonas Beckeman. +// http://www.endogine.com/CS/ +// +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.IO; +using System.Drawing; + +namespace PhotoshopFiles +{ + /// + /// Summary description for Thumbnail. + /// + public class Thumbnail : ImageResource + { + private Bitmap m_thumbnailImage; + public Bitmap Image + { + get { return m_thumbnailImage; } + set { m_thumbnailImage = value; } + } + + public Thumbnail(ImageResource imgRes) + : base(imgRes) + { + using (BinaryReverseReader reader = DataReader) + { + int format = reader.ReadInt32(); + int width = reader.ReadInt32(); + int height = reader.ReadInt32(); + int widthBytes = reader.ReadInt32(); + int size = reader.ReadInt32(); + int compressedSize = reader.ReadInt32(); + short bitPerPixel = reader.ReadInt16(); + short planes = reader.ReadInt16(); + + if (format == 1) + { + + byte[] imgData = reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position)); + + using (MemoryStream strm = new MemoryStream(imgData)) + { + m_thumbnailImage = (Bitmap)(Bitmap.FromStream(strm).Clone()); + } + + if (this.ID == 1033) + { + //// BGR + //for(int y=0;y