views:

751

answers:

5

Hey!

I'm porting a library of image manipulation routines into C from Java and I'm getting some very small differences when I compare the results. Is it reasonable that these differences are in the different languages' handling of float values or do I still have work to do!

The routine is Convolution with a 3 x 3 kernel, it's operated on a bitmap represented by a linear array of pixels, a width and a depth. You need not understand this code exactly to answer my question, it's just here for reference.

Java code;

for (int x = 0; x < width; x++){
      for (int y = 0; y < height; y++){
       int offset = (y*width)+x;
       if(x % (width-1) == 0 || y % (height-1) == 0){
        input.setPixel(x, y, 0xFF000000); // Alpha channel only for border
       } else {
        float r = 0;
        float g = 0;
        float b = 0;
        for(int kx = -1 ; kx <= 1; kx++ ){
         for(int ky = -1 ; ky <= 1; ky++ ){
          int pixel = pix[offset+(width*ky)+kx];
          int t1 = Color.red(pixel);
          int t2 = Color.green(pixel);
          int t3 = Color.blue(pixel);

          float m = kernel[((ky+1)*3)+kx+1];

          r += Color.red(pixel) * m;
          g += Color.green(pixel) * m;
          b += Color.blue(pixel) * m;      
         }
        }
        input.setPixel(x, y, Color.rgb(clamp((int)r), clamp((int)g), clamp((int)b)));
       }
      }
     }
     return input;

Clamp restricts the bands' values to the range [0..255] and Color.red is equivalent to (pixel & 0x00FF0000) >> 16.

The C code goes like this;

for(x=1;x<width-1;x++){
     for(y=1; y<height-1; y++){
      offset = x + (y*width);
      rAcc=0;
      gAcc=0;
      bAcc=0;
      for(z=0;z<kernelLength;z++){
       xk = x + xOffsets[z];
       yk = y + yOffsets[z];
       kOffset = xk + (yk * width);

       rAcc += kernel[z] * ((b1[kOffset] & rMask)>>16);
       gAcc += kernel[z] * ((b1[kOffset] & gMask)>>8);
       bAcc += kernel[z] * (b1[kOffset] & bMask);
      }

      // Clamp values
      rAcc = rAcc > 255 ? 255 : rAcc < 0 ? 0 : rAcc;
      gAcc = gAcc > 255 ? 255 : gAcc < 0 ? 0 : gAcc;
      bAcc = bAcc > 255 ? 255 : bAcc < 0 ? 0 : bAcc;


      // Round the floats
                    r = (int)(rAcc + 0.5);
      g = (int)(gAcc + 0.5);
      b = (int)(bAcc + 0.5);

      output[offset] = (a|r<<16|g<<8|b) ;
     }
    }

It's a little different xOffsets provides the xOffset for the kernel element for example.

The main point is that my results are out by at most one bit. The following are pixel values;

FF205448 expected
FF215449 returned
44 wrong
FF56977E expected
FF56977F returned
45 wrong
FF4A9A7D expected
FF4B9B7E returned
54 wrong
FF3F9478 expected
FF3F9578 returned
74 wrong
FF004A12 expected
FF004A13 returned

Do you believe this is a problem with my code or rather a difference in the language?

Kind regards,

Gav

A: 

This might be due to different default round in the two languages. I'm not saying they have (you need to read up to determine that), but it's an idea.

unwind
+6  A: 

After a quick look:

do you realize that (int)r will floor the r value instead of rounding it normally? in the c code, you seem to use (int)(r + 0.5)

Fortega
+1: I agree - I was about to post the same :-)
Jon Cage
Hi FortegaI thought that (int)(rAcc + 0.5) was a cheap way of rounding floats to ints which you know are positive?You are quite correct however, the legacy code truncates the floats whereas I was rounding them (Trying to). Thank you for your help :)
gav
(int)(rAcc + 0.5) is indeed a (cheap? dirty? quick?) way of rounding floats to ints, both in java and in c...
Fortega
@Gav: That's a neat little trick. I'll remember that!
Jon Cage
A: 

Java's floating point behaviour is quite precise. What I expect to be happening here is that the value as being kept in registers with extended precision. IIRC, Java requires that the precision is rounded to that of the appropriate type. This is to try to make sure you always get the same result (full details in the JLS). C compilers will tend to leave any extra precision there, until the result in stored into main memory.

Tom Hawtin - tackline
That was my initial thought too--but the differences he shows are too big to be accounted for by "long double" precision. I think Fortega's got it.
Drew Hall
+2  A: 

Further to Fortega's answer, try the roundf() function from the C math library.

Jon Cage
Sidenote: If you're using that, just remember that it'll get called at least 3 times per pixel.
Jasper Bekkers
@Jasper: If you're worrying it might take a while I'd try it first and if it seems unacceptably slow _then_ start worrying about it. No point in optomising prematurely.
Jon Cage
+1  A: 

I would suggest you use double instead of float. Float is almost never the best choice.

Peter Lawrey