Add J2K (JPEG 2000) loader using openjpeg2 library

This commit is contained in:
Kim Woelders 2022-04-08 13:45:43 +02:00
parent e8b564f141
commit 9ebd8399d2
4 changed files with 304 additions and 0 deletions

View File

@ -196,6 +196,7 @@ loader_check_gif() {
EC_LOADER_CHECK(GIF, auto, , loader_check_gif)
EC_LOADER_CHECK(HEIF, auto, libheif)
EC_LOADER_CHECK(JPEG, auto, libjpeg)
EC_LOADER_CHECK(J2K, auto, libopenjp2)
EC_LOADER_CHECK(JXL, auto, libjxl libjxl_threads)
EC_LOADER_CHECK(PNG, auto, libpng)
EC_LOADER_CHECK(SVG, auto, librsvg-2.0 >= 2.46)
@ -284,6 +285,7 @@ echo " Regular image loaders"
echo " GIF.....................: $gif_ok"
echo " HEIF....................: $heif_ok"
echo " JPEG....................: $jpeg_ok"
echo " J2K.....................: $j2k_ok"
echo " JXL.....................: $jxl_ok"
echo " PNG.....................: $png_ok"
echo " SVG.....................: $svg_ok"

View File

@ -36,6 +36,9 @@ static const char *const ext_ico[] = { "ico", NULL };
#ifdef BUILD_JPEG_LOADER
static const char *const ext_jpeg[] = { "jpg", "jpeg", "jfif", "jfi", NULL };
#endif
#ifdef BUILD_J2K_LOADER
static const char *const ext_j2k[] = { "jp2", "j2k", NULL };
#endif
#ifdef BUILD_JXL_LOADER
static const char *const ext_jxl[] = { "jxl", NULL };
#endif
@ -86,6 +89,9 @@ static const KnownLoader loaders_known[] = {
#ifdef BUILD_JPEG_LOADER
{"jpeg", ext_jpeg},
#endif
#ifdef BUILD_J2K_LOADER
{"j2k", ext_j2k},
#endif
#ifdef BUILD_JXL_LOADER
{"jxl", ext_jxl},
#endif

View File

@ -23,6 +23,9 @@ endif
if BUILD_JPEG_LOADER
pkg_LTLIBRARIES += jpeg.la
endif
if BUILD_J2K_LOADER
pkg_LTLIBRARIES += j2k.la
endif
if BUILD_JXL_LOADER
pkg_LTLIBRARIES += jxl.la
endif
@ -92,6 +95,12 @@ jpeg_la_LDFLAGS = -module -avoid-version
jpeg_la_LIBADD = $(JPEG_LIBS) $(top_builddir)/src/lib/libImlib2.la
jpeg_la_LIBTOOLFLAGS = --tag=disable-static
j2k_la_SOURCES = loader_j2k.c
j2k_la_CPPFLAGS = $(J2K_CFLAGS) $(AM_CPPFLAGS)
j2k_la_LDFLAGS = -module -avoid-version
j2k_la_LIBADD = $(J2K_LIBS) $(top_builddir)/src/lib/libImlib2.la
j2k_la_LIBTOOLFLAGS = --tag=disable-static
jxl_la_SOURCES = loader_jxl.c
jxl_la_CPPFLAGS = $(JXL_CFLAGS) $(AM_CPPFLAGS)
jxl_la_LDFLAGS = -module -avoid-version

View File

@ -0,0 +1,287 @@
#include "loader_common.h"
#include <openjpeg.h>
#define DBG_PFX "LDR-j2k"
#define JP2_RFC3745_MAGIC "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"
#define JP2_MAGIC "\x0d\x0a\x87\x0a"
/* position 45: "\xff\x52" */
#define J2K_CODESTREAM_MAGIC "\xff\x4f\xff\x51"
#if IMLIB2_DEBUG
static void
_j2k_cb(const char *type, const char *msg, void *data)
{
DL("%s: %p: %s: %s", __func__, data, type, msg);
}
static void
_j2k_cb_info(const char *msg, void *data)
{
_j2k_cb("info", msg, data);
}
static void
_j2k_cb_warn(const char *msg, void *data)
{
_j2k_cb("warn", msg, data);
}
static void
_j2k_cb_err(const char *msg, void *data)
{
_j2k_cb("err", msg, data);
}
#endif /*IMLIB2_DEBUG */
static struct {
const unsigned char *data, *dptr;
unsigned int size;
} mdata;
static void
mm_init(const void *src, unsigned int size)
{
mdata.data = mdata.dptr = src;
mdata.size = size;
}
static OPJ_SIZE_T
mm_read(void *dst, OPJ_SIZE_T len, void *data)
{
DL("%s: len=%ld\n", __func__, len);
if (mdata.dptr >= mdata.data + mdata.size)
return -1; /* Out of data */
if (mdata.dptr + len > mdata.data + mdata.size)
len = mdata.data + mdata.size - mdata.dptr;
memcpy(dst, mdata.dptr, len);
mdata.dptr += len;
return len;
}
static OPJ_OFF_T
mm_seek_cur(OPJ_OFF_T offs, void *data)
{
DL("%s: offs=%ld\n", __func__, offs);
if (mdata.dptr + offs > mdata.data + mdata.size)
return 0; /* Out of data */
mdata.dptr += offs;
return mdata.dptr - mdata.data;
}
static OPJ_BOOL
mm_seek_set(OPJ_OFF_T offs, void *data)
{
DL("%s: offs=%ld\n", __func__, offs);
if (offs > mdata.size)
return OPJ_FALSE; /* Out of data */
mdata.dptr = mdata.data + offs;
return OPJ_TRUE;
}
int
load2(ImlibImage * im, int load_data)
{
int rc;
void *fdata;
int ok;
opj_dparameters_t jparam;
opj_codec_t *jcodec;
opj_stream_t *jstream;
opj_image_t *jimage;
OPJ_CODEC_FORMAT jfmt;
int i, j;
uint32_t *dst;
OPJ_INT32 *pa, *pr, *pg, *pb;
unsigned char a, r, g, b;
fdata = mmap(NULL, im->fsize, PROT_READ, MAP_SHARED, fileno(im->fp), 0);
if (fdata == MAP_FAILED)
return LOAD_BADFILE;
rc = LOAD_FAIL;
jcodec = NULL;
jstream = NULL;
jimage = NULL;
/* Signature check */
if (im->fsize < 12)
goto quit;
if (memcmp(fdata, JP2_MAGIC, 4) == 0 ||
memcmp(fdata, JP2_RFC3745_MAGIC, 12) == 0)
jfmt = OPJ_CODEC_JP2;
else if (memcmp(fdata, J2K_CODESTREAM_MAGIC, 4) == 0)
jfmt = OPJ_CODEC_J2K;
else
goto quit;
DL("format=%d\n", jfmt);
memset(&jparam, 0, sizeof(opj_dparameters_t));
opj_set_default_decoder_parameters(&jparam);
jcodec = opj_create_decompress(jfmt);
if (!jcodec)
goto quit;
rc = LOAD_BADIMAGE; /* Format accepted */
#if IMLIB2_DEBUG
opj_set_info_handler(jcodec, _j2k_cb_info, NULL);
opj_set_warning_handler(jcodec, _j2k_cb_warn, NULL);
opj_set_error_handler(jcodec, _j2k_cb_err, NULL);
#endif
ok = opj_setup_decoder(jcodec, &jparam);
if (!ok)
goto quit;
// May be set with OPJ_NUM_THREADS=number or ALL_CPUS
// opj_codec_set_threads(jcodec, 4);
if (getenv("JP2_USE_FILE"))
{
jstream =
opj_stream_create_default_file_stream(im->real_file, OPJ_TRUE);
}
else
{
jstream = opj_stream_create(OPJ_J2K_STREAM_CHUNK_SIZE, OPJ_TRUE);
if (!jstream)
goto quit;
mm_init(fdata, im->fsize);
opj_stream_set_user_data(jstream, &mdata, NULL);
opj_stream_set_user_data_length(jstream, im->fsize);
opj_stream_set_read_function(jstream, mm_read);
opj_stream_set_skip_function(jstream, mm_seek_cur);
opj_stream_set_seek_function(jstream, mm_seek_set);
}
opj_read_header(jstream, jcodec, &jimage);
if (!jimage)
goto quit;
im->w = jimage->x1 - jimage->x0;
im->h = jimage->y1 - jimage->y0;
IM_FLAG_UPDATE(im, F_HAS_ALPHA,
jimage->numcomps == 4 || jimage->numcomps == 2);
D("WxH=%dx%d alpha=%d numcomp=%d colorspace=%d\n",
im->w, im->h, IM_FLAG_ISSET(im, F_HAS_ALPHA),
jimage->numcomps, jimage->color_space);
for (i = 0; i < (int)jimage->numcomps; i++)
{
DL("%d: dx/y=%d/%d wxh=%d,%d prec=%d bpp=%d sgnd=%d fact=%d\n", i,
jimage->comps[i].dx, jimage->comps[i].dy,
jimage->comps[i].w, jimage->comps[i].h,
jimage->comps[i].prec, jimage->comps[i].bpp,
jimage->comps[i].sgnd, jimage->comps[i].factor);
if (jimage->comps[0].dx != jimage->comps[i].dx ||
jimage->comps[0].dy != jimage->comps[i].dy ||
(int)jimage->comps[i].w != im->w ||
(int)jimage->comps[i].h != im->h)
goto quit;
}
if (!load_data)
QUIT_WITH_RC(LOAD_SUCCESS);
/* Load data */
ok = opj_decode(jcodec, jstream, jimage);
if (!ok)
goto quit;
ok = opj_end_decompress(jcodec, jstream);
if (!ok)
goto quit;
if (!__imlib_AllocateData(im))
QUIT_WITH_RC(LOAD_OOM);
/* Ignoring color_space and data format details... */
dst = im->data;
pa = jimage->comps[0].data; /* Avoid compiler warning */
switch (jimage->numcomps)
{
default:
goto quit;
case 4: /* RGBA */
pa = jimage->comps[3].data;
/* FALLTHROUGH */
case 3: /* RGB */
pr = jimage->comps[0].data;
pg = jimage->comps[1].data;
pb = jimage->comps[2].data;
for (i = 0; i < im->h; i++)
{
for (j = 0; j < im->w; j++)
{
r = *pr++;
g = *pg++;
b = *pb++;
a = (jimage->numcomps == 4) ? *pa++ : 0xff;
*dst++ = PIXEL_ARGB(a, r, g, b);
}
if (im->lc && __imlib_LoadProgressRows(im, i, 1))
QUIT_WITH_RC(LOAD_BREAK);
}
break;
case 2: /* Gray with A */
pa = jimage->comps[1].data;
/* FALLTHROUGH */
case 1: /* Gray */
pg = jimage->comps[0].data;
for (i = 0; i < im->h; i++)
{
for (j = 0; j < im->w; j++)
{
g = *pg++;
a = (jimage->numcomps == 2) ? *pa++ : 0xff;
*dst++ = PIXEL_ARGB(a, g, g, g);
}
if (im->lc && __imlib_LoadProgressRows(im, i, 1))
QUIT_WITH_RC(LOAD_BREAK);
}
break;
}
rc = LOAD_SUCCESS;
quit:
if (jimage)
opj_image_destroy(jimage);
if (jstream)
opj_stream_destroy(jstream);
if (jcodec)
opj_destroy_codec(jcodec);
munmap(fdata, im->fsize);
return rc;
}
void
formats(ImlibLoader * l)
{
static const char *const list_formats[] = { "jp2", "j2k" };
__imlib_LoaderSetFormats(l, list_formats, ARRAY_SIZE(list_formats));
}