Wednesday, 7 May 2014

Back'n'Front App - dual camera "fun"

Two weeks ago there was an App mentioned in a local newspaper - which, apparently, had done pretty well and which now is available also on Android. The app was described as taking 2 photos - one with the front camera and one with the back camera of your phone - and then enabling you to share the result with the rest of the world. How hard could it be to build one yourself?

Even if I can't remember haven taken a selfie myself - I might have been drunk at a point in time more than 30 years ago, where someone else would have captured me on silver chloride "sensitive paper" - but that I guess would not qualify as a selfie. But living in a country with a Prime minister that became famous for doing a self with two other "Head of States" - I thought I would also contribute by making a small dual camera App: Back'N'Front.

I intended the whole adventure to take less than an hour - how hard could it be. I was wrong - partly because I didn't do my homework and partly because FireMonkey is still new to me - having been a moving target ever since its initial release. But I will share my "conceptual" adventure anyway.

My first mistake was to assume I knew how the mentioned App worked, without even looking it up - the app would of course take both a photo of what the user would see and the user her/himself at the same time - with one click and then share it. Easy.

So I fired up Delphi XE6, threw two TCameraComponents, 3 TImages and 3 TSpeedButtons on a blank FireMonkey Mobile Application - set one camera's kind to BackCamera and one to FrontCamera, and activated both.

Well it didn't work, only one of cameras could be active at a time - that didn't make sense.

Seems that the designers of the Android SDK, did keep the "auto"mobile analogy - you can't both do forward and reverse at the same time. How stupid is that? Apparently, some of the hardware manufactures will provide their own API to enable that at some point - but that would not be generic.

I then thought I could fix that by switching between the two cameras, when the buffer was ready or by a timer - once every 1/15 sec. could be bearable. But I hadn't taken the typical lightning differences in account - causing the camera the need to re-focus.

So one photo at a time it is - apparently, this is also how other dual-selfie apps do it.

This is the design of the UI and notice the structure of the components:

First I wanted the App to behave well when losing focus, and regaining it, so I added an AppEventHandler:
function TForm1.AppEvent(AAppEvent: TApplicationEvent;  AContext: TObject): Boolean;begin  case AAppEvent of    TApplicationEvent.WillBecomeInactive,    TApplicationEvent.EnteredBackground,    TApplicationEvent.WillTerminate:    begin      CameraBack.Active := False;      CameraFront.Active := False;    end;    TApplicationEvent.BecameActive:    begin      CameraBack.Active := True;    end;  end;end;
And assigned it on the FormCreate event, together with creating a TBitmap to handle the merged photos and activating the back camera.
procedure TForm1.FormCreate(Sender: TObject);var  AppEventSvc: IFMXApplicationEventService;begin  if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(AppEventSvc)) then    AppEventSvc.SetApplicationEventHandler(AppEvent);  bmp := TBitmap.Create;  bmp.SetSize(trunc(imgPhoto.Width), trunc(imgPhoto.Height));  CameraBack.Active := True;end;
Next up was the re-sizing of the TLayouts - just using the TImages without TLayouts as placeholders did not provide much fun - I think I need to overcome my aversion of "layouts" I have from the Java days - this is different - and we have Anchors and Align :-D - at least if your FMX is one of the later versions.
procedure TForm1.FormResize(Sender: TObject);begin  Layout1.Height := (Form1.Height-Label1.Height)/2;  Layout3.Width := Form1.Width/2;end;
Then I needed the feed from the buffer of the active camera to be shown in the appropriate TImage control - whenever the buffer was ready. This is done for both cameras.
procedure TForm1.CameraBackSampleBufferReady(Sender: TObject;  const ATime: Int64);begin  CameraBack.SampleBufferToBitmap(imgBack.Bitmap, True);end;
I switch between the cameras when "clicking" the respective image, and set the non-active image opacity value to 50%, just to indicate what is active and what is not.
procedure TForm1.SwitchCamera(Sender: TObject);begin  if Sender=imgBack then  begin    CameraFront.Active := False;    CameraBack.Active := True;    imgBack.Opacity := 1;    imgFront.Opacity := 0.5;  end  else  begin    CameraBack.Active := False;    CameraFront.Active := True;    imgFront.Opacity := 1;    imgBack.Opacity := 0.5;  end;end;
The two camera button controls takes the appropriate image and draws it on the TBitmap created on the form creation, positioned either left or right. And the bitmap is shown.
procedure TForm1.TakePhoto(Sender: TObject);begin  if Sender=btnTakeBack then  begin    if CameraBack.Active then      CameraBack.Active := False;    bmp.Canvas.BeginScene();    bmp.Canvas.DrawBitmap(imgBack.Bitmap, RectF(0,0,imgBack.Bitmap.Width, imgBack.Bitmap.Height), RectF(0, 0, bmp.Width/2, bmp.Height), 1, True);    bmp.Canvas.EndScene;    imgBack.Opacity := 0.5;  end  else  begin    if CameraFront.Active then      CameraFront.Active := False;    bmp.Canvas.BeginScene();    bmp.Canvas.DrawBitmap(imgFront.Bitmap, RectF(0,0,imgFront.Bitmap.Width, imgFront.Bitmap.Height), RectF(bmp.Width/2, 0, bmp.Width, bmp.Height), 1, True);    bmp.Canvas.EndScene;    imgFront.Opacity := 0.5;  end;  imgPhoto.Bitmap.Assign(bmp);end;
Finally, in the TActionList a new ShowShareSheetAction action is added, assigned to the share buttons action event and on the actions OnBeforeExecute event we invoke the action with our merged "image".
procedure TForm1.ShowShareSheetAction1BeforeExecute(Sender: TObject);begin  ShowShareSheetAction1.Bitmap.Assign(imgPhoto.Bitmap);end;
Beware of the z-order of the controls - you want to ensure that the buttons are at the top. Also note that a TImage does have a bitmap placeholder which has a canvas which has a bitmap and so forth - it was initially very late at night when I started with the "merge" bit - and I lost track :-) Afterwards it seems so simple.


The source code: BackFrontApp