(ded4a3e0a) v0.9.0.7
This commit is contained in:
+489
@@ -0,0 +1,489 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Graphics.PackedVector;
|
||||
using FreeImageAPI;
|
||||
|
||||
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
|
||||
{
|
||||
public static class GraphicsUtil
|
||||
{
|
||||
internal static BitmapContent Resize(this BitmapContent bitmap, int newWidth, int newHeight)
|
||||
{
|
||||
BitmapContent src = bitmap;
|
||||
SurfaceFormat format;
|
||||
src.TryGetFormat(out format);
|
||||
if (format != SurfaceFormat.Vector4)
|
||||
{
|
||||
var v4 = new PixelBitmapContent<Vector4>(src.Width, src.Height);
|
||||
BitmapContent.Copy(src, v4);
|
||||
src = v4;
|
||||
}
|
||||
|
||||
// Convert to FreeImage bitmap
|
||||
var bytes = src.GetPixelData();
|
||||
var fi = FreeImage.ConvertFromRawBits(bytes, FREE_IMAGE_TYPE.FIT_RGBAF, src.Width, src.Height, SurfaceFormat.Vector4.GetSize() * src.Width, 128, 0, 0, 0, true);
|
||||
|
||||
// Resize
|
||||
var newfi = FreeImage.Rescale(fi, newWidth, newHeight, FREE_IMAGE_FILTER.FILTER_BICUBIC);
|
||||
FreeImage.UnloadEx(ref fi);
|
||||
|
||||
// Convert back to PixelBitmapContent<Vector4>
|
||||
src = new PixelBitmapContent<Vector4>(newWidth, newHeight);
|
||||
bytes = new byte[SurfaceFormat.Vector4.GetSize() * newWidth * newHeight];
|
||||
FreeImage.ConvertToRawBits(bytes, newfi, SurfaceFormat.Vector4.GetSize() * newWidth, 128, 0, 0, 0, true);
|
||||
src.SetPixelData(bytes);
|
||||
FreeImage.UnloadEx(ref newfi);
|
||||
// Convert back to source type if required
|
||||
if (format != SurfaceFormat.Vector4)
|
||||
{
|
||||
var s = (BitmapContent)Activator.CreateInstance(bitmap.GetType(), new object[] { newWidth, newHeight });
|
||||
BitmapContent.Copy(src, s);
|
||||
src = s;
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
public static void BGRAtoRGBA(byte[] data)
|
||||
{
|
||||
for (var x = 0; x < data.Length; x += 4)
|
||||
{
|
||||
data[x] ^= data[x + 2];
|
||||
data[x + 2] ^= data[x];
|
||||
data[x] ^= data[x + 2];
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPowerOfTwo(int x)
|
||||
{
|
||||
return (x & (x - 1)) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next power of two. Returns same value if already is PoT.
|
||||
/// </summary>
|
||||
public static int GetNextPowerOfTwo(int value)
|
||||
{
|
||||
if (IsPowerOfTwo(value))
|
||||
return value;
|
||||
|
||||
var nearestPower = 1;
|
||||
while (nearestPower < value)
|
||||
nearestPower = nearestPower << 1;
|
||||
|
||||
return nearestPower;
|
||||
}
|
||||
|
||||
enum AlphaRange
|
||||
{
|
||||
/// <summary>
|
||||
/// Pixel data has no alpha values below 1.0.
|
||||
/// </summary>
|
||||
Opaque,
|
||||
|
||||
/// <summary>
|
||||
/// Pixel data contains alpha values that are either 0.0 or 1.0.
|
||||
/// </summary>
|
||||
Cutout,
|
||||
|
||||
/// <summary>
|
||||
/// Pixel data contains alpha values that cover the full range of 0.0 to 1.0.
|
||||
/// </summary>
|
||||
Full,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alpha range in a set of pixels.
|
||||
/// </summary>
|
||||
/// <param name="bitmap">A bitmap of full-colour floating point pixel data in RGBA or BGRA order.</param>
|
||||
/// <returns>A member of the AlphaRange enum to describe the range of alpha in the pixel data.</returns>
|
||||
static AlphaRange CalculateAlphaRange(BitmapContent bitmap)
|
||||
{
|
||||
AlphaRange result = AlphaRange.Opaque;
|
||||
var pixelBitmap = bitmap as PixelBitmapContent<Vector4>;
|
||||
if (pixelBitmap != null)
|
||||
{
|
||||
for (int y = 0; y < pixelBitmap.Height; ++y)
|
||||
{
|
||||
var row = pixelBitmap.GetRow(y);
|
||||
foreach (var pixel in row)
|
||||
{
|
||||
if (pixel.W == 0.0)
|
||||
result = AlphaRange.Cutout;
|
||||
else if (pixel.W < 1.0)
|
||||
return AlphaRange.Full;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CompressPvrtc(ContentProcessorContext context, TextureContent content, bool isSpriteFont)
|
||||
{
|
||||
// If sharp alpha is required (for a font texture page), use 16-bit color instead of PVR
|
||||
if (isSpriteFont)
|
||||
{
|
||||
CompressColor16Bit(context, content);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate number of mip levels
|
||||
var width = content.Faces[0][0].Height;
|
||||
var height = content.Faces[0][0].Width;
|
||||
|
||||
if (!IsPowerOfTwo(width) || !IsPowerOfTwo(height) || (width != height))
|
||||
{
|
||||
context.Logger.LogWarning(null, content.Identity, "PVR compression requires width and height to be powers of two and equal. Falling back to 16-bit color.");
|
||||
CompressColor16Bit(context, content);
|
||||
return;
|
||||
}
|
||||
|
||||
var face = content.Faces[0][0];
|
||||
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
if (alphaRange == AlphaRange.Opaque)
|
||||
content.ConvertBitmapType(typeof(PvrtcRgb4BitmapContent));
|
||||
else
|
||||
content.ConvertBitmapType(typeof(PvrtcRgba4BitmapContent));
|
||||
}
|
||||
|
||||
public static void CompressDxt(ContentProcessorContext context, TextureContent content, bool isSpriteFont)
|
||||
{
|
||||
var face = content.Faces[0][0];
|
||||
|
||||
if (context.TargetProfile == GraphicsProfile.Reach)
|
||||
{
|
||||
if (!IsPowerOfTwo(face.Width) || !IsPowerOfTwo(face.Height))
|
||||
throw new PipelineException("DXT compression requires width and height must be powers of two in Reach graphics profile.");
|
||||
}
|
||||
|
||||
// Test the alpha channel to figure out if we have alpha.
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
// TODO: This isn't quite right.
|
||||
//
|
||||
// We should be generating DXT1 textures for cutout alpha
|
||||
// as DXT1 supports 1bit alpha and it uses less memory.
|
||||
//
|
||||
// XNA never generated DXT3 for textures... it always picked
|
||||
// between DXT1 for cutouts and DXT5 for fractional alpha.
|
||||
//
|
||||
// DXT3 however can produce better results for high frequency
|
||||
// alpha like a chain link fence where is DXT5 is better for
|
||||
// low frequency alpha like clouds. I don't know how we can
|
||||
// pick the right thing in this case without a hint.
|
||||
//
|
||||
if (isSpriteFont)
|
||||
CompressFontDXT3(content);
|
||||
else if (alphaRange == AlphaRange.Opaque)
|
||||
content.ConvertBitmapType(typeof(Dxt1BitmapContent));
|
||||
else if (alphaRange == AlphaRange.Cutout)
|
||||
content.ConvertBitmapType(typeof(Dxt3BitmapContent));
|
||||
else
|
||||
content.ConvertBitmapType(typeof(Dxt5BitmapContent));
|
||||
}
|
||||
|
||||
static public void CompressAti(ContentProcessorContext context, TextureContent content, bool isSpriteFont)
|
||||
{
|
||||
// If sharp alpha is required (for a font texture page), use 16-bit color instead of PVR
|
||||
if (isSpriteFont)
|
||||
{
|
||||
CompressColor16Bit(context, content);
|
||||
return;
|
||||
}
|
||||
|
||||
var face = content.Faces[0][0];
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
if (alphaRange == AlphaRange.Full)
|
||||
content.ConvertBitmapType(typeof(AtcExplicitBitmapContent));
|
||||
else
|
||||
content.ConvertBitmapType(typeof(AtcInterpolatedBitmapContent));
|
||||
}
|
||||
|
||||
static public void CompressEtc1(ContentProcessorContext context, TextureContent content, bool isSpriteFont)
|
||||
{
|
||||
// If sharp alpha is required (for a font texture page), use 16-bit color instead of PVR
|
||||
if (isSpriteFont)
|
||||
{
|
||||
CompressColor16Bit(context, content);
|
||||
return;
|
||||
}
|
||||
|
||||
var face = content.Faces[0][0];
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
// Use BGRA4444 for textures with non-opaque alpha values
|
||||
if (alphaRange != AlphaRange.Opaque)
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgra4444>));
|
||||
else
|
||||
{
|
||||
// PVR SGX does not handle non-POT ETC1 textures.
|
||||
// https://code.google.com/p/libgdx/issues/detail?id=1310
|
||||
// Since we already enforce POT for PVR and DXT in Reach, we will also enforce POT for ETC1
|
||||
if (!IsPowerOfTwo(face.Width) || !IsPowerOfTwo(face.Height))
|
||||
{
|
||||
context.Logger.LogWarning(null, content.Identity, "ETC1 compression requires width and height to be powers of two due to hardware restrictions on some devices. Falling back to BGR565.");
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgr565>));
|
||||
}
|
||||
else
|
||||
content.ConvertBitmapType(typeof(Etc1BitmapContent));
|
||||
}
|
||||
}
|
||||
|
||||
static public void CompressColor16Bit(ContentProcessorContext context, TextureContent content)
|
||||
{
|
||||
var face = content.Faces[0][0];
|
||||
var alphaRange = CalculateAlphaRange(face);
|
||||
|
||||
if (alphaRange == AlphaRange.Opaque)
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgr565>));
|
||||
else if (alphaRange == AlphaRange.Cutout)
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgra5551>));
|
||||
else
|
||||
content.ConvertBitmapType(typeof(PixelBitmapContent<Bgra4444>));
|
||||
}
|
||||
|
||||
// Compress the greyscale font texture page using a specially-formulated DXT3 mode
|
||||
static public unsafe void CompressFontDXT3(TextureContent content)
|
||||
{
|
||||
if (content.Faces.Count > 1)
|
||||
throw new PipelineException("Font textures should only have one face");
|
||||
|
||||
var block = new Vector4[16];
|
||||
for (int i = 0; i < content.Faces[0].Count; ++i)
|
||||
{
|
||||
var face = content.Faces[0][i];
|
||||
var xBlocks = (face.Width + 3) / 4;
|
||||
var yBlocks = (face.Height + 3) / 4;
|
||||
var dxt3Size = xBlocks * yBlocks * 16;
|
||||
var buffer = new byte[dxt3Size];
|
||||
|
||||
var bytes = face.GetPixelData();
|
||||
fixed (byte* b = bytes)
|
||||
{
|
||||
Vector4* colors = (Vector4*)b;
|
||||
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
while (h < (face.Height & ~3))
|
||||
{
|
||||
w = 0;
|
||||
x = 0;
|
||||
|
||||
var h0 = h * face.Width;
|
||||
var h1 = h0 + face.Width;
|
||||
var h2 = h1 + face.Width;
|
||||
var h3 = h2 + face.Width;
|
||||
|
||||
while (w < (face.Width & ~3))
|
||||
{
|
||||
block[0] = colors[w + h0];
|
||||
block[1] = colors[w + h0 + 1];
|
||||
block[2] = colors[w + h0 + 2];
|
||||
block[3] = colors[w + h0 + 3];
|
||||
block[4] = colors[w + h1];
|
||||
block[5] = colors[w + h1 + 1];
|
||||
block[6] = colors[w + h1 + 2];
|
||||
block[7] = colors[w + h1 + 3];
|
||||
block[8] = colors[w + h2];
|
||||
block[9] = colors[w + h2 + 1];
|
||||
block[10] = colors[w + h2 + 2];
|
||||
block[11] = colors[w + h2 + 3];
|
||||
block[12] = colors[w + h3];
|
||||
block[13] = colors[w + h3 + 1];
|
||||
block[14] = colors[w + h3 + 2];
|
||||
block[15] = colors[w + h3 + 3];
|
||||
|
||||
int offset = (x + y * xBlocks) * 16;
|
||||
CompressFontDXT3Block(block, buffer, offset);
|
||||
|
||||
w += 4;
|
||||
++x;
|
||||
}
|
||||
|
||||
// Do partial block at end of row
|
||||
if (w < face.Width)
|
||||
{
|
||||
var cols = face.Width - w;
|
||||
Array.Clear(block, 0, 16);
|
||||
for (int r = 0; r < 4; ++r)
|
||||
{
|
||||
h0 = (h + r) * face.Width;
|
||||
for (int c = 0; c < cols; ++c)
|
||||
block[(r * 4) + c] = colors[w + h0 + c];
|
||||
}
|
||||
|
||||
int offset = (x + y * xBlocks) * 16;
|
||||
CompressFontDXT3Block(block, buffer, offset);
|
||||
}
|
||||
|
||||
h += 4;
|
||||
++y;
|
||||
}
|
||||
|
||||
// Do last partial row
|
||||
if (h < face.Height)
|
||||
{
|
||||
var rows = face.Height - h;
|
||||
w = 0;
|
||||
x = 0;
|
||||
while (w < (face.Width & ~3))
|
||||
{
|
||||
Array.Clear(block, 0, 16);
|
||||
for (int r = 0; r < rows; ++r)
|
||||
{
|
||||
var h0 = (h + r) * face.Width;
|
||||
block[(r * 4) + 0] = colors[w + h0 + 0];
|
||||
block[(r * 4) + 1] = colors[w + h0 + 1];
|
||||
block[(r * 4) + 2] = colors[w + h0 + 2];
|
||||
block[(r * 4) + 3] = colors[w + h0 + 3];
|
||||
}
|
||||
|
||||
int offset = (x + y * xBlocks) * 16;
|
||||
CompressFontDXT3Block(block, buffer, offset);
|
||||
|
||||
w += 4;
|
||||
++x;
|
||||
}
|
||||
|
||||
// Do last partial block
|
||||
if (w < face.Width)
|
||||
{
|
||||
var cols = face.Width - w;
|
||||
Array.Clear(block, 0, 16);
|
||||
for (int r = 0; r < rows; ++r)
|
||||
{
|
||||
var h0 = (h + r) * face.Width;
|
||||
for (int c = 0; c < cols; ++c)
|
||||
block[(r * 4) + c] = colors[w + h0 + c];
|
||||
}
|
||||
|
||||
int offset = (x + y * xBlocks) * 16;
|
||||
CompressFontDXT3Block(block, buffer, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dxt3 = new Dxt3BitmapContent(face.Width, face.Height);
|
||||
dxt3.SetPixelData(buffer);
|
||||
content.Faces[0][i] = dxt3;
|
||||
}
|
||||
}
|
||||
|
||||
// Maps a 2-bit greyscale to the index required for DXT3
|
||||
// 00 = color0
|
||||
// 01 = color1
|
||||
// 10 = 2/3 * color0 + 1/3 * color1
|
||||
// 11 = 1/3 * color0 + 2/3 * color1
|
||||
static byte[] dxt3Map = new byte[] { 0, 2, 3, 1 };
|
||||
|
||||
// Compress a single 4x4 block from colors into buffer at the given offset
|
||||
static void CompressFontDXT3Block(Vector4[] colors, byte[] buffer, int offset)
|
||||
{
|
||||
// Get the alpha into a 0-15 range
|
||||
int a0 = (int)(colors[0].W * 15.0);
|
||||
int a1 = (int)(colors[1].W * 15.0);
|
||||
int a2 = (int)(colors[2].W * 15.0);
|
||||
int a3 = (int)(colors[3].W * 15.0);
|
||||
int a4 = (int)(colors[4].W * 15.0);
|
||||
int a5 = (int)(colors[5].W * 15.0);
|
||||
int a6 = (int)(colors[6].W * 15.0);
|
||||
int a7 = (int)(colors[7].W * 15.0);
|
||||
int a8 = (int)(colors[8].W * 15.0);
|
||||
int a9 = (int)(colors[9].W * 15.0);
|
||||
int a10 = (int)(colors[10].W * 15.0);
|
||||
int a11 = (int)(colors[11].W * 15.0);
|
||||
int a12 = (int)(colors[12].W * 15.0);
|
||||
int a13 = (int)(colors[13].W * 15.0);
|
||||
int a14 = (int)(colors[14].W * 15.0);
|
||||
int a15 = (int)(colors[15].W * 15.0);
|
||||
|
||||
// Duplicate the top two bits into the bottom two bits so we get one of four values: b0000, b0101, b1010, b1111
|
||||
a0 = (a0 & 0xC) | (a0 >> 2);
|
||||
a1 = (a1 & 0xC) | (a1 >> 2);
|
||||
a2 = (a2 & 0xC) | (a2 >> 2);
|
||||
a3 = (a3 & 0xC) | (a3 >> 2);
|
||||
a4 = (a4 & 0xC) | (a4 >> 2);
|
||||
a5 = (a5 & 0xC) | (a5 >> 2);
|
||||
a6 = (a6 & 0xC) | (a6 >> 2);
|
||||
a7 = (a7 & 0xC) | (a7 >> 2);
|
||||
a8 = (a8 & 0xC) | (a8 >> 2);
|
||||
a9 = (a9 & 0xC) | (a9 >> 2);
|
||||
a10 = (a10 & 0xC) | (a10 >> 2);
|
||||
a11 = (a11 & 0xC) | (a11 >> 2);
|
||||
a12 = (a12 & 0xC) | (a12 >> 2);
|
||||
a13 = (a13 & 0xC) | (a13 >> 2);
|
||||
a14 = (a14 & 0xC) | (a14 >> 2);
|
||||
a15 = (a15 & 0xC) | (a15 >> 2);
|
||||
|
||||
// 4-bit alpha
|
||||
buffer[offset + 0] = (byte)((a1 << 4) | a0);
|
||||
buffer[offset + 1] = (byte)((a3 << 4) | a2);
|
||||
buffer[offset + 2] = (byte)((a5 << 4) | a4);
|
||||
buffer[offset + 3] = (byte)((a7 << 4) | a6);
|
||||
buffer[offset + 4] = (byte)((a9 << 4) | a8);
|
||||
buffer[offset + 5] = (byte)((a11 << 4) | a10);
|
||||
buffer[offset + 6] = (byte)((a13 << 4) | a12);
|
||||
buffer[offset + 7] = (byte)((a15 << 4) | a14);
|
||||
|
||||
// color0 (transparent)
|
||||
buffer[offset + 8] = 0;
|
||||
buffer[offset + 9] = 0;
|
||||
|
||||
// color1 (white)
|
||||
buffer[offset + 10] = 255;
|
||||
buffer[offset + 11] = 255;
|
||||
|
||||
// Get the red (to be used for green and blue channels as well) into a 0-15 range
|
||||
a0 = (int)(colors[0].X * 15.0);
|
||||
a1 = (int)(colors[1].X * 15.0);
|
||||
a2 = (int)(colors[2].X * 15.0);
|
||||
a3 = (int)(colors[3].X * 15.0);
|
||||
a4 = (int)(colors[4].X * 15.0);
|
||||
a5 = (int)(colors[5].X * 15.0);
|
||||
a6 = (int)(colors[6].X * 15.0);
|
||||
a7 = (int)(colors[7].X * 15.0);
|
||||
a8 = (int)(colors[8].X * 15.0);
|
||||
a9 = (int)(colors[9].X * 15.0);
|
||||
a10 = (int)(colors[10].X * 15.0);
|
||||
a11 = (int)(colors[11].X * 15.0);
|
||||
a12 = (int)(colors[12].X * 15.0);
|
||||
a13 = (int)(colors[13].X * 15.0);
|
||||
a14 = (int)(colors[14].X * 15.0);
|
||||
a15 = (int)(colors[15].X * 15.0);
|
||||
|
||||
// Duplicate the top two bits into the bottom two bits so we get one of four values: b0000, b0101, b1010, b1111
|
||||
a0 = (a0 & 0xC) | (a0 >> 2);
|
||||
a1 = (a1 & 0xC) | (a1 >> 2);
|
||||
a2 = (a2 & 0xC) | (a2 >> 2);
|
||||
a3 = (a3 & 0xC) | (a3 >> 2);
|
||||
a4 = (a4 & 0xC) | (a4 >> 2);
|
||||
a5 = (a5 & 0xC) | (a5 >> 2);
|
||||
a6 = (a6 & 0xC) | (a6 >> 2);
|
||||
a7 = (a7 & 0xC) | (a7 >> 2);
|
||||
a8 = (a8 & 0xC) | (a8 >> 2);
|
||||
a9 = (a9 & 0xC) | (a9 >> 2);
|
||||
a10 = (a10 & 0xC) | (a10 >> 2);
|
||||
a11 = (a11 & 0xC) | (a11 >> 2);
|
||||
a12 = (a12 & 0xC) | (a12 >> 2);
|
||||
a13 = (a13 & 0xC) | (a13 >> 2);
|
||||
a14 = (a14 & 0xC) | (a14 >> 2);
|
||||
a15 = (a15 & 0xC) | (a15 >> 2);
|
||||
|
||||
// Color indices (00 = color0, 01 = color1, 10 = 2/3 * color0 + 1/3 * color1, 11 = 1/3 * color0 + 2/3 * color1)
|
||||
buffer[offset + 12] = (byte)((dxt3Map[a3 >> 2] << 6) | (dxt3Map[a2 >> 2] << 4) | (dxt3Map[a1 >> 2] << 2) | dxt3Map[a0 >> 2]);
|
||||
buffer[offset + 13] = (byte)((dxt3Map[a7 >> 2] << 6) | (dxt3Map[a6 >> 2] << 4) | (dxt3Map[a5 >> 2] << 2) | dxt3Map[a4 >> 2]);
|
||||
buffer[offset + 14] = (byte)((dxt3Map[a11 >> 2] << 6) | (dxt3Map[a10 >> 2] << 4) | (dxt3Map[a9 >> 2] << 2) | dxt3Map[a8 >> 2]);
|
||||
buffer[offset + 15] = (byte)((dxt3Map[a15 >> 2] << 6) | (dxt3Map[a14 >> 2] << 4) | (dxt3Map[a13 >> 2] << 2) | dxt3Map[a12 >> 2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user