Using a sliced strock as a timer
I already have the use of a running sliced strock like a timer. I wanted to do the same with a picture with rounded corners, but it didn't work and it was cropped for a round one.
Real and
Cropped after slice
This is the code I used:
for (int i = 0; i <= 100; i++)
{
Bitmap bitmap2 = Pie(bitmap, i);
var ms = new MemoryStream();
bitmap2.Save(ms, ImageFormat.Png);
RendererManager.LoadImage("NewVisuals.roundedRect" + i, ms);
}
public static Bitmap Pie(System.Drawing.Image source, int pct)
{
Bitmap bitmap = new Bitmap(source.Width, source.Height);
using GraphicsPath graphicsPath = new GraphicsPath();
graphicsPath.AddArc(0, 0, source.Width, source.Height, -90f, 3.6f * (float)pct);
using Graphics graphics = Graphics.FromImage(bitmap);
graphics.SetClip(graphicsPath);
graphics.DrawImage(source, 0, 0, source.Width, source.Height);
return bitmap;
}
How can I avoid cutting corners, but just slice the picture as shown in the example?
Example what i need
Find out how you can slice the outline for later use
Related
i want to save the image in the pictureBox1 including the Graphics.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.DrawRectangle(Pens.Green, 0, 0, pictureBox1.Width - 1, pictureBox1.Height - 1);
Pen p = new Pen(Color.Red);
e.Graphics.DrawLine(p, 256, 0, 256, 512);
e.Graphics.DrawLine(p, 0, 256, 512, 256);
DrawPieOnPicturebox(e.Graphics);
}
public void DrawPieOnPicturebox(Graphics myPieGraphic)
{
Color myPieColors = Color.FromArgb(150, Color.LightGreen);
Size myPieSize = new Size((int)distanceFromCenterPixels, (int)distanceFromCenterPixels);
Point myPieLocation = new Point((pictureBox1.Width - myPieSize.Width) / 2, (pictureBox1.Height - myPieSize.Height) / 2);
DrawMyPie(myPiePercent, myPieColors, myPieGraphic, myPieLocation, myPieSize);
}
int counter = 0;
public void DrawMyPie(int myPiePerecent, Color myPieColor, Graphics myPieGraphic, Point
myPieLocation, Size myPieSize)
{
using (SolidBrush brush = new SolidBrush(myPieColor))
{
myPieGraphic.FillPie(brush, new Rectangle(myPieLocation, myPieSize), Convert.ToSingle(myPiePerecent * 360 / 100), Convert.ToSingle(15 * 360 / 100));
}
myBitmap = new Bitmap(pictureBox1.Image);
myBitmap.Save(#"d:\" + counter + "mybitmap1.bmp");
myBitmap.Dispose();
counter++;
}
it's saving the image in the pictureBox1 but without the drawn FillPie. i want to add to the saved bitmap also the drawn FillPie.
I think your best bet would be to use myPieGraphic.DrawImage followed by your myPieGraphic.FillPie, the use the myPieGraphic.Save method.
It's unclear how this is layered in your app but I'm assuming you have an Image in a Picturebox and then you draw on top of it using a Graphics?
Doing that will render to separate "Layers" so to speak they are not the same Image/Graphics. If you instead draw both the Image and your Pie using the same Graphics you can then save directly from it.
I think that will produce the result you want.
See link for all Graphics methods:
https://learn.microsoft.com/en-us/dotnet/api/system.drawing.graphics.addmetafilecomment?view=dotnet-plat-ext-7.0
Edit: Answering comment.
using(MemoryStream ms = new MemoryStream()) {
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
using (var fs = new FileStream(#"c:\temp\test.png", FileMode.Create))
{
var bytes = ms.ToArray();
await fs.WriteAsync(bytes , 0, bytes.Length);
}
}
I am trying to add a black banner on the top and bottom of an image. I can add the banner but the pixel format of the resulted bitmap is changed to 32-bit. Is there any way to get an 8-bit bitmap as a result.
As mentioned here, If I set the 8-bit pixelFormat in the constructor, creating a graphics will raise an exception.
I read that if I convert from 32 to 8, maybe the pixel values will be different than original ones. Don't know if I can create a new bitmap with the desired height and add the black banners's pixels using for loops. Anyone has a better and simple way?
My code is as below:
Bitmap img = image as Bitmap;
using (Bitmap bitmap = new Bitmap(img.Width, img.Height + 200*2)) // create blank bitmap of desired size
using (Graphics graphics = Graphics.FromImage(bitmap))
{
SolidBrush brush = new SolidBrush(Color.White);
graphics.FillRectangle(brush, 0, 0, img.Width,200);
// draw existing image onto new blank bitmap
graphics.DrawImage(img, 0, 200, img.Width, img.Height);
// draw your rectangle below the original image
graphics.FillRectangle(brush, 0, 200 + img.Height, img.Width, 200);
// bitmap.Save(#"c:\Test1.bmp");
}
Here is a routine that does a FillRectangle on a bmp8bpp bitmap.
You pass in an index which, if positive will be used to put the color into the palette. If you pass in a negative index it will try to find the color and if it doesn't find it it will place the color at the end of the index. This will overwrite whatever color was there before!
void FillIndexedRectangle(Bitmap bmp8bpp, Rectangle rect, Color col, int index)
{
var pal = bmp8bpp.Palette;
int idx = -1;
if (index >= 0) idx = index;
else
{
if (pal.Entries.Where(x => x.ToArgb() == col.ToArgb()).Any())
idx = pal.Entries.ToList().FindIndex(x => x.ToArgb() == col.ToArgb());
if (idx < 0) idx = pal.Entries.Length - 1;
}
pal.Entries[idx] = col;
bmp8bpp.Palette = pal;
var bitmapData =
bmp8bpp.LockBits(new Rectangle(Point.Empty, bmp8bpp.Size),
ImageLockMode.ReadWrite, bmp8bpp.PixelFormat);
byte[] buffer=new byte[bmp8bpp.Width*bmp8bpp.Height];
Marshal.Copy(bitmapData.Scan0, buffer,0, buffer.Length);
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
{
buffer[x + y * bmp8bpp.Width] = (byte)idx;
}
Marshal.Copy(buffer, 0, bitmapData.Scan0,buffer.Length);
bmp8bpp.UnlockBits(bitmapData);
}
Example calls:
Bitmap img = new Bitmap(200, 200, PixelFormat.Format8bppIndexed);
FillIndexedRectangle(img, new Rectangle(0,0,200, 200), Color.Silver, 21);
FillIndexedRectangle(img, new Rectangle(23, 23, 55, 99), Color.Red, 22);
FillIndexedRectangle(img, new Rectangle(123, 123, 55, 33), Color.Black, 23);
FillIndexedRectangle(img, new Rectangle(1, 1, 123, 22), Color.Orange, 34);
FillIndexedRectangle(img, new Rectangle(27, 27, 16, 12), Color.Black, -1);
img.Save("D:\\__bmp8bpp.png");
Result:
There is room for improvement:
All error checking in the lockbits is missing, both wrt pixelformat and rectangle data
Adding colors with a more dynamical scheme could be nice instead of adding to the end
A scheme for finding the closest color already in the palette could also be nice
A scheme for drawing with transparency would also be nice. For this all necessary new colors would have to be determined first; also the tranparency mode.
Maybe one should return the index used from the method so the next calls can refer to it..
For other shapes than rectangles one could use a copy routine that first draws them onto a 'normal' 32bpp bitmap and then transfers the pixels to the buffer..
Update: Here are a few lines to add (**) or change (*) to allow drawing unfilled rectangles; stroke 0 fills the rectangle..:
void FillIndexedRectangle(Bitmap bmp8bpp, Rectangle rect,
Color col, int index, int stroke) // *
...
...
Marshal.Copy(bitmapData.Scan0, buffer,0, buffer.Length);
Rectangle ri = rect; //**
if (stroke > 0) ri.Inflate(-stroke, -stroke); //**
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
{
if (ri == rect || !ri.Contains(x,y)) //**
buffer[x + y * bmp8bpp.Width] = (byte)idx;
}
Marshal.Copy(buffer, 0, bitmapData.Scan0,buffer.Length);
In C#, how do I generate a binary mask which has no gray values/noise in the image?
Right now I am able to generate a fairly simple output which looks very close to what I want but has noise around the edges both inside and outside of the white blob (You need to zoom all the way in to see the noise). I am planning to use the image later for image processing but cannot have anything other than black and white values in the image.
Pictures:
Crop 1
Crop 2
Code:
public CropedImage(List<Node> nodes)
{
InitializeComponent();
//List<PointF> listpoints = new List<PointF>();
nodes.ToArray();
PointF[] points = new PointF[nodes.Count];
for(int i = 0; i < nodes.Count; ++i)
{
points[i].X = nodes[i].X;
points[i].Y = nodes[i].Y;
}
Image SourceImage = ImageOperations.GetImage(MainForm.imageMatrix, pictureBox1);
using (Graphics g = Graphics.FromImage(SourceImage))
{
Color black = ColorTranslator.FromHtml("#000000");
Color white = ColorTranslator.FromHtml("#FFFFFF");
using (Brush b = new SolidBrush(black))
{
Rectangle rect = new Rectangle();
g.FillRectangle(b, 0, 0, MainForm.WIDTH, MainForm.HEIGHT);
}
using (Brush b2 = new SolidBrush(white))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillClosedCurve(b2, points, 0);
}
}
}
How about changing
g.SmoothingMode = SmoothingMode.AntiAlias;
to
g.SmoothingMode = SmoothingMode.None;
If I understand correctly, that would solve your problem.
As eocron said you have to check all the pixels and round them to black or white. the most simple way of doing it (if only you are creating a temp application to do this for a few images) is to use GetPixel() and SetPixel() methods:
Bitmap bmp = Bitmap.FromFile("image.png");
for(int i=0;i<bmp.Width;i++)
for(int j=0;j<bmp.Height;j++)
bmp.SetPixel(i,j, bmp.GetPixel(i,j).R > 127? Color.White: Color.Black); // as its gray I'm only checking Red
However if you are dealing with lots of images especially large ones, or its not a temp application to deal with a few images, and its going to be a feature of your application, the above code is not what you would like to use as it is very slow, and you should use LockBits as it is way faster:
Bitmap bmp = Bitmap.FromFile("image.png");
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect,System.Drawing.Imaging.ImageLockMode.ReadWrite,bmp.PixelFormat;
IntPtr ptr = bmpData.Scan0;
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
for (int counter = 0; counter < rgbValues.Length; counter += 4)
{
int c = rgbValues[counter] > 127 ? 255: 0;
rgbValues[counter] = c;
rgbValues[counter+1] = c;
rgbValues[counter+2] = c;
}
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
bmp.UnlockBits(bmpData);
Currently i'm working on a system that load a very large image, with minimum width x heigh >= 10.000.000 pixel.
But the ratio of the user's upload image usually do not match our requirement ratio so i have to crop it to proper ratio, but when using System.Drawing bitmap to crop it, i always got SytemOutOfMemory exception.
I have try Bitmap.Clone and Graphic.DrawImage with correct RectangleF but no luck.
Is there anyways to do this without getting the outofmemory exception or are there any alternatives to System.Drawing library to get this task done easily ?
My code to load the image from user upload file:
var fileBinary = new byte[stream.Length];
stream.Read(fileBinary, 0, fileBinary.Length);
stream.Position = 0;
var fileExtension = Path.GetExtension(fileName);
using (Image image = Image.FromStream(stream, false, false))
{
//validation and check ratio
CropImage(image, PORTRAIT_RATIO, fileExtension);
}
And the CropImage function:
//Crop Image from center with predefine ratio
private byte[] CropImage(Image sourceImg, float ratio, string fileExtension)
var height = sourceImg.Height;
var width = sourceImg.Width;
var isPortrait = width < height;
RectangleF croppingRec = new RectangleF();
float positionX = 0;
float positionY = 0;
float cropHeight = (float)height;
float cropWidth = cropHeight * PORTRAIT_RATIO;
positionY = 0;
positionX = (width - cropWidth) / 2;
if (cropWidth > width)
{
cropWidth = width;
cropHeight = cropWidth * (1 / PORTRAIT_RATIO);
positionX = 0;
positionY = ((height - cropHeight) / 2);
}
croppingRec.Width = cropWidth;
croppingRec.Height = cropHeight;
croppingRec.X = positionX;
croppingRec.Y = positionY;
Bitmap bmpImage = sourceImg as Bitmap;
Bitmap bmpCrop = bmpImage.Clone(croppingRec, bmpImage.PixelFormat);
bmpCrop.Save("D:/test" + fileExtension, ImageFormat.Jpeg);
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(bmpCrop, typeof(byte[]));
}
}
You could convert the bitmap to a byte array. Try something like this (looks hackie but i don't know another way):
int pixelSize = 3;
int bytesCount = imgHeight * imgWidth * pixelSize;
byte[] byteArray= new byte[bytesCount];
BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, imgWidth, imgHeight), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Marshal.Copy(bitmapData.Scan0, byteArray, 0, bytesCount);
Each pixel in this array is represented by 3 bytes (this depends on the bitmap type). So you know that lenght of a bitmap line is 3 * imgWidth. Using this you could simply navigate in the byte array and copy just what you need into a new array.
You would then create a new bitmap with the desired final size, get the bitmap data and Marshal.Copy the new array into that:
Bitmap newBitmap = new Bitmap(Width, Height);
BitmapData newBitmapData = b.LockBits(BoundsRect,
ImageLockMode.WriteOnly,
newBitmap.PixelFormat);
Marshal.Copy(newByteArray, 0, newBitmapData.Scan0, newBytesCount);
Unlock the bitmaps at the end:
newBitmap.UnlockBits(newBitmapData );
bitmap.UnlockBits(bitmapData);
Hope this helps. Cheers.
Try using graphicsmagick.net library, then use the Crop method on MagickImage. It should still work well under asp.net, and handles huge image files using disk for scratch.
I need to create an image in memory (can be huge image!) and to extract from it byte array in the size of width x height. Each byte must have value of 0-255 (256 gray scale values: 0 for white and 255 for black).
The part of creating the image is easy, here is a simple example of my code:
img = new Bitmap(width, height);
drawing = Graphics.FromImage(img);
drawing.Clear(Color.Black);// paint the background
drawing.DrawString(text, font, Brushes.White, 0, 0);
Problem is to convert it to "my" special gray scale byte array. When I'm using any pixel format other then Format8bppIndexed, the byte array I'm getting from the bitmap is not in the size I need (width*length) so I need a conversion that takes too much time. When I'm using Format8bppIndexed I'm getting the byte array very fast and in the right size, but each byte/pixel is 0-15.
Changing the bitmap palette has no affect:
var pal = img.Palette;
for (int i = 1; i < 256; i++){
pal.Entries[i] = Color.FromArgb(255, 255, 255);
}
img.Palette = pal;
Any idea how to do it?
Edit: Full code:
// assume font can be Times New Roman, size 7500!
static private Bitmap DrawText(String text, Font font)
{
//first, create a dummy bitmap just to get a graphics object
var img = new Bitmap(1, 1);
var drawing = Graphics.FromImage(img);
//measure the string to see how big the image needs to be
var textSize = drawing.MeasureString(text, font);
//free up the dummy image and old graphics object
img.Dispose();
drawing.Dispose();
//create a new image of the right size (must be multiple of 4)
int width = (int) (textSize.Width/4) * 4;
int height = (int)(textSize.Height / 4) * 4;
img = new Bitmap(width, height);
drawing = Graphics.FromImage(img);
// paint the background
drawing.Clear(Color.Black);
drawing.DrawString(text, font, Brushes.White, 0, 0);
var bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
var newBitmap = new Bitmap(width, height, bmpData.Stride, PixelFormat.Format8bppIndexed, bmpData.Scan0);
drawing.Dispose();
return newBitmap;
}
private static byte[] GetGrayscleBytesFastest(Bitmap bitmap)
{
BitmapData bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
int numbytes = bmpdata.Stride * bitmap.Height;
byte[] bytedata = new byte[numbytes];
IntPtr ptr = bmpdata.Scan0;
Marshal.Copy(ptr, bytedata, 0, numbytes);
bitmap.UnlockBits(bmpdata);
return bytedata;
}
You probably want to do this in two steps. First, create a 16bpp grayscale copy of your original image as described in Convert an image to grayscale.
Then, create your 8bpp image with the appropriate color table and draw the 16bpp grayscale image onto that image. That will do the conversion for you, converting the 16-bit grayscale values to your 256 different colors.
You should then have an 8bpp image with your 256 different shades of gray. You can then call LockBits to get access to the bitmap bits, which will be index values in the range 0 to 255.
I have solved this problem with ImageSharp
I calculate the gray value from the rgb values and then add it to the array.
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
private static byte[] GetImageData(byte[] imageData)
{
using (var image = Image.Load<Rgba32>(imageData))
{
var buffer = new byte[image.Width * image.Height];
var index = 0;
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> pixelRow = accessor.GetRowSpan(y);
for (int x = 0; x < pixelRow.Length; x++)
{
ref Rgba32 pixel = ref pixelRow[x];
buffer[index] = (byte)((pixel.R + pixel.G + pixel.B) / 3);
index++;
}
}
});
return buffer;
}
}