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.
Hey Nick,
great Blog post!:)
One thing that can be improved is to use a chunked file upload (30k chunks) to the application process instead of the complete CLOB. I did similar things with my Dropzone plugin and the CLOB way doesn´t work with the old OHS or web tier, because the Apache bundled with it can only handle parameters that are not bigger than 30k…ORDS is fine and does work out well!
You can see it for PL/SQL here:
https://github.com/Dani3lSun/apex-plugin-dropzone#save-to-db-using-plsql
and for the client side Javascript code here:
https://github.com/Dani3lSun/apex-plugin-dropzone/blob/master/server/js/apexdropzone.js
Line 28-45 and line 189-207
Hope this helps!;)
Best wishes
Daniel
LikeLiked by 1 person
Hi Daniel,
Thank you for that valuable information. I actually ran into the OHS limitation while working on a customer’s project. AJAX requests failed and the OHS logs contained the following kind of error messages:
MODPLSQL-00365: mod_plsql: /pls/apex/wwv_flow.show HTTP-400 Parameter value size 452944 exceeds limit of 32512 bytes
I didn’t know about the chunked file upload solution. Thanks for the share.
Kudos on the Dropzone plugin by the way. It’s pretty amazing.
Nick
LikeLike
Hi Nick , It’s a great great work. can u pls supply me with tables that are used at this example, because when i run it it gives me “failed to parse SQL query:
ORA-00942: table or view does not exist”
Once again thank you
LikeLike
Hello Hany, I’m not using any tables in the demo application. I only use the APEX_COLLECTIONS view. Can you please share the code that gives you the “table or view does not exist” error message?
Thanks,
Nick
LikeLike
Hi Nick very nice sharing thanks
FYI now on APEX 5 we can use API
APEX_WEB_SERVICE.BLOB2CLOBBASE64 (
p_blob IN BLOB)
or
APEX_WEB_SERVICE.CLOBBASE642BLOB (
p_clob IN CLOB)
so we don’t need to install utl_base64 package
Regards
LikeLiked by 1 person
You are right. I don’t know why I ended up using the custom UTL_BASE64 package. I’ve updated the article. Thanks for the heads up.
LikeLike
Hi,
thanks for this awesome post. It is not necessary my use case but it show perfectly how all the things related to APEX come together: PLSQL, APEX features, Javascript, ….
By the way: another blog post of You about the region display selectors helped me today finding a workaround for a bad bug within APEX. :-)
greetings,
Peter
LikeLiked by 1 person
Hi Nick,
Great idea! I do have a business case for this.. so I’m really looking forward to make it work but unfortunately I’m running into an issue:
Your application : https://apex.oracle.com/pls/apex/f?p=58006:120 works perfectly, I downloaded the code from the link above and imported it to apex.oracle.com and running “my version” (https://apex.oracle.com/pls/apex/f?p=90944:120) is returning the following error when snapping the picture:
apex.oracle.com says:
Error: parsererror – SyntaxError: Unexpected end of JSON input
I wonder if something changed after the creation of the export file…
Same Chrome browser… Obviously something is different but I don’t know what..
Could you please share your thoughts?
Thanks!
Gaspar
LikeLike
Hey Gaspar,
That’s something I can’t explain. Maybe something went wrong while importing the application. Make sure the File Character Set is set to “Unicode UTF-8”. I’ve tried that myself and the demo application works just fine when using that character set.
Can you give me developer access to your apex.oracle.com workspace? You can send the credentials to apexplained@gmail.com.
Nick
LikeLike
Hi Nick.
I have sent you an email with all details as requested.
Thanks a lot for your help.
Gaspar
LikeLiked by 1 person
Hi Gaspar,
Did your problem got resolved?
I am also getting same error –> ‘Error: parsererror – SyntaxError: Unexpected end of JSON input’
LikeLike
Hey Mah,
Gaspar’s problem was that he was missing the UTL_BASE64 package. I fixed this problem and uploaded a new version of the application export, which doesn’t rely on the UTL_BASE64 package. Is it possible that you’re using the previous version of the application export?
Nick
LikeLike
Hi Nick,
I run Apex in a non secure site (no https).
I installed your demo, it worked on Firefox but not in Chrome.
Please see the image here: https://drive.google.com/open?id=0B3qSdIIpIKPFcng4TEVENlVPOVk
Is there an issue with opening the camera in non-secure sites on Chrome ?
If yes, do you know if there is a workaround for this ?
LikeLike
Hello Mario,
Chrome decided that getUserMedia should only work on HTTPS protocol. You’ll need a SSL certificate for this API to work. So the only real solution is to switch to HTTPS.
It is possible to pass some flags to Chrome in order to make the camera device work on HTTP. This is for development purposes only. More info on this can be found here:
http://stackoverflow.com/questions/33749854/getusermedia-is-not-working-in-chrome-version-48-0-2560-0-while-working-in-46-0
http://stackoverflow.com/questions/34165614/navigator-mediadevices-getusermedia-is-not-working-and-neither-does-webkitgetuse
LikeLike
Hi Nick, great post!
I can’t store image in database. In the SAVE_PHOTO callback process, I insert into table IMG but not work. Sometimes I store but I can’t fetch BLOB, and others I get Error: Bad Request
How I should be insert on table in database?
Can you help me?
LikeLike
Hey Paul,
In the SAVE_PHOTO process you accept a Base64 representation of the image in a CLOB variable, translate it to a BLOB value, which you then insert in a BLOB table column. Is this the part where you run into an error?
It’s difficult for me to really help you. Can you reproduce your problem(s) on apex.oracle.com and grant me developer access?
Best regards,
Nick
LikeLiked by 1 person
Hi Nick!
I could solve it. The problem was apex_web_service.clobbase642blob . To solve it changed apex_web_service for package UTL_BASE64 (https://erikwramner.wordpress.com/2010/02/23/coding-and-decoding-base64-in-plsql/) .
Thank you!
LikeLiked by 2 people
Hi Nick,
I am looking at your HTML5 camera integration in Oracle APEX demo via an iPad and the Launch Camera does launch the modal window, but the video stream is never displayed ???? I am using Ipad + Google Chrome.
Are you aware if this combination works?
Thank you.
Daniel
LikeLiked by 1 person
Hi Daniel,
The demo should work in Google Chrome, but I’m not sure when it comes to combining it with an iPad.
Is it possible to open the browser console while launching the camera modal window on your iPad? Do you get any error or warning messages in the console?
Nick
LikeLiked by 1 person
Hi Nick,
Unfortunately there is no console on Google Chrome when running on iPad.
Nick do you have any other suggestions?
Thank you
Daniel
LikeLiked by 1 person
– Does the demo page work in FireFox?
– There’s probably something wrong on the client-side. So it might be useful to do some remote debugging. Weinre seems to be the best tool in your situation: https://stackoverflow.com/questions/11262236/ios-remote-debugging/22047495#22047495
Can you try out Weinre and let me know the results?
Thanks,
Nick
LikeLiked by 1 person
Hi Nick,
I do not have a mac so I have made use of VirtualBox to setup a virtual Mac OS X and I have made use of Weinre as suggested, unfortunately I have hit a brick wall, as Weinre does not support https which as you know is required when using google chrome when calling internal device such as the camera (as far as I understand).
So I am sorry to report that I have no input (console errors) for you. Do you have any suggestions?
Regards
Daniel
LikeLiked by 1 person
Hi Nick,
Sorry forgot to mention that I installed Firefox on the iPad, and I get the same problem, as when using Chrome.
Regards
Daniel
LikeLiked by 1 person
Daniel,
I’m afraid you won’t get the camera to work on iOS.
“Apple policy forces other browser to use their version of webkit which does not support webRTC, so you will not have webRTC support in a web app on iOS anytime soon.”
Source: http://stackoverflow.com/questions/23374806/webapp-using-webrtc-for-cross-platform-videochat-in-ios-browser-and-android-chro/23391401#23391401
There’s not much we can do about this, other than wait for Apple to support webRTC.
Nick
LikeLiked by 1 person
Hi Nick,
Thank you for the information.
Regards
Daniel
LikeLiked by 1 person
Hi Nick ,
I’m new in APEX i downloaded the attached file and i imported it in my env APEX 5.1. The webcam is working fine but when i pressed snap photo it takes about 15 seconds then give
1 error has occurred
Error: Bad Request
i don’t know what’s the error exactly because there is no error info and also debug is clear no errors displayed in debug viewer
i appreciate if you can support
LikeLike
Hi Sameh,
Is your environment using ORDS, OHS or EPG? The technique described in this article only works in combination with ORDS. Please read the first comment on this article for more information.
There’s a workaround for this problem, which I have described in the following blog post:
https://apexplained.wordpress.com/2016/09/12/chunked-multi-file-upload-with-ajax/
So you’ll have to change the way the photo gets send to the server. The solution is to chunk the data. The above article includes all further details.
Hope this helps,
Nick
LikeLike
Thank you Nick for you reply
but actually i tried to merge the code of chunk and the integrated camera but i failed
LikeLike
Hello Nick.
Great application that you put together here.
But Im having some problems, may you help me?
When I import your application on the apex.oracle, it works, but when i try to import on my server workspace, i got the “Error: Bad Request” when I try to snap the photo.
Any ideas why?
Thanks in advance.
LikeLike
Hi Marvin,
Is your environment using ORDS, OHS or EPG? The technique described in this article only works in combination with ORDS. Please read the first comment on this article for more information.
There’s a workaround for this problem, which I have described in the following blog post:
https://apexplained.wordpress.com/2016/09/12/chunked-multi-file-upload-with-ajax/
So you’ll have to change the way the photo gets send to the server. The solution is to chunk the data. The above article includes all further details.
Hope this helps,
Nick
LikeLike
Thanks very much for the response.
I spoke with our db manager and I installed apache on our database host, and now i can take the photos and store in our db.
But I found one other problem.
Im developing an app for mobile users that will use firefox browser (since we still do not have https on our server and chrome only work with https), and when any photo is taken, the photo is stored rotated180º. This problem only occurs when I run the application on mobile.
Any ideas why?
Thanks in advance.
LikeLike
Hi Marvin,
Sorry for the late reply.
It seems that the image rotation is device dependent. I haven’t tried it myself, but I think you can rotate the image client side. You’ll have to check the EXIF data on the image and rotate when necessary. You can use libraries to help you with this. Take a look at the following pages for more info:
https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images
https://stackoverflow.com/questions/15735335/html5-photo-capture-with-smartphone-camera-image-is-rotated-cw
http://chariotsolutions.com/blog/post/take-and-manipulate-photo-with-web-page/
Hope that helps,
Nick
LikeLike
Hi Nick,
Great application! Thank you!
I have a problem and can’t find any solution. When I try to snap photo, I face with this error:
HTTP ERROR: 500
Problem accessing /ords/wwv_flow.ajax. Reason:
java.lang.IllegalStateException: From too large: 790969 > 200000
Also tried with chunked multi-file upload example but problem not solved.
Any ideas?
Thanks in advance for your response.
LikeLike
Hello,
On what kind of application server is your ORDS deployed? The server doesn’t accept your post because the data you’re sending is too large. You’ll have to increase the maximum post size parameter to prevent that kind of error.
More info:
https://stackoverflow.com/questions/2943477/is-there-a-max-size-for-post-parameter-content
https://www.xwiki.org/xwiki/bin/view/FAQ/FormTooLargeError
Hope that helps,
Nick
LikeLike
Hi Nick Buytaert,
I install your Demo Application but not lunch camera.
LikeLike
Dear Sir i have install your app it show camera view when i click on snap Photo then raise an error Bad Request please help me i and sir i want to save into client side directory by open dialog please sir help me
LikeLike
Hi Nick,
Great job but my phone always opens front-face camera instead of back-face camera, can you plz tell me the way to set the default camera direction?
LikeLike