diff --git a/OpenKh.Kh2/Contextes/FontContext.cs b/OpenKh.Kh2/Contextes/FontContext.cs index f360b8e94..6e98a3e81 100644 --- a/OpenKh.Kh2/Contextes/FontContext.cs +++ b/OpenKh.Kh2/Contextes/FontContext.cs @@ -1,5 +1,7 @@ +using OpenKh.Common; using OpenKh.Imaging; using OpenKh.Kh2.SystemData; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -52,83 +54,187 @@ public void Read(IEnumerable entries) } } - private void ReadFont(Bar.Entry entry, ref IImageRead image1, ref IImageRead image2, ref byte[] spacing) - { - switch (entry.Type) - { - case Bar.EntryType.List: - spacing = ReadSpacing(entry); - break; - case Bar.EntryType.RawBitmap: - entry.Stream.Position = 0; - image1 = ReadImagePalette1(entry); - entry.Stream.Position = 0; - image2 = ReadImagePalette2(entry); - break; - } - } - - private void ReadIcon(Bar.Entry entry, ref IImageRead image, ref byte[] spacing, int width, int height) - { - switch (entry.Type) - { - case Bar.EntryType.List: - spacing = ReadSpacing(entry); - break; - case Bar.EntryType.RawBitmap: - entry.Stream.Position = 0; - image = ReadImage8bit(entry, width, height); - break; - } - } - - private static byte[] ReadSpacing(Bar.Entry entry) - { - return new BinaryReader(entry.Stream).ReadBytes((int)entry.Stream.Length); - } - - private static IImageRead ReadImage8bit(Bar.Entry entry, int width, int height) - { - return RawBitmap.Read8bit(entry.Stream, width, height); - } - - private static IImageRead ReadImagePalette1(Bar.Entry entry) - { - DeductImageSize((int)entry.Stream.Length, out var width, out var height); - return RawBitmap.Read4bitPalette1(entry.Stream, width, height); - } - - private static IImageRead ReadImagePalette2(Bar.Entry entry) - { - DeductImageSize((int)entry.Stream.Length, out var width, out var height); - return RawBitmap.Read4bitPalette2(entry.Stream, width, height); - } - - private static bool DeductImageSize(int rawLength, out int width, out int height) - { - if (rawLength < 128 * 1024) - { - width = 512; - height = 256; - } - else if (rawLength < 256 * 1024) - { - width = 512; - height = 512; - } - else if (rawLength < 512 * 1024) - { - width = 512; - height = 1024; - } - else - { - width = 0; - height = 0; - return false; - } - - return true; - } + public IEnumerable WriteFontImage() + { + var list = new Bar(); + + list.Add(new Bar.Entry { Name = "sys", Type = Bar.EntryType.RawBitmap, Stream = WriteFont(imageSystem, imageSystem2), }); + list.Add(new Bar.Entry { Name = "evt", Type = Bar.EntryType.RawBitmap, Stream = WriteFont(imageEvent, imageEvent2), }); + list.Add(new Bar.Entry { Name = "icon", Type = Bar.EntryType.RawBitmap, Stream = WriteIconBitmap(imageIcon), }); + + return list.AsReadOnly(); + } + + private void ReadFont(Bar.Entry entry, ref IImageRead image1, ref IImageRead image2, ref byte[] spacing) + { + switch (entry.Type) + { + case Bar.EntryType.List: + spacing = ReadSpacing(entry); + break; + case Bar.EntryType.RawBitmap: + entry.Stream.Position = 0; + image1 = ReadImagePalette1(entry); + entry.Stream.Position = 0; + image2 = ReadImagePalette2(entry); + break; + } + } + + private MemoryStream WriteFont(IImageRead image1, IImageRead image2) + { + if (false + || image1 == null + || image2 == null + || image1.Size.Width != 512 + || image1.Size != image2.Size + ) + { + throw new Exception("Font image width expected 512. And two font images expect to be same size."); + } + + var source1 = image1.ToBgra32(); + var source2 = image2.ToBgra32(); + + var height = image1.Size.Height; + var pixels = new byte[256 * height]; + + for (int y = 0; y < height; y++) + { + var readOfs = 512 * 4 * y; + var writeOfs = 256 * y; + + for (int x = 0; x < 512; x += 2, readOfs += 8, writeOfs += 1) + { + var plane1FirstBlue = source1[readOfs]; + var plane1FirstGreen = source1[readOfs + 1]; + var plane1FirstRed = source1[readOfs + 2]; + var plane1SecondBlue = source1[readOfs + 4]; + var plane1SecondGreen = source1[readOfs + 5]; + var plane1SecondRed = source1[readOfs + 6]; + var plane2FirstBlue = source2[readOfs]; + var plane2FirstGreen = source2[readOfs + 1]; + var plane2FirstRed = source2[readOfs + 2]; + var plane2SecondBlue = source2[readOfs + 4]; + var plane2SecondGreen = source2[readOfs + 5]; + var plane2SecondRed = source2[readOfs + 6]; + // At 4bpp bitmap, lo and hi are swapped. lo first, hi second. + pixels[writeOfs] = MakePixel( + CutTo2Bit(plane1FirstBlue, plane1FirstGreen, plane1FirstRed) | (CutTo2Bit(plane2FirstBlue, plane2FirstGreen, plane2FirstRed) << 2), + CutTo2Bit(plane1SecondBlue, plane1SecondGreen, plane1SecondRed) | (CutTo2Bit(plane2SecondBlue, plane2SecondGreen, plane2SecondRed) << 2) + ); + } + } + + return new MemoryStream(pixels); + } + + private static byte MakePixel(int lo, int hi) + { + return (byte)((hi << 4) | (lo & 15)); + } + + private static int CutTo2Bit(byte blue, byte green, byte red) + { + var intensity = (blue + (int)green + red) / 3; + + if (0xe0 <= intensity) + { + return 3; + } + else if (0xb0 <= intensity) + { + return 2; + } + else if (0x90 <= intensity) + { + return 1; + } + else + { + return 0; + } + } + + private void ReadIcon(Bar.Entry entry, ref IImageRead image, ref byte[] spacing, int width, int height) + { + switch (entry.Type) + { + case Bar.EntryType.List: + spacing = ReadSpacing(entry); + break; + case Bar.EntryType.RawBitmap: + entry.Stream.Position = 0; + image = ReadImage8bit(entry, width, height); + break; + } + } + + private MemoryStream WriteIconBitmap(IImageRead image) + { + if (false + || image == null + || image.PixelFormat != PixelFormat.Indexed8 + || image.Size.Width != 256 + || image.Size.Height != 160 + ) + { + throw new Exception("Icon image expects 256 x 160 (Indexed8)"); + } + + var stream = new MemoryStream(256 * 160 + 4 * 256); + stream.Write(image.GetData()); + stream.Write(image.GetClut()); + return stream; + } + + private static byte[] ReadSpacing(Bar.Entry entry) + { + return new BinaryReader(entry.Stream).ReadBytes((int)entry.Stream.Length); + } + + private static IImageRead ReadImage8bit(Bar.Entry entry, int width, int height) + { + return RawBitmap.Read8bit(entry.Stream, width, height); + } + + private static IImageRead ReadImagePalette1(Bar.Entry entry) + { + DeductImageSize((int)entry.Stream.Length, out var width, out var height); + return RawBitmap.Read4bitPalette1(entry.Stream, width, height); + } + + private static IImageRead ReadImagePalette2(Bar.Entry entry) + { + DeductImageSize((int)entry.Stream.Length, out var width, out var height); + return RawBitmap.Read4bitPalette2(entry.Stream, width, height); + } + + private static bool DeductImageSize(int rawLength, out int width, out int height) + { + if (rawLength < 128 * 1024) + { + width = 512; + height = 256; + } + else if (rawLength < 256 * 1024) + { + width = 512; + height = 512; + } + else if (rawLength < 512 * 1024) + { + width = 512; + height = 1024; + } + else + { + width = 0; + height = 0; + return false; + } + + return true; + } } } diff --git a/OpenKh.Kh2/FontEditing/FontIconBitmap.cs b/OpenKh.Kh2/FontEditing/FontIconBitmap.cs new file mode 100644 index 000000000..a4abb720f --- /dev/null +++ b/OpenKh.Kh2/FontEditing/FontIconBitmap.cs @@ -0,0 +1,28 @@ +using OpenKh.Imaging; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenKh.Kh2.FontEditing +{ + public class FontIconBitmap + { + public static byte[] WriteIconBitmap(IImageRead image) + { + if (false + || image == null + || image.PixelFormat != PixelFormat.Indexed8 + || image.Size.Width != 256 + || image.Size.Height != 160 + ) + { + throw new Exception("Icon image expects 256 x 160 (Indexed8)"); + } + + var body = new byte[256 * 160 + 4 * 256]; + Buffer.BlockCopy(image.GetData(), 0, body, 0, 256 * 160); + Buffer.BlockCopy(image.GetClut(), 0, body, 256 * 160, 4 * 256); + return body; + } + } +} diff --git a/OpenKh.Kh2/FontEditing/FontImageBitmap.cs b/OpenKh.Kh2/FontEditing/FontImageBitmap.cs new file mode 100644 index 000000000..181d2604d --- /dev/null +++ b/OpenKh.Kh2/FontEditing/FontImageBitmap.cs @@ -0,0 +1,151 @@ +using OpenKh.Imaging; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Text; + +namespace OpenKh.Kh2.FontEditing +{ + public class FontImageBitmap : IImageRead + { + private readonly byte[] _clut; + private readonly byte[] _data; + + public FontImageBitmap(byte[] body, bool firstImage, byte[] clut = null) + { + if ((body.Length & 255) != 0) + { + throw new InvalidCastException("fontimage evt/sys width must be 512"); + } + + _clut = clut ?? new byte[] + { + 0, 0, 0, 255, + 85, 85, 85, 255, + 170, 170, 170, 255, + 255, 255, 255, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + }; + + int width = 512; + int height = body.Length / 256; + + var data = new byte[width / 2 * height]; + + var rightShift = firstImage ? 0 : 2; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x += 2) + { + var at = width / 2 * y + x / 2; + + var one = body[at] >> rightShift; + var leftPixel = one & 3; + var rightPixel = (one >> 4) & 3; + + data[at] = (byte)((leftPixel << 4) | rightPixel); + } + } + + Size = new Size(width, height); + PixelFormat = PixelFormat.Indexed4; + _data = data; + } + + public Size Size { get; } + + public PixelFormat PixelFormat { get; } + + public byte[] GetClut() => _clut; + + public byte[] GetData() => _data; + + public static byte[] WriteFont(IImageRead image1, IImageRead image2) + { + if (false + || image1 == null + || image2 == null + || image1.Size.Width != 512 + || image1.Size != image2.Size + ) + { + throw new Exception("Font image width expected 512. And two font images expect to be same size."); + } + + var source1 = image1.ToBgra32(); + var source2 = image2.ToBgra32(); + + var height = image1.Size.Height; + var pixels = new byte[256 * height]; + + for (int y = 0; y < height; y++) + { + var readOfs = 512 * 4 * y; + var writeOfs = 256 * y; + + for (int x = 0; x < 512; x += 2, readOfs += 8, writeOfs += 1) + { + var plane1FirstBlue = source1[readOfs]; + var plane1FirstGreen = source1[readOfs + 1]; + var plane1FirstRed = source1[readOfs + 2]; + var plane1SecondBlue = source1[readOfs + 4]; + var plane1SecondGreen = source1[readOfs + 5]; + var plane1SecondRed = source1[readOfs + 6]; + var plane2FirstBlue = source2[readOfs]; + var plane2FirstGreen = source2[readOfs + 1]; + var plane2FirstRed = source2[readOfs + 2]; + var plane2SecondBlue = source2[readOfs + 4]; + var plane2SecondGreen = source2[readOfs + 5]; + var plane2SecondRed = source2[readOfs + 6]; + // At 4bpp bitmap, lo and hi are swapped. lo first, hi second. + pixels[writeOfs] = MakePixel( + CutTo2Bit(plane1FirstBlue, plane1FirstGreen, plane1FirstRed) | (CutTo2Bit(plane2FirstBlue, plane2FirstGreen, plane2FirstRed) << 2), + CutTo2Bit(plane1SecondBlue, plane1SecondGreen, plane1SecondRed) | (CutTo2Bit(plane2SecondBlue, plane2SecondGreen, plane2SecondRed) << 2) + ); + } + } + + return pixels; + } + + private static byte MakePixel(int lo, int hi) + { + return (byte)((hi << 4) | (lo & 15)); + } + + private static int CutTo2Bit(byte blue, byte green, byte red) + { + var intensity = (blue + (int)green + red) / 3; + + if (0xe0 <= intensity) + { + return 3; + } + else if (0xb0 <= intensity) + { + return 2; + } + else if (0x90 <= intensity) + { + return 1; + } + else + { + return 0; + } + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/App.xaml b/OpenKh.Tools.Kh2FontImageEditor/App.xaml new file mode 100644 index 000000000..7ca406e47 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/OpenKh.Tools.Kh2FontImageEditor/App.xaml.cs b/OpenKh.Tools.Kh2FontImageEditor/App.xaml.cs new file mode 100644 index 000000000..b43894607 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/App.xaml.cs @@ -0,0 +1,40 @@ +using Microsoft.Extensions.DependencyInjection; +using OpenKh.Tools.Kh2FontImageEditor.DependencyInjection; +using OpenKh.Tools.Kh2FontImageEditor.Views; +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace OpenKh.Tools.Kh2FontImageEditor +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + private ServiceProvider? _container; + + protected override void OnExit(ExitEventArgs e) + { + _container?.Dispose(); + + base.OnExit(e); + } + + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + + _container = new ServiceCollection() + .UseKh2FontImageEditor() + .BuildServiceProvider(); + ShutdownMode = ShutdownMode.OnMainWindowClose; + MainWindow = _container.GetRequiredService(); + MainWindow.Show(); + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/AssemblyInfo.cs b/OpenKh.Tools.Kh2FontImageEditor/AssemblyInfo.cs new file mode 100644 index 000000000..74087a1fd --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/OpenKh.Tools.Kh2FontImageEditor/DependencyInjection/UseExtension.cs b/OpenKh.Tools.Kh2FontImageEditor/DependencyInjection/UseExtension.cs new file mode 100644 index 000000000..c45c48807 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/DependencyInjection/UseExtension.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.DependencyInjection; +using OpenKh.Tools.Kh2FontImageEditor.Usecases; +using OpenKh.Tools.Kh2FontImageEditor.Views; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.DependencyInjection +{ + internal static class UseExtension + { + public static IServiceCollection UseKh2FontImageEditor(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton>(sp => () => sp.GetRequiredService()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient(); + services.AddSingleton>(sp => () => sp.GetRequiredService()); + services.AddTransient(); + + return services; + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Helpers/CommandWithCanExecute.cs b/OpenKh.Tools.Kh2FontImageEditor/Helpers/CommandWithCanExecute.cs new file mode 100644 index 000000000..80aa9cc85 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Helpers/CommandWithCanExecute.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace OpenKh.Tools.Kh2FontImageEditor.Helpers +{ + public class CommandWithCanExecute : ICommand + { + public CommandWithCanExecute(Action action) + { + _action = action; + } + + private bool _isExecutable; + private readonly Action _action; + + public bool IsExecutable + { + get => _isExecutable; + set + { + _isExecutable = value; + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } + + public event EventHandler? CanExecuteChanged; + + public bool CanExecute(object? parameter) + { + return _isExecutable; + } + + public void Execute(object? parameter) + { + _action(parameter); + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Helpers/FontImageData.cs b/OpenKh.Tools.Kh2FontImageEditor/Helpers/FontImageData.cs new file mode 100644 index 000000000..b28477cb7 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Helpers/FontImageData.cs @@ -0,0 +1,18 @@ +using OpenKh.Imaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Helpers +{ + public record FontImageData( + IImageRead? ImageSystem, + IImageRead? ImageSystem2, + IImageRead? ImageEvent, + IImageRead? ImageEvent2, + IImageRead? ImageIcon) + { + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Helpers/FontInfoData.cs b/OpenKh.Tools.Kh2FontImageEditor/Helpers/FontInfoData.cs new file mode 100644 index 000000000..061178026 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Helpers/FontInfoData.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Helpers +{ + public record FontInfoData( + byte[]? System, + byte[]? Event, + byte[]? Icon) + { + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Helpers/GlyphCell.cs b/OpenKh.Tools.Kh2FontImageEditor/Helpers/GlyphCell.cs new file mode 100644 index 000000000..1349c0fba --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Helpers/GlyphCell.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Helpers +{ + public record GlyphCell(Rectangle Cell, int SpacingIndex) + { + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/OpenKh.Tools.Kh2FontImageEditor.csproj b/OpenKh.Tools.Kh2FontImageEditor/OpenKh.Tools.Kh2FontImageEditor.csproj new file mode 100644 index 000000000..26059efed --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/OpenKh.Tools.Kh2FontImageEditor.csproj @@ -0,0 +1,25 @@ + + + + WinExe + net6.0-windows + enable + true + Font image editor + Font image editor - OpenKH + + + + + + + + + + + + + + + + diff --git a/OpenKh.Tools.Kh2FontImageEditor/Usecases/ApplySpacingToImageReadUsecase.cs b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ApplySpacingToImageReadUsecase.cs new file mode 100644 index 000000000..bafdfd7bd --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ApplySpacingToImageReadUsecase.cs @@ -0,0 +1,165 @@ +using OpenKh.Engine.Extensions; +using OpenKh.Imaging; +using OpenKh.Tools.Kh2FontImageEditor.Helpers; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Usecases +{ + public class ApplySpacingToImageReadUsecase + { + private readonly CopyArrayUsecase _copyArrayUsecase; + + public ApplySpacingToImageReadUsecase( + CopyArrayUsecase copyArrayUsecase) + { + _copyArrayUsecase = copyArrayUsecase; + } + + public IImageRead ApplyToIndexed4(IImageRead image, Func getSpacing, GlyphCell[] glyphCells) + { + if (image.PixelFormat != PixelFormat.Indexed4) + { + throw new NotSupportedException($"The image format must be Indexed4"); + } + + var data = _copyArrayUsecase.Copy(image.GetData()); + var clut = new byte[] // RR GG BB AA + { + 0, 0, 0, 255, // Unprotected glyph intensity 0 (black) + 85, 0, 0, 255, // Unprotected glyph intensity 1 (RED) --> glyph overflow + 170, 0, 0, 255, // Unprotected glyph intensity 2 (RED) --> glyph overflow + 255, 0, 0, 255, // Unprotected glyph intensity 3 (RED) --> glyph overflow + 0, 0, 255, 255, // Protected glyph intensity 0 (blue) + 85, 85, 85, 255, // Protected glyph intensity 1 (gray) + 170, 170, 170, 255, // Protected glyph intensity 2 (gray) + 255, 255, 255, 255, // Protected glyph intensity 3 (white) + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + 0, 0, 0, 255, + }; + + var bitmapWidth = image.Size.Width; + var bitmapHeight = image.Size.Height; + + var stride = bitmapWidth / 2; + + void ProtectRect(int fromX, int fromY, byte width, int height) + { + for (int y = 0; y < height; y++) + { + var vertOfs = stride * (fromY + y); + int dstX = fromX; + + for (int x = 0; x < width; x++, dstX++) + { + int ofs = vertOfs + dstX / 2; + var one = data[ofs]; + if ((dstX & 1) == 0) + { + one |= 0x40; + } + else + { + one |= 0x04; + } + data[ofs] = one; + } + } + } + + for (int idx = 0, max = glyphCells.Length; idx < max; idx++) + { + var glyph = glyphCells[idx]; + + ProtectRect( + glyph.Cell.X, + glyph.Cell.Y, + getSpacing(glyph.SpacingIndex), + glyph.Cell.Height + ); + } + + return new SimpleImage(bitmapWidth, bitmapHeight, PixelFormat.Indexed4, data, PixelFormat.Rgba8888, clut); + } + + public IImageRead ApplyToFullColored(IImageRead image, Func getSpacing, GlyphCell[] glyphCells) + { + var data = _copyArrayUsecase.Copy(image.ToBgra32()); + + var bitmapWidth = image.Size.Width; + var bitmapHeight = image.Size.Height; + + var protect = new BitArray(bitmapWidth * bitmapHeight); + + { + void ProtectRect(int fromX, int fromY, byte width, int height) + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + protect[bitmapWidth * (fromY + y) + fromX + x] = true; + } + } + } + + for (int idx = 0, max = glyphCells.Length; idx < max; idx++) + { + var glyph = glyphCells[idx]; + + ProtectRect( + glyph.Cell.X, + glyph.Cell.Y, + getSpacing(glyph.SpacingIndex), + glyph.Cell.Height + ); + } + } + + var ofs = 0; + + for (int index = 0; index < protect.Length; index++, ofs += 4) + { + if (protect[index]) + { + if (data[ofs + 0] == 0 && data[ofs + 1] == 0 && data[ofs + 2] == 0) + { + // make pixel blue as valid pixel inside cell + data[ofs + 0] = 255; + data[ofs + 1] = 0; + data[ofs + 2] = 0; + data[ofs + 3] = 255; + } + } + else + { + // make pixel red as invalid pixel outside cell + + var b = data[ofs + 0]; + var g = data[ofs + 1]; + var r = data[ofs + 2]; + + var intensity = Math.Max(Math.Max(b, g), r); + + data[ofs + 0] = 0; + data[ofs + 1] = 0; + data[ofs + 2] = intensity; + data[ofs + 3] = 255; + } + } + + return new SimpleImage(bitmapWidth, bitmapHeight, PixelFormat.Rgba8888, data, PixelFormat.Rgba8888); + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Usecases/CombineBarUsecase.cs b/OpenKh.Tools.Kh2FontImageEditor/Usecases/CombineBarUsecase.cs new file mode 100644 index 000000000..fcca626bf --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Usecases/CombineBarUsecase.cs @@ -0,0 +1,33 @@ +using OpenKh.Kh2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Usecases +{ + public class CombineBarUsecase + { + public IEnumerable Combine(IEnumerable left, IEnumerable right) + { + var leftList = left.ToList(); + var rightList = right.ToList(); + + foreach (var one in rightList) + { + var hitIndex = leftList.FindIndex(it => it.Type == one.Type && it.Name == one.Name); + if (hitIndex == -1) + { + leftList.Add(one); + } + else + { + leftList[hitIndex] = one; + } + } + + return leftList.AsReadOnly(); + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Usecases/ConvertFontDataUsecase.cs b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ConvertFontDataUsecase.cs new file mode 100644 index 000000000..fb76d01ac --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ConvertFontDataUsecase.cs @@ -0,0 +1,61 @@ +using OpenKh.Common; +using OpenKh.Kh2; +using OpenKh.Tools.Kh2FontImageEditor.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Usecases +{ + public class ConvertFontDataUsecase + { + public FontInfoData Decode(IEnumerable fontInfoBar) + { + byte[]? sys = null; + byte[]? evt = null; + byte[]? icon = null; + + if (fontInfoBar.SingleOrDefault(it => it.Name == "sys" && it.Type == Bar.EntryType.List) is Bar.Entry sysEntry) + { + sys = sysEntry.Stream.FromBegin().ReadBytes(); + } + + if (fontInfoBar.SingleOrDefault(it => it.Name == "evt" && it.Type == Bar.EntryType.List) is Bar.Entry evtEntry) + { + evt = evtEntry.Stream.FromBegin().ReadBytes(); + } + + if (fontInfoBar.SingleOrDefault(it => it.Name == "icon" && it.Type == Bar.EntryType.List) is Bar.Entry iconEntry) + { + icon = iconEntry.Stream.FromBegin().ReadBytes(); + } + + return new FontInfoData(sys, evt, icon); + } + + public IEnumerable Encode(FontInfoData fontInfoData) + { + var newBar = new List(); + + if (fontInfoData.System is byte[] sys) + { + newBar.Add(new Bar.Entry { Name = "sys", Type = Bar.EntryType.List, Stream = new MemoryStream(sys) }); + } + + if (fontInfoData.Event is byte[] evt) + { + newBar.Add(new Bar.Entry { Name = "evt", Type = Bar.EntryType.List, Stream = new MemoryStream(evt) }); + } + + if (fontInfoData.Icon is byte[] icon) + { + newBar.Add(new Bar.Entry { Name = "icon", Type = Bar.EntryType.List, Stream = new MemoryStream(icon) }); + } + + return newBar.AsReadOnly(); + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Usecases/ConvertFontImageUsecase.cs b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ConvertFontImageUsecase.cs new file mode 100644 index 000000000..b2f8c3ab5 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ConvertFontImageUsecase.cs @@ -0,0 +1,80 @@ +using OpenKh.Common; +using OpenKh.Imaging; +using OpenKh.Kh2; +using OpenKh.Kh2.FontEditing; +using OpenKh.Tools.Kh2FontImageEditor.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Usecases +{ + public class ConvertFontImageUsecase + { + public IEnumerable Encode(FontImageData fontImageData) + { + var newBar = new List(); + + if (fontImageData.ImageIcon is IImageRead icon1) + { + var font = FontIconBitmap.WriteIconBitmap(icon1); + newBar.Add(new Bar.Entry { Name = "icon", Type = Bar.EntryType.RawBitmap, Stream = new MemoryStream(font), }); + } + + if (true + && fontImageData.ImageEvent is IImageRead evt1 + && fontImageData.ImageEvent2 is IImageRead evt2 + ) + { + var font = FontImageBitmap.WriteFont(evt1, evt2); + newBar.Add(new Bar.Entry { Name = "evt", Type = Bar.EntryType.RawBitmap, Stream = new MemoryStream(font), }); + } + + if (true + && fontImageData.ImageSystem is IImageRead sys1 + && fontImageData.ImageSystem2 is IImageRead sys2 + ) + { + var font = FontImageBitmap.WriteFont(sys1, sys2); + newBar.Add(new Bar.Entry { Name = "sys", Type = Bar.EntryType.RawBitmap, Stream = new MemoryStream(font), }); + } + + return newBar.AsReadOnly(); + } + + public FontImageData Decode(IEnumerable fontImageBar) + { + IImageRead? sys1 = null; + IImageRead? sys2 = null; + + if (fontImageBar.SingleOrDefault(it => it.Name == "sys" && it.Type == Bar.EntryType.RawBitmap) is Bar.Entry sys) + { + var body = sys.Stream.FromBegin().ReadBytes(); + sys1 = new FontImageBitmap(body, true); + sys2 = new FontImageBitmap(body, false); + } + + IImageRead? evt1 = null; + IImageRead? evt2 = null; + + if (fontImageBar.SingleOrDefault(it => it.Name == "evt" && it.Type == Bar.EntryType.RawBitmap) is Bar.Entry evt) + { + var body = evt.Stream.FromBegin().ReadBytes(); + evt1 = new FontImageBitmap(body, true); + evt2 = new FontImageBitmap(body, false); + } + + IImageRead? icon1 = null; + + if (fontImageBar.SingleOrDefault(it => it.Name == "icon" && it.Type == Bar.EntryType.RawBitmap) is Bar.Entry icon) + { + icon1 = RawBitmap.Read8bit(icon.Stream.FromBegin(), 256, 160); + } + + return new FontImageData(sys1, sys2, evt1, evt2, icon1); + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Usecases/CopyArrayUsecase.cs b/OpenKh.Tools.Kh2FontImageEditor/Usecases/CopyArrayUsecase.cs new file mode 100644 index 000000000..bdafa38a3 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Usecases/CopyArrayUsecase.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Usecases +{ + public class CopyArrayUsecase + { + internal byte[] Copy(byte[] array) + { + var newArray = new byte[array.Length]; + Buffer.BlockCopy(array, 0, newArray, 0, array.Length); + return newArray; + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Usecases/CreateGlyphCellsUsecase.cs b/OpenKh.Tools.Kh2FontImageEditor/Usecases/CreateGlyphCellsUsecase.cs new file mode 100644 index 000000000..3c65b21a4 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Usecases/CreateGlyphCellsUsecase.cs @@ -0,0 +1,59 @@ +using OpenKh.Tools.Kh2FontImageEditor.Helpers; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.Intrinsics.X86; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Usecases +{ + public class CreateGlyphCellsUsecase + { + public GlyphCell[] CreateSimpleGlyphCells(int bitmapWidth, int bitmapHeight, int glyphWidth, int glyphHeight) + { + var list = new List(); + var idx = 0; + + for (int y = 0; y + glyphHeight <= bitmapHeight; y += glyphHeight) + { + for (int x = 0; x + glyphWidth <= bitmapWidth; x += glyphWidth, idx++) + { + list.Add(new GlyphCell(new Rectangle(x, y, glyphWidth, glyphHeight), idx)); + } + } + + return list.ToArray(); + } + + public GlyphCell[] CreateFontGlyphCells(int bitmapWidth, int bitmapHeight, int blockHeight, bool isFront, int glyphWidth, int glyphHeight) + { + var list = new List(); + + var countPerBlock = (bitmapWidth / glyphWidth) * (blockHeight / glyphHeight); + + for (int blockY = 0, blockYIdx = 0; blockY + blockHeight <= bitmapHeight; blockY += blockHeight, blockYIdx++) + { + var blockYTo = blockY + blockHeight; + + var localIdx = countPerBlock * (2 * blockYIdx + (isFront ? 0 : 1)); + + // F B + // - - + // 0 1 + // 2 3 + + for (int y = blockY; y + glyphHeight <= blockYTo; y += glyphHeight) + { + for (int x = 0; x + glyphWidth <= bitmapWidth; x += glyphWidth, localIdx++) + { + list.Add(new GlyphCell(new Rectangle(x, y, glyphWidth, glyphHeight), localIdx)); + } + } + } + + return list.ToArray(); + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Usecases/ExitAppUsecase.cs b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ExitAppUsecase.cs new file mode 100644 index 000000000..7501596cf --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ExitAppUsecase.cs @@ -0,0 +1,22 @@ +using OpenKh.Tools.Kh2FontImageEditor.Views; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Usecases +{ + public class ExitAppUsecase + { + private readonly Func _mainWindow; + + public ExitAppUsecase( + Func mainWindow) + { + _mainWindow = mainWindow; + } + + public void ExitApp() => _mainWindow().Close(); + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Usecases/ReplacePaletteAlphaUsecase.cs b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ReplacePaletteAlphaUsecase.cs new file mode 100644 index 000000000..25cc511fa --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ReplacePaletteAlphaUsecase.cs @@ -0,0 +1,45 @@ +using OpenKh.Imaging; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Usecases +{ + public class ReplacePaletteAlphaUsecase + { + public IImageRead ReplacePaletteAlphaWith(IImageRead imageRead, byte fixedAlpha) + { + var clut = imageRead.GetClut(); + if (clut != null) + { + var newClut = new byte[clut.Length]; + Buffer.BlockCopy(clut, 0, newClut, 0, clut.Length); + for (int x = 3; x < clut.Length; x += 4) + { + newClut[x] = fixedAlpha; + } + clut = newClut; + } + + return new PrivateProxy( + imageRead.Size, + imageRead.PixelFormat, + clut, + imageRead.GetData() + ); + } + + private record PrivateProxy( + Size Size, + PixelFormat PixelFormat, + byte[]? Clut, + byte[] Data) : IImageRead + { + public byte[]? GetClut() => Clut; + public byte[] GetData() => Data; + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Usecases/ShowErrorMessageUsecase.cs b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ShowErrorMessageUsecase.cs new file mode 100644 index 000000000..71efc27be --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Usecases/ShowErrorMessageUsecase.cs @@ -0,0 +1,18 @@ +using OpenKh.Tools.Common.Wpf; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.Kh2FontImageEditor.Usecases +{ + public class ShowErrorMessageUsecase + { + public void Show(Exception ex) + { + new MessageDialog($"There is a critical error:\n\n{ex}") + .ShowDialog(); + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/UserControls/ImagerTablet.xaml b/OpenKh.Tools.Kh2FontImageEditor/UserControls/ImagerTablet.xaml new file mode 100644 index 000000000..5bb768194 --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/UserControls/ImagerTablet.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/OpenKh.Tools.Kh2FontImageEditor/Views/SpacingWindow.xaml.cs b/OpenKh.Tools.Kh2FontImageEditor/Views/SpacingWindow.xaml.cs new file mode 100644 index 000000000..b8f8661ab --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Views/SpacingWindow.xaml.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace OpenKh.Tools.Kh2FontImageEditor.Views +{ + /// + /// Interaction logic for SpacingWindow.xaml + /// + public partial class SpacingWindow : Window + { + public SpacingWindow( + SpacingWindowVM vm + ) + { + InitializeComponent(); + DataContext = vm; + + _image.MouseDown += (object sender, MouseButtonEventArgs e) => + { + var spacingDelta = (e.LeftButton == MouseButtonState.Pressed) ? -1 + : (e.RightButton == MouseButtonState.Pressed) ? +1 + : 0; + var point = e.GetPosition(_image); + + vm.State?.AdjustSpacing((int)point.X, (int)point.Y, spacingDelta); + }; + } + } +} diff --git a/OpenKh.Tools.Kh2FontImageEditor/Views/SpacingWindowVM.cs b/OpenKh.Tools.Kh2FontImageEditor/Views/SpacingWindowVM.cs new file mode 100644 index 000000000..f8b19d64b --- /dev/null +++ b/OpenKh.Tools.Kh2FontImageEditor/Views/SpacingWindowVM.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows.Media; +using Xe.Tools; + +namespace OpenKh.Tools.Kh2FontImageEditor.Views +{ + public class SpacingWindowVM : BaseNotifyPropertyChanged + { + public record StateModel( + ImageSource Image, + int Width, + int Height, + ICommand SaveCommand, + Action AdjustSpacing + ); + + #region State property + private StateModel? _state; + public StateModel? State + { + get => _state; + set + { + if (_state != value) + { + _state = value; + OnPropertyChanged(); + } + } + } + #endregion + + } +} diff --git a/OpenKh.sln b/OpenKh.sln index c4b5e6a61..95448cd52 100644 --- a/OpenKh.sln +++ b/OpenKh.sln @@ -212,6 +212,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Command.Txa", "OpenK EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simple3DViewport", "Simple3DViewport\Simple3DViewport\Simple3DViewport.csproj", "{CA7DCF50-324E-4C36-B476-49940EBA393A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Tools.Kh2FontImageEditor", "OpenKh.Tools.Kh2FontImageEditor\OpenKh.Tools.Kh2FontImageEditor.csproj", "{97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Tools.Kh2MsetMotionEditor", "OpenKh.Tools.Kh2MsetMotionEditor\OpenKh.Tools.Kh2MsetMotionEditor.csproj", "{4A16893C-3E9B-4547-AE22-FDF7FD216137}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Tools.Kh2ObjectEditor", "OpenKh.Tools.Kh2ObjectEditor\OpenKh.Tools.Kh2ObjectEditor.csproj", "{CF6D785D-602A-46E0-9DCB-5F703E3544F0}" @@ -1486,6 +1488,22 @@ Global {CA7DCF50-324E-4C36-B476-49940EBA393A}.Release|Any CPU.Build.0 = Release|Any CPU {CA7DCF50-324E-4C36-B476-49940EBA393A}.Release|x64.ActiveCfg = Release|Any CPU {CA7DCF50-324E-4C36-B476-49940EBA393A}.Release|x64.Build.0 = Release|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}..NET Debug|Any CPU.Build.0 = Debug|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}..NET Debug|x64.ActiveCfg = Debug|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}..NET Debug|x64.Build.0 = Debug|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}..NET Release|Any CPU.ActiveCfg = Release|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}..NET Release|Any CPU.Build.0 = Release|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}..NET Release|x64.ActiveCfg = Release|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}..NET Release|x64.Build.0 = Release|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}.Debug|x64.ActiveCfg = Debug|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}.Debug|x64.Build.0 = Debug|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}.Release|Any CPU.Build.0 = Release|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}.Release|x64.ActiveCfg = Release|Any CPU + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0}.Release|x64.Build.0 = Release|Any CPU {4A16893C-3E9B-4547-AE22-FDF7FD216137}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU {4A16893C-3E9B-4547-AE22-FDF7FD216137}..NET Debug|Any CPU.Build.0 = Debug|Any CPU {4A16893C-3E9B-4547-AE22-FDF7FD216137}..NET Debug|x64.ActiveCfg = Debug|Any CPU @@ -1622,6 +1640,7 @@ Global {F0A7BD0D-C717-45D5-9544-4C3AECC3740F} = {9C52D787-9819-4B89-989B-EEA6FEB20731} {1D074EA4-9964-43EC-9389-7E60C2545C13} = {0FB7CD6A-EE31-467D-A590-267DCD028618} {EAD6D51F-5106-4E11-A4E4-104A18C0235B} = {9C52D787-9819-4B89-989B-EEA6FEB20731} + {97E01F25-D6FB-45F0-9FD7-7A88F95F88D0} = {402B2669-D594-4DFA-965B-02A92626ADC6} {4A16893C-3E9B-4547-AE22-FDF7FD216137} = {402B2669-D594-4DFA-965B-02A92626ADC6} {CF6D785D-602A-46E0-9DCB-5F703E3544F0} = {402B2669-D594-4DFA-965B-02A92626ADC6} {A635E041-7F5D-4CD5-BB87-0F692EA1AD3E} = {4FBC958A-4685-4DF2-8D4F-98850BAE61B3} diff --git a/docs/kh2/file/type/fontimage.md b/docs/kh2/file/type/fontimage.md new file mode 100644 index 000000000..0ec4d2855 --- /dev/null +++ b/docs/kh2/file/type/fontimage.md @@ -0,0 +1,51 @@ +# [Kingdom Hearts II](../../index.md) - Font and icon sprite images + +fontimage.bar: + +Tag | Type | Purpose +------|-----------|------------------ +`sys` | RawBitmap | System text font +`evt` | RawBitmap | Event text font +`icon`| RawBitmap | Icon sprite + +## `sys` / `evt` bitmap image format + +4 bpp bitmap with fixed *width* of 512 pixel. + +*Height* is calculated by the formula: `(FileSize / 256)`. + +Two 2bpp bitmaps are combined into single 4bpp bitmap. + +First bitmap. Pixel order is: `LL HH` + +|Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|---|:-: |:-: |:-: |:-: |:-: |:-: |:-: |:-: | +| | -- | -- | HH | HH | -- | -- | LL | LL | + +Second bitmap. Pixel order is: `LL HH` + +|Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +|---|:-: |:-: |:-: |:-: |:-: |:-: |:-: |:-: | +| | HH | HH | -- | -- | LL | LL | -- | -- | + +Color palette is 4 colors gray palette like this: + + R | G | B +--:|--:|--: + 0| 0| 0 + 85| 85| 85 +170|170|170 +255|255|255 + +## `icon` + +Sprite image of: 8 bpp bitmap (256 x 160) + 256 colors palette. + +File layout: + +``` +0000h: 8bpp bitmap (256 x 160) +A000h: 256 colors palette (RR GG BB Ps2Alpha) +A400h: EOF + +``` diff --git a/docs/kh2/file/type/fontinfo.md b/docs/kh2/file/type/fontinfo.md new file mode 100644 index 000000000..bd69f6315 --- /dev/null +++ b/docs/kh2/file/type/fontinfo.md @@ -0,0 +1,23 @@ +# [Kingdom Hearts II](../../index.md) - Font and icon spacing + +fontinfo.bar: + +Tag | Type | Purpose +------|-----------|-------------------------- +`sys` | List | System text font spacing +`evt` | List | Event text font spacing +`icon`| List | Icon sprite spacing + +The entire of spacing data is array of `byte`. + +Each character unit size is fixed. + +The spacing declares each character width in pixel unit spreading from left to right. + +![](images/spacing.png) + +The ordering of spacing data is a little bit difficult. +Basically it advances left to right, and then top to bottom. +However if there is a 2 blocks in vertical direction, +at first, the index advances from front plane to back plane, +and then advance to front plane of next block. diff --git a/docs/kh2/file/type/images/spacing.png b/docs/kh2/file/type/images/spacing.png new file mode 100644 index 000000000..bc9009929 Binary files /dev/null and b/docs/kh2/file/type/images/spacing.png differ diff --git a/docs/kh2/notable-files.md b/docs/kh2/notable-files.md index f50b24513..5b6716548 100644 --- a/docs/kh2/notable-files.md +++ b/docs/kh2/notable-files.md @@ -7,5 +7,7 @@ | [00objentry.bin](file/type/00objentry.md) | Object entries and params | | [03system.bin](file/type/03system.md) | Various params | | [15jigsaw.bin](file/type/15jigsaw.md) | Defines puzzle pieces (Final Mix) | +| [fontimage.bar](file/type/fontimage.md) | Font and icon sprite images | +| [fontinfo.bar](file/type/fontinfo.md) | Font and icon spacing | | [jiminy.bar](file/type/jiminy.md) | Jiminy's journal info | | [mixdata.bar](file/type/mixdata.md) | Moogle shop info |