Search code examples
pdfjava-native-interfacejpegmupdfvudroid

mupdf render jpeg2000 lose color?


I am working on an android project, which use vudroid, which in turn use mupdf version 0.5.

Vudroid remove the original openjpeg support of mupdf, I have ported the mupdf version 1.5's openjpeg support.

But I encounter a new problem, color information in jpx image gone, the desired effect: enter image description here

my effect: enter image description

the ported load-jpx code:

#include "fitz.h"
#include "mupdf.h"

/* Without the definition of OPJ_STATIC, compilation fails on windows
 * due to the use of __stdcall. We believe it is required on some
 * linux toolchains too. */
#define OPJ_STATIC
#ifndef _MSC_VER
#define OPJ_HAVE_STDINT_H
#endif

#include <openjpeg.h>

static void fz_opj_error_callback(const char *msg, void *client_data)
{
    //fz_context *ctx = (fz_context *)client_data;
    //fz_warn(ctx, "openjpeg error: %s", msg);
}

static void fz_opj_warning_callback(const char *msg, void *client_data)
{
    //fz_context *ctx = (fz_context *)client_data;
    //fz_warn(ctx, "openjpeg warning: %s", msg);
}

static void fz_opj_info_callback(const char *msg, void *client_data)
{
    /* fz_warn("openjpeg info: %s", msg); */
}

typedef struct stream_block_s
{
    unsigned char *data;
    int size;
    int pos;
} stream_block;

static OPJ_SIZE_T fz_opj_stream_read(void * p_buffer, OPJ_SIZE_T p_nb_bytes, void * p_user_data)
{
    stream_block *sb = (stream_block *)p_user_data;
    int len;

    len = sb->size - sb->pos;
    if (len < 0)
        len = 0;
    if (len == 0)
        return (OPJ_SIZE_T)-1;  /* End of file! */
    if ((OPJ_SIZE_T)len > p_nb_bytes)
        len = p_nb_bytes;
    memcpy(p_buffer, sb->data + sb->pos, len);
    sb->pos += len;
    return len;
}

static OPJ_OFF_T fz_opj_stream_skip(OPJ_OFF_T skip, void * p_user_data)
{
    stream_block *sb = (stream_block *)p_user_data;

    if (skip > sb->size - sb->pos)
        skip = sb->size - sb->pos;
    sb->pos += skip;
    return sb->pos;
}

static OPJ_BOOL fz_opj_stream_seek(OPJ_OFF_T seek_pos, void * p_user_data)
{
    stream_block *sb = (stream_block *)p_user_data;

    if (seek_pos > sb->size)
        return OPJ_FALSE;
    sb->pos = seek_pos;
    return OPJ_TRUE;
}

fz_error
fz_load_jpx(pdf_image* img, unsigned char *data, int size, fz_colorspace *defcs, int indexed)
{
    //fz_pixmap *img;
    opj_dparameters_t params;
    opj_codec_t *codec;
    opj_image_t *jpx;
    opj_stream_t *stream;
    fz_colorspace *colorspace;
    unsigned char *p;
    OPJ_CODEC_FORMAT format;
    int a, n, w, h, depth, sgnd;
    int x, y, k, v;
    stream_block sb;

    if (size < 2)
        fz_throw("not enough data to determine image format");

    /* Check for SOC marker -- if found we have a bare J2K stream */
    if (data[0] == 0xFF && data[1] == 0x4F)
        format = OPJ_CODEC_J2K;
    else
        format = OPJ_CODEC_JP2;

    opj_set_default_decoder_parameters(&params);
    if (indexed)
        params.flags |= OPJ_DPARAMETERS_IGNORE_PCLR_CMAP_CDEF_FLAG;

    codec = opj_create_decompress(format);
    opj_set_info_handler(codec, fz_opj_info_callback, 0);
    opj_set_warning_handler(codec, fz_opj_warning_callback, 0);
    opj_set_error_handler(codec, fz_opj_error_callback, 0);
    if (!opj_setup_decoder(codec, &params))
    {
        fz_throw("j2k decode failed");
    }

    stream = opj_stream_default_create(OPJ_TRUE);
    sb.data = data;
    sb.pos = 0;
    sb.size = size;

    opj_stream_set_read_function(stream, fz_opj_stream_read);
    opj_stream_set_skip_function(stream, fz_opj_stream_skip);
    opj_stream_set_seek_function(stream, fz_opj_stream_seek);
    opj_stream_set_user_data(stream, &sb);
    /* Set the length to avoid an assert */
    opj_stream_set_user_data_length(stream, size);

    if (!opj_read_header(stream, codec, &jpx))
    {
        opj_stream_destroy(stream);
        opj_destroy_codec(codec);
        fz_throw("Failed to read JPX header");
    }

    if (!opj_decode(codec, stream, jpx))
    {
        opj_stream_destroy(stream);
        opj_destroy_codec(codec);
        opj_image_destroy(jpx);
        fz_throw("Failed to decode JPX image");
    }

    opj_stream_destroy(stream);
    opj_destroy_codec(codec);

    /* jpx should never be NULL here, but check anyway */
    if (!jpx)
        fz_throw("opj_decode failed");

    pdf_logimage("opj_decode succeeded");

    for (k = 1; k < (int)jpx->numcomps; k++)
    {
        if (!jpx->comps[k].data)
        {
            opj_image_destroy(jpx);
            fz_throw("image components are missing data");
        }
        if (jpx->comps[k].w != jpx->comps[0].w)
        {
            opj_image_destroy(jpx);
            fz_throw("image components have different width");
        }
        if (jpx->comps[k].h != jpx->comps[0].h)
        {
            opj_image_destroy(jpx);
            fz_throw("image components have different height");
        }
        if (jpx->comps[k].prec != jpx->comps[0].prec)
        {
            opj_image_destroy(jpx);
            fz_throw("image components have different precision");
        }
    }

    n = jpx->numcomps;
    w = jpx->comps[0].w;
    h = jpx->comps[0].h;
    depth = jpx->comps[0].prec;
    sgnd = jpx->comps[0].sgnd;

    if (jpx->color_space == OPJ_CLRSPC_SRGB && n == 4) { n = 3; a = 1; }
    else if (jpx->color_space == OPJ_CLRSPC_SYCC && n == 4) { n = 3; a = 1; }
    else if (n == 2) { n = 1; a = 1; }
    else if (n > 4) { n = 4; a = 1; }
    else { a = 0; }

    if (defcs)
    {
        if (defcs->n == n)
        {
            colorspace = defcs;
        }
        else
        {
            fz_warn("jpx file and dict colorspaces do not match");
            defcs = NULL;
        }
    }

    if (!defcs)
    {
        switch (n)
        {
            case 1: colorspace = pdf_devicegray; break;
            case 3: colorspace = pdf_devicergb; break;
            case 4: colorspace = pdf_devicecmyk; break;
        }
    }

    //error = fz_new_pixmap(&img, colorspace, w, h);
    //if (error)
    //  return error;

    pdf_logimage("colorspace handled\n");

    int bpc = 1;
    if (colorspace) {
        bpc = 1 + colorspace->n;
    };
    pdf_logimage("w = %d, bpc = %d, h = %d\n", w, bpc, h);
    img->samples = fz_newbuffer(w * bpc * h);

    //opj_image_destroy(jpx);
    //fz_throw("out of memory loading jpx");
    p = (char*)img->samples->bp;
    pdf_logimage("start to deal with samples");
    for (y = 0; y < h; y++)
    {
        for (x = 0; x < w; x++)
        {
            for (k = 0; k < n + a; k++)
            {
                v = jpx->comps[k].data[y * w + x];
                if (sgnd)
                    v = v + (1 << (depth - 1));
                if (depth > 8)
                    v = v >> (depth - 8);
                *p++ = v;
            }
            if (!a)
                *p++ = 255;
        }
    }
    img->samples->wp = p;

    pdf_logimage("start to deal with samples succeeded");
    opj_image_destroy(jpx);

//  if (a)
//  {
//      if (n == 4)
//      {
//          fz_pixmap *tmp = fz_new_pixmap(ctx, fz_device_rgb(ctx), w, h);
//          fz_convert_pixmap(ctx, tmp, img);
//          fz_drop_pixmap(ctx, img);
//          img = tmp;
//      }
//      fz_premultiply_pixmap(ctx, img);
//  }
    return fz_okay;
}

The render code:

JNIEXPORT jbyteArray JNICALL Java_org_vudroid_pdfdroid_codec_PdfPage_drawPage
  (JNIEnv *env, jclass clazz, jlong dochandle, jlong pagehandle)
{
    renderdocument_t *doc = (renderdocument_t*) dochandle;
    renderpage_t *page = (renderpage_t*) pagehandle;

    //DEBUG("PdfView(%p).drawpage(%p, %p)", this, doc, page);

    fz_error error;
    fz_matrix ctm;
    fz_irect viewbox;
    fz_pixmap *pixmap;
    jfloat *matrix;
    jint *viewboxarr;
    jint *dimen;
    jint *buffer;
    int length, val;

    pixmap = nil;

    /* initialize parameter arrays for MuPDF */

    ctm.a = 1;
    ctm.b = 0;
    ctm.c = 0;
    ctm.d = 1;
    ctm.e = 0;
    ctm.f = 0;

//  matrix = (*env)->GetPrimitiveArrayCritical(env, matrixarray, 0);
//  ctm.a = matrix[0];
//  ctm.b = matrix[1];
//  ctm.c = matrix[2];
//  ctm.d = matrix[3];
//  ctm.e = matrix[4];
//  ctm.f = matrix[5];
//  (*env)->ReleasePrimitiveArrayCritical(env, matrixarray, matrix, 0);
//  DEBUG("Matrix: %f %f %f %f %f %f",
//          ctm.a, ctm.b, ctm.c, ctm.d, ctm.e, ctm.f);

//  viewboxarr = (*env)->GetPrimitiveArrayCritical(env, viewboxarray, 0);
//  viewbox.x0 = viewboxarr[0];
//  viewbox.y0 = viewboxarr[1];
//  viewbox.x1 = viewboxarr[2];
//  viewbox.y1 = viewboxarr[3];
//  (*env)->ReleasePrimitiveArrayCritical(env, viewboxarray, viewboxarr, 0);
//  DEBUG("Viewbox: %d %d %d %d",
//          viewbox.x0, viewbox.y0, viewbox.x1, viewbox.y1);
    viewbox.x0 = 0;
    viewbox.y0 = 0;
    viewbox.x1 = 595;
    viewbox.y1 = 841;

    /* do the rendering */
    DEBUG("doing the rendering...");
    //buffer = (*env)->GetPrimitiveArrayCritical(env, bufferarray, 0);

    // do the actual rendering:
    error = fz_rendertree(&pixmap, doc->rast, page->page->tree,
            ctm, viewbox, 1);

    /* evil magic: we transform the rendered image's byte order
     */
    int x, y;

    if (bmpdata)
        fz_free(bmpdata);

    bmpstride = ((pixmap->w * 3 + 3) / 4) * 4;
    bmpdata = fz_malloc(pixmap->h * bmpstride);
    DEBUG("inside drawpage, bmpstride = %d, pixmap->w = %d, pixmap->h = %d\n", bmpstride, pixmap->w, pixmap->h);
    if (!bmpdata)
        return;

    for (y = 0; y < pixmap->h; y++)
    {
        unsigned char *p = bmpdata + y * bmpstride;
        unsigned char *s = pixmap->samples + y * pixmap->w * 4;
        for (x = 0; x < pixmap->w; x++)
        {
            p[x * 3 + 0] = s[x * 4 + 3];
            p[x * 3 + 1] = s[x * 4 + 2];
            p[x * 3 + 2] = s[x * 4 + 1];
        }
    }

    FILE* fp = fopen("/sdcard/drawpage", "wb");
    fwrite(bmpdata, pixmap->h * bmpstride, 1, fp);
    fclose(fp);

    jbyteArray array = (*env)->NewByteArray(env, pixmap->h * bmpstride);
    (*env)->SetByteArrayRegion(env, array, 0, pixmap->h * bmpstride, bmpdata);

//  if(!error) {
//      DEBUG("Converting image buffer pixel order");
//      length = pixmap->w * pixmap->h;
//      unsigned int *col = pixmap->samples;
//      int c = 0;
//      for(val = 0; val < length; val++) {
//          col[val] = ((col[val] & 0xFF000000) >> 24) |
//                  ((col[val] & 0x00FF0000) >> 8) |
//                  ((col[val] & 0x0000FF00) << 8);
//      }
//      winconvert(pixmap);
//  }

//  (*env)->ReleasePrimitiveArrayCritical(env, bufferarray, buffer, 0);

    fz_free(pixmap);

    if (error) {
        DEBUG("error!");
        throw_exception(env, "error rendering page");
    }

    DEBUG("PdfView.drawPage() done");
    return array;
}

I have compare the jpx output samples to the mupdf-1.5 windows, it is the same, but the colorspace of original jpx have gone.

Could help me to get the colorspace back?


Solution

  • It seems you are trying to use an old version of MuPDF with some bits pulled in from a more recent version. TO be honest that's hardly likely to work. I would also guess that its not the OpenJPEG library causing your problem, since the image appears, but converted to grayscale.

    Have you tried opening the file in the current version of MuPDF ? Does it work ?

    If so then it seems to me your correct approach should be to use the current code, not try and bolt pieces onto an older version.