   // TODO: handle borders of image, i.e. when px_x==0 and so on
 }
 
-////////////////////////////////// CPU code: ///////////////////////////////////
+/////////////////////////////// CPU functions: /////////////////////////////////
+
+// Average the pixels in a 3*3 box around pixel (px_x,px_y) of old_image.
+// Pixels that fall outside of the image will be excluded from the average.
+uchar average_3x3_CPU(uchar *old_image, int px_x, int px_y) {
+  float total = 0;
+  int px_used = 0;
+
+  int i,j;
+  for (i=px_y-1; i<=px_y+1; i++) {  // loop over 3 rows
+    if ((i < 0) || (i >= image_height)) { continue; }
+    for (j=px_x-1; j<=px_x+1; j++) {  // loop over 3 cols
+      if ((j < 0) || (j >= image_width)) { continue; }
+      total += old_image[ i*image_width+j ];
+      px_used++;
+    }
+  }
+  total = total / px_used;
+
+  return (uchar)total;
+}
+
+/////////////////////////////////// main(): ////////////////////////////////////
 
 int main(int argc, char *argv[])
 {

  else if (mode == 4) {
    checkCudaErrors( cudaFreeHost(new_image) );
  }

  checkCudaErrors( cudaFree(new_image_G) );
  checkCudaErrors( cudaFree(old_image_G) );
  checkCudaErrors( cudaDeviceReset() );

  exit(EXIT_SUCCESS);
}



// These are really only global to match/simplify the pseudocode in the slides.

uchar *old_image;  // original image, CPU-side.  we don't actually use this.
uchar *new_image;  // processed image, CPU-side
int image_height;  // number of rows in image
int image_width;   // number of columns in image
