Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
J
jp4-canvas
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Elphel
jp4-canvas
Commits
3b893d52
Commit
3b893d52
authored
Jul 08, 2015
by
Oleg Dzhimiev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1.Saturation 2.Flips
parent
3b57e648
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
141 additions
and
31 deletions
+141
-31
README.md
README.md
+10
-1
jp4-canvas.js
jp4-canvas.js
+131
-30
No files found.
README.md
View file @
3b893d52
...
...
@@ -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
)
...
...
jp4-canvas.js
View file @
3b893d52
...
...
@@ -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
b
ayerMosaic
=
[
"Gr"
,
"R"
,
"B"
,
"Gb"
var
B
ayerMosaic
=
[
[
"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;
}
*/
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment