The documentation provided for Canon EOS Digital SDK is not very complete and it is difficult to find good examples on the internet. This is a tutorial of some of the most important things I have compiled.
New Version Download
Old Version Download
Introduction
The Canon EOS Digital SDK is quite a powerful SDK to remote control Canon DSLRs. Unfortunately, it is quite difficult to find some good examples for it on the internet and the provided documentation is not very complete. Since I have found out many things already and want to make it easier for others, I thought I could compile some of the most important things together and do a tutorial.
The tutorial and the library is written in C# but it is CLS compliant and therefore can be used from any .NET language.
This tutorial includes:
Note: I'm not affiliated with or funded by Canon Inc. in any way.
I do not guarantee for this software in any way. Use it at your own risk!
Background
You have to have a copy of the Canon EDSDK to get this working. I am not allowed to include the DLLs within the project so you'll have to apply to get them, here:
Once you have the DLLs, put them beside your executable. Other places will cause problems if the main DLL makes a call to a sub-DLL.
It is also important that you use your camera in fully manual or at least half-automated mode for some methods to work.
Using the Code
The solution consists of four projects:
EDSDKLib
: The main project where all the SDK and camera handling happens WinFormsExample
: An example project that consumes the EDSDKLib
and uses it in a Windows Forms UI application WpfExample
: An example project that consumes the EDSDKLib
and uses it in a WPF UI application ConsoleExample
: An example project that consumes the EDSDKLib
and uses it in a console application
I will only concentrate on the EDSDKLib
project here since this is what this whole article is about: Using the EDSDK with C#.
First, let us look at the main classes:
CanonAPI
: The class that mainly handles the SDK lifetime, the connected Cameras and the SDK events Camera
: This is used to communicate with a physical camera. Set
/Get
properties, take photos, download data and more. CanonSDK
: This class contains all the native calls to the Canon SDK DLLs and some helper methods to set
/get
values. STAThread:
A helper class to create an STA thread or execute code on an STA thread ErrorHandler
: A static
class that provides methods to check the SDK return values/error codes
Let's have a closer look into the inner workings of those classes.
Initializing and terminating are the easiest things to do. When you start your program, create a new instance of the CanonAPI
class:
public CanonAPI(bool useCallingThread)
{
try
{
lock (InitLock)
{
if (RefCount == 0)
{
if (useCallingThread)
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
throw new ThreadStateException("Calling thread must be in STA");
ErrorHandler.CheckError(this, CanonSDK.EdsInitializeSDK());
}
else
{
CanonSDK.EdsRelease(IntPtr.Zero);
MainThread = new ApiThread();
MainThread.Start();
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsInitializeSDK()));
}
CanonSDK.InitializeVersion();
CameraAddedEvent = new SDKCameraAddedHandler(CanonAPI_CameraAddedEvent);
ErrorHandler.CheckError(this,
CanonSDK.EdsSetCameraAddedHandler(CameraAddedEvent, IntPtr.Zero));
_IsSDKInitialized = true;
}
RefCount++;
}
}
catch
{
IsDisposed = true;
if (MainThread?.IsRunning == true) MainThread.Shutdown();
throw;
}
}
Ensure that you keep around an instance of this class, otherwise the GC might collect it and the SDK gets terminated with it (because of the destructor of the CanonAPI
class).
And when you close your program, call the Dispose
method. The public Dispose
method in turn calls this overload:
protected virtual void Dispose(bool managed)
{
lock (InitLock)
{
if (!IsDisposed)
{
if (RefCount == 1)
{
_IsSDKInitialized = false;
ErrorCode err = CanonSDK.EdsSetCameraAddedHandler(null, IntPtr.Zero);
if (managed)
{
ErrorHandler.CheckError(this, err);
CurrentCameras.ForEach(t => t.Dispose());
}
if (MainThread?.IsRunning == true)
err = MainThread.Invoke(() => { return CanonSDK.EdsTerminateSDK(); });
if (MainThread?.IsRunning == true) MainThread.Shutdown();
if (managed) ErrorHandler.CheckError(this, err);
}
RefCount--;
IsDisposed = true;
}
}
}
Now that the SDK is initialized, we can get a list of currently connected cameras:
public List<Camera> GetCameraList()
{
if (IsDisposed) throw new ObjectDisposedException(nameof(CanonAPI));
lock (CameraLock)
{
IEnumerable<IntPtr> ptrList = GetCameraPointerList();
List<Camera> camList = new List<Camera>();
foreach (var ptr in ptrList)
{
var oldCam = CurrentCameras.FirstOrDefault(t => t.Reference == ptr);
if (oldCam != null && !oldCam.IsDisposed)
camList.Add(oldCam);
else camList.Add(new Camera(ptr));
}
var oldCameras = CurrentCameras.Where(t => !ptrList.Any(u => u == t.Reference));
foreach (var cam in oldCameras) { if (!cam.IsDisposed) cam.Dispose(); }
CurrentCameras.Clear();
CurrentCameras.AddRange(camList);
return camList;
}
}
The CanonAPI
class keeps a list of connected cameras to ensure that each SDK camera pointer is associated with only one Camera
class instance. Having two or more Camera
class instances that use the same SDK pointer could potentially cause problems if they access the camera at the same time.
The subroutine GetCameraPointerList
that is used above gets a list of pointers that point to the SDKs camera objects:
protected IEnumerable<IntPtr> GetCameraPointerList()
{
if (IsDisposed) throw new ObjectDisposedException(nameof(CanonAPI));
IntPtr camlist;
ErrorHandler.CheckError(this, CanonSDK.EdsGetCameraList(out camlist));
int camCount;
ErrorHandler.CheckError(this, CanonSDK.EdsGetChildCount(camlist, out camCount));
List<IntPtr> ptrList = new List<IntPtr>();
for (int i = 0; i < camCount; i++)
{
IntPtr cptr;
ErrorHandler.CheckError(this, CanonSDK.EdsGetChildAtIndex(camlist, i, out cptr));
ptrList.Add(cptr);
}
ErrorHandler.CheckError(this, CanonSDK.EdsRelease(camlist));
return ptrList;
}
For now, we are done with the CanonAPI
class and can start using the Camera
and open a session with it:
public void OpenSession()
{
CheckState(false);
if (!SessionOpen)
{
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsOpenSession(CamRef));
uint property;
_IsRecordAvailable = CanonSDK.GetPropertyData
(CamRef, PropertyID.Record, 0, out property) == ErrorCode.OK;
SDKStateEvent += new SDKStateEventHandler(Camera_SDKStateEvent);
SDKPropertyEvent += new SDKPropertyEventHandler(Camera_SDKPropertyEvent);
SDKProgressCallbackEvent += new SDKProgressCallback
(Camera_SDKProgressCallbackEvent);
SDKObjectEvent += new SDKObjectEventHandler(Camera_SDKObjectEvent);
ErrorHandler.CheckError(this,
CanonSDK.EdsSetCameraStateEventHandler
(CamRef, StateEventID.All, SDKStateEvent, CamRef));
ErrorHandler.CheckError(this,
CanonSDK.EdsSetObjectEventHandler
(CamRef, ObjectEventID.All, SDKObjectEvent, CamRef));
ErrorHandler.CheckError(this,
CanonSDK.EdsSetPropertyEventHandler
(CamRef, PropertyEventID.All, SDKPropertyEvent, CamRef));
SessionOpen = true;
});
}
}
If you are done working with this camera, close the session this way:
public void CloseSession()
{
CheckState(false);
if (SessionOpen)
{
UnsubscribeEvents();
if (IsLiveViewOn)
{
KeepLVAlive = false;
LVThread.Join(5000);
}
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsCloseSession(CamRef));
SessionOpen = false;
});
}
}
You can safely open and close the session multiple times while the camera is connected to the computer.
If you are completely done with this camera, you can dispose it by calling the Dispose
method (which, as before, internally calls this overload):
protected virtual void Dispose(bool managed)
{
if (!IsDisposed)
{
UnsubscribeEvents();
if (IsLiveViewOn)
{
KeepLVAlive = false;
LVThread.Join();
}
IsLiveViewOn = false;
MainThread.Invoke(() =>
{
if (CanonAPI.IsSDKInitialized)
{
if (SessionOpen) CanonSDK.EdsCloseSession(CamRef);
CanonSDK.EdsRelease(CamRef);
}
_IsDisposed = true;
});
MainThread.Shutdown();
}
}
UnsubscribeEvents
is just a little helper method:
private void UnsubscribeEvents()
{
SDKStateEvent -= Camera_SDKStateEvent;
SDKPropertyEvent -= Camera_SDKPropertyEvent;
SDKProgressCallbackEvent -= Camera_SDKProgressCallbackEvent;
SDKObjectEvent -= Camera_SDKObjectEvent;
if (CanonAPI.IsSDKInitialized)
{
MainThread.Invoke(() =>
{
CanonSDK.EdsSetCameraStateEventHandler(CamRef, StateEventID.All, null, CamRef);
CanonSDK.EdsSetObjectEventHandler(CamRef, ObjectEventID.All, null, CamRef);
CanonSDK.EdsSetPropertyEventHandler(CamRef, PropertyEventID.All, null, CamRef);
});
}
}
Setting and getting camera settings can be very easy for values with an ID, but more difficult for struct
values.
Here is the getting method for normal int
values (like Tv, Av or ISO):
public int GetInt32Setting(PropertyID propID, int inParam = 0)
{
CheckState();
return MainThread.Invoke(() =>
{
int property;
ErrorHandler.CheckError(this, CanonSDK.GetPropertyData
(CamRef, propID, inParam, out property));
return property;
});
}
There are multiple methods for getting values of different types but they all look basically the same. The important part is that all of them call the CanonSDK.GetPropertyData
method that handles all the details. If you are interested in how to get values from managed to unmanaged, have a look at the mentioned method and its overloads in the CanonSDK
class.
Setting a camera setting is similar to getting it but there are only two methods because we can handle it in a more generic way with the object
type.
Any value other than a string
can be set with this method (e.g., integers for Tv, Av or ISO):
public void SetSetting(PropertyID propID, object value, int inParam = 0)
{
CheckState();
MainThread.Invoke(() =>
{
int propsize;
DataType proptype;
ErrorHandler.CheckError(this,
CanonSDK.EdsGetPropertySize(CamRef, propID, inParam, out proptype, out propsize));
ErrorHandler.CheckError(this,
CanonSDK.EdsSetPropertyData(CamRef, propID, inParam, propsize, value));
});
}
Setting a string
value:
public void SetSetting(PropertyID propID, string value, int inParam = 0, int MAX = 32)
{
CheckState();
if (value == null) value = string.Empty;
if (value.Length > MAX - 1) value = value.Substring(0, MAX - 1);
byte[] propBytes = System.Text.Encoding.ASCII.GetBytes(value + '\0');
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsSetPropertyData(CamRef,
propID, inParam, propBytes.Length, propBytes));
});
}
String
length can be limited with the MAX
parameter because some camera properties can only have a specific length. Most of them have a length of 32 characters though.
Different cameras have different settings available and lenses only have a certain range of Av values. That's why you need to get a list of all supported settings. This only works with "AEModeSelect
", "ISO
", "Av
", "Tv
", "MeteringMode
" and "ExposureCompensation
". This method returns an array of CameraValue
s. Each CameraValue
has an integer value (that's the ID used by the SDK), a string
value (that can be used as a label, e.g., "ISO 100
" or "1/100
") and, if applicable, a double value (that can be used for calculations, e.g., ISO 100 has a value of 100 and a Tv value of 1/100 has the value 0.01):
public CameraValue[] GetSettingsList(PropertyID propId)
{
CheckState();
if (propId == PropertyID.AEModeSelect || propId == PropertyID.ISO ||
propId == PropertyID.Av || propId == PropertyID.Tv
|| propId == PropertyID.MeteringMode ||
propId == PropertyID.ExposureCompensation)
{
CameraValue[] vals = null;
PropertyDesc des = default(PropertyDesc);
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsGetPropertyDesc(CamRef, propId, out des)));
vals = new CameraValue[des.NumElements];
for (int i = 0; i < vals.Length; i++)
vals[i] = new CameraValue(des.PropDesc[i], propId);
return vals;
}
else throw new ArgumentException($"Method cannot be used with Property ID {propId}");
}
In the library, there are also five corresponding classes for each type that supports this method. These classes contain a list of all possible values and some regularly used fields (like Auto
or Bulb
). These classes are called AvValues
, TvValues
, ISOValues
, ExpCompValues
, AEModeValues
and MeteringModeValues
. All methods, properties and fields of those classes are static
.
Send a Command to the Camera
With camera commands, you can control several things, for example, take a picture, press the shutter button or drive the focus motor of your lens.
public void SendCommand(CameraCommand command, int inParam = 0)
{
CheckState();
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsSendCommand(CamRef, command, inParam)));
}
Some of the commands are used in the following topics.
To change the lock status of your camera or to enter/exit direct transfer mode, you can use the camera status commands:
public void SendCommand(CameraCommand command, int inParam = 0)
{
CheckState();
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsSendCommand(CamRef, command, inParam)));
}
To take a photo with the current settings, call the TakePhoto
method:
public void TakePhoto()
{
CheckState();
SendCommand(CameraCommand.TakePicture);
}
Alternatively, you can use the TakePhotoAsync
method that executes the command asynchronously (on a Threadpool
Thread, not on a .NET 4.5 Task).
Or if your camera supports it, you can use the TakePhotoShutter
method that uses the PressShutterButton
command instead of the TakePicture
command:
public void TakePhotoShutter()
{
CheckState();
SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Completely);
SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF);
}
Same as before, you can also use the TakePhotoShutterAsync
method for asynchronous execution.
To take a photo in bulb mode, call the TakePhotoBulb
method:
public void TakePhotoBulb(int bulbTime)
{
CheckState();
SendCommand(CameraCommand.BulbStart);
Thread.Sleep(bulbTime);
SendCommand(CameraCommand.BulbEnd);
}
Note that you have to have the Tv value on Bulb
before calling this method, otherwise you'll get an error. The opposite applies to the TakePhoto
methods, there, Tv must not be set to Bulb
.
If you would like to download the taken image directly to your computer, have a look at the next topic.
To save taken photos directly onto the computer instead of the camera-memory, set it by calling the SetSetting
method:
SetSetting(PropertyID.SaveTo, (int)SaveTo.Host);
Most cameras require that you set the remaining disk space on your computer before taking a picture. If you do not do this, most cameras assume that there is no disk space left and will return an error.
You can call the SetCapacity
method to set the remaining disk space (if you want, you can just set an arbitrarily high value):
public void SetCapacity(int bytesPerSector, int numberOfFreeClusters)
{
CheckState();
MainThread.Invoke(() =>
{
Capacity capacity = new Capacity(numberOfFreeClusters, bytesPerSector, true);
ErrorHandler.CheckError(this, CanonSDK.EdsSetCapacity(CamRef, capacity));
});
}
Once you have taken a photo, the SDKObjectEvent
will fire with the inEvent
variable being ObjectEventID.DirItemRequestTransfer
. For convenience, the Camera
class has a DownloadReady
event that fires with all the info you need.
The DownloadReady
event has two parameters, one is the Camera
object from which the event was fired and the second being a DownloadInfo
object. The DownloadInfo
object provides some information about the file and can be passed to three methods:
The DownloadFile
method downloads the image into the provided directory. To get or set the file name, you can use the FileName
property of the DownloadInfo
object.
public void DownloadFile(DownloadInfo Info, string directory)
{
CheckState();
if (Info == null) throw new ArgumentNullException(nameof(Info));
if (directory == null || string.IsNullOrEmpty(directory.Trim())) directory = ".";
string currentFile = Path.Combine(directory, Info.FileName);
if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
DownloadToFile(Info, currentFile);
}
The DownloadFile
method without the directory parameter downloads the image into a System.IO.Stream
instead of saving it to the file system.
public Stream DownloadFile(DownloadInfo Info)
{
CheckState();
if (Info == null) throw new ArgumentNullException(nameof(Info));
return DownloadToStream(Info);
}
With the CancelDownload
method, you can cancel and discard the taken image:
public void CancelDownload(DownloadInfo Info)
{
CheckState();
if (Info == null) throw new ArgumentNullException(nameof(Info));
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsDownloadCancel(Info.Reference));
ErrorHandler.CheckError(this, CanonSDK.EdsRelease(Info.Reference));
});
}
The two DownloadFile
methods used these subroutines to do the actual work:
protected void DownloadToFile(DownloadInfo Info, string filepath)
{
using (var stream = new SDKStream
(filepath, FileCreateDisposition.CreateAlways, FileAccess.ReadWrite))
{
DownloadData(Info, stream.Reference);
}
}
and:
protected Stream DownloadToStream(DownloadInfo Info)
{
SDKStream stream = new SDKStream(Info.Size64);
DownloadData(Info, stream.Reference);
stream.Position = 0;
return stream;
}
The SDKStream
class is a wrapper around SDK streams so it can be used like a normal System.IO.Stream
.
And this is the real download:
protected void DownloadData(DownloadInfo Info, IntPtr stream)
{
MainThread.Invoke(() =>
{
try
{
ErrorHandler.CheckError(this, CanonSDK.EdsSetProgressCallback
(stream, SDKProgressCallbackEvent, ProgressOption.Periodically, Info.Reference));
if (CanonSDK.IsVerGE34) ErrorHandler.CheckError
(this, CanonSDK.EdsDownload(Info.Reference, Info.Size64, stream));
else ErrorHandler.CheckError
(this, CanonSDK.EdsDownload(Info.Reference, Info.Size, stream));
}
finally
{
ErrorHandler.CheckError(this, CanonSDK.EdsDownloadComplete(Info.Reference));
ErrorHandler.CheckError(this, CanonSDK.EdsRelease(Info.Reference));
}
});
}
The live view is one of the more difficult things to do, especially if it should be high-performance. First, we start the live view like this:
public void StartLiveView()
{
CheckState();
if (!IsLiveViewOn) SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.PC);
}
Once this is done, the SDKPropertyEvent
will fire with the inPropertyID
variable being PropertyID.Evf_OutputDevice
:
private ErrorCode Camera_SDKPropertyEvent
(PropertyEventID inEvent, PropertyID inPropertyID, int inParameter, IntPtr inContext)
{
ThreadPool.QueueUserWorkItem((state) =>
{
try
{
if (inPropertyID == PropertyID.Evf_OutputDevice ||
inPropertyID == PropertyID.Record)
{
lock (lvThreadLockObj)
{
EvfOutputDevice outDevice = GetEvf_OutputDevice();
Recording recordState = IsRecordAvailable ?
((Recording)GetInt32Setting(PropertyID.Record)) : Recording.Off;
if (outDevice == EvfOutputDevice.PC ||
(recordState == Recording.Ready &&
outDevice == EvfOutputDevice.Filming) ||
(useFilmingPcLv && recordState == Recording.On &&
(outDevice == EvfOutputDevice.Filming ||
outDevice == EvfOutputDevice.Camera)))
{
if (!KeepLVAlive)
{
KeepLVAlive = true;
LVThread = STAThread.CreateThread(DownloadEvf);
LVThread.Start();
}
}
else if (KeepLVAlive) { KeepLVAlive = false; }
}
}
}
catch (Exception ex)
{ if (!IsDisposed && !ErrorHandler.ReportError(this, ex)) throw; }
PropertyChanged?.Invoke(this, inEvent, inPropertyID, inParameter);
});
return ErrorCode.OK;
}
There are a few more checks in there to ensure that the live view also works while filming.
And the DownloadEvf
method being:
private void DownloadEvf()
{
if (IsLiveViewOn) return;
try
{
IntPtr evfImageRef = IntPtr.Zero;
ErrorCode err;
using (var stream = new SDKStream(0))
{
IsLiveViewOn = true;
while (KeepLVAlive)
{
lock (STAThread.ExecLock)
{
err = CanonSDK.EdsCreateEvfImageRef(stream.Reference, out evfImageRef);
if (err == ErrorCode.OK) err =
CanonSDK.EdsDownloadEvfImage(CamRef, evfImageRef);
}
if (err == ErrorCode.OBJECT_NOTREADY) { continue; }
else if (err != ErrorCode.OK) { ErrorHandler.CheckError(err); continue; }
CanonSDK.EdsRelease(evfImageRef);
stream.Position = 0;
LiveViewUpdated?.Invoke(this, stream);
}
}
}
catch (Exception ex) { if (ex is ThreadAbortException ||
!ErrorHandler.ReportError(this, ex)) throw; }
finally
{
IsLiveViewOn = false;
ThreadPool.QueueUserWorkItem((state) => LiveViewStopped?.Invoke(this));
}
}
To stop the live view, simply call the StopLiveView
method. This will trigger the SDKPropertyEvent
again where the KeepLVAlive
variable is set to false
and the live view loop will stop. With the LVOff
parameter, you can set if the live view should be shut down completely or should be shown on the camera. (Some older cameras might always shut down.)
public void StopLiveView(bool LVOff = true)
{
CheckState();
if (LVOff) SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.Off);
else SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.Camera);
}
Newer cameras have the possibility to record videos built in. To use this, you have to set your camera to video recording mode first (it will not work otherwise!). Usually, there is some button, knob or dial you have to switch. If that is done, you can call the starting method:
public void StartFilming(bool PCLiveview)
{
CheckState();
Recording state = (Recording)GetInt32Setting(PropertyID.Record);
if (state != Recording.On)
{
if (state != Recording.Ready) throw new InvalidOperationException
("The camera is not ready to film. The Record property has to be Recording.Ready");
useFilmingPcLv = PCLiveview;
SetSetting(PropertyID.SaveTo, (int)SaveTo.Camera);
SetSetting(PropertyID.Record, (int)Recording.On);
}
}
Note that the SaveTo
property is set to Camera
. This is a requirement of the SDK, presumably because the data transfer to the computer would be too slow.
Still, you can download the finished film after you stopped filming just by setting the saveFilm
parameter of the StopFilming
method to true
:
public void StopFilming(bool saveFilm, bool stopLiveView)
{
CheckState();
Recording state = (Recording)GetInt32Setting(PropertyID.Record);
if (state == Recording.On)
{
this.saveFilm = saveFilm;
SetSetting(PropertyID.Record, (int)Recording.Off);
useFilmingPcLv = false;
if (IsLiveViewOn && stopLiveView) StopLiveView(false);
}
}
Internally, it checks the SDKObjectEvent
that will fire with inEvent
being ObjectEventID.DirItemCreated
. We can safely assume that this created item is the film. Same as with downloading a photo, the DownloadReady
event will fire and you can download the film with one of the DownloadFile
methods.
To keep the user or from changing settings on the physical camera, or allowing to do so, you can lock or unlock the camera UI like this:
public void UILock(bool lockState)
{
if (lockState) SendStatusCommand(CameraStatusCommand.UILock);
else SendStatusCommand(CameraStatusCommand.UIUnLock);
}
A question often asked all over the internet is how to control the focus of the camera. It's actually quite easy if you know how. Most importantly, the camera has to be in live view and the lens has to be on AF. Then, you can simply call SendCommand
with DriveLensEvf
as the command type:
SendCommand(CameraCommand.DriveLensEvf, (int)DriveLens.Near2);
To control distance and direction, you have to set the DriveLens
enum
appropriately. Near is to focus closer, Far is to focus further away and 1 is a small, 2 is a medium and 3 is a big step.
There are several methods for handling the file system of the camera:
- Use
GetAllVolumes
to get all camera volumes (e.g., CF or SD cards) - Use
GetAllEntries
to get all volumes, files and folders on the camera - Use
GetAllImages
to get all images on the camera - Use
FormatVolume
to format a volume - Use
DownloadFiles
to download one or more files into a directory - Use
DeleteFiles
to delete one or more files on the camera
I won't go into detail here but feel free to look at the source code of these methods to find out how exactly they work.
Sometimes, it is useful to get a thumbnail from an image, regardless if RAW or jpg. To do that, you can call the GetFileThumb
method of the CanonAPI
class.
public Bitmap GetFileThumb(string filepath)
{
using (var stream = new SDKStream
(filepath, FileCreateDisposition.OpenExisting, FileAccess.Read))
{
return GetImage(stream.Reference, ImageSource.Thumbnail);
}
}
whereas the more generic method GetImage
is defined like this:
protected Bitmap GetImage(IntPtr imgStream, ImageSource imageSource)
{
IntPtr imgRef = IntPtr.Zero;
IntPtr streamPointer = IntPtr.Zero;
ImageInfo imageInfo;
try
{
ErrorHandler.CheckError(this,
CanonSDK.EdsCreateImageRef(imgStream, out imgRef));
ErrorHandler.CheckError(this,
CanonSDK.EdsGetImageInfo(imgRef, imageSource, out imageInfo));
Size outputSize = new Size();
outputSize.Width = imageInfo.EffectiveRect.Width;
outputSize.Height = imageInfo.EffectiveRect.Height;
int datalength = outputSize.Height * outputSize.Width * 3;
byte[] buffer = new byte[datalength];
using (var stream = new SDKStream(buffer))
{
ErrorHandler.CheckError(this, CanonSDK.EdsGetImage
(imgRef, imageSource, TargetImageType.RGB,
imageInfo.EffectiveRect, outputSize, stream.Reference));
unsafe
{
byte tmp;
fixed (byte* pix = buffer)
{
for (long i = 0; i < datalength; i += 3)
{
tmp = pix[i];
pix[i] = pix[i + 2];
pix[i + 2] = tmp;
}
}
}
ErrorHandler.CheckError(this,
CanonSDK.EdsGetPointer(stream.Reference, out streamPointer));
return new Bitmap(outputSize.Width, outputSize.Height,
datalength, PixelFormat.Format24bppRgb, streamPointer);
}
}
finally
{
if (imgStream != IntPtr.Zero) ErrorHandler.CheckError
(this, CanonSDK.EdsRelease(imgStream));
if (imgRef != IntPtr.Zero) ErrorHandler.CheckError
(this, CanonSDK.EdsRelease(imgRef));
}
}
A Note to the Methods
I did not add every single method that is used in the code here. Just download the source if something is not completely clear. And if you still have a question after that, simply ask me. :)
Note to Threading
The Canon SDK requires the use of threads in a single threaded apartment (=STA) which makes things a bit difficult. Lucky for you, I added the STAThread
class that handles the setup and issues that arise with this threading model. The CanonAPI
class has one thread that is used for initializing and terminating the SDK and to get all SDK events. Each Camera
instance has one thread that is used to execute all commands on. To ensure that nothing executes at the same time, all commands are surrounded by a lock (with the ExecLock
field as lock object). Should two commands execute at the same time, the SDK will hang completely in most cases. All methods provided by the CanonAPI
and the Camera
are safe to execute but if you want to call methods from the CanonSDK
class, you have to make sure to execute it on the right thread (by calling the Invoke
method of the STAThread
class). If you have any issues with the SDK hanging, please let me know.
Using the GUI
This project includes a Window Forms UI and a WPF UI. They both work the same way and are here to show you how to use the above code in an actual program.
Note: These GUIs are not meant for production use and are merely here as an example to get you started!
Plug in your camera and select in the list. Click on "Open Session" and start to use the camera.
Select the values in the dropdown menus to set them in the camera.
Use the "StartLV" button to start the live view and the buttons with the arrows on it can be used to control the focus (only works if the lens is in AF mode and live view is running).
Points of Interest
This code was tested with:
- EOS 5D Mark III
- EOS 7D
- EOS 40D
- EOS 60D
- EOS 700D
- EOS 600D
- EOS 550D
- EOS 500D
- EOS 450D
- EOS 100D/Rebel SL1
- EOS 1200D/Rebel T5
- EOS 1100D/Rebel T3
- and several others
If you tried it with a different model, please let me know so I can add it to this list.
And if you have found a bug, have an improvement or a new snippet, I'm happy to hear from you!
History
- November 2013 - Initial version
- November 2013 - Bugfix for viewing
LiveView
and taking photos at the same time - December 2013 - Added
GetStringProperty
and GetStructProperty
and added video recording - January 2014 - Added
SetStringProperty
and SetStructProperty
and minor changes in the UI - January 2014 - Added several new methods and made the code much more secure
- June 2014 - Changed a few methods to be more secure and revised the code and UI a bit
- October 2014 - Fixed a few bugs, revised the Winforms UI and added a WPF UI
- November 2014 - Fixed deadlock issue when the live view is running and camera commands are called
- April 2015 - Fixed a bug where the camera would hang before or after filming. Fixed a broken link
- May 2015 - Camera commands are now properly executed on an STA thread. Other smaller fixes
- August 2015 - UI: fixed
CameraAdded
+Shutdown
bugs; Added basic error handling. Lib: smaller fixes
- March 2016 (1.0.0) - Complete revision of the project. It now uses a similar architecture to the commercial library.
- March 2016 (1.0.1) - Fixed wrong check in
StopFilming
method - April 2016 (1.1.0) - Several bugfixes, improvements and support for Canon SDK 3.4
- April 2016 (1.1.1) - Fixed incorrect lock in
STAThread.Invoke
method (thanks Dave) - May 2016 (1.1.2) - Various smaller fixes (thanks elgarf) and added
LiveViewStopped
event