/// <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)); }
protected override void CreateInternal(ReadOnlyMemory <byte> data) { var str = new ReadOnlyLinkedMemoryStream(); str.AddMemory(data); _assContext ??= new AssimpContext(); Scene scene = _assContext.ImportFileFromStream(str, PostProcessSteps.Triangulate | PostProcessSteps.FlipUVs | PostProcessSteps.OptimizeGraph | PostProcessSteps.OptimizeMeshes); var embeddedTextures = new List <Texture>(); for (var i = 0; i < scene.TextureCount; i++) { EmbeddedTexture assTexture = scene.Textures[i]; var embeddedTexture = new TextureAsset(); embeddedTexture.Create(assTexture.CompressedData); embeddedTextures.Add(embeddedTexture.Texture); } _materials = new List <MeshMaterial>(); for (var i = 0; i < scene.MaterialCount; i++) { Material material = scene.Materials[i]; Color4D diffColor = material.ColorDiffuse; bool embeddedTexture = material.HasTextureDiffuse && embeddedTextures.Count > material.TextureDiffuse.TextureIndex; var emotionMaterial = new MeshMaterial { Name = material.Name, DiffuseColor = new Color(new Vector4(diffColor.R, diffColor.G, diffColor.B, diffColor.A)), DiffuseTextureName = embeddedTexture ? $"EmbeddedTexture{material.TextureDiffuse.TextureIndex}" : null, DiffuseTexture = embeddedTexture ? embeddedTextures[material.TextureDiffuse.TextureIndex] : null }; _materials.Add(emotionMaterial); } _animations = new List <SkeletalAnimation>(); ProcessAnimations(scene); _meshes = new List <Mesh>(); Node rootNode = scene.RootNode; SkeletonAnimRigNode animRigRoot = ProcessNode(scene, rootNode); animRigRoot.LocalTransform *= Matrix4x4.CreateRotationX(-90 * Maths.DEG2_RAD); // Convert to right handed Z is up. Entity = new MeshEntity { Name = Name, Meshes = _meshes.ToArray(), Animations = _animations.ToArray(), AnimationRig = animRigRoot }; object scaleData = scene.RootNode.Metadata.GetValueOrDefault("UnitScaleFactor").Data; var scaleF = 1f; if (scaleData is float f) { scaleF = f; } Entity.Scale = scaleF; }