/// <summary> Re-projects an image given a mapping function that maps target to source pixels. </summary> /// <param name="image">The image to re-project.</param> /// <param name="size">Determines the size of the resulting image.</param> /// <param name="targetToSource">Mapping function that maps target to source pixels.</param> public virtual Stream Reproject(Image image, Size size, Func <PointD, PointD> targetToSource) { // Determine the scale to use. The scale is used to virtually inflate the target rectangle to be filled when // re-projecting a single block. The scale specifies the lowest possible integer values that, multiplied with // width and height of the target image, would make the target image larger than the source image. Virtually, // the target image has to be larger than the source image for the interpolation filters to produce proper // results. var scale = new Size( Math.Max(1, (int)Math.Ceiling((double)image.Width / size.Width)), Math.Max(1, (int)Math.Ceiling((double)image.Height / size.Height)) ); // setup source and target image var source = ArgbImage.FromImage(image, ReprojectionOptions.InterpolationMode); var target = new ArgbImage(size); // divide into blocks and re-project each block GetBlocks(size).ForEach(ReprojectionOptions.DegreeOfParallelism, block => { Reproject(source, target, block, targetToSource, scale); }); return(target.Stream); }
/// <summary> /// Performs a scaled linear re-projection into the specified target block. /// <br/> /// Uses the given mapping function only to determine the corresponding corner points in the source image, /// uses a linear interpolation to determine intermediate points. /// </summary> /// <param name="source">The image to be re-projected.</param> /// <param name="target">The resulting image to be filled.</param> /// <param name="block">The block being targeted.</param> /// <param name="transformTargetToSource">Mapping function that maps target to source pixels.</param> /// <param name="scale">A factor to apply when re-projecting the block. See code comments in Reproject method above, where this parameter is set up.</param> private static void ScaledReprojection(ArgbImage source, ArgbImage target, ReprojectionBlock block, Func <PointD, PointD> transformTargetToSource, Size scale) { // determine the number of sections of the scaled block var nx = (block.X1 - block.X0 + 1) * scale.Width - 1; var ny = (block.Y1 - block.Y0 + 1) * scale.Height - 1; // Interpolators for upper and lower line of block var upper = InterpolationData.Create(transformTargetToSource(block.LeftTop), transformTargetToSource(block.RightTop), nx); var lower = InterpolationData.Create(transformTargetToSource(block.LeftBottom), transformTargetToSource(block.RightBottom), nx); // total number of color components collected in a color block due to scaling var colorBlockSize = (uint)(scale.Width * scale.Height); for (var x = block.X0; x <= block.X1; ++x) { // setup scale.Width interpolators for interpolating points on the line // defined through upper+n and lower+n, with n=0..(scale.Width-1) var sourcePoint = new InterpolationData[scale.Width]; for (var xsub = 0; xsub < scale.Width; ++xsub) { sourcePoint[xsub] = InterpolationData.Create(upper++, lower++, ny); } for (var y = block.Y0; y <= block.Y1; ++y) { // storage for color components uint a, r, g, b; a = r = g = b = colorBlockSize >> 2; // collect color components of subpixels. // In the inner loop, we'll step our sourcePoint interpolators. for (var ysub = 0; ysub < scale.Height; ++ysub) { for (var xsub = 0; xsub < scale.Width; ++sourcePoint[xsub], ++xsub) { var color = source[sourcePoint[xsub].x, sourcePoint[xsub].y]; a += (color >> 24) & 0xff; r += (color >> 16) & 0xff; g += (color >> 8) & 0xff; b += color & 0xff; } } // average the collected color components and set the target pixel target[x, y] = ((a / colorBlockSize) << 24) | ((r / colorBlockSize) << 16) | ((g / colorBlockSize) << 8) | (b / colorBlockSize); } } }
/// <summary> /// Performs a unscaled linear re-projection into the specified target block. /// <br/> /// Uses the given mapping function only to determine the corresponding corner points in the source image, /// uses a linear interpolation to determine intermediate points. /// </summary> /// <param name="source">The image to be re-projected.</param> /// <param name="target">The resulting image to be filled.</param> /// <param name="block">The block being targeted.</param> /// <param name="transformTargetToSource">Mapping function that maps target to source pixels.</param> private static void UnscaledReprojection(ArgbImage source, ArgbImage target, ReprojectionBlock block, Func <PointD, PointD> transformTargetToSource) { // Interpolators for upper and lower line of block var upper = InterpolationData.Create(transformTargetToSource(block.LeftTop), transformTargetToSource(block.RightTop), block.X1 - block.X0); var lower = InterpolationData.Create(transformTargetToSource(block.LeftBottom), transformTargetToSource(block.RightBottom), block.X1 - block.X0); for (var x = block.X0; x <= block.X1; ++x, ++upper, ++lower) { // interpolator for points on the current line defined through upper and lower var sourcePoint = InterpolationData.Create(upper, lower, block.Y1 - block.Y0); for (var y = block.Y0; y <= block.Y1; ++y, ++sourcePoint) { target[x, y] = source[sourcePoint.x, sourcePoint.y]; } } }
/// <summary> /// Performs a scaled linear re-projection into the specified target block. /// <br/> /// Uses the given mapping function only to determine the corresponding corner points in the source image, /// uses a linear interpolation to determine intermediate points. /// </summary> /// <param name="source">The image to be re-projected.</param> /// <param name="target">The resulting image to be filled.</param> /// <param name="block">The block being targeted.</param> /// <param name="transformTargetToSource">Mapping function that maps target to source pixels.</param> /// <param name="scaleY">A factor to apply when re-projecting the block. See code comments in Reproject method above, where this parameter is set up.</param> private static void VerticallyScaledReprojection(ArgbImage source, ArgbImage target, ReprojectionBlock block, Func <PointD, PointD> transformTargetToSource, int scaleY) { // determine the number of sections of the scaled block var nx = block.X1 - block.X0; var ny = (block.Y1 - block.Y0 + 1) * scaleY - 1; // Interpolators for upper and lower line of block var upper = InterpolationData.Create(transformTargetToSource(block.LeftTop), transformTargetToSource(block.RightTop), nx); var lower = InterpolationData.Create(transformTargetToSource(block.LeftBottom), transformTargetToSource(block.RightBottom), nx); // total number of color components collected in a color block due to scaling var colorBlockSize = (uint)scaleY; for (var x = block.X0; x <= block.X1; ++x, ++upper, ++lower) { // setup interpolator for interpolating points on the line defined through upper and lower var sourcePoint = InterpolationData.Create(upper, lower, ny); for (var y = block.Y0; y <= block.Y1; ++y) { // storage for color components uint a, r, g, b; a = r = g = b = colorBlockSize >> 2; // collect color components of sub pixels. // In the inner loop, we'll step our sourcePoint interpolators. for (var ysub = 0; ysub < scaleY; ++ysub, ++sourcePoint) { var color = source[sourcePoint.x, sourcePoint.y]; a += (color >> 24) & 0xff; r += (color >> 16) & 0xff; g += (color >> 8) & 0xff; b += color & 0xff; } // average the collected color components and set the target pixel target[x, y] = ((a / colorBlockSize) << 24) | ((r / colorBlockSize) << 16) | ((g / colorBlockSize) << 8) | (b / colorBlockSize); } } }
/// <summary> /// Performs a linear re-projection into the specified target block. /// <br/> /// Uses the given mapping function only to determine the corresponding corner points in the source image, /// uses a linear interpolation to determine intermediate points. /// </summary> /// <param name="source">The image to be re-projected.</param> /// <param name="target">The resulting image to be filled.</param> /// <param name="block">The block being targeted.</param> /// <param name="transformTargetToSource">Mapping function that maps target to source pixels.</param> /// <param name="scale">A factor to apply when re-projecting the block. See code comments in Reproject method above, where this parameter is set up.</param> protected virtual void Reproject(ArgbImage source, ArgbImage target, ReprojectionBlock block, Func <PointD, PointD> transformTargetToSource, Size scale) { try { if (scale.Width != 1) { ScaledReprojection(source, target, block, transformTargetToSource, scale); } else { if (scale.Height == 1) { // use optimized reprojection when scale values are both set to "1" (= unscaled) UnscaledReprojection(source, target, block, transformTargetToSource); } else { // use optimized reprojection when scale.Width is set to "1" (vertical scaling) VerticallyScaledReprojection(source, target, block, transformTargetToSource, scale.Height); } } } catch (Components.Projections.TransformationException) { // ignore transformation exceptions; the given block will simply remain empty. // // seen transformation exceptions to happen in Proj4.cs with the transformation returning error code -14. // This is probably caused by coordinates exceeding the limits; See // // proj-4.8.0\src\pj_strerrno.c // ... // "latitude or longitude exceeded limits", /* -14 */ // ... // // proj-4.8.0\src\pj_transform.c // ... // if( pj_Convert_Geodetic_To_Geocentric( &gi, y[io], x[io], z[io], x + io, y + io, z + io ) != 0 ) { // ret_errno = -14; // ... } }