Commit 3b893d52 authored by Oleg Dzhimiev's avatar Oleg Dzhimiev

1.Saturation 2.Flips

parent 3b57e648
......@@ -4,6 +4,13 @@ A template<br/>
Convert JP4/JP46 image files (\*.jp4/\*.jp46) into human perceivable format.<br/>
This includes reordering within JPEG blocks and demosaicing (bilinear interpolation).<br/>
Files in JPEG format are left untouched.
Operations:
* Read EXIF MakerNote field: color mode, flips, gammas.
* Reorder Bayer Mosaic according to flips - initial mosaic=GRBG
* (if JP4/JP46) Reorder pixels back from 8x32 blocks to 16x16 macroblocks
* Demosaic (bilinear)
* RGB > YCbCr > Apply Saturation (=1/Gamma) > RGB
## Usage
Replace <i>test.jp4</i>
......@@ -15,7 +22,9 @@ Replace <i>test.jp4</i>
## EXIF
In Elphel cameras extra information is added to the EXIF header's
<i>MakerNote</i> field:
> COLOR_MODE = (MakerNote[10]>> 4) & 0x0f;
> COLOR_MODE = (MakerNote[10]>>4) & 0x0f;
> FLIPH = (MakerNote[10] ) & 0x1;
> FLIPV = (MakerNote[10]>>1) & 0x1;
## More info:
* [About JP4](http://wiki.elphel.com/index.php?title=JP4)
......
......@@ -7,18 +7,21 @@ LICENSE: AGPL, see http://www.gnu.org/licenses/agpl.txt
Copyright (C) 2015 Elphel, Inc.
*/
var FLIPV = 0;
var FLIPH = 0;
var COLOR_MODE = 0;
var IS_JP4 = false;
var IS_JP46 = false;
var SATURATION = [0,0,0,0]; //will be set as 1/GAMMA[i] - in fact it's the same for all pixels.
//GRBG
var bayerMosaic = [
"Gr","R",
"B" ,"Gb"
var BayerMosaic = [
["Gr","R"],
["B" ,"Gb"]
];
var SATURATION=2.0;
$(function(){
initCanvas();
});
......@@ -42,15 +45,7 @@ function initCanvas(){
canvas.attr("width",this.width);
canvas.attr("height",this.height);
var MakerNote = EXIF.getTag(this,"MakerNote");
if (typeof MakerNote !== 'undefined') COLOR_MODE=(MakerNote[10]>>4)&0x0f;
switch(COLOR_MODE){
case 2: IS_JP46 = true; break;
case 5: IS_JP4 = true; break;
//default:
}
parseEXIFMakerNote(this);
canvas.drawImage({
x:0, y:0,
......@@ -69,8 +64,9 @@ function redraw(){
if (IS_JP4||IS_JP46){
pixelBlocksReorder(ctx);
demosaic_bilinear(ctx);
//convert to YCbCr to apply some saturation
saturation(ctx);
// RGB > YCbCr x SATURATION > RGB
// Taking SATURATION[0] = 1/GAMMA[0] (green pixel of GR-line)
saturation(ctx,SATURATION[0]);
}
}
});
......@@ -136,10 +132,9 @@ function pixelBlocksReorder(ctx){
for (x=0;x<16;x++) {
//red +0, green +1, blue +2, alpha +3
// thinking: GRBG
bi = 2*(y%2)+x%2;
oPixels[4*(offset+x)+0] = ((bayerMosaic[bi]=="R" ) )?macroblock[y][x]:0;
oPixels[4*(offset+x)+1] = ((bayerMosaic[bi]=="Gr")||(bayerMosaic[bi]=="Gb"))?macroblock[y][x]:0;
oPixels[4*(offset+x)+2] = ((bayerMosaic[bi]=="B" ) )?macroblock[y][x]:0;
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;
}
}
......@@ -162,28 +157,27 @@ function demosaic_bilinear(ctx){
for(var y=0;y<height;y++){
for(var x=0;x<width;x++){
bi = 2*(y%2)+x%2;
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[bi]=="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[bi]=="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[bi]=="B"){
if (BayerMosaic[y%2][x%2]=="B"){
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]);
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]);
}
//Gb
if (bayerMosaic[bi]=="Gb"){
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]);
}
......@@ -192,7 +186,7 @@ function demosaic_bilinear(ctx){
ctx.putImageData(outputImage,0,0);
}
function saturation(ctx){
function saturation(ctx,s){
var width = ctx.canvas.width;
var height = ctx.canvas.height;
......@@ -205,14 +199,14 @@ function saturation(ctx){
for(var y=0;y<height;y++){
for(var x=0;x<width;x++){
//don't forget to multiply?
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+SATURATION*(-0.1687*r-0.3313*g+ 0.500*b);
Cr = 128+SATURATION*( 0.5*r-0.4187*g-0.0813*b);
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;
......@@ -233,3 +227,110 @@ function saturation(ctx){
}
ctx.putImageData(inputImage,0,0);
}
function parseEXIFMakerNote(src){
var exif_orientation = EXIF.getTag(src,"Orientation");
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){
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();}
}
if (FLIPH==1){
for(i=0;i<4;i++){BayerMosaic[(i>>1)][(i%2)] = tmpBayerMosaic[(i>>1)][1-(i%2)];}
}
}
console.log("MakerNote: Flips: V:"+FLIPV+" H:"+FLIPH);
//COLOR_MODE ----------------------------------------------------------------
if (typeof MakerNote !== 'undefined') COLOR_MODE=(MakerNote[10]>>4)&0x0f;
switch(COLOR_MODE){
case 2: IS_JP46 = true; break;
case 5: IS_JP4 = true; break;
//default:
}
//var gains = Array();
//var blacks = Array();
var gammas = Array();
//var gamma_scales = Array();
//var blacks256 = Array();
//var rgammas = Array();
//SATURATION ----------------------------------------------------------------
if (typeof MakerNote !== 'undefined'){
for(i=0;i<4;i++){
//gains[i]= MakerNote[i]/65536.0;
//blacks[i]=(MakerNote[i+4]>>24)/256.0;
gammas[i]=((MakerNote[i+4]>>16)&0xff)/100.0;
//gamma_scales[i]=MakerNote[i+4] & 0xffff;
}
/*
for (i=0;i<4;i++) {
rgammas[i]=elphel_gamma_calc(gammas[i], blacks[i], gamma_scales[i]);
}
console.log(rgammas);
//adjusting gains to have the result picture in the range 0..256
min_gain=2.0*gains[0];
for (i=0;i<4;i++){
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;
for (i=0;i<4;i++) blacks256[i]=256.0*blacks[i];
*/
for (i=0;i<4;i++) SATURATION[i] = 1/gammas[i];
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;
}
*/
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