Jump to content
  • entries
    943
  • comments
    5,899
  • views
    924,417

DPI Scaling


Josh

5,075 views

 Share

I've implemented DPI scaling into Leadwerks GUI (beta branch, Lua on Windows only).

 

To set the GUI scale you just call gui:SetScale(scalefactor). A scale factor greater than one will make it bigger, a scale factor less than one will make it smaller. There is no limit to the range you can set, but negative numbers are probably not good.

 

Scaling causes the GUI to be drawn at a different size, but widget dimensions and mouse events are still interpreted as though they were at their original size. For example, the mouse move script function will always indicate a coordinate of (510,510) when you move the mouse into the lower-left corner of a 500x500 widget positioned at 10,10, no matter how big or small the GUI is scaled onscreen. Widget::GetWidth() will return the widget's unscaled dimensions, not the actual size it appears at. To get the actual size, you would multiply the dimensions by the GUI scale factor and round off. However, you should never need to calculate this yourself, as all numbers are made uniform for you.

 

Scaled widget positions are still rounded to integers in order to make them appears precisely on the screen pixels. We don't want sub-pixel blurring with widgets that line up in between pixels, we want sharp clear pixel-aligned rendering. Interestingly, the mouse script functions are now receiving floating point coordinates instead of integers. For example, if you move the mouse across a widget with the GUI scaled to 200% (2.0) you will see the mouse coordinates change in 0.5 increments like 150, 150.5, 151, 151.5, 152, etc.

 

Scaling presently does not affect text rendering, as this will take more work to figure out how to manage.

 

Below is a simple GUI scaled at 100%:

blogentry-1-0-78804000-1468255134_thumb.png

 

Now at 200%. Note that text scaling is not yet implemented. When it is, the text will appear bigger, proportional to the widget. However lines still appear exactly one pixel thick, as they should:

blogentry-1-0-17379900-1468255146_thumb.png

 

And finally made smaller, at 50%: Notice that although the lines are still one pixel thick, we have lost the space between the inner and outer rectangles. This is being drawn properly, but it is possible you might run into small issues like this in various resolutions, so it's something to keep in mind.

blogentry-1-0-20997000-1468255172_thumb.png

 

Of course images cannot be upsampled without losing precision, so any images your GUI uses should originally be at the maximum scale your GUI may use.

  • Upvote 4
 Share

19 Comments


Recommended Comments

Of course images cannot be upsampled without losing precision, so any images your GUI uses should originally be at the maximum scale your GUI may use.

 

From my experience downscaling will have issues as well with high resolution images. So ideally the system would be able to allow/or have auto swapping of images based on the screen resolution. Maybe something like button_1.png, button_2.png, button_3.png, etc and we can configure (but there is a default) what button number is used from a range of resolution widths (for example).

 

If this system doesn't do stuff like that for us then the framework is minimized in it's use. The stuff you are finding is the hardest part about making a GUI for us all. The framework you have up until now is not the hard part and won't save anyone time or help anyone make a GUI that works beyond their testing on 1 resolution. It's this stuff that prevents people from making a GUI.

Link to comment

If you have to enter a bunch of different resolution images, you might as well just write your own implementation. That's basically the same thing.

 

What you're telling me is artwork is the limiting step.

Link to comment

I view that as very far from the same thing. If the art isn't vector graphics than scaling the same size image to any screen resolution won't work well. Image swapping is common when coding for mobile since the resolutions have a wide range. This is the issue with PC UIs. Having the ability to have the system swap images is one small but important step in a UI system. It's far from the main thing and seems strange to justify not creating one in general because of that. Event processing, resolution independence for size and position are very important aspects of a UI and they can be complicated for people to create on their own.

 

You could support scalable vector graphics and avoid all of this I think. Supporting svg would be sweet I think.

 

If you were to stick with image swapping it's not like the user has to do that. It should be an option that you can configure or auto configured but if no images exist it just keeps using the main one. It's really an optimization step.

Link to comment

SVG loading would be similar to how font loading works. The TTF format is vector-based, and the engine loads the font at a specified resolution into a rasterized image. Right now the DPI scaling automatically reloads the font at the needed scale, so there is no need for the user to mess with it.

 

It would be cool if SVG files could be loaded the same way, but who even has that artwork?

Link to comment

Game developers would need to create their GUI gfx no matter what game they are making. With svg you'd be asking them to make their GUI images with an app that supports svg exports/gfx. It seems like your experience with GUIs is drawn from code but that's a small percentage of games that do that like half life did. Most games I play are image files for the GUI elements. I'm not sure what angle you are attacking here with this GUI framework.

Link to comment

Well, Half-Life style vector drawing elements like above are reusable in both a game and in Leadwerks Editor. Anything image-based is going to be highly stylized and vary from game to game. So I am leaning towards a default set of simple widgets, with the ability to customize the GUI however the developer wants.

 

Since no one is posting any implementations, it seems like most people will just use whatever default widgets I provide.

Link to comment

I wasn't using it because I'm not drawing my ui controls. That seems crazy to me. I'm all about using images for that and you didn't have resolution independence before so there was no point. The framework structure is the easy part of UIs so it's of no benefit really to me. I'll test now that it seems you have this now. Complex controls are generally just a composite of simple controls. Controls are really 2 things. Events and visuals. You could have controls with just events and then we are just skimming the visuals in any fashion we want right.

Link to comment

Your script itself can swap images based on the GUI resolution if needed (GUI:GetScale()), although I really like the idea of loading SVG files like we do fonts.

Link to comment

So really with you telling me "my" script could swap images you're saying your framework won't support non vector images for the UI's. If we play the scenario where you don't incorporate image swapping into your framework here is what we see.

 

I create a button widget that uses non vector images and I do the swapping. Joe Blow creates a list view where my buttons are used as column headers that can be clicked to be sorted but he doesn't use image swapping because he doesn't know about it and doesn't seem to care. Nancy Smith uses Joe Blow's list view widget but notices at different resolutions the buttons look OK but the rest of the control looks bad. Or a game developer has to use some widgets from a handful of different people and some implemented image swapping and some didn't.

 

You know that 1 non vector image file doesn't scale well to all screen resolutions yet you won't include image swapping in your framework so really you're saying you don't support non vector images in your framework but you'd want people who make widgets with non vector images to implement image swapping. If you want the users to do a certain thing then it's a prime candidate to be included in the framework as that's the point of a framework. Make a common way for users to do things.

Link to comment

What is the difference between this:

widget:SetLODImage("Myimage_low.bmp",1)
widget:SetLODImage("Myimage_med.bmp",2)
widget:SetLODImage("Myimage_high.bmp",3)

 

And this?:

if guires==1 then self.image = Image:Load("myimage_low.bmp") end
if guires==2 then self.image = Image:Load("myimage_med.bmp") end
if guires==3 then self.image = Image:Load("myimage_high.bmp") end

 

I'm not really seeing how my framework can add any simplicity in "managing" multiple raster image resolutions, since you still have to input the paths. Unless you had something else in mind.

Link to comment

The process I have in mind allows things to work simply and with default behaviors but also lets people who are more serious about their game configure it. So the simple aspect needs to allow me to just load 1 image for my widget and have it scale. It will look bad at that point pending the resolutions but it'll work with no extra work at this point. That's key so entry is easy for people.

 

Next step is to allow the user to simply make new image resolution files of the one image bit just append a number at the end of it. No code change needed by them. Your load image they used would be setup to look for this and load the right one based on the last step.

 

The last step is to Allow the user to configure, but you also give a default config so they don't have to, what numbers are what resolution width. So I should be able to say 1 mean 0 to 640, 2 means 641 - 720, 3 means 721 to 1920.

 

This is a framework that anyone can start with, just one image, and build apon once they realize they need it, because a lot of people don't know this problem until they are mostly done with the game and testing on different resolutions and things look bad.

 

The learning curve for a user is easy and the ability to polish doesn't require any code changes to their ui code. Just adding image files and setting some config at the start of the app!

 

 

Link to comment

Here is a little bit of code that won't run or anything but it gets the idea across.

 

 

From a widget creation standpoint here is what we'd do if we want to use a non vector image.

 

-- Button.lua
function Script:Start()
self.normal = Widget:LoadImage("Materials/UI/button.png") -- in this UI directory there could be button1.png, button2.png, button3.png and/or just button.png
end


function Script:Draw()
-- draw self.normal image
end

 

 

Leadwerks would define this as default but the user of the widgets could define this custom in main.lua if they wanted:

 

-- main.lua
-- this would be defaulted to some values but users could override it
GUI.config[1] = { min = 0, max = 640 }
GUI.config[2] = { min = 641, max = 720 }
GUI.config[3] = { min = 721, max = 1920 }

 

 

Then the framework implementation of Widget:LoadImage() which does the magic would look at the GUI.config table and compare the current screen width to get what index to use. Then it'll look to see if that image exists using the passed in image name as the "base". If it can't it'll keep looking and if no other "lod" images exist it'll just load the one passed in as a fallback that'll at least work (not give a run-time error) but won't look ideal when scaled to any resolution, but all the user of the widget has to do is add those 'lod" images later.

 

-- leadwerks framework function (could be some engine function or whatever this is just an idea that we need a load image function that has some smarts behind it)
function Widget:LoadImage(src)
local screenWidth = Context:GetCurrent():GetWidth()
local path = GetFilePath(src)
local filename = GetFileNameFromPathWithoutExtension(src)
local ext = GetFileExtension(src)

local imageLOD = nil

for i in ipairs(GUI.config) do
if screenWidth >= GUI.config[i].min and screenWidth <= GUI.config[i].max then
local f = filename..i..ext

-- if a file with this number exists make it the file we will load
if FileExists(f) then
imageLOD = i
break
end
end
end

-- if imageLOD stays nil then we don't look for an lod image we simply load the one they passed into this function, which won't scale but it'll allow something to make things not fail
return Texture:Load(path..'/'..filename..(imageLOD or '')..ext)
end

Link to comment

Why wouldn't you just make a script like this and include it from your widget scripts, then call the LoadLODImage() function? I don't see how having the engine load and store an array from a config file makes things any simpler.

GUIConfig = {}
GUIConfig[1] = 400
GUIConfig[2] = 600
GUIConfig[3] = 1024
GUIConfig[4] = 1650
GUIConfig[5] = 1920

function LoadLODImage(src)
   local screenWidth = Context:GetCurrent():GetWidth()
   local path = GetFilePath(src)
   local filename = GetFileNameFromPathWithoutExtension(src)
   local ext = GetFileExtension(src)

   local imageLOD = nil

   for i in ipairs(GUIConfig) do
       if screenWidth >= GUIConfig[i] and screenWidth <= GUIConfig[i+1] then
           local f = filename..i..ext

           -- if a file with this number exists make it the file we will load
           if FileExists(f) then
               imageLOD = i
               break
           end

       end
       if i==#GUIConfig-1 then
           imageLOD = #GUIConfig
           break
       end
   end

   -- if imageLOD stays nil then we don't look for an lod image we simply load the one they passed into this function, which won't scale but it'll allow something to make things not fail
   return Image:Load(path..'/'..filename..(imageLOD or '')..ext)
end

Link to comment

Because you're asking every widget creator who wants to support non vector style widget images ( which I would think would be the majority because raster images are easier to create and are more creative than anything one can make with using drawing commands) to add this to their widget instead of the framework doing it built in? That means your not supporting non vector images for widgets in you GUI framework because you know they won't look good when there is only one version of them and you scale them and you're saying you don't care about that to unify how widget creators handle that problem. If that's the case then so be it but then your framework is marginalized some. It's not a framework that's solves these ui problems, and if that's the case what the benefit of it? Why would I use it if it's likely I'll run into a widget where the creator didn't know about the script above or worse yet they all make their own implementation and how they handle it is different and I have to remember for the button widget I'm using I have to do this system to image swap and for they combo of widget it's this method to image swap. What a pain that is. It's not needed if your GUI framework would offer a consistent way to handle this issue. That's the point of a framework is it not? Give a consistent way to do something?

 

 

I guess the other option is to resample the image so the quality stays crisp no matter what size it is.

Link to comment

Can you post an example of a GUI image that won't look good when its scaled down to 25% its original size?

Link to comment

If you don't see it as an issue then that's fine. So many questions for knowing that quality loss happens when images are scaled. I'm exhausted already, and have lost interest in the battle :)

Link to comment

Given the lack of feedback I am getting overall, and the complexity of ImageMagick, I am going to just focus on my own code-rendered widgets and build a set of GUI elements. We can always come back to this in the future and add it in, but it sounds like almost everyone will just use whatever default items I provide.

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...