/// <summary> /// Allocates and decodes all mipmap levels of a DDS texture. /// </summary> /// <param name="stream">The stream to read the texture data from.</param> /// <param name="width">The width of the texture at level 0.</param> /// <param name="height">The height of the texture at level 0.</param> /// <param name="count">The mipmap count.</param> /// <returns>The decoded mipmaps.</returns> private MipMap[] AllocateMipMaps <TBlock>(Stream stream, int width, int height, int count) where TBlock : struct, IBlock <TBlock> { var blockFormat = default(TBlock); var mipMaps = new MipMap <TBlock> [count]; for (int i = 0; i < count; i++) { int widthBlocks = blockFormat.Compressed ? Helper.CalcBlocks(width) : width; int heightBlocks = blockFormat.Compressed ? Helper.CalcBlocks(height) : height; int bytesToRead = heightBlocks * widthBlocks * blockFormat.CompressedBytesPerBlock; // Special case for yuv formats with a single pixel. if (bytesToRead < blockFormat.BitsPerPixel / 8) { bytesToRead = blockFormat.BitsPerPixel / 8; } byte[] mipData = new byte[bytesToRead]; int read = stream.Read(mipData, 0, bytesToRead); if (read != bytesToRead) { throw new TextureFormatException("could not read enough texture data from the stream"); } mipMaps[i] = new MipMap <TBlock>(blockFormat, mipData, width, height); width >>= 1; height >>= 1; } return(mipMaps); }
public static MipMap Resize(MipMap mipMap, double xScale, double yScale) { Bitmap bmp = mipMap.GetBitmap(); int width = (int)(mipMap.Width * xScale); int height = (int)(mipMap.Height * yScale); var destRect = new Rectangle(0, 0, width, height); var destImage = new Bitmap(width, height); destImage.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution); using (var graphics = System.Drawing.Graphics.FromImage(destImage)) { graphics.CompositingMode = CompositingMode.SourceCopy; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; using (var wrapMode = new ImageAttributes()) { wrapMode.SetWrapMode(WrapMode.TileFlipXY); graphics.DrawImage(bmp, destRect, 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, wrapMode); } } MipMap result = new MipMap(width, height); result.SetPixels(destImage); return(result); }
public static void importTexture(GpkExport export, string file) { var texture2d = export.Payload as Texture2D; var image = new DdsFile(); var config = new DdsSaveConfig(texture2d.GetFormat(), 0, 0, false, false); image.Load(file); if (image.MipMaps.Count == 0 || Settings.Default.GenerateMipMaps) { image.GenerateMipMaps(); } texture2d.maps = new List <MipMap>(); foreach (DdsMipMap mipMap in image.MipMaps.OrderByDescending(mip => mip.Width)) { byte[] outputData = image.WriteMipMap(mipMap, config); var textureMipMap = new MipMap(); textureMipMap.compFlag = (int)CompressionTypes.LZO; textureMipMap.uncompressedData = outputData; textureMipMap.uncompressedSize = outputData.Length; textureMipMap.uncompressedSize_chunkheader = outputData.Length; textureMipMap.sizeX = mipMap.Width; textureMipMap.sizeY = mipMap.Height; textureMipMap.generateBlocks(); texture2d.maps.Add(textureMipMap); } }
//private void AllocateMipMaps(Stream stream) //{ // if (this.DdsHeader.TextureCount() <= 1) // { // int width = (int)Math.Max(BlockInfo.DivSize, (int)this.DdsHeader.Width); // int height = (int)Math.Max(BlockInfo.DivSize, this.DdsHeader.Height); // int bytesPerPixel = (BlockInfo.BitsPerPixel + 7) / 8; // int stride = CalcStride(width, BlockInfo.BitsPerPixel); // int len = stride * height; // var mipData = new byte[len]; // stream.Read(mipData, 0, len); // var mipMap = new MipMap(BlockInfo, mipData, false, width, height, stride / bytesPerPixel); // Swap(mipMap); // this.mipMaps = new[] { mipMap }; // return; // } // mipMaps = new MipMap[this.DdsHeader.TextureCount() - 1]; // for (int i = 0; i < this.DdsHeader.TextureCount() - 1; i++) // { // int width = (int)Math.Max(BlockInfo.DivSize, (int)(this.DdsHeader.Width / Math.Pow(2, i + 1))); // int height = (int)Math.Max(BlockInfo.DivSize, this.DdsHeader.Height / Math.Pow(2, i + 1)); // int bytesPerPixel = (BlockInfo.BitsPerPixel + 7) / 8; // int stride = CalcStride(width, BlockInfo.BitsPerPixel); // int len = stride * height; // var mipData = new byte[len]; // stream.Read(mipData, 0, len); // var mipMap = new MipMap(BlockInfo, mipData, false, width, height, stride / bytesPerPixel); // Swap(mipMap); // mipMaps[i] = mipMap; // } //} private MipMap[] AllocateMipMaps <TBlock>(Stream stream, int width, int height, int count) where TBlock : struct, IBlock <TBlock> { var blockFormat = default(TBlock); var mipMaps = new MipMap <TBlock> [count]; for (int i = 0; i < count; i++) { int widthBlocks = blockFormat.Compressed ? Helper.CalcBlocks(width) : width; int heightBlocks = blockFormat.Compressed ? Helper.CalcBlocks(height) : height; int len = heightBlocks * widthBlocks * blockFormat.CompressedBytesPerBlock; byte[] mipData = new byte[len]; int read = stream.Read(mipData, 0, len); if (read != len) { throw new InvalidDataException(); } mipMaps[i] = new MipMap <TBlock>(blockFormat, mipData, width, height); width >>= 1; height >>= 1; } return(mipMaps); }
public Bitmap Decompress(int mipLevel = 0, bool suppressAlpha = false) { MipMap mip = MipMaps[mipLevel]; Bitmap b = new Bitmap(mip.Width, mip.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); SquishFlags flags = 0; bool notCompressed = false; switch (Format) { case D3DFormat.DXT1: flags = SquishFlags.kDxt1; break; case D3DFormat.DXT5: flags = SquishFlags.kDxt5; break; case D3DFormat.A8R8G8B8: notCompressed = true; break; default: throw new NotImplementedException($"Can't decompress: {Format}"); } byte[] dest = new byte[mip.Width * mip.Height * 4]; byte[] data = mip.Data; if (notCompressed) { for (uint i = 0; i < data.Length - 4; i += 4) { uint colour = (uint)((data[i + 3] << 24) | (data[i + 2] << 16) | (data[i + 1] << 8) | (data[i + 0] << 0)); dest[i + 0] = (byte)((colour & PixelFormat.BBitMask) >> 0); dest[i + 1] = (byte)((colour & PixelFormat.GBitMask) >> 8); dest[i + 2] = (byte)((colour & PixelFormat.RBitMask) >> 16); dest[i + 3] = (byte)((colour & PixelFormat.ABitMask) >> 24); } } else { Squish.Squish.DecompressImage(dest, mip.Width, mip.Height, ref data, flags); for (uint i = 0; i < dest.Length - 4; i += 4) { byte r = dest[i + 0]; dest[i + 0] = dest[i + 2]; dest[i + 2] = r; } } BitmapData bmpdata = b.LockBits(new Rectangle(0, 0, mip.Width, mip.Height), ImageLockMode.ReadWrite, (suppressAlpha ? System.Drawing.Imaging.PixelFormat.Format32bppRgb : b.PixelFormat)); Marshal.Copy(dest, 0, bmpdata.Scan0, dest.Length); b.UnlockBits(bmpdata); return(b); }
private void LoadRawPixels(byte[] rawPixelData, int width, int height, int numSourceChannels) { if (numSourceChannels < 2) { throw new ArgumentException("Source rawPixelData must have at least 2 channels."); } Width = width; Height = height; MipMaps = new MipMap[1]; MipMap mip = null; if (numSourceChannels == 2) { mip = new MipMap(rawPixelData, width, height); } else { List <byte> formattedPixelData = new List <byte>(); int ptr = 0; while (ptr < rawPixelData.Length) { // KFreon: Read single pixel formattedPixelData.Add(rawPixelData[ptr++]); formattedPixelData.Add(rawPixelData[ptr++]); // KFreon: Skip unneeded channels ptr += numSourceChannels - 2; } mip = new MipMap(formattedPixelData.ToArray(), width, height); } MipMaps[0] = mip; }
public override void Init() { Vector2 size = Engine.Map.GetSize(); float mapWidth = size.X; float mapHeight = size.Y; visiblesMap = new MipMap<VisibleProperty>(mapWidth, mapHeight); smellablesMap = new MipMap<SmellableProperty>(mapWidth, mapHeight); }
public override void Init() { Vector2 size = Engine.Map.GetSize(); float mapWidth = size.X; float mapHeight = size.Y; visiblesMap = new MipMap <VisibleProperty>(mapWidth, mapHeight); smellablesMap = new MipMap <SmellableProperty>(mapWidth, mapHeight); }
public override void Init() { _cluster = new Dictionary <int, PhysicsUnit>(); _collidables = new HashSet <PhysicsUnit>(); _map = Engine.Map; Vector2 size = _map.GetSize(); _mipmap = new MipMap <PhysicsUnit>(size.X, size.Y); }
private void LoadImage(Stream stream) { stream.Seek(0, SeekOrigin.Begin); BinaryReader r = new BinaryReader(stream); int dwMagic = r.ReadInt32(); if (dwMagic != 0x20534444) { throw new Exception("This is not a DDS! Magic number wrong."); } DDS_HEADER header = new DDS_HEADER(); Read_DDS_HEADER(header, r); if (((header.ddspf.dwFlags & 0x00000004) != 0) && (header.ddspf.dwFourCC == 0x30315844 /*DX10*/)) { throw new Exception("DX10 not supported yet!"); } if (header.ddspf.dwFourCC != 0) { throw new InvalidDataException("DDS not V8U8."); } int mipMapCount = 1; if ((header.dwFlags & 0x00020000) != 0) { mipMapCount = header.dwMipMapCount; } int w = 0; int h = 0; double bytePerPixel = 2; MipMaps = new MipMap[mipMapCount]; // KFreon: Get mips for (int i = 0; i < mipMapCount; i++) { w = (int)(header.dwWidth / Math.Pow(2, i)); h = (int)(header.dwHeight / Math.Pow(2, i)); int mipMapBytes = (int)(w * h * bytePerPixel); MipMaps[i] = new MipMap(r.ReadBytes(mipMapBytes), w, h); } Width = header.dwWidth; Height = header.dwHeight; }
/// <summary> /// Ensures all Mipmaps are generated in MipMaps. /// </summary> /// <param name="MipMaps">MipMaps to check.</param> /// <returns>Number of mipmaps present in MipMaps.</returns> internal static int BuildMipMaps(List <MipMap> MipMaps) { if (MipMaps?.Count == 0) { return(0); } MipMap currentMip = MipMaps[0]; // KFreon: Check if mips required int estimatedMips = DDSGeneral.EstimateNumMipMaps(currentMip.Width, currentMip.Height); if (MipMaps.Count > 1) { return(estimatedMips); } // KFreon: Half dimensions until one == 1. MipMap[] newmips = new MipMap[estimatedMips]; // TODO: Component size - pixels //var baseBMP = UsefulThings.WPF.Images.CreateWriteableBitmap(currentMip.Pixels, currentMip.Width, currentMip.Height); //baseBMP.Freeze(); Action <int> action = new Action <int>(item => { int index = item; MipMap newmip; var scale = 1d / (2 << (index - 1)); // Shifting is 2^index - Math.Pow seems extraordinarly slow. //newmip = ImageEngine.Resize(baseBMP, scale, scale, currentMip.Width, currentMip.Height, currentMip.LoadedFormatDetails); newmip = ImageEngine.Resize(currentMip, scale, scale); newmips[index - 1] = newmip; }); // Start at 1 to skip top mip if (ImageEngine.EnableThreading) { Parallel.For(1, estimatedMips + 1, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, action); } else { for (int item = 1; item < estimatedMips + 1; item++) { action(item); } } MipMaps.AddRange(newmips); return(MipMaps.Count); }
static int WriteCompressedMipMap(byte[] destination, int mipOffset, MipMap mipmap, int blockSize, Action <byte[], int, int, byte[], int, AlphaSettings, ImageFormats.ImageEngineFormatDetails> compressor, AlphaSettings alphaSetting) { if (mipmap.Width < 4 || mipmap.Height < 4) { return(-1); } int destinationTexelCount = mipmap.Width * mipmap.Height / 16; int sourceLineLength = mipmap.Width * 4 * mipmap.LoadedFormatDetails.ComponentSize; int numTexelsInLine = mipmap.Width / 4; var mipWriter = new Action <int>(texelIndex => { // Since this is the top corner of the first texel in a line, skip 4 pixel rows (texel = 4x4 pixels) and the number of rows down the bitmap we are already. int sourceLineOffset = sourceLineLength * 4 * (texelIndex / numTexelsInLine); // Length in bytes x 3 lines x texel line index (how many texel sized lines down the image are we). Index / width will truncate, so for the first texel line, it'll be < 0. For the second texel line, it'll be < 1 and > 0. int sourceTopLeftCorner = ((texelIndex % numTexelsInLine) * 16) * mipmap.LoadedFormatDetails.ComponentSize + sourceLineOffset; // *16 since its 4 pixels with 4 channels each. Index % numTexels will effectively reset each line. compressor(mipmap.Pixels, sourceTopLeftCorner, sourceLineLength, destination, mipOffset + texelIndex * blockSize, alphaSetting, mipmap.LoadedFormatDetails); }); // Choose an acceleration method. if (ImageEngine.EnableThreading) { Parallel.For(0, destinationTexelCount, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, (mip, loopState) => { if (ImageEngine.IsCancellationRequested) { loopState.Stop(); } mipWriter(mip); }); } else { for (int i = 0; i < destinationTexelCount; i++) { if (ImageEngine.IsCancellationRequested) { break; } mipWriter(i); } } return(mipOffset + destinationTexelCount * blockSize); }
public DDSImage(string ddsFileName) { using (FileStream ddsStream = File.OpenRead(ddsFileName)) { using (BinaryReader r = new BinaryReader(ddsStream)) { dwMagic = r.ReadInt32(); if (dwMagic != 0x20534444) { throw new Exception("This is not a DDS!"); } Read_DDS_HEADER(header, r); if (((header.ddspf.dwFlags & DDPF_FOURCC) != 0) && (header.ddspf.dwFourCC == FOURCC_DX10 /*DX10*/)) { throw new Exception("DX10 not supported yet!"); } int mipMapCount = 1; if ((header.dwFlags & DDSD_MIPMAPCOUNT) != 0) { mipMapCount = header.dwMipMapCount; } mipMaps = new MipMap[mipMapCount]; ddsFormat = getFormat(); double bytePerPixel = getBytesPerPixel(ddsFormat); for (int i = 0; i < mipMapCount; i++) { int w = (int)(header.dwWidth / Math.Pow(2, i)); int h = (int)(header.dwHeight / Math.Pow(2, i)); if (ddsFormat == DDSFormat.DXT1 || ddsFormat == DDSFormat.DXT5) { w = (w < 4) ? 4 : w; h = (h < 4) ? 4 : h; } int mipMapBytes = (int)(w * h * bytePerPixel); mipMaps[i] = new MipMap(r.ReadBytes(mipMapBytes), ddsFormat, w, h); } } } }
public static void importTexture(GpkExport export, string file) { try { var texture2d = export.Payload as Texture2D; var image = new DdsFile(); var config = new DdsSaveConfig(texture2d.parsedImageFormat, 0, 0, false, false); image.Load(file); if (image.MipMaps.Count == 0 || CoreSettings.Default.GenerateMipMaps) { image.GenerateMipMaps(); } texture2d.maps = new List <MipMap>(); foreach (DdsMipMap mipMap in image.MipMaps.OrderByDescending(mip => mip.Width)) { byte[] outputData = image.WriteMipMap(mipMap, config); var textureMipMap = new MipMap(); textureMipMap.flags = (int)CompressionTypes.LZO; //textureMipMap.flags = 0; textureMipMap.uncompressedData = outputData; textureMipMap.uncompressedSize = outputData.Length; textureMipMap.uncompressedSize_chunkheader = outputData.Length; textureMipMap.sizeX = mipMap.Width; textureMipMap.sizeY = mipMap.Height; if (textureMipMap.flags != 0) { textureMipMap.generateBlocks(); } texture2d.maps.Add(textureMipMap); } int mipTailBaseIdx = (int)Math.Log(image.Width > image.Height ? image.Width : image.Height, 2); ((GpkIntProperty)export.GetProperty("MipTailBaseIdx")).SetValue(mipTailBaseIdx.ToString()); logger.Info("Imported image from {0}, size {1}x{2}, target format {3}, mipTailBaseIdx {4}", file, image.Width, image.Height, config.FileFormat, mipTailBaseIdx); } catch (Exception ex) { logger.Error(ex, "Failed to import texture"); logger.Error(ex); } }
/// <summary> /// Ensures all Mipmaps are generated in MipMaps. /// </summary> /// <param name="MipMaps">MipMaps to check.</param> /// <returns>Number of mipmaps present in MipMaps.</returns> internal static int BuildMipMaps(List <MipMap> MipMaps) { if (MipMaps?.Count == 0) { return(0); } MipMap currentMip = MipMaps[0]; // KFreon: Check if mips required int estimatedMips = DDSGeneral.EstimateNumMipMaps(currentMip.Width, currentMip.Height); if (MipMaps.Count > 1) { return(estimatedMips); } // KFreon: Half dimensions until one == 1. MipMap[] newmips = new MipMap[estimatedMips]; Action <int> action = new Action <int>(item => { int index = item; MipMap newmip; var scale = 1d / (2 << (index - 1)); // Shifting is 2^index - Math.Pow seems extraordinarly slow. newmip = Resize(currentMip, scale, scale); newmips[index - 1] = newmip; }); // Start at 1 to skip top mip if (EnableThreading) { Parallel.For(1, estimatedMips + 1, new ParallelOptions { MaxDegreeOfParallelism = ThreadCount }, action); } else { for (int item = 1; item < estimatedMips + 1; item++) { action(item); } } MipMaps.AddRange(newmips); return(MipMaps.Count); }
private void SaveDDS(List <string> paths, string outputPath) { ImageEngineImage outputImage = new ImageEngineImage(paths[0]); for (int i = 1; i < paths.Count; i++) { ImageEngineImage mipImage = new ImageEngineImage(paths[i]); MipMap mip = new MipMap(mipImage.MipMaps[0].Pixels, mipImage.Width, mipImage.Height, mipImage.FormatDetails); outputImage.MipMaps.Add(mip); } ImageFormats.ImageEngineFormatDetails outputFormat = new ImageFormats.ImageEngineFormatDetails(ImageEngineFormat.DDS_DXT1); byte[] data = outputImage.Save(outputFormat, MipHandling.KeepExisting); using (var file = File.Create(outputPath)) { file.Write(data, 0, data.Length); } }
public override bool BuildMipMaps(bool rebuild = false) { bool success = false; MipMap LastMip = MipMaps.Last(); int width = LastMip.width; int height = LastMip.height; Debug.WriteLine("Building V8U8 Mips with starting MIP size of {0} x {1}.", width, height); List <MipMap> newMips = new List <MipMap>(); foreach (var mip in MipMaps) { newMips.Add(mip); } try { byte[] previousMipData = LastMip.data; while (width > 1 && height > 1) { // KFreon: Need original height to resize. Original = previous, but initially it's the original height. byte[] newMipData = BuildMip(previousMipData, width, height, 2); // KFreon: Adjust to new dimensions for generating the new mipmap height /= 2; width /= 2; newMips.Add(new MipMap(newMipData, width, height)); previousMipData = newMipData; } MipMaps = newMips.ToArray(); success = true; } catch (Exception e) { Console.WriteLine(e); } return(success); }
public static TDX Load(Stream stream, string name = null) { TDX tdx = new TDX { Name = name }; using (BinaryReader br = new BinaryReader(stream)) { if (br.ReadByte() != 0x00 || br.ReadByte() != 0x02) { Logger.LogToFile(Logger.LogLevel.Error, "This isn't a valid TDX"); return(null); } tdx.Width = br.ReadUInt16(); tdx.Height = br.ReadUInt16(); int mipCount = br.ReadUInt16(); tdx.Flags = (TDXFlags)br.ReadUInt32(); tdx.Format = (D3DFormat)br.ReadUInt32(); if (tdx.Flags.HasFlag(TDXFlags.ExtraData)) { int extraDataLength = (int)br.ReadUInt32(); int extraDataType = br.ReadUInt16(); switch (extraDataType) { case 0: tdx.ExtraDataType = ExtraDataTypes.Font; tdx.ExtraData = new FontDefinition(br.ReadBytes(extraDataLength - 2)); break; case 1: tdx.ExtraDataType = ExtraDataTypes.Animation; tdx.ExtraData = new AnimationDefinition(br.ReadBytes(extraDataLength - 2)); break; case 3: tdx.ExtraDataType = ExtraDataTypes.VTMap; tdx.ExtraData = new VTMap(br.ReadBytes(extraDataLength - 2)); break; default: throw new NotImplementedException($"Unknown Extra Data flag: {extraDataType}"); } } for (int i = 0; i < mipCount; i++) { MipMap mip = new MipMap() { Width = tdx.Width >> i, Height = tdx.Height >> i }; switch (tdx.Format) { case D3DFormat.PVRTC4: mip.Data = br.ReadBytes(mip.Width * mip.Height / 2); break; case D3DFormat.R4G4B4A4: case D3DFormat.R5G5B6: case D3DFormat.R5G6B5: mip.Data = br.ReadBytes(mip.Width * mip.Height * 2); break; case D3DFormat.A8B8G8R8: mip.Data = br.ReadBytes(mip.Width * mip.Height * 4); break; case D3DFormat.A8R8G8B8: mip.Data = br.ReadBytes(mip.Width * mip.Height * (tdx.Flags.HasFlag(TDXFlags.AlphaNbit) ? 4 : 3)); break; case D3DFormat.A8: mip.Data = br.ReadBytes(mip.Width * mip.Height); break; case D3DFormat.DXT1: mip.Data = br.ReadBytes((mip.Width + 3) / 4 * ((mip.Height + 3) / 4) * 8); break; case D3DFormat.ATI2: case D3DFormat.DXT5: mip.Data = br.ReadBytes((mip.Width + 3) / 4 * ((mip.Height + 3) / 4) * 16); break; default: Logger.LogToFile(Logger.LogLevel.Error, "Unknown format: {0}", tdx.Format); return(null); } tdx.MipMaps.Add(mip); } } return(tdx); }
public void Initialize() { mipmap = new MipMap <int>(width, height); }
public DDSImage(string ddsFileName) { using (FileStream ddsStream = File.OpenRead(ddsFileName)) { using (BinaryReader r = new BinaryReader(ddsStream)) { dwMagic = r.ReadInt32(); if (dwMagic != 0x20534444) { throw new Exception("This is not a DDS!"); } Read_DDS_HEADER(header, r); if (((header.ddspf.dwFlags & DDPF_FOURCC) != 0) && (header.ddspf.dwFourCC == FOURCC_DX10 /*DX10*/)) { throw new Exception("DX10 not supported yet!"); } int mipMapCount = 1; if ((header.dwFlags & DDSD_MIPMAPCOUNT) != 0) mipMapCount = header.dwMipMapCount; mipMaps = new MipMap[mipMapCount]; ddsFormat = getFormat(); double bytePerPixel = getBytesPerPixel(ddsFormat); for (int i = 0; i < mipMapCount; i++) { int w = (int)(header.dwWidth / Math.Pow(2, i)); int h = (int)(header.dwHeight / Math.Pow(2, i)); if (ddsFormat == DDSFormat.DXT1 || ddsFormat == DDSFormat.DXT5) { w = (w < 4) ? 4 : w; h = (h < 4) ? 4 : h; } int mipMapBytes = (int)(w * h * bytePerPixel); mipMaps[i] = new MipMap(r.ReadBytes(mipMapBytes), ddsFormat, w, h); } } } }
public Bitmap GetBitmap(int mipLevel = 0) { MipMap mip = MipMaps[mipLevel]; int x, y; Bitmap bmp = new Bitmap(mip.Width, mip.Height, PixelFormat.Format32bppArgb); byte[] data; byte[] dest; switch (Format) { case D3DFormat.PVRTC4: data = mip.Data; dest = new byte[mip.Width * mip.Height * 4]; PVTRC.Decompress(data, false, mip.Width, mip.Height, true, dest); for (y = 0; y < mip.Height; y++) { for (x = 0; x < mip.Width; x++) { int offset = (x + y * mip.Width) * 4; Color c = Color.FromArgb(dest[offset + 3], dest[offset + 0], dest[offset + 1], dest[offset + 2]); bmp.SetPixel(x, y, c); } } break; case D3DFormat.R4G4B4A4: data = mip.Data; for (y = 0; y < mip.Height; y++) { for (x = 0; x < mip.Width; x++) { int offset = (x + y * mip.Width) * 2; int pixel = BitConverter.ToUInt16(data, offset); bmp.SetPixel(x, y, ColorHelper.R4G4B4A4ToColor(pixel)); } } break; case D3DFormat.R5G5B6: data = mip.Data; for (y = 0; y < mip.Height; y++) { for (x = 0; x < mip.Width; x++) { int offset = (x + y * mip.Width) * 2; int pixel = BitConverter.ToUInt16(data, offset); bmp.SetPixel(x, y, ColorHelper.R5G5B6ToColor(pixel)); } } break; case D3DFormat.A8B8G8R8: data = mip.Data; for (y = 0; y < mip.Height; y++) { for (x = 0; x < mip.Width; x++) { int offset = (x + y * mip.Width) * 4; uint pixel = BitConverter.ToUInt32(data, offset); bmp.SetPixel(x, y, ColorHelper.A8B8G8R8ToColor(pixel)); } } break; case D3DFormat.R5G6B5: data = mip.Data; for (y = 0; y < mip.Height; y++) { for (x = 0; x < mip.Width; x++) { int offset = (x + y * mip.Width) * 2; int pixel = BitConverter.ToUInt16(data, offset); bmp.SetPixel(x, y, ColorHelper.R5G6B5ToColor(pixel)); } } break; case D3DFormat.A8R8G8B8: data = mip.Data; int pixelSize = Flags.HasFlag(TDXFlags.AlphaNbit) ? 4 : 3; for (y = 0; y < mip.Height; y++) { for (x = 0; x < mip.Width; x++) { int offset = (x + y * mip.Width) * pixelSize; int a = pixelSize == 4 ? data[offset + 3] : 255; Color c = Color.FromArgb(a, data[offset + 0], data[offset + 1], data[offset + 2]); bmp.SetPixel(x, y, c); } } break; case D3DFormat.DXT1: data = mip.Data; dest = new byte[mip.Width * mip.Height * 4]; Squish.Squish.DecompressImage(dest, mip.Width, mip.Height, ref data, SquishFlags.kDxt1); for (y = 0; y < mip.Height; y++) { for (x = 0; x < mip.Width; x++) { int offset = (x + y * mip.Width) * 4; Color c = Color.FromArgb(dest[offset + 3], dest[offset + 0], dest[offset + 1], dest[offset + 2]); bmp.SetPixel(x, y, c); } } break; case D3DFormat.DXT5: data = mip.Data; dest = new byte[mip.Width * mip.Height * 4]; Squish.Squish.DecompressImage(dest, mip.Width, mip.Height, ref data, SquishFlags.kDxt5); for (y = 0; y < mip.Height; y++) { for (x = 0; x < mip.Width; x++) { int offset = (x + y * mip.Width) * 4; Color c = Color.FromArgb(dest[offset + 3], dest[offset + 0], dest[offset + 1], dest[offset + 2]); bmp.SetPixel(x, y, c); } } break; case D3DFormat.ATI2: data = mip.Data; dest = BC5Unorm.Decompress(data, (uint)mip.Width, (uint)mip.Height); for (y = 0; y < mip.Height; y++) { for (x = 0; x < mip.Width; x++) { int offset = (x + y * mip.Width) * 4; Color c = Color.FromArgb(dest[offset + 3], dest[offset + 2], dest[offset + 1], dest[offset + 0]); bmp.SetPixel(x, y, c); } } break; } return(bmp); }
private void LoadImage(Stream stream) { stream.Seek(0, SeekOrigin.Begin); BinaryReader r = new BinaryReader(stream); int dwMagic = r.ReadInt32(); if (dwMagic != 0x20534444) throw new Exception("This is not a DDS! Magic number wrong."); DDS_HEADER header = new DDS_HEADER(); Read_DDS_HEADER(header, r); if (((header.ddspf.dwFlags & 0x00000004) != 0) && (header.ddspf.dwFourCC == 0x30315844 /*DX10*/)) { throw new Exception("DX10 not supported yet!"); } if (header.ddspf.dwFourCC != 0) throw new InvalidDataException("DDS not V8U8."); int mipMapCount = 1; if ((header.dwFlags & 0x00020000) != 0) mipMapCount = header.dwMipMapCount; int w = 0; int h = 0; double bytePerPixel = 2; MipMaps = new MipMap[mipMapCount]; // KFreon: Get mips for (int i = 0; i < mipMapCount; i++) { w = (int)(header.dwWidth / Math.Pow(2, i)); h = (int)(header.dwHeight / Math.Pow(2, i)); int mipMapBytes = (int)(w * h * bytePerPixel); MipMaps[i] = new MipMap(r.ReadBytes(mipMapBytes), w, h); } Width = header.dwWidth; Height = header.dwHeight; }
/// <summary> /// /// </summary> /// <param name="buffer"></param> /// <param name="loadMipmaps"></param> private Dds Parse(byte[] buffer, bool loadMipmaps = false) { var dds = new Dds(); // Adapted from @toji's DDS utils // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js // All values and structures referenced from: // http://msdn.microsoft.com/en-us/library/bb943991.aspx/ var DDS_MAGIC = 0x20534444; var DDSD_CAPS = 0x1; var DDSD_HEIGHT = 0x2; var DDSD_WIDTH = 0x4; var DDSD_PITCH = 0x8; var DDSD_PIXELFORMAT = 0x1000; var DDSD_MIPMAPCOUNT = 0x20000; var DDSD_LINEARSIZE = 0x80000; var DDSD_DEPTH = 0x800000; var DDSCAPS_COMPLEX = 0x8; var DDSCAPS_MIPMAP = 0x400000; var DDSCAPS_TEXTURE = 0x1000; var DDSCAPS2_CUBEMAP = 0x200; var DDSCAPS2_CUBEMAP_POSITIVEX = 0x400; var DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800; var DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000; var DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000; var DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000; var DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000; var DDSCAPS2_VOLUME = 0x200000; var DDPF_ALPHAPIXELS = 0x1; var DDPF_ALPHA = 0x2; var DDPF_FOURCC = 0x4; var DDPF_RGB = 0x40; var DDPF_YUV = 0x200; var DDPF_LUMINANCE = 0x20000; var FOURCC_DXT1 = FourCcToInt32("DXT1"); var FOURCC_DXT3 = FourCcToInt32("DXT3"); var FOURCC_DXT5 = FourCcToInt32("DXT5"); var headerLengthInt = 31; // The header length in 32 bit ints // Offsets into the header array var off_magic = 0; var off_size = 1; var off_flags = 2; var off_height = 3; var off_width = 4; var off_mipmapCount = 7; var off_pfFlags = 20; var off_pfFourCC = 21; var off_RGBBitCount = 22; var off_RBitMask = 23; var off_GBitMask = 24; var off_BBitMask = 25; var off_ABitMask = 26; var off_caps = 27; var off_caps2 = 28; var off_caps3 = 29; var off_caps4 = 30; // Parse header var header = new Int32[headerLengthInt]; Buffer.BlockCopy(buffer, 0, header, 0, headerLengthInt * sizeof(int)); if (header[off_magic] != DDS_MAGIC) { Trace.TraceError("THREE.DDSLoader.parse: Invalid magic number in DDS header."); return(dds); } //if ((header[off_pfFlags] & DDPF_FOURCC) == 0) //{ // Trace.TraceError("THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code."); // return dds; //} var blockBytes = 0; var fourCC = header[off_pfFourCC]; var isRGBAUncompressed = false; if (fourCC == FOURCC_DXT1) { blockBytes = 8; dds.Format = ThreeCs.Three.RGB_S3TC_DXT1_Format; } else if (fourCC == FOURCC_DXT3) { blockBytes = 16; dds.Format = ThreeCs.Three.RGBA_S3TC_DXT3_Format; } else if (fourCC == FOURCC_DXT5) { blockBytes = 16; dds.Format = ThreeCs.Three.RGBA_S3TC_DXT3_Format; } else { if (header[off_RGBBitCount] == 32 && (header[off_RBitMask] & 0xff0000) > 0 && (header[off_GBitMask] & 0xff00) > 0 && (header[off_BBitMask] & 0xff) > 0 && (header[off_ABitMask] & 0xff000000) > 0) { isRGBAUncompressed = true; blockBytes = 64; dds.Format = ThreeCs.Three.RGBAFormat; } else { Trace.TraceError("THREE.DDSLoader.parse: Unsupported FourCC code "); return(dds); } } dds.MipmapCount = 1; if (((header[off_flags] & DDSD_MIPMAPCOUNT) > 0) && loadMipmaps != false) { dds.MipmapCount = Math.Max(1, header[off_mipmapCount]); } //TODO: Verify that all faces of the cubemap are present with DDSCAPS2_CUBEMAP_POSITIVEX, etc. dds.IsCubemap = ((header[off_caps2] & DDSCAPS2_CUBEMAP) > 0) ? true : false; dds.Width = header[off_width]; dds.Height = header[off_height]; var dataOffset = header[off_size] + 4; // Extract mipmaps buffers var width = dds.Width; var height = dds.Height; var faces = dds.IsCubemap ? 6 : 1; for (var face = 0; face < faces; face++) { for (var i = 0; i < dds.MipmapCount; i++) { var dataLength = 0; byte[] byteArray = null; if (isRGBAUncompressed) { byteArray = loadARGBMip(buffer, dataOffset, width, height); dataLength = byteArray.Length; } else { dataLength = Math.Max(4, width) / 4 * Math.Max(4, height) / 4 * blockBytes; byteArray = new byte[dataLength]; Buffer.BlockCopy(buffer, dataOffset, byteArray, 0, dataLength); //var byteArray = new Uint8Array( buffer, dataOffset, dataLength ); } var mipmap = new MipMap() { Data = byteArray, Width = width, Height = height }; dds.Mipmaps.Add(mipmap); dataOffset += dataLength; width = (int)Math.Max(width * 0.5, 1); height = (int)Math.Max(height * 0.5, 1); } width = dds.Width; height = dds.Height; } return(dds); }
static void WriteUncompressedMipMap(byte[] destination, int mipOffset, MipMap mipmap, ImageFormats.ImageEngineFormatDetails destFormatDetails, DDS_Header.DDS_PIXELFORMAT ddspf) { DDS_Encoders.WriteUncompressed(mipmap.Pixels, destination, mipOffset, ddspf, mipmap.LoadedFormatDetails, destFormatDetails); }
BitmapSource GetWPFBitmap(MipMap mip, int maxDimension, bool ShowAlpha) { BitmapSource bmp = null; if (maxDimension != 0) { // Choose a mip of the correct size, if available. var sizedMip = MipMaps.Where(m => (m.Height <= maxDimension && m.Width <= maxDimension) || (m.Width <= maxDimension && m.Height <= maxDimension)); if (sizedMip.Any()) { var mip1 = sizedMip.First(); bmp = mip1.ToImage(); } else { double scale = (double)maxDimension / (Height > Width ? Height : Width); mip = ImageEngine.Resize(mip, scale); bmp = mip.ToImage(); } } else bmp = mip.ToImage(); if (!ShowAlpha) bmp = new FormatConvertedBitmap(bmp, System.Windows.Media.PixelFormats.Bgr32, null, 0); bmp.Freeze(); return bmp; }
internal static List <MipMap> LoadDDS(MemoryStream compressed, DDS_Header header, int desiredMaxDimension, ImageFormats.ImageEngineFormatDetails formatDetails) { MipMap[] MipMaps = null; int mipWidth = header.Width; int mipHeight = header.Height; ImageEngineFormat format = header.Format; int estimatedMips = header.dwMipMapCount; int mipOffset = formatDetails.HeaderSize; int originalOffset = mipOffset; if (!EnsureMipInImage(compressed.Length, mipWidth, mipHeight, 4, formatDetails, out mipOffset)) // Update number of mips too { estimatedMips = 1; } if (estimatedMips == 0) { estimatedMips = EstimateNumMipMaps(mipWidth, mipHeight); } mipOffset = originalOffset; // Needs resetting after checking there's mips in this image. // Ensure there's at least 1 mipmap if (estimatedMips == 0) { estimatedMips = 1; } int orig_estimatedMips = estimatedMips; // Store original count for later (uncompressed only I think) // KFreon: Decide which mip to start loading at - going to just load a few mipmaps if asked instead of loading all, then choosing later. That's slow. if (desiredMaxDimension != 0 && estimatedMips > 1) { if (!EnsureMipInImage(compressed.Length, mipWidth, mipHeight, desiredMaxDimension, formatDetails, out mipOffset)) // Update number of mips too { throw new InvalidDataException($"Requested mipmap does not exist in this image. Top Image Size: {mipWidth}x{mipHeight}, requested mip max dimension: {desiredMaxDimension}."); } // Not the first mipmap. if (mipOffset > formatDetails.HeaderSize) { double divisor = mipHeight > mipWidth ? mipHeight / desiredMaxDimension : mipWidth / desiredMaxDimension; mipHeight = (int)(mipHeight / divisor); mipWidth = (int)(mipWidth / divisor); if (mipWidth == 0 || mipHeight == 0) // Reset as a dimension is too small to resize { mipHeight = header.Height; mipWidth = header.Width; mipOffset = formatDetails.HeaderSize; } else { // Update estimated mips due to changing dimensions. estimatedMips = EstimateNumMipMaps(mipWidth, mipHeight); } } else // The first mipmap { mipOffset = formatDetails.HeaderSize; } } // Move to requested mipmap compressed.Position = mipOffset; // Block Compressed texture chooser. Action <byte[], int, byte[], int, int, bool> DecompressBCBlock = formatDetails.BlockDecoder; MipMaps = new MipMap[estimatedMips]; int blockSize = formatDetails.BlockSize; // KFreon: Read mipmaps if (formatDetails.IsBlockCompressed) // Threading done in the decompression, not here. { for (int m = 0; m < estimatedMips; m++) { if (ImageEngine.IsCancellationRequested) { break; } // KFreon: If mip is too small, skip out. This happens most often with non-square textures. I think it's because the last mipmap is square instead of the same aspect. // Don't do the mip size check here (<4) since we still need to have a MipMap object for those lower than this for an accurate count. if (mipWidth <= 0 || mipHeight <= 0) // Needed cos it doesn't throw when reading past the end for some reason. { break; } MipMap mipmap = ReadCompressedMipMap(compressed, mipWidth, mipHeight, mipOffset, formatDetails, DecompressBCBlock); MipMaps[m] = mipmap; mipOffset += (int)(mipWidth * mipHeight * (blockSize / 16d)); // Also put the division in brackets cos if the mip dimensions are high enough, the multiplications can exceed int.MaxValue) mipWidth /= 2; mipHeight /= 2; } } else { int startMip = orig_estimatedMips - estimatedMips; // UNCOMPRESSED - Can't really do threading in "decompression" so do it for the mipmaps. var action = new Action <int>(mipIndex => { // Calculate mipOffset and dimensions int offset, width, height; offset = GetMipOffset(mipIndex, formatDetails, header.Width, header.Height); double divisor = mipIndex == 0 ? 1d : 2 << (mipIndex - 1); // Divisor represents 2^mipIndex - Math.Pow seems very slow. width = (int)(header.Width / divisor); height = (int)(header.Height / divisor); MipMap mipmap = null; try { mipmap = ReadUncompressedMipMap(compressed, offset, width, height, header.ddspf, formatDetails); } catch (Exception e) { Debug.WriteLine(e.ToString()); } MipMaps[mipIndex] = mipmap; }); if (ImageEngine.EnableThreading) { Parallel.For(startMip, orig_estimatedMips, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, (mip, loopState) => { if (ImageEngine.IsCancellationRequested) { loopState.Stop(); } action(mip); }); } else { for (int i = startMip; i < orig_estimatedMips; i++) { if (ImageEngine.IsCancellationRequested) { break; } action(i); } } } List <MipMap> mips = new List <MipMap>(MipMaps.Where(t => t != null)); if (mips.Count == 0) { throw new InvalidOperationException($"No mipmaps loaded. Estimated mips: {estimatedMips}, mip dimensions: {mipWidth}x{mipHeight}"); } return(mips); }
public override void Deserialize(Stream input) { var encoding = this.Encoding; var basePosition = input.Position; base.Deserialize(input); var endian = this.Endian; var entryCount = input.ReadValueS32(endian); var entryNameTableOffset = input.ReadValueS64(endian); var rawEntries = new RawEntry[entryCount]; for (int i = 0; i < entryCount; i++) { rawEntries[i] = RawEntry.Read(input, endian); } var entryNames = new string[entryCount]; if (entryCount > 0) { input.Position = basePosition + entryNameTableOffset; for (int i = 0; i < entryCount; i++) { var nameLength = input.ReadValueU16(endian); var name = input.ReadString(nameLength, encoding); entryNames[i] = name; } } // we're assuming the entry names match up in order with the entries var entries = new Entry[entryCount]; for (int i = 0; i < entryCount; i++) { var rawEntry = rawEntries[i]; var rawMipMaps = rawEntry.MipMaps; var mipMaps = new MipMap[rawMipMaps.Length]; for (int j = 0; j < rawMipMaps.Length; j++) { var rawMipMap = rawMipMaps[j]; mipMaps[j] = new MipMap() { DataOffset = rawMipMap.DataOffset, DataCompressedSize = rawMipMap.DataCompressedSize, DataUncompressedSize = rawMipMap.DataUncompressedSize, IndexStart = rawMipMap.IndexStart, IndexEnd = rawMipMap.IndexEnd, }; } entries[i] = new Entry() { Name = entryNames[i], NameHash = rawEntry.NameHash, Type = rawEntry.Type, DirectoryNameHash = rawEntry.DirectoryNameHash, Unknown0C = rawEntry.Unknown0C, Height = rawEntry.Height, Width = rawEntry.Width, MipMapCount = rawEntry.MipMapCount, Format = rawEntry.Format, Unknown16 = rawEntry.Unknown16, Unknown17 = rawEntry.Unknown17, MipMaps = mipMaps, }; } this._Entries.Clear(); this._Entries.AddRange(entries); }
public static DDS Load(byte[] data) { DDS dds = new DDS(); using (MemoryStream ms = new MemoryStream(data)) using (BinaryReader br = new BinaryReader(ms)) { if (!IsDDS(br)) { return(null); } br.ReadUInt32(); // header length dds.Flags = (HeaderFlags)br.ReadUInt32(); dds.Height = (int)br.ReadUInt32(); dds.Width = (int)br.ReadUInt32(); dds.Pitch = (int)br.ReadUInt32(); dds.Depth = (int)br.ReadUInt32(); int mipCount = (int)br.ReadUInt32(); for (int i = 0; i < 11; i++) { br.ReadUInt32(); } br.ReadUInt32(); // pixel format length dds.PixelFormat = new DDSPixelFormat { Flags = (PixelFormatFlags)br.ReadUInt32(), FourCC = (PixelFormatFourCC)br.ReadUInt32(), RGBBitCount = (int)br.ReadUInt32(), RBitMask = br.ReadUInt32(), GBitMask = br.ReadUInt32(), BBitMask = br.ReadUInt32(), ABitMask = br.ReadUInt32() }; dds.Caps = (DDSCaps)br.ReadUInt32(); dds.Caps2 = (DDSCaps2)br.ReadUInt32(); br.ReadUInt32(); br.ReadUInt32(); br.ReadUInt32(); if (dds.PixelFormat.Flags.HasFlag(PixelFormatFlags.DDPF_FOURCC)) { dds.Format = (D3DFormat)dds.PixelFormat.FourCC; } else if (dds.PixelFormat.Flags.HasFlag(PixelFormatFlags.DDPF_RGB) & dds.PixelFormat.Flags.HasFlag(PixelFormatFlags.DDPF_ALPHAPIXELS)) { dds.Format = D3DFormat.A8R8G8B8; } for (int i = 0; i < Math.Max(1, mipCount); i++) { MipMap mip = new MipMap { Width = dds.Width >> i, Height = dds.Height >> i }; switch (dds.Format) { case D3DFormat.A8R8G8B8: mip.Data = br.ReadBytes(mip.Width * mip.Height * 4); break; case D3DFormat.DXT1: mip.Data = br.ReadBytes((((mip.Width + 3) / 4) * ((mip.Height + 3) / 4)) * 8); break; case D3DFormat.DXT3: case D3DFormat.DXT5: mip.Data = br.ReadBytes((((mip.Width + 3) / 4) * ((mip.Height + 3) / 4)) * 16); break; } dds.MipMaps.Add(mip); } } return(dds); }
private void readHeader(Stream stream) { byte[] buffer = new byte[HEADER_SIZE]; stream.Read(buffer, 0, HEADER_SIZE); long dataSize = BitConverter.ToUInt32(buffer, 0); bool compressed = dataSize != 0; float alphaTest = BitConverter.ToSingle(buffer, 4); int width = BitConverter.ToUInt16(buffer, 8), height = BitConverter.ToUInt16(buffer, 10); encoding = (Encoding)buffer[12]; //bool hasAlpha = ((int)encoding & 4) == 4; int mipMapCount = Math.Max(1, (int)buffer[13]); int minDataSize; switch (encoding) { case Encoding.Gray: Format = TextureFormat.RGB24; minDataSize = 1; dataSize = width * height * minDataSize; break; case Encoding.RGB: if (compressed) { Format = TextureFormat.DXT1; minDataSize = 8; } else { Format = TextureFormat.RGB24; minDataSize = 3; dataSize = width * height * minDataSize; } break; case Encoding.RGBA: if (compressed) { Format = TextureFormat.DXT5; minDataSize = 16; } else { Format = TextureFormat.RGBA32; minDataSize = 4; dataSize = width * height * minDataSize; } break; case Encoding.BGRA: Format = TextureFormat.BGRA32; minDataSize = 4; dataSize = width * height * minDataSize; break; default: Format = TextureFormat.RGB24; minDataSize = 0; break; } // Calculate the complete data size for images with mipmaps long completeDataSize = dataSize; int w = width, h = height; for (int i = 1; i < mipMapCount; ++i) { w = Math.Max(w >> 1, 1); h = Math.Max(h >> 1, 1); completeDataSize += getDataSize(Format, w, h); } completeDataSize *= _layerCount; stream.Position += completeDataSize; readTXIData(stream); bool isAnimated = false; //checkAnimated(width, height, dataSize); stream.Position = HEADER_SIZE; long fullImageSize = getDataSize(Format, width, height); long combinedSize = 0; mipMaps = new MipMap[mipMapCount]; for (int l = 0; l < _layerCount; l++) { int layerWidth = width; int layerHeight = height; long layerSize = (isAnimated) ? getDataSize(Format, layerWidth, layerHeight) : dataSize; for (int i = 0; i < mipMapCount; i++) { mipMaps[i] = new MipMap(); mipMaps[i].width = Math.Max(layerWidth, 1); mipMaps[i].height = Math.Max(layerHeight, 1); mipMaps[i].size = Math.Max(layerSize, minDataSize); long mipMapDataSize = getDataSize(Format, mipMaps[i].width, mipMaps[i].height); combinedSize += mipMaps[i].size; layerWidth >>= 1; layerHeight >>= 1; layerSize = getDataSize(Format, layerWidth, layerHeight); if ((layerWidth < 1) && (layerHeight < 1)) { break; } } } }
public static XT2 Load(string path) { FileInfo fi = new FileInfo(path); Console.WriteLine("{0}", path); XT2 xt2 = new XT2() { Name = fi.Name.Replace(fi.Extension, "") }; using (BEBinaryReader br = new BEBinaryReader(fi.OpenRead())) { Console.WriteLine("Always 0 : {0}", br.ReadUInt32()); int magic = (int)br.ReadUInt32(); br.ReadUInt32(); // datasize Console.WriteLine("Always 52 : {0}", br.ReadUInt32()); Console.WriteLine("Always 0 : {0}", br.ReadUInt16()); xt2.Width = br.ReadUInt16(); xt2.Height = br.ReadUInt16(); Console.WriteLine("{0} {1}", br.ReadUInt16(), br.ReadUInt16()); xt2.Header = new D3DBaseTexture(br); int W = 0; int H = 0; int TexelPitch = 0; int DataSize = 0; int OutSize = 0; int w = xt2.Width; int h = xt2.Height; switch (xt2.Header.DataFormat) { //case D3DFormat.X360_A8R8G8B8: // W = w; // H = h; // TexelPitch = 4; // DataSize = w * h * 4; // break; //case D3DFormat.X360_DXT1: // W = w / 4; // H = h / 4; // TexelPitch = 8; // DataSize = w * h / 2; // break; //case D3DFormat.X360_DXT2: // W = w / 4; // H = h / 4; // TexelPitch = 16; // DataSize = W * H; // break; } if (H % 128 != 0) { H += 128 - H % 128; } switch (xt2.Header.DataFormat) { //case D3DFormat.X360_A8R8G8B8: // OutSize = W * H * TexelPitch; // break; //case D3DFormat.X360_DXT1: // OutSize = W * H * TexelPitch; // break; //case D3DFormat.X360_DXT2: // DataSize = w * H * 2; // OutSize = W * H * TexelPitch; // break; } byte[] data = new byte[DataSize]; byte[] outdata = new byte[OutSize]; Array.Copy(br.ReadBytes(DataSize), data, DataSize); int step = xt2.Header.Endian == 1 ? 2 : 4; for (int i = 0; i < data.Length; i += step) { for (int j = 0; j < step / 2; j++) { byte tmp = data[i + j]; data[i + j] = data[i + step - j - 1]; data[i + step - j - 1] = tmp; } } for (int y = 0; y < H; y++) { for (int x = 0; x < W; x++) { int offset = Xbox360ConvertTextureAddress(x, y, W, TexelPitch); if (offset * TexelPitch < data.Length) { Array.Copy(data, offset * TexelPitch, outdata, (x + y * W) * TexelPitch, TexelPitch); } } } MipMap mip = new MipMap { Width = xt2.Width, Height = xt2.Height, Data = data }; xt2.MipMaps.Add(mip); } return(xt2); }
/// <summary> /// Ensures all Mipmaps are generated in MipMaps. /// </summary> /// <param name="MipMaps">MipMaps to check.</param> /// <returns>Number of mipmaps present in MipMaps.</returns> internal static int BuildMipMaps(List<MipMap> MipMaps) { if (MipMaps?.Count == 0) return 0; MipMap currentMip = MipMaps[0]; // KFreon: Check if mips required int estimatedMips = DDSGeneral.EstimateNumMipMaps(currentMip.Width, currentMip.Height); if (MipMaps.Count > 1) return estimatedMips; // KFreon: Half dimensions until one == 1. MipMap[] newmips = new MipMap[estimatedMips]; var baseBMP = UsefulThings.WPF.Images.CreateWriteableBitmap(currentMip.Pixels, currentMip.Width, currentMip.Height); baseBMP.Freeze(); Action<int> action = new Action<int>(item => { int index = item; MipMap newmip; var scale = 1d / (2 << (index - 1)); // Shifting is 2^index - Math.Pow seems extraordinarly slow. newmip = ImageEngine.Resize(baseBMP, scale, scale, currentMip.Width, currentMip.Height); newmips[index - 1] = newmip; }); // Start at 1 to skip top mip if (ImageEngine.EnableThreading) Parallel.For(1, estimatedMips + 1, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, action); else for (int item = 1; item < estimatedMips + 1; item++) action(item); MipMaps.AddRange(newmips); return estimatedMips; }
static int WriteUncompressedMipMap(byte[] destination, int mipOffset, MipMap mipmap, ImageEngineFormat saveFormat, DDS_Header.DDS_PIXELFORMAT ddspf) { return DDS_Encoders.WriteUncompressed(mipmap.Pixels, destination, mipOffset, ddspf); }
public static (string, IDxt <IImage>) ReadDds(IFile ddsFile) { using var ddsStream = ddsFile.OpenRead(); var er = new EndianBinaryReader(ddsStream, Endianness.LittleEndian); er.AssertString("DDS "); er.AssertInt32(124); // size var flags = er.ReadInt32(); var width = er.ReadInt32(); var height = er.ReadInt32(); var pitchOrLinearSize = er.ReadInt32(); var depth = er.ReadInt32(); // TODO: Read others var mipmapCount = er.ReadInt32(); var reserved1 = er.ReadInt32s(11); // DDS_PIXELFORMAT er.AssertInt32(32); // size var pfFlags = er.ReadInt32(); var pfFourCc = er.ReadString(4); var pfRgbBitCount = er.ReadInt32(); var pfRBitMask = er.ReadInt32(); var pfGBitMask = er.ReadInt32(); var pfBBitMask = er.ReadInt32(); var pfABitMask = er.ReadInt32(); var caps1 = er.ReadInt32(); var caps2 = er.ReadInt32(); var isCubeMap = (caps2 & 0x200) != 0; var hasPositiveX = (caps2 & 0x400) != 0; var hasNegativeX = (caps2 & 0x800) != 0; var hasPositiveY = (caps2 & 0x1000) != 0; var hasNegativeY = (caps2 & 0x2000) != 0; var hasPositiveZ = (caps2 & 0x4000) != 0; var hasNegativeZ = (caps2 & 0x8000) != 0; var hasVolume = (caps2 & 0x200000) != 0; var sideCount = new[] { hasPositiveX, hasNegativeX, hasPositiveY, hasNegativeY, hasPositiveZ, hasNegativeZ }.Count(b => b); sideCount = Math.Max(1, sideCount); var queue = new Queue <CubeMapSide>(); if (hasPositiveX) { queue.Enqueue(CubeMapSide.POSITIVE_X); } if (hasNegativeX) { queue.Enqueue(CubeMapSide.NEGATIVE_X); } if (hasPositiveY) { queue.Enqueue(CubeMapSide.POSITIVE_Y); } if (hasNegativeY) { queue.Enqueue(CubeMapSide.NEGATIVE_Y); } if (hasPositiveZ) { queue.Enqueue(CubeMapSide.POSITIVE_Z); } if (hasNegativeZ) { queue.Enqueue(CubeMapSide.NEGATIVE_Z); } er.Position = 128; switch (pfFourCc) { case "q\0\0\0": { var q000Text = "a16b16g16r16"; var hdrCubeMap = new CubeMapImpl <IList <float> >(); for (var s = 0; s < sideCount; s++) { var hdrMipMap = new MipMap <IList <float> >(); for (var i = 0; i < mipmapCount; ++i) { var mmWidth = width >> i; var mmHeight = height >> i; var hdr = DecompressA16B16G16R16F(er, mmWidth, mmHeight); hdrMipMap.AddLevel( new MipMapLevel <IList <float> >(hdr, mmWidth, mmHeight)); } if (!isCubeMap) { return( q000Text, new DxtImpl <IImage>( ToneMapAndConvertHdrMipMapsToBitmap(hdrMipMap))); } var side = queue.Dequeue(); switch (side) { case CubeMapSide.POSITIVE_X: { hdrCubeMap.PositiveX = hdrMipMap; break; } case CubeMapSide.NEGATIVE_X: { hdrCubeMap.NegativeX = hdrMipMap; break; } case CubeMapSide.POSITIVE_Y: { hdrCubeMap.PositiveY = hdrMipMap; break; } case CubeMapSide.NEGATIVE_Y: { hdrCubeMap.NegativeY = hdrMipMap; break; } case CubeMapSide.POSITIVE_Z: { hdrCubeMap.PositiveZ = hdrMipMap; break; } case CubeMapSide.NEGATIVE_Z: { hdrCubeMap.NegativeZ = hdrMipMap; break; } default: throw new ArgumentOutOfRangeException(); } } return(q000Text, new DxtImpl <IImage>( ToneMapAndConvertHdrCubemapToBitmap(hdrCubeMap))); } default: { ddsStream.Position = 0; return(pfFourCc, new DxtImpl <IImage>(new DdsReader().Read(ddsStream))); } } }
static int WriteCompressedMipMap(byte[] destination, int mipOffset, MipMap mipmap, int blockSize, Action<byte[], int, int, byte[], int, AlphaSettings> compressor, AlphaSettings alphaSetting) { int destinationTexelCount = mipmap.Width * mipmap.Height / 16; int sourceLineLength = mipmap.Width * 4; int numTexelsInLine = mipmap.Width / 4; var mipWriter = new Action<int>(texelIndex => { // Since this is the top corner of the first texel in a line, skip 4 pixel rows (texel = 4x4 pixels) and the number of rows down the bitmap we are already. int sourceLineOffset = sourceLineLength * 4 * (texelIndex / numTexelsInLine); // Length in bytes x 3 lines x texel line index (how many texel sized lines down the image are we). Index / width will truncate, so for the first texel line, it'll be < 0. For the second texel line, it'll be < 1 and > 0. int sourceTopLeftCorner = ((texelIndex % numTexelsInLine) * 16) + sourceLineOffset; // *16 since its 4 pixels with 4 channels each. Index % numTexels will effectively reset each line. compressor(mipmap.Pixels, sourceTopLeftCorner, sourceLineLength, destination, mipOffset + texelIndex * blockSize, alphaSetting); }); // Choose an acceleration method. if (ImageEngine.EnableGPUAcceleration) Debugger.Break(); else if (ImageEngine.EnableThreading) Parallel.For(0, destinationTexelCount, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, mipWriter); else for (int i = 0; i < destinationTexelCount; i++) mipWriter(i); return mipOffset + destinationTexelCount * blockSize; }
/// <summary> /// Creates image from mipmap. /// </summary> /// <param name="mip">Mipmap to use as source.</param> public ImageEngineImage(MipMap mip) { MipMaps.Add(mip); }
internal static List<MipMap> LoadDDS(MemoryStream compressed, DDS_Header header, int desiredMaxDimension) { MipMap[] MipMaps = null; int mipWidth = header.Width; int mipHeight = header.Height; ImageEngineFormat format = header.Format; int blockSize = ImageFormats.GetBlockSize(format); int estimatedMips = header.dwMipMapCount; int mipOffset = 128; // Includes header. // TODO: Incorrect mip offset for DX10 if (!EnsureMipInImage(compressed.Length, mipWidth, mipHeight, 4, format, out mipOffset)) // Update number of mips too estimatedMips = 1; if (estimatedMips == 0) estimatedMips = EstimateNumMipMaps(mipWidth, mipHeight); mipOffset = 128; // Needs resetting after checking there's mips in this image. // TESTUNIG if (estimatedMips == 0) estimatedMips = 1; int orig_estimatedMips = estimatedMips; // Store original count for later (uncompressed only I think) // KFreon: Decide which mip to start loading at - going to just load a few mipmaps if asked instead of loading all, then choosing later. That's slow. if (desiredMaxDimension != 0 && estimatedMips > 1) { if (!EnsureMipInImage(compressed.Length, mipWidth, mipHeight, desiredMaxDimension, format, out mipOffset)) // Update number of mips too throw new InvalidDataException($"Requested mipmap does not exist in this image. Top Image Size: {mipWidth}x{mipHeight}, requested mip max dimension: {desiredMaxDimension}."); // Not the first mipmap. if (mipOffset > 128) { double divisor = mipHeight > mipWidth ? mipHeight / desiredMaxDimension : mipWidth / desiredMaxDimension; mipHeight = (int)(mipHeight / divisor); mipWidth = (int)(mipWidth / divisor); if (mipWidth == 0 || mipHeight == 0) // Reset as a dimension is too small to resize { mipHeight = header.Height; mipWidth = header.Width; mipOffset = 128; } else { // Update estimated mips due to changing dimensions. estimatedMips = EstimateNumMipMaps(mipWidth, mipHeight); } } else // The first mipmap mipOffset = 128; } // Move to requested mipmap compressed.Position = mipOffset; // Block Compressed texture chooser. Action<byte[], int, byte[], int, int, bool> DecompressBCBlock = null; switch (format) { case ImageEngineFormat.DDS_DXT1: DecompressBCBlock = DDS_Decoders.DecompressBC1Block; break; case ImageEngineFormat.DDS_DXT2: case ImageEngineFormat.DDS_DXT3: DecompressBCBlock = DDS_Decoders.DecompressBC2Block; break; case ImageEngineFormat.DDS_DXT4: case ImageEngineFormat.DDS_DXT5: DecompressBCBlock = DDS_Decoders.DecompressBC3Block; break; case ImageEngineFormat.DDS_ATI1: DecompressBCBlock = DDS_Decoders.DecompressATI1; break; case ImageEngineFormat.DDS_ATI2_3Dc: DecompressBCBlock = DDS_Decoders.DecompressATI2Block; break; } MipMaps = new MipMap[estimatedMips]; // KFreon: Read mipmaps if (ImageFormats.IsBlockCompressed(format)) // Threading done in the decompression, not here. { for (int m = 0; m < estimatedMips; m++) { // KFreon: If mip is too small, skip out. This happens most often with non-square textures. I think it's because the last mipmap is square instead of the same aspect. if (mipWidth <= 0 || mipHeight <= 0) // Needed cos it doesn't throw when reading past the end for some reason. { break; } MipMap mipmap = ReadCompressedMipMap(compressed, mipWidth, mipHeight, blockSize, mipOffset, (format == ImageEngineFormat.DDS_DXT2 || format == ImageEngineFormat.DDS_DXT4), DecompressBCBlock); MipMaps[m] = mipmap; mipOffset += (int)(mipWidth * mipHeight * (blockSize / 16d)); // Also put the division in brackets cos if the mip dimensions are high enough, the multiplications can exceed int.MaxValue) mipWidth /= 2; mipHeight /= 2; } } else { int startMip = orig_estimatedMips - estimatedMips; // UNCOMPRESSED - Can't really do threading in "decompression" so do it for the mipmaps. var action = new Action<int>(mipIndex => { // Calculate mipOffset and dimensions int offset, width, height; offset = GetMipOffset(mipIndex, format, header.Width, header.Height); double divisor = mipIndex == 0 ? 1d : 2 << (mipIndex - 1); // Divisor represents 2^mipIndex - Math.Pow seems very slow. width = (int)(header.Width / divisor); height = (int)(header.Height / divisor); MipMap mipmap = null; try { mipmap = ReadUncompressedMipMap(compressed, offset, width, height, header.ddspf); } catch (Exception e) { Debug.WriteLine(e.ToString()); } MipMaps[mipIndex] = mipmap; }); if (ImageEngine.EnableThreading) Parallel.For(startMip, orig_estimatedMips, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, action); else for (int i = startMip; i < orig_estimatedMips; i++) action(i); } List<MipMap> mips = new List<MipMap>(MipMaps.Where(t => t != null)); if (mips.Count == 0) throw new InvalidOperationException($"No mipmaps loaded. Estimated mips: {estimatedMips}, mip dimensions: {mipWidth}x{mipHeight}"); return mips; }
private void LoadRawPixels(byte[] rawPixelData, int width, int height, int numSourceChannels) { if (numSourceChannels < 2) throw new ArgumentException("Source rawPixelData must have at least 2 channels."); Width = width; Height = height; MipMaps = new MipMap[1]; MipMap mip = null; if (numSourceChannels == 2) mip = new MipMap(rawPixelData, width, height); else { List<byte> formattedPixelData = new List<byte>(); int ptr = 0; while (ptr < rawPixelData.Length) { // KFreon: Read single pixel formattedPixelData.Add(rawPixelData[ptr++]); formattedPixelData.Add(rawPixelData[ptr++]); // KFreon: Skip unneeded channels ptr += numSourceChannels - 2; } mip = new MipMap(formattedPixelData.ToArray(), width, height); } MipMaps[0] = mip; }
public static TDX LoadFromBitmap(Bitmap asset, string name, D3DFormat format) { TDX tdx = null; if (tdx == null) { tdx = new TDX(); Bitmap b = asset; SquishFlags flags = SquishFlags.kDxt1; tdx.Name = name; tdx.Format = format; switch (tdx.Format) { case D3DFormat.DXT1: flags = SquishFlags.kDxt1; break; case D3DFormat.DXT5: flags = SquishFlags.kDxt5; break; } List <Bitmap> mipBitmaps = GenerateMips(b, b.Width, b.Height); foreach (Bitmap mb in mipBitmaps) { MipMap mip = new MipMap() { Width = mb.Width, Height = mb.Height }; byte[] data = new byte[mb.Width * mb.Height * 4]; byte[] dest = new byte[Squish.Squish.GetStorageRequirements(mb.Width, mb.Height, flags | SquishFlags.kColourIterativeClusterFit | SquishFlags.kWeightColourByAlpha)]; int ii = 0; for (int y = 0; y < mb.Height; y++) { for (int x = 0; x < mb.Width; x++) { Color p = mb.GetPixel(x, y); data[ii + 0] = p.R; data[ii + 1] = p.G; data[ii + 2] = p.B; data[ii + 3] = p.A; ii += 4; } } if (format == D3DFormat.ATI2) { dest = BC5Unorm.Compress(data, (ushort)mb.Width, (ushort)mb.Height, GetMipSize(format, (ushort)mb.Width, (ushort)mb.Height)); } else { Squish.Squish.CompressImage(data, mb.Width, mb.Height, ref dest, flags | SquishFlags.kColourClusterFit); } mip.Data = dest; tdx.MipMaps.Add(mip); } } return(tdx); }
public static string Replace(this Texture2D t2d, Image image, PropertyCollection props, string fileSourcePath = null, string forcedTFCName = null) { string errors = ""; var textureCache = forcedTFCName ?? t2d.GetTopMip().TextureCacheName; string fmt = t2d.TextureFormat; PixelFormat pixelFormat = Image.getPixelFormatType(fmt); t2d.RemoveEmptyMipsFromMipList(); // Not sure what this does? // Remove all but one mip? //if (Export.Game == MEGame.ME1 && texture.mipMapsList.Count < 6) //{ // for (int i = texture.mipMapsList.Count - 1; i != 0; i--) // texture.mipMapsList.RemoveAt(i); //} PixelFormat newPixelFormat = pixelFormat; //Changing Texture Type. Not sure what this is, exactly. //if (mod.markConvert) // newPixelFormat = changeTextureType(pixelFormat, image.pixelFormat, ref package, ref texture); if (!image.checkDDSHaveAllMipmaps() || t2d.Mips.Count > 1 && image.mipMaps.Count() <= 1 || image.pixelFormat != newPixelFormat) //(!mod.markConvert && image.pixelFormat != pixelFormat)) { bool dxt1HasAlpha = false; byte dxt1Threshold = 128; if (pixelFormat == PixelFormat.DXT1 && props.GetProp <EnumProperty>("CompressionSettings") is EnumProperty compressionSettings && compressionSettings.Value.Name == "TC_OneBitAlpha") { dxt1HasAlpha = true; if (image.pixelFormat == PixelFormat.ARGB || image.pixelFormat == PixelFormat.DXT3 || image.pixelFormat == PixelFormat.DXT5) { errors += "Warning: Texture was converted from full alpha to binary alpha." + Environment.NewLine; } } //Generate lower mips image.correctMips(newPixelFormat, dxt1HasAlpha, dxt1Threshold); } if (t2d.Mips.Count == 1) { var topMip = image.mipMaps[0]; image.mipMaps.Clear(); image.mipMaps.Add(topMip); } else { // remove lower mipmaps from source image which not exist in game data //Not sure what this does since we just generated most of these mips for (int t = 0; t < image.mipMaps.Count(); t++) { if (image.mipMaps[t].origWidth <= t2d.Mips[0].width && image.mipMaps[t].origHeight <= t2d.Mips[0].height && t2d.Mips.Count > 1) { if (!t2d.Mips.Exists(m => m.width == image.mipMaps[t].origWidth && m.height == image.mipMaps[t].origHeight)) { image.mipMaps.RemoveAt(t--); } } } // put empty mips if missing for (int t = 0; t < t2d.Mips.Count; t++) { if (t2d.Mips[t].width <= image.mipMaps[0].origWidth && t2d.Mips[t].height <= image.mipMaps[0].origHeight) { if (!image.mipMaps.Exists(m => m.origWidth == t2d.Mips[t].width && m.origHeight == t2d.Mips[t].height)) { MipMap mipmap = new MipMap(t2d.Mips[t].width, t2d.Mips[t].height, pixelFormat); image.mipMaps.Add(mipmap); } } } } //if (!texture.properties.exists("LODGroup")) // texture.properties.setByteValue("LODGroup", "TEXTUREGROUP_Character", "TextureGroup", 1025); List <byte[]> compressedMips = new List <byte[]>(); for (int m = 0; m < image.mipMaps.Count(); m++) { if (t2d.Export.Game == MEGame.ME2) { compressedMips.Add(TextureCompression.CompressTexture(image.mipMaps[m].data, StorageTypes.extLZO)); //LZO } else { compressedMips.Add(TextureCompression.CompressTexture(image.mipMaps[m].data, StorageTypes.extZlib)); //ZLib } } List <Texture2DMipInfo> mipmaps = new List <Texture2DMipInfo>(); for (int m = 0; m < image.mipMaps.Count(); m++) { Texture2DMipInfo mipmap = new Texture2DMipInfo(); mipmap.Export = t2d.Export; mipmap.width = image.mipMaps[m].origWidth; mipmap.height = image.mipMaps[m].origHeight; mipmap.TextureCacheName = textureCache; if (t2d.Mips.Exists(x => x.width == mipmap.width && x.height == mipmap.height)) { var oldMip = t2d.Mips.First(x => x.width == mipmap.width && x.height == mipmap.height); mipmap.storageType = oldMip.storageType; } else { mipmap.storageType = t2d.Mips[0].storageType; if (t2d.Mips.Count() > 1) { //Will implement later. ME3Explorer won't support global relinking, that's MEM's job. //if (Export.Game == MEGame.ME1 && matched.linkToMaster == -1) //{ // if (mipmap.storageType == StorageTypes.pccUnc) // { // mipmap.storageType = StorageTypes.pccLZO; // } //} //else if (Export.Game == MEGame.ME1 && matched.linkToMaster != -1) //{ // if (mipmap.storageType == StorageTypes.pccUnc || // mipmap.storageType == StorageTypes.pccLZO || // mipmap.storageType == StorageTypes.pccZlib) // { // mipmap.storageType = StorageTypes.extLZO; // } //} //else } } //ME2,ME3: Force compression type (not implemented yet) if (t2d.Export.Game == MEGame.ME3) { if (mipmap.storageType == StorageTypes.extLZO) //ME3 LZO -> ZLIB { mipmap.storageType = StorageTypes.extZlib; } if (mipmap.storageType == StorageTypes.pccLZO) //ME3 PCC LZO -> PCCZLIB { mipmap.storageType = StorageTypes.pccZlib; } if (mipmap.storageType == StorageTypes.extUnc) //ME3 Uncomp -> ZLib { mipmap.storageType = StorageTypes.extZlib; } //Leave here for future. WE might need this after dealing with double compression //if (mipmap.storageType == StorageTypes.pccUnc && mipmap.width > 32) //ME3 Uncomp -> ZLib // mipmap.storageType = StorageTypes.pccZlib; if (mipmap.storageType == StorageTypes.pccUnc && m < image.mipMaps.Count() - 6 && textureCache != null) //Moving texture to store externally. { mipmap.storageType = StorageTypes.extZlib; } } else if (t2d.Export.Game == MEGame.ME2) { if (mipmap.storageType == StorageTypes.extZlib) //ME2 ZLib -> LZO { mipmap.storageType = StorageTypes.extLZO; } if (mipmap.storageType == StorageTypes.pccZlib) //M2 ZLib -> LZO { mipmap.storageType = StorageTypes.pccLZO; } if (mipmap.storageType == StorageTypes.extUnc) //ME2 Uncomp -> LZO { mipmap.storageType = StorageTypes.extLZO; } //Leave here for future. We might neable this after dealing with double compression //if (mipmap.storageType == StorageTypes.pccUnc && mipmap.width > 32) //ME2 Uncomp -> LZO // mipmap.storageType = StorageTypes.pccLZO; if (mipmap.storageType == StorageTypes.pccUnc && m < image.mipMaps.Count() - 6 && textureCache != null) //Moving texture to store externally. make sure bottom 6 are pcc stored { mipmap.storageType = StorageTypes.extLZO; } } //Investigate. this has something to do with archive storage types //if (mod.arcTexture != null) //{ // if (mod.arcTexture[m].storageType != mipmap.storageType) // { // mod.arcTexture = null; // } //} mipmap.width = image.mipMaps[m].width; mipmap.height = image.mipMaps[m].height; mipmaps.Add(mipmap); if (t2d.Mips.Count() == 1) { break; } } #region MEM code comments. Should probably leave for reference //if (texture.properties.exists("TextureFileCacheName")) //{ // string archive = texture.properties.getProperty("TextureFileCacheName").valueName; // if (mod.arcTfcDLC && mod.arcTfcName != archive) // mod.arcTexture = null; // if (mod.arcTexture == null) // { // archiveFile = Path.Combine(GameData.MainData, archive + ".tfc"); // if (matched.path.ToLowerInvariant().Contains("\\dlc")) // { // mod.arcTfcDLC = true; // string DLCArchiveFile = Path.Combine(Path.GetDirectoryName(GameData.GamePath + matched.path), archive + ".tfc"); // if (File.Exists(DLCArchiveFile)) // archiveFile = DLCArchiveFile; // else if (!File.Exists(archiveFile)) // { // List<string> files = Directory.GetFiles(GameData.bioGamePath, archive + ".tfc", // SearchOption.AllDirectories).Where(item => item.EndsWith(".tfc", StringComparison.OrdinalIgnoreCase)).ToList(); // if (files.Count == 1) // archiveFile = files[0]; // else if (files.Count == 0) // { // using (FileStream fs = new FileStream(DLCArchiveFile, FileMode.CreateNew, FileAccess.Write)) // { // fs.WriteFromBuffer(texture.properties.getProperty("TFCFileGuid").valueStruct); // } // archiveFile = DLCArchiveFile; // newTfcFile = true; // } // else // throw new Exception("More instnces of TFC file: " + archive + ".tfc"); // } // } // else // { // mod.arcTfcDLC = false; // } // // check if texture fit in old space // for (int mip = 0; mip < image.mipMaps.Count(); mip++) // { // Texture.MipMap testMipmap = new Texture.MipMap(); // testMipmap.width = image.mipMaps[mip].origWidth; // testMipmap.height = image.mipMaps[mip].origHeight; // if (ExistMipmap(testMipmap.width, testMipmap.height)) // testMipmap.storageType = texture.getMipmap(testMipmap.width, testMipmap.height).storageType; // else // { // oldSpace = false; // break; // } // if (testMipmap.storageType == StorageTypes.extZlib || // testMipmap.storageType == StorageTypes.extLZO) // { // Texture.MipMap oldTestMipmap = texture.getMipmap(testMipmap.width, testMipmap.height); // if (mod.cacheCprMipmaps[mip].Length > oldTestMipmap.compressedSize) // { // oldSpace = false; // break; // } // } // if (texture.mipMapsList.Count() == 1) // break; // } // long fileLength = new FileInfo(archiveFile).Length; // if (!oldSpace && fileLength + 0x5000000 > 0x80000000) // { // archiveFile = ""; // foreach (TFCTexture newGuid in guids) // { // archiveFile = Path.Combine(GameData.MainData, newGuid.name + ".tfc"); // if (!File.Exists(archiveFile)) // { // texture.properties.setNameValue("TextureFileCacheName", newGuid.name); // texture.properties.setStructValue("TFCFileGuid", "Guid", newGuid.guid); // using (FileStream fs = new FileStream(archiveFile, FileMode.CreateNew, FileAccess.Write)) // { // fs.WriteFromBuffer(newGuid.guid); // } // newTfcFile = true; // break; // } // else // { // fileLength = new FileInfo(archiveFile).Length; // if (fileLength + 0x5000000 < 0x80000000) // { // texture.properties.setNameValue("TextureFileCacheName", newGuid.name); // texture.properties.setStructValue("TFCFileGuid", "Guid", newGuid.guid); // break; // } // } // archiveFile = ""; // } // if (archiveFile == "") // throw new Exception("No free TFC texture file!"); // } // } // else // { // texture.properties.setNameValue("TextureFileCacheName", mod.arcTfcName); // texture.properties.setStructValue("TFCFileGuid", "Guid", mod.arcTfcGuid); // } //} #endregion int allextmipssize = 0; for (int m = 0; m < image.mipMaps.Count(); m++) { Texture2DMipInfo x = mipmaps[m]; var compsize = image.mipMaps[m].data.Length; if (x.storageType == StorageTypes.extZlib || x.storageType == StorageTypes.extLZO || x.storageType == StorageTypes.extUnc) { allextmipssize += compsize; //compsize on Unc textures is same as LZO/ZLib } } //todo: check to make sure TFC will not be larger than 2GiB Guid tfcGuid = Guid.NewGuid(); //make new guid as storage bool locallyStored = mipmaps[0].storageType == StorageTypes.pccUnc || mipmaps[0].storageType == StorageTypes.pccZlib || mipmaps[0].storageType == StorageTypes.pccLZO; for (int m = 0; m < image.mipMaps.Count(); m++) { Texture2DMipInfo mipmap = mipmaps[m]; //if (mipmap.width > 32) //{ // if (mipmap.storageType == StorageTypes.pccUnc) // { // mipmap.storageType = Export.Game == MEGame.ME2 ? StorageTypes.pccLZO : StorageTypes.pccZlib; // } // if (mipmap.storageType == StorageTypes.extUnc) // { // mipmap.storageType = Export.Game == MEGame.ME2 ? StorageTypes.extLZO : StorageTypes.extZlib; // } //} mipmap.uncompressedSize = image.mipMaps[m].data.Length; if (t2d.Export.Game == MEGame.ME1) { if (mipmap.storageType == StorageTypes.pccLZO || mipmap.storageType == StorageTypes.pccZlib) { mipmap.Mip = compressedMips[m]; mipmap.compressedSize = mipmap.Mip.Length; } else if (mipmap.storageType == StorageTypes.pccUnc) { mipmap.compressedSize = mipmap.uncompressedSize; mipmap.Mip = image.mipMaps[m].data; } else { throw new Exception("Unknown mip storage type!"); } } else { if (mipmap.storageType == StorageTypes.extZlib || mipmap.storageType == StorageTypes.extLZO) { if (compressedMips.Count != image.mipMaps.Count()) { throw new Exception("Amount of compressed mips does not match number of mips of incoming image!"); } mipmap.Mip = compressedMips[m]; mipmap.compressedSize = mipmap.Mip.Length; } if (mipmap.storageType == StorageTypes.pccUnc || mipmap.storageType == StorageTypes.extUnc) { mipmap.compressedSize = mipmap.uncompressedSize; mipmap.Mip = image.mipMaps[m].data; } if (mipmap.storageType == StorageTypes.pccLZO || mipmap.storageType == StorageTypes.pccZlib) { mipmap.Mip = compressedMips[m]; mipmap.compressedSize = mipmap.Mip.Length; } if (mipmap.storageType == StorageTypes.extZlib || mipmap.storageType == StorageTypes.extLZO || mipmap.storageType == StorageTypes.extUnc) { if (!string.IsNullOrEmpty(mipmap.TextureCacheName) && mipmap.Export.Game != MEGame.ME1) { //Check local dir string tfcarchive = mipmap.TextureCacheName + ".tfc"; var localDirectoryTFCPath = Path.Combine(Path.GetDirectoryName(mipmap.Export.FileRef.FilePath), tfcarchive); if (File.Exists(localDirectoryTFCPath)) { try { using (FileStream fs = new FileStream(localDirectoryTFCPath, FileMode.Open, FileAccess.ReadWrite)) { tfcGuid = fs.ReadGuid(); fs.Seek(0, SeekOrigin.End); mipmap.externalOffset = (int)fs.Position; fs.Write(mipmap.Mip, 0, mipmap.compressedSize); } } catch (Exception e) { throw new Exception("Problem appending to TFC file " + tfcarchive + ": " + e.Message); } continue; } //Check game var gameFiles = MELoadedFiles.GetFilesLoadedInGame(mipmap.Export.Game, includeTFCs: true); if (gameFiles.TryGetValue(tfcarchive, out string archiveFile)) { try { using (FileStream fs = new FileStream(archiveFile, FileMode.Open, FileAccess.ReadWrite)) { tfcGuid = fs.ReadGuid(); fs.Seek(0, SeekOrigin.End); mipmap.externalOffset = (int)fs.Position; fs.Write(mipmap.Mip, 0, mipmap.compressedSize); } } catch (Exception e) { throw new Exception("Problem appending to TFC file " + archiveFile + ": " + e.Message); } continue; } //Cache not found. Make new TFC try { using (FileStream fs = new FileStream(localDirectoryTFCPath, FileMode.OpenOrCreate, FileAccess.Write)) { fs.WriteGuid(tfcGuid); mipmap.externalOffset = (int)fs.Position; fs.Write(mipmap.Mip, 0, mipmap.compressedSize); } } catch (Exception e) { throw new Exception("Problem creating new TFC file " + tfcarchive + ": " + e.Message); } continue; } } } mipmaps[m] = mipmap; if (t2d.Mips.Count() == 1) { break; } } t2d.ReplaceMips(mipmaps); //Set properties // The bottom 6 mips are apparently always pcc stored. If there is less than 6 mips, set neverstream to true, which tells game // and toolset to never look into archives for mips. //if (Export.Game == MEGame.ME2 || Export.Game == MEGame.ME3) //{ // if (texture.properties.exists("TextureFileCacheName")) // { // if (texture.mipMapsList.Count < 6) // { // mipmap.storageType = StorageTypes.pccUnc; // texture.properties.setBoolValue("NeverStream", true); // } // else // { // if (Export.Game == MEGame.ME2) // mipmap.storageType = StorageTypes.extLZO; // else // mipmap.storageType = StorageTypes.extZlib; // } // } //} var hasNeverStream = props.GetProp <BoolProperty>("NeverStream") != null; if (locallyStored) { // Rules for default neverstream // 1. Must be Package Stored // 2. Must have at least 6 not empty mips if (mipmaps.Count >= 6) { props.AddOrReplaceProp(new BoolProperty(true, "NeverStream")); } // Side case: NeverStream property was already set, we should respect the value // But won't that always be set here? // Is there a time where we should remove NeverStream? I can't see any logical way // neverstream would be here } if (mipmaps.Count < 6) { props.RemoveNamedProperty("NeverStream"); } if (!locallyStored) { props.AddOrReplaceProp(tfcGuid.ToGuidStructProp("TFCFileGuid")); if (mipmaps[0].storageType == StorageTypes.extLZO || mipmaps[0].storageType == StorageTypes.extUnc || mipmaps[0].storageType == StorageTypes.extZlib) { //Requires texture cache name props.AddOrReplaceProp(new NameProperty(textureCache, "TextureFileCacheName")); } else { //Should not have texture cache name var cacheProp = props.GetProp <NameProperty>("TextureFileCacheName"); if (cacheProp != null) { props.Remove(cacheProp); } } } else { props.RemoveNamedProperty("TFCFileGuid"); } props.AddOrReplaceProp(new IntProperty(t2d.Mips.First().width, "SizeX")); props.AddOrReplaceProp(new IntProperty(t2d.Mips.First().height, "SizeY")); if (t2d.Export.Game < MEGame.ME3 && fileSourcePath != null) { props.AddOrReplaceProp(new StrProperty(fileSourcePath, "SourceFilePath")); props.AddOrReplaceProp(new StrProperty(File.GetLastWriteTimeUtc(fileSourcePath).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), "SourceFileTimestamp")); } var mipTailIdx = props.GetProp <IntProperty>("MipTailBaseIdx"); if (mipTailIdx != null) { mipTailIdx.Value = t2d.Mips.Count - 1; } EndianReader mem = new EndianReader(new MemoryStream()) { Endian = t2d.Export.FileRef.Endian }; props.WriteTo(mem.Writer, t2d.Export.FileRef); mem.Position = 0; var test = PropertyCollection.ReadProps(t2d.Export, mem.BaseStream, "Texture2D", true, true); //do not set properties as this may interfere with some other code. may change later. int propStart = t2d.Export.GetPropertyStart(); var pos = mem.Position; mem.Position = 0; byte[] propData = mem.ToArray(); if (t2d.Export.Game == MEGame.ME3) { t2d.Export.Data = t2d.Export.Data.Take(propStart).Concat(propData).Concat(t2d.SerializeNewData()).ToArray(); } else { var array = t2d.Export.Data.Take(propStart).Concat(propData).ToArray(); var testdata = new MemoryStream(array); var test2 = PropertyCollection.ReadProps(t2d.Export, testdata, "Texture2D", true, true, t2d.Export); //do not set properties as this may interfere with some other code. may change later. //ME2 post-data is this right? t2d.Export.Data = t2d.Export.Data.Take(propStart).Concat(propData).Concat(t2d.SerializeNewData()).ToArray(); } //using (MemoryStream newData = new MemoryStream()) //{ // newData.WriteFromBuffer(texture.properties.toArray()); // newData.WriteFromBuffer(texture.toArray(0, false)); // filled later // package.setExportData(matched.exportID, newData.ToArray()); //} //using (MemoryStream newData = new MemoryStream()) //{ // newData.WriteFromBuffer(texture.properties.toArray()); // newData.WriteFromBuffer(texture.toArray(package.exportsTable[matched.exportID].dataOffset + (uint)newData.Position)); // package.setExportData(matched.exportID, newData.ToArray()); //} //Since this is single replacement, we don't want to relink to master //We want to ensure names are different though, will have to implement into UI //if (Export.Game == MEGame.ME1) //{ // if (matched.linkToMaster == -1) // mod.masterTextures.Add(texture.mipMapsList, entryMap.listIndex); //} //else //{ // if (triggerCacheArc) // { // mod.arcTexture = texture.mipMapsList; // mod.arcTfcGuid = texture.properties.getProperty("TFCFileGuid").valueStruct; // mod.arcTfcName = texture.properties.getProperty("TextureFileCacheName").valueName; // } //} return(errors); }
/// <summary> /// Loads useful information from image stream using Windows 8.1+ codecs. /// </summary> /// <param name="stream">Stream containing entire file. NOT just pixels.</param> /// <param name="decodeWidth">Width to decode as. Aspect ratio unchanged if decodeHeight = 0.</param> /// <param name="decodeHeight">Height to decode as. Aspect ratio unchanged if decodeWidth = 0.</param> /// <param name="isDDS">True = image is a DDS.</param> /// <param name="scale">DOMINANT. DecodeWidth and DecodeHeight ignored if this is > 0. Amount to scale by. Range 0-1.</param> /// <returns>BGRA Pixel Data as stream.</returns> internal static List<MipMap> LoadWithCodecs(Stream stream, int decodeWidth, int decodeHeight, double scale, bool isDDS) { if (isDDS && !ImageEngine.WindowsWICCodecsAvailable) return null; bool alternateDecodeDimensions = decodeHeight != 0 || decodeWidth != 0 || scale != 0; int alternateWidth = decodeWidth; int alternateHeight = decodeHeight; List<MipMap> mipmaps = new List<MipMap>(); if (isDDS) { // KFreon: Attempt to load any mipmaps stream.Seek(0, SeekOrigin.Begin); var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.OnDemand); // Setup alternateDimensions if required if (scale != 0) { alternateHeight = (int)(decoder.Frames[0].Height * scale); alternateWidth = (int)(decoder.Frames[0].Width * scale); } foreach (var mipmap in decoder.Frames) { // KFreon: Skip mipmaps that are too big if asked to load a smaller image if (alternateDecodeDimensions) { if (mipmap.Width > alternateWidth || mipmap.Height > alternateHeight) continue; } mipmaps.Add(new MipMap(mipmap.GetPixelsAsBGRA32(), mipmap.PixelWidth, mipmap.PixelHeight)); } if (mipmaps.Count == 0) { // KFreon: Image has no mips, so resize largest var frame = decoder.Frames[0]; var mip = new MipMap(frame.GetPixelsAsBGRA32(), frame.PixelWidth, frame.PixelHeight); mip = ImageEngine.Resize(mip, scale); mipmaps.Add(mip); } } else { // KFreon: No Mipmaps BitmapImage bmp = AttemptUsingWindowsCodecs(stream, alternateWidth, alternateHeight); if (bmp == null) return null; bmp.Freeze(); mipmaps.Add(new MipMap(bmp.GetPixelsAsBGRA32(), bmp.PixelWidth, bmp.PixelHeight)); } return mipmaps; }
byte[] GetPixels(MipMap mip) { var pixels = mip.Pixels; if (AlphaDisplaySetting == AlphaDisplaySettings.AlphaOnly) // Get a different set of pixels pixels = mip.AlphaOnlyPixels; else if (AlphaDisplaySetting == AlphaDisplaySettings.NoAlpha) // Other two are just an alpha channel different - Bitmap objects are BGRA32, so need to set the alpha to opaque when "don't want it". pixels = mip.RGBAOpaque; return pixels; }
public DDS(D3DFormat format, Bitmap bitmap) { SquishFlags flags = SquishFlags.kDxt1; bool compressed = true; switch (format) { case D3DFormat.DXT1: flags = SquishFlags.kDxt1; break; case D3DFormat.DXT3: flags = SquishFlags.kDxt3; break; case D3DFormat.DXT5: flags = SquishFlags.kDxt5; break; default: compressed = false; break; } Format = format; Width = bitmap.Width; Height = bitmap.Height; MipMap mip = new MipMap { Width = Width, Height = Height }; byte[] data = new byte[mip.Width * mip.Height * 4]; BitmapData bmpdata = bitmap.LockBits(new Rectangle(0, 0, mip.Width, mip.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); Marshal.Copy(bmpdata.Scan0, data, 0, bmpdata.Stride * bmpdata.Height); bitmap.UnlockBits(bmpdata); if (compressed) { for (uint i = 0; i < data.Length - 4; i += 4) { byte r = data[i + 0]; data[i + 0] = data[i + 2]; data[i + 2] = r; } byte[] dest = new byte[Squish.Squish.GetStorageRequirements(mip.Width, mip.Height, flags | SquishFlags.kColourIterativeClusterFit)]; Squish.Squish.CompressImage(data, mip.Width, mip.Height, ref dest, flags | SquishFlags.kColourIterativeClusterFit); mip.Data = dest; } else { mip.Data = data; } MipMaps.Add(mip); }