I am working with GDI+, the image I am working with is a 1bbp image. What i would like to do is draw a rectangle on the image and everything under that rectangle will be inverted (white pixels will become black and black pixels become white).
All of the sample code I have seen is for 8 bit RGB color scale images, and I don't think the techniques they use will work for me.
Here is the code I have so far. This is the parent control, one of the Epl2.IDrawableCommand's will be the command that does the inverting.
public class DisplayBox : UserControl
{
(...)
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
(...)
using (Bitmap drawnLabel = new Bitmap((int)((float)Label.LabelHeight * _ImageScaleFactor), (int)((float)Label.LableLength *(int) _ImageScaleFactor), System.Drawing.Imaging.PixelFormat.Format1bppIndexed))
{
using (Graphics drawBuffer = Graphics.FromImage(drawnLabel))
{
(...)
foreach (Epl2.IDrawableCommand cmd in Label.Collection)
{
cmd.Paint(drawBuffer);
}
(...)
}
}
}
}
}
public class InvertArea : IDrawableCommand
{
(...)
public Rectangle InvertRectangle {get; set;}
public void Paint(Graphics g)
{
throw new NotImplementedExecption();
}
}
What should I put in the Paint(Graphic g) for this command?
The trick is to draw the same image again and use a ColorMatrix that inverts the image. For example:
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.DrawImage(mImage, Point.Empty);
ImageAttributes ia = new ImageAttributes();
ColorMatrix cm = new ColorMatrix();
cm.Matrix00 = cm.Matrix11 = cm.Matrix22 = -0.99f;
cm.Matrix40 = cm.Matrix41 = cm.Matrix42 = 0.99f;
ia.SetColorMatrix(cm);
var dest = new Rectangle(50, 50, 100, 100);
e.Graphics.DrawImage(mImage, dest, dest.Left, dest.Top,
dest.Width, dest.Height, GraphicsUnit.Pixel, ia);
}
Where mImage was my sample 1bpp image and I'm inverting a 100x100 rectangle at (50, 50).
Related
Is there any replacement (analogue) for CSS3 function repeating-linear-gradient() in .NET (WinForms, not WPF)?
I need to paint repeating "zebra stripes" (e.g. red, blue, green, red, blue, green, ...) at an angle 45 degrees.
UPD:
Following Jimi's advice I managed to solve the problem only partially:
private void DrawRepeatingStripes(int degree, int stripeWidth, Color[] colors, Rectangle rect, Graphics graphics)
{
using (var img = new Bitmap(colors.Length * stripeWidth, rect.Height))
{
using (var g = Graphics.FromImage(img))
{
for (int i = 0; i < colors.Length; i++)
{
// TODO: cache SolidBrush
g.FillRectangle(new SolidBrush(colors[i]), stripeWidth * i, 0, stripeWidth, rect.Height);
}
}
using (var tb = new TextureBrush(img, WrapMode.Tile))
{
using (var myMatrix = new Matrix())
{
myMatrix.Rotate(degree);
graphics.Transform = myMatrix;
graphics.FillRectangle(tb, rect);
graphics.ResetTransform();
}
}
}
}
Usage (in some form's code):
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
DrawRepeatingStripes(45, 10, new Color[] { Color.Red, Color.Yellow, Color.Green }, e.ClipRectangle, e.Graphics);
}
The problem is that rotation is... well, a rotation, so part of rect is filled with stripes and part is empty. Have no idea how to solve it :(
An example about using a TextureBrush to fill the surface of a Control used as canvas.
The LinearRepeatingGradient class exposes a bindable ColorBands Property (of Type BindingList<ColorBand>) that allows to add or remove ColorBand objects, a record that defines the Color and size of each band you want to generate.
The RotationAngle Property specifies the rotation to apply to the rendering.
In the Paint event of the Control used as canvas, call the Fill(Graphics g) method, passing the e.Graphics object provided by the PaintEventArgs argument.
A new Bitmap is generated, based on the content of the ColorBands Property.
When the rotation angle cannot be exactly divided by 90, the canvas' dimensions are inflated by a third of its diagonal (as the maximum distance from the non-rotated rectangle).
The TextureBrush fills this inflated surface, so no blank space is left on the sides of the canvas.
Since this test sample is built with .NET 7, I'm using record to store the color bands' settings. You can replace it with a class object without changes to the rest of the code.
public record ColorBand(Color Color, int Size) {
public override string ToString() => $"Color: {Color.Name} Size: {Size}";
}
Same as above: using declaration instead of using statements
using System.Drawing;
using System.Drawing.Drawing2D;
public class LinearRepeatingGradient
{
public LinearRepeatingGradient(float rotation = .0f)
{
ColorBands = new BindingList<ColorBand>();
RotationAngle = rotation;
}
public float RotationAngle { get; set; }
[Bindable(true), ListBindable(BindableSupport.Default)]
public BindingList<ColorBand> ColorBands { get; }
public void Fill(Graphics g) => Fill(g, g.ClipBounds);
public void Fill(Graphics g, Rectangle fillArea) => Fill(g, new RectangleF(fillArea.Location, fillArea.Size));
protected virtual void Fill(Graphics g, RectangleF display)
{
if (ColorBands is null || ColorBands.Count == 0 || g.Clip.IsInfinite(g)) return;
var canvas = InflateCanvas(display);
var centerPoint = new PointF(canvas.X + canvas.Width / 2, canvas.Y + canvas.Height / 2);
using var texture = GetTexture(canvas.Width);
if (texture is null) return;
using var brush = new TextureBrush(texture, WrapMode.Tile);
using var mx = new Matrix();
mx.RotateAt(RotationAngle, centerPoint);
g.Transform = mx;
g.FillRectangle(brush, canvas);
g.ResetTransform();
}
private RectangleF InflateCanvas(RectangleF rect)
{
if (RotationAngle % 90.0f == 0) return rect;
float maxInflate = (float)Math.Sqrt(Math.Pow(rect.X - rect.Right, 2) +
Math.Pow(rect.Y - rect.Bottom, 2)) / 3.0f;
var canvas = rect;
canvas.Inflate(maxInflate, maxInflate);
return canvas;
}
private Bitmap? GetTexture(float width)
{
int height = ColorBands!.Sum(c => c.Size);
if (height <= 0) return null;
var texture = new Bitmap((int)(width + .5f), height);
int startPosition = 0;
using var g = Graphics.FromImage(texture);
for (int i = 0; i < ColorBands!.Count; i++) {
var rect = new Rectangle(0, startPosition, texture.Width, ColorBands![i].Size);
using var brush = new SolidBrush(ColorBands![i].Color);
g.FillRectangle(brush, rect);
startPosition += ColorBands![i].Size;
}
return texture;
}
}
This is how it works:
Since the ColorBands property is bindable, you can use data bindings to perform actions, when a ColorBand object is added or removed and also bind the ColorBands collection to Controls, as shown in the animation:
public partial class SomeForm : Form {
LinearRepeatingGradient gradient = new();
public SomeForm()
{
InitializeComponent();
[DataGridView].DataSource = gradient.ColorBands;
gradient.ColorBands.ListChanged += (s, e) => someControl.Invalidate();
}
private void someControl_Paint(object sender, PaintEventArgs e) => gradient.Fill(e.Graphics);
As a consequence, when you add a new ColorBand (or remove it), the internal collection changes and the Control used as canvas is invalidated, showing the new fill:
gradient.ColorBands.Add(new ColorBand(Color.Red, 45f));
The RotationAngle property doesn't use data bindings, so you have to invalidate the canvas manually when you change it. You can of course change that and make this property bindable:
gradient.RotationAngle = 215f;
someControl.Invalidate();
i want to have outer glow text in a label for my winform application some thing like:
i searched for it in stackoverflow and I found this:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
//Create a bitmap in a fixed ratio to the original drawing area.
Bitmap bm=new Bitmap(this.ClientSize.Width/5, this.ClientSize.Height/5);
//Create a GraphicsPath object.
GraphicsPath pth=new GraphicsPath();
//Add the string in the chosen style.
pth.AddString("Text Halo",new FontFamily("Verdana"),(int)FontStyle.Regular,100,new Point(20,20),StringFormat.GenericTypographic);
//Get the graphics object for the image.
Graphics g=Graphics.FromImage(bm);
//Create a matrix that shrinks the drawing output by the fixed ratio.
Matrix mx=new Matrix(1.0f/5,0,0,1.0f/5,-(1.0f/5),-(1.0f/5));
//Choose an appropriate smoothing mode for the halo.
g.SmoothingMode=SmoothingMode.AntiAlias;
//Transform the graphics object so that the same half may be used for both halo and text output.
g.Transform=mx;
//Using a suitable pen...
Pen p=new Pen(Color.Yellow,3);
//Draw around the outline of the path
g.DrawPath(p,pth);
//and then fill in for good measure.
g.FillPath(Brushes.Yellow,pth);
//We no longer need this graphics object
g.Dispose();
//this just shifts the effect a little bit so that the edge isn't cut off in the demonstration
e.Graphics.Transform=new Matrix(1,0,0,1,50,50);
//setup the smoothing mode for path drawing
e.Graphics.SmoothingMode=SmoothingMode.AntiAlias;
//and the interpolation mode for the expansion of the halo bitmap
e.Graphics.InterpolationMode=InterpolationMode.HighQualityBicubic;
//expand the halo making the edges nice and fuzzy.
e.Graphics.DrawImage(bm,ClientRectangle,0,0,bm.Width,bm.Height,GraphicsUnit.Pixel);
//Redraw the original text
e.Graphics.FillPath(Brushes.Black,pth);
//and you're done.
pth.Dispose();
}
but the PROBLEM IS I CAN NOT MOVE IT please help me i need it to be movable and I want to be able to change it's size. the code above, just adds it automatically to somewhere in my form but I want to move that.
thank you
A better approach is to create a custom control for this to use/add some relevant drawing properties. Mainly, the Font and color of the text, the size and color of the outline. Then, you can lay out the custom control in any container at any location and with any size.
Here's a simple example.
[DesignerCategory("Code")]
public class GlowTextLabel : Control
{
private Color outlineColor = SystemColors.Highlight;
private int outlineSize = 1;
public GlowTextLabel() : base()
{
SetStyle(ControlStyles.Selectable, false);
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.SupportsTransparentBackColor, true);
}
[DefaultValue(typeof(Color), "Highlight")]
public Color OutlineColor
{
get => outlineColor;
set
{
if (outlineColor != value)
{
outlineColor = value;
Invalidate();
}
}
}
[DefaultValue(1)]
public int OutlineSize
{
get => outlineSize;
set
{
if (outlineSize != value)
{
outlineSize = Math.Max(1, value);
Invalidate();
}
}
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(BackColor);
var w = Math.Max(8, ClientSize.Width / 5);
var h = Math.Max(8, ClientSize.Height / 5);
using (var bmp = new Bitmap(w, h))
using (var gp = new GraphicsPath())
using (var sf = new StringFormat(StringFormat.GenericTypographic))
{
sf.Alignment = sf.LineAlignment = StringAlignment.Center;
gp.AddString(Text,
Font.FontFamily, (int)Font.Style, GetEmFontSize(Font),
ClientRectangle, sf);
using (var g = Graphics.FromImage(bmp))
using (var m = new Matrix(1.0f / 5, 0, 0, 1.0f / 5, -(1.0f / 5), -(1.0f / 5)))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Transform = m;
using (var pn = new Pen(OutlineColor, OutlineSize))
{
g.DrawPath(pn, gp);
g.FillPath(pn.Brush, gp);
}
}
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Optional for wider blur...
// e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.DrawImage(bmp,
ClientRectangle, 0, 0, bmp.Width, bmp.Height,
GraphicsUnit.Pixel);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var br = new SolidBrush(ForeColor))
e.Graphics.FillPath(br, gp);
}
}
}
private float GetEmFontSize(Font fnt) =>
fnt.SizeInPoints * (fnt.FontFamily.GetCellAscent(fnt.Style) +
fnt.FontFamily.GetCellDescent(fnt.Style)) / fnt.FontFamily.GetEmHeight(fnt.Style);
Rebuild, find the GlowTextLabel control on the ToolBox under your project's components group, drop an instance, try the Font, ForeColor, OutlineColor, and OutlineSize properties with different values.
Pen width 1.
Pen width 10.
Pen width 20.
I am making a custom view using Xamarin.Android, I want draw a circle then a bitmap over it in the canvas. The circle is being drawn properly but the bitmap isn't
The bitmap corresponds to a png image resource.
Here is my OnDraw
protected async override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint buttonBackgroundPaint = new Paint(PaintFlags.AntiAlias);
Color? c = null;
bool colorOk = Resources.TryGetColor(_backgroundColor, out c);
buttonBackgroundPaint.Color = (Color)(colorOk ? c : _context?.Resources.GetColor(AndroidRes.Resource.Color.Black));
canvas.DrawCircle(Width / 2, Height / 2, _radius, buttonBackgroundPaint);
Paint bitmapPaint = new Paint(PaintFlags.AntiAlias);
Bitmap bitmapContent = await BitmapFactory.DecodeResourceAsync(_context?.Resources, _drawableContent);
canvas.DrawBitmap(bitmapContent, 0, 0, bitmapPaint);
}
EDIT : Resources.TryGetColor is an extension method, I've created.
I've noticed that a part of the bitmap is showing when rotating screen.
the problem was with the bitmap bounds, so I solved it as follows:
protected async override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint buttonBackgroundPaint = new Paint(PaintFlags.AntiAlias);
Color? c = null;
bool colorOk = Resources.TryGetColor(_backgroundColor, out c);
buttonBackgroundPaint.Color = (Color)(colorOk ? c : _context?.Resources.GetColor(AndroidRes.Resource.Color.Black));
canvas.DrawCircle(Width / 2, Height / 2, _radius, buttonBackgroundPaint);
Drawable dr = _context.Resources.GetDrawable(Resource.Drawable.image);
dr.SetBounds(0, 0, right, bottom); // appropiate values for my case
dr.Draw(canvas);
}
Building a little paint program and am trying to incorporate the concept of layers.
I'm using a PictureBox control to display the image, and getting the Graphics object from the image being displayed by the PictureBox and drawing to that.
My problem is I'm trying to figure out how to draw to a new Graphics object that is overlayed on top of the picture box, and be able to get the newly drawn image without the original image absorbed into the graphic.
If I do something like:
Graphics gr = Graphics.FromImage(myPictureBox.image);
gr.DrawRectangle(blah blah)
...I am editing the original image in the picture box. I want a way to only capture the new stuff being drawn as a separate image, but still have it displayed as an overlay over top of what was already there.
Anyone able to point me in the right direction? Thanks!
I would reckon to use the transparent control and do some modification so it can be used as image layers:
http://www.codeproject.com/Articles/26878/Making-Transparent-Controls-No-Flickering
Probably something like this (make any modification as necessary).
class LayerControl : UserControl
{
private Image image;
private Graphics graphics;
public LayerControl(int width, int height)
{
this.Width = width;
this.Height = height;
image = new Bitmap(width, height);
graphics = Graphics.FromImage(image);
// Set style for control
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
}
// this function will draw your image
protected override void OnPaint(PaintEventArgs e)
{
var bitMap = new Bitmap(image);
// by default the background color for bitmap is white
// you can modify this to follow your image background
// or create a new Property so it can dynamically assigned
bitMap.MakeTransparent(Color.White);
image = bitMap;
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.GammaCorrected;
float[][] mtxItens = {
new float[] {1,0,0,0,0},
new float[] {0,1,0,0,0},
new float[] {0,0,1,0,0},
new float[] {0,0,0,1,0},
new float[] {0,0,0,0,1}};
ColorMatrix colorMatrix = new ColorMatrix(mtxItens);
ImageAttributes imgAtb = new ImageAttributes();
imgAtb.SetColorMatrix(
colorMatrix,
ColorMatrixFlag.Default,
ColorAdjustType.Bitmap);
g.DrawImage(image,
ClientRectangle,
0.0f,
0.0f,
image.Width,
image.Height,
GraphicsUnit.Pixel,
imgAtb);
}
// this function will grab the background image to the control it self
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
Graphics g = e.Graphics;
if (Parent != null)
{
BackColor = Color.Transparent;
int index = Parent.Controls.GetChildIndex(this);
for (int i = Parent.Controls.Count - 1; i > index; i--)
{
Control c = Parent.Controls[i];
if (c.Bounds.IntersectsWith(Bounds) && c.Visible)
{
Bitmap bmp = new Bitmap(c.Width, c.Height, g);
c.DrawToBitmap(bmp, c.ClientRectangle);
g.TranslateTransform(c.Left - Left, c.Top - Top);
g.DrawImageUnscaled(bmp, Point.Empty);
g.TranslateTransform(Left - c.Left, Top - c.Top);
bmp.Dispose();
}
}
}
else
{
g.Clear(Parent.BackColor);
g.FillRectangle(new SolidBrush(Color.FromArgb(255, Color.Transparent)), this.ClientRectangle);
}
}
// simple drawing circle function
public void DrawCircles()
{
using (Brush b = new SolidBrush(Color.Red))
{
using (Pen p = new Pen(Color.Green, 3))
{
this.graphics.DrawEllipse(p, 25, 25, 20, 20);
}
}
}
// simple drawing rectable function
public void DrawRectangle()
{
using (Brush b = new SolidBrush(Color.Red))
{
using (Pen p = new Pen(Color.Red, 3))
{
this.graphics.DrawRectangle(p, 50, 50, 40, 40);
}
}
}
// Layer control image property
public Image Image
{
get
{
return image;
}
set
{
image = value;
// this will make the control to be redrawn
this.Invalidate();
}
}
}
Example how to use it:
LayerControl lc = new LayerControl(100, 100);
lc.Location = new Point(0, 0);
lc.DrawRectangle();
LayerControl lc2 = new LayerControl(100, 100);
lc2.Location = new Point(0, 0);
lc2.DrawCircles();
LayerControl lc3 = new LayerControl(100, 100);
lc3.Location = new Point(0, 0);
lc3.Image = new Bitmap(#"<Image Path>");
// adding control
this.Controls.Add(dc);
this.Controls.Add(dc2);
this.Controls.Add(dc3);
With this method you can have multiple layers that can put overlapping each other (due to the transparency feature it has).
If you want to add it in top of your PictureBox make sure to re-order the control. The Layer Control should be added before your PictureBox control.
// adding control
this.Controls.Clear();
this.Controls.Add(dc);
this.Controls.Add(dc2);
this.Controls.Add(dc3);
this.Controls.Add(PictureBox1);
Hopefully it help.
example code which working fine - take dummy image and layered the original image with custom text
public void LayerImage(System.Drawing.Image Current, int LayerOpacity)
{
Bitmap bitmap = new Bitmap(Current);
int h = bitmap.Height;
int w = bitmap.Width;
Bitmap backg = new Bitmap(w, h + 20);
Graphics g = null;
try
{
g = Graphics.FromImage(backg);
g.Clear(Color.White);
Font font = new Font("Arial", 12, FontStyle.Bold, GraphicsUnit.Pixel);
RectangleF rectf = new RectangleF(70, 90, 90, 50);
Color color = Color.FromArgb(255, 128, 128, 128);
Point atpoint = new Point(backg.Width / 2, backg.Height - 10);
SolidBrush brush = new SolidBrush(color);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
g.DrawString("BRAND AMBASSADOR", font, brush, atpoint, sf);
g.Dispose();
MemoryStream m = new MemoryStream();
backg.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg);
}
catch { }
Color pixel = new Color();
for (int x = 0; x < bitmap.Width; x++)
{
for (int y = 0; y < bitmap.Height; y++)
{
pixel = bitmap.GetPixel(x, y);
backg.SetPixel(x, y, Color.FromArgb(LayerOpacity, pixel));
}
}
MemoryStream m1 = new MemoryStream();
backg.Save(m1, System.Drawing.Imaging.ImageFormat.Jpeg);
m1.WriteTo(Response.OutputStream);
m1.Dispose();
base.Dispose();
}
Got it working, perhaps I wasn't clear enough in my original question.
Essentially what I ended up doing was storing each layer as a separate Image object, then just hooking into the OnPaint method of my control and manually drawing the graphics in order, instead of just drawing to PictureBox.Image. Works like a charm!
The graphics capabilities of .NET drawing libraries are simple. Their main purpose is direct drawing of GUI. If you want to have layering, alpha transparency or advanced filters, then you should either use 3rd party library or roll your own drawing code.
I'm just starting to learn the GDI+ system for drawing lines, circles etc. I've created a component (scanner) that inherits a Panel to draw on (not sure if panel or picture box is best).
On the "Scanner" Im currently drawing a circle on it. the component can be added to a winform and using docking will resize when the winform resizes. At the moment I'm getting the size of the component to calculate the size of the circle but what I want to do is basically say no matter what size the component is the "canvas" is always 300 x 300 wide, so I can say the circle should be positioned at 25,25 with a size of 250x250.
As you might guess from the name "Scanner" I want to plot points on it, but these will be calculated from the center (150,150) location.
Below is the code I have that basically draws the circle.
Many thanks for any help on this.
public partial class Scanner : Panel
{
public Scanner() {
InitializeComponent();
this.DoubleBuffered = true;
}
protected override void OnPaint(PaintEventArgs e) {
Graphics g = e.Graphics;
Draw(g);
base.OnPaint(e);
}
protected override void OnResize(EventArgs e) {
Graphics g = this.CreateGraphics();
Draw(g);
base.OnResize(e);
}
private void Draw(Graphics g) {
g.Clear(Color.Black);
g.PageUnit = GraphicsUnit.Pixel;
Pen green = new Pen(Color.Green);
Font fnt = new Font("Arial", 10);
SolidBrush sb = new SolidBrush(Color.Red);
int pos = (this.Width < this.Height ? this.Width : this.Height) / 2;
int size = (int)(pos * 1.9);
pos -= ((int)size / 2);
g.DrawEllipse(green, pos, pos, size, size);
g.DrawString(this.Width.ToString(), fnt, sb, new Point(0, 0));
}
}
Based on your recent comment, I understand you want to do your drawing on a fixed-size canvas, and plot this canvas inside the control, as large as will fit in the control.
Try the code below:
public class Scanner : Panel
{
private Image _scanner;
public Scanner()
{
this.SetStyle(ControlStyles.ResizeRedraw, true);
CreateScanner();
}
private void CreateScanner()
{
Bitmap scanner = new Bitmap(300, 300);
Graphics g = Graphics.FromImage(scanner);
g.DrawEllipse(Pens.Green, 25, 25, 250, 250);
g.Dispose();
_scanner = scanner;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
int shortestSide = Math.Min(this.Width, this.Height);
if (null != _scanner)
e.Graphics.DrawImage(_scanner, 0, 0, shortestSide, shortestSide);
}
}