Files
LuaCsForBarotraumaEP/Libraries/MonoGame.Framework/Src/MonoGame.Framework.Content.Pipeline/Graphics/GraphicsUtil.cs
2019-06-25 16:00:44 +03:00

490 lines
20 KiB
C#

// 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]);
}
}
}