This article shares a technique on how to take photos from within an Oracle APEX application using the device’s camera. The solution is built around HTML5 features in combination with standard APEX functionality. Follow the below 8 steps to create a basic working example that lets you take photos and save them in the database. I’ve also created a live demo on apex.oracle.com.

1. You need a place to store your photos

The demo application uses an APEX collection to store the photos you take. A collection in APEX includes one BLOB column: BLOB001. We’ll use that column to store the binary representation of each photo. I created a report page (ID 120) that lists all saved photos. An empty collection PHOTOS gets created when you visit the report page for the first time in your session.

declare
  lco_photos_collection_name constant apex_collections.collection_name%type := 'PHOTOS';
begin
  if not apex_collection.collection_exists(lco_photos_collection_name) then
    apex_collection.create_collection(
      p_collection_name => lco_photos_collection_name
    );
  end if;
end;

The report’s source query is based on the collection.

select photos.seq_id,
       photos.seq_id as view_photo
from apex_collections photos
where photos.collection_name = 'PHOTOS'

The report page also includes the Launch Camera button, which opens a modal dialog (ID 121) and automatically launches the device’s camera.

2. Create the Camera modal dialog page

Simply complete the Create a Blank Page wizard and make sure to select Modal Dialog for the Page Mode attribute. I also specified a custom width and height for the dialog in order to fully display the camera’s view. In my example, the display of the camera has a width of 640px and a height of 480px. That resulted in a width of 700px and a height of 640px for the modal dialog. The page attributes let you specify the width and height of the modal dialog.

3. The video and canvas elements

Create a Static Content region on the modal dialog page and put the following HTML code in the Text attribute.

<div style="text-align:center;">
  <video id="video" width="640" height="480" autoplay></video>
  </div>

The video element will show your device’s camera filming, while the canvas element is used to snap a picture of the video. The canvas element is hidden by default.

4. Initialize the video element on page load

First define a couple of global JavaScript variables.

var video = document.getElementById('video');
var videoStream;
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

Then execute the following JavaScript code on page load.

var errBack = function(error) {
                console.log('video capture error: ', error.code);
              };

if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  navigator.mediaDevices.getUserMedia({ video: true }).then(function(stream) {
    videoStream = stream;
    video.srcObject = stream;
    video.play();
  });
} else if (navigator.getUserMedia) { // Standard
  navigator.getUserMedia({ video: true }, function(stream) {
    videoStream = stream;
    video.src = stream;
    video.play();
  }, errBack);
} else if (navigator.webkitGetUserMedia) { // WebKit-prefixed
  navigator.webkitGetUserMedia({ video: true }, function(stream) {
    videoStream = stream;
    video.srcObject = stream;
    video.play();
  }, errBack);
} else if (navigator.mozGetUserMedia) { // Mozilla-prefixed
  navigator.mozGetUserMedia({ video: true }, function(stream) {
    videoStream = stream;
    video.srcObject = stream;
    video.play();
  }, errBack);
}

Source: https://davidwalsh.name/browser-camera

The getUserMedia API is not supported in Internet Explorer or Safari. That’s a bummer, right?

If you now run the application and open the modal dialog, you should see the video start playing. Your browser will probably ask for confirmation to allow access to your device’s camera.

5. Snap a photo

Create a Snap Photo button which its action is Defined by Dynamic Action. When this button gets clicked, we’ll execute a dynamic action that performs the following tasks:

  • Line 1: Take the video image and draw it on the canvas element.
  • Line 2-4: Hide and stop the video element and show the canvas element instead. This will give the effect of freezing the camera so you can clearly see how the photo will look like.
  • Line 6-18: Perform an AJAX request and pass the Base64 encoding of the photo as a parameter.
  • Line 14: Submit the page when everything goes as planned. The page submit will trigger a Close Dialog process to execute, resulting in a refresh of the report on page 120.
context.drawImage(video, 0, 0, 640, 480);
video.style.display = 'none';
canvas.style.display = 'inline-block';
videoStream.getTracks()[0].stop();

apex.server.process(
  'SAVE_PHOTO',
  {
    p_clob_01: canvas.toDataURL().match(/,(.*)$/)[1]
  },
  {
    success: function(data) {
               if (data.result == 'success') {
                 apex.submit('SNAP_PHOTO');
               }
             }
  }
);
6. The SAVE_PHOTO AJAX callback process

The above AJAX request executes the SAVE_PHOTO callback process. This PL/SQL process takes the Base64 value in a CLOB variable and transforms it into a BLOB variable using a custom package. That BLOB variable is then inserted in the APEX collection PHOTOS.

Here’s the code for the SAVE_PHOTO callback process:

declare
  l_photo_clob clob;
  l_photo_blob blob;
begin
  l_photo_clob := apex_application.g_clob_01;

  l_photo_blob := apex_web_service.clobbase642blob(
                    p_clob => l_photo_clob
                  );

  apex_collection.add_member(
    p_collection_name => 'PHOTOS',
    p_blob001 => l_photo_blob
  );

  apex_json.open_object;
  apex_json.write(
    p_name => 'result',
    p_value => 'success'
  );
  apex_json.close_object;
exception
  when others then
    apex_json.open_object;
    apex_json.write(
      p_name => 'result',
      p_value => 'fail'
    );
    apex_json.close_object;
end;
7. Display a captured photo

Clicking the eye icon in the report on page 120 will open the modal dialog again and show the selected photo. When opening an existing photo, the SEQ_ID value is being passed to the hidden page item P121_PHOTO_SEQ_ID. We need this hidden item to fetch the selected photo on page load, but it is also used to apply conditions on certain components. I’ve put the below JavaScript code in a conditional Page Load dynamic action. It will only execute when the P121_PHOTO_SEQ_ID item is not NULL.

video.style.display = 'none';
canvas.style.display = 'inline-block';

apex.server.process(
  'GET_PHOTO',
  {},
  {
    success: function(data) {
               var image = new Image();
               image.onload = function() {
                 context.drawImage(image, 0, 0);
               };
               image.src = 'data:image/png;base64,' + data.photoBase64;
             }
  }
);

Here’s the code of the GET_PHOTO callback process:

declare
  l_photo_clob clob;
  l_photo_blob blob;
begin
  select blob001
  into l_photo_blob
  from apex_collections
  where collection_name = 'PHOTOS'
  and seq_id = :P121_PHOTO_SEQ_ID;

  l_photo_clob := apex_web_service.blob2clobbase64(
                    p_blob => l_photo_blob
                  );

  apex_json.open_object;
  apex_json.write('photoBase64', l_photo_clob);
  apex_json.close_object;
end;
8. Download the demo application

You can download the demo application here. It includes a little extra code to show a spin.js spinning icon while the AJAX requests are executing.