public static CalculateSize ( int mipmaps, int faces, int width, int height, int depth, PixelFormat format ) : int | ||
mipmaps | int | |
faces | int | |
width | int | |
height | int | |
depth | int | |
format | PixelFormat | |
return | int |
public override Codec.DecodeResult Decode(Stream input) { using (var br = new BinaryReader(input)) { var numFaces = 1; // Assume one face until we know otherwise var imgData = new ImageData(); // Read the PVRTC header var header = PVRTCTexHeader.Read(br); // Get the file type identifier var pvrTag = header.pvrTag; if (this.PVR_MAGIC != pvrTag) { throw new AxiomException("This is not a PVR file!"); } // Get format flags var flags = header.flags; using (var wrap = BufferBase.Wrap(flags, 2)) { _flipEndian(wrap, sizeof(int)); } var formatFlags = flags & PVR_TEXTURE_FLAG_TYPE_MASK; var bitmaskAlpha = header.bitmaskAlpha; using (var wrap = BufferBase.Wrap(bitmaskAlpha, 2)) { _flipEndian(wrap, sizeof(int)); } if (formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypePVRTC_2) { if (formatFlags == kPVRTextureFlagTypePVRTC_4) { imgData.format = bitmaskAlpha != 0 ? PixelFormat.PVRTC_RGBA4 : PixelFormat.PVRTC_RGB4; } else if (formatFlags == kPVRTextureFlagTypePVRTC_2) { imgData.format = bitmaskAlpha != 0 ? PixelFormat.PVRTC_RGBA2 : PixelFormat.PVRTC_RGB2; } imgData.depth = 1; imgData.width = header.width; imgData.height = header.height; imgData.numMipMaps = header.numMipmaps; // PVRTC is a compressed format imgData.flags |= ImageFlags.Compressed; } // Calculate total size from number of mipmaps, faces and size imgData.size = Image.CalculateSize(imgData.numMipMaps, numFaces, imgData.width, imgData.height, imgData.depth, imgData.format); // Now deal with the data var dest = br.ReadBytes(imgData.size); return(new DecodeResult(new MemoryStream(dest), imgData)); } }
public override void EncodeToFile(Stream input, string outFileName, Codec.CodecData data) { // Unwrap codecDataPtr - data is cleaned by calling function var imgData = (ImageData)data; // Check size for cube map faces var isCubeMap = imgData.size == Image.CalculateSize(imgData.numMipMaps, 6, imgData.width, imgData.height, imgData.depth, imgData.format); // Establish texture attributes var isVolume = imgData.depth > 1; var isFloat32r = imgData.format == PixelFormat.FLOAT32_R; var hasAlpha = false; var notImplemented = false; var notImplementedString = string.Empty; // Check for all the 'not implemented' conditions if (imgData.numMipMaps != 0) { // No mip map functionality yet notImplemented = true; notImplementedString += " mipmaps"; } if ((isVolume == true) && (imgData.width != imgData.height)) { // Square textures only notImplemented = true; notImplementedString += " non square textures"; } var size = 1; while (size < imgData.width) { size <<= 1; } if (size != imgData.width) { // Power two textures only notImplemented = true; notImplementedString += " non power two textures"; } switch (imgData.format) { case PixelFormat.A8R8G8B8: case PixelFormat.X8R8G8B8: case PixelFormat.R8G8B8: case PixelFormat.FLOAT32_R: break; default: // No crazy FOURCC or 565 et al. file formats at this stage notImplemented = true; notImplementedString = " unsupported pixel format"; break; } // Except if any 'not implemented' conditions were met if (notImplemented) { throw new NotImplementedException(string.Format("DDS encoding for{0} not supported", notImplementedString)); } else { // Build header and write to disk // Variables for some DDS header flags var ddsHeaderFlags = 0; var ddsHeaderRgbBits = 0; var ddsHeaderSizeOrPitch = 0; var ddsHeaderCaps1 = 0; var ddsHeaderCaps2 = 0; var ddsMagic = this.DDS_MAGIC; // Initalise the header flags ddsHeaderFlags = (isVolume) ? DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_DEPTH | DDSD_PIXELFORMAT : DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT; // Initalise the rgbBits flags switch (imgData.format) { case PixelFormat.A8R8G8B8: ddsHeaderRgbBits = 8 * 4; hasAlpha = true; break; case PixelFormat.X8R8G8B8: ddsHeaderRgbBits = 8 * 4; break; case PixelFormat.R8G8B8: ddsHeaderRgbBits = 8 * 3; break; case PixelFormat.FLOAT32_R: ddsHeaderRgbBits = 32; break; default: ddsHeaderRgbBits = 0; break; } ; // Initalise the SizeOrPitch flags (power two textures for now) ddsHeaderSizeOrPitch = ddsHeaderRgbBits * imgData.width; // Initalise the caps flags ddsHeaderCaps1 = (isVolume || isCubeMap) ? DDSCAPS_COMPLEX | DDSCAPS_TEXTURE : DDSCAPS_TEXTURE; if (isVolume) { ddsHeaderCaps2 = DDSCAPS2_VOLUME; } else if (isCubeMap) { ddsHeaderCaps2 = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX | DDSCAPS2_CUBEMAP_NEGATIVEX | DDSCAPS2_CUBEMAP_POSITIVEY | DDSCAPS2_CUBEMAP_NEGATIVEY | DDSCAPS2_CUBEMAP_POSITIVEZ | DDSCAPS2_CUBEMAP_NEGATIVEZ; } // Populate the DDS header information var ddsHeader = new DDSHeader(); ddsHeader.size = DDS_HEADER_SIZE; ddsHeader.flags = ddsHeaderFlags; ddsHeader.width = imgData.width; ddsHeader.height = imgData.height; ddsHeader.depth = isVolume ? imgData.depth : 0; ddsHeader.depth = isCubeMap ? 6 : ddsHeader.depth; ddsHeader.mipMapCount = 0; ddsHeader.sizeOrPitch = ddsHeaderSizeOrPitch; ddsHeader.reserved1 = new int[11]; ddsHeader.reserved2 = 0; ddsHeader.pixelFormat.size = DDS_PIXELFORMAT_SIZE; ddsHeader.pixelFormat.flags = (hasAlpha) ? DDPF_RGB | DDPF_ALPHAPIXELS : DDPF_RGB; ddsHeader.pixelFormat.flags = (isFloat32r) ? DDPF_FOURCC : ddsHeader.pixelFormat.flags; ddsHeader.pixelFormat.fourCC = (isFloat32r) ? D3DFMT_R32F : 0; ddsHeader.pixelFormat.rgbBits = ddsHeaderRgbBits; ddsHeader.pixelFormat.alphaMask = hasAlpha ? unchecked ((int)0xFF000000) : 0x00000000; ddsHeader.pixelFormat.alphaMask = isFloat32r ? 0x00000000 : ddsHeader.pixelFormat.alphaMask; ddsHeader.pixelFormat.redMask = isFloat32r ? unchecked ((int)0xFFFFFFFF) : 0x00FF0000; ddsHeader.pixelFormat.greenMask = isFloat32r ? 0x00000000 : 0x0000FF00; ddsHeader.pixelFormat.blueMask = isFloat32r ? 0x00000000 : 0x000000FF; ddsHeader.caps.caps1 = ddsHeaderCaps1; ddsHeader.caps.caps2 = ddsHeaderCaps2; ddsHeader.caps.reserved[0] = 0; ddsHeader.caps.reserved[1] = 0; // Swap endian using (var wrap = BufferBase.Wrap(ddsMagic, 2)) { _flipEndian(wrap, sizeof(uint), 1); } using (var wrap = BufferBase.Wrap(ddsHeader, Memory.SizeOf(typeof(DDSHeader)))) { _flipEndian(wrap, 4, Memory.SizeOf(typeof(DDSHeader)) / 4); } // Write the file using (var br = new BinaryWriter(File.Open(outFileName, FileMode.OpenOrCreate, FileAccess.Write))) { br.Write(ddsMagic); ddsHeader.Write(br); // XXX flipEndian on each pixel chunk written unless isFloat32r ? var inputData = new byte[(int)input.Length]; input.Read(inputData, 0, inputData.Length); br.Write(inputData); } } }
public override Codec.DecodeResult Decode(Stream input) { using (var br = new BinaryReader(input)) { // Read 4 character code var fileType = br.ReadInt32(); using (var wrap = BufferBase.Wrap(fileType, 2)) { _flipEndian(wrap, sizeof(uint), 1); } if (FOURCC('D', 'D', 'S', ' ') != fileType) { throw new AxiomException("This is not a DDS file!"); } // Read header in full var header = DDSHeader.Read(br); // Endian flip if required, all 32-bit values using (var wrap = BufferBase.Wrap(header, Memory.SizeOf(typeof(DDSHeader)))) { _flipEndian(wrap, 4, Memory.SizeOf(typeof(DDSHeader)) / 4); } // Check some sizes if (header.size != DDS_HEADER_SIZE) { throw new AxiomException("DDS header size mismatch!"); } if (header.pixelFormat.size != DDS_PIXELFORMAT_SIZE) { throw new AxiomException("DDS header size mismatch!"); } var imgData = new ImageData(); imgData.depth = 1; // (deal with volume later) imgData.width = header.width; imgData.height = header.height; var numFaces = 1; // assume one face until we know otherwise if ((header.caps.caps1 & DDSCAPS_MIPMAP) != 0) { imgData.numMipMaps = header.mipMapCount - 1; } else { imgData.numMipMaps = 0; } imgData.flags = 0; var decompressDXT = false; // Figure out basic image type if ((header.caps.caps2 & DDSCAPS2_CUBEMAP) != 0) { imgData.flags |= ImageFlags.CubeMap; numFaces = 6; } else if ((header.caps.caps2 & DDSCAPS2_VOLUME) != 0) { imgData.flags |= ImageFlags.Volume; imgData.depth = header.depth; } // Pixel format var sourceFormat = PixelFormat.Unknown; if ((header.pixelFormat.flags & DDPF_FOURCC) != 0) { sourceFormat = _convertFourCCFormat(header.pixelFormat.fourCC); } else { sourceFormat = _convertPixelFormat(header.pixelFormat.rgbBits, header.pixelFormat.redMask, header.pixelFormat.greenMask, header.pixelFormat.blueMask, (header.pixelFormat.flags & DDPF_ALPHAPIXELS) != 0 ? header.pixelFormat.alphaMask : 0); } if (PixelUtil.IsCompressed(sourceFormat)) { if (!Root.Instance.RenderSystem.Capabilities.HasCapability(Capabilities.TextureCompressionDXT)) { // We'll need to decompress decompressDXT = true; // Convert format switch (sourceFormat) { case PixelFormat.DXT1: // source can be either 565 or 5551 depending on whether alpha present // unfortunately you have to read a block to figure out which // Note that we upgrade to 32-bit pixel formats here, even // though the source is 16-bit; this is because the interpolated // values will benefit from the 32-bit results, and the source // from which the 16-bit samples are calculated may have been // 32-bit so can benefit from this. var block = DXTColorBlock.Read(br); using (var wrap = BufferBase.Wrap(block.colour_0, sizeof(ushort))) { _flipEndian(wrap, sizeof(ushort), 1); } using (var wrap = BufferBase.Wrap(block.colour_1, sizeof(ushort))) { _flipEndian(wrap, sizeof(ushort), 1); } // skip back since we'll need to read this again br.BaseStream.Seek(0 - (long)Memory.SizeOf(typeof(DXTColorBlock)), SeekOrigin.Current); // colour_0 <= colour_1 means transparency in DXT1 if (block.colour_0 <= block.colour_1) { imgData.format = PixelFormat.BYTE_RGBA; } else { imgData.format = PixelFormat.BYTE_RGB; } break; case PixelFormat.DXT2: case PixelFormat.DXT3: case PixelFormat.DXT4: case PixelFormat.DXT5: // full alpha present, formats vary only in encoding imgData.format = PixelFormat.BYTE_RGBA; break; default: // all other cases need no special format handling break; } } else { // Use original format imgData.format = sourceFormat; // Keep DXT data compressed imgData.flags |= ImageFlags.Compressed; } } else // not compressed { // Don't test against DDPF_RGB since greyscale DDS doesn't set this // just derive any other kind of format imgData.format = sourceFormat; } // Calculate total size from number of mipmaps, faces and size imgData.size = Image.CalculateSize(imgData.numMipMaps, numFaces, imgData.width, imgData.height, imgData.depth, imgData.format); // Now deal with the data var dest = new byte[imgData.size]; var destBuffer = BufferBase.Wrap(dest); // all mips for a face, then each face for (var i = 0; i < numFaces; ++i) { var width = imgData.width; var height = imgData.height; var depth = imgData.depth; for (var mip = 0; mip <= imgData.numMipMaps; ++mip) { var dstPitch = width * PixelUtil.GetNumElemBytes(imgData.format); if (PixelUtil.IsCompressed(sourceFormat)) { // Compressed data if (decompressDXT) { DXTColorBlock col; DXTInterpolatedAlphaBlock iAlpha; DXTExplicitAlphaBlock eAlpha; // 4x4 block of decompressed colour var tempColours = new ColorEx[16]; var destBpp = PixelUtil.GetNumElemBytes(imgData.format); var sx = Utility.Min(width, 4); var sy = Utility.Min(height, 4); var destPitchMinus4 = dstPitch - destBpp * sx; // slices are done individually for (var z = 0; z < depth; ++z) { // 4x4 blocks in x/y for (var y = 0; y < height; y += 4) { for (var x = 0; x < width; x += 4) { if (sourceFormat == PixelFormat.DXT2 || sourceFormat == PixelFormat.DXT3) { // explicit alpha eAlpha = DXTExplicitAlphaBlock.Read(br); using (var wrap = BufferBase.Wrap(eAlpha.alphaRow, eAlpha.alphaRow.Length * sizeof(ushort))) { _flipEndian(wrap, sizeof(ushort), 4); } _unpackDXTAlpha(eAlpha, tempColours); } else if (sourceFormat == PixelFormat.DXT4 || sourceFormat == PixelFormat.DXT5) { // interpolated alpha iAlpha = DXTInterpolatedAlphaBlock.Read(br); using (var wrap = BufferBase.Wrap(iAlpha.alpha_0, 1)) { _flipEndian(wrap, sizeof(ushort), 1); } using (var wrap = BufferBase.Wrap(iAlpha.alpha_1, 1)) { _flipEndian(wrap, sizeof(ushort), 1); } _unpackDXTAlpha(iAlpha, tempColours); } // always read colour col = DXTColorBlock.Read(br); using (var wrap = BufferBase.Wrap(col.colour_0, sizeof(ushort))) { _flipEndian(wrap, sizeof(ushort), 1); } using (var wrap = BufferBase.Wrap(col.colour_1, sizeof(ushort))) { _flipEndian(wrap, sizeof(ushort), 1); } _unpackDXTColor(sourceFormat, col, tempColours); // write 4x4 block to uncompressed version for (var by = 0; by < sy; ++by) { for (var bx = 0; bx < sx; ++bx) { PixelConverter.PackColor(tempColours[by * 4 + bx], imgData.format, destBuffer); destBuffer += destBpp; } // advance to next row destBuffer += destPitchMinus4; } // next block. Our dest pointer is 4 lines down // from where it started if (x + 4 >= width) { // Jump back to the start of the line destBuffer += -destPitchMinus4; } else { // Jump back up 4 rows and 4 pixels to the // right to be at the next block to the right destBuffer += -(dstPitch * sy + destBpp * sx); } } } } } else { // load directly // DDS format lies! sizeOrPitch is not always set for DXT!! var dxtSize = PixelUtil.GetMemorySize(width, height, depth, imgData.format); using (var src = BufferBase.Wrap(br.ReadBytes(dxtSize))) { Memory.Copy(src, destBuffer, dxtSize); } destBuffer += dxtSize; } } else { // Final data - trim incoming pitch int srcPitch; if ((header.flags & DDSD_PITCH) != 0) { srcPitch = header.sizeOrPitch / Utility.Max(1, mip * 2); } else { // assume same as final pitch srcPitch = dstPitch; } Contract.Requires(dstPitch <= srcPitch); var srcAdvance = (long)(srcPitch - dstPitch); for (var z = 0; z < imgData.depth; ++z) { for (var y = 0; y < imgData.height; ++y) { using (var src = BufferBase.Wrap(br.ReadBytes(dstPitch))) { Memory.Copy(src, destBuffer, dstPitch); } if (srcAdvance > 0) { br.BaseStream.Seek(srcAdvance, SeekOrigin.Current); } destBuffer += dstPitch; } } } // Next mip if (width != 1) { width /= 2; } if (height != 1) { height /= 2; } if (depth != 1) { depth /= 2; } } } destBuffer.Dispose(); return(new DecodeResult(new MemoryStream(dest), imgData)); } }
public override object Decode(Stream input, Stream output, params object[] args) { ImageData data = new ImageData(); int imageID; int format, bytesPerPixel, imageType; // create and bind a new image Il.ilGenImages(1, out imageID); Il.ilBindImage(imageID); // Put it right side up Il.ilEnable(Il.IL_ORIGIN_SET); Il.ilSetInteger(Il.IL_ORIGIN_MODE, Il.IL_ORIGIN_UPPER_LEFT); // Keep DXTC(compressed) data if present Il.ilSetInteger(Il.IL_KEEP_DXTC_DATA, Il.IL_TRUE); // create a temp buffer and write the stream into it byte[] buffer = new byte[input.Length]; input.Read(buffer, 0, buffer.Length); // load the data into DevIL Il.ilLoadL(this.ILType, buffer, buffer.Length); // check for an error int ilError = Il.ilGetError(); if (ilError != Il.IL_NO_ERROR) { throw new AxiomException("Error while decoding image data: '{0}'", Ilu.iluErrorString(ilError)); } format = Il.ilGetInteger(Il.IL_IMAGE_FORMAT); imageType = Il.ilGetInteger(Il.IL_IMAGE_TYPE); //bytesPerPixel = Math.Max(Il.ilGetInteger(Il.IL_IMAGE_BPC), // Il.ilGetInteger(Il.IL_IMAGE_BYTES_PER_PIXEL)); // Convert image if ImageType is incompatible with us (double or long) if (imageType != Il.IL_BYTE && imageType != Il.IL_UNSIGNED_BYTE && imageType != Il.IL_FLOAT && imageType != Il.IL_UNSIGNED_SHORT && imageType != Il.IL_SHORT) { Il.ilConvertImage(format, Il.IL_FLOAT); imageType = Il.IL_FLOAT; } // Converted paletted images if (format == Il.IL_COLOUR_INDEX) { Il.ilConvertImage(Il.IL_BGRA, Il.IL_UNSIGNED_BYTE); format = Il.IL_BGRA; imageType = Il.IL_UNSIGNED_BYTE; } bytesPerPixel = Il.ilGetInteger(Il.IL_IMAGE_BYTES_PER_PIXEL); // populate the image data data.format = ILUtil.ConvertFromILFormat(format, imageType); data.width = Il.ilGetInteger(Il.IL_IMAGE_WIDTH); data.height = Il.ilGetInteger(Il.IL_IMAGE_HEIGHT); data.depth = Il.ilGetInteger(Il.IL_IMAGE_DEPTH); data.numMipMaps = Il.ilGetInteger(Il.IL_NUM_MIPMAPS); data.flags = 0; if (data.format == PixelFormat.Unknown) { Il.ilDeleteImages(1, ref imageID); throw new AxiomException("Unsupported DevIL format: ImageFormat = {0:x} ImageType = {1:x}", format, imageType); } // Check for cubemap // int cubeflags = Il.ilGetInteger(Il.IL_IMAGE_CUBEFLAGS); int numFaces = Il.ilGetInteger(Il.IL_NUM_IMAGES) + 1; if (numFaces == 6) { data.flags |= ImageFlags.CubeMap; } else { numFaces = 1; // Support only 1 or 6 face images for now } // Keep DXT data (if present at all and the GPU supports it) int dxtFormat = Il.ilGetInteger(Il.IL_DXTC_DATA_FORMAT); if (dxtFormat != Il.IL_DXT_NO_COMP && Root.Instance.RenderSystem.Caps.CheckCap(Axiom.Graphics.Capabilities.TextureCompressionDXT)) { data.format = ILUtil.ConvertFromILFormat(dxtFormat, imageType); data.flags |= ImageFlags.Compressed; // Validate that this devil version saves DXT mipmaps if (data.numMipMaps > 0) { Il.ilBindImage(imageID); Il.ilActiveMipmap(1); if (Il.ilGetInteger(Il.IL_DXTC_DATA_FORMAT) != dxtFormat) { data.numMipMaps = 0; LogManager.Instance.Write("Warning: Custom mipmaps for compressed image were ignored because they are not loaded by this DevIL version"); } } } // Calculate total size from number of mipmaps, faces and size data.size = Image.CalculateSize(data.numMipMaps, numFaces, data.width, data.height, data.depth, data.format); // set up buffer for the decoded data buffer = new byte[data.size]; // Pin the buffer, so we can use our PixelBox methods on it GCHandle bufGCHandle = new GCHandle(); bufGCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); IntPtr bufPtr = bufGCHandle.AddrOfPinnedObject(); int offset = 0; // Dimensions of current mipmap int width = data.width; int height = data.height; int depth = data.depth; // Transfer data for (int mip = 0; mip <= data.numMipMaps; ++mip) { for (int i = 0; i < numFaces; ++i) { Il.ilBindImage(imageID); if (numFaces > 1) { Il.ilActiveImage(i); } if (data.numMipMaps > 0) { Il.ilActiveMipmap(mip); } /// Size of this face int imageSize = PixelUtil.GetMemorySize(width, height, depth, data.format); if ((data.flags & ImageFlags.Compressed) != 0) { // Compare DXT size returned by DevIL with our idea of the compressed size if (imageSize == Il.ilGetDXTCData(IntPtr.Zero, 0, dxtFormat)) { // Retrieve data from DevIL byte[] tmpBuffer = new byte[imageSize]; Il.ilGetDXTCData(tmpBuffer, imageSize, dxtFormat); // Copy the data into our output buffer Array.Copy(tmpBuffer, 0, buffer, offset, tmpBuffer.Length); } else { LogManager.Instance.Write("Warning: compressed image size mismatch, devilsize={0} oursize={1}", Il.ilGetDXTCData(IntPtr.Zero, 0, dxtFormat), imageSize); } } else { /// Retrieve data from DevIL PixelBox dst = new PixelBox(width, height, depth, data.format, bufPtr); dst.Offset = offset; ILUtil.ToAxiom(dst); } offset += imageSize; } /// Next mip if (width != 1) { width /= 2; } if (height != 1) { height /= 2; } if (depth != 1) { depth /= 2; } } // Restore IL state Il.ilDisable(Il.IL_ORIGIN_SET); Il.ilDisable(Il.IL_FORMAT_SET); // we won't be needing this anymore Il.ilDeleteImages(1, ref imageID); output.Write(buffer, 0, buffer.Length); // Free the buffer we allocated for the conversion. // I used bufPtr to store my data while I converted it. // I need to free it here. This invalidates bufPtr. // My data has already been copied to output. if (bufGCHandle.IsAllocated) { bufGCHandle.Free(); } return(data); }