code:
/*
 * Copyright 2015 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <android/bitmap.h>
#include <GLES2/gl2.h>

#include "com_hippo_image_Image.h"
#include "com_hippo_image_StaticImage.h"
#include "com_hippo_image_StaticDelegateImage.h"
#include "com_hippo_image_AnimatedImage.h"
#include "com_hippo_image_AnimatedDelegateImage.h"
#include "com_hippo_image_BitmapDecoder.h"
#include "com_hippo_image_BitmapRegionDecoder.h"
#include "image.h"
#include "image_convert.h"
#include "image_decoder.h"
#include "animated_image.h"
#include "bitmap_container.h"
#include "java_stream.h"
#include "buffer_stream.h"
#include "../log.h"

#define LITTLE_ENDIAN  0x00
#define BIG_ENDIAN 0x01

static volatile unsigned int endian = ~0U;

static bool is_big_endian() {
  unsigned int x = 1;

  if (endian == ~0U) {
    if (1 == ((char *)&x)[0]) {
      endian = LITTLE_ENDIAN;
    } else {
      endian = BIG_ENDIAN;
    }
  }

  return endian == BIG_ENDIAN;
}

static uint32_t j_color_to_rgba8888(jint origin) {
  uint32_t result;
  uint8_t* src = (uint8_t *) &origin;
  uint8_t* dst = (uint8_t *) &result;

  if (is_big_endian()) {
    dst[0] = src[1];
    dst[1] = src[2];
    dst[2] = src[3];
    dst[3] = src[0];
  } else {
    dst[0] = src[2];
    dst[1] = src[1];
    dst[2] = src[0];
    dst[3] = src[3];
  }

  return result;
}

static int32_t bitmap_format_to_config(int32_t format) {
  switch (format) {
    case ANDROID_BITMAP_FORMAT_RGBA_8888:
      return IMAGE_CONFIG_RGBA_8888;
    case ANDROID_BITMAP_FORMAT_RGB_565:
      return IMAGE_CONFIG_RGB_565;
    default:
      return IMAGE_CONFIG_INVALID;
  }
}

static bool INIT_SUCCEED = false;

static jclass CLASS_STATIC_IMAGE = NULL;
static jclass CLASS_ANIMATED_IMAGE = NULL;
static jclass CLASS_BITMAP_DECODER = NULL;
static jclass CLASS_BITMAP_REGION_DECODER = NULL;

static jmethodID CONSTRUCTOR_STATIC_IMAGE = NULL;
static jmethodID CONSTRUCTOR_ANIMATED_IMAGE = NULL;
static jmethodID CONSTRUCTOR_BITMAP_REGION_DECODER = NULL;

static jmethodID METHOD_ANIMATED_IMAGE_ON_COMPLETE = NULL;
static jmethodID METHOD_IMAGE_INFO_SET = NULL;
static jmethodID METHOD_BITMAP_DECODER_CREATE_BITMAP = NULL;
static jmethodID METHOD_BITMAP_RECYCLE = NULL;

static jobject static_image_object_new(JNIEnv* env, StaticImage* image) {
  return (*env)->NewObject(env, CLASS_STATIC_IMAGE, CONSTRUCTOR_STATIC_IMAGE,
      (jlong) image, (jint) image->width, (jint) image->height, (jint) image->format,
      (jboolean) image->opaque, (jint) (image->width * image->height * 4));
}

static jobject animated_image_object_new(JNIEnv* env, AnimatedImage* image) {
  return (*env)->NewObject(env, CLASS_ANIMATED_IMAGE, CONSTRUCTOR_ANIMATED_IMAGE,
      (jlong) image, (jint) image->width, (jint) image->height,
      (jint) image->format, (jboolean) image->opaque);
}

static jobject bitmap_region_decoder_object_new(JNIEnv* env, Stream* stream, ImageInfo* info) {
  return (*env)->NewObject(env, CLASS_BITMAP_REGION_DECODER, CONSTRUCTOR_BITMAP_REGION_DECODER,
      (jlong) stream, (jint) info->width, (jint) info->height,
      (jint) info->format, (jboolean) info->opaque);
}

static void animated_image_object_on_complete(JNIEnv* env, jobject obj, AnimatedImage* image) {
  uint32_t frame_count;
  uint32_t byte_count;
  jintArray delay_array;
  jint delay;
  uint32_t i;

  frame_count = image->get_frame_count(image);
  byte_count = image->get_byte_count(image);

  delay_array = (*env)->NewIntArray(env, frame_count);
  for (i = 0; i < frame_count; i++) {
    delay = image->get_delay(image, i);
    (*env)->SetIntArrayRegion(env, delay_array, i, 1, &delay);
  }

  (*env)->CallVoidMethod(env, obj, METHOD_ANIMATED_IMAGE_ON_COMPLETE,
      frame_count, delay_array, byte_count);
}

////////////////////////////////
// Image
////////////////////////////////

JNIEXPORT jobject JNICALL
Java_com_hippo_image_Image_nativeDecode(JNIEnv* env, __unused jclass clazz, jobject is, jboolean partially) {
  bool animated;
  void* image = NULL;
  Stream* stream = NULL;
  jobject obj;

  if (!INIT_SUCCEED) {
    return NULL;
  }

  stream = java_stream_new(env, is, true);
  if (stream == NULL) {
    LOGE(MSG("Can't create java stream"));
    return NULL;
  }

  // Decode
  decode(stream, partially, &animated, &image);

  // Close stream is necessary
  if (image == NULL || !animated || ((AnimatedImage*) image)->completed) {
    stream->close(&stream);
  }

  if (image != NULL) {
    if (!animated) {
      obj = static_image_object_new(env, image);
    } else {
      obj = animated_image_object_new(env, image);
      if (((AnimatedImage*) image)->completed) {
        animated_image_object_on_complete(env, obj, image);
      }
    }
  } else {
    obj = NULL;
  }

  return obj;
}

JNIEXPORT jobject JNICALL
Java_com_hippo_image_Image_nativeCreate(JNIEnv* env, __unused jclass clazz, jobject bitmap) {
#ifdef IMAGE_SUPPORT_PLAIN
  AndroidBitmapInfo info;
  void* pixels = NULL;
  StaticImage* image = NULL;
  jobject image_object;

  if (!INIT_SUCCEED) {
    return NULL;
  }

  AndroidBitmap_getInfo(env, bitmap, &info);
  AndroidBitmap_lockPixels(env, bitmap, &pixels);
  if (pixels == NULL) {
    LOGE(MSG("Can't lock bitmap pixels"));
    return NULL;
  }

  image = create(info.width, info.height, pixels);

  AndroidBitmap_unlockPixels(env, bitmap);

  if (image == NULL) {
    return NULL;
  }

  image_object = static_image_object_new(env, image);
  if (image_object == NULL) {
    static_image_delete(&image);
    return NULL;
  } else {
    return image_object;
  }
#else
  return NULL;
#endif
}

JNIEXPORT jlong JNICALL
Java_com_hippo_image_Image_nativeCreateBuffer(__unused JNIEnv* env, __unused jclass clazz, jint size) {
  return (jlong) malloc((size_t) (size * 4));
}

JNIEXPORT void JNICALL
Java_com_hippo_image_Image_nativeDestroyBuffer(__unused JNIEnv* env, __unused jclass clazz, jlong buffer) {
  free((void*) buffer);
}

JNIEXPORT jintArray JNICALL
Java_com_hippo_image_Image_nativeGetSupportedImageFormats(JNIEnv *env, __unused jclass clazz) {
  int formats[IMAGE_FORMAT_MAX_COUNT];
  int count = get_supported_formats(formats);
  jintArray array = (*env)->NewIntArray(env, count);
  if (array == NULL) {
    return NULL;
  }
  (*env)->SetIntArrayRegion(env, array, 0, count, formats);
  return array;
}

JNIEXPORT jstring JNICALL
Java_com_hippo_image_Image_nativeGetLibraryDescription(JNIEnv *env, __unused jclass clazz, jint format) {
  const char *description = get_library_description(format);
  if (description == NULL) {
    return NULL;
  } else {
    return (*env)->NewStringUTF(env, description);
  }
}

////////////////////////////////
// StaticImage
////////////////////////////////

JNIEXPORT void JNICALL
Java_com_hippo_image_StaticImage_nativeRecycle(__unused JNIEnv* env, __unused jclass clazz, jlong ptr) {
  StaticImage* image = (StaticImage *) ptr;
  static_image_delete(&image);
}

////////////////////////////////
// StaticDelegateImage
////////////////////////////////

JNIEXPORT void JNICALL
Java_com_hippo_image_StaticDelegateImage_nativeRender(JNIEnv* env, __unused jclass clazz,
    jlong ptr, jobject bitmap, jint dst_x, jint dst_y, jint src_x, jint src_y,
    jint width, jint height, jint ratio, jboolean fill_blank, jint fill_color) {
  AndroidBitmapInfo info;
  void *pixels = NULL;
  StaticImage* image = (StaticImage *) ptr;

  AndroidBitmap_getInfo(env, bitmap, &info);
  AndroidBitmap_lockPixels(env, bitmap, &pixels);
  if (pixels == NULL) {
    LOGE(MSG("Can't lock bitmap pixels"));
    return;
  }

  convert(pixels, bitmap_format_to_config(info.format),
      info.width, info.height,
      dst_x, dst_y,
      image->buffer, IMAGE_CONFIG_RGBA_8888,
      image->width, image->height,
      src_x, src_y,
      (uint32_t) width, (uint32_t) height,
      ratio < 1 ? 1 : (uint32_t) ratio,
      fill_blank, j_color_to_rgba8888(fill_color));

  AndroidBitmap_unlockPixels(env, bitmap);

  return;
}

JNIEXPORT void JNICALL
Java_com_hippo_image_StaticDelegateImage_nativeGlTex(__unused JNIEnv* env, __unused jclass clazz,
    jlong image_ptr, jlong buffer_ptr, jboolean init, jint tex_w, jint tex_h,
    jint dst_x, jint dst_y, jint src_x, jint src_y, jint width, jint height, jint ratio) {
  StaticImage* image = (StaticImage*) image_ptr;
  void* buffer = (void*) buffer_ptr;

  convert(buffer, IMAGE_CONFIG_RGBA_8888,
      (uint32_t) tex_w, (uint32_t) tex_h,
      dst_x, dst_y,
      image->buffer, IMAGE_CONFIG_RGBA_8888,
      (int) image->width, (int) image->height,
      src_x, src_y,
      (uint32_t) width, (uint32_t) height,
      ratio < 1 ? 1 : (uint32_t) ratio,
      false, 0);

  if (init) {
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_w, tex_h,
        0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
  } else {
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_w, tex_h,
        GL_RGBA, GL_UNSIGNED_BYTE, buffer);
  }
}

////////////////////////////////
// AnimatedImage
////////////////////////////////

JNIEXPORT void JNICALL
Java_com_hippo_image_AnimatedImage_nativeRecycle(__unused JNIEnv* env, __unused jclass clazz, jlong image_ptr) {
  AnimatedImage* image = (AnimatedImage *) image_ptr;
  image->recycle(&image);
}

JNIEXPORT void JNICALL
Java_com_hippo_image_AnimatedImage_nativeComplete(JNIEnv* env, __unused jclass clazz, jobject obj, jlong image_ptr) {
  AnimatedImage* image = (AnimatedImage *) image_ptr;
  Stream* stream = image->get_stream(image);
  if (stream == NULL) {
    LOGE(MSG("Can't get stream from image data"));
    return;
  }

  // Set new env to ensure works in new thread
  java_stream_set_env(stream, env);

  image->complete(image);
  if (!image->completed) {
    LOGE(MSG("Can't complete the image"));
    return;
  }

  animated_image_object_on_complete(env, obj, image);
}

////////////////////////////////
// AnimatedDelegateImage
////////////////////////////////

JNIEXPORT jlong JNICALL Java_com_hippo_image_AnimatedDelegateImage_nativeNew(
    __unused JNIEnv* env, __unused jclass clazz, jint width, jint height) {
  return (jlong) delegate_image_new((uint32_t) width, (uint32_t) height);
}

JNIEXPORT void JNICALL Java_com_hippo_image_AnimatedDelegateImage_nativeRecycle(
    __unused JNIEnv* env, __unused jclass clazz, jlong ptr) {
  DelegateImage* image = (DelegateImage *) ptr;
  delegate_image_delete(&image);
}

JNIEXPORT jint JNICALL
Java_com_hippo_image_AnimatedDelegateImage_nativeGetCurrentDelay(
    __unused JNIEnv* env, __unused jclass clazz, jlong delegate_ptr, jlong image_ptr) {
  AnimatedImage* image = (AnimatedImage *) image_ptr;
  DelegateImage* delegate = (DelegateImage *) delegate_ptr;

  if (delegate->index < 0) {
    return 0;
  } else {
    return image->get_delay(image, (uint32_t) delegate->index);
  }
}

JNIEXPORT void JNICALL Java_com_hippo_image_AnimatedDelegateImage_nativeRender(
    JNIEnv* env, __unused jclass clazz, jlong ptr, jobject bitmap, jint dst_x, jint dst_y,
    jint src_x, jint src_y, jint width, jint height, jint ratio,
    jboolean fill_blank, jint fill_color) {
  AndroidBitmapInfo info;
  void *pixels = NULL;
  DelegateImage* image = (DelegateImage *) ptr;

  AndroidBitmap_getInfo(env, bitmap, &info);
  AndroidBitmap_lockPixels(env, bitmap, &pixels);
  if (pixels == NULL) {
    LOGE(MSG("Can't lock bitmap pixels"));
    return;
  }

  convert(pixels, bitmap_format_to_config(info.format),
      info.width, info.height,
      dst_x, dst_y,
      image->shown, IMAGE_CONFIG_RGBA_8888,
      image->width, image->height,
      src_x, src_y,
      (uint32_t) width, (uint32_t) height,
      ratio < 1 ? 1 : (uint32_t) ratio,
      fill_blank, j_color_to_rgba8888(fill_color));

  AndroidBitmap_unlockPixels(env, bitmap);

  return;
}

JNIEXPORT void JNICALL Java_com_hippo_image_AnimatedDelegateImage_nativeGlTex(
    __unused JNIEnv* env, __unused jclass clazz, jlong image_ptr, jlong buffer_ptr, jboolean init,
    jint tex_w, jint tex_h, jint dst_x, jint dst_y, jint src_x, jint src_y,
    jint width, jint height, jint ratio) {
  DelegateImage* image = (DelegateImage*) image_ptr;
  void* buffer = (void*) buffer_ptr;

  convert(buffer, IMAGE_CONFIG_RGBA_8888,
      (uint32_t) tex_w, (uint32_t) tex_h,
      dst_x, dst_y,
      image->buffer, IMAGE_CONFIG_RGBA_8888,
      (int) image->width, (int) image->height,
      src_x, src_y,
      (uint32_t) width, (uint32_t) height,
      ratio < 1 ? 1 : (uint32_t) ratio,
      false, 0);

  if (init) {
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_w, tex_h,
        0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
  } else {
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_w, tex_h,
        GL_RGBA, GL_UNSIGNED_BYTE, buffer);
  }
}

JNIEXPORT void JNICALL Java_com_hippo_image_AnimatedDelegateImage_nativeAdvance(
    __unused JNIEnv* env, __unused jclass clazz, jlong delegate_ptr, jlong image_ptr) {
  AnimatedImage* image = (AnimatedImage *) image_ptr;
  DelegateImage* delegate = (DelegateImage *) delegate_ptr;
  image->advance(image, delegate);
}

JNIEXPORT void JNICALL Java_com_hippo_image_AnimatedDelegateImage_nativeReset(
    __unused JNIEnv* env, __unused jclass clazz, jlong delegate_ptr, jlong image_ptr) {
  AnimatedImage* image = (AnimatedImage *) image_ptr;
  DelegateImage* delegate = (DelegateImage *) delegate_ptr;
  if (delegate->index == 0) {
    // Already first frame
    return;
  } else {
    // Set index to -1 to make next frame is 0
    delegate->index = -1;
    image->advance(image, delegate);
  }
}

////////////////////////////////
// BitmapDecoder
////////////////////////////////

JNIEXPORT jboolean JNICALL
Java_com_hippo_image_BitmapDecoder_nativeDecodeInfo(JNIEnv* env, __unused jclass clazz, jobject is, jobject info) {
  Stream* stream = NULL;
  ImageInfo iInfo;
  bool result;

  if (!INIT_SUCCEED) {
    return false;
  }

  stream = java_stream_new(env, is, true);
  if (stream == NULL) {
    LOGE(MSG("Can't create java stream"));
    return false;
  }

  result = decode_info(stream, &iInfo);

  if (result) {
    (*env)->CallVoidMethod(env, info, METHOD_IMAGE_INFO_SET, iInfo.width, iInfo.height,
        iInfo.format, iInfo.opaque, iInfo.frame_count);
  }

  stream->close(&stream);

  return (jboolean) result;
}

JNIEXPORT jobject JNICALL
Java_com_hippo_image_BitmapDecoder_nativeDecodeBitmap(JNIEnv* env, __unused jclass clazz, jobject is, jint config, jint ratio) {
  Stream* stream;
  BufferContainer* container;
  jobject bitmap;
  bool result;

  if (!INIT_SUCCEED) {
    return false;
  }

  stream = java_stream_new(env, is, true);
  if (stream == NULL) {
    LOGE(MSG("Can't create java stream"));
    return NULL;
  }

  container = bitmap_container_new(env, CLASS_BITMAP_DECODER, METHOD_BITMAP_DECODER_CREATE_BITMAP);
  if (container == NULL) {
    LOGE(MSG("Can't create bitmap container"));
    stream->close(&stream);
    return NULL;
  }

  result = decode_buffer(stream, false, 0, 0, 0, 0, (int32_t) config, ratio < 1 ? 1 : (uint32_t) ratio, container);
  bitmap = bitmap_container_fetch_bitmap(container);
  bitmap_container_recycle(&container);
  stream->close(&stream);

  if (!result && bitmap != NULL) {
    // Decode failed and the bitmap is not NULL
    // Recycle it!
    (*env)->CallVoidMethod(env, bitmap, METHOD_BITMAP_RECYCLE);
    bitmap = NULL;
  }

  return bitmap;
}

////////////////////////////////
// BitmapRegionDecoder
////////////////////////////////

JNIEXPORT jobject JNICALL
Java_com_hippo_image_BitmapRegionDecoder_nativeNewInstance(JNIEnv* env, __unused jclass clazz, jobject is) {
  Stream* java_stream = NULL;
  Stream* buffer_stream = NULL;
  void* buffer = NULL;
  size_t buffer_size;
  ImageInfo info;
  jobject obj = NULL;

  if (!INIT_SUCCEED) {
    return NULL;
  }

  java_stream = java_stream_new(env, is, false);
  if (java_stream == NULL) { goto end; }
  buffer = stream_read_all(java_stream, &buffer_size);
  if (buffer == NULL) { goto end; }
  buffer_stream = buffer_stream_new(buffer, buffer_size);
  if (buffer_stream == NULL) { goto end; }
  // Clear buffer to avoid free twice
  buffer = NULL;

  // Decode image info
  if (!decode_info(buffer_stream, &info)) { goto end; }

  // Create java object
  obj = bitmap_region_decoder_object_new(env, buffer_stream, &info);

end:
  if (java_stream != NULL) {
    java_stream->close(&java_stream);
  }
  // Only close buffer stream if create BitmapRegionDecoder failed
  if (obj == NULL && buffer_stream != NULL) {
    buffer_stream->close(&buffer_stream);
  }
  free(buffer);
  return obj;
}

JNIEXPORT jobject JNICALL
Java_com_hippo_image_BitmapRegionDecoder_nativeDecodeRegion(JNIEnv* env, __unused jclass clazz, jlong ptr,
    jint x, jint y , jint width, jint height, jint config, jint ratio) {
  BufferContainer* container = NULL;
  Stream* stream = NULL;
  jobject bitmap = NULL;
  bool result;

  stream = (Stream *) ptr;

  container = bitmap_container_new(env, CLASS_BITMAP_DECODER, METHOD_BITMAP_DECODER_CREATE_BITMAP);
  if (container == NULL) { goto end; }

  // Decode regions only if requested width and height are not 0
  bool clip = width != 0 && height != 0;

  // Decode
  buffer_stream_reset(stream);
  result = decode_buffer(stream, clip, (uint32_t) x, (uint32_t) y,
      (uint32_t) width, (uint32_t) height, (int32_t) config, ratio < 1 ? 1 : (uint32_t) ratio, container);
  bitmap = bitmap_container_fetch_bitmap(container);

  if (!result && bitmap != NULL) {
    // Decode failed and the bitmap is not NULL
    // Recycle it!
    (*env)->CallVoidMethod(env, bitmap, METHOD_BITMAP_RECYCLE);
    bitmap = NULL;
  }

end:
  if (container != NULL) {
    bitmap_container_recycle(&container);
  }
  return bitmap;
}

JNIEXPORT void JNICALL
Java_com_hippo_image_BitmapRegionDecoder_nativeRecycle(__unused JNIEnv* env, __unused jclass clazz, jlong ptr) {
  Stream* stream = (Stream *) ptr;
  stream->close(&stream);
}

__unused
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, __unused void* reserved) {
  JNIEnv* env = NULL;
  jclass class_image_info;
  jclass class_bitmap;

  if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) {
    LOGE(MSG("Can't get env in JNI_OnLoad."));
    INIT_SUCCEED = false;
    return JNI_VERSION_1_6;
  }

  CLASS_STATIC_IMAGE = (*env)->FindClass(env, "com/hippo/image/StaticImage");
  CLASS_STATIC_IMAGE = (*env)->NewGlobalRef(env, CLASS_STATIC_IMAGE);
  if (CLASS_STATIC_IMAGE != NULL) {
    CONSTRUCTOR_STATIC_IMAGE = (*env)->GetMethodID(env, CLASS_STATIC_IMAGE, "<init>", "(JIIIZI)V");
  }
  if (CLASS_STATIC_IMAGE == NULL || CONSTRUCTOR_STATIC_IMAGE == NULL) {
    LOGE(MSG("Can't find StaticImage or its constructor."));
    INIT_SUCCEED = false;
    return JNI_VERSION_1_6;
  }

  CLASS_ANIMATED_IMAGE = (*env)->FindClass(env, "com/hippo/image/AnimatedImage");
  CLASS_ANIMATED_IMAGE = (*env)->NewGlobalRef(env, CLASS_ANIMATED_IMAGE);
  if (CLASS_ANIMATED_IMAGE != NULL) {
    CONSTRUCTOR_ANIMATED_IMAGE = (*env)->GetMethodID(env, CLASS_ANIMATED_IMAGE, "<init>", "(JIIIZ)V");
    METHOD_ANIMATED_IMAGE_ON_COMPLETE = (*env)->GetMethodID(env, CLASS_ANIMATED_IMAGE, "onComplete", "(I[II)V");
  }
  if (CLASS_STATIC_IMAGE == NULL || CONSTRUCTOR_STATIC_IMAGE == NULL || METHOD_ANIMATED_IMAGE_ON_COMPLETE == NULL) {
    LOGE(MSG("Can't find AnimatedImage or its constructor or its onComplete()."));
    INIT_SUCCEED = false;
    return JNI_VERSION_1_6;
  }

  class_image_info = (*env)->FindClass(env, "com/hippo/image/ImageInfo");
  if (class_image_info != NULL) {
    METHOD_IMAGE_INFO_SET = (*env)->GetMethodID(env, class_image_info, "set", "(IIIZI)V");
  }
  if (class_image_info == NULL || METHOD_IMAGE_INFO_SET == NULL) {
    LOGE(MSG("Can't find ImageInfo or its set()."));
    INIT_SUCCEED = false;
    return JNI_VERSION_1_6;
  }

  CLASS_BITMAP_DECODER = (*env)->FindClass(env, "com/hippo/image/BitmapDecoder");
  CLASS_BITMAP_DECODER = (*env)->NewGlobalRef(env, CLASS_BITMAP_DECODER);
  if (CLASS_BITMAP_DECODER != NULL) {
    METHOD_BITMAP_DECODER_CREATE_BITMAP = (*env)->GetStaticMethodID(env, CLASS_BITMAP_DECODER, "createBitmap", "(III)Landroid/graphics/Bitmap;");
  }
  if (CLASS_BITMAP_DECODER == NULL || METHOD_BITMAP_DECODER_CREATE_BITMAP == NULL) {
    LOGE(MSG("Can't find BitmapDecoder or its createBitmap()."));
    INIT_SUCCEED = false;
    return JNI_VERSION_1_6;
  }

  CLASS_BITMAP_REGION_DECODER = (*env)->FindClass(env, "com/hippo/image/BitmapRegionDecoder");
  CLASS_BITMAP_REGION_DECODER = (*env)->NewGlobalRef(env, CLASS_BITMAP_REGION_DECODER);
  if (CLASS_BITMAP_REGION_DECODER != NULL) {
    CONSTRUCTOR_BITMAP_REGION_DECODER = (*env)->GetMethodID(env, CLASS_BITMAP_REGION_DECODER, "<init>", "(JIIIZ)V");
  }
  if (CLASS_BITMAP_REGION_DECODER == NULL || CONSTRUCTOR_BITMAP_REGION_DECODER == NULL) {
    LOGE(MSG("Can't find BitmapRegionDecoder or its constructor."));
    INIT_SUCCEED = false;
    return JNI_VERSION_1_6;
  }

  class_bitmap = (*env)->FindClass(env, "android/graphics/Bitmap");
  if (class_bitmap != NULL) {
    METHOD_BITMAP_RECYCLE = (*env)->GetMethodID(env, class_bitmap, "recycle", "()V");
  }
  if (class_bitmap == NULL || METHOD_BITMAP_RECYCLE == NULL) {
    LOGE(MSG("Can't find Bitmap or its recycle()."));
    INIT_SUCCEED = false;
    return JNI_VERSION_1_6;
  }

  java_stream_init(env);
  init_image_libraries();

  INIT_SUCCEED = true;
  return JNI_VERSION_1_6;
}

__unused
JNIEXPORT void JNICALL
JNI_OnUnload(__unused JavaVM *vm, __unused void* reserved) {}


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <malloc.h>

#include "static_image.h"
#include "../log.h"

StaticImage* static_image_new(uint32_t width, uint32_t height) {
  StaticImage* image = malloc(sizeof(StaticImage));
  uint8_t* buffer = malloc(width * height * 4);
  if (image == NULL || buffer == NULL) {
    WTF_OOM;
    free(image);
    free(buffer);
    return NULL;
  }

  image->width = width;
  image->height = height;
  image->buffer = buffer;

  return image;
}

void static_image_delete(StaticImage** image) {
  if (image == NULL || *image == NULL) {
    return;
  }

  free((*image)->buffer);
  (*image)->buffer = NULL;
  free(*image);
  *image = NULL;
}

repo_name: tachiyomiorg/mirror

license: mit


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef IMAGE_STATIC_IMAGE_H
#define IMAGE_STATIC_IMAGE_H

#include <stdbool.h>
#include <stdint.h>

typedef struct {
  uint32_t width;
  uint32_t height;
  int32_t format;
  bool opaque;
  uint8_t* buffer;
} StaticImage;

StaticImage* static_image_new(uint32_t width, uint32_t height);

void static_image_delete(StaticImage** image);

#endif //IMAGE_STATIC_IMAGE_H


code:
/*
 * Copyright 2018 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdint.h>
#include <string.h>
#include <malloc.h>

#include "image_utils.h"
#include "buffer.h"
#include "log.h"
#include "utils.h"

size_t buffer_read(Buffer* buffer, void* dst, size_t size) {
  size_t remain = buffer->length - buffer->position;
  size_t read = MIN(remain, size);

  if (read != 0) {
    memcpy(dst, buffer->raw + buffer->position, read);
    buffer->position += read;
  }

  return read;
}

size_t buffer_write(Buffer* buffer, const void* src, size_t size) {
  size_t remain = buffer->capacity - buffer->length;

  size_t write;
  size_t new_capacity;
  void* new_raw;

  if (remain >= size) {
    write = size;
  } else if (!buffer->extendable) {
    write = remain;
  } else {
    new_capacity = next_pow2_size_t(buffer->length + size);
    new_raw = realloc(buffer->raw, new_capacity);
    if (new_raw == NULL) {
      WTF_OOM;
      write = remain;
    } else {
      buffer->raw = new_raw;
      buffer->capacity = new_capacity;
      write = size;
    }
  }

  if (write != 0) {
    memcpy(buffer->raw + buffer->length, src, write);
    buffer->length += write;
  }

  return write;
}

size_t buffer_seek(Buffer* buffer, size_t position) {
  buffer->position = MIN(position, buffer->length);
  return buffer->position;
}

void buffer_shrink(Buffer* buffer) {
  if (buffer->position == 0) {
    // No data has been read, no need to shrink
  } else {
    buffer->length = buffer->length - buffer->position;
    if (buffer->length == 0) {
      // No need to copy
    } else if (buffer->length <= buffer->position) {
      // No overlap
      memcpy(buffer->raw, buffer->raw + buffer->position, buffer->length);
    } else {
      memmove(buffer->raw, buffer->raw + buffer->position, buffer->length);
    }
    buffer->position = 0;
  }
}

void buffer_close(Buffer** buffer) {
  if (buffer == NULL || *buffer == NULL) {
    return;
  }

  free((*buffer)->raw);
  (*buffer)->raw = NULL;
  free(*buffer);
  *buffer = NULL;
}

Buffer* buffer_new(size_t capacity, bool extendable) {
  void* raw;

  if (capacity == 0) {
    LOGE(MSG("Invalid buffer capacity: 0"));
    return NULL;
  }

  raw = malloc(capacity);
  if (raw == NULL) {
    WTF_OOM;
    free(raw);
    return NULL;
  }

  return buffer_new_from_raw(raw, 0, 0, capacity, extendable);
}

Buffer* buffer_new_from_raw(void* raw, size_t position, size_t length, size_t capacity, bool extendable) {
  Buffer* buffer;

  if (raw == NULL || position > length || length > capacity || capacity == 0) {
    LOGE(MSG("Invalid parameter"));
    return NULL;
  }

  buffer = malloc(sizeof(Buffer));
  if (buffer == NULL) {
    WTF_OOM;
    free(buffer);
    free(raw);
    return NULL;
  }

  buffer->raw = raw;
  buffer->position = position;
  buffer->length = length;
  buffer->capacity = capacity;
  buffer->extendable = extendable;

  return buffer;
}


code:
/*
 * Copyright 2018 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef IMAGE_BUFFER_H
#define IMAGE_BUFFER_H

#include <stdbool.h>

struct BUFFER;
typedef struct BUFFER Buffer;

/**
 * Buffer provides some functions to control a memory block.
 *
 * The memory looks like this.
 * |-------|-----------|---------|
 * 0    position    length    capacity
 */
struct BUFFER {
  /**
   * The memory block.
   * Never access it directly.
   */
  void* raw;
  /**
   * The position of next read. It moves toward after reading. But it can't go beyond length.
   * Read-only.
   */
  size_t position;
  /**
   * The position of next write. It moves toward after writing. But it can't go beyond capacity.
   * Read-only.
   */
  size_t length;
  /**
   * The size of the memory block.
   * Read-only.
   */
  size_t capacity;
  /**
   * Whether the memory block can be extended.
   * Read-only.
   */
  bool extendable;
};

/**
 * Create a buffer with a newly malloc memory block.
 *
 * @param capacity The initially size of the memory block.
 * @param extendable True if the memory block can be extended.
 * @return A buffer, or NULL if capacity is zero or OOM.
 */
Buffer* buffer_new(size_t capacity, bool extendable);

/**
 * Create a buffer with an existed memory block.
 *
 * @param raw The memory block. The buffer handles the freedom of the memory block.
 * @param position The position of next read.
 * @param length The position of next write.
 * @param capacity The initially size of the memory block.
 * @param extendable True if the memory block can be extended.
 * @return A buffer, or NULL if raw is NULL or position > length or length > capacity or capacity is zero or OOM.
 */
Buffer* buffer_new_from_raw(void* raw, size_t position, size_t length, size_t capacity, bool extendable);

/**
 * Read bytes from the memory block to dst. The starting point of the memory block is position.
 * Position moves toward while reading. At most size bytes can be read,
 * or until position reaches length.
 *
 * @param buffer The buffer to read.
 * @param dst The reading destination.
 * @param size The maximum number of bytes to read.
 * @return How many bytes has been read.
 */
size_t buffer_read(Buffer* buffer, void* dst, size_t size);

/**
 * Write bytes from src to the memory block. The starting point of the memory block is length.
 * Length moves toward while reading. At most size bytes can be read,
 * or until length reaches capacity if the buffer is not extendable.
 *
 * @param buffer The buffer to write.
 * @param src The writing source.
 * @param size The maximum number of bytes to write.
 * @return How many bytes has been written.
 */
size_t buffer_write(Buffer* buffer, const void* src, size_t size);

/**
 * Change position of the buffer.
 *
 * @param buffer The buffer to seek.
 * @param position The new position. If it's bigger than length. The new position is length.
 * @return The actual position after seek. It's always between 0 and length.
 */
size_t buffer_seek(Buffer* buffer, size_t position);

/**
 * Discard bytes from 0 to position and move bytes from position to length ahead.
 *
 * @param buffer The buffer to buffer.
 */
void buffer_shrink(Buffer* buffer);

/**
 * Free all resources associated with this buffer and itself.
 *
 * @param buffer The buffer to close.
 */
void buffer_close(Buffer** buffer);

#endif //IMAGE_BUFFER_H


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <malloc.h>
#include <string.h>

#include "buffer_stream.h"
#include "../log.h"

typedef struct {
  void* buffer;
  size_t length;
  void* pos;
  size_t read;
} BufferStreamData;

static size_t read(Stream* stream, void* dst, size_t size) {
  BufferStreamData* data = (BufferStreamData*) stream->data;
  size_t remain = data->length - data->read;
  size_t len = MIN(size, remain);

  memcpy(dst, data->pos, len);
  data->pos += len;
  data->read += len;

  return len;
}

static size_t peek(Stream* stream, void* dst, size_t size) {
  BufferStreamData* data = (BufferStreamData*) stream->data;
  void* pos_bak = data->pos;
  size_t read_bak = data->read;

  size_t len = read(stream, dst, size);
  data->pos = pos_bak;
  data->read = read_bak;

  return len;
}

static void close(Stream** stream) {
  if (stream == NULL || *stream == NULL) {
    return;
  }

  BufferStreamData* data = (*stream)->data;
  free(data->buffer);
  data->buffer = NULL;
  data->pos = NULL;
  free(data);
  (*stream)->data = NULL;
  free(*stream);
  *stream = NULL;
}

Stream* buffer_stream_new(void* buffer, size_t length) {
  Stream* stream;
  BufferStreamData* data;

  stream = malloc(sizeof(Stream));
  data = malloc(sizeof(BufferStreamData));
  if (stream == NULL || data == NULL) {
    WTF_OOM;
    free(stream);
    free(data);
    return NULL;
  }

  data->buffer = buffer;
  data->length = length;
  data->pos = buffer;
  data->read = 0;

  stream->data = data;
  stream->read = read;
  stream->peek = peek;
  stream->close = close;

  return stream;
}

void buffer_stream_reset(Stream* stream) {
  BufferStreamData* data = stream->data;
  data->pos = data->buffer;
  data->read = 0;
}


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef IMAGE_BUFFER_STREAM_H
#define IMAGE_BUFFER_STREAM_H

#include <stddef.h>

#include "stream.h"

Stream* buffer_stream_new(void* buffer, size_t length);

void buffer_stream_reset(Stream* stream);

#endif //IMAGE_BUFFER_STREAM_H


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <malloc.h>
#include <string.h>
#include <stdbool.h>

#include "java_stream.h"
#include "buffer.h"
#include "image_utils.h"
#include "log.h"

static bool INIT_SUCCEED = false;

static jmethodID METHOD_READ = NULL;
static jmethodID METHOD_CLOSE = NULL;

struct JAVA_STREAM_DATA;
typedef struct JAVA_STREAM_DATA JavaStreamData;

struct JAVA_STREAM_DATA {
  JNIEnv* env;
  jobject is;
  jbyteArray j_buffer;

  Buffer* buffer;

  size_t (*read_internal)(JavaStreamData* data, void* dst, size_t size);

  Buffer* backup;
};

static size_t read_internal_with_buffer(JavaStreamData* data, void* dst, size_t size) {
  JNIEnv* env = data->env;
  Buffer* buffer = data->buffer;
  size_t remain = size;
  size_t read = 0;
  int len;

  while (remain > 0) {
    if (buffer->position == buffer->length) {
      // Read from java InputStream to java buffer
      // Always read DEFAULT_BUFFER_SIZE
      len = (*env)->CallIntMethod(env, data->is, METHOD_READ, data->j_buffer, 0, DEFAULT_BUFFER_SIZE);
      if ((*env)->ExceptionCheck(env)) {
        LOGE(MSG("Catch exception"));
        (*env)->ExceptionDescribe(env);
        (*env)->ExceptionClear(env);
        len = -1;
      }

      // end of the stream or catch exception
      if (len <= 0) { break; }

      // Copy bytes to buffer
      // TODO Buffer can't  handle jbyteArray directly. Add a new method like buffer_write_jbyteArray()?
      (*env)->GetByteArrayRegion(env, data->j_buffer, 0, len, (jbyte *) (buffer->raw));
      buffer->position = 0;
      buffer->length = (size_t) len;
    }

    // Copy from c buffer to target buffer
    len = (int) buffer_read(buffer, dst, remain);

    // Update parameters
    remain -= len;
    read += len;
    dst += len;
  }

  return read;
}

static size_t read_internal_without_buffer(JavaStreamData* data, void* dst, size_t size) {
  JNIEnv* env = data->env;
  size_t remain = size;
  size_t read = 0;
  int len;

  while (remain > 0) {
    // Read from java InputStream to java buffer
    len = MIN(DEFAULT_BUFFER_SIZE, (int) remain);
    len = (*env)->CallIntMethod(env, data->is, METHOD_READ, data->j_buffer, 0, len);
    if ((*env)->ExceptionCheck(env)) {
      LOGE(MSG("Catch exception"));
      (*env)->ExceptionDescribe(env);
      (*env)->ExceptionClear(env);
      len = -1;
    }

    // end of the stream or catch exception
    if (len <= 0) { break; }

    // Copy from java buffer to c buffer
    (*env)->GetByteArrayRegion(env, data->j_buffer, 0, len, (jbyte *) dst);

    // Update parameters
    remain -= len;
    read += len;
    dst += len;
  }

  return read;
}

static size_t read(Stream* stream, void* dst, size_t size) {
  JavaStreamData* data = stream->data;
  size_t read = 0;

  if (dst == NULL || size == 0) {
    return 0;
  }

  if (data->backup != NULL) {
    read += buffer_read(data->backup, dst, size);
    dst += read;
    size -= read;

    if (size == 0) {
      return read;
    }
  }

  // Read from stream
  read += data->read_internal(stream->data, dst, size);

  return read;
}

size_t peek(Stream* stream, void* dst, size_t size) {
  JavaStreamData* data = stream->data;
  Buffer* backup = data->backup;
  size_t backup_read = 0;
  size_t stream_read = 0;
  size_t backup_write = 0;

  if (backup != NULL) {
    backup_read = buffer_read(backup, dst, size);
    dst += backup_read;
    size -= backup_read;

    if (size == 0) {
      // Just peek backup
      buffer_seek(backup, backup->position - backup_read);
      return backup_read;
    }
  }

  stream_read = read(stream, dst, size);

  if (backup != NULL) {
    buffer_seek(backup, backup->position - backup_read);
    // Only shrink the backup if it needs more space
    if (backup->capacity - backup->length < stream_read) {
      buffer_shrink(backup);
    }
  } else {
    // Create a backup
    backup = data->backup = buffer_new(next_pow2_size_t(stream_read), true);
    if (backup == NULL) {
      LOGE(MSG("Can't create backup for java stream."));
    }
  }

  if (backup != NULL) {
    backup_write = buffer_write(backup, dst, stream_read);
    if (backup_write != stream_read) {
      LOGE(MSG("Can't write bytes to backup."));
    }
  }

  return backup_read + stream_read;
}

static void close(Stream** stream) {
  if (stream == NULL || *stream == NULL) {
    return;
  }

  JavaStreamData* data = (*stream)->data;
  JNIEnv* env = data->env;

  // Close java InputStream
  (*env)->CallVoidMethod(env, data->is, METHOD_CLOSE);
  if ((*env)->ExceptionCheck(env)) {
    LOGE(MSG("Catch exception"));
    (*env)->ExceptionDescribe(env);
    (*env)->ExceptionClear(env);
  }

  // Delete java object global reference
  (*env)->DeleteGlobalRef(env, data->is);
  (*env)->DeleteGlobalRef(env, data->j_buffer);

  // Free
  free(data->buffer);
  data->buffer = NULL;
  if (data->backup != NULL) {
    buffer_close(&data->backup);
  }
  free(data);
  (*stream)->data = NULL;
  free(*stream);
  *stream = NULL;
}

void java_stream_init(JNIEnv* env) {
  jclass CLAZZ = (*env)->FindClass(env, "java/io/InputStream");

  if (CLAZZ != NULL) {
    METHOD_READ = (*env)->GetMethodID(env, CLAZZ, "read", "([BII)I");
    METHOD_CLOSE = (*env)->GetMethodID(env, CLAZZ, "close", "()V");
    INIT_SUCCEED = METHOD_READ != NULL && METHOD_CLOSE != NULL;
  } else {
    INIT_SUCCEED = false;
  }

  if (!INIT_SUCCEED) {
    LOGE(MSG("Can't init java stream"));
  }
}

Stream* java_stream_new(JNIEnv* env, jobject* is, bool with_buffer) {
  Stream* stream = NULL;
  JavaStreamData* data = NULL;
  Buffer* buffer = NULL;
  jbyteArray j_buffer;

  if (!INIT_SUCCEED) {
    return NULL;
  }

  stream = malloc(sizeof(Stream));
  data = malloc(sizeof(JavaStreamData));
  if (stream == NULL || data == NULL) { WTF_OOM; goto fail; }
  if (with_buffer) {
    buffer = buffer_new(DEFAULT_BUFFER_SIZE, false);
    if (buffer == NULL) { WTF_OOM; goto fail; }
  }

  j_buffer = (*env)->NewByteArray(env, DEFAULT_BUFFER_SIZE);
  j_buffer = (*env)->NewGlobalRef(env, j_buffer);
  if (j_buffer == NULL) { LOGE(MSG("Can't create buffer")); goto fail; }

  data->env = env;
  data->is = (*env)->NewGlobalRef(env, is);
  data->j_buffer = j_buffer;

  data->buffer = buffer;

  data->read_internal = with_buffer ? &read_internal_with_buffer : &read_internal_without_buffer;

  data->backup = NULL;

  stream->data = data;
  stream->read = read;
  stream->peek = peek;
  stream->close = close;

  return stream;

fail:
  free(stream);
  free(data);
  free(buffer);
  return NULL;
}

void java_stream_set_env(Stream* stream, JNIEnv* env) {
  ((JavaStreamData*) stream->data)->env = env;
}


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef IMAGE_JAVA_STREAM_H
#define IMAGE_JAVA_STREAM_H

#include <jni.h>

#include "stream.h"

void java_stream_init(JNIEnv* env);

Stream* java_stream_new(JNIEnv* env, jobject* is, bool with_buffer);

void java_stream_set_env(Stream* stream, JNIEnv* env);

#endif //IMAGE_JAVA_STREAM_H


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <malloc.h>

#include "stream.h"
#include "../log.h"

void* stream_read_all(Stream* stream, size_t* size) {
  size_t len = 0;
  size_t limit = DEFAULT_BUFFER_SIZE;
  size_t read;
  void* buffer = NULL;
  void* buffer_bak = NULL;

  buffer = malloc(limit);
  if (buffer == NULL) {
    WTF_OOM;
    return NULL;
  }

  for (;;) {
    // Read from stream
    read = stream->read(stream, buffer + len, limit - len);
    len += read;
    // Check stream end
    if (len < limit) {
      // Check if stream read any data
      if (len == 0) {
        LOGW("Didn't read anything from stream, is it open?");
        free(buffer);
        return NULL;
      }
      // Get the end, shrink the buffer
      buffer_bak = buffer;
      buffer = realloc(buffer, len);
      if (buffer == NULL) {
        LOGE("Failed to shrink the buffer");
        free(buffer_bak);
        return NULL;
      } else {
        if (size != NULL) {
          *size = len;
        }
        return buffer;
      }
    }
    // Extent buffer
    limit *= 2;
    buffer_bak = buffer;
    buffer = realloc(buffer, limit);
    if (buffer == NULL) {
      WTF_OOM;
      free(buffer_bak);
      return NULL;
    }
  }
}


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef IMAGE_STREAM_H
#define IMAGE_STREAM_H

#include <stdbool.h>
#include <stdint.h>

#define DEFAULT_BUFFER_SIZE 4096 // 1024 * 4

struct STREAM;
typedef struct STREAM Stream;

typedef size_t (*StreamReadFunc) (Stream* stream, void* dst, size_t size);
typedef size_t (*StreamPeekFunc) (Stream* stream, void* dst, size_t size);
typedef void   (*StreamCloseFunc)(Stream** stream);

struct STREAM {
  void* data;
  StreamReadFunc  read;
  StreamPeekFunc peek;
  StreamCloseFunc close;
};

/**
 * Read all data from this stream.
 */
void* stream_read_all(Stream* stream, size_t* size);

#endif //IMAGE_STREAM_H


code:
# Copyright 2018 Hippo Seven
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.4.1)
project(image-jpeg C)

set(LIBJPEG_TURBO_SOURCES
    image_jpeg.c
    libjpeg-turbo/jaricom.c
    libjpeg-turbo/jcomapi.c
    libjpeg-turbo/jdapimin.c
    libjpeg-turbo/jdapistd.c
    libjpeg-turbo/jdarith.c
    libjpeg-turbo/jdatasrc.c
    libjpeg-turbo/jdatasrc-tj.c
    libjpeg-turbo/jdcoefct.c
    libjpeg-turbo/jdcolor.c
    libjpeg-turbo/jddctmgr.c
    libjpeg-turbo/jdhuff.c
    libjpeg-turbo/jdinput.c
    libjpeg-turbo/jdmainct.c
    libjpeg-turbo/jdmarker.c
    libjpeg-turbo/jdmaster.c
    libjpeg-turbo/jdmerge.c
    libjpeg-turbo/jdphuff.c
    libjpeg-turbo/jdpostct.c
    libjpeg-turbo/jdsample.c
    libjpeg-turbo/jdtrans.c
    libjpeg-turbo/jerror.c
    libjpeg-turbo/jfdctflt.c
    libjpeg-turbo/jfdctfst.c
    libjpeg-turbo/jfdctint.c
    libjpeg-turbo/jidctflt.c
    libjpeg-turbo/jidctfst.c
    libjpeg-turbo/jidctint.c
    libjpeg-turbo/jidctred.c
    libjpeg-turbo/jmemmgr.c
    libjpeg-turbo/jmemnobs.c
    libjpeg-turbo/jquant1.c
    libjpeg-turbo/jquant2.c
    libjpeg-turbo/jutils.c
)
set(LIBJPEG_TURBO_INCLUDES
    ${IMAGE_EXPORT_INCLUDES}
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/libjpeg-turbo
)
set(LIBJPEG_TURBO_DEFINITIONS
)
set(LIBJPEG_TURBO_LIBRARIES
)

if (${ANDROID_ABI} STREQUAL "armeabi-v7a")

  enable_language(ASM)

  set(LIBJPEG_TURBO_SOURCES
      ${LIBJPEG_TURBO_SOURCES}
      libjpeg-turbo/simd/arm/jsimd.c
      libjpeg-turbo/simd/arm/jsimd_neon.S
  )

elseif (${ANDROID_ABI} STREQUAL "arm64-v8a")

  enable_language(ASM)

  set(LIBJPEG_TURBO_SOURCES
      ${LIBJPEG_TURBO_SOURCES}
      libjpeg-turbo/simd/arm64/jsimd.c
      libjpeg-turbo/simd/arm64/jsimd_neon.S
  )

elseif (${ANDROID_ABI} STREQUAL "x86")

  enable_language(ASM_NASM)

  set(LIBJPEG_TURBO_SOURCES
      ${LIBJPEG_TURBO_SOURCES}
      libjpeg-turbo/simd/i386/jsimd.c
      libjpeg-turbo/simd/i386/jsimdcpu.asm
      libjpeg-turbo/simd/i386/jfdctflt-3dn.asm
      libjpeg-turbo/simd/i386/jidctflt-3dn.asm
      libjpeg-turbo/simd/i386/jquant-3dn.asm
      libjpeg-turbo/simd/i386/jccolor-mmx.asm
      libjpeg-turbo/simd/i386/jcgray-mmx.asm
      libjpeg-turbo/simd/i386/jcsample-mmx.asm
      libjpeg-turbo/simd/i386/jdcolor-mmx.asm
      libjpeg-turbo/simd/i386/jdmerge-mmx.asm
      libjpeg-turbo/simd/i386/jdsample-mmx.asm
      libjpeg-turbo/simd/i386/jfdctfst-mmx.asm
      libjpeg-turbo/simd/i386/jfdctint-mmx.asm
      libjpeg-turbo/simd/i386/jidctfst-mmx.asm
      libjpeg-turbo/simd/i386/jidctint-mmx.asm
      libjpeg-turbo/simd/i386/jidctred-mmx.asm
      libjpeg-turbo/simd/i386/jquant-mmx.asm
      libjpeg-turbo/simd/i386/jfdctflt-sse.asm
      libjpeg-turbo/simd/i386/jidctflt-sse.asm
      libjpeg-turbo/simd/i386/jquant-sse.asm
      libjpeg-turbo/simd/i386/jccolor-sse2.asm
      libjpeg-turbo/simd/i386/jcgray-sse2.asm
      libjpeg-turbo/simd/i386/jchuff-sse2.asm
      libjpeg-turbo/simd/i386/jcphuff-sse2.asm
      libjpeg-turbo/simd/i386/jcsample-sse2.asm
      libjpeg-turbo/simd/i386/jdcolor-sse2.asm
      libjpeg-turbo/simd/i386/jdmerge-sse2.asm
      libjpeg-turbo/simd/i386/jdsample-sse2.asm
      libjpeg-turbo/simd/i386/jfdctfst-sse2.asm
      libjpeg-turbo/simd/i386/jfdctint-sse2.asm
      libjpeg-turbo/simd/i386/jidctflt-sse2.asm
      libjpeg-turbo/simd/i386/jidctfst-sse2.asm
      libjpeg-turbo/simd/i386/jidctint-sse2.asm
      libjpeg-turbo/simd/i386/jidctred-sse2.asm
      libjpeg-turbo/simd/i386/jquantf-sse2.asm
      libjpeg-turbo/simd/i386/jquanti-sse2.asm
      libjpeg-turbo/simd/i386/jccolor-avx2.asm
      libjpeg-turbo/simd/i386/jcgray-avx2.asm
      libjpeg-turbo/simd/i386/jcsample-avx2.asm
      libjpeg-turbo/simd/i386/jdcolor-avx2.asm
      libjpeg-turbo/simd/i386/jdmerge-avx2.asm
      libjpeg-turbo/simd/i386/jdsample-avx2.asm
      libjpeg-turbo/simd/i386/jfdctint-avx2.asm
      libjpeg-turbo/simd/i386/jidctint-avx2.asm
      libjpeg-turbo/simd/i386/jquanti-avx2.asm
  )

  set(CMAKE_ASM_NASM_FLAGS "-DELF -DPIC -I${CMAKE_CURRENT_SOURCE_DIR}/libjpeg-turbo/simd/nasm")

elseif (${ANDROID_ABI} STREQUAL "x86_64")

  enable_language(ASM_NASM)

  set(LIBJPEG_TURBO_SOURCES
      ${LIBJPEG_TURBO_SOURCES}
      libjpeg-turbo/simd/x86_64/jsimd.c
      libjpeg-turbo/simd/x86_64/jsimdcpu.asm
      libjpeg-turbo/simd/x86_64/jfdctflt-sse.asm
      libjpeg-turbo/simd/x86_64/jccolor-sse2.asm
      libjpeg-turbo/simd/x86_64/jcgray-sse2.asm
      libjpeg-turbo/simd/x86_64/jchuff-sse2.asm
      libjpeg-turbo/simd/x86_64/jcphuff-sse2.asm
      libjpeg-turbo/simd/x86_64/jcsample-sse2.asm
      libjpeg-turbo/simd/x86_64/jdcolor-sse2.asm
      libjpeg-turbo/simd/x86_64/jdmerge-sse2.asm
      libjpeg-turbo/simd/x86_64/jdsample-sse2.asm
      libjpeg-turbo/simd/x86_64/jfdctfst-sse2.asm
      libjpeg-turbo/simd/x86_64/jfdctint-sse2.asm
      libjpeg-turbo/simd/x86_64/jidctflt-sse2.asm
      libjpeg-turbo/simd/x86_64/jidctfst-sse2.asm
      libjpeg-turbo/simd/x86_64/jidctint-sse2.asm
      libjpeg-turbo/simd/x86_64/jidctred-sse2.asm
      libjpeg-turbo/simd/x86_64/jquantf-sse2.asm
      libjpeg-turbo/simd/x86_64/jquanti-sse2.asm
      libjpeg-turbo/simd/x86_64/jccolor-avx2.asm
      libjpeg-turbo/simd/x86_64/jcgray-avx2.asm
      libjpeg-turbo/simd/x86_64/jcsample-avx2.asm
      libjpeg-turbo/simd/x86_64/jdcolor-avx2.asm
      libjpeg-turbo/simd/x86_64/jdmerge-avx2.asm
      libjpeg-turbo/simd/x86_64/jdsample-avx2.asm
      libjpeg-turbo/simd/x86_64/jfdctint-avx2.asm
      libjpeg-turbo/simd/x86_64/jidctint-avx2.asm
      libjpeg-turbo/simd/x86_64/jquanti-avx2.asm
  )

  set(CMAKE_ASM_NASM_FLAGS "-D__x86_64__ -DELF -DPIC -I${CMAKE_CURRENT_SOURCE_DIR}/libjpeg-turbo/simd/nasm")

else ()

  set(LIBJPEG_TURBO_SOURCES
      ${LIBJPEG_TURBO_SOURCES}
      libjpeg-turbo/jsimd_none.c
  )

endif ()

if(IMAGE_SINGLE_SHARED_LIB)
    # Compile a static library for image-singlelib
    set(LIBJPEG_TURBO_LIBRARY_NAME image-jpeg-static)
    set(LIBJPEG_TURBO_LIBRARY_TYPE STATIC)
    set(LIBJPEG_TURBO_INCLUDES_VISIBILITY PUBLIC) # Share include dir with image project
else()
    # Compile a shared library to be imported by image-core
    set(LIBJPEG_TURBO_LIBRARY_NAME image-jpeg)
    set(LIBJPEG_TURBO_LIBRARY_TYPE SHARED)
    set(LIBJPEG_TURBO_INCLUDES_VISIBILITY PRIVATE)

    # Link against image shared lib to get helper methods
    set(LIBJPEG_TURBO_LIBRARIES
        ${LIBJPEG_TURBO_LIBRARIES}
        image
        log
    )
endif()

add_library(${LIBJPEG_TURBO_LIBRARY_NAME} ${LIBJPEG_TURBO_LIBRARY_TYPE} ${LIBJPEG_TURBO_SOURCES})
target_include_directories(${LIBJPEG_TURBO_LIBRARY_NAME} ${LIBJPEG_TURBO_INCLUDES_VISIBILITY} ${LIBJPEG_TURBO_INCLUDES})
target_compile_definitions(${LIBJPEG_TURBO_LIBRARY_NAME} PRIVATE ${LIBJPEG_TURBO_DEFINITIONS})
target_link_libraries(${LIBJPEG_TURBO_LIBRARY_NAME} PRIVATE ${LIBJPEG_TURBO_LIBRARIES})


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <setjmp.h>
#include <malloc.h>
#include <string.h>

#include "image.h"
#include "image_jpeg.h"
#include "image_decoder.h"
#include "image_convert.h"
#include "image_utils.h"
#include "../log.h"

struct my_error_mgr {
  struct jpeg_error_mgr pub;
  jmp_buf setjmp_buffer;
};

typedef struct my_error_mgr * my_error_ptr;

static char emsg[JMSG_LENGTH_MAX];

static void my_error_exit(j_common_ptr cinfo) {
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  (*cinfo->err->format_message)(cinfo, emsg);
  longjmp(myerr->setjmp_buffer, 1);
}

static size_t custom_read(void * custom_stuff, unsigned char * buffer, size_t size) {
  Stream* stream = (Stream*) custom_stuff;
  return stream->read(stream, buffer, size);
}

LIBRARY_EXPORT
bool jpeg_init(ImageLibrary* library) {
    library->loaded = true;
    library->is_magic = jpeg_is_magic;
    library->decode = jpeg_decode;
    library->decode_info = jpeg_decode_info;
    library->decode_buffer = jpeg_decode_buffer;
    library->create = NULL;
    library->get_description = jpeg_get_description;

    return true;
}

bool jpeg_is_magic(Stream* stream) {
    uint8_t magic[2];

    size_t read = stream->peek(stream, magic, sizeof(magic));
    if (read != sizeof(magic)) {
        LOGE(MSG("Could not read %zu bytes from stream, only read %zu"), sizeof(magic), read);
        return false;
    }

    return magic[0] == IMAGE_JPEG_MAGIC_NUMBER_0 && magic[1] == IMAGE_JPEG_MAGIC_NUMBER_1;
}

const char* jpeg_get_description() {
    return IMAGE_JPEG_DECODER_DESCRIPTION;
}

StaticImage* jpeg_decode(Stream* stream, bool unused1, bool* animated) {
  *animated = false;

  StaticImage* image = NULL;
  struct jpeg_decompress_struct cinfo;
  struct my_error_mgr jerr;
  uint8_t* buffer = NULL;
  size_t stride;
  uint8_t* line_buffer_array[3];
  uint32_t read_lines;
  bool result = false;

  // Init
  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;
  if (setjmp(jerr.setjmp_buffer)) { LOGE(MSG("%s"), emsg); goto end; }
  jpeg_create_decompress(&cinfo);
  jpeg_custom_src(&cinfo, &custom_read, stream);
  jpeg_read_header(&cinfo, TRUE);

  // Start decompress
  cinfo.out_color_space = JCS_EXT_RGBA;
  jpeg_start_decompress(&cinfo);

  // New static image
  image = static_image_new(cinfo.output_width, cinfo.output_height);
  if (image == NULL) { goto end; }

  // Set buffer to image->buffer
  buffer = image->buffer;

  // Copy to buffer
  stride = cinfo.output_components * cinfo.output_width;
  line_buffer_array[0] = buffer;
  line_buffer_array[1] = line_buffer_array[0] + stride;
  line_buffer_array[2] = line_buffer_array[1] + stride;
  while (cinfo.output_scanline < cinfo.output_height) {
    read_lines = jpeg_read_scanlines(&cinfo, line_buffer_array, 3);
    line_buffer_array[0] += stride * read_lines;
    line_buffer_array[1] = line_buffer_array[0] + stride;
    line_buffer_array[2] = line_buffer_array[1] + stride;
  }

  // It's not necessary to call jpeg_finish_decompress().
  // Skip it will increase decode speed.

  // Fill jpeg
  image->format = IMAGE_FORMAT_JPEG;
  image->opaque = true;

  // Done!
  result = true;

end:
  if (!result) {
    static_image_delete(&image);
  }
  jpeg_destroy_decompress(&cinfo);
  return image;
}

bool jpeg_decode_info(Stream* stream, ImageInfo* info) {
  struct jpeg_decompress_struct cinfo;
  struct my_error_mgr jerr;
  bool result = false;

  // Init
  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;
  if (setjmp(jerr.setjmp_buffer)) { LOGE(MSG("%s"), emsg); goto end; }
  jpeg_create_decompress(&cinfo);
  jpeg_custom_src(&cinfo, &custom_read, stream);
  jpeg_read_header(&cinfo, TRUE);

  // Assign image info
  info->width = cinfo.image_width;
  info->height = cinfo.image_height;
  info->format = IMAGE_FORMAT_JPEG;
  info->opaque = true;
  info->frame_count = 1;

  // Done
  result = true;

end:
  jpeg_destroy_decompress(&cinfo);
  return result;
}

bool jpeg_decode_buffer(Stream* stream, bool clip, uint32_t x, uint32_t y, uint32_t width,
    uint32_t height, int32_t config, uint32_t ratio, BufferContainer* container) {
  struct jpeg_decompress_struct cinfo;
  struct my_error_mgr jerr;
  bool too_small;
  bool result = false;
  uint32_t i;

  // r_xxx the values of target rect in decoded image.
  // jpeg_crop_scanline() can't crop precisely,
  // ur_x is deserve x, r_x is actual x.
  uint32_t ur_x;
  uint32_t r_x;
  uint32_t r_y;
  uint32_t r_width;
  uint32_t r_height;

  uint32_t d_width;
  uint32_t d_height;

  uint32_t components;

  uint32_t soft_scale;

  uint32_t r_stride;
  uint32_t r_start_stride;
  uint32_t d_stride;

  uint8_t* r_line_1 = NULL;
  uint8_t* r_line_2 = NULL;
  uint8_t* d_buffer = NULL;
  uint8_t* d_line = NULL;

  RowFunc row_func = NULL;

  // Init
  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;
  if (setjmp(jerr.setjmp_buffer)) { LOGE(MSG("%s"), emsg); goto end; }
  jpeg_create_decompress(&cinfo);
  jpeg_custom_src(&cinfo, &custom_read, stream);
  jpeg_read_header(&cinfo, TRUE);

  // Set clip info
  if (!clip) {
    // Decode full image
    x = 0; y = 0; width = cinfo.image_width; height = cinfo.image_height;
  }

  // Set out color space
  // Set config to IMAGE_CONFIG_RGB_565 as default
  if (config == IMAGE_CONFIG_AUTO) {
    config = IMAGE_CONFIG_RGB_565;
  }
  if (config == IMAGE_CONFIG_RGBA_8888) {
    cinfo.out_color_space = JCS_EXT_RGBA;
    components = 4;
    row_func = &RGBA8888_to_RGBA8888_row;
  } else if (config == IMAGE_CONFIG_RGB_565) {
    config = IMAGE_CONFIG_RGB_565;
    cinfo.out_color_space = JCS_RGB565;
    // Disable rgb565 dithering, it make the color really different
    cinfo.dither_mode = JDITHER_NONE;
    components = 2;
    row_func = &RGB565_to_RGB565_row;
  } else {
    LOGE("Invalid config: %d", config);
    goto end;
  }

  // Fix width and height
  width = floor_uint32_t(width, ratio);
  height = floor_uint32_t(height, ratio);
  d_width = width / ratio;
  d_height = height / ratio;
  too_small = d_width == 0 || d_height == 0;

  // Create buffer
  d_buffer = container->create_buffer(container, MAX(d_width, 1), MAX(d_height, 1), config);
  if (d_buffer == NULL) { goto end; }

  // Check image ratio too large
  if (too_small) {
    // Ratio is too large, no need to decode.
    // Still treat it as success.
    LOGE("Ratio is too large!");
    result = true;
    goto end;
  }

  // Assign read info

#define TEST_RATIO(test_ratio)        \
  if (ratio % test_ratio == 0) {      \
    soft_scale = ratio / test_ratio;  \
    cinfo.scale_num = 1;              \
    cinfo.scale_denom = test_ratio;   \
    r_x = ur_x = x / test_ratio;      \
    r_y = y / test_ratio;             \
    r_width = width / test_ratio;     \
    r_height = height / test_ratio;   \
  }

  TEST_RATIO(8)
  else TEST_RATIO(4)
  else TEST_RATIO(2)
  else {
    soft_scale = ratio;
    r_x = ur_x = x, r_y = y, r_width = width, r_height = height;
  }

#undef TEST_RATIO

  // Start decompress
  jpeg_start_decompress(&cinfo);
  jpeg_crop_scanline(&cinfo, &r_x, &r_width);
  jpeg_skip_scanlines(&cinfo, r_y);

  r_stride = r_width * components;
  r_start_stride = (ur_x - r_x) * components;
  d_stride = d_width * components;

  if (soft_scale == 1) {
    r_line_1 = malloc(r_stride);
    if (r_line_1 == NULL) { WTF_OOM; goto end; }

    // Read lines
    d_line = d_buffer;
    for (i = 0; i < r_height; ++i) {
      jpeg_read_scanlines(&cinfo, &r_line_1, 1);
      // No soft scale need, set ratio as 1
      row_func(d_line, r_line_1 + r_start_stride, NULL, d_width, 1);
      d_line += d_stride;
    }
  } else {
    r_line_1 = malloc(r_stride);
    r_line_2 = malloc(r_stride);
    if (r_line_1 == NULL || r_line_2 == NULL) { WTF_OOM; goto end; }

    // Read lines
    uint32_t temp = soft_scale - 2;
    uint32_t skip_start = temp / 2;
    uint32_t skip_end = temp - skip_start;

    d_line = d_buffer;
    for (i = 0; i < d_height; ++i) {
      jpeg_skip_scanlines(&cinfo, skip_start);

      jpeg_read_scanlines(&cinfo, &r_line_1, 1);
      jpeg_read_scanlines(&cinfo, &r_line_2, 1);
      row_func(d_line, r_line_1 + r_start_stride,
          r_line_2 + r_start_stride, d_width, soft_scale);

      jpeg_skip_scanlines(&cinfo, skip_end);

      d_line += d_stride;
    }
  }

  // It's not necessary to call jpeg_finish_decompress().
  // Also the jpeg might not be totally decoded.
  // Skip it will increase decode speed.

  // Done
  result = true;

end:
  free(r_line_1);
  free(r_line_2);
  if (d_buffer != NULL) {
    container->release_buffer(container, d_buffer);
  }
  jpeg_destroy_decompress(&cinfo);

  return result;
}


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef IMAGE_IMAGE_JPEG_H
#define IMAGE_IMAGE_JPEG_H

#include <stdio.h>

#include "jpeglib.h"
#include "image_library.h"
#include "static_image.h"
#include "stream.h"

#define IMAGE_JPEG_DECODER_DESCRIPTION ("libjpeg-turbo " MAKESTRING(STRINGIZE, LIBJPEG_TURBO_VERSION))

#define IMAGE_JPEG_MAGIC_NUMBER_0 0xFF
#define IMAGE_JPEG_MAGIC_NUMBER_1 0xD8

bool jpeg_init(ImageLibrary* library);

bool jpeg_is_magic(Stream* stream);

const char* jpeg_get_description();

StaticImage* jpeg_decode(Stream* stream, bool unused1, bool* animated);

bool jpeg_decode_info(Stream* stream, ImageInfo* info);

bool jpeg_decode_buffer(Stream* stream, bool clip, uint32_t x, uint32_t y, uint32_t width,
    uint32_t height, int32_t config, uint32_t ratio, BufferContainer* container);

#endif // IMAGE_IMAGE_JPEG_H


code:
/* Version ID for the JPEG library.
 * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60".
 */
#define JPEG_LIB_VERSION  62

/* libjpeg-turbo version */
#define LIBJPEG_TURBO_VERSION  2.0.0

/* libjpeg-turbo version in integer form */
#define LIBJPEG_TURBO_VERSION_NUMBER  2000000

/* Support arithmetic encoding */
#define C_ARITH_CODING_SUPPORTED

/* Support arithmetic decoding */
#define D_ARITH_CODING_SUPPORTED

/* Support in-memory source/destination managers */
#define MEM_SRCDST_SUPPORTED

/* Use accelerated SIMD routines. */
#define WITH_SIMD

/*
 * Define BITS_IN_JSAMPLE as either
 *   8   for 8-bit sample values (the usual setting)
 *   12  for 12-bit sample values
 * Only 8 and 12 are legal data precisions for lossy JPEG according to the
 * JPEG standard, and the IJG code does not support anything else!
 * We do not support run-time selection of data precision, sorry.
 */

#define BITS_IN_JSAMPLE  8      /* use 8 or 12 */

/* Define to 1 if you have the <locale.h> header file. */
#define HAVE_LOCALE_H

/* Define to 1 if you have the <stddef.h> header file. */
#define HAVE_STDDEF_H

/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H

/* Define if you need to include <sys/types.h> to get size_t. */
#define NEED_SYS_TYPES_H

/* Define if you have BSD-like bzero and bcopy in <strings.h> rather than
   memset/memcpy in <string.h>. */
/* #undef NEED_BSD_STRINGS */

/* Define to 1 if the system has the type `unsigned char'. */
#define HAVE_UNSIGNED_CHAR

/* Define to 1 if the system has the type `unsigned short'. */
#define HAVE_UNSIGNED_SHORT

/* Compiler does not support pointers to undefined structures. */
/* #undef INCOMPLETE_TYPES_BROKEN */

/* Define if your (broken) compiler shifts signed values as if they were
   unsigned. */
/* #undef RIGHT_SHIFT_IS_UNSIGNED */

/* Define to 1 if type `char' is unsigned and you are not using gcc.  */
#ifndef __CHAR_UNSIGNED__
/* #undef __CHAR_UNSIGNED__ */
#endif

/* Define to empty if `const' does not conform to ANSI C. */
/* #undef const */

/* Define to `unsigned int' if <sys/types.h> does not define. */
/* #undef size_t */


code:
/* libjpeg-turbo build number */
#define BUILD  "20180730"

/* Compiler's inline keyword */
#undef inline

/* How to obtain function inlining. */
#define INLINE  __inline__ __attribute__((always_inline))

/* Define to the full name of this package. */
#define PACKAGE_NAME  "libjpeg-turbo"

/* Version number of package */
#define VERSION  "2.0.0"

/* The size of `size_t', as computed by sizeof. */
#define SIZEOF_SIZE_T  __SIZEOF_SIZE_T__

/* Define if your compiler has __builtin_ctzl() and sizeof(unsigned long) == sizeof(size_t). */
#define HAVE_BUILTIN_CTZL

/* Define to 1 if you have the <intrin.h> header file. */
/* #undef HAVE_INTRIN_H */

#if defined(_MSC_VER) && defined(HAVE_INTRIN_H)
#if (SIZEOF_SIZE_T == 8)
#define HAVE_BITSCANFORWARD64
#elif (SIZEOF_SIZE_T == 4)
#define HAVE_BITSCANFORWARD
#endif
#endif


code:
/*
 * Copyright 2015 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef LOG_H
#define LOG_H

#include <android/log.h>

#include "utils.h"

#define TAG "Image"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG ,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG ,__VA_ARGS__)

#define FILE_LINE MAKESTRING(STRINGIZE, __LINE__ )
#define MSG(msg) __FILE__ "(" FILE_LINE "): "msg

#define OUT_OF_MEMORY "Out of memory"

#define WTF_OOM LOGE(MSG(OUT_OF_MEMORY))

#endif //LOG_H


code:
# Copyright 2018 Hippo Seven
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.4.1)
project(image-png C)

set(LIBPNG_SOURCES
    image_png.c
    libpng/png.c
    libpng/pngerror.c
    libpng/pngget.c
    libpng/pngmem.c
    libpng/pngpread.c
    libpng/pngread.c
    libpng/pngrio.c
    libpng/pngrtran.c
    libpng/pngrutil.c
    libpng/pngset.c
    libpng/pngtrans.c
)
set(LIBPNG_INCLUDES
    ${IMAGE_EXPORT_INCLUDES}
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/libpng
)
set(LIBPNG_DEFINITIONS
)
set(LIBPNG_LIBRARIES
    z
)

if (${ANDROID_ABI} STREQUAL "armeabi-v7a")

  enable_language(ASM)

  set(LIBPNG_SOURCES
      ${LIBPNG_SOURCES}
      libpng/arm/arm_init.c
      libpng/arm/filter_neon.S
  )
  set(LIBPNG_INCLUDES
      ${LIBPNG_INCLUDES}
      ${ANDROID_NDK}/sources/android/cpufeatures
  )
  set(LIBPNG_DEFINITIONS
      ${LIBPNG_DEFINITIONS}
      -DPNG_ARM_NEON_API_SUPPORTED
      -DPNG_ARM_NEON_CHECK_SUPPORTED
      -DPNG_ARM_NEON_OPT=1
      -DPNG_ARM_NEON_IMPLEMENTATION=2
      -DPNG_ARM_NEON_FILE=\"contrib/arm-neon/android-ndk.c\"
  )
  set(LIBPNG_LIBRARIES
      ${LIBPNG_LIBRARIES}
      cpufeatures
  )

elseif (${ANDROID_ABI} STREQUAL "arm64-v8a")

  enable_language(ASM)

  set(LIBPNG_SOURCES
      ${LIBPNG_SOURCES}
      libpng/arm/arm_init.c
      libpng/arm/filter_neon_intrinsics.c
      libpng/arm/palette_neon_intrinsics.c
  )
  set(LIBPNG_INCLUDES
      ${LIBPNG_INCLUDES}
      ${ANDROID_NDK}/sources/android/cpufeatures
  )
  set(LIBPNG_DEFINITIONS
      ${LIBPNG_DEFINITIONS}
      -DPNG_ARM_NEON_API_SUPPORTED
      -DPNG_ARM_NEON_CHECK_SUPPORTED
      -DPNG_ARM_NEON_OPT=1
      -DPNG_ARM_NEON_IMPLEMENTATION=1
      -DPNG_ARM_NEON_FILE=\"contrib/arm-neon/android-ndk.c\"
  )
  set(LIBPNG_LIBRARIES
      ${LIBPNG_LIBRARIES}
      cpufeatures
  )

endif ()

if(IMAGE_SINGLE_SHARED_LIB)
    # Compile a static library for image-singlelib
    set(LIBPNG_LIBRARY_NAME image-png-static)
    set(LIBPNG_LIBRARY_TYPE STATIC)
    set(LIBPNG_INCLUDES_VISIBILITY PUBLIC) # Share include dir with image project
else()
    # Compile a shared library to be imported by image-core
    set(LIBPNG_LIBRARY_NAME image-png)
    set(LIBPNG_LIBRARY_TYPE SHARED)
    set(LIBPNG_INCLUDES_VISIBILITY PRIVATE)

    # Link against image shared lib to get helper methods
    set(LIBPNG_LIBRARIES
        ${LIBPNG_LIBRARIES}
        image
        log
    )
endif()

add_library(${LIBPNG_LIBRARY_NAME} ${LIBPNG_LIBRARY_TYPE} ${LIBPNG_SOURCES})
target_include_directories(${LIBPNG_LIBRARY_NAME} ${LIBPNG_INCLUDES_VISIBILITY} ${LIBPNG_INCLUDES})
target_compile_definitions(${LIBPNG_LIBRARY_NAME} PRIVATE ${LIBPNG_DEFINITIONS})
target_link_libraries(${LIBPNG_LIBRARY_NAME} PRIVATE ${LIBPNG_LIBRARIES})


code:
/*
 * Copyright 2015 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>

#include "image.h"
#include "image_png.h"
#include "image_utils.h"
#include "image_decoder.h"
#include "image_convert.h"
#include "animated_image.h"
#include "../log.h"

#define IMAGE_PNG_PREPARE_NONE 0x00
#define IMAGE_PNG_PREPARE_BACKGROUND 0x01
#define IMAGE_PNG_PREPARE_USE_BACKUP 0x02

typedef struct {
  uint32_t width;
  uint32_t height;
  uint32_t offset_x;
  uint32_t offset_y;
  uint16_t delay_num;
  uint16_t delay_den;
  uint32_t delay; // ms
  uint8_t dop;
  uint8_t bop;
  uint8_t pop;
  uint8_t* buffer;
} PngFrame;

typedef struct {
  PngFrame* frames;
  uint32_t frame_count;
  png_structp png_ptr;
  png_infop info_ptr;
  Stream* stream;
} PngData;

LIBRARY_EXPORT
bool png_init(ImageLibrary* library) {
  library->loaded = true;
  library->is_magic = png_is_magic;
  library->decode = png_decode;
  library->decode_info = png_decode_info;
  library->decode_buffer = png_decode_buffer;
  library->create = NULL;
  library->get_description = png_get_description;

  return true;
}

bool png_is_magic(Stream* stream) {
  uint8_t magic[2];

  size_t read = stream->peek(stream, magic, sizeof(magic));
  if (read != sizeof(magic)) {
    LOGE(MSG("Could not read %zu bytes from stream, only read %zu"), sizeof(magic), read);
    return false;
  }

  return magic[0] == IMAGE_PNG_MAGIC_NUMBER_0 && magic[1] == IMAGE_PNG_MAGIC_NUMBER_1;
}

const char* png_get_description() {
  return IMAGE_PNG_DECODER_DESCRIPTION;
}

/**
 * Skip some rows
 */
static void png_skip_rows(png_structrp png_ptr, png_uint_32 num_rows) {
  for (png_uint_32 i = 0; i < num_rows; ++i) {
    png_read_row(png_ptr, NULL, NULL);
  }
}

/**
 * Skip the whole image
 */
static void png_skip_image(png_structrp png_ptr, png_infop info_ptr) {
  png_uint_32 image_height;
  int pass, i, j;

  image_height = png_get_image_height(png_ptr, info_ptr);
  pass = png_set_interlace_handling(png_ptr);

  for (j = 0; j < pass; j++) {
    for (i = 0; i < image_height; i++) {
      png_read_row(png_ptr, NULL, NULL);
    }
  }
}

static void user_read_fn(png_structp png_ptr,
    png_bytep data, png_size_t length) {
  Stream* stream = png_get_io_ptr(png_ptr);
  stream->read(stream, data, length);
}

static void user_error_fn(__unused png_structp png_ptr,
    png_const_charp error_msg) {
  LOGE(MSG("%s"), error_msg);
}

static void user_warn_fn(__unused png_structp png_ptr,
    png_const_charp error_msg) {
  LOGW(MSG("%s"), error_msg);
}

static void free_frames(PngFrame** frames, size_t count) {
  PngFrame* frame_info;
  size_t i;

  if (frames == NULL || *frames == NULL) {
    return;
  }

  for (i = 0; i < count; i++) {
    frame_info = *frames + i;
    free(frame_info->buffer);
    frame_info->buffer = NULL;
  }
  free(*frames);
  *frames = NULL;
}

static inline void blend_over(uint8_t* dp, const uint8_t* sp, size_t len) {
  uint32_t i;
  uint32_t u, v, al;

  for (i = 0; i < len; i += 4, sp += 4, dp += 4) {
    if (sp[3] == 255) {
      memcpy(dp, sp, 4);
    } else if (sp[3] != 0) {
      if (dp[3] != 0) {
        u = sp[3] * 255u;
        v = (255u - sp[3]) * dp[3];
        al = u + v;
        dp[0] = (uint8_t) ((sp[0] * u + dp[0] * v) / al);
        dp[1] = (uint8_t) ((sp[1] * u + dp[1] * v) / al);
        dp[2] = (uint8_t) ((sp[2] * u + dp[2] * v) / al);
        dp[3] = (uint8_t) (al / 255u);
      } else {
        memcpy(dp, sp, 4);
      }
    }
  }
}

static void blend(uint8_t* dst, uint32_t dst_width, uint32_t dst_height,
    uint8_t* src, uint32_t src_width, uint32_t src_height,
    uint32_t offset_x, uint32_t offset_y, bool blend_op_over) {
  uint32_t i;
  uint8_t* src_ptr;
  uint8_t* dst_ptr;
  size_t len;
  uint32_t copy_width = MIN(dst_width - offset_x, src_width);
  uint32_t copy_height = MIN(dst_height - offset_y, src_height);

  for (i = 0; i < copy_height; i++) {
    src_ptr = src + (i * src_width * 4);
    dst_ptr = dst + (((offset_y + i) * dst_width + offset_x) * 4);
    len = (size_t) (copy_width * 4);

    if (blend_op_over) {
      blend_over(dst_ptr, src_ptr, len);
    } else {
      memcpy(dst_ptr, src_ptr, len);
    }
  }
}

// Read pixels
static void read_image(png_structp png_ptr, uint8_t* buffer, uint32_t width, uint32_t height) {
  uint32_t i;
  uint8_t** image = (png_bytepp) malloc(height * sizeof(png_bytep));
  if (image == NULL) {
    png_error(png_ptr, OUT_OF_MEMORY);
  }

  for (i = 0; i < height; i++) {
    *(image + i) = buffer + (width * i * 4);
  }

  png_read_image(png_ptr, image);

  free(image);
}

// Read frame info and frame image pixel.
// Pixels buffer will be malloc.
static void read_frame(png_structp png_ptr, png_infop info_ptr, PngFrame* frame, PngFrame* pre_frame) {
  int pre_dop;

  png_read_frame_head(png_ptr, info_ptr);
  png_get_next_frame_fcTL(png_ptr, info_ptr, &frame->width, &frame->height,
      &frame->offset_x, &frame->offset_y, &frame->delay_num, &frame->delay_den,
      &frame->dop, &frame->bop);

  // If hide first frame and only one frame,
  // no fcTL chunk, so width and height will be zero.
  if (frame->width == 0 || frame->height == 0) {
    frame->width = png_get_image_width(png_ptr, info_ptr);
    frame->height = png_get_image_height(png_ptr, info_ptr);
    frame->offset_x = 0;
    frame->offset_y = 0;
    frame->delay_num = 0;
    frame->delay_den = 1000;
    frame->dop = PNG_DISPOSE_OP_NONE;
    frame->bop = PNG_BLEND_OP_SOURCE;
  }

  // Set delay
  if (frame->delay_den != 0) {
    frame->delay = 1000u * frame->delay_num / frame->delay_den;
  } else {
    frame->delay = 0;
  }

  // Set pop
  pre_dop = pre_frame != NULL ? pre_frame->dop : PNG_DISPOSE_OP_BACKGROUND;
  switch (pre_dop) {
    case PNG_DISPOSE_OP_NONE:
      frame->pop = IMAGE_PNG_PREPARE_NONE;
      break;
    default:
    case PNG_DISPOSE_OP_BACKGROUND:
      frame->pop = IMAGE_PNG_PREPARE_BACKGROUND;
      break;
    case PNG_DISPOSE_OP_PREVIOUS:
      frame->pop = IMAGE_PNG_PREPARE_USE_BACKUP;
      break;
  }

  // Malloc
  frame->buffer = (png_bytep) malloc(4 * frame->width * frame->height * sizeof(png_byte));
  if (frame->buffer == NULL) {
    png_error(png_ptr, OUT_OF_MEMORY);
  }

  // Read pixels
  read_image(png_ptr, frame->buffer, frame->width, frame->height);
}

static Stream* get_stream(AnimatedImage* image) {
  return ((PngData*) image->data)->stream;
}

static void complete(AnimatedImage* image) {
  PngData* data = image->data;
  PngFrame* frame;
  uint32_t i = 1;
  uint32_t j;
  if (image->completed || data->png_ptr == NULL ||
      data->info_ptr == NULL || data->stream == NULL) {
    return;
  }

  // Set new jmp
  if (setjmp(png_jmpbuf(data->png_ptr))) {
    // i is decoded frame count now.
    for (j = i; j < data->frame_count; j++) {
      frame = data->frames + j;
      if (frame->buffer == NULL) {
        break;
      } else {
        free(frame->buffer);
        frame->buffer = NULL;
      }
    }
    data->frame_count = i;
    png_destroy_read_struct(&data->png_ptr, &data->info_ptr, NULL);
    return;
  }

  for (i = 1; i < data->frame_count; i++) {
    read_frame(data->png_ptr, data->info_ptr, data->frames + i, data->frames + i - 1);
  }

  // End read
  png_read_end(data->png_ptr, data->info_ptr);
  png_destroy_read_struct(&data->png_ptr, &data->info_ptr, NULL);

  // Close stream
  data->stream->close(&data->stream);

  // Clean up
  data->png_ptr = NULL;
  data->info_ptr = NULL;
  data->stream = NULL;

  // Completed
  image->completed = true;
}

static uint32_t get_frame_count(AnimatedImage* image) {
  return ((PngData*)image->data)->frame_count;
}

static uint32_t get_delay(AnimatedImage* image, uint32_t frame) {
  PngData* data = image->data;
  if (frame >= data->frame_count) {
    LOGE(MSG("Frame count is %ud, can't get delay of index %ud"), data->frame_count, frame);
    return 0;
  }
  return (data->frames + frame)->delay;
}

static uint32_t get_byte_count(AnimatedImage* image) {
  uint32_t size = 0;
  uint32_t i;
  PngFrame* frame;
  PngData* data = image->data;
  if (image->completed) {
    for (i = 0; i < data->frame_count; i++) {
      frame = data->frames + i;
      size += frame->width * frame->height * 4;
    }
    return size;
  } else {
    LOGE(MSG("Can't call get_byte_count on Uncompleted AnimatedImage"));
    return 0;
  }
}

static void advance(AnimatedImage* image, DelegateImage* dImage) {
  PngData* data = image->data;
  int32_t target_index = dImage->index + 1;
  uint32_t width = image->width;
  uint32_t height = image->height;
  PngFrame* frame;

  if (target_index < 0 || target_index >= data->frame_count) {
    target_index = 0;
  }
  if (target_index == dImage->index) {
    return;
  }

  frame = data->frames + target_index;

  if (frame->dop == PNG_DISPOSE_OP_PREVIOUS && frame->pop == IMAGE_PNG_PREPARE_USE_BACKUP) {
    delegate_image_switch_data_backup(dImage);
  } else {
    // Backup
    if (frame->dop == PNG_DISPOSE_OP_PREVIOUS) {
      delegate_image_backup(dImage);
    }

    // Prepare
    switch (frame->pop) {
      case IMAGE_PNG_PREPARE_NONE:
        // Do nothing
        break;
      default:
      case IMAGE_PNG_PREPARE_BACKGROUND:
        // Set transparent
        memset(dImage->buffer, '\0', width * height * 4);
        break;
      case IMAGE_PNG_PREPARE_USE_BACKUP:
        delegate_image_restore(dImage);
        break;
    }
  }

  blend(dImage->buffer, dImage->width, dImage->height,
      frame->buffer, frame->width, frame->height, frame->offset_x, frame->offset_y,
      frame->bop == PNG_BLEND_OP_OVER);

  delegate_image_apply(dImage);

  dImage->index = target_index;
}

static void recycle(AnimatedImage** image) {
  PngData* data;

  if (image == NULL || *image == NULL) {
    return;
  }

  data = (PngData*) (*image)->data;

  free_frames(&data->frames, data->frame_count);

  if (data->png_ptr != NULL || data->info_ptr != NULL) {
    png_destroy_read_struct(&data->png_ptr, &data->info_ptr, NULL);
  }
  data->png_ptr = NULL;
  data->info_ptr = NULL;

  if (data->stream != NULL) {
    data->stream->close(&data->stream);
    data->stream = NULL;
  }

  free(data);
  (*image)->data = NULL;

  free(*image);
  *image = NULL;
}

void* png_decode(Stream* stream, bool partially, bool* animated) {
  StaticImage* static_image = NULL;
  AnimatedImage* animated_image = NULL;
  PngData* png_data = NULL;
  PngFrame* frames = NULL;
  png_structp png_ptr = NULL;
  png_infop info_ptr = NULL;
  bool apng;
  uint32_t width;
  uint32_t height;
  uint8_t color_type;
  uint8_t bit_depth;
  uint32_t frame_count = 1;
  bool hide_first_frame = false;
  bool opaque;
  int i;

  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, &user_error_fn, &user_warn_fn);
  if (png_ptr == NULL) {
    WTF_OOM;
    return NULL;
  }

  info_ptr = png_create_info_struct(png_ptr);
  if (info_ptr == NULL) {
    WTF_OOM;
    png_destroy_read_struct(&png_ptr, NULL, NULL);
    return NULL;
  }

  if (setjmp(png_jmpbuf(png_ptr))) {
    static_image_delete(&static_image);
    free(animated_image);
    free(png_data);
    free_frames(&frames, frame_count);
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    return NULL;
  }

  // Init
  png_set_read_fn(png_ptr, stream, &user_read_fn);
  png_read_info(png_ptr, info_ptr);

  // Get info
  apng = (bool) png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL);
  width = png_get_image_width(png_ptr, info_ptr);
  height = png_get_image_height(png_ptr, info_ptr);
  color_type = png_get_color_type(png_ptr, info_ptr);
  bit_depth = png_get_bit_depth(png_ptr, info_ptr);
  if (apng) {
    frame_count = png_get_num_frames(png_ptr, info_ptr);
    // Don't hide first frame for only-one-frame png
    if (frame_count > 1) {
      hide_first_frame = png_get_first_frame_is_hidden(png_ptr, info_ptr);
      if (hide_first_frame) {
        frame_count -= 1;
      }
    }
  }
  // If only one frame, decode all
  if (frame_count == 1) {
    partially = false;
  }
  // Check info invalid
  if (width == 0 || height == 0 || frame_count == 0) {
    png_error(png_ptr, "Invalid png info");
    return NULL;
  }

  // Configure to ARGB
  png_set_expand(png_ptr);
  if (bit_depth == 16) {
    png_set_scale_16(png_ptr);
  }
  if (color_type == PNG_COLOR_TYPE_GRAY ||
      color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
    png_set_gray_to_rgb(png_ptr);
  }
  if (color_type & PNG_COLOR_MASK_ALPHA) {
    opaque = false;
  } else {
    opaque = true;
    png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);
  }

  if (apng) {
    // Skip first frame if necessary
    if (hide_first_frame && frame_count > 0) {
      png_skip_image(png_ptr, info_ptr);
    }

    // Malloc frames
    frames = (PngFrame*) malloc(frame_count * sizeof(PngFrame));
    if (frames == NULL) {
      png_error(png_ptr, OUT_OF_MEMORY);
      return NULL;
    }
    // Set frame buffer NULL for safety
    for (i = 0; i < frame_count; i++) {
      (frames + i)->buffer = NULL;
    }

    // Read first frame
    read_frame(png_ptr, info_ptr, frames, NULL);
    // Fix first frame dop
    if (frames->dop == PNG_DISPOSE_OP_PREVIOUS) {
      frames->dop = PNG_DISPOSE_OP_BACKGROUND;
    }

    // Read next frame
    if (!partially) {
      for (i = 1; i < frame_count; i++) {
        read_frame(png_ptr, info_ptr, frames + i, frames + i - 1);
      }

      // End read
      png_read_end(png_ptr, info_ptr);
      png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    }

    if (frame_count == 1) {
      // For one-frame apng, use StaticImage
      static_image = static_image_new(width, height);
      if (static_image == NULL) {
        png_error(png_ptr, OUT_OF_MEMORY);
        return NULL;
      }
      memset(static_image->buffer, '\0', width * height * 4);
      blend(static_image->buffer, width, height,
          frames->buffer, frames->width, frames->height,
          frames->offset_x, frames->offset_y, false);

      // Free frames, don't need it anymore
      free_frames(&frames, 1);

      // Set final result
      static_image->format = IMAGE_FORMAT_PNG;
      static_image->opaque = opaque;
      *animated = false;
      return static_image;
    } else {
      // For multi-frame apng, use AnimatedImage
      animated_image = (AnimatedImage*) malloc(sizeof(AnimatedImage));
      png_data = (PngData*) malloc(sizeof(PngData));
      if (animated_image == NULL || png_data == NULL) {
        png_error(png_ptr, OUT_OF_MEMORY);
        return NULL;
      }
      animated_image->width = width;
      animated_image->height = height;
      animated_image->format = IMAGE_FORMAT_PNG;
      animated_image->opaque = opaque;
      animated_image->completed = !partially;
      animated_image->data = png_data;

      animated_image->get_stream = &get_stream;
      animated_image->complete = &complete;
      animated_image->get_frame_count = &get_frame_count;
      animated_image->get_delay = &get_delay;
      animated_image->get_byte_count = &get_byte_count;
      animated_image->advance = &advance;
      animated_image->recycle = &recycle;

      png_data->frames = frames;
      png_data->frame_count = frame_count;
      if (partially) {
        png_data->png_ptr = png_ptr;
        png_data->info_ptr = info_ptr;
        png_data->stream = stream;
      } else {
        png_data->png_ptr = NULL;
        png_data->info_ptr = NULL;
        png_data->stream = NULL;
      }

      return animated_image;
    }
  } else {
    // For png, use StaticImage
    static_image = static_image_new(width, height);
    if (static_image == NULL) {
      png_error(png_ptr, OUT_OF_MEMORY);
      return NULL;
    }

    // Read pixel
    read_image(png_ptr, static_image->buffer, width, height);

    // End read
    png_read_end(png_ptr, info_ptr);
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

    // Set final result
    static_image->format = IMAGE_FORMAT_PNG;
    static_image->opaque = opaque;
    *animated = false;
    return static_image;
  }
}

bool png_decode_info(Stream* stream, ImageInfo* info) {
  png_structp png_ptr = NULL;
  png_infop info_ptr = NULL;

  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, &user_error_fn, &user_warn_fn);
  if (png_ptr == NULL) {
    WTF_OOM;
    return false;
  }

  info_ptr = png_create_info_struct(png_ptr);
  if (info_ptr == NULL) {
    WTF_OOM;
    png_destroy_read_struct(&png_ptr, NULL, NULL);
    return false;
  }

  if (setjmp(png_jmpbuf(png_ptr))) {
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    return false;
  }

  // Init
  png_set_read_fn(png_ptr, stream, &user_read_fn);
  png_read_info(png_ptr, info_ptr);

  // Assign
  info->width = png_get_image_width(png_ptr, info_ptr);
  info->height = png_get_image_height(png_ptr, info_ptr);
  info->format = IMAGE_FORMAT_PNG;
  info->opaque = !(png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA);
  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
    // APNG
    info->frame_count = png_get_num_frames(png_ptr, info_ptr);
    if (info->frame_count > 1 && png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
      --info->frame_count;
    }
  } else {
    info->frame_count = 1;
  }

  // End read
  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

  return true;
}

bool png_decode_buffer(Stream* stream, bool clip, uint32_t x, uint32_t y, uint32_t width,
    uint32_t height, int32_t config, uint32_t ratio, BufferContainer* container) {
  png_structp png_ptr = NULL;
  png_infop info_ptr = NULL;
  uint32_t i;
  bool result = false;

  uint32_t i_width;
  uint32_t i_height;
  uint8_t  i_color_type;
  uint8_t  i_bit_depth;
  bool     i_opaque;
  const uint32_t i_components = 4; // Always rgba8888

  uint32_t d_width;
  uint32_t d_height;
  uint32_t d_stride;
  bool     d_too_small;
  uint32_t d_components;

  int32_t  pass;

  uint32_t r_stride;
  uint32_t r_start_stride;
  uint8_t* r_buffer = NULL;
  // Don't free it
  uint8_t* r_line   = NULL;
  uint8_t* r_line_1 = NULL;
  uint8_t* r_line_2 = NULL;
  uint8_t* d_buffer = NULL;
  uint8_t* d_line = NULL;

  RowFunc row_func = NULL;

  // Prepare
  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, &user_error_fn, &user_warn_fn);
  if (png_ptr == NULL) { WTF_OOM; goto end; }
  info_ptr = png_create_info_struct(png_ptr);
  if (info_ptr == NULL) { goto end; }
  if (setjmp(png_jmpbuf(png_ptr))) { goto end; }

  // Init
  png_set_read_fn(png_ptr, stream, &user_read_fn);
  png_read_info(png_ptr, info_ptr);

  // Get png info
  i_width = png_get_image_width(png_ptr, info_ptr);
  i_height = png_get_image_height(png_ptr, info_ptr);
  i_color_type = png_get_color_type(png_ptr, info_ptr);
  i_bit_depth = png_get_bit_depth(png_ptr, info_ptr);

  // Set clip info
  if (!clip) {
    // Decode full image
    x = 0; y = 0; width = i_width; height = i_height;
  }

  // Configure output
  png_set_expand(png_ptr);
  if (i_bit_depth == 16) {
    png_set_scale_16(png_ptr);
  }
  if (i_color_type == PNG_COLOR_TYPE_GRAY ||
      i_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
    png_set_gray_to_rgb(png_ptr);
  }
  if (!(i_color_type & PNG_COLOR_MASK_ALPHA)) {
    i_opaque = true;
    png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);
  } else {
    i_opaque = false;
  }
  pass = png_set_interlace_handling(png_ptr);

  // Resolve config
  if (config == IMAGE_CONFIG_AUTO) {
    config = (int32_t) (i_opaque ? IMAGE_CONFIG_RGB_565 : IMAGE_CONFIG_RGBA_8888);
  } else if (!is_explicit_config(config)) {
    LOGE("Invalid config: %d", config);
    goto end;
  }

  // Fix width and height
  width = floor_uint32_t(width, ratio);
  height = floor_uint32_t(height, ratio);
  d_width = width / ratio;
  d_height = height / ratio;
  d_too_small = d_width == 0 || d_height == 0;

  // Create buffer
  d_buffer = container->create_buffer(container, MAX(d_width, 1), MAX(d_height, 1), config);
  if (d_buffer == NULL) { goto end; }

  // Check image ratio too large
  if (d_too_small) {
    // Ratio is too large, no need to decode.
    // Still treat it as success.
    LOGE("Ratio is too large!");
    result = true;
    goto end;
  }

  d_components = get_depth_for_config(config);
  d_stride = d_width * d_components;
  r_start_stride = x * i_components;
  r_stride = i_width * i_components;

  // Row function
  row_func = config == IMAGE_CONFIG_RGBA_8888 ? &RGBA8888_to_RGBA8888_row : &RGBA8888_to_RGB565_row;

  // Read data
  if (pass > 1) {
    // Interlaced PNG, read all rows to r_buffer,
    // then transfer them to d_buffer

    uint32_t remain_y = i_height - y - height;

    if (ratio == 1) {
      r_buffer = malloc(r_stride * height);
      if (r_buffer == NULL) { WTF_OOM; goto end; }

      // Read all rows to r_buffer
      while (--pass >= 0) {
        // Skip start lines
        png_skip_rows(png_ptr, y);
        // Read rows
        r_line = r_buffer;
        for (i = 0; i < height; ++i) {
          png_read_row(png_ptr, r_line, NULL);
          r_line += r_stride;
        }
        // Skip end lines
        png_skip_rows(png_ptr, remain_y);
      }

      // r_buffer to d_buffer
      d_line = d_buffer;
      r_line = r_buffer;
      for (i = 0; i < height; ++i) {
        row_func(d_line, r_line + r_start_stride, NULL, d_width, 1);
        d_line += d_stride;
        r_line += r_stride;
      }
    } else {
      r_buffer = malloc(r_stride * d_height * 2);
      if (r_buffer == NULL) { WTF_OOM; goto end; }

      uint32_t temp = ratio - 2;
      uint32_t skip_start = temp / 2;
      uint32_t skip_end = temp - skip_start;

      // Read all rows to r_buffer
      while (--pass >= 0) {
        // Skip start lines
        png_skip_rows(png_ptr, y);
        // Read rows
        r_line = r_buffer;
        for (i = 0; i < d_height; ++i) {
          png_skip_rows(png_ptr, skip_start);
          png_read_row(png_ptr, r_line, NULL);
          r_line += r_stride;
          png_read_row(png_ptr, r_line, NULL);
          r_line += r_stride;
          png_skip_rows(png_ptr, skip_end);
        }
        // Skip end lines
        png_skip_rows(png_ptr, remain_y);
      }

      // r_buffer to d_buffer
      d_line = d_buffer;
      r_line = r_buffer;
      for (i = 0; i < d_height; ++i) {
        row_func(d_line, r_line + r_start_stride,
            r_line + r_stride + r_start_stride, d_width, ratio);
        d_line += d_stride;
        r_line += r_stride * 2;
      }
    }
  } else if (ratio == 1) {
    r_line_1 = malloc(r_stride);
    if (r_line_1 == NULL) { WTF_OOM; goto end; }

    // Skip start lines
    png_skip_rows(png_ptr, y);

    // Read lines
    d_line = d_buffer;
    for (i = 0; i < d_height; ++i) {
      png_read_row(png_ptr, r_line_1, NULL);
      row_func(d_line, r_line_1 + r_start_stride, NULL, d_width, 1);
      d_line += d_stride;
    }
  } else {
    r_line_1 = malloc(r_stride);
    r_line_2 = malloc(r_stride);
    if (r_line_1 == NULL || r_line_2 == NULL) { WTF_OOM; goto end; }

    // Skip start lines
    png_skip_rows(png_ptr, y);

    // Read lines
    uint32_t temp = ratio - 2;
    uint32_t skip_start = temp / 2;
    uint32_t skip_end = temp - skip_start;

    d_line = d_buffer;
    for (i = 0; i < d_height; ++i) {
      png_skip_rows(png_ptr, skip_start);

      png_read_row(png_ptr, r_line_1, NULL);
      png_read_row(png_ptr, r_line_2, NULL);
      row_func(d_line, r_line_1 + r_start_stride,
          r_line_2 + r_start_stride, d_width, ratio);

      png_skip_rows(png_ptr, skip_end);

      d_line += d_stride;
    }
  }

  // It's not necessary to call png_read_end().
  // Also the png might not be totally decoded.
  // Skip it will increase decode speed.

  // Done
  result = true;

end:
  free(r_line_1);
  free(r_line_2);
  free(r_buffer);
  if (d_buffer != NULL) {
    container->release_buffer(container, d_buffer);
  }
  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
  return result;
}


code:
/*
 * Copyright 2015 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef IMAGE_IMAGE_PNG_H
#define IMAGE_IMAGE_PNG_H

#include <stdbool.h>

#include "png.h"
#include "image_library.h"
#include "stream.h"

#define IMAGE_PNG_DECODER_DESCRIPTION ("libpng " PNG_LIBPNG_VER_STRING)

#define IMAGE_PNG_MAGIC_NUMBER_0 0x89
#define IMAGE_PNG_MAGIC_NUMBER_1 0x50

bool png_init(ImageLibrary* library);

bool png_is_magic(Stream* stream);

const char* png_get_description();

void* png_decode(Stream* stream, bool partially, bool* animated);

bool png_decode_info(Stream* stream, ImageInfo* info);

bool png_decode_buffer(Stream* stream, bool clip, uint32_t x, uint32_t y, uint32_t width,
    uint32_t height, int32_t config, uint32_t ratio, BufferContainer* container);

#endif // IMAGE_IMAGE_PNG_H