/// <summary> /// Mouse-up handler for main form. The coordinates of the rectangle are /// saved so the new drawing can be rendered. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void mouseUpOnForm(object sender, MouseEventArgs e) { if (zoomCheckbox.Checked) { double x = Convert.ToDouble(e.X); double y = Convert.ToDouble(e.Y); ComplexPoint pixelCoord = new ComplexPoint((int)(xValue + (1005 / (zoomScale)) / 4), (int)(yValue + (691 / (zoomScale)) / 4));// zoomCoord2 = myPixelManager.GetAbsoluteMathsCoord(pixelCoord); // Swap to ensure that zoomCoord1 stores the lower-left // coordinate for the zoom region, and zoomCoord2 stores the // upper right coordinate. if (zoomCoord2.real < zoomCoord1.real) { double temp = zoomCoord1.real; zoomCoord1.real = zoomCoord2.real; zoomCoord2.real = temp; } if (zoomCoord2.img < zoomCoord1.img) { double temp = zoomCoord1.img; zoomCoord1.img = zoomCoord2.img; zoomCoord2.img = temp; } yMinCheckBox.Text = Convert.ToString(zoomCoord1.img); yMaxCheckBox.Text = Convert.ToString(zoomCoord2.img); xMinCheckBox.Text = Convert.ToString(zoomCoord1.real); xMaxCheckBox.Text = Convert.ToString(zoomCoord2.real); RenderImage(); } }
/// <summary> /// Add complex value, arg, to this complex point, Z. The result is /// another complex number. /// </summary> /// <param name="arg">Complex number to add</param> /// <returns>Z + arg</returns> public ComplexPoint doCmplxAdd(ComplexPoint arg) { real += arg.real; img += arg.img; return(this); }
/// <summary> /// On-click handler for main form. Defines the points (lower-left and upper-right) /// of a zoom rectangle. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void mouseClickOnForm(object sender, MouseEventArgs e) { if (zoomCheckbox.Checked) { Pen box = new Pen(Color.Black); double x = Convert.ToDouble(e.X); xValue = x; double y = Convert.ToDouble(e.Y); yValue = y; try { zoomScale = Convert.ToInt16(zoomTextBox.Text); } catch (Exception c) { MessageBox.Show("Error: " + c.Message, "Error"); } // Zoom scale has to be above 0, or their is no point in zooming. if (zoomScale < 1) { MessageBox.Show("Zoom scale must be above 0"); zoomScale = 7; zoomTextBox.Text = "7"; return; } ComplexPoint pixelCoord = new ComplexPoint((int)(xValue - (1005 / (zoomScale)) / 4), (int)(yValue - (691 / (zoomScale)) / 4));// zoomCoord1 = myPixelManager.GetAbsoluteMathsCoord(pixelCoord); } }
/// <summary> /// Add complex value, arg, to this complex point, Z. The result is /// another complex number. /// </summary> /// <param name="arg">Complex number to add</param> /// <returns>Z + arg</returns> public ComplexPoint doCmplxAdd(ComplexPoint arg) { x += arg.x; y += arg.y; return(this); }
/// <summary> /// Converts a pixel-coordinate increment (small change in X, Y /// screen coordiante) to the corresponding increment in mathematical /// coordinates. This is used, for example, when drawing the Mandlebrot /// set with chosen X, Y pixel steps, for which the cooresponding /// mathematical steps need to be known. /// /// This is done using the transformation functions that convert /// from maths coordinates to pixel coordinates. If these are /// respectively: /// /// Fx() and Fy() for the x and y domains, then to convert in the /// opposite direction, from pixels to maths coordinates we need /// to use: /// /// dFx()/dx and dFy()/dy (which are the derivates of each function), /// then multiply each by the corresponding pixel increments in /// either x or y. /// /// This implementation uses pre-calculated constant scale factors /// for an efficient implementation. /// /// </summary> /// <param name="pixelCoord">Screen coordinate</param> /// <returns></returns> public ComplexPoint GetDeltaMathsCoord(ComplexPoint pixelCoord) { ComplexPoint result = new ComplexPoint( pixelCoord.x / convConstX1, pixelCoord.y / convConstY2); return(result); }
/// <summary> /// Convert from maths coordinates to pixel coordinates. /// </summary> /// <param name="cmplxPoint">Complex number (mathematical coordiantes)</param> /// <returns>Pixel coordinate, also a complex number but represented /// as an X,Y screen coordinate</returns> public PixelCoord GetPixelCoord(ComplexPoint cmplxPoint) { PixelCoord result = new PixelCoord(); result.xPixel = (int)(convConstX1 * cmplxPoint.x - convConstX2); result.yPixel = (int)(convConstY1 - convConstY2 * cmplxPoint.y); return(result); }
/// <summary> /// Get absolute maths coordinate from pixel coordinate. This is effectively /// an inverse calcuate: given a pixel screen coordinate it returns the /// corresponding mathematical point. /// </summary> /// <param name="pixelCoord">Screen coordinate</param> /// <returns>Mathematical point corresponding to pixelCoord</returns> public ComplexPoint GetAbsoluteMathsCoord(ComplexPoint pixelCoord) { ComplexPoint result = new ComplexPoint( (convConstX2 + pixelCoord.x) / convConstX1, (convConstY1 - pixelCoord.y) / convConstY2); return(result); }
/// <summary> /// Calculate the square of complex point, Z**2. The /// result is another complex number: (x*x - y*y) + i*2*x*y. /// </summary> /// <returns>Square of complex point</returns> public ComplexPoint doCmplxSq() { ComplexPoint result = new ComplexPoint(0, 0); result.x = x * x - y * y; result.y = 2 * x * y; return(result); }
/// <summary> /// Calculate complex square plus complex constant. The result /// is another complex number. /// </summary> /// <param name="arg"></param> /// <returns>Z**2 + arg</returns> public ComplexPoint DoCmplxSqPlusConst(ComplexPoint arg) { ComplexPoint result = DoCmplxSq(); //new ComplexPoint(0, 0); //result.real = real * real - img * img; //result.img = 2 * real * img; result.real += arg.real; result.img += arg.img; return(result); }
/// <summary> /// Calculate complex square plus complex constant. The result /// is another complex number. /// </summary> /// <param name="arg"></param> /// <returns>Z**2 + arg</returns> public ComplexPoint doCmplxSqPlusConst(ComplexPoint arg) { ComplexPoint result = new ComplexPoint(0, 0); result.x = x * x - y * y; result.y = 2 * x * y; result.x += arg.x; result.y += arg.y; return(result); }
/// <summary> /// Constructor. /// </summary> /// <param name="graphics"></param> /// <param name="screenBottomLeftCorner"></param> /// <param name="screenTopRightCorner"></param> public ScreenPixelManage(Graphics graphics, ComplexPoint screenBottomLeftCorner, ComplexPoint screenTopRightCorner) { // Transform from mathematical to pixel coordinates. // // The following are long-handed calulations, now replaced with more efficient calculations // using convConst** values. // this.xPixel = (int) ((graphics.VisibleClipBounds.Size.Width) / (screenTopRightCorner.x - screenBottomLeftCorner.x) * (cmplxPoint.x - screenBottomLeftCorner.x)); // this.yPixel = (int) (graphics.VisibleClipBounds.Size.Height - graphics.VisibleClipBounds.Size.Height / (screenTopRightCorner.y - screenBottomLeftCorner.y) * (cmplxPoint.y - screenBottomLeftCorner.y)); convConstX1 = graphics.VisibleClipBounds.Size.Width / (screenTopRightCorner.x - screenBottomLeftCorner.x); convConstX2 = convConstX1 * screenBottomLeftCorner.x; convConstY1 = graphics.VisibleClipBounds.Size.Height * (1.0 + screenBottomLeftCorner.y / (screenTopRightCorner.y - screenBottomLeftCorner.y)); convConstY2 = graphics.VisibleClipBounds.Size.Height / (screenTopRightCorner.y - screenBottomLeftCorner.y); }
private void RenderImage() { try { statusLabel.Text = "Status: Rendering"; if (Convert.ToBoolean(pixelStepTextBox.Text.Equals("")) || Convert.ToBoolean(pixelStepTextBox.Text.Equals("0")) || Convert.ToBoolean(iterationCountTextBox.Text.Equals("")) || Convert.ToBoolean(yMinCheckBox.Text.Equals("")) || Convert.ToBoolean(yMaxCheckBox.Text.Equals("")) || Convert.ToBoolean(xMinCheckBox.Text.Equals("")) || Convert.ToBoolean(xMaxCheckBox.Text.Equals(""))) { // Choose default parameters and warn the user if the settings are all empty. pixelStepTextBox.Text = "1"; iterationCountTextBox.Text = "85"; yMinCheckBox.Text = "-1"; yMaxCheckBox.Text = "1"; xMinCheckBox.Text = "-2"; xMaxCheckBox.Text = "1"; MessageBox.Show("Invalid fields detected. Using default values."); statusLabel.Text = "Status: Error"; return; } else { // Show zoom and undo controls. zoomCheckbox.Show(); undoButton.Show(); undoNum++; } // Mandelbrot iteration count. kMax = Convert.ToInt32(iterationCountTextBox.Text); numColours = kMax; // If colourTable is not yet created or kMax has changed, create colourTable. if ((colourTable == null) || (kMax != colourTable.kMax) || (numColours != colourTable.nColour)) { colourTable = new ColourTable(numColours, kMax); } // Get the x, y range (mathematical coordinates) to plot. yMin = Convert.ToDouble(yMinCheckBox.Text); yMax = Convert.ToDouble(yMaxCheckBox.Text); xMin = Convert.ToDouble(xMinCheckBox.Text); xMax = Convert.ToDouble(xMaxCheckBox.Text); // Zoom scale. zoomScale = Convert.ToInt16(zoomTextBox.Text); // Clear any existing graphics content. g.Clear(Color.White); // Initialise working variables. int height = (int)g.VisibleClipBounds.Size.Height; int kLast = -1; double modulusSquared; Color color; Color colorLast = Color.Red; // Get screen boundary (lower left & upper right). This is // used when calculating the pixel scaling factors. ComplexPoint screenBottomLeft = new ComplexPoint(xMin, yMin); ComplexPoint screenTopRight = new ComplexPoint(xMax, yMax); // Create pixel manager. This sets up the scaling factors used when // converting from mathemathical to screen (pixel units) using the myPixelManager = new ScreenPixelManage(g, screenBottomLeft, screenTopRight); // The pixel step size defines the increment in screen pixels for each point // at which the Mandelbrot calcualtion will be done. e.g. a Step of 5 means // that the calcualtion will be done a 5-pixel increments. The X & Y increments // are the same. // // This increment is converted to mathematical coordinates. int xyPixelStep = Convert.ToInt16(pixelStepTextBox.Text); ComplexPoint pixelStep = new ComplexPoint(xyPixelStep, xyPixelStep); ComplexPoint xyStep = myPixelManager.GetDeltaMathsCoord(pixelStep); // Start stopwatch - used to measure performance improvements // (from improving the efficiency of the maths implementation). Stopwatch sw = new Stopwatch(); sw.Start(); // Main loop, nested over Y (outer) and X (inner) values. int lineNumber = 0; int yPix = myBitmap.Height - 1; for (double y = yMin; y < yMax; y += xyStep.img) { int xPix = 0; for (double x = xMin; x < xMax; x += xyStep.real) { // Create complex point C = x + i*y. ComplexPoint c = new ComplexPoint(x, y); // Initialise complex value Zk. ComplexPoint zk = new ComplexPoint(0, 0); // Do the main Mandelbrot calculation. Iterate until the equation // converges or the maximum number of iterations is reached. int k = 0; do { zk = zk.DoCmplxSqPlusConst(c); modulusSquared = zk.DoMoulusSq(); k++; } while ((modulusSquared <= 4.0) && (k < kMax)); if (k < kMax) { // Max number of iterations was not reached. This means that the // equation converged. Now assign a colour to the current pixel that // depends on the number of iterations, k, that were done. if (k == kLast) { // If the iteration count is the same as the last count, re-use the // last pen. This avoids re-calculating colour factors which is // computationally intensive. We benefit from this often because // adjacent pixels are often the same colour, especially in large parts // of the Mandelbrot set that are away from the areas of detail. color = colorLast; } else { // Calculate coluor scaling, from k. We don't use complicated/fancy colour // lookup tables. Instead, the following simple conversion works well: // // hue = (k/kMax)**0.25 where the constant 0.25 can be changed if wanted. // This formula stretches colours allowing more to be assigned at higher values // of k, which brings out detail in the Mandelbrot images. // The following is a full colour calculation, replaced now with colour table. // Uncomment and disable the colour table if wanted. The colour table works // well but supports fewer colours than full calculation of hue and colour // using double-precision arithmetic. //double colourIndex = ((double)k) / kMax; //double hue = Math.Pow(colourIndex, 0.25); // Colour table lookup. // Convert the hue value to a useable colour and assign to current pen. // The saturation and lightness are hard-coded at 0.9 and 0.6 respectively, // which work well. color = colourTable.GetColour(k); colorLast = color; } // Draw single pixel if (xyPixelStep == 1) { // Pixel step is 1, set a single pixel. if ((xPix < myBitmap.Width) && (yPix >= 0)) { myBitmap.SetPixel(xPix, yPix, color); } } else { // Pixel step is > 1, set a square of pixels. for (int pX = 0; pX < xyPixelStep; pX++) { for (int pY = 0; pY < xyPixelStep; pY++) { if (((xPix + pX) < myBitmap.Width) && ((yPix - pY) >= 0)) { myBitmap.SetPixel(xPix + pX, yPix - pY, color); } } } } } xPix += xyPixelStep; } yPix -= xyPixelStep; lineNumber++; if ((lineNumber % 120) == 0) { Refresh(); } } // Finished rendering. Stop the stopwatch and show the elapsed time. sw.Stop(); Refresh(); stopwatchLabel.Text = Convert.ToString(sw.Elapsed.TotalSeconds); statusLabel.Text = "Status: Render complete"; // Save current settings to undo file. StreamWriter writer = new StreamWriter(@"C:\Users\" + userName + "\\mandelbrot_config\\Undo\\undo" + undoNum + ".txt"); writer.Write(pixelStepTextBox.Text + Environment.NewLine + iterationCountTextBox.Text + Environment.NewLine + yMinCheckBox.Text + Environment.NewLine + yMaxCheckBox.Text + Environment.NewLine + xMinCheckBox.Text + Environment.NewLine + xMaxCheckBox.Text); writer.Close(); writer.Dispose(); } catch (Exception e2) { MessageBox.Show("Exception Trapped: " + e2.Message, "Error"); statusLabel.Text = "Status: Error"; } }