Пример #1
0
        public static Stream ProcessTexture(string name, DuplicatableStream ustream, DuplicatableStream wstream)
        {
            FPS4 w    = new FPS4(wstream.Duplicate());
            TXM  wtxm = new TXM(w.GetChildByIndex(0).AsFile.DataStream.Duplicate());
            TXV  wtxv = new TXV(wtxm, w.GetChildByIndex(1).AsFile.DataStream.Duplicate(), false);
            FPS4 u    = new FPS4(ustream.Duplicate());
            TXM  utxm = new TXM(u.GetChildByIndex(0).AsFile.DataStream.Duplicate());
            TXV  utxv = new TXV(utxm, u.GetChildByIndex(1).AsFile.DataStream.Duplicate(), false);
            List <TexConvRules> convs = new List <TexConvRules>();

            if (name == "rootR.cpk/mg/tex/karuta.tex")
            {
                convs.Add(new TexConvRules()
                {
                    WTexId = 2, UTexId = 1, Method = TexConvMethod.Delegate, Delegate = (wtex, utex) => DoKarutaHalfAndHalf(wtex, utex, 82, 134, 294)
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 4, UTexId = 3, Method = TexConvMethod.Delegate, Delegate = (wtex, utex) => DoKarutaHalfAndHalf(wtex, utex, 86, 134, 294)
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 7, UTexId = 7, Method = TexConvMethod.DownscaleTwoThirds
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 13, UTexId = 13, Method = TexConvMethod.Delegate, Delegate = (wtex, utex) => DoKaruta13(wtex, utex)
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 17, UTexId = 17, Method = TexConvMethod.DownscaleTwoThirds
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 22, UTexId = 23, Method = TexConvMethod.Delegate, Delegate = (wtex, utex) => DownscaleTwoThirds(CropExpandCanvas(utex, 2, -2, (uint)(utex.Width + 3), (uint)(utex.Height - 3)))
                });
            }
            else if (name == "rootR.cpk/mnu/tex/main.tex")
            {
                convs.Add(new TexConvRules()
                {
                    WTexId = 110, UTexId = 61, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 111, UTexId = 62, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 112, UTexId = 63, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 113, UTexId = 64, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 114, UTexId = 65, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 115, UTexId = 66, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 116, UTexId = 67, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 117, UTexId = 68, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 118, UTexId = 69, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 119, UTexId = 70, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 124, UTexId = 75, Method = TexConvMethod.Downscale2x
                });
            }
            else if (name == "rootR.cpk/mnu/tex/shop.tex")
            {
                convs.Add(new TexConvRules()
                {
                    WTexId = 1, UTexId = 1, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 2, UTexId = 2, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 3, UTexId = 3, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 4, UTexId = 4, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 5, UTexId = 5, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 6, UTexId = 6, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 7, UTexId = 7, Method = TexConvMethod.Downscale2x
                });
            }
            else if (name == "rootR.cpk/mnu/tex/skill.tex")
            {
                convs.Add(new TexConvRules()
                {
                    WTexId = 0, UTexId = 0, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 1, UTexId = 1, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 2, UTexId = 2, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 3, UTexId = 3, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 4, UTexId = 4, Method = TexConvMethod.Downscale2x
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 5, UTexId = 5, Method = TexConvMethod.Downscale2x
                });
            }
            else if (name == "rootR.cpk/mnu/tex/snd_test.tex")
            {
                convs.Add(new TexConvRules()
                {
                    WTexId = 1, UTexId = 1, Method = TexConvMethod.Downscale2x
                });
            }
            else if (name == "rootR.cpk/SysSub/JA/TitleTexture.tex")
            {
                convs.Add(new TexConvRules()
                {
                    WTexId = 1, UTexId = 1, Method = TexConvMethod.CropExpandCanvas
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 2, UTexId = 4, Method = TexConvMethod.CropExpandCanvas
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 3, UTexId = 6, Method = TexConvMethod.CropExpandCanvas
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 4, UTexId = 8, Method = TexConvMethod.CropExpandCanvas
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 6, UTexId = 12, Method = TexConvMethod.CropExpandCanvas
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 7, UTexId = 14, Method = TexConvMethod.CropExpandCanvas
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 8, UTexId = 16, Method = TexConvMethod.CropExpandCanvas
                });
            }
            else if (name.EndsWith(".CLG"))
            {
                convs.Add(new TexConvRules()
                {
                    WTexId = 0, UTexId = 0, Method = TexConvMethod.DownscaleTwoThirds
                });
                convs.Add(new TexConvRules()
                {
                    WTexId = 1, UTexId = 1, Method = TexConvMethod.Clear
                });
            }

            MemoryStream s = wstream.Duplicate().CopyToMemory();

            s.Position = 0;
            foreach (TexConvRules c in convs)
            {
                var wm = wtxm.TXMRegulars[c.WTexId];
                var um = utxm.TXMRegulars[c.UTexId];
                var wv = wtxv.textures.Where(x => x.TXM == wm).First();
                var uv = utxv.textures.Where(x => x.TXM == um).First();
                System.Drawing.Bitmap newImage = null;
                switch (c.Method)
                {
                case TexConvMethod.Downscale2x: {
                    newImage = FontProcessing.DownscaleInteger(uv.GetBitmaps()[0], 2);
                }
                break;

                case TexConvMethod.DownscaleTwoThirds: {
                    newImage = DownscaleTwoThirds(uv.GetBitmaps()[0]);
                }
                break;

                case TexConvMethod.CropExpandCanvas: {
                    newImage = DoCropExpandCanvas(uv.GetBitmaps()[0], wm.Width, wm.Height);
                }
                break;

                case TexConvMethod.Clear: {
                    newImage = new Bitmap(wv.GetBitmaps()[0]);
                    for (int y = 0; y < newImage.Height; ++y)
                    {
                        for (int x = 0; x < newImage.Width; ++x)
                        {
                            newImage.SetPixel(x, y, Color.FromArgb(0, 0, 0, 0));
                        }
                    }
                }
                break;

                case TexConvMethod.Delegate: {
                    newImage = c.Delegate(wv.GetBitmaps()[0], uv.GetBitmaps()[0]);
                }
                break;

                default: {
                    throw new Exception("don't know how to convert " + uv.TXM.Name);
                }
                }
                if (newImage != null)
                {
                    HyoutaTools.Util.Assert(newImage.Width == wm.Width);
                    HyoutaTools.Util.Assert(newImage.Height == wm.Height);
                    if (wm.Format == HyoutaTools.Tales.Vesperia.Texture.TextureFormat.Indexed8Bits_RGB5A3)
                    {
                        ChopBitsRGB5A3(newImage);
                        var palette = GeneratePalette256(newImage);

                        s.Position = w.Files[1].Location.Value + wm.TxvLocation;
                        foreach (var loc in new HyoutaTools.Textures.PixelOrderIterators.TiledPixelOrderIterator(newImage.Width, newImage.Height, 8, 4))
                        {
                            int cval = 0;
                            if (loc.X < newImage.Width && loc.Y < newImage.Height)
                            {
                                cval = palette.lookup[newImage.GetPixel(loc.X, loc.Y)];
                            }
                            s.WriteByte((byte)cval);
                        }

                        for (int ci = 0; ci < 256; ++ci)
                        {
                            ushort cval = 0;
                            if (ci < palette.colors.Count)
                            {
                                cval = HyoutaTools.Textures.ColorFetchingIterators.ColorFetcherRGB5A3.ColorToRGB5A3(palette.colors[ci]);
                            }
                            s.WriteUInt16(cval.ToEndian(EndianUtils.Endianness.BigEndian));
                        }
                    }
                    else if (wm.Format == HyoutaTools.Tales.Vesperia.Texture.TextureFormat.GamecubeRGBA8)
                    {
                        s.Position = w.Files[1].Location.Value + wm.TxvLocation;
                        byte[] tmpb = new byte[0x40];
                        int    tmpp = 0;
                        foreach (var loc in new HyoutaTools.Textures.PixelOrderIterators.TiledPixelOrderIterator(newImage.Width, newImage.Height, 4, 4))
                        {
                            Color col = newImage.GetPixel(loc.X, loc.Y);
                            tmpb[tmpp * 2 + 0]        = col.A;
                            tmpb[tmpp * 2 + 1]        = col.R;
                            tmpb[tmpp * 2 + 0 + 0x20] = col.G;
                            tmpb[tmpp * 2 + 1 + 0x20] = col.B;
                            ++tmpp;
                            if (tmpp == 16)
                            {
                                tmpp = 0;
                                s.Write(tmpb);
                            }
                        }
                        if (tmpp != 0)
                        {
                            throw new Exception("Unexpected tile size for " + wm.Name);
                        }
                    }
                    else
                    {
                        Console.WriteLine("don't know how to encode into " + wm.Format);
                    }
                }
            }

            s.Position = 0;
            return(s);
        }
Пример #2
0
        public static (DuplicatableStream metrics, DuplicatableStream texture, Dictionary <char, (int w1, int w2)> charToWidthMap) Run(FileFetcher _fc, Config config)
        {
            bool debug         = config.DebugFontOutputPath != null;
            bool adjustMetrics = debug;

            DuplicatableStream metricsWiiStream = _fc.GetFile("rootR.cpk/sys/FontBinary2.bin", Version.W);
            DuplicatableStream textureWiiStream = _fc.GetFile("rootR.cpk/sys/FontTexture2.tex", Version.W);
            DuplicatableStream texturePs3Stream = _fc.GetFile("rootR.cpk/sys/FontTexture2.tex", Version.U);
            FPS4 metricsWiiFps4 = new FPS4(metricsWiiStream);
            DuplicatableStream metricsWiiData = metricsWiiFps4.GetChildByIndex(1).AsFile.DataStream;
            FPS4   textureWiiFps4             = new FPS4(textureWiiStream);
            FPS4   texturePs3Fps4             = new FPS4(texturePs3Stream);
            TXM    textureWiiTxm = new TXM(textureWiiFps4.GetChildByIndex(0).AsFile.DataStream);
            TXV    textureWiiTxv = new TXV(textureWiiTxm, textureWiiFps4.GetChildByIndex(1).AsFile.DataStream, false);
            TXM    texturePs3Txm = new TXM(texturePs3Fps4.GetChildByIndex(0).AsFile.DataStream);
            TXV    texturePs3Txv = new TXV(texturePs3Txm, texturePs3Fps4.GetChildByIndex(1).AsFile.DataStream, false);
            Bitmap bitmapWii     = textureWiiTxv.textures[0].GetBitmaps()[0];
            Bitmap bitmapPs3     = texturePs3Txv.textures[0].GetBitmaps()[0];

            if (debug)
            {
                Directory.CreateDirectory(config.DebugFontOutputPath);
                bitmapWii.Save(Path.Combine(config.DebugFontOutputPath, "wii.png"));
                bitmapPs3.Save(Path.Combine(config.DebugFontOutputPath, "ps3.png"));
            }

            var       img_wii = bitmapWii;
            var       img_ps3 = bitmapPs3;
            const int tile_extent_in_image = 25;
            const int tile_extent_actual   = 24;
            int       tiles_x = (img_wii.Width + 1) / tile_extent_in_image;
            int       tiles_y = (img_wii.Height + 1) / tile_extent_in_image;
            const int ps3_tile_extent_in_image = 37;
            const int ps3_tile_extent_actual   = 36;
            int       ps3_tiles_x = (img_ps3.Width + 1) / ps3_tile_extent_in_image;
            int       ps3_tiles_y = (img_ps3.Height + 1) / ps3_tile_extent_in_image;

            // split into individual tiles and extract source colors
            HashSet <Color> colors    = new HashSet <Color>();
            List <Bitmap>   tiles_wii = new List <Bitmap>();
            List <Bitmap>   tiles_ps3 = new List <Bitmap>();

            for (int ty = 0; ty < tiles_y; ++ty)
            {
                for (int tx = 0; tx < tiles_x; ++tx)
                {
                    var bmp = new Bitmap(tile_extent_actual, tile_extent_actual);
                    for (int y = 0; y < tile_extent_actual; ++y)
                    {
                        for (int x = 0; x < tile_extent_actual; ++x)
                        {
                            var px = img_wii.GetPixel(tx * tile_extent_in_image + x, ty * tile_extent_in_image + y);
                            colors.Add(px);
                            bmp.SetPixel(x, y, px);
                        }
                    }
                    tiles_wii.Add(bmp);
                }
            }
            for (int ty = 0; ty < ps3_tiles_y; ++ty)
            {
                for (int tx = 0; tx < ps3_tiles_x; ++tx)
                {
                    var bmp = new Bitmap(ps3_tile_extent_actual, ps3_tile_extent_actual);
                    for (int y = 0; y < ps3_tile_extent_actual; ++y)
                    {
                        for (int x = 0; x < ps3_tile_extent_actual; ++x)
                        {
                            var px = img_ps3.GetPixel(tx * ps3_tile_extent_in_image + x, ty * ps3_tile_extent_in_image + y);
                            bmp.SetPixel(x, y, px);
                        }
                    }
                    tiles_ps3.Add(bmp);
                }
            }

            // inject ps3 tiles over wii tiles
            List <(int where, int ps3where, string chars)> charsets = new List <(int where, int ps3where, string chars)>();

            charsets.Add((0, 0, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
            charsets.Add((243, 74, ",."));
            charsets.Add((246, 77, ":;?!_"));
            charsets.Add((254, 83, "/\\~|…"));
            charsets.Add((260, 118, "‘"));
            charsets.Add((261, 118, "’"));
            charsets.Add((262, 119, "“"));
            charsets.Add((263, 119, "”"));
            charsets.Add((264, 93, "()[]{}"));
            charsets.Add((276, 105, "+"));
            charsets.Add((277, 82, "-"));             // copy the dash minus instead of the math minus, looks better in text flow
            charsets.Add((278, 107, "±×÷=≠<>≤≥"));
            charsets.Add((289, 118, "'\""));
            charsets.Add((293, 122, "$%#&*@"));

            Dictionary <char, (int w1, int w2)> charToWidthMap = new Dictionary <char, (int w1, int w2)>();

            byte[] metrics = new byte[metricsWiiData.Length];
            metricsWiiData.Read(metrics, 0, metrics.Length);

            foreach (var charset in charsets)
            {
                int where = charset.where;
                int ps3where = charset.ps3where;
                foreach (char ch in charset.chars)
                {
                    var wiitile       = tiles_wii[where];
                    var averagescaled = DownscaleTileFromPs3ToWiiWithUnweightedAverageScaling(tiles_ps3[ps3where]);
                    //var downscaled = DownscaleTileFromPs3ToWiiWithWeightedScaling(tiles_ps3[ps3where]);
                    var downscaled = averagescaled;
                    PosterizeImage(wiitile, downscaled, colors, tile_extent_actual);
                    PosterizeImage(averagescaled, averagescaled, colors, tile_extent_actual);
                    if (debug)
                    {
                        wiitile.Save(Path.Combine(config.DebugFontOutputPath, string.Format("wii_new_{0:D4}.png", where)));
                    }

                    int cutoff_1    = 180;
                    int cutoff_2    = 220;
                    int leftwhere   = where * 8 + 0;
                    int rightwhere  = where * 8 + 1;
                    int leftwhere2  = where * 8 + 4;
                    int rightwhere2 = where * 8 + 5;

                    // forcing vertical extents to be the same for all, because text looks *really weird* in english if lines have different heights
                    // for digits, forcing horizontal extents to be the same as well so they look nice in vertical lists
                    bool isDigit = ch == '0' || ch == '1' || ch == '2' || ch == '3' || ch == '4' || ch == '5' || ch == '6' || ch == '7' || ch == '8' || ch == '9';
                    if (isDigit)
                    {
                        metrics[leftwhere]  = ch == '1' ? (byte)(6) : ch == '2' ? (byte)(6) : (byte)(7);
                        metrics[rightwhere] = ch == '1' ? (byte)(8) : ch == '2' ? (byte)(8) : (byte)(7);
                        //metrics[leftwhere2] = ch == '1' ? (byte)(7) : ch == '2' ? (byte)(7) : (byte)(8);
                        //metrics[rightwhere2] = ch == '1' ? (byte)(9) : ch == '2' ? (byte)(9) : (byte)(8);
                    }
                    else
                    {
                        metrics[leftwhere]  = (byte)MeasureAlphaFromLeft(averagescaled, cutoff_1);
                        metrics[rightwhere] = (byte)MeasureAlphaFromRight(averagescaled, cutoff_1);
                        //metrics[leftwhere2] = (byte)MeasureAlphaFromLeft(averagescaled, cutoff_2);
                        //metrics[rightwhere2] = (byte)MeasureAlphaFromRight(averagescaled, cutoff_2);
                    }

                    switch (ch)
                    {
                    case 'j':
                    case ';':
                        metrics[leftwhere] += 1;
                        break;

                    case 'A':
                    case 'P':
                    case 'Q':
                    case 'T':
                    case 'Y':
                    case 'W':
                    case 'f':
                    case 's':
                    case 'w':
                    case 'y':
                    case '[':
                        metrics[rightwhere] += 1;
                        break;

                    case 't':
                        metrics[leftwhere]  += 1;
                        metrics[rightwhere] += 1;
                        break;

                    default:
                        break;
                    }

                    metrics[leftwhere2]  = metrics[leftwhere];
                    metrics[rightwhere2] = metrics[rightwhere];

                    switch (ch)
                    {
                    case '.':
                    case ',':
                        metrics[leftwhere2] += 1;
                        metrics[rightwhere] += 1;
                        break;

                    default:
                        break;
                    }

                    metrics[where * 8 + 2] = 0;                     //(byte)MeasureAlphaFromTop(test, cutoff_1);
                    metrics[where * 8 + 3] = 4;                     //(byte)MeasureAlphaFromBottom(test, cutoff_1);
                    metrics[where * 8 + 6] = 0;                     //(byte)MeasureAlphaFromTop(test, cutoff_2);
                    metrics[where * 8 + 7] = 4;                     //(byte)MeasureAlphaFromBottom(test, cutoff_2);

                    int width1 = tile_extent_actual - (metrics[leftwhere] + metrics[rightwhere]);
                    int width2 = tile_extent_actual - (metrics[leftwhere2] + metrics[rightwhere2]);
                    charToWidthMap.Add(ch, (width1, width2));

                    ++where;
                    ++ps3where;
                }
            }

            // manually generate good metrics for space; see also code patches in main.dol
            metrics[0x6F70] = 10;
            metrics[0x6F71] = 10;
            metrics[0x6F72] = 0;
            metrics[0x6F73] = 4;
            metrics[0x6F74] = 10;
            metrics[0x6F75] = 10;
            metrics[0x6F76] = 0;
            metrics[0x6F77] = 4;
            charToWidthMap.Add(' ', (tile_extent_actual - 20, tile_extent_actual - 20));

            // write out visual representation of font metrics for adjustments
            if (adjustMetrics)
            {
                foreach (var charset in charsets)
                {
                    int where = charset.where;
                    foreach (char ch in charset.chars)
                    {
                        int factor = 10;
                        var test   = PointScale(tiles_wii[where], factor);

                        for (int metricsset = 0; metricsset < 2; ++metricsset)
                        {
                            Color col = metricsset == 0 ? Color.Red : Color.Yellow;
                            int   xl  = (metrics[where * 8 + metricsset * 4] - 1) * factor + factor - 1;
                            int   xr  = (tiles_wii[where].Width - metrics[where * 8 + metricsset * 4 + 1]) * factor;
                            int   yt  = (metrics[where * 8 + metricsset * 4 + 2] - 1) * factor + factor - 1;
                            int   yb  = (tiles_wii[where].Width - metrics[where * 8 + metricsset * 4 + 3]) * factor;
                            for (int y = 0; y < test.Height; ++y)
                            {
                                if (xl >= 0 && xl < test.Width)
                                {
                                    test.SetPixel(xl, y, col);
                                }
                                if (xr >= 0 && xr < test.Width)
                                {
                                    test.SetPixel(xr, y, col);
                                }
                            }
                            for (int x = 0; x < test.Width; ++x)
                            {
                                if (yt >= 0 && yt < test.Height)
                                {
                                    test.SetPixel(x, yt, col);
                                }
                                if (yb >= 0 && yb < test.Height)
                                {
                                    test.SetPixel(x, yb, col);
                                }
                            }
                        }

                        PointScale(test, 3).Save(Path.Combine(config.DebugFontOutputPath, string.Format("metrics_view_{0:D4}.png", where)));

                        ++where;
                    }
                }
            }

            // join indvidiual tiles back into full texture
            int idx = 0;

            for (int ty = 0; ty < tiles_y; ++ty)
            {
                for (int tx = 0; tx < tiles_x; ++tx)
                {
                    var bmp = tiles_wii[idx];
                    for (int y = 0; y < tile_extent_actual; ++y)
                    {
                        for (int x = 0; x < tile_extent_actual; ++x)
                        {
                            var px = bmp.GetPixel(x, y);
                            img_wii.SetPixel(tx * tile_extent_in_image + x, ty * tile_extent_in_image + y, px);
                        }
                    }
                    ++idx;
                }
            }

            if (debug)
            {
                img_wii.Save(Path.Combine(config.DebugFontOutputPath, "wii_new.png"));
            }

            // inject metrics
            DuplicatableStream outputMetricsStream;
            {
                Stream stream = metricsWiiStream.Duplicate().CopyToMemory();
                stream.Position = 0x43E0;
                stream.Write(metrics);

                stream.Position = 0;
                byte[] data = new byte[stream.Length];
                stream.Read(data, 0, data.Length);
                outputMetricsStream = new HyoutaUtils.Streams.DuplicatableByteArrayStream(data);
            }

            // encode texture
            DuplicatableStream outputTextureStream;

            {
                Stream stream = textureWiiStream.Duplicate().CopyToMemory();
                stream.Position = 0x80100;
                List <(int idx, ushort v)> stuff = new List <(int idx, ushort v)>();
                for (int i = 0; i < 16; ++i)
                {
                    stuff.Add((i, stream.ReadUInt16().FromEndian(EndianUtils.Endianness.BigEndian)));
                }

                stream.Position = 0x100;
                var  pxit    = new HyoutaTools.Textures.PixelOrderIterators.TiledPixelOrderIterator(img_wii.Width, img_wii.Height, 8, 8);
                byte storage = 0;
                bool even    = false;
                foreach (var px in pxit)
                {
                    if (px.X < img_wii.Width && px.Y < img_wii.Height)
                    {
                        Color  col    = img_wii.GetPixel(px.X, px.Y);
                        ushort value  = HyoutaTools.Textures.ColorFetchingIterators.ColorFetcherGrey8Alpha8.ColorToGrey8Alpha8(col);
                        var    colidx = stuff.First(x => x.v == value).idx;
                        if (!even)
                        {
                            storage = (byte)colidx;
                        }
                        else
                        {
                            storage = (byte)(storage << 4 | (byte)colidx);
                            stream.WriteByte(storage);
                        }
                        even = !even;
                    }
                }

                stream.Position = 0;
                byte[] data = new byte[stream.Length];
                stream.Read(data, 0, data.Length);
                outputTextureStream = new HyoutaUtils.Streams.DuplicatableByteArrayStream(data);
            }

            return(outputMetricsStream, outputTextureStream, charToWidthMap);
        }