/// <summary> /// Return a subvolume of this PixelBox. /// </summary> /// <param name="def"> Defines the bounds of the subregion to return </param> /// <returns> A pixel box describing the region and the data in it </returns> /// <remarks> /// This function does not copy any data, it just returns a PixelBox object with a data pointer pointing somewhere inside the data of object. Throws an Exception if def is not fully contained. /// </remarks> public PixelBox GetSubVolume(BasicBox def) { if (Compressed(this.format)) { if (def.Left == left && def.Top == top && def.Front == front && def.Right == right && def.Bottom == bottom && def.Back == back) { // Entire buffer is being queried return(this); } throw new Exception("Cannot return subvolume of compressed PixelBuffer, in PixelBox.GetSubVolume"); } if (!Contains(def)) { throw new Exception("Bounds out of range, in PixelBox.GetSubVolume"); } var elemSize = PixelUtil.GetNumElemBytes(this.format); // Calculate new data origin var rval = new PixelBox(def, this.format, this.data); rval.offset = (((def.Left - left) * elemSize) + ((def.Top - top) * this.rowPitch * elemSize) + ((def.Front - front) * this.slicePitch * elemSize)); rval.rowPitch = this.rowPitch; rval.slicePitch = this.slicePitch; rval.format = this.format; return(rval); }
public static void Convert(PixelBox src, PixelBox dst, IPixelConverter pixelConverter) { { var srcptr = (BufferBase)src.Data.Clone(); var dstptr = (BufferBase)dst.Data.Clone(); var srcSliceSkip = src.SliceSkip; var dstSliceSkip = dst.SliceSkip; var k = src.Right - src.Left; for (var z = src.Front; z < src.Back; z++) { for (var y = src.Top; y < src.Bottom; y++) { for (var x = 0; x < k; x++) { pixelConverter.Convert(srcptr, dstptr, x); } srcptr.Ptr += src.RowPitch * PixelUtil.GetNumElemBytes(src.Format); dstptr.Ptr += dst.RowPitch * PixelUtil.GetNumElemBytes(dst.Format); } srcptr.Ptr += srcSliceSkip; dstptr.Ptr += dstSliceSkip; } } }
public void FlipAroundX() { if (this.buffer == null) { throw new AxiomException("Can not flip an unitialized texture"); } var bytes = PixelUtil.GetNumElemBytes(this.format); this.numMipMaps = 0; // Image operations lose precomputed mipmaps var rowSpan = this.width * bytes; var tempBuffer = new byte[rowSpan * this.height]; int srcOffset = 0, dstOffset = tempBuffer.Length - rowSpan; for (short y = 0; y < this.height; y++) { Array.Copy(this.buffer, srcOffset, tempBuffer, dstOffset, rowSpan); srcOffset += rowSpan; dstOffset -= rowSpan; } Array.Copy(tempBuffer, this.buffer, tempBuffer.Length); }
/// <summary> /// Loads raw image data from memory. /// </summary> /// <param name="stream"> Stream containing the raw image data. </param> /// <param name="width"> Width of this image data (in pixels). </param> /// <param name="height"> Height of this image data (in pixels). </param> /// <param name="depth"> </param> /// <param name="format"> Pixel format used in this texture. </param> /// <returns> A new instance of Image containing the raw data supplied. </returns> public static Image FromRawStream(Stream stream, int width, int height, int depth, PixelFormat format) { // create a new buffer and write the image data directly to it var size = width * height * depth * PixelUtil.GetNumElemBytes(format); var buffer = new byte[size]; stream.Read(buffer, 0, size); return((new Image()).FromDynamicImage(buffer, width, height, depth, format)); }
/// <summary> /// Loads raw image data from a byte array. /// </summary> /// <param name="buffer">Raw image buffer.</param> /// <param name="width">Width of this image data (in pixels).</param> /// <param name="height">Height of this image data (in pixels).</param> /// <param name="format">Pixel format used in this texture.</param> /// <returns>A new instance of Image containing the raw data supplied.</returns> public static Image FromDynamicImage(byte[] buffer, int width, int height, int depth, PixelFormat format) { Image image = new Image(); image.width = width; image.height = height; image.depth = depth; image.format = format; image.size = width * height * depth * PixelUtil.GetNumElemBytes(format); image.SetBuffer(buffer); return(image); }
public void FlipAroundY() { if (this.buffer == null) { throw new AxiomException("Can not flip an unitialized texture"); } this.numMipMaps = 0; // Image operations lose precomputed mipmaps int src = 0, dst = 0; var bytes = PixelUtil.GetNumElemBytes(this.format); var tempBuffer = new byte[this.width * this.height * bytes]; if (bytes > 4 || bytes < 1) { throw new AxiomException("Unknown pixel depth"); } else if (bytes == 3) { for (int y = 0; y < this.height; y++) { dst = ((y * this.width) + this.width - 1) * 3; for (int x = 0; x < this.width; x++) { Array.Copy(this.buffer, src, tempBuffer, dst, bytes); src += 3; dst -= 3; } } } else { for (int y = 0; y < this.height; y++) { dst = ((y * this.width) + this.width - 1); for (int x = 0; x < this.width; x++) { Array.Copy(this.buffer, src++, tempBuffer, dst--, bytes); } } } Array.Copy(tempBuffer, this.buffer, tempBuffer.Length); }
/// <summary> /// Flips this image around the X axis. /// This will invalidate any /// </summary> public void FlipAroundX() { int bytes = PixelUtil.GetNumElemBytes(format); int rowSpan = width * bytes; byte[] tempBuffer = new byte[rowSpan * height]; int srcOffset = 0, dstOffset = tempBuffer.Length - rowSpan; for (short y = 0; y < height; y++) { Array.Copy(buffer, srcOffset, tempBuffer, dstOffset, rowSpan); srcOffset += rowSpan; dstOffset -= rowSpan; } Array.Copy(tempBuffer, buffer, tempBuffer.Length); }
///<summary> /// Convert pixels from one format to another. No dithering or filtering is being done. Converting /// from RGB to luminance takes the R channel. ///</summary> ///<param name="src">PixelBox containing the source pixels, pitches and format</param> ///<param name="dst">PixelBox containing the destination pixels, pitches and format</param> ///<remarks> /// The source and destination boxes must have the same /// dimensions. In case the source and destination format match, a plain copy is done. ///</remarks> public static void BulkPixelConversion(PixelBox src, PixelBox dst) { Debug.Assert(src.Width == dst.Width && src.Height == dst.Height && src.Depth == dst.Depth); // Check for compressed formats, we don't support decompression, compression or recoding if (PixelBox.Compressed(src.Format) || PixelBox.Compressed(dst.Format)) { if (src.Format == dst.Format) { CopyBytes(dst.Data, dst.Offset, src.Data, src.Offset, src.ConsecutiveSize); return; } else { throw new Exception("This method can not be used to compress or decompress images, in PixelBox.BulkPixelConversion"); } } // The easy case if (src.Format == dst.Format) { // Everything consecutive? if (src.Consecutive && dst.Consecutive) { CopyBytes(dst.Data, dst.Offset, src.Data, src.Offset, src.ConsecutiveSize); return; } unsafe { byte *srcBytes = (byte *)src.Data.ToPointer(); byte *dstBytes = (byte *)dst.Data.ToPointer(); byte *srcptr = srcBytes + src.Offset; byte *dstptr = dstBytes + dst.Offset; int srcPixelSize = PixelUtil.GetNumElemBytes(src.Format); int dstPixelSize = PixelUtil.GetNumElemBytes(dst.Format); // Calculate pitches+skips in bytes int srcRowPitchBytes = src.RowPitch * srcPixelSize; //int srcRowSkipBytes = src.RowSkip * srcPixelSize; int srcSliceSkipBytes = src.SliceSkip * srcPixelSize; int dstRowPitchBytes = dst.RowPitch * dstPixelSize; //int dstRowSkipBytes = dst.RowSkip * dstPixelSize; int dstSliceSkipBytes = dst.SliceSkip * dstPixelSize; // Otherwise, copy per row int rowSize = src.Width * srcPixelSize; for (int z = src.Front; z < src.Back; z++) { for (int y = src.Top; y < src.Bottom; y++) { byte *s = srcptr; byte *d = dstptr; for (int i = 0; i < rowSize; i++) { *d++ = *s++; } srcptr += srcRowPitchBytes; dstptr += dstRowPitchBytes; } srcptr += srcSliceSkipBytes; dstptr += dstSliceSkipBytes; } } return; } if (PixelConversionLoops.DoOptimizedConversion(src, dst)) { // If so, good return; } unsafe { byte *srcBytes = (byte *)src.Data.ToPointer(); byte *dstBytes = (byte *)dst.Data.ToPointer(); byte *srcptr = srcBytes + src.Offset; byte *dstptr = dstBytes + dst.Offset; int srcPixelSize = PixelUtil.GetNumElemBytes(src.Format); int dstPixelSize = PixelUtil.GetNumElemBytes(dst.Format); // Calculate pitches+skips in bytes int srcRowSkipBytes = src.RowSkip * srcPixelSize; int srcSliceSkipBytes = src.SliceSkip * srcPixelSize; int dstRowSkipBytes = dst.RowSkip * dstPixelSize; int dstSliceSkipBytes = dst.SliceSkip * dstPixelSize; // The brute force fallback float r, g, b, a; for (int z = src.Front; z < src.Back; z++) { for (int y = src.Top; y < src.Bottom; y++) { for (int x = src.Left; x < src.Right; x++) { UnpackColor(out r, out g, out b, out a, src.Format, srcptr); PackColor(r, g, b, a, dst.Format, dstptr); srcptr += srcPixelSize; dstptr += dstPixelSize; } srcptr += srcRowSkipBytes; dstptr += dstRowSkipBytes; } srcptr += srcSliceSkipBytes; dstptr += dstSliceSkipBytes; } } }
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)); } }
/// <summary> /// </summary> /// <param name="src"> </param> /// <param name="dst"> </param> public void Scale(PixelBox src, PixelBox dst) { var srcelemsize = PixelUtil.GetNumElemBytes(src.Format); var dstelemsize = PixelUtil.GetNumElemBytes(dst.Format); var dstOffset = 0; // sx_48,sy_48,sz_48 represent current position in source // using 16/48-bit fixed precision, incremented by steps var stepx = ((UInt64)src.Width << 48) / (UInt64)dst.Width; var stepy = ((UInt64)src.Height << 48) / (UInt64)dst.Height; var stepz = ((UInt64)src.Depth << 48) / (UInt64)dst.Depth; // temp is 16/16 bit fixed precision, used to adjust a source // coordinate (x, y, or z) backwards by half a pixel so that the // integer bits represent the first sample (eg, sx1) and the // fractional bits are the blend weight of the second sample uint temp; // note: ((stepz>>1) - 1) is an extra half-step increment to adjust // for the center of the destination pixel, not the top-left corner var sz_48 = (stepz >> 1) - 1; for (var z = dst.Front; z < dst.Back; z++, sz_48 += stepz) { temp = (uint)(sz_48 >> 32); temp = (temp > 0x8000) ? temp - 0x8000 : 0; var sz1 = (int)(temp >> 16); var sz2 = System.Math.Min(sz1 + 1, src.Depth - 1); var szf = (temp & 0xFFFF) / 65536f; var sy_48 = (stepy >> 1) - 1; for (var y = dst.Top; y < dst.Bottom; y++, sy_48 += stepy) { temp = (uint)(sy_48 >> 32); temp = (temp > 0x8000) ? temp - 0x8000 : 0; var sy1 = (int)(temp >> 16); // src x #1 var sy2 = System.Math.Min(sy1 + 1, src.Height - 1); // src x #2 var syf = (temp & 0xFFFF) / 65536f; // weight of #2 var sx_48 = (stepx >> 1) - 1; for (var x = dst.Left; x < dst.Right; x++, sx_48 += stepx) { temp = (uint)(sy_48 >> 32); temp = (temp > 0x8000) ? temp - 0x8000 : 0; var sx1 = (int)(temp >> 16); // src x #1 var sx2 = System.Math.Min(sx1 + 1, src.Width - 1); // src x #2 var sxf = (temp & 0xFFFF) / 65536f; // weight of #2 ColorEx x1y1z1 = ColorEx.White, x2y1z1 = ColorEx.White, x1y2z1 = ColorEx.White, x2y2z1 = ColorEx.White; ColorEx x1y1z2 = ColorEx.White, x2y1z2 = ColorEx.White, x1y2z2 = ColorEx.White, x2y2z2 = ColorEx.White; Unpack(ref x1y1z1, sx1, sy1, sz1, src.Format, src.Data, src, srcelemsize); Unpack(ref x2y1z1, sx2, sy1, sz1, src.Format, src.Data, src, srcelemsize); Unpack(ref x1y2z1, sx1, sy2, sz1, src.Format, src.Data, src, srcelemsize); Unpack(ref x2y2z1, sx2, sy2, sz1, src.Format, src.Data, src, srcelemsize); Unpack(ref x1y1z2, sx1, sy1, sz2, src.Format, src.Data, src, srcelemsize); Unpack(ref x2y1z2, sx2, sy1, sz2, src.Format, src.Data, src, srcelemsize); Unpack(ref x1y2z2, sx1, sy2, sz2, src.Format, src.Data, src, srcelemsize); Unpack(ref x2y2z2, sx2, sy2, sz2, src.Format, src.Data, src, srcelemsize); var accum = x1y1z1 * ((1.0f - sxf) * (1.0f - syf) * (1.0f - szf)) + x2y1z1 * (sxf * (1.0f - syf) * (1.0f - szf)) + x1y2z1 * ((1.0f - sxf) * syf * (1.0f - szf)) + x2y2z1 * (sxf * syf * (1.0f - szf)) + x1y1z2 * ((1.0f - sxf) * (1.0f - syf) * szf) + x2y1z2 * (sxf * (1.0f - syf) * szf) + x1y2z2 * ((1.0f - sxf) * syf * szf) + x2y2z2 * (sxf * syf * szf); PixelConverter.PackColor(accum, dst.Format, dst.Data + dstOffset); dstOffset += dstelemsize; } dstOffset += dstelemsize * dst.RowSkip; } dstOffset += dstelemsize * dst.SliceSkip; } }
/// <summary> /// </summary> /// <param name="src"> </param> /// <param name="temp"> </param> public static void Scale(PixelBox src, PixelBox temp) { Scale(src, temp, PixelUtil.GetNumElemBytes(src.Format)); }