| Index: chrome/test/data/kraken/hosted/explanations/darkroom.js
|
| diff --git a/chrome/test/data/kraken/hosted/explanations/darkroom.js b/chrome/test/data/kraken/hosted/explanations/darkroom.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..001c0544bfda573bcdf1bc5eff81dabee0cdc411
|
| --- /dev/null
|
| +++ b/chrome/test/data/kraken/hosted/explanations/darkroom.js
|
| @@ -0,0 +1,571 @@
|
| +/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40; -*- */
|
| +
|
| +// The if (0) block of function definitions here tries to use
|
| +// faster math primitives, based on being able to reinterpret
|
| +// floats as ints and vice versa. We do that using the
|
| +// WebGL arrays.
|
| +
|
| +if (0) {
|
| +
|
| +var gConversionBuffer = new ArrayBuffer(4);
|
| +var gFloatConversion = new WebGLFloatArray(gConversionBuffer);
|
| +var gIntConversion = new WebGLIntArray(gConversionBuffer);
|
| +
|
| +function AsFloat(i) {
|
| + gIntConversion[0] = i;
|
| + return gFloatConversion[0];
|
| +}
|
| +
|
| +function AsInt(f) {
|
| + gFloatConversion[0] = f;
|
| + return gIntConversion[0];
|
| +}
|
| +
|
| +// magic constants used for various floating point manipulations
|
| +var kMagicFloatToInt = (1 << 23);
|
| +var kOneAsInt = 0x3F800000;
|
| +var kScaleUp = AsFloat(0x00800000);
|
| +var kScaleDown = 1.0 / kScaleUp;
|
| +
|
| +function ToInt(f) {
|
| + // force integer part into lower bits of mantissa
|
| + var i = ReinterpretFloatAsInt(f + kMagicFloatToInt);
|
| + // return lower bits of mantissa
|
| + return i & 0x3FFFFF;
|
| +}
|
| +
|
| +function FastLog2(x) {
|
| + return (AsInt(x) - kOneAsInt) * kScaleDown;
|
| +}
|
| +
|
| +function FastPower(x, p) {
|
| + return AsFloat(p * AsInt(x) + (1.0 - p) * kOneAsInt);
|
| +}
|
| +
|
| +var LOG2_HALF = FastLog2(0.5);
|
| +
|
| +function FastBias(b, x) {
|
| + return FastPower(x, FastLog2(b) / LOG2_HALF);
|
| +}
|
| +
|
| +} else {
|
| +
|
| +function FastLog2(x) {
|
| + return Math.log(x) / Math.LN2;
|
| +}
|
| +
|
| +var LOG2_HALF = FastLog2(0.5);
|
| +
|
| +function FastBias(b, x) {
|
| + return Math.pow(x, FastLog2(b) / LOG2_HALF);
|
| +}
|
| +
|
| +}
|
| +
|
| +function FastGain(g, x) {
|
| + return (x < 0.5) ?
|
| + FastBias(1.0 - g, 2.0 * x) * 0.5 :
|
| + 1.0 - FastBias(1.0 - g, 2.0 - 2.0 * x) * 0.5;
|
| +}
|
| +
|
| +function Clamp(x) {
|
| + return (x < 0.0) ? 0.0 : ((x > 1.0) ? 1.0 : x);
|
| +}
|
| +
|
| +function ProcessImageData(imageData, params) {
|
| + var saturation = params.saturation;
|
| + var contrast = params.contrast;
|
| + var brightness = params.brightness;
|
| + var blackPoint = params.blackPoint;
|
| + var fill = params.fill;
|
| + var temperature = params.temperature;
|
| + var shadowsHue = params.shadowsHue;
|
| + var shadowsSaturation = params.shadowsSaturation;
|
| + var highlightsHue = params.highlightsHue;
|
| + var highlightsSaturation = params.highlightsSaturation;
|
| + var splitPoint = params.splitPoint;
|
| +
|
| + var brightness_a, brightness_b;
|
| + var oo255 = 1.0 / 255.0;
|
| +
|
| + // do some adjustments
|
| + fill *= 0.2;
|
| + brightness = (brightness - 1.0) * 0.75 + 1.0;
|
| + if (brightness < 1.0) {
|
| + brightness_a = brightness;
|
| + brightness_b = 0.0;
|
| + } else {
|
| + brightness_b = brightness - 1.0;
|
| + brightness_a = 1.0 - brightness_b;
|
| + }
|
| + contrast = contrast * 0.5;
|
| + contrast = (contrast - 0.5) * 0.75 + 0.5;
|
| + temperature = (temperature / 2000.0) * 0.1;
|
| + if (temperature > 0.0) temperature *= 2.0;
|
| + splitPoint = ((splitPoint + 1.0) * 0.5);
|
| +
|
| + // apply to pixels
|
| + var sz = imageData.width * imageData.height;
|
| + var data = imageData.data;
|
| + for (var j = 0; j < sz; j++) {
|
| + var r = data[j*4+0] * oo255;
|
| + var g = data[j*4+1] * oo255;
|
| + var b = data[j*4+2] * oo255;
|
| + // convert RGB to YIQ
|
| + // this is a less than ideal colorspace;
|
| + // HSL would probably be better, but more expensive
|
| + var y = 0.299 * r + 0.587 * g + 0.114 * b;
|
| + var i = 0.596 * r - 0.275 * g - 0.321 * b;
|
| + var q = 0.212 * r - 0.523 * g + 0.311 * b;
|
| + i = i + temperature;
|
| + q = q - temperature;
|
| + i = i * saturation;
|
| + q = q * saturation;
|
| + y = (1.0 + blackPoint) * y - blackPoint;
|
| + y = y + fill;
|
| + y = y * brightness_a + brightness_b;
|
| + y = FastGain(contrast, Clamp(y));
|
| +
|
| + if (y < splitPoint) {
|
| + q = q + (shadowsHue * shadowsSaturation) * (splitPoint - y);
|
| + } else {
|
| + i = i + (highlightsHue * highlightsSaturation) * (y - splitPoint);
|
| + }
|
| +
|
| + // convert back to RGB for display
|
| + r = y + 0.956 * i + 0.621 * q;
|
| + g = y - 0.272 * i - 0.647 * q;
|
| + b = y - 1.105 * i + 1.702 * q;
|
| +
|
| + // clamping is "free" as part of the ImageData object
|
| + data[j*4+0] = r * 255.0;
|
| + data[j*4+1] = g * 255.0;
|
| + data[j*4+2] = b * 255.0;
|
| + }
|
| +}
|
| +
|
| +//
|
| +// UI code
|
| +//
|
| +
|
| +var gFullCanvas = null;
|
| +var gFullContext = null;
|
| +var gFullImage = null;
|
| +var gDisplayCanvas = null;
|
| +var gDisplayContext = null;
|
| +var gZoomPoint = null;
|
| +var gDisplaySize = null;
|
| +var gZoomSize = [600, 600];
|
| +var gMouseStart = null;
|
| +var gMouseOrig = [0, 0];
|
| +var gDirty = true;
|
| +
|
| +// If true, apply image correction to the original
|
| +// source image before scaling down; if false,
|
| +// scale down first.
|
| +var gCorrectBefore = false;
|
| +
|
| +var gParams = null;
|
| +var gIgnoreChanges = true;
|
| +
|
| +function OnSliderChanged() {
|
| + if (gIgnoreChanges)
|
| + return;
|
| +
|
| + gDirty = true;
|
| +
|
| + gParams = {};
|
| +
|
| + // The values will come in as 0.0 .. 1.0; some params want
|
| + // a different range.
|
| + var ranges = {
|
| + "saturation": [0, 2],
|
| + "contrast": [0, 2],
|
| + "brightness": [0, 2],
|
| + "temperature": [-2000, 2000],
|
| + "splitPoint": [-1, 1]
|
| + };
|
| +
|
| + $(".slider").each(function(index, e) {
|
| + var val = Math.floor($(e).slider("value")) / 1000.0;
|
| + var id = e.getAttribute("id");
|
| + if (id in ranges)
|
| + val = val * (ranges[id][1] - ranges[id][0]) + ranges[id][0];
|
| + gParams[id] = val;
|
| + });
|
| +
|
| + Redisplay();
|
| +}
|
| +
|
| +function ClampZoomPointToTranslation() {
|
| + var tx = gZoomPoint[0] - gZoomSize[0]/2;
|
| + var ty = gZoomPoint[1] - gZoomSize[1]/2;
|
| + tx = Math.max(0, tx);
|
| + ty = Math.max(0, ty);
|
| +
|
| + if (tx + gZoomSize[0] > gFullImage.width)
|
| + tx = gFullImage.width - gZoomSize[0];
|
| + if (ty + gZoomSize[1] > gFullImage.height)
|
| + ty = gFullImage.height - gZoomSize[1];
|
| + return [tx, ty];
|
| +}
|
| +
|
| +function Redisplay() {
|
| + if (!gParams)
|
| + return;
|
| +
|
| + var angle =
|
| + (gParams.angle*2.0 - 1.0) * 90.0 +
|
| + (gParams.fineangle*2.0 - 1.0) * 2.0;
|
| +
|
| + angle = Math.max(-90, Math.min(90, angle));
|
| + angle = (angle * Math.PI) / 180.0;
|
| +
|
| + var processTime;
|
| + var processWidth, processHeight;
|
| +
|
| + var t0 = (new Date()).getTime();
|
| +
|
| + // Render the image with rotation; we only need to render
|
| + // if we're either correcting just the portion that's visible,
|
| + // or if we're correcting the full thing and the sliders have been
|
| + // changed. Otherwise, what's in the full canvas is already corrected
|
| + // and correct.
|
| + if ((gCorrectBefore && gDirty) ||
|
| + !gCorrectBefore)
|
| + {
|
| + gFullContext.save();
|
| + gFullContext.translate(Math.floor(gFullImage.width / 2), Math.floor(gFullImage.height / 2));
|
| + gFullContext.rotate(angle);
|
| + gFullContext.globalCompositeOperation = "copy";
|
| + gFullContext.drawImage(gFullImage,
|
| + -Math.floor(gFullImage.width / 2),
|
| + -Math.floor(gFullImage.height / 2));
|
| + gFullContext.restore();
|
| + }
|
| +
|
| + function FullToDisplay() {
|
| + gDisplayContext.save();
|
| + if (gZoomPoint) {
|
| + var pt = ClampZoomPointToTranslation();
|
| +
|
| + gDisplayContext.translate(-pt[0], -pt[1]);
|
| + } else {
|
| + gDisplayContext.translate(0, 0);
|
| + var ratio = gDisplaySize[0] / gFullCanvas.width;
|
| + gDisplayContext.scale(ratio, ratio);
|
| + }
|
| +
|
| + gDisplayContext.globalCompositeOperation = "copy";
|
| + gDisplayContext.drawImage(gFullCanvas, 0, 0);
|
| + gDisplayContext.restore();
|
| + }
|
| +
|
| + function ProcessCanvas(cx, canvas) {
|
| + var ts = (new Date()).getTime();
|
| +
|
| + var data = cx.getImageData(0, 0, canvas.width, canvas.height);
|
| + ProcessImageData(data, gParams);
|
| + cx.putImageData(data, 0, 0);
|
| +
|
| + processWidth = canvas.width;
|
| + processHeight = canvas.height;
|
| +
|
| + processTime = (new Date()).getTime() - ts;
|
| + }
|
| +
|
| + if (gCorrectBefore) {
|
| + if (gDirty) {
|
| + ProcessCanvas(gFullContext, gFullCanvas);
|
| + } else {
|
| + processTime = -1;
|
| + }
|
| + gDirty = false;
|
| + FullToDisplay();
|
| + } else {
|
| + FullToDisplay();
|
| + ProcessCanvas(gDisplayContext, gDisplayCanvas);
|
| + }
|
| +
|
| + var t3 = (new Date()).getTime();
|
| +
|
| + if (processTime != -1) {
|
| + $("#log")[0].innerHTML = "<p>" +
|
| + "Size: " + processWidth + "x" + processHeight + " (" + (processWidth*processHeight) + " pixels)<br>" +
|
| + "Process: " + processTime + "ms" + " Total: " + (t3-t0) + "ms<br>" +
|
| + "Throughput: " + Math.floor((processWidth*processHeight) / (processTime / 1000.0)) + " pixels per second<br>" +
|
| + "FPS: " + (Math.floor((1000.0 / (t3-t0)) * 100) / 100) + "<br>" +
|
| + "</p>";
|
| + } else {
|
| + $("#log")[0].innerHTML = "<p>(No stats when zoomed and no processing done)</p>";
|
| + }
|
| +}
|
| +
|
| +function ZoomToPoint(x, y) {
|
| + if (gZoomSize[0] > gFullImage.width ||
|
| + gZoomSize[1] > gFullImage.height)
|
| + return;
|
| +
|
| + var r = gDisplaySize[0] / gFullCanvas.width;
|
| +
|
| + gDisplayCanvas.width = gZoomSize[0];
|
| + gDisplayCanvas.height = gZoomSize[1];
|
| + gZoomPoint = [x/r, y/r];
|
| + $("#canvas").removeClass("canzoomin").addClass("cangrab");
|
| + Redisplay();
|
| +}
|
| +
|
| +function ZoomReset() {
|
| + gDisplayCanvas.width = gDisplaySize[0];
|
| + gDisplayCanvas.height = gDisplaySize[1];
|
| + gZoomPoint = null;
|
| + $("#canvas").removeClass("canzoomout cangrab isgrabbing").addClass("canzoomin");
|
| + Redisplay();
|
| +}
|
| +
|
| +function LoadImage(url) {
|
| + if (!gFullCanvas)
|
| + gFullCanvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
| + if (!gDisplayCanvas)
|
| + gDisplayCanvas = $("#canvas")[0];
|
| +
|
| + var img = new Image();
|
| + img.onload = function() {
|
| + var w = img.width;
|
| + var h = img.height;
|
| +
|
| + gFullImage = img;
|
| +
|
| + gFullCanvas.width = w;
|
| + gFullCanvas.height = h;
|
| + gFullContext = gFullCanvas.getContext("2d");
|
| +
|
| + // XXX use the actual size of the visible region, so that
|
| + // we rescale along with the window
|
| + var dim = 600;
|
| + if (Math.max(w,h) > dim) {
|
| + var scale = dim / Math.max(w,h);
|
| + w *= scale;
|
| + h *= scale;
|
| + }
|
| +
|
| + gDisplayCanvas.width = Math.floor(w);
|
| + gDisplayCanvas.height = Math.floor(h);
|
| + gDisplaySize = [ Math.floor(w), Math.floor(h) ];
|
| + gDisplayContext = gDisplayCanvas.getContext("2d");
|
| +
|
| + $("#canvas").removeClass("canzoomin canzoomout cangrab isgrabbing");
|
| +
|
| + if (gZoomSize[0] <= gFullImage.width &&
|
| + gZoomSize[1] <= gFullImage.height)
|
| + {
|
| + $("#canvas").addClass("canzoomin");
|
| + }
|
| +
|
| + OnSliderChanged();
|
| + };
|
| + //img.src = "foo.jpg";
|
| + //img.src = "Nina6.jpg";
|
| + img.src = url ? url : "sunspots.jpg";
|
| +}
|
| +
|
| +function SetupDnD() {
|
| + $("#imagedisplay").bind({
|
| + dragenter: function(e) {
|
| + $("#imagedisplay").addClass("indrag");
|
| + return false;
|
| + },
|
| +
|
| + dragover: function(e) {
|
| + return false;
|
| + },
|
| +
|
| + dragleave: function(e) {
|
| + $("#imagedisplay").removeClass("indrag");
|
| + return false;
|
| + },
|
| +
|
| + drop: function(e) {
|
| + e = e.originalEvent;
|
| + var dt = e.dataTransfer;
|
| + var files = dt.files;
|
| +
|
| + if (files.length > 0) {
|
| + var file = files[0];
|
| + var reader = new FileReader();
|
| + reader.onload = function(e) { LoadImage(e.target.result); };
|
| + reader.readAsDataURL(file);
|
| + }
|
| +
|
| + $("#imagedisplay").removeClass("indrag");
|
| + return false;
|
| + }
|
| + });
|
| +}
|
| +
|
| +function SetupZoomClick() {
|
| + $("#canvas").bind({
|
| + click: function(e) {
|
| + if (gZoomPoint)
|
| + return true;
|
| +
|
| + var bounds = $("#canvas")[0].getBoundingClientRect();
|
| + var x = e.clientX - bounds.left;
|
| + var y = e.clientY - bounds.top;
|
| +
|
| + ZoomToPoint(x, y);
|
| + return false;
|
| + },
|
| +
|
| + mousedown: function(e) {
|
| + if (!gZoomPoint)
|
| + return true;
|
| +
|
| + $("#canvas").addClass("isgrabbing");
|
| +
|
| + gMouseOrig[0] = gZoomPoint[0];
|
| + gMouseOrig[1] = gZoomPoint[1];
|
| + gMouseStart = [ e.clientX, e.clientY ];
|
| +
|
| + return false;
|
| + },
|
| +
|
| + mouseup: function(e) {
|
| + if (!gZoomPoint || !gMouseStart)
|
| + return true;
|
| + $("#canvas").removeClass("isgrabbing");
|
| +
|
| + gZoomPoint = ClampZoomPointToTranslation();
|
| +
|
| + gZoomPoint[0] += gZoomSize[0]/2;
|
| + gZoomPoint[1] += gZoomSize[1]/2;
|
| +
|
| + gMouseStart = null;
|
| + return false;
|
| + },
|
| +
|
| + mousemove: function(e) {
|
| + if (!gZoomPoint || !gMouseStart)
|
| + return true;
|
| +
|
| + gZoomPoint[0] = gMouseOrig[0] + (gMouseStart[0] - e.clientX);
|
| + gZoomPoint[1] = gMouseOrig[1] + (gMouseStart[1] - e.clientY);
|
| + Redisplay();
|
| +
|
| + return false;
|
| + }
|
| + });
|
| +
|
| +}
|
| +
|
| +function CheckboxToggled(skipRedisplay) {
|
| + gCorrectBefore = $("#correct_before")[0].checked ? true : false;
|
| +
|
| + if (!skipRedisplay)
|
| + Redisplay();
|
| +}
|
| +
|
| +function ResetSliders() {
|
| + gIgnoreChanges = true;
|
| +
|
| + $(".slider").each(function(index, e) { $(e).slider("value", 500); });
|
| + $("#blackPoint").slider("value", 0);
|
| + $("#fill").slider("value", 0);
|
| + $("#shadowsSaturation").slider("value", 0);
|
| + $("#highlightsSaturation").slider("value", 0);
|
| +
|
| + gIgnoreChanges = false;
|
| +}
|
| +
|
| +function DoReset() {
|
| + ResetSliders();
|
| + ZoomReset();
|
| + OnSliderChanged();
|
| +}
|
| +
|
| +function DoRedisplay() {
|
| + Redisplay();
|
| +}
|
| +
|
| +// Speed test: run 10 processings, report in thousands-of-pixels-per-second
|
| +function Benchmark() {
|
| + var times = [];
|
| +
|
| + var width = gFullCanvas.width;
|
| + var height = gFullCanvas.height;
|
| +
|
| + $("#benchmark-status")[0].innerHTML = "Resetting...";
|
| +
|
| + ResetSliders();
|
| +
|
| + setTimeout(RunOneTiming, 0);
|
| +
|
| + function RunOneTiming() {
|
| +
|
| + $("#benchmark-status")[0].innerHTML = "Running... " + (times.length + 1);
|
| +
|
| + // reset to original image
|
| + gFullContext.save();
|
| + gFullContext.translate(Math.floor(gFullImage.width / 2), Math.floor(gFullImage.height / 2));
|
| + gFullContext.globalCompositeOperation = "copy";
|
| + gFullContext.drawImage(gFullImage,
|
| + -Math.floor(gFullImage.width / 2),
|
| + -Math.floor(gFullImage.height / 2));
|
| + gFullContext.restore();
|
| +
|
| + // time the processing
|
| + var start = (new Date()).getTime();
|
| + var data = gFullContext.getImageData(0, 0, width, height);
|
| + ProcessImageData(data, gParams);
|
| + gFullContext.putImageData(data, 0, 0);
|
| + var end = (new Date()).getTime();
|
| + times.push(end - start);
|
| +
|
| + if (times.length < 5) {
|
| + setTimeout(RunOneTiming, 0);
|
| + } else {
|
| + displayResults();
|
| + }
|
| +
|
| + }
|
| +
|
| + function displayResults() {
|
| + var totalTime = times.reduce(function(p, c) { return p + c; });
|
| + var totalPixels = height * width * times.length;
|
| + var MPixelsPerSec = totalPixels / totalTime / 1000;
|
| + $("#benchmark-status")[0].innerHTML = "Complete: " + MPixelsPerSec.toFixed(2) + " megapixels/sec";
|
| + $("#benchmark-ua")[0].innerHTML = navigator.userAgent;
|
| + }
|
| +}
|
| +
|
| +function SetBackground(n) {
|
| + $("body").removeClass("blackbg whitebg graybg");
|
| +
|
| + switch (n) {
|
| + case 0: // black
|
| + $("body").addClass("blackbg");
|
| + break;
|
| + case 1: // gray
|
| + $("body").addClass("graybg");
|
| + break;
|
| + case 2: // white
|
| + $("body").addClass("whitebg");
|
| + break;
|
| + }
|
| +}
|
| +
|
| +$(function() {
|
| + $(".slider").slider({
|
| + orientation: 'horizontal',
|
| + range: "min",
|
| + max: 1000,
|
| + value: 500,
|
| + slide: OnSliderChanged,
|
| + change: OnSliderChanged
|
| + });
|
| + ResetSliders();
|
| + SetupDnD();
|
| + SetupZoomClick();
|
| + CheckboxToggled(true);
|
| + LoadImage();
|
| + });
|
|
|