/// <summary> /// Constructs a targa image from a targa image and raw data /// </summary> private Targa(TargaHeader header, PfimConfig config, byte[] data, int dataLen) { _config = config; Header = header; Data = data; DataLen = dataLen; }
protected override void Decode(Stream stream, PfimConfig config) { if (config.Decompress) { Data = DataDecode(stream, config); } else { var heightBlockAligned = HeightBlocks; long totalSize = WidthBlocks * CompressedBytesPerBlock * heightBlockAligned; for (int i = 1; i < Header.MipMapCount; i++) { var width = Math.Max((int)(Header.Width / Math.Pow(2, i)), 1); var height = Math.Max((int)(Header.Height / Math.Pow(2, i)), 1); var widthBlocks = CalcBlocks(width); var heightBlocks = CalcBlocks(height); totalSize += widthBlocks * heightBlocks * CompressedBytesPerBlock; } DataLen = (int)totalSize; Data = config.Allocator.Rent((int)totalSize); _compressed = true; Util.Fill(stream, Data, DataLen, config.BufferSize); } }
protected override void Decode(Stream stream, PfimConfig config) { if (config.Decompress) { Data = DataDecode(stream, config); } else { var heightBlockAligned = HeightBlocks; long totalSize = WidthBlocks * CompressedBytesPerBlock * heightBlockAligned; var width = (int)Header.Width; var height = (int)Header.Height; for (int i = 1; i < Header.MipMapCount; i++) { width = (int)Math.Pow(2, Math.Floor(Math.Log(width - 1, 2))); height = (int)Math.Pow(2, Math.Floor(Math.Log(height - 1, 2))); var widthBlocks = Math.Max(DivSize, width) / DivSize; var heightBlocks = Math.Max(DivSize, height) / DivSize; totalSize += widthBlocks * heightBlocks * CompressedBytesPerBlock; } DataLen = (int)totalSize; Data = config.Allocator.Rent((int)totalSize); _compressed = true; Util.Fill(stream, Data, DataLen, config.BufferSize); } }
private static Dds DecodeDds(Stream stream, PfimConfig config, DdsHeader header) { Dds dds; switch (header.PixelFormat.FourCC) { case CompressionAlgorithm.D3DFMT_DXT1: dds = new Dxt1Dds(header, config); break; case CompressionAlgorithm.D3DFMT_DXT2: case CompressionAlgorithm.D3DFMT_DXT4: throw new ArgumentException("Cannot support DXT2 or DXT4"); case CompressionAlgorithm.D3DFMT_DXT3: dds = new Dxt3Dds(header, config); break; case CompressionAlgorithm.D3DFMT_DXT5: dds = new Dxt5Dds(header, config); break; case CompressionAlgorithm.None: dds = new UncompressedDds(header, config); break; case CompressionAlgorithm.DX10: var header10 = new DdsHeaderDxt10(stream); dds = header10.NewDecoder(header, config); dds.Header10 = header10; break; case CompressionAlgorithm.ATI1: case CompressionAlgorithm.BC4U: dds = new Bc4Dds(header, config); break; case CompressionAlgorithm.BC4S: dds = new Bc4sDds(header, config); break; case CompressionAlgorithm.ATI2: case CompressionAlgorithm.BC5U: dds = new Bc5Dds(header, config); break; case CompressionAlgorithm.BC5S: dds = new Bc5sDds(header, config); break; default: throw new ArgumentException($"FourCC: {header.PixelFormat.FourCC} not supported."); } dds.Decode(stream, config); return(dds); }
/// <summary>Fills data starting from the bottom left</summary> public byte[] BottomLeft(Stream str, TargaHeader header, PfimConfig config) { var stride = Util.Stride(header.Width, header.PixelDepthBits); var len = header.Height * stride; var data = config.Allocator.Rent(len); var trueStride = header.PixelDepthBytes * header.Width; InnerBottomLeft(str, config, data, len, stride, trueStride); return(data); }
/// <summary> /// Instantiate a targa header from a given stream. The stream will be parsed /// </summary> public TargaHeader(Stream str, PfimConfig config) { byte[] buf = new byte[MINIMUM_SIZE]; if (str.Read(buf, 0, MINIMUM_SIZE) != MINIMUM_SIZE) { throw new ArgumentException("Stream doesn't have enough data for a .tga", nameof(str)); } DecodeTargaHeader(str, buf, MINIMUM_SIZE, config); }
private static void InnerBottomLeft(Stream str, PfimConfig config, byte[] data, int dataLen, int stride, int trueStride) { if (str is MemoryStream s && s.TryGetBuffer(out var arr)) { int dataIndex = dataLen - stride; for (int i = 0; dataIndex > 0; i++, dataIndex -= stride) { Buffer.BlockCopy(arr.Array, (int)(s.Position + i * trueStride), data, dataIndex, trueStride); } }
private static void InnerBottomLeft(Stream str, PfimConfig config, byte[] data, int dataLen, int stride, int rowBits) { if (str is MemoryStream s && s.TryGetBuffer(out var arr)) { int dataIndex = dataLen - stride; int rowBytes = rowBits / 8; int totalRows = dataLen / rowBytes; for (int i = 0; i < totalRows; i++, dataIndex -= stride) { Buffer.BlockCopy(arr.Array, (int)(s.Position + i * rowBytes), data, dataIndex, rowBytes); } }
/// <summary>Decode data into raw rgb format</summary> private byte[] DataDecode(Stream str, PfimConfig config) { var imageInfo = ImageInfo(); _format = imageInfo.Format; var len = CalcSize(imageInfo); DataLen = len; byte[] data = config.Allocator.Rent(len); var stride = Util.Stride((int)Header.Width, BitsPerPixel); if (Header.Width * BytesPerPixel == stride) { Util.Fill(str, data, len, config.BufferSize); } else { Util.InnerFillUnaligned(str, data, len, (int)Header.Width * BytesPerPixel, stride, config.BufferSize); } // Swap the R and B channels if (imageInfo.Swap) { switch (imageInfo.Format) { case ImageFormat.Rgba32: for (int i = 0; i < len; i += 4) { byte temp = data[i]; data[i] = data[i + 2]; data[i + 2] = temp; } break; case ImageFormat.Rgba16: for (int i = 0; i < len; i += 2) { byte temp = (byte)(data[i] & 0xF); data[i] = (byte)((data[i] & 0xF0) + (data[i + 1] & 0XF)); data[i + 1] = (byte)((data[i + 1] & 0xF0) + temp); } break; default: throw new Exception($"Do not know how to swap {imageInfo.Format}"); } } return(data); }
/// <summary> /// Create image from stream. Pfim will try to detect the format based on several leading bytes /// </summary> public static IImage FromStream(Stream stream, PfimConfig config) { byte[] magic = new byte[4]; if (stream.Read(magic, 0, 4) != 4) { throw new ArgumentException("stream must contain magic header", nameof(stream)); } if (magic[0] == 0x44 && magic[1] == 0x44 && magic[2] == 0x53 && magic[3] == 0x20) { return(Dds.CreateSkipMagic(stream, config)); } throw new Exception("This is not a DDS image file"); }
/// <summary> /// Create image from stream. Pfim will try to detect the format based on several leading bytes /// </summary> public static IImage FromStream(Stream stream, PfimConfig config) { byte[] magic = new byte[4]; if (stream.Read(magic, 0, 4) != 4) { throw new ArgumentException("stream must contain magic header", nameof(stream)); } if (magic[0] == 0x44 && magic[1] == 0x44 && magic[2] == 0x53 && magic[3] == 0x20) { return(Dds.CreateSkipMagic(stream, config)); } return(Targa.CreateWithPartialHeader(stream, config, magic)); }
/// <summary>Constructs an image from a given file</summary> public static IImage FromFile(string path, PfimConfig config) { if (String.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } if (!File.Exists(path)) { throw new FileNotFoundException($"Image does not exist: {Path.GetFullPath(path)}", path); } using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, config.BufferSize)) { return(FromStream(fs, config)); } }
private static Targa DecodeTarga(Stream str, PfimConfig config, TargaHeader header) { var targa = (header.IsCompressed) ? (IDecodeTarga)(new CompressedTarga()) : new UncompressedTarga(); byte[] data; switch (header.Orientation) { case TargaHeader.TargaOrientation.BottomLeft: data = targa.BottomLeft(str, header, config); break; case TargaHeader.TargaOrientation.BottomRight: data = targa.BottomRight(str, header, config); break; case TargaHeader.TargaOrientation.TopRight: data = targa.TopRight(str, header, config); break; case TargaHeader.TargaOrientation.TopLeft: data = targa.TopLeft(str, header, config); break; default: throw new Exception("Targa orientation not recognized"); } var stride = Util.Stride(header.Width, header.PixelDepthBits); var len = header.Height * stride; var result = new Targa(header, config, data, len); if (config.ApplyColorMap) { result.ApplyColorMap(); } return(result); }
/// <summary> /// Instantiates a direct draw surface image from a header, the data, /// and additional info. /// </summary> protected Dds(DdsHeader header, PfimConfig config) { Header = header; _config = config; }
protected abstract void Decode(Stream stream, PfimConfig config);
internal Dds NewDecoder(DdsHeader header, PfimConfig config) { switch (DxgiFormat) { case DxgiFormat.BC1_TYPELESS: case DxgiFormat.BC1_UNORM_SRGB: case DxgiFormat.BC1_UNORM: return(new Dxt1Dds(header, config)); case DxgiFormat.BC2_TYPELESS: case DxgiFormat.BC2_UNORM: case DxgiFormat.BC2_UNORM_SRGB: return(new Dxt3Dds(header, config)); case DxgiFormat.BC3_TYPELESS: case DxgiFormat.BC3_UNORM: case DxgiFormat.BC3_UNORM_SRGB: return(new Dxt5Dds(header, config)); case DxgiFormat.BC4_TYPELESS: case DxgiFormat.BC4_UNORM: return(new Bc4Dds(header, config)); case DxgiFormat.BC4_SNORM: return(new Bc4sDds(header, config)); case DxgiFormat.BC5_TYPELESS: case DxgiFormat.BC5_UNORM: return(new Bc5Dds(header, config)); case DxgiFormat.BC5_SNORM: return(new Bc5sDds(header, config)); case DxgiFormat.BC6H_TYPELESS: case DxgiFormat.BC6H_UF16: case DxgiFormat.BC6H_SF16: return(new Bc6hDds(header, config)); case DxgiFormat.BC7_TYPELESS: case DxgiFormat.BC7_UNORM: case DxgiFormat.BC7_UNORM_SRGB: return(new Bc7Dds(header, config)); case DxgiFormat.R8G8B8A8_TYPELESS: case DxgiFormat.R8G8B8A8_UNORM: case DxgiFormat.R8G8B8A8_UNORM_SRGB: case DxgiFormat.R8G8B8A8_UINT: case DxgiFormat.R8G8B8A8_SNORM: case DxgiFormat.R8G8B8A8_SINT: return(new UncompressedDds(header, config, 32, true)); case DxgiFormat.B8G8R8A8_TYPELESS: case DxgiFormat.B8G8R8A8_UNORM: case DxgiFormat.B8G8R8A8_UNORM_SRGB: return(new UncompressedDds(header, config, 32, false)); case DxgiFormat.UNKNOWN: case DxgiFormat.R32G32B32A32_TYPELESS: case DxgiFormat.R32G32B32A32_FLOAT: case DxgiFormat.R32G32B32A32_UINT: case DxgiFormat.R32G32B32A32_SINT: case DxgiFormat.R32G32B32_TYPELESS: case DxgiFormat.R32G32B32_FLOAT: case DxgiFormat.R32G32B32_UINT: case DxgiFormat.R32G32B32_SINT: case DxgiFormat.R16G16B16A16_TYPELESS: case DxgiFormat.R16G16B16A16_FLOAT: case DxgiFormat.R16G16B16A16_UNORM: case DxgiFormat.R16G16B16A16_UINT: case DxgiFormat.R16G16B16A16_SNORM: case DxgiFormat.R16G16B16A16_SINT: case DxgiFormat.R32G32_TYPELESS: case DxgiFormat.R32G32_FLOAT: case DxgiFormat.R32G32_UINT: case DxgiFormat.R32G32_SINT: case DxgiFormat.R32G8X24_TYPELESS: case DxgiFormat.D32_FLOAT_S8X24_UINT: case DxgiFormat.R32_FLOAT_X8X24_TYPELESS: case DxgiFormat.X32_TYPELESS_G8X24_UINT: case DxgiFormat.R10G10B10A2_TYPELESS: case DxgiFormat.R10G10B10A2_UNORM: case DxgiFormat.R10G10B10A2_UINT: case DxgiFormat.R11G11B10_FLOAT: case DxgiFormat.R16G16_TYPELESS: case DxgiFormat.R16G16_FLOAT: case DxgiFormat.R16G16_UNORM: case DxgiFormat.R16G16_UINT: case DxgiFormat.R16G16_SNORM: case DxgiFormat.R16G16_SINT: case DxgiFormat.R32_TYPELESS: case DxgiFormat.D32_FLOAT: case DxgiFormat.R32_FLOAT: case DxgiFormat.R32_UINT: case DxgiFormat.R32_SINT: case DxgiFormat.R24G8_TYPELESS: case DxgiFormat.D24_UNORM_S8_UINT: case DxgiFormat.R24_UNORM_X8_TYPELESS: case DxgiFormat.X24_TYPELESS_G8_UINT: case DxgiFormat.R8G8_TYPELESS: case DxgiFormat.R8G8_UNORM: case DxgiFormat.R8G8_UINT: case DxgiFormat.R8G8_SNORM: case DxgiFormat.R8G8_SINT: case DxgiFormat.R16_TYPELESS: case DxgiFormat.R16_FLOAT: case DxgiFormat.D16_UNORM: case DxgiFormat.R16_UNORM: case DxgiFormat.R16_UINT: case DxgiFormat.R16_SNORM: case DxgiFormat.R16_SINT: case DxgiFormat.R8_TYPELESS: case DxgiFormat.R8_UNORM: case DxgiFormat.R8_UINT: case DxgiFormat.R8_SNORM: case DxgiFormat.R8_SINT: case DxgiFormat.A8_UNORM: case DxgiFormat.R1_UNORM: case DxgiFormat.R9G9B9E5_SHAREDEXP: case DxgiFormat.R8G8_B8G8_UNORM: case DxgiFormat.G8R8_G8B8_UNORM: case DxgiFormat.B8G8R8X8_UNORM: case DxgiFormat.R10G10B10_XR_BIAS_A2_UNORM: case DxgiFormat.B8G8R8X8_TYPELESS: case DxgiFormat.B8G8R8X8_UNORM_SRGB: case DxgiFormat.NV12: case DxgiFormat.P010: case DxgiFormat.P016: case DxgiFormat.OPAQUE_420: case DxgiFormat.YUY2: case DxgiFormat.Y210: case DxgiFormat.Y216: case DxgiFormat.NV11: case DxgiFormat.AI44: case DxgiFormat.IA44: case DxgiFormat.P8: case DxgiFormat.A8P8: case DxgiFormat.B4G4R4A4_UNORM: case DxgiFormat.P208: case DxgiFormat.V208: case DxgiFormat.V408: default: throw new ArgumentOutOfRangeException(); } }
/// <summary>Decode data into raw rgb format</summary> public byte[] DataDecode(Stream stream, PfimConfig config) { // If we are decoding in memory data, decode stream from that instead of // an intermediate buffer if (stream is MemoryStream s && s.TryGetBuffer(out var arr)) { return(InMemoryDecode(arr.Array, (int)s.Position)); } DataLen = HeightBlocks * DivSize * DeflatedStrideBytes; var totalLen = AllocateMipMaps(); byte[] data = Config.Allocator.Rent(totalLen); var pixelsLeft = totalLen; int dataIndex = 0; int imageIndex = 0; int divSize = DivSize; int stride = DeflatedStrideBytes; int blocksPerStride = WidthBlocks; int indexPixelsLeft = HeightBlocks * DivSize * stride; var stridePixels = StridePixels; int bytesPerStride = BytesPerStride; int bufferSize; byte[] streamBuffer = config.Allocator.Rent(config.BufferSize); try { do { int workingSize; bufferSize = workingSize = stream.Read(streamBuffer, 0, config.BufferSize); int bIndex = 0; while (workingSize > 0 && indexPixelsLeft > 0) { // If there is not enough of the buffer to fill the next // set of 16 square pixels Get the next buffer if (workingSize < bytesPerStride) { bufferSize = workingSize = Util.Translate(stream, streamBuffer, config.BufferSize, bIndex); bIndex = 0; } var origDataIndex = dataIndex; // Now that we have enough pixels to fill a stride (and // this includes the normally 4 pixels below the stride) for (uint i = 0; i < blocksPerStride; i++) { bIndex = Decode(streamBuffer, data, bIndex, (uint)dataIndex, (uint)stridePixels); // Advance to the next block, which is (pixel depth * // divSize) bytes away dataIndex += divSize * PixelDepthBytes; } // Each decoded block is divSize by divSize so pixels left // is Width * multiplied by block height workingSize -= bytesPerStride; var filled = stride * divSize; pixelsLeft -= filled; indexPixelsLeft -= filled; // Jump down to the block that is exactly (divSize - 1) // below the current row we are on dataIndex = origDataIndex + filled; if (indexPixelsLeft <= 0 && imageIndex < MipMaps.Length) { var mip = MipMaps[imageIndex]; var widthBlocks = CalcBlocks(mip.Width); var heightBlocks = CalcBlocks(mip.Height); stridePixels = widthBlocks * DivSize; stride = stridePixels * PixelDepthBytes; blocksPerStride = widthBlocks; indexPixelsLeft = heightBlocks * DivSize * stride; bytesPerStride = widthBlocks * CompressedBytesPerBlock; imageIndex++; } } } while (bufferSize != 0 && pixelsLeft > 0); return(data); } finally { config.Allocator.Return(streamBuffer); } }
/// <summary>Fills data starting from the bottom left</summary> public byte[] BottomLeft(Stream str, TargaHeader header, PfimConfig config) { var stride = Util.Stride(header.Width, header.PixelDepthBits); var dataLen = header.Height * stride; var data = config.Allocator.Rent(dataLen); if (str is MemoryStream s && s.TryGetBuffer(out var arr)) { return(FastPass(data, arr, header, stride, s.Position)); } int dataIndex = dataLen - stride; int bytesPerPixel = header.PixelDepthBytes; int fileBufferIndex = 0; // Calculate the maximum number of bytes potentially needed from the buffer. // If our buffer doesn't have enough to decode the maximum number of bytes, // fetch another batch of bytes from the stream. int maxRead = bytesPerPixel * 128 + 1; byte[] filebuffer = config.Allocator.Rent(config.BufferSize); try { int workingSize = str.Read(filebuffer, 0, config.BufferSize); while (dataIndex >= 0) { int colIndex = 0; do { if (config.BufferSize - fileBufferIndex < maxRead && workingSize == config.BufferSize) { workingSize = Util.Translate(str, filebuffer, config.BufferSize, fileBufferIndex); fileBufferIndex = 0; } bool isRunLength = (filebuffer[fileBufferIndex] & 128) != 0; int count = isRunLength ? bytesPerPixel + 1 : filebuffer[fileBufferIndex] + 1; // If the first bit is on, it means that the next packet is run length encoded if (isRunLength) { RunLength(data, filebuffer, dataIndex, fileBufferIndex, bytesPerPixel); dataIndex += (filebuffer[fileBufferIndex] - 127) * bytesPerPixel; colIndex += filebuffer[fileBufferIndex] - 127; fileBufferIndex += count; } else { int bytcount = count * bytesPerPixel; fileBufferIndex++; Buffer.BlockCopy(filebuffer, fileBufferIndex, data, dataIndex, bytcount); fileBufferIndex += bytcount; colIndex += count; dataIndex += bytcount; } } while (colIndex < header.Width); dataIndex -= bytesPerPixel * header.Width + stride; } return(data); } finally { config.Allocator.Return(filebuffer); } }
internal UncompressedDds(DdsHeader header, PfimConfig config) : base(header, config) { }
/// <summary> /// Same as a regular create except assumes that the magic number has already been consumed /// </summary> internal static IImage CreateSkipMagic(Stream stream, PfimConfig config) { DdsHeader header = new DdsHeader(stream, true); return(DecodeDds(stream, config, header)); }
internal static IImage CreateWithPartialHeader(Stream str, PfimConfig config, byte[] magic) { var header = new TargaHeader(str, magic, 4, config); return(DecodeTarga(str, config, header)); }
private void DecodeTargaHeader(Stream str, byte[] partial, int partialLen, PfimConfig config) { byte[] buf; if (partialLen == MINIMUM_SIZE) { buf = partial; } else if (partialLen > MINIMUM_SIZE) { throw new ArgumentException($"Partial header can't exceed {MINIMUM_SIZE} bytes"); } else { buf = new byte[MINIMUM_SIZE]; var left = MINIMUM_SIZE - partialLen; for (int i = 0; i < partialLen; i++) { buf[i] = partial[i]; } if (str.Read(buf, partialLen, left) != left) { throw new ArgumentException("Stream doesn't have enough data for a .tga", nameof(str)); } } IDLength = buf[0]; HasColorMap = buf[1] == 1; ImageType = (TargaImageType)buf[2]; if (!Enum.IsDefined(typeof(TargaImageType), ImageType)) { throw new ArgumentException("Detected invalid targa image"); } ColorMapOrigin = BitConverter.ToInt16(buf, 3); ColorMapLength = BitConverter.ToInt16(buf, 5); ColorMapDepthBits = buf[7]; XOrigin = BitConverter.ToInt16(buf, 8); YOrigin = BitConverter.ToInt16(buf, 10); Width = BitConverter.ToInt16(buf, 12); Height = BitConverter.ToInt16(buf, 14); PixelDepthBits = buf[16]; // Extract the bits in place 4 and 5 for orientation Orientation = (TargaOrientation)((buf[17] >> 4) & 3); if (IDLength != 0) { var idBuf = new byte[IDLength]; var amount = str.Read(idBuf, 0, IDLength); ImageId = Encoding.Unicode.GetString(idBuf, 0, amount); } if (HasColorMap) { if (ColorMapOrigin > ColorMapLength) { throw new ArgumentException("Color map origin was detected to exceed color map length"); } var mapBytes = ColorMapDepthBytes; ColorMap = new byte[ColorMapLength * mapBytes]; str.Read(ColorMap, ColorMapOrigin * mapBytes, (ColorMapLength - ColorMapOrigin) * mapBytes); } }
internal TargaHeader(Stream str, byte[] partial, int partialLen, PfimConfig config) { DecodeTargaHeader(str, partial, partialLen, config); }
public Dxt5Dds(DdsHeader header, PfimConfig config) : base(header, config) { }
protected override void Decode(Stream stream, PfimConfig config) { Data = DataDecode(stream, config); }
public static Dds Create(byte[] data, PfimConfig config) { return(Create(Util.CreateExposed(data), config)); }
/// <summary>Create a direct draw image from a stream</summary> public static Dds Create(Stream stream, PfimConfig config) { DdsHeader header = new DdsHeader(stream); return(DecodeDds(stream, config, header)); }
protected CompressedDds(DdsHeader header, PfimConfig config) : base(header, config) { }
/// <summary> /// Creates a targa image from a given stream. The type of targa is determined from the /// targa header, which is assumed to be a part of the stream /// </summary> /// <param name="str">Stream to read the targa image from</param> /// <returns>A targa image</returns> public static Targa Create(Stream str, PfimConfig config) { var header = new TargaHeader(str, config); return(DecodeTarga(str, config, header)); }
internal UncompressedDds(DdsHeader header, PfimConfig config, uint bitsPerPixel, bool rgbSwapped) : base(header, config) { _bitsPerPixel = bitsPerPixel; _rgbSwapped = rgbSwapped; }