4 votes

Résultats étranges lors de la compression d'un lot d'images avec libjpegturbo

D'abord, ce que je (veux) faire : compresser et mettre à l'échelle un lot d'images (jpg). Supposons que l'image originale ait ces dimensions 1600w x 1200h. Maintenant, je veux avoir une copie compressée de 1600x1200 et une autre de 800x600 et 400x300.

Ce que j'utilise : J'utilise la libJpegTurob pour y parvenir. Si la libJpegTurob a des problèmes, j'essaie d'utiliser les méthodes données par Android.

Déjà essayé : D'abord, j'ai utilisé le Wrapper Java porté par Tom Gall ( https://github.com/jberkel/libjpeg-turbo ).

Tout s'est bien passé (sur le Nexus 4) jusqu'à ce que je commence à utiliser des images de plus de 4 Mo. Ce qui s'est passé, c'est qu'Android a lancé des exceptions OutOfMemory. Cela s'est produit lorsque j'ai utilisé des images plus petites (~1-2mb) mais compressées les unes après les autres.

C'est devenu encore pire après l'avoir fait fonctionner sur des appareils à bas prix avec moins de mémoire comme le Nexus S. Le problème est dû à la faible quantité de mémoire, c'est ce que je pense.

Alors, je me suis dit que je devais le faire en C. Les problèmes de mémoire semblent résolus, tant que j'utilise des images de moins de 3mb sur un appareil budgétaire. Sur un Nexus 4, j'ai même pu compresser une image de plus de 15 Mo.

C'est la photo de la src. enter image description here

Mais maintenant... le problème. La première image compressée semble bonne enter image description here

mais tous les autres ressemblent à ceci enter image description here ou ceci enter image description here

Cela s'est produit tant que j'ai sélectionné des images et que je les ai compressées.

Maintenant le code.

C'est ici que la mise à l'échelle et la compression ont lieu

    #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;
}

La partie Java pour commencer la 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();
            }
        }
    }
}

Qu'est-ce que j'ai fait de mal ?

Merci beaucoup !

P.S. Désolé pour le mauvais anglais !

3voto

Blauesocke Points 1170

Ça m'a l'air bien. Avez-vous vérifié que les sources de libjpegturbo sont valides et stables ?

2voto

Dragos Iordache Points 139

Votre code semble correct, il est bien écrit et gère bien les erreurs. Mais d'après ce que je vois, le problème peut être soit une erreur dans la librairie externe, vous pouvez tester cela en déchargeant (ou désinit) et en rechargeant et réinitialisant la librairie puis en encodant le fichier suivant.

Vous pouvez également essayer une ancienne version de la librairie pour voir si elle fonctionne.

Le second problème peut être un pointeur qui n'est pas mis à NULL après la libération et qui commence à écrire de mauvaises données en mémoire. Vous devriez utiliser des outils comme valgrind, ou mapper tous vos mallocs dans leur propre page, et les remplir avec des pages de mémoire en lecture seule (voir : mprotect), puis lorsque vous écrivez dans une mauvaise page, le programme va segfail et vous verrez le problème.

Je ne suis pas un expert de cette librairie, mais vérifiez bien la documentation et le code d'exemple. Peut-être qu'il y a quelque chose qui doit être appelé entre 2 fichiers ou après chacun d'eux, et que le premier qui fonctionne est juste un coup de chance.

De plus, essayez de changer l'ordre des fichiers que vous encodez, cela vous aidera peut-être.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X