diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d4046d..87c8026 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+## [0.7.1] / 2024-11-11 - 2024-11-19
+### Features
+- Support `tiff` files for icon.
+### Updated
+- Update `GetBitmapFrame` to round `width` that is changed by `dpi`.
+- Add `GetBitmapFrameByWidthAndDpi` to get optimal frame by `dpi` and `width`.
+- Add `GetSystemDpi` to get the system `dpi` on the fly.
+- Add `SystemDpi` to store the system `dpi` value.
+- Update `GetBitmapFrameByWidthAndDpi` to round `dpi` frame value.
+- Update `Width` to `Math.Round` to improve order by `Width`.
+- Update `GetBitmapFrameByWidthAndDpi` to public.
+- Update `GetBitmapFrameByWidthAndDpi` to return last `Width`.
+- Update `UriToBitmapFrame` to use `GetBitmapFrameByWidthAndDpi` with `int.MaxValue`.
+### Example
+- Add `Cube-Grey-Light.tiff` and `Cube-Grey-Dark.tiff` in `AppTheme`.
+### Tests
+- Add `ResourceTiffTests` with `Cube.tiff` with multiple dpi and scales.
+
## [0.7.0] / 2024-07-06 - 2024-07-25
### Features
- Auto set image based on the theme of the Ribbon using `light` and `dark` image pattern.
@@ -360,6 +378,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- First Release
[vNext]: ../../compare/1.0.0...HEAD
+[0.7.1]: ../../compare/0.7.0...0.7.1
[0.7.0]: ../../compare/0.6.2...0.7.0
[0.6.2]: ../../compare/0.6.1...0.6.2
[0.6.1]: ../../compare/0.6.0...0.6.1
diff --git a/Directory.Build.props b/Directory.Build.props
index 5596472..787fd63 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,5 @@
- 0.7.0
+ 0.7.1
\ No newline at end of file
diff --git a/README.md b/README.md
index 3ed96c9..541b236 100644
--- a/README.md
+++ b/README.md
@@ -389,9 +389,6 @@ Autodesk.Windows.RibbonPanel awRibbonPanel;
awRibbonPanel.SetDialogLauncher(ribbonCommandItem);
```
-### Ribbon Description Extension
-... Todo
-
## Release
* [Latest release](https://github.com/ricaun-io/ricaun.Revit.UI/releases/latest)
diff --git a/ricaun.Revit.UI.Example/Resources/Cube-Grey-Dark.tiff b/ricaun.Revit.UI.Example/Resources/Cube-Grey-Dark.tiff
new file mode 100644
index 0000000..12bc09f
Binary files /dev/null and b/ricaun.Revit.UI.Example/Resources/Cube-Grey-Dark.tiff differ
diff --git a/ricaun.Revit.UI.Example/Resources/Cube-Grey-Light.tiff b/ricaun.Revit.UI.Example/Resources/Cube-Grey-Light.tiff
new file mode 100644
index 0000000..c99ad07
Binary files /dev/null and b/ricaun.Revit.UI.Example/Resources/Cube-Grey-Light.tiff differ
diff --git a/ricaun.Revit.UI.Example/Revit/AppTheme.cs b/ricaun.Revit.UI.Example/Revit/AppTheme.cs
index e1aeb80..d8aa158 100644
--- a/ricaun.Revit.UI.Example/Revit/AppTheme.cs
+++ b/ricaun.Revit.UI.Example/Revit/AppTheme.cs
@@ -61,6 +61,37 @@ public Result OnStartup(UIControlledApplication application)
ribbonPanel.AddSeparator();
+
+ {
+ var buttonTiff = ribbonPanel.CreatePushButton("Grey")
+ .SetLargeImage("Resources/Cube-Grey-Light.tiff");
+ //if (buttonTiff.LargeImage is System.Windows.Media.Imaging.BitmapSource largeImage)
+ //{
+ // System.Console.WriteLine($"{largeImage.GetType().Name} | {largeImage.Width:0}x{largeImage.Height:0} ({largeImage.PixelWidth}x{largeImage.PixelHeight}) {largeImage.DpiX:0}:{largeImage.DpiY:0}");
+ //}
+ //if (buttonTiff.Image is System.Windows.Media.Imaging.BitmapSource smallImage)
+ //{
+ // System.Console.WriteLine($"{smallImage.GetType().Name} | {smallImage.Width:0}x{smallImage.Height:0} ({smallImage.PixelWidth}x{smallImage.PixelHeight}) {smallImage.DpiX:0}:{smallImage.DpiY:0}");
+ //}
+ }
+
+ ribbonPanel.RowStackedItems(
+ ribbonPanel.CreatePushButton("Grey")
+ .SetLargeImage("Resources/Cube-Grey-Light.tiff"),
+ ribbonPanel.CreatePushButton("Grey")
+ .SetLargeImage("Resources/Cube-Grey-Light.tiff"),
+ ribbonPanel.CreatePushButton("Grey")
+ .SetLargeImage("Resources/Cube-Grey-Light.tiff")
+ );
+ ribbonPanel.RowLargeStackedItems(
+ ribbonPanel.CreatePushButton("Grey")
+ .SetLargeImage("Resources/Cube-Grey-Light.tiff"),
+ ribbonPanel.CreatePushButton("Grey")
+ .SetLargeImage("Resources/Cube-Grey-Light.tiff")
+ );
+
+ ribbonPanel.AddSeparator();
+
ribbonPanel.FlowStackedItems(
ribbonPanel.CreatePushButton("1").SetLargeImage(LIGHT_RED),
ribbonPanel.CreatePushButton("2").SetLargeImage(DARK_RED),
diff --git a/ricaun.Revit.UI.Example/ricaun.Revit.UI.Example.csproj b/ricaun.Revit.UI.Example/ricaun.Revit.UI.Example.csproj
index cb6fb12..855d528 100644
--- a/ricaun.Revit.UI.Example/ricaun.Revit.UI.Example.csproj
+++ b/ricaun.Revit.UI.Example/ricaun.Revit.UI.Example.csproj
@@ -13,10 +13,20 @@
- net48;net8.0-windows
+ net46;net47;net48;net8.0-windows
-
+
+
+ 2017
+
+
+
+
+ 2019
+
+
+
2021
@@ -86,6 +96,8 @@
+
+
@@ -116,6 +128,8 @@
+
+
diff --git a/ricaun.Revit.UI.Tests/Resources/Images/Cube.tiff b/ricaun.Revit.UI.Tests/Resources/Images/Cube.tiff
new file mode 100644
index 0000000..c99ad07
Binary files /dev/null and b/ricaun.Revit.UI.Tests/Resources/Images/Cube.tiff differ
diff --git a/ricaun.Revit.UI.Tests/Resources/ResourcesFramesTests.cs b/ricaun.Revit.UI.Tests/Resources/ResourceIcoFramesTests.cs
similarity index 96%
rename from ricaun.Revit.UI.Tests/Resources/ResourcesFramesTests.cs
rename to ricaun.Revit.UI.Tests/Resources/ResourceIcoFramesTests.cs
index a6917b5..f933c14 100644
--- a/ricaun.Revit.UI.Tests/Resources/ResourcesFramesTests.cs
+++ b/ricaun.Revit.UI.Tests/Resources/ResourceIcoFramesTests.cs
@@ -3,7 +3,7 @@
namespace ricaun.Revit.UI.Tests.Resources
{
- public class ResourcesFramesTests
+ public class ResourceIcoFramesTests
{
const string ResourceNameIcoFrames = "Resources/Images/Revit21Frames.ico";
diff --git a/ricaun.Revit.UI.Tests/Resources/ResourceTiffTests.cs b/ricaun.Revit.UI.Tests/Resources/ResourceTiffTests.cs
new file mode 100644
index 0000000..8198cfe
--- /dev/null
+++ b/ricaun.Revit.UI.Tests/Resources/ResourceTiffTests.cs
@@ -0,0 +1,102 @@
+using NUnit.Framework;
+using System;
+
+namespace ricaun.Revit.UI.Tests.Resources
+{
+ public class ResourceTiffTests
+ {
+ ///
+ /// Tiff file with multiple scales, 1.0x, 1.5x, 2.0x, 3.0x, 4.0x for 16x16 and 32x32, 10 frames.
+ ///
+ ///
+ /// Tiff file based in the https://github.com/ricaun-io/Autodesk.Icon.Example?tab=readme-ov-file#autodesk-high-resolution-icons
+ ///
+ const string ResourceNameTiff = "Resources/Images/Cube.tiff";
+
+ System.Windows.Media.Imaging.BitmapFrame BitmapFrame;
+ [OneTimeSetUp]
+ public void Setup()
+ {
+ BitmapFrame = ResourceNameTiff.GetBitmapSource() as System.Windows.Media.Imaging.BitmapFrame;
+ }
+
+ [Test]
+ public void GetBitmapSource_NotNull()
+ {
+ Console.WriteLine(ResourceNameTiff.GetBitmapSource());
+ Assert.IsNotNull(ResourceNameTiff.GetBitmapSource());
+ Assert.IsNotNull(("/" + ResourceNameTiff).GetBitmapSource());
+ }
+
+ [TestCase(32)]
+ public void GetBitmapSource_Default_Width(int width)
+ {
+ var source = ResourceNameTiff.GetBitmapSource();
+ Assert.AreEqual(width, Math.Round(source.Width));
+ var systemDpi = BitmapExtension.SystemDpi;
+ if (systemDpi != Math.Round(source.DpiX))
+ Assert.Ignore($"SystemDpi:{systemDpi} != {Math.Round(source.DpiX)}");
+ }
+
+ [TestCase(10)]
+ public void BitmapFrame_CountFrames(int count)
+ {
+ Assert.IsNotNull(BitmapFrame);
+ var decoder = BitmapFrame.Decoder;
+ Assert.AreEqual(count, decoder.Frames.Count);
+ }
+
+ [TestCase(16, 96)] // 1.0
+ [TestCase(16, 144)] // 1.5
+ [TestCase(16, 192)] // 2.0
+ [TestCase(16, 288)] // 3.0
+ [TestCase(16, 384)] // 4.0
+ [TestCase(32, 96)] // 1.0
+ [TestCase(32, 144)] // 1.5
+ [TestCase(32, 192)] // 2.0
+ [TestCase(32, 288)] // 3.0
+ [TestCase(32, 384)] // 4.0
+ public void BitmapFrame_ByWidthAndDpi(int width, int dpi)
+ {
+ Assert.IsNotNull(BitmapFrame);
+ var decoder = BitmapFrame.Decoder;
+ var frame = decoder.GetBitmapFrameByWidthAndDpi(width, dpi) as System.Windows.Media.Imaging.BitmapFrame;
+ Assert.IsNotNull(frame);
+ Assert.AreEqual(width, Math.Round(frame.Width));
+ Assert.AreEqual(dpi, Math.Round(frame.DpiX));
+ }
+
+ [TestCase(16, 168, 192)] // 1.75
+ [TestCase(32, 168, 192)] // 1.75
+ [TestCase(16, 240, 288)] // 2.5
+ [TestCase(32, 240, 288)] // 2.5
+ [TestCase(16, 336, 384)] // 3.5
+ [TestCase(32, 336, 384)] // 3.5
+ [TestCase(16, 480, 384)] // 5.0
+ [TestCase(32, 480, 384)] // 5.0
+ public void BitmapFrame_ByWidthAndDpi_DpiExpected(int width, int dpi, int dpiExpected)
+ {
+ Assert.IsNotNull(BitmapFrame);
+ var decoder = BitmapFrame.Decoder;
+ var frame = decoder.GetBitmapFrameByWidthAndDpi(width, dpi) as System.Windows.Media.Imaging.BitmapFrame;
+ Assert.IsNotNull(frame);
+ Assert.AreEqual(width, Math.Round(frame.Width));
+ Assert.AreEqual(dpiExpected, Math.Round(frame.DpiX));
+ }
+
+ [TestCase(64, 96, 32)] // 1.0
+ [TestCase(64, 144, 32)] // 1.5
+ [TestCase(64, 192, 32)] // 2.0
+ [TestCase(64, 288, 32)] // 3.0
+ [TestCase(64, 384, 32)] // 4.0
+ public void BitmapFrame_ByWidthAndDpi_WidthExpected(int width, int dpi, int widthExpected)
+ {
+ Assert.IsNotNull(BitmapFrame);
+ var decoder = BitmapFrame.Decoder;
+ var frame = decoder.GetBitmapFrameByWidthAndDpi(width, dpi) as System.Windows.Media.Imaging.BitmapFrame;
+ Assert.IsNotNull(frame);
+ Assert.AreEqual(widthExpected, Math.Round(frame.Width));
+ Assert.AreEqual(dpi, Math.Round(frame.DpiX));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ricaun.Revit.UI.Tests/ricaun.Revit.UI.Tests.csproj b/ricaun.Revit.UI.Tests/ricaun.Revit.UI.Tests.csproj
index 107558b..57ee646 100644
--- a/ricaun.Revit.UI.Tests/ricaun.Revit.UI.Tests.csproj
+++ b/ricaun.Revit.UI.Tests/ricaun.Revit.UI.Tests.csproj
@@ -34,14 +34,10 @@
-
-
-
-
-
+
-
+
@@ -69,6 +65,7 @@
+
diff --git a/ricaun.Revit.UI/BitmapExtension.cs b/ricaun.Revit.UI/BitmapExtension.cs
index 78f1f69..e0d7818 100644
--- a/ricaun.Revit.UI/BitmapExtension.cs
+++ b/ricaun.Revit.UI/BitmapExtension.cs
@@ -17,7 +17,7 @@ internal static BitmapFrame UriToBitmapFrame(string uriString)
{
var uri = new Uri(uriString, UriKind.RelativeOrAbsolute);
var decoder = BitmapDecoder.Create(uri, BitmapCreateOptions.None, BitmapCacheOption.Default);
- return decoder.Frames.OrderBy(e => e.Width).LastOrDefault();
+ return decoder.GetBitmapFrameByWidthAndDpi(int.MaxValue);
}
///
@@ -82,14 +82,77 @@ public static ImageSource Scale(this ImageSource imageSource, double scale)
return imageSource;
}
+#if NET47_OR_GREATER || NET
+ ///
+ /// Get the system DPI based on the using a new .
+ ///
+#else
+ ///
+ /// Get the system DPI based on the using a new from .
+ ///
+#endif
+ internal static double GetSystemDpi()
+ {
+ double systemDpi = 96;
+ try
+ {
+#if NET47_OR_GREATER || NET
+ var imageScaleInfo = VisualTreeHelper.GetDpi(new System.Windows.Controls.Image());
+ systemDpi = imageScaleInfo.PixelsPerInchX;
+#else
+ using (var g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero))
+ {
+ systemDpi = g.DpiX;
+ }
+#endif
+ }
+ catch { }
+ return systemDpi;
+ }
+
+ ///
+ /// System Dpi
+ ///
+ public readonly static double SystemDpi = GetSystemDpi();
+
+ ///
+ /// Get the bitmap frame from the based on the DPI and width.
+ ///
+ /// The bitmap decoder.
+ /// The desired width of the bitmap frame. When set to zero, the smallest width frame is returned.
+ /// The optimal dpi for the frame. When set to zero, is used.
+ /// The bitmap frame with the specified width or the smallest width frame.
+ public static BitmapFrame GetBitmapFrameByWidthAndDpi(this BitmapDecoder bitmapDecoder, int width, int dpi = 0)
+ {
+ double systemDpi = dpi > 0 ? dpi : SystemDpi;
+
+ double OrderDpiX(BitmapFrame frame)
+ {
+ var dpiX = Math.Round(frame.DpiX);
+ return dpiX >= systemDpi ? -systemDpi / dpiX : systemDpi / dpiX;
+ }
+
+ var frames = bitmapDecoder.Frames;
+ var framesOrder = frames
+ .OrderBy(OrderDpiX)
+ .ThenBy(e => Math.Round(e.Width));
+
+ var widthMax = (int)Math.Round(framesOrder.LastOrDefault().Width);
+ if (width > widthMax)
+ width = widthMax;
+
+ var frame = framesOrder.FirstOrDefault(e => Math.Round(e.Width) >= width);
+ return frame;
+ }
+
///
/// GetBitmapFrame with Width Equal or Scale
///
- ///
- ///
- ///
- ///
- /// When is zero, return the smallest width frame.
+ /// The image source.
+ /// The desired width of the bitmap frame. When set to zero, the smallest width frame is returned.
+ /// An optional action to be executed when the download of the bitmap frame is completed.
+ /// The bitmap frame with the specified width or the scaled bitmap frame.
+ /// When is zero, the smallest width frame is returned.
public static TImageSource GetBitmapFrame(this TImageSource imageSource, int width = 0, Action downloadCompleted = null) where TImageSource : ImageSource
{
TImageSource ScaleDownIfWidthIsGreater(TImageSource imageSource, int width)
@@ -97,29 +160,20 @@ TImageSource ScaleDownIfWidthIsGreater(TImageSource imageSource, int width)
if (width <= 0)
return imageSource;
- if (imageSource.Width > width)
- imageSource = imageSource.Scale(width / imageSource.Width) as TImageSource;
+ var imageRoundWidth = Math.Round(imageSource.Width);
+ if (imageRoundWidth > width)
+ imageSource = imageSource.Scale(width / imageRoundWidth) as TImageSource;
return imageSource;
}
if (imageSource is BitmapFrame bitmapFrame)
{
- BitmapFrame GetBitmapFrameByWidth(BitmapFrame bitmapFrame, int width)
- {
- var frames = bitmapFrame.Decoder.Frames;
- var frame = frames
- .OrderBy(e => e.Width)
- .FirstOrDefault(e => e.Width >= width);
-
- return frame;
- }
-
if (bitmapFrame.IsDownloading)
{
bitmapFrame.DownloadCompleted += (s, e) =>
{
- if (GetBitmapFrameByWidth(bitmapFrame, width) is TImageSource frame)
+ if (bitmapFrame.Decoder.GetBitmapFrameByWidthAndDpi(width) is TImageSource frame)
imageSource = frame;
imageSource = ScaleDownIfWidthIsGreater(imageSource, width);
@@ -128,7 +182,7 @@ BitmapFrame GetBitmapFrameByWidth(BitmapFrame bitmapFrame, int width)
};
}
- if (GetBitmapFrameByWidth(bitmapFrame, width) is TImageSource frame)
+ if (bitmapFrame.Decoder.GetBitmapFrameByWidthAndDpi(width) is TImageSource frame)
imageSource = frame;
}