I am asking this question as the other one is two years old and not answered accurately.
I'm looking to replicate the PhotoShop effect mentioned in this article in C#. Adobe call it a Color halftone, I think it looks like some sort of rotated CMYK halftone thingy. Either way I don't know how I would do it.
Current code sample is below.
Any ideas?
P.S.
This isn't homework. I'm looking to upgrade the comic book effect I have in my OSS project ImageProcessor.
Progress Update.
So here's some code to show what I have done so far...
I can convert to and from CMYK to RGB fairly easily and accurately enough for my needs and also print out a patterned series of ellipses based on the the intensity of each colour component at a series of points.
What I am stuck at just now is rotating the graphics object for each colour so that the points are laid at the angles specified in the code. Can anyone give me some pointers as how to go about that?
public Image ProcessImage(ImageFactory factory)
{
Bitmap newImage = null;
Image image = factory.Image;
try
{
int width = image.Width;
int height = image.Height;
// These need to be used.
float cyanAngle = 105f;
float magentaAngle = 75f;
float yellowAngle = 90f;
float keylineAngle = 15f;
newImage = new Bitmap(width, height);
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(newImage))
{
// Reduce the jagged edges.
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.Clear(Color.White);
using (FastBitmap sourceBitmap = new FastBitmap(image))
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0; x < width; x += 4)
{
Color color = sourceBitmap.GetPixel(x, y);
if (color != Color.White)
{
CmykColor cmykColor = color;
float cyanBrushRadius = (cmykColor.C / 100) * 3;
graphics.FillEllipse(Brushes.Cyan, x, y, cyanBrushRadius, cyanBrushRadius);
float magentaBrushRadius = (cmykColor.M / 100) * 3;
graphics.FillEllipse(Brushes.Magenta, x, y, magentaBrushRadius, magentaBrushRadius);
float yellowBrushRadius = (cmykColor.Y / 100) * 3;
graphics.FillEllipse(Brushes.Yellow, x, y, yellowBrushRadius, yellowBrushRadius);
float blackBrushRadius = (cmykColor.K / 100) * 3;
graphics.FillEllipse(Brushes.Black, x, y, blackBrushRadius, blackBrushRadius);
}
}
}
}
}
image.Dispose();
image = newImage;
}
catch (Exception ex)
{
if (newImage != null)
{
newImage.Dispose();
}
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
}
return image;
}
Input Image
Current Output
As you can see since the drawn ellipses are not angled colour output is incorrect.
So here's a working solution. It ain't pretty, it ain't fast (2 seconds on my laptop) but the output is good. It doesn't exactly match Photoshop's output though I think they are performing some additional work.
Slight moiré patterns sometimes appear on different test images but descreening is out of scope for the current question.
The code performs the following steps.
Loop through the pixels of the image at a given interval
For each colour component, CMYK draw an ellipse at a given point which is calculated by rotating the current point by the set angle. The dimensions of this ellipse are determined by the level of each colour component at each point.
Create a new image by looping though the pixel points and adding the CMYK colour component values at each point to determine the correct colour to draw to the image.
Output image
The code
public Image ProcessImage(ImageFactory factory)
{
Bitmap cyan = null;
Bitmap magenta = null;
Bitmap yellow = null;
Bitmap keyline = null;
Bitmap newImage = null;
Image image = factory.Image;
try
{
int width = image.Width;
int height = image.Height;
// Angles taken from Wikipedia page.
float cyanAngle = 15f;
float magentaAngle = 75f;
float yellowAngle = 0f;
float keylineAngle = 45f;
int diameter = 4;
float multiplier = 4 * (float)Math.Sqrt(2);
// Cyan color sampled from Wikipedia page.
Brush cyanBrush = new SolidBrush(Color.FromArgb(0, 153, 239));
Brush magentaBrush = Brushes.Magenta;
Brush yellowBrush = Brushes.Yellow;
Brush keylineBrush;
// Create our images.
cyan = new Bitmap(width, height);
magenta = new Bitmap(width, height);
yellow = new Bitmap(width, height);
keyline = new Bitmap(width, height);
newImage = new Bitmap(width, height);
// Ensure the correct resolution is set.
cyan.SetResolution(image.HorizontalResolution, image.VerticalResolution);
magenta.SetResolution(image.HorizontalResolution, image.VerticalResolution);
yellow.SetResolution(image.HorizontalResolution, image.VerticalResolution);
keyline.SetResolution(image.HorizontalResolution, image.VerticalResolution);
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
// Check bounds against this.
Rectangle rectangle = new Rectangle(0, 0, width, height);
using (Graphics graphicsCyan = Graphics.FromImage(cyan))
using (Graphics graphicsMagenta = Graphics.FromImage(magenta))
using (Graphics graphicsYellow = Graphics.FromImage(yellow))
using (Graphics graphicsKeyline = Graphics.FromImage(keyline))
{
// Ensure cleared out.
graphicsCyan.Clear(Color.Transparent);
graphicsMagenta.Clear(Color.Transparent);
graphicsYellow.Clear(Color.Transparent);
graphicsKeyline.Clear(Color.Transparent);
// This is too slow. The graphics object can't be called within a parallel
// loop so we have to do it old school. :(
using (FastBitmap sourceBitmap = new FastBitmap(image))
{
for (int y = -height * 2; y < height * 2; y += diameter)
{
for (int x = -width * 2; x < width * 2; x += diameter)
{
Color color;
CmykColor cmykColor;
float brushWidth;
// Cyan
Point rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), cyanAngle);
int angledX = rotatedPoint.X;
int angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.C / 255f) * multiplier;
graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth);
}
// Magenta
rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), magentaAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.M / 255f) * multiplier;
graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth);
}
// Yellow
rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), yellowAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.Y / 255f) * multiplier;
graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth);
}
// Keyline
rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), keylineAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.K / 255f) * multiplier;
// Just using blck is too dark.
keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K));
graphicsKeyline.FillEllipse(keylineBrush, angledX, angledY, brushWidth, brushWidth);
}
}
}
}
// Set our white background.
using (Graphics graphics = Graphics.FromImage(newImage))
{
graphics.Clear(Color.White);
}
// Blend the colors now to mimic adaptive blending.
using (FastBitmap cyanBitmap = new FastBitmap(cyan))
using (FastBitmap magentaBitmap = new FastBitmap(magenta))
using (FastBitmap yellowBitmap = new FastBitmap(yellow))
using (FastBitmap keylineBitmap = new FastBitmap(keyline))
using (FastBitmap destinationBitmap = new FastBitmap(newImage))
{
Parallel.For(
0,
height,
y =>
{
for (int x = 0; x < width; x++)
{
// ReSharper disable AccessToDisposedClosure
Color cyanPixel = cyanBitmap.GetPixel(x, y);
Color magentaPixel = magentaBitmap.GetPixel(x, y);
Color yellowPixel = yellowBitmap.GetPixel(x, y);
Color keylinePixel = keylineBitmap.GetPixel(x, y);
CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel);
destinationBitmap.SetPixel(x, y, blended);
// ReSharper restore AccessToDisposedClosure
}
});
}
}
cyan.Dispose();
magenta.Dispose();
yellow.Dispose();
keyline.Dispose();
image.Dispose();
image = newImage;
}
catch (Exception ex)
{
if (cyan != null)
{
cyan.Dispose();
}
if (magenta != null)
{
magenta.Dispose();
}
if (yellow != null)
{
yellow.Dispose();
}
if (keyline != null)
{
keyline.Dispose();
}
if (newImage != null)
{
newImage.Dispose();
}
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
}
return image;
}
Additional code for rotating the pixels is as follows. This can be found at Rotating a point around another point
I've left out the colour addition code for brevity.
/// <summary>
/// Rotates one point around another
/// <see href="https://stackoverflow.com/questions/13695317/rotate-a-point-around-another-point"/>
/// </summary>
/// <param name="pointToRotate">The point to rotate.</param>
/// <param name="centerPoint">The centre point of rotation.</param>
/// <param name="angleInDegrees">The rotation angle in degrees.</param>
/// <returns>Rotated point</returns>
private static Point RotatePoint(Point pointToRotate, Point centerPoint, double angleInDegrees)
{
double angleInRadians = angleInDegrees * (Math.PI / 180);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)
((cosTheta * (pointToRotate.X - centerPoint.X)) -
((sinTheta * (pointToRotate.Y - centerPoint.Y)) + centerPoint.X)),
Y =
(int)
((sinTheta * (pointToRotate.X - centerPoint.X)) +
((cosTheta * (pointToRotate.Y - centerPoint.Y)) + centerPoint.Y))
};
}
Related
I'm determining the rectangular area in an image and showing it to the user in a PictureBox.
Since the image can sometimes be very large, I'm using a PictureBox with its SizeMode set to Zoom.
I'm using the following code to translate the Rectangle (X, Y) coordinates:
public Point TranslateZoomMousePosition(Point coordinates)
{
// test to make sure our image is not null
if (pictureBox5.Image == null) return coordinates;
// Make sure our control width and height are not 0 and our
// image width and height are not 0
if (pictureBox5.Width == 0 || pictureBox5.Height == 0 || pictureBox5.Image.Width == 0 || pictureBox5.Image.Height == 0) return coordinates;
// This is the one that gets a little tricky. Essentially, need to check
// the aspect ratio of the image to the aspect ratio of the control
// to determine how it is being rendered
float imageAspect = (float)pictureBox5.Image.Width / pictureBox5.Image.Height;
float controlAspect = (float)pictureBox5.Width / pictureBox5.Height;
float newX = coordinates.X;
float newY = coordinates.Y;
if (imageAspect > controlAspect)
{
// This means that we are limited by width,
// meaning the image fills up the entire control from left to right
float ratioWidth = (float)pictureBox5.Image.Width / pictureBox5.Width;
newX *= ratioWidth;
float scale = (float)pictureBox5.Width / pictureBox5.Image.Width;
float displayHeight = scale * pictureBox5.Image.Height;
float diffHeight = pictureBox5.Height - displayHeight;
diffHeight /= 2;
newY -= diffHeight;
newY /= scale;
}
else
{
// This means that we are limited by height,
// meaning the image fills up the entire control from top to bottom
float ratioHeight = (float)pictureBox5.Image.Height / pictureBox5.Height;
newY *= ratioHeight;
float scale = (float)pictureBox5.Height / pictureBox5.Image.Height;
float displayWidth = scale * pictureBox5.Image.Width;
float diffWidth = pictureBox5.Width - displayWidth;
diffWidth /= 2;
newX -= diffWidth;
newX /= scale;
}
return new Point((int)newX, (int)newY);
}
Adding a frame control at the determined position:
pictureBox5.Controls.Clear();
var c = new FrameControl();
c.Size = new Size(myrect.Width, myrect.Height);
c.Location=TranslateZoomMousePosition(newPoint(myrect.Location.X,myrect.Location.Y));
pictureBox5.Controls.Add(c);
But the determined frame/rectangle location is not correct.
What am I i doing wrong?
Update:
I'm trying to translate a Rectangle on an image to a Frame Control on a PictureBox using similar code
public Rectangle GetRectangeOnPictureBox(PictureBox p, Rectangle selectionRect,Bitmap bit)
{
var method = typeof(PictureBox).GetMethod("ImageRectangleFromSizeMode",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var imageRect = (Rectangle)method.Invoke(p, new object[] { p.SizeMode });
if (p.Image == null)
return selectionRect;
int cx = bit.Width / imageRect.Width;
int cy = bit.Height / imageRect.Height;
Rectangle trsRectangle = new Rectangle(selectionRect.X * cx, selectionRect.Y * cy, selectionRect.Width * cx, selectionRect.Height * cy);
trsRectangle.Offset(imageRect.X, imageRect.Y);
return trsRectangle;
}
This produces invalid result.Please advice
You can translate selected rectangle on the picture box to the rectangle on image this way:
public RectangleF GetRectangeOnImage(PictureBox p, Rectangle selectionRect)
{
var method = typeof(PictureBox).GetMethod("ImageRectangleFromSizeMode",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var imageRect = (Rectangle)method.Invoke(p, new object[] { p.SizeMode });
if (p.Image == null)
return selectionRect;
var cx = (float)p.Image.Width / (float)imageRect.Width;
var cy = (float)p.Image.Height / (float)imageRect.Height;
var r2 = Rectangle.Intersect(imageRect, selectionRect);
r2.Offset(-imageRect.X, -imageRect.Y);
return new RectangleF(r2.X * cx, r2.Y * cy, r2.Width * cx, r2.Height * cy);
}
Note: You can find ImageRectangleFromSizeMode method source code here and use it as write such method as part of your application code.
Example - Crop Image of PictureBox having SizeMode = Zoom
As an example, the following code will crop the given rectangle of the picture box 1 and will set the result as image of picture box 2:
var selectedRectangle = new Rectangle(7, 30, 50, 40);
var result = GetRectangeOnImage(pictureBox1, selectedRectangle);
using (var bm = new Bitmap((int)result.Width, (int)result.Height))
{
using (var g = Graphics.FromImage(bm))
g.DrawImage(pictureBox1.Image, 0, 0, result, GraphicsUnit.Pixel);
pictureBox2.Image = (Image)bm.Clone();
}
Here is the input image:
And this is the result:
A specialized class that provides some helper tools to determine the scaling factor of a selection and translates the selection coordinates to the scaled Bitmap coordinates.
This version is for zoomed images only.
The ZoomFactor class provides these methods:
PointF TranslateZoomPosition(PointF Coordinates, SizeF ContainerSize, SizeF ImageSize):
returns the PointF translated coordinates of a Point location inside a Container to the Point location inside a Bitmap, zoomed in the container.
RectangleF TranslateZoomSelection(RectangleF Selection, SizeF ContainerSize, SizeF ImageSize):
returns a RectangleF representing a selection created inside a Container, translated to the Bitmap coordinates.
RectangleF TranslateSelectionToZoomedSel(RectangleF SelectionRect, SizeF ContainerSize, SizeF ImageSize):
returns a RectangleF representing a pre-selected area of the original Bitmap translated to the zoomed selection Image inside a Container.
PointF GetImageScaledOrigin(SizeF ContainerSize, SizeF ImageSize):
returns the PointF reference of the zoomed Image origin coordinates inside the Container.
SizeF GetImageScaledSize(SizeF ContainerSize, SizeF ImageSize):
returns the SizeF reference of the Image when scaled inside the Container.
Sample usage, showing how to crop a Bitmap using a selection Rectangle created inside a Container control. The TranslateZoomSelection method returns the Bitmap section corresponding to a selection area:
ZoomFactor zoomHelper = new ZoomFactor()
Bitmap originalBitmap;
RectangleF currentSelection = [Current Selection Rectangle];
RectangleF bitmapRect = zoomHelper.TranslateZoomSelection(currentSelection, [Container].Size, originalBitmap.Size);
var croppedBitmap = new Bitmap((int)bitmapRect.Width, (int)bitmapRect.Height, originalBitmap.PixelFormat))
using (var g = Graphics.FromImage(croppedBitmap))
{
g.DrawImage(originalBitmap, new Rectangle(Point.Empty, Size.Round(bitmapRect.Size)),
bitmapRect, GraphicsUnit.Pixel);
[Container].Image = croppedBitmap;
}
A Sample of the behaviour described above:
Note: In the example, the pre-selection of the image in Portrait inverts Width and Height
The ZoomFactor class:
public class ZoomFactor
{
public ZoomFactor() { }
public PointF TranslateZoomPosition(PointF coordinates, SizeF containerSize, SizeF imageSize)
{
PointF imageOrigin = TranslateCoordinatesOrigin(coordinates, containerSize, imageSize);
float scaleFactor = GetScaleFactor(containerSize, imageSize);
return new PointF(imageOrigin.X / scaleFactor, imageOrigin.Y / scaleFactor);
}
public RectangleF TranslateZoomSelection(RectangleF selectionRect, SizeF containerSize, SizeF imageSize)
{
PointF selectionTrueOrigin = TranslateZoomPosition(selectionRect.Location, containerSize, imageSize);
float scaleFactor = GetScaleFactor(containerSize, imageSize);
SizeF selectionTrueSize = new SizeF(selectionRect.Width / scaleFactor, selectionRect.Height / scaleFactor);
return new RectangleF(selectionTrueOrigin, selectionTrueSize);
}
public RectangleF TranslateSelectionToZoomedSel(RectangleF selectionRect, SizeF containerSize, SizeF imageSize)
{
float scaleFactor = GetScaleFactor(containerSize, imageSize);
RectangleF zoomedSelectionRect = new
RectangleF(selectionRect.X * scaleFactor, selectionRect.Y * scaleFactor,
selectionRect.Width * scaleFactor, selectionRect.Height * scaleFactor);
PointF imageScaledOrigin = GetImageScaledOrigin(containerSize, imageSize);
zoomedSelectionRect.Location = new PointF(zoomedSelectionRect.Location.X + imageScaledOrigin.X,
zoomedSelectionRect.Location.Y + imageScaledOrigin.Y);
return zoomedSelectionRect;
}
public PointF TranslateCoordinatesOrigin(PointF coordinates, SizeF containerSize, SizeF imageSize)
{
PointF imageOrigin = GetImageScaledOrigin(containerSize, imageSize);
return new PointF(coordinates.X - imageOrigin.X, coordinates.Y - imageOrigin.Y);
}
public PointF GetImageScaledOrigin(SizeF containerSize, SizeF imageSize)
{
SizeF imageScaleSize = GetImageScaledSize(containerSize, imageSize);
return new PointF((containerSize.Width - imageScaleSize.Width) / 2,
(containerSize.Height - imageScaleSize.Height) / 2);
}
public SizeF GetImageScaledSize(SizeF containerSize, SizeF imageSize)
{
float scaleFactor = GetScaleFactor(containerSize, imageSize);
return new SizeF(imageSize.Width * scaleFactor, imageSize.Height * scaleFactor);
}
internal float GetScaleFactor(SizeF scaled, SizeF original)
{
return (original.Width > original.Height) ? (scaled.Width / original.Width)
: (scaled.Height / original.Height);
}
}
I'm looking to remove all color from WMF image file by only 1 color.
Metafile img = new Metafile(path + strFilename + ".wmf");
float planScale = 0.06615f;
float scale = 1200f / (float)img.Width;
planScale = planScale / scale; ;
float widht = img.Width * scale;
float height = img.Height * scale;
using (var target = new Bitmap((int)widht, (int)height))
{
using (var g = Graphics.FromImage(target))
{
g.DrawImage(img, 0, 0, (int)widht, (int)height);
target.Save("image.png", ImageFormat.Png);
}
}
For the moment, I load a WMF file, set the scale and save it as PNG file.
Example of PNG result:
But now I need to remove all the colors (green, purple....) and set only 1 color like Gray for example.
If the background is always white you can do something like that. You can change the 200 to something you want, to adjust the Color that shouldn't be changed. In this example the white color is not changed. If you don't want to draw black, you can adjust the Color at target.SetPixel(x,y,Color.Black);
Metafile img = new Metafile("D:\\Chrysanthemum.wmf");
float planScale = 0.06615f;
float scale = 1200f / (float)img.Width;
planScale = planScale / scale; ;
float widht = img.Width * scale;
float height = img.Height * scale;
using (var target = new Bitmap((int)widht, (int)height))
{
using (var g = Graphics.FromImage(target))
{
g.DrawImage(img, 0, 0, (int)widht, (int)height);
}
for (int x = 0; x < target.Width; x++)
{
for (int y = 0; y < target.Height; y++)
{
Color white = target.GetPixel(x, y);
if ((int)white.R > 200 || (int)white.G > 200 || (int)white.B > 200)
{
target.SetPixel(x, y, Color.Black);
}
}
}
target.Save("D:\\image.png", ImageFormat.Png);
}
WMF Image:
PNG Image:
I hope that is what you are searching for.
I have a method that takes in a bitmap object and overlays dates and times strings over it and returns that new bitmap. The code is below.
public static Bitmap overlayBitmap(Bitmap sourceBMP, int width, int height, List<String> times, List<String> dates, IEnumerable<Color> colors) {
// Determine the new width
float newWidth = width + (width / 3.0f);
float newHeight = height + (height / 3.0f);
// Intelligent vertical + horizontal text distance calculator
float verticalDistance = height / (times.Count - 1.0f);
float horizontalDistance = width / (dates.Count - 1.0f);
Bitmap result = new Bitmap((int)newWidth, (int)newHeight);
using (Graphics g = Graphics.FromImage(result)) {
// Background color
Brush brush = new SolidBrush(colors.First());
g.FillRectangle(brush, 0, 0, newWidth, newHeight);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
// Times text configs
StringFormat stringFormatTimes = new StringFormat();
stringFormatTimes.LineAlignment = StringAlignment.Center;
stringFormatTimes.Alignment = StringAlignment.Center;
Font drawFontY = new Font("Whitney", newHeight / 70);
// Dates text configs
StringFormat stringFormatDates = new StringFormat();
stringFormatDates.LineAlignment = StringAlignment.Center;
stringFormatTimes.Alignment = StringAlignment.Center;
stringFormatDates.FormatFlags = StringFormatFlags.DirectionVertical;
Font drawFontX = new Font("Whitney", newHeight / 70);
// Location of times text
for (int i = 0; i < times.Count; i++) {
if (i % determineIncrementTimes(times.Count) == 0) {
g.DrawString(times[i], drawFontX, Brushes.White, (((newWidth - width) / 2) / 2), ((newHeight - height) / 2) + (verticalDistance * i), stringFormatTimes);
}
}
// Location of dates text
for (int i = 0; i < dates.Count; i++) {
if (i % determineIncrementDates(dates.Count) == 0) {
g.DrawString(dates[i], drawFontY, Brushes.White, ((newWidth - width) / 2) + (horizontalDistance * i), ((newHeight - height) / 2) + height, stringFormatDates);
}
}
// New X and Y Position of the sourceBMP within the new BMP.
int XPos = width / 6;
int YPos = height / 6;
// Int -> Float casting for the outline
float fXPos = width / 6.0f;
float fYPos = height / 6.0f;
float fWidth = width / 1.0f;
float fHeight = height / 1.0f;
// Draw new image at the position width/6 and height/6 with the size at width and height
g.DrawImage(sourceBMP, fXPos, fYPos, fWidth, fHeight);
g.DrawRectangle(Pens.White, fXPos, fYPos, fWidth, fHeight); // white outline
g.Dispose();
}
return result;
}
My concern is, I would like to be able, for the next developer, to easily access and set particular values that currently I've only "hardcoded" in. An example being the x-position of the time text calculated via this snippet of code:
(((newWidth - width) / 2) / 2)
Realistically I'd like to have the developer be able to access and/or set this value through simply typing in:
something.XPos = [someFloat];
How my method above is used (is pseudo-code) is as the following:
private readonly Bitmap _image;
private readonly Bitmap _overlayedImage;
public myConstructor(int someInputValues){
// some code that generates the first bitmap called _image
_newImage = overlayImage(_image, ....);
}
For reference this is the image drawn:
My question is - since some values need to be casted and initialized first, can I set my instance variables at the end of the method, before the closing brace?
public Bitmap overlayBitmap
{
get
{
// Build bitmap overlay
return overlayBitmapOutput;
}
...
}
[Edit: Answer Insufficient >> Wait]
I'm trying to create a shadow to a rectangle drawn dynamically on a bitmap. The problem is the shadow gets darker each time I draw a new rectangle (please see screenshot). I suspect that the same bitmap is used to draw the new rectangles. I tried using Graphics.clear() but it cleans the screen which I don't want. How can solve this problem?
Here is the code which draws the shadow:
public void drawAll(Rectangle baseRect,Graphics g)
{
int shadWidth = 10;
Bitmap bm = new Bitmap(shadWidth, baseRect.Height+shadWidth);//baseRect is created dynamically
for (int y = 0; y < baseRect.Height + shadWidth; y++)
{
int factor = 255 / shadWidth;//255 is the alpha color divided over the shadow width
int alpha = 255;
for (int x = 0; x < shadWidth; x++)
{
alpha -= factor;
if (alpha < 0) alpha = 0;
Color transColr = Color.FromArgb(alpha, 0, 0, 0);
bm.SetPixel(x, y, transColr);
}
}
GraphicsPath path = new GraphicsPath();
PointF[] pts = new[] {new PointF(baseRect.Right, baseRect.Top),
new PointF(baseRect.Right+shadWidth, baseRect.Top+shadWidth),
new PointF(baseRect.Right+shadWidth, baseRect.Bottom+shadWidth),
new PointF(baseRect.Right, baseRect.Bottom),
new PointF(baseRect.Right, baseRect.Top)};
path.AddLines(pts);
SmoothingMode old = g.SmoothingMode;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawImageUnscaled(bm, baseRect.Right, baseRect.Y);
}
How to resample an image to square, padding with white background in c# preferable without using any 3rd party libraries (.Net framework only)?
Thanks!
This can actually be done pretty easily.
public static Image PadImage(Image originalImage)
{
int largestDimension = Math.Max(originalImage.Height, originalImage.Width);
Size squareSize = new Size(largestDimension, largestDimension);
Bitmap squareImage = new Bitmap(squareSize.Width, squareSize.Height);
using (Graphics graphics = Graphics.FromImage(squareImage))
{
graphics.FillRectangle(Brushes.White, 0, 0, squareSize.Width, squareSize.Height);
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphics.DrawImage(originalImage, (squareSize.Width / 2) - (originalImage.Width / 2), (squareSize.Height / 2) - (originalImage.Height / 2), originalImage.Width, originalImage.Height);
}
return squareImage;
}
Try using this method. The last argument is a switch for whether you want to stretch the image to fit. If false, the image is centered inside the new white canvas. You can pass a square or non-square size to it as needed.
public static Bitmap ResizeBitmapOnWhiteCanvas(Bitmap bmpOriginal, Size szTarget, bool Stretch)
{
Bitmap result = new Bitmap(szTarget.Width, szTarget.Height);
using (Graphics g = Graphics.FromImage((Image)result))
{
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.FillRectangle(Brushes.White, new Rectangle(0, 0, szTarget.Width, szTarget.Height));
if (Stretch)
{
g.DrawImage(bmpOriginal, 0, 0, szTarget.Width, szTarget.Height); // fills the square (stretch)
}
else
{
float OriginalAR = bmpOriginal.Width / bmpOriginal.Height;
float TargetAR = szTarget.Width / szTarget.Height;
if (OriginalAR >= TargetAR)
{
// Original is wider than target
float X = 0F;
float Y = ((float)szTarget.Height / 2F) - ((float)szTarget.Width / (float)bmpOriginal.Width * (float)bmpOriginal.Height) / 2F;
float Width = szTarget.Width;
float Height = (float)szTarget.Width / (float)bmpOriginal.Width * (float)bmpOriginal.Height;
g.DrawImage(bmpOriginal, X, Y, Width, Height);
}
else
{
// Original is narrower than target
float X = ((float)szTarget.Width / 2F) - ((float)szTarget.Height / (float)bmpOriginal.Height * (float)bmpOriginal.Width) / 2F;
float Y = 0F;
float Width = (float)szTarget.Height / (float)bmpOriginal.Height * (float)bmpOriginal.Width;
float Height = szTarget.Height;
g.DrawImage(bmpOriginal, X, Y, Width, Height);
}
}
}
return result;
}
You don't say how you want it padded. Assuming you want the image centered, with the image file name in imageFileName and the desired output file name in newFileName:
Bitmap orig = new Bitmap(imageFileName);
int dim = Math.Max(orig.Width, orig.Height);
Bitmap dest;
using (Graphics origG = Graphics.FromImage(orig))
{
dest = new Bitmap(dim, dim, origG);
}
using (Graphics g = Graphics.FromImage(dest))
{
Pen white = new Pen(Color.White, 22);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, dim, dim);
g.DrawImage(orig, new Point((dim - orig.Width) / 2, (dim - orig.Height) / 2));
}
dest.Save(newFileName);