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);
        }
 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 #3
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 #4
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 #5
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 #6
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");
        }
Beispiel #7
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 #8
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 #9
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 #10
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));
        }