public static ArgbColor FromArgb(int r, int g, int b) { ArgbColor retval = new ArgbColor(); retval.B = (byte)(b > 255 ? 255 : (b < 0 ? 0 : b)); retval.G = (byte)(g > 255 ? 255 : (g < 0 ? 0 : g)); retval.R = (byte)(r > 255 ? 255 : (r < 0 ? 0 : r)); retval.A = 255; return retval; }
public static ArgbColor FromDotNetColor(Color c) { ArgbColor retval = new ArgbColor(); retval.B = c.B; retval.G = c.G; retval.R = c.R; retval.A = c.A; return retval; }
public int GetNearestIndex(ArgbColor src, int excludedIndex = -1) { PaletteEntry[] colors = _entries; // キャッシュ 0面 { int cache0 = _cacheIndex0; PaletteEntry e = colors[cache0]; int dB = src.B -e.Color.B; int dG = src.G -e.Color.G; int dR = src.R -e.Color.R; int dist = (dB * dB + dG * dG + dR * dR) + e.DistanceModifier; if (dist <= e.ExclusiveRadius) { _cacheHitCount++; _eezHitCount++; return cache0; } } int nearestColorIndex = -1; //KDTreeNode nearestNode = _treeNodeTableArray[0]; int nearestDist = AbsoluteMaxDist; fixed (KDTreeNode** stackPtrBase = stackArray) fixed (KDTreeNode* treeNodes = _treeNodeTableArray) { KDTreeNode** stackPtr = &stackPtrBase[0]; *(stackPtr++) = &treeNodes[0]; //KDTreeNode currentNode = **(--stackPtr); //KDTreeNode currentNode = treeNodes[0]; do { //while (true) { KDTreeNode currentNode = **(--stackPtr); // 現在見つかっている最近傍点より接近する可能性がある場合のみ探索する if (currentNode.DistanceMemo >= nearestDist) continue; int testedIndex = currentNode.MedianColorIndex; // 除外対象でなければ距離を調べる PaletteEntry testedEntry = colors[testedIndex]; // ユークリッド距離 int dB = src.B - testedEntry.Color.B; int dG = src.G - testedEntry.Color.G; int dR = src.R - testedEntry.Color.R; int testedDist = dB * dB + dG * dG + dR * dR + testedEntry.DistanceModifier; // 排他領域にヒットしたら即確定 if (testedDist <= testedEntry.ExclusiveRadius) { stackPtr = stackPtrBase; nearestDist = testedDist; nearestColorIndex = testedIndex; //nearestNode = currentNode; _eezHitCount++; break; } if (testedDist < nearestDist) { nearestDist = testedDist; nearestColorIndex = testedIndex; //nearestNode = currentNode; } byte testVal = src.Channels[currentNode.MedianChannel]; int leftDist = int.MaxValue; if (currentNode.LeftIdx >= 0) { leftDist = testVal - currentNode.LeftMax; if (leftDist < 0) leftDist = 0; treeNodes[currentNode.LeftIdx].DistanceMemo = leftDist * leftDist; } int rightDist = int.MaxValue; if (currentNode.RightIdx >= 0) { rightDist = currentNode.RightMin - testVal; if (rightDist < 0) rightDist = 0; treeNodes[currentNode.RightIdx].DistanceMemo = rightDist * rightDist; } // 近そうな方が先に処理されるようスタックに積み if (leftDist < rightDist) { if (rightDist < nearestDist) *(stackPtr++) = &treeNodes[currentNode.RightIdx]; if (leftDist < nearestDist) *(stackPtr++) = &treeNodes[currentNode.LeftIdx]; } else { if (leftDist < nearestDist) *(stackPtr++) = &treeNodes[currentNode.LeftIdx]; if (rightDist < nearestDist) *(stackPtr++) = &treeNodes[currentNode.RightIdx]; } } while (stackPtr > stackPtrBase); } _cacheIndex0 = nearestColorIndex; return nearestColorIndex; } // GetNearestEntry
public int GetManhattanDistanceTo(ArgbColor target) { uint x = target.AsUInt32; uint y = this.AsUInt32; uint a = x | 0x01010100u; uint b = y & 0x00fefeffu; uint c = a - b; uint d = c & 0x01010100u; uint e = d - (d >> 8); uint f = ~e; uint g = c ^ f; return (int)( ((g & 0x00ff0000u) >> 16) + ((g & 0x0000ff00u) >> 8) + ((g & 0x000000ffu) >> 0)); //return // (target.R > R ? target.R - R : R - target.R) + // (target.G > G ? target.G - G : G - target.G) + // (target.B > B ? target.B - B : B - target.B); }
public int GetSquareEuclidDistanceTo(ArgbColor target) { int db = target.B - this.B; int dg = target.G - this.G; int dr = target.R - this.R; return dr * dr + dg * dg + db * db; }
public float GetEuclidDistanceTo(ArgbColor target) { int db = target.B - this.B; int dg = target.G - this.G; int dr = target.R - this.R; return (float)Math.Sqrt( dr * dr + dg * dg + db * db); }
public void CalcWeight(ColorEntry[] colorArray,ArgbColor[] importantColors) { byte minR = 255, maxR = 0; byte minG = 255, maxG = 0; byte minB = 255, maxB = 0; int varianceR = 0; int varianceG = 0; int varianceB = 0; int length = ColorIndexMax - ColorIndexMin + 1; // 最大/最小と分散の算出 for (int i = ColorIndexMin; i <= ColorIndexMax; i++) { ColorEntry color = colorArray[i]; if (color.Color.R < minR) minR = color.Color.R; if (color.Color.R > maxR) maxR = color.Color.R; if (color.Color.G < minG) minG = color.Color.G; if (color.Color.G > maxG) maxG = color.Color.G; if (color.Color.B < minB) minB = color.Color.B; if (color.Color.B > maxB) maxB = color.Color.B; } int medianR = (maxR + minR) / 2; int medianG = (maxG + minG) / 2; int medianB = (maxB + minB) / 2; // 重要色からの距離 int penalty = 0; foreach (var e in importantColors) { ArgbColor medianColor = ArgbColor.FromArgb(medianR, medianG, medianB); int dist = (int)medianColor.GetEuclidDistanceTo(e); if (dist > 0) { medianR += (medianR - e.R) * 100 / (dist * dist); medianG += (medianG - e.R) * 100 / (dist * dist); medianB += (medianB - e.R) * 100 / (dist * dist); } penalty += dist; } if (importantColors.Length > 0) { const int a = 4, b = 1, ab = a + b; if (medianR < (minR * a + maxR * b) / ab) medianR = (minR * a + maxR * b) / ab; if (medianR > (minR * b + maxR * a) / ab) medianR = (minR * b + maxR * a) / ab; if (medianG < (minG * a + maxG * b) / ab) medianG = (minG * a + maxG * b) / ab; if (medianG > (minG * b + maxG * a) / ab) medianG = (minG * b + maxG * a) / ab; if (medianB < (minB * a + maxB * b) / ab) medianB = (minB * a + maxB * b) / ab; if (medianB > (minB * b + maxB * a) / ab) medianB = (minB * b + maxB * a) / ab; penalty /= (importantColors.Length); } // 分散の算出 for (int i = ColorIndexMin; i <= ColorIndexMax; i++) { ColorEntry color = colorArray[i]; // RGB各軸方向の分散 int diffR = ((int)color.Color.R - medianR); int diffG = ((int)color.Color.G - medianG); int diffB = ((int)color.Color.B - medianB); varianceR += diffR * diffR; varianceG += diffG * diffG; varianceB += diffB * diffB; } int weightR = varianceR / (penalty + 1); int weightG = varianceG / (penalty + 1); int weightB = varianceB / (penalty + 1); // 次回のメディアンカットに備えて分散の値を記録 if (weightR > weightG && weightR > weightB) { DivWeight = weightR; DivChannel = ArgbColor.ChannelR; MedianValue = medianR; } else if (weightG > weightB) { DivWeight = weightG; DivChannel = ArgbColor.ChannelG; MedianValue = medianG; } else { DivWeight = weightB; DivChannel = ArgbColor.ChannelB; MedianValue = medianB; } }
// 代表色の選択 private ArgbPalette generatePalette(Size size, ColorEntry[] colors, ColorBox[] boxes, ArgbColor[] importantColors, int paletteSize) { #if DEBUG var stopWatch = new System.Diagnostics.Stopwatch(); stopWatch.Start(); #endif int totalPixelCount = size.Width * size.Height; ArgbColor center = ArgbColor.FromArgb(128, 128, 128); ArgbPalette palette = new ArgbPalette(paletteSize); for (int i = 0; i < paletteSize; i++) { uint sumR = 0; uint sumG = 0; uint sumB = 0; uint sumCount = 0; int indexMin = boxes[i].ColorIndexMin; int indexMax = boxes[i].ColorIndexMax; int length = (indexMax - indexMin + 1); int maxDist = 0; ColorEntry maxE = colors[indexMin]; for (int j = indexMin; j <= indexMax; j++) { ArgbColor thisColor = colors[j].Color; int penalty = 0; foreach (var c in importantColors) { int dist2 = (int)thisColor.GetEuclidDistanceTo(c); if (dist2 > 64) dist2 = 0; penalty += dist2 *10; } if (importantColors.Length > 0) { penalty /= importantColors.Length; } sumR += thisColor.R * colors[j].Count; sumG += thisColor.G * colors[j].Count; sumB += thisColor.B * colors[j].Count; int dist = (int)center.GetEuclidDistanceTo(thisColor) + (1000 * (int)colors[j].Count / totalPixelCount) - penalty; if (dist > maxDist) { maxDist = dist; maxE = colors[j]; } sumCount += colors[j].Count; } byte r = (byte)Math.Round((float)sumR / (float)sumCount); byte g = (byte)Math.Round((float)sumG / (float)sumCount); byte b = (byte)Math.Round((float)sumB / (float)sumCount); palette.Set(i, r, g, b, 1); } #if DEBUG Console.WriteLine("palette generation ({0} msec).", stopWatch.ElapsedMilliseconds); #endif return palette; }
// メディアンカット private unsafe ColorBox[] medianCut(ColorEntry[] colors, ArgbColor[] importantColors, int paletteSize) { #if DEBUG var stopWatch = new System.Diagnostics.Stopwatch(); stopWatch.Start(); #endif ColorBox[] boxes = new ColorBox[paletteSize]; int boxCount = 0; boxes[boxCount++] = new ColorBox(0, colors.Length - 1) ; boxes[0].CalcWeight(colors, importantColors); fixed (ColorEntry* table = colors) while (boxCount < paletteSize) { // 分散が最も大きなBoxを探す int divIndex = -1; int maxWeight = int.MinValue; for (int i = 0; i < boxCount; i++) { if (boxes[i].DivWeight >= maxWeight) { maxWeight = boxes[i].DivWeight; divIndex = i; } } int minIdx = boxes[divIndex].ColorIndexMin; int maxIdx = boxes[divIndex].ColorIndexMax; int ch = boxes[divIndex].DivChannel; // 中央値を決めて分割 int median = boxes[divIndex].MedianValue; int l = minIdx; int r = maxIdx; do { while (l + 1 < r && table[l].Color.Channels[ch] <= median) l++; while (l < r - 1 && table[r].Color.Channels[ch] >= median) r--; if (table[l].Color.Channels[ch] > table[r].Color.Channels[ch]) { ColorEntry temp = table[l]; table[l] = table[r]; table[r] = temp; } } while (l + 1 < r); ColorBox leftBox = new ColorBox( minIdx, l); leftBox.CalcWeight(colors, importantColors); boxes[divIndex] = leftBox; ColorBox rightBox = new ColorBox( r, maxIdx); rightBox.CalcWeight(colors, importantColors); boxes[boxCount++] = rightBox; } #if DEBUG Console.WriteLine("median cut ({0} msec).", stopWatch.ElapsedMilliseconds); #endif return boxes; }