using System; namespace SpriteWave { public class ColorPattern : ColorTable { // RGBA pattern private byte[] _rgbaOrder, _rgbaOrderInv; private byte[] _rgbaDepth; /* RGBA Order And Depth: A formula descriptor for how to arrange different formats of RGBA. The input parameter as passed as the integer 0xrgbaRGBA where: r = position of red channel (0-3) g = position of green channel (0-3) b = position of blue channel (0-3) a = position of alpha channel (0-3) R = depth (number of bits) for the red channel (0-8) G = depth for the green channel (0-8) B = depth for the blue channel (0-8) A = depth for the alpha channel (0-8) Each digit takes up 4 bits in the input parameter. */ public ColorPattern(uint rgbaOrderAndDepth, uint[] defSel) { _rgbaOrder = new byte[4]; _rgbaOrderInv = new byte[4]; _rgbaDepth = new byte[4]; uint cCfg = rgbaOrderAndDepth; // Here we take each of the 8 hex digits inside 'cCfg' and place them in their appropriate elements for (int i = 0; i < 4; i++) { int shift = (4 + (3 - i)) * 4; uint order = Utils.GetBits(cCfg, 4, shift); if (order > 3) throw new ArgumentException("RGBA Order Error:\nInvalid order index " + order + " for the " + Utils.RGBANames[i] + " channel (" + i + ")"); uint depth = Utils.GetBits(cCfg, 4, shift - 16); if (depth > 8) throw new ArgumentException("RGBA Depth Error:\nInvalid bit depth " + depth + " for the " + Utils.RGBANames[i] + " channel (" + i + ")"); _rgbaOrder[i] = (byte)order; _rgbaDepth[i] = (byte)depth; } // We make sure that _rgbaOrder is bijective (1:1 correspondence) to the set {0, 1, 2, 3} // This means _rgbaOrder will be bijective with its inverted set (_rgbaOrderInv), a fact we will rely upon later for (int i = 0; i < 4; i++) { int chn = -1, freq = 0; for (int j = 0; j < 4; j++) { if (_rgbaOrder[j] == i) { chn = j; freq++; } } if (chn < 0) throw new ArgumentException("RGBA Order Error:\nNo channel for position " + i + " was given"); if (freq != 1) throw new ArgumentException("RGBA Order Error:\nMultiple channels were attempted to placed in the same position"); _rgbaOrderInv[i] = (byte)chn; } _defSel = defSel; } public override uint NativeToRGBA(uint idx) { /* We turn our 'index' into an RGBA color using our 'rgbaOrderAndDepth' formula descriptor. This is something I came up with, so a bit of explanation is always nice. I've noticed there are two common attributes of RGBA color that are often tailored to particular pixel formats: order of channels and bits per channel. '_rgbaOrder' tells us the order of channels as they appear in 'idx', and '_rgbaDepth' tells us how many bits are used for the R, G, B and A channels respectively. */ // This for loop takes the channel values out of 'idx' and places them in RGBA order in the process int[] chVals = new int[4]; for (int i = 3; i >= 0; i--) { int which = _rgbaOrderInv[i]; int depth = _rgbaDepth[which]; chVals[which] = (int)idx & ((1 << depth) - 1); idx >>= depth; } /* This loop takes each channel value from above and pads it out as evenly as possible, while placing it in the final RGBA value we return. */ uint rgba = 0; for (int i = 0; i < 4; i++) { int depth = _rgbaDepth[i]; if (depth == 0) { if (i != 3) rgba <<= 8; continue; } /* If we continuously pad the remaining bits with the existing bits, it produces the smoothest pattern. Eg. 00 -> 00000000, 01 -> 01000000, 10 -> 10000000, 11 -> 11000000 is not as smooth/even as 00 -> 00000000, 01 -> 01010101, 10 -> 10101010, 11 -> 11111111 , the latter of which this while loop achieves. */ int val = 0; int left = 8; while (left > 0) { int pad = chVals[i]; if (left < depth) { pad >>= (depth - left); depth = left; } val <<= depth; val |= pad; left -= depth; } val &= 0xff; // Place the new channel value in the final output rgba |= (uint)val; if (i != 3) rgba <<= 8; } return rgba; } public override uint RGBAToNative(uint rgba) { uint native = 0; for (int i = 0; i < 4; i++) { int which = _rgbaOrderInv[i]; int depth = _rgbaDepth[which]; uint channel = Utils.ColorAt(rgba, _rgbaOrder[3 - i] * 8); uint val = channel >> (8 - depth); native <<= depth; native |= val; } return native; } } }