private Bitmap RenderFrame(LinePairCollection lines, FastBitmap startImage, FastBitmap endImage, double percent) { var forwardsAndBackwards = InterpolateLines(lines, percent); using (Bitmap forward = ComputePreimage( startImage, forwardsAndBackwards.Item1)) using (Bitmap backward = ComputePreimage( endImage, forwardsAndBackwards.Item2)) return BlendImages(forward, backward, 1 - percent); }
/// <summary>Create the morphed images and video.</summary> public void Render(IImageWriter imageWriter, LinePairCollection lines, Bitmap startImageBmp, Bitmap endImageBmp) { _curFrame = 0; double percentagePerFrame = 1.0 / (_options.NumberOfOutputFrames - 1); using (Bitmap clonedStart = Utilities.CreateNewBitmapFrom(startImageBmp)) using (Bitmap clonedEnd = Utilities.CreateNewBitmapFrom(endImageBmp)) { // Write out the starting picture imageWriter.AddFrame(clonedStart); _curFrame = 1; UpdateProgressChanged(); using (FastBitmap startImage = new FastBitmap(startImageBmp)) using (FastBitmap endImage = new FastBitmap(endImageBmp)) { for (int i = 1; i < _options.NumberOfOutputFrames - 1; i++) { _cancellationToken.ThrowIfCancellationRequested(); using (Bitmap frame = RenderFrame(lines, startImage, endImage, percentagePerFrame * i)) { imageWriter.AddFrame(frame); } _curFrame++; UpdateProgressChanged(); } } imageWriter.AddFrame(clonedEnd); _curFrame++; UpdateProgressChanged(); } }
private void ConfigurePictureBoxes(LinePairCollection lines, Image startImage, Image endImage) { // The lines collection stores the line pairs. Configure the start image to selec the 0th line from each pair. pbStartImage.LinePairs = _lines; pbStartImage.ImageNumber = 0; pbStartImage.Image = startImage; // ... and configure the start image to selec the 0th line from each pair. pbEndImage.LinePairs = _lines; pbEndImage.ImageNumber = 1; pbEndImage.Image = endImage; if (autoSizeToolStripMenuItem.Checked) { pbStartImage.Dock = DockStyle.None; pbStartImage.SizeMode = PictureBoxSizeMode.AutoSize; pbEndImage.Dock = DockStyle.None; pbEndImage.SizeMode = PictureBoxSizeMode.AutoSize; } else if (zoomToolStripMenuItem.Checked) { pbStartImage.Dock = DockStyle.Fill; pbStartImage.SizeMode = PictureBoxSizeMode.Zoom; pbEndImage.Dock = DockStyle.Fill; pbEndImage.SizeMode = PictureBoxSizeMode.Zoom; } btnMorph.Enabled = pbStartImage.Image != null && pbEndImage.Image != null; pbStartImage.Refresh(); pbEndImage.Refresh(); }
public Bitmap RenderFrame(LinePairCollection lines, Bitmap startImage, Bitmap endImage, double percent) { using (FastBitmap fastStartImage = new FastBitmap(startImage)) using (FastBitmap fastEndImage = new FastBitmap(endImage)) { return RenderFrame(lines, fastStartImage, fastEndImage, percent); } }
private void Form1_Load(object sender, EventArgs e) { // Create a Task factory to use for targetting the UI thread _uiThread = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()); // Initialize the picture boxes _lines = new LinePairCollection(); ConfigurePictureBoxes(_lines, null, null); // Set up the parallel menu item to reflect the number of cores parallelProcessingModeToolStripMenuItem.Text += " (" + Environment.ProcessorCount + "x)"; }
/// <summary>Generates the output image for a source image and a collection of morph lines.</summary> /// <param name="bmp">The input image.</param> /// <param name="pairs">The lines used to skew the image.</param> /// <returns>The computed image.</returns> private unsafe Bitmap ComputePreimage(FastBitmap bmp, LinePairCollection pairs) { int width = bmp.Size.Width, height = bmp.Size.Height; Bitmap output = new Bitmap(width, height); using (FastBitmap fastOut = new FastBitmap(output)) { if (!_useParallelism) { for (int j = 0; j < height; j++) { _cancellationToken.ThrowIfCancellationRequested(); for (int i = 0; i < width; i++) { PointF pf = GetPreimageLocation(new PointF(i, j), pairs); Point p = ClampPoint(pf, new Size(width, height)); PixelData* inPixel = bmp[p.X, p.Y]; PixelData* outPixel = fastOut[i, j]; outPixel->R = inPixel->R; outPixel->G = inPixel->G; outPixel->B = inPixel->B; } } } else { Parallel.For(0, height, new ParallelOptions { CancellationToken=_cancellationToken }, (j, loop) => { for (int i = 0; i < width; i++) { PointF pf = GetPreimageLocation(new PointF(i, j), pairs); Point p = ClampPoint(pf, new Size(width, height)); PixelData* inPixel = bmp[p.X, p.Y]; PixelData* outPixel = fastOut[i, j]; outPixel->R = inPixel->R; outPixel->G = inPixel->G; outPixel->B = inPixel->B; } }); } } return output; }
/// <summary>Gets the X location of a point in the source image for a point X' in the target image.</summary> /// <param name="p">The point whose preimage location we need.</param> /// <param name="pairs">The morph line pairs used to translate the point.</param> /// <returns>The point in the original image.</returns> private PointF GetPreimageLocation(PointF p, LinePairCollection pairs) { if (pairs.Count == 0) return p; // Grab settings double constA = _options.ConstA; double constB = _options.ConstB; double constP = _options.ConstP; PointF dSum = PointF.Empty; double weightSum = 0; foreach (var pair in pairs) { PointF srcStart = pair.Item1.Item1, srcEnd = pair.Item1.Item2; PointF destStart = pair.Item2.Item1, destEnd = pair.Item2.Item2; if (srcStart == srcEnd || destStart == destEnd) continue; PointF PQ = SubPoints(destEnd, destStart); double length = NormalizePoint(PQ); PointF PpQp = SubPoints(srcEnd, srcStart); PointF PX = SubPoints(p, destStart); double u = DotProduct(PX, PQ) / (length * length); double v = DotProduct(PX, FlipPerpendicular(PQ)) / length; PointF Xp = AddPoints( AddPoints(srcStart, ScalePoint(PpQp, u)), ScalePoint(FlipPerpendicular(PpQp), v / NormalizePoint(PpQp))); // Compute shortest distance from X to the line segment PQ double dist; if (u < 0) dist = NormalizePoint(SubPoints(p, destStart)); else if (u > 1) dist = NormalizePoint(SubPoints(p, destEnd)); else dist = Math.Abs(v); double strength = Math.Pow(length, constP) / (constA + dist); double weight = (constB == 2.0) ? strength * strength : Math.Pow(strength, constB); dSum = AddPoints(dSum, ScalePoint(SubPoints(Xp, p), weight)); weightSum += weight; } return AddPoints(p, ScalePoint(dSum, 1.0 / weightSum)); }
/// <summary>Interpolates between the starting and ending morph lines.</summary> /// <param name="pairs">The morph lines to interpolate.</param> /// <param name="percent">The percent of the way through the morph.</param> /// <param name="interpolatedForwards">Resulting interpolated lines for the starting image.</param> /// <param name="interpolatedBackwards">Resulting interpolated lines for the ending image.</param> private Tuple<LinePairCollection,LinePairCollection> InterpolateLines( LinePairCollection pairs, double percent) { LinePairCollection interpolatedForwards = new LinePairCollection(), interpolatedBackwards = new LinePairCollection(); foreach (Tuple<Line,Line> pair in pairs) { // Source line is the same as the original; dest line is the interpolated line // Add the new pair to the forwards list and the inverse to the backwards list. var newPair = Tuple.Create( new Line(pair.Item1.Item1, pair.Item1.Item2), new Line( AddPoints(pair.Item1.Item1, ScalePoint(SubPoints(pair.Item2.Item1, pair.Item1.Item1), percent)), AddPoints(pair.Item1.Item2, ScalePoint(SubPoints(pair.Item2.Item2, pair.Item1.Item2), percent)) )); interpolatedForwards.Add(newPair); interpolatedBackwards.Add(Tuple.Create( new Line(pair.Item2.Item1, pair.Item2.Item2), new Line(newPair.Item2.Item1, newPair.Item2.Item2))); } return Tuple.Create(interpolatedForwards, interpolatedBackwards); }
private void openToolStripMenuItem_Click(object sender, EventArgs e) { _lines.Selected = null; using (OpenFileDialog ofd = new OpenFileDialog()) { ofd.Filter = "Morph Files (*.morph)|*.morph|All Files (*.*)|*.*"; if (ofd.ShowDialog(this) == DialogResult.OK) { var formatter = new BinaryFormatter(); using (Stream fileStream = ofd.OpenFile()) { SavedSettings ss = (SavedSettings)formatter.Deserialize(fileStream); _lines = ss.Lines; _morphSettings = ss.Settings; ConfigurePictureBoxes(_lines, ss.FirstImage, ss.SecondImage); } } } }
private void newToolStripMenuItem_Click(object sender, EventArgs e) { this.Text = "Parallel Morph"; _lines = new LinePairCollection(); ConfigurePictureBoxes(_lines, null, null); }
private void PrepareUIDataForMorph( out UiSettings settings, out LinePairCollection lines, out Bitmap newStartImage, out Bitmap newEndImage) { // Grab UI data settings = Utilities.DeepClone(_morphSettings); lines = Utilities.DeepClone(_lines); // Get the original iamges var origStartImage = (Bitmap)pbStartImage.Image; var origEndImage = (Bitmap)pbEndImage.Image; // Get a scale factor from the UI float startImageScaleFactor = int.Parse(outputSizeToolStripTextBox.Text) / 100f; // Create the starting and ending images. The starting image is scaled by the UI setting, // and the ending image is created to match the starting image's size. newStartImage = Utilities.CreateNewBitmapFrom(origStartImage, startImageScaleFactor); newEndImage = Utilities.CreateNewBitmapFrom(origEndImage, newStartImage.Width, newStartImage.Height); // Get a scale factor by comparing the new and old ending images var endImageScaleFactor = new PointF( newEndImage.Width / (float)origEndImage.Width, newEndImage.Height / (float)origEndImage.Height); // Create new line pairings to cope with the scaling var newlineList = (from pair in _lines select Tuple.Create( new Line( new PointF(pair.Item1.Item1.X * startImageScaleFactor, pair.Item1.Item1.Y * startImageScaleFactor), new PointF(pair.Item1.Item2.X * startImageScaleFactor, pair.Item1.Item2.Y * startImageScaleFactor)), new Line( new PointF(pair.Item2.Item1.X * endImageScaleFactor.X, pair.Item2.Item1.Y * endImageScaleFactor.Y), new PointF(pair.Item2.Item2.X * endImageScaleFactor.X, pair.Item2.Item2.Y * endImageScaleFactor.Y)))).ToList(); lines = new LinePairCollection(newlineList); }