Skip to content

Commit

Permalink
initial GenerateMipmaps command
Browse files Browse the repository at this point in the history
  • Loading branch information
Pedro413 committed May 19, 2024
1 parent 91a7fb5 commit a436586
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 0 deletions.
97 changes: 97 additions & 0 deletions TagTool/Bitmaps/Utils/BitmapDownsampler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TagTool.Common;

namespace TagTool.Bitmaps.Utils
{
public static class BitmapDownsampler
{
public static byte[] Downsample4x4BlockRgba(BitmapSampler sampler)
{
sampler.SetFilteringMode(BitmapSampler.FilteringMode.Bilinear); // must use bilinear

int width = sampler.GetWidth() / 2;
int height = sampler.GetHeight() / 2;
byte[] result = new byte[width * height * 4];

float pixelWidth = 1.0f / width;
float pixelHeight = 1.0f / height;
float halfPxWidth = pixelWidth * 0.5f;
float halfPxHeight = pixelHeight * 0.5f;

int rowBytes = width * 4;
int rowOffset = 0;

for (int i = 0; i < height; i++)
{
float yCoord = (pixelHeight * i) + halfPxHeight;

for (int j = 0; j < width; j++)
{
float xCoord = (pixelWidth * j) + halfPxWidth;

// 4x4 block
var sample0 = sampler.Sample2dOffsetF(xCoord, yCoord, -1, -1);
var sample1 = sampler.Sample2dOffsetF(xCoord, yCoord, 1, -1);
var sample2 = sampler.Sample2dOffsetF(xCoord, yCoord, -1, 1);
var sample3 = sampler.Sample2dOffsetF(xCoord, yCoord, 1, 1);
RealVector4d sample = (sample0 + sample1 + sample2 + sample3) / 4.0f;
sample *= 255; // to byte

int streamOffset = rowOffset + j * 4;
result[streamOffset] = (byte)sample.I;
result[streamOffset + 1] = (byte)sample.J;
result[streamOffset + 2] = (byte)sample.K;
result[streamOffset + 3] = (byte)sample.W;
}

rowOffset += rowBytes;
}

return result;
}

// try not to use this
public static byte[] DownsampleBilinearRgba(BitmapSampler sampler)
{
sampler.SetFilteringMode(BitmapSampler.FilteringMode.Bilinear); // must use bilinear

int width = sampler.GetWidth() / 2;
int height = sampler.GetHeight() / 2;
byte[] result = new byte[width * height * 4];

float pixelWidth = 1.0f / width;
float pixelHeight = 1.0f / height;
float halfPxWidth = pixelWidth * 0.5f;
float halfPxHeight = pixelHeight * 0.5f;

int rowBytes = width * 4;
int rowOffset = 0;

for (int i = 0; i < height; i++)
{
float yCoord = (pixelHeight * i) + halfPxHeight;

for (int j = 0; j < width; j++)
{
float xCoord = (pixelWidth * j) + halfPxWidth;

var sample = sampler.Sample2d(xCoord, yCoord);

int streamOffset = rowOffset + j * 4;
result[streamOffset] = sample.R;
result[streamOffset + 1] = sample.G;
result[streamOffset + 2] = sample.B;
result[streamOffset + 3] = sample.A;
}

rowOffset += rowBytes;
}

return result;
}
}
}
27 changes: 27 additions & 0 deletions TagTool/Bitmaps/Utils/BitmapSampler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using TagTool.Common;

namespace TagTool.Bitmaps.Utils
{
Expand Down Expand Up @@ -46,6 +47,32 @@ public RGBAColor Sample2d(float x, float y)
}
}

public RGBAColor Sample2dOffset(float x, float y, int offsetX, int offsetY)
{
float pixelWidth = 1.0f / GetWidth();
float pixelHeight = 1.0f / GetHeight();
float pxOffsetX = pixelWidth * offsetX;
float pxOffsetY = pixelHeight * offsetY;

x += pxOffsetX;
y += pxOffsetY;

switch (FilterMode)
{
case FilteringMode.Bilinear:
return Sample2dBilinear(x, y);
case FilteringMode.Point:
default:
return Sample2dPoint(x, y);
}
}

public RealVector4d Sample2dOffsetF(float x, float y, int offsetX, int offsetY)
{
RGBAColor sample = Sample2dOffset(x, y, offsetX, offsetY);
return new RealVector4d(sample.R / 255.0f, sample.G / 255.0f, sample.B / 255.0f, sample.A / 255.0f);
}

private float PixelWidth() => 1.0f / Width;
private float PixelHeight() => 1.0f / Height;
private int UvToIndex(float x, float y) => 4 * ((int)(y * Height) * Width + (int)(x * Width));
Expand Down
1 change: 1 addition & 0 deletions TagTool/Commands/Bitmaps/BitmapContextFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static void Populate(CommandContext commandContext, GameCache cache, Cach
commandContext.AddCommand(new ExtractBitmapCommand(cache, tag, bitmap));
commandContext.AddCommand(new ImportBitmapCommand(cache, tag, bitmap));
commandContext.AddCommand(new ReencodeBitmapCommand(cache, tag, bitmap));
commandContext.AddCommand(new GenerateMipmapsCommand(cache, bitmap));
}
}
}
152 changes: 152 additions & 0 deletions TagTool/Commands/Bitmaps/GenerateMipmapsCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.IO;
using TagTool.Cache;
using TagTool.Bitmaps;
using TagTool.Tags;
using TagTool.Tags.Definitions;
using TagTool.Commands.Common;
using TagTool.Bitmaps.Utils;

namespace TagTool.Commands.Bitmaps
{
public class GenerateMipmapsCommand : Command
{
private GameCache Cache { get; }
private Bitmap Bitmap { get; }

public GenerateMipmapsCommand(GameCache cache, Bitmap bitmap)
: base(false,

"GenerateMipmaps",
"Generates mipmaps for the specified image. Removes any existing mips from the chain",

"GenerateMipmaps [image index]",
"Generates mipmaps for the specified image. Removes any existing mips from the chain")
{
Cache = cache;
Bitmap = bitmap;
}

public override object Execute(List<string> args)
{
List<TagResourceReference> newHardwareTextures = new List<TagResourceReference>();
if (args.Count > 0)
{
if (!int.TryParse(args[0], out int imageIndex) || imageIndex >= Bitmap.Images.Count)
{
return new TagToolError(CommandError.ArgInvalid, $"\"{args[1]}\" is not a valid image index");
}
else
{
newHardwareTextures.Add(DownsampleBitmapImage(imageIndex));
}
}
else
{
for (int i = 0; i < Bitmap.Images.Count; i++)
{
newHardwareTextures.Add(DownsampleBitmapImage(i));
}
}

Bitmap.InterleavedHardwareTextures = new List<TagResourceReference>();
Bitmap.HardwareTextures = newHardwareTextures;
return true;
}

private TagResourceReference DownsampleBitmapImage(int imageIndex)
{
BitmapFormat currentFormat = Bitmap.Images[imageIndex].Format;
var destFormat = currentFormat;

var resourceReference = Bitmap.HardwareTextures[imageIndex];
var resourceDefinition = Cache.ResourceCache.GetBitmapTextureInteropResource(resourceReference);

byte[] primaryData = resourceDefinition?.Texture.Definition.PrimaryResourceData.Data;
byte[] secondaryData = resourceDefinition?.Texture.Definition.SecondaryResourceData.Data;

byte[] bitmapData;
using (var result = new MemoryStream())
{
for (int layerIndex = 0; layerIndex < Bitmap.Images[imageIndex].Depth; layerIndex++)
{
var pixelDataOffset = BitmapUtilsPC.GetTextureOffset(Bitmap.Images[imageIndex], 0);
var pixelDataSize = BitmapUtilsPC.GetMipmapPixelDataSize(Bitmap.Images[imageIndex], 0);

byte[] pixelData = new byte[pixelDataSize];
if (resourceDefinition.Texture.Definition.Bitmap.HighResInSecondaryResource > 0 || primaryData == null)
{
Array.Copy(secondaryData, pixelDataOffset, pixelData, 0, pixelData.Length);
}
else
{
if (secondaryData != null)
pixelDataOffset -= secondaryData.Length;
Array.Copy(primaryData, pixelDataOffset, pixelData, 0, pixelDataSize);
}

result.Write(pixelData, 0, pixelData.Length);
}

bitmapData = result.ToArray();
}

byte[] rawData = BitmapDecoder.DecodeBitmap(bitmapData, Bitmap.Images[imageIndex].Format, Bitmap.Images[imageIndex].Width, Bitmap.Images[imageIndex].Height);

int lowestDimension = Math.Min(Bitmap.Images[imageIndex].Width, Bitmap.Images[imageIndex].Height);
sbyte mipCount = (sbyte)Math.Log(lowestDimension, 2);
BaseBitmap resultBitmap = new BaseBitmap(Bitmap.Images[imageIndex]);

// this is unoptimized af, todo: clean it up

byte[] mipData = new byte[rawData.Length];
Buffer.BlockCopy(rawData, 0, mipData, 0, rawData.Length);
int mipWidth = Bitmap.Images[imageIndex].Width, mipHeight = Bitmap.Images[imageIndex].Height;

byte[] resultData = BitmapDecoder.EncodeBitmap(rawData, destFormat, resultBitmap.Width, resultBitmap.Height);

int blockSize = BitmapFormatUtils.GetBlockSize(destFormat); // note: this can be -1

for (int i = 0; i < mipCount; i++)
{
if (((mipWidth / 2) * (mipHeight / 2)) < blockSize) // limit mips for compressed formats
{
mipCount = (sbyte)i; // already +1 from loop
break;
}

BitmapSampler sampler = new BitmapSampler(mipData, mipWidth, mipHeight, BitmapSampler.FilteringMode.Bilinear, 0);
mipData = BitmapDownsampler.Downsample4x4BlockRgba(sampler);

mipWidth /= 2; mipHeight /= 2;

// generate mip and re-encode the bitmap to the specified format
byte[] encodedMip = BitmapDecoder.EncodeBitmap(mipData, destFormat, mipWidth, mipHeight);

int currentLength = resultData.Length;
Array.Resize(ref resultData, currentLength + encodedMip.Length);
Buffer.BlockCopy(encodedMip, 0, resultData, currentLength, encodedMip.Length);
}


// update image data

resultBitmap.Data = resultData;
Bitmap.Images[imageIndex].PixelDataOffset = 0;
for (int i = 0; i < imageIndex; i++)
{
Bitmap.Images[imageIndex].PixelDataOffset += Bitmap.Images[i].PixelDataSize;
}

Bitmap.Images[imageIndex].PixelDataSize = resultBitmap.Data.Length;
Bitmap.Images[imageIndex].MipmapCount = mipCount;

// write to new resource

var bitmapResourceDefinition = BitmapUtils.CreateBitmapTextureInteropResource(resultBitmap);
var newResourceReference = Cache.ResourceCache.CreateBitmapResource(bitmapResourceDefinition);
return newResourceReference;
}
}
}
2 changes: 2 additions & 0 deletions TagTool/TagTool.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@
<Compile Include="Audio\SoundDistanceParameters.cs" />
<Compile Include="Audio\Utils\AudioUtils.cs" />
<Compile Include="Bitmaps\BitmapCurveMode.cs" />
<Compile Include="Bitmaps\Utils\BitmapDownsampler.cs" />
<Compile Include="Bitmaps\Utils\BitmapSampler.cs" />
<Compile Include="Bitmaps\Utils\BitmapUtilsPC.cs" />
<Compile Include="Bitmaps\Utils\RGBAColor.cs" />
Expand Down Expand Up @@ -400,6 +401,7 @@
<Compile Include="Cache\HaloOnline\StringTableHaloOnline.cs" />
<Compile Include="Cache\HaloOnline\TagCacheHaloOnline.cs" />
<Compile Include="Cache\ModPackages\GameCacheModPackage.cs" />
<Compile Include="Commands\Bitmaps\GenerateMipmapsCommand.cs" />
<Compile Include="Commands\Bitmaps\ReencodeBitmapCommand.cs" />
<Compile Include="Commands\Camera\CameraTrackContextFactory.cs" />
<Compile Include="Commands\Camera\AdjustPositionCommand.cs" />
Expand Down

0 comments on commit a436586

Please sign in to comment.