///////////////////////////////////////////////////////////////////////////////// // // 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 { const 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 AlphaChannel { get; private set; } public PsdColorMode ColorMode { get; private set; } public NativeArray 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(alphaSize, Allocator.TempJob); if (HasAlphaChannel > 0) NativeArray.Copy(layer.AlphaChannel.ImageData, AlphaChannel, layer.AlphaChannel.ImageData.Length); ColorMode = layer.PsdFile.ColorMode; ColorModeData = new NativeArray(layer.PsdFile.ColorModeData, Allocator.TempJob); Rectangle = 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."); } } /// /// Decode image from Photoshop's channel-separated formats to BGRA. /// public static JobHandle DecodeImage(BitmapLayer pdnLayer, PhotoshopFile.Layer psdLayer, JobHandle inputDeps) { UnityEngine.Profiling.Profiler.BeginSample("DecodeImage"); var decodeContext = new DecodeContext(psdLayer, pdnLayer.localRect); 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; } /// /// Decode image from Photoshop's channel-separated formats to BGRA, /// using the specified decode delegate on each row. /// static JobHandle DecodeImage(BitmapLayer pdnLayer, DecodeContext decodeContext, DecodeType decoderType, JobHandle inputDeps) { 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. var jobCount = Unity.Jobs.LowLevel.Unsafe.JobsUtility.JobWorkerMaximumCount; var execCount = (rect.Bottom - rect.Top); var sliceCount = execCount / jobCount; var decoderJob = new PDNDecoderJob(); decoderJob.data.Rect = rect; decoderJob.data.SurfaceWidth = surface.width; 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 var jobHandle = decoderJob.Schedule(execCount, sliceCount, inputDeps); // Mask and Alpha. var userMaskContextSize = decodeContext.UserMaskContext != null ? decodeContext.Rectangle.Width : 1; var layerMaskContextSize = decodeContext.LayerMaskContext != null ? decodeContext.Rectangle.Width : 1; var userAlphaMask = new NativeArray(userMaskContextSize, Allocator.TempJob); var layerAlphaMask = new NativeArray(layerMaskContextSize, Allocator.TempJob); var userAlphaMaskEmpty = new NativeArray(rect.Bottom, Allocator.TempJob); var layerAlphaMaskEmpty = new NativeArray(rect.Bottom, Allocator.TempJob); var alphaMaskJob = new PDNAlphaMaskJob(); for (var 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.SurfaceWidth = surface.width; 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 /////////////////////////////////////////////////////////////////////////// /// /// Decode image from Photoshop's channel-separated formats to BGRA. /// public static void DecodeImage(BitmapLayer pdnLayer, PhotoshopFile.Layer psdLayer) { UnityEngine.Profiling.Profiler.BeginSample("DecodeImage"); var decodeContext = new DecodeContext(psdLayer, pdnLayer.localRect); 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 color, int idxSrc, DecodeContext context); /// /// Decode image from Photoshop's channel-separated formats to BGRA, /// using the specified decode delegate on each row. /// 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(userMaskContextSize, Allocator.TempJob); var layerAlphaMask = new NativeArray(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 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 color, int idxSrc, int byteDepth, int hasAlphaChannel, NativeArray 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 srcAlphaChannel = alphaChannel.Reinterpret(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 pAlpha, int pAlphaStart, int pAlphaEnd, NativeArray pMask, int pMaskStart) { NativeArray floatArray = pMask.Reinterpret(1); while (pAlphaStart < pAlphaEnd) { pAlpha[pAlphaStart] = RGBByteFromHDRFloat(floatArray[pMaskStart * 4]); pAlphaStart++; pMaskStart += 4; } } private static void DecodeMaskAlphaRow(NativeArray pAlpha, int pAlphaStart, int pAlphaEnd, NativeArray 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 color, NativeArray layerMaskAlpha, NativeArray 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 color, int idxSrc, DecodeContext context) { NativeArray redChannel = context.Channels[0].ImageData.Reinterpret(1); NativeArray greenChannel = context.Channels[1].ImageData.Reinterpret(1); NativeArray blueChannel = context.Channels[2].ImageData.Reinterpret(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 color, int idxSrc, DecodeContext context) { NativeArray channel = context.Channels[0].ImageData.Reinterpret(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 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 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 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 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 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 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 } }