/// <summary> /// Adding one stroke /// </summary> /// <param name="ink"></param> /// <param name="guid"></param> /// <param name="finished"></param> public void AddInk(Microsoft.Ink.Ink newInk, Guid guid) { if (newInk.Strokes.Count > 1) { AddInk(newInk); } else if (newInk.Strokes.Count > 0) { if (guid.Equals(Guid.Empty)) { throw new ApplicationException("Ink Guid must not be Guid.Empty."); } if (newInk.Strokes[0].DrawingAttributes.Transparency != 255) { //Ignore invisible strokes. These can occur in CP when the user erases individual strokes. if (newInk.Strokes[0].DrawingAttributes.Transparency == 0) { AddInkToInk(ref opaqueInk, newInk, guid); } else { AddInkToInk(ref transparentInk, newInk, guid); } dirtyBit = true; } } }
public void InkExport(Microsoft.Ink.PersistenceFormat persistenceFormat, string filterString) { byte[] buffer = null; //variables used to create filename string file = null; string ext = null; string defaultFileName = "SavedInk"; int slideIndex = 0; //Create a new save file dialog SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Filter = filterString; saveFileDialog.FileName = defaultFileName; if (saveFileDialog.ShowDialog() == DialogResult.OK) { //Iterate throught the slide deck to check for ink on each one using (this.m_Model.Workspace.Lock()) { using (Synchronizer.Lock(this.m_Model.Workspace.CurrentDeckTraversal.Value.SyncRoot)) { DeckModel deck = this.m_Model.Workspace.CurrentDeckTraversal.Value.Deck; using (Synchronizer.Lock(deck.SyncRoot)) { foreach (SlideModel slide in deck.Slides) { using (Synchronizer.Lock(slide.SyncRoot)) { foreach (SheetModel sheet in slide.AnnotationSheets) { if (sheet is InkSheetModel) { using (Synchronizer.Lock(sheet.SyncRoot)) { Microsoft.Ink.Ink ink = ((InkSheetModel)sheet).Ink; if (ink.Strokes.Count != 0) { //figure out the filename string fileNameTemp = saveFileDialog.FileName; ext = Path.GetExtension(fileNameTemp); file = Path.GetFileNameWithoutExtension(fileNameTemp); string n = slideIndex.ToString(); string fileName = file + "[" + n + "]" + ext; buffer = ink.Save(persistenceFormat); //open a stream and output the buffer FileStream myStream = System.IO.File.Create(fileName); myStream.Write(buffer, 0, buffer.Length); myStream.Close(); slideIndex++; } } } } } } } } } } }
/// <summary> /// Constructor for the WorkPath class /// </summary> /// <param name="projectPath">String containing full filename of Xilinx .ise /// project file</param> /// <param name="filePath">String containing full filename of sketch xml</param> /// <param name="ink">Microsoft.Ink.Ink object</param> public WorkPath(String projectPath, String filePath, Microsoft.Ink.Ink ink) { this.filePath = filePath; //converts ink to sketch ReadInk newSketch = new ReadInk(ink); this.sketch = newSketch.Sketch; this.projectPath = projectPath; }
/// <summary> /// Constructor /// </summary> /// <param name="param">The event parameters</param> public InkDrawingEvent( ArrayList param ) { PlaybackInk = new Microsoft.Ink.Ink(); if( param.Count >= 1 ) { LoadRecordedData( (string)param[0] ); } if( param.Count >= 2 ) { this.m_Path = Script.ParsePath( (string)param[1] ); } }
/// <summary> /// Overlay one stroke on the image. /// </summary> /// <param name="img"></param> /// <param name="s"></param> /// <param name="size"></param> /// PRI2: I believe we can use the DibGraphicsBuffer (see opaque ink handling above) to improve the /// look of the transparency. This currently has the following shortcoming: The colors produced by the exported gif /// from CP's highlight pens have some whiteness which causes a little fog over the dark colors /// below, for example the yellow highlight over a black background results in a lighter dark color, /// no longer quite black. In contrast the native ink mixing gives us nearly the original black. It is as if /// the colors are on colored but clear mylar that overlay one another. It's probably possible to get this effect /// with DrawImage somehow?? private void addTransparentStroke(Image img, Microsoft.Ink.Strokes s, double size) { Microsoft.Ink.Ink tmpInk = new Microsoft.Ink.Ink(); tmpInk.AddStrokesAtRectangle(s, s.GetBoundingBox()); //Make a GIF Image from the Stroke. Note that this image is assumed to be in a 500x500 pixel space. byte[] ba = tmpInk.Save(Microsoft.Ink.PersistenceFormat.Gif); Image inkImg = Image.FromStream(new MemoryStream(ba)); Graphics g = Graphics.FromImage(img); //Get the origin from the ink Bounding Box (in ink space) Rectangle inkBB = tmpInk.GetBoundingBox(); //Convert the origin of the ink rectangle to pixel space (500x500) Microsoft.Ink.Renderer r = new Microsoft.Ink.Renderer(); Point inkOrigin = inkBB.Location; r.InkSpaceToPixel(g, ref inkOrigin); //Convert the transparency coefficient from 0-255 with 0==opaque to the range of 0-1 with 1==opaque. int t1 = Math.Abs(tmpInk.Strokes[0].DrawingAttributes.Transparency - 255); float t2 = (float)t1 / 255f; //Setting transparency with a ColorMatrix float[][] ptsArray = { new float[] { 1, 0, 0, 0, 0 }, //r new float[] { 0, 1, 0, 0, 0 }, //g new float[] { 0, 0, 1, 0, 0 }, //b new float[] { 0, 0, 0, t2, 0 }, //alpha new float[] { 0, 0, 0, 0, 1 } }; ColorMatrix clrMatrix = new ColorMatrix(ptsArray); ImageAttributes imgAttributes = new ImageAttributes(); imgAttributes.SetColorMatrix(clrMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); //Adjust Y origin to account for horizontal scroll. (scrollPos becomes more positive as ink goes upward.) float scrolledYInkOrigin = (float)inkOrigin.Y - (500 * (float)scrollPos); //Still in 500x500 space //Scale and locate the destination rectangle of the ink within the slide image: RectangleF destRect = new RectangleF( (float)inkOrigin.X * ((float)img.Width / 500f) * (float)size, scrolledYInkOrigin * ((float)img.Height / 500f) * (float)size, (float)inkImg.Width * ((float)img.Width / 500f) * (float)size, (float)inkImg.Height * ((float)img.Height / 500f) * (float)size); Rectangle destRect2 = new Rectangle((int)destRect.X, (int)destRect.Y, (int)destRect.Width, (int)destRect.Height); //Draw the overlay: g.DrawImage(inkImg, destRect2, 0, 0, inkImg.Width, inkImg.Height, GraphicsUnit.Pixel, imgAttributes); g.Dispose(); }
/// <summary> /// Constructor /// </summary> /// <param name="param">The event parameters</param> public InkDrawingEvent(ArrayList param) { PlaybackInk = new Microsoft.Ink.Ink(); if (param.Count >= 1) { LoadRecordedData((string)param[0]); } if (param.Count >= 2) { this.m_Path = Script.ParsePath((string)param[1]); } }
public SlideImage() { whiteboardBackground = true; slideBitmap = null; rawCompositeImage = null; dirtyBit = true; opaqueInk = null; transparentInk = null; filePath = null; quickPoll = null; lastSize = 1.0; lastBgColor = Color.PapayaWhip; scrollPos = 0.0; scrollExtent = 1.5; }
/// <summary> /// Adding multiple strokes (which can't be erased individually because there are no Guids) /// Specifically, this is for student submission overlays. /// There are also some CP3 scenarios such as when the instructor opens a CP3 file that has pre-existing ink. /// </summary> /// <param name="ink"></param> public void AddInk(Microsoft.Ink.Ink newInk) { if ((newInk != null) && (newInk.Strokes.Count > 0)) { //separate transparent and opaque strokes List <int> transparentStrokes = new List <int>(); List <int> opaqueStrokes = new List <int>(); //iterate over strokes, adding the ink ids to the correct list foreach (Microsoft.Ink.Stroke s in newInk.Strokes) { if ((s.DrawingAttributes.Transparency != 0) && (s.DrawingAttributes.Transparency != 255)) { transparentStrokes.Add(s.Id); } else { opaqueStrokes.Add(s.Id); } } //Add transparent strokes to transparentInk. if (transparentStrokes.Count > 0) { Microsoft.Ink.Strokes tStrokes = newInk.CreateStrokes((int[])transparentStrokes.ToArray()); if (transparentInk == null) { transparentInk = new Microsoft.Ink.Ink(); } transparentInk.AddStrokesAtRectangle(tStrokes, tStrokes.GetBoundingBox()); dirtyBit = true; } //Add opaque strokes to opaqueInk. if (opaqueStrokes.Count > 0) { Microsoft.Ink.Strokes oStrokes = newInk.CreateStrokes((int[])opaqueStrokes.ToArray()); if (opaqueInk == null) { opaqueInk = new Microsoft.Ink.Ink(); } opaqueInk.AddStrokesAtRectangle(oStrokes, oStrokes.GetBoundingBox()); dirtyBit = true; } } }
/// <summary> /// Loads an Ink instance and synchronizes Sketch /// </summary> /// <param name="ink">The Ink to load</param> public void LoadInk(Microsoft.Ink.Ink ink) { unsubscribeToInk(); this.Clear(); mInk = ink; // Load strokes using (Microsoft.Ink.Strokes iStrokes = ink.Strokes) { foreach (Microsoft.Ink.Stroke iStroke in iStrokes) { AddInkStroke(iStroke); } } subscribeToInk(); }
private void removeFromInk(Microsoft.Ink.Ink ink, Guid guid) { if (ink == null) { return; } foreach (Microsoft.Ink.Stroke s in ink.Strokes) { if (s.ExtendedProperties.DoesPropertyExist(Constants.CPInkExtendedPropertyTag)) { if (s.ExtendedProperties[Constants.CPInkExtendedPropertyTag].Data.Equals(guid.ToString())) { ink.DeleteStroke(s); dirtyBit = true; return; } } } }
/// <summary> /// Constructor helper. If both arguments are null, an empty /// instance of this class is created. If Ink or Sketch /// is null, then one object is synchronized to the other /// (e.g. if ink is null, an empty Ink will be created and /// synchronized with Sketch). If neither argument is null, then /// Sketch will be synchronized with Ink. Subscribes to Ink events /// after any synchronization. /// </summary> /// <param name="ink">The Ink with which to (possibly) synchronize</param> /// <param name="sketch">The Sketch with which to (possibly) synchronize</param> protected virtual void init(Microsoft.Ink.Ink ink, FeatureSketch featureSketch) { // Instantiate new Ink and/or Sketch where necessary if (ink == null) { mInk = new Microsoft.Ink.Ink(); } else { mInk = ink; } if (featureSketch == null) { mFeatureSketch = new FeatureSketch(); Sketch.XmlAttrs.Id = System.Guid.NewGuid(); Sketch.XmlAttrs.Units = DefaultSketchUnits; } else { mFeatureSketch = featureSketch; } // Intialize other fields mReadJnt = new ReadJnt(); ink2sketchStr = new Dictionary <int, Guid?>(); sketchStr2ink = new Dictionary <Guid?, int>(); substrokeIdMap = new Dictionary <Guid?, Substroke>(); // Synchronize Ink or Sketch as necessary if (ink == null && featureSketch != null) { LoadFeatureSketch(featureSketch); } else // (ink != null && featureSketch == null) { LoadInk(ink); } subscribeToInk(); }
/// <summary> /// Clears the contents of this InkSketch. Deletes /// all Ink and Sketch strokes and maintains /// synchronization between Ink and Sketch. /// </summary> public virtual void Clear() { mFeatureSketch = new FeatureSketch(); Sketch.XmlAttrs.Id = System.Guid.NewGuid(); Sketch.XmlAttrs.Units = DefaultSketchUnits; mInk = new Microsoft.Ink.Ink(); if (ink2sketchStr != null) { ink2sketchStr.Clear(); } if (sketchStr2ink != null) { sketchStr2ink.Clear(); } if (substrokeIdMap != null) { substrokeIdMap.Clear(); } }
/// <summary> /// Add a stroke to an Ink /// </summary> /// <param name="destInk"></param> /// <param name="srcInk"></param> /// <param name="guid"></param> private void AddInkToInk(ref Microsoft.Ink.Ink destInk, Microsoft.Ink.Ink srcInk, Guid guid) { if (destInk == null) { //first stroke: create new ink destInk = new Microsoft.Ink.Ink(); } //Remove the stroke, if it exists. RemoveInk(guid); //Set the extended property. if (srcInk.Strokes[0].ExtendedProperties.DoesPropertyExist(Constants.CPInkExtendedPropertyTag)) { srcInk.Strokes[0].ExtendedProperties.Remove(Constants.CPInkExtendedPropertyTag); } srcInk.Strokes[0].ExtendedProperties.Add(Constants.CPInkExtendedPropertyTag, guid.ToString()); //Add stroke destInk.AddStrokesAtRectangle(srcInk.Strokes, srcInk.Strokes.GetBoundingBox()); }
/// <summary> /// Loads Ink Data from a specified ISF or JNT file /// </summary> /// <param name="inkDataFileName"> /// The file to load the Ink Data from.</param> protected void LoadRecordedData(string inkDataFileName) { // First, reset the Ink object PlaybackInk.DeleteStrokes(); if (!File.Exists(inkDataFileName)) { MessageBox.Show("Cannot find file: " + inkDataFileName); return; } // Read in the specified file FileStream isfStream = new FileStream(inkDataFileName, FileMode.Open, FileAccess.Read); // Verify the filestream creation was successful if (null != isfStream && isfStream.Length > 0) { byte[] isfBytes = new byte[isfStream.Length]; // read in the ISF doc isfStream.Read(isfBytes, 0, (int)isfStream.Length); // Load the ink into a new Temporary object // Once an Ink object has been "dirtied" it cannot load ink Microsoft.Ink.Ink TemporaryInk = new Microsoft.Ink.Ink(); // Load the ISF into the ink object TemporaryInk.Load(isfBytes); // Add the loaded strokes to our Ink object PlaybackInk.AddStrokesAtRectangle(TemporaryInk.Strokes, TemporaryInk.Strokes.GetBoundingBox()); // Close the file stream isfStream.Close(); } }
/// <summary> /// Handle frames from a native RTDocument generator such as the CXP presentation tool /// </summary> /// <param name="rtobj"></param> private void acceptRTDocFrame(object rtobj) { ///Notes about RTDocuments: /// /// RTDocuments have Resources and Organizations. Resources contain Pages/Images etc while Organizations /// contain the TOC/titles, etc. The TOC Nodes contain references to the resources and resource IDs. /// The navigation message RTNodeChanged just tells us the organization node ID, while /// Page and ink messages only contain the Resource ID. RTDocument messages contain the TOC which maps pages and org nodes. /// PageAdds will not have an existing TocNode in the RTDocument map, but they carry their own TocNode property. /// /// For this application, we only care about having one unique page identifier. We take the strategy of storing /// SlideImages under the resource Identifier, and maintaining a lookup table of Organization identifier to /// resource identifier. We use this table to resolve Organization identifiers when navigation messages are received. if (rtobj is RTDocument) { RTDocument rtd = (RTDocument)rtobj; //Keep the mapping of TocNode.Identifier to TocNode.ResourceIdentifier foreach (TOCNode tn in rtd.Organization.TableOfContents) { if (!orgToResource.ContainsKey(tn.Identifier)) { orgToResource.Add(tn.Identifier, tn.ResourceIdentifier); } else { orgToResource[tn.Identifier] = tn.ResourceIdentifier; } } //There is an implicit navigation to the first slide here. this.currentSlide.SetRTDocReference(rtd.Organization.TableOfContents[0].ResourceIdentifier); } else if (rtobj is Page) { //These are slide deck pages //p.Identifier is a Resource Identifier. Store the image under that Identifier. Page p = (Page)rtobj; if (!slideImages.ContainsKey(p.Identifier.ToString())) { slideImages.Add(p.Identifier.ToString(), new SlideImage()); } ((SlideImage)slideImages[p.Identifier.ToString()]).SetImage(p.Image, false); } else if (rtobj is RTPageAdd) { //These are dynamically added pages such as WB and screenshots RTPageAdd rtpa = (RTPageAdd)rtobj; //RTPageAdd comes with a TocNode. Store the mapping of resource ID to TocNode.Identifier if (!orgToResource.ContainsKey(rtpa.TOCNode.Identifier)) { orgToResource.Add(rtpa.TOCNode.Identifier, rtpa.Page.Identifier); } else { orgToResource[rtpa.TOCNode.Identifier] = rtpa.Page.Identifier; } //Store the page Image under the resource ID. if (!slideImages.ContainsKey(rtpa.Page.Identifier.ToString())) { slideImages.Add(rtpa.Page.Identifier.ToString(), new SlideImage()); } ((SlideImage)slideImages[rtpa.Page.Identifier.ToString()]).SetImage(rtpa.Page.Image, false); } else if (rtobj is RTNodeChanged) { RTNodeChanged rtnc = (RTNodeChanged)rtobj; //Look up the resource ID and update curent page. if (orgToResource.ContainsKey(rtnc.OrganizationNodeIdentifier)) { currentSlide.SetRTDocReference(((Guid)orgToResource[rtnc.OrganizationNodeIdentifier])); } else { //Indicate slide missing by setting currentSlide reference to Guid.Empty currentSlide.SetRTDocReference(Guid.Empty); } } else if (rtobj is RTStroke) { RTStroke rts = (RTStroke)rtobj; //apply the ink to the given Page Identifier. Create a new SlideImage if necessary. if (!slideImages.ContainsKey(rts.PageIdentifier.ToString())) { slideImages.Add(rts.PageIdentifier.ToString(), new SlideImage()); } Microsoft.Ink.Ink ink = rts.Stroke.Ink.Clone(); for (int i = 0; i < ink.Strokes.Count; i++) { ink.Strokes[i].Scale(500f / 960f, 500f / 720f); } ((SlideImage)slideImages[rts.PageIdentifier.ToString()]).AddInk(ink, rts.StrokeIdentifier); //There appears to be an implicit navigation here. currentSlide.SetRTDocReference(rts.PageIdentifier); } else if (rtobj is RTStrokeRemove) { RTStrokeRemove rtsr = (RTStrokeRemove)rtobj; //Use the PageIdentifer to identify the page from which to remove the stroke. if (slideImages.ContainsKey(rtsr.PageIdentifier.ToString())) { ((SlideImage)slideImages[rtsr.PageIdentifier.ToString()]).RemoveInk(rtsr.StrokeIdentifier); } } else if (rtobj is RTFrame) { RTFrame rtf = (RTFrame)rtobj; if (rtf.ObjectTypeIdentifier == Constants.RTDocEraseAllGuid) { //Erase all ink on the current slide. if ((currentSlide.IsSet) && (slideImages.ContainsKey(currentSlide.GetStringHashCode()))) { ((SlideImage)slideImages[currentSlide.GetStringHashCode()]).RemoveAllInk(); } } else { Debug.WriteLine("Unhandled RTFrame type."); } } else { Debug.WriteLine("Unhandled RT obj:" + rtobj.ToString()); } }
/// <summary> /// Loads Ink Data from a specified ISF or JNT file /// </summary> /// <param name="inkDataFileName"> /// The file to load the Ink Data from.</param> protected void LoadRecordedData(string inkDataFileName) { // First, reset the Ink object PlaybackInk.DeleteStrokes(); if( !File.Exists( inkDataFileName ) ) { MessageBox.Show( "Cannot find file: " + inkDataFileName ); return; } // Read in the specified file FileStream isfStream = new FileStream( inkDataFileName, FileMode.Open, FileAccess.Read ); // Verify the filestream creation was successful if (null != isfStream && isfStream.Length > 0) { byte[] isfBytes = new byte[isfStream.Length]; // read in the ISF doc isfStream.Read(isfBytes, 0, (int)isfStream.Length); // Load the ink into a new Temporary object // Once an Ink object has been "dirtied" it cannot load ink Microsoft.Ink.Ink TemporaryInk = new Microsoft.Ink.Ink(); // Load the ISF into the ink object TemporaryInk.Load(isfBytes); // Add the loaded strokes to our Ink object PlaybackInk.AddStrokesAtRectangle(TemporaryInk.Strokes, TemporaryInk.Strokes.GetBoundingBox()); // Close the file stream isfStream.Close(); } }
/// <summary> /// Constructor. Creates an empty Sketch and synchronizes it /// with the contents of the given Ink. /// </summary> /// <param name="ink">The Ink with which to synchronize</param> public InkSketch(Microsoft.Ink.Ink ink) { init(ink, null); }