Changeset - de6e00728a10
[Not reviewed]
default
0 3 0
Jason Maltzen - 12 months ago 2023-12-07 04:39:45
jason@hiddenachievement.com
Fixed scanning of RGB bars to correctly account for the right edge highlight. Fixes problems detecting high-value clipping.
3 files changed with 13 insertions and 10 deletions:
0 comments (0 inline, 0 general)
Services/ReactionScannerService.cs
Show inline comments
...
 
@@ -274,199 +274,199 @@ namespace DesertPaintCodex.Services
 
            }
 
            if (!result)
 
            {
 
                // No dark border on the left side
 
                WriteLog(LogVerbosity.Normal, "Failed to find left border for potential swatch of color {2}, {3}, {4} at {0}, {1}", x, y, swatchColor.R, swatchColor.G, swatchColor.B);
 
                return false;
 
            }
 

	
 
            // test the dark top border and for background above and below the swatch
 
            bool borderError = false;
 
            int bgErrorCount = 0;
 
            for (i = 0; result && (i < _swatchWidth - _pixelMultiplier); ++i)
 
            {
 
                bool isBorder = pixels.DoesPixelMatch(x + i, y, PixelColor.IsDark);
 
                result &= isBorder;
 
                if (!isBorder)
 
                {
 
                    WriteLog(LogVerbosity.Normal, "Probable swatch at {0},{1} failed upper border test at {2},{3}", x, y, x + i, y);
 
                    borderError = true;
 
                }
 

	
 
                // Checking along the top of the swatch for background
 
                // The row just above is shaded, so check 2 above
 
                if (y > 1)
 
                {
 
                    bool isUiBackground = pixels.DoesPixelMatch(x + i, y - _swatchTopBorder, PixelColor.IsUiBackground);
 
                    bgErrorCount += isUiBackground ? 0 : 1;
 

	
 
                }
 
                else
 
                {
 
                    ++bgErrorCount;
 
                }
 

	
 
                // Checking along the bottom of the swatch for background
 
                if (y < pixels.Height)
 
                {
 
                    bool isUiBackground = pixels.DoesPixelMatch(x + i, y + _swatchHeight, PixelColor.IsUiBackground);
 
                    bgErrorCount += isUiBackground ? 0 : 1;
 
                }
 
                else
 
                {
 
                    ++bgErrorCount;
 
                }
 
            }
 

	
 
            result &= (bgErrorCount < (_swatchWidth / 20)); // allow up to 5% error rate checking for background texture, because this seems to be inconsistent
 
            if (!result && ((i > (_swatchWidth * 0.8)) || (bgErrorCount >= (_swatchWidth / 20))))
 
            {
 
                if (!borderError && (bgErrorCount < _swatchWidth))
 
                {
 
                    WriteLog(LogVerbosity.Normal, "Found a potential swatch candidate of width {0} at {1},{2} that had {3} failures matching background color", i, x, y, bgErrorCount);
 
                }
 
            }
 

	
 
            return result;
 
        }
 

	
 
        private bool TestPosition(int x, int y, Pixels pixels, ref PaintColor reactedColor)
 
        {
 
            // Check 4 corners of solid area and left/right solid bar areas
 
            bool foundSwatch = IsPossibleSwatchUpperLeft(pixels, x, y);
 
            if (foundSwatch)
 
            {
 
                WriteLog(LogVerbosity.Normal, "Found probable swatch at {0},{1} - checking border slices", x, y);
 
                int borderXOffset = 0;
 
                for (borderXOffset = _swatchLeftBorder; foundSwatch && (borderXOffset < _swatchTestWidth); ++borderXOffset)
 
                {
 
                    foundSwatch &= IsPossibleSwatchSlice(pixels, x + borderXOffset, y);
 
                    if (!foundSwatch)
 
                    {
 
                        WriteLog(LogVerbosity.Normal, "Failed slice test at {0},{1}", x + borderXOffset, y);
 
                        break;
 
                    }
 
                    foundSwatch &= IsPossibleSwatchSlice(pixels, x + _swatchWidth - borderXOffset, y);
 
                    if (!foundSwatch)
 
                    {
 
                        WriteLog(LogVerbosity.Normal, "Failed slice test at {0},{1}", x + _swatchWidth - borderXOffset, y);
 
                        break;
 
                    }
 
                }
 
            }
 

	
 
            if (!foundSwatch)
 
            {
 
                if (!_lockedSwatchWidth)
 
                {
 
                    // Reset our guess at swatch width.
 
                    _swatchWidth = MinSwatchWidth;
 
                }
 
                return false;
 
            }
 
            
 
            // WE FOUND THE SWATCH!
 
            // Now we know where the color bars are.
 
            _lockedSwatchWidth = true;
 
            int redPixelCount = pixels.LengthOfColorAt(x, y + _redBarSpacing, PixelColor.IsRed);
 
            int redPixelCount = pixels.LengthOfColorAt(x, y + _redBarSpacing, _swatchWidth, PixelColor.IsRed);
 
            reactedColor.Red = (byte)Math.Round(redPixelCount * 255f / _swatchWidth);
 

	
 
            int greenPixelCount = pixels.LengthOfColorAt(x, y + _greenBarSpacing, PixelColor.IsGreen);
 
            int greenPixelCount = pixels.LengthOfColorAt(x, y + _greenBarSpacing, _swatchWidth, PixelColor.IsGreen);
 
            reactedColor.Green = (byte)Math.Round(greenPixelCount * 255f / _swatchWidth);
 

	
 
            int bluePixelCount = pixels.LengthOfColorAt(x, y + _blueBarSpacing, PixelColor.IsBlue);
 
            int bluePixelCount = pixels.LengthOfColorAt(x, y + _blueBarSpacing, _swatchWidth, PixelColor.IsBlue);
 
            reactedColor.Blue = (byte)Math.Round(bluePixelCount * 255f / _swatchWidth);
 
            WriteLog(LogVerbosity.Low, "Found the color swatch at {0}, {1}. Color={2} Red={3}px Green={4}px Blue={5}px", x, y, reactedColor, redPixelCount, greenPixelCount, bluePixelCount);
 
            return true;
 
        }
 

	
 
        public async Task<bool> CaptureReactionAsync(IProgress<float> progress)
 
        {
 
            _canceler = new CancellationTokenSource(); // You can't re-use these, sadly.
 
            return await Task.Run(() => CaptureReaction(progress, _canceler.Token));
 
        }
 

	
 
        private bool CaptureReaction(IProgress<float> progress, CancellationToken cancellToken)
 
        {
 
            PaintColor reactedColor = new();
 
            IsCaptured = false;
 
            _recordedColor.Clear();
 

	
 
            using (var g = Graphics.FromImage(_targetBitmap))
 
            {
 
                Debug.WriteLine("Scan starting at [" + _screenX + ", " + _screenY + "]");
 
                g.CopyFromScreen(_screenX, _screenY, 0, 0, _targetBitmap.Size, CopyPixelOperation.SourceCopy);
 
            }
 

	
 
            SettingsService.Get("Log.Verbosity", out var verbosityIdx, 1);
 
            _logVerbosity = (LogVerbosity)verbosityIdx;
 

	
 
            SettingsService.Get("Debug.Screenshot", out bool debugScreenshot, false);
 
            if (debugScreenshot)
 
            {
 
                _targetBitmap.Save(Path.Combine(ProfileManager.CurrentProfile?.Directory ?? "", "screenshot.png"), ImageFormat.Png);
 
            }
 

	
 
            Pixels pixels = new(_targetBitmap, _pixelMultiplier);
 

	
 
            IsCaptured = false;
 
            if (!_firstRun)
 
            {
 
                // If this is not the first run, let's check the last location, to see if the UI is still there.
 
                if (TestPosition(_lastSwatchX, _lastSwatchY, pixels, ref reactedColor))
 
                {
 
                    IsCaptured = true;
 
                    RecordedColor = reactedColor;
 
                    return true;
 
                }
 
                
 
                _firstRun = true;
 
            }
 

	
 
            int patchTestSize = ((_swatchHeight - _swatchTopBorder - _swatchBottomBorder) / 2) - 1;
 

	
 
            SettingsService.Get("ScanArea.Min.X", out var startX, 0);
 
            SettingsService.Get("ScanArea.Min.Y", out var startY, 0);
 
            SettingsService.Get("ScanArea.Max.X", out var endX,   _targetBitmap.Width);
 
            SettingsService.Get("ScanArea.Max.Y", out var endY,   _targetBitmap.Height);
 
            
 
            startX = Math.Max(2, Math.Min(startX, _targetBitmap.Width - 2));
 
            startY = Math.Max(2, Math.Min(startY, _targetBitmap.Height - 2));
 
            endX = Math.Min(_targetBitmap.Width  - 2, Math.Max(2, endX)) - _minSwatchWidth; //  + patchTestSize;
 
            endY = Math.Min(_targetBitmap.Height - 2, Math.Max(2, endY)) - (_blueBarSpacing + 10); // + patchTestSize;
 

	
 
            Debug.WriteLine("startX: " + startX + " endX: " + endX + " startY: " + startY + " endY: " + endY + " with patch test size of " + patchTestSize);
 
            
 
            int xSpan = endX - startX;
 
            int ySpan = endY - startY;
 
            int total = xSpan * ySpan;
 

	
 
            for (int roughX = startX; roughX < endX ; roughX += patchTestSize)
 
            {
 
                int xMark = roughX - startX;
 
                
 
                for (int roughY = startY; roughY < endY; roughY += patchTestSize)
 
                {
 
                    progress.Report((float)((xMark * ySpan) + (roughY - startY)) / total);
 

	
 
                    cancellToken.ThrowIfCancellationRequested();
 

	
 
                    if (!pixels.IsSolidPatchAt(roughX, roughY, patchTestSize, patchTestSize)) continue;
 
                    Color patchColor = pixels.ColorAt(roughX, roughY);
 

	
 
                    WriteLog(LogVerbosity.Excessive, "Found a solid patch of {2},{3},{4} at {0}, {1}", roughX, roughY, patchColor.R, patchColor.G, patchColor.B);
 
                    for (int x = Math.Max(0, roughX - patchTestSize - _swatchLeftBorder + _pixelMultiplier); x < roughX; ++x)
 
                    {
 
                        for (int y = Math.Max(0, roughY - patchTestSize - _swatchTopBorder + _pixelMultiplier); y < roughY; ++y)
 
                        {
 
                            WriteLog(LogVerbosity.Excessive, "Searching for potential swatch at {0},{1} after found square at {2},{3}", x, y, roughX, roughY);
 
                            
 
                            if (!TestPosition(x, y, pixels, ref reactedColor)) continue;
 
                            
 
                            RecordedColor = reactedColor;
 
                            _lastSwatchX  = x;
 
                            _lastSwatchY  = y;
 
                            _firstRun     = false;
 
                            IsCaptured    = true;
 
                            return true;
 
                        }
 
                    }
Util/PixelColor.cs
Show inline comments
 
using System;
 
using System.Drawing;
 
using DesertPaintCodex.Services;
 

	
 
namespace DesertPaintCodex.Util
 
{
 
    public class PixelColor
 
    {
 
        private const int ColorTolerance = 2;
 
        
 
        public static bool IsMatch(Color colorA, Color colorB)
 
        {
 
            return ((Math.Abs(colorA.R - colorB.R) <= ColorTolerance) &&
 
                (Math.Abs(colorA.G - colorB.G) <= ColorTolerance) &&
 
                (Math.Abs(colorA.B - colorB.B) <= ColorTolerance));
 
        }
 

	
 
        public static bool IsDark(Color color)
 
        {
 
            return (color.R < 0x47) && (color.G < 0x47) && (color.B < 0x47);
 
        }
 

	
 
        public static bool IsUiBackground(Color color)
 
        {
 
            return (color.R is >= 0x27 and <= 0x2F) && (color.G is >= 0x3B and <= 0x42) && (color.B is >= 0x41 and <= 0x48);
 
        }
 

	
 
        public static bool IsRed(Color color)
 
        {
 
            return (color.R > 0x9D) && (color.G < 0x60) && (color.B < 0x60);
 
            return (color.R > 0x9D) && (color.G < 0x64) && (color.B < 0x64);
 
        }
 
        public static bool IsGreen(Color color)
 
        {
 
            return (color.R < 0x60) && (color.G > 0x9D) && (color.B < 0x60);
 
            return (color.R < 0x64) && (color.G > 0x9D) && (color.B < 0x64);
 
        }
 
        public static bool IsBlue(Color color)
 
        {
 
            return (color.R < 0x60) && (color.G < 0x60) && (color.B > 0x9D);
 
            return (color.R < 0x64) && (color.G < 0x64) && (color.B > 0x9D);
 
        }
 

	
 
    }
 
}
...
 
\ No newline at end of file
Util/Pixels.cs
Show inline comments
 
using System;
 
using System.Diagnostics;
 
using System.Drawing;
 

	
 
namespace DesertPaintCodex.Util
 
{
 
    public class Pixels
 
    {
 
        private readonly Bitmap _bitmap;
 
        private readonly int _pixelMultiplier;
 

	
 
        public int Height => _bitmap.Height / _pixelMultiplier;
 
        public int Width => _bitmap.Width / _pixelMultiplier;
 

	
 
        public Pixels(Bitmap bitmap, int pixellMutliplier)
 
        {
 
            _bitmap = bitmap;
 
            _pixelMultiplier = pixellMutliplier;
 
        }
 

	
 
        public Color ColorAt(int x, int y)
 
        {
 
            return _bitmap.GetPixel(x * _pixelMultiplier, y * _pixelMultiplier);
 
        }
 

	
 
        public bool DoesPixelMatch(int x, int y, Func<Color, bool> matchFunc)
 
        {
 
            int xMult = x * _pixelMultiplier;
 
            int yMult = y * _pixelMultiplier;
 

	
 
            if (xMult < 0 || xMult >= _bitmap.Width || yMult < 0 || yMult >= _bitmap.Height)
 
            {
 
                Debug.WriteLine("Problem at position [" + x + ", " + y + "]");
 
            }
 
            
 
            return matchFunc(_bitmap.GetPixel(x * _pixelMultiplier, y * _pixelMultiplier));
 
        }
 

	
 
        // Compute length of horizontal bar starting at x,y using matching function
 
        public int LengthOfColorAt(int x, int y, Func<Color, bool> matchFunc)
 
        public int LengthOfColorAt(int x, int y, int scanWidth, Func<Color, bool> matchFunc)
 
        {
 
            int count = 0;
 
            for (int xVal = x; xVal < Width; ++xVal)
 
            for (int xVal = x; xVal < scanWidth + x; ++xVal)
 
            {
 
                if (!matchFunc(_bitmap.GetPixel(xVal * _pixelMultiplier, y * _pixelMultiplier))) break;
 
                Color c = _bitmap.GetPixel(xVal * _pixelMultiplier, y * _pixelMultiplier);
 
                if (!matchFunc(c))
 
                {
 
                    break;
 
                }
 
                ++count;
 
            }
 
            return count;
 
        }
 

	
 
        public bool IsSolidPatchAt(int x, int y, int patchWidth, int patchHeight)
 
        {
 
            if ((x + patchWidth >= Width) || (y + patchHeight >= Height)) return false;
 
            Color color = _bitmap.GetPixel(x * _pixelMultiplier, y * _pixelMultiplier);
 
            
 
            // Debug.WriteLine("color at {0},{1} = {2},{3},{4}", x, y, color.R, color.G, color.B);
 

	
 
            for (int pX = 0; pX < patchWidth; pX++)
 
            {
 
                for (int pY = 0; pY < patchHeight; pY++)
 
                {
 
                    if (!PixelColor.IsMatch(color, _bitmap.GetPixel((x + pX) * _pixelMultiplier, (y + pY) * _pixelMultiplier)))
 
                        return false;
 
                }
 
            }
 

	
 
            return true;
 
        }
 
    }
 
}
...
 
\ No newline at end of file
0 comments (0 inline, 0 general)