Skip to content
This repository has been archived by the owner on Nov 27, 2020. It is now read-only.

Commit

Permalink
Added opacity attribute; Supporting short hand color format (eg. fff …
Browse files Browse the repository at this point in the history
…for white); Scaled images are now anti aliased; Renamed MyImage to CommentImage; Added contenttype "code++.F#" for F#; Added logo source files; Misc Refactoring
  • Loading branch information
TomSmartBishop committed Mar 8, 2019
1 parent 152bcba commit 32b0d07
Show file tree
Hide file tree
Showing 21 changed files with 319 additions and 312 deletions.
209 changes: 209 additions & 0 deletions ImageCommentsExtension/CommentImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
using System.IO;
using System.Windows;

namespace LM.ImageComments.EditorComponent
{
using System;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

public class ImageAttributes
{
public string Url;
public double Scale;
public double Opacity;
public Color Background;
private const double Tolerance = 0.001;

public ImageAttributes()
{
Url = "";
Scale = 1.0;
Opacity = 1.0;
}

public override bool Equals(object obj)
{
if (obj == null)
return false;
if (!(obj is ImageAttributes other))
return false;
return other.Url == Url &&
Math.Abs(other.Scale - Scale) < Tolerance &&
Math.Abs(other.Opacity - Opacity) < Tolerance &&
other.Background.Equals(Background);
}
}

/// <summary>
/// Sub-class of Image with convenient URL-based Source changing
/// </summary>
public class CommentImage : Image
{

private readonly VariableExpander _variableExpander;
private FileSystemWatcher _watcher;

public ImageAttributes Attributes;

public CommentImage(VariableExpander variableExpander)
: base()
{
_variableExpander = variableExpander ?? throw new ArgumentNullException(nameof(variableExpander));
this.VisualBitmapScalingMode = BitmapScalingMode.HighQuality;
}

private bool LoadFromUri(string rawUri, string sourceFileDir, Action refreshAction, out string errorString)
{
if (string.IsNullOrWhiteSpace(rawUri))
{
Source = null;
errorString = "No image specified";
return false;
}

var expandedUrl = _variableExpander.ProcessText(rawUri);
if (!File.Exists(expandedUrl)) //TODO: Refactor this eg. post processing step
{
// if the file does not exists, but we have an existing "docfx.json", lets try to find file in "$(ProjectDir)\images" directory
var jsonFile = _variableExpander.ProcessText("$(ProjectDir)\\docfx.json");
if (File.Exists(jsonFile))
{
// Example: we replace in "..\\images\picture.png" all the ".." with "$ProjectDir" --> "$ProjectDir\\images\\picture.png"
expandedUrl = rawUri.Replace("..", "$(ProjectDir)");
expandedUrl = _variableExpander.ProcessText(expandedUrl);
}
}

var success = Uri.TryCreate(_variableExpander.ProcessText(expandedUrl), UriKind.Absolute, out var uri);
var canLoadData = success && DataUriLoader.CanLoad(uri);
var canLoadFromWeb = success && WebLoader.CanLoad(uri);
if (canLoadData)
{
//TODO [!]: Currently, this loading system prevents images from being changed on disk, fix this
// e.g. using http://stackoverflow.com/questions/1763608/display-an-image-in-wpf-without-holding-the-file-open
Source = BitmapFrame.Create(DataUriLoader.Load(uri));
}
else if (canLoadFromWeb)
{
expandedUrl = WebLoader.Load(uri);
}
else if (!success && !Path.IsPathRooted(expandedUrl) && sourceFileDir != null)
{
expandedUrl = Path.Combine(sourceFileDir, expandedUrl);
expandedUrl = Path.GetFullPath((new Uri(expandedUrl)).LocalPath);
}

if (!canLoadData && File.Exists(expandedUrl))
{
var data = new MemoryStream(File.ReadAllBytes(expandedUrl));
Source = BitmapFrame.Create(data);
// Create file system watcher to update changed image file.
_watcher = new FileSystemWatcher
{
//NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Size,
Path = Path.GetDirectoryName(expandedUrl),
Filter = Path.GetFileName(expandedUrl)
};
var w = _watcher;

void Refresh(object sender, FileSystemEventArgs e)
{
try
{
var enableRaisingEvents = w.EnableRaisingEvents;
w.EnableRaisingEvents = false;
if (!enableRaisingEvents)
return;

Attributes.Url = null;
refreshAction();
}
catch
{
// ignored
}
}

_watcher.Changed += Refresh;
_watcher.Renamed += Refresh;
_watcher.Deleted += Refresh;
_watcher.EnableRaisingEvents = true;

errorString = null;
return true;
}

Source = null;
errorString = $"Could not load image '{uri}' (resolved to '{expandedUrl}')";
return false;
}

/// <summary>
/// Sets image source and size (by scale factor)
/// </summary>
/// <param name="directory">The directory where the text document referencing the image lives</param>
/// <param name="attribs"> The image attributes used to set the image</param>
/// <param name="error">Error message if image couldn't be loaded, otherwise null</param>
/// <param name="refreshAction">The action to be performed if the image was successfully refreshed</param>
/// <returns>Returns true if image was successfully loaded, otherwise false</returns>
public bool TrySet(string directory, ImageAttributes attribs, out string error, Action refreshAction)
{
// Remove old watcher.
var watcher = _watcher;
_watcher = null;
watcher?.Dispose();
// ---
error = null;

try
{
if (LoadFromUri(attribs.Url, directory, refreshAction, out var errorString))
{
Attributes = attribs;
ProcessBitmap();
}

error = errorString;
}
catch (Exception ex)
{
Source = null;
error = ex.Message;
return false;
}

return true;
}

private void ProcessBitmap()
{
if (Source == null)
return;

var scale = Math.Min(Math.Max(Attributes.Scale, 0.01), 100);

var scaledRect = new Rect(0, 0, (int)(Source.Width * scale), (int)(Source.Height * scale));
var rect = new Rect(0, 0, (int)Source.Width, (int)Source.Height);
var visual = new DrawingVisual();
var context = visual.RenderOpen();
context.PushTransform(new ScaleTransform(scale, scale));
context.PushOpacity(Math.Min(Math.Max(Attributes.Opacity, 0),1));
context.DrawRectangle(new SolidColorBrush(Attributes.Background), null, rect);
context.DrawImage(Source, rect);
context.Close();

var render = new RenderTargetBitmap((int)scaledRect.Width, (int)scaledRect.Height,
96, 96, PixelFormats.Pbgra32);
RenderOptions.SetEdgeMode(render, EdgeMode.Aliased);
RenderOptions.SetBitmapScalingMode(render, BitmapScalingMode.HighQuality);

render.Render(visual);
Source = BitmapFrame.Create(render);
Height = Source.Height;
Width = Source.Width;
}
}
}
1 change: 1 addition & 0 deletions ImageCommentsExtension/ErrorTaggerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ContentType("CSharp"),
ContentType("C/C++"),
ContentType("Basic"),
ContentType("code++.F#"),
ContentType("F#"),
ContentType("JScript"),
ContentType("Python")
Expand Down
2 changes: 1 addition & 1 deletion ImageCommentsExtension/ExceptionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static void Notify(Exception ex, bool showMessage)
{
ThreadHelper.ThrowIfNotOnUIThread();

string message = string.Format("{0}: {1}", DateTime.Now, ex.ToString());
string message = $"{DateTime.Now}: {ex.ToString()}";
Console.WriteLine(message);
if (showMessage)
{
Expand Down
34 changes: 13 additions & 21 deletions ImageCommentsExtension/ImageAdornmentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class ImageAdornmentManager : ITagger<ErrorTag>, IDisposable
public static bool Enabled { get; set; }

// Dictionary to map line number to image
internal Dictionary<int, MyImage> Images { get; set; }
internal Dictionary<int, CommentImage> Images { get; set; }

/// <summary>
/// Initializes static members of the <see cref="ImageAdornmentManager"/> class
Expand All @@ -52,9 +52,8 @@ public ImageAdornmentManager(IWpfTextView view)
{
_view = view;
_layer = view.GetAdornmentLayer("ImageCommentLayer");
Images = new Dictionary<int, MyImage>();
Images = new Dictionary<int, CommentImage>();
_view.LayoutChanged += LayoutChangedHandler;

_contentTypeName = view.TextBuffer.ContentType.TypeName;
_view.TextBuffer.ContentTypeChanged += contentTypeChangedHandler;

Expand All @@ -79,7 +78,6 @@ private void LayoutChangedHandler(object sender, TextViewLayoutChangedEventArgs

_errorTags.Clear();
TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(_view.TextSnapshot, new Span(0, _view.TextSnapshot.Length))));

OnTagsChanged(new SnapshotSpan(_view.TextSnapshot, new Span(0, _view.TextSnapshot.Length)));

foreach (var line in _view.TextViewLines) // TODO [?]: implement more sensible handling of removing error tags, then use e.NewOrReformattedLines
Expand Down Expand Up @@ -122,20 +120,19 @@ private void CreateVisuals(ITextViewLine line, int lineNumber, string absFilenam
ThreadHelper.ThrowIfNotOnUIThread();

var directory = absFilename!=null ? System.IO.Path.GetDirectoryName(absFilename) : null;
var lineText = line.Extent.GetText().Split(new string[] { "\r\n", "\r" }, StringSplitOptions.None)[0];
var lineText = line.Extent.GetText().Split(new[] { "\r\n", "\r" }, StringSplitOptions.None)[0];
var matchIndex = ImageCommentParser.Match(_contentTypeName, lineText, out var matchedText);
if (matchIndex >= 0)
{
lineText = line.Extent.GetText().Split(new string[] { "\r\n", "\r" }, StringSplitOptions.None)[0];
//lineText = line.Extent.GetText().Split(new string[] { "\r\n", "\r" }, StringSplitOptions.None)[0];
// Get coordinates of text
var start = line.Extent.Start.Position + matchIndex;
var end = line.Start + (line.Extent.Length - 1);
var span = new SnapshotSpan(_view.TextSnapshot, Span.FromBounds(start, end));

var bgColor = new Color();
ImageCommentParser.TryParse(matchedText, out var imageUrl, out var scale, ref bgColor, out var xmlParseError);
ImageCommentParser.TryParse(matchedText, out var parsedImgData, out var parsingError);

if (xmlParseError != null)
if (parsingError != null)
{
if (Images.ContainsKey(lineNumber))
{
Expand All @@ -144,31 +141,26 @@ private void CreateVisuals(ITextViewLine line, int lineNumber, string absFilenam
}

_errorTags.Add(new TagSpan<ErrorTag>(span,
new ErrorTag("XML parse error", $"Problem with comment format: {xmlParseError}")));
new ErrorTag("XML parse error", $"Problem with comment format: {parsingError}")));

return;
}

MyImage image;
string loadingMessage = null;

// Check for and update existing image
MyImage existingImage = Images.ContainsKey(lineNumber) ? Images[lineNumber] : null;
if (existingImage != null)
CommentImage image = Images.ContainsKey(lineNumber) ? Images[lineNumber] : null;
if (image != null)
{
image = existingImage;
if (existingImage.Url != imageUrl || existingImage.BgColor!= bgColor) // URL different, so set new source
{
existingImage.TrySet(directory, imageUrl, scale, bgColor, out loadingMessage, () => CreateVisuals(line, lineNumber, absFilename));
} else if (existingImage.Url == imageUrl && Math.Abs(existingImage.Scale - scale) > 0.0001) // URL same but scale changed
if (!image.Attributes.Equals(parsedImgData)) // URL different, so set new source
{
existingImage.Scale = scale;
image.TrySet(directory, parsedImgData, out loadingMessage, () => CreateVisuals(line, lineNumber, absFilename));
}
}
else // No existing image, so create new one
{
image = new MyImage(_variableExpander);
image.TrySet(directory, imageUrl, scale, bgColor, out loadingMessage, () => CreateVisuals(line, lineNumber, absFilename));
image = new CommentImage(_variableExpander);
image.TrySet(directory, parsedImgData, out loadingMessage, () => CreateVisuals(line, lineNumber, absFilename));
Images.Add(lineNumber, image);
}

Expand Down
1 change: 1 addition & 0 deletions ImageCommentsExtension/ImageAdornmentManagerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace LM.ImageComments.EditorComponent
ContentType("CSharp"),
ContentType("C/C++"),
ContentType("Basic"),
ContentType("code++.F#"),
ContentType("F#"),
ContentType("JScript"),
ContentType("Python")
Expand Down
Loading

0 comments on commit 32b0d07

Please sign in to comment.