public static byte[] EncodeRgb4Bpp(Bitmap bitmap) { if (bitmap.Height != bitmap.Width) { throw new Exception("Texture isn't square!"); } if (!((bitmap.Height & (bitmap.Height - 1)) == 0)) { throw new Exception("Texture resolution must be 2^N!"); } int size = bitmap.Width; int blocks = size / 4; int blockMask = blocks - 1; PvrtcPacket[] packets = new PvrtcPacket[blocks * blocks]; for (int i = 0; i < packets.Length; i++) { packets[i] = new PvrtcPacket(); } for (int y = 0; y < blocks; ++y) { for (int x = 0; x < blocks; ++x) { Color minColor = Color.White; // white is same as all 255, should be same as Color.max Color maxColor = Color.Black; // clear is same as all 0, should be same as Color.min GetMinMaxColors(bitmap, 4 * x, 4 * y, ref minColor, ref maxColor); PvrtcPacket packet = packets[GetMortonNumber(x, y)]; packet.SetPunchthroughAlpha(false); packet.SetColorA(minColor.R, minColor.G, minColor.B); packet.SetColorB(maxColor.R, maxColor.G, maxColor.B); } } int currentFactorIndex = 0; for (int y = 0; y < blocks; ++y) { for (int x = 0; x < blocks; ++x) { currentFactorIndex = 0; uint modulationData = 0; for (int py = 0; py < 4; ++py) { int yOffset = (py < 2) ? -1 : 0; int y0 = (y + yOffset) & blockMask; int y1 = (y0 + 1) & blockMask; for (int px = 0; px < 4; ++px) { int xOffset = (px < 2) ? -1 : 0; int x0 = (x + xOffset) & blockMask; int x1 = (x0 + 1) & blockMask; PvrtcPacket p0 = packets[GetMortonNumber(x0, y0)]; PvrtcPacket p1 = packets[GetMortonNumber(x1, y0)]; PvrtcPacket p2 = packets[GetMortonNumber(x0, y1)]; PvrtcPacket p3 = packets[GetMortonNumber(x1, y1)]; byte[] currentFactors = PvrtcPacket.BILINEAR_FACTORS[currentFactorIndex]; Vector3Int ca = p0.GetColorRgbA() * currentFactors[0] + p1.GetColorRgbA() * currentFactors[1] + p2.GetColorRgbA() * currentFactors[2] + p3.GetColorRgbA() * currentFactors[3]; Vector3Int cb = p0.GetColorRgbB() * currentFactors[0] + p1.GetColorRgbB() * currentFactors[1] + p2.GetColorRgbB() * currentFactors[2] + p3.GetColorRgbB() * currentFactors[3]; Color pixel = bitmap.GetPixel(4 * x + px, 4 * y + py); Vector3Int d = cb - ca; Vector3Int p = new Vector3Int(pixel.R * 16, pixel.G * 16, pixel.B * 16); Vector3Int v = p - ca; // PVRTC uses weightings of 0, 3/8, 5/8 and 1 // The boundaries for these are 3/16, 1/2 (=8/16), 13/16 int projection = (v % d) * 16; // Mathf.RoundToInt(Vector3.Dot(v, d)) * 16; int lengthSquared = d % d; //Mathf.RoundToInt(Vector3.Dot(d,d)); if (projection > 3 * lengthSquared) { modulationData++; } if (projection > 8 * lengthSquared) { modulationData++; } if (projection > 13 * lengthSquared) { modulationData++; } modulationData = RotateRight(modulationData, 2); currentFactorIndex++; } } PvrtcPacket packet = packets[GetMortonNumber(x, y)]; packet.SetModulationData(modulationData); } } byte[] returnValue = new byte[size * size / 2]; // Create final byte array from PVRTC packets for (int i = 0; i < packets.Length; i++) { byte[] tempArray = packets[i].GetAsByteArray(); Buffer.BlockCopy(tempArray, 0, returnValue, 8 * i, 8); } return(returnValue); }
// This function assumes that input texture is square! (width == height) public static Bitmap DecodeRgb4Bpp(byte[] data, int width) { int size = width; int blocks = size / 4; int blockMask = blocks - 1; Bitmap returnValue = new Bitmap(size, size, PixelFormat.Format24bppRgb); PvrtcPacket[] packets = new PvrtcPacket[blocks * blocks]; byte[] eightBytes = new byte[8]; for (int i = 0; i < packets.Length; i++) { packets[i] = new PvrtcPacket(); Buffer.BlockCopy(data, i * 8, eightBytes, 0, 8); packets[i].InitFromBytes(eightBytes); } int currentFactorIndex = 0; for (int y = 0; y < blocks; ++y) { for (int x = 0; x < blocks; ++x) { currentFactorIndex = 0; PvrtcPacket packet = packets[GetMortonNumber(x, y)]; uint mod = packet.GetModulationData(); for (int py = 0; py < 4; ++py) { int yOffset = (py < 2) ? -1 : 0; int y0 = (y + yOffset) & blockMask; int y1 = (y0 + 1) & blockMask; for (int px = 0; px < 4; ++px) { int xOffset = (px < 2) ? -1 : 0; int x0 = (x + xOffset) & blockMask; int x1 = (x0 + 1) & blockMask; PvrtcPacket p0 = packets[GetMortonNumber(x0, y0)]; PvrtcPacket p1 = packets[GetMortonNumber(x1, y0)]; PvrtcPacket p2 = packets[GetMortonNumber(x0, y1)]; PvrtcPacket p3 = packets[GetMortonNumber(x1, y1)]; byte[] currentFactors = PvrtcPacket.BILINEAR_FACTORS[currentFactorIndex]; Vector3Int ca = p0.GetColorRgbA() * currentFactors[0] + p1.GetColorRgbA() * currentFactors[1] + p2.GetColorRgbA() * currentFactors[2] + p3.GetColorRgbA() * currentFactors[3]; Vector3Int cb = p0.GetColorRgbB() * currentFactors[0] + p1.GetColorRgbB() * currentFactors[1] + p2.GetColorRgbB() * currentFactors[2] + p3.GetColorRgbB() * currentFactors[3]; byte[] currentWeights = PvrtcPacket.WEIGHTS[4 * packet.GetPunchthroughAlpha() + mod & 3]; Color c = Color.White; c = Color.FromArgb(c.A, (byte)((int)(Math.Round((decimal)(ca.x * currentWeights[0] + cb.x * currentWeights[1]), MidpointRounding.AwayFromZero)) >> 7), (byte)((int)(Math.Round((decimal)(ca.y * currentWeights[0] + cb.y * currentWeights[1]), MidpointRounding.AwayFromZero)) >> 7), (byte)((int)(Math.Round((decimal)(ca.z * currentWeights[0] + cb.z * currentWeights[1]), MidpointRounding.AwayFromZero)) >> 7)); returnValue.SetPixel((px + x * 4), (py + y * 4), c); mod >>= 2; currentFactorIndex++; } } } } return(returnValue); }