internal static Bitmap RenderFractal(Size imageSize, RectangleD complexArea, int maximumIterations, Func<int, double, Color> colorFunction) { var fractalBitmap = new Bitmap(imageSize.Width, imageSize.Height, PixelFormat.Format24bppRgb); // Use LockBits and pointers for faster image processing. GetPixel and SetPixel are slow. BitmapData fractalBitmapData = fractalBitmap.LockBits(new Rectangle(0, 0, imageSize.Width, imageSize.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); // Process each row of the image in parallel. Parallel.For(0, imageSize.Height, yPixelCoord => { for (long xPixelCoord = 0; xPixelCoord < imageSize.Width; xPixelCoord++) { unsafe { // 24bit bitmaps store color bytes in BGR order, not RGB. byte* pointerBGR = (byte*)((long)fractalBitmapData.Scan0 + (yPixelCoord * fractalBitmapData.Stride) + (xPixelCoord * 3)); // Convert pixel coords to complex plane coords, using the center of the pixel, not the upper left corner. double x = (complexArea.Width * ((xPixelCoord + 0.5D) / imageSize.Width)) + complexArea.Left; double y = (complexArea.Height * ((yPixelCoord + 0.5D) / imageSize.Height)) + complexArea.Top; // Color pixel based on number of iterations and escape magnitude double finalMagnitude; Color color = colorFunction(CountIterations(x, y, maximumIterations, out finalMagnitude), finalMagnitude); *pointerBGR = color.B; *++pointerBGR = color.G; *++pointerBGR = color.R; } } }); fractalBitmap.UnlockBits(fractalBitmapData); return fractalBitmap; }
internal static RectangleD GetNextZoomArea(Size screenSize, Point zoomLocation, RectangleD currentArea) { // Convert pixel coords of next zoom area into complex coords. double newLeft = (((double)zoomLocation.X / screenSize.Width) * currentArea.Width) + currentArea.Left; double newTop = (((double)zoomLocation.Y / screenSize.Height) * currentArea.Height) + currentArea.Top; return new RectangleD(newLeft, newTop, currentArea.Width / 10D, currentArea.Height / 10D); }
internal static RectangleD GetInitialArea(Size screenSize) { // Adjust viewing area to contain the whole Mandelbrot set while matching the screen's aspect ratio. var rectangle = new RectangleD(-2, -1, 3, 2); double scalar = (screenSize.Width / 3D) / (screenSize.Height / 2D); if (scalar > 1) { rectangle.Width *= scalar; rectangle.Left = -0.5 - (rectangle.Width / 2D); } else { rectangle.Height /= scalar; rectangle.Top = 0 - (rectangle.Height / 2D); } return rectangle; }
private void ScreenSaverForm_Shown(object sender, EventArgs e) { Graphics formGraphics = this.CreateGraphics(); formGraphics.DrawImageUnscaled(this.initialFractalBitmap, 0, 0); this.stopwatch.Restart(); Task.Run(() => { while (true) { RectangleD fractalArea = this.initialFractalArea; var currentBitmap = this.initialFractalBitmap.Clone() as Bitmap; for (int zoom = 1; zoom < 10; zoom++) { Rectangle zoomRectangle = Mandelbrot.GetNextZoomRectangle(currentBitmap); // If couldn't find suitable area to zoom in, zoom out to starting fractal and start over. if (zoomRectangle == Rectangle.Empty) { break; } fractalArea = Mandelbrot.GetNextZoomArea(this.Size, zoomRectangle.Location, fractalArea); Bitmap nextBitmap = Mandelbrot.RenderFractal(this.Size, fractalArea, BaseMaximumIterations * (zoom + 1), this.colorFunction); IntPtr[] nativeZoomInBitmapHandles = CreateNativeZoomInBitmaps(currentBitmap, nextBitmap, zoomRectangle.Location); // Wait a bit between zooms, even if all computations are complete. this.stopwatch.Stop(); if (this.stopwatch.ElapsedMilliseconds < WaitMilliseconds) { Thread.Sleep(WaitMilliseconds - (int)this.stopwatch.ElapsedMilliseconds); } AnimateBlink(formGraphics, currentBitmap, zoomRectangle); AnimateZoomIn(formGraphics, nativeZoomInBitmapHandles, this.Size); this.stopwatch.Restart(); // Clean up animation bitmaps after use. foreach (IntPtr nativeZoomInBitmapHandle in nativeZoomInBitmapHandles) { NativeMethods.GDI32.DeleteObject(nativeZoomInBitmapHandle); } currentBitmap.Dispose(); currentBitmap = nextBitmap; } // After 10 zoom-ins, zoom out to starting fractal and start over. double percentX = ((fractalArea.Left + (fractalArea.Width / 2D)) - this.initialFractalArea.Left) / this.initialFractalArea.Width; double percentY = ((fractalArea.Top + (fractalArea.Height / 2D)) - this.initialFractalArea.Top) / this.initialFractalArea.Height; var zoomPoint = new Point((int)(this.Width * percentX), (int)(this.Height * percentY)); IntPtr[] nativeZoomOutBitmapHandles = CreateNativeZoomOutBitmaps(currentBitmap, this.initialFractalBitmap, zoomPoint); currentBitmap.Dispose(); // Wait a bit between zooms, even if all computations are complete. this.stopwatch.Stop(); if (this.stopwatch.ElapsedMilliseconds < WaitMilliseconds) { Thread.Sleep(WaitMilliseconds - (int)this.stopwatch.ElapsedMilliseconds); } AnimateZoomOut(formGraphics, nativeZoomOutBitmapHandles, this.Size); this.stopwatch.Restart(); // Clean up animation bitmaps after use. foreach (IntPtr nativeZoomOutBitmapHandle in nativeZoomOutBitmapHandles) { NativeMethods.GDI32.DeleteObject(nativeZoomOutBitmapHandle); } } }); }
private void ScreenSaverForm_Load(object sender, EventArgs e) { this.AssignColorScheme(); this.initialFractalArea = Mandelbrot.GetInitialArea(this.Size); this.initialFractalBitmap = Mandelbrot.RenderInitialFractal(this.Size, this.initialFractalArea, BaseMaximumIterations, this.colorFunction); }
internal static Bitmap RenderInitialFractal(Size imageSize, RectangleD complexArea, int maximumIterations, Func<int, double, Color> colorFunction) { var fractalBitmap = new Bitmap(imageSize.Width, imageSize.Height, PixelFormat.Format24bppRgb); // Use LockBits and pointers for faster image processing. GetPixel and SetPixel are slow. BitmapData fractalBitmapData = fractalBitmap.LockBits(new Rectangle(Point.Empty, imageSize), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); // Process each row of the image in parallel, using multiple CPU cores for speed. Parallel.For(0, imageSize.Height / 2, yPixelCoord => { for (long xPixelCoord = 0; xPixelCoord < imageSize.Width; xPixelCoord++) { unsafe { // 24bit bitmaps store color bytes in BGR order, not RGB. byte* pointerBGR = (byte*)((long)fractalBitmapData.Scan0 + (yPixelCoord * fractalBitmapData.Stride) + (xPixelCoord * 3)); // For initial fractal image, the bottom half is a mirror of top half. byte* mirrorBGR = (byte*)((long)fractalBitmapData.Scan0 + (((imageSize.Height - 1) - yPixelCoord) * fractalBitmapData.Stride) + (xPixelCoord * 3)); // Convert pixel coords to complex plane coords, using the center of the pixel, not the upper left corner. double x = (complexArea.Width * ((xPixelCoord + 0.5D) / imageSize.Width)) + complexArea.Left; double y = (complexArea.Height * ((yPixelCoord + 0.5D) / imageSize.Height)) + complexArea.Top; // Color pixel based on number of iterations and escape magnitude // Save time by only calculating iterations for points not in one of the large bulbs, as many pixels in the initial fractal are. double finalMagnitude; var color = (IsInCardioid(x, y) || IsInPeriod2Bulb(x, y)) ? Color.Black : colorFunction(CountIterations(x, y, maximumIterations, out finalMagnitude), finalMagnitude); *mirrorBGR = *pointerBGR = color.B; *++mirrorBGR = *++pointerBGR = color.G; *++mirrorBGR = *++pointerBGR = color.R; } } }); fractalBitmap.UnlockBits(fractalBitmapData); return fractalBitmap; }
internal static RectangleD GetNextZoomArea(Size screenSize, Point zoomLocation, RectangleD currentArea) { // Convert pixel coords of next zoom area into complex coords. double newLeft = (((double)zoomLocation.X / screenSize.Width) * currentArea.Width) + currentArea.Left; double newTop = (((double)zoomLocation.Y / screenSize.Height) * currentArea.Height) + currentArea.Top; return(new RectangleD(newLeft, newTop, currentArea.Width / 10D, currentArea.Height / 10D)); }