--- CV/CV.xs 2017/12/23 04:11:49 1.51 +++ CV/CV.xs 2021/07/15 00:41:44 1.60 @@ -9,6 +9,8 @@ #include #include +#include + #include #include #include @@ -20,16 +22,20 @@ #include #if WEBP +#include #include #endif +#if JXL +#include +#include "jxl/thread_parallel_runner.h" +#endif + #include "perlmulticore.h" #define IW 80 /* MUST match Schnauzer.pm! */ #define IH 60 /* MUST match Schnauzer.pm! */ -#define RAND (seed = (seed + 7141) * 54773 % 134456) - #define LINELENGTH 240 #define ELLIPSIS "\xe2\x80\xa6" @@ -200,11 +206,106 @@ } ///////////////////////////////////////////////////////////////////////////// +// memory source for libjpeg + +static void cv_ms_init (j_decompress_ptr cinfo) +{ +} + +static void cv_ms_term (j_decompress_ptr cinfo) +{ +} + +static boolean cv_ms_fill (j_decompress_ptr cinfo) +{ + // unexpected EOF, warn and generate fake EOI marker + + WARNMS (cinfo, JWRN_JPEG_EOF); + + struct jpeg_source_mgr *src = (struct jpeg_source_mgr *)cinfo->src; + + static const JOCTET eoi[] = { 0xFF, JPEG_EOI }; + + src->next_input_byte = eoi; + src->bytes_in_buffer = sizeof (eoi); + + return TRUE; +} + +static void cv_ms_skip (j_decompress_ptr cinfo, long num_bytes) +{ + struct jpeg_source_mgr *src = (struct jpeg_source_mgr *)cinfo->src; + + src->next_input_byte += num_bytes; + src->bytes_in_buffer -= num_bytes; +} + +static void cv_jpeg_mem_src (j_decompress_ptr cinfo, void *buf, size_t buflen) +{ + struct jpeg_source_mgr *src; + + if (!cinfo->src) + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ( + (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (struct jpeg_source_mgr) + ); + + src = (struct jpeg_source_mgr *)cinfo->src; + src->init_source = cv_ms_init; + src->fill_input_buffer = cv_ms_fill; + src->skip_input_data = cv_ms_skip; + src->resync_to_restart = jpeg_resync_to_restart; + src->term_source = cv_ms_term; + src->next_input_byte = (JOCTET *)buf; + src->bytes_in_buffer = buflen; +} + +///////////////////////////////////////////////////////////////////////////// + +/* great, the jpeg-xl reference implementaton requires us to parse bmff files */ + +struct bmff_box +{ + char type[4]; + const uint8_t *ptr; + size_t size; +}; + +static int +bmff_parse_box (struct bmff_box *box, const uint8_t **next_in, size_t *avail_in) +{ + if (*avail_in < 8) + return 0; + + box->size = ((*next_in)[0] << 24) + | ((*next_in)[1] << 16) + | ((*next_in)[2] << 8) + | ((*next_in)[3] ); + + if (box->size < 8) + return 0; + + if (*avail_in < box->size) + return 0; + + memcpy (box->type, *next_in + 4, 4); + box->ptr = *next_in + 8; + + *next_in += box->size; + *avail_in -= box->size; + + box->size -= 8; + + return 1; +} + +///////////////////////////////////////////////////////////////////////////// MODULE = Gtk2::CV PACKAGE = Gtk2::CV PROTOTYPES: ENABLE +# calculate the common prefix length of two strings # missing function in perl. really :) int common_prefix_length (a, b) @@ -234,17 +335,19 @@ STRLEN len; char *data = SvPVbyte (path_or_data, len); - perlinterp_release (); - if (!magic_cookie[0]) { magic_cookie[0] = magic_open (MAGIC_SYMLINK); - magic_cookie[1] = magic_open (MAGIC_SYMLINK | MAGIC_MIME); + magic_cookie[1] = magic_open (MAGIC_SYMLINK | MAGIC_MIME_TYPE); + magic_load (magic_cookie[0], 0); + magic_load (magic_cookie[1], 0); } + perlinterp_release (); + RETVAL = ix & 2 - ? magic_buffer (magic_cookie[ix], data, len) - : magic_file (magic_cookie[ix], data); + ? magic_buffer (magic_cookie[ix & 1], data, len) + : magic_file (magic_cookie[ix & 1], data); perlinterp_acquire (); } @@ -310,30 +413,85 @@ OUTPUT: RETVAL +const char * +filetype (SV *image_data) + CODE: +{ + STRLEN data_len; + U8 *data = SvPVbyte (image_data, data_len); + static const unsigned char jxl_header[] = { + 0, 0, 0, 0x0c, 0x4a, 0x58, 0x4c, 0x20, + 0x0d, 0xa, 0x87, 0x0a, 0, 0, 0, 0x14, + 0x66, 0x74, 0x79, 0x70, 0x6a, 0x78, 0x6c, 0x20, + 0, 0, 0, 0, 0x6a, 0x78, 0x6c, 0x20, + }; + + if (data_len >= 20 + && data[0] == 0xff + && data[1] == 0xd8 + && data[2] == 0xff) + RETVAL = "image/jpeg"; + else if (data_len >= 12 + && data[ 0] == (U8)'R' + && data[ 1] == (U8)'I' + && data[ 2] == (U8)'F' + && data[ 3] == (U8)'F' + && data[ 8] == (U8)'W' + && data[ 9] == (U8)'E' + && data[10] == (U8)'B' + && data[11] == (U8)'P') + RETVAL = "image/webp"; + else if (data_len >= 16 + && data[ 0] == 0x89 + && data[ 1] == (U8)'P' + && data[ 2] == (U8)'N' + && data[ 3] == (U8)'G' + && data[ 4] == 0x0d + && data[ 5] == 0x0a + && data[ 6] == 0x1a + && data[ 7] == 0x0a) + RETVAL = "image/png"; + else if (data_len >= sizeof (jxl_header) && memcmp (data, jxl_header, sizeof (jxl_header)) == 0) + RETVAL = "image/jxl"; + else + XSRETURN_UNDEF; +} + OUTPUT: + RETVAL + GdkPixbuf_noinc * decode_webp (SV *image_data, int thumbnail = 0, int iw = 0, int ih = 0) CODE: { #if WEBP - guchar *data; STRLEN data_size; int alpha; - WebPDecoderConfig config; + WebPData data; + WebPDemuxer *demux; + WebPIterator iter; + WebPDecoderConfig config; int inw, inh; - data = SvPVbyte (image_data, data_size); + data.bytes = (uint8_t *)SvPVbyte (image_data, data_size); + data.size = data_size; perlinterp_release (); RETVAL = 0; - if (!WebPInitDecoderConfig (&config)) - goto err; + if (!(demux = WebPDemux (&data))) + goto err_demux; + + if (!WebPDemuxGetFrame (demux, 1, &iter)) + goto err_iter; + + if (!WebPInitDecoderConfig (&config)) + goto err_iter; config.options.use_threads = 1; - if (WebPGetFeatures (data, data_size, &config.input) != VP8_STATUS_OK) - goto err; + if (WebPGetFeatures (iter.fragment.bytes, iter.fragment.size, &config.input) != VP8_STATUS_OK) + goto err_iter; inw = config.input.width; inh = config.input.height; @@ -358,11 +516,11 @@ ih = inh; } - alpha = config.input.has_alpha; + alpha = !!config.input.has_alpha; - RETVAL = gdk_pixbuf_new (GDK_COLORSPACE_RGB, !!alpha, 8, iw, ih); + RETVAL = gdk_pixbuf_new (GDK_COLORSPACE_RGB, alpha, 8, iw, ih); if (!RETVAL) - goto err; + goto err_iter; config.output.colorspace = alpha ? MODE_RGBA : MODE_RGB; config.output.u.RGBA.rgba = gdk_pixbuf_get_pixels (RETVAL); @@ -370,14 +528,18 @@ config.output.u.RGBA.size = gdk_pixbuf_get_byte_length (RETVAL); config.output.is_external_memory = 1; - if (WebPDecode (data, data_size, &config) != VP8_STATUS_OK) + if (WebPDecode (iter.fragment.bytes, iter.fragment.size, &config) != VP8_STATUS_OK) { g_object_unref (RETVAL); RETVAL = 0; - goto err; + goto err_iter; } - err: + err_iter: + WebPDemuxReleaseIterator (&iter); + err_demux: + WebPDemuxDelete (demux); + perlinterp_acquire (); #else croak ("load_webp: webp not enabled at compile time"); @@ -387,23 +549,130 @@ RETVAL GdkPixbuf_noinc * -load_jpeg (SV *path, int thumbnail = 0, int iw = 0, int ih = 0) +decode_jxl (SV *image_data, int thumbnail = 0, int iw = 0, int ih = 0) + CODE: +{ +#if JXL + JxlDecoder *dec = JxlDecoderCreate (0); + JxlBasicInfo info; + const uint8_t *next_in = (uint8_t *)SvPVbyte_nolen (image_data); + size_t avail_in = SvCUR (image_data); + const char *error = 0; + void *runner = 0; + struct bmff_box box; + + perlinterp_release (); + + RETVAL = 0; + + error = "JxlDecoderCreate failed"; + if (!dec) + goto done; + + runner = JxlThreadParallelRunnerCreate (0, JxlThreadParallelRunnerDefaultNumWorkerThreads ()); + + error = "JxlDecoderSetParallelRunner failed"; + if (JxlDecoderSetParallelRunner (dec, JxlThreadParallelRunner, runner) != JXL_DEC_SUCCESS) + goto done; + + error = "JxlDecoderSubscribeEvents failed"; + if (JxlDecoderSubscribeEvents (dec, JXL_DEC_FULL_IMAGE | JXL_DEC_BASIC_INFO) != JXL_DEC_SUCCESS) + goto done; + + error = "JxlDecoderSetInput failed"; + if (JxlDecoderSetInput (dec, next_in, avail_in) != JXL_DEC_SUCCESS) + goto done; + + for (;;) + { + JxlDecoderStatus status = JxlDecoderProcessInput (dec); + + printf ("status %d\n",status); + + switch (status) + { + case JXL_DEC_ERROR: + error = "JxlDecoderProcessInput failed"; + goto done; + + case JXL_DEC_NEED_MORE_INPUT: + error = "incomplete file"; + goto done; + + case JXL_DEC_SUCCESS: + goto done; + + case JXL_DEC_NEED_IMAGE_OUT_BUFFER: + { + error = "JxlDecoderGetBasicInfo failed"; + if (JxlDecoderGetBasicInfo (dec, &info) != JXL_DEC_SUCCESS) + goto done; + + RETVAL = gdk_pixbuf_new (GDK_COLORSPACE_RGB, !!info.alpha_bits, 8, info.xsize, info.ysize); + error = "unable to allocate pixbuf"; + if (!RETVAL) + goto done; + + JxlPixelFormat format = { + info.alpha_bits ? 4 : 3, + JXL_TYPE_UINT8, + JXL_NATIVE_ENDIAN, + gdk_pixbuf_get_rowstride (RETVAL) + }; + + error = "JxlDecoderSetImageOutBuffer failed"; + if (JxlDecoderSetImageOutBuffer ( + dec, + &format, + gdk_pixbuf_get_pixels (RETVAL), + gdk_pixbuf_get_byte_length (RETVAL) + ) != JXL_DEC_SUCCESS) + goto done; + } + break; + + default: + error = "unexpected event"; + goto done; + } + } + + done: + if (dec) + JxlDecoderDestroy (dec); + + if (runner) + JxlThreadParallelRunnerDestroy (runner); + + perlinterp_acquire (); + + if (error) + { + if (RETVAL) + g_object_unref (RETVAL); + + croak ("load_jxl: %s", error); + } +#else + croak ("load_jxl: jpeg-xl not enabled at compile time"); +#endif +} + OUTPUT: + RETVAL + +GdkPixbuf_noinc * +decode_jpeg (SV *image_data, int thumbnail = 0, int iw = 0, int ih = 0) CODE: { struct jpeg_decompress_struct cinfo; struct jpg_err_mgr jerr; - guchar *data; int rs; - FILE *fp; volatile GdkPixbuf *pb = 0; + STRLEN data_len; + guchar *data = SvPVbyte (image_data, data_len); RETVAL = 0; - fp = fopen (SvPVbyte_nolen (path), "rb"); - - if (!fp) - XSRETURN_UNDEF; - perlinterp_release (); cinfo.err = jpeg_std_error (&jerr.err); @@ -413,7 +682,6 @@ if ((rs = setjmp (jerr.setjmp_buffer))) { - fclose (fp); jpeg_destroy_decompress (&cinfo); if (pb) @@ -423,9 +691,12 @@ XSRETURN_UNDEF; } + if (!data_len) + longjmp (jerr.setjmp_buffer, 4); + jpeg_create_decompress (&cinfo); + cv_jpeg_mem_src (&cinfo, data, data_len); - jpeg_stdio_src (&cinfo, fp); jpeg_read_header (&cinfo, TRUE); cinfo.dct_method = JDCT_DEFAULT; @@ -514,7 +785,6 @@ } jpeg_finish_decompress (&cinfo); - fclose (fp); jpeg_destroy_decompress (&cinfo); perlinterp_acquire (); } @@ -583,7 +853,7 @@ STRLEN plen; U8 *path = (U8 *)SvPV (pathsv, plen); U8 *pend = path + plen; - U8 dst [plen * 6 * 3], *dstp = dst; + U8 dst [plen * 8 * 3], *dstp = dst; while (path < pend) { @@ -595,11 +865,12 @@ *dstp++ = *path++ + ('a' - 'A'); else if (ch >= '0' && ch <= '9') { + /* version sort, up to 8 digits */ STRLEN el, nl = 0; while (*path >= '0' && *path <= '9' && path < pend) path++, nl++; - for (el = nl; el < 6; el++) + for (el = nl; el < 8; el++) *dstp++ = '0'; memcpy (dstp, path - nl, nl);