Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / test / data / third_party / kraken / hosted / explanations / darkroom.js
blob001c0544bfda573bcdf1bc5eff81dabee0cdc411
1 /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40; -*- */
3 // The if (0) block of function definitions here tries to use
4 // faster math primitives, based on being able to reinterpret
5 // floats as ints and vice versa.  We do that using the
6 // WebGL arrays.
8 if (0) {
10 var gConversionBuffer = new ArrayBuffer(4);
11 var gFloatConversion = new WebGLFloatArray(gConversionBuffer);
12 var gIntConversion = new WebGLIntArray(gConversionBuffer);
14 function AsFloat(i) {
15   gIntConversion[0] = i;
16   return gFloatConversion[0];
19 function AsInt(f) {
20   gFloatConversion[0] = f;
21   return gIntConversion[0];
24 // magic constants used for various floating point manipulations
25 var kMagicFloatToInt = (1 << 23);
26 var kOneAsInt = 0x3F800000;
27 var kScaleUp = AsFloat(0x00800000);
28 var kScaleDown = 1.0 / kScaleUp;
30 function ToInt(f) {
31   // force integer part into lower bits of mantissa
32   var i = ReinterpretFloatAsInt(f + kMagicFloatToInt);
33   // return lower bits of mantissa
34   return i & 0x3FFFFF;
37 function FastLog2(x) {
38   return (AsInt(x) - kOneAsInt) * kScaleDown;
41 function FastPower(x, p) {
42   return AsFloat(p * AsInt(x) + (1.0 - p) * kOneAsInt);
45 var LOG2_HALF = FastLog2(0.5);
47 function FastBias(b, x) {
48   return FastPower(x, FastLog2(b) / LOG2_HALF);
51 } else {
53 function FastLog2(x) {
54   return Math.log(x) / Math.LN2;
57 var LOG2_HALF = FastLog2(0.5);
59 function FastBias(b, x) {
60   return Math.pow(x, FastLog2(b) / LOG2_HALF);
65 function FastGain(g, x) {
66   return (x < 0.5) ?
67     FastBias(1.0 - g, 2.0 * x) * 0.5 :
68     1.0 - FastBias(1.0 - g, 2.0 - 2.0 * x) * 0.5;
71 function Clamp(x) {
72   return (x < 0.0) ? 0.0 : ((x > 1.0) ? 1.0 : x);
75 function ProcessImageData(imageData, params) {
76   var saturation = params.saturation;
77   var contrast = params.contrast;
78   var brightness = params.brightness;
79   var blackPoint = params.blackPoint;
80   var fill = params.fill;
81   var temperature = params.temperature;
82   var shadowsHue = params.shadowsHue;
83   var shadowsSaturation = params.shadowsSaturation;
84   var highlightsHue = params.highlightsHue;
85   var highlightsSaturation = params.highlightsSaturation;
86   var splitPoint = params.splitPoint;
88   var brightness_a, brightness_b;
89   var oo255 = 1.0 / 255.0;
91   // do some adjustments
92   fill *= 0.2;
93   brightness = (brightness - 1.0) * 0.75 + 1.0;
94   if (brightness < 1.0) {
95     brightness_a = brightness;
96     brightness_b = 0.0;
97   } else {
98     brightness_b = brightness - 1.0;
99     brightness_a = 1.0 - brightness_b;
100   }
101   contrast = contrast * 0.5;
102   contrast = (contrast - 0.5) * 0.75 + 0.5;
103   temperature = (temperature / 2000.0) * 0.1;
104   if (temperature > 0.0) temperature *= 2.0;
105   splitPoint = ((splitPoint + 1.0) * 0.5);
107   // apply to pixels
108   var sz = imageData.width * imageData.height;
109   var data = imageData.data;
110   for (var j = 0; j < sz; j++) {
111     var r = data[j*4+0] * oo255;
112     var g = data[j*4+1] * oo255;
113     var b = data[j*4+2] * oo255;
114     // convert RGB to YIQ
115     // this is a less than ideal colorspace;
116     // HSL would probably be better, but more expensive
117     var y = 0.299 * r + 0.587 * g + 0.114 * b;
118     var i = 0.596 * r - 0.275 * g - 0.321 * b;
119     var q = 0.212 * r - 0.523 * g + 0.311 * b;
120     i = i + temperature;
121     q = q - temperature;
122     i = i * saturation;
123     q = q * saturation;
124     y = (1.0 + blackPoint) * y - blackPoint;
125     y = y + fill;
126     y = y * brightness_a + brightness_b;
127     y = FastGain(contrast, Clamp(y));
129     if (y < splitPoint) {
130       q = q + (shadowsHue * shadowsSaturation) * (splitPoint - y);
131     } else {
132       i = i + (highlightsHue * highlightsSaturation) * (y - splitPoint);
133     }
135     // convert back to RGB for display
136     r = y + 0.956 * i + 0.621 * q;
137     g = y - 0.272 * i - 0.647 * q;
138     b = y - 1.105 * i + 1.702 * q;
140     // clamping is "free" as part of the ImageData object
141     data[j*4+0] = r * 255.0;
142     data[j*4+1] = g * 255.0;
143     data[j*4+2] = b * 255.0;
144   }
148 // UI code
151 var gFullCanvas = null;
152 var gFullContext = null;
153 var gFullImage = null;
154 var gDisplayCanvas = null;
155 var gDisplayContext = null;
156 var gZoomPoint = null;
157 var gDisplaySize = null;
158 var gZoomSize = [600, 600];
159 var gMouseStart = null;
160 var gMouseOrig = [0, 0];
161 var gDirty = true;
163 // If true, apply image correction to the original
164 // source image before scaling down; if false,
165 // scale down first.
166 var gCorrectBefore = false;
168 var gParams = null;
169 var gIgnoreChanges = true;
171 function OnSliderChanged() {
172   if (gIgnoreChanges)
173     return;
175   gDirty = true;
177   gParams = {};
179   // The values will come in as 0.0 .. 1.0; some params want
180   // a different range.
181   var ranges = {
182     "saturation": [0, 2],
183     "contrast": [0, 2],
184     "brightness": [0, 2],
185     "temperature": [-2000, 2000],
186     "splitPoint": [-1, 1]
187   };
189   $(".slider").each(function(index, e) {
190                       var val = Math.floor($(e).slider("value")) / 1000.0;
191                       var id = e.getAttribute("id");
192                       if (id in ranges)
193                         val = val * (ranges[id][1] - ranges[id][0]) + ranges[id][0];
194                       gParams[id] = val;
195                     });
197   Redisplay();
200 function ClampZoomPointToTranslation() {
201   var tx = gZoomPoint[0] - gZoomSize[0]/2;
202   var ty = gZoomPoint[1] - gZoomSize[1]/2;
203   tx = Math.max(0, tx);
204   ty = Math.max(0, ty);
206   if (tx + gZoomSize[0] > gFullImage.width)
207     tx = gFullImage.width - gZoomSize[0];
208   if (ty + gZoomSize[1] > gFullImage.height)
209     ty = gFullImage.height - gZoomSize[1];
210   return [tx, ty];
213 function Redisplay() {
214   if (!gParams)
215     return;
217   var angle =
218     (gParams.angle*2.0 - 1.0) * 90.0 +
219     (gParams.fineangle*2.0 - 1.0) * 2.0;
221   angle = Math.max(-90, Math.min(90, angle));
222   angle = (angle * Math.PI) / 180.0;
224   var processTime;
225   var processWidth, processHeight;
227   var t0 = (new Date()).getTime();
229   // Render the image with rotation; we only need to render
230   // if we're either correcting just the portion that's visible,
231   // or if we're correcting the full thing and the sliders have been
232   // changed.  Otherwise, what's in the full canvas is already corrected
233   // and correct.
234   if ((gCorrectBefore && gDirty) ||
235       !gCorrectBefore)
236   {
237     gFullContext.save();
238     gFullContext.translate(Math.floor(gFullImage.width / 2), Math.floor(gFullImage.height / 2));
239     gFullContext.rotate(angle);
240     gFullContext.globalCompositeOperation = "copy";
241     gFullContext.drawImage(gFullImage,
242                            -Math.floor(gFullImage.width / 2),
243                            -Math.floor(gFullImage.height / 2));
244     gFullContext.restore();
245   }
247   function FullToDisplay() {
248     gDisplayContext.save();
249     if (gZoomPoint) {
250       var pt = ClampZoomPointToTranslation();
252       gDisplayContext.translate(-pt[0], -pt[1]);
253     } else {
254       gDisplayContext.translate(0, 0);
255       var ratio = gDisplaySize[0] / gFullCanvas.width;
256       gDisplayContext.scale(ratio, ratio);
257     }
259     gDisplayContext.globalCompositeOperation = "copy";
260     gDisplayContext.drawImage(gFullCanvas, 0, 0);
261     gDisplayContext.restore();
262   }
264   function ProcessCanvas(cx, canvas) {
265     var ts = (new Date()).getTime();
267     var data = cx.getImageData(0, 0, canvas.width, canvas.height);
268     ProcessImageData(data, gParams);
269     cx.putImageData(data, 0, 0);
271     processWidth = canvas.width;
272     processHeight = canvas.height;
274     processTime = (new Date()).getTime() - ts;
275   }
277   if (gCorrectBefore) {
278     if (gDirty) {
279       ProcessCanvas(gFullContext, gFullCanvas);
280     } else {
281       processTime = -1;
282     }
283     gDirty = false;
284     FullToDisplay();
285   } else {
286     FullToDisplay();
287     ProcessCanvas(gDisplayContext, gDisplayCanvas);
288   }
290   var t3 = (new Date()).getTime();
292   if (processTime != -1) {
293     $("#log")[0].innerHTML = "<p>" +
294       "Size: " + processWidth + "x" + processHeight + " (" + (processWidth*processHeight) + " pixels)<br>" +
295       "Process: " + processTime + "ms" + " Total: " + (t3-t0) + "ms<br>" + 
296       "Throughput: " + Math.floor((processWidth*processHeight) / (processTime / 1000.0)) + " pixels per second<br>" + 
297       "FPS: " + (Math.floor((1000.0 / (t3-t0)) * 100) / 100) + "<br>" +
298     "</p>";
299   } else {
300     $("#log")[0].innerHTML = "<p>(No stats when zoomed and no processing done)</p>";
301   }
304 function ZoomToPoint(x, y) {
305   if (gZoomSize[0] > gFullImage.width ||
306       gZoomSize[1] > gFullImage.height)
307     return;
309   var r = gDisplaySize[0] / gFullCanvas.width;
311   gDisplayCanvas.width = gZoomSize[0];
312   gDisplayCanvas.height = gZoomSize[1];
313   gZoomPoint = [x/r, y/r];
314   $("#canvas").removeClass("canzoomin").addClass("cangrab");
315   Redisplay();  
318 function ZoomReset() {
319   gDisplayCanvas.width = gDisplaySize[0];
320   gDisplayCanvas.height = gDisplaySize[1];  
321   gZoomPoint = null;
322   $("#canvas").removeClass("canzoomout cangrab isgrabbing").addClass("canzoomin");
323   Redisplay();
326 function LoadImage(url) {
327   if (!gFullCanvas)
328     gFullCanvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
329   if (!gDisplayCanvas)
330     gDisplayCanvas = $("#canvas")[0];
332   var img = new Image();
333   img.onload = function() {
334     var w = img.width;
335     var h = img.height;
337     gFullImage = img;
339     gFullCanvas.width = w;
340     gFullCanvas.height = h;
341     gFullContext = gFullCanvas.getContext("2d");
343     // XXX use the actual size of the visible region, so that
344     // we rescale along with the window
345     var dim = 600;
346     if (Math.max(w,h) > dim) {
347       var scale = dim / Math.max(w,h);
348       w *= scale;
349       h *= scale;
350     }
352     gDisplayCanvas.width = Math.floor(w);
353     gDisplayCanvas.height = Math.floor(h);
354     gDisplaySize = [ Math.floor(w), Math.floor(h) ];
355     gDisplayContext = gDisplayCanvas.getContext("2d");
357     $("#canvas").removeClass("canzoomin canzoomout cangrab isgrabbing");
359     if (gZoomSize[0] <= gFullImage.width &&
360         gZoomSize[1] <= gFullImage.height)
361     {
362       $("#canvas").addClass("canzoomin");
363     }
365     OnSliderChanged();
366   };
367   //img.src = "foo.jpg";
368   //img.src = "Nina6.jpg";
369   img.src = url ? url : "sunspots.jpg";
372 function SetupDnD() {
373   $("#imagedisplay").bind({
374                             dragenter: function(e) {
375                               $("#imagedisplay").addClass("indrag");
376                               return false;
377                             },
379                             dragover: function(e) {
380                               return false;
381                             },
383                             dragleave: function(e) {
384                               $("#imagedisplay").removeClass("indrag");
385                               return false;
386                             },
388                             drop: function(e) {
389                               e = e.originalEvent;
390                               var dt = e.dataTransfer;
391                               var files = dt.files;
393                               if (files.length > 0) {
394                                 var file = files[0];
395                                 var reader = new FileReader();
396                                 reader.onload = function(e) { LoadImage(e.target.result); };
397                                 reader.readAsDataURL(file);
398                               }
400                               $("#imagedisplay").removeClass("indrag");
401                               return false;
402                             }
403                           });
406 function SetupZoomClick() {
407   $("#canvas").bind({
408                       click: function(e) {
409                         if (gZoomPoint)
410                           return true;
412                         var bounds = $("#canvas")[0].getBoundingClientRect();
413                         var x = e.clientX - bounds.left;
414                         var y = e.clientY - bounds.top;
416                         ZoomToPoint(x, y);
417                         return false;
418                       },
420                       mousedown: function(e) {
421                         if (!gZoomPoint)
422                           return true;
424                         $("#canvas").addClass("isgrabbing");
426                         gMouseOrig[0] = gZoomPoint[0];
427                         gMouseOrig[1] = gZoomPoint[1];
428                         gMouseStart = [ e.clientX, e.clientY ];
430                         return false;
431                       },
433                       mouseup: function(e) {
434                         if (!gZoomPoint || !gMouseStart)
435                           return true;
436                         $("#canvas").removeClass("isgrabbing");
438                         gZoomPoint = ClampZoomPointToTranslation();
440                         gZoomPoint[0] += gZoomSize[0]/2;
441                         gZoomPoint[1] += gZoomSize[1]/2;
443                         gMouseStart = null;
444                         return false;
445                       },
447                       mousemove: function(e) {
448                         if (!gZoomPoint || !gMouseStart)
449                           return true;
451                         gZoomPoint[0] = gMouseOrig[0] + (gMouseStart[0] - e.clientX);
452                         gZoomPoint[1] = gMouseOrig[1] + (gMouseStart[1] - e.clientY);
453                         Redisplay();
455                         return false;
456                       }
457                     });
458   
461 function CheckboxToggled(skipRedisplay) {
462   gCorrectBefore = $("#correct_before")[0].checked ? true : false;
464   if (!skipRedisplay)
465     Redisplay();
468 function ResetSliders() {
469   gIgnoreChanges = true;
471   $(".slider").each(function(index, e) { $(e).slider("value", 500); });
472   $("#blackPoint").slider("value", 0);
473   $("#fill").slider("value", 0);
474   $("#shadowsSaturation").slider("value", 0);
475   $("#highlightsSaturation").slider("value", 0);
477   gIgnoreChanges = false;
480 function DoReset() {
481   ResetSliders();  
482   ZoomReset();
483   OnSliderChanged();
486 function DoRedisplay() {
487   Redisplay();
490 // Speed test: run 10 processings, report in thousands-of-pixels-per-second
491 function Benchmark() {
492   var times = [];
494   var width = gFullCanvas.width;
495   var height = gFullCanvas.height;
497   $("#benchmark-status")[0].innerHTML = "Resetting...";
499   ResetSliders();
501   setTimeout(RunOneTiming, 0);
503   function RunOneTiming() {
505     $("#benchmark-status")[0].innerHTML = "Running... " + (times.length + 1);
507     // reset to original image
508     gFullContext.save();
509     gFullContext.translate(Math.floor(gFullImage.width / 2), Math.floor(gFullImage.height / 2));
510     gFullContext.globalCompositeOperation = "copy";
511     gFullContext.drawImage(gFullImage,
512                            -Math.floor(gFullImage.width / 2),
513                            -Math.floor(gFullImage.height / 2));
514     gFullContext.restore();
516     // time the processing
517     var start = (new Date()).getTime();
518     var data = gFullContext.getImageData(0, 0, width, height);
519     ProcessImageData(data, gParams);
520     gFullContext.putImageData(data, 0, 0);
521     var end = (new Date()).getTime();
522     times.push(end - start);
524     if (times.length < 5) {
525       setTimeout(RunOneTiming, 0);
526     } else {
527       displayResults();
528     }
530   }
532   function displayResults() {
533     var totalTime = times.reduce(function(p, c) { return p + c; });
534     var totalPixels = height * width * times.length;
535     var MPixelsPerSec = totalPixels / totalTime / 1000;
536     $("#benchmark-status")[0].innerHTML = "Complete: " + MPixelsPerSec.toFixed(2) + " megapixels/sec";
537     $("#benchmark-ua")[0].innerHTML = navigator.userAgent;
538   }
541 function SetBackground(n) {
542   $("body").removeClass("blackbg whitebg graybg");
544   switch (n) {
545   case 0: // black
546     $("body").addClass("blackbg");
547     break;
548   case 1: // gray
549     $("body").addClass("graybg");
550     break;
551   case 2: // white
552     $("body").addClass("whitebg");
553     break;
554   }
557 $(function() {
558     $(".slider").slider({
559                           orientation: 'horizontal',
560                           range: "min",
561                           max: 1000,
562                           value: 500,
563                           slide: OnSliderChanged,
564                           change: OnSliderChanged
565                         });
566     ResetSliders();
567     SetupDnD();
568     SetupZoomClick();
569     CheckboxToggled(true);
570     LoadImage();
571   });