public static void vertical(Kernel1D_I32 kernel, GrayS16 input, GrayI16 output) {
    final short[] dataSrc = input.data;
    final short[] dataDst = output.data;
    final int[] dataKer = kernel.data;

    final int kernelWidth = kernel.getWidth();
    final int offsetL = kernel.getOffset();
    final int offsetR = kernelWidth - offsetL - 1;

    final int imgWidth = output.getWidth();
    final int imgHeight = output.getHeight();

    final int yEnd = imgHeight - offsetR;

    for (int y = 0; y < offsetL; y++) {
      int indexDst = output.startIndex + y * output.stride;
      int i = input.startIndex + y * input.stride;
      final int iEnd = i + imgWidth;

      int kStart = offsetL - y;

      int weight = 0;
      for (int k = kStart; k < kernelWidth; k++) {
        weight += dataKer[k];
      }

      for (; i < iEnd; i++) {
        int total = 0;
        int indexSrc = i - y * input.stride;
        for (int k = kStart; k < kernelWidth; k++, indexSrc += input.stride) {
          total += (dataSrc[indexSrc]) * dataKer[k];
        }
        dataDst[indexDst++] = (short) ((total + weight / 2) / weight);
      }
    }

    for (int y = yEnd; y < imgHeight; y++) {
      int indexDst = output.startIndex + y * output.stride;
      int i = input.startIndex + y * input.stride;
      final int iEnd = i + imgWidth;

      int kEnd = imgHeight - (y - offsetL);

      int weight = 0;
      for (int k = 0; k < kEnd; k++) {
        weight += dataKer[k];
      }

      for (; i < iEnd; i++) {
        int total = 0;
        int indexSrc = i - offsetL * input.stride;
        for (int k = 0; k < kEnd; k++, indexSrc += input.stride) {
          total += (dataSrc[indexSrc]) * dataKer[k];
        }
        dataDst[indexDst++] = (short) ((total + weight / 2) / weight);
      }
    }
  }
  public static void vertical(
      Kernel1D_I32 kernelX, Kernel1D_I32 kernelY, GrayS32 input, GrayI16 output) {
    final int[] dataSrc = input.data;
    final short[] dataDst = output.data;
    final int[] dataKer = kernelY.data;

    final int offsetY = kernelY.getOffset();
    final int kernelWidthY = kernelY.getWidth();

    final int offsetX = kernelX.getOffset();
    final int kernelWidthX = kernelX.getWidth();
    final int offsetX1 = kernelWidthX - offsetX - 1;

    final int imgWidth = output.getWidth();
    final int imgHeight = output.getHeight();

    final int yEnd = imgHeight - (kernelWidthY - offsetY - 1);

    int startWeightX = 0;
    for (int k = offsetX; k < kernelWidthX; k++) {
      startWeightX += kernelX.data[k];
    }

    for (int y = 0; y < offsetY; y++) {
      int indexDst = output.startIndex + y * output.stride;
      int i = input.startIndex + y * input.stride;
      final int iEnd = i + imgWidth;

      int kStart = offsetY - y;

      int weightY = 0;
      for (int k = kStart; k < kernelWidthY; k++) {
        weightY += dataKer[k];
      }
      int weightX = startWeightX;

      for (int x = 0; i < iEnd; i++, x++) {
        int weight = weightX * weightY;
        int total = 0;
        int indexSrc = i - y * input.stride;
        for (int k = kStart; k < kernelWidthY; k++, indexSrc += input.stride) {
          total += (dataSrc[indexSrc]) * dataKer[k];
        }
        dataDst[indexDst++] = (short) ((total + weight / 2) / weight);
        if (x < offsetX) {
          weightX += kernelX.data[offsetX - x - 1];
        } else if (x >= input.width - (kernelWidthX - offsetX)) {
          weightX -= kernelX.data[input.width - x + offsetX - 1];
        }
      }
    }

    for (int y = yEnd; y < imgHeight; y++) {
      int indexDst = output.startIndex + y * output.stride;
      int i = input.startIndex + y * input.stride;
      final int iEnd = i + imgWidth;

      int kEnd = imgHeight - (y - offsetY);

      int weightY = 0;
      for (int k = 0; k < kEnd; k++) {
        weightY += dataKer[k];
      }
      int weightX = startWeightX;

      for (int x = 0; i < iEnd; i++, x++) {
        int weight = weightX * weightY;
        int total = 0;
        int indexSrc = i - offsetY * input.stride;
        for (int k = 0; k < kEnd; k++, indexSrc += input.stride) {
          total += (dataSrc[indexSrc]) * dataKer[k];
        }
        dataDst[indexDst++] = (short) ((total + weight / 2) / weight);
        if (x < offsetX) {
          weightX += kernelX.data[offsetX - x - 1];
        } else if (x >= input.width - (kernelWidthX - offsetX)) {
          weightX -= kernelX.data[input.width - x + offsetX - 1];
        }
      }
    }

    // left and right border
    int weightY = kernelY.computeSum();
    for (int y = offsetY; y < yEnd; y++) {
      int indexDst = output.startIndex + y * output.stride;
      int i = input.startIndex + y * input.stride;

      // left side
      int iEnd = i + offsetY;
      int weightX = startWeightX;
      for (int x = 0; i < iEnd; i++, x++) {
        int weight = weightX * weightY;
        int total = 0;
        int indexSrc = i - offsetY * input.stride;
        for (int k = 0; k < kernelWidthY; k++, indexSrc += input.stride) {
          total += (dataSrc[indexSrc]) * dataKer[k];
        }
        dataDst[indexDst++] = (short) ((total + weight / 2) / weight);
        weightX += kernelX.data[offsetX - x - 1];
      }

      // right side
      int startX = input.width - offsetX1;
      indexDst = output.startIndex + y * output.stride + startX;
      i = input.startIndex + y * input.stride + startX;
      iEnd = input.startIndex + y * input.stride + input.width;
      for (int x = startX; i < iEnd; i++, x++) {
        weightX -= kernelX.data[input.width - x + offsetX];
        int weight = weightX * weightY;
        int total = 0;
        int indexSrc = i - offsetY * input.stride;
        for (int k = 0; k < kernelWidthY; k++, indexSrc += input.stride) {
          total += (dataSrc[indexSrc]) * dataKer[k];
        }
        dataDst[indexDst++] = (short) ((total + weight / 2) / weight);
      }
    }
  }