Commit b7d797ad authored by Oleg Dzhimiev's avatar Oleg Dzhimiev
Browse files

revisited jquery-jp4 - optimized exif reading (don't need findIPTCinJPEG), simplified image loading

parent 979e470b
Loading
Loading
Loading
Loading
+4 −4
Original line number Original line Diff line number Diff line
@@ -129,13 +129,13 @@ var Elphel = {
    * @lowres - valid values: 1 (not scaled), 2, 4, 8 (lowest resolution) 
    * @lowres - valid values: 1 (not scaled), 2, 4, 8 (lowest resolution) 
    *    
    *    
    */
    */
    reorderBlocksJP4_lowres: function(pixels,width,height,format="JP4",mosaic=[["Gr","R"],["B" ,"Gb"]],lowres){
    //reorderBlocksJP4_lowres: function(pixels,width,height,format="JP4",mosaic=[["Gr","R"],["B" ,"Gb"]],lowres){
      
    reorderBlocksJP4_lowres: async function(pixels,width,height,format="JP4",mosaic=[["Gr","R"],["B" ,"Gb"]],lowres){
      // the output image is 1/4 because demosaicing = 4 single color channel pixels are put into 1 rgb pixel 
      // the output image is 1/4 because demosaicing = 4 single color channel pixels are put into 1 rgb pixel 
      var oPixels = new Uint8Array(pixels.length/4);
      var oPixels = new Uint8Array(pixels.length/4);
      
      
      // check
      // check
      if ((lowres!=1)&&(lowres!=2)&&(lowres!=4)&&(lowres!=8)){
      if (![1,2,4,8].includes(lowres)){
        lowres = 4;
        lowres = 4;
      }
      }
      
      
+4 −4
Original line number Original line Diff line number Diff line
@@ -7,12 +7,12 @@
  <body>
  <body>
    <table>
    <table>
    <tr>
    <tr>
      <td><div id='test1'></div></td>
      <td valign='top'><div id='test1'></div></td>
      <td><div id='test2'></div></td>
      <td valign='top'><div id='test2'></div></td>
    </tr>
    </tr>
    <tr>
    <tr>
      <td><div id='test3'></div></td>
      <td valign='top'><div id='test3'></div></td>
      <td><div id='test4'></div></td>
      <td valign='top'><div id='test4'></div></td>
    </tr>
    </tr>
    </table>
    </table>
    <script src="js/elphel.js"></script>
    <script src="js/elphel.js"></script>
+5 −5
Original line number Original line Diff line number Diff line
@@ -27,9 +27,9 @@


$(function(){
$(function(){


  var t1 = $("#test1").jp4({ip:location.host, port:2323,width:600,fast:true,lowres:4});
  let t1 = $("#test1").jp4({src:"http://"+location.host+":"+2323+"/img",width:600,fast:true,lowres:4,debug:false,refresh:false});
  var t2 = $("#test2").jp4({ip:location.host, port:2324,width:600,fast:true,lowres:4});
  let t2 = $("#test2").jp4({src:"http://"+location.host+":"+2324+"/img",width:600,fast:true,lowres:4,debug:false,refresh:false});
  var t3 = $("#test3").jp4({ip:location.host, port:2325,width:600,fast:true,lowres:4});
  let t3 = $("#test3").jp4({src:"http://"+location.host+":"+2325+"/img",width:600,fast:true,lowres:4,debug:false,refresh:false});
  var t4 = $("#test4").jp4({ip:location.host, port:2326,width:600,fast:true,lowres:4});
  let t4 = $("#test4").jp4({src:"http://"+location.host+":"+2326+"/img",width:600,fast:true,lowres:4,debug:false,refresh:false});


});
});
+279 −377
Original line number Original line Diff line number Diff line
@@ -28,15 +28,13 @@
(function ( $ ) {
(function ( $ ) {


    //https://gist.github.com/leolux/c794fc63d9c362013448
    //https://gist.github.com/leolux/c794fc63d9c362013448
  var JP4 = function(element,options){
    let JP4 = function(element,options){


    var elem = $(element);
        let elem = $(element);
    var obj = this;
        let obj = this;


    var settings = $.extend({
        let settings = $.extend({
      ip: "",
            src: "test.jp4",
      port: "",
      image: "test.jp4",
            fromhtmlinput: false,
            fromhtmlinput: false,
            refresh: false,
            refresh: false,
            mosaic: [["Gr","R"],["B" ,"Gb"]],
            mosaic: [["Gr","R"],["B" ,"Gb"]],
@@ -56,22 +54,23 @@
            }
            }
        },options);
        },options);


    var DEBUG = settings.debug;
        let DEBUG = settings.debug;
        // work time
        let T0, TX;


    // working time
        let BAYER = settings.mosaic;
    var T0;
        let FLIPV = 0;
    var TX;
        let FLIPH = 0;
        let IMAGE_FORMAT = "JPEG";
        let SATURATION = [0,0,0,0];


    var BAYER = settings.mosaic;
        let PIXELS = [];
    var FLIPV = 0;
    var FLIPH = 0;
    var IMAGE_FORMAT = "JPEG";
    var SATURATION = [0,0,0,0];


    var PIXELS = [];
        let INIT = false;


        // https://stackoverflow.com/questions/28495390/thermal-imaging-palette
        // https://stackoverflow.com/questions/28495390/thermal-imaging-palette
    var iron_palette = ["#00000a","#000014","#00001e","#000025","#00002a","#00002e","#000032","#000036",
        let iron_palette = [
            "#00000a","#000014","#00001e","#000025","#00002a","#00002e","#000032","#000036",
            "#00003a","#00003e","#000042","#000046","#00004a","#00004f","#000052","#010055",
            "#00003a","#00003e","#000042","#000046","#00004a","#00004f","#000052","#010055",
            "#010057","#020059","#02005c","#03005e","#040061","#040063","#050065","#060067",
            "#010057","#020059","#02005c","#03005e","#040061","#040063","#050065","#060067",
            "#070069","#08006b","#09006e","#0a0070","#0b0073","#0c0074","#0d0075","#0d0076",
            "#070069","#08006b","#09006e","#0a0070","#0b0073","#0c0074","#0d0075","#0d0076",
@@ -125,55 +124,45 @@
            "#fff5a6","#fff6aa","#fff6af","#fff7b3","#fff7b6","#fff8ba","#fff8bd","#fff8c1",
            "#fff5a6","#fff6aa","#fff6af","#fff7b3","#fff7b6","#fff8ba","#fff8bd","#fff8c1",
            "#fff8c4","#fff9c7","#fff9ca","#fff9cd","#fffad1","#fffad4","#fffbd8","#fffcdb",
            "#fff8c4","#fff9c7","#fff9ca","#fff9cd","#fffad1","#fffad4","#fffbd8","#fffcdb",
            "#fffcdf","#fffde2","#fffde5","#fffde8","#fffeeb","#fffeee","#fffef1","#fffef4",
            "#fffcdf","#fffde2","#fffde5","#fffde8","#fffeeb","#fffeee","#fffef1","#fffef4",
                        "#fffff6"];
            "#fffff6"
        ];


        function get_palette_color(v){
        function get_palette_color(v){

            v = v*(iron_palette.length-1);
            v = v*(iron_palette.length-1);
            v_lo = Math.floor(v);
            v_lo = Math.floor(v);
            //v_hi = Math.ceil(v);
            //v_hi = Math.ceil(v);

            // don't interpolate
            // don't interpolate

            return iron_palette[v_lo];
            return iron_palette[v_lo];
        }
        }


        // only valid values are allowed otherwise - disable
        // only valid values are allowed otherwise - disable
    if ((settings.lowres!=0)&&(settings.lowres!=1)&&(settings.lowres!=2)&&(settings.lowres!=4)&&(settings.lowres!=8)){
        if (![0,1,2,4,8].includes(settings.lowres)){
            settings.lowres = 0;
            settings.lowres = 0;
        }
        }


        var cnv_working = $("<canvas>",{id:"working"});
        var cnv_working = $("<canvas>",{id:"working"});
        var cnv_display = $("<canvas>",{id:"display"});
        var cnv_display = $("<canvas>",{id:"display"});


    obj.busy = false;

        // hide working canvas
        // hide working canvas
        cnv_working.css({display:"none"});
        cnv_working.css({display:"none"});
    /*
    cnv_working.css({
      position:"absolute",
      top: "500px",
      left: "500px"
    });
    */


        elem.append(cnv_working);
        elem.append(cnv_working);
        elem.append(cnv_display);
        elem.append(cnv_display);


        if (DEBUG){
        if (DEBUG){
        TX = Date.now();
            T0 = Date.now();
            T0 = Date.now();
            TX = T0;
        }
        }


        obj.busy = true;

        if (settings.fromhtmlinput){
        if (settings.fromhtmlinput){
            /*
            /*
            * if image is being loaded from <input type='file'>
            * if image is being loaded from <input type='file'>
            * make sure the image data starts with: "data:image/jpeg;base64,"
            * make sure the image data starts with: "data:image/jpeg;base64,"
            * EXIF.js does not like empty data type: "data:;base64,"
            * EXIF.js does not like empty data type: "data:;base64,"
            */
            */
        obj.busy = true;
            process_image(settings.src);
        process_image(settings.image);
        }else{
        }else{
            send_request();
            send_request();
        }
        }
@@ -181,21 +170,14 @@


    function send_request(){
    function send_request(){


      var rq = "";
        let rq = settings.src;

        // old, when CORS was not enabled
      var http = new XMLHttpRequest();
        //rq = "http://"+settings.ip+"/get-image.php?ip="+settings.ip+"&port="+settings.port+"&rel=img&ts="+Date.now();

      if (settings.port!=""&&settings.ip!=""){
        //rq = "/get-image.php?ip="+settings.ip+"&port="+settings.port+"&rel=bimg&ts="+Date.now();
        rq = "http://"+settings.ip+"/get-image.php?ip="+settings.ip+"&port="+settings.port+"&rel=img&ts="+Date.now();
        //rq = "get-image.php?ip="+settings.ip+"&port="+settings.port+"&rel=img&ts="+Date.now();
        //settings.refresh = true;
      }else{
        rq = settings.image;
      }


        let http = new XMLHttpRequest();
        http.open("GET", rq, true);
        http.open("GET", rq, true);
      http.responseType = "blob";
        //http.responseType = "blob";
        http.responseType = "arraybuffer";
        http.onload = function(e) {
        http.onload = function(e) {


            if (DEBUG){
            if (DEBUG){
@@ -205,57 +187,45 @@


            if (this.status===200) {
            if (this.status===200) {


            var contentType = http.getResponseHeader("Content-Type");
                let contentType = http.getResponseHeader("Content-Type");

                if (contentType=="image/tiff"){
                if (contentType=="image/tiff"){
                    process_image_tiff(http.response);
                    process_image_tiff(http.response);
                }else{
                }else{
              obj.blob = window.URL.createObjectURL(http.response);
                    //obj.blob = window.URL.createObjectURL(http.response);
              process_image(obj.blob);
                    //process_image(obj.blob);
                    process_image(http.response);
                }
                }

                delete this;
                delete this;
                //URL.revokeObjectURL(imgdata);
                //URL.revokeObjectURL(imgdata);
            }
            }
        };
        };

      obj.busy = true;
        http.send();
        http.send();

    }
    }


    this.refresh = function(){
    this.refresh = function(){
        // skip if busy?
        // skip if busy?
        if (!obj.busy){
        if (!obj.busy){
            if (DEBUG){
                T0 = Date.now();
                TX = T0;
            }
            send_request();
            send_request();
        }
        }
    }
    }


    this.resize = function(w){
    this.resize = function(w){

        settings.width = w;
        settings.width = w;
        INIT = false;
        send_request();
        send_request();

    }
    }


    this.setAddr = function(url,port){
    this.setSrc = function(src){

      settings.src = src;
      settings.port = port;
      settings.ip = url;

      return 0;
      return 0;

    }
    }


    this.getFormat = function(){
    this.getFormat = () => this.format;

    this.getSrc = () => settings.src;
      return this.format;

    }

    this.getAddr = function(){
      return Array(settings.ip,settings.port);
    }


    function process_image_tiff(blob){
    function process_image_tiff(blob){


@@ -397,89 +367,78 @@


    function process_image(imagedata){
    function process_image(imagedata){


        var canvas = cnv_working;
        let img = {};
        //reset format
        img.exifdata = EXIF.readFromBinaryFile(imagedata);
        IMAGE_FORMAT = "JPEG";
        parseEXIFMakerNote(img);


        var heavyImage = new Image();
        if (DEBUG){
            console.log("#"+elem.attr("id")+", exif parse time: "+(Date.now()-TX)/1000+" s");
            TX = Date.now();
        }


        var heavyImage = new Image();
        heavyImage.onload = function(){
        heavyImage.onload = function(){


          /*
            if (DEBUG){
          if (obj.blob){
                console.log("#"+elem.attr("id")+", file reload (from Blob) as image time: "+(Date.now()-TX)/1000+" s");
            console.log("revoking object");
                TX = Date.now();
            window.URL.revokeObjectURL(obj.blob);
            }
            }
          */



          EXIF.getData(this, function() {
            let cnv_w = this.width;

            let cnv_h = this.height;
              var cnv_w;
              var cnv_h;


            if (settings.lowres!=0){
            if (settings.lowres!=0){
                cnv_w = this.width/settings.lowres;
                cnv_w = this.width/settings.lowres;
                cnv_h = this.height/settings.lowres;
                cnv_h = this.height/settings.lowres;
              }else{
            }
                  cnv_w = this.width;
                  cnv_h = this.height;
              }

              //update canvas size
              canvas.attr("width",cnv_w);
              canvas.attr("height",cnv_h);

              parseEXIFMakerNote(this);

              canvas.drawImage({
                  x:0, y:0,
                  source: this,
                  width: cnv_w,
                  height: cnv_h,
                  //source: heavyImage,
                  load: redraw,
                  sx: 0,
                  sy: 0,
                  sWidth: this.width,
                  sHeight: this.height,
                  //scale: scale,
                  fromCenter: false
              });


          });
            if (!INIT) {
                let w = settings.width;
                let h = (cnv_h/cnv_w*settings.width).toFixed(0);


        };
                if ((cnv_display[0].width!=w)||(cnv_display[0].height!=h)){
        heavyImage.src = imagedata;
                    cnv_display[0].width  = w;
                    cnv_display[0].height = h;
                }


                INIT = true;
            }
            }


    function redraw(){
            if (IMAGE_FORMAT=="JPEG"){


      //URL.revokeObjectURL($(this).source.src);
                let canvas = cnv_display[0];
      //console.log(this);
                //canvas.width  = settings.width;
                //canvas.height = Math.floor(cnv_h*settings.width/cnv_w);
                let ctx = canvas.getContext('2d');
                ctx.drawImage(this,0,0,canvas.width,canvas.height);


      //for debugging
                conclude_processing();
      //IMAGE_FORMAT="JPEG";
            }


      $(this).draw({
            if ((IMAGE_FORMAT=="JP4")||(IMAGE_FORMAT=="JP46")){
        fn: function(ctx){


          if (DEBUG){
                let canvas = cnv_working[0];
            console.log("#"+elem.attr("id")+", raw image drawn time: "+(Date.now()-TX)/1000+" s");
                canvas.width = cnv_w;
            TX = Date.now();
                canvas.height = cnv_h;
          }


          if (IMAGE_FORMAT=="JPEG"){
                let ctx = canvas.getContext('2d');
                ctx.drawImage(this,0,0,cnv_w,cnv_h);


            // if JP4/JP46 it will work through webworker and exit later on workers message
                decode_and_display(ctx);
            Elphel.Canvas.drawScaled(cnv_working,cnv_display,settings.width);
            }

        };
        //obj.blob = window.URL.createObjectURL(http.response);
        //process_image(obj.blob);
        heavyImage.src = window.URL.createObjectURL(new Blob([imagedata]));
    }


    function conclude_processing(){
        if (DEBUG){
        if (DEBUG){
            console.log("#"+elem.attr("id")+", Total time: "+(Date.now()-T0)/1000+" s");
            console.log("#"+elem.attr("id")+", Total time: "+(Date.now()-T0)/1000+" s");
        }
        }


            $(this).trigger("canvas_ready");
        cnv_working.trigger("canvas_ready");
        obj.busy = false;
        obj.busy = false;


        if (settings.refresh) {
        if (settings.refresh) {
@@ -489,70 +448,20 @@
            }
            }
            send_request();
            send_request();
        }
        }

          }else if ((IMAGE_FORMAT=="JP4")||(IMAGE_FORMAT=="JP46")){

            if (settings.fast){
              quickestPreview(ctx);
            }/*else{
              Elphel.reorderJP4Blocks(ctx,"JP4");

              if (settings.precise){
                PIXELS = Elphel.pixelsToArrayLinear(ctx);
                Elphel.demosaicBilinear(ctx,PIXELS,settings.mosaic,true);
                PIXELS = Elphel.pixelsToArray(ctx);
              }else{
                PIXELS = Elphel.pixelsToArray(ctx);
                Elphel.demosaicBilinear(ctx,PIXELS,settings.mosaic,false);
                PIXELS = Elphel.pixelsToArray(ctx);
              }

              if (settings.channel!="all"){
                  Elphel.showSingleColorChannel(ctx,settings.channel);
              }

              if (settings.diff){
                Elphel.diffColorChannels(PIXELS,settings.chn1,settings.chn2,1);
                Elphel.drawImageData(ctx,PIXELS);
              }

              if (settings.ndvi){
                console.log(PIXELS[0]+" "+PIXELS[1]+" "+PIXELS[2]+" "+PIXELS[3]+" ");
                PIXELS = Elphel.someIndex(PIXELS);
                console.log(PIXELS[0]+" "+PIXELS[1]+" "+PIXELS[2]+" "+PIXELS[3]+" ");
                Elphel.drawImageData(ctx,PIXELS);
              }

            }
            */
            // RGB -> YCbCr x SATURATION -> RGB
            // Taking SATURATION[0] = 1/GAMMA[0] (green pixel of GR-line)
            //saturation(ctx,SATURATION[0]);
    }
    }


          // too early
    function decode_and_display(ctx){
          //console.log("#"+elem.attr("id")+", time: "+(Date.now()-t0)/1000+" s");
        }
      });
    }

    function quickestPreview(ctx){


      var worker = new Worker(settings.webworker_path+'/webworker.js');
        let worker = new Worker(settings.webworker_path+'/webworker.js');


        if (DEBUG){
        if (DEBUG){
            TX = Date.now();
            TX = Date.now();
        }
        }


      //ctx.canvas.width = ctx.canvas.width/2;
        let width  = ctx.canvas.width;
      //ctx.canvas.height = ctx.canvas.height/2;
        let height = ctx.canvas.height;
      //ctx.canvas.style.width = ctx.canvas.style.width/4;
        let image  = ctx.getImageData(0,0,width,height);
      //ctx.canvas.style.height = ctx.canvas.style.height/4;
        let pixels = image.data;

      var width = ctx.canvas.width;
      var height = ctx.canvas.height;
      var image = ctx.getImageData(0,0,width,height);
      var pixels = image.data;


        if (DEBUG){
        if (DEBUG){
            console.log("#"+elem.attr("id")+", data from canvas for webworker time: "+(Date.now()-TX)/1000+" s");
            console.log("#"+elem.attr("id")+", data from canvas for webworker time: "+(Date.now()-TX)/1000+" s");
@@ -574,42 +483,34 @@
            },
            },
        },[pixels.buffer]);
        },[pixels.buffer]);



        worker.onmessage = function(e){
        worker.onmessage = function(e){


        var pixels = new Uint8Array(e.data.pixels);
            let width  = e.data.width;
        var working_context = cnv_working[0].getContext('2d');
            let height = e.data.height;

            let pixels = new Uint8Array(e.data.pixels);
        var width = e.data.width;
        var height = e.data.height;


            if (DEBUG){
            if (DEBUG){
                console.log("#"+elem.attr("id")+", worker time: "+(Date.now()-TX)/1000+" s");
                console.log("#"+elem.attr("id")+", worker time: "+(Date.now()-TX)/1000+" s");
                TX = Date.now();
                TX = Date.now();
            }
            }


        Elphel.Canvas.putImageData(working_context,pixels,width,height);
            let ctx = cnv_working[0].getContext('2d');
            Elphel.Canvas.putImageData(ctx,pixels,width,height);
            Elphel.Canvas.drawScaled(cnv_working,cnv_display,settings.width);
            Elphel.Canvas.drawScaled(cnv_working,cnv_display,settings.width);


        if (DEBUG){
            /*
          // report time
            let ctx = cnv_display[0].getContext('2d');
          console.log("#"+elem.attr("id")+", Total time: "+(Date.now()-T0)/1000+" s");
            let imgdata = new ImageData(new Uint8ClampedArray(pixels), width, height);
        }
            ctx.putImageData(imgdata,0,0);
        //trigger here
            let k = settings.width/width;
        cnv_working.trigger("canvas_ready");
            ctx.scale(k,k);
        obj.busy = false;
            ctx.drawImage(cnv_display[0],0,0);

            ctx.scale(1/k,1/k);
        if (settings.refresh) {
            */
            if (DEBUG){
                TX = Date.now();
                T0 = Date.now();
            }
            send_request();
        }


            conclude_processing();
            this.terminate();
            this.terminate();
        }
        }

    }
    }


    /**
    /**
@@ -667,6 +568,7 @@
      if (typeof MakerNote !== 'undefined') color_mode=(MakerNote[10]>>4)&0x0f;
      if (typeof MakerNote !== 'undefined') color_mode=(MakerNote[10]>>4)&0x0f;


      switch(color_mode){
      switch(color_mode){
        case 0: IMAGE_FORMAT = "JPEG"; break;
        case 2: IMAGE_FORMAT = "JP46"; break;
        case 2: IMAGE_FORMAT = "JP46"; break;
        case 5: IMAGE_FORMAT = "JP4"; break;
        case 5: IMAGE_FORMAT = "JP4"; break;
        //default:
        //default:
+33 −32
Original line number Original line Diff line number Diff line


importScripts('elphel.js');
importScripts('elphel.js');


self.onmessage = function(e) {
onmessage = async (e) => {


  var W = e.data.width;
    let W = e.data.width;
  var H = e.data.height;
    let H = e.data.height;
  var Mosaic = e.data.mosaic;
    let Mosaic = e.data.mosaic;
  var Format = e.data.format;
    let Format = e.data.format;


  var settings = e.data.settings;
    let settings = e.data.settings;


  var Pixels = new Uint8Array(e.data.pixels);
    let Pixels = new Uint8Array(e.data.pixels);

    let reorderedPixels;


    if (settings.lowres==0){
    if (settings.lowres==0){
    var reorderedPixels = Elphel.Pixels.reorderBlocksJPx(Pixels,W,H,Format,Mosaic,settings.fast);
        reorderedPixels = Elphel.Pixels.reorderBlocksJPx(Pixels,W,H,Format,Mosaic,settings.fast);
        //reorder first then downscale
        //reorder first then downscale
        if (settings.fast){
        if (settings.fast){
            W = W/2;
            W = W/2;
            H = H/2;
            H = H/2;
        }
        }
    }else{
    }else{
    var reorderedPixels = Elphel.Pixels.reorderBlocksJP4_lowres(Pixels,W,H,Format,Mosaic,settings.lowres);
        reorderedPixels = await Elphel.Pixels.reorderBlocksJP4_lowres(Pixels,W,H,Format,Mosaic,settings.lowres);
        W = W/2;
        W = W/2;
        H = H/2;
        H = H/2;
    }
    }
@@ -35,5 +37,4 @@ self.onmessage = function(e) {


    //Elphel.test();
    //Elphel.test();
    this.close();
    this.close();

};
};