/// <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; }
/// <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; }