using System; using System.IO; using System.Threading; namespace DesertPaintLab { [System.ComponentModel.ToolboxItem(true)] public partial class CaptureView : Gtk.Bin { Reagent[] reagents = new Reagent[3]; PaintRecipe recipe = new PaintRecipe(); PaintColor expectedColor = new PaintColor(); PaintColor reactedColor = new PaintColor(); PlayerProfile profile = null; Gdk.Pixbuf screenBuffer = null; Gtk.ThreadNotify notifyCaptureProgress; Gtk.ThreadNotify notifyCaptureComplete; bool recordEnabled = true; bool isCaptured = false; public CaptureView(PlayerProfile profile, Gdk.Pixbuf screenBuffer) { this.profile = profile; this.screenBuffer = screenBuffer; this.Build(); reagents[0] = null; reagents[1] = null; reagents[2] = null; if (unmodifiedSwatch != null) { unmodifiedSwatch.Clear(); } if (reactionSwatch != null) { reactionSwatch.Clear(); } PopulateDropDowns(); recipe.Reactions = profile.Reactions; } public Gdk.Pixbuf ScreenBuffer { get { return screenBuffer; } set { if (screenBuffer != null) { // TODO: free it up? } screenBuffer = value; } } public Gdk.Pixbuf RecordBuffer { get; set; } public PlayerProfile Profile { set { profile = value; PopulateDropDowns(); recipe.Reactions = profile.Reactions; UpdateIngredients(); // TODO: reset views } } public void DisableRecord() { recordEnabled = false; recordButton.Sensitive = false; recordNoReactionButton.Sensitive = false; } public void EnableRecord() { recordEnabled = true; if (isCaptured) { recordEnabled = true; } UpdateIngredients(); } public void PopulateDropDowns() { ReagentManager.PopulateReagents(ref ingredient1ComboBox); ReagentManager.PopulateReagents(ref ingredient2ComboBox); ReagentManager.PopulateReagents(ref ingredient3ComboBox); ingredient2ComboBox.Sensitive = false; ingredient3ComboBox.Sensitive = false; Gtk.TreeIter iter; ingredient1ComboBox.Model.IterNthChild(out iter, 0); ingredient1ComboBox.SetActiveIter(iter); ingredient2ComboBox.Model.IterNthChild(out iter, 0); ingredient2ComboBox.SetActiveIter(iter); ingredient3ComboBox.Model.IterNthChild(out iter, 0); ingredient3ComboBox.SetActiveIter(iter); } protected void SetExpectedColor(byte red, byte green, byte blue) { expectedColor.Red = red; expectedColor.Green = green; expectedColor.Blue = blue; unmodifiedSwatch.Color = expectedColor; } protected void SetExpectedColor(PaintColor color) { SetExpectedColor(color.Red, color.Green, color.Blue); } protected string GetSelectedReagentName(int reagentNum) { Gtk.ComboBox[] comboBox = { ingredient1ComboBox, ingredient2ComboBox, ingredient3ComboBox }; Gtk.ComboBox desired = comboBox[reagentNum-1]; Gtk.TreeIter selectIter; string reagentName = null; if (desired.GetActiveIter(out selectIter)) { reagentName = (string)desired.Model.GetValue(selectIter, 0); } if (reagentName != null && reagentName.Length == 0) { reagentName = null; } return reagentName; } protected void UpdateIngredients() { Reaction reaction1, reaction2; string reagentName; reagents[0] = null; reagents[1] = null; reagents[2] = null; bool reactionKnown = true; isCaptured = false; recordButton.Sensitive = false; recordNoReactionButton.Sensitive = false; clearReactionButton.Sensitive = false; clearReactionButton.Visible = false; recordNoReactionButton.Sensitive = false; recordNoReactionButton.Visible = false; recipe.Clear(); if ((reagentName = GetSelectedReagentName(1)) == null) { // Nothing selected as reagent 1 ingredient2ComboBox.Sensitive = false; ingredient3ComboBox.Sensitive = false; unmodifiedSwatch.Clear(); reactionSwatch.Clear(); captureButton.Sensitive = false; recordNoReactionButton.Sensitive = false; recordNoReactionButton.Visible = false; return; } recipe.AddReagent(reagentName); reagents[0] = ReagentManager.GetReagent(reagentName); ingredient2ComboBox.Sensitive = true; if ((reagentName = GetSelectedReagentName(2)) == null) { ingredient3ComboBox.Sensitive = false; recordButton.Sensitive = false; recordNoReactionButton.Sensitive = false; recordNoReactionButton.Visible = false; reactionKnown = false; reactionSwatch.Clear(); return; } recipe.AddReagent(reagentName); reagents[1] = ReagentManager.GetReagent(reagentName); ingredient3ComboBox.Sensitive = true; captureButton.Sensitive = true; recordNoReactionButton.Sensitive = true; reaction1 = profile.FindReaction(reagents[0], reagents[1]); // TODO: really should handle reagent0==reagent1 better if ((reaction1 != null) || (reagents[0] == reagents[1])) { clearReactionButton.Sensitive = recordEnabled; clearReactionButton.Visible = true; ingredient3ComboBox.Sensitive = true; recordNoReactionButton.Sensitive = false; recordNoReactionButton.Visible = false; if ((reagentName = GetSelectedReagentName(3)) != null) { clearReactionButton.Sensitive = false; clearReactionButton.Visible = false; recordNoReactionButton.Sensitive = false; recordNoReactionButton.Visible = false; recipe.AddReagent(reagentName); reagents[2] = ReagentManager.GetReagent(reagentName); if (!reactionKnown) { Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Error, Gtk.ButtonsType.Ok, "To do a three-ingredient reaction test, " + "you must first recored the reaction of " + "the first two ingredients."); md.Run(); md.Destroy(); captureButton.Sensitive = false; } reaction1 = profile.FindReaction(reagents[0], reagents[2]); reaction2 = profile.FindReaction(reagents[1], reagents[2]); if (reactionKnown && (reaction1 == null) && (reaction2 == null)) { Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Error, Gtk.ButtonsType.Ok, "To do a three-ingredient reaction test, " + "you must first record the reaction of " + "either the first or second ingredient " + "with the third ingredient."); md.Run(); md.Destroy(); captureButton.Sensitive = false; } if ((reaction1 == null) && (reagents[0] != reagents[2])) { reactionKnown = false; } if ((reaction2 == null) && (reagents[1] != reagents[2])) { reactionKnown = false; } } } else { reactionKnown = false; ingredient3ComboBox.Sensitive = false; recordNoReactionButton.Sensitive = recordEnabled; recordNoReactionButton.Visible = true; } expectedColor.Set(recipe.BaseColor); unmodifiedSwatch.Color = expectedColor; //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; recordNoReactionButton.Sensitive = false; progressBar.Show(); recordButton.Hide(); recordNoReactionButton.Hide(); 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() { byte* pixBytes = (byte*)RecordBuffer.Pixels; ReactionRecorder.Instance.OnCaptureProgress += OnCaptureProgress; ReactionRecorder.Instance.CaptureReaction(pixBytes, RecordBuffer.Width, RecordBuffer.Height, RecordBuffer.Rowstride, RecordBuffer.NChannels, RecordBuffer.BitsPerSample); ReactionRecorder.Instance.OnCaptureProgress -= OnCaptureProgress; notifyCaptureComplete.WakeupMain(); } private void OnCaptureProgress(int x, int y) { notifyCaptureProgress.WakeupMain(); } private void NotifyCaptureProgress() { // TODO: progress bar //ReactionRecorder.Instance.X; //ReactionRecorder.Instance.Y; double denom = (double)(ReactionRecorder.Instance.ScreenWidth - ReactionRecorder.Instance.ColorBarWidth); 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; 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); //Gdk.Pixbuf outPixBuf = new Gdk.Pixbuf(RecordBuffer, Math.Max(0, redPixelStartX), Math.Max(0, redPixelStartY), // ReactionRecorder.Instance.ColorBarWidth, ReactionRecorder.Instance.GreenBarSpacing - ReactionRecorder.Instance.RedBarSpacing); //RecordBuffer.Save(filename, "png"); outPixBuf.Save(filename, "png"); } } //RecordBuffer.Save("screenshot.png", "png"); if (isCaptured) { string warning = ""; if (reactedColor.Red == 0) { warning = warning + "\nRed is too low."; } if (reactedColor.Green == 0) { warning = warning + "\nGreen is too low."; } if (reactedColor.Blue == 0) { warning = warning + "\nBlue is too low."; } if (reactedColor.Red == 255) { warning = warning + "\nRed is too high."; } if (reactedColor.Green == 255) { warning = warning + "\nGreen is too high."; } if (reactedColor.Blue == 255) { warning = warning + "\nBlue is too high."; } // Always update the reacted swatch this.reactionSwatch.Color = reactedColor; this.reactedColor.Set(reactedColor); if (warning.Length != 0) { isCaptured = true; Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Error, Gtk.ButtonsType.Ok, "Reaction clipped. You will need to do a " + "3-way reaction to test this pair. Details: " + warning); md.Run(); md.Destroy(); // reaction clipped - don't let them record recordButton.Sensitive = false; recordNoReactionButton.Sensitive = false; } else { recordButton.Sensitive = recordEnabled; recordNoReactionButton.Sensitive = false; } } else { Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Error, Gtk.ButtonsType.Ok, "Pigment Lab dialog box NOT FOUND. Please ensure " + "that there is an unobstructed view of the dialog " + "and that your interface size is set to 'small' " + "when you press the Capture button."); md.Run(); md.Destroy(); } progressBar.Hide(); 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; 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, 1); if (pixelMultiplier == 0) pixelMultiplier = 1; 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; } ReactionRecorder.Instance.InterfaceSize = interfaceSize; System.Console.WriteLine("Loaded {0} size {1} x {2} stride {3} bits {4} colorspace {5} channels {6} hasAlpha {7}", filePath, RecordBuffer.Width, RecordBuffer.Height, RecordBuffer.Rowstride, RecordBuffer.BitsPerSample, RecordBuffer.Colorspace, RecordBuffer.NChannels, RecordBuffer.HasAlpha); System.GC.Collect(); // really, clean up now BeginCapture(); } protected virtual void OnCapture(object sender, System.EventArgs e) { CaptureReactionColor(); } protected void OnCaptureFromFile(object sender, EventArgs e) { Gtk.FileChooserDialog fileDialog = new Gtk.FileChooserDialog("Select screenshot file.", (Gtk.Window)Toplevel, Gtk.FileChooserAction.Open, Gtk.Stock.Cancel, Gtk.ResponseType.Cancel, Gtk.Stock.Open, Gtk.ResponseType.Accept); Gtk.FileFilter filter = new Gtk.FileFilter(); filter.AddPattern("*.png"); fileDialog.AddFilter(filter); Gtk.ResponseType resp = (Gtk.ResponseType)fileDialog.Run(); if (resp == Gtk.ResponseType.Accept) { string fileName = fileDialog.Filename; string directory = fileDialog.CurrentFolder; if (directory != null) { CaptureReactionColorFromLoadedImage(System.IO.Path.Combine(directory, fileName)); } else { CaptureReactionColorFromLoadedImage(fileName); } } fileDialog.Destroy(); } protected virtual void OnRecord(object sender, System.EventArgs e) { if (ReactionRecorder.Instance.RecordReaction(profile, expectedColor, reactedColor, reagents)) { recordButton.Sensitive = false; } } protected virtual void OnRecordNoReaction(object sender, System.EventArgs e) { if (ReactionRecorder.Instance.RecordReaction(profile, expectedColor, expectedColor, reagents)) { recordNoReactionButton.Sensitive = false; recordNoReactionButton.Visible = false; clearReactionButton.Sensitive = true; clearReactionButton.Visible = true; this.reactionSwatch.Color = expectedColor; this.reactedColor.Set(expectedColor); } } protected virtual void OnChangedIngredient1(object sender, System.EventArgs e) { UpdateIngredients(); } protected virtual void OnChangedIngredient2(object sender, System.EventArgs e) { UpdateIngredients(); } protected virtual void OnChangedIngredient3(object sender, System.EventArgs e) { UpdateIngredients(); } protected void OnClearReaction(object sender, EventArgs e) { string reagentName1 = GetSelectedReagentName(1); string reagentName2 = GetSelectedReagentName(2); string reagentName3 = GetSelectedReagentName(3); if ((reagentName1 != null) && (reagentName2 != null) && (reagentName3 == null)) { Reagent reagent1 = ReagentManager.GetReagent(reagentName1); Reagent reagent2 = ReagentManager.GetReagent(reagentName2); if (null != profile.FindReaction(reagent1, reagent2)) { Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Warning, Gtk.ButtonsType.OkCancel, "This will delete the reaction status between " + // TODO: ingredient1Name + " and " + ingredient2Name + "\n\n" + "ARE YOU SURE?" ); Gtk.ResponseType response = (Gtk.ResponseType)md.Run(); if (response == Gtk.ResponseType.Ok) { // really delete it profile.ClearReaction(reagent1, reagent2); reactionSwatch.Clear(); reactedColor.Clear(); clearReactionButton.Sensitive = false; clearReactionButton.Visible = false; recordNoReactionButton.Sensitive = true; recordNoReactionButton.Visible = true; } md.Destroy(); } } } } }