File Coverage

blib/lib/Image/ExifTool/Jpeg2000.pm
Criterion Covered Total %
statement 330 408 80.8
branch 155 290 53.4
condition 76 130 58.4
subroutine 12 12 100.0
pod 0 9 0.0
total 573 849 67.4


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: Jpeg2000.pm
3             #
4             # Description: Read JPEG 2000 meta information
5             #
6             # Revisions: 02/11/2005 - P. Harvey Created
7             # 06/22/2007 - PH Added write support (EXIF, IPTC and XMP only)
8             #
9             # References: 1) http://www.jpeg.org/public/fcd15444-2.pdf
10             # 2) ftp://ftp.remotesensing.org/jpeg2000/fcd15444-1.pdf
11             #------------------------------------------------------------------------------
12              
13             package Image::ExifTool::Jpeg2000;
14              
15 12     12   8942 use strict;
  12         33  
  12         534  
16 12     12   81 use vars qw($VERSION);
  12         29  
  12         673  
17 12     12   83 use Image::ExifTool qw(:DataAccess :Utils);
  12         33  
  12         74246  
18              
19             $VERSION = '1.33';
20              
21             sub ProcessJpeg2000Box($$$);
22             sub ProcessJUMD($$$);
23              
24             my %resolutionUnit = (
25             -3 => 'km',
26             -2 => '100 m',
27             -1 => '10 m',
28             0 => 'm',
29             1 => '10 cm',
30             2 => 'cm',
31             3 => 'mm',
32             4 => '0.1 mm',
33             5 => '0.01 mm',
34             6 => 'um',
35             );
36              
37             # map of where information is written in JPEG2000 image
38             my %jp2Map = (
39             IPTC => 'UUID-IPTC',
40             IFD0 => 'UUID-EXIF',
41             XMP => 'UUID-XMP',
42             'UUID-IPTC' => 'JP2',
43             'UUID-EXIF' => 'JP2',
44             'UUID-XMP' => 'JP2',
45             jp2h => 'JP2',
46             colr => 'jp2h',
47             ICC_Profile => 'colr',
48             IFD1 => 'IFD0',
49             EXIF => 'IFD0', # to write EXIF as a block
50             ExifIFD => 'IFD0',
51             GPS => 'IFD0',
52             SubIFD => 'IFD0',
53             GlobParamIFD => 'IFD0',
54             PrintIM => 'IFD0',
55             InteropIFD => 'ExifIFD',
56             MakerNotes => 'ExifIFD',
57             );
58              
59             # map of where information is written in a JXL image
60             my %jxlMap = (
61             IFD0 => 'Exif',
62             XMP => 'xml ',
63             'Exif' => 'JP2',
64             IFD1 => 'IFD0',
65             EXIF => 'IFD0', # to write EXIF as a block
66             ExifIFD => 'IFD0',
67             GPS => 'IFD0',
68             SubIFD => 'IFD0',
69             GlobParamIFD => 'IFD0',
70             PrintIM => 'IFD0',
71             InteropIFD => 'ExifIFD',
72             MakerNotes => 'ExifIFD',
73             );
74              
75             # UUID's for writable UUID directories (by tag name)
76             my %uuid = (
77             'UUID-EXIF' => 'JpgTiffExif->JP2',
78             'UUID-EXIF2' => '', # (flags a warning when writing)
79             'UUID-EXIF_bad' => '0', # (flags a warning when reading and writing)
80             'UUID-IPTC' => "\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38",
81             'UUID-XMP' => "\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac",
82             # (can't yet write GeoJP2 information)
83             # 'UUID-GeoJP2' => "\xb1\x4b\xf8\xbd\x08\x3d\x4b\x43\xa5\xae\x8c\xd7\xd5\xa6\xce\x03",
84             );
85              
86             # JPEG2000 codestream markers (ref ISO/IEC FCD15444-1/2)
87             my %j2cMarker = (
88             0x4f => 'SOC', # start of codestream
89             0x51 => 'SIZ', # image and tile size
90             0x52 => 'COD', # coding style default
91             0x53 => 'COC', # coding style component
92             0x55 => 'TLM', # tile-part lengths
93             0x57 => 'PLM', # packet length, main header
94             0x58 => 'PLT', # packet length, tile-part header
95             0x5c => 'QCD', # quantization default
96             0x5d => 'QCC', # quantization component
97             0x5e => 'RGN', # region of interest
98             0x5f => 'POD', # progression order default
99             0x60 => 'PPM', # packed packet headers, main
100             0x61 => 'PPT', # packed packet headers, tile-part
101             0x63 => 'CRG', # component registration
102             0x64 => 'CME', # comment and extension
103             0x90 => 'SOT', # start of tile-part
104             0x91 => 'SOP', # start of packet
105             0x92 => 'EPH', # end of packet header
106             0x93 => 'SOD', # start of data
107             # extensions (ref ISO/IEC FCD15444-2)
108             0x70 => 'DCO', # variable DC offset
109             0x71 => 'VMS', # visual masking
110             0x72 => 'DFS', # downsampling factor style
111             0x73 => 'ADS', # arbitrary decomposition style
112             # 0x72 => 'ATK', # arbitrary transformation kernels ?
113             0x78 => 'CBD', # component bit depth
114             0x74 => 'MCT', # multiple component transformation definition
115             0x75 => 'MCC', # multiple component collection
116             0x77 => 'MIC', # multiple component intermediate collection
117             0x76 => 'NLT', # non-linearity point transformation
118             );
119              
120             # JPEG 2000 "box" (ie. atom) names
121             # Note: only tags with a defined "Format" are extracted
122             %Image::ExifTool::Jpeg2000::Main = (
123             GROUPS => { 2 => 'Image' },
124             PROCESS_PROC => \&ProcessJpeg2000Box,
125             WRITE_PROC => \&ProcessJpeg2000Box,
126             PREFERRED => 1, # always add these tags when writing
127             NOTES => q{
128             The tags below are found in JPEG 2000 images and the JUMBF metadata in JPEG
129             images, but not all of these are extracted. Note that ExifTool currently
130             writes only EXIF, IPTC and XMP tags in Jpeg2000 images.
131             },
132             #
133             # NOTE: ONLY TAGS WITH "Format" DEFINED ARE EXTRACTED!
134             #
135             'jP ' => 'JP2Signature', # (ref 1)
136             "jP\x1a\x1a" => 'JP2Signature', # (ref 2)
137             prfl => 'Profile',
138             ftyp => {
139             Name => 'FileType',
140             SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::FileType' },
141             },
142             rreq => 'ReaderRequirements',
143             jp2h => {
144             Name => 'JP2Header',
145             SubDirectory => { },
146             },
147             # JP2Header sub boxes...
148             ihdr => {
149             Name => 'ImageHeader',
150             SubDirectory => {
151             TagTable => 'Image::ExifTool::Jpeg2000::ImageHeader',
152             },
153             },
154             bpcc => 'BitsPerComponent',
155             colr => {
156             Name => 'ColorSpecification',
157             SubDirectory => {
158             TagTable => 'Image::ExifTool::Jpeg2000::ColorSpec',
159             },
160             },
161             pclr => 'Palette',
162             cdef => 'ComponentDefinition',
163             'res '=> {
164             Name => 'Resolution',
165             SubDirectory => { },
166             },
167             # Resolution sub boxes...
168             resc => {
169             Name => 'CaptureResolution',
170             SubDirectory => {
171             TagTable => 'Image::ExifTool::Jpeg2000::CaptureResolution',
172             },
173             },
174             resd => {
175             Name => 'DisplayResolution',
176             SubDirectory => {
177             TagTable => 'Image::ExifTool::Jpeg2000::DisplayResolution',
178             },
179             },
180             jpch => {
181             Name => 'CodestreamHeader',
182             SubDirectory => { },
183             },
184             # CodestreamHeader sub boxes...
185             'lbl '=> {
186             Name => 'Label',
187             Format => 'string',
188             },
189             cmap => 'ComponentMapping',
190             roid => 'ROIDescription',
191             jplh => {
192             Name => 'CompositingLayerHeader',
193             SubDirectory => { },
194             },
195             # CompositingLayerHeader sub boxes...
196             cgrp => 'ColorGroup',
197             opct => 'Opacity',
198             creg => 'CodestreamRegistration',
199             dtbl => 'DataReference',
200             ftbl => {
201             Name => 'FragmentTable',
202             Subdirectory => { },
203             },
204             # FragmentTable sub boxes...
205             flst => 'FragmentList',
206             cref => 'Cross-Reference',
207             mdat => 'MediaData',
208             comp => 'Composition',
209             copt => 'CompositionOptions',
210             inst => 'InstructionSet',
211             asoc => {
212             Name => 'Association',
213             SubDirectory => { },
214             },
215             # (Association box may contain any other sub-box)
216             nlst => 'NumberList',
217             bfil => 'BinaryFilter',
218             drep => 'DesiredReproductions',
219             # DesiredReproductions sub boxes...
220             gtso => 'GraphicsTechnologyStandardOutput',
221             chck => 'DigitalSignature',
222             mp7b => 'MPEG7Binary',
223             free => 'Free',
224             jp2c => [{
225             Name => 'ContiguousCodestream',
226             Condition => 'not $$self{jumd_level}',
227             },{
228             Name => 'PreviewImage',
229             Groups => { 2 => 'Preview' },
230             Format => 'undef',
231             Binary => 1,
232             }],
233             jp2i => {
234             Name => 'IntellectualProperty',
235             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
236             },
237             'xml '=> [{
238             Name => 'XML',
239             Condition => 'not $$self{IsJXL}',
240             Writable => 'undef',
241             Flags => [ 'Binary', 'Protected', 'BlockExtract' ],
242             List => 1,
243             Notes => q{
244             by default, the XML data in this tag is parsed using the ExifTool XMP module
245             to to allow individual tags to be accessed when reading, but it may also be
246             extracted as a block via the "XML" tag, which is also how this tag is
247             written and copied. It may also be extracted as a block by setting the API
248             BlockExtract option. This is a List-type tag because multiple XML blocks
249             may exist
250             },
251             # (note: extracting as a block was broken in 11.04, and finally fixed in 12.14)
252             SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' },
253             },{
254             Name => 'XMP',
255             Notes => 'used for XMP in JPEG XL files',
256             # NOTE: the hacked code relies on this being at index 1 of the tagInfo list!
257             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
258             }],
259             uuid => [
260             {
261             Name => 'UUID-EXIF',
262             # (this is the EXIF that we create)
263             Condition => '$$valPt=~/^JpgTiffExif->JP2(?!Exif\0\0)/',
264             SubDirectory => {
265             TagTable => 'Image::ExifTool::Exif::Main',
266             ProcessProc => \&Image::ExifTool::ProcessTIFF,
267             WriteProc => \&Image::ExifTool::WriteTIFF,
268             DirName => 'EXIF',
269             Start => '$valuePtr + 16',
270             },
271             },
272             {
273             Name => 'UUID-EXIF2',
274             # written by Photoshop 7.01+Adobe JPEG2000-plugin v1.5
275             Condition => '$$valPt=~/^\x05\x37\xcd\xab\x9d\x0c\x44\x31\xa7\x2a\xfa\x56\x1f\x2a\x11\x3e/',
276             SubDirectory => {
277             TagTable => 'Image::ExifTool::Exif::Main',
278             ProcessProc => \&Image::ExifTool::ProcessTIFF,
279             WriteProc => \&Image::ExifTool::WriteTIFF,
280             DirName => 'EXIF',
281             Start => '$valuePtr + 16',
282             },
283             },
284             {
285             Name => 'UUID-EXIF_bad',
286             # written by Digikam
287             Condition => '$$valPt=~/^JpgTiffExif->JP2/',
288             SubDirectory => {
289             TagTable => 'Image::ExifTool::Exif::Main',
290             ProcessProc => \&Image::ExifTool::ProcessTIFF,
291             WriteProc => \&Image::ExifTool::WriteTIFF,
292             DirName => 'EXIF',
293             Start => '$valuePtr + 22',
294             },
295             },
296             {
297             Name => 'UUID-IPTC',
298             # (this is the IPTC that we create)
299             Condition => '$$valPt=~/^\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38/',
300             SubDirectory => {
301             TagTable => 'Image::ExifTool::IPTC::Main',
302             Start => '$valuePtr + 16',
303             },
304             },
305             {
306             Name => 'UUID-IPTC2',
307             # written by Photoshop 7.01+Adobe JPEG2000-plugin v1.5
308             Condition => '$$valPt=~/^\x09\xa1\x4e\x97\xc0\xb4\x42\xe0\xbe\xbf\x36\xdf\x6f\x0c\xe3\x6f/',
309             SubDirectory => {
310             TagTable => 'Image::ExifTool::IPTC::Main',
311             Start => '$valuePtr + 16',
312             },
313             },
314             {
315             Name => 'UUID-XMP',
316             # ref http://www.adobe.com/products/xmp/pdfs/xmpspec.pdf
317             Condition => '$$valPt=~/^\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac/',
318             SubDirectory => {
319             TagTable => 'Image::ExifTool::XMP::Main',
320             Start => '$valuePtr + 16',
321             },
322             },
323             {
324             Name => 'UUID-GeoJP2',
325             # ref http://www.remotesensing.org/jpeg2000/
326             Condition => '$$valPt=~/^\xb1\x4b\xf8\xbd\x08\x3d\x4b\x43\xa5\xae\x8c\xd7\xd5\xa6\xce\x03/',
327             SubDirectory => {
328             TagTable => 'Image::ExifTool::Exif::Main',
329             ProcessProc => \&Image::ExifTool::ProcessTIFF,
330             Start => '$valuePtr + 16',
331             },
332             },
333             {
334             Name => 'UUID-Photoshop',
335             # written by Photoshop 7.01+Adobe JPEG2000-plugin v1.5
336             Condition => '$$valPt=~/^\x2c\x4c\x01\x00\x85\x04\x40\xb9\xa0\x3e\x56\x21\x48\xd6\xdf\xeb/',
337             SubDirectory => {
338             TagTable => 'Image::ExifTool::Photoshop::Main',
339             Start => '$valuePtr + 16',
340             },
341             },
342             {
343             Name => 'UUID-Signature', # (seen in JUMB data of JPEG images)
344             # (may be able to remove this when JUMBF specification is finalized)
345             Condition => '$$valPt=~/^casg\x00\x11\x00\x10\x80\x00\x00\xaa\x00\x38\x9b\x71/',
346             Format => 'undef',
347             ValueConv => 'substr($val,16)',
348             },
349             {
350             Name => 'UUID-C2PAClaimSignature', # (seen in incorrectly-formatted JUMB data of JPEG images)
351             # (may be able to remove this when JUMBF specification is finalized)
352             Condition => '$$valPt=~/^c2cs\x00\x11\x00\x10\x80\x00\x00\xaa\x00\x38\x9b\x71/',
353             SubDirectory => {
354             TagTable => 'Image::ExifTool::CBOR::Main',
355             Start => '$valuePtr + 16',
356             },
357             },
358             {
359             Name => 'UUID-Unknown',
360             },
361             # also written by Adobe JPEG2000 plugin v1.5:
362             # 3a 0d 02 18 0a e9 41 15 b3 76 4b ca 41 ce 0e 71 - 1 byte (01)
363             # 47 c9 2c cc d1 a1 45 81 b9 04 38 bb 54 67 71 3b - 1 byte (01)
364             # bc 45 a7 74 dd 50 4e c6 a9 f6 f3 a1 37 f4 7e 90 - 4 bytes (00 00 00 32)
365             # d7 c8 c5 ef 95 1f 43 b2 87 57 04 25 00 f5 38 e8 - 4 bytes (00 00 00 32)
366             ],
367             uinf => {
368             Name => 'UUIDInfo',
369             SubDirectory => { },
370             },
371             # UUIDInfo sub boxes...
372             ulst => 'UUIDList',
373             'url '=> {
374             Name => 'URL',
375             Format => 'string',
376             },
377             # JUMBF boxes (ref https://github.com/thorfdbg/codestream-parser)
378             jumd => {
379             Name => 'JUMBFDescr',
380             SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::JUMD' },
381             },
382             jumb => {
383             Name => 'JUMBFBox',
384             SubDirectory => {
385             TagTable => 'Image::ExifTool::Jpeg2000::Main',
386             ProcessProc => \&ProcessJUMB,
387             },
388             },
389             json => {
390             Name => 'JSONData',
391             Flags => [ 'Binary', 'Protected', 'BlockExtract' ],
392             Notes => q{
393             by default, data in this tag is parsed using the ExifTool JSON module to to
394             allow individual tags to be accessed when reading, but it may also be
395             extracted as a block via the "JSONData" tag or by setting the API
396             BlockExtract option
397             },
398             SubDirectory => { TagTable => 'Image::ExifTool::JSON::Main' },
399             },
400             cbor => {
401             Name => 'CBORData',
402             Flags => [ 'Binary', 'Protected' ],
403             SubDirectory => { TagTable => 'Image::ExifTool::CBOR::Main' },
404             },
405             bfdb => { # used in JUMBF (see # (used when tag is renamed according to JUMDLabel)
406             Name => 'BinaryDataType',
407             Notes => 'JUMBF, MIME type and optional file name',
408             Format => 'undef',
409             # (ignore "toggles" byte and just extract MIME type and file name)
410             ValueConv => '$_=substr($val,1); s/\0+$//; s/\0/, /; $_',
411             JUMBF_Suffix => 'Type', # (used when tag is renamed according to JUMDLabel)
412             },
413             bidb => { # used in JUMBF
414             Name => 'BinaryData',
415             Notes => 'JUMBF',
416             Groups => { 2 => 'Preview' },
417             Format => 'undef',
418             Binary => 1,
419             JUMBF_Suffix => 'Data', # (used when tag is renamed according to JUMDLabel)
420             },
421             c2sh => { # used in JUMBF
422             Name => 'C2PASaltHash',
423             Format => 'undef',
424             ValueConv => 'unpack("H*",$val)',
425             JUMBF_Suffix => 'Salt', # (used when tag is renamed according to JUMDLabel)
426             },
427             #
428             # stuff seen in JPEG XL images:
429             #
430             # jbrd - JPEG Bitstream Reconstruction Data (allows lossless conversion back to original JPG)
431             jxlc => {
432             Name => 'JXLCodestream',
433             Format => 'undef',
434             Notes => q{
435             Codestream in JPEG XL image. Currently processed only to determine
436             ImageSize
437             },
438             RawConv => 'Image::ExifTool::Jpeg2000::ProcessJXLCodestream($self,\$val); undef',
439             },
440             Exif => {
441             Name => 'EXIF',
442             SubDirectory => {
443             TagTable => 'Image::ExifTool::Exif::Main',
444             ProcessProc => \&Image::ExifTool::ProcessTIFF,
445             WriteProc => \&Image::ExifTool::WriteTIFF,
446             DirName => 'EXIF',
447             Start => '$valuePtr + 4 + (length($$dataPt)-$valuePtr > 4 ? unpack("N", $$dataPt) : 0)',
448             },
449             },
450             );
451              
452             %Image::ExifTool::Jpeg2000::ImageHeader = (
453             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
454             GROUPS => { 2 => 'Image' },
455             0 => {
456             Name => 'ImageHeight',
457             Format => 'int32u',
458             },
459             4 => {
460             Name => 'ImageWidth',
461             Format => 'int32u',
462             },
463             8 => {
464             Name => 'NumberOfComponents',
465             Format => 'int16u',
466             },
467             10 => {
468             Name => 'BitsPerComponent',
469             PrintConv => q{
470             $val == 0xff and return 'Variable';
471             my $sign = ($val & 0x80) ? 'Signed' : 'Unsigned';
472             return (($val & 0x7f) + 1) . " Bits, $sign";
473             },
474             },
475             11 => {
476             Name => 'Compression',
477             PrintConv => {
478             0 => 'Uncompressed',
479             1 => 'Modified Huffman',
480             2 => 'Modified READ',
481             3 => 'Modified Modified READ',
482             4 => 'JBIG',
483             5 => 'JPEG',
484             6 => 'JPEG-LS',
485             7 => 'JPEG 2000',
486             8 => 'JBIG2',
487             },
488             },
489             );
490              
491             # (ref fcd15444-1/2/6.pdf)
492             # (also see http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html)
493             %Image::ExifTool::Jpeg2000::FileType = (
494             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
495             GROUPS => { 2 => 'Video' },
496             FORMAT => 'int32u',
497             0 => {
498             Name => 'MajorBrand',
499             Format => 'undef[4]',
500             PrintConv => {
501             'jp2 ' => 'JPEG 2000 Image (.JP2)', # image/jp2
502             'jpm ' => 'JPEG 2000 Compound Image (.JPM)', # image/jpm
503             'jpx ' => 'JPEG 2000 with extensions (.JPX)', # image/jpx
504             'jxl ' => 'JPEG XL Image (.JXL)', # image/jxl
505             },
506             },
507             1 => {
508             Name => 'MinorVersion',
509             Format => 'undef[4]',
510             ValueConv => 'sprintf("%x.%x.%x", unpack("nCC", $val))',
511             },
512             2 => {
513             Name => 'CompatibleBrands',
514             Format => 'undef[$size-8]',
515             # ignore any entry with a null, and return others as a list
516             ValueConv => 'my @a=($val=~/.{4}/sg); @a=grep(!/\0/,@a); \@a',
517             },
518             );
519              
520             %Image::ExifTool::Jpeg2000::CaptureResolution = (
521             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
522             GROUPS => { 2 => 'Image' },
523             FORMAT => 'int8s',
524             0 => {
525             Name => 'CaptureYResolution',
526             Format => 'rational32u',
527             },
528             4 => {
529             Name => 'CaptureXResolution',
530             Format => 'rational32u',
531             },
532             8 => {
533             Name => 'CaptureYResolutionUnit',
534             SeparateTable => 'ResolutionUnit',
535             PrintConv => \%resolutionUnit,
536             },
537             9 => {
538             Name => 'CaptureXResolutionUnit',
539             SeparateTable => 'ResolutionUnit',
540             PrintConv => \%resolutionUnit,
541             },
542             );
543              
544             %Image::ExifTool::Jpeg2000::DisplayResolution = (
545             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
546             GROUPS => { 2 => 'Image' },
547             FORMAT => 'int8s',
548             0 => {
549             Name => 'DisplayYResolution',
550             Format => 'rational32u',
551             },
552             4 => {
553             Name => 'DisplayXResolution',
554             Format => 'rational32u',
555             },
556             8 => {
557             Name => 'DisplayYResolutionUnit',
558             SeparateTable => 'ResolutionUnit',
559             PrintConv => \%resolutionUnit,
560             },
561             9 => {
562             Name => 'DisplayXResolutionUnit',
563             SeparateTable => 'ResolutionUnit',
564             PrintConv => \%resolutionUnit,
565             },
566             );
567              
568             %Image::ExifTool::Jpeg2000::ColorSpec = (
569             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
570             WRITE_PROC => \&Image::ExifTool::WriteBinaryData, # (we don't actually call this)
571             GROUPS => { 2 => 'Image' },
572             FORMAT => 'int8s',
573             WRITABLE => 1,
574             # (Note: 'colr' is not a real group, but is used as a hack to write the
575             # necessary colr box. This hack necessitated another hack in TagInfoXML.pm
576             # to avoid reporting this fake group in the XML output)
577             WRITE_GROUP => 'colr',
578             DATAMEMBER => [ 0 ],
579             IS_SUBDIR => [ 3 ],
580             NOTES => q{
581             The table below contains tags in the color specification (colr) box. This
582             box may be rewritten by writing either ICC_Profile, ColorSpace or
583             ColorSpecData. When writing, any existing colr boxes are replaced with the
584             newly created colr box.
585              
586             B: Care must be taken when writing this color specification because
587             writing a specification that is incompatible with the image data may make
588             the image undisplayable.
589             },
590             0 => {
591             Name => 'ColorSpecMethod',
592             RawConv => '$$self{ColorSpecMethod} = $val',
593             Protected => 1,
594             Notes => q{
595             default for writing is 2 when writing ICC_Profile, 1 when writing
596             ColorSpace, or 4 when writing ColorSpecData
597             },
598             PrintConv => {
599             1 => 'Enumerated',
600             2 => 'Restricted ICC',
601             3 => 'Any ICC',
602             4 => 'Vendor Color',
603             },
604             },
605             1 => {
606             Name => 'ColorSpecPrecedence',
607             Notes => 'default for writing is 0',
608             Protected => 1,
609             },
610             2 => {
611             Name => 'ColorSpecApproximation',
612             Notes => 'default for writing is 0',
613             Protected => 1,
614             PrintConv => {
615             0 => 'Not Specified',
616             1 => 'Accurate',
617             2 => 'Exceptional Quality',
618             3 => 'Reasonable Quality',
619             4 => 'Poor Quality',
620             },
621             },
622             3 => [
623             {
624             Name => 'ICC_Profile',
625             Condition => q{
626             $$self{ColorSpecMethod} == 2 or
627             $$self{ColorSpecMethod} == 3
628             },
629             Format => 'undef[$size-3]',
630             SubDirectory => {
631             TagTable => 'Image::ExifTool::ICC_Profile::Main',
632             },
633             },
634             {
635             Name => 'ColorSpace',
636             Condition => '$$self{ColorSpecMethod} == 1',
637             Format => 'int32u',
638             Protected => 1,
639             PrintConv => { # ref 15444-2 2002-05-15
640             0 => 'Bi-level',
641             1 => 'YCbCr(1)',
642             3 => 'YCbCr(2)',
643             4 => 'YCbCr(3)',
644             9 => 'PhotoYCC',
645             11 => 'CMY',
646             12 => 'CMYK',
647             13 => 'YCCK',
648             14 => 'CIELab',
649             15 => 'Bi-level(2)', # (incorrectly listed as 18 in 15444-2 2000-12-07)
650             16 => 'sRGB',
651             17 => 'Grayscale',
652             18 => 'sYCC',
653             19 => 'CIEJab',
654             20 => 'e-sRGB',
655             21 => 'ROMM-RGB',
656             # incorrect in 15444-2 2000-12-07
657             #22 => 'sRGB based YCbCr',
658             #23 => 'YPbPr(1125/60)',
659             #24 => 'YPbPr(1250/50)',
660             22 => 'YPbPr(1125/60)',
661             23 => 'YPbPr(1250/50)',
662             24 => 'e-sYCC',
663             },
664             },
665             {
666             Name => 'ColorSpecData',
667             Format => 'undef[$size-3]',
668             Writable => 'undef',
669             Protected => 1,
670             Binary => 1,
671             },
672             ],
673             );
674              
675             # JUMBF description box
676             %Image::ExifTool::Jpeg2000::JUMD = (
677             PROCESS_PROC => \&ProcessJUMD,
678             GROUPS => { 0 => 'JUMBF', 1 => 'JUMBF', 2 => 'Image' },
679             NOTES => 'Information extracted from the JUMBF description box.',
680             'type' => {
681             Name => 'JUMDType',
682             ValueConv => 'unpack "H*", $val',
683             PrintConv => q{
684             my @a = $val =~ /^(\w{8})(\w{4})(\w{4})(\w{16})$/;
685             return $val unless @a;
686             my $ascii = pack 'H*', $a[0];
687             $a[0] = "($ascii)" if $ascii =~ /^[a-zA-Z0-9]{4}$/;
688             return join '-', @a;
689             },
690             # seen:
691             # cacb/cast/caas/cacl/casg/json-00110010800000aa00389b71
692             # 6579d6fbdba2446bb2ac1b82feeb89d1 - JPEG image
693             },
694             'label' => { Name => 'JUMDLabel' },
695             'toggles' => {
696             Name => 'JUMDToggles',
697             Unknown => 1,
698             PrintConv => { BITMASK => {
699             0 => 'Requestable',
700             1 => 'Label',
701             2 => 'ID',
702             3 => 'Signature',
703             }},
704             },
705             'id' => { Name => 'JUMDID', Description => 'JUMD ID' },
706             'sig' => { Name => 'JUMDSignature', PrintConv => 'unpack "H*", $val' },
707             );
708              
709             #------------------------------------------------------------------------------
710             # Read JUMBF box to keep track of sub-document numbers
711             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
712             # Returns: 1 on success
713             sub ProcessJUMB($$$)
714             {
715 114     114 0 303 my ($et, $dirInfo, $tagTablePtr) = @_;
716 114 100       410 if ($$et{jumd_level}) {
717 95         230 ++$$et{jumd_level}[-1]; # increment current sub-document number
718             } else {
719 19         82 $$et{jumd_level} = [ ++$$et{DOC_COUNT} ]; # new top-level sub-document
720 19         75 $$et{SET_GROUP0} = 'JUMBF';
721             }
722 114         205 $$et{DOC_NUM} = join '-', @{$$et{jumd_level}};
  114         477  
723 114         184 push @{$$et{jumd_level}}, 0;
  114         262  
724 114         671 ProcessJpeg2000Box($et, $dirInfo, $tagTablePtr);
725 114         219 delete $$et{DOC_NUM};
726 114         212 delete $$et{JUMBFLabel};
727 114         174 pop @{$$et{jumd_level}};
  114         222  
728 114 100       205 if (@{$$et{jumd_level}} < 2) {
  114         309  
729 19         80 delete $$et{jumd_level};
730 19         64 delete $$et{SET_GROUP0};
731             }
732 114         273 return 1;
733             }
734              
735             #------------------------------------------------------------------------------
736             # Read JUMBF description box (ref https://github.com/thorfdbg/codestream-parser)
737             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
738             # Returns: 1 on success
739             sub ProcessJUMD($$$)
740             {
741 114     114 0 274 my ($et, $dirInfo, $tagTablePtr) = @_;
742 114         223 my $dataPt = $$dirInfo{DataPt};
743 114         211 my $pos = $$dirInfo{DirStart};
744 114         217 my $end = $pos + $$dirInfo{DirLen};
745 114         441 $et->VerboseDir('JUMD', 0, $end-$pos);
746 114         247 delete $$et{JUMBFLabel};
747 114 50       317 $$dirInfo{DirLen} < 17 and $et->Warn('Truncated JUMD directory'), return 0;
748 114         255 my $type = substr($$dataPt, $pos, 4);
749 114         492 $et->HandleTag($tagTablePtr, 'type', substr($$dataPt, $pos, 16));
750 114         259 $pos += 16;
751 114         411 my $flags = Get8u($dataPt, $pos++);
752 114         497 $et->HandleTag($tagTablePtr, 'toggles', $flags);
753 114 50       353 if ($flags & 0x02) { # label exists?
754 114         329 pos($$dataPt) = $pos;
755 114 50       485 $$dataPt =~ /\0/g or $et->Warn('Missing JUMD label terminator'), return 0;
756 114         225 my $len = pos($$dataPt) - $pos;
757 114         277 my $name = substr($$dataPt, $pos, $len);
758 114         378 $et->HandleTag($tagTablePtr, 'label', $name);
759 114         232 $pos += $len;
760 114 50       327 if ($len) {
761 114         921 $name =~ s/[^-_a-zA-Z0-9]([a-z])/\U$1/g; # capitalize characters after illegal characters
762 114         305 $name =~ tr/-_a-zA-Z0-9//dc; # remove other illegal characters
763 114         266 $name =~ s/__/_/; # collapse double underlines
764 114         291 $name = ucfirst $name; # capitalize first letter
765 114 50       296 $name = "Tag$name" if length($name) < 2; # must at least 2 characters long
766 114         339 $$et{JUMBFLabel} = $name;
767             }
768             }
769 114 50       290 if ($flags & 0x04) { # ID exists?
770 0 0       0 $pos + 4 > $end and $et->Warn('Missing JUMD ID'), return 0;
771 0         0 $et->HandleTag($tagTablePtr, 'id', Get32u($dataPt, $pos));
772 0         0 $pos += 4;
773             }
774 114 50       246 if ($flags & 0x08) { # signature exists?
775 0 0       0 $pos + 32 > $end and $et->Warn('Missing JUMD signature'), return 0;
776 0         0 $et->HandleTag($tagTablePtr, 'sig', substr($$dataPt, $pos, 32));
777 0         0 $pos += 32;
778             }
779 114         201 my $more = $end - $pos;
780 114 50       251 if ($more) {
781             # (may find c2sh box hiding after JUMD record)
782 0 0       0 if ($more >= 8) {
783             my %dirInfo = (
784             DataPt => $dataPt,
785             DataLen => $$dirInfo{DataLen},
786 0         0 DirStart => $pos,
787             DirLen => $more,
788             DirName => 'JUMDPrivate',
789             );
790 0         0 $et->ProcessDirectory(\%dirInfo, GetTagTable('Image::ExifTool::Jpeg2000::Main'));
791             } else {
792 0         0 $et->Warn("Extra data in JUMD box $more bytes)", 1);
793             }
794             }
795 114         377 return 1;
796             }
797              
798             #------------------------------------------------------------------------------
799             # Create new JPEG 2000 boxes when writing
800             # (Currently only supports adding top-level Writable JPEG2000 tags and certain UUID boxes)
801             # Inputs: 0) ExifTool object ref, 1) Output file or scalar ref
802             # Returns: 1 on success
803             sub CreateNewBoxes($$)
804             {
805 2     2 0 16 my ($et, $outfile) = @_;
806 2         9 my $addTags = $$et{AddJp2Tags};
807 2         6 my $addDirs = $$et{AddJp2Dirs};
808 2         7 delete $$et{AddJp2Tags};
809 2         5 delete $$et{AddJp2Dirs};
810 2         5 my ($tag, $dirName);
811             # add JPEG2000 tags
812 2         11 foreach $tag (sort keys %$addTags) {
813 1         2 my $tagInfo = $$addTags{$tag};
814 1         9 my $nvHash = $et->GetNewValueHash($tagInfo);
815             # (native JPEG2000 information is always preferred, so don't check IsCreating)
816 1 50 33     12 next unless $$tagInfo{List} or $et->IsOverwriting($nvHash) > 0;
817 1 50       5 next if $$nvHash{EditOnly};
818 1         4 my @vals = $et->GetNewValue($nvHash);
819 1         3 my $val;
820 1         3 foreach $val (@vals) {
821 1         6 my $boxhdr = pack('N', length($val) + 8) . $$tagInfo{TagID};
822 1 50       5 Write($outfile, $boxhdr, $val) or return 0;
823 1         3 ++$$et{CHANGED};
824 1         6 $et->VerboseValue("+ Jpeg2000:$$tagInfo{Name}", $val);
825             }
826             }
827             # add UUID boxes (and/or JXL Exif/XML boxes)
828 2         25 foreach $dirName (sort keys %$addDirs) {
829             # handle JPEG XL XMP and EXIF
830 13 100 100     47 if ($dirName eq 'xml ' or $dirName eq 'Exif') {
831 2 100       9 my ($tag, $dir) = $dirName eq 'xml ' ? ('xml ', 'XMP') : ('Exif', 'EXIF');
832 2         5 my $tagInfo = $Image::ExifTool::Jpeg2000::Main{$tag};
833 2 100       11 $tagInfo = $$tagInfo[1] if ref $tagInfo eq 'ARRAY'; # (hack for stupid JXL XMP)
834 2         6 my $subdir = $$tagInfo{SubDirectory};
835 2         10 my $tagTable = GetTagTable($$subdir{TagTable});
836 2 100       14 $tagTable = GetTagTable('Image::ExifTool::XMP::Main') if $dir eq 'XMP';
837 2         9 my %dirInfo = (
838             DirName => $dir,
839             Parent => 'JP2',
840             );
841 2         13 my $newdir = $et->WriteDirectory(\%dirInfo, $tagTable, $$subdir{WriteProc});
842 2 50 33     17 if (defined $newdir and length $newdir) {
843             # not sure why, but EXIF box is padded with leading 0's in my sample
844 2 100       7 my $pad = $dirName eq 'Exif' ? "\0\0\0\0" : '';
845 2         10 my $boxhdr = pack('N', length($newdir) + length($pad) + 8) . $tag;
846 2 50       7 Write($outfile, $boxhdr, $pad, $newdir) or return 0;
847 2         9 next;
848             }
849             }
850 11 100       34 next unless $uuid{$dirName};
851 2         3 my $tagInfo;
852 2         4 foreach $tagInfo (@{$Image::ExifTool::Jpeg2000::Main{uuid}}) {
  2         6  
853 10 100       22 next unless $$tagInfo{Name} eq $dirName;
854 2         3 my $subdir = $$tagInfo{SubDirectory};
855 2         20 my $tagTable = GetTagTable($$subdir{TagTable});
856             my %dirInfo = (
857 2   33     14 DirName => $$subdir{DirName} || $dirName,
858             Parent => 'JP2',
859             );
860             # remove "UUID-" from start of directory name to allow appropriate
861             # directories to be written as a block
862 2         12 $dirInfo{DirName} =~ s/^UUID-//;
863 2         12 my $newdir = $et->WriteDirectory(\%dirInfo, $tagTable, $$subdir{WriteProc});
864 2 50 33     25 if (defined $newdir and length $newdir) {
865 2         12 my $boxhdr = pack('N', length($newdir) + 24) . 'uuid' . $uuid{$dirName};
866 2 50       10 Write($outfile, $boxhdr, $newdir) or return 0;
867 2         9 last;
868             }
869             }
870             }
871 2         14 return 1;
872             }
873              
874             #------------------------------------------------------------------------------
875             # Create Color Specification Box
876             # Inputs: 0) ExifTool object ref, 1) Output file or scalar ref
877             # Returns: 1 on success
878             sub CreateColorSpec($$)
879             {
880 1     1 0 4 my ($et, $outfile) = @_;
881 1         4 my $meth = $et->GetNewValue('Jpeg2000:ColorSpecMethod');
882 1   50     5 my $prec = $et->GetNewValue('Jpeg2000:ColorSpecPrecedence') || 0;
883 1   50     5 my $approx = $et->GetNewValue('Jpeg2000:ColorSpecApproximation') || 0;
884 1         4 my $icc = $et->GetNewValue('ICC_Profile');
885 1         4 my $space = $et->GetNewValue('Jpeg2000:ColorSpace');
886 1         6 my $cdata = $et->GetNewValue('Jpeg2000:ColorSpecData');
887 1 50       5 unless ($meth) {
888 1 50       5 if ($icc) {
    50          
    0          
889 0         0 $meth = 2;
890             } elsif (defined $space) {
891 1         3 $meth = 1;
892             } elsif (defined $cdata) {
893 0         0 $meth = 4;
894             } else {
895 0         0 $et->Warn('Color space not defined'), return 0;
896             }
897             }
898 1 50 0     4 if ($meth eq '1') {
    0          
    0          
899 1 50       4 defined $space or $et->Warn('Must specify ColorSpace'), return 0;
900 1         4 $cdata = pack('N', $space);
901             } elsif ($meth eq '2' or $meth eq '3') {
902 0 0       0 defined $icc or $et->Warn('Must specify ICC_Profile'), return 0;
903 0         0 $cdata = $icc;
904             } elsif ($meth eq '4') {
905 0 0       0 defined $cdata or $et->Warn('Must specify ColorSpecData'), return 0;
906             } else {
907 0         0 $et->Warn('Unknown ColorSpecMethod'), return 0;
908             }
909 1         6 my $boxhdr = pack('N', length($cdata) + 11) . 'colr';
910 1 50       6 Write($outfile, $boxhdr, pack('CCC',$meth,$prec,$approx), $cdata) or return 0;
911 1         3 ++$$et{CHANGED};
912 1         5 $et->VPrint(1, " + Jpeg2000:ColorSpec\n");
913 1         5 return 1;
914             }
915              
916             #------------------------------------------------------------------------------
917             # Process JPEG 2000 box
918             # Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) Pointer to tag table
919             # Returns: 1 on success when reading, or -1 on write error
920             # (or JP2 box or undef when writing from buffer)
921             sub ProcessJpeg2000Box($$$)
922             {
923 144     144 0 362 my ($et, $dirInfo, $tagTablePtr) = @_;
924 144         299 my $dataPt = $$dirInfo{DataPt};
925 144         246 my $dataLen = $$dirInfo{DataLen};
926 144   100     441 my $dataPos = $$dirInfo{DataPos} || 0;
927 144   100     399 my $dirLen = $$dirInfo{DirLen} || 0;
928 144   100     427 my $dirStart = $$dirInfo{DirStart} || 0;
929 144   100     413 my $base = $$dirInfo{Base} || 0;
930 144         265 my $raf = $$dirInfo{RAF};
931 144         244 my $outfile = $$dirInfo{OutFile};
932 144         241 my $dirEnd = $dirStart + $dirLen;
933 144         264 my ($err, $outBuff, $verbose, $doColour);
934              
935 144 100       282 if ($outfile) {
936 3 100       9 unless ($raf) {
937             # buffer output to be used for return value
938 1         2 $outBuff = '';
939 1         2 $outfile = \$outBuff;
940             }
941             # determine if we will be writing colr box
942 3 100 66     28 if ($$dirInfo{DirName} and $$dirInfo{DirName} eq 'JP2Header') {
943 1 0 33     5 $doColour = 2 if defined $et->GetNewValue('ColorSpecMethod') or $et->GetNewValue('ICC_Profile') or
      33        
      33        
      33        
      0        
944             defined $et->GetNewValue('ColorSpecPrecedence') or defined $et->GetNewValue('ColorSpace') or
945             defined $et->GetNewValue('ColorSpecApproximation') or defined $et->GetNewValue('ColorSpecData');
946             }
947             } else {
948             # (must not set verbose flag when writing!)
949 141         302 $verbose = $$et{OPTIONS}{Verbose};
950 141 50       353 $et->VerboseDir($$dirInfo{DirName}) if $verbose;
951             }
952             # loop through all contained boxes
953 144         265 my ($pos, $boxLen, $lastBox);
954 144         293 for ($pos=$dirStart; ; $pos+=$boxLen) {
955 471         886 my ($boxID, $buff, $valuePtr);
956 471         750 my $hdrLen = 8; # the box header length
957 471 100       1220 if ($raf) {
    100          
958 40         129 $dataPos = $raf->Tell() - $base;
959 40         111 my $n = $raf->Read($buff,$hdrLen);
960 40 100       117 unless ($n == $hdrLen) {
961 6 50       22 $n and $err = '', last;
962 6 100 50     31 CreateNewBoxes($et, $outfile) or $err = 1 if $outfile;
963 6         15 last;
964             }
965 34         73 $dataPt = \$buff;
966 34         55 $dirLen = $dirEnd = $hdrLen;
967 34         62 $pos = 0;
968             } elsif ($pos >= $dirEnd - $hdrLen) {
969 138 50       344 $err = '' unless $pos == $dirEnd;
970 138         256 last;
971             }
972 327         1071 $boxLen = unpack("x$pos N",$$dataPt); # (length includes header and data)
973 327         842 $boxID = substr($$dataPt, $pos+4, 4);
974             # remove old colr boxes if necessary
975 327 100 100     872 if ($doColour and $boxID eq 'colr') {
976 1 50       4 if ($doColour == 1) { # did we successfully write the new colr box?
977 1         4 $et->VPrint(1," - Jpeg2000:ColorSpec\n");
978 1         2 ++$$et{CHANGED};
979 1         3 next;
980             }
981 0         0 $et->Warn('Out-of-order colr box encountered');
982 0         0 undef $doColour;
983             }
984 326         496 $lastBox = $boxID;
985 326         531 $pos += $hdrLen; # move to end of box header
986 326 50       823 if ($boxLen == 1) {
    50          
987             # box header contains an additional 8-byte integer for length
988 0         0 $hdrLen += 8;
989 0 0       0 if ($raf) {
990 0         0 my $buf2;
991 0 0       0 if ($raf->Read($buf2,8) == 8) {
992 0         0 $buff .= $buf2;
993 0         0 $dirLen = $dirEnd = $hdrLen;
994             }
995             }
996 0 0       0 $pos > $dirEnd - 8 and $err = '', last;
997 0         0 my ($hi, $lo) = unpack("x$pos N2",$$dataPt);
998 0 0       0 $hi and $err = "Can't currently handle JPEG 2000 boxes > 4 GB", last;
999 0         0 $pos += 8; # move to end of extended-length box header
1000 0         0 $boxLen = $lo - $hdrLen; # length of remaining box data
1001             } elsif ($boxLen == 0) {
1002 0 0       0 if ($raf) {
1003 0 0       0 if ($outfile) {
    0          
1004 0 0       0 CreateNewBoxes($et, $outfile) or $err = 1;
1005             # copy over the rest of the file
1006 0 0       0 Write($outfile, $$dataPt) or $err = 1;
1007 0         0 while ($raf->Read($buff, 65536)) {
1008 0 0       0 Write($outfile, $buff) or $err = 1;
1009             }
1010             } elsif ($verbose) {
1011 0         0 my $msg = sprintf("offset 0x%.4x to end of file", $dataPos + $base + $pos);
1012 0         0 $et->VPrint(0, "$$et{INDENT}- Tag '${boxID}' ($msg)\n");
1013             }
1014 0         0 last; # (ignore the rest of the file when reading)
1015             }
1016 0         0 $boxLen = $dirEnd - $pos; # data runs to end of file
1017             } else {
1018 326         500 $boxLen -= $hdrLen; # length of remaining box data
1019             }
1020 326 50       690 $boxLen < 0 and $err = 'Invalid JPEG 2000 box length', last;
1021 326         1102 my $tagInfo = $et->GetTagInfo($tagTablePtr, $boxID);
1022 326 50 33     924 unless (defined $tagInfo or $verbose) {
1023             # no need to process this box
1024 0 0       0 if ($raf) {
    0          
1025 0 0       0 if ($outfile) {
1026 0 0       0 Write($outfile, $$dataPt) or $err = 1;
1027 0 0       0 $raf->Read($buff,$boxLen) == $boxLen or $err = '', last;
1028 0 0       0 Write($outfile, $buff) or $err = 1;
1029             } else {
1030 0 0       0 $raf->Seek($boxLen, 1) or $err = 'Seek error', last;
1031             }
1032             } elsif ($outfile) {
1033 0 0       0 Write($outfile, substr($$dataPt, $pos-$hdrLen, $boxLen+$hdrLen)) or $err = '', last;
1034             }
1035 0         0 next;
1036             }
1037 326 100       949 if ($raf) {
    50          
1038             # read the box data
1039 34         99 $dataPos = $raf->Tell() - $base;
1040 34 50       88 $raf->Read($buff,$boxLen) == $boxLen or $err = '', last;
1041 34         102 $valuePtr = 0;
1042 34         66 $dataLen = $boxLen;
1043             } elsif ($pos + $boxLen > $dirEnd) {
1044 0         0 $err = '';
1045 0         0 last;
1046             } else {
1047 292         506 $valuePtr = $pos;
1048             }
1049 326 100 66     1270 if (defined $tagInfo and not $tagInfo) {
1050             # GetTagInfo() required the value for a Condition
1051 10 100       44 my $tmpVal = substr($$dataPt, $valuePtr, $boxLen < 128 ? $boxLen : 128);
1052 10         42 $tagInfo = $et->GetTagInfo($tagTablePtr, $boxID, \$tmpVal);
1053             }
1054             # delete all UUID boxes and any writable box if deleting all information
1055 326 100 66     842 if ($outfile and $tagInfo) {
1056 9 50 66     73 if ($boxID eq 'uuid' and $$et{DEL_GROUP}{'*'}) {
    100          
1057 0         0 $et->VPrint(0, " Deleting $$tagInfo{Name}\n");
1058 0         0 ++$$et{CHANGED};
1059 0         0 next;
1060             } elsif ($$tagInfo{Writable}) {
1061 2         4 my $isOverwriting;
1062 2 50       7 if ($$et{DEL_GROUP}{Jpeg2000}) {
1063 0         0 $isOverwriting = 1;
1064             } else {
1065 2         8 my $nvHash = $et->GetNewValueHash($tagInfo);
1066 2         9 $isOverwriting = $et->IsOverwriting($nvHash);
1067             }
1068 2 50       6 if ($isOverwriting) {
    0          
1069 2         10 my $val = substr($$dataPt, $valuePtr, $boxLen);
1070 2         18 $et->VerboseValue("- Jpeg2000:$$tagInfo{Name}", $val);
1071 2         4 ++$$et{CHANGED};
1072 2         5 next;
1073             } elsif (not $$tagInfo{List}) {
1074 0         0 delete $$et{AddJp2Tags}{$boxID};
1075             }
1076             }
1077             }
1078             # create new tag for JUMBF data values with name corresponding to JUMBFLabel
1079 324 100 66     1916 if ($tagInfo and $$et{JUMBFLabel} and (not $$tagInfo{SubDirectory} or $$tagInfo{BlockExtract})) {
      66        
      100        
1080 57   50     733 $tagInfo = { %$tagInfo, Name => $$et{JUMBFLabel} . ($$tagInfo{JUMBF_Suffix} || '') };
1081 57         180 delete $$tagInfo{Description};
1082 57         279 AddTagToTable($tagTablePtr, '_JUMBF_' . $$et{JUMBFLabel}, $tagInfo);
1083 57         138 delete $$tagInfo{Protected}; # (must do this so -j -b returns JUMBF binary data)
1084 57         126 $$tagInfo{TagID} = $boxID;
1085             }
1086 324 50       671 if ($verbose) {
1087 0         0 $et->VerboseInfo($boxID, $tagInfo,
1088             Table => $tagTablePtr,
1089             DataPt => $dataPt,
1090             Size => $boxLen,
1091             Start => $valuePtr,
1092             Addr => $valuePtr + $dataPos + $base,
1093             );
1094 0 0       0 next unless $tagInfo;
1095             }
1096 324 100 66     788 if ($$tagInfo{SubDirectory}) {
    100          
    100          
1097 319         600 my $subdir = $$tagInfo{SubDirectory};
1098 319         516 my $subdirStart = $valuePtr;
1099 319         511 my $subdirLen = $boxLen;
1100 319 100       767 if (defined $$subdir{Start}) {
1101             #### eval Start ($valuePtr, $dataPt)
1102 11         573 $subdirStart = eval($$subdir{Start});
1103 11         62 $subdirLen -= $subdirStart - $valuePtr;
1104 11 50       39 if ($subdirLen < 0) {
1105 0         0 $subdirStart = $valuePtr;
1106 0         0 $subdirLen = 0;
1107             }
1108             }
1109             my %subdirInfo = (
1110             Parent => 'JP2',
1111             DataPt => $dataPt,
1112             DataPos => -$subdirStart, # (relative to Base)
1113             DataLen => $dataLen,
1114             DirStart => $subdirStart,
1115             DirLen => $subdirLen,
1116             DirName => $$subdir{DirName} || $$tagInfo{Name},
1117 319   66     3189 OutFile => $outfile,
1118             Base => $base + $dataPos + $subdirStart,
1119             );
1120 319         820 my $uuid = $uuid{$$tagInfo{Name}};
1121             # remove "UUID-" prefix to allow appropriate directories to be written as a block
1122 319         729 $subdirInfo{DirName} =~ s/^UUID-//;
1123 319   66     965 my $subTable = GetTagTable($$subdir{TagTable}) || $tagTablePtr;
1124 319 100       771 if ($outfile) {
1125             # remove this directory from our create list
1126 6         30 delete $$et{AddJp2Dirs}{$$tagInfo{Name}}; # (eg. 'EXIF' or 'XMP')
1127 6         14 delete $$et{AddJp2Dirs}{$boxID}; # (eg. 'Exif' or 'xml ')
1128 6         20 my $newdir;
1129             # only edit writable UUID, Exif and jp2h boxes
1130 6 100 66     90 if ($uuid or $boxID eq 'Exif' or ($boxID eq 'xml ' and $$et{IsJXL}) or
    50 33        
      66        
      66        
      66        
1131             ($boxID eq 'jp2h' and $$et{EDIT_DIRS}{jp2h}))
1132             {
1133 2         13 $newdir = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc});
1134 2 50 33     10 next if defined $newdir and not length $newdir; # next if deleting the box
1135             } elsif (defined $uuid) {
1136 0         0 $et->Warn("Not editing $$tagInfo{Name} box", 1);
1137             }
1138             # use old box data if not changed
1139 6 100       24 defined $newdir or $newdir = substr($$dataPt, $subdirStart, $subdirLen);
1140 6         14 my $prefixLen = $subdirStart - $valuePtr;
1141 6         33 my $boxhdr = pack('N', length($newdir) + 8 + $prefixLen) . $boxID;
1142 6 100       18 $boxhdr .= substr($$dataPt, $valuePtr, $prefixLen) if $prefixLen;
1143 6 50       23 Write($outfile, $boxhdr, $newdir) or $err = 1;
1144             # write new colr box immediately after ihdr
1145 6 100 66     40 if ($doColour and $boxID eq 'ihdr') {
1146             # (shouldn't be multiple ihdr boxes, but just in case, write only 1)
1147 1 50       7 $doColour = $doColour==2 ? CreateColorSpec($et, $outfile) : 0;
1148             }
1149             } else {
1150             # extract as a block if specified
1151 313 100       1704 $subdirInfo{BlockInfo} = $tagInfo if $$tagInfo{BlockExtract};
1152 313 50 66     805 $et->Warn("Reading non-standard $$tagInfo{Name} box") if defined $uuid and $uuid eq '0';
1153 313 50       1594 unless ($et->ProcessDirectory(\%subdirInfo, $subTable, $$subdir{ProcessProc})) {
1154 0 0       0 if ($subTable eq $tagTablePtr) {
1155 0         0 $err = 'JPEG 2000 format error';
1156 0         0 last;
1157             }
1158 0         0 $et->Warn("Unrecognized $$tagInfo{Name} box");
1159             }
1160             }
1161             } elsif ($$tagInfo{Format} and not $outfile) {
1162             # only save tag values if Format was specified
1163 1         2 my $rational;
1164 1         7 my $val = ReadValue($dataPt, $valuePtr, $$tagInfo{Format}, undef, $boxLen, \$rational);
1165 1 50       6 if (defined $val) {
1166 1         9 my $key = $et->FoundTag($tagInfo, $val);
1167             # save Rational value
1168 1 50 33     7 $$et{RATIONAL}{$key} = $rational if defined $rational and defined $key;
1169             }
1170             } elsif ($outfile) {
1171 1         7 my $boxhdr = pack('N', $boxLen + 8) . $boxID;
1172 1 50       7 Write($outfile, $boxhdr, substr($$dataPt, $valuePtr, $boxLen)) or $err = 1;
1173             }
1174             }
1175 144 50       353 if (defined $err) {
1176 0 0       0 $err or $err = 'Truncated JPEG 2000 box';
1177 0 0       0 if ($outfile) {
1178 0 0       0 $et->Error($err) unless $err eq '1';
1179 0 0       0 return $raf ? -1 : undef;
1180             }
1181 0         0 $et->Warn($err);
1182             }
1183 144 100 100     378 return $outBuff if $outfile and not $raf;
1184 143         347 return 1;
1185             }
1186              
1187             #------------------------------------------------------------------------------
1188             # Return bits from a bitstream object
1189             # Inputs: 0) array ref, 1) number of bits
1190             # Returns: specified number of bits as an integer, and shifts input bitstream
1191             sub GetBits($$)
1192             {
1193 12     12 0 23 my ($a, $n) = @_;
1194 12         17 my $v = 0;
1195 12         15 my $bit = 1;
1196 12         17 my $i;
1197 12         36 while ($n--) {
1198 52         134 for ($i=0; $i<@$a; ++$i) {
1199             # consume bits LSB first
1200 624         780 my $set = $$a[$i] & 1;
1201 624         768 $$a[$i] >>= 1;
1202 624 100       890 if ($i) {
1203 572 100       1209 $$a[$i-1] |= 0x80 if $set;
1204             } else {
1205 52 100       83 $v |= $bit if $set;
1206 52         93 $bit <<= 1;
1207             }
1208             }
1209             }
1210 12         37 return $v;
1211             }
1212              
1213             #------------------------------------------------------------------------------
1214             # Extract parameters from JPEG XL codestream [unverified!]
1215             # Inputs: 0) ExifTool ref, 1) codestream ref
1216             # Returns: 1
1217             sub ProcessJXLCodestream($$)
1218             {
1219 2     2 0 9 my ($et, $dataPt) = @_;
1220             # add padding if necessary to avoid unpacking past end of data
1221 2 100       8 if (length $$dataPt < 14) {
1222 1         5 my $tmp = $$dataPt . ("\0" x 14);
1223 1         3 $dataPt = \$tmp;
1224             }
1225 2         16 my @a = unpack 'x2C12', $$dataPt;
1226 2         4 my ($x, $y);
1227 2         9 my $small = GetBits(\@a, 1);
1228 2 50       16 if ($small) {
1229 0         0 $y = (GetBits(\@a, 5) + 1) * 8;
1230             } else {
1231 2         12 $y = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1;
1232             }
1233 2         6 my $ratio = GetBits(\@a, 3);
1234 2 50       8 if ($ratio == 0) {
1235 2 50       6 if ($small) {
1236 0         0 $x = (GetBits(\@a, 5) + 1) * 8;;
1237             } else {
1238 2         8 $x = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1;
1239             }
1240             } else {
1241 0         0 my $r = [[1,1],[12,10],[4,3],[3,2],[16,9],[5,4],[2,1]]->[$ratio-1];
1242 0         0 $x = int($y * $$r[0] / $$r[1]);
1243             }
1244 2         19 $et->FoundTag(ImageWidth => $x);
1245 2         10 $et->FoundTag(ImageHeight => $y);
1246 2         40 return 1;
1247             }
1248              
1249             #------------------------------------------------------------------------------
1250             # Read/write meta information from a JPEG 2000 image
1251             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1252             # Returns: 1 on success, 0 if this wasn't a valid JPEG 2000 file, or -1 on write error
1253             sub ProcessJP2($$)
1254             {
1255 7     7 0 15 local $_;
1256 7         20 my ($et, $dirInfo) = @_;
1257 7         20 my $raf = $$dirInfo{RAF};
1258 7         16 my $outfile = $$dirInfo{OutFile};
1259 7         10 my $hdr;
1260              
1261             # check to be sure this is a valid JPG2000 file
1262 7 50       23 return 0 unless $raf->Read($hdr,12) == 12;
1263 7 100 66     64 unless ($hdr eq "\0\0\0\x0cjP \x0d\x0a\x87\x0a" or # (ref 1)
      100        
1264             $hdr eq "\0\0\0\x0cjP\x1a\x1a\x0d\x0a\x87\x0a" or # (ref 2)
1265             $$et{IsJXL})
1266             {
1267 1 50       8 return 0 unless $hdr =~ /^\xff\x4f\xff\x51\0/; # check for JP2 codestream format
1268 1 50       4 if ($outfile) {
1269 0         0 $et->Error('Writing of J2C files is not yet supported');
1270 0         0 return 0
1271             }
1272             # add J2C markers if not done already
1273 1 50       6 unless ($Image::ExifTool::jpegMarker{0x4f}) {
1274 1         37 $Image::ExifTool::jpegMarker{$_} = $j2cMarker{$_} foreach keys %j2cMarker;
1275             }
1276 1         7 $et->SetFileType('J2C');
1277 1         6 $raf->Seek(0,0);
1278 1         7 return $et->ProcessJPEG($dirInfo); # decode with JPEG processor
1279             }
1280 6 100       24 if ($outfile) {
1281 2 50       15 Write($outfile, $hdr) or return -1;
1282 2 100       12 if ($$et{IsJXL}) {
1283 1         6 $et->InitWriteDirs(\%jxlMap);
1284 1         4 $$et{AddJp2Tags} = { }; # (don't add JP2 tags in JXL files)
1285             } else {
1286 1         7 $et->InitWriteDirs(\%jp2Map);
1287 1         9 $$et{AddJp2Tags} = $et->GetNewTagInfoHash(\%Image::ExifTool::Jpeg2000::Main);
1288             }
1289             # save list of directories to create
1290 2         8 my %addDirs = %{$$et{ADD_DIRS}}; # (make a copy)
  2         17  
1291 2         11 $$et{AddJp2Dirs} = \%addDirs;
1292             } else {
1293 4         9 my ($buff, $fileType);
1294             # recognize JPX and JPM as unique types of JP2
1295 4 50 33     16 if ($raf->Read($buff, 12) == 12 and $buff =~ /^.{4}ftyp(.{4})/s) {
1296 4 50       22 $fileType = 'JPX' if $1 eq 'jpx ';
1297 4 50       17 $fileType = 'JPM' if $1 eq 'jpm ';
1298 4 100       17 $fileType = 'JXL' if $1 eq 'jxl ';
1299             }
1300 4 50       26 $raf->Seek(-length($buff), 1) if defined $buff;
1301 4         30 $et->SetFileType($fileType);
1302             }
1303 6         32 SetByteOrder('MM'); # JPEG 2000 files are big-endian
1304             my %dirInfo = (
1305             RAF => $raf,
1306             DirName => 'JP2',
1307             OutFile => $$dirInfo{OutFile},
1308 6         36 );
1309 6         21 my $tagTablePtr = GetTagTable('Image::ExifTool::Jpeg2000::Main');
1310 6         44 return $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
1311             }
1312              
1313             #------------------------------------------------------------------------------
1314             # Read meta information from a JPEG XL image
1315             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1316             # Returns: 1 on success, 0 if this wasn't a valid JPEG XL file, -1 on write error
1317             sub ProcessJXL($$)
1318             {
1319 3     3 0 10 my ($et, $dirInfo) = @_;
1320 3         9 my $raf = $$dirInfo{RAF};
1321 3         7 my $outfile = $$dirInfo{OutFile};
1322 3         8 my ($hdr, $buff);
1323              
1324 3 50       10 return 0 unless $raf->Read($hdr,12) == 12;
1325 3 100       31 if ($hdr eq "\0\0\0\x0cJXL \x0d\x0a\x87\x0a") {
    50          
1326             # JPEG XL in ISO BMFF container
1327 1         5 $$et{IsJXL} = 1;
1328             } elsif ($hdr =~ /^\xff\x0a/) {
1329             # JPEG XL codestream
1330 2 100       8 if ($outfile) {
1331 1 50       6 if ($$et{OPTIONS}{IgnoreMinorErrors}) {
1332 1         5 $et->Warn('Wrapped JXL codestream in ISO BMFF container');
1333             } else {
1334 0         0 $et->Error('Will wrap JXL codestream in ISO BMFF container for writing',1);
1335 0         0 return 0;
1336             }
1337 1         4 $$et{IsJXL} = 2;
1338 1         4 my $buff = "\0\0\0\x0cJXL \x0d\x0a\x87\x0a\0\0\0\x14ftypjxl \0\0\0\0jxl ";
1339             # add metadata to empty ISO BMFF container
1340 1         5 $$dirInfo{RAF} = new File::RandomAccess(\$buff);
1341             } else {
1342 1         5 $et->SetFileType('JXL Codestream','image/jxl', 'jxl');
1343 1         6 return ProcessJXLCodestream($et, \$hdr);
1344             }
1345             } else {
1346 0         0 return 0;
1347             }
1348 2 50       7 $raf->Seek(0,0) or $et->Error('Seek error'), return 0;
1349              
1350 2         19 my $success = ProcessJP2($et, $dirInfo);
1351              
1352 2 50 66     18 if ($outfile and $success > 0 and $$et{IsJXL} == 2) {
      66        
1353             # attach the JXL codestream box to the ISO BMFF file
1354 1 50       6 $raf->Seek(0,2) or return -1;
1355 1         6 my $size = $raf->Tell();
1356 1 50       8 $raf->Seek(0,0) or return -1;
1357 1         14 SetByteOrder('MM');
1358 1 50       13 Write($outfile, Set32u($size + 8), 'jxlc') or return -1;
1359 1         9 while ($raf->Read($buff, 65536)) {
1360 1 50       8 Write($outfile, $buff) or return -1;
1361             }
1362             }
1363 2         8 return $success;
1364             }
1365              
1366             1; # end
1367              
1368             __END__