# The myth of infinite detail: Bilinear vs. Bicubic

Have you ever noticed how, in movies and television, actors can take a crappy, grainy low-res traffic camera picture of a distant automobile and somehow "enhance" the image until they can read the license plate perfectly?

This is a companion discussion topic for the original blog entry at: http://www.codinghorror.com/blog/2005/08/the-myth-of-infinite-detail-bilinear-vs-bicubic.html
1 Like

Stuff like 2xSaI will get much better results for drawings like Hello Kitty there.

BTW, for EXACTLY the type of image you show here - the low-res, low-color, sharp Hello Kitty - the emulator writers have created a bunch of very good heuristics interpolation algorithms, e.g. 2xSAI.

The example you gave is not very example of the astonishingly high quality effects you get with bicubic and bilinear scaling.

Line drawings, such as the one you use, donâ€™t scale well.

However, photographs scale extremely well and the difference between bicubic and bilinear become very apparent.

photographs scale extremely well

A photograph actually has a lot of pixels (a real one, that is)

I think both of you are referring to real photography with professional grade pixel counts, eg, many megapixels. Iâ€™m thinking of the low-res, grainy security and traffic camera images I frequently see â€śenhancedâ€ť to reveal detail on shows like televisionâ€™s â€ś24â€ť.

But I agree that if you have a picture with megapixels of detail to start with, a bit of reasonable upscaling can be done.

very good heuristics interpolation algorithms, e.g. 2xSAI

That is very cool! More screenshots of it in action:

http://elektron.its.tudelft.nl/~dalikifa/#Screenshots

as well as a detail shot at the top of that page.

The problem of photographic enhancement is usually not one of â€śmaking pixelsâ€ť (scaling up). A photograph actually has a lot of pixels (a real one, that is); the average size of a silver grain on film is about 1 micron. A typical 35mm negative is 24x36 mm (donâ€™t ask). Assuming you scan the film at the full resolution of the grains, you would get 24M x 36M pixels, or 864M pixels. Even if the region of interest doesnâ€™t fill the frame, you will generally have plenty of pixels for the subregion (e.g. license plate).

The problem is that the lens system probably wasnâ€™t focused exactly on the subregion (license plate), so you have to sharpen the image to enhance it. There are many techniques for sharpening, most of which use a convolution kernel. This is a matrix operation in which a pixelâ€™s value is changed by applying postive and negative offsets computed from nearby pixels. For example, the â€śunsharp maskâ€ť operation is a convolution kernel. It is amazing how much contrast and detail can be recovered from an out-of-focus image using this technique. Often you really can read the license plate.

Ole

P.S. In case youâ€™re wondering, there are devices which can scan at micron or even submicron resolution, which is two orders of magnitude beyond your typical flatbed scanner. My company Aperio makes instruments called ScanScopes which are used for scanning microscope slides; the resulting images can have a resolution of .25 microns (approx. 100,000 dpi). A typical microscope slide has a tissue area of about 20mm x 15mm, so the resulting digital images are around 100M x 60M pixels. Thatâ€™s a lot of pixels.

Sigh. This is what I get for commenting before Iâ€™ve finished my coffee. I used some Ms instead of Ks. A 35mm negative at 1 micron/pixel would have 24K x 36K pixels (which is 864M pixels, as noted). A 20mm x 15mm microscope slide at .25 microns/pixel would have 100K x 60K pixels (which is 6G pixels).

Donâ€™t you know that traffic cameras record everything using vector based representation? Thatâ€™s how they do it. And they have in instantaneous shutter speed and Petabytes of storage capacity.

However, photographs scale extremely well and the difference between bicubic and bilinear become very apparent.

Iâ€™m not so sure about this, particularly for the relatively low-res images I was referring to in the post. Hereâ€™s an example 640x428 photo image:

http://www.codinghorror.com/blog/images/woz_roth.jpg

I blew this up 300% using both bilinear and bicubic. Then I zoomed in to 100% and browsed the image. I noticed that the sharpening effect of bicubic makes the JPEG artifacts far more prononounced.

That said, bicubic and bilinear are essentially the same. Youâ€™re basically choosing between a SLIGHTLY sharper image (bicubic) or a SLIGHTLY blurrier one (bilinear). When upsizing an image, I think itâ€™s somewhat dangerous to err on the side of sharpening, although I guess this depends how good the source image is. And your decision might be different when you are downsizing the imageâ€¦

I think the people pointing out that bicubic interpolation works best on photos werenâ€™t trying to claim that this would enable you to expand 3 pixels into a sharp and legible number plate. I donâ€™t think anyoneâ€™s taking issue with your basic claim that the magic zoom beloved of crime drama writers is impossible.

I think they were just pointing out that bicubic interpolation is often significantly better than bilinear interpolation on photographic images. What you wrote implies that bicubic interpolation is pointless because it offers no benefits over bilinear. Thatâ€™s not actually true - itâ€™s not a magic bullet, but itâ€™s a definite improvement for certain scenarios.

(But it does interact unfortunately with artifacts on over-compressed JPEGs, as you observeâ€¦)

Hi,

And have you heard of GREYCstoration?
http://www.greyc.ensicaen.fr/~dtschump/greycstoration

It can be used for image resizing, among the others, and it does wonders.

See Image Resizing on http://www.greyc.ensicaen.fr/~dtschump/greycstoration/demonstration.html

the problem with your hello kitty isnâ€™t that itâ€™s not like a photograph. The problem is that many of the jaggies are double-pixel. If you look at the feet, you can see single-pixel jaggies. These turn into very impressive straight lines using bicubic. The double-pixel features hardly even get blurred by either algorithm, and that is the way you would want it to work.

Anyway, youâ€™re second on a google search of â€śbilinear vs bicubic,â€ť so you have much honor and responsibility to fix this.

tho i wonder if maybe one reason youâ€™re second is cuz you seem to â€śdebunkâ€ť the advantage of bicubic.

If you want to see an example of an â€śimpossibleâ€ť increase in clarity of a blurry image, check out the sharpened video of the tiles that fell off the space shuttle, leading to the explosion on reentry. The original video that they had was taken from miles away, and showed about what youâ€™d expect: a blurry mess. After some NASA magic, it clearly showed the tiles hitting the wing. If you ask me, Iâ€™d say that itâ€™s possible because Iâ€™ve seen it (but I donâ€™t know how they did it!)

I saw this algorithm called hq2x/hq3x/hq4x (pick one, it just describes the magnification). It seems fairly good for images like this

1 Like

10 years later, there is super resolution with deep neural networks

1 Like

â€śA bit blurry, yes, but clearly superior to giant chunky pixels.â€ť I Actually prefer the sharp blocky 300% version using pixel scaling (naive nearest neighbour) over the bilinear filtering and bicubic filtering versions. To me those last two with their blurry borders are clearly not better at all. The reason is that limited lineart resolution/fixed palette images (2,4,8 bit) should not be blurred by bilinear/bicubic filtering. That only works sort of okay on true colour (24bit colour) images / photographic material.

Also note that bicubic isnâ€™t one specific interpolation method. http://entropymine.com/imageworsener/bicubic/

1 Like

I made an image scaling method called four times four oversampling, and another image scaling method called smooth image scaling method.

Four times four oversampling scaling:

``````#include <stdint.h>
#include <fstream>
#include <math.h>

std::fstream input;
std::fstream output;

const int_fast32_t sRGBtolinear[256] = {0, 20, 40, 60, 80, 99, 119, 139, 159, 179, 199,
219, 241, 264, 288, 313, 340, 367, 396, 427, 458, 491, 526, 562, 599, 637, 677,
718, 761, 805, 851, 898, 947, 997, 1048, 1101, 1156, 1212, 1270, 1330, 1391,
1453, 1517, 1583, 1651, 1720, 1791, 1863, 1937, 2013, 2090, 2170, 2250, 2333,
2418, 2504, 2592, 2681, 2773, 2866, 2961, 3058, 3157, 3258, 3360, 3464, 3570,
3678, 3788, 3900, 4014, 4129, 4247, 4366, 4488, 4611, 4736, 4864, 4993, 5124,
5257, 5392, 5530, 5669, 5810, 5953, 6099, 6246, 6395, 6547, 6701, 6856, 7014,
7174, 7336, 7500, 7666, 7834, 8004, 8177, 8352, 8529, 8708, 8889, 9072, 9258,
9446, 9636, 9828, 10022, 10219, 10418, 10619, 10822, 11028, 11236, 11446, 11658,
11873, 12090, 12309, 12531, 12754, 12981, 13209, 13440, 13673, 13909, 14147,
14387, 14629, 14874, 15122, 15372, 15624, 15878, 16135, 16394, 16656, 16920,
17187, 17456, 17727, 18001, 18278, 18556, 18838, 19121, 19408, 19696, 19988,
20281, 20578, 20876, 21178, 21481, 21788, 22096, 22408, 22722, 23038, 23357,
23679, 24003, 24329, 24659, 24991, 25325, 25662, 26002, 26344, 26689, 27036,
27387, 27739, 28095, 28453, 28813, 29177, 29543, 29911, 30283, 30657, 31033,
31413, 31795, 32180, 32567, 32957, 33350, 33746, 34144, 34545, 34949, 35355,
35765, 36177, 36591, 37009, 37429, 37852, 38278, 38707, 39138, 39572, 40009,
40449, 40892, 41337, 41786, 42237, 42691, 43147, 43607, 44069, 44534, 45003,
45474, 45947, 46424, 46904, 47386, 47871, 48360, 48851, 49345, 49842, 50342,
50844, 51350, 51859, 52370, 52884, 53402, 53922, 54445, 54972, 55501, 56033,
56568, 57106, 57647, 58191, 58738, 59288, 59841, 60397, 60956, 61518, 62083,
62651, 63222, 63796, 64373, 64953, 65536};
const int_fast32_t lineartosRGBthr[256] = {0, 10, 30, 50, 70, 90, 110, 130, 150, 170, 189, 209, 230, 253, 276, 301, 327, 354,
382, 412, 443, 475, 509, 544, 580, 618, 657, 698, 740, 783, 828, 875, 923, 972,
1023, 1075, 1129, 1185, 1242, 1300, 1360, 1422, 1486, 1551, 1617, 1685, 1755, 1827
, 1900, 1975, 2052, 2130, 2210, 2292, 2376, 2461, 2548, 2637, 2727, 2820, 2914
, 3010, 3108, 3208, 3309, 3412, 3518, 3625, 3734, 3844, 3957, 4072, 4188, 4307,
4427, 4550, 4674, 4800, 4929, 5059, 5191, 5325, 5461, 5600, 5740, 5882, 6026, 6172
, 6321, 6471, 6624, 6779, 6935, 7094, 7255, 7418, 7583, 7750, 7920, 8091, 8265
, 8440, 8618, 8798, 8981, 9165, 9352, 9541, 9732, 9925, 10121, 10318, 10518, 10721
, 10925, 11132, 11341, 11552, 11766, 11981, 12200, 12420, 12643, 12868, 13095,
13325, 13557, 13791, 14028, 14267, 14508, 14752, 14998, 15247, 15498, 15751, 16007
, 16265, 16525, 16788, 17054, 17322, 17592, 17864, 18140, 18417, 18697, 18980
, 19265, 19552, 19842, 20135, 20430, 20727, 21027, 21330, 21635, 21942, 22252, 22565
, 22880, 23198, 23518, 23841, 24166, 24494, 24825, 25158, 25494, 25832, 26173
, 26517, 26863, 27212, 27563, 27917, 28274, 28633, 28995, 29360, 29727, 30097,
30470, 30845, 31223, 31604, 31987, 32373, 32762, 33154, 33548, 33945, 34345, 34747
, 35152, 35560, 35971, 36384, 36800, 37219, 37641, 38065, 38493, 38923, 39355,
39791, 40229, 40671, 41115, 41562, 42011, 42464, 42919, 43377, 43838, 44302, 44769
, 45238, 45711, 46186, 46664, 47145, 47629, 48116, 48605, 49098, 49593, 50092
, 50593, 51097, 51604, 52114, 52627, 53143, 53662, 54184, 54709, 55236, 55767, 56300
, 56837, 57377, 57919, 58465, 59013, 59564, 60119, 60676, 61237, 61800, 62367
, 62936, 63509, 64084, 64663, 65245};

uint_least8_t lineartosRGB(int32_t value){
uint_least8_t a = 0;
if(lineartosRGBthr[a+128] <= value) a+=128;
if(lineartosRGBthr[a+ 64] <= value) a+= 64;
if(lineartosRGBthr[a+ 32] <= value) a+= 32;
if(lineartosRGBthr[a+ 16] <= value) a+= 16;
if(lineartosRGBthr[a+  8] <= value) a+=  8;
if(lineartosRGBthr[a+  4] <= value) a+=  4;
if(lineartosRGBthr[a+  2] <= value) a+=  2;
if(lineartosRGBthr[a+  1] <= value) a+=  1;
return a;
}

uint32_t RGBavg(const uint32_t* pixel, const int_fast16_t amount){
int_fast32_t red = 0;
int_fast32_t green = 0;
int_fast32_t blue = 0;
for(int_fast16_t i=0; i<amount; i++){
red   += sRGBtolinear[(pixel[i]/65536)%256];
green += sRGBtolinear[(pixel[i]/  256)%256];
blue  += sRGBtolinear[(pixel[i]/    1)%256];
}
return lineartosRGB((red+(amount/2))/amount)*65536+lineartosRGB((green+(amount/2))/amount)*256+lineartosRGB((blue+(amount/2))/amount)*1;
}

uint32_t findpixel(uint64_t* coord, uint32_t* originalimage, uint64_t width, uint64_t* numerator, uint64_t* denominator){
uint64_t x[4];
uint64_t y[4];
x[0] = ((coord[0]*8+1)*denominator[0])/(8*numerator[0]);
x[1] = ((coord[0]*8+3)*denominator[0])/(8*numerator[0]);
x[2] = ((coord[0]*8+5)*denominator[0])/(8*numerator[0]);
x[3] = ((coord[0]*8+7)*denominator[0])/(8*numerator[0]);
y[0] = ((coord[1]*8+1)*denominator[1])/(8*numerator[1]);
y[1] = ((coord[1]*8+3)*denominator[1])/(8*numerator[1]);
y[2] = ((coord[1]*8+5)*denominator[1])/(8*numerator[1]);
y[3] = ((coord[1]*8+7)*denominator[1])/(8*numerator[1]);
uint32_t pixel[16];
for(int_fast8_t i=0; i<16; i++){
pixel[i] = originalimage[x[i%4]+y[i/4]*width];
}
return RGBavg(pixel, 16);
}

#include <iostream>

int main(){
uint_fast32_t inputwidth; uint_fast32_t inputheight; uint_fast16_t inputpadding; uint_fast32_t outputwidth; uint_fast32_t outputheight;
std::cout << "Input width, input height, input padding, output width, output height\n";
std::cin >> inputwidth; std::cin >> inputheight; std::cin >> inputpadding; std::cin >> outputwidth; std::cin >> outputheight;
uint32_t* image = (uint32_t*)malloc(inputwidth*inputheight*sizeof(uint32_t));
input.open("input", std::ios::in|std::ios::binary);
input.seekg (0, input.beg);
for(uint64_t i=0; i<inputwidth*inputheight; i++){
if(!(i%inputwidth) && i>0){
char garbage;
}
}
}
input.close();
uint32_t color = 0;
output.open("output", std::ios::out|std::ios::binary);
for(uint64_t i=0; i<outputwidth*outputheight; i++){
uint64_t coord[2] = {i%outputwidth, i/outputwidth};
uint64_t numerator[2] = {outputwidth, outputheight};
uint64_t denominator[2] = {inputwidth, inputheight};
color = findpixel(coord, image, inputwidth, numerator, denominator);
output.write((char*)&color, 3);
}
output.close();
free(image);
}

``````

Smooth image scaling filter:

``````/*
smooth integrated filter
Piotr Grochowski, piotrunio-2004@wp.pl

The name of a smooth integrated filter and the names of its contributors may be
used to endorse and/or promote products derived from this software. The
contributor names include "Piotr Grochowski".

All trademark and patent rights held by a smooth integrated filter are waived,
abandoned, surrendered, licensed and affected by this document.

This copyright notice and this permission notice do not have to appear in any
copies or derivative works.

Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/

#include <stdint.h>
#include <fstream>
#include <math.h>

std::fstream input;
std::fstream output;

double findsmoothfiltervalue(double x){
if(x>=2.5||x<=-2.5)return 0.0;
if(x>=1.5||x<=-1.5)return pow(2.5-abs(x), 2.0)*0.25+pow(2.5-abs(x), 3.0)/-3.0;
if(x>=0.5||x<=-0.5)return 1.0/-12.0+(1.5-abs(x))*-0.5+pow(1.5-abs(x), 2.0)*0.5+pow(1.5-abs(x), 3.0)/1.5;
return 29.0/24.0-x*x*2.5;
}

double findsmoothfilterintegralvalue(double x){
if(x<=-2.5)return 0.0;
if(x<=-1.5)return pow(2.5-abs(x), 3.0)/12.0+pow(2.5-abs(x), 4.0)/-12.0;
if(x<=-0.5)return (1.5-abs(x))/-12.0+pow(1.5-abs(x), 2.0)*-0.25+pow(1.5-abs(x), 3.0)/6.0+pow(1.5-abs(x), 4.0)/6.0;
if(x>=+2.5)return 1.0;
if(x>=+1.5)return 1.0-(pow(2.5-abs(x), 3.0)/12.0+pow(2.5-abs(x), 4.0)/-12.0);
if(x>=+0.5)return 1.0-((1.5-abs(x))/-12.0+pow(1.5-abs(x), 2.0)*-0.25+pow(1.5-abs(x), 3.0)/6.0+pow(1.5-abs(x), 4.0)/6.0);
return 0.5+29.0/24.0*x-x*x*x*(5.0/6.0);
}

double findsmoothfilterintegralrange(double x, double y){
return findsmoothfilterintegralvalue(y)-findsmoothfilterintegralvalue(x);
}

const int_fast32_t sRGBtolinear[256] = {0, 20, 40, 60, 80, 99, 119, 139, 159, 179, 199,
219, 241, 264, 288, 313, 340, 367, 396, 427, 458, 491, 526, 562, 599, 637, 677,
718, 761, 805, 851, 898, 947, 997, 1048, 1101, 1156, 1212, 1270, 1330, 1391,
1453, 1517, 1583, 1651, 1720, 1791, 1863, 1937, 2013, 2090, 2170, 2250, 2333,
2418, 2504, 2592, 2681, 2773, 2866, 2961, 3058, 3157, 3258, 3360, 3464, 3570,
3678, 3788, 3900, 4014, 4129, 4247, 4366, 4488, 4611, 4736, 4864, 4993, 5124,
5257, 5392, 5530, 5669, 5810, 5953, 6099, 6246, 6395, 6547, 6701, 6856, 7014,
7174, 7336, 7500, 7666, 7834, 8004, 8177, 8352, 8529, 8708, 8889, 9072, 9258,
9446, 9636, 9828, 10022, 10219, 10418, 10619, 10822, 11028, 11236, 11446, 11658,
11873, 12090, 12309, 12531, 12754, 12981, 13209, 13440, 13673, 13909, 14147,
14387, 14629, 14874, 15122, 15372, 15624, 15878, 16135, 16394, 16656, 16920,
17187, 17456, 17727, 18001, 18278, 18556, 18838, 19121, 19408, 19696, 19988,
20281, 20578, 20876, 21178, 21481, 21788, 22096, 22408, 22722, 23038, 23357,
23679, 24003, 24329, 24659, 24991, 25325, 25662, 26002, 26344, 26689, 27036,
27387, 27739, 28095, 28453, 28813, 29177, 29543, 29911, 30283, 30657, 31033,
31413, 31795, 32180, 32567, 32957, 33350, 33746, 34144, 34545, 34949, 35355,
35765, 36177, 36591, 37009, 37429, 37852, 38278, 38707, 39138, 39572, 40009,
40449, 40892, 41337, 41786, 42237, 42691, 43147, 43607, 44069, 44534, 45003,
45474, 45947, 46424, 46904, 47386, 47871, 48360, 48851, 49345, 49842, 50342,
50844, 51350, 51859, 52370, 52884, 53402, 53922, 54445, 54972, 55501, 56033,
56568, 57106, 57647, 58191, 58738, 59288, 59841, 60397, 60956, 61518, 62083,
62651, 63222, 63796, 64373, 64953, 65536};
const int_fast32_t lineartosRGBthr[256] = {0, 10, 30, 50, 70, 90, 110, 130, 150, 170, 189, 209, 230, 253, 276, 301, 327, 354,
382, 412, 443, 475, 509, 544, 580, 618, 657, 698, 740, 783, 828, 875, 923, 972,
1023, 1075, 1129, 1185, 1242, 1300, 1360, 1422, 1486, 1551, 1617, 1685, 1755, 1827
, 1900, 1975, 2052, 2130, 2210, 2292, 2376, 2461, 2548, 2637, 2727, 2820, 2914
, 3010, 3108, 3208, 3309, 3412, 3518, 3625, 3734, 3844, 3957, 4072, 4188, 4307,
4427, 4550, 4674, 4800, 4929, 5059, 5191, 5325, 5461, 5600, 5740, 5882, 6026, 6172
, 6321, 6471, 6624, 6779, 6935, 7094, 7255, 7418, 7583, 7750, 7920, 8091, 8265
, 8440, 8618, 8798, 8981, 9165, 9352, 9541, 9732, 9925, 10121, 10318, 10518, 10721
, 10925, 11132, 11341, 11552, 11766, 11981, 12200, 12420, 12643, 12868, 13095,
13325, 13557, 13791, 14028, 14267, 14508, 14752, 14998, 15247, 15498, 15751, 16007
, 16265, 16525, 16788, 17054, 17322, 17592, 17864, 18140, 18417, 18697, 18980
, 19265, 19552, 19842, 20135, 20430, 20727, 21027, 21330, 21635, 21942, 22252, 22565
, 22880, 23198, 23518, 23841, 24166, 24494, 24825, 25158, 25494, 25832, 26173
, 26517, 26863, 27212, 27563, 27917, 28274, 28633, 28995, 29360, 29727, 30097,
30470, 30845, 31223, 31604, 31987, 32373, 32762, 33154, 33548, 33945, 34345, 34747
, 35152, 35560, 35971, 36384, 36800, 37219, 37641, 38065, 38493, 38923, 39355,
39791, 40229, 40671, 41115, 41562, 42011, 42464, 42919, 43377, 43838, 44302, 44769
, 45238, 45711, 46186, 46664, 47145, 47629, 48116, 48605, 49098, 49593, 50092
, 50593, 51097, 51604, 52114, 52627, 53143, 53662, 54184, 54709, 55236, 55767, 56300
, 56837, 57377, 57919, 58465, 59013, 59564, 60119, 60676, 61237, 61800, 62367
, 62936, 63509, 64084, 64663, 65245};

uint_least8_t lineartosRGB(int32_t value){
uint_least8_t a = 0;
if(lineartosRGBthr[a+128] <= value) a+=128;
if(lineartosRGBthr[a+ 64] <= value) a+= 64;
if(lineartosRGBthr[a+ 32] <= value) a+= 32;
if(lineartosRGBthr[a+ 16] <= value) a+= 16;
if(lineartosRGBthr[a+  8] <= value) a+=  8;
if(lineartosRGBthr[a+  4] <= value) a+=  4;
if(lineartosRGBthr[a+  2] <= value) a+=  2;
if(lineartosRGBthr[a+  1] <= value) a+=  1;
return a;
}

uint32_t RGBavg(const uint32_t* pixel, const int_fast16_t amount){
int_fast32_t red = 0;
int_fast32_t green = 0;
int_fast32_t blue = 0;
for(int_fast16_t i=0; i<amount; i++){
red   += sRGBtolinear[(pixel[i]/65536)%256];
green += sRGBtolinear[(pixel[i]/  256)%256];
blue  += sRGBtolinear[(pixel[i]/    1)%256];
}
return lineartosRGB((red+(amount/2))/amount)*65536+lineartosRGB((green+(amount/2))/amount)*256+lineartosRGB((blue+(amount/2))/amount)*1;
}

uint32_t RGBweightedavg(const uint32_t* pixel, const double* weight, const int amount){
double red = -0.0;
double green = -0.0;
double blue = -0.0;
double sum = -0.0;
for(int i=0; i<amount; i++){
red   += sRGBtolinear[(pixel[i]/65536U)%256U]*weight[i];
green += sRGBtolinear[(pixel[i]/  256U)%256U]*weight[i];
blue  += sRGBtolinear[(pixel[i]/    1U)%256U]*weight[i];
sum   += weight[i];
}
return lineartosRGB(floor(red/sum+0.5))*65536+lineartosRGB(floor(green/sum+0.5))*256+lineartosRGB(floor(blue/sum+0.5))*1;
}

uint32_t findpixel(uint_fast32_t* coord, uint32_t* originalimage, int width, int height, int width2, int height2){
double filtersize[2];
if(width2 >width )filtersize[0]=1.0; else filtersize[0]=(double)width /(double)width2 ;
if(height2>height)filtersize[1]=1.0; else filtersize[1]=(double)height/(double)height2;
double offset[4];
if(width2 >width ){
offset[0]=(1.0-(double)width /(double)width2 )/2.0;
offset[2]=(1.0+(double)width /(double)width2 )/2.0;
}
else{
offset[0]=0;
offset[2]=1;
}
if(height2>height){
offset[1]=(1.0-(double)height/(double)height2)/2.0;
offset[3]=(1.0+(double)height/(double)height2)/2.0;
}
else{
offset[1]=0;
offset[3]=1;
}
double center[2];
center[0]=(double)width /(double)width2 *((double)coord[0]+0.5);
center[1]=(double)height/(double)height2*((double)coord[1]+0.5);
int endpoints[4];
if(endpoints[0]<0     )endpoints[0]=0;
if(endpoints[1]<0     )endpoints[1]=0;
if(endpoints[2]>width )endpoints[2]=width;
if(endpoints[3]>height)endpoints[3]=height;
//return (endpoints[2]-endpoints[0])*(endpoints[3]-endpoints[1]);
uint32_t* pixel  = (uint32_t*)malloc(sizeof(uint32_t)*(endpoints[2]-endpoints[0])*(endpoints[3]-endpoints[1]));
double  * weight = (double  *)malloc(sizeof(double  )*(endpoints[2]-endpoints[0])*(endpoints[3]-endpoints[1]));
for(int i=endpoints[1]; i<endpoints[3]; i++){
for(int j=endpoints[0]; j<endpoints[2]; j++){
pixel [(i-endpoints[1])*(endpoints[2]-endpoints[0])+(j-endpoints[0])]=originalimage[i*width+j];
weight[(i-endpoints[1])*(endpoints[2]-endpoints[0])+(j-endpoints[0])]=
(findsmoothfilterintegralrange((j+offset[0]-center[0])/filtersize[0], (j+offset[2]-center[0])/filtersize[0]))*
(findsmoothfilterintegralrange((i+offset[1]-center[1])/filtersize[1], (i+offset[3]-center[1])/filtersize[1]));
}
}
uint32_t found = RGBweightedavg(pixel, weight, (endpoints[2]-endpoints[0])*(endpoints[3]-endpoints[1]));
free(pixel);
free(weight);
return found;
}

#include <iostream>

int main(){
uint_fast16_t inputheader; uint_fast32_t inputwidth; uint_fast32_t inputheight; uint_fast16_t inputpadding; uint_fast32_t outputwidth; uint_fast32_t outputheight;
std::cout << "Input header, input width, input height, input padding, output width, output height\n";
std::cin >> inputheader; std::cin >> inputwidth; std::cin >> inputheight; std::cin >> inputpadding; std::cin >> outputwidth; std::cin >> outputheight;
uint32_t* image = (uint32_t*)malloc(inputwidth*inputheight*sizeof(uint32_t));
input.open("input", std::ios::in|std::ios::binary);
input.seekg (0, input.beg);
char garbage;
}
for(uint64_t i=0; i<inputwidth*inputheight; i++){
if(!(i%inputwidth) && i>0){
}
}
}
input.close();
uint32_t color = 0;
output.open("output", std::ios::out|std::ios::binary);
for(int i=0; i<outputwidth*outputheight; i++){
uint_fast32_t coord[2] = {i%outputwidth, i/outputwidth};
color = findpixel(coord, image, inputwidth, inputheight, outputwidth, outputheight);
output.write((char*)&color, 3);
}
output.close();
free(image);
}

``````
2 Likes

One of the uses of smooth integrated filter is in Fuzzy PC and Zwarakay. In the two software, I make use of an image that gets scaled to the full screen with the smooth integrated filter, giving it a realistic appearance. In particular, Fuzzy PC originated from https://www.reddit.com/gg4rha/ , and later when I recreated it, the original user who had Fuzzy PC confirmed that my recreation is exactly the same.

2 Likes