Change alpha coefficient using Lockbits - c#

I have written a function which changes the alpha coefficient of an image. I use setpixel and getpixel,which is very slow. I found out that Lockbits method is faster.How can I do it with Lockbits?
Here is my current code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
private static Image Tran(Image s,int alpha)
{
int x = 0, y = 0;
Bitmap tImage = new Bitmap(s);
for (x = 0; x < tImage.Width; x++)
{
for (y = 0; y < tImage.Height; y++)
{
tImage.SetPixel(x, y, Color.FromArgb(alpha, tImage.GetPixel(x, y).R, tImage.GetPixel(x, y).G, tImage.GetPixel(x, y).B));
}
}
return tImage;
}
public Form1()
{
InitializeComponent();
trackBar1.TickStyle = TickStyle.Both;
trackBar1.Orientation = Orientation.Vertical;
trackBar1.Minimum = 0;
trackBar1.Maximum = 255;
trackBar1.Height = 101;
trackBar1.Value = 255;
pictureBox1.Image = Image.FromFile("C:\\Users\\rati\\Desktop\\water.jpg");
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
pictureBox1.Image = ChangeAlpha(pictureBox1.Image, trackBar1.Value);
textBox1.Text = trackBar1.Value.ToString();
}
}
}

You can change the opacity of your image by drawing it in a new bitmap using a new ColorMatrix and assigning a float value between 0 and 1 to its Matrix33 as its new alpha value:
public Image ChangeAlpha(Image img, int value)
{
if (value < 0 || value > 255)
throw new Exception("value must be between 0 and 255");
Bitmap bmp = new Bitmap(img.Width, img.Height); // Determining Width and Height of Source Image
Graphics graphics = Graphics.FromImage(bmp);
ColorMatrix colormatrix = new ColorMatrix();
colormatrix.Matrix33 = value / 255f;
ImageAttributes imgAttribute = new ImageAttributes();
imgAttribute.SetColorMatrix(colormatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
graphics.DrawImage(img, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttribute);
graphics.Dispose(); // Releasing all resource used by graphics
return bmp;
}
And here is a sample usage:
private void button1_Click(object sender, EventArgs e)
{
int opacityvalue = 127;
var img = ChangeAlpha(Image.FromFile(#"d:\1.png"), opacityvalue);
img.Save(#"d:\2.png");
}
Don't forget to add using System.Drawing; and using System.Drawing.Imaging;.
You can see before and after calling the function with value=127 below:
EDIT
If you want to see the result in a PictureBox you should pay attention to using 2 different picture boxes, one for original image, and one for changed image:
private void trackBar1_Scroll(object sender, EventArgs e)
{
this.pictureBox2.Image = ChangeAlpha(this.pictureBox1.Image, this.trackBar1.Value);
}
As I see in your code when you change the opacity, you set the result as image of your PictureBox1 again and apply opacity again on the result. In other word you apply opacity on an image that you applied opacity to it before over and over again.

public Form1()
{
...
originalImage = new Bitmap("C:\\Users\\rati\\Desktop\\water.jpg");
pictureBox1.Image = originalImage;
...
}
// Add an extra field to the Form class.
Bitmap originalImage;
void trackBar1_Scroll(object sender, EventArgs e)
{
pictureBox1.Image = ChangeAlpha((byte)trackBar1.Value);
textBox1.Text = trackBar1.Value.ToString();
}
Image ChangeAlpha(byte alpha)
{
Bitmap bmp = new Bitmap(originalImage);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
Marshal.Copy(ptr, rgbValues, 0, bytes);
// Set every fourth value to alpha. A 32bpp bitmap will change transparency.
for (int counter = 3; counter < rgbValues.Length; counter += 4)
rgbValues[counter] = alpha;
// Also you can try parallelize loop.
//int length = rgbValues.Length / 4;
//Parallel.For(3, length, counter => rgbValues[counter * 4 - 1] = alpha);
Marshal.Copy(rgbValues, 0, ptr, bytes);
bmp.UnlockBits(bmpData);
return bmp;
}

Related

How to copy the rgbalues to the pictureBox1 but one by one or each time a group of pixels?

private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
Bitmap bmp = new Bitmap(pictureBox1.Image);
// Lock the bitmap's bits.
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);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
// Set every third value to 255. A 24bpp bitmap will look red.
//for (int counter = 2; counter < rgbValues.Length; counter +=64)
// rgbValues[counter] = 255;
// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
// Unlock the bits.
bmp.UnlockBits(bmpData);
// Draw the modified image.
e.Graphics.DrawImage(bmp, 0, 0);
}
i mean to see in pictureBox2 the image get fill slowly like a paint get painted each time a bit. and not at once. with the original colors of the image in pictureBox1 to copy the image in pictureBox1 to pictureBox2 buti nstead in once to make it slowly and each time copy some pixels or one by one until the whole image paint is completed in pictureBox2.
I tried this.
in time tick event :
int cc = 0;
private void timer1_Tick(object sender, EventArgs e)
{
cc++;
pictureBox2.Invalidate();
}
in pictureBox2 paint event
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
Bitmap bmp = new Bitmap(pictureBox1.Image);
// Lock the bitmap's bits.
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);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * cc;//bmp.Height;
byte[] rgbValues = new byte[bytes];
// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
// Unlock the bits.
bmp.UnlockBits(bmpData);
// Draw the modified image.
e.Graphics.DrawImage(bmp, 0, 0);
}
but the code in the pictureBox2 paint event delete the image in the pictureBox2 delete slowly from the top to the bottom.
but i want the opposite that it will start that the pictureBox2 is clear and then the image will be painted slowly.
i tried to change the line :
Bitmap bmp = new Bitmap(pictureBox1.Image);
to
Bitmap bmp = new Bitmap(512, 512);
but then it does nothing in the pictureBox2.
Here's an example that will copy the image line by line:
private async void button1_Click(object sender, EventArgs e)
{
if (pictureBox1.Image != null)
{
button1.Enabled = false;
Bitmap bmp1 = new Bitmap(pictureBox1.Image);
Bitmap bmp2 = new Bitmap(bmp1.Width, bmp1.Height);
pictureBox2.Image = bmp2;
using (Graphics G = Graphics.FromImage(bmp2))
{
for (int y = 0; y < bmp1.Height; y++)
{
Rectangle rc = new Rectangle(new Point(0, y), new Size(bmp1.Width, 1));
G.DrawImage(bmp1, rc, rc, GraphicsUnit.Pixel);
pictureBox2.Invalidate();
await Task.Delay(1);
}
}
button1.Enabled = true;
}
}
Sample run:
To make it go faster, you can increase the height of the rectangle being copied, and then make the for loop jump by that much:
int height = 12; // added
for (int y = 0; y < bmp1.Height; y = y + height) // change
{
Rectangle rc = new Rectangle(new Point(0, y), new Size(bmp1.Width, height)); // change
G.DrawImage(bmp1, rc, rc, GraphicsUnit.Pixel);
pictureBox2.Invalidate();
await Task.Delay(1);
}
Here's another approach showing a radial expansion using a GraphicsPath, Region, and a Clip:
private async void button2_Click(object sender, EventArgs e)
{
if (pictureBox1.Image != null)
{
button2.Enabled = false;
Bitmap bmp1 = new Bitmap(pictureBox1.Image);
Bitmap bmp2 = new Bitmap(bmp1.Width, bmp1.Height);
pictureBox2.Image = bmp2;
int radius = Math.Max(bmp1.Width, bmp1.Height);
Point center = new Point(bmp1.Width / 2, bmp2.Height / 2);
using (Graphics G = Graphics.FromImage(bmp2))
{
int step = 10;
for (int r=0; r <=radius; r=r+step)
{
Rectangle rc = new Rectangle(center, new Size(1, 1));
rc.Inflate(r, r);
using (System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath())
{
gp.AddEllipse(rc);
using (Region rgn = new Region(gp))
{
G.Clip = rgn;
G.DrawImage(bmp1, 0, 0);
}
}
pictureBox2.Invalidate();
await Task.Delay(1);
}
}
button2.Enabled = true;
}
}
Play with the step value to make it go faster or slower:

Why SharpDX render my bitmap in grey and corrupted?

I am new in SharpDX and I want to simulate code to render a 24-bit bitmap image straight from memory and display to PictureBox. *This code is to be use in later project to quickly render images from memory.
I have no issue render using standard DrawImage() method. I opt for SharpDX because DrawImage is too slow.
But when I try render using SharpDX, the image become grey in color and corrupted (see image below)
The image I want to render is in 24-bit RGB bitmap.
Using DrawImage
Using SharpDX
What is wrong with my code?
Below is my code:
using System;
using System.Windows.Forms;
using SharpDX;
using SharpDX.Direct2D1;
using System.Diagnostics;
namespace bitmap_test
{
public partial class Form1 : Form
{
private System.Drawing.Bitmap image1 = null;
private System.Drawing.Imaging.BitmapData bmpdata1 = null;
//target of rendering
WindowRenderTarget target;
//factory for creating 2D elements
SharpDX.Direct2D1.Factory factory = new SharpDX.Direct2D1.Factory();
//this one is for creating DirectWrite Elements
SharpDX.DirectWrite.Factory factoryWrite = new SharpDX.DirectWrite.Factory();
private SharpDX.DXGI.Format bmp_format = SharpDX.DXGI.Format.B8G8R8A8_UNorm;
private AlphaMode bmp_alphamode = AlphaMode.Ignore;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
InitSharpDX();
//load 24-bit depth bitmap
LoadBitmapFromFile(#"D:\lena.bmp"); //https://software.intel.com/sites/default/files/forum/351974/lena.bmp
}
private void InitSharpDX()
{
//Init Direct Draw
//Set Rendering properties
RenderTargetProperties renderProp = new RenderTargetProperties()
{
DpiX = 0,
DpiY = 0,
MinLevel = FeatureLevel.Level_10,
PixelFormat = new PixelFormat(bmp_format, bmp_alphamode),
Type = RenderTargetType.Hardware,
Usage = RenderTargetUsage.None
};
//set hwnd target properties (permit to attach Direct2D to window)
HwndRenderTargetProperties winProp = new HwndRenderTargetProperties()
{
Hwnd = this.pictureBox1.Handle,
PixelSize = new Size2(this.pictureBox1.ClientSize.Width, this.pictureBox1.ClientSize.Height),
PresentOptions = PresentOptions.Immediately
};
//target creation
target = new WindowRenderTarget(factory, renderProp, winProp);
}
//load bmp file into program memory
private void LoadBitmapFromFile(string file)
{
image1 = (System.Drawing.Bitmap)System.Drawing.Image.FromFile(file, true);
var sourceArea = new System.Drawing.Rectangle(0, 0, image1.Width, image1.Height);
bmpdata1 = image1.LockBits(sourceArea, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
image1.UnlockBits(bmpdata1);
}
private void drawBitmap(IntPtr pBuffer, int len, int width, int height)
{
try
{
var bitmapProperties = new BitmapProperties(new PixelFormat(bmp_format, bmp_alphamode));
var size = new Size2(width, height);
int stride = width * 3; //only want RGB, ignore alpha
var bmp = new SharpDX.Direct2D1.Bitmap(target, size, new DataPointer(pBuffer, len), stride, bitmapProperties);
//draw elements
Draw(ref bmp);
bmp.Dispose();
}
finally
{
}
}
private void Draw(ref SharpDX.Direct2D1.Bitmap bmp)
{
//begin rendering
target.BeginDraw();
//clear target
target.Clear(null);
//draw bitmap
target.DrawBitmap(bmp, 1.0f, BitmapInterpolationMode.Linear);
//end drawing
target.EndDraw();
}
//draw image using SharpDX
private void cmdRender_Click(object sender, EventArgs e)
{
if (bmpdata1 != null)
{
int len = bmpdata1.Width * bmpdata1.Height * 3;
var sw = Stopwatch.StartNew();
drawBitmap(bmpdata1.Scan0, len, bmpdata1.Width, bmpdata1.Height);
sw.Stop();
Console.WriteLine("SharpDX: {0}us", sw.ElapsedTicks / (TimeSpan.TicksPerMillisecond / 1000));
}
}
//draw image using DrawImage()
private void cmdDrawImage_Click(object sender, EventArgs e)
{
if (image1 != null)
{
var g = pictureBox1.CreateGraphics();
var sourceArea = new System.Drawing.Rectangle(0, 0, image1.Width, image1.Height);
var sw = Stopwatch.StartNew();
g.DrawImage(image1, sourceArea); //DrawImage is slow
sw.Stop();
Console.WriteLine("DrawImage: {0}us", sw.ElapsedTicks/(TimeSpan.TicksPerMillisecond / 1000));
}
}
}
}
The bmp_format B8G8R8A8_UNorm doesn't match the System.Drawing lock format Format24bppRgb... also use bmpdata1.Stride instead of calculating a potential invalid stride.
Your stride is usually the width of the bitmap multipled by the byte depth.
so 512 * 4 would be a 512 wide bitmap with a 32 bit palette. Eg RGBA

Apply custom shape to any image

I need to apply a custom shape (this, a .jpg) to any image I load into my application, using Windows Forms if possible (since my whole project is made in Winforms), but I guess I can use WPF if it's needed.
So for example if I have a photo like this, the result should be like this (I made this with Photoshop for you to see), maybe even without the white borders if possible (please note that the image was resized and centered to fit the custom shape, and that's how it should be).
I can't figure out how to do this, but from what I've searched I suppose it has something to do with brushes (TextureBrush, ImageBrush, etc).
Regards.
it seems to me that you want to apply a mask, you could do it with something like this (using /unsafe code):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace testImageBlender
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Bitmap imBase;
Bitmap imMask,imMask2;
Bitmap blendedImage;
private void Form1_Load(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
if (openFileDialog2.ShowDialog() == DialogResult.OK)
{
imBase = new Bitmap(openFileDialog1.FileName);
imMask = new Bitmap(openFileDialog2.FileName);
blendedImage = new Bitmap(openFileDialog2.FileName);
blendImage();
}
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (imBase != null)
{
if (imMask != null)
{
if (blendedImage != null)
{
e.Graphics.DrawImage(blendedImage, e.ClipRectangle);
}
}
}
}
private void blendImage()
{
Rectangle mRec = new Rectangle(0, 0, imMask.Width, imMask.Height);
float intensity = 0;
//playing around with images to get pixel format 24bpp RGB, most probably a very bad way of doing it, but I don't know enough about image format and lockbits to do it nice and clean yet
imMask2 = new Bitmap(imMask);
Graphics.FromImage(imBase).DrawImage(imBase, 0, 0, imMask.Width, imMask.Height);
Graphics.FromImage(blendedImage).DrawImage(imMask, 0, 0, imMask.Width, imMask.Height);
Graphics.FromImage(imMask2).DrawImage(imMask, 0, 0, imMask.Width, imMask.Height);
BitmapData imageData = imBase.LockBits(mRec, ImageLockMode.ReadOnly, imBase.PixelFormat);
BitmapData maskData = imMask2.LockBits(mRec, ImageLockMode.ReadOnly, imMask.PixelFormat);
BitmapData blendedData = blendedImage.LockBits(mRec, ImageLockMode.WriteOnly, blendedImage.PixelFormat);
unsafe
{
byte* imagePointer = (byte*)imageData.Scan0;
byte* maskPointer = (byte*)maskData.Scan0;
byte* blendedPointer = (byte*)blendedData.Scan0;
for (int i = 0; i < mRec.Height; i++)
{
for (int j = 0; j < mRec.Width; j++)
{
intensity = 1-((float)(maskPointer[0] + maskPointer[1] + maskPointer[2])) / ((float)3 * 255);
for (int k = 0; k < 3; k++)
{
blendedPointer[k] = (intensity>0.8)?(byte)((float)imagePointer[k]):(byte)255;
}
// 3 bytes per pixel
imagePointer += 3;
maskPointer += 3;
blendedPointer += 3;
}
// Moving the pointers to the next pixel row
imagePointer += imageData.Stride - (mRec.Width * 3);
maskPointer += maskData.Stride - (mRec.Width * 3);
blendedPointer += blendedData.Stride - (mRec.Width * 3);
}
}
imBase.UnlockBits(imageData);
imMask2.UnlockBits(maskData);
blendedImage.UnlockBits(blendedData);
}
}
}
this is very much inspired by Balazs Tihanyi answers on Draw image on top of another image with blending mode color
Here is an example tuned for the image you linked to:
using (var source = (Bitmap) Image.FromFile(#"image filename"))
{
using (var target = new Bitmap(source.Width, source.Height))
{
using (var graphics = Graphics.FromImage(target))
{
using (var path = new GraphicsPath())
{
path.StartFigure();
path.AddLine(161, 665, 223, 624);
path.AddLine(223, 624, 252, 568);
path.AddLine(252, 568, 239, 525);
path.AddLine(239, 525, 200, 432);
path.AddLine(200, 432, 178, 343);
path.AddLine(178, 343, 156, 198);
path.AddLine(156, 198, 161, 131);
path.AddLine(161, 131, 248, 38);
path.AddLine(248, 38, 366, 4);
path.AddLine(366, 4, 657, 10);
path.AddLine(657, 10, 735, 70);
path.AddLine(735, 70, 741, 195);
path.AddLine(741, 195, 746, 282);
path.AddLine(746, 282, 762, 410);
path.AddLine(762, 410, 730, 498);
path.AddLine(730, 498, 686, 582);
path.AddLine(686, 582, 727, 669);
path.CloseFigure();
graphics.Clip = new Region(path);
}
graphics.DrawImage(source, 0, 0);
}
this.PictureBox.Image = new Bitmap(target);
}
}

Auto Resize Font to fit rectangle

How can I create in .NET 4.5 / C# a font with a adaptive size to fit a specified rectangle ?
I have a resize based on the string length, the longer the string, the smaller the fontsize, but it does not work very well, if the string is too long the text gets very small. The problem with this method is that if I change the rectangle size all the font sizes are not good again.
It's been a while but I came across this issue.
MSDN offers a sample with a GetAdjustedFont method to help with this process:
(shamelessly stolen from https://msdn.microsoft.com/en-us/library/bb986765.aspx)
public Font GetAdjustedFont(Graphics g, string graphicString, Font originalFont, int containerWidth, int maxFontSize, int minFontSize, bool smallestOnFail)
{
Font testFont = null;
// We utilize MeasureString which we get via a control instance
for (int adjustedSize = maxFontSize; adjustedSize >= minFontSize; adjustedSize--)
{
testFont = new Font(originalFont.Name, adjustedSize, originalFont.Style);
// Test the string with the new size
SizeF adjustedSizeNew = g.MeasureString(graphicString, testFont);
if (containerWidth > Convert.ToInt32(adjustedSizeNew.Width))
{
// Good font, return it
return testFont;
}
}
// If you get here there was no fontsize that worked
// return minimumSize or original?
if (smallestOnFail)
{
return testFont;
}
else
{
return originalFont;
}
}
With Graphics.MeasureString you can measure the size of a string, so you can calculate what you need.
Sample from MSDN
private void MeasureStringMin(PaintEventArgs e)
{
// Set up string.
string measureString = "Measure String";
Font stringFont = new Font("Arial", 16);
// Measure string.
SizeF stringSize = new SizeF();
stringSize = e.Graphics.MeasureString(measureString, stringFont);
// Draw rectangle representing size of string.
e.Graphics.DrawRectangle(new Pen(Color.Red, 1), 0.0F, 0.0F, stringSize.Width, stringSize.Height);
// Draw string to screen.
e.Graphics.DrawString(measureString, stringFont, Brushes.Black, new PointF(0, 0));
}
You can measure the string width with whatever font size (e.g. 16), and then you can calculate your desired font size like this:
fontSize = (previouslyMeasuredFontSize * targetWidthOfString) / previouslyMeasuredStringWidth;
I also needed something similar. I created some code to answer the need of drawing text inside a defined rectangle with several auto font size options (see DrawMethod enum). It also supports auto warp with and without auto font size. My solution was inspired by the answers above.
Be sure to add WindowsBase and PresentationCore assemblies.
DrawTextToBitmap.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ReportPrepare
{
class TextDrawing
{
public enum DrawMethod
{
AutosizeAccordingToText, // create the smallest bitmap needed to draw the text without word warp
AutoFitInConstantRectangleWithoutWarp, // draw text with the biggest font possible while not exceeding rectangle dimensions, without word warp
AutoWarpInConstantRectangle, // draw text in rectangle while performing word warp. font size is a constant input. drawing may exceed bitmap rectangle.
AutoFitInConstantRectangleWithWarp // draw text with the biggest font possible while not exceeding rectangle dimensions, with word warp
}
private static void SetGraphicsHighQualityForTextRendering(Graphics g)
{
// The smoothing mode specifies whether lines, curves, and the edges of filled areas use smoothing (also called antialiasing). One exception is that path gradient brushes do not obey the smoothing mode. Areas filled using a PathGradientBrush are rendered the same way (aliased) regardless of the SmoothingMode property.
g.SmoothingMode = SmoothingMode.AntiAlias;
// The interpolation mode determines how intermediate values between two endpoints are calculated.
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Use this property to specify either higher quality, slower rendering, or lower quality, faster rendering of the contents of this Graphics object.
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
// This one is important
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
}
public static Size MeasureDrawTextBitmapSize(string text, Font font)
{
Bitmap bmp = new Bitmap(1, 1);
using (Graphics g = Graphics.FromImage(bmp))
{
SizeF size = g.MeasureString(text, font);
return new Size((int)(Math.Ceiling(size.Width)), (int)(Math.Ceiling(size.Height)));
}
}
public static int GetMaximumFontSizeFitInRectangle(string text, Font font, RectangleF rectanglef, bool isWarp, int MinumumFontSize=6, int MaximumFontSize=1000)
{
Font newFont;
Rectangle rect = Rectangle.Ceiling(rectanglef);
for (int newFontSize = MinumumFontSize; ; newFontSize++)
{
newFont = new Font(font.FontFamily, newFontSize, font.Style);
List<string> ls = WarpText(text, newFont, rect.Width);
StringBuilder sb = new StringBuilder();
if (isWarp)
{
for (int i = 0; i < ls.Count; ++i)
{
sb.Append(ls[i] + Environment.NewLine);
}
}
else
{
sb.Append(text);
}
Size size = MeasureDrawTextBitmapSize(sb.ToString(), newFont);
if (size.Width > rectanglef.Width || size.Height > rectanglef.Height)
{
return (newFontSize - 1);
}
if (newFontSize >= MaximumFontSize)
{
return (newFontSize - 1);
}
}
}
public static List<string> WarpText(string text, Font font, int lineWidthInPixels)
{
string[] originalLines = text.Split(new string[] { " " }, StringSplitOptions.None);
List<string> wrappedLines = new List<string>();
StringBuilder actualLine = new StringBuilder();
double actualWidthInPixels = 0;
foreach (string str in originalLines)
{
Size size = MeasureDrawTextBitmapSize(str, font);
actualLine.Append(str + " ");
actualWidthInPixels += size.Width;
if (actualWidthInPixels > lineWidthInPixels)
{
actualLine = actualLine.Remove(actualLine.ToString().Length - str.Length - 1, str.Length);
wrappedLines.Add(actualLine.ToString());
actualLine.Clear();
actualLine.Append(str + " ");
actualWidthInPixels = size.Width;
}
}
if (actualLine.Length > 0)
{
wrappedLines.Add(actualLine.ToString());
}
return wrappedLines;
}
public static Bitmap DrawTextToBitmap(string text, Font font, Color color, DrawMethod mode, RectangleF rectanglef)
{
StringFormat drawFormat = new StringFormat();
Bitmap bmp;
switch (mode)
{
case DrawMethod.AutosizeAccordingToText:
{
Size size = MeasureDrawTextBitmapSize(text, font);
if (size.Width == 0 || size.Height == 0)
{
bmp = new Bitmap(1, 1);
}
else
{
bmp = new Bitmap(size.Width, size.Height);
}
using (Graphics g = Graphics.FromImage(bmp))
{
SetGraphicsHighQualityForTextRendering(g);
g.DrawString(text, font, new SolidBrush(color), 0, 0);
return bmp;
}
}
case DrawMethod.AutoWarpInConstantRectangle:
{
Rectangle rect = Rectangle.Ceiling(rectanglef);
bmp = new Bitmap(rect.Width,rect.Height);
if (rect.Width == 0 || rect.Height == 0)
{
bmp = new Bitmap(1, 1);
}
else
{
bmp = new Bitmap(rect.Width, rect.Height);
}
using (Graphics g = Graphics.FromImage(bmp))
{
SetGraphicsHighQualityForTextRendering(g);
g.DrawString(text, font, new SolidBrush(color), rectanglef, drawFormat);
return bmp;
}
}
case DrawMethod.AutoFitInConstantRectangleWithoutWarp:
{
Rectangle rect = Rectangle.Ceiling(rectanglef);
bmp = new Bitmap(rect.Width, rect.Height);
if (rect.Width == 0 || rect.Height == 0)
{
bmp = new Bitmap(1, 1);
}
else
{
bmp = new Bitmap(rect.Width, rect.Height);
}
using (Graphics g = Graphics.FromImage(bmp))
{
int fontSize = GetMaximumFontSizeFitInRectangle(text, font, rectanglef, false);
SetGraphicsHighQualityForTextRendering(g);
g.DrawString(text, new Font(font.FontFamily, fontSize,font.Style, GraphicsUnit.Point), new SolidBrush(color), rectanglef, drawFormat);
return bmp;
}
}
case DrawMethod.AutoFitInConstantRectangleWithWarp:
{
Rectangle rect = Rectangle.Ceiling(rectanglef);
if (rect.Width == 0 || rect.Height == 0)
{
bmp = new Bitmap(1, 1);
}
else
{
bmp = new Bitmap(rect.Width, rect.Height);
}
using (Graphics g = Graphics.FromImage(bmp))
{
int fontSize = GetMaximumFontSizeFitInRectangle(text, font, rectanglef, true);
SetGraphicsHighQualityForTextRendering(g);
g.DrawString(text, new Font(font.FontFamily, fontSize, font.Style, GraphicsUnit.Point), new SolidBrush(color), rectanglef, drawFormat);
return bmp;
}
}
}
return null;
}
}
}
Usage Example:
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
// add WindowsBase and PresentationCore assemblies
namespace ReportPrepare
{
public partial class Form1 : Form
{
PictureBox picbox = new PictureBox();
int i = 0;
Timer t = new Timer();
public Form1()
{
InitializeComponent();
this.Controls.Add(picbox);
picbox.Dock = DockStyle.Fill;
t.Interval = 5000;
t.Tick += t_Tick;
t.Enabled = true;
this.Shown += Form1_Shown;
this.SizeChanged += Form1_SizeChanged;
this.Size = new Size(812, 400);
this.StartPosition = FormStartPosition.CenterScreen;
}
void Form1_Shown(object sender, EventArgs e)
{
DrawText();
}
void t_Tick(object sender, EventArgs e)
{
i++;
if (i > 3)
{
i = 0;
}
DrawText();
}
private void DrawText()
{
// text and font
string text = "one two three four five six seven eight nine ten eleven twelve";
Font font = new System.Drawing.Font("Arial", 30, FontStyle.Regular, GraphicsUnit.Point);
switch (i)
{
case 0:
picbox.Image = TextDrawing.DrawTextToBitmap(text, font, Color.Red, TextDrawing.DrawMethod.AutosizeAccordingToText, new RectangleF(0, 0, picbox.Width, picbox.Height));
break;
case 1:
picbox.Image = TextDrawing.DrawTextToBitmap(text, font, Color.Red, TextDrawing.DrawMethod.AutoFitInConstantRectangleWithoutWarp, new RectangleF(0, 0, picbox.Width, picbox.Height));
break;
case 2:
picbox.Image = TextDrawing.DrawTextToBitmap(text, font, Color.Red, TextDrawing.DrawMethod.AutoWarpInConstantRectangle, new RectangleF(0, 0, picbox.Width, picbox.Height));
break;
case 3:
picbox.Image = TextDrawing.DrawTextToBitmap(text, font, Color.Red, TextDrawing.DrawMethod.AutoFitInConstantRectangleWithWarp, new RectangleF(0, 0, picbox.Width, picbox.Height));
break;
}
this.Text = ((TextDrawing.DrawMethod)(i)).ToString() + " Please resize window size by mouse to see drawing methods differences";
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
t.Enabled = false;
t.Enabled = true;
DrawText();
}
}
}
The example toggles between drawing modes automatically once every 5 seconds. The picturebox is docked inside main form. Resizing of the form shows the user the difference between the drawing modes.

Can't resize the changed image? C# WinForms

If i change the colour of the image and then try to resize, it only resizes the original image. Why is this happening and how do i fix it?
Here is my code:
private PrintDocument printDoc = new PrintDocument();
private PageSettings pgSettings = new PageSettings();
private PrinterSettings prtSettings = new PrinterSettings();
Bitmap myBitmapImage; // image (bitmap) for some background mountains
Boolean isInvert = false;
Boolean isLOaded = false;
Boolean isGrayscale = false;
Boolean isThreshold = false;
Boolean isResize = false;
OpenFileDialog ofd;
Bitmap bmBack;
public EditImage()
{
printDoc.PrintPage += new PrintPageEventHandler(printDoc_PrintPage);
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
}
private void EditImage_Load(object sender, EventArgs e)
{
}
private void EditImage_Paint(object sender, PaintEventArgs e)
{
if (isLOaded == true)
{
Graphics gWindow; // reference to the graphic surface of this window
Graphics gBack; // reference to in-memory surface
bmBack = new Bitmap(Width, Height); // bitmap for window surface copy
gWindow = e.Graphics; // get our current window's surface
gBack = Graphics.FromImage(bmBack); // create surfaces from the bitmaps
gBack.DrawImage(myBitmapImage, 0, 0, Width, Height);
if (isInvert == true)
{
InvertBitmap(bmBack);
}
else if (isGrayscale == true)
{
GrayscaleBitmap(bmBack);
}
else if (isThreshold == true)
{
ThresholdBitmap(bmBack);
}
else if (isResize == true)
{
bmBack = resizeImage(bmBack, 10, 100);
}
gWindow.DrawImage(bmBack, 0, 0);
}
}
private void toolStripMenuItemLoadImage_Click(object sender, EventArgs e)
{
using (ofd = new OpenFileDialog())
{
ofd.Title = "Load Image";
if (ofd.ShowDialog() == DialogResult.OK)
{
myBitmapImage = new Bitmap(ofd.FileName);
this.Invalidate();
}
}
isLOaded = true;
}
private void GrayscaleBitmap(Bitmap bmp)
{
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format32bppRgb);
IntPtr ptr = bmpData.Scan0;
int numPixels = bmpData.Width * bmp.Height;
int numBytes = numPixels * 4;
byte[] rgbValues = new byte[numBytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, numBytes);
for (int i = 0; i < rgbValues.Length; i += 4)
{
byte gray = (byte)(.3 * rgbValues[i + 0]); //blue
gray += (byte)(.59 * rgbValues[i + 1]); //green
gray += (byte)(.11 * rgbValues[i + 2]); //red
rgbValues[i + 0] = gray; //blue
rgbValues[i + 1] = gray; //green
rgbValues[i + 2] = gray; //red
}
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, numBytes);
bmp.UnlockBits(bmpData);
}
private Bitmap resizeImage(Bitmap sourceBMP, int width, int height)
{
Bitmap result = new Bitmap(width, height);
using(Graphics g = Graphics.FromImage(result))
g.DrawImage(sourceBMP, 0, 0, width, height);
return result;
}
i also have methods which deal with when the user clicks on a button and sets the bools to appropriate values so that it calls the correct method. The images DO change colour - as intended.. but when i click on resize, i want it to resize the version of the image that has changed colour - not the original image...
There are lots of things wrong here:
resizeImage()
You should dispose of the Graphics object that you create here (wrap it in a using() statement)
Don't call invalidate from here, this function just resizes your image, you invalidate after you've changed your image and your Paint method paints the image which has now changed.
You should also think about disposing of the sourceBMP right before you return the function if you no longer have any use for it.
GrayscaleBitmap()
This looks right, but again there's no reason to invalidate here. You should invalidate after you call this method in the calling function. It makes more sense.
EditImage_Paint
You should not be calling these functions from within your Paint event. And you should not be creating a new Bitmap and a new Graphics class on each Paint. This is way more work than necessary. These functions should only execute when the data needs to be changed based on user input (user clicks a button to apply a grayscale effect).
For what you want to do, you should only need 2 bitmap variables at most. One to store the original unmodified bitmap in case you want to let the user "Reset" it or to undo any effects (most effects cause permanent data loss, you can't make a grayscale image color again). And the other to store the bitmap that gets painted. Each time the user applies an effect, it modifies the 2nd bitmap, and then calls invalidate.
All your Paint function should do is paint the 2nd bitmap:
Bitmap originalBitmap; // load from file, do not modify
Bitmap currentBitmap; // when user clicks an effect, modify this bitmap and then Invalidate afterward
private void EditImage_Paint(object sender, PaintEventArgs e)
{
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Draw the currentBitmap centered on the window
SizeF clientSize = this.ClientSize;
double zoomRatio = Math.Min(
clientSize.Width / currentBitmap.Width,
clientSize.Height / currentBitmap.Height
);
SizeF zoomedSize = new SizeF(
(float)(currentBitmap.Width * zoomRatio),
(float)(currentBitmap.Height * zoomRatio)
);
PointF imageOffset = new PointF(
(clientSize.Width - zoomedSize.Width) / 2,
(clientSize.Height - zoomedSize.Height) / 2
);
e.Graphics.Clear(Color.White);
e.Graphics.DrawImage(currentBitmap, imageOffset.X, imageOffset.Y, zoomedSize.Width, zoomedSize.Height);
}
This emulates a simple zoom effect that centers the image on the control and draws it to fit the window.

Categories

Resources