commit 736add2ab9539eae5892dd7e9ec00a7511dbf815 Author: Michael Adams Date: Sun Jan 30 22:15:05 2022 -0800 Rewrote much of the JPEG-2000 driver, which employs the JasPer library, so that this driver correctly initializes the library and does not directly invoke internal entry points in the library. Also, added support for the upcoming 3.0.0 release of JasPer. Added a check on a strncpy invocation in order to avoid an ASan failure. Updated the revision date (REVDATE) and version string (VERSTR). Added Michael Adams to the list of contributors. --- copyright.h +++ copyright.h @@ -100,6 +100,9 @@ * Tavis Ormandy (taviso gentoo.org) * Werner Fink (http://www.suse.de/~werner/) * + * Portions copyright 2022 by the following contributors: + * Michael D. Adams (http://github.com/mdadams) + * * Other credits are as listed on the XV Downloads page or in the respective * patches (e.g., the jp-extension patches or within the PNG patch). * --- xvdir.c +++ xvdir.c @@ -1387,7 +1387,11 @@ void SetDirFName(st) static void setFName(st) const char *st; { - strncpy(filename, st, (size_t) MAXFNLEN-1); + /* Prevent an ASan failure. */ + /* Why is this code being called with filename == st??? */ + if (filename != st) { + strncpy(filename, st, (size_t) MAXFNLEN-1); + } filename[MAXFNLEN-1] = '\0'; /* make sure it's terminated */ curPos = strlen(st); stPos = 0; enPos = curPos; --- xv.h +++ xv.h @@ -18,8 +18,9 @@ /* GRR 4th public jumbo F+E patch: 20070520 */ /* GRR 5th public jumbo F+E patch: 200xxxxx (probably mid-2009) */ /* FLMask 2.1 (modified) patch: 20090820 */ -#define REVDATE "version 3.10a-jumboFix+Enh of 20081216 (interim!)+FLmask" -#define VERSTR "3.10a-20081216+FLmask" +/* jasper patch 20220127 */ +#define REVDATE "version 3.10a-jumboFix+Enh+1 of 20220127" +#define VERSTR "3.10a-20220127" /* * uncomment the following, and modify for your site, but only if you've --- xvjp2k.c +++ xvjp2k.c @@ -2,20 +2,20 @@ * xvjp2k.c - I/O subroutines for JPEG 2000 format pictures * * This module is a "shim" between XV and a JPEG 2000 CODEC in the open-source - * JasPer Library created by Michael D. Adams; for more information, see the URL - * "http://www.ece.uvic.ca/~mdadams/jasper". We don't use most of the other - * facilities in this library, so it's better to link XV with a UNIX "archive" - * representation of it, not a DLL. + * JasPer Library created by Michael D. Adams; for more information, see the + * URL "http://www.ece.uvic.ca/~mdadams/jasper". We don't use most of the + * other facilities in this library, so it's better to link XV with a UNIX + * "archive" representation of it, not a DLL. * * JPEG 2000 files can be represented in either of two general ways: The * simplest representation is a "code stream", which often has a ".jpc" file * name suffix and is organized much like a classical JPEG file, except that * unfortunately, JPEG 2000 code streams indicate the no. of colors in an image * but no longer give any clues about its color space (e.g., RGB or YCbCr). - * Instead, there is now a semantically higher-level representation, which often - * has a ".jp2" file name suffix and encapsulates a "code stream" with (possibly - * a lot of) color-space information, optionally including things like ICC - * correction tables. + * Instead, there is now a semantically higher-level representation, which + * often has a ".jp2" file name suffix and encapsulates a "code stream" with + * (possibly a lot of) color-space information, optionally including things + * like ICC correction tables. * * Compared to the IJG JPEG Library used in file "xvjpeg.c", one must solve * several problems for color images when marrying JasPer to XV. @@ -25,9 +25,9 @@ * normal "X Windows" display, so we must carefully check a decoded image's * parameters in order to reject anything that we can't handle gracefully. * - * 2. JasPer prefers to decode/encode images using color-plane "slices", instead - * of interleaved pixels needed by "X Windows", so we must (de)interleave - * copies of the image buffer here. + * 2. JasPer prefers to decode/encode images using color-plane "slices", + * instead of interleaved pixels needed by "X Windows", so we must + * (de)interleave copies of the image buffer here. * * XXX Things to do: * @@ -42,599 +42,416 @@ * * --Scott Marovich , Hewlett-Packard Laboratories, * January 2005. + * + * Michael Aadams , University of Victoria, January 2022. + * The original code needed to be almost entirely rewritten due to its + * insistence on bypassing the JasPer library API and violating many + * preconditions in the usage of the API (which, of course, caused + * many problems as the JasPer library evolved over time). + * */ #include "copyright.h" -#define NEEDSARGS +#define NEEDSARGS #include "xv.h" #ifdef HAVE_JP2K #include -/* missing prototype in 1.701.0, sigh: */ -jas_stream_t *jas_stream_freopen PARM((const char *, const char *, FILE *)); -static const char *fbasename, /* File's base name, for error/warning msgs */ - bad_samp[] = "%s: can't read %d-plane %s file!", - fmode[] = "rb", - full_msg[] = "%s %s. (%ld bytes)", - jp2_kind[] = "JP2", - jpc_kind[] = "JPEG 2000", - load_msg[] = "Loading %dx%d %s %s (%ld bytes)...", - no_mem[] = "%s: can't read %s file - out of memory", - pixel_size[] = "%s: can't display %d-bit pixels!", - shrt_msg[] = "%dx%d %s %s. ", - truncated[] = "%s: Unexpected end of %s file", - read_err[] = "%s: I/O error reading %s file", - bad_dims[] = "%s: error in JPEG-2000 header (bad image size)"; - -/* We only want to override the JasPer Library's "jas_eprintf()" subroutine in - order to make it a "wrapper" around XV's own error-reporting subroutine, but - because of the way the former is currently packaged in JasPer Library Version - 1.701, we must override everything else packaged in the "jas_debug.o" module - with it, otherwise we get "duplicate definition" messages from the linker. -*/ -int jas_getdbglevel(void) {return 0;} -int jas_setdbglevel(int n) {return 0;} -int jas_memdump(FILE *fp,void *data,size_t len) {return 0;} +#define GIBI (1024ULL * 1024ULL * 1024ULL) -int jas_eprintf(const char *fmt,...) /* Handle JasPer Library message */ -{ - static char error[] = "error: ", warning[]= "warning: "; - va_list ap; - int kind = ISTR_WARNING; - char buffer[512]; - register char *p; - - /* Unlike the IJG JPEG Library, the JasPer Library current has no graceful way - for an application (= us!) to intercept its diagnostic messages and output - them using our own subroutines, so this ugly replacement for its output - subroutine will have to suffice. At Version 1.701, lthough the library's - own "jas_eprintf()" is a varargs subroutine, all calls currently pass just - 1 string with a Line Feed at the end and no "printf(3C)" arguments. Most - strings begin with "error: " or "warning: ", although a few have neither. - We try to translate these into the format preferred by XV, trimming any - trailing Line Feed character (ugh!). - */ - va_start(ap, fmt); - vsnprintf(p = buffer,512,fmt,ap); - va_end(ap); - while (*p++); - if (p[-2] == '\n') p[-2] = '\0'; - p = buffer; - if (strncmp(p,error,sizeof error) == 0) /* "error: ... " */ - { - kind = ISTR_WARNING; - p += sizeof error; - } - else /* "warning: ... " */ - if (strncmp(p,warning,sizeof warning) == 0) - { - kind = ISTR_INFO; - p += sizeof warning; - }; - SetISTR(kind,"%s: %s",fbasename,p); - return strlen(fmt); -} +static const char *fbasename, /* File's base name, for error/warning msgs */ + bad_samp[] = "%s: can't read %d-plane %s file!", fmode[] = "rb", + full_msg[] = "%s %s. (%ld bytes)", jp2_kind[] = "JP2", + jpc_kind[] = "JPEG 2000", load_msg[] = "Loading %dx%d %s %s (%ld bytes)...", + no_mem[] = "%s: can't read %s file - out of memory", + pixel_size[] = "%s: can't display %d-bit pixels!", + shrt_msg[] = "%dx%d %s %s. ", + truncated[] = "%s: Unexpected end of %s file", + read_err[] = "%s: I/O error reading %s file", + bad_dims[] = "%s: error in JPEG-2000 header (bad image size)"; -static char *SetBuf(FILE *f) +static int get_debug_level(void) { - char *buf; - register char *p; - - /* JPEG 2000 image files are apt to be large, but the buffer size allocated by - most implementations of the "C" Standard I/O Library is still ridiculously - small, typically 1 KB. We want to allocate a much larger buffer for higher - I/O efficiency, but the details are unfortunately a bit platform-specific. - Under UNIX systems with virtual memory, we want to encourage its internal - "physio()" subroutine by making the buffer an integral number of pages, - aligned on a page-multiple memory address boundary. Under HP-UX 11.1+ and - perhaps other operating-systems, a Standard I/O buffer is preceded by a - header whose size must also be taken into account. - */ -# ifndef IOBUFSIZ -# define IOBUFSIZ 65536 -# endif /* IOBUFSIZ */ -# ifdef __hpux -# define OVERHEAD sizeof(mbstate_t) -# endif /* __hpux */ -# ifndef OVERHEAD -# define OVERHEAD 0 -# endif /* OVERHEAD */ - -# ifdef NBPG - if (!(buf = p = malloc(NBPG+OVERHEAD+IOBUFSIZ))) return 0; - p = (char *)((unsigned long)p+NBPG-1 & ~(NBPG-1)); - p -= OVERHEAD; -# else /* not NBPG */ - if (!(buf = p = malloc(OVERHEAD+IOBUFSIZ))) return 0; - p += OVERHEAD; -# endif /* NBPG */ - setvbuf(f,p,_IOFBF,OVERHEAD+IOBUFSIZ); - return buf; -# undef OVERHEAD -# undef IOBUFSIZ + int debug_level = 0; + const char *cp; + if ((cp = getenv("XV_JASPER_DEBUG_LEVEL"))) { + debug_level = atoi(cp); + } + return debug_level; } -int LoadJPC(char *fname,register PICINFO *pinfo,int quick) -{ - jas_image_t *img; - jas_stream_t *str; - FILE *fp; - char *iobuf; - const char *s; - unsigned long filesize; - long w, h, npixels, bufsize; - int ok = 0, vstride; - register int i; - - /* Load a JPEG 2000 "code stream" image file into a pixel buffer for XV. - Unlike classical JPEG files, they have no clue about the image's color - space, so we check for 8-bit data samples but make no effort to check or - convert color spaces, and "what you see is what you get". For now, ignore - the "quick" option to return a reduced-resolution or -size image. Return 1 - on success, or 0 on failure. - */ - if (!(fp = xv_fopen(fname,fmode))) return 0; - fbasename = BaseName(fname); /* Input file's base name, for message(s) */ - if (!(iobuf = SetBuf(fp))) - { - (void)fclose(fp); - SetISTR(ISTR_WARNING,no_mem,fbasename,jpc_kind); - goto L3; - } - - /* Before telling the JasPer Library about this file, get its size for display - purposes. Non-UNIX systems don't necessarily simulate "stat(2)", so do it - crudely but portably by seeking to the end, then back to the beginning. - */ - fseek(fp,0L,2); - filesize = ftell(fp); - fseek(fp,0L,0); - - /* "jas_stream_close()" will eventually close the input file, so only do it - explicitly if no stream can be created: - */ - if (!(str = jas_stream_freopen(fname,fmode,fp))) /* nice if prototype... */ - { - (void)fclose(fp); - goto L3; - } - - /* It's not clear to me whether the following represents a JasPer Library "bug" - but it sure looks goofy: Unless a stream buffer is marked "read only", - which only happens when the stream's "fillbuf" method is called, even though - our buffers are always "read only", the library will try to flush out buffer - contents when the stream is destroyed, which makes it die a horrible death. - So, mark the stream buffer proactively: - */ - str->bufmode_ |= JAS_STREAM_RDBUF; /* We will only read the stream buffer */ - if (!(img = jpc_decode(str,0))) goto L2; - if ((vstride = jas_image_numcmpts(img))) /* num. color planes */ - { - - /* After the image-component streams created, they are left in a "write" - state with the streams' cursors positioned at their ends, so "seek" in - order to "read" each stream from its beginning. - */ - i = vstride; - while (--i >= 0) - if (jas_stream_seek(img->cmpts_[i]->stream_,0L,0)) - { - SetISTR(ISTR_WARNING,read_err,fbasename,jpc_kind); - goto L1; - } - } - w = jas_image_width(img); - h = jas_image_height(img); - - /* avoid buffer overflow */ - npixels = w * h; - bufsize = vstride * npixels; - if (w <= 0 || h <= 0 || npixels/w != h || bufsize/vstride != npixels) - { - (void)fclose(fp); - SetISTR(ISTR_WARNING,bad_dims,fbasename); - goto L1; - } - pinfo->normw = pinfo->w = w; - pinfo->normh = pinfo->h = h; - - /* Sanity-check the image's color space and no. of colors. For now, accept - only "generic" color spaces, not files needing image-specific color - correction, but fix that someday... - */ - switch (vstride) - { - default: - SetISTR(ISTR_WARNING,bad_samp,fbasename,vstride,jpc_kind); - goto L1; - case 1: - if ((i = jas_image_cmptprec(img,0)) != 8) /* not 8-bit pixels */ - { - SetISTR(ISTR_WARNING,pixel_size,fbasename,i); - goto L1; - } - s = "Greyscale"; - pinfo->type = PIC8; - pinfo->colType = F_GREYSCALE; - i = 256; /* Return fake indexed-color "map" */ - while (--i >= 0) pinfo->r[i] = pinfo->g[i] = pinfo->b[i] = i; - break; - case 3: - - /* BEWARE OF KLUDGE: If the image's color space is RGB, assume that the - data-sample precision for all color planes is the - same. If the color space is YCbCr, assume the luminance (Y = 0th) - component has the greatest precision, although the chrominance - (Cr = 1st, Cb = 2nd) components are usually sub-sampled. - */ - if ((i = jas_image_cmptprec(img,0)) != 8) /* not 24-bit pixels */ - { - SetISTR(ISTR_WARNING,pixel_size,fbasename,i*3); - goto L1; - } - s = "Color"; - pinfo->type = PIC24; - pinfo->colType = F_FULLCOLOR; - - /* XXX Unlike the IJG JPEG Library, the JasPer Library is apparently - unable to quantize colors or tell us whether the image's colors - were quantized by its creator, so it seems that we can't return a - color map for XV to potentially use 8-bit indexed color. If there - *is* an easy way to do it that escapes me, put the code here someday. - */ - } - if (!(pinfo->pic = (byte *)malloc(bufsize))) /* image buffer for XV */ - { - SetISTR(ISTR_WARNING,no_mem,fbasename,jpc_kind); - goto L1; - } - pinfo->frmType = F_JPC; - sprintf(pinfo->fullInfo,full_msg,s,jpc_kind,filesize); - sprintf(pinfo->shrtInfo,shrt_msg,pinfo->w,pinfo->h,s,jpc_kind); - SetISTR(ISTR_INFO,load_msg,pinfo->normw,pinfo->normh,s,jpc_kind,filesize); - if (vstride == 1) /* gray-scale image */ - { register jas_stream_t *c = img->cmpts_[0]->stream_; - register byte *p = pinfo->pic; - - /* Since this is a 1-plane image, avoid a lot of errant nonsense in the - JasPer Library by sequentially reading all of the data into our buffer - directly. - */ - do if ((i = (*c->ops_->read_)(c->obj_,(char *)p,bufsize)) <= 0) - { - SetISTR(ISTR_WARNING,i < 0 ? read_err : truncated,fbasename, - jpc_kind); - goto L1; - } - while ((p += i),(bufsize -= i) > 0); - } - else /* RGB color image */ - { - - /* Reading color images is inefficient because JPEG 2000 wants to partition - file data into separate image planes (colors), while XV wants data - samples from each plane to be interleaved as 3-byte pixels. Apparently - the fastest method consists of 3 passes through the XV image buffer, - into which we insert the bytes of each component. - */ - i = 0; - do /* each color component */ - { long npix = npixels; - register jas_stream_t *c = img->cmpts_[i]->stream_; - register byte *p = pinfo->pic + i; - - do /* each pixel */ - { register int b; - - if ((b = jas_stream_getc(c)) < 0) - { - SetISTR(ISTR_WARNING, - (c->flags_ & JAS_STREAM_EOF) ? truncated : read_err, - fbasename,jpc_kind); - goto L1; - } - *p = b; - } - while ((p += 3),--npix > 0); - } - while (++i <= 2); - } - ok = 1; /* Success! */ -L1: jas_image_destroy(img); -L2: (void)jas_stream_close(str); - free(iobuf); -L3: return ok; +#if (JAS_VERSION_MAJOR >= 3) +static int print_log(jas_logtype_t type, const char *format, va_list ap) { + const int buffer_size = 512; + char buffer[buffer_size]; + int count; + + int log_class = jas_logtype_getclass(type); + int kind; + switch (log_class) { + case JAS_LOGTYPE_CLASS_INFO: + kind = ISTR_INFO; + break; + case JAS_LOGTYPE_CLASS_WARN: + case JAS_LOGTYPE_CLASS_ERROR: + default: + kind = ISTR_WARNING; + break; + } + + count = vsnprintf(buffer, buffer_size, format, ap); + + if (log_class == JAS_LOGTYPE_CLASS_WARN || + log_class == JAS_LOGTYPE_CLASS_ERROR) { + if (get_debug_level() >= 1) { + jas_eprintf("%s", buffer); + } else { + for (int i = 0; i < count; ++i) { + if (buffer[i] == '\n') { + buffer[i] = ' '; + } + } + SetISTR(kind, "%s: %s", fbasename, buffer); + } + } else { + jas_eprintf("%s", buffer); + } + return count; } +#endif -int LoadJP2(char *fname,register PICINFO *pinfo,int quick) -{ - jas_image_t *img; - jas_stream_t *str; - FILE *fp; - char *iobuf; - const char *s; - unsigned long filesize; - long w, h, npixels, bufsize; - int ok = 0, vstride; - register int i; - - /* Load a JPEG 2000 JP2 image file into a pixel buffer for XV, doing any - necessary color-space conversion to end up with 8-bit gray scale or 24-bit - RGB. For now, ignore the "quick" option to return a reduced-resolution - or -size image. Return 1 on success, or 0 on failure. - */ - if (!(fp = xv_fopen(fname,fmode))) return 0; - fbasename = BaseName(fname); /* Input file's base name, for message(s) */ - if (!(iobuf = SetBuf(fp))) - { - (void)fclose(fp); - SetISTR(ISTR_WARNING,no_mem,fbasename,jpc_kind); - goto L3; - } - - /* Before telling the JasPer Library about this file, get its size for display - purposes. Non-UNIX systems don't necessarily simulate "stat(2)", so do it - crudely but portably by seeking to the end, then back to the beginning. - */ - fseek(fp,0L,2); - filesize = ftell(fp); - fseek(fp,0L,0); +static int LoadJP2K(char *fname, register PICINFO *pinfo, int quick, + bool jpc_format) { + jas_image_t *img = 0; + jas_stream_t *str = 0; + FILE *fp; + const char *s; + unsigned long filesize; + long w, h, npixels, bufsize; + int vstride; + register int i; + jas_matrix_t *data = 0; + + int ret = 1; + + int debug_level = get_debug_level(); +#if (JAS_VERSION_MAJOR >= 3) + size_t max_mem = jas_get_total_mem_size(); + if (!max_mem) { + max_mem = GIBI; + } + jas_conf_clear(); + jas_conf_set_max_mem_usage(max_mem); + jas_init_library(); + jas_init_thread(); + jas_set_vlogmsgf(print_log); +#else + jas_init(); +#endif +#if (JAS_VERSION_MAJOR >= 3) + jas_set_debug_level(debug_level); +#else + jas_setdbglevel(debug_level); +#endif + + if (!(fp = xv_fopen(fname, fmode))) { + return 0; + } + /* Input file's base name, for message(s) */ + fbasename = BaseName(fname); + + /* Compute file size is portable way. */ + fseek(fp, 0L, 2); + filesize = ftell(fp); + fseek(fp, 0L, 0); + +#if (JAS_VERSION_MAJOR >= 3) + /* + This approach will not work in JasPer prior to 3.0.0 due to a bug in + the stream code. + */ + if (!(str = jas_stream_freopen(fname,fmode,fp))) { + fclose(fp); + ret = 0; + goto done; + } + + /* It's not clear to me whether the following represents a JasPer Library + "bug" but it sure looks goofy: Unless a stream buffer is marked "read + only", which only happens when the stream's "fillbuf" method is called, + even though our buffers are always "read only", the library will try to + flush out buffer contents when the stream is destroyed, which makes it + die a horrible death. So, mark the stream buffer proactively: + */ + str->bufmode_ |= JAS_STREAM_RDBUF; /* We will only read the stream buffer */ +#else + { + if (!(str = jas_stream_memopen(0, 0))) { + ret = 0; + goto done; + } + const size_t buffer_size = 1024; + char buffer[buffer_size]; + for (;;) { + size_t count; + count = fread(buffer, 1, buffer_size, fp); + if (!count) { + if (!feof(fp)) { + ret = 0; + goto done; + } + break; + } + if (jas_stream_write(str, buffer, count) != count) { + ret = 0; + goto done; + } + } + jas_stream_rewind(str); + } +#endif + + const jas_image_fmtinfo_t *fmtinfo = jas_image_lookupfmtbyname( + jpc_format ? "jpc" : "jp2"); + assert(fmtinfo); + if (!(img = jas_image_decode(str, fmtinfo->id, 0))) { + ret = 0; + goto done; + } + + w = jas_image_width(img); + h = jas_image_height(img); + vstride = jas_image_numcmpts(img); + + /* avoid buffer overflow */ + npixels = w * h; + bufsize = vstride * npixels; + if (w <= 0 || h <= 0 || npixels / w != h || bufsize / vstride != npixels) { + (void)fclose(fp); + SetISTR(ISTR_WARNING, bad_dims, fbasename); + ret = 0; + goto done; + } + pinfo->normw = pinfo->w = w; + pinfo->normh = pinfo->h = h; + + /* Sanity-check the image's color space and no. of colors. For now, accept + only "generic" color spaces, not files needing image-specific color + correction, but fix that someday... + */ + switch (vstride) { + static char color_space[] = {"%s: invalid color space!"}; + + default: + SetISTR(ISTR_WARNING, bad_samp, fbasename, vstride, jp2_kind); + ret = 0; + goto done; + case 1: + if (!jas_clrspc_isunknown(i = jas_image_clrspc(img)) && + jas_clrspc_fam(i) != JAS_CLRSPC_FAM_GRAY) { + SetISTR(ISTR_WARNING, color_space, fbasename); + ret = 0; + goto done; + } + if ((i = jas_image_cmptprec(img, 0)) != 8) /* not 8-bit pixels */ + { + SetISTR(ISTR_WARNING, pixel_size, fbasename, i); + ret = 0; + goto done; + } + s = "Greyscale"; + pinfo->type = PIC8; + pinfo->colType = F_GREYSCALE; + i = 256; /* Return fake indexed-color "map" */ + while (--i >= 0) + pinfo->r[i] = pinfo->g[i] = pinfo->b[i] = i; + break; + case 3: + if (jas_clrspc_isunknown(i = jas_image_clrspc(img))) { + SetISTR(ISTR_WARNING, color_space, fbasename); + ret = 0; + goto done; + } + if (jas_clrspc_fam(i) != JAS_CLRSPC_FAM_RGB) { + jas_image_t *oimg; + jas_cmprof_t *profile; + + /* Here's where the JasPer Library really shines. The only color + space that XV handles is RGB, so if that's not what our image + uses, then to quote Capt. Kirk: "Make it so!" + */ + if (!(profile = jas_cmprof_createfromclrspc(JAS_CLRSPC_SRGB))) { + SetISTR(ISTR_WARNING, "%s: can't create RGB profile", + fbasename); + ret = 0; + goto done; + } + img = + jas_image_chclrspc(oimg = img, profile, JAS_CMXFORM_INTENT_PER); + jas_cmprof_destroy(profile); + if (!img) /* Oops! We failed, so restore original image */ + { + img = oimg; + SetISTR(ISTR_WARNING, "%s: can't convert to RGB", fbasename); + ret = 0; + goto done; + } + jas_image_destroy(oimg); + } + + /* BEWARE OF KLUDGE: If the image's color space is RGB, assume that the + data-sample precision for all color planes is the + same. If the color space is YCbCr, assume the luminance (Y = 0th) + component has the greatest precision, although the chrominance + (Cr = 1st, Cb = 2nd) components are usually sub-sampled. + */ + if ((i = jas_image_cmptprec(img, 0)) != 8) /* not 24-bit pixels */ + { + SetISTR(ISTR_WARNING, pixel_size, fbasename, i * 3); + ret = 0; + goto done; + } + s = "Color"; + pinfo->type = PIC24; + pinfo->colType = F_FULLCOLOR; + + /* XXX Unlike the IJG JPEG Library, the JasPer Library is apparently + unable to quantize colors or tell us whether the image's colors + were quantized by its creator, so it seems that we can't return a + color map for XV to potentially use 8-bit indexed color. If there + *is* an easy way to do it that escapes me, put the code here someday. + */ + } + + /* image buffer for XV */ + if (!(pinfo->pic = (byte *)malloc(bufsize))) + { + SetISTR(ISTR_WARNING, no_mem, fbasename, jp2_kind); + ret = 0; + goto done; + } + pinfo->frmType = F_JP2; + sprintf(pinfo->fullInfo, full_msg, s, jp2_kind, filesize); + sprintf(pinfo->shrtInfo, shrt_msg, pinfo->w, pinfo->h, s, jp2_kind); + SetISTR(ISTR_INFO, load_msg, pinfo->normw, pinfo->normh, s, jp2_kind, + filesize); + + /* Copy the sample data from the JasPer image to the xv image. */ + { + int num_comps = vstride; + int width = w; + int height = h; + data = jas_matrix_create(height, width); + assert(data); + for (int comp_ind = 0; comp_ind < num_comps; ++comp_ind) { + if (jas_image_readcmpt(img, comp_ind, 0, 0, width, height, data)) { + ret = 0; + goto done; + } + unsigned char *buffer; + jas_seqent_t *src; + unsigned char *dst; + dst = pinfo->pic + comp_ind; + for (int yy = 0; yy < height; ++yy) { + src = jas_matrix_getvref(data, yy); + for (int xx = 0; xx < width; ++xx) { + *dst = *src; + ++src; + dst += num_comps; + } + } + } + } + + /* Success! */ + ret = 1; + +done: + if (data) { + jas_matrix_destroy(data); + } + if (img) { + jas_image_destroy(img); + } + if (str) { + jas_stream_close(str); + } +#if (JAS_VERSION_MAJOR >= 3) + jas_cleanup_thread(); + jas_cleanup_library(); +#else + jas_cleanup(); +#endif + return ret; +} - /* "jas_stream_close()" will eventually close the input file, so only do it - explicitly if no stream can be created: - */ - if (!(str = jas_stream_freopen(fname,fmode,fp))) - { - (void)fclose(fp); - goto L3; - } - - /* It's not clear to me whether the following represents a JasPer Library "bug" - but it sure looks goofy: Unless a stream buffer is marked "read only", - which only happens when the stream's "fillbuf" method is called, even though - our buffers are always "read only", the library will try to flush out buffer - contents when the stream is destroyed, which makes it die a horrible death. - So, mark the stream buffer proactively: - */ - str->bufmode_ |= JAS_STREAM_RDBUF; /* We will only read the stream buffer */ - if (!(img = jp2_decode(str,0))) goto L2; - if ((vstride = jas_image_numcmpts(img))) /* num. color planes */ - { - - /* After the image-component streams created, they are left in a "write" - state with the streams' cursors positioned at their ends, so "seek" in - order to "read" each stream from its beginning. - */ - i = vstride; - while (--i >= 0) - if (jas_stream_seek(img->cmpts_[i]->stream_,0L,0)) - { - SetISTR(ISTR_WARNING,read_err,fbasename,jp2_kind); - goto L1; - } - } - w = jas_image_width(img); - h = jas_image_height(img); - - /* avoid buffer overflow */ - npixels = w * h; - bufsize = vstride * npixels; - if (w <= 0 || h <= 0 || npixels/w != h || bufsize/vstride != npixels) - { - (void)fclose(fp); - SetISTR(ISTR_WARNING,bad_dims,fbasename); - goto L1; - } - pinfo->normw = pinfo->w = w; - pinfo->normh = pinfo->h = h; - - /* Sanity-check the image's color space and no. of colors. For now, accept - only "generic" color spaces, not files needing image-specific color - correction, but fix that someday... - */ - switch (vstride) - { static char color_space[]={"%s: invalid color space!"}; +int LoadJP2(char *fname, register PICINFO *pinfo, int quick) { + return LoadJP2K(fname, pinfo, quick, false); +} - default: - SetISTR(ISTR_WARNING,bad_samp,fbasename,vstride,jp2_kind); - goto L1; - case 1: - if ( !jas_clrspc_isunknown(i = jas_image_clrspc(img)) - && jas_clrspc_fam(i) != JAS_CLRSPC_FAM_GRAY - ) - { - SetISTR(ISTR_WARNING,color_space,fbasename); - goto L1; - } - if ((i = jas_image_cmptprec(img,0)) != 8) /* not 8-bit pixels */ - { - SetISTR(ISTR_WARNING,pixel_size,fbasename,i); - goto L1; - } - s = "Greyscale"; - pinfo->type = PIC8; - pinfo->colType = F_GREYSCALE; - i = 256; /* Return fake indexed-color "map" */ - while (--i >= 0) pinfo->r[i] = pinfo->g[i] = pinfo->b[i] = i; - break; - case 3: - if (jas_clrspc_isunknown(i = jas_image_clrspc(img))) - { - SetISTR(ISTR_WARNING,color_space,fbasename); - goto L1; - } - if (jas_clrspc_fam(i) != JAS_CLRSPC_FAM_RGB) - { jas_image_t *oimg; - jas_cmprof_t *profile; - - /* Here's where the JasPer Library really shines. The only color - space that XV handles is RGB, so if that's not what our image - uses, then to quote Capt. Kirk: "Make it so!" - */ - if (!(profile = jas_cmprof_createfromclrspc(JAS_CLRSPC_SRGB))) - { - SetISTR(ISTR_WARNING,"%s: can't create RGB profile", - fbasename); - goto L1; - } - img = jas_image_chclrspc( oimg = img - , profile - , JAS_CMXFORM_INTENT_PER - ); - jas_cmprof_destroy(profile); - if (!img) /* Oops! We failed, so restore original image */ - { - img = oimg; - SetISTR(ISTR_WARNING,"%s: can't convert to RGB",fbasename); - goto L1; - } - jas_image_destroy(oimg); - } - - /* BEWARE OF KLUDGE: If the image's color space is RGB, assume that the - data-sample precision for all color planes is the - same. If the color space is YCbCr, assume the luminance (Y = 0th) - component has the greatest precision, although the chrominance - (Cr = 1st, Cb = 2nd) components are usually sub-sampled. - */ - if ((i = jas_image_cmptprec(img,0)) != 8) /* not 24-bit pixels */ - { - SetISTR(ISTR_WARNING,pixel_size,fbasename,i*3); - goto L1; - } - s = "Color"; - pinfo->type = PIC24; - pinfo->colType = F_FULLCOLOR; - - /* XXX Unlike the IJG JPEG Library, the JasPer Library is apparently - unable to quantize colors or tell us whether the image's colors - were quantized by its creator, so it seems that we can't return a - color map for XV to potentially use 8-bit indexed color. If there - *is* an easy way to do it that escapes me, put the code here someday. - */ - } - if (!(pinfo->pic = (byte *)malloc(bufsize))) /* image buffer for XV */ - { - SetISTR(ISTR_WARNING,no_mem,fbasename,jp2_kind); - goto L1; - } - pinfo->frmType = F_JP2; - sprintf(pinfo->fullInfo,full_msg,s,jp2_kind,filesize); - sprintf(pinfo->shrtInfo,shrt_msg,pinfo->w,pinfo->h,s,jp2_kind); - SetISTR(ISTR_INFO,load_msg,pinfo->normw,pinfo->normh,s,jp2_kind,filesize); - if (vstride == 1) /* gray-scale image */ - { register jas_stream_t *c = img->cmpts_[0]->stream_; - register byte *p = pinfo->pic; - - /* Since this is a 1-plane image, avoid a lot of errant nonsense in the - JasPer Library by sequentially reading all of the data into our buffer - directly. - */ - do if ((i = (*c->ops_->read_)(c->obj_,(char *)p,bufsize)) <= 0) - { - SetISTR(ISTR_WARNING,i < 0 ? read_err : truncated,fbasename, - jp2_kind); - goto L1; - } - while ((p += i),(bufsize -= i) > 0); - } - else /* RGB color image */ - { - - /* Reading color images is inefficient because JPEG 2000 wants to partition - file data into separate image planes (colors), while XV wants data - samples from each plane to be interleaved as 3-byte pixels. Apparently - the fastest method consists of 3 passes through the XV image buffer, - into which we insert the bytes of each component. - */ - i = 0; - do /* each color component */ - { long npix = npixels; - register jas_stream_t *c = img->cmpts_[i]->stream_; - register byte *p = pinfo->pic + i; - - do /* each pixel */ - { register int b; - - if ((b = jas_stream_getc(c)) < 0) - { - SetISTR(ISTR_WARNING, - (c->flags_ & JAS_STREAM_EOF) ? truncated : read_err, - fbasename,jp2_kind); - goto L1; - } - *p = b; - } - while ((p += 3),--npix > 0); - } - while (++i <= 2); - } - ok = 1; /* Success! */ -L1: jas_image_destroy(img); -L2: (void)jas_stream_close(str); - free(iobuf); -L3: return ok; +int LoadJPC(char *fname, register PICINFO *pinfo, int quick) { + return LoadJP2K(fname, pinfo, quick, true); } /* The following variables and subroutines are used when writing a JPEG 2000 file, which is done mainly using call-backs from "X Windows" widgets. The most complicated part of this interface is: managing interactions with a - window to request the boat-loads of options that the JasPer Library supports. - Start by defining subwindow sizes, plus indices into several arrays of - corresponding widget-state variables. + window to request the boat-loads of options that the JasPer Library + supports. Start by defining subwindow sizes, plus indices into several + arrays of corresponding widget-state variables. IMPLEMENTATION NOTES: The following dimensions create a tall, thin window - which appears to have considerable empty space at the + which appears to have considerable empty space at the bottom. Before you complain, click the Precinct Height menu button in order - to the tall pop-up subwindow that it generates. If the parent window is made - shorter, then this pop-up will be clipped, which is an ugly nuisance. I + to the tall pop-up subwindow that it generates. If the parent window is + made shorter, then this pop-up will be clipped, which is an ugly nuisance. I don't know how to make the pop-up visible outside its parent's borders; do - you? If there's some way to make "X Windows" do this, then we might consider - making the parent shorter. + you? If there's some way to make "X Windows" do this, then we might + consider making the parent shorter. Note that there is currently no mechanism to program the no. of intermediate layers used by the encoder, or their rates. This is potentially a large and complicated data-entry problem, and perhaps someday we can invent a clever solution using the rest of the parent window's space. */ -# define JP2KW 275 /* Window width, in pixels */ -# define JP2KH 400 /* Window height, in pixels */ -# define BUTTW 51 /* Button width, in pixels (odd for half-toning) */ -# define BUTTH 20 /* Button height, in pixels */ -# define MENUW 75 /* Menu-button width, in pixels (odd for half-toning) */ -# define MENUH 24 /* Menu-button height, in pixels */ -# define RBUTH 20 /* Radio button height, in pixels */ -# define RBUTW 51 /* Radio button width, in pixels (odd for half-toning) */ -# define TEXTH (LINEHIGH+5) /* Text subwindow height, in pixels */ -# define TEXTW 75 /* Text subwindow width, in pixels */ - -# define J_BOK 0 /* Boolean "Ok" button */ -# define J_BCANC 1 /* Boolean "Cancel" button */ -# define J_NBUTT 2 /* No. of regular button widgets */ - -# define J_CSOP 0 /* Boolean encoding-style option buttons */ -# define J_CEPH 1 -# define J_CLAZY 2 -# define J_CTERM 3 -# define J_CSEGS 4 -# define J_CVCAU 5 -# define J_CPTRM 6 -# define J_CRSTP 7 -# define J_NCHKB 8 /* No. of check-box button widgets */ - -# define J_MCBXW 0 /* 1-of-N menu-selection buttons */ -# define J_MCBXH 1 -# define J_MPREW 2 -# define J_MPREH 3 -# define J_MPROG 4 -# define J_NMENU 5 /* No. of menu-button widgets */ - -# define J_TGBIT 0 /* (Unsigned numeric) string subwindows */ -# define J_TRES 1 -# define J_TRATE 2 -# define J_NTEXT 3 /* No. of text subwindows */ +#define JP2KW 275 /* Window width, in pixels */ +#define JP2KH 400 /* Window height, in pixels */ +#define BUTTW 51 /* Button width, in pixels (odd for half-toning) */ +#define BUTTH 20 /* Button height, in pixels */ +#define MENUW 75 /* Menu-button width, in pixels (odd for half-toning) */ +#define MENUH 24 /* Menu-button height, in pixels */ +#define RBUTH 20 /* Radio button height, in pixels */ +#define RBUTW 51 /* Radio button width, in pixels (odd for half-toning) */ +#define TEXTH (LINEHIGH + 5) /* Text subwindow height, in pixels */ +#define TEXTW 75 /* Text subwindow width, in pixels */ + +#define J_BOK 0 /* Boolean "Ok" button */ +#define J_BCANC 1 /* Boolean "Cancel" button */ +#define J_NBUTT 2 /* No. of regular button widgets */ + +#define J_CSOP 0 /* Boolean encoding-style option buttons */ +#define J_CEPH 1 +#define J_CLAZY 2 +#define J_CTERM 3 +#define J_CSEGS 4 +#define J_CVCAU 5 +#define J_CPTRM 6 +#define J_CRSTP 7 +#define J_NCHKB 8 /* No. of check-box button widgets */ + +#define J_MCBXW 0 /* 1-of-N menu-selection buttons */ +#define J_MCBXH 1 +#define J_MPREW 2 +#define J_MPREH 3 +#define J_MPROG 4 +#define J_NMENU 5 /* No. of menu-button widgets */ + +#define J_TGBIT 0 /* (Unsigned numeric) string subwindows */ +#define J_TRES 1 +#define J_TRATE 2 +#define J_NTEXT 3 /* No. of text subwindows */ static BUTT button[J_NBUTT]; static CBUTT chkbut[J_NCHKB]; @@ -642,685 +459,753 @@ static MBUTT menu[J_NMENU]; static RBUTT *radio; static Window text[J_NTEXT]; static int colorType, format, textval[J_NTEXT]; -static const char *ProgList[]={"lrcp","rlcp","rpcl","pcrl","cprl"}; - -void CreateJP2KW(void) -{ - static const char EXP2_0[] ={ "1"}, /* Successive powers of 2 */ - EXP2_1[] ={ "2"}, - EXP2_2[] ={ "4"}, - EXP2_3[] ={ "8"}, - EXP2_4[] ={ "16"}, - EXP2_5[] ={ "32"}, - EXP2_6[] ={ "64"}, - EXP2_7[] ={ "128"}, - EXP2_8[] ={ "256"}, - EXP2_9[] ={ "512"}, - EXP2_10[]={ "1024"}, - EXP2_11[]={ "2048"}, - EXP2_12[]={ "4096"}, - EXP2_13[]={ "8192"}, - EXP2_14[]={"16384"}, - EXP2_15[]={"32768"}; - static const char *CBoxList[]= - { - EXP2_1 ,EXP2_2 ,EXP2_3 ,EXP2_4 ,EXP2_5,EXP2_6 ,EXP2_7 ,EXP2_8 ,EXP2_9, - EXP2_10,EXP2_11 - }; - static const char *PrecList[]= - { - EXP2_0,EXP2_1,EXP2_2 ,EXP2_3 ,EXP2_4 ,EXP2_5 ,EXP2_6 ,EXP2_7 , - EXP2_8,EXP2_9,EXP2_10,EXP2_11,EXP2_12,EXP2_13,EXP2_14,EXP2_15 - }; - static const char hstr[]={"Height"}, wstr[]={"Width"}; - - if (!(jp2kW = CreateWindow( "xvjp2k" - , "XVjp2k" - , 0 - , JP2KW - , JP2KH - , infofg - , infobg - , 0 - ) - ) - ) FatalError("can't create JPEG 2000 window!"); - XSelectInput(theDisp,jp2kW,ExposureMask|ButtonPressMask|KeyPressMask); - - /* Create a row of 2 boolean-valued, regular buttons ("Ok" and "Cancel") in the - window's bottom right corner. - */ - BTCreate(&button[J_BOK ],jp2kW, - JP2KW-2*BUTTW-20,JP2KH-10-BUTTH-1,BUTTW,BUTTH, - "Ok" ,infofg,infobg,hicol,locol); - BTCreate(&button[J_BCANC],jp2kW, - JP2KW- BUTTW-10,JP2KH-10-BUTTH-1,BUTTW,BUTTH, - "Cancel",infofg,infobg,hicol,locol); +static const char *ProgList[] = {"lrcp", "rlcp", "rpcl", "pcrl", "cprl"}; - /* Create a vertical column of 8 boolean-valued, check-box buttons (for - encoding-style options) down the window's left side. - */ - CBCreate(&chkbut[J_CSOP] ,jp2kW, - 10,10+ASCENT+SPACING+2*LINEHIGH+0*BUTTH, - "sop" ,infofg,infobg,hicol,locol); - CBCreate(&chkbut[J_CEPH] ,jp2kW, - 10,10+ASCENT+SPACING+2*LINEHIGH+1*BUTTH, - "eph" ,infofg,infobg,hicol,locol); - CBCreate(&chkbut[J_CLAZY],jp2kW, - 10,10+ASCENT+SPACING+2*LINEHIGH+2*BUTTH, - "lazy" ,infofg,infobg,hicol,locol); - CBCreate(&chkbut[J_CTERM],jp2kW, - 10,10+ASCENT+SPACING+2*LINEHIGH+3*BUTTH, - "termall" ,infofg,infobg,hicol,locol); - CBCreate(&chkbut[J_CSEGS],jp2kW, - 10,10+ASCENT+SPACING+2*LINEHIGH+4*BUTTH, - "segsym" ,infofg,infobg,hicol,locol); - CBCreate(&chkbut[J_CVCAU],jp2kW, - 10,10+ASCENT+SPACING+2*LINEHIGH+5*BUTTH, - "vcausal" ,infofg,infobg,hicol,locol); - CBCreate(&chkbut[J_CPTRM],jp2kW, - 10,10+ASCENT+SPACING+2*LINEHIGH+6*BUTTH, - "pterm" ,infofg,infobg,hicol,locol); - CBCreate(&chkbut[J_CRSTP],jp2kW, - 10,10+ASCENT+SPACING+2*LINEHIGH+7*BUTTH, - "resetprob",infofg,infobg,hicol,locol); - /* Create text subwindows for unsigned decimal integer values. */ - - text[J_TGBIT] = XCreateSimpleWindow(theDisp,jp2kW, - JP2KW-TEXTW-10,10+ASCENT+SPACING+2*LINEHIGH+3*MENUH+0*TEXTH,TEXTW,TEXTH, - 1,infofg,infobg); - XSelectInput(theDisp,text[J_TGBIT],ExposureMask|KeyPressMask); - text[J_TRES ] = XCreateSimpleWindow(theDisp,jp2kW, - JP2KW-TEXTW-10,10+ASCENT+SPACING+2*LINEHIGH+3*MENUH+1*TEXTH,TEXTW,TEXTH, - 1,infofg,infobg); - XSelectInput(theDisp,text[J_TRES ],ExposureMask|KeyPressMask); - text[J_TRATE] = XCreateSimpleWindow(theDisp,jp2kW, - JP2KW-TEXTW-10,10+ASCENT+SPACING+2*LINEHIGH+3*MENUH+2*TEXTH,TEXTW,TEXTH, - 1,infofg,infobg); - XSelectInput(theDisp,text[J_TRATE],ExposureMask|KeyPressMask); - - /* Create a row of 2 boolean-valued radio buttons (for the "Rate" subwindow - value's unit of measure). The 1st button is "selected" by default. - */ - radio = RBCreate(0,jp2kW, - JP2KW-19*RBUTW/8-10,10+ASCENT+SPACING+2*LINEHIGH+3*MENUH+3*TEXTH+4, - "Percent",infofg,infobg,hicol,locol); - (void)RBCreate(radio,jp2kW, - JP2KW-1*RBUTW-10 ,10+ASCENT+SPACING+2*LINEHIGH+3*MENUH+3*TEXTH+4, - "Bytes",infofg,infobg,hicol,locol); - - /* Create pop-up menu-selection buttons after mapping the above subwindows, - since we don't want the pop-up menus mapped unless the corresponding button - is selected. - */ - XMapSubwindows(theDisp,jp2kW); - MBCreate(&menu[J_MCBXW],jp2kW, - JP2KW-2*MENUW-10,10+ASCENT+SPACING+2*LINEHIGH+0*MENUH,MENUW,MENUH, - wstr ,CBoxList,sizeof CBoxList/sizeof *CBoxList,infofg,infobg, - hicol,locol); - MBCreate(&menu[J_MCBXH],jp2kW, - JP2KW-2*MENUW-10,10+ASCENT+SPACING+2*LINEHIGH+1*MENUH,MENUW,MENUH, - hstr ,CBoxList,sizeof CBoxList/sizeof *CBoxList,infofg,infobg, - hicol,locol); - MBCreate(&menu[J_MPREW],jp2kW, - JP2KW-1*MENUW-10,10+ASCENT+SPACING+2*LINEHIGH+0*MENUH,MENUW,MENUH, - wstr ,PrecList,sizeof PrecList/sizeof *PrecList,infofg,infobg, - hicol,locol); - MBCreate(&menu[J_MPREH],jp2kW, - JP2KW-1*MENUW-10,10+ASCENT+SPACING+2*LINEHIGH+1*MENUH,MENUW,MENUH, - hstr ,PrecList,sizeof PrecList/sizeof *PrecList,infofg,infobg, - hicol,locol); - MBCreate(&menu[J_MPROG],jp2kW, - JP2KW-1*MENUW-10,10+ASCENT+SPACING+2*LINEHIGH+2*MENUH,MENUW,MENUH, - "Order",ProgList,sizeof ProgList/sizeof *ProgList,infofg,infobg, - hicol,locol); - - /* Initialize values represented by widgets, which should correspond to default - compiled into the JasPer Library. Unfortunately, as of Version 1.701 there - is no easy way for an application to extract these from the library, so the - following code might get out of sync over time: - */ - menu[J_MCBXW].hascheck = 1; menu[J_MCBXW].flags[ 5] = 1; - menu[J_MCBXH].hascheck = 1; menu[J_MCBXH].flags[ 5] = 1; - menu[J_MPREW].hascheck = 1; menu[J_MPREW].flags[15] = 1; - menu[J_MPREH].hascheck = 1; menu[J_MPREH].flags[15] = 1; - menu[J_MPROG].hascheck = 1; menu[J_MPROG].flags[ 0] = 1; - textval[J_TGBIT] = 2; /* No. of guard bits */ - textval[J_TRES ] = 6; /* Max. no. of resolution levels */ - textval[J_TRATE] = 100; /* Rate = 100% */ +void CreateJP2KW(void) { + static const char EXP2_0[] = {"1"}, /* Successive powers of 2 */ + EXP2_1[] = {"2"}, EXP2_2[] = {"4"}, EXP2_3[] = {"8"}, EXP2_4[] = {"16"}, + EXP2_5[] = {"32"}, EXP2_6[] = {"64"}, EXP2_7[] = {"128"}, + EXP2_8[] = {"256"}, EXP2_9[] = {"512"}, + EXP2_10[] = {"1024"}, EXP2_11[] = {"2048"}, + EXP2_12[] = {"4096"}, EXP2_13[] = {"8192"}, + EXP2_14[] = {"16384"}, EXP2_15[] = {"32768"}; + static const char *CBoxList[] = {EXP2_1, EXP2_2, EXP2_3, EXP2_4, + EXP2_5, EXP2_6, EXP2_7, EXP2_8, + EXP2_9, EXP2_10, EXP2_11}; + static const char *PrecList[] = { + EXP2_0, EXP2_1, EXP2_2, EXP2_3, EXP2_4, EXP2_5, EXP2_6, EXP2_7, + EXP2_8, EXP2_9, EXP2_10, EXP2_11, EXP2_12, EXP2_13, EXP2_14, EXP2_15}; + static const char hstr[] = {"Height"}, wstr[] = {"Width"}; + + if (!(jp2kW = CreateWindow("xvjp2k", "XVjp2k", 0, JP2KW, JP2KH, infofg, + infobg, 0))) + FatalError("can't create JPEG 2000 window!"); + XSelectInput(theDisp, jp2kW, ExposureMask | ButtonPressMask | KeyPressMask); + + /* Create a row of 2 boolean-valued, regular buttons ("Ok" and "Cancel") in + the window's bottom right corner. + */ + BTCreate(&button[J_BOK], jp2kW, JP2KW - 2 * BUTTW - 20, + JP2KH - 10 - BUTTH - 1, BUTTW, BUTTH, "Ok", infofg, infobg, hicol, + locol); + BTCreate(&button[J_BCANC], jp2kW, JP2KW - BUTTW - 10, + JP2KH - 10 - BUTTH - 1, BUTTW, BUTTH, "Cancel", infofg, infobg, + hicol, locol); + + /* Create a vertical column of 8 boolean-valued, check-box buttons (for + encoding-style options) down the window's left side. + */ + CBCreate(&chkbut[J_CSOP], jp2kW, 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 0 * BUTTH, "sop", infofg, + infobg, hicol, locol); + CBCreate(&chkbut[J_CEPH], jp2kW, 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 1 * BUTTH, "eph", infofg, + infobg, hicol, locol); + CBCreate(&chkbut[J_CLAZY], jp2kW, 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 2 * BUTTH, "lazy", infofg, + infobg, hicol, locol); + CBCreate(&chkbut[J_CTERM], jp2kW, 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 3 * BUTTH, "termall", + infofg, infobg, hicol, locol); + CBCreate(&chkbut[J_CSEGS], jp2kW, 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 4 * BUTTH, "segsym", infofg, + infobg, hicol, locol); + CBCreate(&chkbut[J_CVCAU], jp2kW, 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 5 * BUTTH, "vcausal", + infofg, infobg, hicol, locol); + CBCreate(&chkbut[J_CPTRM], jp2kW, 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 6 * BUTTH, "pterm", infofg, + infobg, hicol, locol); + CBCreate(&chkbut[J_CRSTP], jp2kW, 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 7 * BUTTH, "resetprob", + infofg, infobg, hicol, locol); + /* Create text subwindows for unsigned decimal integer values. */ + + text[J_TGBIT] = XCreateSimpleWindow(theDisp, jp2kW, JP2KW - TEXTW - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + + 3 * MENUH + 0 * TEXTH, + TEXTW, TEXTH, 1, infofg, infobg); + XSelectInput(theDisp, text[J_TGBIT], ExposureMask | KeyPressMask); + text[J_TRES] = XCreateSimpleWindow(theDisp, jp2kW, JP2KW - TEXTW - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + + 3 * MENUH + 1 * TEXTH, + TEXTW, TEXTH, 1, infofg, infobg); + XSelectInput(theDisp, text[J_TRES], ExposureMask | KeyPressMask); + text[J_TRATE] = XCreateSimpleWindow(theDisp, jp2kW, JP2KW - TEXTW - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + + 3 * MENUH + 2 * TEXTH, + TEXTW, TEXTH, 1, infofg, infobg); + XSelectInput(theDisp, text[J_TRATE], ExposureMask | KeyPressMask); + + /* Create a row of 2 boolean-valued radio buttons (for the "Rate" subwindow + value's unit of measure). The 1st button is "selected" by default. + */ + radio = RBCreate(0, jp2kW, JP2KW - 19 * RBUTW / 8 - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 3 * MENUH + + 3 * TEXTH + 4, + "Percent", infofg, infobg, hicol, locol); + (void)RBCreate(radio, jp2kW, JP2KW - 1 * RBUTW - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 3 * MENUH + + 3 * TEXTH + 4, + "Bytes", infofg, infobg, hicol, locol); + + /* Create pop-up menu-selection buttons after mapping the above subwindows, + since we don't want the pop-up menus mapped unless the corresponding + button is selected. + */ + XMapSubwindows(theDisp, jp2kW); + MBCreate(&menu[J_MCBXW], jp2kW, JP2KW - 2 * MENUW - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 0 * MENUH, MENUW, MENUH, + wstr, CBoxList, sizeof CBoxList / sizeof *CBoxList, infofg, infobg, + hicol, locol); + MBCreate(&menu[J_MCBXH], jp2kW, JP2KW - 2 * MENUW - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 1 * MENUH, MENUW, MENUH, + hstr, CBoxList, sizeof CBoxList / sizeof *CBoxList, infofg, infobg, + hicol, locol); + MBCreate(&menu[J_MPREW], jp2kW, JP2KW - 1 * MENUW - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 0 * MENUH, MENUW, MENUH, + wstr, PrecList, sizeof PrecList / sizeof *PrecList, infofg, infobg, + hicol, locol); + MBCreate(&menu[J_MPREH], jp2kW, JP2KW - 1 * MENUW - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 1 * MENUH, MENUW, MENUH, + hstr, PrecList, sizeof PrecList / sizeof *PrecList, infofg, infobg, + hicol, locol); + MBCreate(&menu[J_MPROG], jp2kW, JP2KW - 1 * MENUW - 10, + 10 + ASCENT + SPACING + 2 * LINEHIGH + 2 * MENUH, MENUW, MENUH, + "Order", ProgList, sizeof ProgList / sizeof *ProgList, infofg, + infobg, hicol, locol); + + /* Initialize values represented by widgets, which should correspond to + default compiled into the JasPer Library. Unfortunately, as of + Version 1.701 there is no easy way for an application to extract these + from the library, so the following code might get out of sync over time: + */ + menu[J_MCBXW].hascheck = 1; + menu[J_MCBXW].flags[5] = 1; + menu[J_MCBXH].hascheck = 1; + menu[J_MCBXH].flags[5] = 1; + menu[J_MPREW].hascheck = 1; + menu[J_MPREW].flags[15] = 1; + menu[J_MPREH].hascheck = 1; + menu[J_MPREH].flags[15] = 1; + menu[J_MPROG].hascheck = 1; + menu[J_MPROG].flags[0] = 1; + textval[J_TGBIT] = 2; /* No. of guard bits */ + textval[J_TRES] = 6; /* Max. no. of resolution levels */ + textval[J_TRATE] = 100; /* Rate = 100% */ } -void JP2KSaveParams(int fmt,char *fname,int col) /* Save output-file parms */ +void JP2KSaveParams(int fmt, char *fname, int col) /* Save output-file parms */ { - format = fmt; /* Desired file format: F_JPC|F_JP2 */ - fbasename = fname; /* ->Output file path */ - colorType = col; /* Desired color space: F_GREYSCALE|... */ + format = fmt; /* Desired file format: F_JPC|F_JP2 */ + fbasename = fname; /* ->Output file path */ + colorType = col; /* Desired color space: F_GREYSCALE|... */ } -static void StoreJP2K(char *options) -{ - static jas_image_cmptparm_t parm[3]= /* Image parameters */ - {{0,0,1,1,0,0,8,0},{0,0,1,1,0,0,8,0},{0,0,1,1,0,0,8,0}}; - static char nomem[]={"StoreJP2K: out of memory\n"}, - write[]={"w"}; - jas_image_t *img; - jas_stream_t *str; - FILE *fp, *fp2; - byte *pic, *r, *g, *b; - const char *filename; - char *iobuf = 0; - unsigned long imagesize; - jas_clrspc_t color_space; - int nc, w, h, pfree, ptype, error = 1; - register int i; - - /* This is a generic subroutine for writing JPEG 2000 image files using the - JasPer Library. Our argument is an ASCII string, containing a Space (" ")- - separated sequence of encoder options that currently aren't well documented. - Most of the work is identical for both ".jpc" and ".jp2" files. Start by - verifying that the output file can be opened, then get an image buffer from - XV and begin crunching it into a suitable form for the JasPer Library. - */ - if (!(fp = OpenOutFile(filename = fbasename))) return; /* Oops! */ - setbuf(fp,0); /* We don't really use this file pointer for I/O; see below */ - fbasename = BaseName(filename); - WaitCursor(); - pic = GenSavePic(&ptype,&w,&h,&pfree,&nc,&r,&g,&b); - imagesize = w*h; - if (ptype == PIC24) imagesize *= 3; - - /* As an optimization to save file space, even if our user didn't ask to store - a gray-scale image, check whether we could and, if so, do it anyway. - */ - if (colorType != F_GREYSCALE) /* can we force a gray-scale image? */ - { - if (ptype == PIC8) - { - i = nc; - while (--i >= 0 && r[i] == g[i] && r[i] == b[i]); - } - else /* PIC24 */ - { register byte *p = pic + imagesize; - - while ((p -= 3) >= pic && p[0] == p[1] && p[0] == p[2]); - i = p-pic; - }; - if (i < 0) colorType = F_GREYSCALE; /* We made it all the way through */ - }; - - /* If XV is currently color-mapping the image, make a color-mapped copy so that - the map needn't be transmitted in the output file. - */ - if ((i = (colorType != F_GREYSCALE) << 1 | (ptype != PIC8)) != 3) - { byte *tmp = pic, *last = pic + imagesize; - register byte *from = tmp, *to = pic; - - if (!(pic = (byte *)malloc(imagesize))) FatalError(nomem); - switch (i) - { - - /* Color-map 8->8 bit image. */ - - case 0: do - { - i = *from; - *to++ = MONO(r[i],g[i],b[i]); - } - while (++from < last); - break; - - /* Color-map 24->8 bit image. */ - - case 1: do *to++ = MONO(from[0],from[1],from[2]); - while ((from += 3) < last); - break; - - /* Color-map 8->24 bit image. */ - - case 2: do - { - i = *from; - *to++ = r[i]; *to++ = g[i]; *to++ = b[i]; - } - while (++from < last); - break; - }; - if (pfree) free(tmp); /* Release the original image buffer if we can */ - pfree = 1; /* Let the modified buffer be released later */ - }; - - /* Initialize various image-file parameter variables. */ - - parm[0].width = w; - parm[0].height = h; - if (colorType == F_GREYSCALE) /* gray-scale image */ - { - ptype = 1; /* No. of color planes */ - color_space = JAS_CLRSPC_SGRAY; - } - else /* RGB color image */ - { - ptype = 3; /* No. of color planes */ - color_space = JAS_CLRSPC_SRGB; - parm[2].width = parm[1].width = parm[0].width; - parm[2].height = parm[1].height = parm[0].height; - }; - - /* Now comes a egregious hack: The JasPer Library will eventually want to - close the output file that it writes, but since XV opened the file, XV also - thinks it has the right to close the file! In order to pacify them both, - we duplicate the file pointer and let the JasPer Library have it, while we - retain the original for XV. - - XXX This code is very UNIX-specific; what's an equivalent hack for Windows? - */ - if (!(fp2 = (i = dup(fileno(fp))) >= 0 ? fdopen(i,write) : 0)) - FatalError("StoreJP2K: can't duplicate output file pointer\n"); - if (!(iobuf = SetBuf(fp2))) - { - (void)fclose(fp2); - FatalError(nomem); - }; - - /* Hand our output file to the JasPer Library and create an image object. - "jas_stream_close()" will eventually close our output file, so only do it - explicitly if no stream can be created. If everything looks copacetic, - then write our buffer contents to the image components' streams. - */ - if (!(str = jas_stream_freopen(filename,write,fp2))) - { - (void)fclose(fp2); - FatalError("StoreJP2K: can't open output stream\n"); - }; - if (!(img = jas_image_create(ptype,parm,color_space))) goto L2; - if (ptype == 1) - { register jas_stream_t *c = img->cmpts_[0]->stream_; - register byte *p = pic; - - /* Since this is a 1-plane image, avoid a lot of errant nonsense in the - JasPer Library by sequentially writing all of the data directly from our - buffer. - */ - jas_image_setcmpttype(img,0,JAS_IMAGE_CT_GRAY_Y); - img->cmpts_[0]->type_ = JAS_IMAGE_CT_GRAY_Y; - do if ((i = (*c->ops_->write_)(c->obj_,(char *)p,imagesize)) <= 0) - goto L1; - while ((p += i),(imagesize -= i) > 0); - if (jas_stream_flush(c) < 0) goto L1; - } - else /* RGB color image */ - { - - /* Writing color images is inefficient because JPEG 2000 wants to partition - file data into separate image planes (colors), while XV wants data - samples from each plane to be interleaved as 3-byte pixels. Apparently - the fastest method consists of 3 passes through the XV image buffer, - from which we extract the bytes of each component. - */ - i = 0; - do /* each color component */ - { long npix = imagesize/3; - register jas_stream_t *c = img->cmpts_[i]->stream_; - register byte *p = pic + i; - - jas_image_setcmpttype(img,i,i+JAS_IMAGE_CT_RGB_R); - do if (jas_stream_putc(c,*p) < 0) goto L1; - while ((p += 3),--npix > 0); - if (jas_stream_flush(c) < 0) goto L1; - } - while (++i <= 2); - }; - if ( (*(format == F_JPC ? jpc_encode : jp2_encode))(img,str,options) >= 0 - && jas_stream_flush(str) >= 0 - ) error = 0; /* Success! */ -L1: jas_image_destroy(img); -L2: (void)jas_stream_close(str); - if (iobuf) free(iobuf); - if (pfree) free(pic); - if (!CloseOutFile(fp,filename,error)) DirBox(0); - SetCursors(-1); +static void StoreJP2K(char *options) { + static jas_image_cmptparm_t parm[3] = /* Image parameters */ + {{0, 0, 1, 1, 0, 0, 8, 0}, + {0, 0, 1, 1, 0, 0, 8, 0}, + {0, 0, 1, 1, 0, 0, 8, 0}}; + static char nomem[] = {"StoreJP2K: out of memory\n"}, write[] = {"w"}; + jas_image_t *img = 0; + jas_stream_t *str = 0; + FILE *fp = 0; + FILE *fp2 = 0; + byte *pic, *r, *g, *b; + const char *filename; + unsigned long imagesize; + jas_clrspc_t color_space; + int nc, w, h; + int pfree, ptype; + int error = 1; + register int i; + jas_matrix_t *data = 0; + int debug_level = get_debug_level(); + +#if (JAS_VERSION_MAJOR >= 3) + size_t max_mem = jas_get_total_mem_size(); + if (!max_mem) { + max_mem = GIBI; + } + jas_conf_clear(); + jas_conf_set_max_mem_usage(max_mem); + jas_init_library(); + jas_init_thread(); + jas_set_vlogmsgf(print_log); + jas_set_debug_level(debug_level); +#else + jas_init(); + jas_setdbglevel(debug_level); +#endif + + /* This is a generic subroutine for writing JPEG 2000 image files using the + JasPer Library. Our argument is an ASCII string, containing a Space (" + ")- separated sequence of encoder options that currently aren't well + documented. Most of the work is identical for both ".jpc" and ".jp2" + files. Start by verifying that the output file can be opened, then get an + image buffer from XV and begin crunching it into a suitable form for the + JasPer Library. + */ + if (!(fp = OpenOutFile(filename = fbasename))) + return; /* Oops! */ + setbuf(fp, + 0); /* We don't really use this file pointer for I/O; see below */ + fbasename = BaseName(filename); + WaitCursor(); + pic = GenSavePic(&ptype, &w, &h, &pfree, &nc, &r, &g, &b); + assert(ptype == PIC8 || ptype == PIC24); + imagesize = w * h; + if (ptype == PIC24) + imagesize *= 3; + + assert(colorType == F_GREYSCALE || colorType == F_FULLCOLOR); +#if 1 + /* As an optimization to save file space, even if our user didn't ask to + store a gray-scale image, check whether we could and, if so, do it + anyway. + */ + if (colorType != F_GREYSCALE) /* can we force a gray-scale image? */ + { + if (ptype == PIC8) { + i = nc; + while (--i >= 0 && r[i] == g[i] && r[i] == b[i]) {} + } else { + assert(ptype == PIC24); + /* PIC24 */ + byte *p = pic + imagesize; + while ((p -= 3) >= pic && p[0] == p[1] && p[0] == p[2]) {} + i = p - pic; + }; + if (i < 0) { + colorType = F_GREYSCALE; /* We made it all the way through */ + } + }; +#endif + + /* Now comes a egregious hack: The JasPer Library will eventually want to + close the output file that it writes, but since XV opened the file, XV + also thinks it has the right to close the file! In order to pacify them + both, we duplicate the file pointer and let the JasPer Library have it, + while we retain the original for XV. + + XXX This code is very UNIX-specific; what's an equivalent hack for + Windows? + */ + { + int fd = dup(fileno(fp)); + if (fd < 0) { + FatalError("StoreJP2K: can't duplicate output file pointer\n"); + } + if (!(fp2 = fdopen(fd, write))) { + FatalError("StoreJP2K: can't duplicate output file pointer\n"); + } + } + + /* Hand our output file to the JasPer Library and create an image object. + "jas_stream_close()" will eventually close our output file, so only do it + explicitly if no stream can be created. If everything looks copacetic, + then write our buffer contents to the image components' streams. + */ + if (!(str = jas_stream_freopen(filename, write, fp2))) { + (void)fclose(fp2); + FatalError("StoreJP2K: can't open output stream\n"); + }; + fp2 = 0; + + int num_comps = (colorType == F_GREYSCALE) ? 1 : 3; + if (num_comps == 3) { + color_space = JAS_CLRSPC_SRGB; + } else { + color_space = JAS_CLRSPC_SGRAY; + } + for (int comp_ind = 0; comp_ind < num_comps; ++comp_ind) { + parm[comp_ind].tlx = 0; + parm[comp_ind].tly = 0; + parm[comp_ind].hstep = 1; + parm[comp_ind].vstep = 1; + parm[comp_ind].width = w; + parm[comp_ind].height = h; + parm[comp_ind].prec = 8; + parm[comp_ind].sgnd = 0; + } + if (!(img = jas_image_create(num_comps, parm, color_space))) { + goto L2; + } + if (get_debug_level() >= 1) { + fprintf(stderr, "num_comps %d width %d height %d\n", num_comps, w, h); + fprintf(stderr, "colorType %d\n", colorType); + fprintf(stderr, "ptype %d\n", ptype); + } + + { + if (!(data = jas_matrix_create(h, w))) { + goto done; + } + if (num_comps == 3) { + for (int comp_ind = 0; comp_ind < num_comps; ++comp_ind) { + int comp_type; + switch (comp_ind) { + case 0: + comp_type = JAS_IMAGE_CT_RGB_R; + break; + case 1: + comp_type = JAS_IMAGE_CT_RGB_G; + break; + case 2: + comp_type = JAS_IMAGE_CT_RGB_B; + break; + } + jas_image_setcmpttype(img, comp_ind, comp_type); + unsigned char *buffer; + unsigned char *src; + jas_seqent_t *dst; + src = pic + comp_ind; + for (int y = 0; y < h; ++y) { + dst = jas_matrix_getvref(data, y); + for (int x = 0; x < w; ++x) { + *dst = *src; + ++dst; + src += num_comps; + } + } + if (jas_image_writecmpt(img, comp_ind, 0, 0, w, h, data)) { + goto L1; + } + } + } else { + int mode = (ptype == PIC8) ? 0 : 1; + assert(num_comps == 1); + jas_image_setcmpttype(img, 0, JAS_IMAGE_CT_GRAY_Y); + unsigned char *buffer; + unsigned char *src; + jas_seqent_t *dst; + src = pic; + for (int y = 0; y < h; ++y) { + dst = jas_matrix_getvref(data, y); + for (int x = 0; x < w; ++x) { + int v; + if (mode == 0) { + v = *src; + v = MONO(r[v], g[v], b[v]); + } else if (mode == 1) { + v = MONO(src[0], src[1], src[2]); + } + *dst = v; + ++dst; + ++src; + } + } + if (jas_image_writecmpt(img, 0, 0, 0, w, h, data)) { + goto L1; + } + } + } + + const jas_image_fmtinfo_t *fmtinfo = + jas_image_lookupfmtbyname(((format == F_JPC) ? "jpc" : "jp2")); + if (!jas_image_encode(img, str, fmtinfo->id, options) && + jas_stream_flush(str) >= 0) { + error = 0; /* Success! */ + } + +done: + if (data) { + jas_matrix_destroy(data); + } +L1: + if (img) { + jas_image_destroy(img); + } +L2: + if (str) { + jas_stream_close(str); + } + if (pfree) { + free(pic); + } + if (!CloseOutFile(fp, filename, error)) { + DirBox(0); + } + SetCursors(-1); + +#if (JAS_VERSION_MAJOR >= 3) + jas_cleanup_thread(); + jas_cleanup_library(); +#else + jas_cleanup(); +#endif } -void JP2KDialog(int vis) -{ - if ((jp2kUp = vis)) CenterMapWindow(jp2kW,0,0,JP2KW,JP2KH); - else XUnmapWindow(theDisp,jp2kW); +void JP2KDialog(int vis) { + if ((jp2kUp = vis)) + CenterMapWindow(jp2kW, 0, 0, JP2KW, JP2KH); + else + XUnmapWindow(theDisp, jp2kW); } -static void TWRedraw(Window w,unsigned int val) -{ - char buf[11]; - register int i; - - /* Draw a 1-line numeric text string in the specified window, representing the - argument value as a left-justified unsigned decimal integer, followed by a - "cursor" icon. - */ - sprintf(buf,"%u",val); - if (ctrlColor) XClearArea(theDisp,w,2,2,TEXTW-4,TEXTH-4,False); - else XClearWindow(theDisp,w); - Draw3dRect(w,0,0,TEXTW-1,TEXTH-1,R3D_IN,2,hicol,locol,infobg); - XSetForeground(theDisp,theGC,infofg); - XDrawString(theDisp,w,theGC,3,ASCENT+3,buf,i = strlen(buf)); - - /* Draw a "cursor" icon after the numeric string. */ - - i = XTextWidth(mfinfo,buf,i); - XDrawLine(theDisp,w,theGC,i+3,2 ,i+3,2+CHIGH+1); - XDrawLine(theDisp,w,theGC,i+3,2+CHIGH+1,i+5,2+CHIGH+3); - XDrawLine(theDisp,w,theGC,i+3,2+CHIGH+1,i+1,2+CHIGH+3); +static void TWRedraw(Window w, unsigned int val) { + char buf[11]; + register int i; + + /* Draw a 1-line numeric text string in the specified window, representing + the argument value as a left-justified unsigned decimal integer, followed + by a "cursor" icon. + */ + sprintf(buf, "%u", val); + if (ctrlColor) + XClearArea(theDisp, w, 2, 2, TEXTW - 4, TEXTH - 4, False); + else + XClearWindow(theDisp, w); + Draw3dRect(w, 0, 0, TEXTW - 1, TEXTH - 1, R3D_IN, 2, hicol, locol, infobg); + XSetForeground(theDisp, theGC, infofg); + XDrawString(theDisp, w, theGC, 3, ASCENT + 3, buf, i = strlen(buf)); + + /* Draw a "cursor" icon after the numeric string. */ + + i = XTextWidth(mfinfo, buf, i); + XDrawLine(theDisp, w, theGC, i + 3, 2, i + 3, 2 + CHIGH + 1); + XDrawLine(theDisp, w, theGC, i + 3, 2 + CHIGH + 1, i + 5, 2 + CHIGH + 3); + XDrawLine(theDisp, w, theGC, i + 3, 2 + CHIGH + 1, i + 1, 2 + CHIGH + 3); } -int JP2KCheckEvent(register XEvent *xev) -{ - int ok = 0; +int JP2KCheckEvent(register XEvent *xev) { + int ok = 0; - /* Check whether the argument "X Windows" event is for one of our subwindows. - If it is, handle the event and return 1; otherwise, return 0. - */ - if (!jp2kUp) return 0; - switch (xev->type) - { KeySym ks; - int len; - char buf[128]; - register int i; - - /* Throw away excess "expose" events for "dumb" windows. */ - - case Expose : -# define E ((XExposeEvent *)xev) - if (E->window == jp2kW) - { XRectangle rect; - - rect.x = E->x ; rect.y = E->y ; - rect.width = E->width; rect.height = E->height; - XSetClipRectangles(theDisp,theGC,0,0,&rect,1,Unsorted); - XSetForeground(theDisp,theGC,infofg); - XSetBackground(theDisp,theGC,infobg); - i = sizeof button/sizeof *button; - while (--i >= 0) BTRedraw(&button[i]); - i = sizeof chkbut/sizeof *chkbut; - while (--i >= 0) CBRedraw(&chkbut[i]); - i = sizeof menu/sizeof *menu; - while (--i >= 0) MBRedraw(&menu[i]); - RBRedraw(radio,-1); - DrawString(jp2kW,10,10+ASCENT,"Save JPEG 2000 File..."); - DrawString(jp2kW,10,10+ASCENT+2*LINEHIGH,"Style options:"); - DrawString(jp2kW,JP2KW-2*MENUW-10,10+ASCENT+2*LINEHIGH, - "Coding Blk"); - DrawString(jp2kW,JP2KW-1*MENUW-10,10+ASCENT+2*LINEHIGH, - " Precinct"); - DrawString(jp2kW, - JP2KW-2*MENUW-10,10+ASCENT+SPACING+3*LINEHIGH+2*MENUH, - "Progression:"); - DrawString(jp2kW, - JP2KW-2*TEXTW-10,10+ASCENT+SPACING+3*LINEHIGH+3*MENUH+0*TEXTH, - "Guard bits:"); - DrawString(jp2kW, - JP2KW-2*TEXTW-10,10+ASCENT+SPACING+3*LINEHIGH+3*MENUH+1*TEXTH, - "Res levels:"); - DrawString(jp2kW, - JP2KW-2*TEXTW-10,10+ASCENT+SPACING+3*LINEHIGH+3*MENUH+2*TEXTH, - "Rate:"); - XSetClipMask(theDisp,theGC,None); - ok = 1; - break; - }; - i = sizeof text/sizeof *text; - while (--i >= 0 && E->window != text[i]); - if (i >= 0) - { - TWRedraw(E->window,textval[i]); - ok = 1; - }; - break; -# undef E - - /* Check whether the user pressed one of our buttons. */ - - case ButtonPress: -# define E ((XButtonEvent *)xev) - if (E->button == Button1 && E->window == jp2kW) - { register int j; - - ok = 1; /* Check whether a regular button was pressed */ - i = sizeof button/sizeof *button; - while ( --i >= 0 - && !PTINRECT(E->x,E->y,button[i].x,button[i].y,button[i].w,button[i].h) - ); - if (i >= 0) /* our button was pressed */ - { char options[1024]; - register char *p; - register const char *q; - - if (!BTTrack(&button[i])) break; /* Ignore it */ - if (i != J_BOK) /* must be "Cancel" button */ - { - JP2KDialog(0); - break; - }; - - /* Our user hit the "Ok" button. At this point, we have an ugly - job to do: JasPer Library encoder options must be specified - as an ASCII string of Space (" ")-separated [=] - entries, so we must collect the values of all of our widgets - and subwindows, determine whether our user has requested any - non-default values, then build the string only for the JasPer - Library to immediately tear it apart again. Yechh! - */ - if ((unsigned)textval[J_TGBIT]-1 > 7) - { - OpenAlert("No. of guard bits must be 1-8"); - sleep(3); - CloseAlert(); - break; - }; - if ((unsigned)textval[J_TRES ] <= 0) - { - OpenAlert("Maximum resolution levels must be >= 1"); - sleep(3); - CloseAlert(); - break; - }; - - /* XXX Should we check and complain if the rate is zero? - JasPer Library Version 1.701 apparently accepts that - value, even though it seems kinda weird. - */ - p = options; - i = 0; - do if ((j = MBWhich(&menu[i])) != 5) - { static const char *parm[2] = - {"cblkwidth=%u", "cblkheight=%u"}; - - if (p > options) *p++ = ' '; - sprintf(p,parm[i-J_MCBXW],++j); - while (*++p); - } - while (++i <= J_MCBXH); - do if ((j = MBWhich(&menu[i])) < 15) - { static const char *parm[2] = - {"prcwidth=%u", "prcheight=%u"}; - - if (p > options) *p++ = ' '; - sprintf(p,parm[i-J_MPREW],j); - while (*++p); - } - while (++i <= J_MPREH); - if ((j = MBWhich(&menu[i]))) - { - if (p > options) *p++ = ' '; - *p++ = 'p'; *p++ = 'r'; *p++ = 'g'; *p = '='; - q = ProgList[j]; - while ((*++p = *q++)); - }; - if ((i = textval[J_TRES ]) != 6) - { - if (p > options) *p++ = ' '; - sprintf(p,"numrlvls=%u",i); - while (*++p); - }; - i = 0; - do if (chkbut[i].val) /* append this encoding option */ - { - if (p > options) *p++ = ' '; - q = chkbut[i].str; - while ((*p++ = *q++)); - *--p = '\000'; - } - while (++i < sizeof chkbut/sizeof *chkbut); - if ((i = textval[J_TGBIT]) != 2) - { - if (p > options) *p++ = ' '; - sprintf(p,"numgbits=%u",i); - while (*++p); - }; - if ((i = textval[J_TRATE]) != 100) - { - if (p > options) *p++ = ' '; - *p++ = 'r'; *p++ = 'a'; *p++ = 't'; *p++ = 'e'; - *p++ = '='; - if (i) /* non-zero rate */ - { - if (RBWhich(radio)) sprintf(p,"%uB",i); /* # Bytes */ - else /* the value is a percentage */ - { - if (i > 100) i = 200; /* => Raw size + 1 */ - sprintf(p,"%u.%.2u",i/100,i%100); - } - while (*++p); - } - else /* rate = 0 */ - { - - /* XXX This is apparently valid, since JasPer Library - Version 1.701 doesn't complain about it. - */ - *p++ = '0'; - *p = '\000'; - } - }; - StoreJP2K(options); /* Finally, do the *real* work! */ - JP2KDialog(0); - p = GetDirFullName(); - if (!ISPIPE(*p)) - { - XVCreatedFile(p); - StickInCtrlList(0); - }; - break; - }; - - /* See whether a check-box button was pressed. */ - - i = sizeof chkbut/sizeof *chkbut; - while (--i >= 0 && !CBClick(&chkbut[i],E->x,E->y)); - if (i >= 0) /* our button was pressed */ - { - (void)CBTrack(&chkbut[i]); - break; - }; - - /* See whether a menu button was pressed. */ - - i = sizeof menu/sizeof *menu; - while (--i >= 0 && !MBClick(&menu[i],E->x,E->y)); - if (i >= 0) /* our button was pressed */ - { register int j; - - if ((j = MBTrack(&menu[i])) >= 0) - { - switch (i) - { - - /* The JasPer Library constrains a coding box's area - (width x height) to be <= 4096 pixels, so if this - button-press would violate that condition, then - quietly limit the box's orthogonal dimension by just - enough to compensate. - */ - case J_MCBXH: - case J_MCBXW: if (MBWhich(&menu[!i]) + j >= 10) - MBSelect(&menu[!i],10 - j); - }; - MBSelect(&menu[i],j); - MBSetActive(&menu[i],1); - }; - break; - }; - - /* See whether a radio button was pressed. */ - - if ( (i = RBClick(radio,E->x,E->y)) >= 0 /* button pressed */ - && RBTrack(radio,i) - ) - { - RBSelect(radio,i); - RBSetActive(radio,i,1); - }; - break; - }; - XBell(theDisp,50); - break; -# undef E - - /* Translate a few key-press events into simulated button events. */ - - case KeyPress : -# define E ((XKeyEvent *)xev) - buf[len = XLookupString(E,buf,sizeof buf,&ks,0)] = '\000'; - RemapKeyCheck(ks,buf,&len); - if (E->window == jp2kW) - { - ok = 1; - if (len > 0) - switch (buf[0]) - { - case '\r' : - case '\n' : FakeButtonPress(&button[J_BOK ]); - break; - case '\033': FakeButtonPress(&button[J_BCANC]); - }; - break; - }; - i = sizeof text/sizeof *text; - while (--i >= 0 && E->window != text[i]); - if (i >= 0) /* a key was pressed in our text window */ - { int oldval = textval[i]; /* Save this subwindow's old value */ - register int c, j = 0; - - /* This is a *very* simple text-input editing loop that assembles an - an unsigned integer from successive ASCII decimal digits, typed - into one of our 1-line subwindows. For convenience, the only - character recognized is Backspace. - */ - ok = 1; - L: if (j >= len) break; - if ((unsigned)(c = buf[j++] - '0') <= 9) - { - TWRedraw(text[i],textval[i] = textval[i]*10 + c); - goto L; - }; - if ((c += '0') == '\b') - { - TWRedraw(text[i],textval[i] = textval[i]/10); - goto L; - }; - textval[i] = oldval; - }; - XBell(theDisp,50); -# undef E - }; - return ok; + /* Check whether the argument "X Windows" event is for one of our + subwindows. If it is, handle the event and return 1; otherwise, return 0. + */ + if (!jp2kUp) + return 0; + switch (xev->type) { + KeySym ks; + int len; + char buf[128]; + register int i; + + /* Throw away excess "expose" events for "dumb" windows. */ + + case Expose: +#define E ((XExposeEvent *)xev) + if (E->window == jp2kW) { + XRectangle rect; + + rect.x = E->x; + rect.y = E->y; + rect.width = E->width; + rect.height = E->height; + XSetClipRectangles(theDisp, theGC, 0, 0, &rect, 1, Unsorted); + XSetForeground(theDisp, theGC, infofg); + XSetBackground(theDisp, theGC, infobg); + i = sizeof button / sizeof *button; + while (--i >= 0) + BTRedraw(&button[i]); + i = sizeof chkbut / sizeof *chkbut; + while (--i >= 0) + CBRedraw(&chkbut[i]); + i = sizeof menu / sizeof *menu; + while (--i >= 0) + MBRedraw(&menu[i]); + RBRedraw(radio, -1); + DrawString(jp2kW, 10, 10 + ASCENT, "Save JPEG 2000 File..."); + DrawString(jp2kW, 10, 10 + ASCENT + 2 * LINEHIGH, "Style options:"); + DrawString(jp2kW, JP2KW - 2 * MENUW - 10, + 10 + ASCENT + 2 * LINEHIGH, "Coding Blk"); + DrawString(jp2kW, JP2KW - 1 * MENUW - 10, + 10 + ASCENT + 2 * LINEHIGH, " Precinct"); + DrawString(jp2kW, JP2KW - 2 * MENUW - 10, + 10 + ASCENT + SPACING + 3 * LINEHIGH + 2 * MENUH, + "Progression:"); + DrawString(jp2kW, JP2KW - 2 * TEXTW - 10, + 10 + ASCENT + SPACING + 3 * LINEHIGH + 3 * MENUH + + 0 * TEXTH, + "Guard bits:"); + DrawString(jp2kW, JP2KW - 2 * TEXTW - 10, + 10 + ASCENT + SPACING + 3 * LINEHIGH + 3 * MENUH + + 1 * TEXTH, + "Res levels:"); + DrawString(jp2kW, JP2KW - 2 * TEXTW - 10, + 10 + ASCENT + SPACING + 3 * LINEHIGH + 3 * MENUH + + 2 * TEXTH, + "Rate:"); + XSetClipMask(theDisp, theGC, None); + ok = 1; + break; + }; + i = sizeof text / sizeof *text; + while (--i >= 0 && E->window != text[i]) + ; + if (i >= 0) { + TWRedraw(E->window, textval[i]); + ok = 1; + }; + break; +#undef E + + /* Check whether the user pressed one of our buttons. */ + + case ButtonPress: +#define E ((XButtonEvent *)xev) + if (E->button == Button1 && E->window == jp2kW) { + register int j; + + ok = 1; /* Check whether a regular button was pressed */ + i = sizeof button / sizeof *button; + while (--i >= 0 && !PTINRECT(E->x, E->y, button[i].x, button[i].y, + button[i].w, button[i].h)) + ; + if (i >= 0) /* our button was pressed */ + { + char options[1024]; + register char *p; + register const char *q; + + if (!BTTrack(&button[i])) + break; /* Ignore it */ + if (i != J_BOK) /* must be "Cancel" button */ + { + JP2KDialog(0); + break; + }; + + /* Our user hit the "Ok" button. At this point, we have an ugly + job to do: JasPer Library encoder options must be specified + as an ASCII string of Space (" ")-separated [=] + entries, so we must collect the values of all of our widgets + and subwindows, determine whether our user has requested any + non-default values, then build the string only for the JasPer + Library to immediately tear it apart again. Yechh! + */ + if ((unsigned)textval[J_TGBIT] - 1 > 7) { + OpenAlert("No. of guard bits must be 1-8"); + sleep(3); + CloseAlert(); + break; + }; + if ((unsigned)textval[J_TRES] <= 0) { + OpenAlert("Maximum resolution levels must be >= 1"); + sleep(3); + CloseAlert(); + break; + }; + + /* XXX Should we check and complain if the rate is zero? + JasPer Library Version 1.701 apparently accepts that + value, even though it seems kinda weird. + */ + p = options; + i = 0; + do + if ((j = MBWhich(&menu[i])) != 5) { + static const char *parm[2] = {"cblkwidth=%u", + "cblkheight=%u"}; + + if (p > options) + *p++ = ' '; + sprintf(p, parm[i - J_MCBXW], ++j); + while (*++p) + ; + } + while (++i <= J_MCBXH); + do + if ((j = MBWhich(&menu[i])) < 15) { + static const char *parm[2] = {"prcwidth=%u", + "prcheight=%u"}; + + if (p > options) + *p++ = ' '; + sprintf(p, parm[i - J_MPREW], j); + while (*++p) + ; + } + while (++i <= J_MPREH); + if ((j = MBWhich(&menu[i]))) { + if (p > options) + *p++ = ' '; + *p++ = 'p'; + *p++ = 'r'; + *p++ = 'g'; + *p = '='; + q = ProgList[j]; + while ((*++p = *q++)) + ; + }; + if ((i = textval[J_TRES]) != 6) { + if (p > options) + *p++ = ' '; + sprintf(p, "numrlvls=%u", i); + while (*++p) + ; + }; + i = 0; + do + if (chkbut[i].val) /* append this encoding option */ + { + if (p > options) + *p++ = ' '; + q = chkbut[i].str; + while ((*p++ = *q++)) + ; + *--p = '\000'; + } + while (++i < sizeof chkbut / sizeof *chkbut); + if ((i = textval[J_TGBIT]) != 2) { + if (p > options) + *p++ = ' '; + sprintf(p, "numgbits=%u", i); + while (*++p) + ; + }; + if ((i = textval[J_TRATE]) != 100) { + if (p > options) + *p++ = ' '; + *p++ = 'r'; + *p++ = 'a'; + *p++ = 't'; + *p++ = 'e'; + *p++ = '='; + if (i) /* non-zero rate */ + { + if (RBWhich(radio)) + sprintf(p, "%uB", i); /* # Bytes */ + else /* the value is a percentage */ + { + if (i > 100) + i = 200; /* => Raw size + 1 */ + sprintf(p, "%u.%.2u", i / 100, i % 100); + } + while (*++p) + ; + } else /* rate = 0 */ + { + + /* XXX This is apparently valid, since JasPer Library + Version 1.701 doesn't complain about it. + */ + *p++ = '0'; + *p = '\000'; + } + }; + StoreJP2K(options); /* Finally, do the *real* work! */ + JP2KDialog(0); + p = GetDirFullName(); + if (!ISPIPE(*p)) { + XVCreatedFile(p); + StickInCtrlList(0); + }; + break; + }; + + /* See whether a check-box button was pressed. */ + + i = sizeof chkbut / sizeof *chkbut; + while (--i >= 0 && !CBClick(&chkbut[i], E->x, E->y)) + ; + if (i >= 0) /* our button was pressed */ + { + (void)CBTrack(&chkbut[i]); + break; + }; + + /* See whether a menu button was pressed. */ + + i = sizeof menu / sizeof *menu; + while (--i >= 0 && !MBClick(&menu[i], E->x, E->y)) + ; + if (i >= 0) /* our button was pressed */ + { + register int j; + + if ((j = MBTrack(&menu[i])) >= 0) { + switch (i) { + + /* The JasPer Library constrains a coding box's area + (width x height) to be <= 4096 pixels, so if this + button-press would violate that condition, then + quietly limit the box's orthogonal dimension by just + enough to compensate. + */ + case J_MCBXH: + case J_MCBXW: + if (MBWhich(&menu[!i]) + j >= 10) + MBSelect(&menu[!i], 10 - j); + }; + MBSelect(&menu[i], j); + MBSetActive(&menu[i], 1); + }; + break; + }; + + /* See whether a radio button was pressed. */ + + if ((i = RBClick(radio, E->x, E->y)) >= 0 /* button pressed */ + && RBTrack(radio, i)) { + RBSelect(radio, i); + RBSetActive(radio, i, 1); + }; + break; + }; + XBell(theDisp, 50); + break; +#undef E + + /* Translate a few key-press events into simulated button events. */ + + case KeyPress: +#define E ((XKeyEvent *)xev) + buf[len = XLookupString(E, buf, sizeof buf, &ks, 0)] = '\000'; + RemapKeyCheck(ks, buf, &len); + if (E->window == jp2kW) { + ok = 1; + if (len > 0) + switch (buf[0]) { + case '\r': + case '\n': + FakeButtonPress(&button[J_BOK]); + break; + case '\033': + FakeButtonPress(&button[J_BCANC]); + }; + break; + }; + i = sizeof text / sizeof *text; + while (--i >= 0 && E->window != text[i]) + ; + if (i >= 0) /* a key was pressed in our text window */ + { + int oldval = textval[i]; /* Save this subwindow's old value */ + register int c, j = 0; + + /* This is a *very* simple text-input editing loop that assembles an + an unsigned integer from successive ASCII decimal digits, typed + into one of our 1-line subwindows. For convenience, the only + character recognized is Backspace. + */ + ok = 1; + L: + if (j >= len) + break; + if ((unsigned)(c = buf[j++] - '0') <= 9) { + TWRedraw(text[i], textval[i] = textval[i] * 10 + c); + goto L; + }; + if ((c += '0') == '\b') { + TWRedraw(text[i], textval[i] = textval[i] / 10); + goto L; + }; + textval[i] = oldval; + }; + XBell(theDisp, 50); +#undef E + }; + return ok; } - /*******************************************/ -void -VersionInfoJP2K() /* GRR 20070304 */ +void VersionInfoJP2K() /* GRR 20070304 */ { - fprintf(stderr, " Compiled with libjasper %s; using libjasper %s.\n", - JAS_VERSION, jas_getversion()); + fprintf(stderr, " Compiled with libjasper %s; using libjasper %s.\n", + JAS_VERSION, jas_getversion()); } #endif /* HAVE_JP2K */