public object Invoke(IMethodInvocation invocation)
        {
            string assemblyName = invocation.TargetType.Assembly.GetName().Name;
            string className    = invocation.TargetType.Name;
            string methodName   = invocation.Method.Name;

            if (assemblyName == "IBB360.Socials" && className == "ActivityApi" && methodName == "BulkInsertProfilerMessages")
            {
                return(invocation.Proceed());
            }
            PerfProfiler p = new PerfProfiler();

            p.OnMethodEnter();
            try
            {
                return(invocation.Proceed());
            }
            catch
            {
                p.OnMethodException();
                throw;
            }
            finally
            {
                string requestUrl = "";
                if (HttpContext.Current != null && HttpContext.Current.Request != null && HttpContext.Current.Request.Url != null)
                {
                    requestUrl = HttpContext.Current.Request.Url.ToString();
                }
                p.OnMethodExit(requestUrl, assemblyName, className, methodName);
            }
        }
        protected override void CreateInternal(byte[] data)
        {
            byte[] pixels  = null;
            var    width   = 0;
            var    height  = 0;
            var    flipped = false; // Whether the image was uploaded flipped - top to bottom.

            PerfProfiler.ProfilerEventStart("Decoding Image", "Loading");

            // Check if PNG.
            if (PngFormat.IsPng(data))
            {
                pixels = PngFormat.Decode(data, out PngFileHeader header);
                width  = header.Width;
                height = header.Height;
            }
            // Check if BMP.
            else if (BmpFormat.IsBmp(data))
            {
                pixels  = BmpFormat.Decode(data, out BmpFileHeader header);
                width   = header.Width;
                height  = header.Height;
                flipped = true;
            }

            if (pixels == null || width == 0 || height == 0)
            {
                Engine.Log.Warning($"Couldn't load texture - {Name}.", MessageSource.AssetLoader);
                return;
            }

            PerfProfiler.ProfilerEventEnd("Decoding Image", "Loading");
            UploadTexture(new Vector2(width, height), pixels, flipped);
        }
Beispiel #3
0
        /// <summary>
        /// Set a new state.
        /// </summary>
        /// <param name="newState">The state to set.</param>
        /// <param name="force">Whether to set it regardless of the previous state.</param>
        public void SetState(RenderState newState, bool force = false)
        {
            FlushRenderStream();
            RenderState currentState = Engine.Renderer.CurrentState;

            // Check which state changes should apply, by checking which were set and which differ from the current.
            PerfProfiler.FrameEventStart("ShaderSet");
            if (newState.Shader != null && (force || newState.Shader != currentState.Shader))
            {
                ShaderProgram.EnsureBound(newState.Shader.Pointer);
                Engine.Renderer.CurrentState.Shader = newState.Shader;
                Engine.Renderer.SyncShader();
            }

            PerfProfiler.FrameEventEnd("ShaderSet");

            PerfProfiler.FrameEventStart("Depth/Stencil/Blend Set");
            if (newState.DepthTest != null && (force || newState.DepthTest != currentState.DepthTest))
            {
                SetDepthTest((bool)newState.DepthTest);
            }
            if (newState.StencilTest != null && (force || newState.StencilTest != currentState.StencilTest))
            {
                SetStencilTest((bool)newState.StencilTest);
            }
            if (newState.SFactorRgb != null && newState.SFactorRgb != currentState.SFactorRgb ||
                newState.DFactorRgb != null && newState.DFactorRgb != currentState.DFactorRgb ||
                newState.SFactorA != null && newState.SFactorA != currentState.SFactorA ||
                newState.DFactorA != null && newState.DFactorA != currentState.DFactorA)
            {
                SetAlphaBlendType(
                    newState.SFactorRgb ?? currentState.SFactorRgb !.Value,
                    newState.DFactorRgb ?? currentState.DFactorRgb !.Value,
                    newState.SFactorA ?? currentState.SFactorA !.Value,
                    newState.DFactorA ?? currentState.DFactorA !.Value);
            }
            if (newState.AlphaBlending != null && (force || newState.AlphaBlending != currentState.AlphaBlending))
            {
                SetAlphaBlend((bool)newState.AlphaBlending);
            }
            PerfProfiler.FrameEventEnd("Depth/Stencil/Blend Set");

            PerfProfiler.FrameEventStart("View/Clip Set");
            if (force || newState.ViewMatrix != null && newState.ViewMatrix != currentState.ViewMatrix)
            {
                SetUseViewMatrix((bool)newState.ViewMatrix);
            }
            if (force || newState.ProjectionBehavior != null && newState.ProjectionBehavior != currentState.ProjectionBehavior)
            {
                SetProjectionBehavior((ProjectionBehavior)newState.ProjectionBehavior);
            }
            if (force || newState.ClipRect != currentState.ClipRect)
            {
                SetClipRect(newState.ClipRect);
            }
            PerfProfiler.FrameEventEnd("Depth/Stencil/Blend Set");
        }
Beispiel #4
0
 /// <summary>
 /// Invalidates the current batch - flushing it to the current buffer.
 /// This should be done when the state changes in some way because calls afterwards will differ from those before and
 /// cannot be batched.
 /// </summary>
 public void InvalidateStateBatches()
 {
     if (ActiveQuadBatch == null || ActiveQuadBatch.BatchedSprites == 0)
     {
         return;
     }
     PerfProfiler.FrameEventStart($"RenderBatch {ActiveQuadBatch.BatchedSprites} Sprites {ActiveQuadBatch.TextureSlotUtilization} Textures");
     ActiveQuadBatch.Render(this);
     ActiveQuadBatch.Recycle();
     PerfProfiler.FrameEventEnd($"RenderBatch {ActiveQuadBatch.BatchedSprites} Sprites {ActiveQuadBatch.TextureSlotUtilization} Textures");
 }
 protected virtual void UploadTexture(Vector2 size, byte[] bgraPixels, bool flipped)
 {
     GLThread.ExecuteGLThread(() =>
     {
         PerfProfiler.ProfilerEventStart($"Uploading Image {Name}", "Loading");
         Texture = new Texture(size, bgraPixels)
         {
             FlipY = flipped
         };
         PerfProfiler.ProfilerEventEnd($"Uploading Image {Name}", "Loading");
     });
 }
Beispiel #6
0
        public void RenderObjects(RenderComposer composer)
        {
            // Check if anything is loaded.
            List <T> objects = QueryObjectsToRender();

            PerfProfiler.FrameEventStart("TileMap: Objects");
            for (var i = 0; i < objects.Count; i++)
            {
                objects[i].Render(composer);
            }

            PerfProfiler.FrameEventEnd("TileMap: Objects");
        }
Beispiel #7
0
        protected virtual void UploadTexture(Vector2 size, byte[] pixels, bool flipped, PixelFormat pixelFormat)
        {
            Texture       = Texture.NonGLThreadInitialize(size);
            Texture.FlipY = flipped;
            GLThread.ExecuteGLThreadAsync(() =>
            {
                PerfProfiler.ProfilerEventStart($"Uploading Image {Name}", "Loading");
                Texture.NonGLThreadInitializedCreatePointer(Texture);
                Texture.Upload(size, pixels, pixelFormat, pixelFormat == PixelFormat.Red ? InternalFormat.Red : null);
                PerfProfiler.ProfilerEventEnd($"Uploading Image {Name}", "Loading");

#if DEBUG
                Texture.CreationStack = new string(' ', 3) + Name + new string(' ', 100) + "\n" + Texture.CreationStack;
#endif
            });
        }
Beispiel #8
0
        /// <summary>
        /// Set whether to use alpha blending.
        /// This causes transparent objects to blend their colors when drawn on top of each other.
        /// </summary>
        /// <param name="alphaBlend">Whether to use alpha blending.</param>
        public void SetAlphaBlend(bool alphaBlend)
        {
            PerfProfiler.FrameEventStart("StateChange: AlphaBlend");
            FlushRenderStream();

            if (alphaBlend)
            {
                Gl.Enable(EnableCap.Blend);
                Gl.BlendFuncSeparate(CurrentState.SFactorRgb !.Value, CurrentState.DFactorRgb !.Value, CurrentState.SFactorA !.Value, CurrentState.DFactorA !.Value);
            }
            else
            {
                Gl.Disable(EnableCap.Blend);
            }

            Engine.Renderer.CurrentState.AlphaBlending = alphaBlend;
            PerfProfiler.FrameEventEnd("StateChange: AlphaBlend");
        }
Beispiel #9
0
        /// <summary>
        /// Set whether to use depth testing.
        /// </summary>
        /// <param name="depth">Whether to use depth testing.</param>
        public void SetDepthTest(bool depth)
        {
            PerfProfiler.FrameEventStart("StateChange: DepthTest");
            FlushRenderStream();

            if (depth)
            {
                Gl.Enable(EnableCap.DepthTest);
                Gl.DepthFunc(DepthFunction.Lequal);
            }
            else
            {
                Gl.Disable(EnableCap.DepthTest);
            }

            Engine.Renderer.CurrentState.DepthTest = depth;
            PerfProfiler.FrameEventEnd("StateChange: DepthTest");
        }
Beispiel #10
0
        protected override void CreateInternal(ReadOnlyMemory <byte> data)
        {
            byte[]  pixels  = null;
            Vector2 size    = Vector2.Zero;
            var     flipped = false; // Whether the image was uploaded flipped - top to bottom.
            var     format  = PixelFormat.Unknown;

            PerfProfiler.ProfilerEventStart("Decoding Image", "Loading");

            // Magic number check to find out format.
            if (PngFormat.IsPng(data))
            {
                pixels = PngFormat.Decode(data, out PngFileHeader header);
                size   = header.Size;
                format = header.PixelFormat;
            }
            else if (BmpFormat.IsBmp(data))
            {
                pixels  = BmpFormat.Decode(data, out BmpFileHeader header);
                size    = new Vector2(header.Width, header.HeaderSize);
                flipped = true;
                format  = PixelFormat.Bgra;
            }
            else if (ImgBinFormat.IsImgBin(data))
            {
                pixels = ImgBinFormat.Decode(data, out ImgBinFileHeader header);
                size   = header.Size;
                format = header.Format;
            }

            if (pixels == null || size.X == 0 || size.Y == 0)
            {
                Texture = Texture.EmptyWhiteTexture;
                Engine.Log.Warning($"Couldn't load texture - {Name}.", MessageSource.AssetLoader);
                return;
            }

            ByteSize = pixels.Length;
            PerfProfiler.ProfilerEventEnd("Decoding Image", "Loading");
            UploadTexture(size, pixels, flipped, format);
        }
Beispiel #11
0
        /// <summary>
        /// Whether, and where to clip.
        /// </summary>
        /// <param name="clip">The rectangle to clip outside of.</param>
        public void SetClipRect(Rectangle?clip)
        {
            PerfProfiler.FrameEventStart("StateChange: Clip");
            FlushRenderStream();

            if (clip == null)
            {
                Gl.Disable(EnableCap.ScissorTest);
            }
            else
            {
                Gl.Enable(EnableCap.ScissorTest);
                Rectangle c = clip.Value;
                Gl.Scissor((int)c.X,
                           (int)(Engine.Renderer.CurrentTarget.Viewport.Height - c.Height - c.Y),
                           (int)c.Width,
                           (int)c.Height);
            }

            Engine.Renderer.CurrentState.ClipRect = clip;
            PerfProfiler.FrameEventEnd("StateChange: Clip");
        }
Beispiel #12
0
        public DrawableFontAtlas GetAtlas(float fontSize, uint firstChar = 0, int numChars = -1, bool smooth = true)
        {
            int hash = $"{fontSize}-{firstChar}-{numChars}".GetHashCode();

            // Check if the atlas is already loaded.
            bool found = _loadedAtlases.TryGetValue(hash, out DrawableFontAtlas atlas);

            if (found)
            {
                return(atlas);
            }

            // Load the atlas manually.
            PerfProfiler.ProfilerEventStart($"FontAtlas {Name} {fontSize} {hash}", "Loading");
            FontAtlas standardAtlas = Font.GetAtlas(fontSize, firstChar, numChars, _rasterizer);

            atlas = new DrawableFontAtlas(standardAtlas, smooth);
            PerfProfiler.ProfilerEventEnd($"FontAtlas {Name} {fontSize} {hash}", "Loading");

            _loadedAtlases.Add(hash, atlas);

            return(atlas);
        }
Beispiel #13
0
        /// <summary>
        /// Enable or disable stencil testing.
        /// When enabling the stencil buffer is cleared.
        /// </summary>
        /// <param name="stencil">Whether to enable or disable stencil testing.</param>
        public void SetStencilTest(bool stencil)
        {
            PerfProfiler.FrameEventStart("StateChange: Stencil");
            FlushRenderStream();

            // Set the stencil test to it's default state - don't write to it.

            if (stencil)
            {
                Gl.Enable(EnableCap.StencilTest);

                // Clear after enabling.
                ClearStencil();
                StencilStateDefault();
            }
            else
            {
                Gl.Disable(EnableCap.StencilTest);
                StencilStateDefault(); // Some drivers don't understand that off means off
            }

            Engine.Renderer.CurrentState.StencilTest = stencil;
            PerfProfiler.FrameEventEnd("StateChange: Stencil");
        }
Beispiel #14
0
        /// <summary>
        /// Provides default button behavior for all platforms.
        /// </summary>
        protected bool DefaultButtonBehavior(Key key, KeyStatus state)
        {
            if (Engine.Configuration.DebugMode)
            {
                Engine.Log.Trace($"Key {key} is {state}.", MessageSource.Input);

                bool ctrl = IsKeyHeld(Key.LeftControl) || IsKeyHeld(Key.RightControl);
                if (key >= Key.F1 && key <= Key.F10 && state == KeyStatus.Down && ctrl && Window != null)
                {
                    Vector2 chosenSize = _windowSizes[key - Key.F1];
                    Window.Size = chosenSize;
                    Engine.Log.Info($"Set window size to {chosenSize}", MessageSource.Platform);
                    return(false);
                }

                switch (key)
                {
                case Key.F11 when state == KeyStatus.Down && ctrl && Window != null:
                    Window.Size = (Engine.Configuration.RenderSize * 1.999f) - Vector2.One;
                    break;

                case Key.Pause when state == KeyStatus.Down:
                    PerfProfiler.ProfileNextFrame();
                    break;
                }
            }

            bool alt = IsKeyHeld(Key.LeftAlt) || IsKeyHeld(Key.RightAlt);

            if (key == Key.Enter && state == KeyStatus.Down && alt && Window != null)
            {
                Window.DisplayMode = Window.DisplayMode == DisplayMode.Fullscreen ? DisplayMode.Windowed : DisplayMode.Fullscreen;
            }

            return(true);
        }
Beispiel #15
0
        /// <summary>
        /// Decode the provided png file to a BGRA pixel array, Y flipped.
        /// </summary>
        /// <param name="pngData">The png file as bytes.</param>
        /// <param name="fileHeader">The png file's header.</param>
        /// <returns>The RGBA pixel data from the png.</returns>
        public static byte[] Decode(ReadOnlyMemory <byte> pngData, out PngFileHeader fileHeader)
        {
            fileHeader = new PngFileHeader();
            if (!IsPng(pngData))
            {
                Engine.Log.Warning("Tried to decode a non-png image!", MessageSource.ImagePng);
                return(null);
            }

            using var stream = new ByteReader(pngData);
            stream.Seek(8, SeekOrigin.Current); // Increment by header bytes.

            // Read chunks while there are valid chunks.
            var      dataStream = new ReadOnlyLinkedMemoryStream();
            PngChunk currentChunk;
            var      endChunkReached = false;
            ReadOnlyMemory <byte> palette = null, paletteAlpha = null;
            int width = 0, height = 0;

            while ((currentChunk = new PngChunk(stream)).Valid)
            {
                if (endChunkReached)
                {
                    Engine.Log.Warning("Image did not end with an end chunk...", MessageSource.ImagePng);
                    continue;
                }

                switch (currentChunk.Type)
                {
                case PngChunkTypes.HEADER:
                {
                    ByteReader chunkReader = currentChunk.ChunkReader;

                    width                        = chunkReader.ReadInt32BE();
                    height                       = chunkReader.ReadInt32BE();
                    fileHeader.Size              = new Vector2(width, height);
                    fileHeader.BitDepth          = chunkReader.ReadByte();
                    fileHeader.ColorType         = chunkReader.ReadByte();
                    fileHeader.CompressionMethod = chunkReader.ReadByte();
                    fileHeader.FilterMethod      = chunkReader.ReadByte();
                    fileHeader.InterlaceMethod   = chunkReader.ReadByte();
                    break;
                }

                case PngChunkTypes.DATA:
                    dataStream.AddMemory(currentChunk.ChunkReader.Data);
                    break;

                case PngChunkTypes.PALETTE:
                    palette = currentChunk.ChunkReader.Data;
                    break;

                case PngChunkTypes.PALETTE_ALPHA:
                    paletteAlpha = currentChunk.ChunkReader.Data;
                    break;

                case PngChunkTypes.END:
                    endChunkReached = true;
                    break;
                }
            }

            // Decompress data.
            PerfProfiler.ProfilerEventStart("PNG Decompression", "Loading");
            byte[] data = ZlibStreamUtility.Decompress(dataStream);
            PerfProfiler.ProfilerEventEnd("PNG Decompression", "Loading");
            if (data == null)
            {
                return(null);
            }

            var         channelsPerColor    = 0;
            int         bytesPerPixelOutput = 4;
            ColorReader reader;

            fileHeader.PixelFormat = PixelFormat.Bgra; // Default.
            switch (fileHeader.ColorType)
            {
            case 0:
                // Grayscale - No Alpha
                channelsPerColor    = 1;
                bytesPerPixelOutput = 1;
                reader = Grayscale;
                fileHeader.PixelFormat = PixelFormat.Red;
                break;

            case 2:
                // RGB
                channelsPerColor = 3;
                reader           = Rgb;
                break;

            case 3:
                // Palette
                channelsPerColor = 1;
                reader           = (rowPixels, row, imageDest, destRow) => { Palette(palette.Span, paletteAlpha.Span, rowPixels, row, imageDest, destRow); };
                break;

            case 4:
                // Grayscale - Alpha
                channelsPerColor = 2;
                reader           = GrayscaleAlpha;
                break;

            case 6:
                // RGBA
                channelsPerColor       = 4;
                reader                 = Rgba;
                fileHeader.PixelFormat = PixelFormat.Rgba;
                break;

            default:
                reader = null;
                break;
            }

            // Unknown color mode.
            if (reader == null)
            {
                Engine.Log.Warning($"Unsupported color type - {fileHeader.ColorType}", MessageSource.ImagePng);
                return(new byte[width * height * bytesPerPixelOutput]);
            }

            // Calculate the bytes per pixel.
            var bytesPerPixel = 1;

            if (fileHeader.BitDepth == 0 || fileHeader.BitDepth == 3)
            {
                Engine.Log.Warning("Invalid bit depth.", MessageSource.ImagePng);
                return(null);
            }
            if (fileHeader.BitDepth != 8)
            {
                Engine.Log.Warning("Loading PNGs with a bit depth different than 8 will be deprecated in future versions.", MessageSource.ImagePng);
            }
            if (fileHeader.BitDepth >= 8)
            {
                bytesPerPixel = channelsPerColor * fileHeader.BitDepth / 8;
            }

            // Check interlacing.
            if (fileHeader.InterlaceMethod == 1)
            {
                Engine.Log.Warning("Loading interlaced PNGs will be deprecated in future versions. Convert your images!", MessageSource.ImagePng);
                return(ParseInterlaced(data, fileHeader, bytesPerPixel, channelsPerColor, reader));
            }

            int scanlineLength = GetScanlineLength(fileHeader, channelsPerColor) + 1;
            int scanLineCount  = data.Length / scanlineLength;

            return(Parse(scanlineLength, scanLineCount, data, bytesPerPixel, fileHeader, reader, bytesPerPixelOutput));
        }
Beispiel #16
0
        private static byte[] Parse(int scanlineLength, int scanlineCount, byte[] pureData, int bytesPerPixel, PngFileHeader header, ColorReader reader, int bytesPerPixelOutput)
        {
            var width  = (int)header.Size.X;
            var height = (int)header.Size.Y;
            var pixels = new byte[width * height * bytesPerPixelOutput];
            int length = scanlineLength - 1;
            var data   = new Span <byte>(pureData);

            // Analyze if the scanlines can be read in parallel.
            byte filterMode = data[0];

            for (var i = 0; i < scanlineCount; i++)
            {
                byte f = data[scanlineLength * i];
                if (f == filterMode)
                {
                    continue;
                }
                filterMode = byte.MaxValue;
                break;
            }

            // Multiple filters or a dependency filter are in affect.
            if (filterMode == byte.MaxValue || filterMode != 0 && filterMode != 1)
            {
                if (scanlineCount >= 1500)
                {
                    Engine.Log.Trace("Loaded a big PNG with scanlines which require filtering. If you re-export it without filters, it will load faster.", MessageSource.ImagePng);
                }

                PerfProfiler.ProfilerEventStart("PNG Parse Sequential", "Loading");
                var readOffset       = 0;
                var previousScanline = Span <byte> .Empty;
                for (var i = 0; i < scanlineCount; i++)
                {
                    // Early out for invalid data.
                    if (data.Length - readOffset < scanlineLength)
                    {
                        break;
                    }

                    Span <byte> scanline = data.Slice(readOffset + 1, length);
                    int         filter   = data[readOffset];
                    ApplyFilter(scanline, previousScanline, filter, bytesPerPixel);

                    reader(width, ConvertBitArray(scanline, header), pixels, i);
                    previousScanline = scanline;
                    readOffset      += scanlineLength;
                }

                PerfProfiler.ProfilerEventEnd("PNG Parse Sequential", "Loading");
                return(pixels);
            }

            // Single line filter
            if (filterMode == 1)
            {
                PerfProfiler.ProfilerEventStart("PNG Parse Threaded", "Loading");
                ParallelWork.FastLoops(scanlineCount, (start, end) =>
                {
                    int readOffset = start * scanlineLength;
                    for (int i = start; i < end; i++)
                    {
                        // Early out for invalid data.
                        if (pureData.Length - readOffset < scanlineLength)
                        {
                            break;
                        }
                        Span <byte> scanline = new Span <byte>(pureData).Slice(readOffset + 1, length);
                        for (int j = bytesPerPixel; j < scanline.Length; j++)
                        {
                            scanline[j] = (byte)(scanline[j] + scanline[j - bytesPerPixel]);
                        }

                        reader(width, ConvertBitArray(scanline, header), pixels, i);
                        readOffset += scanlineLength;
                    }
                }).Wait();
                PerfProfiler.ProfilerEventEnd("PNG Parse Threaded", "Loading");
            }

            // No filter!
            // ReSharper disable once InvertIf
            if (filterMode == 0)
            {
                PerfProfiler.ProfilerEventStart("PNG Parse Threaded", "Loading");
                ParallelWork.FastLoops(scanlineCount, (start, end) =>
                {
                    int readOffset = start * scanlineLength;
                    for (int i = start; i < end; i++)
                    {
                        // Early out for invalid data.
                        if (pureData.Length - readOffset < scanlineLength)
                        {
                            break;
                        }
                        Span <byte> row = ConvertBitArray(new Span <byte>(pureData).Slice(readOffset + 1, length), header);
                        reader(width, row, pixels, i);
                        readOffset += scanlineLength;
                    }
                }).Wait();
                PerfProfiler.ProfilerEventEnd("PNG Parse Threaded", "Loading");
            }

            return(pixels);
        }
Beispiel #17
0
        private static byte[] ParseInterlaced(byte[] pureData, PngFileHeader fileHeader, int bytesPerPixel, int channelsPerColor, ColorReader reader)
        {
            PerfProfiler.ProfilerEventStart("PNG Parse Interlaced", "Loading");

            // Combine interlaced pixels into one image here.
            var width       = (int)fileHeader.Size.X;
            var height      = (int)fileHeader.Size.Y;
            var combination = new byte[width * height * channelsPerColor];
            var pixels      = new byte[width * height * 4];

            const int passes     = 7;
            var       readOffset = 0;
            var       done       = false;
            var       data       = new Span <byte>(pureData);

            for (var i = 0; i < passes; i++)
            {
                int columns = Adam7.ComputeColumns(width, i);
                if (columns == 0)
                {
                    continue;
                }
                int scanlineLength = GetScanlineLengthInterlaced(columns, fileHeader, channelsPerColor) + 1;
                int length         = scanlineLength - 1;

                int pixelsInLine = Adam7.ComputeBlockWidth(width, i);

                // Read scanlines in this pass.
                var previousScanline = Span <byte> .Empty;
                for (int row = Adam7.FirstRow[i]; row < height; row += Adam7.RowIncrement[i])
                {
                    // Early out if invalid pass.
                    if (data.Length - readOffset < scanlineLength)
                    {
                        done = true;
                        break;
                    }

                    Span <byte> scanLine = data.Slice(readOffset + 1, length);
                    int         filter   = data[readOffset];
                    readOffset += scanlineLength;
                    ApplyFilter(scanLine, previousScanline, filter, bytesPerPixel);

                    // Dump the row into the combined image.
                    Span <byte> convertedLine = ConvertBitArray(scanLine, fileHeader);
                    for (var pixel = 0; pixel < pixelsInLine; pixel++)
                    {
                        int offset    = row * bytesPerPixel * width + (Adam7.FirstColumn[i] + pixel * Adam7.ColumnIncrement[i]) * bytesPerPixel;
                        int offsetSrc = pixel * bytesPerPixel;
                        for (var p = 0; p < bytesPerPixel; p++)
                        {
                            combination[offset + p] = convertedLine[offsetSrc + p];
                        }
                    }

                    previousScanline = scanLine;
                }

                if (done)
                {
                    break;
                }
            }

            // Read the combined image.
            int stride        = width * bytesPerPixel;
            int scanlineCount = combination.Length / stride;

            for (var i = 0; i < scanlineCount; i++)
            {
                Span <byte> row = new Span <byte>(combination).Slice(stride * i, stride);
                reader(width, row, pixels, i);
            }

            PerfProfiler.ProfilerEventEnd("PNG Parse Interlaced", "Loading");
            return(pixels);
        }
Beispiel #18
0
        /// <summary>
        /// Decode the provided png file to a BGRA pixel array, Y flipped.
        /// </summary>
        /// <param name="pngData">The png file as bytes.</param>
        /// <param name="fileHeader">The png file's header.</param>
        /// <returns>The RGBA pixel data from the png.</returns>
        public static byte[] Decode(byte[] pngData, out PngFileHeader fileHeader)
        {
            fileHeader = new PngFileHeader();

            if (!IsPng(pngData))
            {
                Engine.Log.Warning("Tried to decode a non-png image!", MessageSource.ImagePng);
                return(null);
            }

            using var stream = new MemoryStream(pngData);
            stream.Seek(8, SeekOrigin.Current);

            var endChunkReached = false;

            byte[] palette      = null;
            byte[] paletteAlpha = null;

            using var dataStream = new MemoryStream();

            // Read chunks while there are valid chunks.
            PngChunk currentChunk;

            while ((currentChunk = new PngChunk(stream)).Valid)
            {
                if (endChunkReached)
                {
                    Engine.Log.Warning("Image did not end with an end chunk...", MessageSource.ImagePng);
                    continue;
                }

                switch (currentChunk.Type)
                {
                case PngChunkTypes.HEADER:
                {
                    Array.Reverse(currentChunk.Data, 0, 4);
                    Array.Reverse(currentChunk.Data, 4, 4);

                    fileHeader.Width  = BitConverter.ToInt32(currentChunk.Data, 0);
                    fileHeader.Height = BitConverter.ToInt32(currentChunk.Data, 4);

                    fileHeader.BitDepth          = currentChunk.Data[8];
                    fileHeader.ColorType         = currentChunk.Data[9];
                    fileHeader.FilterMethod      = currentChunk.Data[11];
                    fileHeader.InterlaceMethod   = currentChunk.Data[12];
                    fileHeader.CompressionMethod = currentChunk.Data[10];

                    // Validate header.
                    if (fileHeader.ColorType >= ColorTypes.Length || ColorTypes[fileHeader.ColorType] == null)
                    {
                        Engine.Log.Warning($"Color type '{fileHeader.ColorType}' is not supported or not valid.", MessageSource.ImagePng);
                        return(null);
                    }

                    if (!ColorTypes[fileHeader.ColorType].SupportedBitDepths.Contains(fileHeader.BitDepth))
                    {
                        Engine.Log.Warning($"Bit depth '{fileHeader.BitDepth}' is not supported or not valid for color type {fileHeader.ColorType}.", MessageSource.ImagePng);
                    }

                    if (fileHeader.FilterMethod != 0)
                    {
                        Engine.Log.Warning("The png specification only defines 0 as filter method.", MessageSource.ImagePng);
                    }
                    break;
                }

                case PngChunkTypes.DATA:
                    dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length);
                    break;

                case PngChunkTypes.PALETTE:
                    palette = currentChunk.Data;
                    break;

                case PngChunkTypes.PALETTE_ALPHA:
                    paletteAlpha = currentChunk.Data;
                    break;

                case PngChunkTypes.END:
                    endChunkReached = true;
                    break;
                }
            }

            stream.Dispose();

            // Determine color reader to use.
            PngColorTypeInformation colorTypeInformation = ColorTypes[fileHeader.ColorType];

            if (colorTypeInformation == null)
            {
                return(null);
            }
            IColorReader colorReader = colorTypeInformation.CreateColorReader(palette, paletteAlpha);

            // Calculate the bytes per pixel.
            var bytesPerPixel = 1;

            if (fileHeader.BitDepth >= 8)
            {
                bytesPerPixel = colorTypeInformation.ChannelsPerColor * fileHeader.BitDepth / 8;
            }

            // Decompress data.
            dataStream.Position = 0;
            PerfProfiler.ProfilerEventStart("PNG Decompression", "Loading");
            byte[] data = ZlibStreamUtility.Decompress(dataStream);
            PerfProfiler.ProfilerEventEnd("PNG Decompression", "Loading");

            // Parse into pixels.
            var pixels = new byte[fileHeader.Width * fileHeader.Height * 4];

            if (fileHeader.InterlaceMethod == 1)
            {
                ParseInterlaced(data, fileHeader, bytesPerPixel, colorReader, pixels);
            }
            else
            {
                Parse(data, fileHeader, bytesPerPixel, colorReader, pixels);
            }

            return(pixels);
        }
Beispiel #19
0
        private static void Parse(byte[] data, PngFileHeader fileHeader, int bytesPerPixel, IColorReader reader, byte[] pixels)
        {
            // Find the scan line length.
            int scanlineLength = GetScanlineLength(fileHeader.Width, fileHeader) + 1;
            int scanLineCount  = data.Length / scanlineLength;

            // Run through all scanlines.
            var cannotParallel = new List <int>();

            ParallelWork.FastLoops(scanLineCount, (start, end) =>
            {
                int readOffset = start * scanlineLength;
                for (int i = start; i < end; i++)
                {
                    // Early out for invalid data.
                    if (data.Length - readOffset < scanlineLength)
                    {
                        break;
                    }

                    // Get the current scanline.
                    var rowData = new Span <byte>(data, readOffset + 1, scanlineLength - 1);
                    int filter  = data[readOffset];
                    readOffset += scanlineLength;

                    // Check if it has a filter.
                    // PNG filters require the previous row.
                    // We can't do those in parallel.
                    if (filter != 0)
                    {
                        lock (cannotParallel)
                        {
                            cannotParallel.Add(i);
                        }

                        continue;
                    }

                    reader.ReadScanline(rowData, pixels, fileHeader, i);
                }
            }).Wait();

            if (cannotParallel.Count == 0)
            {
                return;
            }

            PerfProfiler.ProfilerEventStart("PNG Parse Sequential", "Loading");

            // Run scanlines which couldn't be parallel processed.
            if (scanLineCount >= 2000)
            {
                Engine.Log.Trace("Loaded a big PNG with scanlines which require filtering. If you re-export it without that, it will load faster.", MessageSource.ImagePng);
            }
            cannotParallel.Sort();
            for (var i = 0; i < cannotParallel.Count; i++)
            {
                int idx = cannotParallel[i];

                int         rowStart    = idx * scanlineLength;
                Span <byte> prevRowData = idx == 0 ? null : new Span <byte>(data, (idx - 1) * scanlineLength + 1, scanlineLength - 1);
                var         rowData     = new Span <byte>(data, rowStart + 1, scanlineLength - 1);

                // Apply filter to the whole row.
                int filter = data[rowStart];
                for (var column = 0; column < rowData.Length; column++)
                {
                    rowData[column] = ApplyFilter(rowData, prevRowData, filter, column, bytesPerPixel);
                }

                reader.ReadScanline(rowData, pixels, fileHeader, idx);
            }

            PerfProfiler.ProfilerEventEnd("PNG Parse Sequential", "Loading");
        }