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
 
using System;
 
using System.Diagnostics;
 
using System.IO;
 
using System.Drawing;
 
using System.Drawing.Imaging;
 
using System.Threading;
 
using System.Threading.Tasks;
 
using DesertPaintCodex.Models;
 
using DesertPaintCodex.Util;
 

	
 
namespace DesertPaintCodex.Services
 
{
 
    public class ReactionScannerService
 
    {
 
        private enum LogVerbosity
 
        {
 
            Low,
 
            Normal,
 
            High,
 
            Excessive
 
        }
 

	
 
        private const int SwatchHeight    = 24;
 
        private const int RedBarSpacing   = 32;
 
        private const int GreenBarSpacing = 42;
 
        private const int BlueBarSpacing  = 52;
 

	
 
        private const int MinSwatchWidth = 306;
 
        private const int MaxSwatchWidth = 411;
 

	
 
        private static ReactionScannerService? s_instance;
 
        public static ReactionScannerService Instance => s_instance ??= new ReactionScannerService();
 

	
 
        private int _swatchHeight    = 0;
 
        private int _minSwatchWidth  = 0;
 
        private int _maxSwatchWidth  = 0;
 
        private int _swatchWidth     = 0;
 
        private int _redBarSpacing   = 0;
 
        private int _greenBarSpacing = 0;
 
        private int _blueBarSpacing  = 0;
 
        private int _swatchTopBorder    = 0;
 
        private int _swatchBottomBorder = 0;
 
        private int _swatchLeftBorder   = 0;
 
        private int _swatchRightBorder  = 0;
 
        private int _swatchTestWidth = 0;
 

	
 
        private bool _lockedSwatchWidth = false;
 

	
 
        // Current Status
 
        public bool IsCaptured { get; private set; }
 

	
 
        private readonly PaintColor _recordedColor = new();
 
        public PaintColor RecordedColor {
 
            get => _recordedColor;
 
            private set => _recordedColor.Set(value);
 
        }
 

	
 
        private int _pixelMultiplier = 1;
 

	
 
        private bool _firstRun   = true;
 
        private int _lastSwatchX = -1;
 
        private int _lastSwatchY = -1;
 
        private int _screenX     = 0;
 
        private int _screenY     = 0;
 

	
 
        private Bitmap _targetBitmap;
 

	
 
        private LogVerbosity _logVerbosity = LogVerbosity.Normal;
 

	
 
        private CancellationTokenSource? _canceler;
 

	
 
        public StreamWriter? Log { get; set; }
 
        
 
        private void WriteLog(LogVerbosity verbosity, string format, params object[] args)
 
        {
 
            // Dumping this to Debug, for now.
 
            if (verbosity <= _logVerbosity)
 
            {
 
                Debug.WriteLine(format, args);
 
            }
 
            
 
            /*
 
            if ((Log != null) && (verbosity <= _logVerbosity))
 
            {
 
                Log.WriteLine(format, args);
 
            }
 
            */
 
        }
 
        
 
        public ReactionScannerService()
 
        {
 
            RefreshFromSettings();
 
            Debug.Assert(_targetBitmap != null);
 
        }
 

	
 
        public void RefreshFromSettings()
 
        {
 
            SettingsService.Get(SettingKey.PixelMultiplier, out _pixelMultiplier, Constants.DefaultPixelMultiplier);
 

	
 
            SettingsService.Get(SettingKey.ScreenX, out _screenX, Constants.DefaultScreenX);
 
            SettingsService.Get(SettingKey.ScreenY, out _screenY, Constants.DefaultScreenY);
 
            
 
            SettingsService.Get(SettingKey.ScreenWidth,  out int screenWidth, Constants.DefaultScreenWidth);
 
            SettingsService.Get(SettingKey.ScreenHeight, out int screenHeight, Constants.DefaultScreenHeight);
 
            
 
            _targetBitmap = new Bitmap(screenWidth, screenHeight);
 

	
 
            UpdateSwatchSizes();
 
        }
 
        
 

	
 
        private void UpdateSwatchSizes()
 
        {
 
            _swatchWidth     = MinSwatchWidth * _pixelMultiplier;
 
            _swatchHeight    = SwatchHeight    * _pixelMultiplier;
 
            _redBarSpacing   = RedBarSpacing   * _pixelMultiplier;
 
            _greenBarSpacing = GreenBarSpacing * _pixelMultiplier;
 
            _blueBarSpacing  = BlueBarSpacing  * _pixelMultiplier;
 
            _swatchTopBorder    = 2 * _pixelMultiplier;
 
            _swatchBottomBorder = 2 * _pixelMultiplier;
 
            _swatchLeftBorder   = 2 * _pixelMultiplier;
 
            _swatchRightBorder  = 2 * _pixelMultiplier;
 
            _minSwatchWidth = MinSwatchWidth * _pixelMultiplier;
 
            _maxSwatchWidth = MaxSwatchWidth * _pixelMultiplier;
 
            _swatchTestWidth = (int)(0.1f * MinSwatchWidth * _pixelMultiplier);
 
        }
 

	
 
        public void CancelScan()
 
        {
 
            _canceler?.Cancel();
 
        }
 

	
 
        private static class ColorMatcher
 
        {
 
            public static Color Color;
 

	
 
            public static bool IsMatch(Color otherColor)
 
            {
 
                return PixelColor.IsMatch(Color, otherColor);
 
            }
 
        }
 
        
 
        private bool IsPossibleSwatchSlice(Pixels pixels, int x, int y)
 
        {
 
            // 1.) Check if the top pixel is a dark pixel.
 
            if (!PixelColor.IsDark(pixels.ColorAt(x, y))) return false;
 
            
 
            // 2.) grab the swatch color 2 down from top border
 
            ColorMatcher.Color = pixels.ColorAt(x, y + _swatchTopBorder);
 

	
 
            // Scan the column from 2 below the top to 3 above the bottom to ensure the color matches
 
            for (int i = 2; i < (_swatchHeight - 3); ++i)
 
            {
 
                if (!pixels.DoesPixelMatch(x, y + i, ColorMatcher.IsMatch)) return false;
 
            }
 

	
 
            return true;
 
        }
 

	
 
        private bool IsPossibleSwatchUpperLeft(Pixels pixels, int x, int y)
 
        {
 
            bool result = true;
 

	
 
            int swatchSolidWidth   = _swatchWidth  - _swatchLeftBorder - _swatchRightBorder;
 
            int swatchSolidHeight  = _swatchHeight - _swatchTopBorder - _swatchBottomBorder;
 
            int swatchSolidLeftX   = x + _swatchLeftBorder;
 
            int swatchSolidTopY    = y + _swatchTopBorder;
 
            int swatchSolidRightX  = swatchSolidLeftX + swatchSolidWidth - 1;
 
            int swatchSolidBottomY = swatchSolidTopY + swatchSolidHeight - 1;
 
            
 
            Color swatchColor = pixels.ColorAt(swatchSolidLeftX, swatchSolidTopY);
 
            ColorMatcher.Color = swatchColor;
 

	
 
            // Check the other 3 corners of the swatch size for color match
 
            Color testColor = Color.Black;
 
            bool upperRightResult = pixels.DoesPixelMatch(swatchSolidRightX, swatchSolidTopY, ColorMatcher.IsMatch);
 
            if (!upperRightResult)
 
            {
 
                testColor = pixels.ColorAt(swatchSolidRightX, swatchSolidTopY);
 
                WriteLog(LogVerbosity.Excessive, "Upper-right mismatch for {8}, {9} - found {0},{1},{2} at {3}, {4} expected {5},{6},{7}", testColor.R, testColor.G, testColor.B, swatchSolidRightX, swatchSolidTopY, swatchColor.R, swatchColor.G, swatchColor.B, x, y);
 
            }
 
            bool lowerLeftResult = pixels.DoesPixelMatch(swatchSolidLeftX, swatchSolidBottomY, ColorMatcher.IsMatch);
 
            if (!lowerLeftResult)
 
            {
 
                testColor = pixels.ColorAt(swatchSolidLeftX, swatchSolidBottomY);
 
                WriteLog(LogVerbosity.Excessive, "Lower-left mismatch for {8}, {9} - found {0},{1},{2} at {3}, {4} expected {5},{6},{7}", testColor.R, testColor.G, testColor.B, swatchSolidLeftX, swatchSolidBottomY, swatchColor.R, swatchColor.G, swatchColor.B, x, y);
 
            }
 
            bool lowerRightResult = pixels.DoesPixelMatch(swatchSolidRightX, swatchSolidBottomY, ColorMatcher.IsMatch);
 
            if (!lowerRightResult)
 
            {
 
                testColor = pixels.ColorAt(swatchSolidRightX, swatchSolidBottomY);
 
                WriteLog(LogVerbosity.Excessive, "Lower-right mismatch for {8}, {9} - found {0},{1},{2} at {3}, {4} expected {5},{6},{7}", testColor.R, testColor.G, testColor.B, swatchSolidRightX, swatchSolidBottomY, swatchColor.R, swatchColor.G, swatchColor.B, x, y);
 
            }
 

	
 
            result &= upperRightResult;
 
            result &= lowerLeftResult;
 
            result &= lowerRightResult;
 
            if (!result)
 
            {
 
                // Box corners test failed
 
                WriteLog(LogVerbosity.High, "Failed to find left edge for potential swatch of color {2}, {3}, {4} at {0}, {1}", x, y, swatchColor.R, swatchColor.G, swatchColor.B);
 
                return false;
 
            }
 
            
 
            // Find the right side of the swatch, if necessary.
 
            if (!_lockedSwatchWidth)
 
            {
 
                int xCutoff = Math.Min(x + _maxSwatchWidth - _swatchRightBorder, _targetBitmap.Width);
 
                int newRight = swatchSolidRightX;
 
                for (int px = swatchSolidRightX; px < xCutoff; px++)
 
                {
 
                    if (pixels.DoesPixelMatch(px, swatchSolidTopY, ColorMatcher.IsMatch) &&
 
                        pixels.DoesPixelMatch(px, swatchSolidBottomY, ColorMatcher.IsMatch))
 
                    {
 
                        newRight = px;
 
                    }
 
                    else break;
 
                }
 

	
 
                swatchSolidRightX = newRight;
 
                _swatchWidth = swatchSolidRightX + _swatchRightBorder - x + _pixelMultiplier;
 
                WriteLog(LogVerbosity.High, "Setting new swatch width of {0}.", _swatchWidth);
 
            }
 

	
 
            // scan down the right and left sides
 
            for (int yOff = _pixelMultiplier; yOff < (swatchSolidHeight - _pixelMultiplier); ++yOff)
 
            {
 
                result &= pixels.DoesPixelMatch(swatchSolidLeftX, swatchSolidTopY + yOff, ColorMatcher.IsMatch);
 
                if (!result)
 
                {
 
                    testColor = pixels.ColorAt(swatchSolidLeftX, swatchSolidTopY + yOff);
 
                    break;
 
                }
 
                result &= pixels.DoesPixelMatch(swatchSolidRightX, swatchSolidTopY + yOff, ColorMatcher.IsMatch);
 
                if (!result)
 
                {
 
                    testColor = pixels.ColorAt(swatchSolidRightX, swatchSolidTopY + yOff);
 
                    break;
 
                }
 
            }
 
            if (!result)
 
            {
 
                WriteLog(LogVerbosity.Normal, "Failed to find left/right edges for potential swatch of color {2}, {3}, {4} at ({0}, {1}) [failed color = {5},{6},{7}]", x, y, swatchColor.R, swatchColor.G, swatchColor.B, testColor.R, testColor.G, testColor.B);
 
                return false;
 
            }
 
            for (int xOff = _pixelMultiplier; xOff < (swatchSolidWidth - _pixelMultiplier); ++xOff)
 
            {
 
                result &= pixels.DoesPixelMatch(swatchSolidLeftX + xOff, swatchSolidTopY, ColorMatcher.IsMatch);
 
                if (!result)
 
                {
 
                    testColor = pixels.ColorAt(swatchSolidLeftX + xOff, swatchSolidTopY);
 
                    break;
 
                }
 
                result &= pixels.DoesPixelMatch(swatchSolidLeftX + xOff, swatchSolidBottomY, ColorMatcher.IsMatch);
 
                if (!result)
 
                {
 
                    testColor = pixels.ColorAt(swatchSolidLeftX + xOff, swatchSolidBottomY);
 
                    break;
 
                }
 
            }
 
            if (!result)
 
            {
 
                WriteLog(LogVerbosity.Normal, "Failed to match upper/lower edges for potential swatch of color {2}, {3}, {4} at ({0}, {1}) [failed color = {5},{6},{7}]", x, y, swatchColor.R, swatchColor.G, swatchColor.B, testColor.R, testColor.G, testColor.B);
 
                return false;
 
            }
 

	
 

	
 
            // test the left edge for dark pixels -- the bottom-most pixel is bright now
 
            int i = 0;
 
            for (i = _pixelMultiplier; result && i < _swatchHeight - _pixelMultiplier; ++i)
 
            {
 
                result &= pixels.DoesPixelMatch(x, y + i, PixelColor.IsDark);
 
                if (!result) break;
 
            }
 
            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;
 
                        }
 
                    }
 
                    WriteLog(LogVerbosity.Excessive, "False-positive patch of color {0},{1},{2} at {3},{4}", patchColor.R, patchColor.G, patchColor.B, roughX, roughY);
 
                }
 
            }
 
            return false;
 
        }
 

	
 

	
 
        public static Reaction Calculate3WayReaction(PlayerProfile profile, PaintColor expectedColor, PaintColor reactedColor, Reagent reagent0, Reagent reagent1, Reagent reagent2, bool firstBuffer)
 
        {
 
            // A 3-reagent reaction.
 
            Reaction? reaction1;
 
            Reaction? reaction2;
 

	
 
            if (firstBuffer)
 
            {
 
                reaction1 = profile.FindReaction(reagent0, reagent1);
 
                reaction2 = profile.FindReaction(reagent0, reagent2);                
 
            }
 
            else
 
            {
 
                 reaction1 = profile.FindReaction(reagent0, reagent2);
 
                 reaction2 = profile.FindReaction(reagent1, reagent2);               
 
            }
 

	
 
            Debug.Assert(reaction1 != null);
 
            Debug.Assert(reaction2 != null);
 

	
 
            int r = reactedColor.Red   - expectedColor.Red   - reaction1.Red   - reaction2.Red;
 
            int g = reactedColor.Green - expectedColor.Green - reaction1.Green - reaction2.Green;
 
            int b = reactedColor.Blue  - expectedColor.Blue  - reaction1.Blue  - reaction2.Blue;
 
            
 
            return new Reaction(r, g, b);
 
        }
 
        
 
        public static Reaction CalculateReaction(PaintColor expectedColor, PaintColor reactedColor)
 
        {
 
            // A 2-reagent reaction.
 
            int r = reactedColor.Red   - expectedColor.Red;
 
            int g = reactedColor.Green - expectedColor.Green;
 
            int b = reactedColor.Blue  - expectedColor.Blue;
 
            return new Reaction(r, g, b);
 
        }
 
    }
 
}
...
 
\ No newline at end of file
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)