I'm using Media Foundation in my WPF application to implement functionality that is similar to the Windows Camera App: show a live webcam feed and take a high resolution image when pressing a button. While the Windows Camera App always takes a correctly exposed snapshot, the snapshot taken by Media Foundation is often overexposed.
The application is only used by devices that have built-in high resolutions for Photos (eg Surface Go 2, Surface 7 Pro, ...). A specific image stream is used to take the snapshot instead of reading one frame of the video stream.
I have already tried changing the IAMCameraControl and IAMVideoProcAmp properties like Exposure or Brightness before taking a snapshot but the photo was always overexposed.
Are there any additional settings or approaches for fixing overexposure when taking a high resolution snapshot (eg. the same way the Windows Camera App works)?
Overexposed snapshot
High resolutions for photos
Code to take the snapshot
IMFCaptureEngineClassFactory captureEngineClassFactory = null;
IMFCaptureEngine captureEngine = null;
IMFCapturePhotoSink capturePhotoSink = null;
IMFMediaType photoMediaType = null;
try
{
// Create a CaptureEngineClassFactory
captureEngineClassFactory = mff.GetCaptureEngineClassFactory();
// Create a CaptureEngine
captureEngine = mff.GetCaptureEngine(captureEngineClassFactory);
// Initialize the CaptureEngine
MFCaptureEngineOnEventCallback captureEngineOnEventCallback = MFCaptureEngineOnEventCallback.GetInstance();
mff.Initialize(captureEngine, captureEngineOnEventCallback, mediaSource);
captureEngineOnEventCallback.WaitUntilInitialized();
// Create a Photo Sink
capturePhotoSink = mff.CreateCapturePhotoSink(captureEngine);
// Create a Photo Media Type
photoMediaType = CreatePhotoMediaType(mediaType);
// Remove all streams
mff.RemoveAllStreams(capturePhotoSink);
// Add Stream
int sinkStreamIndex = mff.AddStream(capturePhotoSink, streamIndex, photoMediaType);
// Set output filename
mff.SetSampleCallback(capturePhotoSink, this);
// Take photo
mff.TakePhoto(captureEngine);
captureEngineOnEventCallback.WaitUntilDone();
// imageSource should be filled in after executing TakePhoto
return image;
}
finally
{
TryRelease(captureEngineClassFactory);
TryRelease(captureEngine);
TryRelease(capturePhotoSink);
TryRelease(photoMediaType);
}
Related
An alternative title could be: What happened to PIN_CATEGORY_STILL?
I am currently comparing images that were captured using DirectShow and PIN_CATEGORY_STILL with images that were captured using UWP MediaCapture.
On the device I am testing/playing around with DirectShow and MediaCapture, DirectShow detects a PIN_CATEGORY_STILL but I am not able to initialize an instance of MediaCapture with anything other than PhotoCaptureSource.VideoPreview.
MediaCaptureInitializationSettings settings = new()
{
VideoDeviceId = someDeviceId,
PhotoCaptureSource = PhotoCaptureSource.Photo
};
MediaCapture capture = new();
// this throws an exception
// "The capture source does not have an independent photo stream."
await capture.InitializeAsync(settings);
At this point I'm not even sure if PhotoCaptureSource.Photo is meant to be used as an equivalent to PIN_CATEGORY_STILL.
Images captured with PIN_CATEGORY_STILL are way brighter in a dark environment and have a much better quality (in file size and resolution) (which is clear to me, since I am using PhotoCaptureSource.VideoPreview for MediaCapture).
Considering this resource Win32 and COM for UWP apps, it seems like UWP MediaCapture does not use DirectShow underneath but MediaFoundation (which is meant to be a successor for DirectShow).
This article led me to this StackOverflow question Media Foundation is incorrectly marking still image capture stream descriptors as video capture, which basically states that MediaFoundation has no PIN_CATEGORY_STILL but returns 1 FPS as video capability for such devices (or profiles).
Since I am not directly using MediaFoundation nor C++, I tried testing this by querying GetAvailableMediaStreamProperties:
private void Foo()
{
var videoPreviewProperties = GetEncodingProperties(MediaStreamType.VideoRecord);
var photoProperties = GetEncodingProperties(MediaStreamType.Photo);
var videoRecordProperties = GetEncodingProperties(MediaStreamType.VideoPreview);
}
private List<VideoEncodingProperties> GetEncodingProperties(MediaStreamType streamType)
{
// MediaCapture was previously initialized with PhotoCaptureSource.VideoPreview
return MediaCapture.VideoDeviceController
.GetAvailableMediaStreamProperties(streamType)
.OfType<VideoEncodingProperties>()
.ToList();
}
None of these returns a VideoEncodingProperties with only 1 FPS.
To test MediaCapture any further I tried some of the sample applications from here UWP sample. I tried CameraAdvancedCapture, CameraOpenCV and CameraManualControls, but the results were not nearly as good as good old PIN_CATEGORY_STILL.
What happened to PIN_CATEGORY_STILL?
Is there any way to capture images without DirectShow/PIN_CATEGORY_STILL and still keeping this level of quality?
Any enlightenment is much appricated.
Hello I displayed 1 webcam preview in UWP and that was a success.
But now I want to use 2 camera's preview on my program or be able to choose between the two cameras while connected 2 cameras on computer.
When I run 1 webcam preview, I referred to documentation on using MediaCapture and it was good.
But now I don't know how to display 2 camera previews or select a one between cameras.
Is it impossible?
Yes, it is possible :-) . The MediaCapture class takes the default camera when you call the InitializeAsync method without parameters, but there is another overload that allows you to specify the device ID.
The documentation shows how to discover video capture devices:
DeviceInformationCollection devices =
await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
Now you can initialize multiple MediaCapture instances like this:
foreach ( var device in devices )
{
var mediaInitSettings =
new MediaCaptureInitializationSettings { VideoDeviceId = device.Id };
MediaCapture mediaCapture = new MediaCapture();
mediaCapture.InitializeAsync(mediaInitSettings);
//do something with the media capture
}
Naturally, when you want to display multiple previews, you will need to have multiple CaptureElements, each set to the specific MediaCapture instance you want.
However this approach is quite simplified. To make sure the concurrent capture and preview is supported, you must first ensure to query only cameras that support device profile using MediaCapture.IsVideoProfileSupported method as shown in the documentation and then also check find a concurrency-enabled profile common for both cameras - MediaCapture.FindConcurrentProfiles, see docs. Only then you can safely create the two previews and know the app will not crash.
So I have some code that takes a photo in a UWP app (running on windows desktops and phones) using this code.
await _mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), stream);
And it works great but it always takes the image at the full resolution of the device... (so 44 megapixels on a Lumia 1020) which is too big for me. I want to limit the resolution to a fixed size (say around 16 megapixel).
So is there a way of setting the camera capture resolution or will I have to capture it at full resolution and downscale it myself?
You should be able to change resolution of MediaCapture element by setting MediaStreamProperties just after initialization:
// initialization here
// get available resolutions
var resolutions = captureManager.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo).ToList();
// set used resolution
await captureManager.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, resolutions[1]);
i am developing a windows phone app where application has an option to pin the application to home screen. And i am using ShellTileSchedule class to do schedule the update periodically. Some reason my app is not pushing any update to tile. My app data is completely local, no data is coming from outside.
In my tile update, i am not updating any image on the lile, but only changing the data to display.
foreach (ShellTile tile in ShellTile.ActiveTiles)
{
IconicTileData tileData = GetTileData();
tileSchedule = new ShellTileSchedule(tile, tileData);
tileSchedule.Interval = UpdateInterval.EveryHour;
tileSchedule.Recurrence = UpdateRecurrence.Interval;
tileSchedule.Count=GetUserData();
tileSchedule.StartTime = DateTime.Now;
tileSchedule.Start();
tile.Update(tileData);
}
Any help in this regard? Or do i need to background agent to update the tile?
ShellTileSchedule can only pull images off the web, not from the phone itself. This is one of the limitations of ShellTileSchedule. If you want to set background images to resources on the phone, look at using push notifications instead.
Source: http://www.silverlightshow.net/news/WP7-Using-ShellTileSchedule-to-update-your-app-s-Live-Tile-background.aspx
Shouldn't you be setting a ShellTileSchedule.RemoteImageUri somewhere? I mean, that's kinda what ShellTileSchedule is there for, to update your tile image from a remote Uri on a regular interval... See sample of how to use this class for secondary tiles here.
You have to fill the properties of IconicTileData. In your sample you just create empty data structure and use it for the schedule, that won't work. I use it like this:
IconicTileData newTileData = new IconicTileData
{
Title = SharedResources.AppName,
Count = BatteryHelper.BateryLevel,
SmallIconImage = new Uri(#"/Assets/IconicSmall.png", UriKind.Relative),
IconImage = new Uri(#"/Assets/IconicMedium.png", UriKind.Relative),
};
I want to provide the user with a scaled-down screenshot of their desktop in my application.
Is there a way to take a screenshot of the current user's Windows desktop?
I'm writing in C#, but if there's a better solution in another language, I'm open to it.
To clarify, I need a screenshot of the Windows Desktop - that's the wallpaper and icons only; no applications or anything that's got focus.
You're looking for Graphics.CopyFromScreen. Create a new Bitmap of the right size and pass the Bitmap's Graphics object screen coordinates of the region to copy.
There's also an article describing how to programmatically take snapshots.
Response to edit: I misunderstood what you meant by "desktop". If you want to take a picture of the desktop, you'll have to:
Minimize all windows using the Win32API (send a MIN_ALL message) first
Take the snapshot
Then undo the minimize all (send a MIN_ALL_UNDO message).
A better way to do this would be not to disturb the other windows, but to copy the image directly from the desktop window. GetDesktopWindow in User32 will return a handle to the desktop. Once you have the window handle, get it's device context and copy the image to a new Bitmap.
There's an excellent example on CodeProject of how to copy the image from it. Look for the sample code about getting and creating the device context in the "Capturing the window content" section.
I get the impression that you are shooting for taking a picture of the actual desktop (with wallpaper and icons), and nothing else.
1) Call ToggleDesktop() in Shell32 using COM
2) Use Graphics.CopyFromScreen to copy the current desktop area
3) Call ToggleDesktop() to restore previous desktop state
Edit: Yes, calling MinimizeAll() is belligerent.
Here's an updated version that I whipped together:
/// <summary>
/// Minimizes all running applications and captures desktop as image
/// Note: Requires reference to "Microsoft Shell Controls and Automation"
/// </summary>
/// <returns>Image of desktop</returns>
private Image CaptureDesktopImage() {
//May want to play around with the delay.
TimeSpan ToggleDesktopDelay = new TimeSpan(0, 0, 0, 0, 150);
Shell32.ShellClass ShellReference = null;
Bitmap WorkingImage = null;
Graphics WorkingGraphics = null;
Rectangle TargetArea = Screen.PrimaryScreen.WorkingArea;
Image ReturnImage = null;
try
{
ShellReference = new Shell32.ShellClass();
ShellReference.ToggleDesktop();
System.Threading.Thread.Sleep(ToggleDesktopDelay);
WorkingImage = new Bitmap(TargetArea.Width,
TargetArea.Height);
WorkingGraphics = Graphics.FromImage(WorkingImage);
WorkingGraphics.CopyFromScreen(TargetArea.X, TargetArea.X, 0, 0, TargetArea.Size);
System.Threading.Thread.Sleep(ToggleDesktopDelay);
ShellReference.ToggleDesktop();
ReturnImage = (Image)WorkingImage.Clone();
}
catch
{
System.Diagnostics.Debugger.Break();
//...
}
finally
{
WorkingGraphics.Dispose();
WorkingImage.Dispose();
}
return ReturnImage;
}
Adjust to taste for multiple monitor scenarios (although it sounds like this should work just fine for your application).
You might look at using GetDesktopWindow through p/invoke, then get the device context and take your snapshot. There is a very detailed tutorial here. It might be better than perturbing the existing windows.
You can always catch screenshot on windows startup before applications run
or use other desktop by using desktop switching. Take a look here:
http://www.codeproject.com/Articles/7666/Desktop-Switching
Switch to other desktop, take picture and remember you dont need to show it just create it "DESKTOP"
All former answers are completely wrong (minimizing apps is absurd)
Simply use SHW api : one line of code (the desktop bitmap being cached, the api simply blits it to your HDC...)