int llcv_gl_error_count() {
	EGLint error = eglGetError();
	if (error != EGL_SUCCESS) {
		dmz_error_log("egl error: %i", error);
		dmz_set_gles_warp(0);
		return 1;
	}

	int err_count = 0;
	GLenum glerror;
	while ((glerror = glGetError())) {
		err_count++;
		dmz_error_log("gl error: 0x%X (%i)", glerror, glerror);
	}
	if (err_count) 	dmz_set_gles_warp(0);
	return err_count;
}
void setDetectedCardImage(JNIEnv* env, jobject jCardResultBitmap, IplImage* cardY, IplImage* cb, IplImage* cr,
                          dmz_corner_points corner_points, int orientation) {

  char* pixels = NULL;

  AndroidBitmapInfo  bmInfo;
  int bmRes = AndroidBitmap_getInfo(env, jCardResultBitmap, &bmInfo);
  // Yes, it really is defined as _RESUT_ ... figures. <sigh>
  bool validCardInfo = (bmRes == ANDROID_BITMAP_RESUT_SUCCESS);
  if (!validCardInfo) {
    dmz_error_log("AndroidBitmap_getInfo() failed! error=%i", bmRes);
  }
  if (validCardInfo && bmInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
    dmz_error_log("the dmz was given a bitmap that is not RGBA_8888");
    validCardInfo = false;
  }

  bmRes = AndroidBitmap_lockPixels(env, jCardResultBitmap, (void**) &pixels );
  if (bmRes != ANDROID_BITMAP_RESUT_SUCCESS) {
    dmz_error_log("couldn't lock bitmap:%i", bmRes);
  }
  else {
    IplImage* bigCb = NULL;
    dmz_transform_card(NULL, cb, corner_points, orientation, true, &bigCb);

    IplImage* bigCr = NULL;
    dmz_transform_card(NULL, cr, corner_points, orientation, true, &bigCr);

    IplImage* cardResult = cvCreateImageHeader(cvSize(bmInfo.width, bmInfo.height), IPL_DEPTH_8U, 4);
    cvSetData(cardResult, pixels, bmInfo.stride);

    dmz_YCbCr_to_RGB(cardY, bigCb, bigCr, &cardResult);
    AndroidBitmap_unlockPixels(env, jCardResultBitmap);

    cvReleaseImageHeader(&cardResult);
    cvReleaseImage(&bigCb);
    cvReleaseImage(&bigCr);
  }
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  JNIEnv* env;
  jint status = vm->GetEnv( (void**) &env, JNI_VERSION_1_6);
  if (status != JNI_OK)
    return -1;

  /* find class ref and field IDs.
   * class refs must be explicitly stated as global.
   * field IDs could change with a new classloader, but this method will get called in that case.
   * see http://www.milk.com/kodebase/dalvik-docs-mirror/docs/jni-tips.html
   */

  jclass myClass = env->FindClass("io/card/payment/CardScanner");
  if (!myClass) {
    dmz_error_log("Couldn't find CardScanner from JNI");
    return -1;
  }
  cardScannerId.classRef = (jclass)env->NewGlobalRef(myClass);
  cardScannerId.edgeUpdateCallback = env->GetMethodID(myClass, "onEdgeUpdate", "(Lio/card/payment/DetectionInfo;)V");
  if (!cardScannerId.edgeUpdateCallback) {
    dmz_error_log("Couldn't find edge update callback");
    return -1;
  }

  jclass rectClass = env->FindClass("android/graphics/Rect");
  if (!rectClass) {
    dmz_error_log("Couldn't find Rect class");
    return -1;
  }
  rectId.classRef = (jclass)env->NewGlobalRef(rectClass);
  rectId.top = env->GetFieldID(rectClass, "top", "I");
  rectId.bottom = env->GetFieldID(rectClass, "bottom", "I");
  rectId.left = env->GetFieldID(rectClass, "left", "I");
  rectId.right = env->GetFieldID(rectClass, "right", "I");

  if (!(rectId.top && rectId.bottom && rectId.left && rectId.right)) {
    dmz_error_log("Couldn't find square class");
    return -1;
  }

  jclass creditCardClass = (jclass)env->FindClass("io/card/payment/CreditCard");
  if (creditCardClass == NULL) {
    dmz_error_log("Couldn't find CreditCard class");
    return -1;
  }
  creditCardId.classRef = (jclass)env->NewGlobalRef(creditCardClass);
  creditCardId.flipped = env->GetFieldID(creditCardClass, "flipped", "Z");
  creditCardId.yoff = env->GetFieldID(creditCardClass, "yoff", "I");
  creditCardId.xoff = env->GetFieldID(creditCardClass, "xoff", "[I");
  if (!( creditCardId.flipped && creditCardId.yoff && creditCardId.xoff )) {
    dmz_error_log("at least one filed was not found for CreditCard");
    return -1;
  }

  jclass dInfoClass = env->FindClass("io/card/payment/DetectionInfo");
  if (dInfoClass == NULL) {
    dmz_error_log("Couldn't find DetectionInfo class");
    return -1;
  }
  detectionInfoId.classRef = (jclass)env->NewGlobalRef(dInfoClass);
  detectionInfoId.complete = env->GetFieldID(dInfoClass, "complete", "Z");
  detectionInfoId.topEdge = env->GetFieldID(dInfoClass, "topEdge", "Z");
  detectionInfoId.bottomEdge = env->GetFieldID(dInfoClass, "bottomEdge", "Z");
  detectionInfoId.leftEdge =  env->GetFieldID(dInfoClass, "leftEdge", "Z");
  detectionInfoId.rightEdge = env->GetFieldID(dInfoClass, "rightEdge", "Z");
  detectionInfoId.focusScore = env->GetFieldID(dInfoClass, "focusScore", "F");
  detectionInfoId.prediction = env->GetFieldID(dInfoClass, "prediction", "[I");
  detectionInfoId.expiry_month = env->GetFieldID(dInfoClass, "expiry_month", "I");
  detectionInfoId.expiry_year = env->GetFieldID(dInfoClass, "expiry_year", "I");
  detectionInfoId.detectedCard = env->GetFieldID(dInfoClass, "detectedCard", "Lio/card/payment/CreditCard;");

  if (!(detectionInfoId.complete && detectionInfoId.topEdge && detectionInfoId.bottomEdge
        && detectionInfoId.leftEdge && detectionInfoId.rightEdge
        && detectionInfoId.focusScore && detectionInfoId.prediction
        && detectionInfoId.expiry_month && detectionInfoId.expiry_year
        && detectionInfoId.detectedCard
       )) {
    dmz_error_log("at least one field was not found for DetectionInfo");
    return -1;
  }

  return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL Java_io_card_payment_CardScanner_nScanFrame(JNIEnv *env, jobject thiz,
    jbyteArray jb, jint width, jint height, jint orientation, jobject dinfo,
    jobject jCardResultBitmap, jboolean jScanExpiry) {
  dmz_trace_log("Java_io_card_payment_CardScanner_nScanFrame ... width:%i height:%i orientation:%i", width, height, orientation);

  if (orientation == 0) {
    dmz_error_log("orientation is 0. Nothing good can come from this.");
    return;
  }

  if (flipped) {
    orientation = dmz_opposite_orientation(orientation);
  }

  FrameScanResult result;

  IplImage *image = cvCreateImageHeader(cvSize(width, height), IPL_DEPTH_8U, 1);
  jbyte *jBytes = env->GetByteArrayElements(jb, 0);
  image->imageData = (char *)jBytes;

  float focusScore = dmz_focus_score(image, false);
  env->SetFloatField(dinfo, detectionInfoId.focusScore, focusScore);
  dmz_trace_log("focus score: %f", focusScore);
  if (focusScore >= minFocusScore) {

    IplImage *cbcr = cvCreateImageHeader(cvSize(width / 2, height / 2), IPL_DEPTH_8U, 2);
    cbcr->imageData = ((char *)jBytes) + width * height;
    IplImage *cb, *cr;

    // Note: cr and cb are reversed here because Android uses android.graphics.ImageFormat.NV21. This is actually YCrCb rather than YCbCr!
    dmz_deinterleave_uint8_c2(cbcr, &cr, &cb);

    cvReleaseImageHeader(&cbcr);

    dmz_edges found_edges;
    dmz_corner_points corner_points;
    bool cardDetected = dmz_detect_edges(image, cb, cr,
                                         orientation,
                                         &found_edges, &corner_points
                                        );

    updateEdgeDetectDisplay(env, thiz, dinfo, found_edges);

    if (cardDetected) {
      IplImage *cardY = NULL;
      dmz_transform_card(NULL, image, corner_points, orientation, false, &cardY);

      if (!detectOnly) {
        result.focus_score = focusScore;
        result.flipped = flipped;
        scanner_add_frame_with_expiry(&scannerState, cardY, jScanExpiry, &result);
        if (result.usable) {
          ScannerResult scanResult;
          scanner_result(&scannerState, &scanResult);

          if (scanResult.complete) {
            setScanCardNumberResult(env, dinfo, &scanResult);
            logDinfo(env, dinfo);
          }
        }
        else if (result.upside_down) {
          flipped = !flipped;
        }
      }

      setDetectedCardImage(env, jCardResultBitmap, cardY, cb, cr, corner_points, orientation);
      cvReleaseImage(&cardY);
    }

    cvReleaseImage(&cb);
    cvReleaseImage(&cr);
  }

  cvReleaseImageHeader(&image);
  env->ReleaseByteArrayElements(jb, jBytes, 0);
}