Changeset - f1323ba34306
[Not reviewed]
default
0 8 0
Jason Maltzen - 5 years ago 2019-11-07 16:31:54
jason@hiddenachievement.com
Improved logging, sanity checking of settings (especially pixel multiplier), updated readme with some additional details on advanced settings.
8 files changed with 157 insertions and 95 deletions:
0 comments (0 inline, 0 general)
AppSettings.cs
Show inline comments
 
using System;
 

	
 
namespace DesertPaintLab
 
{
 
    public class AppSettings
 
    {
 
        private AppSettings()
 
        {
 
        }
 

	
 
        private static Settings _settings = new Settings();
 

	
 
        public static void Get(string key, out int value)
 
        public static void Get(string key, out int value, int defaultValue)
 
        {
 
            _settings.Get(key, out value);
 
            if (!_settings.TryGet(key, out value))
 
            {
 
                value = defaultValue;
 
            }
 
        }
 
        public static void Get(string key, out bool value)
 
        public static void Get(string key, out bool value, bool defaultValue)
 
        {
 
            _settings.Get(key, out value);
 
            if (!_settings.TryGet(key, out value))
 
            {
 
                value = defaultValue;
 
            }
 
        }
 
        public static void Set(string key, int value)
 
        {
 
            _settings.Set(key, value);
 
        }
 
        public static void Set(string key, bool value)
 
        {
 
            _settings.Set(key, value);
 
        }
 

	
 
        public static void Save()
 
        {
 
            string settingsPath = System.IO.Path.Combine(FileUtils.AppDataPath, "settings");
 
            _settings.Save(settingsPath);
 
        }
 
        
 

 
        public static bool Load()
 
        {
 
            string settingsPath = System.IO.Path.Combine(FileUtils.AppDataPath, "settings");
 
            return _settings.Load(settingsPath);
 
        }
 
    }
 
}
 

	
MainWindow.cs
Show inline comments
...
 
@@ -92,49 +92,49 @@ public partial class MainWindow : Gtk.Wi
 
        {
 
            // failed to find ingredients.txt file
 
            MessageDialog md = new MessageDialog(this,
 
                DialogFlags.DestroyWithParent,
 
                MessageType.Error, ButtonsType.Close,
 
                "Failed to find ingredients.txt file. Please check your installation.");
 

	
 
            md.Run();
 
            md.Destroy();
 
            Application.Quit();
 
        }
 
        ReagentManager.Load(ingredientsPath);
 

	
 
        Build();
 

	
 
        // get the root window
 
        rootWindow = Gdk.Global.DefaultRootWindow;
 

	
 
        // get its width and height
 
        AppSettings.Load();
 

	
 
        ShowPreferencesDialog();
 

	
 
        bool enableDebugMenu;
 
        AppSettings.Get("EnableDebugMenu", out enableDebugMenu);
 
        AppSettings.Get("EnableDebugMenu", out enableDebugMenu, false);
 
        this.DebugAction.Visible = enableDebugMenu;
 

	
 
        ReactionRecorder.Instance.OnReactionRecorded += OnReactionRecorded;
 

	
 
        if (!OpenProfile())
 
        {
 
            shouldShutDown = true;
 
        }
 

	
 
        if (!shouldShutDown)
 
        {
 
            captureView = new CaptureView(profile, screenBuffer);
 
            generatorView = new RecipeGeneratorView(profile);
 
            generatorView.SetStatus += OnStatusUpdate;
 
            generatorView.Started += OnGeneratorRunning;
 
            generatorView.Stopped += OnGeneratorStopped;
 
            simulatorView = new SimulatorView(profile);
 

	
 
            contentContainer.Add(captureView);
 
            captureView.Show();
 
        }
 
    }
 

	
 
    bool ConfirmedExit()
...
 
@@ -329,51 +329,52 @@ public partial class MainWindow : Gtk.Wi
 
                md.Destroy();
 
                profileSelected = false;
 
            }
 
        }
 

	
 
        return profileSelected;
 
    }
 

	
 
    protected void OnDeleteEvent(object sender, DeleteEventArgs a)
 
    {
 
        if (ConfirmedExit())
 
        {
 
            a.RetVal = true;
 
            Application.Quit();
 
        }
 
        else
 
        {
 
            a.RetVal = false;
 
        }
 
    }
 

	
 
    protected virtual void OnDebugScreenshot(object sender, System.EventArgs e)
 
    {
 
        int screenWidth, screenHeight;
 
        rootWindow.GetSize(out screenWidth, out screenHeight);
 
        DesertPaintLab.AppSettings.Get("ScreenWidth", out screenWidth);
 
        DesertPaintLab.AppSettings.Get("ScreenHeight", out screenHeight);
 
        int windowScreenWidth, windowScreenHeight;
 
        rootWindow.GetSize(out windowScreenWidth, out windowScreenHeight);
 
        AppSettings.Get("ScreenWidth", out screenWidth, windowScreenWidth);
 
        AppSettings.Get("ScreenHeight", out screenHeight, windowScreenHeight);
 
        Gdk.Image rootImage = rootWindow.GetImage(0, 0, screenWidth, screenHeight);
 
        screenBuffer.GetFromImage(rootImage, rootImage.Colormap, 0, 0, 0, 0, screenWidth, screenHeight);
 
        string screenshotDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
 
        string filename;
 
        int i = 0;
 
        do
 
        {
 
            ++i;
 
            filename = System.IO.Path.Combine(screenshotDir, String.Format("DesertPaintLab_{0}.png", i));
 
        } while (System.IO.File.Exists(filename));
 
        screenBuffer.Save(filename, "png");
 
    }
 

	
 
    protected virtual void OnNewProfile(object sender, System.EventArgs e)
 
    {
 
        if (unsavedData)
 
        {
 
            MessageDialog md = new MessageDialog(this,
 
                DialogFlags.DestroyWithParent,
 
                MessageType.Warning, ButtonsType.OkCancel,
 
                "Your last reaction was unsaved." +
 
                "Are you sure you want to lose your changes?");
 

	
 
            ResponseType resp = (ResponseType)md.Run();
...
 
@@ -704,76 +705,79 @@ public partial class MainWindow : Gtk.Wi
 
        if (simulatorView.Visible == false)
 
        {
 
            simulatorView.ResizeChildren();
 
            simulatorView.CheckResize();
 
            contentContainer.Add(simulatorView);
 
            simulatorView.Show();
 
        }
 
        CheckResize();
 
        // TODO: enable/disable menu options based on view
 
        NewProfileAction.Sensitive = true;
 
        OpenProfileAction.Sensitive = true;
 
        ImportProfileAction.Sensitive = true;
 
    }
 

	
 
    protected void ShowPreferencesDialog()
 
    {
 
        int detectedScreenWidth;
 
        int detectedScreenHeight;
 
        rootWindow.GetSize(out detectedScreenWidth, out detectedScreenHeight);
 
        int screenWidth = detectedScreenWidth;
 
        int screenHeight = detectedScreenHeight;
 
        int pixelMultiplier = 1;
 
        int interfaceSizeIndex = (int)(InterfaceSize.Small);
 

	
 
        AppSettings.Get("ScreenWidth", out screenWidth);
 
        AppSettings.Get("ScreenHeight", out screenHeight);
 
        AppSettings.Get("PixelMultiplier", out pixelMultiplier);
 
        AppSettings.Get("InterfaceSize", out interfaceSizeIndex);
 
        AppSettings.Get("ScreenWidth", out screenWidth, detectedScreenWidth);
 
        AppSettings.Get("ScreenHeight", out screenHeight, detectedScreenHeight);
 
        AppSettings.Get("PixelMultiplier", out pixelMultiplier, 1);
 
        AppSettings.Get("InterfaceSize", out interfaceSizeIndex, (int)InterfaceSize.Small);
 
        InterfaceSize interfaceSize = (InterfaceSize)interfaceSizeIndex;
 

	
 
        // Sanitize
 
        pixelMultiplier = Math.Max(1, Math.Min(pixelMultiplier, 4));
 

	
 
        ScreenCheckDialog screenCheckDialog = new ScreenCheckDialog {
 
            DetectedScreenWidth = detectedScreenWidth,
 
            DetectedScreenHeight = detectedScreenHeight,
 
            ScreenWidth = screenWidth,
 
            ScreenHeight = screenHeight,
 
            GamePixelWidth = pixelMultiplier,
 
            InterfaceSize = interfaceSize
 
        };
 

	
 
        ResponseType resp = (ResponseType)screenCheckDialog.Run();
 

	
 
        screenWidth = Math.Max(640, screenCheckDialog.ScreenWidth); // don't support screen smaller than 640x480 no matter what the user says
 
        screenHeight = Math.Max(480, screenCheckDialog.ScreenHeight);
 
        pixelMultiplier = Math.Max(1, screenCheckDialog.GamePixelWidth); // Don't allow 0/negative pixel multiplier
 
        interfaceSize = screenCheckDialog.InterfaceSize;
 
        screenCheckDialog.Destroy();
 

	
 
        AppSettings.Set("ScreenWidth", screenWidth);
 
        AppSettings.Set("ScreenHeight", screenHeight);
 
        AppSettings.Set("PixelMultiplier", pixelMultiplier);
 
        AppSettings.Set("InterfaceSize", (int)interfaceSize);
 
        AppSettings.Save();
 

	
 
        screenBuffer = new Gdk.Pixbuf(Gdk.Colorspace.Rgb, false, 8, screenWidth, screenHeight);
 

	
 
        ReactionRecorder.Instance.SetPixelMultiplier(pixelMultiplier);
 
        ReactionRecorder.Instance.SetInterfaceSize(interfaceSize);
 
    }
 

	
 
    protected void OnPreferences(object sender, EventArgs e)
 
    {
 
        ShowPreferencesDialog();
 

	
 
        int screenWidth = 1920;
 
        int screenHeight = 1080;
 
        int pixelMultiplier = 1;
 
        int interfaceSizeIndex = (int)(InterfaceSize.Small);
 
        int screenWidth;
 
        int screenHeight;
 
        int pixelMultiplier;
 
        int interfaceSizeIndex;
 

	
 
        AppSettings.Get("ScreenWidth", out screenWidth);
 
        AppSettings.Get("ScreenHeight", out screenHeight);
 
        AppSettings.Get("PixelMultiplier", out pixelMultiplier);
 
        AppSettings.Get("InterfaceSize", out interfaceSizeIndex);
 
        AppSettings.Get("ScreenWidth", out screenWidth, 1920);
 
        AppSettings.Get("ScreenHeight", out screenHeight, 1080);
 
        AppSettings.Get("PixelMultiplier", out pixelMultiplier, 1);
 
        AppSettings.Get("InterfaceSize", out interfaceSizeIndex, (int)InterfaceSize.Small);
 

	
 
        captureView.ScreenBuffer = screenBuffer;
 
    }
 
}
README.md
Show inline comments
...
 
@@ -83,43 +83,55 @@ In many cases, it may be easiest to go b
 

	
 
### 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.
 
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.
 

	
 
* _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
 
* _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 was developed using [MonoDevelop](http://www.monodevelop.com/), and originally used the [Stetic GTK UI designer](http://www.monodevelop.com/documentation/stetic-gui-designer/).
ReactionRecorder.cs
Show inline comments
...
 
@@ -10,48 +10,56 @@
 

	
 
 The above copyright notice and this permission notice shall be included in
 
 all copies or substantial portions of the Software.
 

	
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
 THE SOFTWARE.
 
*/
 

	
 
using System;
 
using System.IO;
 

	
 
namespace DesertPaintLab
 
{
 
    // ReactionRecorder - business logic for recording paint reactions
 
    public class ReactionRecorder
 
    {
 
        public delegate void CaptureProgressHandler(int x, int y);
 
        public CaptureProgressHandler OnCaptureProgress;
 

	
 
        public enum LogVerbosity
 
        {
 
            Low,
 
            Normal,
 
            High,
 
            Excessive
 
        }
 

	
 
        const int COLOR_TOLERANCE = 3;
 

	
 
        const InterfaceSize DEFAULT_INTERFACE_SIZE = InterfaceSize.Small;
 

	
 
        // Swatch is 301x20 solid color, 1px darker left/top, 1px black outer left/top, 1px light right/bottom, 1px bright right/bottom
 
        // top-right and bottom-left are bright instead of black
 

	
 
        // Color bars are 4x302 solid color, 2px border on all sides (darker left/top, lighter bottom/right
 

	
 
        public static readonly int[] SWATCH_HEIGHT = 
 
        {
 
            24, // tiny
 
            24, // small
 
            24, // medium
 
            24, // large
 
            24 // huge
 
        };
 
        public static readonly int[] SWATCH_WIDTH =
 
        {
 
            306, // tiny
 
            306, // small
 
            306, // medium
 
            320, // large
 
            350 // huge
...
 
@@ -136,48 +144,50 @@ namespace DesertPaintLab
 
                pixelMultiplier = value;
 
                UpdateSwatchSizes();
 
            }
 
        }
 
        private InterfaceSize interfaceSize;
 
        public InterfaceSize InterfaceSize
 
        {
 
            get
 
            {
 
                return interfaceSize;
 
            }
 
            set
 
            {
 
                interfaceSize = value;
 
                UpdateSwatchSizes();
 
            }
 
        }
 

	
 
        bool firstRun = true;
 
        int lastSwatchX = -1;
 
        int lastSwatchY = -1;
 
        public int SwatchX { get { return lastSwatchX; } }
 
        public int SwatchY { get { return lastSwatchY; } }
 

	
 
        private LogVerbosity logVerbosity = LogVerbosity.Normal;
 

	
 
        private class PixelColor
 
        {
 
            public byte r;
 
            public byte g;
 
            public byte b;
 

	
 
            public bool IsMatch(PixelColor other)
 
            {
 
                return ((Math.Abs(r - other.r) <= COLOR_TOLERANCE) &&
 
                    (Math.Abs(g - other.g) <= COLOR_TOLERANCE) &&
 
                    (Math.Abs(b - other.b) <= COLOR_TOLERANCE));
 
            }
 

	
 
            public static bool IsDark(PixelColor color)
 
            {
 
                return (color.r < 0x47) && (color.g < 0x47) && (color.b < 0x47);
 
            }
 

	
 
            public static bool IsPapyrus(PixelColor color)
 
            {
 
                // red between 208 and 244
 
                // 240 and 255
 
                // green between 192 and 237
 
                // 223 and 250
...
 
@@ -285,51 +295,51 @@ namespace DesertPaintLab
 
                for (int yOff = 1; ok && (yOff < patchHeight - 1); ++yOff)
 
                {
 
                    for (int xOff = 1; ok && (xOff < patchWidth - 1); ++xOff)
 
                    {
 
                        ok &= DoesPixelMatch(x + xOff, y + yOff, _tempColor2.IsMatch);
 
                    }
 
                }
 
                return ok;
 
            }
 
        }
 

	
 
        private static ReactionRecorder _instance;
 
        public static ReactionRecorder Instance
 
        {
 
            get {
 
                if (_instance == null)
 
                {
 
                    _instance = new ReactionRecorder();
 
                }
 
                return _instance;
 
            }
 
        }
 

	
 
        public StreamWriter Log { get; set; }
 
        private void WriteLog(string format, params object[] args)
 
        private void WriteLog(LogVerbosity verbosity, string format, params object[] args)
 
        {
 
            if (Log != null)
 
            if ((Log != null) && (verbosity <= logVerbosity))
 
            {
 
                Log.WriteLine(format, args);
 
            }
 
        }
 

	
 
        public delegate void ReactionRecordedHandler(Reagent reagent1, Reagent reagent2, Reaction reaction);
 
        public ReactionRecordedHandler OnReactionRecorded;
 

	
 
        public ReactionRecorder()
 
        {
 
            pixelMultiplier = 1;
 
            interfaceSize = DEFAULT_INTERFACE_SIZE;
 

	
 
            UpdateSwatchSizes();
 
        }
 

	
 
        public ReactionRecorder(int pixelMultiplier)
 
        {
 
            this.pixelMultiplier = pixelMultiplier;
 
            this.interfaceSize = DEFAULT_INTERFACE_SIZE;
 

	
 
            UpdateSwatchSizes();
 
        }
 

	
...
 
@@ -382,339 +392,339 @@ namespace DesertPaintLab
 
            //// 2.) Check the pixel above it to see if it's from the papy texture.
 
            //int otherPixelStart = testPixelStart - stride;
 
            //isPapyAbove = ((otherPixelStart >= 0) &&
 
            //    IsPapyTexture(pixBytes[otherPixelStart++], pixBytes[otherPixelStart++], pixBytes[otherPixelStart]));
 

	
 
            //// 3.) Check the pixel below where the swatch should be to see if it's also from the papy texture.
 
            //otherPixelStart = testPixelStart + (stride * swatchHeight);
 
            //isPapyBelow = (IsPapyTexture(pixBytes[otherPixelStart++], pixBytes[otherPixelStart++], pixBytes[otherPixelStart]));
 

	
 
            //bool result = isDarkPixel && isPapyAbove && isPapyBelow;
 
            bool result = isDarkPixel;
 

	
 
            // grab the swatch color
 
            // 2 down from top border
 
            pixels.ColorAt(x, y + 2, ref color);
 

	
 
            // scan the column from 2 below the top to 3 above the bottom to ensure the color matches
 
            for (int i = 2; result && (i < (swatchHeight-3)); ++i)
 
            {
 
                result &= pixels.DoesPixelMatch(x, y + i, color.IsMatch);
 
            }
 

	
 
            if (!result)
 
            {
 
                WriteLog("Swatch slice at {0}, {1} failed to match", x, y);
 
                WriteLog(LogVerbosity.Normal, "Swatch slice at {0}, {1} failed to match", x, y);
 
            }
 

	
 
            return result;
 
        }
 

	
 
        unsafe private bool IsPossibleSwatchUpperLeft(Pixels pixels, int x, int y)
 
        {
 
            int testPixelStart = pixels.ComputeOffset(x, y);
 

	
 
            if (testPixelStart < pixels.stride)
 
            {
 
                WriteLog("Can't test {0},{1} - is not at least 1 row in", x, y);
 
                WriteLog(LogVerbosity.Normal, "Can't test {0},{1} - is not at least 1 row in", x, y);
 
                return false;
 
            }
 

	
 
            bool result = true;
 

	
 
            int swatchSolidWidth = swatchWidth - 4;
 
            int swatchSolidHeight = swatchHeight - 5; // 2 top and 3 bottom pixels are slightly different colors
 
            int swatchSolidLeftX = x + 2;
 
            int swatchSolidTopY = y + 2;
 
            int swatchSolidRightX = swatchSolidLeftX + swatchSolidWidth - 1;
 
            int swatchSolidBottomY = swatchSolidTopY + swatchSolidHeight - 1;
 

	
 
            PixelColor swatchColor = new PixelColor();
 
            pixels.ColorAt(swatchSolidLeftX, swatchSolidTopY, ref swatchColor);
 

	
 
            // Check the other 3 corners of the swatch size for color match
 
            PixelColor testColor = new PixelColor();
 
            bool upperRightResult = pixels.DoesPixelMatch(swatchSolidRightX, swatchSolidTopY, swatchColor.IsMatch);
 
            //if (!upperRightResult)
 
            //{
 
            //    pixels.ColorAt(swatchSolidRightX, swatchSolidTopY, ref testColor);
 
            //    WriteLog("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);
 
            //}
 
            if (!upperRightResult)
 
            {
 
                pixels.ColorAt(swatchSolidRightX, swatchSolidTopY, ref testColor);
 
                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, swatchColor.IsMatch);
 
            //if (!lowerLeftResult)
 
            //{
 
            //    pixels.ColorAt(swatchSolidLeftX, swatchSolidBottomY, ref testColor);
 
            //    WriteLog("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);
 
            //}
 
            if (!lowerLeftResult)
 
            {
 
                pixels.ColorAt(swatchSolidLeftX, swatchSolidBottomY, ref testColor);
 
                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, swatchColor.IsMatch);
 
            //if (!lowerRightResult)
 
            //{
 
            //    pixels.ColorAt(swatchSolidRightX, swatchSolidBottomY, ref testColor);
 
            //    WriteLog("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);
 
            //}
 
            if (!lowerRightResult)
 
            {
 
                pixels.ColorAt(swatchSolidRightX, swatchSolidBottomY, ref testColor);
 
                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("Failed to find left edge for potential swatch of color {2}, {3}, {4} at {0}, {1}", x, y, swatch_r, swatch_g, swatch_b);
 
                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;
 
            }
 

	
 
            // scan down the right and left sides
 
            for (int yOff = 1; yOff < (swatchSolidHeight - 1); ++yOff)
 
            {
 
                result &= pixels.DoesPixelMatch(swatchSolidLeftX, swatchSolidTopY + yOff, swatchColor.IsMatch);
 
                if (!result)
 
                {
 
                    pixels.ColorAt(swatchSolidLeftX, swatchSolidTopY + yOff, ref testColor);
 
                    break;
 
                }
 
                result &= pixels.DoesPixelMatch(swatchSolidRightX, swatchSolidTopY + yOff, swatchColor.IsMatch);
 
                if (!result)
 
                {
 
                    pixels.ColorAt(swatchSolidRightX, swatchSolidTopY + yOff, ref testColor);
 
                }
 
            }
 
            if (!result)
 
            {
 
                WriteLog("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);
 
                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 = 1; xOff < (swatchSolidWidth - 1); ++xOff)
 
            {
 
                result &= pixels.DoesPixelMatch(swatchSolidLeftX + xOff, swatchSolidTopY, swatchColor.IsMatch);
 
                if (!result)
 
                {
 
                    pixels.ColorAt(swatchSolidLeftX + xOff, swatchSolidTopY, ref testColor);
 
                    break;
 
                }
 
                result &= pixels.DoesPixelMatch(swatchSolidLeftX + xOff, swatchSolidBottomY, swatchColor.IsMatch);
 
                if (!result)
 
                {
 
                    pixels.ColorAt(swatchSolidLeftX + xOff, swatchSolidBottomY, ref testColor);
 
                    break;
 
                }
 
            }
 
            if (!result)
 
            {
 
                WriteLog("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);
 
                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 = 1; result && i < swatchHeight - 1; ++i)
 
            {
 
                result &= pixels.DoesPixelMatch(x, y + i, PixelColor.IsDark);
 
            }
 
            if (!result)
 
            {
 
                // No dark border on the left side
 
                WriteLog("Failed to find left border for potential swatch of color {2}, {3}, {4} at {0}, {1}", x, y, swatchColor.r, swatchColor.g, swatchColor.b);
 
                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 papyrus above and below the swatch
 
            bool borderError = false;
 
            int papyErrorCount = 0;
 
            for (i = 0; result && (i < swatchWidth - 1); ++i)
 
            {
 
                bool isBorder = pixels.DoesPixelMatch(x + i, y, PixelColor.IsDark);
 
                result &= isBorder;
 
                if (!isBorder)
 
                {
 
                    WriteLog("Probably swatch at {0},{1} failed upper border test at {2},{3}", x, y, x + i, y);
 
                    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 papyrus
 
                // The row just above is shaded, so check 2 above
 
                if (y > 1)
 
                {
 
                    bool isPapyrus = pixels.DoesPixelMatch(x + i, y - 2, PixelColor.IsPapyrus);
 
                    papyErrorCount += isPapyrus ? 0 : 1;
 

	
 
                }
 
                else
 
                {
 
                    ++papyErrorCount;
 
                }
 

	
 
                // Checking along the bottom of the swatch for papyrus
 
                if (y < pixels.height)
 
                {
 
                    bool isPapyrus = pixels.DoesPixelMatch(x + i, y + swatchHeight, PixelColor.IsPapyrus);
 
                    papyErrorCount += isPapyrus ? 0 : 1;
 
                }
 
                else
 
                {
 
                    ++papyErrorCount;
 
                }
 
            }
 

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

	
 
            return result;
 
        }
 

	
 
        unsafe private bool TestPosition(int x, int y, Pixels pixels, ref PaintColor reactedColor, ref int redPixelStart)
 
        {
 
            PixelColor pixelColor = new PixelColor();
 

	
 
            // Check 4 corners of solid area and left/right solid bar areas
 
            bool foundSwatch = IsPossibleSwatchUpperLeft(pixels, x, y); // ((pixel_r < 0x46) && (pixel_g < 0x46) && (pixel_b < 0x46));
 
            if (foundSwatch)
 
            {
 
                WriteLog("Found probable swatch at {0},{1} - checking border slices", x, y);
 
                WriteLog(LogVerbosity.Normal, "Found probable swatch at {0},{1} - checking border slices", x, y);
 
                int borderXOffset = 0;
 
                for (borderXOffset = 2; foundSwatch && (borderXOffset < swatchTestWidth); ++borderXOffset)
 
                {
 
                    foundSwatch &= IsPossibleSwatchSlice(pixels, x + borderXOffset, y);
 
                    if (!foundSwatch)
 
                    {
 
                        WriteLog("Failed slice test at {0},{1}", x + borderXOffset, y);
 
                        WriteLog(LogVerbosity.Normal, "Failed slice test at {0},{1}", x + borderXOffset, y);
 
                        break;
 
                    }
 
                    foundSwatch &= IsPossibleSwatchSlice(pixels, x + swatchWidth - borderXOffset, y);
 
                    if (!foundSwatch)
 
                    {
 
                        WriteLog("Failed slice test at {0},{1}", x + swatchWidth - borderXOffset, y);
 
                        WriteLog(LogVerbosity.Normal, "Failed slice test at {0},{1}", x + swatchWidth - borderXOffset, y);
 
                        break;
 
                    }
 
                }
 
            }
 

	
 
            if (foundSwatch)
 
            {
 
                // WE FOUND THE SWATCH!
 
                // Now we know where the color bars are.
 
                int redPixelCount = pixels.LengthOfColorAt(x, y + redBarSpacing, PixelColor.IsRed);
 
                reactedColor.Red = (byte)Math.Round((float)redPixelCount * 255f / (float)colorBarWidth);
 

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

	
 
                int bluePixelCount = pixels.LengthOfColorAt(x, y + blueBarSpacing, PixelColor.IsBlue);
 
                reactedColor.Blue = (byte)Math.Round((float)bluePixelCount * 255f / (float)colorBarWidth);
 
                WriteLog("Found the color swatch at {0}, {1}. Color={2} Red={3}px Green={4}px Blue={5}px", x, y, reactedColor, redPixelCount, greenPixelCount, bluePixelCount);
 
                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;
 
            }
 
            return false;
 
        }
 

	
 
        unsafe public bool CaptureReaction(byte* pixBytes, int screenshotWidth, int screenshotHeight, int stride, int numChannels, int bitsPerSample)
 
        {
 
            PaintColor reactedColor = new PaintColor();
 
            int redPixelStart = 0;
 
            ScreenWidth = screenshotWidth;
 
            ScreenHeight = screenshotHeight;
 
            IsCaptured = false;
 
            _recordedColor.Clear();
 

	
 
            Pixels pixels = new Pixels
 
            {
 
                pixBytes = pixBytes,
 
                width = screenshotWidth,
 
                height = screenshotHeight,
 
                stride = stride,
 
                numChannels = numChannels,
 
                pixelMultiplier = 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, ref redPixelStart))
 
                {
 
                    IsCaptured = true;
 
                    RedBarX = (redPixelStart % stride) / numChannels;
 
                    RedBarY = redPixelStart / stride;
 
                    RecordedColor = reactedColor;
 
                    return true;
 
                }
 
                else
 
                {
 
                    firstRun = true;
 
                }
 
            }
 

	
 
            int verbosityIdx;
 
            AppSettings.Get("Log.Verbosity", out verbosityIdx, 1);
 
            logVerbosity = (LogVerbosity)verbosityIdx;
 

	
 
            int startX;
 
            int endX;
 
            int startY;
 
            int endY;
 
            DesertPaintLab.AppSettings.Get("ScanOffsetX", out startX);
 
            DesertPaintLab.AppSettings.Get("ScanOffsetY", out startY);
 
            DesertPaintLab.AppSettings.Get("ScanLimitX", out endX);
 
            DesertPaintLab.AppSettings.Get("ScanLimitY", out endY);
 
            if (endX == 0) endX = screenshotWidth;
 
            if (endY == 0) endY = screenshotHeight;
 
            if (endX > screenshotWidth) endX = screenshotWidth;
 
            if (endY > screenshotHeight) endY = screenshotHeight;
 
            if (startX < 2) startX = 2;
 
            if (startY < 2) startY = 2;
 
            if (startX > screenshotWidth) startX = 2;
 
            if (startY > screenshotHeight) startY = 2;
 
            AppSettings.Get("ScanArea.Min.X", out startX, 0);
 
            AppSettings.Get("ScanArea.Min.Y", out startY, 0);
 
            AppSettings.Get("ScanArea.Max.X", out endX, screenshotWidth);
 
            AppSettings.Get("ScanArea.Max.Y", out endY, screenshotHeight);
 
            startX = Math.Max(2, Math.Min(startX, screenshotWidth-2));
 
            startY = Math.Max(2, Math.Min(startY, screenshotHeight-2));
 
            endX = Math.Min(screenshotWidth-2, Math.Max(2, endX));
 
            endY = Math.Min(screenshotHeight-2, Math.Max(2, endY));
 

	
 
            int patchTestSize = ((swatchHeight - 5) / 2) - 1;
 
            PixelColor patchColor = new PixelColor();
 
            for (int roughX = startX; roughX < endX - colorBarWidth + patchTestSize; roughX += patchTestSize)
 
            {
 
                for (int roughY = startY; roughY < (endY - (blueBarSpacing + 10) + patchTestSize  /*53*/); roughY += patchTestSize)
 
                {
 
                    OnCaptureProgress?.Invoke(roughX, roughY);
 
                    if (!pixels.IsSolidPatchAt(roughX, roughY, patchTestSize, patchTestSize)) continue;
 
                    pixels.ColorAt(roughX, roughY, ref patchColor);
 
                    //Console.WriteLine("Found a solid patch of {2},{3},{4} at {0}, {1}", roughX, roughY, patchColor.r, patchColor.g, patchColor.b);
 
                    WriteLog(LogVerbosity.Excessive, "Found a solid patch of {2},{3},{4} at {0}, {1}", roughX, roughY, patchColor.r, patchColor.g, patchColor.b);
 
                    for (X = Math.Max(0, roughX - patchTestSize); X < roughX; ++X)
 
                    {
 
                        for (Y = Math.Max(0, roughY - patchTestSize); Y < roughY; ++Y)
 
                        {
 
                            //WriteLog("Checking for potential swatch at {0},{1} after found square at {2},{3}", X, Y, roughX, roughY);
 
                            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, ref redPixelStart))
 
                            {
 
                                RedBarX = (redPixelStart % stride) / numChannels;
 
                                RedBarY = redPixelStart / stride;
 
                                RecordedColor = reactedColor;
 
                                lastSwatchX = X;
 
                                lastSwatchY = Y;
 
                                firstRun = false;
 
                                IsCaptured = true;
 
                                return true;
 
                            }
 
                        }
 
                    }
 
                    //System.Console.WriteLine("False-positive patch of color {0},{1},{2} at {3},{4}", patchColor.r, patchColor.g, patchColor.b, roughX, roughY);
 
                    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 bool RecordReaction(PlayerProfile profile, PaintColor expectedColor, PaintColor reactedColor, Reagent[] reagents)
 
        {
 
            bool saved = false;
 
            int r, g, b;
 
            if (reagents[2] != null)
 
            {
 
                // A 3-reagent reaction.
 
                Reaction reaction1 = profile.FindReaction(reagents[0], reagents[1]);
 
                Reaction reaction2 = profile.FindReaction(reagents[0], reagents[2]);
 
                Reaction reaction3 = profile.FindReaction(reagents[1], reagents[2]);
 
                
 
                r = reactedColor.Red - expectedColor.Red;
 
                g = reactedColor.Green - expectedColor.Green;
 
                b = reactedColor.Blue - expectedColor.Blue;
 
                
 
                if (reaction2 == null)
 
                {
 
                    r = r - reaction1.Red - reaction3.Red;
 
                    g = g - reaction1.Green - reaction3.Green;
Settings.cs
Show inline comments
 
using System;
 
using System.Collections.Generic;
 
using System.IO;
 
using System.Text.RegularExpressions;
 

	
 
namespace DesertPaintLab
 
{
 
    public class Settings
 
    {
 
        public Settings()
 
        {
 
        }
 

	
 
        private Dictionary<string, string> _settings = new Dictionary<string, string>();
 

	
 
        public void Get(string key, out int value)
 
        public bool TryGet(string key, out int value)
 
        {
 
            value = 0;
 
            string valStr;
 
            if ( _settings.TryGetValue(key.ToLower(), out valStr) )
 
            bool found = _settings.TryGetValue(key.ToLower(), out valStr);
 
            if (found)
 
            {
 
                Int32.TryParse(valStr, out value);
 
            }
 
            return found;
 
        }
 
        public void Get(string key, out bool value)
 
        public bool TryGet(string key, out bool value)
 
        {
 
            value = false;
 
            string valStr;
 
            if ( _settings.TryGetValue(key.ToLower(), out valStr) )
 
            bool found = _settings.TryGetValue(key.ToLower(), out valStr);
 
            if (found)
 
            {
 
                Boolean.TryParse(valStr, out value);
 
            }
 
            return found;
 
        }
 
        public void Set(string key, int value)
 
        {
 
            _settings[key.ToLower()] = value.ToString();
 
        }
 
        public void Set(string key, bool value)
 
        {
 
            _settings[key.ToLower()] = value.ToString();
 
        }
 

	
 
        public void Reset()
 
        {
 
            _settings.Clear();
 
        }
 

	
 
        public void Save(string settingsPath)
 
        {
 
            using (StreamWriter writer = new StreamWriter(settingsPath))
 
            {
 
                foreach (KeyValuePair<string, string> pair in _settings)
 
                {
 
                    writer.WriteLine("{0}={1}", pair.Key, pair.Value);
 
                }
 
            }
UI/CaptureView.cs
Show inline comments
...
 
@@ -255,50 +255,50 @@ namespace DesertPaintLab
 
            //SetExpectedColor(recipeColor.Red, recipeColor.Green, recipeColor.Blue);
 
            
 
            if (reactionKnown)
 
            {
 
                reactedColor.Set(recipe.ReactedColor);
 
                reactionSwatch.Color = reactedColor;
 
            }
 
            else
 
            {
 
                reactionSwatch.Clear(); 
 
            }
 
        }
 

	
 
        unsafe void BeginCapture()
 
        {
 
            captureButton.Sensitive = false;
 
            ingredient1ComboBox.Sensitive = false;
 
            ingredient2ComboBox.Sensitive = false;
 
            ingredient3ComboBox.Sensitive = false;
 
            clearReactionButton.Sensitive = false;
 

	
 
            progressBar.Show();
 
            recordButton.Hide();
 

	
 
            bool enableDebugMenu = false;
 
            DesertPaintLab.AppSettings.Get("EnableDebugMenu", out enableDebugMenu);
 
            bool enableDebugMenu;
 
            AppSettings.Get("EnableDebugMenu", out enableDebugMenu, false);
 

	
 
            StreamWriter log = null;
 
            if (enableDebugMenu)
 
            {
 
                string logfile = FileUtils.FindNumberedFile("DesertPaintLab_Capture", "log", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
 
                log = new StreamWriter(logfile);
 
                ReactionRecorder.Instance.Log = log;
 
            }
 

	
 
            if (notifyCaptureProgress == null)
 
            {
 
                notifyCaptureProgress = new Gtk.ThreadNotify(new Gtk.ReadyEvent(NotifyCaptureProgress));
 
                notifyCaptureComplete = new Gtk.ThreadNotify(new Gtk.ReadyEvent(NotifyCaptureComplete));
 
            }
 

	
 
            this.reactionSwatch.Clear();
 

	
 
            Thread thr = new Thread(new ThreadStart(this.CaptureReactionThread));
 
            thr.Priority = ThreadPriority.AboveNormal;
 
            thr.Start();
 
            //OnCaptureComplete(isCaptured, reactedColor, screenWidth, screenHeight, stride, redPixelStart);
 
        }
 

	
 
        unsafe private void CaptureReactionThread()
...
 
@@ -326,52 +326,52 @@ namespace DesertPaintLab
 
            double numer = (double)(ReactionRecorder.Instance.X);
 
            double fraction = (denom > 0 && numer > 0) ? (numer / denom) : 0;
 
            if (numer > denom)
 
            {
 
                fraction = 1;
 
            }
 
            progressBar.Fraction = fraction;
 
        }
 

	
 
        void OnCaptureComplete(bool isCaptured, PaintColor reactedColor, int swatchX, int swatchY)
 
        {
 
            int screenWidth = screenBuffer.Width;
 
            int screenHeight = RecordBuffer.Height;
 
            int stride = RecordBuffer.Rowstride;
 

	
 
            StreamWriter log = ReactionRecorder.Instance.Log;
 
            if (log != null)
 
            {
 
                log.Flush();
 
                log.Close();
 
                log = null;
 
                ReactionRecorder.Instance.Log = null;
 
            }
 

	
 
            bool enableDebugMenu = false;
 
            DesertPaintLab.AppSettings.Get("EnableDebugMenu", out enableDebugMenu);
 
            bool debugScreenshot = false;
 
            DesertPaintLab.AppSettings.Get("DebugScreenshot", out debugScreenshot);
 
            bool enableDebugMenu;
 
            DesertPaintLab.AppSettings.Get("EnableDebugMenu", out enableDebugMenu, false);
 
            bool debugScreenshot;
 
            DesertPaintLab.AppSettings.Get("DebugScreenshot", out debugScreenshot, false);
 
            if (enableDebugMenu && debugScreenshot)
 
            {
 
                if (!isCaptured)
 
                {
 
                    // write out the whole screenshot on a failure to capture
 
                    string screenshotDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
 
                    string filename = FileUtils.FindNumberedFile("DesertPaintLab_Colormatch", "png", screenshotDir);
 
                    RecordBuffer.Save(filename, "png");
 
                }
 
                else
 
                {
 
                    // record the swatch that was captured
 
                    // write out the screenshot
 
                    string screenshotDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
 
                    string filename = FileUtils.FindNumberedFile("DesertPaintLab_Colormatch", "png", screenshotDir);
 
                    int x = swatchX - 16;
 
                    int captureAreaWidth = ReactionRecorder.Instance.ColorBarWidth + 32;
 
                    captureAreaWidth = Math.Min(screenWidth - x, captureAreaWidth);
 
                    int y = swatchY - 16;
 
                    int captureAreaHeight = ReactionRecorder.Instance.BlueBarSpacing + 42;
 
                    captureAreaHeight = Math.Min(screenHeight - y, captureAreaHeight);
 

	
 
                    Gdk.Pixbuf outPixBuf = new Gdk.Pixbuf(RecordBuffer, Math.Max(0, x), Math.Max(0, y), 
 
                        captureAreaWidth, captureAreaHeight);
...
 
@@ -452,61 +452,61 @@ namespace DesertPaintLab
 
            recordButton.Show();
 

	
 
            // release
 
            RecordBuffer = null;
 

	
 
            ingredient1ComboBox.Sensitive = true;
 
            ingredient2ComboBox.Sensitive = true;
 
            ingredient3ComboBox.Sensitive = (GetSelectedReagentName(1) != null) && (GetSelectedReagentName(2) != null);
 
        }
 

	
 
        private void NotifyCaptureComplete()
 
        {
 
            OnCaptureComplete(
 
                ReactionRecorder.Instance.IsCaptured,
 
                ReactionRecorder.Instance.RecordedColor,
 
                ReactionRecorder.Instance.SwatchX,
 
                ReactionRecorder.Instance.SwatchY);
 
        }
 

	
 
        unsafe void CaptureReactionColor()
 
        {
 
            // Take a screenshot.
 
            int screenWidth, screenHeight;
 
            Gdk.Window rootWindow = Gdk.Global.DefaultRootWindow;
 
            DesertPaintLab.AppSettings.Get("ScreenWidth", out screenWidth);
 
            DesertPaintLab.AppSettings.Get("ScreenHeight", out screenHeight);
 
            AppSettings.Get("ScreenWidth", out screenWidth, 1920);
 
            AppSettings.Get("ScreenHeight", out screenHeight, 1080);
 
            Gdk.Image rootImage = rootWindow.GetImage(0, 0, screenWidth, screenHeight);
 
            RecordBuffer = screenBuffer;
 
            RecordBuffer.GetFromImage(rootImage, rootImage.Colormap, 0, 0, 0, 0, screenWidth, screenHeight);
 
            rootImage.Unref();
 
            System.GC.Collect(); // really, clean up now
 

	
 
            int pixelMultiplier;
 
            AppSettings.Get("PixelMultiplier", out pixelMultiplier);
 
            AppSettings.Get("PixelMultiplier", out pixelMultiplier, 1);
 
            if (pixelMultiplier == 0) pixelMultiplier = 1;
 
            int interfaceSizeIndex = 1;
 
            AppSettings.Get("InterfaceSize", out interfaceSizeIndex);
 
            int interfaceSizeIndex;
 
            AppSettings.Get("InterfaceSize", out interfaceSizeIndex, (int)InterfaceSize.Small);
 
            InterfaceSize interfaceSize = (InterfaceSize)interfaceSizeIndex;
 
            ReactionRecorder.Instance.PixelMultiplier = pixelMultiplier;
 
            ReactionRecorder.Instance.InterfaceSize = interfaceSize;
 

	
 
            BeginCapture();
 
        }
 

	
 
        unsafe void CaptureReactionColorFromLoadedImage(string filePath)
 
        {
 
            // read the screenshot from a file
 
            string screenshotDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
 
            FileStream pixStream = new FileStream(filePath, FileMode.Open);
 
            RecordBuffer = new Gdk.Pixbuf(pixStream);
 
            ReactionRecorder.Instance.SetPixelMultiplier(1);
 
            // TODO: allow for other size
 
            InterfaceSize interfaceSize = InterfaceSize.Small;
 
            if (filePath.Contains("Large"))
 
            {
 
                interfaceSize = InterfaceSize.Large;
 
            }
 
            else if (filePath.Contains("Huge"))
 
            {
 
                interfaceSize = InterfaceSize.Huge;
 
            }
UI/RecipeGeneratorView.cs
Show inline comments
...
 
@@ -92,49 +92,49 @@ namespace DesertPaintLab
 
            recipeColorColumn.Title = "Color";
 

	
 
            recipeList.AppendColumn(recipeColorColumn);
 
            recipeColorColumn.AddAttribute(recipeColumnCell, "text", 0);
 

	
 
            colorStore.Clear();
 

	
 
            colorStore.SetSortColumnId(0, Gtk.SortType.Ascending);
 
            recipeList.Model = RecipeModel;
 

	
 
            recipeList.Selection.Changed += OnColorSelected;
 

	
 
            recipeIngredientsView.AppendColumn("Quantity", new Gtk.CellRendererText(), "text", 0);
 
            recipeIngredientsView.AppendColumn("Ingredient", new Gtk.CellRendererText(), "text", 1);
 
            recipeIngredientsView.Model = new Gtk.ListStore(typeof(string), typeof(string));
 

	
 
            profile.LoadRecipes();
 

	
 
            canceling = false;
 
            running = false;
 
            pauseForCheckpoint = false;
 

	
 
            generator = new RecipeGenerator(profile.Reactions);
 
            int threads;
 
            DesertPaintLab.AppSettings.Get("GeneratorThreads", out threads);
 
            DesertPaintLab.AppSettings.Get("GeneratorThreads", out threads, 15);
 
            if (threads <= 0) { threads = 15; }
 
            generator.MaxThreads = (uint)threads;
 
            generator.InitRecipes(profile.Recipes);
 

	
 
            generator.Progress += OnProgress;
 
            generator.Finished += OnFinished;
 
            generator.NewRecipe += OnNewRecipe;
 

	
 
            string stateFile = System.IO.Path.Combine(profile.Directory, PAINT_STATE_FILE);
 
            if (System.IO.File.Exists(stateFile))
 
            {
 
                generator.LoadState(stateFile);
 
                if (generator.CanResume)
 
                {
 
                    beginButton.Label = "Restart";
 
                    stopResumeButton.Label = "Resume";
 
                    stopResumeButton.Sensitive = true;
 

	
 
                    maxRecipeSpinButton.Value = Math.Max(generator.MaxConcentration, 14); //
 
                    fullQuantitySpinButton.Value = generator.FullQuantity; // TODO: read/save profile info
 
                    fullQuantityDepthSpinButton.Value = generator.FullQuantityDepth; // TODO: read/save profile info
 
                    maxIngredientsSpinButton.Value = generator.MaxReagents;
 
                    checkButtonRibbon.Active = false;
 
                }
...
 
@@ -190,83 +190,83 @@ namespace DesertPaintLab
 
            reagentMaxColumn.PackStart (reagentMaxCell, true);
 
            //reagentMaxColumn.AddAttribute(reagentMaxCell, "text", 0);
 

	
 
            reagentListStore = new Gtk.ListStore(typeof(Reagent), typeof(string), typeof(Reagent), typeof(Reagent));
 
            foreach (string reagentName in ReagentManager.Names)
 
            {
 
                Reagent reagent = ReagentManager.GetReagent(reagentName);
 
                reagentListStore.AppendValues(reagent, reagentName); // , reagent, reagent);
 
            }
 

	
 
            reagentEnabledColumn.SetCellDataFunc (reagentEnabledCell, new Gtk.TreeCellDataFunc (RenderReagentToggle));
 
            reagentCostColumn.SetCellDataFunc (reagentCostCell, new Gtk.TreeCellDataFunc (RenderReagentCost));
 
            reagentMaxColumn.SetCellDataFunc (reagentMaxCell, new Gtk.TreeCellDataFunc (RenderReagentQuantity));
 

	
 
            // Assign the model to the TreeView
 
            reagentListView.Model = reagentListStore;
 
            reagentListView.Sensitive = true;
 

	
 
            reagentListView.AppendColumn(reagentEnabledColumn);
 
            reagentListView.AppendColumn(reagentNameColumn);
 
            reagentListView.AppendColumn(reagentCostColumn);
 
            reagentListView.AppendColumn(reagentMaxColumn);
 

	
 
            bool ribbons = false;
 
            profile.ProfileSettings.Get("Generator.Ribbons", out ribbons);
 
            bool foundSetting = profile.ProfileSettings.TryGet("Generator.Ribbons", out ribbons);
 
            if (ribbons)
 
            {
 
                checkButtonRibbon.Active = true;
 
                InitStateForRibbons();
 
            }
 
            else
 
            {
 
                checkButtonRibbon.Active = false;
 
                InitStateForPaint();
 
            }
 
            
 
            ShowAll();
 
        }
 

	
 
        private void InitStateForPaint()
 
        {
 
            maxRecipeSpinButton.Adjustment.Lower = PaintRecipe.PAINT_RECIPE_MIN_CONCENTRATION;
 
            fullQuantitySpinButton.Adjustment.Upper = 30;
 
            if (generator == null)
 
            {
 
                generator = new RecipeGenerator(profile.Reactions);
 
                int threads;
 
                DesertPaintLab.AppSettings.Get("GeneratorThreads", out threads);
 
                AppSettings.Get("GeneratorThreads", out threads, 15);
 
                if (threads <= 0) { threads = 15; }
 
                generator.MaxThreads = (uint)threads;
 

	
 
                generator.Progress += OnProgress;
 
                generator.Finished += OnFinished;
 
                generator.NewRecipe += OnNewRecipe;
 

	
 
                bool enableDebugMenu = false;
 
                DesertPaintLab.AppSettings.Get("EnableDebugMenu", out enableDebugMenu);
 
                bool logGenerator = false;
 
                DesertPaintLab.AppSettings.Get("GeneratorLog", out logGenerator);
 
                bool enableDebugMenu;
 
                AppSettings.Get("EnableDebugMenu", out enableDebugMenu, false);
 
                bool logGenerator;
 
                AppSettings.Get("GeneratorLog", out logGenerator, false);
 
                if (enableDebugMenu && logGenerator)
 
                {
 
                    string logDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
 
                    generator.Log = System.IO.Path.Combine(logDir, "dpl_generator.txt");
 
                }
 
            }
 
            generator.InitRecipes(profile.Recipes);
 

	
 
            string stateFile = System.IO.Path.Combine(profile.Directory, PAINT_STATE_FILE);
 
            if (System.IO.File.Exists(stateFile))
 
            {
 
                generator.LoadState(stateFile);
 
                if (generator.CanResume)
 
                {
 
                    beginButton.Label = "Restart";
 
                    stopResumeButton.Label = "Resume";
 
                    stopResumeButton.Sensitive = true;
 

	
 
                    maxRecipeSpinButton.Value = Math.Max(generator.MaxConcentration, 14); //
 
                    fullQuantitySpinButton.Value = generator.FullQuantity; // TODO: read/save profile info
 
                    fullQuantityDepthSpinButton.Value = generator.FullQuantityDepth; // TODO: read/save profile info
 
                    maxIngredientsSpinButton.Value = generator.MaxReagents;
 
                }
 
            }
...
 
@@ -283,49 +283,49 @@ namespace DesertPaintLab
 
            }
 
            //generator.Log = System.IO.Path.Combine(profile.Directory, "dp_log.txt");
 
            countLabel.Text = String.Format("{0} / {1}", profile.RecipeCount, Palette.Count);
 

	
 
            colorStore.Clear();
 
            foreach (KeyValuePair<string, PaintRecipe> pair in profile.Recipes)
 
            {
 
                if (pair.Value.IsValidForConcentration(PaintRecipe.PAINT_RECIPE_MIN_CONCENTRATION))
 
                {
 
                    string colorName = pair.Key;
 
                    colorStore.AppendValues(colorName);
 
                }
 
            }
 
            recipeList.Show();
 
        }
 

	
 
        private void InitStateForRibbons()
 
        {
 
            maxRecipeSpinButton.Adjustment.Lower = PaintRecipe.RIBBON_RECIPE_MIN_CONCENTRATION;
 
            fullQuantitySpinButton.Adjustment.Upper = 100;
 
            if (generator == null)
 
            {
 
                generator = new RecipeGenerator(profile.Reactions);
 
                int threads;
 
                DesertPaintLab.AppSettings.Get("GeneratorThreads", out threads);
 
                AppSettings.Get("GeneratorThreads", out threads, 15);
 
                if (threads <= 0) { threads = 15; }
 
                generator.MaxThreads = (uint)threads;
 

	
 
                generator.Progress += OnProgress;
 
                generator.Finished += OnFinished;
 
                generator.NewRecipe += OnNewRecipe;
 
            }
 
            generator.InitRecipes(profile.RibbonRecipes);
 

	
 
            string stateFile = System.IO.Path.Combine(profile.Directory, RIBBON_STATE_FILE);
 
            if (System.IO.File.Exists(stateFile))
 
            {
 
                generator.LoadState(stateFile);
 
                if (generator.CanResume)
 
                {
 
                    beginButton.Label = "Restart";
 
                    stopResumeButton.Label = "Resume";
 
                    stopResumeButton.Sensitive = true;
 

	
 
                    maxRecipeSpinButton.Value = Math.Max(generator.MaxConcentration, PaintRecipe.RIBBON_RECIPE_MIN_CONCENTRATION); //
 
                    fullQuantitySpinButton.Value = generator.FullQuantity; // TODO: read/save profile info
 
                    fullQuantityDepthSpinButton.Value = generator.FullQuantityDepth; // TODO: read/save profile info
 
                    maxIngredientsSpinButton.Value = generator.MaxReagents;
 
                }
...
 
@@ -345,49 +345,49 @@ namespace DesertPaintLab
 

	
 
            countLabel.Text = String.Format("{0} / {1}", profile.RibbonCount, Palette.Count);
 

	
 
            colorStore.Clear();
 
            foreach (KeyValuePair<string, PaintRecipe> pair in profile.RibbonRecipes)
 
            {
 
                if (pair.Value.IsValidForConcentration(PaintRecipe.RIBBON_RECIPE_MIN_CONCENTRATION))
 
                {
 
                    string colorName = pair.Key;
 
                    colorStore.AppendValues(colorName);
 
                }
 
            }
 
            recipeList.Show();
 
        }
 

	
 
        public PlayerProfile Profile {
 
            set {
 
                if (profile != value) {
 
                    // TODO: ensure not running
 
                    profile = value;
 
                    profile.LoadRecipes();
 

	
 
                    generator = new RecipeGenerator(profile.Reactions);
 
                    int threads;
 
                    DesertPaintLab.AppSettings.Get("GeneratorThreads", out threads);
 
                    AppSettings.Get("GeneratorThreads", out threads, 15);
 
                    if (threads <= 0) { threads = 15; }
 
                    generator.MaxThreads = (uint)threads;
 
        
 
                    generator.Progress += OnProgress;
 
                    generator.Finished += OnFinished;
 
                    generator.NewRecipe += OnNewRecipe;
 

	
 
                    if (checkButtonRibbon.Active)
 
                    {
 
                        InitStateForRibbons();
 
                    }
 
                    else
 
                    {
 
                        InitStateForPaint();
 
                    }
 
                }
 
            }
 
        }
 

	
 
        protected void OnMinIngredientsChanged(object sender, EventArgs e)
 
        {
 
            Gtk.SpinButton button = (Gtk.SpinButton) sender;
 
            if (button.ValueAsInt > maxIngredientsSpinButton.ValueAsInt)
 
            {
gtk-gui/DesertPaintLab.ScreenCheckDialog.cs
Show inline comments
 

	
 
// This file has been generated by the GUI designer. Do not modify.
 
namespace DesertPaintLab
 
{
 
	public partial class ScreenCheckDialog
 
	{
 
		private global::Gtk.VBox vbox2;
 
		
 
		private global::Gtk.HBox hbox1;
 
		
 
		private global::Gtk.Label label1;
 

	
 
        private global::Gtk.Label detectedResolutionLabel;
 

	
 
        private global::Gtk.Entry screenWidthEntry;
 
		
 
		private global::Gtk.Entry screenHeightEntry;
 
		
 
		private global::Gtk.HBox hbox2;
 

	
 
        private global::Gtk.VBox vbox3;
 
		
 
		private global::Gtk.Label label2;
 
		
 
		private global::Gtk.Entry gamePixelWidthEntry;
 

	
 
        private global::Gtk.Entry gamePixelWidthEntry;
 

	
 
        private global::Gtk.Label label3;
 

	
 
        private global::Gtk.HBox hbox3;
 

	
 
        private global::Gtk.Label interfaceSizeLlabel;
 

	
 
        private global::Gtk.ComboBox interfaceSizeComboBox;
 

	
 
        private global::Gtk.Button buttonOk;
 

	
 
		protected virtual void Build ()
 
		{
 
			global::Stetic.Gui.Initialize (this);
 
			// Widget DesertPaintLab.ScreenCheckDialog
 
			this.Name = "DesertPaintLab.ScreenCheckDialog";
 
			this.Title = "Screen Check";
 
			this.WindowPosition = ((global::Gtk.WindowPosition)(4));
 
			this.Modal = true;
 
			this.BorderWidth = ((uint)(9));
 
			// Internal child DesertPaintLab.ScreenCheckDialog.VBox
 
			global::Gtk.VBox w1 = this.VBox;
 
			w1.Name = "dialog1_VBox";
 
			// Container child dialog1_VBox.Gtk.Box+BoxChild
 
			this.vbox2 = new global::Gtk.VBox ();
 
			this.vbox2.Name = "vbox2";
...
 
@@ -77,80 +81,102 @@ namespace DesertPaintLab
 
			this.screenHeightEntry.WidthRequest = 50;
 
			this.screenHeightEntry.CanFocus = true;
 
			this.screenHeightEntry.Name = "screenHeightEntry";
 
			this.screenHeightEntry.IsEditable = true;
 
			this.screenHeightEntry.InvisibleChar = '●';
 
			this.hbox1.Add (this.screenHeightEntry);
 
			global::Gtk.Box.BoxChild w4 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.screenHeightEntry]));
 
			w4.Position = 2;
 
			this.vbox2.Add (this.hbox1);
 
			global::Gtk.Box.BoxChild w5 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbox1]));
 
			w5.Position = 0;
 
			w5.Expand = false;
 
			w5.Fill = false;
 

	
 
            // Container child vbox2.Gtk.Box+BoxChild
 
            this.detectedResolutionLabel = new global::Gtk.Label();
 
            this.detectedResolutionLabel.Name = "detectedResolutionLabel";
 
            this.detectedResolutionLabel.LabelProp = "Detected Screen Resolution: ??";
 
            this.vbox2.Add(this.detectedResolutionLabel);
 
            global::Gtk.Box.BoxChild wDR = ((global::Gtk.Box.BoxChild)(this.vbox2[this.detectedResolutionLabel]));
 
            wDR.Position =  1;
 
            wDR.Expand = false;
 
            wDR.Fill = false;
 

	
 
            // Container child dialog1_VBox.Gtk.Box+BoxChild
 
            this.vbox3 = new global::Gtk.VBox();
 
            this.vbox3.Name = "vbox3";
 
            this.vbox3.Spacing = 2;
 
            this.vbox3.BorderWidth = ((uint)(20));
 

	
 
            // Container child vbox2.Gtk.Box+BoxChild
 
            this.hbox2 = new global::Gtk.HBox ();
 
			this.hbox2.Name = "hbox2";
 
			this.hbox2.Spacing = 20;
 
			this.hbox2.BorderWidth = ((uint)(10));
 
			// Container child hbox2.Gtk.Box+BoxChild
 
			this.label2 = new global::Gtk.Label ();
 
			this.label2.Name = "label2";
 
			this.label2.LabelProp = "Game Pixel Width in Screen Pixels";
 
			this.hbox2.Add (this.label2);
 
			global::Gtk.Box.BoxChild w6 = ((global::Gtk.Box.BoxChild)(this.hbox2 [this.label2]));
 
			w6.Position = 0;
 
			w6.Expand = false;
 
			w6.Fill = false;
 
			// Container child hbox2.Gtk.Box+BoxChild
 
			this.gamePixelWidthEntry = new global::Gtk.Entry ();
 
			this.gamePixelWidthEntry.WidthRequest = 50;
 
			this.gamePixelWidthEntry.CanFocus = true;
 
			this.gamePixelWidthEntry.Name = "gamePixelWidthEntry";
 
			this.gamePixelWidthEntry.IsEditable = true;
 
			this.gamePixelWidthEntry.InvisibleChar = '●';
 
			this.hbox2.Add (this.gamePixelWidthEntry);
 
			global::Gtk.Box.BoxChild w7 = ((global::Gtk.Box.BoxChild)(this.hbox2 [this.gamePixelWidthEntry]));
 
			w7.Position = 1;
 
			w7.Expand = false;
 
			w7.Fill = false;
 
			this.vbox2.Add (this.hbox2);
 
			global::Gtk.Box.BoxChild w8 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbox2]));
 
			w8.Position = 2;
 
			this.vbox3.Add (this.hbox2);
 
			global::Gtk.Box.BoxChild w8 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.hbox2]));
 
			w8.Position = 0;
 
			w8.Expand = false;
 
			w8.Fill = false;
 

	
 
            // Container child hbox2.Gtk.Box+BoxChild
 
            this.label3 = new global::Gtk.Label();
 
            this.label3.Name = "label2";
 
            this.label3.LabelProp = "(Advanced, should be 1 for most displays and 2 for Macs with retina displays)";
 
            this.vbox3.Add(this.label3);
 
            global::Gtk.Box.BoxChild w8a = ((global::Gtk.Box.BoxChild)(this.vbox3[this.label3]));
 
            w8a.Position = 1;
 
            w8a.Expand = false;
 
            w8a.Fill = false;
 

	
 
            this.vbox2.Add(this.vbox3);
 
            global::Gtk.Box.BoxChild w8b = ((global::Gtk.Box.BoxChild)(this.vbox2[this.vbox3]));
 
            w8b.Position = 2;
 
            w8b.Expand = false;
 
            w8b.Fill = false;
 

	
 
            // Container child vbox2.Gtk.Box+BoxChild
 
            this.hbox3 = new global::Gtk.HBox();
 
            this.hbox3.Name = "hbox3";
 
            this.hbox3.Spacing = 20;
 
            this.hbox3.BorderWidth = ((uint)(10));
 
            // Container child hbox3.Gtk.Box+BoxChild
 
            this.interfaceSizeLlabel = new global::Gtk.Label();
 
            this.interfaceSizeLlabel.Name = "interfaceSizeLabel";
 
            this.interfaceSizeLlabel.LabelProp = "Interface Size";
 
            this.hbox3.Add(this.interfaceSizeLlabel);
 
            global::Gtk.Box.BoxChild interfaceSizeContainer = ((global::Gtk.Box.BoxChild)(this.hbox3[this.interfaceSizeLlabel]));
 
            interfaceSizeContainer.Position = 0;
 
            interfaceSizeContainer.Expand = false;
 
            interfaceSizeContainer.Fill = false;
 
            // Container child hbox2.Gtk.Box+BoxChild
 
            this.interfaceSizeComboBox = global::Gtk.ComboBox.NewText();
 
            this.interfaceSizeComboBox.Name = "interfaceSizeComboBox";
 
            this.hbox3.Add(this.interfaceSizeComboBox);
 
            global::Gtk.Box.BoxChild wInterfaceSize = ((global::Gtk.Box.BoxChild)(this.hbox2[this.interfaceSizeComboBox]));
 
            wInterfaceSize.Position = 1;
 
            this.vbox2.Add(this.hbox3);
 
            global::Gtk.Box.BoxChild wInterfaceSizeBox = ((global::Gtk.Box.BoxChild)(this.vbox2[this.hbox3]));
 
            wInterfaceSizeBox.Position = 0;
 
            wInterfaceSizeBox.Expand = false;
0 comments (0 inline, 0 general)