xBR algorithm tutorial

As someone that does pixel art, that isn’t hi-res pixel art.

you’re right about the “determine the edges” part , the whole idea seems kinda impossible now :sweat_smile: this was the attempt

well potato potatoes :upside_down_face: it would be classified 720P

Some characteristic of scalers are that they leave some blur at the output image. It’s basically unavoidable when you need to scale by 4x or more, because you need to fill 16 pixels for each input pixel. Some scalers like pure xbr can scale without introducing blur, though they can’t resolve low contrast color transition very well (basically, games with seamless color transitions like DKC or MK).

So, to use a general purpose scaler (the ones that resolve low contrast color transitions well), it’s recommended to use some sharpening shader as a final touch to get rid of the blur byproduct of scaling.

I’ve been testing many sharpening filters available out there (adaptive sharpen, anime4k, finesharp, etc). Though they bring good results, the one that really shines is deblur (by @guest). It just works perfectly by removing blur without introducing collateral effects (like ringing).

On super-xbr, deblur combined with smartsmooth (another great shader by @guest), it gets some impressive results I didn’t see before. Smartsmooth is good to thin dark lines. As low res cartoon games use black outlines for objects, if you scale by 4x, the black lines become too thick, so it’s necessary to thin those lines for a better ballanced image output. That’s where smartsmooth enter.

Here’s a combination of super-xbr + smartsmooth + deblur:

Preset:

shaders = "8"
shader0 = "shaders_slang/xbr/shaders/super-xbr/super-xbr-pass0.slang"
filter_linear0 = "false"
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
alias0 = ""
float_framebuffer0 = "false"
srgb_framebuffer0 = "false"
scale_type_x0 = "source"
scale_x0 = "1.000000"
scale_type_y0 = "source"
scale_y0 = "1.000000"
shader1 = "shaders_slang/xbr/shaders/super-xbr/super-xbr-pass1.slang"
filter_linear1 = "false"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = ""
float_framebuffer1 = "false"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "2.000000"
scale_type_y1 = "source"
scale_y1 = "2.000000"
shader2 = "shaders_slang/xbr/shaders/super-xbr/super-xbr-pass2.slang"
filter_linear2 = "false"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "false"
alias2 = "PassPrev2"
float_framebuffer2 = "false"
srgb_framebuffer2 = "false"
scale_type_x2 = "source"
scale_x2 = "1.000000"
scale_type_y2 = "source"
scale_y2 = "1.000000"
shader3 = "shaders_slang/xbr/shaders/super-xbr/super-xbr-pass0.slang"
filter_linear3 = "false"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = ""
float_framebuffer3 = "false"
srgb_framebuffer3 = "false"
scale_type_x3 = "source"
scale_x3 = "1.000000"
scale_type_y3 = "source"
scale_y3 = "1.000000"
shader4 = "shaders_slang/xbr/shaders/super-xbr/super-xbr-pass1b.slang"
filter_linear4 = "false"
wrap_mode4 = "clamp_to_border"
mipmap_input4 = "false"
alias4 = ""
float_framebuffer4 = "false"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "2.000000"
scale_type_y4 = "source"
scale_y4 = "2.000000"
shader5 = "shaders_slang/xbr/shaders/super-xbr/super-xbr-pass2.slang"
filter_linear5 = "false"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
alias5 = ""
float_framebuffer5 = "false"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "1.000000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "shaders_slang/warp/shaders/smart-morph.slang"
filter_linear6 = "false"
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
alias6 = ""
float_framebuffer6 = "false"
srgb_framebuffer6 = "false"
scale_type_x6 = "source"
scale_x6 = "1.000000"
scale_type_y6 = "source"
scale_y6 = "1.000000"
shader7 = "shaders_slang/deblur/shaders/deblur.slang"
filter_linear7 = "true"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
alias7 = ""
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
parameters = "XBR_EDGE_STR;XBR_WEIGHT;XBR_ANTI_RINGING;MODE;XBR_EDGE_SHP;XBR_TEXTURE_SHP;SM_MODE;SM_PWR;SM_STRMIN;SM_STRMAX;SM_CUTLO;SM_CUTHI;SM_DEBUG;OFFSET;DEBLUR;SMART"
XBR_EDGE_STR = "2.000000"
XBR_WEIGHT = "1.000000"
XBR_ANTI_RINGING = "1.000000"
MODE = "2.000000"
XBR_EDGE_SHP = "0.400000"
XBR_TEXTURE_SHP = "1.000000"
SM_MODE = "0.000000"
SM_PWR = "0.500000"
SM_STRMIN = "0.000000"
SM_STRMAX = "1.000000"
SM_CUTLO = "0.000000"
SM_CUTHI = "1.000000"
SM_DEBUG = "0.000000"
OFFSET = "2.000000"
DEBLUR = "2.000000"
SMART = "0.500000"
3 Likes

Hey mate , I kinda hit into a wall .

First Image is done with ESRGAN , second is no shader and the third is this preset over here:

https://mega.nz/file/UcZG0LTS#esPVq1Jk0EGaDxE9nlc8mq3qPFSXurTlZHB75WIplx0

Now I understand that Esrgan results are down right impossible , but aside from the detail aspect , I was wondering if I could the same sort of crisp look as Scalefx . Thoughts ?

1 Like

Use the new shaders/presets from @guest in this Thread post.

To achieve the desired sharpness/smoothness you have to use anti-aliasing shaders combined with deblur shader after the scaler.

The scaler is the shader that will increase/decrease resolution. It can be many: xbr, scalerfx, bicubic, jinc, super-xbr etc.

Anti-aliasing should be set at 1x and using linear sampling. Some of them: advanced-aa, fxaa, 4xsoft, etc. These shaders can work as scalers too though.

Deblur is a sharpen shader. It will increase gradient of edges minimizing introduction of ringing. It’s the best of its kind for pixel art images. It should be put before the last shader, working at 1x and using linear sampling.

If the scaler already output anti-aliased image you should skip the anti-aliasing step.

yeah , coincidentally I was just about test that .talk about timing :sweat_smile:

1 Like

Hi @Hyllian,

Eight years ago, I implemented a version of xBR 2x as a g’mic filter (which is still functional to this day). It was a direct conversion of C# code from “ImageResizer”, but with no real understanding of how it worked. @Reptoria has recently made me think about updating this filter for the benefit of all g’mic users and to truly understand the method.

To that end, I have a couple questions:

  1. Could you point me to a canonical/baseline implementation to work from? I became truly lost amongst the forest of shader code (there appear to be many versions). A direct link would be most appreciated!
  2. Has the distance function since changed? In particular, the yuv conversion looks unusual. I’d be happy to discuss that if you wish.

I totally understand if you don’t have the time or will to respond, but thanks for the great algorithm anyway!

1 Like

Hi.

The xBR C implementation is very old and very different from shaders.

The latest advance C implementation are those made by zenjhu in xBRZ. I think you can find C or C++ implementation of this using Google.

As for The most representative xbr shader, my vanilla shader is always the xbr-lv2.cg. Though it isn’t the most advanced.

The distance is different depending on The shader flavour. The accuracy one is the most advanced, though a bit slower.

1 Like

Excellent thanks, in that case this seems like a good time to learn shader code! At first glance it doesn’t look too intimidating.

The curiosity I have about yuv is use of abs() on the (I assume “gamma” compressed for crt) rgb difference values before conversion, whereas that would normally be the final step only. That follows from: target space difference = Ma - Mb = M(a - b), where M is the colour space conversion matrix and a and b are rgb vectors to compare. But, this post being so very old now that could have no relevance.

Thanks again, I’ll try to keep further questions to a minimum!

1 Like

There’s no gamma conversion on xBR. And I converter rgb to luma only. Y is just luminance. The diferences are just lum differencies, hence the abs function.

Agreed, it all looks fine to me in the shader version; abs only happens after luma conversion. This also looks more readable than the old C# version, which is a bonus.

I’ll drop this here. The deblur shader i’ve been working out recently works very well with xbr-lvl3-noblend too. A modified version (included here) must be used, since the repo version does blending.

Edit: multipass version added:

https://mega.nz/file/QgYwBBKZ#0btmhv8QuJStpT9aJPsZg3rfeDWkb8WtSvgk1TwFzWQ

2 Likes

That’s a very good combination, @guest.r!

It reminds me I have to start working on a lvl6 noblend version. I’m just a bit lazy.

I’ll need to refactore all super-xbr shader presets. I’ll swap jinc by deblur as the final touch. I think it gives a better sharpness and doesn’t introduce ringing.

3 Likes

Hi @Hyllian I’m really enjoying all the use of the downscaled XBR in your shader and others, it looks really great :smiley:.

I was wondering if you could explain what is the difference between the different XBRs, 2p, 3p, 6p, adaptive, lv3-multipass , mlv4-multipass, etc.

3 Likes

Lately I’m a bit busy with real life, but I’ll try to explain your question here. Sorry for my weird english, kkk:

2p, 3p are related to the numbers of passes. So, super-xbr-2p-xxx delivers sxbr filtering in only two passes. Though I can filter in only two passes, it can be improved with an aditional pass. So, usually, I upscale (with sxbr algorithm) at 2x scale in two or three passes. The 3p version is a bit better than 2p in IQ, though a bit slower. The 3p versions fix the half pixel offset result of the sxbr (in two passes) and smooths the overall image quality.

And, the 6p version is just sxbr-3p applied two times so that we can get a 4x scaling.

Now, about the LVL keyword I use (It’s used only for XBR, not super-xbr), it’s related to the kind of staircase that can be completely smoothned by the algorithm. So, for example, a XBR-LV1 can only smooths perfectly a staircase of 45 degree. If the staircase is 60 degree, for example, the result will be a softened staircase not entirely smoothened. For that I need a LVL2 version. The LVL2 takes care of staircases of 30, 45 and 60 degree staircases. And LVL3 takes care of even more kinds of staircases.

The most advanced XBR I could implement is the mlv4, which means multilevel4. While the others use a greedy implementation where they always try to smooth with the biggest LVL possible. This one use some euristics where it always decides the most appropriate LVL (among 1 to 4) for each staircase kind. It’s a bit complex so I had to implement it in four passes.

For future develpment, I think it’s possible to achieve LVL6 (like in ScaleFX), though I couldn’t find an easy way to make it, yet. Maybe someone else can continue from here (@guest.r ? :wink: ).

2 Likes

Thanks so much for the explanation! This helps me a bunch :slight_smile:

Also, not to worry @Hyllian, your English is great! :smile:

2 Likes

I think most folks get it a bit wrong here, since xBR LVL2 is a great achievement and coders who continued xBR focused more or less on the LVL2 implementation. Currently i’m interested in adding some accuracy to the single pass xBR-LVL3 (noblend) and see how it goes. From my observations LVL3 could be a reasonable limitation for fast high consistency arbitrary logic scalers within current times reach.

2 Likes

I strongly agree on the accuracy thing . I think one way to obtain the accuracy , is to allow the shader to produce it’s own artifacts whenever it’s trying to deal with curves .That is to say , not everything has to be vectors especially the curves.

I think the best type of artifacts would be tiny square pixels , even smaller then the raw image.

Here is couple of pics that illustrate how each shader deal with curves

Raw (no shader)

MMPX , new shader that keeps original corners intact

xbr3

scalefx-hybrid-scaler-sharp (default values)

scalefx-hybrid-scaler-sharp (Adjusted values)

Biliner

A shader preset I put together from different passes ,it’s a bit blurry so it can’t show good colors.

(mega link down below )

https://mega.nz/file/cZh3wYLY#1E-VgIiPV3bGJsM-GxBgpziBfrcOkcT7vYxisj-hXiY

I think it would be more accurate to source material if we target a shader that aims to produce a slightly pixelated image (like Hi-res sprites) rather then HD one that would produce vectors on curves.

3 Likes

Fascinating topic. It’s interesting to see how theae shaders developed over time. Quick question: What shader is supposed to be faster, less resoirces hungry? Xbr (2p, 3p?) or ScaleFX?