Strange results while compressing batch of pictures with libjpegturbo

1.3k views Asked by At

First, what I (want to) do: compress and scale down an batch of pictures (jpg). Lets assume the original picture has this 1600w x 1200h dimensions. Now, i want to have one compressed copy of 1600x1200 and another 800x600 and 400x300.

What I use: I'm using the libJpegTurob to achieve this. If the LibJpegTurob has some problem I try to use the android given methods.

Already tried: First, I used the Java Wrapper ported from Tom Gall (https://github.com/jberkel/libjpeg-turbo).

It went pretty fine (on nexus 4) until I start using pictures over 4mb. What basically happened was android throw OutOfMemory exceptions. This happened when I used smaller pictures (~1-2mb) but compressed one after another.

This became even worst after it run this on budget devices with lower memory like nexus s. The problem where caused by the low heap, that's what I think.

Well then, I thought, i have to do it in c. The memory problems seems solved, as long I used pictures smaller then 3mb on an budget device. On an nexus 4 I could even compress an >15mb picture.

This is the src picture.enter image description here

But now... the problem. The first compressed picture looks good enter image description here

but all other ones looks like this enter image description here or this enter image description here

This happened as long as I keep select pictures and compress them.

Now the code.

This is where the scaling and compression happened

    #include "_HelloJNI.h"

#include <errno.h>
#include <jni.h>
#include <sys/time.h>
#include <time.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <android/bitmap.h>
#include <unistd.h>
#include <setjmp.h>
#include "jpeglib.h"
#include "turbojpeg.h"


#define  LOG_TAG    "DEBUG"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGV(...)  __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)


int IMAGE_COMPRESS_QUALITY = 80;

typedef struct  {
    int width;
    int height;
}tSize;


JNIEXPORT jint JNICALL Java_com_example_LibJpegTurboTest_NdkCall_nativeCompress
(JNIEnv * env, jobject onj, jstring jniSrcImgPath, jstring jniDestDir, jstring jniDestImgName, jint jniSrcWidth, jint jniSrcHeight) {

    int pyramidRet = 0;

    tSize fileSize;
    fileSize.width = (int)jniSrcWidth;
    fileSize.height = (int)jniSrcHeight;

    const char* srcImgPath = (*env)->GetStringUTFChars(env, jniSrcImgPath, 0);
    const char* destDir = (*env)->GetStringUTFChars(env, jniDestDir, 0);
    const char* destFileName = (*env)->GetStringUTFChars(env, jniDestImgName, 0);

    pyramidRet = createPreviewPyramidUsingCustomScaling(srcImgPath, destDir, destFileName, fileSize, 4);

    return 0;
}

static tSize imageSizeForStep(int step, tSize *originalSize) {
    float factor = 1 / pow(2, step);

    return (tSize) {
        round(originalSize->width  * factor),
        round(originalSize->height * factor) };
}

int saveBitmapBufferImage(unsigned char *data, tSize *imageSize, char *destFileName, int quality) {

    int retValue = 1;
    int res = 0;
    unsigned long destinationJpegBufferSize = 0;
    tjhandle tjCompressHandle = NULL;
    unsigned char *destinationJpegBuffer = NULL;
    FILE *file = NULL;

    // jpgeg compress
    tjCompressHandle = tjInitCompress();
    if(tjCompressHandle == NULL) {
        retValue = -1;
        goto cleanup;
    } 

    res = tjCompress2(tjCompressHandle, data, imageSize->width, imageSize->width * tjPixelSize[TJPF_RGBX], imageSize->height, TJPF_RGBX, &destinationJpegBuffer, &destinationJpegBufferSize, 1,
    quality, TJFLAG_FASTUPSAMPLE);
    if(res < 0) {
        retValue = -1;
        goto cleanup;
    }

    file = fopen(destFileName, "wb");
    if(file == NULL) {
        retValue = -1;
        goto cleanup;
    } 

    long written = fwrite(destinationJpegBuffer, destinationJpegBufferSize, 1, file);
    retValue = (written == 1);

    cleanup:
    if(tjCompressHandle) {
        tjDestroy(tjCompressHandle);
    }
    if(destinationJpegBuffer) {
        tjFree(destinationJpegBuffer);
    }
    if(file) {
        fclose(file);
    }

    return retValue;
}


int createBitmapBufferFromFile(char *srcFileName, tSize imageDimensions, long *bytesPerRow, long *dataBufferSize, unsigned char **dataBuffer) {
    int retValue = 1;
    int res = 0;

    FILE *file = NULL;

    unsigned char* sourceJpegBuffer = NULL;
    long sourceJpegBufferSize = 0;

    tjhandle tjDecompressHandle = NULL;
    int fileWidth = 0, fileHeight = 0, jpegSubsamp = 0;

    unsigned char* temp = NULL;

    unsigned char* rotatedSourceJpegBuffer = NULL;
    tjhandle tjTransformHandle = NULL;

    file = fopen(srcFileName, "rb");
    if (file == NULL) {
        retValue = -1;
        goto cleanup;
    } 


    res = fseek(file, 0, SEEK_END);
    if(res < 0) {
        retValue = -1;
        goto cleanup;
    } 


    sourceJpegBufferSize = ftell(file);
    if(sourceJpegBufferSize <= 0) {
        retValue = -1;
        goto cleanup;
    } 

    sourceJpegBuffer = tjAlloc(sourceJpegBufferSize);
    if(sourceJpegBuffer == NULL) {
        retValue = -1;
        goto cleanup;
    } 


    res = fseek(file, 0, SEEK_SET);
    if(res < 0) {
        retValue = -1;
        goto cleanup;
    } 


    res = fread(sourceJpegBuffer, (long)sourceJpegBufferSize, 1, file);
    if(res != 1) {      
        retValue = -1;
        goto cleanup;
    } 


    tjDecompressHandle = tjInitDecompress();
    if(tjDecompressHandle == NULL) {        
        retValue = -1;
        goto cleanup;
    } 

    // decompress header to get image dimensions
    res = tjDecompressHeader2(tjDecompressHandle, sourceJpegBuffer, sourceJpegBufferSize, &fileWidth, &fileHeight, &jpegSubsamp);
    if(res < 0) {
        retValue = -1;
        goto cleanup;
    } 

    float destWidth = (float)imageDimensions.width;
    float destHeight = (float)imageDimensions.height;

    *bytesPerRow = destWidth * tjPixelSize[TJPF_RGBX];

    // buffer for uncompressed image-data
    *dataBufferSize = *bytesPerRow * destHeight;

    temp = tjAlloc(*dataBufferSize);
    if(temp == NULL) {  
        retValue = -1;
        goto cleanup;
    } 


    res = tjDecompress2(tjDecompressHandle,
                                 sourceJpegBuffer,
                                 sourceJpegBufferSize,
                                 temp,
                                 destWidth,
                                 *bytesPerRow,
                                 destHeight,
                                 TJPF_RGBX,
                                 TJ_FASTUPSAMPLE);
    if(res < 0) {
        retValue = -1;
        goto cleanup;
    } 

    *dataBuffer = temp;
    temp = NULL;

    cleanup:
    if(file) {
        fclose(file);
    }
    if(sourceJpegBuffer) {
        tjFree(sourceJpegBuffer);
    }
    if(tjDecompressHandle) {
        tjDestroy(tjDecompressHandle);
    }
    if(temp) {      
        tjFree(temp);
    }

    return retValue;
}



int createPreviewPyramidUsingCustomScaling(char* srcImgPath, char* destDir, char* destFileName, tSize orginalImgSize, int maxStep) {
    int retValue = 1;
    int res = 1;
    int success = 0;
    int loopStep = 0;
    tSize previewSize;

    long bytesPerRow;
    long oldBytesPerRow = 0;

    unsigned char* sourceDataBuffer = NULL;
    long sourceDataBufferSize = 0;

    unsigned char* destinationDataBuffer = NULL;
    long destinationDataBufferSize = 0;

    unsigned char* buf1 = NULL;
    unsigned char* buf2 = NULL;
    long workBufSize = 0;

    void* sourceRow = NULL;
    void* targetRow = NULL;

    char* destFilePrefix = "sample_";
    char* fooDestName;
    char* fooStrBuilder;


    tSize orginSizeTmp;
    orginSizeTmp.width = orginalImgSize.width;
    orginSizeTmp.height = orginalImgSize.height;

    previewSize = imageSizeForStep(1, &orginSizeTmp);
    long width = (long)previewSize.width;
    long height = (long)previewSize.height;


    int errorCode = 0;  
    errorCode = createBitmapBufferFromFile(srcImgPath, previewSize, &bytesPerRow, &sourceDataBufferSize, &buf1);
    if(errorCode != 1) {    
        retValue = errorCode;
        goto cleanup;
    } 

    workBufSize = sourceDataBufferSize; 
    buf2 = tjAlloc(workBufSize);
    if(buf2 == NULL) {      
        retValue = -1;
        goto cleanup;
    } else {
        memset(buf2,0,workBufSize); 
    }

    sourceDataBuffer = buf1;

    fooDestName = strcat(destDir, destFilePrefix);
    fooStrBuilder = strcat(fooDestName, "1_");
    fooDestName = strcat(fooStrBuilder, destFileName);    


    success = saveBitmapBufferImage(sourceDataBuffer, &previewSize, fooDestName, IMAGE_COMPRESS_QUALITY);
    if(success <= 0) {
        retValue = -1;
        goto cleanup;
    } 


    cleanup:
    if(sourceDataBuffer) {      
        tjFree(sourceDataBuffer);
    }
    if(destinationDataBuffer) {     
        tjFree(destinationDataBuffer);
    }

    return retValue;
}

The Java part to start the compression..

private void invokeCompress(ArrayList<PictureItem> picturesToCompress) {
    if(picturesToCompress != null && picturesToCompress.size() > 0) {
        for(int i=0; i<picturesToCompress.size(); i++) {
            String srcPicturePath = picturesToCompress.get(i).getSrcImg();
            String destDir = "/storage/emulated/0/1_TEST_FOLDER/";
            String destFileName = getRandomString(4)+".jpg";

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(srcPicturePath, options);

            try {
                ndkCall.compress(srcPicturePath, destDir, destFileName, options.outWidth, options.outHeight);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
}

What i do wrong???

Thanks a lot!!

P.S. Sorry for the bad english!

2

There are 2 answers

0
Thomas Kekeisen On BEST ANSWER

Looks good for me. Have you made sure that the libjpegturbo sources are valid and stable?

0
Dragos Iordache On

Your code seems ok, it is well written and handles errors ok. But from what I see the problem can be either an error in the external lib, you cand test this by unloading(or uninit) and the reloading and reiniting the library and then encoding the next file.

You can also try an older version of the lib to see if it works.

The second problem may be a pointer not set to NULL after free and starting to write bad data to memory. You should use tools like valgrind, or map all your malloc in it's own page, and padding them with readonly memory pages(see: mprotect), then when you write in a bad page the program will segfail and you will see the problem.

I am not an expert in the this lib, but double check the documentation and the example code. Maybe there is something that needs to be called between 2 files or after each one, and the first one working is just pure luck.

Also: try changing the order of the files you encode, maybe it will help.