public static byte[] EncodeRgba4Bpp(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(); } List <Color> tempColor32Array = new List <Color>(); for (int y = 0; y < bitmap.Height; y++) { for (int x = 0; x < bitmap.Width; x++) { tempColor32Array.Add(bitmap.GetPixel(x, y)); } } 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 GetMinMaxColorsWithAlpha(tempColor32Array, size, 4 * x, 4 * y, ref minColor, ref maxColor); PvrtcPacket packet = packets[GetMortonNumber(x, y)]; packet.SetPunchthroughAlpha(false); packet.SetColorA(minColor); packet.SetColorB(maxColor); } } 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]; Vector4Int ca = p0.GetColorRgbaA() * currentFactors[0] + p1.GetColorRgbaA() * currentFactors[1] + p2.GetColorRgbaA() * currentFactors[2] + p3.GetColorRgbaA() * currentFactors[3]; Vector4Int cb = p0.GetColorRgbaB() * currentFactors[0] + p1.GetColorRgbaB() * currentFactors[1] + p2.GetColorRgbaB() * currentFactors[2] + p3.GetColorRgbaB() * currentFactors[3]; Color pixel = tempColor32Array[4 * x + px + (4 * y + py) * size]; //(Color32)bitmap.GetPixel(4*x + px, 4*y + py); Vector4Int d = cb - ca; Vector4Int p = new Vector4Int(pixel.R * 16, pixel.G * 16, pixel.B * 16, pixel.A * 16); Vector4Int 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(Vector4.Dot(v, d)) * 16; int lengthSquared = d % d; //Mathf.RoundToInt(Vector4.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 DecodeRgba4Bpp(byte[] data, int width) { int size = width; int blocks = size / 4; int blockMask = blocks - 1; Bitmap returnValue = new Bitmap(size, size, PixelFormat.Format32bppRgb); Color[] tempColor32Array = new Color[size * size]; 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]; Vector4Int ca = p0.GetColorRgbaA() * currentFactors[0] + p1.GetColorRgbaA() * currentFactors[1] + p2.GetColorRgbaA() * currentFactors[2] + p3.GetColorRgbaA() * currentFactors[3]; Vector4Int cb = p0.GetColorRgbaB() * currentFactors[0] + p1.GetColorRgbaB() * currentFactors[1] + p2.GetColorRgbaB() * currentFactors[2] + p3.GetColorRgbaB() * currentFactors[3]; byte[] currentWeights = PvrtcPacket.WEIGHTS[4 * packet.GetPunchthroughAlpha() + mod & 3]; Color c = Color.White; c = Color.FromArgb( (int)(Math.Round((decimal)(ca.w * currentWeights[2] + cb.w * currentWeights[3]), MidpointRounding.AwayFromZero)) >> 7, (int)(Math.Round((decimal)(ca.x * currentWeights[0] + cb.x * currentWeights[1]), MidpointRounding.AwayFromZero)) >> 7, (int)(Math.Round((decimal)(ca.y * currentWeights[0] + cb.y * currentWeights[1]), MidpointRounding.AwayFromZero)) >> 7, (int)(Math.Round((decimal)(ca.z * currentWeights[0] + cb.z * currentWeights[1]), MidpointRounding.AwayFromZero)) >> 7); //returnValue.SetPixel((px+x*4), (py+y*4), c); tempColor32Array[(px + x * 4) + (py + y * 4) * size] = c; mod >>= 2; currentFactorIndex++; } } } } for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { returnValue.SetPixel(x, y, tempColor32Array[y * size + x]); } } return(returnValue); }