0

UPDATE: Code is OK now, see edits at the end of question

I'm writing a simple application that is supposed to scale given image and display the result on the screen. Image loading, displaying etc is achieved throught SDL, but I still have problem with scaling function - it produces gibberish.

I have to operate on 24-bit images, thus the uint8_t casting and byte by byte calculations.

#include <stdint.h>
void blin(uint8_t* pixelsIn, uint8_t* pixelsOut, int w, int h, float scale)
{
    int index1, index2;
    int w2, h2;
    int i, j, k;
    float x, y;
    float t;
    int p1, p2;

    w2 = (int)(scale*w + 0.5);
    h2 = (int)(scale*h + 0.5);
    p1 = w*3;
    if(p1%4) p1 += (4-p1%4);
    p2 = w2*3;
    if(p2%4) p2 += (4-p2%4);
    for(i=0;i<h2;i++) //line
    {
        index2=i*p2;
        for(j=0;j<w2;j++) //column
        {
            x=((float)(j))/scale;
            index1=(int)(x) * 3;
            x-=(int)(x);
            y=((float)(i))/scale;
            index1+=(int)(y) * p1;
            y-=(int)(y);
            for(k=0;k<3;k++) //for color in R, G, B
            {
                t = (float)(pixelsIn[index1]) * (1.0-x)*(1.0-y);
                t += (float)(pixelsIn[index1+3]) * (x)*(1.0-y);
                t += (float)(pixelsIn[index1+p1]) * (1.0-x)*(y);
                t += (float)(pixelsIn[index1+p1+3]) * (x)*(y);
                pixelsOut[index2] = (uint8_t)(t);
                index1++;
                index2++;
            }
        }
    }
}

Edit: obvious error, index2 was not zeroed and x was calculated without multiplying by 3 (bytes per pixel). But image is still not properly scaled, this is before and after for scale=1.0 (jpgs just for faster upload):

Edit2: 2nd problem was 4-byte alignment inside SDL_Surface pixel structure. Now it works like a charm (code here is updated), though it's intended to work only on 24bit images - see comments to best answer.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
MJBogusz
  • 83
  • 1
  • 8
  • It looks quite ok to me. Perhaps you should try what happens when you use prototype `blin(src, dst, width_src,height_src, width_dst, height_dst);` making sure `dst` has exactly the right amount of space reserved and calculate separately `scale_w` and `scale_h` for each dimension. Also you should be aware, that if you downscale, you must start to pre-filter the image approximately at scale < 0.7. – Aki Suihkonen Jan 21 '14 at 07:19
  • For now I'm only upscaling both dimensions by same scale. – MJBogusz Jan 21 '14 at 07:48
  • Edit time passed... I'm well aware of interpolation having no sense when downscaling, and using unified scale is both a simplification and project assumption. It'll also make it easier for me later - the code will be rewritten to ASM. – MJBogusz Jan 21 '14 at 07:55
  • Some thoughts: If you wants to only use `float` math and not `double`, change `1.0` to 1.0f`, etc. Rather than `/scale`, pre-calc `float invscale = 1.0f/scale` and the use `*invscale`. May also pre-calc `(1.0-x)*(1.0-y);`. Note that ` index1=(int)(x);` is truncating and not rounding. Not sure `t` in `(uint8_t)(t)` will always be in 0-255 range. From a performance view, all FP math in the `for` loops can be eliminated with a `a*b >> shift`. But I do not think these will solve the issue - just some ideas. – chux - Reinstate Monica Jan 21 '14 at 08:33
  • Using 1.0f might be a good idea, but fixed-point arithmetics is not, at least in this case - the app is supposed to use SSE unit. T after casting to uint8 have no other choice than to be 0-255, it's an alias for unsigned char. And that truncation is intended. Also, scale is given as a float applicable for both dimensions. – MJBogusz Jan 21 '14 at 09:08
  • Okay, I've found the fundamental, yet easy to overlook error - index2 was never zeroed. However, though output is not a white noise anymore, it's not properly scaled - I'll update the question with image samples. – MJBogusz Jan 21 '14 at 10:26

1 Answers1

0

I think this line:-

index1=(int)(x);

should be:-

index1=(int)(x)*3;

Also, don't assume that the stride is the same as width.sizeof(pixel), the first pixel on a line may be aligned to a word/dword/etc boundary, so I'd change the code to:-

void blin(uint8_t* pixelsIn, uint8_t* pixelsOut, int w, int h, float scale, int input_stride, int output_stride)
{
    int index1, index2;
    int w2, h2;
    int i, j, k;
    float x, y;
    float t;

    w2=(int)(scale*w + 0.5);
    h2=(int)(scale*h + 0.5);
    index2=0;
    for(i=0;i<h2;i++) //line
    {
        int pixelindex2=index2;
        for(j=0;j<w2;j++) //column
        {
            x=((float)(j))/scale;
            index1=(int)(x)*3;
            x-=(int)(x);
            y=((float)(i))/scale;
            index1+=(int)(y) * input_stride;
            y-=(int)(y);
            for(k=0;k<3;k++) //for color in R, G, B
            {
                t = (float)(pixelsIn[index1]) * (1.0-x)*(1.0-y);
                t += (float)(pixelsIn[index1+3]) * (x)*(1.0-y);
                t += (float)(pixelsIn[index1+w*3]) * (1.0-x)*(y);
                t += (float)(pixelsIn[index1+w*3+3]) * (x)*(y);
                pixelsOut[pixelindex2] = (uint8_t)(t);
                index1++;
                pixelindex2++;
            }
        } //column
        index2+=output_stride;
    } //line
}

where input_stride and output_stride are the number of bytes between the start of consecutive lines which may not be the same as width * 3.

You may also want to consider making the '3' constant a variable so that you can deal with different image formats.

Skizz
  • 69,698
  • 10
  • 71
  • 108
  • Yes, this was the 2nd main problem - I've found it more or less at the same time. – MJBogusz Jan 21 '14 at 11:47
  • (1st was index2 was not zeroed - see edit). But there still are some wrong offset calculations - on 1.0, 1.2, 1.4 etc scaling is ok, but on 1.1, 1.3 etc there is a visible shift, probably during writing (http://i.imgur.com/3WFZ7w3.png). If I'll find an answer I'll add it here and update code in question for future reference - I've found no good and simple bilinear scaling C code so far. PS. Damn auto-send on enter and short edit times. – MJBogusz Jan 21 '14 at 11:56
  • @JohnSmith105127: I think you've now got a stride issue (i.e. number of pixels in line != number of bytes). Have updated answer. – Skizz Jan 21 '14 at 12:24
  • Yes, that was it, great thanks. Basing on some SDL tutorials I assumed SDL stores pixels (within SDL_Surface) side by side with no alignments - however it does align every line to 4 bytes. Again solution appeared in 2 places simultaneously :) I've chosen your answer as the right, though I'm calculating alignment another way (see updated question) and I won't variablise '3', as it'll work solely on 24bit images. – MJBogusz Jan 21 '14 at 12:52