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.
But now... the problem. The first compressed picture looks good
but all other ones looks like this or this
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!
Looks good for me. Have you made sure that the libjpegturbo sources are valid and stable?