/// <summary> /// Initializes a new instance of the <see cref="Paint.PictureStateManager"/> class. /// <param name='filenameResolver' Determines the filename for all files saved/loaded /> /// <param name='pictureIOManager' Handles the saving of the imageStateData /> /// <param name='renderTargetHandler' Handles the rendering of the appropriate render target when undo/redoing a change /> /// <param name='imageStateData' Current save point, max undo/redo count etc /> /// </summary> public PictureStateManager(IFilenameResolver filenameResolver, IPictureIOManager pictureIOManager, IRenderTargertHandler renderTargetHandler, ImageStateData imageStateData) { this.canvasRecorder = new CanvasRecorder(); this.renderTargetHandler = renderTargetHandler; this.filenameResolver = filenameResolver; this.pictureIOManager = pictureIOManager; this.ImageStateData = imageStateData; }
/// <summary> /// Initializes a new instance of the <see cref="Paint.CanvasPlaybackApp"/> class. /// </summary> /// <param name='canvasPlayback'>Canvas Playback data</param> /// <param name='imageStateData'>ImageSaveData</param> /// <param name='toolboxLayoutDefinition'>Layout of the toolbox</param> /// <param name='deviceScale'>The device scale/resolution. 1 = normal. 2 = retina.</param> public CanvasPlaybackApp( ICanvasPlayback canvasPlayback, ImageStateData imageStateData, ToolboxLayoutDefinition toolboxLayoutDefinition, int deviceScale) : base(imageStateData, toolboxLayoutDefinition, deviceScale) { this.canvasPlayback = canvasPlayback; this.calculatePlaybackSpeed = new CalculatePlaybackSpeed(); }
/// <summary> /// Initializes a new instance of the <see cref="Paint.PaintApp"/> class. /// Instantiate our GraphicsDeviceManager and establish where the content folder is /// </summary> /// <param name='pictureIOManager'>Picture IO Manager</param> /// <param name='filenameResolver'>Filename Resolver</param> /// <param name='imageStateData'>ImageSaveData</param> /// <param name='saveBusyMessageDisplay'>Class for dsplaying the 'Busy - saving' message</param> /// <param name='toolboxLayoutDefinition'>Layout of the toolbox</param> /// <param name='deviceScale'>The device scale/resolution. 1 = normal. 2 = retina.</param> public PaintApp( IPictureIOManager pictureIOManager, IFilenameResolver filenameResolver, ImageStateData imageStateData, IUIBusyMessage saveBusyMessageDisplay, ToolboxLayoutDefinition toolboxLayoutDefinition, int deviceScale) : base(imageStateData, toolboxLayoutDefinition, deviceScale) { this.filenameResolver = filenameResolver; this.pictureIOManager = pictureIOManager; this.saveBusyMessageDisplay = saveBusyMessageDisplay; }
/// <summary> /// Sets the orientation of the device based on the image dimensions. /// </summary> /// <param name='imageStateData'> /// Image state data. /// </param> private void SetOrientationForImage(ImageStateData imageStateData) { if (imageStateData.Height > imageStateData.Width) { this.SetOrientation(PictureOrientation.Portrait); } else { this.SetOrientation(PictureOrientation.Landscape); } }
/// <summary> /// Start painting a new image /// </summary> /// <param name='orientation'> /// The orientation of the new image /// </param> private void NewImage(PictureOrientation orientation) { // If the device is still mid turn then the reported width and height may be wrong - hence we are using // Math.Max and Math.Min to ensure we get the right size. [Actually I've delayed the turning until we are // ready to display the app (inside call to EditImage) so it wouldn't have turned yet anyway] ImageStateData imageStateData = null; int deviceWidth = (int)UIScreen.MainScreen.Bounds.Width * this.deviceScale; int deviceHeight = (int)UIScreen.MainScreen.Bounds.Height * this.deviceScale; if (orientation == PictureOrientation.Landscape) { imageStateData = new ImageStateData(Math.Max(deviceHeight, deviceWidth), Math.Min(deviceHeight, deviceWidth), UndoRedoBufferSize); } else { imageStateData = new ImageStateData(Math.Min(deviceHeight, deviceWidth), Math.Max(deviceHeight, deviceWidth), UndoRedoBufferSize); } this.EditImage(Guid.NewGuid(), imageStateData); }
/// <summary> /// Edits a specific image. /// </summary> /// <param name='pictureId'> /// Unique ID referencing the specific image we want to edit /// </param> /// <param name='imageStateData'> /// Image state data for this image - if null then it will be read from disk (so should not be null for new images) /// </param> private void EditImage(Guid pictureId, ImageStateData imageStateData = null) { var filenameResolver = this.CreateFilenameResolver(pictureId); var pictureIOManager = new PictureIOManager(filenameResolver); pictureIOManager.CreateDirectoryStructure(); if (imageStateData == null) { imageStateData = pictureIOManager.LoadImageStateData(); } this.SetOrientationForImage(imageStateData); BusyMessageDisplay busyMessageDisplay = new BusyMessageDisplay("Saving", "Please wait..."); // Simply instantiate the class derived from monogame:game and away we go... ToolboxLayoutDefinition layoutDefinition = imageStateData.Width > imageStateData.Height ? this.toolboxLayoutManager.PaintLandscapeToolboxLayout : this.toolboxLayoutManager.PaintPortraitToolboxLayout; this.paintApp = new PaintApp(pictureIOManager, filenameResolver, imageStateData, busyMessageDisplay, layoutDefinition, this.deviceScale); this.paintApp.Exiting += PaintAppExiting; this.paintApp.Run(); }
/// <summary> /// Initializes a new instance of the <see cref="Paint.BaseGame"/> class. /// </summary> /// <param name='imageStateData'>ImageSaveData</param> /// <param name='toolboxLayoutDefinition'>Layout of the toolbox</param> /// <param name='deviceScale'>Layout of the toolbox</param> /// <param name='deviceScale'>The device scale/resolution. 1 = normal. 2 = retina.</param> public BaseGame(ImageStateData imageStateData, ToolboxLayoutDefinition toolboxLayoutDefinition, int deviceScale) { this.ImageStateData = imageStateData; this.ToolboxLayoutDefinition = toolboxLayoutDefinition; this.DeviceScale = deviceScale; this.GraphicsDeviceManager = new GraphicsDeviceManager(this); this.GraphicsDeviceManager.IsFullScreen = true; if (imageStateData.Width > imageStateData.Height) { this.GraphicsDeviceManager.SupportedOrientations = DisplayOrientation.LandscapeLeft | DisplayOrientation.LandscapeRight; } else { this.GraphicsDeviceManager.SupportedOrientations = DisplayOrientation.Portrait | DisplayOrientation.PortraitUpsideDown; } this.Content.RootDirectory = "Content"; }
/// <summary> /// Saves the imageStateData to disk /// </summary> /// <param name='filename'>File to save the image data</param> /// <param name='imageStateData'>Image state data.</param> public void SaveImageStateData(string filename, ImageStateData imageStateData) { // Next write out the information file var dataArray = new int[] { imageStateData.Width, imageStateData.Height, imageStateData.MaxUndoRedoCount, imageStateData.FirstSavePoint, imageStateData.LastSavePoint, imageStateData.CurrentSavePoint }; using (var stream = File.Open(filename, FileMode.Create, FileAccess.Write)) { foreach (int val in dataArray) { stream.WriteByte((byte)val); stream.WriteByte((byte)(val >> 8)); stream.WriteByte((byte)(val >> 16)); stream.WriteByte((byte)(val >> 24)); } } }
/// <summary> /// Saves all the undoRedoRenderTargets to disk and the imageStateData /// </summary> /// <param name='imageStateData'>Image state data.</param> /// <param name='masterImageRenderTarget' Master image render target/> /// <param name='undoRedoRenderTargets'>Sequence of images representing the undo/redo chain</param> /// <param name='bottomMarginToCutOff'>Because the toolbox will always take up some space we will cut off the bottom section (toolbox height) /// when saving the master image so that there is no annoying white space at the bottom</param> public void SaveData(ImageStateData imageStateData, RenderTarget2D masterImageRenderTarget, RenderTarget2D[] undoRedoRenderTargets, int bottomMarginToCutOff) { this.SaveImageStateData(this.filenameResolver.MasterImageInfoFilename, imageStateData); int end = imageStateData.FirstSavePoint == 0 ? imageStateData.LastSavePoint : imageStateData.MaxUndoRedoCount - 1; for (int count = 0; count <= end; count++) { var renderTarget = undoRedoRenderTargets[count]; // Save the render target to disk renderTarget.SaveAsPng( this.filenameResolver.ImageSavePointFilename(count), renderTarget.Width, renderTarget.Height); // copy the working canvas recorder file into the master folder. var masterCanvasRecorderFile = this.filenameResolver.MasterCanvasRecorderFilename(count); if (File.Exists(masterCanvasRecorderFile)) { File.Delete(masterCanvasRecorderFile); } File.Move(this.filenameResolver.WorkingCanvasRecorderFilename(count), masterCanvasRecorderFile); } // Save the Master image as a JPG as it has no alpha channel - which is ideal for displaying on the home // home screen where we don't want the background image showing through masterImageRenderTarget.SaveAsJpeg( this.filenameResolver.MasterImageFilename, masterImageRenderTarget.Width, masterImageRenderTarget.Height - bottomMarginToCutOff); File.Delete(this.filenameResolver.WorkingImageInfoFilename); }