I use Core Text to draw text to an offscreen bitmap context using CTLineDraw(). The bitmap is then processed internally before it is drawn to my window.
The problem here is that bitmap contexts aren't scaled on Retina Macs. Thus, on a Retina Mac, the text is still drawn at 72dpi to the bitmap but it should be drawn in 144dpi of course, because the pixel density is twice as high. Thus, the text currently looks blurry because it is drawn at 72dpi to the offscreen bitmap and this bitmap is then scaled when it is drawn to the window.
What is the best way to make Core Text Retina-aware in this context? Should I simply pass a transformation matrix to CTFontCreateWithName() that contains the screen's backingScaleFactor in its scale coefficients? That does look a little hackish, though. That's why I'm asking for some feedback or a better idea...
Related
So I followed a few tutorials on how to draw on window using windows.h library, the part of the tutorial that I don't really understand is the bitmap part. They used createbitmap() and StrechBit() functions to draw on window. Do window references bitmap to draw pixels on the screen accordingly and bitmap is basically a chunk of memory large enough to store pixel's position and color value. If so, does bitmap automatically generate every time you created a window, because it seems that you don't really need to declare bitmap or use createbitmap() functions to type word on the window you created, you only need to create bitmap when you want to draw a custom pixel.
A window will receive the WM_PAINT message when it needs to be painted. This can happen because InvalidateRect was called, the window was resized etc.
Where the pixels are stored ("in" the HWND) is an implementation detail you don't have to worry about. On some versions/configurations the GDI functions are hardware accelerated and the result might be stored directly in the GPU, in others everything might be implemented in software and run on the CPU. When using a layered window I'm guessing everything older than Vista will use an internal bitmap to store the pixels.
GDI/GDI+ is the classic way to draw windows. If you need per-pixel alpha transparency you would draw to a bitmap and call UpdateLayeredWindow, otherwise you would just draw using any GDI function you want in WM_PAINT. This might include drawing one or several bitmaps, text, and lines/curves directly to the HWNDs HDC. As this can cause flicker in certain cases (if any area is drawn to more than once in one paint cycle), so people might draw to their own bitmap first and then BitBlt this bitmap to the window, this is called double-buffering.
The new way to draw is Direct2d/DirectComposition.
I have a MFC application with toolbars (using CMFCToolbar). I create the toolbar bitmap on the fly using bitmaps from files and resources. The DIBs have different color formats.
So I create an empty bitmap toolbar image compatible to screen DC.
Then I open all the bitmaps and blit the content to the toolbar bitmap (GDI does colorspace conversion and stretching for me).
Then I save the bitmap to a 24-bit DIB file.
Then I create the toolbar object and load the image.
That has worked for ages and is working now except for one case:
Recently we had to enable GDI scaling for Windows 10 1703 and later.
On a system with high resolution display and 200% scaling (like Surface) the following effect occurs:
All toolbar icons are distorted.
I also found the reason:
When saving the composed image I only get the top-left quarter of the image.
Width and height of the bitmap did not change (say 1024x15) compared to normal resolution display without GDI scaling. But that bitmap only contains the pixels of the top-left quarter (see example below).
So I assume the device context tells Windows about 200% scaling. When blitting from source to target the image gets scaled up automatically but the dimension of the bitmap does not change.
How can I save the unscaled bitmap?
-or-
How can I correctly save the scaled bitmap? Where to get the missing pixels? Where to get proper dimensions? (HBITMAP refers only to unscaled dimensions).
Example:
no GDI scaling, correct:
200% scaling, same dimensions, but only top-left quarter of the correct image:
Summary and solution:
Let's say we create a memory bitmap compatible to screen format (DDB):
CBitmap toolBitmap;
toolBitmap.CreateCompatibleBitmap (pDC, 1000, 20);
Later we blit something into the memory bitmap (does not matter here). Now we want to save the bitmap (write as DIB to file).
Although we know the dimensions (here: 1000x20) we should not use them. Because on Window 10 and process has GDI scaling activated and high resolution display with scaling is used - the dimensions might have changed internally. Thus the bitmap is not 1000x20 anymore.
This one fails:
BITMAP bmHdr;
toolBitmap.GetObject(sizeof(BITMAP), &bmHdr);
The Bitmap header contains the original dimensions (1000x20). Using them for saving to file results in incomplete image. Only upper left part will be stored.
This one works - we can retrieve scaled dimensions:
BITMAPINFO bi = {};
bi.dwSize = sizeof(bi);
int result = GetDIBits(pDC->GetSafeHdc(), (HBITMAP)toolBitmap.GetSafeHandle(), 0, 0, NULL, &bi, DIB_RGB_COLORS);
Now we can proceed with new dimensions.
I ended up using GDI+ functions, which also save the complete (scaled) bitmap:
Gdiplus::Bitmap bm((HBITMAP)toolBitmap.GetSafeHandle(), NULL);
Gdiplus::Status status = bm.Save(pwszFileName, &clsidEncoder, NULL);
I presume there are tons of old MFC and GDI code which will not work correctly with activated GDI scaling on Windows 10.
How does the TextSize property on an SKPaint object relate to the 'standard' Xamarin Forms FontSize?
In the image you can see the difference between size 40 on a label and as painted. What would I need to do to make them the same size?
As #hankide mentioned, it has to do with the fact that the native OS has scaling for UI elements so the app "looks the same size" on different devices.
This is great for buttons and all that as the OS is drawing them. So if the button is bigger, the OS just scales up the text. However, with SkiaSharp, we have no idea what you are drawing so we can't do any scaling. If we were to scale, the image would become blurry or pixelated on the high resolution screens.
One way to get everything the same size is to do a global scale before drawing anything:
var scale = canvasWidth / viewWidth;
canvas.Scale(scale);
And this is often good enough, but sometimes you really want to draw items differently on a high resolution screen. An example would be a tiled background. Instead of stretching the image on a bigger canvas, you may want to just tile it - preserving the pixels.
In the case of this question, you can either scale the entire canvas before drawing, or you can just scale the text:
var paint = new SKPaint {
TextSize = 40 * scale
};
This way, the text size is increased, but the rest of the drawing is on a larger canvas.
I have an example on GitHub: https://github.com/mattleibow/SkiaSharpXamarinFormsDemo
This compares Xamarin.Forms, SkiaSharp and Native labels. (They should all be exactly the same size)
I think that the problem is in the way Xamarin.Forms handles font sizes. For example on Android, you could define the font size in pixels (px), scale-independent pixels (sp), inches (in), millimeters and density-independent pixels (dp/dip).
I can't remember how Xamarin.Forms handles the sizes (px,sp or dp) but the difference you see here is because of that. What you could do, is create an Effect that changes the font size handling on the native control and try to match the sizing provided by SkiaSharp.
I'm trying to simulate the scroll pane in HTML canvas.
Maybe font rendering is much slower than bitmap rendering.
I tested it on my phone and PC.
Anyone has a comparison between them?
Yes, font rendering would be slower.
At it's base, html canvas displays pixels so drawing a bitmap is just straightforward copying each bitmap pixel to a canvas pixel (blitting).
Drawing text involves looking up the glyph for each letter, scaling that glyph, positioning that glyph relative to other letters and then finally painting the glyph as pixels on the canvas. Much more involved than copying bitmap pixels.
My application draws some images using cairo like this:
cairo_set_source_surface(cr, _page_down_icon, icon_x, y);
cairo_paint(cr);
where the page down icon is a png I loaded via cairo_image_surface_create_from_png.
This works fine on standard screens but produces a low quality image on retina displays. So I'm thinking of having a second image with double resolution (as it is usual for NSImage). However, if I just draw this image the result is twice as large as the standard image. So my question is: how would I draw the highres image with cairo on a retina display so that it looks crisp?
cairo_scale is your friend. With this method you can adjust the scaling of the axes of your surface. In order to get the result you want youd scale by 0.5 for your second image (not that you'll have to adjust the targetposition for the image as well!).