11 Replies Latest reply: Jul 11, 2011 12:13 AM by itobinh RSS

    Capture Web Cam image in APEX and Upload into the Database

    trent
      Overview

      By using a flash object, you should be able to interface with a usb web cam connected to the client machine. Their are a couple of open source ones that I know about, but the one I chose to go with is by Taboca Labs and is called CamCanvas. This is released under the MIT license, and it is at version 0.2, so not very mature - but in saying that it seems to do the trick. The next part is to upload a snapshot into the database - in this particular implementation, it is achieved by taking a snapshot, and putting that data into the canvas object. This is a new HTML5 element, so I am not certain what the IE support would be like. Once you have the image into the canvas, you can then use the provided function convertToDataURL() to convert the image into a Base64 encoded string, which you can then use to convert into to a BLOB. There is however one problem with the Base64 string - APEX has a limitation of 32k for and item value, so can't be submitted by normal means, and a workaround (AJAX) has to be implemented.

      Part 1. Capturing the Image from the Flash Object into the Canvas element

      Set up the Page

      Required Files

      Download the tarball of the webcam library from: https://github.com/taboca/CamCanvas-API-/tarball/master

      Upload the necessary components to your application. (The flash swf file can be got from one of the samples in the Samples folder. In the root of the tarball, there is actually a swf file, but this seems to be a different file than of what is in the samples - so I just stick with the one from the samples)

      Page Body

      Create a HTML region, and add the following:
          <div class="container">
             <object  id="iembedflash" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" 
      codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="320" height="240">
                  <param name="movie" value="#APP_IMAGES#camcanvas.swf" />
                  <param name="quality" value="high" />
                <param name="allowScriptAccess" value="always" />
                  <embed  allowScriptAccess="always"  id="embedflash" src="#APP_IMAGES#camcanvas.swf" quality="high" width="320" height="240" 
      type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" mayscript="true"  />
          </object>
      
          </div>
      <p><a href="javascript:captureToCanvas()">Capture</a></p>
      <canvas style="border:1px solid yellow"  id="canvas" width="320" height="240"></canvas>
      That will create the webcam container, and an empty canvas element for the captured image to go into.

      Also, have a hidden unprotected page item to store the Base64 code into - I called mine P2_IMAGE_BASE64

      HTML Header and Body Attribute

      Add the Page HTML Body Attribute as:
      onload="init(320,240)"
      JavaScript

      Add the following in the Function and Global Variable Declarations for the page (mostly taken out of the samples provided)
      //Camera relations functions
      
      var gCtx = null;
      var gCanvas = null;
      
      var imageData = null;
      var ii=0;
      var jj=0;
      var c=0;
      
      function init(ww,hh){
           gCanvas = document.getElementById("canvas");
           var w = ww;
           var h = hh;
           gCanvas.style.width = w + "px";
           gCanvas.style.height = h + "px";
           gCanvas.width = w;
           gCanvas.height = h;
           gCtx = gCanvas.getContext("2d");
           gCtx.clearRect(0, 0, w, h);
           imageData = gCtx.getImageData( 0,0,320,240);
      }
      
      function passLine(stringPixels) { 
           //a = (intVal >> 24) & 0xff;
      
           var coll = stringPixels.split("-");
      
           for(var i=0;i<320;i++) { 
                var intVal = parseInt(coll);
                r = (intVal >> 16) & 0xff;
                g = (intVal >> 8) & 0xff;
                b = (intVal ) & 0xff;
                imageData.data[c+0]=r;
                imageData.data[c+1]=g;
                imageData.data[c+2]=b;
                imageData.data[c+3]=255;
                c+=4;
           }

           if(c>=320*240*4) {
                c=0;
                gCtx.putImageData(imageData, 0,0);
           }
      }


      function captureToCanvas() {
           flash = document.getElementById("embedflash");
           flash.ccCapture();
           var canvEle = document.getElementById('canvas');
           $s('P2_IMAGE_BASE64', canvEle.toDataURL());//Assumes hidden item name is P2_IMAGE_BASE64
           clob_Submit();//this is a part of part (AJAX submit value to a collection) two
      }
      In the footer region of the page (which is just a loading image to show whilst the data is being submitted to the collection [hidden by default]) :
      <img src="#IMAGE_PREFIX#processing3.gif" id="AjaxLoading"
      style="display:none;position:absolute;left:45%;top:45%;padding:10px;border:2px solid black;background:#FFF;" />
      If you give it a quick test, you should be able to see the webcam feed and capture it into the canvas element by clicking the capture link, in between the two elements - it might through a JS error since the clob_Submit() function does not exist yet.
      
      *Part 2. Upload the image into the Database*
      
      As mentioned in the overview, the main limitation is that APEX can't submit values larger than 32k, which I hope the APEX development team will be fixing this limitation in a future release, the workaround isn't really good from a maintainability perspective. 
      
      In the sample applications, there is one that demonstrates saving values to the database that are over 32k, which uses an AJAX technique: see http://www.oracle.com/technetwork/developer-tools/apex/application-express/packaged-apps-090453.html#LARGE. 
      
      *Required Files*
      
      From the sample application, there is a script you need to upload, and reference in your page. So you can either install the sample application I linked to, or grab the script from the demonstration I have provided - its called apex_save_large.js. 
      
      *Create a New Page*
      
      Create a page to Post the large value to (I created mine as 1000), and create the following process, with the condition that Request = SAVE. (All this is in the sample application for saving large values).
      declare

           l_code clob := empty_clob;
      begin

           dbms_lob.createtemporary( l_code, false, dbms_lob.SESSION );
           for i in 1..wwv_flow.g_f01.count loop
                dbms_lob.writeappend(l_code,length(wwv_flow.g_f01(i)),wwv_flow.g_f01(i));
           end loop;
           
           apex_collection.create_or_truncate_collection(p_collection_name => wc_pkg_globals.g_base64_collection);
           apex_collection.add_member(p_collection_name => wc_pkg_globals.g_base64_collection,p_clob001 => l_code);
           
           htmldb_application.g_unrecoverable_error := TRUE;
      end;
      I also created a package for storing the collection name, which is referred to in the process, for the collection name:
      create or replace
      package
      wc_pkg_globals
      as

      g_base64_collection constant varchar2(40) := 'BASE64_IMAGE';

      end wc_pkg_globals;
      That is all that needs to be done for page 1000. You don't use this for anything else, *so go back to edit the camera page*.
      
      *Modify the Function and Global Variable Declarations* (to be able to submit large values.) 
      
      The below again assumes the item that you want to submit has an item name of 'P2_IMAGE_BASE64', the condition of the process on the POST page is request = SAVE, and the post page is page 1000. This has been taken srtaight from the sample application for saving large values.
      //32K Limit workaround functions

      function clob_Submit(){
                $x_Show('AjaxLoading')
                $a_PostClob('P2_IMAGE_BASE64','SAVE','1000',clob_SubmitReturn);
      }
      function clob_SubmitReturn(){
                if(p.readyState == 4){
                               $x_Hide('AjaxLoading');
                               $x('P2_IMAGE_BASE64').value = '';
                }else{return false;}
      }

      function doSubmit(r){
      $x('P2_IMAGE_BASE64').value = ''
           flowSelectAll();
           document.wwv_flow.p_request.value = r;
           document.wwv_flow.submit();
      }
      Also, reference the script that the above code makes use of, in the page header
      <script type="text/javascript" src="#WORKSPACE_IMAGES#apex_save_large.js"></script>
      Assuming the script is located in workspace images, and not associated to a specific app. Other wise reference #APP_IMAGES#
      
      *Set up the table to store the images*
      CREATE TABLE "WC_SNAPSHOT"
      (
      "WC_SNAPSHOT_ID" NUMBER NOT NULL ENABLE,
      "BINARY" BLOB,
      CONSTRAINT "WC_SNAPSHOT_PK" PRIMARY KEY ("WC_SNAPSHOT_ID")
      );
      /

      create sequence seq_wc_snapshot start with 1 increment by 1;
      /

      CREATE OR REPLACE TRIGGER "BI_WC_SNAPSHOT" BEFORE
      INSERT ON WC_SNAPSHOT FOR EACH ROW BEGIN
      SELECT seq_wc_snapshot.nextval INTO :NEW.wc_snapshot_id FROM dual;
      END;
      /
      Then finally, create a page process to save the image:
      declare
      v_image_input CLOB;
      v_image_output BLOB;
      v_buffer NUMBER := 64;
      v_start_index NUMBER := 1;
      v_raw_temp raw(64);

      begin

      --discard the bit of the string we dont need
      select substr(clob001, instr(clob001, ',')+1, length(clob001)) into v_image_input
      from apex_collections
      where collection_name = wc_pkg_globals.g_base64_collection;

      dbms_lob.createtemporary(v_image_output, true);

      for i in 1..ceil(dbms_lob.getlength(v_image_input)/v_buffer) loop
      v_raw_temp := utl_encode.base64_decode(utl_raw.cast_to_raw(dbms_lob.substr(v_image_input, v_buffer, v_start_index)));
      dbms_lob.writeappend(v_image_output, utl_raw.length(v_raw_temp),v_raw_temp);
      v_start_index := v_start_index + v_buffer;
      end loop;

      insert into WC_SNAPSHOT (binary) values (v_image_output); commit;

      end;
      Create a save button - add some sort of validation to make sure the hidden item has a value (i.e. image has been captured). Make the above conditional for request = button name so it only runs when you click Save (you probably want to disable this button until the data has been completely submitted to the collection - I haven't done this in the demonstration). 
      
      Voila, you should have now be able to capture the image from a webcam. Take a look at the samples from the CamCanvas API for extra effects if you wanted to do something special.
      
      And of course, all the above assumed you want a resolution of 320 x 240 for the image. 
      
      Disclaimer: At time of writing, this worked with a logitech something or rather webcam, and is completely untested on IE.
      
      Check out a demo: http://apex.oracle.com/pls/apex/f?p=trents_demos:webcam_i (my image is a bit blocky, but i think its just my webcam. I've seen others that are much more crisp using this) Also, just be sure to wait for the progress bar to dissappear before clicking Save.
      
      Feedback welcomed.