/// <summary>
        /// Generate the stereogram on a background thread.  First wait for the specified interval and see if another request pre-empts us.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void generate_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            // Keep generating as long as there's a request in the pipe
            while (requestOptions != null)
            {
                Options options = requestOptions;
                requestOptions = null;

                if (options.time.Ticks > DateTime.Now.Ticks)
                {
                    TimeSpan delta = new TimeSpan(options.time.Ticks - DateTime.Now.Ticks);
                    if (delta.TotalMilliseconds > 50)
                    {
                        Thread.Sleep(delta);
                    }
                }

                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }

                if (options != null && requestOptions == null)
                {
                    ThreadPriority old = Thread.CurrentThread.Priority;
                    try
                    {
                        Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
                        generator = StereogramGenerator.Get(options);
                        Stereogram stereogram = generator.Generate();
                        stereogram.Name = String.Format("{0} + {1}", options.depthmap.Name, options.texture.Name);
                        e.Result        = stereogram;
                    }
                    finally
                    {
                        Thread.CurrentThread.Priority = old;
                    }
                }
            }
        }
        // When the thread completes, we hopefully have a completed stereogram to return to the callback
        private void generate_Completed(object sender, RunWorkerCompletedEventArgs e)
        {
            // First, handle the case where an exception was thrown.
            if (e.Error != null)
            {
                // Let the callback handle a NULL result
                if (callback != null)
                {
                    callback(null);
                }
            }
            else if (e.Cancelled)
            {
            }
            else if (e.Result is Stereogram)
            {
                Stereogram stereogram = (Stereogram)e.Result;

                if (callback != null)
                {
                    callback(stereogram);
                }
            }
        }
        /// <summary>
        /// Generate the stereogram.  Does all the common functionality, then calls the delegate
        /// set by the subclass to do the actual work.
        /// </summary>
        public Stereogram Generate()
        {
            // Let's do some profiling
            System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
            timer.Start();

            // Convert texture to RGB24 and scale it to fit the separation (preserving ratio but doubling width for HQ mode)
            bmTexture = texture.GetToScaleAndFormat(textureWidth, textureHeight, PixelFormats.Pbgra32);

            // Resize the depthmap to our target resolution
            bmDepthMap = depthmap.GetToScale(depthWidth, resolutionY);

            // Create a great big 2D array to hold the bytes - wasteful but convenient
            // ... and necessary for parallelisation
            pixels = new UInt32[lineWidth * rows];

            // Copy the texture data into a buffer
            texturePixels = new UInt32[textureWidth * textureHeight];
            bmTexture.CopyPixels(new Int32Rect(0, 0, textureWidth, textureHeight), texturePixels, textureWidth * bytesPerPixel, 0);

            // Copy the depthmap data into a buffer
            depthBytes = new byte[depthWidth * rows];
            bmDepthMap.CopyPixels(new Int32Rect(0, 0, depthWidth, rows), depthBytes, depthWidth, 0);

            // Can mock up a progress indicator
            GeneratedLines = 0;

            // Prime candidate for Parallel.For... yes, about doubles the speed of generation on my Quad-Core
            if (System.Diagnostics.Debugger.IsAttached)     // Don't run parallel when debugging
            {
                for (int y = 0; y < rows; y++)
                {
                    DoLine(y);
                    if (y > GeneratedLines)
                    {
                        GeneratedLines = y;
                    }
                }
            }
            else
            {
                Parallel.For(0, rows, y =>
                {
                    if (false == abort)
                    {
                        DoLine(y);
                    }
                    if (y > GeneratedLines)
                    {
                        GeneratedLines = y;
                    }
                });
            }

            if (abort)
            {
                return(null);
            }

            // Virtual finaliser... not needed for any current algorithms
            Finalise();

            // Create a writeable bitmap to dump the stereogram into
            wbStereogram = new WriteableBitmap(lineWidth, resolutionY, 96.0, 96.0, bmTexture.Format, bmTexture.Palette);
            wbStereogram.WritePixels(new Int32Rect(0, 0, lineWidth, rows), pixels, lineWidth * bytesPerPixel, 0);

            BitmapSource bmStereogram = wbStereogram;

            // High quality images need to be scaled back down...
            if (oversample > 1)
            {
                double over   = (double)oversample;
                double centre = lineWidth / 2;
                while (over > 1)
                {
                    // Scale by steps... could do it in one pass, but quality would depend on what the hardware does?
                    double div = Math.Min(over, 2.0);
//                    double div = over;
                    ScaleTransform scale = new ScaleTransform(1.0 / div, 1.0, centre, 0);
                    bmStereogram = new TransformedBitmap(bmStereogram, scale);
                    over        /= div;
                    centre      /= div;
                }
            }

            if (bAddConvergenceDots)
            {
                // Because I made these fields read-only, I can't now restore them... 'spose I could add the dots at hi-res but I'd still need to account for the stretching
                double sep = separation / oversample;
                double mid = midpoint / oversample;

                RenderTargetBitmap rtStereogram = new RenderTargetBitmap(bmStereogram.PixelWidth, bmStereogram.PixelHeight, 96.0, 96.0, PixelFormats.Pbgra32);

                DrawingVisual  dots = new DrawingVisual();
                DrawingContext dc   = dots.RenderOpen();
                dc.DrawImage(bmStereogram, new Rect(0.0, 0.0, rtStereogram.Width, rtStereogram.Height));
                dc.DrawEllipse(new SolidColorBrush(Colors.Black),
                               new Pen(new SolidColorBrush(Color.FromArgb(128, 0, 0, 0)), 1.0),
                               new Point(mid - sep / 2, rtStereogram.Height / 16), sep / 16, sep / 16);
                dc.DrawEllipse(new SolidColorBrush(Colors.Black),
                               new Pen(new SolidColorBrush(Color.FromArgb(128, 0, 0, 0)), 1.0),
                               new Point(mid + sep / 2, rtStereogram.Height / 16), sep / 16, sep / 16);
                dc.Close();

                rtStereogram.Render(dots);

                bmStereogram = rtStereogram;
            }

            // Freeze the bitmap so it can be passed to other threads
            bmStereogram.Freeze();

            timer.Stop();

            Stereogram stereogram = new Stereogram(bmStereogram);

            stereogram.options      = this.options;
            stereogram.Name         = String.Format("{0}+{1}+{2}", depthmap.Name, texture.Name, options.algorithm.ToString());
            stereogram.Milliseconds = timer.ElapsedMilliseconds;
            return(stereogram);
        }