/// <summary> /// Strips a set of Visualizations out of animations in the set. The set is not /// modified; rather, a new set is generated with updated entries. /// </summary> /// <param name="visSet">Input set.</param> /// <param name="removedSerials">Serial numbers of removed items.</param> /// <param name="newSet">Updated set.</param> /// <returns>True if entries were removed.</returns> public static bool StripEntriesFromAnimations(VisualizationSet visSet, List <int> removedSerials, out VisualizationSet newSet) { bool somethingRemoved = false; newSet = new VisualizationSet(visSet.Count); foreach (Visualization vis in visSet) { if (!(vis is VisualizationAnimation)) { newSet.Add(vis); continue; } if (VisualizationAnimation.StripEntries((VisualizationAnimation)vis, removedSerials, out VisualizationAnimation newAnim)) { somethingRemoved = true; if (newAnim.Count != 0) { newSet.Add(newAnim); } else { Debug.WriteLine("Deleting empty animation " + vis.Tag); } } } return(somethingRemoved); }
public static FormattedParts CreateVisualizationSet(VisualizationSet visSet) { FormattedParts parts = new FormattedParts(); if (visSet.Count == 0) { // should not happen parts.Comment = "!EMPTY VSET!"; parts.IsLongComment = true; } else { string fmt; if (visSet.Count == 1) { fmt = Res.Strings.VIS_SET_SINGLE_FMT; } else { fmt = Res.Strings.VIS_SET_MULTIPLE_FMT; } parts.Comment = string.Format(fmt, "Bitmap", visSet[0].Tag, visSet.Count - 1); parts.VisualizationSet = visSet.ToArray(); parts.IsVisualizationSet = true; } return(parts); }
/// <summary> /// Generates a cached image for the animation. /// </summary> /// <remarks> /// Currently just using the first frame. We could do fancy things, like make a /// poster with the first N images. /// </remarks> /// <param name="visSets">List of visualization sets.</param> public void GenerateImage(SortedList <int, VisualizationSet> visSets) { CachedImage = BLANK_IMAGE; if (mSerialNumbers.Count == 0) { return; } Visualization vis = VisualizationSet.FindVisualizationBySerial(visSets, mSerialNumbers[0]); if (vis != null) { CachedImage = vis.CachedImage; } }
/// <summary> /// Finds a Visualization by serial number. /// </summary> /// <param name="visSets">List of sets of visualizations.</param> /// <param name="serial">Serial number to search for.</param> /// <returns>Matching Visualization, or null if not found.</returns> public static Visualization FindVisualizationBySerial( SortedList <int, VisualizationSet> visSets, int serial) { foreach (KeyValuePair <int, VisualizationSet> kvp in visSets) { VisualizationSet visSet = kvp.Value; foreach (Visualization vis in visSet) { if (vis.SerialNumber == serial) { return(vis); } } } return(null); }
/// <summary> /// Creates an UndoableChange for a visualization set update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldVisSet">Old visualization set.</param> /// <param name="newVisSet">New visualization set.</param> /// <returns>Change record.</returns> public static UndoableChange CreateVisualizationSetChange(int offset, VisualizationSet oldVisSet, VisualizationSet newVisSet) { if (oldVisSet == newVisSet) { Debug.WriteLine("No-op visualization set change"); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetVisualizationSet; uc.Offset = offset; uc.OldValue = oldVisSet; uc.NewValue = newVisSet; uc.ReanalysisRequired = ReanalysisScope.DisplayOnly; // no change to code/data return(uc); }
/// <summary> /// Attempts to refresh broken thumbnails across all visualization sets in the project. /// </summary> /// <param name="project">Project reference.</param> public static void RefreshAllThumbnails(DisasmProject project) { ScriptSupport iapp = null; Dictionary <string, IPlugin> plugins = null; SortedList <int, VisualizationSet> visSets = project.VisualizationSets; foreach (KeyValuePair <int, VisualizationSet> kvp in visSets) { VisualizationSet visSet = kvp.Value; foreach (Visualization vis in visSet) { if (vis.HasImage) { continue; } //Debug.WriteLine("Vis needs refresh: " + vis.Tag); if (vis is VisualizationAnimation) { continue; } if (iapp == null) { // Prep the plugins on first need. iapp = new ScriptSupport(); project.PrepareScripts(iapp); } if (plugins == null) { plugins = project.GetActivePlugins(); } IPlugin_Visualizer vplug = FindPluginByVisGenIdent(plugins, vis.VisGenIdent, out VisDescr visDescr); if (vplug == null) { Debug.WriteLine("Unable to refresh " + vis.Tag + ": plugin not found"); continue; } IVisualization2d vis2d; try { vis2d = vplug.Generate2d(visDescr, new ReadOnlyDictionary <string, object>(vis.VisGenParams)); if (vis2d == null) { Debug.WriteLine("Vis generator returned null"); } } catch (Exception ex) { Debug.WriteLine("Vis generation failed: " + ex); vis2d = null; } if (vis2d != null) { //Debug.WriteLine(" Rendered thumbnail: " + vis.Tag); vis.SetThumbnail(vis2d); } } } if (iapp != null) { project.UnprepareScripts(); } // Now that we've generated images for the Visualizations, update any // VisualizationAnimation thumbnails that may have been affected. foreach (KeyValuePair <int, VisualizationSet> kvp in visSets) { VisualizationSet visSet = kvp.Value; foreach (Visualization vis in visSet) { if (!(vis is VisualizationAnimation)) { continue; } VisualizationAnimation visAnim = (VisualizationAnimation)vis; visAnim.GenerateImage(visSets); } } }
/// <summary> /// Generate one or more GIF image files, and output references to them. /// </summary> /// <param name="offset">Visualization set file offset.</param> /// <param name="sb">String builder for the HTML output.</param> private void OutputVisualizationSet(int offset, StringBuilder sb) { const int IMAGE_SIZE = 64; const int MAX_WIDTH_PER_LINE = 768; if (!mProject.VisualizationSets.TryGetValue(offset, out VisualizationSet visSet)) { sb.Append("Internal error - visualization set missing"); Debug.Assert(false); return; } if (visSet.Count == 0) { sb.Append("Internal error - empty visualization set"); Debug.Assert(false); return; } string imageDirFileName = Path.GetFileName(mImageDirPath); int outputWidth = 0; for (int index = 0; index < visSet.Count; index++) { string fileName = "vis" + offset.ToString("x6") + "_" + index.ToString("d2"); int dispWidth, dispHeight; Visualization vis = visSet[index]; if (vis is VisualizationAnimation) { // Animated visualization. VisualizationAnimation visAnim = (VisualizationAnimation)vis; int frameDelay = PluginCommon.Util.GetFromObjDict(visAnim.VisGenParams, VisualizationAnimation.FRAME_DELAY_MSEC_PARAM, 330); AnimatedGifEncoder encoder = new AnimatedGifEncoder(); // Gather list of frames. for (int i = 0; i < visAnim.Count; i++) { Visualization avis = VisualizationSet.FindVisualizationBySerial( mProject.VisualizationSets, visAnim[i]); if (avis != null) { encoder.AddFrame(BitmapFrame.Create(avis.CachedImage), frameDelay); } else { Debug.Assert(false); // not expected } } #if false // try feeding the animated GIF into our GIF unpacker using (MemoryStream ms = new MemoryStream()) { encoder.Save(ms); Debug.WriteLine("TESTING"); UnpackedGif anim = UnpackedGif.Create(ms.GetBuffer()); anim.DebugDump(); } #endif // Create new or replace existing image file. fileName += "_ani.gif"; string pathName = Path.Combine(mImageDirPath, fileName); try { using (FileStream stream = new FileStream(pathName, FileMode.Create)) { encoder.Save(stream, out dispWidth, out dispHeight); } } catch (Exception ex) { // TODO: add an error report Debug.WriteLine("Error creating animated GIF file '" + pathName + "': " + ex.Message); dispWidth = dispHeight = 1; } } else { // Bitmap visualization. // // Encode a GIF the same size as the original bitmap. GifBitmapEncoder encoder = new GifBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(vis.CachedImage)); // Create new or replace existing image file. fileName += ".gif"; string pathName = Path.Combine(mImageDirPath, fileName); try { using (FileStream stream = new FileStream(pathName, FileMode.Create)) { encoder.Save(stream); } } catch (Exception ex) { // Something went wrong with file creation. We don't have an error // reporting mechanism, so this will just appear as a broken or stale // image reference. // TODO: add an error report Debug.WriteLine("Error creating GIF file '" + pathName + "': " + ex.Message); } dispWidth = (int)vis.CachedImage.Width; dispHeight = (int)vis.CachedImage.Height; } // Output thumbnail-size IMG element, preserving proportions. I'm assuming // images will be small enough that generating a separate thumbnail would be // counter-productive. This seems to look best if the height is consistent // across all visualization lines, but that can create some monsters (e.g. // a bitmap that's 1 pixel high and 40 wide), so we cap the width. int dimMult = IMAGE_SIZE; double maxDim = dispHeight; if (dispWidth > dispHeight * 2) { // Too proportionally wide, so use the width as the limit. Allow it to // up to 2x the max width (which can't cause the thumb height to exceed // the height limit). maxDim = dispWidth; dimMult *= 2; } int thumbWidth = (int)Math.Round(dimMult * (dispWidth / maxDim)); int thumbHeight = (int)Math.Round(dimMult * (dispHeight / maxDim)); //Debug.WriteLine(dispWidth + "x" + dispHeight + " --> " + // thumbWidth + "x" + thumbHeight + " (" + maxDim + ")"); if (outputWidth > MAX_WIDTH_PER_LINE) { // Add a line break. In "pre" mode the bitmaps just run off the right // edge of the screen. The way we're doing it is imprecise and doesn't // flow with changes to the browser width, but it'll do for now. sb.AppendLine("<br/>"); for (int i = 0; i < mColStart[(int)Col.Label]; i++) { sb.Append(' '); } outputWidth = 0; } else if (index != 0) { sb.Append(" "); } outputWidth += thumbWidth; sb.Append("<img class=\"vis\" alt=\"vis\" src=\""); sb.Append(imageDirFileName); sb.Append('/'); sb.Append(fileName); sb.Append("\" width=\"" + thumbWidth + "\" height=\"" + thumbHeight + "\"/>"); } }