Earlier this week, the .NET SDK and Runtimes received some updates. Together with that, also Visual Studio for Mac was updated. Once I got past the installation of all updates, both Visual Studio and Rider were no longer restoring the required NuGet packages for my .NET MAUI project running on .NET 6.
I eventually fixed that issue by cleaning up all the .NET SDKs, Runtimes, workloads and NuGet caches on my MacBook Pro. Read on to learn about the tools I used.
.NET uninstall tool
I have been using the .NET uninstall tool in the past. Unlike on Windows, you have to download the executable from GitHub in its zipped form.
While the releases page shows some terminal commands to unpack and run the tool, they never worked for me as stated there. While I was able to make the new directory with the mkdir command, the unpacking always shows an error. So I opened up Finder and unzipped it manually with the Archive Utility app that ships with macOS.
After switching to the folder in Terminal, the tool is supposed to show the help. Instead, I got an error showing me that I am not allowed to run this app for security reasons. The OS blocks the execution. If the same happens for you, right click on the extracted executable and select “Open With” followed by “Terminal.app (default)“. This will prompt you with this screen:
Once you click on “Open“, a new Terminal window appears. Close this window, it is unusable as we are already in the exited state. Instead, open a new Terminal and change to the installation folder and call the help command:
cd ~/dotnet-core-uninstall
./dotnet-core-uninstall -h
Now that we are able to run the tool, let’s have a look what we have installed by running the dotnet --list command. We need to call the command twice, once for the installed -sdks and once for the installed -runtimes:
dotnet --list-sdks
dotnet --list-runtimes
You may be as surprised (I was, at least) how many versions you are accumulating over time. They never get removed by newer versions (it’s by design, according to Microsoft). To get rid of all versions except the latest, run the following commands with the uninstall tool (again once for — sdk, once for –runtime):
After uninstalling all previous versions, you may have to reinstall the latest .NET 6 SDK again. You could also use the –-all-but [Versions] command to specify the versions explicitly. No matter which way you’re going, if you run the dotnet --list commands again, you should see something similar to this:
As I had problems getting the required NuGet packages for my MAUI app, I decided to uninstall all .NET MAUI workloads as well. First, I had a look what is installed with the list command:
dotnet workload list
Once you have that list, you need to call the uninstall command for every single installed workload:
The final clean-up step involves all NuGet caches on your machine. Yes, you read that right, multiple caches. To see them all, run the following command:
dotnet nuget locals all --list
This will get you something like this:
Now let’s get rid of all those old NuGet packages:
sudo dotnet nuget locals all --clear
If you’re lucky, you will see this message:
My first attempt was not that successful. I needed to open the global packages’ folder in Finder and delete some remaining packages manually. Only after that, I was able to run the clear command with success.
Conclusion
Neither Visual Studio nor the .NET installer perform clean-up tasks on macOS. Until Microsoft changes their mind here, we will have to clean-up old packages manually to keep our system smoothly running. Luckily, there are at least CLI tools around to help us with that job. As always, I hope this blog post will be helpful for some of you.
This is the third and last post of this series. In the first two posts I showed you how to start the preview of MediaCapture and some modifications we can apply to it. In this post, we are finally capturing and saving the photo – including the modifications we made before.
The easiest way – capture as is:
The easiest way to capture the photo is to use MediaCapture’s CapturePhotoToStorageFileAsync() method. This method shows you how to do it:
//declare image format
ImageEncodingProperties format = ImageEncodingProperties.CreateJpeg();
//generate file in local folder:
StorageFile capturefile = await ApplicationData.Current.LocalFolder.CreateFileAsync("photo_" + DateTime.Now.Ticks.ToString(), CreationCollisionOption.ReplaceExisting);
////take & save photo
await captureManager.CapturePhotoToStorageFileAsync(format, capturefile);
//show captured photo
BitmapImage img = new BitmapImage(new Uri(capturefile.Path));
takenImage.Source = img;
takenImage.Visibility = Visibility.Visible;
This way however does not respect any modifications we made to the preview. The only thing that gets respected is the camera device we are using.
Respecting rotation in the captured photo:
In our ongoing sample, we are using a 90 degree rotation to display the preview element in portrait mode. Naturally, we want to port over this orientation in our captured image.
There are two ways to achieve this. We could capture the photo to a WriteableBitmap and manipulate it, or we could manipulate the image stream directly with the BitmapDecoder and BitmapEncoder classes. We will do the latter one.
First, we need to open an InMemoryRandomAccessStream for our the captured photo. We are capturing the photo to the stream with MediaCapture’s CapturePhotoToStreamAsync() method, specifing the stream name and the image format.
The next step is to decode the stream with our BitmapDecoder. If we are performing only rotation, we can directly re-encode the InMemoryRandomAccessStream we are using. Rotating the captured photo is very simple with just setting the BitmapTransform.Rotation property to be rotated by 90 degrees, pretty much as easy as rotating the preview.
The last steps are generating a file in the storage, followed by copying the transcoded image stream into the file stream. Here is the complete code that does all this:
//declare string for filename
string captureFileName = string.Empty;
//declare image format
ImageEncodingProperties format = ImageEncodingProperties.CreateJpeg();
//rotate and save the image
using (var imageStream = new InMemoryRandomAccessStream())
{
//generate stream from MediaCapture
await captureManager.CapturePhotoToStreamAsync(format, imageStream);
//create decoder and encoder
BitmapDecoder dec = await BitmapDecoder.CreateAsync(imageStream);
BitmapEncoder enc = await BitmapEncoder.CreateForTranscodingAsync(imageStream, dec);
//roate the image
enc.BitmapTransform.Rotation = BitmapRotation.Clockwise90Degrees;
//write changes to the image stream
await enc.FlushAsync();
//save the image
StorageFolder folder = KnownFolders.SavedPictures;
StorageFile capturefile = await folder.CreateFileAsync("photo_" + DateTime.Now.Ticks.ToString() + ".jpg", CreationCollisionOption.ReplaceExisting);
captureFileName = capturefile.Name;
//store stream in file
using (var fileStream = await capturefile.OpenStreamForWriteAsync())
{
try
{
//because of using statement stream will be closed automatically after copying finished
await RandomAccessStream.CopyAsync(imageStream, fileStream.AsOutputStream());
}
catch
{
}
}
}
Of course, we need to stop the preview after we captured the photo. It also makes all sense to load the saved image and display it to the user. This is the code to stop the preview:
The result of above mentioned code (screenshot of preview left, captured photo right):
Cropping the captured photo
Not all Windows Phone devices have an aspect ratio of 16:9. In fact, most devices in the market have an aspect ratio of 15:9, due to the fact that they are WVGA or WXGA devices (I talked a bit about this already in my second post). If we are just capturing the photo with the method above, we will have the same black bands in our image as we have in our preview. To get around this and capture a photo that has a true 15:9 resolution (makes sense for photos that get reused in apps, but less for real life photos), additional code is needed.
As with getting the right camera solution, I generated an Enumeration that holds all possible values as well as a helper method to detect which aspect ratio the currently used device has:
public enum DisplayAspectRatio
{
Unknown = -1,
FifteenByNine = 0,
SixteenByNine = 1
}
private DisplayAspectRatio GetDisplayAspectRatio()
{
DisplayAspectRatio result = DisplayAspectRatio.Unknown;
//WP8.1 uses logical pixel dimensions, we need to convert this to raw pixel dimensions
double logicalPixelWidth = Windows.UI.Xaml.Window.Current.Bounds.Width;
double logicalPixelHeight = Windows.UI.Xaml.Window.Current.Bounds.Height;
double rawPerViewPixels = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
double rawPixelHeight = logicalPixelHeight * rawPerViewPixels;
double rawPixelWidth = logicalPixelWidth * rawPerViewPixels;
//calculate and return screen format
double relation = Math.Max(rawPixelWidth, rawPixelHeight) / Math.Min(rawPixelWidth, rawPixelHeight);
if (Math.Abs(relation - (15.0 / 9.0)) < 0.01)
{
result = DisplayAspectRatio.FifteenByNine;
}
else if (Math.Abs(relation - (16.0 / 9.0)) < 0.01)
{
result = DisplayAspectRatio.SixteenByNine;
}
return result;
}
In Windows Phone 8.1, all Elements use logical pixel size. To get the values that most of us are used to, we need to calculate the raw pixels from the logical pixels. After that, we use the same math operations I used already for detecting the ratio of the camera resolution (see post 2). I tried to calculate the values with the logical pixels as well, but this ended up in some strange rounding behavior and not the results I wanted. That’s why I use the raw pixel sizes.
Before we continue with capturing the photo, we are going to add a border that is displayed and shows the area which is captured to the user in XAML:
When we are cropping our photo, we need to treaten the BitmapEncoder and the BitmapDecoder separately. To crop an image, we need to set the Bounds and the new Width and Height of the photo via the BitmapTransform.Bounds property. We also need to read the PixelData via the GetPixelDataAsync() method, apply the changed Bounds to it and pass them to BitmapEncoder via the SetPixelData() method.
At the end, we are flushing the changed stream data directly into the file stream of our StorageFile. Here is how:
//declare string for filename
string captureFileName = string.Empty;
//declare image format
ImageEncodingProperties format = ImageEncodingProperties.CreateJpeg();
using (var imageStream = new InMemoryRandomAccessStream())
{
//generate stream from MediaCapture
await captureManager.CapturePhotoToStreamAsync(format, imageStream);
//create decoder and transform
BitmapDecoder dec = await BitmapDecoder.CreateAsync(imageStream);
BitmapTransform transform = new BitmapTransform();
//roate the image
transform.Rotation = BitmapRotation.Clockwise90Degrees;
transform.Bounds = GetFifteenByNineBounds();
//get the conversion data that we need to save the cropped and rotated image
BitmapPixelFormat pixelFormat = dec.BitmapPixelFormat;
BitmapAlphaMode alpha = dec.BitmapAlphaMode;
//read the PixelData
PixelDataProvider pixelProvider = await dec.GetPixelDataAsync(
pixelFormat,
alpha,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.ColorManageToSRgb
);
byte[] pixels = pixelProvider.DetachPixelData();
//generate the file
StorageFolder folder = KnownFolders.SavedPictures;
StorageFile capturefile = await folder.CreateFileAsync("photo_" + DateTime.Now.Ticks.ToString() + ".jpg", CreationCollisionOption.ReplaceExisting);
captureFileName = capturefile.Name;
//writing directly into the file stream
using (IRandomAccessStream convertedImageStream = await capturefile.OpenAsync(FileAccessMode.ReadWrite))
{
//write changes to the BitmapEncoder
BitmapEncoder enc = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, convertedImageStream);
enc.SetPixelData(
pixelFormat,
alpha,
transform.Bounds.Width,
transform.Bounds.Height,
dec.DpiX,
dec.DpiY,
pixels
);
await enc.FlushAsync();
}
}
You may have notice the GetFifteenByNineBounds() method in the above code. As we need to calculate some values for cropping the image, I decided to separate them. They are not only providing values for the image to be cropped, but also size values for our earlier added Border that is used in my sample (download link at the end of the project) to show the size that the photo will have after our cropping (which is an automatic process in our case,). Here is the code:
private BitmapBounds GetFifteenByNineBounds()
{
BitmapBounds bounds = new BitmapBounds();
//image size is raw pixels, so we need also here raw pixels
double logicalPixelWidth = Windows.UI.Xaml.Window.Current.Bounds.Width;
double logicalPixelHeight = Windows.UI.Xaml.Window.Current.Bounds.Height;
double rawPerViewPixels = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
double rawPixelHeight = logicalPixelHeight * rawPerViewPixels;
double rawPixelWidth = logicalPixelWidth * rawPerViewPixels;
//calculate scale factor of UniformToFill Height (remember, we rotated the preview)
double scaleFactorVisualHeight = maxResolution().Width / rawPixelHeight;
//calculate the visual Width
//(because UniFormToFill scaled the previewElement Width down to match the previewElement Height)
double visualWidth = maxResolution().Height / scaleFactorVisualHeight;
//calculate cropping area for 15:9
uint scaledBoundsWidth = maxResolution().Height;
uint scaledBoundsHeight = (scaledBoundsWidth / 9) * 15;
//we are starting at the top of the image
bounds.Y = 0;
//cropping the image width
bounds.X = 0;
bounds.Height = scaledBoundsHeight;
bounds.Width = scaledBoundsWidth;
//set finalPhotoAreaBorder values that shows the user the area that is captured
finalPhotoAreaBorder.Width = (scaledBoundsWidth / scaleFactorVisualHeight) / rawPerViewPixels;
finalPhotoAreaBorder.Height = (scaledBoundsHeight / scaleFactorVisualHeight) / rawPerViewPixels;
finalPhotoAreaBorder.Margin = new Thickness(
Math.Floor(((rawPixelWidth - visualWidth) / 2) / rawPerViewPixels),
0,
Math.Floor(((rawPixelWidth - visualWidth) / 2) / rawPerViewPixels),
0);
finalPhotoAreaBorder.Visibility = Visibility.Visible;
return bounds;
}
Again, we need to apply raw pixels to achieve the best results here (I just pasted those lines in for this sample). To calculate the correct values for our Border, we need the scale factor between the screen and the preview resolution we used (which is the scaleFactorVisualHeight double). Before we’re calculating the border values, we are setting the Width to resolution’s Height (we rotated, remember?) and calculate the matching 15:9 Height.
The Border values are based on the Width and Height of the cropped image, but scaled down by scaleFactorVisualHeight’s value and converted in raw pixel. The Margin positions the border accordingly on top of the preview element.
This is the result of above mentioned code (screenshot of preview left, captured photo right):
That’s all you need to know to get started with basic photo capturing from within your Windows Phone 8.1 Runtime app. Of course, there are also other modifications that you can apply, and I mentioned already most of the classes that lead you to the matching methods and properties (click on the links to get to the documentation)
By the way, most of the code can be adapted in a Windows 8.1 app as well (with some differences, of course).
Sample project
As promised, you can download the sample here. It contains all code snippets I showed you and is able to run as you build and deploy it.
As always, feedback is welcome and I hope this post is helpful for some of you.
Like promised in my first post about photo capturing, I will provide some common modification scenarios when using the MediaCapture API. This is what this post is about.
Choosing a camera
If you read my first post, you probably remember that the MediaCapture API automatically selected the front camera of my Lumia 1020. Like often, we have to write some additional code to switch between the cameras.
The cameras are listed in the Panels in the Windows.Devices.Enumeration Namespace. This namespace contains all “devices” that are connected to the phone and has different properties to detect the correct panel. We are going to use the DeviceClass to detect all video capture devices (which are normally also the photo capture devices on Windows Phone, but can be different on a PC/Tablet). As we want to switch between Front and Back, we are also detecting the EnclosureLocation. Here is how I implemented it:
In this case, we selected the back camera. To make the MediaCapture API actually use this device, we need to generate a new instance of MediaCaptureInitializationSettings, where we select the cameras Id as VideDeviceId. If you now start capturing, this is an exemplary result:
Rotating the preview
However, this not quite satisfying, because the preview automatically uses the landscape orientation. Luckily, this can be changed with just one single line of code (that needs to be added before actually starting the preview):
Note: the black bands on both sides may happen due to the fact that most devices have a 15:9 ratio (WXGA, WVGA). On Devices like the Lumia 830 or 930, which have a 16:9 ratio, the preview will use the full screen in portrait mode. I tried a lot of things to get rid of those bands already, sadly without success. Once I found a proper solution, I will write another blog post and link it here on how to do it (any tips are welcome).
Limiting resolution
Sometimes, we need to limit resolutions (for example resolution limits on other parts in our app). This is possible by detecting the supported solutions and matching them to the screen ratio. As we are using the whole screen for previewing, of course we want to get our captured photo to use the same space, too.
My way to do this is to calculate the screen ratio, and return an enumeration value. This is the easiest way, and can be easily used in the further code to limit the resolution. The enumeration looks like this:
And this is my helper to match the screen format (which is always wide screen on Windows Phone):
private CameraResolutionFormat MatchScreenFormat(Size resolution)
{
CameraResolutionFormat result = CameraResolutionFormat.Unknown;
double relation = Math.Max(resolution.Width, resolution.Height) / Math.Min(resolution.Width, resolution.Height);
if (Math.Abs(relation - (4.0 / 3.0)) < 0.01)
{
result = CameraResolutionFormat.FourByThree;
}
else if (Math.Abs(relation - (16.0 / 9.0)) < 0.01)
{
result = CameraResolutionFormat.SixteenByNine;
}
return result;
}
We could easily extend the calculation to 15:9, too. However, as the most camera resolutions are 4:3 or 16:9, this makes no sense in our use case (as 15:9 is still a widescreen format). The next thing we need to add is another helper to get the highest possible resolution for our photo and the preview. We are achieving this by generating a new object of type VideoEncodingProperties:
private VideoEncodingProperties maxResolution()
{
VideoEncodingProperties resolutionMax = null;
//get all photo properties
var resolutions = captureManager.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo);
//generate new list to work with
List<VideoEncodingProperties> vidProps = new List<VideoEncodingProperties>();
//add only those properties that are 16:9 to our own list
for (var i = 0; i < resolutions.Count; i++)
{
VideoEncodingProperties res = (VideoEncodingProperties)resolutions[i];
if (MatchScreenFormat(new Size(res.Width, res.Height)) != CameraResolutionFormat.FourByThree)
{
vidProps.Add(res);
}
}
//order the list, and select the highest resolution that fits our limit
if (vidProps.Count != 0)
{
vidProps = vidProps.OrderByDescending(r => r.Width).ToList();
resolutionMax = vidProps.Where(r => r.Width < 2600).First();
}
return resolutionMax;
}
What I am doing here: I read all available VideoEncodingProperties for the MediaStreamType Photo. As I mentioned before, we need only wide screen resolution for Windows Phone, that’s why I add only those that have not a 4:3 ratio to my list. Then I am using LINQ to order the list and select the highest resolution from that list.
Using this helper is also very easy, done with one line of code before starting the preview and best also before rotating the preview:
This way, we are able to respect any resolution limits that we might face while developing our app, while keeping the photo quality as high as possible.
private CameraResolutionFormat MatchScreenFormat(Size resolution)
{
CameraResolutionFormat result = CameraResolutionFormat.Unknown;
double relation = Math.Max(resolution.Width, resolution.Height) / Math.Min(resolution.Width, resolution.Height);
if (Math.Abs(relation - (4.0 / 3.0)) < 0.01)
{
result = CameraResolutionFormat.FourByThree;
}
else if (Math.Abs(relation - (16.0 / 9.0)) < 0.01)
{
result = CameraResolutionFormat.SixteenByNine;
}
return result;
}
Focus
Focusing on objects in your photo is quite important. Sadly, it seems that currently we are not able to have a one solution fits all devices solution for using AutoFocus. I experimented a lot with it, and finally I got aware of known issues with Nokia drivers and the new MediaCapture API’s, as described here. Microsoft is working with Nokia (or their devices department) to fix this problem.
The only solution I got working for an Runtime app is to use manual focus. All other attempts gave me one Exception after the other, be it on cancelling the preview or be it on while previewing itself. I’ll write another post on how to use the AutoFocus as soon as it is working like it should. In the meantime, here is my solution for manual focusing.
Notice that as with any slider, you need to follow the order: Set Maximum first, then Minimum. If you do not, you will likely get an unusable Slider in return. If the VideoDeviceController.Focus property would work (seems like it is also affected by the above mentioned driver problems), we could read and set the Slider values from its MediaDeviceControl.Capabilities property. I tried to read them at any stage of previewing, but their values are always 0.0, null and false. The range up to 1000 fits in very well on all devices I tested (Lumia 920, 930 and 1020).
Ok, enough of whining. Let’s have a look at my solution. First, we need to generate a small helper that allows us to adjust the focus based on the slider values:
private async void SetFocus(uint? focusValue = null)
{
//try catch used to avoid app crash at startup when no CaptureElement is active
try
{
//setting default value
if (!focusValue.HasValue)
{
focusValue = 500;
}
//check if the devices camera supports focus control
if (captureManager.VideoDeviceController.FocusControl.Supported)
{
//disable flash assist for focus control
captureManager.VideoDeviceController.FlashControl.AssistantLightEnabled = false;
//configure the FocusControl to manual mode
captureManager.VideoDeviceController.FocusControl.Configure(new FocusSettings() { Mode = FocusMode.Manual, Value = focusValue, DisableDriverFallback = true });
//update the focus on our MediaCapture
await captureManager.VideoDeviceController.FocusControl.FocusAsync();
}
}
catch { }
}
This methods checks if the current camera supports Focus, and sets its value according to the slider. The AssistantLight is disabled in this case. Its default is enabled (true).
To add the possibility to adjust the focus, we need to configure our own FocusSettings that tell the camera that we are focusing manually based on the slider’s value. Finally, we need to perform the focusing action by calling the FocusControl’s FocusAsync method.
The next step is to hook up to changes in the slider values within the FocusValueSlider_ValueChanged event:
Now every move of the slider will change the focus of the preview and of course also of the captured photo (which we will learn more about in the third post of this series). To initialize our Focus correctly with the value of 500 we set in XAML, just call SetFocus(); before you start the preview. Here is the result:
Disclaimer: I do not know if this follows best practices, but it works. If you have feedback for the above mentioned code snippets, feel free to leave a comment below.
In the third and last post I’ll show you how to save the images (also in different folders or only within the app).
With the recent release of the public beta of RandR, I also learned a lot about taking photos from within an Windows Phone 8.1 app. There are several differences to Windows Phone 8, so I decided to start this three part series on how to capture a photo in your app (it would be too much for one single post).
The series will contain following topics:
Part I (this one): the preview of the photo to capture
The series concentrates on basic features to enable you to get started. I am adding relevant links to those posts, and at the end of the series, I will also attach a sample project.
Let’s start
Before we can use MediaCapture, please make sure that you enable Webcam and Microphone in your app’s Package.appxmanifest file. Then, we need is an Element that shows us the preview of the content we want to capture. In a Runtime app, we are using a CaptureElement for this. We also need to buttons, one to start/cancel the preview operation, and one to save the photo. Of course we want to show the photo we have taken, so we need also an image element.
Asign the click handlers to the code behind file, where we will also continue to work now.
Before we’ll have a look at the preview code, we need to enable our app to obtain the whole screen. This makes all sense, as we want to capture a photo, and of course we want to see as much as possible in the preview. Add these two lines to the constructor of the page:
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
appView.SetDesiredBoundsMode(ApplicationViewBoundsMode.UseCoreWindow);
The ApplicationViewBoundsMode enumeration has two values (UseVisible and UseCoreWindow). The later one uses the whole screen (even behind the SystemTray and also behind the BottomAppBar) and suits our needs. Only one thing to remember for your app: You need to set the Margins in this case to get your UI right.
The preview code
Windows Runtime apps use the MediaCapture class for all photo and video capturing.
To enable your app to preview the things you want to capture, we first need to initialize the MediaCapture. We are doing this by a helper method, as we will need it in the next post to create some options for our MediaCapture. After declaring a page wide variable for the MediaCapture, add the following code to your code behind file:
What we are doing is to set the Source of our previewElement that we declared in XAML to our captureManager and asynchronously start the preview. The isPreviewing Boolean is used to detect if we are actually previewing. We’ll need it in our method to stop the preview. This is very important. If you do not stop the preview, chances are high that you will freeze your phone or make the camera unusable for other apps, too!
We need to do a double check here: First, we need to see if we have a captureManager instance. Then, if we are previewing, we are going to stop it. If we are no longer previewing, we are setting the CaptureElement Source to null, rename our button and free all resources our captureManager used with the Dispose() method.
Now that we have everything for the preview in place, we are able to connect it to our captureButton:
Now we are already able to start previewing (without any options) on our phone:
You might get similar strange results if you start capturing. For example, the preview on my Lumia 1020 is flipped upside down and the front camera is used.