Index: chrome/test/data/third_party/kraken/hosted/explanations/darkroom.js |
diff --git a/chrome/test/data/third_party/kraken/hosted/explanations/darkroom.js b/chrome/test/data/third_party/kraken/hosted/explanations/darkroom.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..001c0544bfda573bcdf1bc5eff81dabee0cdc411 |
--- /dev/null |
+++ b/chrome/test/data/third_party/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(); |
+ }); |