Example #1
0
        void WritePalette(CppWriter writer, Color[] palette)
        {
            for (int i = 0; i < palette.Length; ++i)
            {
                Color  colour   = palette[i];
                UInt16 rbgColor = PaletteHelper.ToRgb16(colour);

                writer.Write(rbgColor);
            }
        }
Example #2
0
        List <UInt32> WriteSpriteData(
            Bitmap bitmap,
            Color[] palette,
            int width,
            int height,
            int xPos,
            int yPos,
            Compression.CompressionType compressionType,
            uint destBpp)
        {
            int        tilesWide             = width / TileConfig.TileWidth;
            int        tilesTall             = height / TileConfig.TileHeight;
            int        totalTiles            = (width * height) / (TileConfig.TileWidth * TileConfig.TileHeight);
            List <int> colourPaletteIndicies = new List <int>();

            for (int tileY = 0; tileY < tilesTall; ++tileY)
            {
                for (int tileX = 0; tileX < tilesWide; ++tileX)
                {
                    int tileXOffset = xPos + tileX * TileConfig.TileWidth;
                    int tileYOffset = yPos + tileY * TileConfig.TileHeight;

                    for (int y = 0; y < TileConfig.TileHeight; ++y)
                    {
                        for (int x = 0; x < TileConfig.TileWidth; ++x)
                        {
                            Color color = bitmap.GetPixel(tileXOffset + x, tileYOffset + y);
                            int   index = PaletteHelper.ColorToPaletteIndex(palette, color);
                            colourPaletteIndicies.Add(index);
                        }
                    }
                }
            }

            List <UInt32> compressedIndicies;

            switch (compressionType)
            {
            case Compression.CompressionType.BitPacked:
            {
                Compression.BitPack(colourPaletteIndicies, destBpp, out compressedIndicies);
            }
            break;

            default:
                throw new NotImplementedException();
            }

            return(compressedIndicies);
        }
        static Tile GetTile(Bitmap bitmap, Color[] palette, int x, int y)
        {
            Tile tile = new Tile();

            for (int pixX = x; pixX < x + TileConfig.TileWidth; ++pixX)
            {
                for (int pixY = y; pixY < y + TileConfig.TileHeight; ++pixY)
                {
                    Color color = bitmap.GetPixel(pixX, pixY);
                    tile.paletteIndicies[pixX - x, pixY - y] = PaletteHelper.ColorToPaletteIndex(palette, color);
                }
            }

            return(tile);
        }
        void WritePalette(Color[] palette, StringBuilder sb)
        {
            sb.Append(VAR_PALLET);
            sb.Append(namespaceTabs + "{\n\t" + namespaceTabs);
            for (int i = 0; i < palette.Length; ++i)
            {
                Color  color    = palette[i];
                UInt16 rbgColor = (UInt16)(PaletteHelper.ScaleToRgb16(color.R) + (PaletteHelper.ScaleToRgb16(color.G) << 5) + (PaletteHelper.ScaleToRgb16(color.B) << 10));

                sb.AppendFormat("0x{0:X4}, ", rbgColor);
                if ((i + 1) % c_arrayNewlineCount == 0)
                {
                    sb.Append("\n\t" + namespaceTabs);
                }
            }
            sb.Append("\n" + namespaceTabs + "};\n\n");
        }
        /// <param name="inputPath">Input file path, namespace name is derived from this</param>
        /// <param name="outputPath">Output cpp file</param>
        /// <param name="bitmaps">List of image files to map tilemaps from</param>
        /// <param name="paletteBankIndexOffset">Allows us to force things like UI palettes into the back of the palette bank</param>
        public void Convert(string inputPath, string outputPath, IList <Bitmap> bitmaps, byte paletteBankIndexOffset = 0)
        {
            CppWriter cppWriter = new CppWriter(Path.GetFileName(Path.GetFileNameWithoutExtension(inputPath)), outputPath);

            Console.WriteLine("Processing master palette");

            Color[]   masterPalette;
            Tile[]    masterTileSet;
            TileMap[] tileMapList;

            GenerateTileMaps(bitmaps, paletteBankIndexOffset, out masterPalette, out masterTileSet, out tileMapList);
            GBAMapData gbaMapData = GenerateMapData(tileMapList, paletteBankIndexOffset);

            const int maxMaps = sizeof(byte) * 8;

            if (gbaMapData.mapIsDynamic.Count >= maxMaps)
            {
                // Can't store this in the current bitmask size
                throw new Exception(string.Format("Too many tilemaps found. Maps = {0}, max is {1}. Consider increasing the bitmask of \"mapIsDynamicBitMask\".", gbaMapData.mapIsDynamic.Count, maxMaps));
            }

            Console.WriteLine("Total unique tiles = " + masterTileSet.Length);
            Console.WriteLine("Processing tilemaps");

            Compression.CompressionType compressionType = Compression.CompressionType.BitPacked;
            uint destBpp = CalculateDestBitsPerPixel(masterTileSet);

            List <UInt32> tileSetData = GenerateTileSetData(masterTileSet, compressionType, destBpp);

            // Write palette
            {
                cppWriter.Write(paletteBankIndexOffset);
                cppWriter.Write((byte)masterPalette.Length);

                for (int i = 0; i < masterPalette.Length; ++i)
                {
                    Color  colour   = masterPalette[i];
                    UInt16 rbgColor = PaletteHelper.ToRgb16(colour);
                    cppWriter.Write(rbgColor);
                }
            }

            // Write tileset
            {
                // Compression flags
                UInt32 compressionTypeSize = Compression.ToGBACompressionHeader(compressionType, destBpp);
                cppWriter.Write(compressionTypeSize);

                // Actual data
                cppWriter.Write((UInt32)tileSetData.Count);
                for (int i = 0; i < tileSetData.Count; ++i)
                {
                    cppWriter.Write(tileSetData[i]);
                }
            }

            // Write tile maps
            {
                cppWriter.Write((byte)tileMapList.Length);
                cppWriter.Write((UInt32)gbaMapData.screenEntries.Length);

                byte mapIsDynamicBitMask = BoolListToBitmaskU8(gbaMapData.mapIsDynamic);
                cppWriter.Write(mapIsDynamicBitMask);

                // Width and height arrays
                foreach (int width in gbaMapData.widthLists)
                {
                    cppWriter.Write((byte)width);
                }

                foreach (int height in gbaMapData.heightLists)
                {
                    cppWriter.Write((byte)height);
                }

                foreach (var mapEntry in gbaMapData.screenEntries)
                {
                    cppWriter.Write(mapEntry.m_data);
                }
            }

            Console.WriteLine("Tilemap \"" + outputPath + "\" successfully converted");

            cppWriter.Finalise();
        }
        public static void GenerateTileMaps(
            IList <Bitmap> bitmaps
            , byte paletteBankIndexOffset
            , out Color[] masterPalette
            , out Tile[] masterTileSet
            , out TileMap[] tileMaps
            , bool skipSort = false
            )
        {
            List <ProcessedPaletteContainer> bitmapPalettes = new List <ProcessedPaletteContainer>();

            // Validate bitmaps and collect palettes
            for (int bitmapIndex = 0; bitmapIndex < bitmaps.Count; ++bitmapIndex)
            {
                Bitmap bitmap = bitmaps[bitmapIndex];

                Size size = bitmap.Size;

                if (size.Width % TileConfig.TileWidth != 0 || size.Height % TileConfig.TileHeight != 0)
                {
                    throw new Exception("Size not compatible with GBA tiles. Width and height of pixels must be multiples of 8.");
                }

                int     xStart = 0;
                int     yStart = 0;
                int     width  = bitmap.Width;
                int     height = bitmap.Height;
                Color[] preProcessedPalette = PaletteHelper.GeneratePaletteFromImage(bitmap, xStart, yStart, width, height);

                // Validate pallet length
                {
                    if (preProcessedPalette.Length > PaletteHelper.MAX_PALETTE_LENGTH)
                    {
                        throw new Exception(string.Format("Palette length ({0}) out of range for the GBA ({1})", preProcessedPalette.Length, PaletteHelper.MAX_PALETTE_LENGTH));
                    }
                }

                ProcessedPaletteContainer palContainer = new ProcessedPaletteContainer();
                palContainer.bitmapIndex = bitmapIndex;
                palContainer.palette     = preProcessedPalette;

                bitmapPalettes.Add(palContainer);
            }

            if (!skipSort)
            {
                // Sort bitmap palette. Anything more than 16 colours should be grouped next to each other. Less than 16 should always be a full 16.
                bitmapPalettes.Sort(new ProcessedPaletteComparer());
            }

            // Get palettes of each image, merge into a master palette
            List <Color> masterPaletteList = new List <Color>();
            List <ProcessedBitmapContainer> processedBitmapList = new List <ProcessedBitmapContainer>();

            {
                Color transparencyColour = Color.FromArgb(0);

                for (int i = 0; i < bitmapPalettes.Count; ++i)
                {
                    var bp = bitmapPalettes[i];

                    Debug.Assert(bp.palette[0] == transparencyColour);

                    bool pal4bbp      = bp.palette.Length <= PaletteHelper.PALETTE_LENGTH_4BBP;
                    int  paletteIndex = pal4bbp ? Find4bbpPaletteIndex(masterPaletteList, bp.palette) : -1;

                    if (pal4bbp && paletteIndex < 0)    // Add the new palette in
                    {
                        // Make sure our 4bbp palette is aligned properly, every set of 16 colour palettes starts with pure transparency
                        while (masterPaletteList.Count % PaletteHelper.PALETTE_LENGTH_4BBP != 0)
                        {
                            masterPaletteList.Add(transparencyColour);
                        }

                        // TODO
                        // If 2 bitmap palettes can fit in the same GBA palette, merge them into one.
                        paletteIndex = Get4bbpPaletteIndexForSize(masterPaletteList);

                        masterPaletteList.AddRange(bp.palette);
                    }

                    if (!pal4bbp)
                    {
                        // Only add colour into the master palette if it's not already in there.
                        foreach (Color col in bp.palette)
                        {
                            if (!masterPaletteList.Contains(col))
                            {
                                masterPaletteList.Add(col);
                            }
                        }
                    }

                    if ((paletteIndex + paletteBankIndexOffset) >= PaletteHelper.MAX_PALETTE_INDEX)
                    {
                        throw new Exception(string.Format("Palette index {0} with palette bank offset {1} is not valid for map data", paletteIndex, paletteBankIndexOffset));
                    }

                    ProcessedBitmapContainer pbc = new ProcessedBitmapContainer();
                    pbc.bitmapIndex  = bp.bitmapIndex;
                    pbc.paletteIndex = paletteIndex;
                    pbc.bitmap       = bitmaps[bp.bitmapIndex];

                    processedBitmapList.Add(pbc);
                }
            }

            masterPalette = masterPaletteList.ToArray();

            Console.WriteLine("Total colours found = " + masterPalette.Length);

            if (masterPalette.Length >= PaletteHelper.MAX_PALETTE_LENGTH)
            {
                throw new Exception(string.Format("Master palette has too many colours ({0}/{1})", masterPalette.Length, PaletteHelper.MAX_PALETTE_LENGTH));
            }

            Console.WriteLine("Processing master tileset");

            // Get all unique tiles sorted via palette indicies. Need to check if local palette was greater than 16 or not
            List <Tile> uniqueTileSetList = new List <Tile>();

            foreach (ProcessedBitmapContainer pbc in processedBitmapList)
            {
                Tile[,] rawTileMap = BitmapToTileArray(pbc.bitmap, masterPalette, pbc.paletteIndex);
                pbc.rawTileMap     = rawTileMap;

                FillUniqueTileset(rawTileMap, uniqueTileSetList);
            }

            masterTileSet = uniqueTileSetList.ToArray();

            // Generate tilemaps from master list. Remember palette index, but we should be able to forget about it. We can overlap tiles that are the same but different colours.
            List <TileMap> tileMapList = new List <TileMap>();

            foreach (ProcessedBitmapContainer pbc in processedBitmapList)
            {
                TileMap tilemap = new TileMap(pbc.rawTileMap, masterTileSet, pbc.paletteIndex);
                tileMapList.Add(tilemap);
            }

            tileMaps = tileMapList.ToArray();
        }
        public void Convert(string inputPath, string outputPath, Bitmap bitmap, UVs[] sliceCoordinates)
        {
            string namespaceName = string.Format(NAMESPACE_FORMAT, Path.GetFileName(Path.GetFileNameWithoutExtension(inputPath)));

            StringBuilder sb   = new StringBuilder();
            Size          size = bitmap.Size;

            if (size.Width % TileConfig.c_TILEWIDTH != 0 || size.Height % TileConfig.c_TILEHEIGHT != 0)
            {
                throw new Exception("Size not compatible with GBA tiles. Width and height of pixels must be multiples of 8.");
            }

            Console.WriteLine("Processing colour palette");

            int xStart = 0;
            int yStart = 0;
            int width  = bitmap.Width;
            int height = bitmap.Height;

            Color[] preProcessedPalette = PaletteHelper.GeneratePaletteFromImage(bitmap, xStart, yStart, width, height);

            // Validate pallet length
            {
                if (preProcessedPalette.Length > 256)
                {
                    throw new Exception("Palette length out of range for the GBA");
                }

                // Todo, currently not supported
                if (preProcessedPalette.Length > 16)
                {
                    throw new Exception("Palette length out of range for 4bbp format");
                }
            }

            Compression.CompressionType compressionType = Compression.CompressionType.BitPacked;
            uint maxColours = MathX.IsPowerOf2((uint)preProcessedPalette.Length) ? (uint)preProcessedPalette.Length : MathX.NextPowerOf2((uint)preProcessedPalette.Length);
            uint destBpp    = (uint)MathX.IndexOfHighestSetBit(maxColours);

            if (!MathX.IsPowerOf2(destBpp))
            {
                destBpp = MathX.NextPowerOf2(destBpp);
            }

            Console.WriteLine(string.Format("Compression type = {0}", compressionType.ToString()));
            Console.WriteLine(string.Format("Destination Bit Depth = {0}", destBpp));

            List <int>           dataOffsets = new List <int>();
            List <StringBuilder> spriteData  = new List <StringBuilder>();

            dataOffsets.Add(0);
            int totalData = 0;

            // Collect data and add offsets
            {
                Console.WriteLine("Processing sprite data");

                for (int i = 0; i < sliceCoordinates.Length; ++i)
                {
                    StringBuilder dataSb = new StringBuilder();
                    UVs           slice = sliceCoordinates[i];
                    int           spriteWidth = slice.width, spriteHeight = slice.height;
                    int           dataCount = WriteSpriteData(dataSb, bitmap, preProcessedPalette, spriteWidth, spriteHeight, slice.x, slice.y, compressionType, destBpp);

                    spriteData.Add(dataSb);

                    // Add offsets
                    if (i < sliceCoordinates.Length - 1)
                    {
                        dataOffsets.Add(dataOffsets[i] + dataCount);
                    }

                    totalData += dataCount;
                }
            }

            sb.Append(MACRO_DEFINES);
            sb.Append(namespaceName);

            WriteHeader(sliceCoordinates, preProcessedPalette, totalData, compressionType, destBpp, sb);
            WritePalette(preProcessedPalette, sb);

            // Write width
            {
                sb.Append(VAR_WIDTHMAP);
                sb.Append(namespaceTabs + "{\n");
                sb.Append(namespaceTabs + TAB_CHAR);

                for (int i = 0; i < sliceCoordinates.Length; ++i)
                {
                    int spriteWidth = sliceCoordinates[i].width;
                    sb.AppendFormat("{0}, ", spriteWidth);
                    if ((i + 1) % c_arrayNewlineCount == 0)
                    {
                        sb.AppendFormat("\n" + namespaceTabs + TAB_CHAR);
                    }
                }

                sb.Append("\n");
                sb.Append(namespaceTabs + "};\n\n");
            }

            // Write height
            {
                sb.Append(VAR_HEIGHTMAP);
                sb.Append(namespaceTabs + "{\n");
                sb.Append(namespaceTabs + TAB_CHAR);

                for (int i = 0; i < sliceCoordinates.Length; ++i)
                {
                    int spriteHeight = sliceCoordinates[i].height;   // Todo
                    sb.AppendFormat("{0}, ", spriteHeight);
                    if ((i + 1) % c_arrayNewlineCount == 0)
                    {
                        sb.AppendFormat("\n" + namespaceTabs + TAB_CHAR);
                    }
                }

                sb.Append("\n");
                sb.Append(namespaceTabs + "};\n\n");
            }

            // Write offsets
            {
                const string tabs = namespaceTabs + TAB_CHAR;

                sb.Append(VAR_OFFSETS);
                sb.Append(namespaceTabs + "{\n" + tabs);
                for (int i = 0; i < dataOffsets.Count; i++)
                {
                    sb.AppendFormat("{0}, ", dataOffsets[i]);

                    if ((i + 1) % c_arrayNewlineCount == 0)
                    {
                        sb.AppendFormat("\n" + tabs);
                    }
                }

                sb.Append('\n' + namespaceTabs + "};\n\n");
            }

            // Write data
            {
                sb.Append(VAR_DATA);
                sb.Append(namespaceTabs + "{\n");

                foreach (StringBuilder dataSB in spriteData)
                {
                    sb.Append(dataSB.ToString());
                }

                sb.Append(namespaceTabs + "};\n\n");
            }

            sb.Append("}\n");

            Console.WriteLine("Sprite \"" + outputPath + "\" successfully converted");
            File.WriteAllText(outputPath, sb.ToString());
        }
        int WriteSpriteData(
            StringBuilder sbOutput,
            Bitmap bitmap,
            Color[] palette,
            int width,
            int height,
            int xPos,
            int yPos,
            Compression.CompressionType compressionType,
            uint destBpp)
        {
            const string tabs = namespaceTabs;

            int        tilesWide             = width / TileConfig.c_TILEWIDTH;
            int        tilesTall             = height / TileConfig.c_TILEHEIGHT;
            int        totalTiles            = (width * height) / (TileConfig.c_TILEWIDTH * TileConfig.c_TILEHEIGHT);
            int        hexCount              = 0;
            List <int> colourPaletteIndicies = new List <int>();

            sbOutput.Append(namespaceTabs + TAB_CHAR);

            for (int tileY = 0; tileY < tilesTall; ++tileY)
            {
                for (int tileX = 0; tileX < tilesWide; ++tileX)
                {
                    int tileXOffset = xPos + tileX * TileConfig.c_TILEWIDTH;
                    int tileYOffset = yPos + tileY * TileConfig.c_TILEHEIGHT;

                    for (int y = 0; y < TileConfig.c_TILEHEIGHT; ++y)
                    {
                        for (int x = 0; x < TileConfig.c_TILEWIDTH; ++x)
                        {
                            Color color = bitmap.GetPixel(tileXOffset + x, tileYOffset + y);
                            int   index = PaletteHelper.ColorToPaletteIndex(palette, color);
                            colourPaletteIndicies.Add(index);
                        }
                    }
                }
            }

            List <UInt32> compressedIndicies;

            switch (compressionType)
            {
            case Compression.CompressionType.BitPacked:
            {
                Compression.BitPack(colourPaletteIndicies, destBpp, out compressedIndicies);
            }
            break;

            default:
                throw new NotImplementedException();
            }

            foreach (var hexNum in compressedIndicies)
            {
                sbOutput.AppendFormat("0x{0:X8}, ", hexNum);
                if ((hexCount + 1) % c_arrayNewlineCount == 0)
                {
                    sbOutput.Append("\n\t" + tabs);
                }
                ++hexCount;
            }

            sbOutput.Append("\n\n");

            return(hexCount);
        }
Example #9
0
        public void Convert(string inputPath, string outputPath, Bitmap bitmap, UVs[] sliceCoordinates)
        {
            CppWriter cppWriter = new CppWriter(Path.GetFileName(Path.GetFileNameWithoutExtension(inputPath)), outputPath);

            Size size = bitmap.Size;

            if (size.Width % TileConfig.TileWidth != 0 || size.Height % TileConfig.TileHeight != 0)
            {
                throw new Exception("Size not compatible with GBA tiles. Width and height of pixels must be multiples of 8.");
            }

            Console.WriteLine("Processing colour palette");

            int xStart = 0;
            int yStart = 0;
            int width  = bitmap.Width;
            int height = bitmap.Height;

            Color[] preProcessedPalette = PaletteHelper.GeneratePaletteFromImage(bitmap, xStart, yStart, width, height);

            // Validate pallet length
            {
                if (preProcessedPalette.Length > 256)
                {
                    throw new Exception("Palette length out of range for the GBA");
                }

                // Todo, currently not supported
                if (preProcessedPalette.Length > 16)
                {
                    throw new Exception("Palette length out of range for 4bbp format");
                }
            }

            Console.WriteLine(String.Format("Palette length: {0}", preProcessedPalette.Length));

            Compression.CompressionType compressionType = Compression.CompressionType.BitPacked;
            uint maxColours = MathX.IsPowerOf2((uint)preProcessedPalette.Length) ? (uint)preProcessedPalette.Length : MathX.NextPowerOf2((uint)preProcessedPalette.Length);
            uint destBpp    = (uint)MathX.IndexOfHighestSetBit(maxColours);

            if (!MathX.IsPowerOf2(destBpp))
            {
                destBpp = MathX.NextPowerOf2(destBpp);
            }

            Console.WriteLine(string.Format("Compression type = {0}", compressionType.ToString()));
            Console.WriteLine(string.Format("Destination Bit Depth = {0}", destBpp));

            List <int>            dataOffsets = new List <int>();
            List <List <UInt32> > spriteData  = new List <List <UInt32> >();

            dataOffsets.Add(0);
            int totalData = 0;

            // Collect data and add offsets
            {
                Console.WriteLine("Processing sprite data");

                for (int i = 0; i < sliceCoordinates.Length; ++i)
                {
                    StringBuilder dataSb = new StringBuilder();
                    UVs           slice = sliceCoordinates[i];
                    int           spriteWidth = slice.width, spriteHeight = slice.height;
                    List <UInt32> data = WriteSpriteData(bitmap, preProcessedPalette, spriteWidth, spriteHeight, slice.x, slice.y, compressionType, destBpp);

                    spriteData.Add(data);

                    // Add offsets
                    if (i < sliceCoordinates.Length - 1)
                    {
                        dataOffsets.Add(dataOffsets[i] + data.Count);
                    }

                    totalData += data.Count;
                }
            }

            WriteHeader(cppWriter, sliceCoordinates, preProcessedPalette, totalData, compressionType, destBpp);
            WritePalette(cppWriter, preProcessedPalette);

            // Write width
            {
                for (int i = 0; i < sliceCoordinates.Length; ++i)
                {
                    int spriteWidth = sliceCoordinates[i].width;
                    cppWriter.Write((byte)spriteWidth);
                }
            }

            // Write height
            {
                for (int i = 0; i < sliceCoordinates.Length; ++i)
                {
                    int spriteHeight = sliceCoordinates[i].height;
                    cppWriter.Write((byte)spriteHeight);
                }
            }

            // Write offsets
            {
                for (int i = 0; i < dataOffsets.Count; i++)
                {
                    cppWriter.Write((UInt32)dataOffsets[i]);
                }
            }

            // Write data
            {
                foreach (var dataList in spriteData)
                {
                    foreach (UInt32 data in dataList)
                    {
                        cppWriter.Write((UInt32)data);
                    }
                }
            }

            Console.WriteLine("Sprite \"" + outputPath + "\" successfully converted");

            cppWriter.Finalise();
        }
        public void Convert(string inputPath, string outputPath, Bitmap inputBitmap)
        {
            string namespaceName = string.Format(NAMESPACE_FORMAT, Path.GetFileName(Path.GetFileNameWithoutExtension(inputPath)));

            List <Bitmap> bitmaps = new List <Bitmap>();

            bitmaps.Add(inputBitmap);

            List <ProcessedPaletteContainer> bitmapPalettes = new List <ProcessedPaletteContainer>();

            Console.WriteLine("Processing master palette");

            // Validate bitmaps and collect palettes
            for (int bitmapIndex = 0; bitmapIndex < bitmaps.Count; ++bitmapIndex)
            {
                Bitmap bitmap = bitmaps[bitmapIndex];

                Size size = bitmap.Size;
                if (size.Width % TileConfig.c_TILEWIDTH != 0 || size.Height % TileConfig.c_TILEHEIGHT != 0)
                {
                    throw new Exception("Size not compatible with GBA tiles. Width and height of pixels must be multiples of 8.");
                }

                int     xStart = 0;
                int     yStart = 0;
                int     width  = bitmap.Width;
                int     height = bitmap.Height;
                Color[] preProcessedPalette = PaletteHelper.GeneratePaletteFromImage(bitmap, xStart, yStart, width, height);

                // Validate pallet length
                {
                    if (preProcessedPalette.Length > PaletteHelper.MAX_PALETTE_LENGTH)
                    {
                        throw new Exception(string.Format("Palette length ({0}) out of range for the GBA ({1})", preProcessedPalette.Length, PaletteHelper.MAX_PALETTE_LENGTH));
                    }
                }

                ProcessedPaletteContainer palContainer = new ProcessedPaletteContainer();
                palContainer.bitmapIndex = bitmapIndex;
                palContainer.palette     = preProcessedPalette;

                bitmapPalettes.Add(palContainer);
            }

            // Sort bitmap palette. Anything more than 16 colours should be grouped next to each other. Less than 16 should always be a full 16.
            bitmapPalettes.Sort(new ProcessedPaletteComparer());

            // Get palettes of each image, merge into a master palette
            List <Color> masterPaletteList = new List <Color>();
            List <ProcessedBitmapContainer> processedBitmapList = new List <ProcessedBitmapContainer>();

            {
                Color transparencyColour = Color.FromArgb(0);

                for (int i = 0; i < bitmapPalettes.Count; ++i)
                {
                    var bp = bitmapPalettes[i];

                    bool pal4bbp = bp.palette.Length <= PaletteHelper.PALETTE_LENGTH_4BBP;
                    if (pal4bbp)
                    {
                        // Make sure our 4bbp palette align properly
                        while (masterPaletteList.Count % PaletteHelper.PALETTE_LENGTH_4BBP != 0)
                        {
                            masterPaletteList.Add(transparencyColour);
                        }
                    }

                    int currentPaletteIndex = masterPaletteList.Count / PaletteHelper.PALETTE_LENGTH_4BBP;
                    masterPaletteList.AddRange(bp.palette);

                    ProcessedBitmapContainer pbc = new ProcessedBitmapContainer();
                    pbc.bitmapIndex  = bp.bitmapIndex;
                    pbc.paletteIndex = pal4bbp ? currentPaletteIndex : -1;
                    pbc.bitmap       = bitmaps[bp.bitmapIndex];

                    processedBitmapList.Add(pbc);
                }
            }

            Color[] masterPalette = masterPaletteList.ToArray();

            Console.WriteLine("Total colours found = " + masterPalette.Length);

            Console.WriteLine("Processing master tileset");

            // Get all unique tiles sorted via palette indicies. Need to check if local palette was greater than 16 or not
            List <Tile> uniqueTileSetList = new List <Tile>();

            foreach (ProcessedBitmapContainer pbc in processedBitmapList)
            {
                Tile[,] rawTileMap = BitmapToTileArray(pbc.bitmap, masterPalette, pbc.paletteIndex);
                pbc.rawTileMap     = rawTileMap;

                FillUniqueTileset(rawTileMap, uniqueTileSetList);
            }

            Tile[] masterTileSet = uniqueTileSetList.ToArray();

            if (masterTileSet.Length > MAX_UNIQUE_TILES)
            {
                throw new Exception(string.Format("Too many tiles present in tilemap. Max tiles = {0}. Total titles found = {1}", MAX_UNIQUE_TILES, masterTileSet.Length));
            }

            Console.WriteLine("Total unique tiles = " + masterTileSet.Length);
            Console.WriteLine("Processing tilemaps");

            // Generate tilemaps from master list. Remember palette index, but we should be able to forget about it. We can overlap tiles that are the same but different colours.
            List <TileMap> tileMapList = new List <TileMap>();

            foreach (ProcessedBitmapContainer pbc in processedBitmapList)
            {
                TileMap tilemap = new TileMap(pbc.rawTileMap, masterTileSet, pbc.paletteIndex);
                tileMapList.Add(tilemap);
            }

            Compression.CompressionType compressionType = Compression.CompressionType.BitPacked;
            uint destBpp = 4;

            foreach (Tile tile in masterTileSet)
            {
                foreach (int index in tile.paletteIndicies)
                {
                    if (index >= PaletteHelper.PALETTE_LENGTH_4BBP)
                    {
                        destBpp = 8;
                        goto WriteMasterTileSetToSb;
                    }
                }
            }

WriteMasterTileSetToSb:

            int tilesetLength;
            StringBuilder tilesetSb = new StringBuilder();

            WriteTileSet(masterTileSet, tilesetSb, compressionType, destBpp, out tilesetLength);

            StringBuilder sb = new StringBuilder();

            sb.Append(MACRO_DEFINES);
            sb.Append(namespaceName);

            WriteHeader(masterPalette, tilesetLength, sb);
            WritePalette(masterPalette, sb);

            // Real write tileset
            sb.Append(tilesetSb);

            // Write tile maps
            WriteMapData(tileMapList, sb);

            sb.Append("}\n");

            Console.WriteLine("Tilemap \"" + outputPath + "\" successfully converted");
            File.WriteAllText(outputPath, sb.ToString());
        }