정보
요청한 주제가 아래에 표시됩니다. 그러나 이 주제는 이 라이브러리에 포함되지 않습니다.

뒤로 및 앞으로 단추 사용

Mandelbrot 이미지에 회색조 추가에서 제공한 정보를 기반으로 여기서는 브라우저의 뒤로 및 앞으로 단추를 사용하는 방법에 대해 설명합니다.

브라우저의 뒤로 및 앞으로 단추를 사용하도록 설정하는 데 사용되는 기술은 다음과 같이 페이지의 URL 해시 문자열을 가져오고 설정하는 것입니다.


http://samples.msdn.microsoft.com/Workshop/samples/mandelbrot/mandelbrotExplorer.html#0.722,-0.178,0.846,0.246,1

일반적으로 URL이 변경될 때마다 브라우저의 뒤로 및 앞으로 단추가 사용하도록 설정됩니다. 따라서 고유한 각 Mandelbrot 이미지(페이지)에는 고유한 해시 문자열이 있습니다.

해시 문자열 #0.722,-0.178,0.846,0.246,1은 다음과 같이 해석할 수 있습니다.

  • globals.reMax = 0.722
  • globals.reMin = -0.178
  • globals.ImMax = 0.846
  • globals.ImMin = 0.246
  • globals.grayscaleFactor = 1

이 문자열은 뒷부분에서 보다 자세히 설명하며 여기서 globals.grayscaleFactor는 지정된 이미지를 밝게 할 정보를 나타냅니다(값이 1이면 아무 효과가 없음).

Mandelbrot 4는 다음에 표시된 대로 Mandelbrot 5와 유사합니다.

Mandelbrot 5


<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=10" />
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Mandelbrot 5</title>
  <style>
    html, body {
      margin: 0;
      padding: 0;
      text-align: center;
    }
    
    canvas {
      border: 2px solid black;
    }    
    
    table {
      margin: 0 auto; /* Center the table. */
    }
    
    #messageBox {
      text-align: left;
    }
        
    #elapsedTime {
      width: 23em;
      text-align: right;
    }    

    button {
      width: 5em;
    }
    
    #filenameForm {
      visibility: hidden; /* As opposed to "display: none", keep room for this hidden element in the layout. */
    }
}
  </style>      
</head>

<body>
  <h1>Mandelbrot 5</h1>
  <p>This example demonstrates how to enable the browser's back and forward buttons.</p>      
  <table>
    <tr>
      <td id="messageBox"></td>
      <td id="elapsedTime"></td>
    </tr>
  </table>
  <canvas width="600" height="400" oncontextmenu="return false;"> <!-- Because the hold gesture event can fire more than once, the 'oncontextmenu="return false;"' attribute is used to stop the right-click context menu from appearing inappropriately. -->
    Canvas not supported - upgrade your browser (after checking that your browser is in the correct mode).
  </canvas><br>
  <button type="button" id="resetButton">Reset</button>  
  <button type="button" id="lightenButton">Lighten</button>    
  <button type="button" id="saveButton">Save</button>
  <form id="filenameForm"> 
    Extensionless filename: <input id="filename" type="text"> <input type="submit" value="Submit">
  </form>
  <script>
    var RE_MAX = 1.1; // This value will be adjusted as necessary to ensure that the rendered Mandelbrot set is never skewed (that is, true to it's actual shape).
    var RE_MIN = -2.5;
    var IM_MAX = 1.2;
    var IM_MIN = -1.2;
    var MAX_ITERATIONS = 1200; // Increase this value to improve detection of complex c values that belong to the Mandelbrot set.
    var STATIC_ZOOM_BOX_FACTOR = 0.25; // Increase to make the double-click and hold gesture zoom box bigger.
    var DEFAULT_MESSAGE = "Click or click-and-drag to zoom."
      
      
      var globals = {}; // See the handleLoad function.
      
      window.addEventListener('load', handleLoad, false);
                
    /************************************************************************************************************************************************************/
    
    Number.prototype.format = function() {
    /* 
      Formats this integer so that it has commas in the expected places.
    */
    	var numberString = Math.round(this).toString(); // An integer value is assumed, so we ensure that it is indeed an integer.
    	var precompiledRegularExpression = /(\d+)(\d{3})/;
    	
    	while ( precompiledRegularExpression.test(numberString) ) {
    		numberString = numberString.replace(precompiledRegularExpression, '$1' + ',' + '$2'); // For this integer, inject ","'s at the appropriate locations.
    	} // while
    	
    	return numberString;
    } // Number.prototype.format

    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/    

    function handleLoad() {          
      var canvas = document.getElementsByTagName('canvas')[0];
      var canvasWidth = canvas.width;
      var canvasHeight = canvas.height;      
      var ctx = canvas.getContext('2d');
      
      document.getElementsByTagName('table')[0].width = canvasWidth; // Make the table's width the same as the canvas's width. 
      document.getElementById('messageBox').innerHTML = DEFAULT_MESSAGE;            

      globals.canvas = canvas;
      globals.canvas.context = ctx;
      globals.canvas.context.imageDataObject = ctx.createImageData(canvasWidth, canvasHeight); // Create an appropriately sized but empty canvas image data object.
      
      globals.staticZoomBoxWidth = STATIC_ZOOM_BOX_FACTOR * canvasWidth; // Maintains the original canvas width/height ratio.
      globals.staticZoomBoxHeight = STATIC_ZOOM_BOX_FACTOR * canvasHeight; // Maintains the original canvas width/height ratio.      
      
      globals.pointer = {};
      globals.pointer.down = false;  
                 
      window.addEventListener('hashchange', handleHashChange, false); // This event handler executes whenever the URL hash string changes.
      
      canvas.addEventListener('mousedown', handlePointer, false);
      canvas.addEventListener('mousemove', handlePointer, false);
      canvas.addEventListener('mouseup', handlePointer, false);    
            
      document.getElementById('resetButton').addEventListener('click', handleResetButton, false);
      document.getElementById('lightenButton').addEventListener('click', handleLightenButton, false);    
      document.getElementById('saveButton').addEventListener('click', handleSaveButton, false);        
      document.getElementById('filenameForm').addEventListener('submit', handleFormSubmit, false);    
      
      ctx.fillStyle = "rgba(255, 0, 0, 0.3)"; // The color and opacity of the zoom box. This is what gets saved when calling ctx.save().          
 
      handleHashChange();
    } // handleLoad
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/    
    
    function handleHashChange() {
      var hashValues = getHashValues(); // This function examines window.location.hash but doesn't change it.
      
      if (hashValues) {
        globals.ReMax = hashValues.ReMax;
        globals.ReMin = hashValues.ReMin;
        globals.ImMax = hashValues.ImMax;
        globals.ImMin = hashValues.ImMin;
        globals.grayscaleFactor = hashValues.grayscaleFactor;
      }
      else {
        globals.ReMax = adjusted_RE_MAX();
        globals.ReMin = RE_MIN;
        globals.ImMax = IM_MAX;
        globals.ImMin = IM_MIN;     
        globals.grayscaleFactor = 1; // Multiplying any value by 1 has no effect.
      } // if-else
      
      drawMandelbrot(globals.ReMax, globals.ReMin, globals.ImMax, globals.ImMin, globals.grayscaleFactor);
    } // handelHashChange    

    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/        

    function getHashValues() {
      var dirtyComplexPlaneExtremaString = (window.location.hash).replace('#', ''); // Remove the leading "#" character from the string.
      var complexPlaneExtremaString = dirtyComplexPlaneExtremaString.split(','); // Returns an array. Assumes the following string form: "ReMax,ReMin,ImMax,ImMin,grayscaleFactor" (note that if grayscaleFactor is 1, the image's grayscale is not affected).
      
      var ReMax = parseFloat( complexPlaneExtremaString[0] ); 
      var ReMin = parseFloat( complexPlaneExtremaString[1] ); 
      var ImMax = parseFloat( complexPlaneExtremaString[2] ); 
      var ImMin = parseFloat( complexPlaneExtremaString[3] );
      var grayscaleFactor = parseFloat( complexPlaneExtremaString[4] );
      
      if ( isNaN(ReMax) || isNaN(ReMin) || isNaN(ImMax) || isNaN(ImMin) || isNaN(grayscaleFactor) ) { 
        return null;
      } // if 
      
      return {ReMax: ReMax, ReMin: ReMin, ImMax: ImMax, ImMin: ImMin, grayscaleFactor: grayscaleFactor};
    } // getHashValues
        
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/        

    function adjusted_RE_MAX() {    
      var ReMax = globals.canvas.width * ( (IM_MAX - IM_MIN) / globals.canvas.height ) + RE_MIN;
      
      if (RE_MAX != ReMax) {
        alert("RE_MAX has been adjusted to: " + ReMax); // The user should never see this if RE_MAX is set correctly above.
      } // if

      return ReMax;
    } // adjusted_RE_MAX    
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/    
    
    function drawMandelbrot(ReMax, ReMin, ImMax, ImMin, grayscaleFactor) {            
      document.getElementById('messageBox').innerHTML = "Calculating..."; // This isn't displayed until the drawMandelbrot function block exits. 
      document.getElementById('elapsedTime').innerHTML = ""; // Erase the prior run's statistics.           
      
      if (window.setImmediate) {
        window.setImmediate(calculateMandelbrot); // Allow the drawMandelbrot function to immediately terminate, thus printing "Calculating..." to the screen.       
      }
      else {
        window.setTimeout(calculateMandelbrot, 0); // Allow the drawMandelbrot function to immediately terminate, thus printing "Calculating..." to the screen.      
      }           
      
      function calculateMandelbrot() {
        var startTime = new Date(); // Report how long it takes to render this particular region of the Mandelbrot set.             
        
        var canvas = globals.canvas;
        var canvasWidth = canvas.width;
        var canvasHeight = canvas.height;
        var ctx = canvas.context;
        var imageDataObjectData = ctx.imageDataObject.data; // imageDataObjectData is a reference to, not a copy of, ctx.imageDataObject.data
        
        var maxPixelGrayscaleValue = 0; // This will contain the lightest shade of gray in the drawn Mandelbrot image.
        
        var x_coefficient = (ReMax - ReMin) / canvasWidth; // Keep the below loops as computation-free as possible.
        var y_coefficient = (ImMin - ImMax) / canvasHeight; // Keep the below loops as computation-free as possible.
  
        var iterationSum = 0;
        var currentPixel = 0;          
        for (var y = 0; y < canvasHeight; y++) {
          var c_Im = (y * y_coefficient) + ImMax; // Note that c = c_Re + c_Im*i
          
          for (var x = 0; x < canvasWidth; x++) {
            var c_Re = (x * x_coefficient) + ReMin // Convert the canvas x-coordinate to a complex plane Re-coordinate. c_Re represents the real part of a c value.
            
            var z_Re = 0; // The first z value (Zo) must be 0.
            var z_Im = 0; // The first z value (Zo) must be 0. Note that z = z_Re + z_Im*i
            
            var c_belongsToMandelbrotSet = true; // Assume that the c associated with Zn belongs to the Mandelbrot set (i.e., Zn remains bounded under iteration of Zn+1 = (Zn)^2 + c).
            var exponentialSmoothingSum = 0;
            for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
              iterationSum++; // Keep track of how many iterations were performed in total so we can report this to the user.
            
              var z_Re_squared = z_Re * z_Re; // A small speed optimization.
              var z_Im_squared = z_Im * z_Im; // A small speed optimization.
              
              exponentialSmoothingSum += Math.exp( -(z_Re_squared + z_Im_squared) ); // Technically, this should be e^(-|z|). However, avoiding the expensive square root operation doesn't really affect the resulting image.              
              if (exponentialSmoothingSum >= 255) { // Don't cycle through the gray colors.
                exponentialSmoothingSum = 255;
              }
      
              if (z_Re_squared + z_Im_squared > 4) { // Checks if |z^2| is greater than 2. This approach avoids the expensive square root operation.
                c_belongsToMandelbrotSet = false; // This complex c value is not part of the Mandelbrot set (because it will always tend towards infinity under iteration).
                break; // Immediately check the next c value to see if it belongs to the Mandelbrot set or not.
              } // if
              
              // The next two lines perform Zn+1 = (Zn)^2 + c (recall that (x + yi)^2 = x^2 - y^2 + 2xyi, thus the real part is x^2 - y^2 and the imaginary part is 2xyi).
              z_Im = (2 * z_Re * z_Im) + c_Im; // We must calculate the next value of z_Im first because it depends on the current value of z_Re (not the next value of z_Re).
              z_Re = z_Re_squared - z_Im_squared + c_Re; // Calculate the next value of z_Re.
            } // for   
            
            if (c_belongsToMandelbrotSet) { // This complex c value is probably part of the Mandelbrot set because Zn did not tend toward infinity within MAX_ITERATIONS iterations.
              imageDataObjectData[currentPixel++] = 0; // Red. Note that there are 255 possible shades of red, green, blue, and alpha (i.e., opacity).
              imageDataObjectData[currentPixel++] = 0; // Green.
              imageDataObjectData[currentPixel++] = 0; // Blue.
              imageDataObjectData[currentPixel++] = 255; // Alpha (i.e., 0% transparency).
            } 
            else { // This complex c value is definitely not part of the Mandelbrot set because Zn would tend toward infinity under iteration (i.e., |Zn| > 2).
              var pixelGrayscaleValue = 255 - exponentialSmoothingSum % 256; // Force the value of exponentialSmoothingSum to be between 0 and 255 inclusively. Note that all values for red, green, and blue are identical when using a grayscale.
              var adjustedPixelGrayscaleValue = pixelGrayscaleValue * grayscaleFactor; // Avoids doing this more than once.
              
              imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // Because we mod by 256, the value of exponentialSmoothingSum will always be between 0 and 255.
              imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // If exponentialSmoothingSum is 255 (it's maximum possible value), then 255 % 256 = 255.
              imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // When exponentialSmoothingSum is 255, we have 255 - 255 = 0, so the shade values for RGB are all set to 0 (that is, the c-value pixel is rendered black - indicating that this particular c-value very slowly tends towards infinity).
              imageDataObjectData[currentPixel++] = 255; // Always draw the c-value pixels with no transparency.
              
              if (pixelGrayscaleValue > maxPixelGrayscaleValue) {
                maxPixelGrayscaleValue = pixelGrayscaleValue; // Determine the lightest shade of gray in case the user clicks the Lighten button.
              } // if
            } // if-else
          } // for
        } // for        
        
        globals.maxPixelGrayscaleValue = maxPixelGrayscaleValue; // Store the lightest shade of gray in case the user clicks the Lighten button.      
        ctx.putImageData(ctx.imageDataObject, 0, 0); // Render our carefully constructed canvas image data array to the canvas.
            
        var elapsedMilliseconds = (new Date()) - startTime;
        document.getElementById('elapsedTime').innerHTML = iterationSum.format() + " iterations in " + (elapsedMilliseconds / 1000).toFixed(2) + " seconds"; // Note that the UI element is not updated until after this block terminates (which is the desired behavior).            
        document.getElementById('messageBox').innerHTML = DEFAULT_MESSAGE; // Erase the "Calculating..." message and replace it with the default message.        
      } // calculateMandelbrot
    } // drawMandelbrot
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/
    
    function xToRe(x) {
      var x_coefficient = (globals.ReMax - globals.ReMin) / globals.canvas.width; 
      
      return (x * x_coefficient) + globals.ReMin; // Converts a canvas x-coordinate value to the associated complex plane Re-coordinate.
    } // xToRe
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/    

    function yToIm(y) {
      var y_coefficient = (globals.ImMin - globals.ImMax) / globals.canvas.height; 
      
      return (y * y_coefficient) + globals.ImMax; // Converts a canvas y-coordinate value to the associated complex plane Im-coordinate.
    } // yToIm
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/

    function handlePointer(evt) {
      var canvasWidthHeightRatio = globals.canvas.width / globals.canvas.height;
      var ctx = globals.canvas.context;
      
      var canvasX;
      var canvasY;      
      
      if (evt.offsetX && evt.offsetY) {
        canvasX = evt.offsetX; // Not supported in Firefox.
        canvasY = evt.offsetY; // Does not assume that the canvas element is a direct descendent of the body element.
      } else {
        canvasX = evt.clientX - evt.target.offsetLeft; // Supported in Firefox.
        canvasY = evt.clientY - evt.target.offsetTop; // Assumes that the canvas element is a direct descendent of the body element.
      } // if-else
      
      var zoomBoxWidth;
      var zoomBoxHeight;
      
      var ReMax;
      var ReMin;
      var ImMax;
      var ImMin;
      
      var staticZoomBoxWidth = globals.staticZoomBoxWidth;
      var staticZoomBoxHeight = globals.staticZoomBoxHeight;
      var halfStaticZoomBoxWidth = staticZoomBoxWidth / 2;xx
      var halfStaticZoomBoxHeight = staticZoomBoxHeight / 2;
      
      switch (evt.type) {
        case 'mousedown':
          globals.pointer.down = true;      
          globals.pointer.x1 = canvasX;
          globals.pointer.y1 = canvasY;
          break;
        case 'mousemove':
          if (globals.pointer.down) {
            zoomBoxHeight = Math.abs(canvasY - globals.pointer.y1);  
            zoomBoxWidth = zoomBoxHeight * canvasWidthHeightRatio; // We must keep the zoom box dimensions proportional to the canvas dimensions in order to ensure that the resulting zoomed Mandelbrot image does not become skewed.
            ctx.putImageData(ctx.imageDataObject, 0, 0); // Assumes that an initial image of the Mandelbrot set is drawn before we get to this point in the code. The purpose of this line is to erase the prior zoom box rectangle before drawing the next zoom box rectangle.
            ctx.fillRect(globals.pointer.x1, globals.pointer.y1, zoomBoxWidth, zoomBoxHeight); // With a freshly painted image of the current Mandelbrot set in place (see prior line), draw a new zoom box rectangle.
          }
          break;
        case 'mouseup':
          globals.pointer.down = false;          
          
          zoomBoxHeight = Math.abs(canvasY - globals.pointer.y1); // Only allow the zoom box to be drawn from an upper-left corner position down to a lower-right corner position.
          zoomBoxWidth = zoomBoxHeight * canvasWidthHeightRatio; // Again, ensure that the width/height ratio of the zoom box is proportional to the canvas's (this simplifies the algorithm).          
          
          if (zoomBoxHeight == 0) { // No zoom box has been drawn, so honor the fixed sized zoom box.          
            ctx.fillRect(canvasX - halfStaticZoomBoxWidth, canvasY - halfStaticZoomBoxHeight, staticZoomBoxWidth, staticZoomBoxHeight); // Just leave this on the screen.
                         
            ReMin = xToRe(canvasX - halfStaticZoomBoxWidth); // Center the static zoom box about the point (evt.offsetX, evt.offsetY).
            ImMax = yToIm(canvasY - halfStaticZoomBoxHeight); 
            
            ReMax = xToRe(canvasX + halfStaticZoomBoxWidth);
            ImMin = yToIm(canvasY + halfStaticZoomBoxHeight);
          } 
          else { // A (possibly tiny) zoom box has been drawn, so honor it.
            ReMin = xToRe(globals.pointer.x1); // Convert the mouse's x-coordinate value (on the canvas) to the associated Re-coordinate value in the complex plane.
            ImMax = yToIm(globals.pointer.y1); // Convert the mouse's y-coordinate value (on the canvas) to the associated Im-coordinate value in the complex plane.
                                      
            ReMax = xToRe(zoomBoxWidth + globals.pointer.x1); // Convert the zoom box's final x-coordinate value to the associated Re-coordinate value in the complex plane.  
            ImMin = yToIm(zoomBoxHeight + globals.pointer.y1);  // Convert the zoom box's final y-coordinate value to the associated Re-coordinate value in the complex plane.            
          } // if-else        
        
          window.location.hash = ReMax + "," + ReMin + "," + ImMax + "," + ImMin + "," + globals.grayscaleFactor; // This triggers the handleHashChange event handler which, among other things, is responsible for drawing the Mandelbrot set.
          break; 
        default:
          alert("Error in switch statement."); // Although unnecessary, defensive programming techniques such as this are highly recommended.
      } // switch              
    } // handlePointer    
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/
    
    function handleResetButton() {
      window.location.hash = adjusted_RE_MAX() + "," + RE_MIN + "," + IM_MAX + "," + IM_MIN + "," + 1; // // This triggers the handleHashChange event handler which, among other things, is responsible for drawing the Mandelbrot set.
    } // handleResetButton
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/
    
    function handleLightenButton() {
    /* 
      This multiplies by a value (factor) such that black (0) stays black and the lightest gray value in the image becomes white (255). Thus, clicking the 
      Lighten button removes much of the mathematical meaning of the (proper) grayscale but can make "dark" images more visible.
    */
      var grayscaleFactor = 255 / globals.maxPixelGrayscaleValue; // For the canvas element, 255 is white, 0 is black.

      window.location.hash = globals.ReMax + "," + globals.ReMin + "," + globals.ImMax + "," + globals.ImMin + "," + grayscaleFactor; // This invokes handleHashChange which, among other things, is responsibile for drawing the Mandelbrot set.
    } // handleResetButton
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/

    function handleSaveButton() {
      document.getElementById('filenameForm').style.visibility = "visible";
      document.getElementById('filename').focus(); // Place the cursor in the filename text input box.
    } // handleResetButton 
 
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/

    function handleFormSubmit(evt) {
      alert("handleFormSubmit fired.");    
      evt.preventDefault(); // Do not refresh the page when the submit button is clicked.
      document.getElementById('filenameForm').style.visibility = "hidden";
    } // handleFormSubmit
  </script>
</body>

</html>

Mandelbrot 4Mandelbrot 5의 주요 차이점은 handleHashChange 이벤트 처리기(페이지의 URL이 변경될 때마다 호출됨)에서 Mandelbrot 집합의 그리기를 트리거한다는 점입니다.


function handleHashChange() {
  var hashValues = getHashValues(); // This function examines window.location.hash but doesn't change it.
  
  if (hashValues) {
    globals.ReMax = hashValues.ReMax;
    globals.ReMin = hashValues.ReMin;
    globals.ImMax = hashValues.ImMax;
    globals.ImMin = hashValues.ImMin;
    globals.grayscaleFactor = hashValues.grayscaleFactor;
  }
  else {
    globals.ReMax = adjusted_RE_MAX();
    globals.ReMin = RE_MIN;
    globals.ImMax = IM_MAX;
    globals.ImMin = IM_MIN;     
    globals.grayscaleFactor = 1; // Multiplying any value by 1 has no effect.
  } // if-else
  
  drawMandelbrot(globals.ReMax, globals.ReMin, globals.ImMax, globals.ImMin, globals.grayscaleFactor);
} // handelHashChange

따라서 Mandelbrot 집합의 새 이미지를 표시하려면 window.location.hash를 업데이트하여 페이지의 URL 해시 문자열을 변경하기만 하면 됩니다. 처음에는 handleLoad 이벤트 처리기에서 handleHashChange 이벤트 처리기(마지막 줄 참조)를 직접 호출할 때 발생합니다.


function handleLoad() {          
  var canvas = document.getElementsByTagName('canvas')[0];
  var canvasWidth = canvas.width;
  var canvasHeight = canvas.height;      
  var ctx = canvas.getContext('2d');
  
  document.getElementsByTagName('table')[0].width = canvasWidth;
  document.getElementById('messageBox').innerHTML = DEFAULT_MESSAGE;            

  globals.canvas = canvas;
  globals.canvas.context = ctx;
  globals.canvas.context.imageDataObject = ctx.createImageData(canvasWidth, canvasHeight);
  
  globals.staticZoomBoxWidth = STATIC_ZOOM_BOX_FACTOR * canvasWidth; 
  globals.staticZoomBoxHeight = STATIC_ZOOM_BOX_FACTOR * canvasHeight; 
  
  globals.pointer = {};
  globals.pointer.down = false;  
             
  window.addEventListener('hashchange', handleHashChange, false); // This event handler executes whenever the URL hash string changes.
  
  canvas.addEventListener('mousedown', handlePointer, false);
  canvas.addEventListener('mousemove', handlePointer, false);
  canvas.addEventListener('mouseup', handlePointer, false);    
        
  document.getElementById('resetButton').addEventListener('click', handleResetButton, false);
  document.getElementById('lightenButton').addEventListener('click', handleLightenButton, false);    
  document.getElementById('saveButton').addEventListener('click', handleSaveButton, false);        
  document.getElementById('filenameForm').addEventListener('submit', handleFormSubmit, false);    
  
  ctx.fillStyle = "rgba(255, 0, 0, 0.3)";

  handleHashChange(); // On page load, simulate a page URL change to draw the initial Mandelbrot set.
} // handleLoad

getHashValues 함수는 현재 해시 문자열을 사용 가능한 숫자 값으로 변환하는 데 사용됩니다.


function getHashValues() {
  var dirtyComplexPlaneExtremaString = (window.location.hash).replace('#', ''); // Remove the leading "#" character from the string.
  var complexPlaneExtremaString = dirtyComplexPlaneExtremaString.split(','); // Returns an array. Assumes the following string form: "ReMax,ReMin,ImMax,ImMin,grayscaleFactor" (note that if grayscaleFactor is 1, the image's grayscale is not effected).
  
  var ReMax = parseFloat( complexPlaneExtremaString[0] ); 
  var ReMin = parseFloat( complexPlaneExtremaString[1] ); 
  var ImMax = parseFloat( complexPlaneExtremaString[2] ); 
  var ImMin = parseFloat( complexPlaneExtremaString[3] );
  var grayscaleFactor = parseFloat( complexPlaneExtremaString[4] );
  
  if ( isNaN(ReMax) || isNaN(ReMin) || isNaN(ImMax) || isNaN(ImMin) || isNaN(grayscaleFactor) ) { 
    return null;
  } // if 
  
  return {ReMax: ReMax, ReMin: ReMin, ImMax: ImMax, ImMin: ImMin, grayscaleFactor: grayscaleFactor};
} // getHashValues

첫 번째 단계에서는 해시 문자열의 앞에 오는 "#" 문자를 제거합니다. 그런 다음 복소 평면 극값을 "ReMax,ReMin,ImMax,ImMin,grayscaleFactor" 형식으로 가정하여 배열로 분할한 후 parseFloat를 사용하여 숫자 값으로 변환합니다.

예상 값이 누락되거나 형식이 잘못된 경우 null이 반환됩니다. 그렇지 않으면 복소 평면 극값을 포함하는 개체 리터럴이 반환됩니다.

handlePointer 이벤트 처리기는 다음과 같은 점을 제외하면 이전과 거의 동일합니다.


setExtrema(ReMax, ReMin, ImMax, ImMin); // Must set these globals prior to calling drawMandelbort because drawMandelbort accesses them.
if (window.setImmediate) {
  window.setImmediate(drawMandelbrot);
}
else {
  window.setTimeout(drawMandelbrot, 0);
}

다음으로 대체되었습니다.


window.location.hash = ReMax + "," + ReMin + "," + ImMax + "," + ImMin + "," + globals.grayscaleFactor;

예상할 수 있듯이 setImmediate/setTimeout 메서드가 drawMandelbrot 함수로 이동했습니다.


function drawMandelbrot(ReMax, ReMin, ImMax, ImMin, grayscaleFactor) {            
  document.getElementById('messageBox').innerHTML = "Calculating..."; // This isn't displayed until the drawMandelbrot function block exits. 
  document.getElementById('elapsedTime').innerHTML = ""; // Erase the prior run's statistics.           
  
  if (window.setImmediate) {
    window.setImmediate(calculateMandelbrot); // Allow the drawMandelbrot function to immediately terminate, thus printing "Calculating..." to the screen.       
  }
  else {
    window.setTimeout(calculateMandelbrot, 0); // Allow the drawMandelbrot function to immediately terminate, thus printing "Calculating..." to the screen.      
  }           
  
  function calculateMandelbrot() {
    var startTime = new Date(); // Report how long it takes to render this particular region of the Mandelbrot set.             
    
    var canvas = globals.canvas;
    var canvasWidth = canvas.width;
    var canvasHeight = canvas.height;
    var ctx = canvas.context;
    var imageDataObjectData = ctx.imageDataObject.data; // imageDataObjectData is a reference to, not a copy of, ctx.imageDataObject.data
    
    var maxPixelGrayscaleValue = 0; // This will contain the lightest shade of gray in the drawn Mandelbrot image.
    
    var x_coefficient = (ReMax - ReMin) / canvasWidth; // Keep the below loops as computation-free as possible.
    var y_coefficient = (ImMin - ImMax) / canvasHeight; // Keep the below loops as computation-free as possible.

    var iterationSum = 0;
    var currentPixel = 0;          
    for (var y = 0; y < canvasHeight; y++) {
      var c_Im = (y * y_coefficient) + ImMax; // Note that c = c_Re + c_Im*i
      
      for (var x = 0; x < canvasWidth; x++) {
        var c_Re = (x * x_coefficient) + ReMin // Convert the canvas x-coordinate to a complex plane Re-coordinate. c_Re represents the real part of a c value.
        
        var z_Re = 0; // The first z value (Zo) must be 0.
        var z_Im = 0; // The first z value (Zo) must be 0. Note that z = z_Re + z_Im*i
        
        var c_belongsToMandelbrotSet = true; // Assume that the c associated with Zn belongs to the Mandelbrot set (i.e., Zn remains bounded under iteration of Zn+1 = (Zn)^2 + c).
        var exponentialSmoothingSum = 0;
        for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
          iterationSum++; // Keep track of how many iterations were performed in total so we can report this to the user.
        
          var z_Re_squared = z_Re * z_Re; // A small speed optimization.
          var z_Im_squared = z_Im * z_Im; // A small speed optimization.
          
          exponentialSmoothingSum += Math.exp( -(z_Re_squared + z_Im_squared) ); // Technically, this should be e^(-|z|). However, avoiding the expensive square root operation doesn't really affect the resulting image.              
          if (exponentialSmoothingSum >= 255) { // Don't cycle through the gray colors.
            exponentialSmoothingSum = 255;
          }
  
          if (z_Re_squared + z_Im_squared > 4) { // Checks if |z^2| is greater than 2. This approach avoids the expensive square root operation.
            c_belongsToMandelbrotSet = false; // This complex c value is not part of the Mandelbrot set (because it will always tend towards infinity under iteration).
            break; // Immediately check the next c value to see if it belongs to the Mandelbrot set or not.
          } // if
          
          // The next two lines perform Zn+1 = (Zn)^2 + c (recall that (x + yi)^2 = x^2 - y^2 + 2xyi, thus the real part is x^2 - y^2 and the imaginary part is 2xyi).
          z_Im = (2 * z_Re * z_Im) + c_Im; // We must calculate the next value of z_Im first because it depends on the current value of z_Re (not the next value of z_Re).
          z_Re = z_Re_squared - z_Im_squared + c_Re; // Calculate the next value of z_Re.
        } // for   
        
        if (c_belongsToMandelbrotSet) { // This complex c value is probably part of the Mandelbrot set because Zn did not tend toward infinity within MAX_ITERATIONS iterations.
          imageDataObjectData[currentPixel++] = 0; // Red. Note that there are 255 possible shades of red, green, blue, and alpha (i.e., opacity).
          imageDataObjectData[currentPixel++] = 0; // Green.
          imageDataObjectData[currentPixel++] = 0; // Blue.
          imageDataObjectData[currentPixel++] = 255; // Alpha (i.e., 0% transparency).
        } 
        else { // This complex c value is definitely not part of the Mandelbrot set because Zn would tend toward infinity under iteration (i.e., |Zn| > 2).
          var pixelGrayscaleValue = 255 - exponentialSmoothingSum % 256; // Force the value of exponentialSmoothingSum to be between 0 and 255 inclusively. Note that all values for red, green, and blue are identical when using a grayscale.
          var adjustedPixelGrayscaleValue = pixelGrayscaleValue * grayscaleFactor; // Avoids doing this more than once.
          
          imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // Because we mod by 256, the value of exponentialSmoothingSum will always be between 0 and 255.
          imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // If exponentialSmoothingSum is 255 (its maximum possible value), then 255 % 256 = 255.
          imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // When exponentialSmoothingSum is 255, we have 255 - 255 = 0, so the shade values for RGB are all set to 0 (that is, the c-value pixel is rendered black - indicating that this particular c-value very slowly tends towards infinity).
          imageDataObjectData[currentPixel++] = 255; // Always draw the c-value pixels with no transparency.
          
          if (pixelGrayscaleValue > maxPixelGrayscaleValue) {
            maxPixelGrayscaleValue = pixelGrayscaleValue; // Determine the lightest shade of gray in case the user clicks the Lighten button.
          } // if
        } // if-else
      } // for
    } // for        
    
    globals.maxPixelGrayscaleValue = maxPixelGrayscaleValue; // Store the lightest shade of gray in case the user clicks the Lighten button.      
    ctx.putImageData(ctx.imageDataObject, 0, 0); // Render our carefully constructed canvas image data array to the canvas.
        
    var elapsedMilliseconds = (new Date()) - startTime;
    document.getElementById('elapsedTime').innerHTML = iterationSum.format() + " iterations in " + (elapsedMilliseconds / 1000).toFixed(2) + " seconds"; // Note that the UI element is not updated until after this block terminates (which is the desired behavior).            
    document.getElementById('messageBox').innerHTML = DEFAULT_MESSAGE; // Erase the "Calculating..." message and replace it with the default message.        
  } // calculateMandelbrot
} // drawMandelbrot

drawMandelbrot 함수의 주요 변경 내용은 grayscaleFactor 매개 변수의 값을 기반으로 이미지를 밝게하는 기능입니다.


function drawMandelbrot(ReMax, ReMin, ImMax, ImMin, grayscaleFactor) {
  .
  .
  .        
        if (c_belongsToMandelbrotSet) {
          imageDataObjectData[currentPixel++] = 0;
          imageDataObjectData[currentPixel++] = 0;
          imageDataObjectData[currentPixel++] = 0;
          imageDataObjectData[currentPixel++] = 255;
        } 
        else {
          var pixelGrayscaleValue = 255 - exponentialSmoothingSum % 256;green, and blue are identical when using a grayscale.
          var adjustedPixelGrayscaleValue = pixelGrayscaleValue * grayscaleFactor; 
          
          imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // Because we mod by 256, the value of exponentialSmoothingSum will always be between 0 and 255.
          imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // If exponentialSmoothingSum is 255 (its maximum possible value), then 255 % 256 = 255.
          imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // When exponentialSmoothingSum is 255, we have 255 - 255 = 0, so the shade values for RGB are all set to 0 (that is, the c-value pixel is rendered black - indicating that this particular c-value very slowly tends towards infinity).
          imageDataObjectData[currentPixel++] = 255;
          
          if (pixelGrayscaleValue > maxPixelGrayscaleValue) {
            maxPixelGrayscaleValue = pixelGrayscaleValue; // Determine the lightest shade of gray in case the user clicks the Lighten button.
          }
  .
  .
  . 
      
    globals.maxPixelGrayscaleValue = maxPixelGrayscaleValue; // Store the lightest shade of gray in case the user clicks the Lighten button.      
    ctx.putImageData(ctx.imageDataObject, 0, 0); // Render our carefully constructed canvas image data array to the canvas.
  .
  .
  . 


var adjustedPixelGrayscaleValue = pixelGrayscaleValue * grayscaleFactor 줄에서 볼 수 있듯이 grayscaleFactor 매개 변수를 현재 픽셀의 회색조 값(pixelGrayscaleValue)에 곱합니다. 그런 다음 조정된 이 회색조 값(adjustedPixelGrayscaleValue)을 사용하여 이전 코드 예제에 표시된 대로 imageDataObjectData 배열을 설정합니다.

이미지가 계산되면서 가장 밝은(최대값) 회색조 값이 결정되고 전역 변수에 저장됩니다.


globals.maxPixelGrayscaleValue = maxPixelGrayscaleValue;

밝게 단추를 클릭하면 현재 이미지의 가장 밝은 회색 음영이 이미 알려져 있으므로 더 밝은 버전의 이미지가 다음과 같이 그려집니다.


function handleLightenButton() {
  var grayscaleFactor = 255 / globals.maxPixelGrayscaleValue; // For the canvas element, 255 is white, 0 is black.

  window.location.hash = globals.ReMax + "," + globals.ReMin + "," + globals.ImMax + "," + globals.ImMin + "," + grayscaleFactor; // This invokes handleHashChange which, among other things, is responsibile for drawing the Mandelbrot set.
}

즉, handleLightenButton에서 다음과 같이 줄을 실행합니다.


window.location.hash = globals.ReMax + "," + globals.ReMin + "," + globals.ImMax + "," + globals.ImMin + "," + grayscaleFactor

URL 해시 문자열을 통해 drawMandelbrot 함수로 전달된 값(grayscaleFactor)이 검정색(0)은 검정색으로 유지되고 이미지에서 가장 밝은(최대값) 회색조 값은 흰색(255)이 되는 방식입니다.

다음과 같은 어두운 이미지를 URL과 함께 살펴보겠습니다.

Mandelbrot 집합의 어두운 영역(밝게 단추를 누르기 전)

http://samples.msdn.microsoft.com/Workshop/samples/mandelbrot/mandelbrotExplorer.html#-0.7521294801871914,-0.7521614447995544,0.03795943464882932,0.037938124907254026,1

URL에서 뒤에 오는 "1"은 이 이미지에 대한 입니다(grayscaleFactor실수 값에 1을 곱하면 아무 효과가 없으므로 1 값은 조명 효과가 없음).

밝게 단추를 클릭하여 가장 밝은 회색조 값을 사용하여 약 3.0386의 grayscaleFactor를 생성합니다.

Mandelbrot 집합의 어두운 영역(밝게 단추를 누른 후)

http://samples.msdn.microsoft.com/Workshop/samples/mandelbrot/mandelbrotExplorer.html#-0.7521294801871914,-0.7521614447995544,0.03795943464882932,0.037938124907254026,3.03862433470554

이 값(3.0386)에 첫 번째 이미지에 있는 모든 픽셀의 회색조 값을 곱하여 밝아진 두 번째 이미지가 생성되었습니다.

이와 별도로 grayscaleFactor = 255 / globals.maxPixelGrayscaleValue이므로 어두운 이미지의 가장 밝은(최대값) 회색조 값은 약 84가 됩니다.

grayScaleFactor 방정식

따라서 가장 밝은 회색조 값은 밝아진 이미지에서 다음과 같이 흰색이 됩니다.

84 x 3.0386(약 255임)의 방정식

0에 실수를 곱하면 항상 0이 되므로 검정색 픽셀(0)은 검정색으로 유지됩니다.

Mandelbrot 집합의 품질 이미지를 생성할 수 있으므로 다음으로 추가할 논리적 기능은 로컬에 이미지를 저장하는 기능입니다. 이 기능은 캔버스 이미지를 로컬에 저장에서 설명합니다.

관련 항목

캔버스 이미지를 로컬에 저장

 

 

표시:
© 2014 Microsoft