Winforms Print Preview Blurry - winforms

I have a print preview that displays a captured panel on a form 'Panel1.DrawToBitmap(memoryImage, bounds);'
I also save the image to my hard drive - 'memoryImage.Save("diary.png")'
The image in the print preview at any zoom level is blurry, the saved image is perfect (viewed in windows photo viewer & PS).
Id like the print preview to be as good as the saved image, any ideas?
here's the code:-
private void CaptureScreen()
{
int x = splitContainerDiary.Location.X;
int y = splitContainerDiary.Location.Y;
int SCwidth = splitContainerDiary.Panel1.Width;
int SCheight = splitContainerDiary.Panel1.Height;
Rectangle bounds = new Rectangle(x, y, SCwidth, SCheight);
memoryImage = new Bitmap(SCwidth, SCheight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
splitContainerDiary.Panel1.DrawToBitmap(memoryImage, bounds);
memoryImage.Save("diary.png");
}
private void printDocumentDiary_PrintPage(object sender, PrintPageEventArgs e)
{
CaptureScreen();
Font HeaderFont = new Font("Consolas", 16, FontStyle.Bold);
e.Graphics.DrawString(selectedYear.ToString() + " - " + name, HeaderFont, Brushes.Black, 15, 15);
e.Graphics.DrawImage(Image.FromFile("diary.png"), 5, 5);
// e.Graphics.DrawImage(memoryImage, 0, 40);
PrintDoodle(e);
}
I have tried to draw the image from memory (e.Graphics.DrawImage(memoryImage, 0, 40) and also from the saved image 'e.Graphics.DrawImage(Image.FromFile("diary.png"), 5, 5);' They are both blurry in print preview.
I have tried different Pixel formats with no joy either.
I have tried saving the image as BMP, JPG, PNG with no joy either (when drawing image fromFile).
I have tried using BitBlt routine also with the same results.
Tino

This is an inevitable consequence of the dramatic difference between the device resolution of a printer vs a monitor. A printer typically can print with a resolution of 600 dots per inch. A monitor is typically set to 96 DPI. So when you print an image that's razor sharp on a monitor, each pixel of the image requires printing a blob of 6 x 6. Short from the blockiness this produces, anything that's drawn on screen with anti-aliasing will get those anti-aliasing pixels drawn 6 times larger as well. Completely ruining the effect. This is especially noticeable with any text that's drawn with ClearType anti-aliasing. The red and blue fringes become very noticeable on paper.
You can partly solve this by drawing the image one-to-one on the printer, ensuring that 1 pixel in the image becomes 1 pixel on paper. That ought to now look nice and sharp (minus the ClearType problem) but you'll be looking at a postage stamp. Growing your arms six times longer would have the same effect.
Well, this just doesn't work well. Use the PrintDocument class so you can draw stuff to the printer using its native resolution. Use the methods provided by e.Graphics in the PrintPage event handler. Avoid images unless they are photos, anything that doesn't have finely detailed line art will scale well.

I have encountered a similar "blurry font" problem, while trying to print out some custom text, which I've pre-arranged as Labels in the TableLayoutPanel.
My solution for the blurriness was as follows: I have created a panel and labels four times as big as the desired final size (by using font 44 instead of 11, and using width and height four times greater).
Then I've created a (large) bitmap, and downscaled it in the final step (DrawImage):
using (var bmp = new Bitmap(tableLayout.Width, tableLayout.Height))
{
tableLayout.DrawToBitmap(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height));
printPageEventArgs.Graphics.DrawImage(
bmp,
printPageEventArgs.MarginBounds.X,
printPageEventArgs.MarginBounds.Y,
bmp.Width / 4,
bmp.Height / 4);
}
The resulting text looks much sharper both in the preview and in the actual printed page.
Of course, such an approach can only work if you can manipulate the Control's size, for example by creating it "off screen". But it will not work if you require the actual displayed control to be printed.

Related

How can images taking up the whole screen be taken on Android and iPhone?

I am looking to take photos on mobile that can cover the whole screen, which is done in the Snapchat app. Images taken by Android and iPhone only cover a part in the center of the screen (probably intented so that that menu bars could cover the rest).
If you take an image in snapchat - and export it to your camera roll, you will see the image remains enlarged - covering the whole screen. That is how I would like my photos being taken. (Right now they become deformed as the images are too small - as I try to display them using the whole screen)
I am using Gluon and the PicturesService: http://docs.gluonhq.com/charm/javadoc/4.1.0-SNAPSHOT/index.html?com/gluonhq/charm/down/plugins/PicturesService.html
However I assume you need to adjust things in deeper layers to acheive this. Could someone point me in the right direction?
The camera aspect ratio and the screen aspect ratio on iOS differ. That is why you cannot display the image fully and at the same time avoid deformation. However in video it matches the screen aspect ratio. So I thought Snapchat somehow took screenshots, or one single frame out of the camera in video mode.
However, looking at my Android phone - the camera on video mode did not display on the whole screen (no matching aspect ratio). I was suprised. I downloaded snapchat on it and found the snaps still covered the whole screen - and I was even more suprised.
Then I started realizing the snap was cut, at the sides. So what they do is to simply cut the original image until it matches the aspect ratio of the display.
The following worked for me to cut Android images:
public Image cutImageFit(Scene scene, Image image){
PixelReader pixelReader = image.getPixelReader();
double sceneRatio = scene.getWidth() / scene.getHeight();
double width = image.getWidth();
double height = image.getHeight();
double ratio = width / height;
// System.out.println("Scene is " + scene.getWidth() + " and " + scene.getHeight());
// System.out.println("sceneRatio " + sceneRatio);
// System.out.println("ratio is " + ratio);
if(sceneRatio > 1){
double cutTo = width / sceneRatio;
height = cutTo;
}
else if(sceneRatio < 1){
double cutTo = height * sceneRatio;
width = cutTo;
}
else{
// Square scene...
}
Double d = width;
int w = d.intValue();
d = height;
int h = d.intValue();
System.out.println("new width " + w + " and height " + h + " for the image");
WritableImage writableImage = new WritableImage(pixelReader, 0, 0, w, h);
return writableImage;
}
... You can could also edit the method so that it cuts both sides, instead of only one. This was helpful: Effective image cropping in JavaFX
However, when it comes to iOS images I am having problems. The image has to be rotated. If it was taken with iphone vertically that is. I would be glad if someone could help out with that!
This latter problem I could solve by rotating the image with the imageView and then taking a javafx snapshot, to pas as image parameter with the method instead of the iOS original image. That way the local cordinate system also stays "in sync" with the rest of the GUI cordinalts in ate system after rotating. So in effect a relabeling of the x and y - axises is done.
Edit: The android defualt camera actually contained a "Fullscreen" setting of which I was not aware of. So that setting simply have to be selected. It results in a white area however of which I think comes from that the app cannot display onto the statusbar area. Everything looks fine in the other default photo viewer apps are able to do (displaying image beneath status bar). So either the image can be cropped, using this method above - or a FullscreenService can be create to display the taken image, using a independant seperate Android layer, suggested by José in the comments to the question.
When it comes to iOS it might be possible to use camera third party apps, that does this - or edit the IOSPicturesService of which have native ObjectiveC code connected to it.

WPF: Get 1:1 pixel rendering in Image whose size is modified with a LayoutTransform

Let me start by saying I have searched extensively on this and have found partial answers, but nothing that works all the way.
I need to display bitmap images in my WPF application that are not scaled. I want to map 1 pixel of the bitmap to 1 pixel of the display. I do intend to support multiple resolutions by shipping multiple versions of my bitmaps. But I want to know that, when a particular bitmap has been chosen, it will be rendered EXACTLY as it has been designed.
My strategy for overcoming the automatic scaling that happens in WPF is to look at what is being applied automatically (by virtue of the OS DPI setting), and then apply a LayoutTransform that is the inverse, to the outermost container of my window.
This ensures that, no matter what the user's DPI settings are, the app renders the contents of the window a 1:1 ratio of WPF pixels to hardware pixels. So far, so good.
That code looks like this. (Presume this is called with an argument of 1.0).
private void SetScale(double factor)
{
// First note the current window transform factor.
// This is the factor being applied to the entire window due to OS DPI settings.
Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
double currentWindowTransformFactorX = m.M11;
double currentWindowTransformFactorY = m.M22;
// Now calculate the inverse.
double currentWindowTransformInverseX = (1 / m.M11);
double currentWindowTransformInverseY = (1 / m.M22);
// This factor will put us "back to 1.0" in terms of a device-independent-pixel to physical pixel mapping.
// On top of this, we can apply our caller-specified factor.
double transformFactorX = currentWindowTransformInverseX * factor;
double transformFactorY = currentWindowTransformInverseY * factor;
// Apply the transform to the registered target container
ScaleTransform dpiTransform = new ScaleTransform(transformFactorX, transformFactorY);
if (dpiTransform.CanFreeze)
dpiTransform.Freeze();
this.pnlOutermost.LayoutTransform = dpiTransform;
}
Up to here, everything works great. No matter what I set my Windows DPI to, the contents of that main container are always exactly the same size, and the bitmaps are rendered precisely.
Now comes the fun part. I want to support different screen resolutions by providing resolution-specific artwork, and scaling my entire UI as appropriate.
It turns out that LayoutTransform works really well for this. So if I call the above method with 1.25 or 1.5 or whatever, the entire UI scales and everything looks perfect...except my images, which are back to looking stretched and crappy, even when I change the source to be an image that is exactly the right size for the new, scaled dimensions.
For example, suppose I have an image that is 100x100 in the XAML. My artwork comes in three flavors: 100x100, 125x125, and 150x150. When I scale the container that houses the image, I also change the source of that image to the appropriate one.
Interestingly, if the image object is sitting at a position that, when scaled by the factor, yields integral results, then the scaled image looks fine. That is to say, suppose the image has the following properties:
Canvas.Left = 12
Canvas.Top = 100
When we apply a factor of 1.25, this yields 15 and 125, and the image looks great. But if the image is moved by one pixel, to say:
Canvas.Left = 13
Canvas.Top = 100
Now when we apply a factor of 1.25, we get 15.25 and 125, and the result looks crappy.
Clearly, this looks like some kind of rounding issue or something like that. So I've tried:
UseLayoutRounding="True"
SnapsToDevicePixels="True"
RenderOptions.EdgeMode="Aliased"
RenderOptions.BitmapScalingMode="NearestNeighbor"
I've tried these in the window, in the container being scaled, and in the image object. And nothing works. And the BitmapScalingMode doesn't really make sense anyway, because the image should not be being scaled at all.
Eternal thanks to anyone who can shed some light on this.
I had the exact same problem so it looks like this has not been fixed in the framework as of 2019.
I managed to solve the issue using a three step approach.
Enable layout rounding on my top level UI element
<UserControl ... UseLayoutRounding="True">
Apply the inverse LayoutTransform to my Imageobjects (the LayoutTransformwas applied to the parent ListBox).
<Image ... LayoutTransform="{Binding Path=LayoutTransform.Inverse,
Mode=OneTime,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}}">
Subclass Imageand add a custom override for OnRender.
internal class CustomImage: Image {
private PresentationSource presentationSource;
public CustomImage() => Loaded += OnLoaded;
protected override void OnRender(DrawingContext dc) {
if (this.Source == null) {
return;
}
var offset = GetOffset();
dc.DrawImage(this.Source, new Rect(offset, this.RenderSize));
}
private Point GetOffset() {
var offset = new Point(0, 0);
var root = this.presentationSource?.RootVisual;
var compositionTarget = this.presentationSource?.CompositionTarget;
if (root == null || compositionTarget == null) {
return offset;
}
// Transform origin to device (pixel) coordinates.
offset = TransformToAncestor(root).Transform(offset);
offset = compositionTarget.TransformToDevice.Transform(offset);
// Round to nearest integer value.
offset.X = Math.Round(offset.X);
offset.Y = Math.Round(offset.Y);
// Transform back to local coordinate system.
offset = compositionTarget.TransformFromDevice.Transform(offset);
offset = root.TransformToDescendant(this).Transform(offset);
return offset;
}
private void OnLoaded(object sender, RoutedEventArgs e) {
this.presentationSource = PresentationSource.FromVisual(this);
InvalidateVisual();
}
}
}
The code from step 3 is based on this blogpost.
By using the CustomImage class in my XAML instead of Image and binding to a BitmapSource that will return a properly sized image based on the current scale factor, I managed to achieve great looking images without any unwanted scaling.
Note that you might need to call InvalidateVisual on your images when they need to be re-rendered.

ComponentOne for WPF: Text seems to render in the incorrect position?

I’m running into problems when rendering text on my document. Specifically, the text renders too low. I tried filling a rectangle behind the text to see what happens, and I discovered that they appear to render slightly offset:
Here’s the code I used to render the box and text:
_doc.FillRectangle(Colors.LightGray, 36, 72, 37.344, 9);
_doc.DrawString("Lorem", new Font("Arial", 12), Colors.Black,
new Rect(36, 72, 37.344, 9));
I know that the height of the rectangle (9) doesn’t appear to match the height of the font (12), which I thought might have been the problem at first. However, I then did a MeasureString on the font itself and discovered that its height was actually 9 rather than 12 (I used the immediate window for this, which is why it's a pic and not a text block):
Any ideas as to what could be causing it and how to avoid it?
Thanks!
-Ari
There are couple of posts that discuss the WPF text rendering inconsistencies.
One of the other posts: WPF Text rendering problem, stated that SnapToDevicePixels could ruin text rendering if text has been resized to display across pixels. The suggested answer was to keep,
SnapToDevicePixels = True on borders/backgrounds but turn it off for text elements.
As for the current method your are using. Please take a look at one of my earliers posts: Increase bar chart values with button clicks : I have used DrawString() to add a letter within a rectangle. All drawing is done in a Panel.
code:
...
panel1.Paint += new PaintEventHandler(panel1_Paint);
using (Graphics g = this.panel1.CreateGraphics())
{
Brush brush = new SolidBrush(Color.Green);
g.FillRectangle(brush, px, py, 20, 20);
Pen pen = new Pen(new SolidBrush(Color.White));
g.DrawRectangle(pen, px, py, 20, 20);
//add each total5Click into chart block
g.DrawString((total5Times).ToString(), new Font("Arial", 7),
new SolidBrush(Color.AntiqueWhite),
px + 1, py+8, StringFormat.GenericDefault);
pen.Dispose();}
...
I would suggest using the method DrawString Method (String, Font, Brush, RectangleF, StringFormat) and supplying the String Format. After reviewing ComponentOne it appears they are putting together several methods so I may be an issue with the StringFormat default set for the method. I am kind of assuming they are calling the main DrawString method and passing in default params if one was not supplied.
Also be sure to check the section for
Use LineAlignment to specify the vertical alignment of the string.
in the link below
Link to Method
Well, after further research and experimentation there's definitely a bug in the ComponentOne library. Specifically, the overload I happened to have used here returned the wrong hight. If you specific an available width explicitly, you get the correct height. Specifically, this code generates the correct data:
var resultHeight = _doc.MeasureString(text, pdfFont, double.MaxValue).Height;
var resultWidth = _doc.MeasureString(text, pdfFont).Width;
return new Tuple<double,double>(resultHeight, resultWidth);
Note the addition of the third parameter for the height only -- double.MaxValue. The width is correctly calculated in both cases, but the height is only correctly calculated if you provide that double parameter. I chose double.MaxValue in this case simply because I don't know how wide the string is going to turn out to be so I don't want to risk being given a multi-line height.

Drawing to a bitmap from a WPF canvas

I have a canvas that contains an Image in which I dislay an existing BMP. I draw rectangles on the canvas and add these to the Children colllection. When I click save, I want to update the underlying BMP file.
The following code works, but the rectangle that gets drawn to the BMP is way smaller than what I drew. I guess there's some difference in the co-ordinates? Maybe I shouldn't be using System.Drawing?
using (Graphics g = Graphics.FromImage(image))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
foreach (var child in canvas.Children)
{
if (child is System.Windows.Shapes.Rectangle)
{
var oldRect = child as System.Windows.Shapes.Rectangle;
// need to do something here to make the new rect bigger as the scale is clearly different
var rect = new Rectangle((int)Canvas.GetLeft(oldRect), (int)Canvas.GetTop(oldRect), (int)oldRect.Width, (int)oldRect.Height);
g.FillRectangle(Brushes.Black, rect);
}
}
... code to save bmp
All suggestions welcome!
Thanks
Try using the System.Windows.Media.Imaging.RenderTargetBitmap Class (an example here).
Wpf uses Device Independent Graphics so you have to compensate for the DPI :
RenderTargetBitmap bmp = new RenderTargetBitmap((int)Canvas1.Width, (int)Canvas1.Height, 96, 96, PixelFormats.Default);
bmp.Render(Canvas1);
From Third Link:
There are two system factors that determine the size of text and graphics on your screen: resolution and DPI. Resolution describes the number of pixels that appear on the screen. As the resolution gets higher, pixels get smaller, causing graphics and text to appear smaller. A graphic displayed on a monitor set to 1024 x 768 will appear much smaller when the resolution is changed to 1600 x 1200.

How can I create beveled corners on a border in WPF?

I'm trying to do simple drawing in a subclass of a decorator, similar to what they're doing here...
How can I draw a border with squared corners in wpf?
...except with a single-pixel border thickness instead of the two they're using there. However, no matter what I do, WPF decides it needs to do its 'smoothing' (e.g. instead of rendering a single-pixel line, it renders a two-pixel line with each 'half' about 50% of the opacity.) In other words, it's trying to anti-alias the drawing. I do not want anti-aliased drawing. I want to say if I draw a line from 0,0 to 10,0 that I get a single-pixel-wide line that's exactly 10 pixels long without smoothing.
Now I know WPF does that, but I thought that's specifically why they introduced SnapsToDevicePixels and UseLayoutRounding, both of which I've set to 'True' in the XAML. I'm also making sure that the numbers I'm using are actual integers and not fractional numbers, but still I'm not getting the nice, crisp, one-pixel-wide lines I'm hoping for.
Help!!!
Mark
Aaaaah.... got it! WPF considers a line from 0,0 to 10,0 to literally be on that logical line, not the row of pixels as it is in GDI. To better explain, think of the coordinates in WPF being representative of the lines drawn on a piece of graph paper whereas the pixels are the squares those lines make up (assuming 96 DPI that is. You'd need to adjust accordingly if they are different.)
So... to get the drawing to refer to the pixel locations, we need to shift the drawing from the lines themselves to be the center of the pixels (squares on graph paper) so we shift all drawing by 0.5, 0.5 (again, assuming a DPI of 96)
So if it is a 96 DPI setting, simply adding this in the OnRender method worked like a charm...
drawingContext.PushTransform(new TranslateTransform(.5, .5));
Hope this helps others!
M
Have a look at this article: Draw lines exactly on physical device pixels
UPD
Some valuable quotes from the link:
The reason why the lines appear blurry, is that our points are center
points of the lines not edges. With a pen width of 1 the edges are
drawn excactly between two pixels.
A first approach is to round each point to an integer value (snap to a
logical pixel) an give it an offset of half the pen width. This
ensures, that the edges of the line align with logical pixels.
Fortunately the developers of the milcore (MIL stands for media
integration layer, that's WPFs rendering engine) give us a way to
guide the rendering engine to align a logical coordinate excatly on a
physical device pixels. To achieve this, we need to create a
GuidelineSet
protected override void OnRender(DrawingContext drawingContext)
{
Pen pen = new Pen(Brushes.Black, 1);
Rect rect = new Rect(20,20, 50, 60);
double halfPenWidth = pen.Thickness / 2;
// Create a guidelines set
GuidelineSet guidelines = new GuidelineSet();
guidelines.GuidelinesX.Add(rect.Left + halfPenWidth);
guidelines.GuidelinesX.Add(rect.Right + halfPenWidth);
guidelines.GuidelinesY.Add(rect.Top + halfPenWidth);
guidelines.GuidelinesY.Add(rect.Bottom + halfPenWidth);
drawingContext.PushGuidelineSet(guidelines);
drawingContext.DrawRectangle(null, pen, rect);
drawingContext.Pop();
}

Resources