Ejemplo n.º 1
0
        /// <summary>
        /// Encodes RGBA5551 formatted data into the TKMK00 format. Uses a Huffman tree to store
        ///  color values that are added/subtracted/sometimes overwrite a predicted color for the
        ///  current pixel. Look up DPCM for a conceptual idea of what it's doing (the Huffman tree
        ///  stands in place for entropy coding.
        /// </summary>
        public static byte[] Encode(byte[] imageData, int width, int height, ushort alphaColor)
        {
            //ushort[] colors = new ushort[width * height]; //Color map that gets written to mid-conversion
            ushort[] colorsRef = new ushort[width * height]; //Color map that has all image data from the beginning
            byte[] colorChangeMap = new byte[width * height]; //
            byte[] predictedColorsGreen = new byte[width * height]; //Contains predicted green values for all pixels
            byte[] predictedColorsRed = new byte[width * height]; //Contains predicted red values for all pixels
            byte[] predictedColorsBlue = new byte[width * height]; //Contains predicted blue values for all pixels
            byte[] nearestIndeticalPixel = new byte[width * height]; //Used for finding pixels to copy the color down to, 0 - none, 1-5 - varying positions down one row
            int[] colorCounts = new int[32]; //Contains # of times each huffman tree value is used. This is for constructing the Huffman tree to be most efficient
            bool[] usedIdenticalColors = new bool[colorsRef.Length]; //True if the current pixel was copied from one a row up

            //First, convert all colors from 2-bytes into 1 ushort value (colorsRef), then calculate the predicted color values
            for (int row = 0; row < height; row++)
            {
                for (int col = 0; col < width; col++)
                {
                    int currentPixel = row * width + col;
                    colorsRef[currentPixel] = ByteHelper.ReadUShort(imageData, currentPixel * 2);

                    //Predicted colors
                    int actualGreen = ((colorsRef[currentPixel] & 0x7C0) >> 6);
                    int actualRed = ((colorsRef[currentPixel] & 0xF800) >> 11);
                    int actualBlue = ((colorsRef[currentPixel] & 0x3E) >> 1);

                    int rgbaTop = (row == 0 ? 0 : colorsRef[currentPixel - width]);
                    int rgbaLeft = (row == 0 && col == 0 ? 0 : colorsRef[currentPixel - 1]);

                    ushort greenTop = (byte)((rgbaTop & 0x7C0) >> 6);
                    ushort greenLeft = (byte)((rgbaLeft & 0x7C0) >> 6);
                    int greenPrediction = (greenTop + greenLeft) / 2;

                    //Use the change between the old & new green values to project expected
                    // values for the red & blue colors
                    predictedColorsGreen[currentPixel] = (byte)ReverseColorCombine(greenPrediction, actualGreen);

                    int greenChange = actualGreen - greenPrediction;

                    //Combine red values of the pixels to make our predicted pixel color
                    ushort redTop = (byte)((rgbaTop & 0xF800) >> 11);
                    ushort redLeft = (byte)((rgbaLeft & 0xF800) >> 11);
                    int redPrediction = greenChange + (redTop + redLeft) / 2;
                    redPrediction = Math.Max(0, Math.Min(0x1F, redPrediction)); //Keep between 0 and 0x1F

                    predictedColorsRed[currentPixel] = (byte)ReverseColorCombine(redPrediction, actualRed);

                    //Combine blue values of the pixels to make our predicted pixel color
                    ushort blueTop = (byte)((rgbaTop & 0x3E) >> 1);
                    ushort blueLeft = (byte)((rgbaLeft & 0x3E) >> 1);
                    int bluePrediction = greenChange + (blueTop + blueLeft) / 2;
                    bluePrediction = Math.Max(0, Math.Min(0x1F, bluePrediction)); //Keep between 0 and 0x1F

                    predictedColorsBlue[currentPixel] = (byte)ReverseColorCombine(bluePrediction, actualBlue);
                }
            }

            //Set up an rgbaBuffer to hold the last 0x40 color values used
            ushort[] rgbaBuffer = new ushort[0x40];
            for (int i = 0; i < 0x40; i++)
                rgbaBuffer[i] = 0xFF;

            ushort lastColor = 0; //Last color used

            //Set up the master writer & 8 channel command writers
            TKMK00CommandWriter masterWriter = new TKMK00CommandWriter();
            TKMK00CommandWriter[] channelWriters = new TKMK00CommandWriter[8];
            for (int i = 0; i < 8; i++)
                channelWriters[i] = new TKMK00CommandWriter();

            List<byte> huffmanValues = new List<byte>(); //Contains the huffman commands that will be written to channelWriters[0], but only
                                                         // after the Huffman tree is constructed.

            //Main loop for determining the TKMK00 commands
            for (int row = 0; row < height; row++)
            {
                for (int col = 0; col < width; col++)
                {
                    int currentPixel = row * width + col;

                    //If this pixel has been colored already, then no commands are necessary
                    if (usedIdenticalColors[currentPixel])
                    {
                        lastColor = colorsRef[currentPixel];
                        continue;
                    }

                    //Channel index is determined from the colorChangeMap
                    int channelIndex = colorChangeMap[currentPixel] + 1;

                    if (colorsRef[currentPixel] == lastColor)
                    {
                        channelWriters[channelIndex].Write(0, 1); //Write the 'last color' command to the channel writer
                    }
                    else
                    {
                        channelWriters[channelIndex].Write(1, 1); //Write the 'new color' command to the channel writer

                        if (rgbaBuffer.Contains(colorsRef[currentPixel]))
                        {
                            masterWriter.Write(0, 1); //Write the 'use existing color' command to the master writer
                            int index = 0;
                            for (; index < rgbaBuffer.Length; index++)
                            {
                                if (rgbaBuffer[index] == colorsRef[currentPixel]) break;
                            }

                            masterWriter.Write(index, 6); //Write the index of the existing color to the master writer

                            //Move the selected color to the front of the rgba buffer
                            for (int k = index; k > 0; k--)
                            {
                                rgbaBuffer[k] = rgbaBuffer[k - 1];
                            }
                            rgbaBuffer[0] = colorsRef[currentPixel];
                        }
                        else
                        {
                            masterWriter.Write(1, 1); //Write the 'new color' command to the master writer

                            //3 color commands
                            huffmanValues.Add(predictedColorsGreen[currentPixel]);
                            huffmanValues.Add(predictedColorsRed[currentPixel]);
                            huffmanValues.Add(predictedColorsBlue[currentPixel]);

                            //Update the color counts
                            colorCounts[predictedColorsGreen[currentPixel]]++;
                            colorCounts[predictedColorsRed[currentPixel]]++;
                            colorCounts[predictedColorsBlue[currentPixel]]++;

                            //Add the new color to the front of the buffer
                            for (int k = rgbaBuffer.Length - 1; k > 0; k--)
                            {
                                rgbaBuffer[k] = rgbaBuffer[k - 1];
                            }
                            rgbaBuffer[0] = colorsRef[currentPixel];
                        }

                        //Add nearby pixels to the colorChangeMap
                        bool hasLeftCol = (col != 0);
                        bool hasRightCol = (col < (width - 1));
                        bool has2RightCols = (col < (width - 2));
                        bool hasDownRow = (row < (height - 1));
                        bool has2DownRows = (row < (height - 2));

                        //Right 1
                        if (hasRightCol)
                            colorChangeMap[currentPixel + 1]++;
                        //Right 2
                        if (has2RightCols)
                            colorChangeMap[currentPixel + 2]++;
                        //Down 1 Left 1
                        if (hasDownRow && hasLeftCol)
                            colorChangeMap[currentPixel + width - 1]++;
                        //Down 1
                        if (hasDownRow)
                            colorChangeMap[currentPixel + width]++;
                        //Down 1 Right 1
                        if (hasDownRow && hasRightCol)
                            colorChangeMap[currentPixel + width + 1]++;
                        //Down 2
                        if (has2DownRows)
                            colorChangeMap[currentPixel + width * 2]++;

                        lastColor = colorsRef[currentPixel];

                        //Determine if the current pixel has a pixel beneath it that it can be copied to
                        nearestIndeticalPixel[currentPixel] = setNearestPixel(currentPixel, width, colorsRef, usedIdenticalColors);

                        if (nearestIndeticalPixel[currentPixel] == 0)
                        {
                            masterWriter.Write(0, 1); //Write the 'don't copy' command to the master writer
                        }
                        else
                        {
                            //There's still a chance that we won't actually copy the pixel down, so don't write the 'copy' command yet
                            bool hasntWrittenFirstCommand = true;

                            int pixelOffset = currentPixel; //Contains the new pixel location to copy into

                            //Loop here, to continue copying down until it hits the end of the chain.
                            while (nearestIndeticalPixel[pixelOffset] != 0)
                            {
                                int pixelValue = nearestIndeticalPixel[pixelOffset];
                                switch (pixelValue)
                                {
                                    case 1: //back 2
                                        pixelOffset -= 2;
                                        break;
                                    case 2: //back 1
                                        pixelOffset--;
                                        break;
                                    case 3://stay
                                        break;
                                    case 4://forward 1
                                        pixelOffset++;
                                        break;
                                    case 5://forward 2
                                        pixelOffset += 2;
                                        break;
                                }

                                pixelOffset += width; //Down one row

                                if (usedIdenticalColors[pixelOffset])
                                    break;//If the pixel in questinon has already been copied to, quit out of the loop
                                else
                                {
                                    usedIdenticalColors[pixelOffset] = true; //Mark this pixel as having been copied to

                                    if (hasntWrittenFirstCommand)
                                    {
                                        masterWriter.Write(1, 1); //Write the 'copy' command to the master writer
                                        hasntWrittenFirstCommand = false;
                                    }

                                    //Write the pixel offset commands to the master writer (see the decoding process for the
                                    // logic behind these numbers)
                                    switch (pixelValue)
                                    {
                                        case 1: //back 2
                                            masterWriter.Write(2, 4); //0010
                                            break;
                                        case 2: //back 1
                                            masterWriter.Write(1, 2); //01
                                            break;
                                        case 3://stay
                                            masterWriter.Write(2, 2); //10
                                            break;
                                        case 4://forward 1
                                            masterWriter.Write(3, 2); //11
                                            break;
                                        case 5://forward 2
                                            masterWriter.Write(3, 4); //0011
                                            break;
                                    }
                                }

                                nearestIndeticalPixel[pixelOffset] = setNearestPixel(pixelOffset, width, colorsRef, usedIdenticalColors);
                            }

                            if (hasntWrittenFirstCommand)
                            {
                                masterWriter.Write(0, 1); //Write the 'don't copy' command to the master writer
                            }
                            else
                            {
                                //Write the quit command to the master writer (see the decoding process for the
                                // logic behind this number)
                                masterWriter.Write(0, 3); //000
                            }
                        }
                    }
                }
            }

            //Post pixel loop, now we need to finalize and save all the data

            //Load the Huffman tree
            TKMK00HuffmanTreeNode headNode = ConstructTree(colorCounts);

            int[] huffmanTreeTraversalCommands = new int[0x20];
            int[] huffmanTreeTraversalBitCounts = new int[0x20];

            //Get the bits for each huffman value
            GetHuffmanTreeTraversalCommands(headNode, 0, 0, huffmanTreeTraversalCommands, huffmanTreeTraversalBitCounts);

            //Write in the Huffman tree here
            WriteHuffmanTree(headNode, channelWriters[0]);

            //Write the convertedHuffmanValues
            foreach(byte index in huffmanValues)
                channelWriters[0].Write(huffmanTreeTraversalCommands[index], huffmanTreeTraversalBitCounts[index]);

            //Here the writers should be good, finish and combine into a single byte[]
            masterWriter.Finish();
            foreach (TKMK00CommandWriter writer in channelWriters)
                writer.Finish();

            //Set up the header here
            TKMK00Header header = new TKMK00Header();
            int channelOffset = 0x2C + masterWriter.FinalData.Count;
            List<byte[]> channelDatas = new List<byte[]>();
            byte repeatEnabledMask = 0;
            for (int i = 0; i < 8; i++)
            {
                header.ChannelPointers[i] = channelOffset;
                bool useRepeat = channelWriters[i].BytesSavedInRepeat > 0;
                if (useRepeat)
                {
                    channelOffset += channelWriters[i].FinalRepeatingData.Count;
                    channelDatas.Add(channelWriters[i].FinalRepeatingData.ToArray());
                    repeatEnabledMask |= (byte)(1 << i);
                }
                else
                {
                    channelOffset += channelWriters[i].FinalData.Count;
                    channelDatas.Add(channelWriters[i].FinalData.ToArray());
                }
            }
            header.Width = (ushort)width;
            header.Height = (ushort)height;
            header.RepeatModeMask = repeatEnabledMask;
            header.Unknown = 0x0F;

            //Combine all the data together
            byte[] finalData = ByteHelper.CombineIntoBytes(header.GetAsBytes(), masterWriter.FinalData,
                channelDatas[0], channelDatas[1], channelDatas[2], channelDatas[3],
                channelDatas[4], channelDatas[5], channelDatas[6], channelDatas[7]);

            return finalData;
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Decodes the TKMK00 format into RGBA5551 formatted data. Uses a Huffman tree to store
        ///  color values that are added/subtracted/sometimes overwrite a predicted color for the
        ///  current pixel. Look up DPCM for a conceptual idea of what it's doing (the Huffman tree
        ///  stands in place for entropy coding.
        /// </summary>
        public static byte[] Decode(byte[] data, int tkmk00Offset, ushort alphaColor)
        {
            //Initialize the header & readers
            byte[] headerBytes = new byte[TKMK00Header.DataSize];
            Array.Copy(data, tkmk00Offset, headerBytes, 0, TKMK00Header.DataSize);
            TKMK00Header header = new TKMK00Header(headerBytes);
            TKMK00CommandReader masterReader = new TKMK00CommandReader(data, tkmk00Offset + TKMK00Header.DataSize, false);
            TKMK00CommandReader[] channelReaders = new TKMK00CommandReader[8];
            for(int i = 0; i < 8; i++)
                channelReaders[i] = new TKMK00CommandReader(data, tkmk00Offset + header.ChannelPointers[i], header.RepeatEnabledFor(i));

            //Set up the image data/buffers
            ushort[] rgbaBuffer = new ushort[0x40];
            for (int i = 0; i < 0x40; i++)
                rgbaBuffer[i] = 0xFF;
            byte[] colorChangeMap = new byte[header.Width * header.Height];
            byte[] imageData = new byte[header.Width * header.Height * 2];
            int pixelIndex = 0;

            //Set up the Huffman binary tree
            TKMK00HuffmanTreeNode headTreeNode = SetUpHuffmanTree(0x20, data, channelReaders[0]);

            ushort lastPixelColor = 0;

            //Iterate through each pixel in order left to right, top to bottom
            for (int row = 0; row < header.Height; row++)
            {
                for (int col = 0; col < header.Width; col++)
                {
                    //Look at the current pixel's color. If it's not empty, then it's already been
                    // set to its correct value, and we can skip to the next pixel
                    ushort currentPixelColor = ByteHelper.ReadUShort(imageData, pixelIndex * 2);

                    if (currentPixelColor != 0) //Color already exists
                    {
                        lastPixelColor = currentPixelColor;

                        //Test to make sure that the curent color is not the alpha value with the incorrect alpha channel value
                        ushort currentPixelWithoutAlpha = (ushort)(currentPixelColor & 0xFFFE);
                        if (currentPixelWithoutAlpha == alphaColor)
                        {
                            ByteHelper.WriteUShort(alphaColor, imageData, pixelIndex * 2);
                            lastPixelColor = alphaColor;
                        }

                        //Done, go to end of the loop
                    }
                    else
                    {
                        //Load up the channel reader that is associated with the given color change value in the
                        // colorChangeMap (low values = not much change around that pixel, high values = lots of change)
                        byte channelIndex = (byte)(colorChangeMap[pixelIndex] + 1);

                        int command = channelReaders[channelIndex].ReadBits(1); // 0 - Use the previous color, 1 - Use new color

                        if (command == 0)
                        {
                            ByteHelper.WriteUShort(lastPixelColor, imageData, pixelIndex * 2);
                            //End of this line
                        }
                        else
                        {
                            command = masterReader.ReadBits(1); // 1 - Create new RGBA, 0 - Use existing RGBA
                            if (command != 0)
                            {
                                //Load in the huffman values for the new green, red and blue. These are combined
                                // with a predicted pixel value for them later on.
                                int newGreen = RetrieveHuffmanTreeValue(data, headTreeNode, channelReaders[0]);
                                int newRed = RetrieveHuffmanTreeValue(data, headTreeNode, channelReaders[0]);
                                int newBlue = RetrieveHuffmanTreeValue(data, headTreeNode, channelReaders[0]);

                                //Retreive the pixel colors from the pixel above and the pixel to the left
                                ushort rgbaTop, rgbaLeft;

                                if (row != 0)
                                {
                                    rgbaTop = ByteHelper.ReadUShort(imageData, (pixelIndex - header.Width) * 2);
                                    rgbaLeft = ByteHelper.ReadUShort(imageData, (pixelIndex - 1) * 2);
                                }
                                else
                                {
                                    rgbaTop = 0;
                                    if (col != 0)
                                        rgbaLeft = ByteHelper.ReadUShort(imageData, (pixelIndex - 1) * 2);
                                    else
                                        rgbaLeft = 0;
                                }

                                //Combine green values of the pixels to make our predicted pixel color
                                ushort greenTop = (byte)((rgbaTop & 0x7C0) >> 6);
                                ushort greenLeft = (byte)((rgbaLeft & 0x7C0) >> 6);
                                int greenPrediction = (greenTop + greenLeft) / 2;

                                //Combine the prediction & huffman value to make the output color
                                ColorCombine(greenPrediction, ref newGreen);

                                //Use the change between the old & new green values to project expected
                                // values for the red & blue colors
                                int greenChange = newGreen - greenPrediction;

                                //Combine red values of the pixels to make our predicted pixel color
                                ushort redTop = (byte)((rgbaTop & 0xF800) >> 11);
                                ushort redLeft = (byte)((rgbaLeft & 0xF800) >> 11);
                                int redPrediction = greenChange + (redTop + redLeft) / 2;
                                redPrediction = Math.Max(0, Math.Min(0x1F, redPrediction)); //Keep between 0 and 0x1F

                                //Combine the prediction & huffman value to make the output color
                                ColorCombine(redPrediction, ref newRed);

                                //Combine blue values of the pixels to make our predicted pixel color
                                ushort blueTop = (byte)((rgbaTop & 0x3E) >> 1);
                                ushort blueLeft = (byte)((rgbaLeft & 0x3E) >> 1);
                                int bluePrediction = greenChange + (blueTop + blueLeft) / 2;
                                bluePrediction = Math.Max(0, Math.Min(0x1F, bluePrediction)); //Keep between 0 and 0x1F

                                //Combine the prediction & huffman value to make the output color
                                ColorCombine(bluePrediction, ref newBlue);

                                //Make the newpixel color
                                currentPixelColor = (ushort)((newRed << 11) | (newGreen << 6) | (newBlue << 1));
                                if (currentPixelColor != alphaColor) //Only transparent if it matches the transparency pixel
                                    currentPixelColor |= 0x1;

                                //Add to the front of the color buffer
                                for (int i = rgbaBuffer.Length - 1; i > 0; i--)
                                    rgbaBuffer[i] = rgbaBuffer[i - 1];
                                rgbaBuffer[0] = currentPixelColor;
                            }
                            else //Use existing RGBA
                            {
                                command = masterReader.ReadBits(6); // Returns index of color in color buffer to use

                                currentPixelColor = rgbaBuffer[command];
                                if (command != 0)
                                {
                                    //Bump the selected color to the front of the buffer
                                    for (int i = command; i > 0; i--)
                                        rgbaBuffer[i] = rgbaBuffer[i - 1];
                                    rgbaBuffer[0] = currentPixelColor;
                                }
                            }

                            //Write the RGBA to the imageData
                            ByteHelper.WriteUShort(currentPixelColor, imageData, pixelIndex * 2);
                            lastPixelColor = currentPixelColor;

                            //Add nearby pixels to the colorChangeMap
                            bool hasLeftCol = (col != 0);
                            bool hasRightCol = (col < (header.Width - 1));
                            bool has2RightCols = (col < (header.Width - 2));
                            bool hasDownRow = (row < (header.Height - 1));
                            bool has2DownRows = (row < (header.Height - 2));

                            //Right 1
                            if (hasRightCol)
                                colorChangeMap[pixelIndex + 1]++;
                            //Right 2
                            if (has2RightCols)
                                colorChangeMap[pixelIndex + 2]++;
                            //Down 1 Left 1
                            if (hasDownRow && hasLeftCol)
                                colorChangeMap[pixelIndex + header.Width - 1]++;
                            //Down 1
                            if (hasDownRow)
                                colorChangeMap[pixelIndex + header.Width]++;
                            //Down 1 Right 1
                            if (hasDownRow && hasRightCol)
                                colorChangeMap[pixelIndex + header.Width + 1]++;
                            //Down 2
                            if (has2DownRows)
                                colorChangeMap[pixelIndex + header.Width * 2]++;

                            //Now test to see if we need to continue writing this color down the column
                            command = masterReader.ReadBits(1);//1 - repeat color, 0 - continue

                            if (command == 1) //Repeat color
                            {
                                //Basically move down one row each repeat, possibly moving to the side, and write the color again
                                int pixelOffset = 0;
                                ushort currentPixelColorOpaque = (ushort)(currentPixelColor | 0x1); //Not sure why this is the case, is it to catch it in the first if statement?

                                while(true) //I hate while(true)
                                {
                                    command = masterReader.ReadBits(2);//0 - advanced move, 1 - back one, 2 - no lateral move, 3 - forward one
                                    if (command == 0)
                                    {
                                        //Advanced move
                                        command = masterReader.ReadBits(1);//0 - stop, 1 - advanced move

                                        if (command == 0)
                                        {
                                            break;
                                        }

                                        command = masterReader.ReadBits(1); //0 - move back 2, 1 - move forward 2
                                        if (command == 0)
                                        {
                                            pixelOffset -= 2;
                                        }
                                        else
                                        {
                                            pixelOffset += 2;
                                        }
                                    }
                                    else if (command == 1)
                                    {
                                        pixelOffset--;
                                    }
                                    else if (command == 3)
                                    {
                                        pixelOffset++;
                                    }

                                    pixelOffset += header.Width; //move down a row
                                    ByteHelper.WriteUShort(currentPixelColorOpaque, imageData, (pixelIndex + pixelOffset) * 2);
                                }

                            }
                        }
                    }

                    //Next pixel
                    pixelIndex++;
                }
            }

            return imageData;
        }