Reputation: 163
I have the following function to brighten/darken an image.
<script>
brightness = function(delta) {
return function (pixels, args) {
var d = pixels.data;
for (var i = 0; i < d.length; i += 4) {
d[i] += delta; // red
d[i + 1] += delta; // green
d[i + 2] += delta; // blue
}
return pixels;
};
};
</script>
Just want to try out something more interesting, is it possible that I can perform auto enhancement to an image? What I mean is to brighten/darken only certain area in a photo. How should I go about detecting if the pixel is dark for example, then I should brighten it slightly? Thanks.
Upvotes: 1
Views: 2759
Reputation: 19294
What you want to do is to modify the image dynamic.
So you must decide, for normalized pixel's luminosity ranging from 0.0 to 1.0, of a transform function that you will apply on the luminosity of each pixel.
The function you seek will have to enhance lower luminosity (near 0), and keep quite the same luminosity for high luminosity (near 1).
Here's an example of a (typicall) transfert function :
So you'll want a gamma > 1.
You see here, for instance, that if the input luminosity is 0.2, the output luminosity is 0.45 , that is more than twice the original value.
for 0.8 input, we have 0.95 value, a 20% increase.
To change only the luminosity without changing the perceived color, the simplest solution i see is to use another colorspace, like hsl.
With h,s,l, you represent a color in a way that is meaningfull for the human eye :
h is hue : the 'color',
saturation is the color 'strength',
and l is ... the luminosity.
So the steps are :
for each pixel
compute h,s,l of the pixel out of its r,g,b
apply one transform function on luminosity
a good one is : new l = Math.pow(l, 1 / gamma);
compute new (r,g,b) out of (h, s, new l)
write those values.
i made a fiddle to illustrate :
fiddle result : http://jsfiddle.net/gamealchemist/yqvmC/embedded/result/
fiddle itself : http://jsfiddle.net/gamealchemist/yqvmC/
Edit : here is a modified version, that takes an image as input, and returns an image.
The parameter can either be a gamma value (number), or the transform function
that you like.
I added a gamma compress function for the example. You can see on the result
(scroll down to see it), that compressing is quite harsh : all luminosity values
get centered around ratio*(max-min), which makes the image very readable, but with a low contrast.
Here is the code :
// pow is the power of the function
// min is min value returned
// max is max value returned.
function gamma(pow, min, max, x) {
return min + Math.pow(x, 1 / pow) * (max - min);
}
// pow is the 'gamma' used for both part of the curves
// min is the minimum value returned / max the max
// center is the luminosity where we stop reducing and start expanding
// ratio states where reduced luminosity should lay between min and max.
function gammaCompress(pow, min, max, center, ratio, x) {
var xr = 0;
if (x < center) {
xr = x / center;
return min + Math.pow(xr, 1 / pow) * (max - min) * ratio;
} else {
xr = (x - center) / (1 - center);
return min + (max - min) * ratio + Math.pow(xr, 1 / pow) * (max - min) * (1 - ratio);
}
}
function getEnligthedImage(sourceImage, transform) {
// if a number, not a bound transform function, was provided,
// assume it's a gamma targetting [0;1]
if (typeof transform != 'function') {
transform = gamma.bind(null, transform, 0, 1);
}
var tgtCv = document.createElement('canvas');
tgtCv.width = sourceImage.width;
tgtCv.height = sourceImage.height;
var context = tgtCv.getContext('2d');
context.drawImage(img, 0, 0);
var imgData = context.getImageData(0, 0, canvasWidth, canvasHeight);
var pix = imgData.data;
var hslValue = { h: 0, s: 0, l: 0 };
var rgbValue = { r: 0, g: 0, b: 0 };
for (var i = 0; i < pix.length; i += 4) {
rgbToHsl(pix[i], pix[i + 1], pix[i + 2], hslValue);
hslValue.l = transform(hslValue.l);
hslToRgb(hslValue.h, hslValue.s, hslValue.l, rgbValue);
pix[i] = rgbValue.r;
pix[i + 1] = rgbValue.g;
pix[i + 2] = rgbValue.b;
}
context.putImageData(imgData, 0, 0);
var newImage = new Image();
newImage.src = tgtCv.toDataURL("image/png");
return newImage;
}
var result = getEnligthedImage(img, 1.6);
var pr = document.createElement('div');
pr.innerHTML = 'example for a gamma 1.6'
document.body.appendChild(pr);
document.body.appendChild(result);
var compressor = gammaCompress.bind(null, 1.4, 0.2, 1.0, 0.5, 0.5);
var compressedResult = getEnligthedImage(img, compressor);
pr = document.createElement('div');
pr.innerHTML = 'example using a gamma compressor. min is 0.2'
document.body.appendChild(pr);
document.body.appendChild(compressedResult);
If you want to do some other things with the image, save to file, send to server, or like, search google :-) , this link might help :
https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
Upvotes: 3
Reputation: 4715
Simple function for auto adjust colors. Will brighten or darken image based on histogram.
Brighten/darken only certain area in a photo is hard, because it is difficult to join changed and unchanged areas.
Sorry no fiddle, i have problems with images here.
<img alt="" src="bright.png" />
<br /><br />
<canvas id="cc"></canvas>
<script>
var img = new Image();
img.src = 'bright.png';
img.onload = function(){
var canvas = document.getElementById("cc");
var ctx = canvas.getContext("2d");
canvas.width=300;
canvas.height=200;
ctx.drawImage(img, 0, 0);
auto_adjust(ctx, 300, 200);
}
function auto_adjust(context, W, H){
//settings
var white = 240; //white color min
var black = 30; //black color max
var target_white = 1; //how much % white colors should take
var target_black = 0.5; //how much % black colors should take
var modify = 1.1; //color modify strength
var img = context.getImageData(0, 0, W, H);
var imgData = img.data;
var n = 0; //pixels count without transparent
//make sure we have white
var n_valid = 0;
for(var i = 0; i < imgData.length; i += 4){
if(imgData[i+3] == 0) continue; //transparent
if((imgData[i] + imgData[i+1] + imgData[i+2]) / 3 > white) n_valid++;
n++;
}
target = target_white;
var n_fix_white = 0;
var done = false;
for(var j=0; j < 30; j++){
if(n_valid * 100 / n >= target) done = true;
if(done == true) break;
n_fix_white++;
//adjust
for(var i = 0; i < imgData.length; i += 4){
if(imgData[i+3] == 0) continue; //transparent
for(var c = 0; c < 3; c++){
var x = i + c;
if(imgData[x] < 10) continue;
//increase white
imgData[x] *= modify;
imgData[x] = Math.round(imgData[x]);
if(imgData[x] > 255) imgData[x] = 255;
}
}
//recheck
n_valid = 0;
for(var i = 0; i < imgData.length; i += 4){
if(imgData[i+3] == 0) continue; //transparent
if((imgData[i] + imgData[i+1] + imgData[i+2]) / 3 > white) n_valid++;
}
}
//make sure we have black
n_valid = 0;
for(var i = 0; i < imgData.length; i += 4){
if(imgData[i+3] == 0) continue; //transparent
if((imgData[i] + imgData[i+1] + imgData[i+2]) / 3 < black) n_valid++;
}
target = target_black;
var n_fix_black = 0;
var done = false;
for(var j=0; j < 30; j++){
if(n_valid * 100 / n >= target) done = true;
if(done == true) break;
n_fix_black++;
//adjust
for(var i = 0; i < imgData.length; i += 4){
if(imgData[i+3] == 0) continue; //transparent
for(var c = 0; c < 3; c++){
var x = i + c;
if(imgData[x] > 240) continue;
//increase black
imgData[x] -= (255-imgData[x]) * modify - (255-imgData[x]);
imgData[x] = Math.round(imgData[x]);
}
}
//recheck
n_valid = 0;
for(var i = 0; i < imgData.length; i += 4){
if(imgData[i+3] == 0) continue; //transparent
if((imgData[i] + imgData[i+1] + imgData[i+2]) / 3 < black) n_valid++;
}
}
//save
context.putImageData(img, 0, 0);
//log('Iterations: brighten='+n_fix_white+", darken="+n_fix_black);
}
</script>
Upvotes: 0