Changeset - 70b1de28b2a2
[Not reviewed]
default
0 2 0
Jason Maltzen - 12 months ago 2023-12-03 06:00:18
jason@hiddenachievement.com
Re-enable the ability to save debug screenshots based on a setting value to help debug reaction capturing. Update the README to correctly reflect the debug.screenshot setting name, location of the settings file, and removal of the old debug menu.
2 files changed with 11 insertions and 8 deletions:
0 comments (0 inline, 0 general)
README.md
Show inline comments
 
# Desert Paint Codex
 

	
 
(Formerly _Desert Paint Lab_)
 

	
 
This is a tool for players to record their [Pigment Laboratory](https://atitd.wiki/tale10/Pigment_Laboratory)
 
ingredient reactions in [A Tale in the Desert](http://www.atitd.com/).
 

	
 
### Latest Releases
 

	
 
You can find the latest releases at https://desert-paint-lab.malkyne.org/
 

	
 
### Features
 

	
 
* Scans your screen for the Pigment Lab reaction values, without any math, measuring, or manual number entry.
 
* Warns you when a reaction clipped outside of readable range.
 
* Supports multiple character profiles.
 
* Can export to [PracticalPaint](http://www.atitd.org/wiki/tale7/File:PracticalPaint.Zip) data format.
 
* Includes simulator for experimenting with recipes without spending ingredients.
 
* Includes a recipe generator
 

	
 
## Tale 10
 

	
 
* Desert Paint Lab is now Desert Paint Codex, and has a whole new UI, for Tale 10.
 
* Untested reactions are now kept in a list to the left, so you can immediately see what still needs testing.
 
* The application has not yet been updated for ATITD's new UI.  This will be coming soon.
 

	
 

	
 
## Tale 9
 

	
 
* New papyrus texture in interfaces
 
* Earth Light was replaced by Falcon's Bait mushrooms. Practical Paint still uses the name EarthLight internally, so the data files still use that name here as well.
 
* macOS is now a 64-bit app. Windows remains 32-bit due to incompatibilities with the UI library.
 
* macOS build does not support Catalina (is not notarized)
 

	
 
### Known issues
 

	
 
* A batcher installed on a Pigment Lab will prevent finding the right part of the screen with the resulting color.
 

	
 

	
 
## Directions
 

	
 
### Download Desert Paint Lab
 

	
 
You can always find the available downloads, on [the download page](https://desert-paint-lab.malkyne.org/).
 

	
 
Desert Paint Codex now uses Net Core, instead of Mono.  The package should come with everything you need to make it work.
 

	
 

	
 
### Set Up a Profile
 

	
 
The first time you run Desert Paint Lab, it will prompt you to make a profile.  You may either create a new profile, or import an existing PracticalPaint reactions.txt file.
 

	
 
### Start Testing!
 

	
 
Add two ingredients to your Pigment Lab.  Select those same ingredients in Desert Paint Lab.  Then, with the Pigment Lab dialog unobstructred, select the **Capture** button.  Once you are satisfied with the result, click the **Record** button.  The data will automatically be added to your profile.
 

	
 
### Clipped?  Huh?
 

	
 
Occasionally, you will see a warning dialog that informs you that a "Reaction clipped."  That means that one or more of the color components moved outside of the testable range.  This makes it impossible to calculate the reaction from these two ingredients.
 

	
 
You can solve this by doing a three-way test.
 

	
 
1. In your clipped reaction, let's call your **1st** ingredient **A** and your **2nd** ingredient **B**.
 
1. Select a **non-catalyst** ingredient **C** for which you have already tested **C + A** and **C + B**.  It is OK if there was a reaction in those earlier tests.  Desert Paint Lab can work out the math.
 
1. In Desert Paint Lab, select ingredients as follows:
 
    1. **C**
 
    2. **A**
 
    3. **B**
 
1. Add the ingredients in your Pigment Lab _in exactly that order_.
 
1. Capture.
 
1. Record.
 

	
 
For clipped reactions, your **C** ingredient should be one that corrects for the clipped color.  Here are some suggested **C** ingredients for clipping correction:
 

	
 

	
 
Clip Color  | Clip Direction | Correction | Suggested "C" Ingredient
 
------------|----------------|------------|---------------------------
 
Red         | Low            | +Red       | Carrot (224) or Red Sand (144)
 
Red         | High           | -Red       | Silver Powder (16) or Toad Skin (48)
 
Green       | Low            | +Green     | Falcon's Bait (240) or Copper (192)
 
Green       | High           | -Green     | Red Sand (16) or Silver Powder (16)
 
Blue        | Low            | +Blue      | Falcon's Bait (224) or Copper (192)
 
Blue        | High           | -Blue      | Red Sand (24) or Clay (32) or Carrot (32) or Silver Powder (32)
 

	
 

	
 
In many cases, it may be easiest to go back and do these three-way tests after you have finished all of your other testing.
 

	
 
### Catalysts
 

	
 
For catalyst to catalyst reaction tests, follow the three-way instructions, as above.
 

	
 
### Finishing Up
 

	
 
When you're done testing your reactions, you can either use the built-in Pigment Lab simulator (`Window > Run Simulator`) to experiment with recipes, without dipping into your precious ingredient stocks.  Alternatively, you can export your reactions in PracticalPaint format.
 

	
 
## Generating Recipes
 

	
 
### What do all these settings do?
 

	
 
* _Minimum Ingredients_ is the minimum number of ingredients the generator will use in a recipe. It's best to leave this at 1 so the simple single-ingredient recipes are included (like 10 carrots for 'carrot')
 
* _Maximum Ingredients_ limits how many ingredients will be used in a recipe. Ingredients after the 5th will still affect the resulting color, but won't use their reactions.
 
* _Silk Ribbon Recipes_ sets the generator up to create recipes for silk ribbons. Ribbons use a base concentration of 50 instead of the 10 that is used for paint.
 
* _Maximum Concentration_ is the maximum recipe concentration. Recipes with a concentration above 10 will still produce a single db of paint, but can use larger quantities of ingredients to make smaller color adjustments. Increasing this will increase the time required to search all the possible combinations.
 
* _Full Quantity Depth_ is the number of ingredients in the recipe that will use up to the "full quantity" value as the limit for those ingredients over the ingredient's maximum.
 
* _Full Quantity_ is the maximum quantity of any ingredient to use up to the Full Quantity Depth. For example, with a Full Quantity of 15 and a Full Quantity Depth of 3, a recipe could use up to 15 of each of the first 3 ingredients. After that it will be limited by the setting for that specific ingredient.
 

	
 
## Known Issues
 

	
 
### Slowness
 

	
 
If you are running on a multi-screen system, or a very high-resolution screen, you may find that Desert Paint Lab is rather slow in determining paint reactions.  That's because you have a lot of screen real-estate to scan, to look for the Pigment Lab dialog.  You can speed up the scanning process by ensuring that your Pigment Lab Dialog is as far to the upper-left of the screen as possible. Also see the advanced settings (below) for some additional options that may help.
 

	
 
### Multiple Displays
 

	
 
When Desert Paint Lab asks for the resolution for a multiple-display setup, enter the combined resolution of all displays. Generally the detected default for this will be correct. For example, two 1920x1080 displays in a side-by-side configuration would be a combined resolution of 3840x1080. In a stacked configuration, they would be 1920x2160.
 

	
 
### Retina / High-Density Screens
 

	
 
High DPI screens may be displaying the game at something other than a 1:1 game-pixel to screen-pixel ratio.  These screens didn't exist, back when Desert Paint Lab was created. The current version now prompts you for your screen resolution when it starts to work around this. If you see a crash when trying to capture a reaction, it's likely because the resolution is incorrect. For example, Snoerr's MacBook Pro has a retina screen with a resolution of 2880x1800. Mono (and therefore DesertPaintLab) detects it as having a resolution of 1440x900. He enters his resolution as 2880x1800 and sets the Game pixel width in screen pixels to 2.
 

	
 
## Mac user FAQ
 

	
 
Q: How do I find my native screen resolution?
 

	
 
A: You can find it by opening up "About this Mac" from the Apple menu and clicking on the "Displays" tab. That should show the screen size and resolution to enter.
 

	
 
## Advanced Settings
 

	
 
The settings file has several advanced options that are not available in the interface. Some of these are for debugging, while others are useful for improving the speed of finding the pigment lab interface. The settings file is found in AppData\Local\DesertPaintLab\settings on Windows, and in ~/.local/share/DesertPaintLab/settings on macOS.
 
The settings file has several advanced options that are not available in the interface. Some of these are for debugging, while others are useful for improving the speed of finding the pigment lab interface. The settings file is found in AppData\Local\DesertPaintCodex\settings on Windows, and in ~/.local/share/DesertPaintCodex/settings on macOS.
 

	
 
* _scanarea.min.x_ sets the starting horizontal offset when searching for the pigment lab. This is useful in multi-screen setups to skip any leftmost displays. For example, a dual-screen setup with two side-by-side 3840x2160 displays where the game runs on the right screen would set this to 3840 to bypass scanning the left display.
 
* _scanarea.min.y_ Like scanarea.min.x, but skips the upper section of display
 
* _scanarea.max.x_ defines the rightmost edge of the area to scan
 
* _scanarea.max.y_ defines the bottom edge of the area to scan
 
* _enabledebugmenu_ enables debug tool menu to help track down problems. This also causes DPL to write debug log files during scanning and recipe generation. This can slow down scanning and recipe searching.
 
* _debugscreenshot_ automatically saves out a screenshot when searching for the pigment lab to help in debugging
 
* _debug.screenshot_ automatically saves out a screenshot when searching for the pigment lab to help in debugging
 
* _logging.verbosity_ sets the verbosity of the logging between 0 and 3. The higher the value, the more detail is included in the log file.
 

	
 
## For Developers
 

	
 
This application is written in C#, and it uses the [Avalonia UI Framework](https://avaloniaui.net/). You can learn more about getting set up to use Avalonia [here](https://docs.avaloniaui.net/docs/getting-started/).
 

	
 
## Older Versions
 

	
 
The old [Desert Paint Lab repository](https://repos.malkyne.org/ATITD-Tools/Desert-Paint-Lab) is still publicly available, in case anyone wishes to reference the old code, or tinker with it.  However, it will no longer be maintained in any way by Afrah.
...
 
\ No newline at end of file
Services/ReactionScannerService.cs
Show inline comments
...
 
@@ -143,370 +143,374 @@ namespace DesertPaintCodex.Services
 
        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);
 
            reactedColor.Red = (byte)Math.Round(redPixelCount * 255f / _swatchWidth);
 

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

	
 
            int bluePixelCount = pixels.LengthOfColorAt(x, y + _blueBarSpacing, 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);
 
            }
 
            
 
            // _targetBitmap.Save(Path.Combine(ProfileManager.CurrentProfile?.Directory ?? "", "screenshot.png"), ImageFormat.Png);
 

	
 
            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;
 
            }
 

	
 
            SettingsService.Get("Log.Verbosity", out var verbosityIdx, 1);
 
            _logVerbosity = (LogVerbosity)verbosityIdx;
 
            
 
            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
0 comments (0 inline, 0 general)