I'm using the Cairo bindings for C#, and I wanted to be able to crop an ImageSurface and put it into a new ImageSurface variable, which I will then use in a seperate subroutine. The question is how would I do this properly.
Here's my code as it stands:
ImageSurface OutputImage = new ImageSurface (Format.Rgb24, (int)RectangleToCropTo.Width, (int)RectangleToCropTo.Height);
using (Cairo.Context cr = new Cairo.Context(OutputImage)) {
cr.SetSource (originalImage);
cr.Rectangle (RectangleToCropTo);
cr.Clip ();
cr.Paint ();
}
As it stands, it does actually crop the image down, and it appears to start at the correct point - but the OutputImage is offset in the x and y axis by the amount of pixels from 0,0 that the top left of the selection is. The area that the image doesn't populate is transparent.
I also tried the following:
ImageSurface OutputImage = new ImageSurface (Format.Rgb24, (int)RectangleToCropTo.Width, (int)RectangleToCropTo.Height);
using (Cairo.Context cr = new Cairo.Context(OutputImage)) {
cr.SetSource (originalImage, RectangleToCropTo.X, RectangleToCropTo.Y);
cr.Rectangle (RectangleToCropTo);
cr.Clip ();
cr.Paint ();
}
This additional two parameters supposedly mark from which point the 'source' should be stored (from my understanding, '40, 40' would mean that 'painting' the image would result in the image from 40, 40 and whatever is to the right of and below it).
However, this neither provides me with a cropped image, but instead includes the troublesome offset and starts drawing from the top left of the image.
I also figured that maybe this would be the solution:
ImageSurface OutputImage = new ImageSurface (Format.Rgb24, (int)RectangleToCropTo.Width, (int)RectangleToCropTo.Height);
using (Cairo.Context cr = new Cairo.Context(OutputImage)) {
cr.SetSource (originalImage, RectangleToCropTo.X, RectangleToCropTo.Y);
cr.Clip ();
cr.Paint ();
}
But to no avail, this simply produces identical results to the previous code chunk.
If any of you could tell me what I'm doing wrong (and it's probably the dumbest of mistakes), I'd appreciate it.
I'm use the Cairo-Sharp that comes with Gtk3.
Thanks :)
You almost got it. Try this:
ImageSurface OutputImage = new ImageSurface (Format.Rgb24, (int)RectangleToCropTo.Width, (int)RectangleToCropTo.Height);
using (Cairo.Context cr = new Cairo.Context(OutputImage)) {
cr.SetSource (originalImage, -RectangleToCropTo.X, -RectangleToCropTo.Y);
cr.Paint ();
}
The important difference is the negative arguments to SetSource. This places the source image so that the (x, y) pixel from the source falls onto the (0, 0) pixel on the target.
Since you target covers all the pixels needed, your rectangle() and clip() calls aren't needed anymore.
clip does not resize the surface, it just masks out anything fur future drawing operations.
Create a new surface of proper size and copy the content of the desired (clipped or not) region.
Related
Bitmap image = ReadBitmap("image.png");
Bitmap imageCopy = new Bitmap(image);
Bitmap canvas = new Bitmap(imageCopy.Width+100, imageCopy.Height);
// From this bitmap, the graphics can be obtained, because it has the right PixelFormat
using(Graphics g = Graphics.FromImage(canvas))
{
// Draw the original bitmap onto the graphics of the new bitmap
g.DrawImage(image, 0, 0);
}
// Use tempBitmap as you would have used originalBmp
InputPictureBox.Image = image;
OutputPictureBox.Image = canvas;
I haven't understood the output of this c# code.
The original image is not placed at the correct position. It should have been at (0, 0).
Also, I need a black background.
So, what is going on and how to correct this?
You are loading an Image, then a copy of this source is created using:
Bitmap bitmap = new Bitmap();
When you create a copy of an Image this way, you sacrifice/alter some details:
Dpi Resolution: if not otherwise specified, the resolution is set to the UI resolution. 96 Dpi, as a standard; it might be different with different screen resolutions and scaling. The System in use also affects this value (Windows 7 and Windows 10 will probably/possibly provide different values)
PixelFormat: If not directly copied from the Image source or explicitly specified, the PixelFormat is set to PixelFormat.Format32bppArgb.
From what you were saying, you probably wanted something like this:
var imageSource = Image.FromStream(new MemoryStream(File.ReadAllBytes(#"[SomeImageOfLena]"))), true, false)
var imageCopy = new Bitmap(imageSource.Width + 100, imageSource.Height, imageSource.PixelFormat))
imageCopy.SetResolution(imageSource.HorizontalResolution, imageSource.VerticalResolution);
using (var g = Graphics.FromImage(imageCopy)) {
g.Clear(Color.Black);
g.CompositingMode = CompositingMode.SourceCopy;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(imageSource, (imageCopy.Width - imageSource.Width) / 2, 0);
pictureBox1.Image?.Dispose();
pictureBox2.Image?.Dispose();
pictureBox1.Image = imageSource;
pictureBox2.Image = imageCopy;
}
This is the result:
(The upper/lower frame black color is actually the Picturebox background color)
When the original Image Dpi Resolution is different from the base Dpi Resolution used when creating an Image copy with new Bitmap(), your results may be different from what is expected.
This is what happens with a source Image of 150, 96 and 72 Dpi in the same scenario:
Another important detail is the IDisposable nature of the Image object.
When you create one, you have to Dispose() of it; explicitly, calling the Dispose method, or implicitly, enclosing the Image contructor in a Using statement.
Also, possibly, don't assign an Image object directly loaded from a FileStream.
GDI+ will lock the file, and you will not be able to copy, move or delete it.
With the file, all resources tied to the Images will also be locked.
Make a copy with new Bitmap() (if you don't care of the above mentioned details), or with Image.Clone(), which will preserve the Image Dpi Resolution and PixelFormat.
I am not completely clear on what you are actually needing to do. But anyway, here is a WPF-friendly example of how to draw an image at a specific position inside another image.
Note if all you want to do is display the image in different size and/or put a black border around it, there are much simpler ways to do simply that, without having to create a second image, such as just laying out the image inside a panel that already has the border style you want.
Notice that I am using classes from the System.Windows.Media namespace because that is what WPF uses. These don't mix easily with the older classes from System.Drawing namespace (some of the class names conflict, and Microsoft's .Net framework lacks built-in methods for converting objects between those types), so normally one needs to simply decide whether to use one or the other sets of drawing tools. I assume you have been trying to use System.Drawing. Each has its own pros and cons that would take too long to explain here.
// using System.Windows.Media;
// using System.Windows.Media.Imaging;
private void DrawTwoImages()
{
// For InputPictureBox
var file = new Uri("C:\\image.png");
var inputImage = new BitmapImage(file);
// If your image is stored in a Resource Dictionary, instead use:
// var inputImage = (BitmapImage) Resources["image.png"];
InputPicture.Source = inputImage;
// imageCopy isn't actually needed for this example.
// But since you had it in yours, here is how it's done, anyway.
var imageCopy = inputImage.Clone();
// Parameters for setting up our output picture
int leftMargin = 50;
int topMargin = 5;
int rightMargin = 50;
int bottomMargin = 5;
int width = inputImage.PixelWidth + leftMargin + rightMargin;
int height = inputImage.PixelHeight + topMargin + bottomMargin;
var backgroundColor = Brushes.Black;
var borderColor = (Pen) null;
// Use a DrawingVisual and DrawingContext for drawing
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
// Draw the black background
dc.DrawRectangle(backgroundColor, borderColor, new Rect(0, 0, width, height));
// Copy input image onto output image at desired position
dc.DrawImage(inputImage, new Rect(leftMargin, topMargin,
inputImage.PixelWidth, inputImage.PixelHeight));
}
// For displaying output image
var rtb = new RenderTargetBitmap( width, height, 96, 96, PixelFormats.Pbgra32 );
rtb.Render(dv);
OutputPicture.Source = rtb;
}
Hi I have the issue that when I use ScaleTransform(zoomFactor,zoomFactor) the image saved on disk is the original version always, while on screen in the picturebox the image is distorted in proportion to the zoomFactor.
Why this could be happening ? Shouldn't I have the final result as applied from e.Graphics on disk written image ?
My code is the following which is a version with matrix. but the instead of matrix I have used the ScaleTransform as well. Result is always the same:
g=e.Graphics;//inside picturebox_paint()
g.ScaleTransform(ratio * zoomFac, ratio * zoomFac);
e.Graphics.DrawImage((Bitmap)bmp, 0, 0);
int seed = Convert.ToInt32(Regex.Match(Guid.NewGuid().ToString(), #"\d+").Value);
String destinationFile = #"C:\tmp\photoid\" + new Random(seed).Next() + "_conv.jpg";
//Here I get always the original image back!!!!
bmp.Save(destinationFile);
I have used as well the following idiom but with same results:
//Matrix matrix = new Matrix();
//matrix.Scale(zoomFac, zoomFac);
//e.Graphics.Transform = matrix;
You need to make the PictureBox draw the things it shows on screen into a new Bitmap, which you then can save!
As it is the Image will be saved in the original form and nothing you did in the Paint event, which actually painst onto the surface of the PictureBox will be saved.
So to save everything, i.e. The Image, possibly a BackgroundImage and all you draw in the Paint event you would call DrawToBitmap somehwere.
Somewhere means somewhere else, not in the Paint event, as it will call the Paint event to create the new Bitmap, causing an endless loop..
To call it you would do something like this:
Bitmap bmpSave = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
pictureBox1.DrawToBitmap(bmpSave, pictureBox1.ClientRectangle);
But maybe this is not really what you want? Maybe you actually want to modify the Image? In that case do not use the Paint event at all!
Instead do something like this:
Bitmap bmpSave = new Bitmap(yourNewWidth, yourNewHeight);
using (Graphics g = Graphics.FromImage(bmpSave))
{
g.ScaleTransform(ratio * zoomFac, ratio * zoomFac);
g.DrawImage((Bitmap)pictureBox1.Image, 0, 0); //
pictureBox1.Image = bmpSave;
bmpSave.Save(...);
}
You could call this from somewhere where the scaling is being triggered from.
Note that doing the scaling repeatedly and each time from the previoulsy scaled version will degrade the quality rather fast. For this always scale from a saved version of the original!!
Btw: Using a Matrix for scaling doesn't really make a difference over ScaleTransform.
But if you want to do a direct scaling why not use the DrawImage overload which takes two Rectangles? This is the most common solution if all you want to to scale and maybe draw other stuff additionally..:
int newWidth = 100; int newHeight = 100; string yourFileName = "D:\\xyz123.jpg";
Bitmap bmpSave = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
Rectangle newRectangle = new Rectangle(0, 0, newWidth, newHeight);
Rectangle oldRectangle = new Rectangle(Point.Empty, pictureBox1.Image.Size);
using (Graphics g = Graphics.FromImage(bmpSave))
{
g.DrawImage((Bitmap)pictureBox1.Image, newRectangle, oldRectangle, GraphicsUnit.Pixel);
bmpSave.Save(yourFileName, ImageFormat.Jpeg);
}
And there there is the scaling Bitmap constructor:
Bitmap bmp = new Bitmap(pictureBox1.Image, newWidth, newHeight);
Which I would recommend if all you want is to scale the Image. As the other solutions it will not change the Image displayed until you assign it back into the PictureBox..:
pictureBox1.Image = bmp ;
Don't forget to dispose of the old Image..
Been a while since I messed with GDI but I think you need to copy back to the Bitmap here.
g.DrawImage(bmp, scaledwidth, scaledheight);
Try something like that before bmp.Save
Edit
Apologies for not seeing that you were copying back to the bitmap. Perhaps the overload which specifies the output rectangle is what you need. Try a DrawImage overload which has the destination Rect. https://msdn.microsoft.com/en-us/library/ms142040(v=vs.110).aspx
Graphics gr;
gr = CreateGraphics();
Pen p = new Pen(System.Drawing.Color.FromArgb(r.Next(255), r.Next(255), r.Next(255)), 1.1f);
Point p1 = new Point(array1[currentadd], dx);
Point p2 = new Point(array1[currentadd], dx = dx + 7);
gr.DrawLine(p, p1, p2);
now i have drawn a line and i want to write the info of points where the line is connecting them ...so i want so set label position to write the point position ... but how?
You should take a look at this document here at MSDN.
In particular something you'll notice in the example is this line:
formGraphics.DrawLine(myPen, 0, 0, 200, 200);
This is doing five different things:
First it will call our Drawing Object.
Second and third are our drawing points.
Fourth and fifth are the floating location.
This will obviously allow you to create some bounded locations. Obviously, if you've generated a class that will handle this initially to build it. Then when you call this object in another class you can position, dock, location, and anchor to that form you've called it in.
Please bear in mind that this assumes a basic line, but you can make more complex things as found here on the MSDN.
Potential Problem:
If you utilize the methodology in the example directly on your Form please bare in mind that the this will bind to the current scope of the object that the this is represented for. An example, if this code was on a button:
Pen myPen = Pen(Color.Red);
Graphics formGraphics = this.CreateGraphics();
formGraphics.DrawLine(myPen, 0, 100, 200, 200);
Then it will be bind the points based on the location of the button object in this case.
Hopefully that helps point you in the right direction.
Update:
Based on your question, it sounds like your trying to place the line either above or below a standard label. Which can be done quite simple, such as:
// Define Label
Label i = new Label();
// Create our Line.
Pen iPen = Pen(Color.Red);
Graphics iGraphics = i.CreateGraphics();
iGraphics.DrawLine(iPen, 0, 250, 0, 0);
// Dock to Bottom of Form
i.Dock = DockStyle.Bottom;
Then if you change the location at any point with a button, or you modify the text within a loop your line will remain bound to your label object. Is that not what you wanted?
I have a image where I am doing resizing,drawString and FillEllipse.
There are many points(FillEllipse) that needs to shown n bitmap, so I am using for loop.
Here is the code:
using (System.Drawing.Graphics Gfx = System.Drawing.Graphics.FromImage(OrginalBitmap))
{
Gfx.SmoothingMode = SmoothingMode.HighQuality;
Gfx.CompositingQuality = CompositingQuality.HighQuality;
Gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
Gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
foreach (var points in SelectedPoints)
{
Gfx.FillEllipse(
Brushes.Yellow,new Rectangle(points.X , points.Y, 8, 8));
Gfx.DrawString("M", new Font("Arial",8),
Brushes.Yellow, points.X, points.Y);
//points.X and points.X are the points that needs to be drawn on bitmap(particular location).
}
}
((IDisposable)OrginalBitmap).Dispose;
Loading of drawn bitmap takes very long time if there are many points in SelectedPoints.
Performance had drastically come down and loading takes too much memory.
Please let me know what to do.
Thanks in advance.
Drawing just 200 points should really not cause any performance problems, even at the highest quality setting. Using you code, I can draw around 40000 points in one second on my system.
Assuming that SelectedPoints is Point[] or List<Point> or some other efficient type, I would suspect the FontFacade.Large call. Is a new Font instance created each time around?
EDIT:
Running your modified code using new Font("Arial", 8) on 200 points takes around 20 milliseconds on my system, so there have to be something else that is causing your problems. How long does it take to run the code on your system?
Stopwatch timer = Stopwatch.StartNew();
[...]
Debug.WriteLine(timer.ElapsedMilliseconds);
The created font objects should be disposed when done, I would also move it outside the loop so that only one instance is created although that does not seem to be the source of your problems.
using(Font font = new Font("Arial", 8))
{
foreach(var point = SelectedPoints)
{
[...]
}
}
What dimensions is the OriginalBitmap, and what is it's PixelFormat?
What type is SelectedPoints?
In my project, I'm using (uncompressed 16-bit grayscale) gigapixel images which come from a high resolution scanner for measurement purposes. Since these bitmaps can not be loaded in memory (mainly due to memory fragmentation) I'm using tiles (and tiled TIFF on disc). (see StackOverflow topic on this)
I need to implement panning/zooming in a way like Google Maps or DeepZoom. I have to apply image processing on the fly before presenting it on screen, so I can not use a precooked library which directly accesses an image file. For zooming I intend to keep a multi-resolution image in my file (pyramid storage). The most useful steps seem to be +200%, 50% and show all.
My code base is currently C# and .NET 3.5. Currently I assume Forms type, unless WPF gives me great advantage in this area. I have got a method which can return any (processed) part of the underlying image.
Specific issues:
hints or references on how to implement this pan/zoom with on-demand generation of image parts
any code which could be used as a basis (preferably commercial or LGPL/BSD like licenses)
can DeepZoom be used for this (i.e. is there a way that I can provide a function to provide a tile at the right resulution for the current zoom level?) ( I need to have pixel accurate addressing still)
This CodeProject article: Generate...DeepZoom Image Collection might be a useful read since it talks about generating a DeepZoom image source.
This MSDN article has a section Dynamic Deep Zoom: Supplying Image Pixels at Run Time and links to this Mandelbrot Explorer which 'kinda' sounds similar to what you're trying to do (ie. he is generating specific parts of the mandelbrot set on-demand; you want to retrieve specific parts of your gigapixel image on-demand).
I think the answer to "can DeepZoom be used for this?" is probably "Yes", however as it is only available in Silverlight you will have to do some tricks with an embedded web browser control if you need a WinForms/WPF client app.
Sorry I can't provide more specific answers - hope those links help.
p.s. I'm not sure if Silverlight supports TIFF images - that might be an issue unless you convert to another format.
I decided to try something myself. I came up with a straightforward GDI+ code, which uses the tiles I've already got. I just filter out the parts which are relevant for current clipping region. It works like magic! Please find my code below.
(Form settings double buffering for the best results)
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics dc = e.Graphics;
dc.ScaleTransform(1.0F, 1.0F);
Size scrollOffset = new Size(AutoScrollPosition);
int start_x = Math.Min(matrix_x_size,
(e.ClipRectangle.Left - scrollOffset.Width) / 256);
int start_y = Math.Min(matrix_y_size,
(e.ClipRectangle.Top - scrollOffset.Height) / 256);
int end_x = Math.Min(matrix_x_size,
(e.ClipRectangle.Right - scrollOffset.Width + 255) / 256);
int end_y = Math.Min(matrix_y_size,
(e.ClipRectangle.Bottom - scrollOffset.Height + 255) / 256);
// start * contain the first and last tile x/y which are on screen
// and which need to be redrawn.
// now iterate trough all tiles which need an update
for (int y = start_y; y < end_y; y++)
for (int x = start_x; x < end_x; x++)
{ // draw bitmap with gdi+ at calculated position.
dc.DrawImage(BmpMatrix[y, x],
new Point(x * 256 + scrollOffset.Width,
y * 256 + scrollOffset.Height));
}
}
To test it, I've created a matrix of 80x80 of 256 tiles (420 MPixel). Of course I'll have to add some deferred loading in real life. I can leave tiles out (empty) if they are not yet loaded. In fact, I've asked my client to stick 8 GByte in his machine so I don't have to bother about performance too much. Once loaded tiles can stay in memory.
public partial class Form1 : Form
{
bool dragging = false;
float Zoom = 1.0F;
Point lastMouse;
PointF viewPortCenter;
private readonly Brush solidYellowBrush = new SolidBrush(Color.Yellow);
private readonly Brush solidBlueBrush = new SolidBrush(Color.LightBlue);
const int matrix_x_size = 80;
const int matrix_y_size = 80;
private Bitmap[,] BmpMatrix = new Bitmap[matrix_x_size, matrix_y_size];
public Form1()
{
InitializeComponent();
Font font = new Font("Times New Roman", 10, FontStyle.Regular);
StringFormat strFormat = new StringFormat();
strFormat.Alignment = StringAlignment.Center;
strFormat.LineAlignment = StringAlignment.Center;
for (int y = 0; y < matrix_y_size; y++)
for (int x = 0; x < matrix_x_size; x++)
{
BmpMatrix[y, x] = new Bitmap(256, 256, PixelFormat.Format24bppRgb);
// BmpMatrix[y, x].Palette.Entries[0] = (x+y)%1==0?Color.Blue:Color.White;
using (Graphics g = Graphics.FromImage(BmpMatrix[y, x]))
{
g.FillRectangle(((x + y) % 2 == 0) ? solidBlueBrush : solidYellowBrush, new Rectangle(new Point(0, 0), new Size(256, 256)));
g.DrawString("hello world\n[" + x.ToString() + "," + y.ToString() + "]", new Font("Tahoma", 8), Brushes.Black,
new RectangleF(0, 0, 256, 256), strFormat);
g.DrawImage(BmpMatrix[y, x], Point.Empty);
}
}
BackColor = Color.White;
Size = new Size(300, 300);
Text = "Scroll Shapes Correct";
AutoScrollMinSize = new Size(256 * matrix_x_size, 256 * matrix_y_size);
}
Turned out this was the easy part. Getting async multithreaded i/o done in the background was a lot harder to acchieve. Still, I've got it working in the way described here. The issues to resolve were more .NET/Form multithreading related than to this topic.
In pseudo code it works like this:
after onPaint (and on Tick)
check if tiles on display need to be retrieved from disc
if so: post them to an async io queue
if not: check if tiles close to display area are already loaded
if not: post them to an async io/queue
check if bitmaps have arrived from io thread
if so: updat them on screen, and force repaint if visible
Result: I now have my own Custom control which uses roughly 50 MByte for very fast access to arbitrary size (tiled) TIFF files.
I guess you can address this issue following the steps below:
Image generation:
segment your image in multiple subimages (tiles) of a small resolution, for instace, 500x500. These images are depth 0
combine a series of tiles with depth 0 (4x4 or 6x6), resize the combination generating a new tile with 500x500 pixels in depth 1.
continue with this approach until get the entire image using only a few tiles.
Image visualization
Start from the highest depth
When user drags the image, load the tiles dynamically
When the user zoom a region of the image, decrease the depth, loading the tiles for that region in a higher resolution.
The final result is similar to Google Maps.