Vision2 and DPI aware applications on UHD monitors

63 views
Skip to first unread message

Finnian Reilly

unread,
Mar 9, 2018, 1:20:56 PM3/9/18
to Eiffel Users


Lately I have connected my Windows 10 test machine to an UHD 4K monitor and set the resolution to "recommended" i.e. 3840 x 2160. So that my Desktop icons etc don't look absolutely tiny, I set the custom scaling in the display settings to 200%. Doing this however has opened a real can of worms.

Before I explain further I should backtrack a little and explain that my application My Ching is designed to be both DPI aware and user display size aware. All font and box sizes are expressed in centimeters.  All the graphical assets for buttons etc. are generated from SVG to an exact size in centimeters independent of display resolution during a once-off graphic initialization. This works well for my development monitor which is 1920 x 1200. For smaller laptop screens that do not comfortably allows for the design size of the application window, there is a down-scaling. This also works well.

However using a 4K monitor, the first thing I noted was that the fonts were pixellated and also the graphical assets were not being generated at the expected resolution. It indicates that Windows was pretending to the application that the monitor was a lower resolution then the actual. The solution to this problem was to configure at compile time the application to be DPI per monitor aware. Now the text display fonts and graphical assets appear as I would expect at 4K. But now I have a different problem. Any Vision2 component that is not `fontable' looks absolutely tiny. I have tried fiddling around with the compatibility settings in the app launcher properties but it is impossible to have both normal looking menus and radio buttons, without affecting the per application screen resolution. Anything less than full resolution, negatively impacts the graphical assets and text font appearance. So this has turned into a conundrum.

It might be that the only solution will be to over-ride how Vision2 and the docking library handles tabs, menus and radio buttons. I have had a cursory look at the code for how menu are sized. It may not be easy to fix.

Any ideas on how to avoid a lot of work on a Vision2 over-ride are appreciated.

Finnian


Finnian Reilly

unread,
Mar 9, 2018, 1:28:40 PM3/9/18
to Eiffel Users
I should mention that I am using the Windows SDK Ver 7.1 with the `/win7' compiler switch. The dev machine is Windows 7. The binary works OK with Windows 10 even without setting any compatibility modes. I am stuck with this version as it allows me to link to a Cairo 2D graphics library that was probably built using mingw. This is essential for SVG rendering and for doing graphical operations with anti-aliasing and transparencies. The latest compiler breaks this.

Emmanuel Stapf

unread,
Mar 10, 2018, 6:53:06 PM3/10/18
to eiffel...@googlegroups.com

Once upon a day, Microsoft Windows had something wonderful that allowed you to adjust the font DPI. Typically, you could use small font or big font. If an application was programmed correctly, it would work properly, if not, you would get text truncated in tiny dialog boxes/windows. Because at Eiffel Software we were heavy users of big font (at the time we had 21” CRT monitors where everything would be blurry in small font, and you had really no choice but to use big font to be able to read the text), we made sure that EiffelVision would handle this well and it did.

 

Then came Windows 8/10 and Microsoft decided that this existing technology was not good enough and created something of a mess. We understand why but still they could have reused the existing infrastructure instead of creating another one. This requires some work on the internal of EiffelVision to support proper sizing of fonts and windows to handle them, multi-monitor support with various DPI settings. But even Microsoft doesn’t get it right. Anyone with a 4K screen at 200% and another screen at 100% has discovered that most Office apps don’t render properly.

 

I believe the quick solution is to change all the size computations involving fonts to be updated. Usually we say we want 8pt, from there we get the size in pixels of the string to display. Now, instead of asking for the size in pixels, we need to scale the initial font to match the scaling factor set for the monitor on which the text will be displayed, then can ask the underlying API to draw the text properly.

Manu

 

--
You received this message because you are subscribed to the Google Groups "Eiffel Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to eiffel-users...@googlegroups.com.
Visit this group at https://groups.google.com/group/eiffel-users.
For more options, visit https://groups.google.com/d/optout.

Jonathan Ostroff

unread,
Mar 11, 2018, 8:34:58 PM3/11/18
to 'Alexander Kogtenkov' via Eiffel Users
In the Eiffel code below, f3 stores a function with no open arguments. 

But, one cannot execute it as: r3 := f3

That is a syntax error.

One can execute the function as: r3 := f3.item ([])

I am wondering if a consistent syntax can be supported?

Thanks

Jonathan

==================================================

do_something (flag: BOOLEAN; x: INTEGER) : REAL_64
-- Return 1000*x if `flag’ holds
  do
    if flag then
  Result := x/3
  end
end

test_function_do_something : BOOLEAN
local
-- f1: FUNCTION[TUPLE[BOOLEAN,INTEGER],REAL_64]
f1: FUNCTION[BOOLEAN,INTEGER,REAL_64]
f2: FUNCTION[INTEGER, REAL_64]
f3: FUNCTION[REAL_64]
r1, r2, r3: REAL_64
do
-- store functions, but don't execute them
f1 := agent do_something(?, ?)     -- two open args
f2 := agent do_something (true, ?) -- one open arg
f3 := agent do_something (false, 7)

-- above is not executed until what follows

r1 := f1(true, 7)  -- f3.item ([true, 7])
Result := 2.333 <= r1 and r1 <= 2.334
check Result end

r2 := f2(7)
Result := 2.333 <= r2 and r2 <= 2.334
check Result end

r3 := f3.item ([])
Result := r3 = 0  -- default value
check Result end

end

Alexander Kogtenkov

unread,
Mar 12, 2018, 3:23:34 AM3/12/18
to eiffel...@googlegroups.com
Short answer. It should be OK to use any of the following
   f1.item (True, 7)
   f2.item (7)
   f3.item

Long answer. According to the standard, a formal argument list should not be empty, i.e. f3() should raise a syntax error. For backward compatibility, the compiler reports a warning instead of the error in this case, and translates f3() into f3. This goes fine with the uniform access principle and allows to treat calls like `foo` either as the calls to functions or as the calls to attributes. Unfortunately, this does not work well with agents, where f3 could mean either a reference to an agent object or a call to this agent. The first interpretation takes precedence over the second one. Adding the explicit call to `item` solves the issue.

Best regards,
Alexander Kogtenkov

Jonathan Ostroff <jonathan....@gmail.com>:

Jonathan Ostroff

unread,
Mar 12, 2018, 1:07:36 PM3/12/18
to eiffel...@googlegroups.com
Thanks for that explanation, Alexander.

Jonathan

Bertrand Meyer

unread,
Mar 12, 2018, 2:02:54 PM3/12/18
to eiffel...@googlegroups.com, me...@inf.ethz.ch

Dear Jonathan,

 

Alexander Kogtenkov has already answered but let me mention one reason  why there is no simple way to accept just `f3’ as a function call where f3 is a variable (or more generally an entity, e.g. formal argument) of an agent type. The problem is that indeed `f3’ is a variable. It cannot be both a variable and a function call! As a simple experiment, take your code (minus the assignment of r3 to Result) and change the type of r3 from REAL_64 to

 

                FUNCTION [REAL_64]

 

i.e. the same type as f3. The code compiles, as it should, since the instruction r3 := f3 is an assignment to a variable of a certain type of an expression (itself a variable) of the same type. Of course it does not do what you wanted (calling the agent, rather than just returning it), but its acceptability excludes acceptability of the “call f3” interpretation.

 

For the anecdote: Pascal fell into such a trap. The convention there (they hadn’t invented `Result’) is that the name of a function denotes the result to be returned by the function. With recursion, this is all fine for a function with arguments, where you can have an instruction such as factorial := n * factorial (n-1). The presence of an argument list distinguishes a recursive call (here on the right) from a denotation of the variable (on the left). But for an argumentless function?

 

The recent evolution of Eiffel has made it possible in most cases to “call an agent” – meaning: to call the routine associated with the agent -- in the same way that we call the associated routine. For example if p is an agent denoting a procedure r you can get the equivalent of r (arg1, …) (of course, you do not know what r is, that’s the whole idea) not only through the long form p.call ([arg1, …]) but also through simpler variants: p.call (arg1, …), with an implicit tuple, and just p (arg1, …), with an implicit call to `call’, i.e. exactly in the same way you call `r’ directly. But for argumentless functions, i.e. the case you raised, one has to retain just a trifle of the extra baggage (in the forms explained by Alexander) to avoid ambiguity.

 

Thanks for raising the issue and best regards,

 

-- Bertrand Meyer

Finnian Reilly

unread,
Mar 12, 2018, 2:57:18 PM3/12/18
to Eiffel Users

A pleasant surprise from a Windows 10 fairy

For once in my life I have been unexpectedly surprised by a Windows OS. I was doing some data gathering research to see if I could fix the problem of tiny menus and tooltips on a DPI aware application. Then a Windows 10 upgrade came through asking for a reboot. I rebooted and when I launched the application again, I was astonished. As if by magic all the menu sizes, tooltip text, scroll bar buttons etc had transformed to a sensible size. And at the same time my DPI aware fonts were all at the correct size with no pixelation, and all the graphical assets were crisp. What the hell happened?

 I updated the exe again and the problem of tiny menu fonts came back again. But once again rebooting the machine fixed it. This suggests that Windows 10 does some kind of scan on installed apps during startup and changes it's hosting behavior. Perhaps there is some kind of registry entry I can insert during the installation that will inform Windows ahead of time. I will research this.

But anyway I am very pleased that my application now displays well at 4K even if it requires a reboot after installation.










Jonathan Ostroff

unread,
Mar 12, 2018, 10:13:38 PM3/12/18
to 'Alexander Kogtenkov' via Eiffel Users
Thanks Bertrand (and Alexander) for the clear explanation. The simplified agent notation is easier to use and more elegant than the original. Its a productive step forward. 

(I do have to keep changing my teachings slides :-)

Regards,

Jonathan

Finnian Reilly

unread,
Mar 13, 2018, 10:51:02 AM3/13/18
to Eiffel Users
Hi Manu

 But even Microsoft doesn’t get it right. Anyone with a 4K screen at 200% and another screen at 100% has discovered that most Office apps don’t render properly.


I notice Microsoft are presenting UWP as a solution to the high DPI problem. (but I have lost that link) They promise it will scale well for any device. It seems the GDI api is now seen as legacy.
 

 

I believe the quick solution is to change all the size computations involving fonts to be updated. Usually we say we want 8pt, from there we get the size in pixels of the string to display. Now, instead of asking for the size in pixels, we need to scale the initial font to match the scaling factor set for the monitor on which the text will be displayed, then can ask the underlying API to draw the text properly.

 
Eiffel-Loop already has solutions already for scaling fonts and bitmaps appropriately for the real DPI calculated from the physical monitor size**. All font and bitmap size arguments are in centimeters.All font and bitmap scaling hinges on two routines from a shared instance of class EL_SCREEN. These are horizontal_pixels and vertical_pixels which take an argument in centimeters and return the dimension in pixels. But for very small displays there is a way to perform application wide scaling at a single point, so the centimeters are rendered smaller than actual centimeters. (See {EL_SCREEN}.set_dimensions)

There is also the question of including default library bitmaps. I think hard coding them like in the docking library is not a viable method for high DPI. I suggest using a wrapper for librsvg library or the Cairo library and getting the application to draw them at the required DPI. Eiffel-Loop has wrappers for both, but the binaries only work with SDK 7.1. But in theory the underlying C libraries could be compiled for later versions. These libraries work very well and have support for anti-aliasing, transparencies and opacity setting. Eiffel-Loop can render all  graphical resources at runtime from a mixture of SVG and high resolution images. But for better performance there is a disk caching mechanism.

** For Windows 10 an accurate monitor size can be found by a call to get_device_caps (dc.item, Vertical_size). Possibly works for W8 as well but I am not sure. For older Windows versions I found the information can be retrieved from the EDID registry entry, although it is only accurate to the nearest centimeter. See class EL_WEL_DISPLAY_MONITOR_INFO.

For Unix an accurate monitor size can be found by some calls to the X11 library.

Finnian

Louis M

unread,
Mar 19, 2018, 12:47:05 PM3/19/18
to Eiffel Users
Hi Finnian,


There is also the question of including default library bitmaps. I think hard coding them like in the docking library is not a viable method for high DPI. I suggest using a wrapper for librsvg library or the Cairo library and getting the application to draw them at the required DPI. Eiffel-Loop has wrappers for both, but the binaries only work with SDK 7.1. But in theory the underlying C libraries could be compiled for later versions. These libraries work very well and have support for anti-aliasing, transparencies and opacity setting. Eiffel-Loop can render all  graphical resources at runtime from a mixture of SVG and high resolution images. But for better performance there is a disk caching mechanism.

Just to let you know that I already have Cairo Wrapper (and library) and it is compatible with Vision2. I didn't test it on Windows yet (the Vision2 adapter I mean), but it should work because I'm using a EV_PIXEL_BUFFER to translate the image and not any GTK internal function.

https://github.com/tioui/Eiffel_Cairo

Good day,

Louis M

Finnian Reilly

unread,
Mar 20, 2018, 6:46:16 AM3/20/18
to Eiffel Users
Hi Louis

 I didn't test it on Windows yet (the Vision2 adapter I mean),
I have extensively tested the Eiffel-Loop Cairo wrapper on Windows and my experience was that it was not a  trivial matter to get it working. In fact it took weeks to get it right. This was because of the large number of DLL dependencies that must be managed. I developed this scons script to manage the DLLs. It downloads a large GTK archive and then extracts all the DLL's necessary for both Cairo and librsvg, then it builds the static library  stubs using MS VC, and does this for both win32 and win64. The general Eiffel-Loop build system takes care of invoking the build script and then copying the required DLL's into the application bin directory if the Vision2-x library is included in the application.
 
but it should work because I'm using a EV_PIXEL_BUFFER to translate the image and not any GTK internal function.
 
I had a look at your library. Conceptually we have a different approach. Cairo has a different conceptual model to the Vision2 drawing area model. What i have done in my wrapper is to "shoehorn" Cairo to match the Vision2 way of doing things so as a library user you are not required to deal with any Cairo concepts (or objects) but instead continue to think (as much as possible) in the familiar Vision2 model. The Cairo implementation details are completely hidden from the user. All of the Cairo functionality is accessed via EL_DRAWABLE_PIXEL_BUFFER which inherits EV_PIXEL_BUFFER.  The following initialization and conversion options are available:

create
   default_create
, make_with_pixmap, make_with_size, make_rgb_24_with_pixmap,
   make_rgb_24_with_size
, make_with_path, make_from_svg_image

feature
{NONE} -- Initialization

   make_with_size
(a_width, a_height: INTEGER)
         
--- make alpha rgb 32 bit format

   make_rgb_24_with_size
(a_width, a_height: INTEGER)
         
-- make rgb 24 bit format

   make_with_pixmap
(a_pixmap: EV_PIXMAP)
         
-- make alpha rgb 32 bit format

   make_rgb_24_with_pixmap
(a_pixmap: EV_PIXMAP)
         
-- make rgb 24 bit format

   make_with_path
(a_png_file_path: EL_FILE_PATH)
     
-- make from a PNG file

   make_from_svg_image
(svg_image: EL_SVG_IMAGE; a_background_color: EL_COLOR)

feature
-- Conversion

   to_pixmap
: EL_PIXMAP
         
-- Convert to EV_PIXMAP.

   to_rgb_24_buffer
: EL_DRAWABLE_PIXEL_BUFFER


An important difference between EL_DRAWABLE_PIXEL_BUFFER and it's ancestor EV_PIXEL_BUFFER is that the former is a 32-bit buffer and the latter a 24-bit buffer, hence the `to_rgb_24_buffer' routine. Needless to say that EL_PIXMAP inherits EV_PIXMAP.

One interesting point to mention in regard to initialization from a PNG file. I had to write a small bit of C code to swap the RED and  BLUE channels on the bitmap in order to get it work correctly with Linux. This channel swap is accessible via {EL_IMAGE_UTILS_API}.format_argb_to_abgr. Mainly this class has librsvg related routines plus a few routines to support the Cairo wrapper. (Accessible via class EL_SHARED_IMAGE_UTILS_API).

Finnian Reilly

unread,
Mar 20, 2018, 7:47:31 AM3/20/18
to Eiffel Users
Hi Louis
exploring your library further, I have to commend that it has excellent coverage of the many operations available in the Cairo library but this does require the user to get familiar with the Cairo way of doing things. The coverage in Eiffel-Loop I must confess is rather limited by comparison. Basically I was only interested in ones that would enable a particular project but has the advantage that it is easy to use for people who are used to the Vision2 way of doing things.

One other observation is that Eiffel_Cairo seems to be based purely on a statically linked implementation.  This is fine for GTK but I think this approach might very difficult to get working with the MS VC compiler. In Eiffel-Loop the Windows implementation is dynamically loadable as it seemed the less daunting approach and worked well in practice. But this did require some additional abstractions to make a common Linux/Windows API interface. See Windows EL_CAIRO_API v GTK EL_CAIRO_API

regards
Finnian

regards
Finnian

Louis M

unread,
Mar 22, 2018, 1:23:59 PM3/22/18
to Eiffel Users
Hi Finnian,


 I didn't test it on Windows yet (the Vision2 adapter I mean),
I have extensively tested the Eiffel-Loop Cairo wrapper on Windows and my experience was that it was not a  trivial matter to get it working. In fact it took weeks to get it right. This was because of the large number of DLL dependencies that must be managed. I developed this scons script to manage the DLLs. It downloads a large GTK archive and then extracts all the DLL's necessary for both Cairo and librsvg, then it builds the static library  stubs using MS VC, and does this for both win32 and win64. The general Eiffel-Loop build system takes care of invoking the build script and then copying the required DLL's into the application bin directory if the Vision2-x library is included in the application.

Interesting. I will look at it. But just to be clear. I have test Eiffel_Cairo on Windows a lots of time but not with the Vision2 adapter. In fact, I created it initially to add functionality to my Eiffel_Game2 library. So yes, I have solved all the DLL dependencies problems myself.
 
I had a look at your library. Conceptually we have a different approach. Cairo has a different conceptual model to the Vision2 drawing area model. What i have done in my wrapper is to "shoehorn" Cairo to match the Vision2 way of doing things so as a library user you are not required to deal with any Cairo concepts (or objects) but instead continue to think (as much as possible) in the familiar Vision2 model. The Cairo implementation details are completely hidden from the user. All of the Cairo functionality is accessed via EL_DRAWABLE_PIXEL_BUFFER which inherits EV_PIXEL_BUFFER.

I wanted my library to be as independent as possible (from Eiffel_Game2 or Vision2). Using the same architecture as the original Cairo library is useful because it make documentation more easy. But I also have adapt it to make it more Eiffel like.

 
 
One other observation is that Eiffel_Cairo seems to be based purely on a statically linked implementation.  This is fine for GTK but I think this approach might very difficult to get working with the MS VC compiler. In Eiffel-Loop the Windows implementation is dynamically loadable as it seemed the less daunting approach and worked well in practice. But this did require some additional abstractions to make a common Linux/Windows API interface. See Windows EL_CAIRO_API v GTK EL_CAIRO_API

I did not try it a lot, but I remember having tried it with the Visual C compiler (2012 if I am not mistaken) and it worked. But then again, I don't like to work with Visual C compiler. It does not follow the POSIX standard and force us to double all the work.I think that as a free and open source software, Eiffel Studio should adopt a free and open source compiler. But that, of course, is just my opinion.

Good day,

Louis M
Reply all
Reply to author
Forward
0 new messages