I have an ellipse which is growing with time.
To detect the ellipse I have used CvInvoke.AbsDiff method .
and I gets an image like this
I want to put this ellipse to fit-ellipse method and gain the radius es of it.
This is the approach I took.
CvInvoke.AbsDiff(First, img, grayscale);
CvInvoke.CvtColor(grayscale, grayscale, ColorConversion.Bgr2Gray);
CvInvoke.GaussianBlur(grayscale, grayscale, new System.Drawing.Size(11, 11), 15, 15);
CvInvoke.Threshold(grayscale, grayscale, Convert.ToInt16(Threshold), Convert.ToInt16(Threshold * 2), ThresholdType.Binary );
Mat element = CvInvoke.GetStructuringElement(Emgu.CV.CvEnum.ElementShape.Rectangle, new System.Drawing.Size(3, 3), new System.Drawing.Point(-1, -1));
CvInvoke.Dilate(grayscale, grayscale, element, new System.Drawing.Point(-1, 1), 5, BorderType.Constant, new MCvScalar(255, 255, 255));
CvInvoke.Canny(grayscale, grayscale, Threshold, MaxThreshold * 2, 3);
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(grayscale, contours, null, RetrType.Ccomp, ChainApproxMethod.ChainApproxTc89Kcos);
double area = 0;
double ContourArea = 0;
int contour = 0;
int CenterX;
int CenterY;
for (int i = 0; i < contours.Size; i++)
{
System.Drawing.Rectangle rec = CvInvoke.BoundingRectangle(contours[i]);
output.Draw(rec, new Bgr(255, 0, 255), 2);
CenterX = ((rec.Width) / 2) + rec.X;
CenterY = ((rec.Height) / 2) + rec.Y;
ContourArea = rec.Width * rec.Height; ;
if ((HWidth - CenterFactor) < CenterX && CenterX < (HWidth + CenterFactor) && (HHeight - CenterFactor) < CenterY && CenterY< (HHeight + CenterFactor) )
{
if (ContourArea < 1000000)
if (area < ContourArea)
{
area = ContourArea;
contour = i;
}
}
}
//if (contour == 0)
//{
// return arr;
//}
System.Drawing.Rectangle rect = CvInvoke.BoundingRectangle(contours[contour]);
output.Draw(rect, new Bgr(0, 255, 0), 3);
But i am not getting the best ellipse everytime. This is the contour which I'm getting
Is there any other way to do this?
Although this method is not completely perfect, this could be a possible direction that you could take.
Mat input = CvInvoke.Imread(#"C:\Users\ajones\Desktop\Images\inputImg.png", ImreadModes.AnyColor);
Mat input2 = input.Clone();
Mat thresh = new Mat();
CvInvoke.GaussianBlur(input, thresh, new System.Drawing.Size(7, 7), 10, 10);
CvInvoke.Threshold(thresh, thresh, 3, 10, ThresholdType.Binary);
CvInvoke.Imshow("The Thresh", thresh);
CvInvoke.WaitKey(0);
Mat output = new Mat();
CvInvoke.CvtColor(thresh, output, ColorConversion.Bgr2Gray);
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(output, contours, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);
CvInvoke.DrawContours(input, contours, -1, new MCvScalar(0, 255, 0), 3, LineType.FourConnected);
CvInvoke.Imshow("The Image", input);
CvInvoke.WaitKey(0);
int biggest = 0;
int index = 0;
for (int i = 0; i<contours.Size; i++)
{
if (contours[i].Size > biggest)
{
biggest = contours[i].Size;
index = i;
}
}
CvInvoke.DrawContours(input2, contours, index, new MCvScalar(0, 255, 0), 3, LineType.FourConnected);
CvInvoke.Imshow("The Image2", input2);
CvInvoke.WaitKey(0);
First blur the image using a Gaussian filter.
Then, using a binary threshold.
Afterwards, find all contours on the image
Finally, all you would need to do is just sort through your contours until you found the biggest one.
Like I said, its not completely perfect, but I should help push you in the right direction.
Related
I used some basic code to draw lines on an bitmap, but I cannot think of any reason
why the surroundings of these lines are not deep black. If there is any solution I would be really interested in it. Thank you!
This is my code:
Bitmap bm = new Bitmap(3200, 1600, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
double step = werte.Count / 3200;
Pen whitePen = new Pen(Color.White, 3);
var graphics = Graphics.FromImage(bm);
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
graphics.FillRectangle(new SolidBrush(Color.Black), 0, 0, 3200, 1600);
int lastangezeigtH = (int)(1600 - ((werte[(int)(0 * step)] - min) * 1600 / deltaMinMax));
for (int i = 1; i < 3200; i++)
{
double h = (werte[(int)(i * step)] - min) * 1600 / deltaMinMax;
int angezeigtH = (int)(1600 - h);
//bm.SetPixel(i, angezeigtH, Color.White);
graphics.DrawLine(whitePen,i - 1, lastangezeigtH, i, angezeigtH);
lastangezeigtH = angezeigtH;
}
Regarding this Opencv Tutorial, the following C++ code snippet:
vector<Vec4i> lines;
// Find hough lines
HoughLinesP(edges, lines, 1, CV_PI / 180, 100, 100, 10);
// Prepare blank mat with same sizes as image
Mat Blank(image.rows, image.cols, CV_8UC3, Scalar(0, 0, 0));
// Draw lines into image and Blank images
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i l = lines[i];
line(image, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 0), 2, CV_AA);
line(Blank, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(255, 255, 255), 2, CV_AA);
}
has been translated into C# like this:
private void openFileDialogButton_Click(object sender, EventArgs e)
{
try
{
const string filename = #"E:\___MSc in Computer Systems & Network\EMSC1,2,3\lena.png";
Mat image = Cv2.ImRead(filename, LoadMode.GrayScale);
Mat edges = new Mat();
Cv2.Canny(image, edges, 95, 100);
Cv2.ImWrite("edges.jpg", edges);
Mat dx = new Mat();
Mat dy = new Mat();
Cv2.Sobel(edges, dx, MatType.CV_32F, 1, 0);
Cv2.Sobel(edges, dy, MatType.CV_32F, 0, 1);
Cv2.ImWrite("dx.jpg", dx);
Cv2.ImWrite("dy.jpg", dy);
Mat linesssssss = new Mat();
Cv.HoughLines2((CvArr)linesssssss.ToIplImage(),
(CvMat)edges,
HoughLinesMethod.Standard,
1,
Math.PI / 180,
100, 100, 10);
//Cv2.HoughLinesP(edges, lines, 1, Math.PI / 180, 100, 100, 10);
List<Vec4i> lines = IntPtrToList(linesssssss.Data);
Mat Blank = new Mat(image.Rows, image.Cols, MatType.CV_8UC3, new Scalar(0, 0, 0));
for (int i = 0; i < lines.Count; i++)
{
Vec4i l = lines[i];
Cv2.Line(image, new OpenCvSharp.CPlusPlus.Point(l[0], l[1]), new OpenCvSharp.CPlusPlus.Point(l[2], l[3]), new Scalar(0, 0, 0), 2, Cv.AA);
Cv2.Line(Blank, new OpenCvSharp.CPlusPlus.Point(l[0], l[1]), new OpenCvSharp.CPlusPlus.Point(l[2], l[3]), new Scalar(255, 255, 255), 2, Cv.AA);
}
//Cv2.ImWrite("houg.jpg", image);
//Cv2.ImShow("Edges", image);
//Cv2.ImWrite("houg2.jpg", Blank);
//Cv2.ImShow("Edges Structure", Blank);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Unfortunately, it seems to be not working. It is giving the following exception:
Nonpositive cols or rows
This is the original image from the article:
Based on Github's sample code for HoughLinesP:
static void Main(string[] args)
{
string filename = "Alliance.jpg";
Mat imageIn = Cv2.ImRead(filename, ImreadModes.GrayScale).Resize(new Size(800, 600));
Mat edges = new Mat();
Cv2.Canny(imageIn, edges, 95, 100);
//HoughLinesP
LineSegmentPoint[] segHoughP = Cv2.HoughLinesP(edges, 1, Math.PI / 180, 100, 100, 10);
Mat imageOutP = imageIn.EmptyClone();
foreach (LineSegmentPoint s in segHoughP)
imageOutP.Line(s.P1, s.P2, Scalar.White, 1, LineTypes.AntiAlias, 0);
using (new Window("Edges", WindowMode.AutoSize, edges))
using (new Window("HoughLinesP", WindowMode.AutoSize, imageOutP))
{
Window.WaitKey(0);
}
}
UPDATE as on 12 Nov 2015
I used PanoTools plugin with Photoshop and Hugin and played with all those parameters. End up i found the parameters for projection, HFOV and image output size that fulfill my lowest requirement.
Parameteres:
Processed Output:
My question is then how can i convert all these parameters and values into C# algorithm coding so that when I provide the original image, i will get the corrected output image?
Thanks a lot.
I have a square image captured from a circular fisheye camera. The size is 2650 * 2650 pixels.
Now, i will need to programmatically dewarp the image to a flat panorama image using C# language.
I had look around from internet with different algorithm example from Link for code below , Link1 and Link2 but just can't make it success. My maths sincerely sucks and can't help me with that. Hopefully someone able to guide me through this.
Thanks a lot.
Example of image output from the camera:
--Image grabbed from Wikipedia Fisheye Lens & size modified to fit my sample pixel.
The code i tried to dewarp it but no luck:
Bitmap sourceImage = (Bitmap)Bitmap.FromFile("circularfisheye.jpg");
double factor = 0.5;
Boolean autoCrop = false;
Color backgroundColor = Color.White;
Bitmap StartImage = null;
BitmapData srcBitmapData = null;
Byte[] srcPixels = null;
Byte[] dstPixels = null;
Bitmap NewImage = null;
BitmapData dstBitmapData = null;
try
{
// Checks whether bpp ( Bits Per Pixel ) is 8 , 24, or 32
int Depth = System.Drawing.Bitmap.GetPixelFormatSize(sourceImage.PixelFormat);
if (Depth != 8 && Depth != 24 && Depth != 32)
{
throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
}
// Retrieves the count of the color components
int cCount = Depth / 8;
Size baseSize = new Size(sourceImage.Width, sourceImage.Height);
// check if a low image resize and need to improve the quality
// and not generate image aliasing
Int32 maxSize = Math.Max(sourceImage.Width, sourceImage.Height);
if (maxSize < 3000)
{
float percent = 3000F / (float)maxSize;
baseSize = new Size((Int32)((float)sourceImage.Width * percent), (Int32)((float)sourceImage.Height * percent));
}
StartImage = new Bitmap(baseSize.Width, baseSize.Height, sourceImage.PixelFormat);
StartImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
// Create the drawing object and white background
Graphics g = Graphics.FromImage(StartImage);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawImage(sourceImage, new Rectangle(-1, -1, baseSize.Width + 1, baseSize.Height + 1), 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel);
g.Dispose();
// Locks the source image and copies it to the byte array and releases the source image
srcBitmapData = StartImage.LockBits(new Rectangle(0, 0, StartImage.Width, StartImage.Height), ImageLockMode.ReadOnly, StartImage.PixelFormat);
srcPixels = new byte[StartImage.Width * StartImage.Height * (Depth / 8)];
Marshal.Copy(srcBitmapData.Scan0, srcPixels, 0, srcPixels.Length);
StartImage.UnlockBits(srcBitmapData);
srcBitmapData = null;
// Create the target image byte array
dstPixels = new Byte[srcPixels.Length];
// Fill the entire frame with the selected background color
Int32 index = ((1 * StartImage.Width) + 1) * cCount; //index = ((Y * Width) + X) * cCount
do
{
if (Depth == 32) //For 32 bpp defines Red , Green, Blue and Alpha
{
dstPixels[index++] = backgroundColor.B;
dstPixels[index++] = backgroundColor.G;
dstPixels[index++] = backgroundColor.R;
dstPixels[index++] = backgroundColor.A; // a
}
if (Depth == 24) //For 24 bpp defines Red , Green and Blue
{
dstPixels[index++] = backgroundColor.B;
dstPixels[index++] = backgroundColor.G;
dstPixels[index++] = backgroundColor.R;
}
if (Depth == 8)
// For 8 bpp defines the value of color ( Red , Green and Blue to be the same thing)
{
dstPixels[index++] = backgroundColor.B;
}
} while (index < srcPixels.Length);
// Calculate the maximum possible extent for the image and multiply by the desired factor
double amp = 0;
double ang = Math.PI * 0.5;
for (Int32 a = 0; a < StartImage.Height; a++)
{
int y = (int)((StartImage.Height / 2) - amp * Math.Sin(ang));
if ((y < 0) || (y > StartImage.Height))
break;
amp = a;
}
amp = (amp - 2) * (factor < -1 ? -1 : (factor > 1 ? 1 : factor));
// Define variables that calculates the cutoff points (if any)
Int32 x1, y1, x2, y2;
x1 = StartImage.Width;
y1 = StartImage.Height;
x2 = 0;
y2 = 0;
// Copy pixel by pixel for the new positions
index = ((1 * StartImage.Width) + 1) * cCount;
do
{
Int32 y = (Int32)((index / cCount) / StartImage.Width);
Int32 x = (index / cCount) - (y * StartImage.Width);
Point pt = NewPoint(new Point(x, y), StartImage.Width, StartImage.Height, amp, factor < 0);
//Values for crop
if (factor >= 0)
{
if (x == StartImage.Width / 2)
{
if (pt.Y < y1)
y1 = pt.Y;
if (pt.Y > y2)
y2 = pt.Y;
}
if (y == StartImage.Height / 2)
{
if (pt.X < x1)
x1 = pt.X;
if (pt.X > x2)
x2 = pt.X;
}
}
else
{
if ((x == 1) && (y == 1))
{
y1 = pt.Y;
x1 = pt.X;
}
if ((x == StartImage.Width - 1) && (y == StartImage.Height - 1))
{
y2 = pt.Y;
x2 = pt.X;
}
}
//Bytes Index which will apply the pixel
Int32 dstIndex = ((pt.Y * StartImage.Width) + pt.X) * cCount;
if (Depth == 32)
{
dstPixels[dstIndex] = srcPixels[index++];
dstPixels[dstIndex + 1] = srcPixels[index++];
dstPixels[dstIndex + 2] = srcPixels[index++];
dstPixels[dstIndex + 3] = srcPixels[index++]; // a
}
if (Depth == 24)
{
dstPixels[dstIndex] = srcPixels[index++];
dstPixels[dstIndex + 1] = srcPixels[index++];
dstPixels[dstIndex + 2] = srcPixels[index++];
}
if (Depth == 8)
{
dstPixels[dstIndex] = srcPixels[index++];
}
} while (index < srcPixels.Length);
//Creates a new image based on the byte array previously created
NewImage = new Bitmap(StartImage.Width, StartImage.Height, StartImage.PixelFormat);
NewImage.SetResolution(StartImage.HorizontalResolution, StartImage.VerticalResolution);
dstBitmapData = NewImage.LockBits(new Rectangle(0, 0, StartImage.Width, StartImage.Height), ImageLockMode.WriteOnly, StartImage.PixelFormat);
Marshal.Copy(dstPixels, 0, dstBitmapData.Scan0, dstPixels.Length);
NewImage.UnlockBits(dstBitmapData);
//Generates the final image to crop or resize the real coo
Bitmap FinalImage = new Bitmap(sourceImage.Width + 1, sourceImage.Height, StartImage.PixelFormat);
NewImage.SetResolution(StartImage.HorizontalResolution, StartImage.VerticalResolution);
Graphics g1 = Graphics.FromImage(FinalImage);
g1.SmoothingMode = SmoothingMode.AntiAlias;
g1.InterpolationMode = InterpolationMode.HighQualityBicubic;
g1.PixelOffsetMode = PixelOffsetMode.HighQuality;
//Performs the cut if enabled automatic cutting and there is need to cut
if ((autoCrop) && ((x1 > 0) || (y1 > 0) || (x2 < NewImage.Height) || (y2 < NewImage.Height)))
{
Rectangle cropRect = new Rectangle(x1, y1, x2 - x1, y2 - y1);
g1.DrawImage(NewImage, new Rectangle(-1, -1, FinalImage.Width + 1, FinalImage.Height + 1), cropRect.X, cropRect.Y, cropRect.Width, cropRect.Height, GraphicsUnit.Pixel);
}
else
{
g1.DrawImage(NewImage, new Rectangle(-1, -1, FinalImage.Width + 1, FinalImage.Height + 1), 0, 0, NewImage.Width, NewImage.Height, GraphicsUnit.Pixel);
}
g1.Dispose();
g1 = null;
NewImage = null;
FinalImage.Save("output.jpg");
FinalImage.Dispose();
}
finally
{
srcBitmapData = null;
srcPixels = null;
dstPixels = null;
dstBitmapData = null;
}
Such a distortion as a symmetry of revolution.
In polar coordinates, with the pole at the center of the image, it is expressed as
r' = f(r)
Θ' = Θ
where the quote indicates the distorted coordinates. The function f is unknown and should be measured empirically, by calibration (looking at a regular target).
To correct the image, you need to invert the function f and apply the reverse transform to the image. In fact, it is easier to measure g directly by calibration. As a starting approximation, a simple model like
r = r' + a.r'³
can do.
Most probably you don't have a picture of a grid taken with the same lens. Your last resort is to implement the undistortion function with adjustable parameters, and optimize these by trial and error.
It should also be possible to derive the calibration curve by looking at the deformation of straight lines, but this is more "technical".
In Cartesian coordinates, you can express the correction transform as
x = g(r').x'/r'
y = g(r').y'/r'
where r' = √x'²+y'².
Use the algorithm from here:
http://www.helviojunior.com.br/fotografia/barrel-and-pincushion-distortion/
It worked for me
I've made some revamp to the HelvioJunior's library (that was linked by #Tarek.Mh), I think this may suit your need:
Below, the code:
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using static System.Math;
namespace HelvioJunior
{
//https://www.helviojunior.com.br/fotografia/barrel-and-pincushion-distortion/
public class Program
{
private static void Main(string[] args)
{
Bitmap source = (Bitmap)Image.FromFile(#"JpwX0.png");
Bitmap bmp = BarrelDistortion(source, 4/10f, true);
bmp.Save(#"test.png");
bmp.Dispose();
source.Dispose();
}
static public Bitmap BarrelDistortion(Bitmap sourceImage, double factor = 0, bool autoCrop = true, uint previewRectangleWidth = 0, Color? fillerColor = null)
{
int sourceRight = sourceImage.Width - 1, sourceBottom = sourceImage.Height - 1;
// Vertical amplitude is half the height times factor
// Horizontal amplitude is missing ; vertical amplitude's applied to both directions
double amp = sourceBottom / 2f * factor;
// Inner shrinking area points
RePoint[] lPts;
bool inverse = factor < 0;
// Shrinking area coordinates (center point is considered always available)
double x1 = sourceRight / 2f,
y1 = sourceBottom / 2f,
x2 = sourceRight / 2f,
y2 = sourceBottom / 2f;
if (inverse)
{
lPts = new RePoint[]
{
new RePoint(0, 0),
new RePoint(0, sourceBottom),
new RePoint(sourceRight, sourceBottom),
new RePoint(sourceRight, 0)
};
}
else
{
lPts = new RePoint[]
{
new RePoint(sourceRight * 1 / 2f, 0),
new RePoint(0, sourceBottom * 1 / 2f),
new RePoint(sourceRight, sourceBottom * 1 / 2f),
new RePoint(sourceRight * 1 / 2f, sourceBottom)
};
}
foreach (var pN in lPts.Select(pt => NewPoint(pt, sourceImage.Width, sourceImage.Height, amp, inverse)))
{
if (pN.Y < y1) y1 = pN.Y;
if (pN.Y > y2) y2 = pN.Y;
if (pN.X < x1) x1 = pN.X;
if (pN.X > x2) x2 = pN.X;
}
// Bytes per color from bit per pixel (bpp) format
int bpcCount = Image.GetPixelFormatSize(sourceImage.PixelFormat) / 8;
Rectangle sourceRectangle = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
int srcLength = sourceImage.Width * sourceImage.Height * bpcCount;
// Gets sourceImage byte array as srcpixels
BitmapData srcBitmapData = sourceImage.LockBits(sourceRectangle, ImageLockMode.ReadOnly, sourceImage.PixelFormat);
byte[] srcPixels = new byte[srcLength];
Marshal.Copy(srcBitmapData.Scan0, srcPixels, 0, srcLength);
sourceImage.UnlockBits(srcBitmapData);
srcBitmapData = null;
// Destination byte array preparation as dstPixels
byte[] dstPixels = new byte[srcLength];
int dstIndex = 0;
// Filler color preparation
Color fillColor = fillerColor ?? Color.Transparent;
if (!autoCrop)
{
if (bpcCount <= 4) // Depth > 32bpp may not work as expected, filler color's not applied for bit safety reason
do
{
dstPixels[dstIndex++] = fillColor.B;
if (bpcCount > 1)
{
dstPixels[dstIndex++] = fillColor.G;
dstPixels[dstIndex++] = fillColor.R;
if (bpcCount > 3)
dstPixels[dstIndex++] = fillColor.A; // a
}
} while (dstIndex < srcLength);
}
// Byte-to-byte copy (incl. Point transformation)
int index = 0, srcBpcLength = srcLength - bpcCount;
do
{
int comp = index / bpcCount; // comp yields the current "pixel" position
int y = comp / sourceImage.Width; // Each line is sourceImage.Width bytes wide
int x = comp - (y * sourceImage.Width); // Remaining (comp - lines) bytes is target column (ranges from 0 to width - 1)
// Destination "pixel"
RePoint pt = NewPoint(new RePoint(x, y), sourceImage.Width, sourceImage.Height, amp, inverse);
dstIndex = (((int)pt.Y * sourceImage.Width) + (int)pt.X) * bpcCount; // dstIndex++ overflows when |amp| >= 2
if (dstIndex >= 0 && dstIndex <= srcBpcLength)
for (int i = 0; i++ < bpcCount;)
dstPixels[dstIndex++] = srcPixels[index++];
else
index += bpcCount;
} while (index < srcLength);
srcPixels = null;
// Destination bytes application
BitmapData dstBitmapData = sourceImage.LockBits(sourceRectangle, ImageLockMode.WriteOnly, sourceImage.PixelFormat);
Marshal.Copy(dstPixels, 0, dstBitmapData.Scan0, srcLength);
sourceImage.UnlockBits(dstBitmapData);
dstBitmapData = null;
dstPixels = null;
// Final Image area
Rectangle cropRect = new Rectangle((int)Ceiling(x1), (int)Ceiling(y1), (int)Ceiling(x2 - x1), (int)Ceiling(y2 - y1));
Rectangle destRectangle = autoCrop ? cropRect : sourceRectangle;
// Final image preparation
Bitmap FinalImage = new Bitmap(destRectangle.Width, destRectangle.Height, sourceImage.PixelFormat);
FinalImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
Graphics g1 = Graphics.FromImage(FinalImage);
g1.DrawImage(sourceImage, -destRectangle.X, -destRectangle.Y);
// Previsualization rectangle
if (previewRectangleWidth > 0)
g1.DrawRectangle(new Pen(Color.Red, previewRectangleWidth), cropRect.X - 1, cropRect.Y - 1, cropRect.Width + previewRectangleWidth, cropRect.Height + previewRectangleWidth);
g1.Dispose();
g1 = null;
return FinalImage;
}
private static RePoint NewPoint(RePoint aP, double Width, double Height, double Amplitude, bool inverse)
{
double h = aP.Y / (Height - 1);
double w = aP.X / (Width - 1);
// Works ok for [0/2] to [1/2]
// Floating point error(s) here, in the range of ]1/2] to [2/2] (No workaround found)
double sinX = Round(Sin(PI * w), 15); // Range of [0] to [1] * PI ; result ranges from 0 (far from center) to 1 (at center)
double sinY = Round(Sin(PI * h), 15);
double caX = Amplitude * (1 - 2 * w);
double caY = Amplitude * (1 - 2 * h);
double aY = 0, aX = 0;
if (inverse)
{
aX = -caX;
aY = -caY;
}
double pY = aP.Y + aY + caY * sinX;
double pX = aP.X + aX + caX * sinY;
return new RePoint(pX, pY);
}
private struct RePoint
{
public double X;
public double Y;
public RePoint(double x, double y)
{
X = x;
Y = y;
}
}
}
}
I need to create a thumbnail image with transparent rounded corners. Before this requirement I used the simple:
using (var b = new Bitmap(dataSize.Width, dataSize.Height, PixelFormat.Format32bppArgb))
using (var g = Graphics.FromImage(b))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Default;
g.DrawImage(original, 0, 0, b.Width, b.Height);
}
which produced great results (for reductions to approx 50x50px) even without any interpolation. Now with the rounded corners I used the following algorithm (the 4 'if's are there so I can have variable roundness on each of the 4 corners):
using (var b = new Bitmap(dataSize.Width, dataSize.Height, PixelFormat.Format32bppArgb))
using (var g = Graphics.FromImage(b))
{
// set interpolation
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
// transformation to scale and shift the brush
var transform = new Matrix();
transform.Scale(ratio, ratio);
transform.Translate(start.X / ratio, start.Y / ratio);
var brush = new TextureBrush(original) { Transform = transform };
// create path for stamping the iamge
var gp = new GraphicsPath(FillMode.Winding);
if (descriptor.CornerRadiusLeftTop > 0)
gp.AddArc(descriptor.GetLeftTopCorner(b.Size), 180, 90);
else
gp.AddLine(-1, -1, -1, -1);
if (descriptor.CornerRadiusRightTop > 0)
gp.AddArc(descriptor.GetRightTopCorner(b.Size), 270, 90);
else
gp.AddLine(b.Width + 1, -1, b.Width + 1, -1);
if (descriptor.CornerRadiusRightBottom > 0)
gp.AddArc(descriptor.GetRightBottomCorner(b.Size), 0, 90);
else
gp.AddLine(b.Width + 1, b.Height + 1, b.Width + 1, b.Height + 1);
if (descriptor.CornerRadiusLeftBottom > 0)
gp.AddArc(descriptor.GetLeftBottomCorner(b.Size), 90, 90);
else
gp.AddLine(-1, b.Height + 1, -1, b.Height + 1);
// stamp the image with original
g.FillPath(brush, gp);
}
but this approach produced ugly un-interpolated imaged with really jagged gradients. Is there a better approach to create transparent thumbnails or are there some settings I could use to improve the output?
I've written a blog post which explains exactly how to do this.
http://danbystrom.se/2008/08/24/soft-edged-images-in-gdi/
If you look at the first sample images, you're seeing 5) and I show how to arrive at 6). Good luck.
I would first copy the image to second one with rounded corners, then use GetThumbnailImage to scale it down.
I've used a modified TransferChannel method to add mask, which is not unsafe as in blog post by danbystrom.
public static void TransferChannel(Bitmap src, Bitmap dst, ChannelARGB sourceChannel, ChannelARGB destChannel)
{
if (src.Size != dst.Size)
throw new ArgumentException();
var r = new Rectangle(Point.Empty, src.Size);
var bdSrc = src.LockBits(r, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bdDst = dst.LockBits(r, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
var s = bdSrc.Stride * src.Height;
var baSrc = new byte[s];
var baDst = new byte[s];
Marshal.Copy(bdSrc.Scan0, baSrc, 0, s);
Marshal.Copy(bdDst.Scan0, baDst, 0, s);
for (var counter = 0; counter < baSrc.Length; counter += 4)
baDst[counter + (int)destChannel] = baSrc[counter + (int)sourceChannel];
Marshal.Copy(baDst, 0, bdDst.Scan0, s);
src.UnlockBits(bdSrc);
dst.UnlockBits(bdDst);
}
And my method to resize and make rounded corners is:
var b = new Bitmap(dataSize.Width, dataSize.Height, PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(b))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(original, start.X, start.Y, original.Width * ratio, original.Height * ratio);
if (hasRoundedCorners)
using (var mask = CreateMask(dataSize, radius))
TransferChannel(mask, b, ChannelARGB.Blue, ChannelARGB.Alpha);
}
return b;
I have been trying to draw an annulus (ring with thickness) with a transparent hole and a gradient rim in C# with very little success. Does anyone have any suggestions on how to do this?
here's a nice Blend Utility
Here's the Final result - thanks to BlueMonkMN
Rectangle GetSquareRec(double radius, int x, int y)
{
double r = radius;
double side = Math.Sqrt(Math.Pow(r, 2) / 2);
Rectangle rec = new Rectangle(x - ((int)side), y - ((int)side), (int)(side * 2) + x, (int)(side * 2) + y);
return rec;
}
void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics gTarget = e.Graphics;
gTarget.SmoothingMode = SmoothingMode.AntiAlias;
GraphicsPath pTemp = new GraphicsPath();
Rectangle r = GetSquareRec(200, 225, 225);
pTemp.AddEllipse(r);
pTemp.AddEllipse(GetSquareRec(50, 225, 225));
Color[] colors = new Color[5];
colors[0] = Color.FromArgb(192, 192, 192);
colors[1] = Color.FromArgb(105, 0, 0);
colors[2] = Color.FromArgb(169, 169, 169);
colors[3] = Color.FromArgb(0, 0, 0);
colors[4] = Color.FromArgb(0, 0, 0);
float[] positions = new float[5];
positions[0] = 0f;
positions[1] = 0.1f;
positions[2] = 0.35f;
positions[3] = 0.5f;
positions[4] = 1f;
ColorBlend Cb = new ColorBlend();
Cb.Colors = colors;
Cb.Positions = positions;
PathGradientBrush pgb = new PathGradientBrush(pTemp);
pgb.InterpolationColors = Cb;
pgb.CenterPoint = new PointF(r.X + (r.Width / 2), r.Y + (r.Height / 2));
gTarget.FillPath(pgb, pTemp);
}
http://www.freeimagehosting.net/uploads/th.515733e62e.jpg
This is how I did it in the Scrolling Game Development Kit:
pTemp = new GraphicsPath();
pTemp.AddEllipse(Start.X, Start.Y, End.X - Start.X, End.Y - Start.Y);
pTemp.AddEllipse((Start.X * 3 + End.X) / 4f,
(Start.Y * 3 + End.Y) / 4f,
(End.X - Start.X) / 2f,
(End.Y - Start.Y) / 2f);
PathGradientBrush pgb = new PathGradientBrush(pTemp);
Blend b = new Blend();
b.Factors = new float[] { 0, 1, 1 };
b.Positions = new float[] { 0, .5F, 1 };
pgb.Blend = b;
pgb.CenterColor = ((SolidBrush)CurrentBrush).Color;
pgb.SurroundColors = new Color[] {CurrentPen.Color};
gTarget.FillPath(pgb, pTemp);
pgb.Dispose();
pTemp.Dispose();
(source: enigmadream.com)
I edited the original SGDK code for this sample because originally I wasn't smart enough to scale the gradient to exclude the hole, but now I guess I am :).
If you would rather see the gradient like this:
(source: enigmadream.com)
Then change the blend code to look like this:
Blend blend = new Blend();
blend.Factors = new float[] { 0, 1, 0, 0 };
blend.Positions = new float[] { 0, 0.25F, .5F, 1 };
pgb.Blend = blend;
You may use two calls to Graphics.DrawArc combined, drawing the top and bottom or left and right portions of the annulus, one portion at a time.