using System; using System.Drawing; using System.Windows.Forms; namespace SpriteWave { public class PalettePanel : Panel { private readonly int scrollW = SystemInformation.VerticalScrollBarWidth; private const int pixPerScroll = 20; private IPalettePicker _uiParent; private ColorBox _box; private VScrollBar _scroll; private int _nCols; private int _nRows; private int _maxVisRows; public int CurrentCell = -1; private int _hoveredCell = -1; private int _palLen = 0; private int[] _palPolarLum = null; private byte[] _palNumbers = null; private IPalette _pal; public IPalette Palette { set { _pal = value; if (_pal == null) return; int len = _pal.ColorCount; if (_palLen != len) { _palLen = len; _palNumbers = DigitImages.Generate(_palLen); } //CalcBrightness(); } get { return _pal; } } private int FirstVisibleCell { get { return _scroll.Enabled ? _scroll.Value * _nCols : 0; } } private int VisibleRows { get { return Math.Min(_nRows, _maxVisRows); } } public PalettePanel(IPalettePicker uiParent, IPalette pal, int maxVisRows = 2) { _uiParent = uiParent; this.Palette = pal; _maxVisRows = maxVisRows; this.Name = "paletteBox"; this.Location = new Point(0, 0); Size size = new Size(80, 20); this.Size = size; _box = new ColorBox(); _box.MouseDown += this.boxClickHandler; _box.MouseWheel += this.boxScrollHandler; _box.MouseMove += this.cursorHandler; _box.MouseLeave += (s, e) => { _hoveredCell = -1; Draw(); }; _scroll = new VScrollBar(); _scroll.Scroll += (s, e) => Draw(); int scW = SystemInformation.VerticalScrollBarWidth; _scroll.Location = new Point(size.Width - scW, 0); _scroll.Size = new Size(scW, size.Height); _scroll.Minimum = 0; _scroll.Value = 0; _scroll.LargeChange = 1; _scroll.SmallChange = 1; this.Controls.Add(_box); this.Controls.Add(_scroll); } public void AdjustContents(ToolBoxOrientation layout) { _nCols = 1; _nRows = 1; if (_pal != null) { if (this.Width >= 400) _nCols = 16; else if (this.Width >= 200) _nCols = 8; else _nCols = 4; _nCols = Math.Min(_nCols, _palLen); _nRows = (int)Math.Ceiling((float)_palLen / (float)_nCols); } bool scVis = _nRows > _maxVisRows; if (scVis) { if (!_scroll.Enabled) _scroll.Enabled = true; _scroll.Maximum = _nRows - _maxVisRows; } else if (_scroll.Enabled) _scroll.Enabled = false; int scX = layout == ToolBoxOrientation.Left ? 0 : this.Width - scrollW; _scroll.Location = new Point(scX, 0); _scroll.Size = new Size(scrollW, this.Height); int boxX = layout == ToolBoxOrientation.Left ? scrollW : 0; _box.Location = new Point(boxX, 0); int visRows = VisibleRows; if (this.Width > scrollW && this.Height > 0) { Size s = new Size(this.Width - scrollW, this.Height); //_box.Image = new Bitmap(s.Width, s.Height); _box.Size = s; } } private static void BlendPixel(byte[] pixbuf, int idx, float b, float g, float r, float alpha) { pixbuf[idx] = (byte)((float)pixbuf[idx] * (1f - alpha) + b * alpha); pixbuf[idx+1] = (byte)((float)pixbuf[idx+1] * (1f - alpha) + g * alpha); pixbuf[idx+2] = (byte)((float)pixbuf[idx+2] * (1f - alpha) + r * alpha); } public void Draw() { if (_pal == null || this.Height <= 0) return; float[] shades = { Utils.AlphaShades[0] * 255f, Utils.AlphaShades[1] * 255f }; // Calculate the brightness of each color to determine its opposite luminance pole (black or white) uint[] colors = _pal.GetList(); _palPolarLum = new int[_palLen]; const int white = 0, black = 1; for (int i = 0; i < _palLen; i++) { uint clr = colors[i]; double a = (double)(clr & 0xff) / 255f; double r = (double)((clr >> 8) & 0xff) / 255f; double g = (double)((clr >> 16) & 0xff) / 255f; double b = (double)(clr >> 24) / 255f; /* // If a cell is selected and it's not this cell, increase the brightness if (CurrentCell >= 0 && i != CurrentCell) { r = 1 - ((1 - r) / 2); g = 1 - ((1 - g) / 2); b = 1 - ((1 - b) / 2); } */ double lum = 0.2126*r + 0.7152*g + 0.0722*b; lum += (1f - lum) * (1f - a); _palPolarLum[i] = lum >= 0.5f ? black : white; // Reverse pixel channel order colors[i] = Utils.ComposeBGRA(a, r, g, b); } _box.Lock(); var buf = _box.Buffer; int width = _box.Image.Size.Width; int height = _box.Image.Size.Height; float visRowsF = (float)VisibleRows; float cellW = (float)width / (float)_nCols; float cellH = (float)height / visRowsF; // Minus one because maximum value is the last index, not the size int nDigits = Utils.DigitCount(_palLen - 1); float numW = (float)(DigitImages.digitW * nDigits); float numH = (float)DigitImages.digitH; const float border = 1; int firstCellIdx = FirstVisibleCell; int idx = 0; for (int i = 0; i < height; i++) { int y = (int)((float)i / cellH); float subY = (float)i % cellH; int cellY = firstCellIdx + y * _nCols; for (int j = 0; j < width; j++) { int x = (int)((float)j / cellW); float subX = (float)j % cellW; int cell = cellY + x; if ((cell == CurrentCell || cell == _hoveredCell) && (subX < border || subX >= cellW - border || subY < border || subY >= cellH - border)) { if (_palPolarLum[cell] == white) { buf[idx++] = 0xff; buf[idx++] = 0xff; buf[idx++] = 0xff; } else { buf[idx++] = 0; buf[idx++] = 0; buf[idx++] = 0; } buf[idx++] = 0xff; continue; } // In most cases, no pixel blending occurs. // Therefore, we just use the quickest method (I think) to copy a pixel to a pixel buffer in C#. // Note that pixels are laid out as b.g.r.a in memory and x86 is little-endian, // meaning that we need to make sure colors[cell] (the input) is written as 0xaarrggbb before copying. Buffer.BlockCopy(colors, cell * 4, buf, idx, 4); // If the colour isn't fully opaque if (buf[idx+3] != 0xff) { int tileX = ((j % 16) < 8) ? 1 : 0; int tileY = ((i % 16) < 8) ? 1 : 0; float sh = shades[tileX ^ tileY]; BlendPixel(buf, idx, sh, sh, sh, (float)(255 - buf[idx+3]) / 255f); buf[idx+3] = 0xff; } // If this pixel is in the top-left corner, blend it with its corresponding pixel inside the appropriate digit bitmap. // Plain English: this is the part where we draw the numbers. if (subY < numH && subX < numW) { // XOR 1 because we want the text colour to oppose the brightness level of the cell int clrVersion = _palPolarLum[cell] ^ 1; int numLine = (cell * 2 + clrVersion) * (int)numH; int numPixIdx = ((numLine + (int)subY) * (int)numW + (int)subX) * Utils.cLen; // If the current pixel inside the digit bitmap isn't fully transparent if (_palNumbers[numPixIdx + 3] != 0) { float dgAlpha = (float)_palNumbers[numPixIdx + 3] / 255f; BlendPixel(buf, idx, (float)_palNumbers[numPixIdx], (float)_palNumbers[numPixIdx+1], (float)_palNumbers[numPixIdx+2], dgAlpha); } } idx += 4; } } _box.Unlock(); } private void boxClickHandler(object sender, MouseEventArgs e) { float cellW = (float)_box.Image.Size.Width / (float)_nCols; float cellH = (float)_box.Image.Size.Height / (float)VisibleRows; int col = (int)((float)e.X / cellW); int row = (int)((float)e.Y / cellH) + (FirstVisibleCell / _nCols); int idx = row * _nCols + col; _uiParent.SelectFromTable(this, idx); } private void boxScrollHandler(object sender, MouseEventArgs e) { if (!_scroll.Enabled) return; int delta = e.Delta != 0 ? e.Delta / Math.Abs(e.Delta) : 0; int oldValue = _scroll.Value; int newValue = oldValue - delta; if (newValue < _scroll.Minimum) newValue = _scroll.Minimum; else if (newValue > _scroll.Maximum) newValue = _scroll.Maximum; _scroll.Value = newValue; if (newValue != oldValue) Draw(); } private void cursorHandler(object sender, MouseEventArgs e) { float x = (float)e.X / (float)_box.Width; float y = (float)e.Y / (float)_box.Height; int col = (int)(x * (float)_nCols); int row = (int)(y * (float)VisibleRows); int idx = FirstVisibleCell + (row * _nCols) + col; if (idx != _hoveredCell) { _hoveredCell = idx; Draw(); } } } }