I am running the latest version and I am getting the error as preceding.
Severity Code Description Project File Line Suppression State Error
CS1061 'IImageProcessingContext' does not contain a definition for
'ApplyScalingWaterMark' and no accessible extension method
'ApplyScalingWaterMark' accepting a first argument of type
'IImageProcessingContext' could be found (are you missing a using
directive or an assembly reference?) GitHubFuncs
C:\Sibeesh\Github\GitHubFuncs\GetLatestPosts.cs 39 Active
When I run the code below.
using(var img = Image.Load("canvas.jpg")) {
Font font = SystemFonts.CreateFont("Arial", 10);
using
var img2 = img.Clone(ctx => ctx.ApplyScalingWaterMark(font, feedItem.Summary.Text, Color.HotPink, 5, true));
img2.Save("images/wrapped.png");
}
Already added the using statements.
using SixLabors.Fonts;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
What am I missing here? Is this an issue with the latest version?
Finally, I got it working. I just had to add a few extension methods.
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.Processing;
using System;
namespace GitHubFuncs.ExtensionMethods {
public static class ImageSharpExtensions {
public static IImageProcessingContext ApplyScalingWaterMark(this IImageProcessingContext processingContext,
Font font,
string text,
Color color,
float padding,
bool wordwrap) {
if (wordwrap) {
return processingContext.ApplyScalingWaterMarkWordWrap(font, text, color, padding);
} else {
return processingContext.ApplyScalingWaterMarkSimple(font, text, color, padding);
}
}
private static IImageProcessingContext ApplyScalingWaterMarkSimple(this IImageProcessingContext processingContext,
Font font,
string text,
Color color,
float padding) {
Size imgSize = processingContext.GetCurrentSize();
float targetWidth = imgSize.Width - (padding * 2);
float targetHeight = imgSize.Height - (padding * 2);
// measure the text size
FontRectangle size = TextMeasurer.Measure(text, new RendererOptions(font));
//find out how much we need to scale the text to fill the space (up or down)
float scalingFactor = Math.Min(imgSize.Width / size.Width, imgSize.Height / size.Height);
//create a new font
Font scaledFont = new Font(font, scalingFactor * font.Size);
var center = new PointF(imgSize.Width / 2, imgSize.Height / 2);
var textGraphicOptions = new TextGraphicsOptions() {
TextOptions = {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
};
return processingContext.DrawText(textGraphicOptions, text, scaledFont, color, center);
}
private static IImageProcessingContext ApplyScalingWaterMarkWordWrap(this IImageProcessingContext processingContext,
Font font,
string text,
Color color,
float padding) {
Size imgSize = processingContext.GetCurrentSize();
float targetWidth = imgSize.Width - (padding * 2);
float targetHeight = imgSize.Height - (padding * 2);
float targetMinHeight = imgSize.Height - (padding * 3); // must be with in a margin width of the target height
// now we are working i 2 dimensions at once and can't just scale because it will cause the text to
// reflow we need to just try multiple times
var scaledFont = font;
FontRectangle s = new FontRectangle(0, 0, float.MaxValue, float.MaxValue);
float scaleFactor = (scaledFont.Size / 2); // every time we change direction we half this size
int trapCount = (int) scaledFont.Size * 2;
if (trapCount < 10) {
trapCount = 10;
}
bool isTooSmall = false;
while ((s.Height > targetHeight || s.Height < targetMinHeight) && trapCount > 0) {
if (s.Height > targetHeight) {
if (isTooSmall) {
scaleFactor = scaleFactor / 2;
}
scaledFont = new Font(scaledFont, scaledFont.Size - scaleFactor);
isTooSmall = false;
}
if (s.Height < targetMinHeight) {
if (!isTooSmall) {
scaleFactor = scaleFactor / 2;
}
scaledFont = new Font(scaledFont, scaledFont.Size + scaleFactor);
isTooSmall = true;
}
trapCount--;
s = TextMeasurer.Measure(text, new RendererOptions(scaledFont) {
WrappingWidth = targetWidth
});
}
var center = new PointF(padding, imgSize.Height / 2);
var textGraphicOptions = new TextGraphicsOptions() {
TextOptions = {
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = targetWidth
}
};
return processingContext.DrawText(textGraphicOptions, text, scaledFont, color, center);
}
}
}
And in the end, I could use this method as preceding.
private static string WriteOnImage(SyndicationItem feedItem) {
using var img = Image.Load("images/canvas.jpg");
var font = SystemFonts.CreateFont("Arial", 20);
using var img2 = img.Clone(ctx => ctx.ApplyScalingWaterMark(font, feedItem.Summary.Text, Color.White, 5, true));
return img2.ToBase64String(PngFormat.Instance);
}
Related
I have created a program to draw square grids on a selected image. It works fine for images that has small resolution, but it doesn't work properly on large images.
The all grid lines are not visible seem when the image is saved as file.
The image I am testing has resolution 3600x4320 and can be shown in the link.
How can I fix this problem?
My code:
Image drawGrid(int n, string imgPath)
{
Image img = Image.FromFile(imgPath);
Graphics grp = Graphics.FromImage(img);
grp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
float m = img.Width * 1f / n;
for (int i = 1; i < n; i++)
{
var x = new PointF(i * m, 0);
var y = new PointF(i * m, img.Height);
grp.DrawLine(Pens.Red, x, y);
}
for (int i = 1; i <= (int)(this.Height / m); i++)
{
var x = new PointF(0, i * m);
var y = new PointF(img.Width, i * m);
grp.DrawLine(new Pen(Color.Red, 5f), x, y);
}
return img;
}
void BtnExportClick(object sender, EventArgs e)
{
if(saveFileDialog1.ShowDialog() == DialogResult.OK)
{
int n = (int)numericUpDown1.Value;
drawGrid(n, txtImagePath.Text).Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
MessageBox.Show("Done");
}
}
The result image is below (resolution reduced to upload)
The grid lines not shown correctly.
The major problem is in this line:
for (int i = 1; i <= (int)(this.Height / m); i++)
▶ this.Height is clearly not what you wanted to write, let's replace it with the Image.Height
▶ grp.DrawLine(Pens.Red, x, y); and grp.DrawLine(new Pen(Color.Red, 5f), x, y); will draw lines of different size (1 and 5 pixels). In the sample code, the two methods accept Color and float arguments that define the Pen color and size.
▶ grp.SmoothingMode: we don't want any smoothing mode here, not needed to draw straight lines and it will add anti-alias which will be clearly visible, especially when saving the Image in JPEG format (it will anti-alias - sort of, it actually mangles the colors - these lines by itself).
▶ You're not disposing of any of the Graphics object you create. This is quite important with both frequent graphics operations and when working with large Bitmaps.
The Pen and Graphics objects needs to be disposed.
Since it's not exactly clear if you want to generate a grid that has the same number of lines in both dimensions - hence, a grid with Cells in which the Width is not equal to the Height, most probably - or a grid with squared Cells (this is what the sample Image seems to show, not the code), I posted two method that draw both grid types:
First method, same number of lines in both width and height:
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
private Image DrawGridLines(int lines, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
for (int i = 1; i < lines; i++) {
var pointX1 = new PointF(0, i * gridSizeY);
var pointX2 = new PointF(image.Width, i * gridSizeY);
var pointY1 = new PointF(i * gridSizeX, 0);
var pointY2 = new PointF(i * gridSizeX, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
g.DrawLine(pen, pointY1, pointY2);
}
}
return image;
}
}
Second method, drawing a squared grid. The integer value, gridSection, is used to define a grid Cell based on the minimum dimension of the Bitmap.
This dimension is then used to determine how many lines to draw in the other dimension.
The grid size is calculated on the minimum dimension:
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
And the Cell are determined as a consequence:
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
private Image DrawSquaredGrid(int gridSection, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
for (int i = 1; i < gridStepMin; i++) {
var pointY1 = new PointF(i * gridSize, 0);
var pointY2 = new PointF(i * gridSize, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointY1, pointY2);
}
}
for (int i = 1; i < gridStepMax; i++) {
var pointX1 = new PointF(0, i * gridSize);
var pointX2 = new PointF(image.Width, i * gridSize);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
}
}
return image;
}
}
The SaveFileDialog is refactored to allow multiple Image formats. and to call one of the drawing methods based on a selection (in the sample code, a CheckBox (chkSquared) is used select one of the Grid types).
You can add more formats, the ImageFormatFromFileName() methods selects the ImageFormat type based on the SaveFileDialog.FielName extension.
private void BtnExportClick(object sender, EventArgs e)
{
string imagePath = [Some Path];
using (var sfd = new SaveFileDialog()) {
sfd.Filter = "PNG Image (*.png)|*.png|TIFF Image (*.tif)|*.tif|JPEG Image (*.jpg)|*.jpg";
sfd.RestoreDirectory = true;
sfd.AddExtension = true;
if (sfd.ShowDialog() == DialogResult.OK) {
Image image = null;
if (chkSquared.Checked) {
image = DrawSquaredGrid((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
else {
image = DrawGridLines((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
image.Save(sfd.FileName, ImageFormatFromFileName(sfd.FileName));
MessageBox.Show("Done");
image.Dispose();
}
}
}
private ImageFormat ImageFormatFromFileName(string fileName)
{
string fileType = Path.GetExtension(fileName).Remove(0, 1);
if (fileType.Equals("tif")) fileType = "tiff";
if (fileType.Equals("jpg")) fileType = "jpeg";
return (ImageFormat)new ImageFormatConverter().ConvertFromString(fileType);
}
I have two images, one as background and one containing a signature.
I want to be able to down/up scale the signature depending on a scalefactor but within the the height/width of the background.
I've tried AffineTransformBuilder.AppendScale without any success so instead i've tried to scale the signature with resize instead.
How can i scale the signature image within the bounds of the background image?
Image results
Expected result
Actual result with resize
Code
public class ImageConverter
{
public byte[] GetPngImage(byte[] plots)
{
var desiredWidth = 250;
var desiredHeight = 250;
var Rgba32 backgroundColor = Rgba32.White;
var Rgba32 foregroundColor = Rgba32.Black;
var plottedLines = GetPlottedLines(plots);
var minX = lines.Min(p => p.Start.X > p.End.X ? p.Start.X : p.End.X);
var maxX = lines.Max(p => p.Start.X > p.End.X ? p.Start.X : p.End.X);
var minY = lines.Min(p => p.Start.Y > p.End.Y ? p.Start.Y : p.End.Y);
var maxY = lines.Max(p => p.Start.Y > p.End.Y ? p.Start.Y : p.End.Y);
var width = maxX - minX;
var height = maxY - minY;
var scaleFactor = CalculateScaleFactor(width, height, desiredWidth, desiredHeight);
using (var canvas = new Image<Rgba32>(desiredWidth, desiredHeight))
using(var signature = new Image<Rgba32>(width, height))
{
signature.Mutate(ctx =>
{
var pen = new Pen<Rgba32>(foregroundColor, 1);
foreach (var line in plottedLines)
{
ctx.DrawLines(pen, new PointF(line.Start.X - minX, line.Start.Y - minY),
new PointF(line.End.X - minX, line.End.Y - minY));
}
ctx.Resize(new ResizeOptions
{
Size = new Size(desiredWidth, desiredHeight),
Mode = ResizeMode.BoxPad,
});
//var transformX = ((desiredWidth / 2f) - (width * scaleFactor) / 2f) < 0f ? 0f : ((desiredWidth / 2f) - (width * scaleFactor) / 2f);
//var transformY = ((desiredHeight / 2f) - (height * scaleFactor) / 2f) < 0f ? 0f : ((desiredHeight / 2f) - (height * scaleFactor) / 2f);
//var transformBuilder = new AffineTransformBuilder();
//transformBuilder.AppendTranslation(new PointF(transformX, transformY));
//transformBuilder.AppendScale(scaleFactor);
//ctx.Transform(transformBuilder);
//var pen = new Pen<Rgba32>(foregroundColor, 1);
//foreach (var line in plottedLines)
//{
//ctx.DrawLines(pen, new PointF(line.Start.X - minX, line.Start.Y - minY), new PointF(line.End.X - minX, line.End.Y - minY));
//}
});
canvas.Mutate(ctx =>
{
ctx.Fill(new SolidBrush<Rgba32>(backgroundColor), new RectangleF(0, 0, desiredWidth, desiredHeight));
ctx.DrawImage(signature, new Point(0, 0), 1);
});
using (var ms = new MemoryStream())
{
canvas.Save(ms, new PngEncoder());
return ms.ToArray();
}
}
}
private List<Line> GetPlottedLines(byte[] plotChunk)
{
var plotString = Encoding.Default.GetString(plotChunk);
var lines = plotString.Split(new[] {"|"}, StringSplitOptions.RemoveEmptyEntries);
return lines.Select(line =>
line.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries))
.Select(points => new Line(
new Point(Convert.ToInt32(points[0]), Convert.ToInt32(points[1])),
new Point(Convert.ToInt32(points[2]), Convert.ToInt32(points[3]))))
.ToList();
}
private float CalculateScaleFactor(float currentImageWidthInPixels, float currentImageHeightInPixels,
int desiredWidthInPixels, int desiredHeightInPixels)
{
var scaleFactorWidth = desiredWidthInPixels / currentImageWidthInPixels;
var scaleFactorHeight = desiredHeightInPixels / currentImageHeightInPixels;
var scaleFactor = scaleFactorWidth < scaleFactorHeight ? scaleFactorWidth : scaleFactorHeight;
return scaleFactor == 0.0 ? 1.0f : scaleFactor;
}
private class Line
{
internal Point Start { get; }
internal Point End { get; }
public Line(Point start, Point end)
{
Start = start;
End = end;
}
}
}
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]
My text and font looks good for most of my application; however, when I do .DrawPath() and the path has a string / text in the GraphicsPath it always looks fussy or blurry.
Can someone tell me how to get the .DrawPath with a string in to look as nice as a GrahpicsPath.DrawString?
//Bay Symbol
dc.DrawImage(Symbols.GetSymbol(SymbolType.Bay, (i + 1).ToString()), (x + (wPx / 2)) - 10, Constants.ELEVATION_START_Y - 35);
public static Bitmap GetSymbol(SymbolType symType, string text = "", bool attachLeg = true)
{
var sym = new Bitmap(50, 50);
sym.SetResolution(150, 150);
using (Graphics dc = Graphics.FromImage(sym))
{
dc.SmoothingMode = SmoothingMode.AntiAlias;
switch (symType)
{
//code removed for clarity
case SymbolType.Bay:
dc.DrawPath(ShopDrawing.Pens.GetSymbolPen(), CreateBaySymbol(text));
if (attachLeg)
AttachSymbolLeg(dc, Constants.BAY_SYMBOL);
break;
};
dc.SmoothingMode = SmoothingMode.Default;
return sym;
}
}
internal static GraphicsPath CreateBaySymbol(string text = "")
{
GraphicsPath myPath = new GraphicsPath();
myPath.AddRectangle(Constants.BAY_SYMBOL);
if (!string.IsNullOrEmpty(text)) { Util.AddStringToPath(myPath, text); }
return myPath;
}
internal static void AddStringToPath(GraphicsPath path, string text)
{
var textSize = TextRenderer.MeasureText(text, Constants.DETAIL_BOX_FONT);
float centerX = (path.GetBounds().Width / 2) - (textSize.Width / 2),
centerY = (path.GetBounds().Height / 2) - (textSize.Height / 2);
//Add the string to the path.
path.AddString(text,
Constants.DETAIL_BOX_FONT.FontFamily,
(int)Constants.DETAIL_BOX_FONT.Style,
Constants.DETAIL_BOX_FONT.Size,
new PointF(centerX + 2, centerY + 2),
StringFormat.GenericDefault)
Symbols = a symbol that numbers each part of the elevation below. You can see how the numbering will start at 1 then 2 then 3 and so on.
Here is an snapshot that shows how all the font / text looks good, as in the numbers for the width and height, but the numbering inside the symbols look horrible.
UIImageView not show/display Original Image as that of available on server.
for more ref check it
float scaleFactor = 0.0f;`
float scaleWidth = dstRectangle.Width; //width =748
float scaleHeight = dstRectangle.Height; // height = 759
if(!SizeF.Equals(srcRectangle ,dstRectangle))
{
float widthFactor =(float) dstRectangle.Width / srcRectangle.Width; //srcRectHeight = 512
float heightFactor =(float) dstRectangle.Height / srcRectangle.Height; //srcRectangle.Height = 314
if(widthFactor >heightFactor)
scaleFactor = widthFactor ;
else
scaleFactor = heightFactor;
scaleWidth = srcRectangle.Width * scaleFactor;
scaleHeight = srcRectangle.Height * scaleFactor;
if(widthFactor >heightFactor)
{
thumbnailPoint.Y = (dstRectangle.Height - scaleHeight) * .5f;
}
else
if(widthFactor < heightFactor)
{
thumbnailPoint.X = (dstRectangle.Width - scaleWidth) *.5f;
}
}
UIGraphics.BeginImageContext(new SizeF(dstRectangle.Width , dstRectangle.Height));
RectangleF thumbnailRect = RectangleF.Empty;
thumbnailRect.Offset(thumbnailPoint);
thumbnailRect.Size.Width = scaleWidth;
thumbnailRect.Size.Height = scaleHeight;
CGImage _bitmap = UIGraphics.GetImageFromCurrentImageContext().CGImage;
ImgVIew.Image = UIImage.FromImage(_bitmap);
UIGraphics.EndImageContext();
It gives me an UIImage But not display on the UIImageView
In CGImage I got ALphaInfo = PremultipledLast
BitsPerComponant = 8
BitsPerPixel =32
BytesPerRow = 3008
Height= 759
Width = 748
this values I get into CGImage but this CGImage I conver it to UIImage but still it can not display
Can you just do something like this:
using (var url = NSUrl.FromString ("http://yoururl.com/yourpic.jpg"))
{
using (var data = NSData.FromUrl (url))
{
imageView.Image = UIImage.LoadFromData (data);
}
}
It sounds like you just need to download an image and set it to a UIImageView, right?
It seems like you are writing a lot of code for something pretty simple.
NOTE: use a try-catch for networking errors.