gt:         long nbOp    = nbSteps * width;
context: /*****************************************************
    DIVADid
    
    A document image degradation method.
    ------------------------------
    Author:
    2015 by Mathias Seuret <mathias.seuret@unifr.ch>
    -------------------
  
    This software is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation version 3.
  
    This software is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.
  
    You should have received a copy of the GNU Lesser General Public
    License along with this software; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
   ******************************************************************************/
  package ch.unifr.diuf.diva.did;
  
  import java.util.concurrent.Semaphore;
  import java.util.concurrent.atomic.AtomicBoolean;
  import org.jocl.CL;
  import static org.jocl.CL.CL_CONTEXT_PLATFORM;
  import static org.jocl.CL.CL_DEVICE_TYPE_ALL;
  import static org.jocl.CL.CL_MEM_COPY_HOST_PTR;
  import static org.jocl.CL.CL_MEM_READ_ONLY;
  import static org.jocl.CL.CL_MEM_READ_WRITE;
  import static org.jocl.CL.CL_PLATFORM_NAME;
  import static org.jocl.CL.CL_TRUE;
  import static org.jocl.CL.clBuildProgram;
  import static org.jocl.CL.clCreateBuffer;
  import static org.jocl.CL.clCreateCommandQueue;
  import static org.jocl.CL.clCreateContext;
  import static org.jocl.CL.clCreateKernel;
  import static org.jocl.CL.clCreateProgramWithSource;
  import static org.jocl.CL.clEnqueueNDRangeKernel;
  import static org.jocl.CL.clEnqueueReadBuffer;
  import static org.jocl.CL.clGetDeviceIDs;
  import static org.jocl.CL.clGetPlatformIDs;
  import static org.jocl.CL.clGetPlatformInfo;
  import static org.jocl.CL.clReleaseCommandQueue;
  import static org.jocl.CL.clReleaseContext;
  import static org.jocl.CL.clReleaseKernel;
  import static org.jocl.CL.clReleaseMemObject;
  import static org.jocl.CL.clReleaseProgram;
  import static org.jocl.CL.clSetKernelArg;
  import org.jocl.Pointer;
  import org.jocl.Sizeof;
  import org.jocl.cl_command_queue;
  import org.jocl.cl_context;
  import org.jocl.cl_context_properties;
  import org.jocl.cl_device_id;
  import org.jocl.cl_event;
  import org.jocl.cl_kernel;
  import org.jocl.cl_mem;
  import org.jocl.cl_platform_id;
  import org.jocl.cl_program;
  
  /**
   * This class represents the channel of an image with its gradients.
   * It also stores the original values for reconstruction purposes.
   * It also has methods for reconstructing the values from the gradients,
   * either using a single thread, several threads or a GPU.
   * Take note that for using the GPU, openCL has to be installed and
   * working.
   * @author Mathias Seuret
   */
  public class GradientMap {
      
      float[][] gx;
      float[][] gy;
      float[][] val;
      int width;
      int height;
  
      public GradientMap(Image img, int channelNum) {
          width = img.getWidth();
          height = img.getHeight();
          gx = new float[width][height];
          gy = new float[width][height];
          val = new float[width][height];
          for (int x = 0; x < width; x++) {
              for (int y = 0; y < height; y++) {
                  val[x][y] = img.get(channelNum, x, y);
              }
          }
          for (int x = 0; x < width - 1; x++) {
              for (int y = 0; y < height - 1; y++) {
                  gx[x][y] = val[x + 1][y] - val[x][y];
                  gy[x][y] = val[x][y + 1] - val[x][y];
              }
          }
      }
      
      public float[][] getGX() {
          return gx;
      }
      
      public float[][] getGY() {
          return gy;
      }
      
      public int getWidth() {
          return width;
      }
      
      public int getHeight() {
          return height;
      }
  
      /**
       * Evaluates how far the values are from the gradient.
       * @return 
       */
      public float getError() {
          float err = 0.0f;
  
          for (int x = 0; x < width - 1; x++) {
              for (int y = 0; y < height - 1; y++) {
                  err += Math.abs(gx[x][y] - (val[x + 1][y] - val[x][y]));
                  err += Math.abs(gy[x][y] - (val[x][y + 1] - val[x][y]));
              }
          }
  
          return err;
      }
  
      /**
       * Pastes the (reconstructed?) values onto an image.
       * @param target image
       * @param channel color
       * @return the difference between the original data and the pasted values
       */
      public float pasteValues(Image target, int channel) {
          float diff = 0;
          for (int x = 0; x < width; x++) {
              for (int y = 0; y < height; y++) {
                  if (target.getValues(x, y)[0]<0.1 && target.getValues(x, y)[1]<0.1 && target.getValues(x, y)[2]<0.1) {
                      continue;
                  }
                  diff += (float)Math.abs(target.getValues(x,y)[channel]-val[x][y]);
                  target.set(channel, x, y, val[x][y]);
              }
          }
          return diff;
      }
      
      public void pasteGradient(Image img, GradientMap target, int x, int y, float boost) {
          for (int dx = 0; dx < width; dx++) {
              int px = x + dx;
              if (px >= target.width - 1 || px<0) {
                  continue;
              }
              for (int dy = 0; dy < height; dy++) {
                  int py = y + dy;
                  if (py >= target.height - 1 || py<0) {
                      continue;
                  }
                  if (img.getValues(px, py)[0]<0.025 && img.getValues(px, py)[1]<0.025 && img.getValues(px, py)[2]<0.025) {
                      continue;
                  }
                  float gxb = Math.abs(gx[dx][dy]*boost);
                  float gyb = Math.abs(gy[dx][dy]*boost);
                  float tx  = Math.abs(target.gx[px][py]);
                  float ty  = Math.abs(target.gy[px][py]);
                  if (gxb*gxb+gyb*gyb > tx*tx+ty*ty) {
                      target.gx[px][py] = gx[dx][dy]*boost;
                      target.gy[px][py] = gy[dx][dy]*boost;
                  }
              }
          }
      }
  
      public float[] getValues1D() {
          float[] res = new float[width * height];
          int p = 0;
          for (int y = 0; y < height; y++) {
              for (int x = 0; x < width; x++) {
                  res[p++] = val[x][y];
              }
          }
          return res;
      }
  
      public void setValues1D(float[] in) {
          int p = 0;
          for (int y = 0; y < height; y++) {
              for (int x = 0; x < width; x++) {
                  val[x][y] = in[p++];
              }
          }
      }
  
      public float[] getGX1D() {
          float[] res = new float[width * height];
          int p = 0;
          for (int y = 0; y < height; y++) {
              for (int x = 0; x < width; x++) {
                  res[p++] = gx[x][y];
              }
          }
          return res;
      }
  
      public float[] getGY1D() {
          float[] res = new float[width * height];
          int p = 0;
          for (int y = 0; y < height; y++) {
              for (int x = 0; x < width; x++) {
                  res[p++] = gy[x][y];
              }
          }
          return res;
      }
  
      protected void reconstructStepLR() {
          for (int y = 2; y<height-2; y++) {
              reconstructStepLR(y);
          }
      }
      
      protected void reconstructStepLR(int y) {
          for (int x=2; x<width-2; x++) {
              val[x][y] = (val[x][y] + val[x - 1][y] + gx[x - 1][y]) / 2;
          }
      }
  
      protected void reconstructStepRL() {
          for (int y=2; y<height-2; y++) {
              reconstructStepRL(y);
          }
      }
      
      protected void reconstructStepRL(int y) {
          for (int x=width-3; x>=2; x--) {
              val[x][y] = (val[x][y] + val[x + 1][y] - gx[x][y]) / 2;
          }
      }
  
      protected void reconstructStepTB() {
          for (int x=2; x<width-2; x++) {
              reconstructStepTB(x);
          }
      }
      
      protected void reconstructStepTB(int x) {
          for (int y=2; y<height-2; y++) {
              val[x][y] = (val[x][y] + val[x][y - 1] + gy[x][y - 1]) / 2;
          }
      }
  
      protected void reconstructStepBT() {
          for (int x=2; x<width-2; x++) {
              reconstructStepBT(x);
          }
      }
      
      protected void reconstructStepBT(int x) {
          for (int y=height-3; y>=2; y--) {
              val[x][y] = (val[x][y] + val[x][y + 1] - gy[x][y]) / 2;
          }
      }
  
      /**
       * If you want to understand how the method works, this is the method
       * you should look at. The other ones do the same, but in more complicated
       * ways.
       * @param nbSteps 
       */
      public void CPUReconstruct(int nbSteps) {
          long start = System.currentTimeMillis();
          for (int step=0; step<nbSteps; step++) {
              switch (step%4) {
                  case 0:
                      reconstructStepLR();
                      break;
                  case 1:
                      reconstructStepTB();
                      break;
                  case 2:
                      reconstructStepRL();
                      break;
                  case 3:
                      reconstructStepBT();
                      break;
              }
          }
          long compTime = System.currentTimeMillis() - start;
          long workMem = width*height*4;
  