Important bis: scale factor in wxBitmap API?

93 views
Skip to first unread message

Vadim Zeitlin

unread,
Jul 17, 2020, 8:02:25 PM7/17/20
to wx-dev
Sorry for the avalanche of emails, but I feel that if I've started the
discussion about GetContentScaleFactor(), I should raise this question too,
because it might be related.

Anyhow, this message is about wxBitmap APIs involving the scaling factor,
i.e. basically everything dealing with "double" parameters or return type
(and also GetScaledSize()). I have a nagging impression that there must be
something wrong with them too because I just can't believe that blissfully
multiplying (or dividing) integers by fractional scaling factor and
discarding the result can be the right thing to do. The only actual bug due
to this that I'm aware of is https://trac.wxwidgets.org/ticket/17505 and
I've marked it as closed a long time ago in 3380a2438d (Fix scale factor
confusion in wxMac wxBitmap implementation, 2017-12-02), but looking at it
again now, I'm not actually that sure that it was really fixed because the
rounding error still seems to be present in the code.

We've started discussing these rounding problems and possible solutions in
https://github.com/wxWidgets/wxWidgets/pull/1963 but I wonder if the real
problem is not even more fundamental: why exactly should we have the
scaling factor in wxBitmap at all? The more I think about it, less it makes
sense to me. Consider, for example, an application with 2 windows on 2
monitors with different DPIs and hence scaling factors. It should be
possible to draw the same bitmap correctly in both of these windows, but
currently it doesn't look like this is the case because the bitmap scaling
factor will be wrong for at least one of the monitors.

IMO it would be much more logical to have the scaling factor as property
of wxDC/wxGraphicsContext on which the bitmap is drawn. After all, we don't
need it for any operations involving wxBitmap itself: neither for loading
nor saving it, nor for conversion to and from wxImage. The only (albeit
very important) place where scaling is needed is when we actually draw it,
and then it depends on where exactly do we do it, i.e. on the context.

I'd really like to know why do we need to have the scaling factor inside
wxBitmap itself. AFAICS this creates many problems and I don't see what
advantages compensate for them. Clearly, we're not going to change this for
3.1.4 (I'm crazy but not that much), but perhaps we should deprecate the
wxBitmap methods mentioned above in it and change this for 3.1.5.

Again, please let me know if I'm missing something crucial here because
it's perfectly possible that I am.

Thanks again,
VZ

Stefan Csomor

unread,
Jul 18, 2020, 4:54:16 AM7/18/20
to wx-...@googlegroups.com
Hi
Yes, these two are related. And it's as well about logical vs device units.

Our API has many places where wxBitmaps and not wxImage (where we could use the resolution) is used, and in some of these instances at least on macOS/iOS I have to use the size of these objects to set or calculate the size of a control. This on mac is always in logical units, so when retina displays started, proper support included having @2 bitmaps, and all these objects got too big. And neither for drawing on a dc nor setting on a control, you couldn't always just assume they were @2, perhaps some were still not updated and at @1. So wxBitmap, or another class as a stand-in, needs some kind of scale, in itself.

Best,

Stefan


Vadim Zeitlin

unread,
Jul 18, 2020, 7:53:59 AM7/18/20
to wx-...@googlegroups.com
On Sat, 18 Jul 2020 08:54:13 +0000 Stefan Csomor wrote:

SC> Yes, these two are related. And it's as well about logical vs device
SC> units.

In this sense they're definitely related, but I'm not sure if changing
GetContentScaleFactor() to return 1 under MSW even in high DPI is going to
affect this. AFAICS it won't, really, so I can still make the other change
while postponing this one.

SC> Our API has many places where wxBitmaps and not wxImage (where we could
SC> use the resolution) is used, and in some of these instances at least on
SC> macOS/iOS I have to use the size of these objects to set or calculate
SC> the size of a control.

This seems wrong... We should adapt the bitmap to the control size, not
vice versa.

SC> This on mac is always in logical units, so when retina displays
SC> started, proper support included having @2 bitmaps, and all these
SC> objects got too big. And neither for drawing on a dc nor setting on a
SC> control, you couldn't always just assume they were @2, perhaps some
SC> were still not updated and at @1. So wxBitmap, or another class as a
SC> stand-in, needs some kind of scale, in itself.

Thanks for explaining this, it's a scenario I didn't think about. But I'm
still not convinced we need a scale in wxBitmap itself. AFAICS the size of
the control is fixed, in logical pixels, so we know the size of the bitmap
it needs. And so the control really has no choice but to scale the bitmap
to its physical size (of course, this scaling can/should be skipped if the
sizes are the same).

What prevents us from doing it like this?
VZ

Stefan Csomor

unread,
Jul 18, 2020, 8:24:41 AM7/18/20
to wx-...@googlegroups.com
Hi

SC> Yes, these two are related. And it's as well about logical vs device
SC> units.

In this sense they're definitely related, but I'm not sure if changing
GetContentScaleFactor() to return 1 under MSW even in high DPI is going to
affect this. AFAICS it won't, really, so I can still make the other change
while postponing this one.

Yes, I think so

SC> This on mac is always in logical units, so when retina displays
SC> started, proper support included having @2 bitmaps, and all these
SC> objects got too big. And neither for drawing on a dc nor setting on a
SC> control, you couldn't always just assume they were @2, perhaps some
SC> were still not updated and at @1. So wxBitmap, or another class as a
SC> stand-in, needs some kind of scale, in itself.

Thanks for explaining this, it's a scenario I didn't think about. But I'm
still not convinced we need a scale in wxBitmap itself. AFAICS the size of
the control is fixed, in logical pixels, so we know the size of the bitmap
it needs. And so the control really has no choice but to scale the bitmap
to its physical size (of course, this scaling can/should be skipped if the
sizes are the same).

Right now we have quite important places where the size of the bitmap has not to be given, or the control sizes to the bitmap and not vice versa.

Perhaps doing concrete code situations can help us:

in src/osx/iphone/AppIfon.appiconset we have different resolutions of the same icon,

eg wx-40.png exists in 1x, 2x and in 3x resolution, wx-76.png only in 1x, and 2x

when I want to load it with bmp.LoadFile("wx-40" or "wx-76", wxBITMAP_TYPE_PNG_RESOURCE), I can eg do a dc.DrawBitmap(bmp, 10, 10); it will always be drawn correctly, picking the best resolution, using the correct size, I don't have to code this somewhere, no scaling has to be done on my side, ... if I have a wxBitmapButton I can do a SetBitmap, and it will be at the correct size, best resolution. Even when running on the 3x iPad Platform, and wx-76 not existing in this scale, it still will be drawn in the right size, although a little blurry ... neither the dc rendering nor bitmap button will not suddenly be smaller, just because the poor programmer did not include a 3x version when doing his artwork ...

All the other situations where the control sizes the bitmap are no problem of course

Best,

Stefan





Vadim Zeitlin

unread,
Jul 18, 2020, 9:29:55 AM7/18/20
to wx-...@googlegroups.com
On Sat, 18 Jul 2020 12:24:38 +0000 Stefan Csomor wrote:

SC> Right now we have quite important places where the size of the bitmap
SC> has not to be given, or the control sizes to the bitmap and not vice
SC> versa.
SC>
SC> Perhaps doing concrete code situations can help us:

Yes, perfect, thank you for the example.

SC> in src/osx/iphone/AppIfon.appiconset we have different resolutions of
SC> the same icon,
SC>
SC> eg wx-40.png exists in 1x, 2x and in 3x resolution, wx-76.png only in
SC> 1x, and 2x
SC>
SC> when I want to load it with bmp.LoadFile("wx-40" or "wx-76",
SC> wxBITMAP_TYPE_PNG_RESOURCE),

Looking at wxBundleResourceHandler::LoadFile(), it seems that it always
uses the scale factor of the main screen to select the bitmap to load here.
And this is exactly what I mean when I say that having the scale factor in
the bitmap is incompatible with using displays with different DPI and this
just can't be resolved, because when you load the bitmap, you don't know on
which display it will be used yet.

There is also the fact that 3x seems to not be supported at all (please
correct me if I'm missing anything) and it doesn't help at all with the
dynamically generated bitmaps, which are not uncommon.

SC> I can eg do a dc.DrawBitmap(bmp, 10, 10);
SC> it will always be drawn correctly, picking the best resolution, using
SC> the correct size, I don't have to code this somewhere, no scaling has
SC> to be done on my side,

Of course this is the goal, but I think we need to implement it
differently. We've discussed this several times in the past, without ever
reaching a firm conclusion, but to me it still seems clear that we either
need to switch to using wxIconBundle in the API instead of/in parallel with
wxBitmap or, more realistically, make wxBitmap itself bundle-like, i.e.
allow it to contain several versions, at different resolutions, of the same
bitmap. And in this case it wouldn't need to have any scale factor and
would just have to provide a way to find the bitmap of the size closest to
the given one, where the target size would come from wxDC (if bitmap is
used for drawing on it) or wxWindow (if bitmap is used to determine its
size).

Doesn't NSImage behave like this BTW?

SC> ... if I have a wxBitmapButton I can do a SetBitmap, and it will be at
SC> the correct size, best resolution. Even when running on the 3x iPad
SC> Platform, and wx-76 not existing in this scale, it still will be drawn
SC> in the right size, although a little blurry ... neither the dc
SC> rendering nor bitmap button will not suddenly be smaller, just because
SC> the poor programmer did not include a 3x version when doing his artwork

How exactly does this work? Looking at LoadFile(), it will only use scale
of 2.0 when @2x image exists and falls back to using scale of 1.0
otherwise. What am I missing here?


Anyhow, for now I think my point is that we really shouldn't expose the
scale-related methods of wxBitmap to wxPython and maybe already deprecate
them on C++ side because I'm almost sure we won't have them as (preferred)
way of working with bitmaps in 3.2.0.

What do you think?
VZ

Stefan Csomor

unread,
Jul 18, 2020, 10:00:23 AM7/18/20
to wx-...@googlegroups.com
Hi

Looking at wxBundleResourceHandler::LoadFile(), it seems that it always
uses the scale factor of the main screen to select the bitmap to load here.
And this is exactly what I mean when I say that having the scale factor in
the bitmap is incompatible with using displays with different DPI and this
just can't be resolved, because when you load the bitmap, you don't know on
which display it will be used

on macOS we can have several displays with factors 1.0 or 2.0
on iOS we have most of the time one display with factors 1.0, 2.0 or 3.0

so on macOS you are right, the algorithm would have to be changed to use the highest

it does not matter if a 2x bitmap is used on a 1x display, it is just nicely scaled down

There is also the fact that 3x seems to not be supported at all (please
correct me if I'm missing anything) and it doesn't help at all with the
dynamically generated bitmaps, which are not uncommon.

@3 is supported in the native image loading calls, and in the different parts where the OS retrieves the pngs from our resources, but in wx this is only done just now ....
https://github.com/wxWidgets/wxWidgets/pull/1980

As you can create a wxBitmap with the attributes of a wxDC creating a corresponding bitmap is respecting the scale

SC> I can eg do a dc.DrawBitmap(bmp, 10, 10);
SC> it will always be drawn correctly, picking the best resolution, using
SC> the correct size, I don't have to code this somewhere, no scaling has
SC> to be done on my side,

Of course this is the goal, but I think we need to implement it
differently. We've discussed this several times in the past, without ever
reaching a firm conclusion, but to me it still seems clear that we either
need to switch to using wxIconBundle in the API instead of/in parallel with
wxBitmap or, more realistically, make wxBitmap itself bundle-like, i.e.
allow it to contain several versions, at different resolutions, of the same
bitmap. And in this case it wouldn't need to have any scale factor and
would just have to provide a way to find the bitmap of the size closest to
the given one, where the target size would come from wxDC (if bitmap is
used for drawing on it) or wxWindow (if bitmap is used to determine its
size).

Doesn't NSImage behave like this BTW?

Exactly, NSImage would be the model for a multi-res image bundle ... (and multi-resolution TIFF would be a possible representation if we don't want multiple files)

SC> ... if I have a wxBitmapButton I can do a SetBitmap, and it will be at
SC> the correct size, best resolution. Even when running on the 3x iPad
SC> Platform, and wx-76 not existing in this scale, it still will be drawn
SC> in the right size, although a little blurry ... neither the dc
SC> rendering nor bitmap button will not suddenly be smaller, just because
SC> the poor programmer did not include a 3x version when doing his artwork

How exactly does this work? Looking at LoadFile(), it will only use scale
of 2.0 when @2x image exists and falls back to using scale of 1.0
otherwise. What am I missing here?

@3 is part of the PR above for explicit loading, but the point is that the user's code does not have to be changed at all, if the best-res image is there fine, if not, no problem

Anyhow, for now I think my point is that we really shouldn't expose the
scale-related methods of wxBitmap to wxPython and maybe already deprecate
them on C++ side because I'm almost sure we won't have them as (preferred)
way of working with bitmaps in 3.2.0.

I'm fine with not exposing them for 3.1.4, and I see that the issues for 96, 128, et DPI interfaces are different, because while on macOS we can have a common scale factor, without problems, we cannot on win ...

Best,

Stefan

Vadim Zeitlin

unread,
Jul 18, 2020, 10:22:58 AM7/18/20
to wx-...@googlegroups.com
On Sat, 18 Jul 2020 14:00:19 +0000 Stefan Csomor wrote:

SC> it does not matter if a 2x bitmap is used on a 1x display, it is just
SC> nicely scaled down

Sorry, I have to disagree with this. IME scaling down 32*32 icon to 16*16
is almost never a good idea, you need to have different icons for best
results. At the very least, you need to edit the scaled down version
manually to remove blurring.

SC> @3 is supported in the native image loading calls, and in the different
SC> parts where the OS retrieves the pngs from our resources, but in wx
SC> this is only done just now ....
SC> https://github.com/wxWidgets/wxWidgets/pull/1980

Ah, I didn't notice this, I just saw "assert" in the subject of the email
notification for this PR... FWIW I'd like to split this into 2 commits (as
usual, I can do this, if you don't mind), to keep these changes separate.

SC> I'm fine with not exposing them for 3.1.4, and I see that the issues
SC> for 96, 128, et DPI interfaces are different, because while on macOS we
SC> can have a common scale factor, without problems, we cannot on win ...

Yes, we really need some API that works for all systems, not just Mac. And
I think AUI is a good testing ground for this, as it uses dynamically
generated bitmaps, which is one of the most complicated cases.

But right now the question is about what to do with wxBitmap scale-related
methods. I'd really like to deprecate them to give at least some indication
that people shouldn't use them (although I suspect that quite a few of them
have already started using them, since 4+ years that they exist), but I am
not sure if we can do this right now.

Regards,
VZ

Stefan Csomor

unread,
Jul 18, 2020, 11:26:48 AM7/18/20
to wx-...@googlegroups.com
Hi

SC> it does not matter if a 2x bitmap is used on a 1x display, it is just
SC> nicely scaled down

Sorry, I have to disagree with this. IME scaling down 32*32 icon to 16*16
is almost never a good idea, you need to have different icons for best
results. At the very least, you need to edit the scaled down version
manually to remove blurring.

Yes, for small icons you are right

SC> I'm fine with not exposing them for 3.1.4, and I see that the issues
SC> for 96, 128, et DPI interfaces are different, because while on macOS we
SC> can have a common scale factor, without problems, we cannot on win ...

Yes, we really need some API that works for all systems, not just Mac. And
I think AUI is a good testing ground for this, as it uses dynamically
generated bitmaps, which is one of the most complicated cases.

But right now the question is about what to do with wxBitmap scale-related
methods. I'd really like to deprecate them to give at least some indication
that people shouldn't use them (although I suspect that quite a few of them
have already started using them, since 4+ years that they exist), but I am
not sure if we can do this right now.

Right now I have to support creation of wxBitmap also from system objects which might be scaled. And this will probably always be so, but as long as it's not public in creation, but only for accessing this should not be a problem ...

A NSImage always has the same 'logical' size, only different 'physical' renderings, which somehow does not fit into my understanding of a wxBitmap. It is asked for a bestRepresentation for a graphics context.

A IconBundle has no notion of logical size, it is just a bundle of different icons with different sizes, it is a bundle of wxIcons/wxBitmaps though. How about a wxBitmapBundle ? having only ONE logical size, with a GetBestBitmap( wxWindow* ) etc ?

Best,

Stefan


Stefan Csomor

unread,
Jul 18, 2020, 11:28:47 AM7/18/20
to wx-...@googlegroups.com
Hi

Ah, I didn't notice this, I just saw "assert" in the subject of the email
notification for this PR... FWIW I'd like to split this into 2 commits (as
usual, I can do this, if you don't mind), to keep these changes separate.

ok, I'm fine with you splitting this, actually the @3 could go to master, and we experiment in the PR with bitmaps and scales ...

Thanks,

Stefan

Vadim Zeitlin

unread,
Jul 18, 2020, 11:38:41 AM7/18/20
to wx-...@googlegroups.com
On Sat, 18 Jul 2020 15:26:44 +0000 Stefan Csomor wrote:

SC> Right now I have to support creation of wxBitmap also from system
SC> objects which might be scaled. And this will probably always be so, but
SC> as long as it's not public in creation, but only for accessing this
SC> should not be a problem ...

Yes, while I don't want to give the impression that I don't care about
internal quality of wxOSX code, because I do, I still care incomparably
more about the public API. So as long as we don't have APIs which basically
can never be implemented 100% correctly in wxBitmap, I'd be happy enough.

SC> A NSImage always has the same 'logical' size, only different 'physical'
SC> renderings, which somehow does not fit into my understanding of a
SC> wxBitmap. It is asked for a bestRepresentation for a graphics context.

I think wxBitmap should really be this, it's a logical evolution of the
current concept. It provides clear backwards compatibility, as you can
always have a single (which would also be best, due to lack of choice)
representation, while being extensible enough to support what we need.

SC> A IconBundle has no notion of logical size, it is just a bundle of
SC> different icons with different sizes, it is a bundle of
SC> wxIcons/wxBitmaps though. How about a wxBitmapBundle ? having only ONE
SC> logical size, with a GetBestBitmap( wxWindow* ) etc ?

I agree that current wxIconBundle is inappropriate because it can contain
icons of completely different sizes (and, in practice, will, i.e. it's
really our equivalent of Apple .appiconset thing, IIUC), but I don't think
it's worth creating a separate wxBitmapBundle. IMO we should either find
some way to change wxIconBundle to make it usable for what we need here or,
preferably, integrate having multiple representations in wxBitmap itself.

The latter would IMO be much better because not only we have many
functions taking or returning wxBitmap, which would be difficult to convert
to using wxIconBundle (or wxBitmapBundle, i.e. anything other than
wxBitmap), inside wx itself, but such functions are also very common in the
user code and we just can't change that. While changing wxBitmap could,
hopefully, be done in such a way that things would continue to work as well
as before without any changes and could be improved by changing just a few
small things in a single place (when loading/creating the bitmaps) instead
of having to modify the entire code base to use wxFooBundle instead of
wxBitmap.

So if at all possible, I'd be strongly in favour of handling this in
wxBitmap itself. In fact, we could even do it for Mac (and GTK 3?) only for
now (because MSW ignores bitmap scale anyhow), as long as we were sure that
the new API could be implemented in the other ports too.

Stefan Csomor

unread,
Jul 18, 2020, 12:36:06 PM7/18/20
to wx-...@googlegroups.com
Hi

Am 18.07.20, 17:38 schrieb "Vadim Zeitlin" <wx-...@googlegroups.com im Auftrag von va...@wxwidgets.org>:

On Sat, 18 Jul 2020 15:26:44 +0000 Stefan Csomor wrote:

SC> Right now I have to support creation of wxBitmap also from system
SC> objects which might be scaled. And this will probably always be so, but
SC> as long as it's not public in creation, but only for accessing this
SC> should not be a problem ...

Yes, while I don't want to give the impression that I don't care about
internal quality of wxOSX code, because I do, I still care incomparably
more about the public API. So as long as we don't have APIs which basically
can never be implemented 100% correctly in wxBitmap, I'd be happy enough.

understood, +1 from me

SC> A NSImage always has the same 'logical' size, only different 'physical'
SC> renderings, which somehow does not fit into my understanding of a
SC> wxBitmap. It is asked for a bestRepresentation for a graphics context.

I think wxBitmap should really be this, it's a logical evolution of the
current concept. It provides clear backwards compatibility, as you can
always have a single (which would also be best, due to lack of choice)
representation, while being extensible enough to support what we need.

but isn't a wxBitmap just ONE bitmap, one physical pixmap, I mean we have raw access, conversions many of these concepts are not valid for a groupd of bitmaps ... what would we do then ?

would we need a wxBitmapRefData type that carries a container of implementations, and certain methods are only valid if there is just ONE representation in the wxBitmapRefData

and the accessor GetBestBitmap would return a wxBitmap having just this one representation ?

Best,

Stefan


Vadim Zeitlin

unread,
Jul 18, 2020, 12:45:53 PM7/18/20
to wx-...@googlegroups.com
On Sat, 18 Jul 2020 16:36:03 +0000 Stefan Csomor wrote:

SC> but isn't a wxBitmap just ONE bitmap, one physical pixmap,

Of course it is currently, but I think it could be extended...

SC> I mean we have raw access, conversions many of these concepts are not
SC> valid for a groupd of bitmaps ... what would we do then ?

Make them behave as they do right now by default for compatibility and add
an optional parameter to specify the representation to use for the new
code.

SC> would we need a wxBitmapRefData type that carries a container of
SC> implementations, and certain methods are only valid if there is just
SC> ONE representation in the wxBitmapRefData

At least for MSW I think it could be conceptually better to have an array
of wxBitmapRefData, but maybe for wxOSX a single wxBitmapRefData would be
enough, if it's backed by NSImage?

But even for MSW, considering the way wxObjectRefData works, we'd still
need to have a single pointer, so it would be something like

wxBitmap -> wxBitmapRefData -> N * wxBitmapRepresentationData

where wxBitmapRepresentationData would basically be the renamed current
wxBitmapRefData.

I didn't have time at all to look at this in the details yet, but I think
this should be doable...

SC> and the accessor GetBestBitmap would return a wxBitmap having just this
SC> one representation ?

We'd definitely need GetDefaultRepresentation() or something like this
internally to implement the compatibility behaviour of the various methods.
But this is again beyond where I'm prepared to go right now, unless we
decide to postpone 3.1.4 until September, as it's clearly not feasible to
change this in the remaining couple of days.

Of course, it's also possible that I'm missing some difficulty which would
prevent us from implementing this approach.

Regards,
VZ

Stefan Csomor

unread,
Jul 18, 2020, 1:30:49 PM7/18/20
to wx-...@googlegroups.com
Hi

At least for MSW I think it could be conceptually better to have an array
of wxBitmapRefData, but maybe for wxOSX a single wxBitmapRefData would be
enough, if it's backed by NSImage?

Because of all of the current ref counting etc. being based on RefData, I thought about renaming what now is my wxBitmapRefData to wxBitmapRepRefData or something and then put the array into wxBitmapRefData

But even for MSW, considering the way wxObjectRefData works, we'd still
need to have a single pointer, so it would be something like

wxBitmap -> wxBitmapRefData -> N * wxBitmapRepresentationData

where wxBitmapRepresentationData would basically be the renamed current
wxBitmapRefData.

Exactly ..

I didn't have time at all to look at this in the details yet, but I think
this should be doable...

I'll try to do this in my PR

SC> and the accessor GetBestBitmap would return a wxBitmap having just this
SC> one representation ?

We'd definitely need GetDefaultRepresentation() or something like this
internally to implement the compatibility behaviour of the various methods.

If we don't fill wxBitmap automatically with more representations, there would never be more then 1 representation in the backwards case

What would you use as a selector for GetBestBitmap, I thought about the DPIScalingFactor ?

But this is again beyond where I'm prepared to go right now, unless we
decide to postpone 3.1.4 until September, as it's clearly not feasible to
change this in the remaining couple of days.

Of course, it's also possible that I'm missing some difficulty which would
prevent us from implementing this approach.

I'll start, it won't have to be in 3.1.4 at all, we can test things

Best,

Stefan




Vadim Zeitlin

unread,
Jul 18, 2020, 4:00:29 PM7/18/20
to wx-...@googlegroups.com
On Sat, 18 Jul 2020 17:30:47 +0000 Stefan Csomor wrote:

SC> If we don't fill wxBitmap automatically with more representations,
SC> there would never be more then 1 representation in the backwards case

We definitely need some way of adding more representations too, but those
will be new functions and so there are no backwards-compatibility concerns
for them. It doesn't look difficult to add some AddAtScale() which could be
called when loading bitmaps from disk or resources. I'm less sure about how
should we handle bitmaps that are created on-demand. I guess we need some
new wxBitmapProducer (generator? painter? composer? the-thing-that-can't-be
-named-er?) which could be associated with wxBitmap using SetProducer() (or
whatever...) and which would be asked to provide a representation at the
given scale.

AFAIR NSImage does something similar and allows to define a block used to
produce bitmap representations, so it should be possible to implement this
by directly mapping to the native API under Mac.

SC> What would you use as a selector for GetBestBitmap, I thought about the
SC> DPIScalingFactor ?

I rather thought about the desired physical size.

SC> I'll start, it won't have to be in 3.1.4 at all, we can test things

Thanks!
VZ

Eric Jensen

unread,
Jul 18, 2020, 4:54:31 PM7/18/20
to Vadim Zeitlin
Hello Vadim,

Saturday, July 18, 2020, 10:00:23 PM, you wrote:

i tried to follow the whole discussion, but as i only work under
Windows, i didn't get all of it.

But i have a very bad feeling that you're trying to apply the "broken"
OSX system to other platforms as well and taking away a lot of
control from the (wxWidgets) user in the process. I can't give an
exact example where i expect problems, but this all feels like
it will cause more problems than it solves (under Windows).

But then again, i do know only Windows where the situation is quite
simple and i see that you're trying to create an API that works for
all platforms. I just don't want to come in a situation where i need
code to somehow "undo" the internal calculations wx makes internally.

What if i want to display a bitmap 1:1 pixel perfect regardless of the DPI of
the display?

Eric




--

Vadim Zeitlin

unread,
Jul 18, 2020, 6:14:02 PM7/18/20
to wx-...@googlegroups.com
On Sat, 18 Jul 2020 22:54:32 +0200 Eric Jensen wrote:

EJ> i tried to follow the whole discussion, but as i only work under
EJ> Windows, i didn't get all of it.

Thanks for participating in it nevertheless, this is a really important
topic and while I understand that it's difficult to wade through several
dozens of messages in this thread, I'd really ask as many people as
possible to think about this because the decisions here are going to be
very important for 3.2, i.e. next several years.

EJ> But i have a very bad feeling that you're trying to apply the "broken"
EJ> OSX system to other platforms as well and taking away a lot of
EJ> control from the (wxWidgets) user in the process.

No, I don't think so. Apple API is not really broken and, in fact, I'd
love to have it under MSW too, but it requires using floating point
coordinates (which is why I suspect that it is "broken" in GTK, where
they've only partially copied Apple approach), so we can't do this, which
is exactly the point of view I defended in this (and parallel) thread.

EJ> But then again, i do know only Windows where the situation is quite
EJ> simple and i see that you're trying to create an API that works for
EJ> all platforms. I just don't want to come in a situation where i need
EJ> code to somehow "undo" the internal calculations wx makes internally.

I don't think you'd ever need to do this. The only "overhead" under MSW is
that you have to use GetContentScaleFactor() correctly if you want your
code to work under Apple platforms and GTK 3, even though it's useless
(well, will become useless after the changes of PR 1985) under MSW. OTOH
you also need to use FromDIP() to make the code work under MSW, even though
it's useless under those other platforms. So, all in all, justice is
preserved as programmers under all platforms have to make a (small) effort
to make their code portable.

EJ> What if i want to display a bitmap 1:1 pixel perfect regardless of the
EJ> DPI of the display?

This was, is, and will remain possible simply by using wxDC::DrawBitmap()
or Blit(). The question is how do you convert the size of your bitmap
to/from the size of the window. With PR 1985 the answer becomes: you use
GetContentScaleFactor() on all platforms. I.e. if you have a window of size
100*100, as returned by its GetClientSize(), you need to make a bitmap of
size 100*GetContentScaleFactor() to entirely cover this window client area
without any scaling.

There is also the reverse question of how big your window should be to
display a bitmap. Of course, if the size of your bitmap is fixed, this is
trivial, as you just divide by GetContentScaleFactor(). But usually you
will have several bitmap sizes, from which you would select the most
appropriate one for the current DPI. And this is where the new (also from
the PR 1985) GetDPIScaleFactor() is useful: it works as FromDIP(), except
it does the same thing on all platforms, and returns 2.0 under both MSW and
OS X when using 200% scaling. So if you use 32*32 icons in standard
resolution, you would use 32*GetDPIScaleFactor() icon in the general case.

I hope this explains how things are/will be supposed to work. This seems
relatively logical, even if a bit complicated to me, but I'm afraid I don't
see any way to make this simpler. Please let me know if you do.

TIA!
VZ

Jorge Moraleda

unread,
Jul 18, 2020, 6:41:09 PM7/18/20
to wx-dev
Hello Vadim, Stefan and Eric,

I wanted to share my current use case in case it helps with the discussion.

I have a custom artprovider that runs on Linux, Windows and Mac. My artprovider has an imageProvider that can returns wxImages backed by  a database.

The API of my imageProvider has two relevant methods `getAvailableSizes` (which given the name of an image returns a list of wxSize for all the sizes in which that image is available) and `getImage` which returns a specific image given a name and a size (will throw if the requested name/size does not exist).

The `CreateBitmap` method of my artprovider first determines the target image size it wants multiplying the requested size (or the value of GetSizeHint for the given client) times `getContentScaleFactor` then it queries the `getAvailableSizes` method of the imageProvider to find what images are available. Then it retrieves the image with the best size among the available sizes given the target size  (in one strategy, the smallest available size that is larger than the target size) and scales it to the target size. Then it creates a bitmap with that image and the scale returned by getContentScaleFactor and returns that. Obviously by construction the scaled size of this bitmap is an integer. This works on windows since the scale factor is ignored and I have SetProcessDpiAwareness. It works on OSX as being discussed, and works on Linux, although on Linux I have never encountered the case where getContentScaleFactor returns something other than 1, so this is moot.

My implementation is in wxPython, but I think this does not matter for this discussion. Thank you,

Jorge

Vadim Zeitlin

unread,
Jul 18, 2020, 7:04:26 PM7/18/20
to wx-...@googlegroups.com
On Sat, 18 Jul 2020 15:36:17 -0700 (PDT) Jorge Moraleda wrote:

JM> I wanted to share my current use case in case it helps with the discussion.

Yes, it certainly does, we must check that our API allows implementing
this.

JM> I have a custom artprovider that runs on Linux, Windows and Mac. My
JM> artprovider has an imageProvider that can returns wxImages backed by a
JM> database.
JM>
JM> The API of my imageProvider has two relevant methods `getAvailableSizes`
JM> (which given the name of an image returns a list of wxSize for all the
JM> sizes in which that image is available) and `getImage` which returns a
JM> specific image given a name and a size (will throw if the requested
JM> name/size does not exist).
JM>
JM> The `CreateBitmap` method of my artprovider first determines the target
JM> image size it wants multiplying the requested size (or the value of
JM> GetSizeHint for the given client) times `getContentScaleFactor`

AFAICS here you will use the new (see PR 1985) GetDPIScaleFactor() because
you want this scale factor to depend only on DPI, and not on the platform,
i.e. want it to be 2 when using 200% scaling under both MSW and OSX.

JM> then it queries the `getAvailableSizes` method of the imageProvider to
JM> find what images are available. Then it retrieves the image with the
JM> best size among the available sizes given the target size (in one
JM> strategy, the smallest available size that is larger than the target
JM> size) and scales it to the target size. Then it creates a bitmap with
JM> that image and the scale returned by getContentScaleFactor and returns
JM> that.

We don't have the new API for wxBitmap yet, but I think this particular
use case should actually work without any extra API calls at all. I.e. your
wxBitmap of the correct physical size should be rendered without scaling on
all platforms, regardless of the DPI. I'm not sure if Stefan agrees with
this however...

JM> This works on
JM> windows since the scale factor is ignored and I have SetProcessDpiAwareness.
JM> It works on OSX as being discussed, and works on Linux, although on Linux I
JM> have never encountered the case where getContentScaleFactor returns
JM> something other than 1, so this is moot.

FWIW I have a high DPI Linux notebook, so I'm interested in this working
in wxGTK if only as a user.

Regards,
VZ

Eric Jensen

unread,
Jul 18, 2020, 7:16:48 PM7/18/20
to Vadim Zeitlin
Hello Vadim,

Sunday, July 19, 2020, 12:13:45 AM, you wrote:

VZ> On Sat, 18 Jul 2020 22:54:32 +0200 Eric Jensen wrote:

EJ>> But i have a very bad feeling that you're trying to apply the "broken"
EJ>> OSX system to other platforms as well and taking away a lot of
EJ>> control from the (wxWidgets) user in the process.

VZ> No, I don't think so. Apple API is not really broken and, in fact, I'd
VZ> love to have it under MSW too, but it requires using floating point
VZ> coordinates (which is why I suspect that it is "broken" in GTK, where
VZ> they've only partially copied Apple approach), so we can't do this, which
VZ> is exactly the point of view I defended in this (and parallel) thread.

AFAIK under OSX the app only "sees" logical coordinates that are half
of the physical size on Retina displays? If that's correct, that's
what i call "broken". They probably only did this so that the majority
of applications ran correctly on Retina displays right away without any
modfications.

What exactly does GTK do? Is it like Windows, OSX, or even something
different to both?


EJ>> What if i want to display a bitmap 1:1 pixel perfect regardless of the
EJ>> DPI of the display?

VZ> This was, is, and will remain possible simply by using wxDC::DrawBitmap()
VZ> or Blit(). The question is how do you convert the size of your bitmap
VZ> to/from the size of the window. With PR 1985 the answer becomes: you use
VZ> GetContentScaleFactor() on all platforms. I.e. if you have a window of size
VZ> 100*100, as returned by its GetClientSize(), you need to make a bitmap of
VZ> size 100*GetContentScaleFactor() to entirely cover this window client area
VZ> without any scaling.

So under MSW GetContentScaleFactor() would always be 1? In that case
that makes sense to me.

The part that confused me was your discussion about wxBitmap under MSW
having multiple bitmaps of different sizes under the hood. I don't
quite understand under which circumstances they would come into play.

Eric

Vadim Zeitlin

unread,
Jul 19, 2020, 9:01:54 AM7/19/20
to wx-...@googlegroups.com
On Sun, 19 Jul 2020 01:16:51 +0200 Eric Jensen wrote:

EJ> AFAIK under OSX the app only "sees" logical coordinates that are half
EJ> of the physical size on Retina displays? If that's correct,

No, not really. Integer pixel coordinates correspond to logical pixels,
but you can also use fractional coordinates, which means that you still can
address physical pixels too.

EJ> What exactly does GTK do? Is it like Windows, OSX, or even something
EJ> different to both?

GTK 2 has no high DPI support. GTK 3 follows Apple (as usual).

EJ> So under MSW GetContentScaleFactor() would always be 1? In that case
EJ> that makes sense to me.

Yes.

EJ> The part that confused me was your discussion about wxBitmap under MSW
EJ> having multiple bitmaps of different sizes under the hood. I don't
EJ> quite understand under which circumstances they would come into play.

This is practically indispensable when you need to support multiple
resolutions in the same application. You need to use different bitmaps
depending on DPI, but doing it manually is all but infeasible, just imagine
how unpleasant would it be. So the idea is that you specify that you have
the following sizes of the given bitmap and then let wx choose the one to
use for the current DPI automatically.

This API isn't finalized yet however...

Regards,
VZ

Jorge Moraleda

unread,
Jul 19, 2020, 3:18:01 PM7/19/20
to wx-dev
For consistency, it seems to me that if representations are added by scale with AddAtScale() then they should also be retrieved by scale in GetBestBitmap(). Alternatively instead of using scale the API could use physical size to add or retrieve bitmatps: AddAtPhysicalSize() and then they would be retrieved by their desired physical size.

 

Jorge Moraleda

unread,
Jul 19, 2020, 3:24:22 PM7/19/20
to wx-dev

During GetBestBitmap(), not only a single representation must be returned: Even in the simplest case of a bitmap with a single representation, there are two reasonable strategies to pick the returned scale/physical size in GetBestBitmap: Return 1:1 (useful for legacy applications) return the same scale that was passed in the query (useful for applications that know that they want their bitmap represented in a pixel perfect way regardless of pixel size). Choosing best strategy is further complicated when multiple representations exist: After choosing a best representation somehow, a scale/physical still needs to be chosen if the exact queried a representation did not exist at exact scale/physical size of the query. It  seems to me that a pluggable `GetBestRepresentationStrategy` class would need to be passed that would be able to query all available representations and then make a choice.

Perhaps what we need is to keep the wxBitmap API as simple as possible, with a public API that is virtually identical to the current one, with only the one method added GetBestRepresentation(scale or physical size). Then we could have several concrete classes implementing this API. One of these classes LegacyBitmap would be like the old wxBitmap, with no-dpi awareness and a single representation. GetBestRepresentation would return this representation and 1:1 Another of these classes: SingleRepresentationBitmap would be virtually identical to the current dpi-aware implementation, with a constructor taking an Image and optionally a scale. Future implementations could have additional methods to add multiple representation, strategies to choose among them, etc.

Vadim Zeitlin

unread,
Jul 19, 2020, 5:39:53 PM7/19/20
to wx-...@googlegroups.com
On Sun, 19 Jul 2020 12:24:22 -0700 (PDT) Jorge Moraleda wrote:

JM> During GetBestBitmap(), not only a single representation must be returned:

I don't understand this, "best" is the bitmap which is used if no other
representation is explicitly specified (maybe "best" is not the right word,
it could be "principal" or something like that). It is unique by
definition.

JM> Even in the simplest case of a bitmap with a single representation, there
JM> are two reasonable strategies to pick the returned scale/physical size

Yes, there are multiple strategies and we should allow specifying them,
including completely custom ones, but this should be done via some callback
object and doesn't affect the best/principal/main bitmap choice.

JM> It seems to me that a pluggable `GetBestRepresentationStrategy` class
JM> would need to be passed that would be able to query all available
JM> representations and then make a choice.

Yes, exactly.

JM> Perhaps what we need is to keep the wxBitmap API as simple as possible,
JM> with a public API that is virtually identical to the current one, with only
JM> the one method added GetBestRepresentation(scale or physical size). Then we
JM> could have several concrete classes implementing this API.

No, this is completely impossible at C++ level where we operate with
wxBitmap objects. It can't be derived from and can only have an (optional)
callback object that it would use for selecting the best fitting bitmap.

Regards,
VZ

Jorge Moraleda

unread,
Jul 19, 2020, 5:58:44 PM7/19/20
to wx-dev
JM> During GetBestBitmap(), not only a single representation must be returned:

I don't understand this, "best" is the bitmap which is used if no other
representation is explicitly specified (maybe "best" is not the right word,
it could be "principal" or something like that). It is unique by
definition.
Sorry my sentence was cropped. It should have read:  "During GetBestBitmap(), not only a single representation must be returned, also a scale/physical size needs to be returned with it"

Jorge Moraleda

unread,
Jul 19, 2020, 6:16:39 PM7/19/20
to wx-dev
JM> Perhaps what we need is to keep the wxBitmap API as simple as possible,
JM> with a public API that is virtually identical to the current one, with only
JM> the one method added GetBestRepresentation(scale or physical size). Then we
JM> could have several concrete classes implementing this API.

No, this is completely impossible at C++ level where we operate with
wxBitmap objects. It can't be derived from and can only have an (optional)
callback object that it would use for selecting the best fitting bitmap.

What about we make the single-represention current bitmap, including its wxImage(image, scale) constructor plus a method GetBestRepresentation(scale or physical size) and in the future we allow to derive more advanced classes from it?

When this new class becomes available, a user that wants a multi-represention bitmap may explicitly construct an object of this new class, and the loadBitmap that currently looks at "@nx" suffixes could also return an object of this new class when available. Also this object new class could support strategies to choose the best bitmap.

My overarching goal with this line of comments is to suggest that we should strive to find a way to add features to wxBitmap in an incremental way. In particular, it seems to me that the current single-representation bitmap with one scale and throwing in the cases when the scaled sizes are not ints has been shown over the last several years to be a reasonable baseline that balances simplicity and usefulness and works in all platforms, and I would push for finding the minimal API that allows it to be released while allowing it to add new features as their need and design becomes clear.

Thank you. Regards,
Jorge

Vadim Zeitlin

unread,
Jul 19, 2020, 8:12:38 PM7/19/20
to wx-...@googlegroups.com
On Sun, 19 Jul 2020 15:16:39 -0700 (PDT) Jorge Moraleda wrote:

JM> What about we make the single-represention current bitmap, including its
JM> wxImage(image, scale) constructor plus a method GetBestRepresentation(scale
JM> or physical size) and in the future we allow to derive more advanced
JM> classes from it?

No, we can't allow deriving anything from wxBitmap. C++ is not as dynamic
as Python and we just can't start using wxBitmap polymorphically, this would
break all the existing code using it.

JM> My overarching goal with this line of comments is to suggest that we should
JM> strive to find a way to add features to wxBitmap in an incremental way.

This risks to be pretty much impossible in practice because in addition to
API, which prevents us from changing wxBitmap in any fundamental way (as
your proposal above does), there is also ABI, which prevents us from adding
anything to it in stable 3.2 branch. This is exactly why it's so important
to ensure that we have a good API in 3.2.0 -- because we'll have to live
with it for the next few years.

Regards,
VZ
Reply all
Reply to author
Forward
0 new messages