| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| /** |
| * @author Oleg V. Khaschansky |
| * |
| */ |
| |
| #include "pngdecoder.h" |
| |
| /* |
| * Class: org_apache_harmony_awt_gl_image_PngDecoder |
| * Method: initIDs |
| * Signature: ()V |
| */ |
| JNIEXPORT void JNICALL Java_org_apache_harmony_awt_gl_image_PngDecoder_initIDs |
| (JNIEnv *env, jclass cls) { |
| img_PNG_imageWidthID = (*env)->GetFieldID(env, cls, "imageWidth", "I"); |
| img_PNG_imageHeightID = (*env)->GetFieldID(env, cls, "imageHeight", "I"); |
| img_PNG_colorTypeID = (*env)->GetFieldID(env, cls, "colorType", "I"); |
| img_PNG_bitDepthID = (*env)->GetFieldID(env, cls, "bitDepth", "I"); |
| img_PNG_cmapID = (*env)->GetFieldID(env, cls, "cmap", "[B"); |
| img_PNG_updateFromScanlineID = (*env)->GetFieldID(env, cls, "updateFromScanline", "I"); |
| img_PNG_numScanlinesID = (*env)->GetFieldID(env, cls, "numScanlines", "I"); |
| img_PNG_intOutID = (*env)->GetFieldID(env, cls, "intOut", "[I"); |
| img_PNG_byteOutID = (*env)->GetFieldID(env, cls, "byteOut", "[B"); |
| |
| img_PNG_returnHeaderID = (*env)->GetMethodID(env, cls, "returnHeader", "()V"); |
| } |
| |
| /* |
| * Class: org_apache_harmony_awt_gl_image_PngDecoder |
| * Method: decode |
| * Signature: ([BIJ)J |
| */ |
| JNIEXPORT jlong JNICALL Java_org_apache_harmony_awt_gl_image_PngDecoder_decode |
| (JNIEnv *env, jobject obj, jbyteArray jInput, jint bytesInBuffer, jlong hDecoder) { |
| png_decoder_info_ptr decoderInfo = (png_decoder_info_ptr) ((IDATA)hDecoder); |
| |
| // Init decoder if needed |
| if(!decoderInfo) { |
| if(!(decoderInfo = initPng())) { |
| throwNewExceptionByName(env, "java/lang/RuntimeException", |
| "Can't create native PNG decoder"); |
| return 0; // NULL |
| } |
| } |
| |
| if(setjmp(decoderInfo->jmpBuf)) { // Only way to deal with errors in libpng |
| destroyPng(&decoderInfo); |
| return 0; |
| } |
| |
| // Update JNI-related fields |
| decoderInfo->env = env; |
| decoderInfo->obj = obj; |
| |
| // Get java array for image data if it is created (header is passed already) |
| if(decoderInfo->colorType >= 0) { |
| if(decoderInfo->colorType == PNG_COLOR_TYPE_RGB || |
| decoderInfo->colorType == PNG_COLOR_TYPE_RGB_ALPHA) { |
| decoderInfo->jImageData = (*env)->GetObjectField(env, obj, img_PNG_intOutID); |
| } else { |
| decoderInfo->jImageData = (*env)->GetObjectField(env, obj, img_PNG_byteOutID); |
| } |
| decoderInfo->imageData = (*env)->GetPrimitiveArrayCritical(env, decoderInfo->jImageData, 0); |
| } |
| |
| // Obtain input data |
| decoderInfo->jInputData = jInput; |
| decoderInfo->inputBuffer = (*env)->GetPrimitiveArrayCritical(env, jInput, 0); |
| |
| // Now process data |
| processData(decoderInfo, decoderInfo->inputBuffer, bytesInBuffer); |
| |
| releaseArrays(decoderInfo); |
| |
| if(decoderInfo->doneDecoding) |
| destroyPng(&decoderInfo); |
| |
| return (jlong) ((IDATA)decoderInfo); |
| } |
| |
| /* |
| * Class: org_apache_harmony_awt_gl_image_PngDecoder |
| * Method: releaseNativeDecoder |
| * Signature: (J)V |
| */ |
| JNIEXPORT void JNICALL Java_org_apache_harmony_awt_gl_image_PngDecoder_releaseNativeDecoder |
| (JNIEnv *env, jclass cls, jlong hDecoder) { |
| png_decoder_info_ptr decoderInfo = (png_decoder_info_ptr) ((IDATA)hDecoder); |
| } |
| |
| void releaseArrays(png_decoder_info_ptr decoderInfo) { |
| JNIEnv *env = decoderInfo->env; |
| jobject obj = decoderInfo->obj; |
| |
| // Release arrays |
| if(decoderInfo->jInputData && decoderInfo->inputBuffer) { |
| (*env)->ReleasePrimitiveArrayCritical( |
| env, decoderInfo->jInputData, decoderInfo->inputBuffer, 0 |
| ); |
| decoderInfo->inputBuffer = NULL; |
| } |
| |
| if(decoderInfo->jImageData && decoderInfo->imageData) { |
| (*env)->ReleasePrimitiveArrayCritical( |
| env, decoderInfo->jImageData, decoderInfo->imageData, 0 |
| ); |
| decoderInfo->imageData = NULL; |
| } |
| } |
| |
| void gl_error_fn(png_structp png_ptr, png_const_charp error_msg) { |
| png_decoder_info_ptr decoderInfo = png_get_error_ptr(png_ptr); |
| throwNewExceptionByName(decoderInfo->env, "java/lang/RuntimeException", |
| error_msg); |
| |
| if(decoderInfo) { // Else there's no way to terminate correctly |
| longjmp(decoderInfo->jmpBuf, 1); |
| } |
| } |
| |
| void gl_warning_fn(png_structp png_ptr, png_const_charp warning_msg) { |
| } |
| |
| void destroyPng(png_decoder_info_ptr *decoderInfoP) { |
| png_decoder_info_ptr decoderInfo = *decoderInfoP; |
| releaseArrays(decoderInfo); |
| |
| if(decoderInfo) { |
| if(decoderInfo->png_ptr && decoderInfo->info_ptr) |
| png_destroy_read_struct(&decoderInfo->png_ptr, &decoderInfo->info_ptr, NULL); |
| |
| free(decoderInfo); |
| *decoderInfoP = NULL; |
| } |
| } |
| |
| void gl_info_callback(png_structp png_ptr, png_infop info) { |
| JNIEnv *env; |
| jobject obj; |
| boolean hasTRNS = FALSE; |
| |
| // Get pointer to global info |
| png_decoder_info_ptr decoderInfo = (png_decoder_info_ptr) png_get_progressive_ptr(png_ptr); |
| |
| // Read in header information |
| png_get_IHDR( |
| decoderInfo->png_ptr, |
| decoderInfo->info_ptr, |
| &decoderInfo->width, |
| &decoderInfo->height, |
| &decoderInfo->bitDepth, |
| &decoderInfo->colorType, |
| NULL, NULL, NULL |
| ); |
| |
| |
| png_set_interlace_handling(decoderInfo->png_ptr); |
| |
| if(decoderInfo->bitDepth < 8) |
| png_set_packing(decoderInfo->png_ptr); |
| |
| if (decoderInfo->bitDepth == 16) |
| png_set_strip_16(decoderInfo->png_ptr); |
| |
| if (decoderInfo->colorType == PNG_COLOR_TYPE_RGB) |
| png_set_filler(decoderInfo->png_ptr, 0xFF, PNG_FILLER_AFTER); |
| |
| if (decoderInfo->colorType == PNG_COLOR_TYPE_GRAY_ALPHA) |
| png_set_gray_to_rgb(decoderInfo->png_ptr); |
| |
| // Note that if there's tRNS tag, PLTE images will be converted to RGBA by the libpng |
| if (png_get_valid(decoderInfo->png_ptr, decoderInfo->info_ptr, PNG_INFO_tRNS)) { |
| png_set_tRNS_to_alpha(decoderInfo->png_ptr); |
| hasTRNS = TRUE; |
| } |
| /* |
| if(decoderInfo->colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { |
| png_set_expand(decoderInfo->png_ptr); |
| } |
| */ |
| /* |
| if(png_get_bKGD(decoderInfo->png_ptr, decoderInfo->info_ptr, &imageBackground)) { |
| png_set_background( |
| decoderInfo->png_ptr, |
| imageBackground, |
| PNG_BACKGROUND_GAMMA_FILE, |
| 1, 1.0 |
| ); |
| |
| if (decoderInfo->colorType == PNG_COLOR_TYPE_RGBA) // It is converted to RGB, so filler needed |
| png_set_filler(decoderInfo->png_ptr, 0xFF, PNG_FILLER_AFTER); |
| } |
| */ |
| png_read_update_info(decoderInfo->png_ptr, decoderInfo->info_ptr); |
| |
| // Update image parameters after all transformations are specified |
| decoderInfo->rowbytes = png_get_rowbytes(decoderInfo->png_ptr, decoderInfo->info_ptr); |
| decoderInfo->colorType = png_get_color_type(decoderInfo->png_ptr, decoderInfo->info_ptr); |
| |
| // Don't update bit depth since image data aren't changed, only expanded to 8 bpc |
| // If tRNS tag presents data are really expanded so bit depth should be 8 |
| // If bit depth was 16 data are stripped to 8 bpc |
| //decoderInfo->bitDepth = png_get_bit_depth(decoderInfo->png_ptr, decoderInfo->info_ptr); |
| if(decoderInfo->bitDepth == 16 || hasTRNS) |
| decoderInfo->bitDepth = 8; |
| |
| // Set java fields... |
| env = decoderInfo->env; |
| obj = decoderInfo->obj; |
| |
| // images that have width or height > 2^31 aren't supported :) |
| (*env)->SetIntField(env, obj, img_PNG_imageWidthID, decoderInfo->width); |
| (*env)->SetIntField(env, obj, img_PNG_imageHeightID, decoderInfo->height); |
| (*env)->SetIntField(env, obj, img_PNG_bitDepthID, decoderInfo->bitDepth); |
| (*env)->SetIntField(env, obj, img_PNG_colorTypeID, decoderInfo->colorType); |
| |
| // Update palette if needed |
| if(decoderInfo->colorType == PNG_COLOR_TYPE_PALETTE) { |
| int cmapLength; |
| png_colorp cmap; |
| jbyteArray jCmap; |
| unsigned char *cmapData; |
| |
| png_get_PLTE(decoderInfo->png_ptr, decoderInfo->info_ptr, &cmap, &cmapLength); |
| |
| // Copy palette to java array |
| jCmap = (*env)->NewByteArray(env, cmapLength*3); |
| cmapData = (*env)->GetPrimitiveArrayCritical(env, jCmap, 0); |
| memcpy(cmapData, cmap, cmapLength*3); |
| (*env)->ReleasePrimitiveArrayCritical(env, jCmap, cmapData, 0); |
| |
| (*env)->SetObjectField(env, obj, img_PNG_cmapID, jCmap); |
| } |
| |
| (*env)->CallVoidMethod(env, obj, img_PNG_returnHeaderID); |
| if ((*env)->ExceptionCheck(env)) |
| longjmp(decoderInfo->jmpBuf, 1); |
| |
| |
| // Get java array for image data |
| if(decoderInfo->colorType == PNG_COLOR_TYPE_RGB || |
| decoderInfo->colorType == PNG_COLOR_TYPE_RGB_ALPHA) { |
| // Need BGR for java color models |
| png_set_bgr(decoderInfo->png_ptr); |
| decoderInfo->jImageData = (*env)->GetObjectField(env, obj, img_PNG_intOutID); |
| } else { |
| decoderInfo->jImageData = (*env)->GetObjectField(env, obj, img_PNG_byteOutID); |
| } |
| decoderInfo->imageData = (*env)->GetPrimitiveArrayCritical(env, decoderInfo->jImageData, 0); |
| } |
| |
| void gl_end_callback(png_structp png_ptr, png_infop info) { |
| png_decoder_info_ptr decoderInfo = (png_decoder_info_ptr) png_get_progressive_ptr(png_ptr); |
| decoderInfo->doneDecoding = TRUE; |
| } |
| |
| void gl_row_callback( |
| png_structp png_ptr, |
| png_bytep new_row, |
| png_uint_32 row_num, |
| int pass) { |
| // Get pointer to global info |
| png_decoder_info_ptr decoderInfo = (png_decoder_info_ptr) png_get_progressive_ptr(png_ptr); |
| |
| png_progressive_combine_row( |
| png_ptr, |
| decoderInfo->imageData + decoderInfo->rowbytes*row_num, |
| new_row |
| ); |
| |
| if(decoderInfo->updateFromScanline < 0) |
| decoderInfo->updateFromScanline = row_num; |
| decoderInfo->numScanlines++; |
| } |
| |
| png_decoder_info_ptr initPng() { |
| png_decoder_info_ptr decoderInfo = calloc(sizeof(png_decoder_info), 1); |
| if(!decoderInfo) { |
| return NULL; |
| } |
| |
| decoderInfo->png_ptr = png_create_read_struct( |
| PNG_LIBPNG_VER_STRING, |
| decoderInfo, |
| gl_error_fn, |
| gl_warning_fn |
| ); |
| |
| if(!decoderInfo->png_ptr) { |
| return NULL; |
| } |
| |
| decoderInfo->info_ptr = png_create_info_struct(decoderInfo->png_ptr); |
| |
| if (!decoderInfo->info_ptr) { |
| png_destroy_read_struct(&decoderInfo->png_ptr, NULL, NULL); |
| return NULL; |
| } |
| |
| png_set_progressive_read_fn( |
| decoderInfo->png_ptr, |
| decoderInfo, |
| gl_info_callback, |
| gl_row_callback, |
| gl_end_callback |
| ); |
| |
| decoderInfo->colorType = -1; // Will notify us that we have not read header yet... |
| |
| return decoderInfo; |
| } |
| |
| |
| void processData(png_decoder_info_ptr decoderInfo, png_bytep buffer, png_uint_32 length) { |
| JNIEnv *env = decoderInfo->env; |
| jobject obj = decoderInfo->obj; |
| |
| decoderInfo->updateFromScanline = -1; |
| decoderInfo->numScanlines = 0; |
| |
| png_process_data(decoderInfo->png_ptr, decoderInfo->info_ptr, buffer, length); |
| |
| // If at least one full pass was performed we need to send all the data to consumers |
| if(decoderInfo->numScanlines > decoderInfo->height) { |
| decoderInfo->numScanlines = decoderInfo->height; |
| decoderInfo->updateFromScanline = 0; |
| } |
| (*env)->SetIntField(env, obj, img_PNG_updateFromScanlineID, decoderInfo->updateFromScanline); |
| (*env)->SetIntField(env, obj, img_PNG_numScanlinesID, decoderInfo->numScanlines); |
| } |