Commit 4ff8c216 authored by Oleg Dzhimiev's avatar Oleg Dzhimiev

moved to lib

parent 129d9d9a
...@@ -5,7 +5,8 @@ INSTALL = install ...@@ -5,7 +5,8 @@ INSTALL = install
DOCS= jp4-canvas.html \ DOCS= jp4-canvas.html \
jp4-canvas.js jp4-canvas.js
LIBS= exif.js \ LIBS= elphel.js \
exif.js \
jquery-jp4.js \ jquery-jp4.js \
jcanvas.min.js jcanvas.min.js
......
/*
* FILE NAME : jp4-canvas.js
* DESCRIPTION: Converts jp4/jp46 files into human perceivable format in html5 canvas.
* VERSION: 1.0
* AUTHOR: Oleg K Dzhimiev <oleg@elphel.com>
* LICENSE: AGPL, see http://www.gnu.org/licenses/agpl.txt
* Copyright (C) 2016 Elphel, Inc.
*/
var Elphel = {
/* Name: reorderJP4Blocks
* Description: clear from the function's name
*
* ctx - canvas 2D context
* format:
* 'jpeg' - skip reordering
* 'jp4' - jp4 reordeing
* 'jp46' - jp46 reordering
* mosaic - [["Gr","R"],["B","Gb"]] =
* odd lines: Gr,R,Gr,R
* even lines: B,Gb,B,Gb
* nwd - true/false - demosaicing: 'Nearest Neighbor' with 1/2 scale -
* put mosaic (different channels) for GrRBGb into one pixel for
* faster performance
*/
reorderJP4Blocks: function(ctx, format="JP4", mosaic=[["Gr","R"],["B" ,"Gb"]], nwd=false) {
var t0 = Date.now();
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var inputImage = ctx.getImageData(0,0,width,height);
var iPixels = inputImage.data;
var outputImage = ctx.createImageData((nwd)?width/2:width,(nwd)?height/2:height);
var oPixels = outputImage.data;
// img.data is a long 1-D array with the following structure:
// pix[i+0] - red
// pix[i+1] - green
// pix[i+2] - blue
// pix[i+3] - alpha
// buffer for reordering pixels
var macroblock = new Array(); //16x16
for (var y=0;y<16;y++) macroblock[y]=new Array();
// in JP4 format the 16x16 block is 32x8 (GRBG)
// the 1st line of 32x8 blocks is the left half of the image
// the 2nd line of 32x8 blocks is the right half
// vertical step = 16 pixels
for (yb=0;yb<(height>>4);yb++){
// horizontal step = 16 pixels
for (xb=0;xb<(width>>4);xb++) {
if (format=="JP4") {
// 32x8 block reorder into 16x16
for (nb=0;nb<4;nb++) {
xbyr= nb&1; // horizontal odd-even
ybyr=(nb>>1)&1; // vertical odd-even
for (y=0;y<8;y++) {
// xb < half image -> 1st line of 32x8
// xb >= half image -> 2nd line of 32x8
//offset=(((yb<<4)+y)*width)+(nb<<3)+((xb>=(width>>5))?(((xb<<5)-width)+(width<<3)):(xb<<5));
offset=(((yb<<4)+y)*width)+(nb<<3)+(xb<<5)+((xb>=(width>>5))?((width<<3)-width):0);
for (x=0;x<8;x++) {
macroblock[(y<<1)|ybyr][(x<<1)|xbyr]=iPixels[4*(offset+x)];
}
}
}
}
if (format=="JP46") {
for (y=0;y<16;y++) {
offset=((yb<<4)+y)*width+(xb<<4);
for (x=0;x<16;x++) {
//Red value only
macroblock[((y<<1)&0xe)|((y>>3)&0x1)][((x<<1)&0xe)|((x>>3)&0x1)]=iPixels[4*(offset+x)];
}
}
}
if (nwd){
for (y=0;y<8;y++){
offset=width/2*((yb<<4)/2+y)+(xb<<4)/2;
for (x=0;x<8;x++) {
for(k=0;k<4;k++){
y0 = 2*y+((k>>1)&1);
x0 = 2*x+(k&1);
if (mosaic[y0&1][x0&1]=="R") r = macroblock[y0][x0];
else if (mosaic[y0&1][x0&1]=="Gr") gr = macroblock[y0][x0];
else if (mosaic[y0&1][x0&1]=="Gb") gb = macroblock[y0][x0];
else if (mosaic[y0&1][x0&1]=="B") b = macroblock[y0][x0];
}
g = (gr+gb)>>1;
oPixels[4*(offset+x)+0] = r;//4*(8*y+x);
oPixels[4*(offset+x)+1] = g;//4*(8*y+x);
oPixels[4*(offset+x)+2] = b;//4*(8*y+x);
oPixels[4*(offset+x)+3] = 255;
}
}
}else{
for (y=0;y<16;y++){
offset=width*((yb<<4)+y)+(xb<<4);
for (x=0;x<16;x++) {
//red +0, green +1, blue +2, alpha +3
// thinking: GRBG
oPixels[4*(offset+x)+0] = ((mosaic[y&1][x&1]=="R" ) )?macroblock[y][x]:0;
oPixels[4*(offset+x)+1] = ((mosaic[y&1][x&1]=="Gr")||(mosaic[y%2][x%2]=="Gb"))?macroblock[y][x]:0;
oPixels[4*(offset+x)+2] = ((mosaic[y&1][x&1]=="B" ) )?macroblock[y][x]:0;
oPixels[4*(offset+x)+3] = 255;
}
}
}
}
}
if (nwd) {
ctx.canvas.width = width/2;
ctx.canvas.height = height/2;
}
ctx.putImageData(outputImage,0,0);
console.log("reorderJP4Blocks(): "+(Date.now()-t0)/1000+" s");
},
/* Name: demosaicNearestNeighbor
* Description: A separate function, just in case, same as in
* reorderJP4Blocks
*
* ctx - canvas 2D context
* mosaic - [["Gr","R"],["B","Gb"]] =
* odd lines: Gr,R,Gr,R
* even lines: B,Gb,B,Gb
*/
demosaicNearestNeighbor: function(ctx, mosaic=[["Gr","R"],["B" ,"Gb"]]){
var t0 = Date.now();
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var inputImage = ctx.getImageData(0,0,width,height);
var iPixels = inputImage.data;
var outputImage = ctx.createImageData(width/2,height/2);
var oPixels = outputImage.data;
for(var y=0;y<height/2;y++){
for(var x=0;x<width/2;x++){
for(var k=0;k<4;k++){
y0 = (k>>1)&1;
x0 = k&1;
if (mosaic[y0&1][x0&1]=="R") r = iPixels[4*(width*(2*y+y0)+2*x+x0)+0];
else if (mosaic[y0&1][x0&1]=="Gr") gr = iPixels[4*(width*(2*y+y0)+2*x+x0)+1];
else if (mosaic[y0&1][x0&1]=="Gb") gb = iPixels[4*(width*(2*y+y0)+2*x+x0)+1];
else if (mosaic[y0&1][x0&1]=="B") b = iPixels[4*(width*(2*y+y0)+2*x+x0)+2];
}
g = (gr+gb)>>1;
oPixels[4*(width/2*y+x)+0] = r;
oPixels[4*(width/2*y+x)+1] = g;
oPixels[4*(width/2*y+x)+2] = b;
oPixels[4*(width/2*y+x)+3] = 255;
}
}
ctx.canvas.width = width/2;
ctx.canvas.height = height/2;
ctx.putImageData(outputImage,0,0);
console.log("demosaicNearestNeighbor(): "+(Date.now()-t0)/1000+" s");
},
/* Name: demosaicBilinear
* Description: a simple bilinear demosaicing
*
* ctx - canvas 2D context
* px - pixel array as in "ctx.getImageData(0,0,width,height)"
* or use pixelsToArray(ctx,true) to get linear array from canvas
* mosaic - [["Gr","R"],["B","Gb"]] =
* odd lines: Gr,R,Gr,R
* even lines: B,Gb,B,Gb
* precise - true/false:
* true - calculate values then apply gamma - provide linear pixel array
* px_linear (8bit) = px^gamma (32bit), gamma=2
* false - calculate values from gamma encoded pixels - 2-4x times faster
*/
demosaicBilinear: function(ctx, px, mosaic=[["Gr","R"],["B" ,"Gb"]], precise=false){
var t0 = Date.now();
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var outputImage = ctx.getImageData(0,0,width,height);
var oPixels = outputImage.data;
var x_l = 0, x_r = 0;
var y_t = 0, y_b = 0;
var x = width;
var y = height;
for(var y=0;y<height;y++){
for(var x=0;x<width;x++){
x_l = (x==0)?1:(x-1);
x_r = (x==(width-1))?(width-2):(x+1);
y_t = (y==0)?1:(y-1);
y_b = (y==(height-1))?(height-2):(y+1);
//Gr
if (mosaic[y%2][x%2]=="Gr"){
Pr_y0xl = px[4*(width*(y+0)+(x_l))+0];
Pr_y0xr = px[4*(width*(y+0)+(x_r))+0];
Pb_ybx0 = px[4*(width*(y_b)+(x+0))+2];
Pb_ytx0 = px[4*(width*(y_t)+(x+0))+2];
if (precise){
//Pr = pixel_l2g(1/2*(pixel_g2l(Pr_y0xl)+pixel_g2l(Pr_y0xr)));
//Pb = pixel_l2g(1/2*(pixel_g2l(Pb_ybx0)+pixel_g2l(Pb_ytx0)));
Pr = this.l2g(1/2*(Pr_y0xl+Pr_y0xr));
Pb = this.l2g(1/2*(Pb_ybx0+Pb_ytx0));
}else{
Pr = 1/2*(Pr_y0xl+Pr_y0xr);
Pb = 1/2*(Pb_ybx0+Pb_ytx0);
}
oPixels[4*(width*y+x)+0]=Math.round(Pr);
oPixels[4*(width*y+x)+2]=Math.round(Pb);
}
//R
if (mosaic[y%2][x%2]=="R"){
Pg_ytx0 = px[4*(width*(y_t)+(x+0))+1];
Pg_y0xl = px[4*(width*(y+0)+(x_l))+1];
Pg_y0xr = px[4*(width*(y+0)+(x_r))+1];
Pg_ybx0 = px[4*(width*(y_b)+(x+0))+1];
Pb_ytxl = px[4*(width*(y_t)+(x_l))+2];
Pb_ytxr = px[4*(width*(y_t)+(x_r))+2];
Pb_ybxl = px[4*(width*(y_b)+(x_l))+2];
Pb_ybxr = px[4*(width*(y_b)+(x_r))+2];
if (precise){
Pg = this.l2g(1/4*(Pg_ytx0+Pg_y0xl+Pg_y0xr+Pg_ybx0));
Pb = this.l2g(1/4*(Pb_ytxl+Pb_ytxr+Pb_ybxl+Pb_ybxr));
}else{
Pg = 1/4*(Pg_ytx0+Pg_y0xl+Pg_y0xr+Pg_ybx0);
Pb = 1/4*(Pb_ytxl+Pb_ytxr+Pb_ybxl+Pb_ybxr);
}
oPixels[4*(width*y+x)+1]=Math.round(Pg);
oPixels[4*(width*y+x)+2]=Math.round(Pb);
}
//B
if (mosaic[y%2][x%2]=="B"){
Pr_ytxl = px[4*(width*(y_t)+(x_l))+0];
Pr_ytxr = px[4*(width*(y_t)+(x_r))+0];
Pr_ybxl = px[4*(width*(y_b)+(x_l))+0];
Pr_ybxr = px[4*(width*(y_b)+(x_r))+0];
Pg_ytx0 = px[4*(width*(y_t)+(x+0))+1];
Pg_y0xl = px[4*(width*(y+0)+(x_l))+1];
Pg_y0xr = px[4*(width*(y+0)+(x_r))+1];
Pg_ybx0 = px[4*(width*(y_b)+(x+0))+1];
if (precise){
Pr = this.l2g(1/4*(Pr_ytxl+Pr_ytxr+Pr_ybxl+Pr_ybxr));
Pg = this.l2g(1/4*(Pg_ytx0+Pg_y0xl+Pg_y0xr+Pg_ybx0));
}else{
Pr = 1/4*(Pr_ytxl+Pr_ytxr+Pr_ybxl+Pr_ybxr);
Pg = 1/4*(Pg_ytx0+Pg_y0xl+Pg_y0xr+Pg_ybx0);
}
oPixels[4*(width*y+x)+0]=Math.round(Pr);
oPixels[4*(width*y+x)+1]=Math.round(Pg);
}
//Gb
if (mosaic[y%2][x%2]=="Gb"){
Pr_ytx0 = px[4*(width*(y_t)+(x+0))+0];
Pr_ybx0 = px[4*(width*(y_b)+(x+0))+0];
Pb_y0xl = px[4*(width*(y+0)+(x_l))+2];
Pb_y0xr = px[4*(width*(y+0)+(x_r))+2];
if (precise){
Pr = this.l2g(1/2*(Pr_ytx0+Pr_ybx0));
Pb = this.l2g(1/2*(Pb_y0xl+Pb_y0xr));
}else{
Pr = 1/2*(Pr_ytx0+Pr_ybx0);
Pb = 1/2*(Pb_y0xl+Pb_y0xr);
}
oPixels[4*(width*y+x)+0]=Math.round(Pr);
oPixels[4*(width*y+x)+2]=Math.round(Pb);
}
}
}
ctx.putImageData(outputImage,0,0);
console.log("demosaicBilinear(): "+(Date.now()-t0)/1000+" s");
},
/* Name: showSingleColorChannel
* Description: make a BW from selected channel
*
* ctx - canvas 2D context
* channel - "red","green","blue"
*/
showSingleColorChannel: function(ctx, channel){
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var inputImage = ctx.getImageData(0,0,width,height);
var iPixels = inputImage.data;
for(var y=0;y<height;y++){
for(var x=0;x<width;x++){
r = iPixels[4*(width*y+x)+0];
g = iPixels[4*(width*y+x)+1];
b = iPixels[4*(width*y+x)+2];
if (channel=="red"){
iPixels[4*(width*y+x)+0]=r;
iPixels[4*(width*y+x)+1]=r;
iPixels[4*(width*y+x)+2]=r;
}else if (channel=="green"){
iPixels[4*(width*y+x)+0]=g;
iPixels[4*(width*y+x)+1]=g;
iPixels[4*(width*y+x)+2]=g;
}else if (channel=="blue"){
iPixels[4*(width*y+x)+0]=b;
iPixels[4*(width*y+x)+1]=b;
iPixels[4*(width*y+x)+2]=b;
}
}
}
ctx.putImageData(inputImage,0,0);
},
/* Name: applySaturation
* Description: get data from ctx saturate and redraw
*
* ctx - canvas 2D context
* s - value
*/
applySaturation: function(ctx,s){
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var inputImage = ctx.getImageData(0,0,width,height);
var iPixels = inputImage.data;
var r,g,b;
var Y,Cb,Cr;
for(var y=0;y<height;y++){
for(var x=0;x<width;x++){
r = iPixels[4*(width*y+x)+0];
g = iPixels[4*(width*y+x)+1];
b = iPixels[4*(width*y+x)+2];
Y = 0.299*r+0.5870*g+ 0.144*b;
Cb = 128+s*(-0.1687*r-0.3313*g+ 0.500*b);
Cr = 128+s*( 0.5*r-0.4187*g-0.0813*b);
if (Cb<0) Cb=0; if (Cb>255) Cb=255;
if (Cr<0) Cr=0; if (Cr>255) Cr=255;
r = Y + 1.402*(Cr-128);
g = Y - 0.34414*(Cb-128)-0.71414*(Cr-128);
b = Y + 1.772*(Cb-128);
if (r<0) r=0; if (r>255) r=255;
if (g<0) g=0; if (g>255) g=255;
if (b<0) b=0; if (b>255) b=255;
iPixels[4*(width*y+x)+0]=r;
iPixels[4*(width*y+x)+1]=g;
iPixels[4*(width*y+x)+2]=b;
iPixels[4*(width*y+x)+3]=255;
}
}
ctx.putImageData(inputImage,0,0);
},
/* Name: l2g
* Desctiptin: convert a value from linear to gamma encoded,
* close to square root
*/
l2g: function(pv){
// assuming gamma=2
var tmp = Math.sqrt(pv);
if (tmp>255) tmp = 255;
return tmp;
},
/* Name: g2l
* Desctiptin: convert a value gamma encoded to linear,
* close to square
*/
g2l: function(pv){
// assuming gamma = 2
var tmp = pv*pv;
return tmp;
},
/* Name: pixelsToArray
* Desctiptin: get pixel data from canvas context and copy to
* an array. The array will be Uint8ClampedArray
*/
pixelsToArray: function(ctx){
t0 = Date.now();
var px = [];
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var image = ctx.getImageData(0,0,width,height);
var pixels = image.data;
px = pixels.slice(0);
console.log("pixelsToArray(): "+(Date.now()-t0)/1000+" s");
return px;
},
/* Name: pixelsToArrayLinear
* Desctiptin: get pixel data from canvas context, convert to linear,
* then copy to an array.
*/
pixelsToArrayLinear: function(ctx){
t0 = Date.now();
var px = [];
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var image = ctx.getImageData(0,0,width,height);
var pixels = image.data;
for(var i=0;i<pixels.length;i+=4){
px[i+0] = this.g2l(pixels[i+0]);
px[i+1] = this.g2l(pixels[i+1]);
px[i+2] = this.g2l(pixels[i+2]);
px[i+3] = pixels[i+3]; //alpha
}
console.log("pixelsToArrayLinear(): "+(Date.now()-t0)/1000+" s");
return px;
},
/* Name: drawScaled
* Description: Plugin specific. Takes source canvas - draws a scaled
* version on destination canvas
*/
drawScaled: function(cnv_src,cnv_dst,width){
var t0 = Date.now();
var ctx = cnv_src[0].getContext('2d');
var tw = ctx.canvas.width;
var th = ctx.canvas.height;
var tr = tw/th;
var sctx = cnv_dst[0].getContext('2d');
var w = Math.round(width);
var h = Math.round(w/tr);
sctx.canvas.width = w;
sctx.canvas.height = h;
cscale = Math.round(w/tw*100)/100;
sctx.scale(cscale,cscale);
sctx.drawImage(cnv_src[0],0,0);
console.log("drawScaled(): "+(Date.now()-t0)/1000+" s");
},
/* Name: diffColorChannels
* Description: color channel difference
*
*/
diffColorChannels: function(px,chn1,chn2,k=1){
var t0 = Date.now();
var i1 = 0;
if (chn1=="green") i1 = 1;
if (chn1=="blue") i1 = 2;
var i2 = 0;
if (chn2=="green") i2 = 1;
if (chn2=="blue") i2 = 2;
for(var i=0;i<(px.length>>2);i++){
diff = k*px[4*i+i1] - px[4*i+i2];
px[4*i+0] = diff;
px[4*i+1] = diff;
px[4*i+2] = diff;
px[4*i+3] = 255;
}
console.log("diffColorChannels(): "+(Date.now()-t0)/1000+" s");
return px;
},
/* Name: ndvi
* Description: get ndvi
*/
ndvi: function(px){
var r,g,b;
var NIR,RED,NDVI;
for(var i=0;i<(px.length>>2);i++){
r = px[4*i+0];
g = px[4*i+1];
b = px[4*i+2];
if ((b>r)||(g>r)) {
console.log("Color error!");
NIR = 0;
RED = 1;
//if (b>r) px[4*(width*y+x)+2]=255;
if (b>r) px[4*i+2]=0;
if (g>r) px[4*i+1]=0;
px[4*i+0]=200;
}else{
if ((r-b)<20) b = 0;
k = 0.6;
RED = (r-g)/(1-k);
NIR = (r - RED)*2.5;
NDVI = (NIR-RED)/(NIR+RED);
//color
if (NDVI<0.00){r=0;g= 0;b= 0;}
else if (NDVI<0.50){r=200 ;g=400*(0.5-NDVI);b=0;}
else if (NDVI<1.00){r=400*(1-NDVI);g=200; b=0;}
else{
r = 150; g = 150; b = 150;
}
//console.log("RED="+RED+" NIR="+NIR);
px[4*i+0]=r;
px[4*i+1]=g;
px[4*i+2]=b;
}
}
return px;
},
/* Name: gammaEncode
* Description: -
*/
gammaEncode: function(px){
for(var i=0;i<(px.length>>2);i++){
px[4*i+0] = this.l2g(px[4*i+0]);
px[4*i+1] = this.l2g(px[4*i+1]);
px[4*i+2] = this.l2g(px[4*i+2]);
}
return px;
},
/* Name: gammaDecode
* Description: -
*/
gammaDecode: function(px){
for(var i=0;i<(px.length>>2);i++){
px[4*i+0] = this.g2l(px[4*i+0]);
px[4*i+1] = this.g2l(px[4*i+1]);
px[4*i+2] = this.g2l(px[4*i+2]);
}
return px;
},
/* Name: drawImageData
* Description: -
*/
drawImageData: function(ctx,px){
var t0 = Date.now();
var img = ctx.createImageData(ctx.canvas.width,ctx.canvas.height);
var imgdata = img.data;
for(var i=0;i<(imgdata.length>>2);i++){
imgdata[4*i+0] = px[4*i+0];
imgdata[4*i+1] = px[4*i+1];
imgdata[4*i+2] = px[4*i+2];
imgdata[4*i+3] = px[4*i+3];
}
ctx.putImageData(img,0,0);
console.log("drawImageData(): "+(Date.now()-t0)/1000+" s");
}
};
\ No newline at end of file
...@@ -10,7 +10,14 @@ if (isset($_GET['rel'])) ...@@ -10,7 +10,14 @@ if (isset($_GET['rel']))
else else
die(); die();
/*
header("Location: http://{$_SERVER['HTTP_HOST']}:$port/$rel");
die();
*/
header('Content-type:image/jpeg'); header('Content-type:image/jpeg');
echo file_get_contents("http://localhost:$port/$rel"); echo file_get_contents("http://localhost:$port/$rel");
die();
?> ?>
...@@ -7,18 +7,19 @@ ...@@ -7,18 +7,19 @@
<body> <body>
<table> <table>
<tr> <tr>
<td><div id='test1'></div></td>
<td><div id='test2'></div></td> <td><div id='test2'></div></td>
<td><div id='test4'></div></td>
</tr> </tr>
<tr> <tr>
<td><div id='test1'></div></td>
<td><div id='test3'></div></td> <td><div id='test3'></div></td>
<td><div id='test4'></div></td>
</tr> </tr>
</table> </table>
<script src="js/elphel.js"></script>
<script src="js/jquery-2.2.3.min.js"></script> <script src="js/jquery-2.2.3.min.js"></script>
<script src="js/jcanvas.min.js"></script> <script src="js/jcanvas.min.js"></script>
<script src="js/exif.js"></script> <script src="js/exif.js"></script>
<script src="jp4-canvas.js"></script>
<script src="js/jquery-jp4.js"></script> <script src="js/jquery-jp4.js"></script>
<script src="jp4-canvas.js"></script>
</body> </body>
</html> </html>
...@@ -8,8 +8,17 @@ Copyright (C) 2016 Elphel, Inc. ...@@ -8,8 +8,17 @@ Copyright (C) 2016 Elphel, Inc.
*/ */
$(function(){ $(function(){
$("#test1").jp4({port:2323,width:600});
$("#test2").jp4({port:2324,width:600}); var t1 = $("#test1").jp4({port:2323,width:600,image:"images/elphelimg_lowpass_1.jp4",fast:true});
$("#test3").jp4({port:2325,width:600});
$("#test4").jp4({port:2326,width:600}); var t2 = $("#test2").jp4({port:2323,width:600,image:"images/elphelimg_lowpass_1.jp4",precise:true});
t1.cnv.on("canvas_ready",function(){
console.log("canvas1 ready");
});
t2.cnv.on("canvas_ready",function(){
console.log("canvas2 ready");
});
}); });
(function ( $ ) { (function ( $ ) {
$.fn.jp4 = function(options) { //https://gist.github.com/leolux/c794fc63d9c362013448
var settings = $.extend({ var JP4 = function(element,options){
port: 2323,
rel: "bimg", var elem = $(element);
mosaic: [["Gr","R"],["B" ,"Gb"]], var obj = this;
width: 600
},options); var settings = $.extend({
port: "",
var BayerMosaic = settings.mosaic; image: "test.jp4",
mosaic: [["Gr","R"],["B" ,"Gb"]],
var FLIPV = 0; fast: false,
var FLIPH = 0; precise: false,
width: 600,
channel: "all",
diff: false,
chn1: "red",
chn2: "green",
ndvi: false,
callback: function(){
console.log("callback");
}
},options);
var COLOR_MODE = 0; var BAYER = settings.mosaic;
var IS_JP4 = false; var FLIPV = 0;
var IS_JP46 = false; var FLIPH = 0;
var IMAGE_FORMAT = "JPEG";
var SATURATION = [0,0,0,0]; var SATURATION = [0,0,0,0];
var cnv_working = $("<canvas>").css({ var PIXELS = [];
display:"none"
}); var cnv_working = $("<canvas>",{id:"result"}).css({
display:"none"
var cnv_display = $("<canvas>"); });
var cnv_display = $("<canvas>",{id:"scaled"});
this.append(cnv_working).append(cnv_display);
elem.append(cnv_working).append(cnv_display);
get_image();
//end
function get_image(){ get_image();
//end
function get_image(){
var ts = new Date().getTime();
var canvas = cnv_working; var canvas = cnv_working;
var scaled_canvas = cnv_display; var scaled_canvas = cnv_display;
IS_JP4 = false; //reset format
IS_JP46 = false; IMAGE_FORMAT = "JPEG";
var http = new XMLHttpRequest(); var http = new XMLHttpRequest();
http.open("GET", "get-image.php?port="+settings.port+"&rel="+settings.rel+"&ts="+ts, true);
var rq = "";
if (settings.port!=""){
rq = "get-image.php?port="+settings.port+"&rel=bimg&ts="+Date.now();
}else{
rq = settings.image;
}
console.log("my rq is "+rq);
http.open("GET", rq, true);
http.responseType = "blob"; http.responseType = "blob";
http.onload = function(e) { http.onload = function(e) {
if (this.status === 200) { if (this.status === 200) {
var heavyImage = new Image(); var heavyImage = new Image();
heavyImage.onload = function(){ heavyImage.onload = function(){
EXIF.getData(this, function() { EXIF.getData(this, function() {
//update canvas size //update canvas size
canvas.attr("width",this.width); canvas.attr("width",this.width);
canvas.attr("height",this.height); canvas.attr("height",this.height);
parseEXIFMakerNote(this); parseEXIFMakerNote(this);
canvas.drawImage({ canvas.drawImage({
x:0, y:0, x:0, y:0,
source: heavyImage, source: heavyImage,
load: redraw, load: redraw,
//scale: 1, //scale: 1,
fromCenter: false fromCenter: false
}); });
});
var tw = this.width; };
var th = this.height; heavyImage.src = URL.createObjectURL(http.response);
var tr = tw/th; }
var sctx = scaled_canvas[0].getContext('2d');
var w = Math.round(settings.width);
var h = Math.round(w/tr);
sctx.canvas.width = w;
sctx.canvas.height = h;
cscale = Math.round(w/this.width*100)/100;
//console.log("cscale is "+cscale);
sctx.scale(cscale,cscale);
sctx.drawImage(canvas[0],0,0);
});
};
heavyImage.src = URL.createObjectURL(http.response);
}
}; };
http.send(); http.send();
} }
function redraw(){ function redraw(){
$(this).draw({ $(this).draw({
fn: function(ctx){ fn: function(ctx){
if (IS_JP4||IS_JP46){ var t0 = Date.now();
pixelBlocksReorder(ctx); console.log("Processing "+elem.attr("id"));
demosaic_bilinear(ctx); if ((IMAGE_FORMAT=="JP4")||(IMAGE_FORMAT=="JP46")){
// RGB > YCbCr x SATURATION > RGB if (settings.fast){
// Taking SATURATION[0] = 1/GAMMA[0] (green pixel of GR-line) quickestPreview(ctx);
saturation(ctx,SATURATION[0]); }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);
} }
get_image();
}
});
}
// reorder blocks if needed
function pixelBlocksReorder(ctx){
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var inputImage = ctx.getImageData(0,0,width,height);
var iPixels = inputImage.data;
var outputImage = ctx.createImageData(width,height);
var oPixels = outputImage.data;
// img.data is a long 1-D array with the following structure:
// pix[i+0] - red
// pix[i+1] - green
// pix[i+2] - blue
// pix[i+3] - alpha
// buffer for reordering pixels
var macroblock = new Array(); //16x16
for (var y=0;y<16;y++) macroblock[y]=new Array();
// in JP4 format the 16x16 block is 32x8 (GRBG)
// the 1st line of 32x8 blocks is the left half of the image
// the 2nd line of 32x8 blocks is the right half
// vertical step = 16 pixels
for (yb=0;yb<(height>>4);yb++){
// horizontal step = 16 pixels
for (xb=0;xb<(width>>4);xb++) {
if (IS_JP4) {
// 32x8 block reorder into 16x16
for (nb=0;nb<4;nb++) {
xbyr= nb&1; // horizontal odd-even
ybyr=(nb>>1)&1; // vertical odd-even
for (y=0;y<8;y++) {
// xb < half image -> 1st line of 32x8
// xb >= half image -> 2nd line of 32x8
//offset=(((yb<<4)+y)*width)+(nb<<3)+((xb>=(width>>5))?(((xb<<5)-width)+(width<<3)):(xb<<5));
offset=(((yb<<4)+y)*width)+(nb<<3)+(xb<<5)+((xb>=(width>>5))?((width<<3)-width):0);
for (x=0;x<8;x++) {
macroblock[(y<<1)|ybyr][(x<<1)|xbyr]=iPixels[4*(offset+x)];
}
}
}
}
if (IS_JP46) {
for (y=0;y<16;y++) {
offset=((yb<<4)+y)*width+(xb<<4);
for (x=0;x<16;x++) {
macroblock[((y<<1)&0xe)|((y>>3)&0x1)][((x<<1)&0xe)|((x>>3)&0x1)]=iPixels[4*(offset+x)];
}
}
}
for (y=0;y<16;y++) { if (settings.channel!="all"){
offset=width*((yb<<4)+y)+(xb<<4); Elphel.showSingleColorChannel(ctx,settings.channel);
for (x=0;x<16;x++) {
//red +0, green +1, blue +2, alpha +3
// thinking: GRBG
oPixels[4*(offset+x)+0] = ((BayerMosaic[y%2][x%2]=="R" ) )?macroblock[y][x]:0;
oPixels[4*(offset+x)+1] = ((BayerMosaic[y%2][x%2]=="Gr")||(BayerMosaic[y%2][x%2]=="Gb"))?macroblock[y][x]:0;
oPixels[4*(offset+x)+2] = ((BayerMosaic[y%2][x%2]=="B" ) )?macroblock[y][x]:0;
oPixels[4*(offset+x)+3] = 255;
}
} }
}
}
ctx.putImageData(outputImage,0,0);
}
// demosaic GRBG array - bilinear if (settings.diff){
function demosaic_bilinear(ctx){ Elphel.diffColorChannels(PIXELS,settings.chn1,settings.chn2,1);
Elphel.drawImageData(ctx,PIXELS);
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var outputImage = ctx.getImageData(0,0,width,height);
var oPixels = outputImage.data;
var x_l = 0, x_r = 0;
var y_t = 0, y_b = 0;
for(var y=0;y<height;y++){
for(var x=0;x<width;x++){
x_l = (x==0)?1:(x-1);
x_r = (x==(width-1))?(width-2):(x+1);
y_t = (y==0)?1:(y-1);
y_b = (y==(height-1))?(height-2):(y+1);
//Gr
if (BayerMosaic[y%2][x%2]=="Gr"){
oPixels[4*(width*y+x)+0]=1/2*(oPixels[4*(width*(y+0)+(x_l))+0]+oPixels[4*(width*(y+0)+(x_r))+0]);
oPixels[4*(width*y+x)+2]=1/2*(oPixels[4*(width*(y_b)+(x+0))+2]+oPixels[4*(width*(y_t)+(x+0))+2]);
}
//R
if (BayerMosaic[y%2][x%2]=="R"){
oPixels[4*(width*y+x)+1]=1/4*(oPixels[4*(width*(y_t)+(x+0))+1]+oPixels[4*(width*(y+0)+(x_l))+1]+oPixels[4*(width*(y+0)+(x_r))+1]+oPixels[4*(width*(y_b)+(x+0))+1]);
oPixels[4*(width*y+x)+2]=1/4*(oPixels[4*(width*(y_t)+(x_l))+2]+oPixels[4*(width*(y_t)+(x_r))+2]+oPixels[4*(width*(y_b)+(x_l))+2]+oPixels[4*(width*(y_b)+(x_r))+2]);
} }
//B
if (BayerMosaic[y%2][x%2]=="B"){ if (settings.ndvi){
oPixels[4*(width*y+x)+0]=1/4*(oPixels[4*(width*(y_t)+(x_l))+0]+oPixels[4*(width*(y_t)+(x_r))+0]+oPixels[4*(width*(y_b)+(x_l))+0]+oPixels[4*(width*(y_b)+(x_r))+0]); console.log(PIXELS[0]+" "+PIXELS[1]+" "+PIXELS[2]+" "+PIXELS[3]+" ");
oPixels[4*(width*y+x)+1]=1/4*(oPixels[4*(width*(y_t)+(x+0))+1]+oPixels[4*(width*(y+0)+(x_l))+1]+oPixels[4*(width*(y+0)+(x_r))+1]+oPixels[4*(width*(y_b)+(x+0))+1]); PIXELS = Elphel.someIndex(PIXELS);
} console.log(PIXELS[0]+" "+PIXELS[1]+" "+PIXELS[2]+" "+PIXELS[3]+" ");
//Gb Elphel.drawImageData(ctx,PIXELS);
if (BayerMosaic[y%2][x%2]=="Gb"){
oPixels[4*(width*y+x)+0]=1/2*(oPixels[4*(width*(y_t)+(x+0))+0]+oPixels[4*(width*(y_b)+(x+0))+0]);
oPixels[4*(width*y+x)+2]=1/2*(oPixels[4*(width*(y+0)+(x_l))+2]+oPixels[4*(width*(y+0)+(x_r))+2]);
} }
}
}
ctx.putImageData(outputImage,0,0);
}
function saturation(ctx,s){
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var inputImage = ctx.getImageData(0,0,width,height);
var iPixels = inputImage.data;
var r,g,b;
var Y,Cb,Cr;
for(var y=0;y<height;y++){
for(var x=0;x<width;x++){
r = iPixels[4*(width*y+x)+0];
g = iPixels[4*(width*y+x)+1];
b = iPixels[4*(width*y+x)+2];
Y = 0.299*r+0.5870*g+ 0.144*b;
Cb = 128+s*(-0.1687*r-0.3313*g+ 0.500*b);
Cr = 128+s*( 0.5*r-0.4187*g-0.0813*b);
if (Cb<0) Cb=0; if (Cb>255) Cb=255;
if (Cr<0) Cr=0; if (Cr>255) Cr=255;
r = Y + 1.402*(Cr-128);
g = Y - 0.34414*(Cb-128)-0.71414*(Cr-128);
b = Y + 1.772*(Cb-128);
if (r<0) r=0; if (r>255) r=255;
if (g<0) g=0; if (g>255) g=255;
if (b<0) b=0; if (b>255) b=255;
iPixels[4*(width*y+x)+0]=r;
iPixels[4*(width*y+x)+1]=g;
iPixels[4*(width*y+x)+2]=b;
iPixels[4*(width*y+x)+3]=255;
} }
// RGB -> YCbCr x SATURATION -> RGB
// Taking SATURATION[0] = 1/GAMMA[0] (green pixel of GR-line)
//saturation(ctx,SATURATION[0]);
} }
ctx.putImageData(inputImage,0,0); Elphel.drawScaled(cnv_working,cnv_display,settings.width);
console.log(elem.attr("id")+" processing finished, total time: "+(Date.now()-t0)/1000+" s");
$(this).trigger("canvas_ready");
//get_image();
} }
});
function parseEXIFMakerNote(src){ }
var exif_orientation = EXIF.getTag(src,"Orientation"); function quickestPreview(ctx){
Elphel.reorderJP4Blocks(ctx,"JP4",settings.mosaic,settings.fast);
//console.log("Exif:Orientation: "+exif_orientation); }
var MakerNote = EXIF.getTag(src,"MakerNote");
//FLIPH & FLIPV
if (typeof MakerNote !== 'undefined'){
FLIPH = (MakerNote[10] )&0x1;
FLIPV = (MakerNote[10]>>1)&0x1;
var tmpBayerMosaic = Array();
for (var i=0;i<BayerMosaic.length;i++){tmpBayerMosaic[i] = BayerMosaic[i].slice();}
if (FLIPV==1){ function parseEXIFMakerNote(src){
for(i=0;i<4;i++){BayerMosaic[(i>>1)][(i%2)] = tmpBayerMosaic[1-(i>>1)][(i%2)];}
for(i=0;i<BayerMosaic.length;i++){tmpBayerMosaic[i] = BayerMosaic[i].slice();} var exif_orientation = EXIF.getTag(src,"Orientation");
}
if (FLIPH==1){ //console.log("Exif:Orientation: "+exif_orientation);
for(i=0;i<4;i++){BayerMosaic[(i>>1)][(i%2)] = tmpBayerMosaic[(i>>1)][1-(i%2)];}
} var MakerNote = EXIF.getTag(src,"MakerNote");
}
//FLIPH & FLIPV
//console.log("MakerNote: Flips: V:"+FLIPV+" H:"+FLIPH); if (typeof MakerNote !== 'undefined'){
FLIPH = (MakerNote[10] )&0x1;
//COLOR_MODE ---------------------------------------------------------------- FLIPV = (MakerNote[10]>>1)&0x1;
if (typeof MakerNote !== 'undefined') COLOR_MODE=(MakerNote[10]>>4)&0x0f;
switch(COLOR_MODE){ var tmpBAYER = Array();
case 2: IS_JP46 = true; break; for (var i=0;i<BAYER.length;i++){tmpBAYER[i] = BAYER[i].slice();}
case 5: IS_JP4 = true; break;
//default: if (FLIPV==1){
} for(i=0;i<4;i++){BAYER[(i>>1)][(i%2)] = tmpBAYER[1-(i>>1)][(i%2)];}
for(i=0;i<BAYER.length;i++){tmpBAYER[i] = BAYER[i].slice();}
//var gains = Array(); }
//var blacks = Array(); if (FLIPH==1){
var gammas = Array(); for(i=0;i<4;i++){BAYER[(i>>1)][(i%2)] = tmpBAYER[(i>>1)][1-(i%2)];}
//var gamma_scales = Array(); }
//var blacks256 = Array(); }
//var rgammas = Array();
//console.log("MakerNote: Flips: V:"+FLIPV+" H:"+FLIPH);
//SATURATION ---------------------------------------------------------------- //COLOR_MODE ----------------------------------------------------------------
if (typeof MakerNote !== 'undefined'){ var color_mode = 0;
for(i=0;i<4;i++){ if (typeof MakerNote !== 'undefined') color_mode=(MakerNote[10]>>4)&0x0f;
//gains[i]= MakerNote[i]/65536.0;
//blacks[i]=(MakerNote[i+4]>>24)/256.0; switch(color_mode){
gammas[i]=((MakerNote[i+4]>>16)&0xff)/100.0; case 2: IMAGE_FORMAT = "JP46"; break;
//gamma_scales[i]=MakerNote[i+4] & 0xffff; case 5: IMAGE_FORMAT = "JP4"; break;
} //default:
/* }
for (i=0;i<4;i++) {
rgammas[i]=elphel_gamma_calc(gammas[i], blacks[i], gamma_scales[i]); //var gains = Array();
} //var blacks = Array();
console.log(rgammas); var gammas = Array();
//adjusting gains to have the result picture in the range 0..256 //var gamma_scales = Array();
min_gain=2.0*gains[0]; //var blacks256 = Array();
for (i=0;i<4;i++){ //var rgammas = Array();
if (min_gain > (gains[i]*(1.0-blacks[i]))) min_gain = gains[i]*(1.0-blacks[i]);
}
for (i=0;i<4;i++) gains[i]/=min_gain; //SATURATION ----------------------------------------------------------------
for (i=0;i<4;i++) blacks256[i]=256.0*blacks[i]; if (typeof MakerNote !== 'undefined'){
*/ for(i=0;i<4;i++){
for (i=0;i<4;i++) SATURATION[i] = 1/gammas[i]; //gains[i]= MakerNote[i]/65536.0;
//console.log("MakerNote: Saturations: "+SATURATION[0]+" "+SATURATION[1]+" "+SATURATION[2]+" "+SATURATION[3]); //blacks[i]=(MakerNote[i+4]>>24)/256.0;
} gammas[i]=((MakerNote[i+4]>>16)&0xff)/100.0;
//gamma_scales[i]=MakerNote[i+4] & 0xffff;
} }
/* /*
function elphel_gamma_calc(gamma,black,gamma_scale){ for (i=0;i<4;i++) {
rgammas[i]=elphel_gamma_calc(gammas[i], blacks[i], gamma_scales[i]);
gtable = Array(); }
rgtable = Array(); console.log(rgammas);
//adjusting gains to have the result picture in the range 0..256
black256=black*256.0; min_gain=2.0*gains[0];
k=1.0/(256.0-black256); for (i=0;i<4;i++){
if (gamma < 0.13) gamma=0.13; if (min_gain > (gains[i]*(1.0-blacks[i]))) min_gain = gains[i]*(1.0-blacks[i]);
if (gamma >10.0) gamma=10.0;
for (var i=0;i<257;i++) {
x=k*(i-black256);
if (x<0.0) x=0.0;
ig = 0.5+65535.0*Math.pow(x,gamma);
ig = (ig*gamma_scale)/0x400;
if (ig>0xffff) ig=0xffff;
gtable[i]=ig;
}
// now gtable[] is the same as was used in the camera
// FPGA was using linear interpolation between elements of the gamma table, so now we'll reverse that process
indx=0;
for (i=0;i<256;i++) {
outValue=128+(i<<8);
while ((gtable[indx+1]<outValue) && (indx<256)) indx++;
if (indx>=256) rgtable[i]=65535.0/256;
else if (gtable[indx+1]==gtable[indx])
rgtable[i]=i;
else
rgtable[i]=indx+(1.0*(outValue-gtable[indx]))/(gtable[indx+1] - gtable[indx]);
}
return rgtable;
} }
for (i=0;i<4;i++) gains[i]/=min_gain;
for (i=0;i<4;i++) blacks256[i]=256.0*blacks[i];
*/ */
for (i=0;i<4;i++) {
//SATURATION[i] = 1/gammas[i];
//SATURATION[i] = 1.75; //nightmate time
SATURATION[i] = 2;
}
//console.log("MakerNote: Saturations: "+SATURATION[0]+" "+SATURATION[1]+" "+SATURATION[2]+" "+SATURATION[3]);
}
}
/*
function elphel_gamma_calc(gamma,black,gamma_scale){
gtable = Array();
rgtable = Array();
black256=black*256.0;
k=1.0/(256.0-black256);
if (gamma < 0.13) gamma=0.13;
if (gamma >10.0) gamma=10.0;
for (var i=0;i<257;i++) {
x=k*(i-black256);
if (x<0.0) x=0.0;
ig = 0.5+65535.0*Math.pow(x,gamma);
ig = (ig*gamma_scale)/0x400;
if (ig>0xffff) ig=0xffff;
gtable[i]=ig;
}
// now gtable[] is the same as was used in the camera
// FPGA was using linear interpolation between elements of the gamma table, so now we'll reverse that process
indx=0;
for (i=0;i<256;i++) {
outValue=128+(i<<8);
while ((gtable[indx+1]<outValue) && (indx<256)) indx++;
if (indx>=256) rgtable[i]=65535.0/256;
else if (gtable[indx+1]==gtable[indx])
rgtable[i]=i;
else
rgtable[i]=indx+(1.0*(outValue-gtable[indx]))/(gtable[indx+1] - gtable[indx]);
}
return rgtable;
}
*/
};
$.fn.jp4 = function(options){
var element = $(this);
return this; // Return early if this element already has a plugin instance
}; if (element.data('jp4')) return element.data('jp4');
}( jQuery )); var jp4 = new JP4(this,options);
element.data('jp4',jp4);
var res = new Object();
res.cnv = element;
res.data = jp4;
return res;
};
}(jQuery));
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment