-
Notifications
You must be signed in to change notification settings - Fork 0
/
xvjp2k.c
1326 lines (1216 loc) · 50.3 KB
/
xvjp2k.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* 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.
*
* 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.
*
* Compared to the IJG JPEG Library used in file "xvjpeg.c", one must solve
* several problems for color images when marrying JasPer to XV.
*
* 1. JPEG 2000 files can represent a wide range of image sizes, resolutions,
* and color spaces, not all of which can be easily rendered "as is" on a
* 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.
*
* XXX Things to do:
*
* 1. In "LoadJP{2,C}()" try to handle the "quick" option, which requests
* faster loading of a reduced-size image for the visual schnauzer. The
* schnauzer's icon size is currently 80x60, so the old "xvjpeg.c" module tries
* to produce a 2X (160x120) image. Can we do the same?
*
* 2. In "StoreJP2K()", JasPer Library Version 1.701 apparently has no API to
* let the XV global "picComments" string be inserted in a JPEG 2000 comment
* record. If the library ever gets fixed, enhance our code accordingly.
*
* --Scott Marovich <[email protected]>, Hewlett-Packard Laboratories,
* January 2005.
*/
#include "copyright.h"
#define NEEDSARGS
#include "xv.h"
#ifdef HAVE_JP2K
#include <jasper/jasper.h>
/* 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;}
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 char *SetBuf(FILE *f)
{
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 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;
}
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);
/* "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!"};
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;
}
/* 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.
IMPLEMENTATION NOTES: The following dimensions create a tall, thin window
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
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.
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 */
static BUTT button[J_NBUTT];
static CBUTT chkbut[J_NCHKB];
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);
/* 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 */
{
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);
}
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);