#------------------------------------------------------------------------------ # File: CanonVRD.pm # # Description: Read/write Canon VRD and DR4 information # # Revisions: 2006/10/30 - P. Harvey Created # 2007/10/23 - PH Added new VRD 3.0 tags # 2008/08/29 - PH Added new VRD 3.4 tags # 2008/12/02 - PH Added new VRD 3.5 tags # 2010/06/18 - PH Support variable-length CustomPictureStyle data # 2010/09/14 - PH Added r/w support for XMP in VRD # 2015/05/16 - PH Added DR4 support (DPP 4.1.50.0) # 2018/03/13 - PH Update to DPP 4.8.20 # # References: 1) Bogdan private communication (Canon DPP v3.4.1.1) # 2) Gert Kello private communiation (DPP 3.8) #------------------------------------------------------------------------------ package Image::ExifTool::CanonVRD; use strict; use vars qw($VERSION); use Image::ExifTool qw(:DataAccess :Utils); use Image::ExifTool::Canon; $VERSION = '1.32'; sub ProcessCanonVRD($$;$); sub WriteCanonVRD($$;$); sub ProcessEditData($$$); sub ProcessIHL($$$); sub ProcessIHLExif($$$); sub ProcessDR4($$;$); sub SortDR4($$); # map for adding directories to VRD my %vrdMap = ( XMP => 'CanonVRD', CanonVRD => 'VRD', ); my %noYes = ( PrintConvColumns => 2, PrintConv => { 0 => 'No', 1 => 'Yes' }, ); # DR4 format codes my %vrdFormat = ( 1 => 'int32u', 2 => 'string', 8 => 'int32u', 9 => 'int32s', 13 => 'double', 33 => 'int32u', # (array) 38 => 'double', # (array) # 254 => 'undef', ? 255 => 'undef', ); # empty VRD header/footer for creating VRD from scratch my $blankHeader = "CANON OPTIONAL DATA\0\0\x01\0\0\0\0\0\0"; my $blankFooter = "CANON OPTIONAL DATA\0" . ("\0" x 42) . "\xff\xd9"; # main tag table blocks in CanonVRD trailer (ref PH) %Image::ExifTool::CanonVRD::Main = ( WRITE_PROC => \&WriteCanonVRD, PROCESS_PROC => \&ProcessCanonVRD, NOTES => q{ Canon Digital Photo Professional writes VRD (Recipe Data) information as a trailer record to JPEG, TIFF, CRW and CR2 images, or as stand-alone VRD or DR4 files. The tags listed below represent information found in these records. The complete VRD/DR4 data record may be accessed as a block using the Extra 'CanonVRD' or 'CanonDR4' tag, but this tag is not extracted or copied unless specified explicitly. }, 0xffff00f4 => { Name => 'EditData', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Edit' }, }, 0xffff00f5 => { Name => 'IHLData', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::IHL' }, }, 0xffff00f6 => { Name => 'XMP', Flags => [ 'Binary', 'Protected' ], Writable => 'undef', # allow writing/deleting as a block SubDirectory => { DirName => 'XMP', TagTable => 'Image::ExifTool::XMP::Main', }, }, 0xffff00f7 => { Name => 'Edit4Data', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Edit4' }, }, ); # the VRD edit information is divided into sections %Image::ExifTool::CanonVRD::Edit = ( WRITE_PROC => \&ProcessEditData, PROCESS_PROC => \&ProcessEditData, VARS => { ID_LABEL => 'Index' }, # change TagID label in documentation NOTES => 'Canon VRD edit information.', 0 => { Name => 'VRD1', Size => 0x272, # size of version 1.0 edit information in bytes SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Ver1' }, }, 1 => { Name => 'VRDStampTool', Size => 0, # size is variable, and obtained from int32u at directory start SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::StampTool' }, }, 2 => { Name => 'VRD2', Size => undef, # size is the remaining edit data SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::Ver2' }, }, ); # Canon DPP version 4 edit information %Image::ExifTool::CanonVRD::Edit4 = ( WRITE_PROC => \&ProcessEditData, PROCESS_PROC => \&ProcessEditData, VARS => { ID_LABEL => 'Index' }, # change TagID label in documentation NOTES => 'Canon DPP version 4 edit information.', 0 => { Name => 'DR4', Size => undef, # size is the remaining edit data SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::DR4' }, }, ); # "IHL Created Optional Item Data" tags (not yet writable) %Image::ExifTool::CanonVRD::IHL = ( PROCESS_PROC => \&ProcessIHL, TAG_PREFIX => 'VRD_IHL', GROUPS => { 2 => 'Image' }, 1 => [ # this contains edited TIFF-format data, with an original IFD at 0x0008 # and an edited IFD with offset given in the TIFF header. { Name => 'IHL_EXIF', Condition => '$self->Options("ExtractEmbedded")', SubDirectory => { TagTable => 'Image::ExifTool::Exif::Main', ProcessProc => \&ProcessIHLExif, }, },{ Name => 'IHL_EXIF', Notes => q{ extracted as a block if the Unknown option is used, or processed as the first sub-document with the ExtractEmbedded option }, Binary => 1, Unknown => 1, }, ], # 2 - written by DPP 3.0.2.6, and it looks something like edit data, # but I haven't decoded it yet - PH 3 => { # (same size as the PreviewImage with DPP 3.0.2.6) Name => 'ThumbnailImage', Groups => { 2 => 'Preview' }, Binary => 1, }, 4 => { Name => 'PreviewImage', Groups => { 2 => 'Preview' }, Binary => 1, }, 5 => { Name => 'RawCodecVersion', ValueConv => '$val =~ s/\0.*//s; $val', # truncate string at null }, 6 => { Name => 'CRCDevelParams', Binary => 1, Unknown => 1, }, ); # VRD version 1 tags (ref PH) %Image::ExifTool::CanonVRD::Ver1 = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, WRITE_PROC => \&Image::ExifTool::WriteBinaryData, CHECK_PROC => \&Image::ExifTool::CheckBinaryData, WRITABLE => 1, FIRST_ENTRY => 0, GROUPS => { 2 => 'Image' }, DATAMEMBER => [ 0x002 ], # necessary for writing # # RAW image adjustment # 0x002 => { Name => 'VRDVersion', Format => 'int16u', Writable => 0, DataMember => 'VRDVersion', RawConv => '$$self{VRDVersion} = $val', PrintConv => '$val =~ s/^(\d)(\d*)(\d)$/$1.$2.$3/; $val', }, 0x006 => { Name => 'WBAdjRGGBLevels', Format => 'int16u[4]', }, 0x018 => { Name => 'WhiteBalanceAdj', Format => 'int16u', PrintConvColumns => 2, PrintConv => { 0 => 'Auto', 1 => 'Daylight', 2 => 'Cloudy', 3 => 'Tungsten', 4 => 'Fluorescent', 5 => 'Flash', 8 => 'Shade', 9 => 'Kelvin', 30 => 'Manual (Click)', 31 => 'Shot Settings', }, }, 0x01a => { Name => 'WBAdjColorTemp', Format => 'int16u', }, # 0x01c similar to 0x006 0x024 => { Name => 'WBFineTuneActive', Format => 'int16u', %noYes, }, 0x028 => { Name => 'WBFineTuneSaturation', Format => 'int16u', }, 0x02c => { Name => 'WBFineTuneTone', Format => 'int16u', }, 0x02e => { Name => 'RawColorAdj', Format => 'int16u', PrintConv => { 0 => 'Shot Settings', 1 => 'Faithful', 2 => 'Custom', }, }, 0x030 => { Name => 'RawCustomSaturation', Format => 'int32s', }, 0x034 => { Name => 'RawCustomTone', Format => 'int32s', }, 0x038 => { Name => 'RawBrightnessAdj', Format => 'int32s', ValueConv => '$val / 6000', ValueConvInv => 'int($val * 6000 + ($val < 0 ? -0.5 : 0.5))', PrintConv => 'sprintf("%.2f",$val)', PrintConvInv => '$val', }, 0x03c => { Name => 'ToneCurveProperty', Format => 'int16u', PrintConvColumns => 2, PrintConv => { 0 => 'Shot Settings', 1 => 'Linear', 2 => 'Custom 1', 3 => 'Custom 2', 4 => 'Custom 3', 5 => 'Custom 4', 6 => 'Custom 5', }, }, # 0x040 usually "10 9 2" 0x07a => { Name => 'DynamicRangeMin', Format => 'int16u', }, 0x07c => { Name => 'DynamicRangeMax', Format => 'int16u', }, # 0x0c6 usually "10 9 2" # # RGB image adjustment # 0x110 => { Name => 'ToneCurveActive', Format => 'int16u', %noYes, }, 0x113 => { Name => 'ToneCurveMode', PrintConv => { 0 => 'RGB', 1 => 'Luminance' }, }, 0x114 => { Name => 'BrightnessAdj', Format => 'int8s', }, 0x115 => { Name => 'ContrastAdj', Format => 'int8s', }, 0x116 => { Name => 'SaturationAdj', Format => 'int16s', }, 0x11e => { Name => 'ColorToneAdj', Notes => 'in degrees, so -1 is the same as 359', Format => 'int32s', }, 0x126 => { Name => 'LuminanceCurvePoints', Format => 'int16u[21]', PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', }, 0x150 => { Name => 'LuminanceCurveLimits', Notes => '4 numbers: input and output highlight and shadow points', Format => 'int16u[4]', }, 0x159 => { Name => 'ToneCurveInterpolation', PrintConv => { 0 => 'Curve', 1 => 'Straight' }, }, 0x160 => { Name => 'RedCurvePoints', Format => 'int16u[21]', PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', }, # 0x193 same as 0x159 0x19a => { Name => 'GreenCurvePoints', Format => 'int16u[21]', PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', }, # 0x1cd same as 0x159 0x1d4 => { Name => 'BlueCurvePoints', Format => 'int16u[21]', PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', }, 0x18a => { Name => 'RedCurveLimits', Format => 'int16u[4]', }, 0x1c4 => { Name => 'GreenCurveLimits', Format => 'int16u[4]', }, 0x1fe => { Name => 'BlueCurveLimits', Format => 'int16u[4]', }, # 0x207 same as 0x159 0x20e => { Name => 'RGBCurvePoints', Format => 'int16u[21]', PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', }, 0x238 => { Name => 'RGBCurveLimits', Format => 'int16u[4]', }, # 0x241 same as 0x159 0x244 => { Name => 'CropActive', Format => 'int16u', %noYes, }, 0x246 => { Name => 'CropLeft', Notes => 'crop coordinates in original unrotated image', Format => 'int16u', }, 0x248 => { Name => 'CropTop', Format => 'int16u', }, 0x24a => { Name => 'CropWidth', Format => 'int16u', }, 0x24c => { Name => 'CropHeight', Format => 'int16u', }, 0x25a => { Name => 'SharpnessAdj', Format => 'int16u', }, 0x260 => { Name => 'CropAspectRatio', Format => 'int16u', PrintConv => { 0 => 'Free', 1 => '3:2', 2 => '2:3', 3 => '4:3', 4 => '3:4', 5 => 'A-size Landscape', 6 => 'A-size Portrait', 7 => 'Letter-size Landscape', 8 => 'Letter-size Portrait', 9 => '4:5', 10 => '5:4', 11 => '1:1', 12 => 'Circle', 65535 => 'Custom', }, }, 0x262 => { Name => 'ConstrainedCropWidth', Format => 'float', PrintConv => 'sprintf("%.7g",$val)', PrintConvInv => '$val', }, 0x266 => { Name => 'ConstrainedCropHeight', Format => 'float', PrintConv => 'sprintf("%.7g",$val)', PrintConvInv => '$val', }, 0x26a => { Name => 'CheckMark', Format => 'int16u', PrintConv => { 0 => 'Clear', 1 => 1, 2 => 2, 3 => 3, }, }, 0x26e => { Name => 'Rotation', Format => 'int16u', PrintConv => { 0 => 0, 1 => 90, 2 => 180, 3 => 270, }, }, 0x270 => { Name => 'WorkColorSpace', Format => 'int16u', PrintConv => { 0 => 'sRGB', 1 => 'Adobe RGB', 2 => 'Wide Gamut RGB', 3 => 'Apple RGB', 4 => 'ColorMatch RGB', }, }, # (VRD 1.0.0 edit data ends here -- 0x272 bytes) ); # VRD Stamp Tool tags (ref PH) %Image::ExifTool::CanonVRD::StampTool = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, GROUPS => { 2 => 'Image' }, 0x00 => { Name => 'StampToolCount', Format => 'int32u', }, ); # VRD version 2 and 3 tags (ref PH) %Image::ExifTool::CanonVRD::Ver2 = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, WRITE_PROC => \&Image::ExifTool::WriteBinaryData, CHECK_PROC => \&Image::ExifTool::CheckBinaryData, WRITABLE => 1, FIRST_ENTRY => 0, FORMAT => 'int16s', DATAMEMBER => [ 0x58, 0xdc, 0xdf, 0xe0 ], # (required for DataMember and var-format tags) IS_SUBDIR => [ 0xe0 ], GROUPS => { 2 => 'Image' }, NOTES => 'Tags added in DPP version 2.0 and later.', 0x02 => { Name => 'PictureStyle', PrintConvColumns => 2, PrintConv => { 0 => 'Standard', 1 => 'Portrait', 2 => 'Landscape', 3 => 'Neutral', 4 => 'Faithful', 5 => 'Monochrome', 6 => 'Unknown?', # PH (maybe in-camera custom picture style?) 7 => 'Custom', }, }, 0x03 => { Name => 'IsCustomPictureStyle', %noYes }, # 0x08: 3 # 0x09: 4095 # 0x0a: 0 # 0x0b: 4095 # 0x0c: 0 0x0d => 'StandardRawColorTone', 0x0e => 'StandardRawSaturation', 0x0f => 'StandardRawContrast', 0x10 => { Name => 'StandardRawLinear', %noYes }, 0x11 => 'StandardRawSharpness', 0x12 => 'StandardRawHighlightPoint', 0x13 => 'StandardRawShadowPoint', 0x14 => 'StandardOutputHighlightPoint', #2 0x15 => 'StandardOutputShadowPoint', #2 0x16 => 'PortraitRawColorTone', 0x17 => 'PortraitRawSaturation', 0x18 => 'PortraitRawContrast', 0x19 => { Name => 'PortraitRawLinear', %noYes }, 0x1a => 'PortraitRawSharpness', 0x1b => 'PortraitRawHighlightPoint', 0x1c => 'PortraitRawShadowPoint', 0x1d => 'PortraitOutputHighlightPoint', 0x1e => 'PortraitOutputShadowPoint', 0x1f => 'LandscapeRawColorTone', 0x20 => 'LandscapeRawSaturation', 0x21 => 'LandscapeRawContrast', 0x22 => { Name => 'LandscapeRawLinear', %noYes }, 0x23 => 'LandscapeRawSharpness', 0x24 => 'LandscapeRawHighlightPoint', 0x25 => 'LandscapeRawShadowPoint', 0x26 => 'LandscapeOutputHighlightPoint', 0x27 => 'LandscapeOutputShadowPoint', 0x28 => 'NeutralRawColorTone', 0x29 => 'NeutralRawSaturation', 0x2a => 'NeutralRawContrast', 0x2b => { Name => 'NeutralRawLinear', %noYes }, 0x2c => 'NeutralRawSharpness', 0x2d => 'NeutralRawHighlightPoint', 0x2e => 'NeutralRawShadowPoint', 0x2f => 'NeutralOutputHighlightPoint', 0x30 => 'NeutralOutputShadowPoint', 0x31 => 'FaithfulRawColorTone', 0x32 => 'FaithfulRawSaturation', 0x33 => 'FaithfulRawContrast', 0x34 => { Name => 'FaithfulRawLinear', %noYes }, 0x35 => 'FaithfulRawSharpness', 0x36 => 'FaithfulRawHighlightPoint', 0x37 => 'FaithfulRawShadowPoint', 0x38 => 'FaithfulOutputHighlightPoint', 0x39 => 'FaithfulOutputShadowPoint', 0x3a => { Name => 'MonochromeFilterEffect', PrintConv => { -2 => 'None', -1 => 'Yellow', 0 => 'Orange', 1 => 'Red', 2 => 'Green', }, }, 0x3b => { Name => 'MonochromeToningEffect', PrintConv => { -2 => 'None', -1 => 'Sepia', 0 => 'Blue', 1 => 'Purple', 2 => 'Green', }, }, 0x3c => 'MonochromeContrast', 0x3d => { Name => 'MonochromeLinear', %noYes }, 0x3e => 'MonochromeSharpness', 0x3f => 'MonochromeRawHighlightPoint', 0x40 => 'MonochromeRawShadowPoint', 0x41 => 'MonochromeOutputHighlightPoint', 0x42 => 'MonochromeOutputShadowPoint', 0x45 => { Name => 'UnknownContrast', Unknown => 1 }, 0x46 => { Name => 'UnknownLinear', %noYes, Unknown => 1 }, 0x47 => { Name => 'UnknownSharpness', Unknown => 1 }, 0x48 => { Name => 'UnknownRawHighlightPoint', Unknown => 1 }, 0x49 => { Name => 'UnknownRawShadowPoint', Unknown => 1 }, 0x4a => { Name => 'UnknownOutputHighlightPoint',Unknown => 1 }, 0x4b => { Name => 'UnknownOutputShadowPoint', Unknown => 1 }, 0x4c => 'CustomColorTone', 0x4d => 'CustomSaturation', 0x4e => 'CustomContrast', 0x4f => { Name => 'CustomLinear', %noYes }, 0x50 => 'CustomSharpness', 0x51 => 'CustomRawHighlightPoint', 0x52 => 'CustomRawShadowPoint', 0x53 => 'CustomOutputHighlightPoint', 0x54 => 'CustomOutputShadowPoint', 0x58 => { Name => 'CustomPictureStyleData', Format => 'var_int16u', Binary => 1, Notes => 'variable-length data structure', Writable => 0, RawConv => 'length($val) == 2 ? undef : $val', # ignore if no data }, # (VRD 2.0.0 edit data ends here: 178 bytes, index 0x59) 0x5e => [{ Name => 'ChrominanceNoiseReduction', Condition => '$$self{VRDVersion} < 330', Notes => 'VRDVersion prior to 3.3.0', PrintConv => { 0 => 'Off', 58 => 'Low', 100 => 'High', }, },{ #1 Name => 'ChrominanceNoiseReduction', Notes => 'VRDVersion 3.3.0 or later', PrintHex => 1, PrintConvColumns => 4, PrintConv => { 0x00 => 0, 0x10 => 1, 0x21 => 2, 0x32 => 3, 0x42 => 4, 0x53 => 5, 0x64 => 6, 0x74 => 7, 0x85 => 8, 0x96 => 9, 0xa6 => 10, 0xa7 => 11, 0xa8 => 12, 0xa9 => 13, 0xaa => 14, 0xab => 15, 0xac => 16, 0xad => 17, 0xae => 18, 0xaf => 19, 0xb0 => 20, }, }], 0x5f => [{ Name => 'LuminanceNoiseReduction', Condition => '$$self{VRDVersion} < 330', Notes => 'VRDVersion prior to 3.3.0', PrintConv => { 0 => 'Off', 65 => 'Low', 100 => 'High', }, },{ #1 Name => 'LuminanceNoiseReduction', Notes => 'VRDVersion 3.3.0 or later', PrintHex => 1, PrintConvColumns => 4, PrintConv => { 0x00 => 0, 0x41 => 1, 0x64 => 2, 0x6e => 3, 0x78 => 4, 0x82 => 5, 0x8c => 6, 0x96 => 7, 0xa0 => 8, 0xaa => 9, 0xb4 => 10, 0xb5 => 11, 0xb6 => 12, 0xb7 => 13, 0xb8 => 14, 0xb9 => 15, 0xba => 16, 0xbb => 17, 0xbc => 18, 0xbd => 19, 0xbe => 20, }, }], 0x60 => [{ Name => 'ChrominanceNR_TIFF_JPEG', Condition => '$$self{VRDVersion} < 330', Notes => 'VRDVersion prior to 3.3.0', PrintConv => { 0 => 'Off', 33 => 'Low', 100 => 'High', }, },{ #1 Name => 'ChrominanceNR_TIFF_JPEG', Notes => 'VRDVersion 3.3.0 or later', PrintHex => 1, PrintConvColumns => 4, PrintConv => { 0x00 => 0, 0x10 => 1, 0x21 => 2, 0x32 => 3, 0x42 => 4, 0x53 => 5, 0x64 => 6, 0x74 => 7, 0x85 => 8, 0x96 => 9, 0xa6 => 10, 0xa7 => 11, 0xa8 => 12, 0xa9 => 13, 0xaa => 14, 0xab => 15, 0xac => 16, 0xad => 17, 0xae => 18, 0xaf => 19, 0xb0 => 20, }, }], # 0x61: 1 # (VRD 3.0.0 edit data ends here: 196 bytes, index 0x62) 0x62 => { Name => 'ChromaticAberrationOn', %noYes }, 0x63 => { Name => 'DistortionCorrectionOn', %noYes }, 0x64 => { Name => 'PeripheralIlluminationOn', %noYes }, 0x65 => { Name => 'ColorBlur', %noYes }, 0x66 => { Name => 'ChromaticAberration', ValueConv => '$val / 0x400', ValueConvInv => 'int($val * 0x400 + 0.5)', PrintConv => 'sprintf("%.0f%%", $val * 100)', PrintConvInv => 'ToFloat($val) / 100', }, 0x67 => { Name => 'DistortionCorrection', ValueConv => '$val / 0x400', ValueConvInv => 'int($val * 0x400 + 0.5)', PrintConv => 'sprintf("%.0f%%", $val * 100)', PrintConvInv => 'ToFloat($val) / 100', }, 0x68 => { Name => 'PeripheralIllumination', ValueConv => '$val / 0x400', ValueConvInv => 'int($val * 0x400 + 0.5)', PrintConv => 'sprintf("%.0f%%", $val * 100)', PrintConvInv => 'ToFloat($val) / 100', }, 0x69 => { Name => 'AberrationCorrectionDistance', Notes => '100% = infinity', RawConv => '$val == 0x7fff ? undef : $val', ValueConv => '1 - $val / 0x400', ValueConvInv => 'int((1 - $val) * 0x400 + 0.5)', PrintConv => 'sprintf("%.0f%%", $val * 100)', PrintConvInv => 'ToFloat($val) / 100', }, 0x6a => 'ChromaticAberrationRed', 0x6b => 'ChromaticAberrationBlue', 0x6d => { #1 Name => 'LuminanceNR_TIFF_JPEG', Notes => 'val = raw / 10', ValueConv => '$val / 10', ValueConvInv => 'int($val * 10 + 0.5)', }, # (VRD 3.4.0 edit data ends here: 220 bytes, index 0x6e) 0x6e => { Name => 'AutoLightingOptimizerOn', %noYes }, 0x6f => { Name => 'AutoLightingOptimizer', PrintConv => { 100 => 'Low', 200 => 'Standard', 300 => 'Strong', 0x7fff => 'n/a', #1 }, }, # 0x71: 200 # 0x73: 100 # (VRD 3.5.0 edit data ends here: 232 bytes, index 0x74) 0x75 => { Name => 'StandardRawHighlight', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x76 => { Name => 'PortraitRawHighlight', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x77 => { Name => 'LandscapeRawHighlight', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x78 => { Name => 'NeutralRawHighlight', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x79 => { Name => 'FaithfulRawHighlight', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x7a => { Name => 'MonochromeRawHighlight', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x7b => { Name => 'UnknownRawHighlight', Unknown => 1, ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x7c => { Name => 'CustomRawHighlight', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x7e => { Name => 'StandardRawShadow', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x7f => { Name => 'PortraitRawShadow', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x80 => { Name => 'LandscapeRawShadow', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x81 => { Name => 'NeutralRawShadow', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x82 => { Name => 'FaithfulRawShadow', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x83 => { Name => 'MonochromeRawShadow', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x84 => { Name => 'UnknownRawShadow', Unknown => 1, ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x85 => { Name => 'CustomRawShadow', ValueConv => '$val / 10', ValueConvInv => '$val * 10', }, 0x8b => { #2 Name => 'AngleAdj', Format => 'int32s', ValueConv => '$val / 100', ValueConvInv => '$val * 100', }, 0x8e => { Name => 'CheckMark2', Format => 'int16u', PrintConvColumns => 2, PrintConv => { 0 => 'Clear', 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, }, }, # (VRD 3.8.0 edit data ends here: 286 bytes, index 0x8f) 0x90 => { Name => 'UnsharpMask', PrintConv => { 0 => 'Off', 1 => 'On' }, }, 0x92 => 'StandardUnsharpMaskStrength', 0x94 => 'StandardUnsharpMaskFineness', 0x96 => 'StandardUnsharpMaskThreshold', 0x98 => 'PortraitUnsharpMaskStrength', 0x9a => 'PortraitUnsharpMaskFineness', 0x9c => 'PortraitUnsharpMaskThreshold', 0x9e => 'LandscapeUnsharpMaskStrength', 0xa0 => 'LandscapeUnsharpMaskFineness', 0xa2 => 'LandscapeUnsharpMaskThreshold', 0xa4 => 'NeutraUnsharpMaskStrength', 0xa6 => 'NeutralUnsharpMaskFineness', 0xa8 => 'NeutralUnsharpMaskThreshold', 0xaa => 'FaithfulUnsharpMaskStrength', 0xac => 'FaithfulUnsharpMaskFineness', 0xae => 'FaithfulUnsharpMaskThreshold', 0xb0 => 'MonochromeUnsharpMaskStrength', 0xb2 => 'MonochromeUnsharpMaskFineness', 0xb4 => 'MonochromeUnsharpMaskThreshold', 0xb6 => 'CustomUnsharpMaskStrength', 0xb8 => 'CustomUnsharpMaskFineness', 0xba => 'CustomUnsharpMaskThreshold', 0xbc => 'CustomDefaultUnsharpStrength', 0xbe => 'CustomDefaultUnsharpFineness', 0xc0 => 'CustomDefaultUnsharpThreshold', # (VRD 3.9.1 edit data ends here: 392 bytes, index 0xc4) # 0xc9: 3 - some RawSharpness # 0xca: 4095 - some RawHighlightPoint # 0xcb: 0 - some RawShadowPoint # 0xcc: 4095 - some OutputHighlightPoint # 0xcd: 0 - some OutputShadowPoint # 0xd1: 3 - some UnsharpMaskStrength # 0xd3: 7 - some UnsharpMaskFineness # 0xd5: 3,4 - some UnsharpMaskThreshold 0xd6 => { Name => 'CropCircleActive', %noYes }, 0xd7 => 'CropCircleX', 0xd8 => 'CropCircleY', 0xd9 => 'CropCircleRadius', # 0xda: 0, 1 # 0xdb: 100 0xdc => { Name => 'DLOOn', DataMember => 'DLOOn', RawConv => '$$self{DLOOn} = $val', %noYes, }, 0xdd => 'DLOSetting', # (VRD 3.11.0 edit data ends here: 444 bytes, index 0xde) 0xde => { Name => 'DLOShootingDistance', Notes => '100% = infinity', RawConv => '$val == 0x7fff ? undef : $val', ValueConv => '1 - $val / 0x400', ValueConvInv => 'int((1 - $val) * 0x400 + 0.5)', PrintConv => 'sprintf("%.0f%%", $val * 100)', PrintConvInv => 'ToFloat($val) / 100', }, 0xdf => { Name => 'DLODataLength', DataMember => 'DLODataLength', Format => 'int32u', Writable => 0, RawConv => '$$self{DLODataLength} = $val', }, 0xe0 => { # (yes, this overlaps DLODataLength) Name => 'DLOInfo', # - have seen DLODataLengths of 65536,64869 when DLO is Off, so must test DLOOn flag Condition => '$$self{DLOOn}', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::DLOInfo' }, Hook => '$varSize += $$self{DLODataLength} + 0x16', }, 0xe1 => 'CameraRawColorTone', # (VRD 3.11.2 edit data ends here: 452 bytes, index 0xe2, unless DLO is on) 0xe2 => 'CameraRawSaturation', 0xe3 => 'CameraRawContrast', 0xe4 => { Name => 'CameraRawLinear', %noYes }, 0xe5 => 'CameraRawSharpness', 0xe6 => 'CameraRawHighlightPoint', 0xe7 => 'CameraRawShadowPoint', 0xe8 => 'CameraRawOutputHighlightPoint', 0xe9 => 'CameraRawOutputShadowPoint', ); # DLO tags (ref PH) %Image::ExifTool::CanonVRD::DLOInfo = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, WRITE_PROC => \&Image::ExifTool::WriteBinaryData, CHECK_PROC => \&Image::ExifTool::CheckBinaryData, WRITABLE => 1, FIRST_ENTRY => 1, FORMAT => 'int16s', GROUPS => { 2 => 'Image' }, NOTES => 'Tags added when DLO (Digital Lens Optimizer) is on.', # 0x01 - seen 3112,3140 0x04 => 'DLOSettingApplied', 0x05 => { Name => 'DLOVersion', #(NC) Format => 'string[10]', }, 0x0a => { Name => 'DLOData', LargeTag => 1, # large tag, so avoid storing unnecessarily Notes => 'variable-length Digital Lens Optimizer data, stored in JPEG-like format', Format => 'undef[$$self{DLODataLength}]', Writable => 0, Binary => 1, }, ); # VRD version 4 tags (ref PH) %Image::ExifTool::CanonVRD::DR4 = ( PROCESS_PROC => \&ProcessDR4, WRITE_PROC => \&ProcessDR4, WRITABLE => 1, GROUPS => { 2 => 'Image' }, VARS => { HEX_ID => 1, SORT_PROC => \&SortDR4 }, NOTES => q{ Tags written by Canon DPP version 4 in CanonVRD trailers and DR4 files. Each tag has three associated flag words which are stored with the directory entry, some of which are extracted as a separate tag, indicated in the table below by a decimal appended to the tag ID (.0, .1 or .2). }, header => { Name => 'DR4Header', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::DR4Header' }, }, 0x10002 => 'Rotation', # left/right rotation 90,180,270 0x10003 => 'AngleAdj', # crop angle # 0x10018 - fmt=8: 0 # 0x10020 - fmt=2: '' 0x10021 => 'CustomPictureStyle', # (string) 0x10101 => { Name => 'CheckMark', PrintConv => { 0 => 'Clear', 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, }, }, 0x10200 => { Name => 'WorkColorSpace', PrintConv => { 1 => 'sRGB', 2 => 'Adobe RGB', 3 => 'Wide Gamut RGB', 4 => 'Apple RGB', 5 => 'ColorMatch RGB', }, }, # 0x10201 - fmt=9: 0 # 0x10f20 - fmt=9: 350 0x20001 => 'RawBrightnessAdj', 0x20101 => { Name => 'WhiteBalanceAdj', PrintConvColumns => 2, PrintConv => { -1 => 'Manual (Click)', 0 => 'Auto', 1 => 'Daylight', 2 => 'Cloudy', 3 => 'Tungsten', 4 => 'Fluorescent', 5 => 'Flash', 8 => 'Shade', 9 => 'Kelvin', 255 => 'Shot Settings', }, }, 0x20102 => 'WBAdjColorTemp', 0x20105 => 'WBAdjMagentaGreen', 0x20106 => 'WBAdjBlueAmber', 0x20125 => { Name => 'WBAdjRGGBLevels', PrintConv => '$val =~ s/^\d+ //; $val', # remove first integer (14: what is this for?) PrintConvInv => '"14 $val"', }, 0x20200 => { Name => 'GammaLinear', %noYes }, 0x20301 => { Name => 'PictureStyle', PrintHex => 1, PrintConv => { 0x81 => 'Standard', 0x82 => 'Portrait', 0x83 => 'Landscape', 0x84 => 'Neutral', 0x85 => 'Faithful', 0x86 => 'Monochrome', 0x87 => 'Auto', 0x88 => 'Fine Detail', 0xf0 => 'Shot Settings', 0xff => 'Custom', }, }, # 0x20302 - Gamma curve data 0x20303 => 'ContrastAdj', 0x20304 => 'ColorToneAdj', 0x20305 => 'ColorSaturationAdj', 0x20306 => { Name => 'MonochromeToningEffect', PrintConv => { 0 => 'None', 1 => 'Sepia', 2 => 'Blue', 3 => 'Purple', 4 => 'Green', }, }, 0x20307 => { Name => 'MonochromeFilterEffect', PrintConv => { 0 => 'None', 1 => 'Yellow', 2 => 'Orange', 3 => 'Red', 4 => 'Green', }, }, 0x20308 => 'UnsharpMaskStrength', 0x20309 => 'UnsharpMaskFineness', 0x2030a => 'UnsharpMaskThreshold', 0x2030b => 'ShadowAdj', 0x2030c => 'HighlightAdj', 0x20310 => { Name => 'SharpnessAdj', PrintConv => { 0 => 'Sharpness', 1 => 'Unsharp Mask', }, }, '0x20310.0' => { Name => 'SharpnessAdjOn', %noYes }, 0x20311 => 'SharpnessStrength', 0x20400 => { Name => 'ToneCurve', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::ToneCurve' }, }, '0x20400.1' => { Name => 'ToneCurveOriginal', %noYes }, # 0x20401 - fmt=33 (312 bytes) 0x20410 => 'ToneCurveBrightness', 0x20411 => 'ToneCurveContrast', 0x20500 => { Name => 'AutoLightingOptimizer', PrintConv => { 0 => 'Low', 1 => 'Standard', 2 => 'Strong', }, }, '0x20500.0' => { Name => 'AutoLightingOptimizerOn', Notes => 'ignored if gamma is linear', %noYes, }, # 0x20501 - fmt=13: 0 # 0x20502 - fmt=13: 0 0x20600 => 'LuminanceNoiseReduction', 0x20601 => 'ChrominanceNoiseReduction', # 0x20650 - fmt=9: 0 (JPG images) 0x20670 => 'ColorMoireReduction', '0x20670.0' => { Name => 'ColorMoireReductionOn', %noYes }, 0x20701 => { Name => 'ShootingDistance', Notes => '100% = infinity', ValueConv => '$val / 10', ValueConvInv => '$val * 10', PrintConv => 'sprintf("%.0f%%", $val * 100)', PrintConvInv => 'ToFloat($val) / 100', }, 0x20702 => { Name => 'PeripheralIllumination', PrintConv => 'sprintf "%g", $val', PrintConvInv => '$val', }, '0x20702.0' => { Name => 'PeripheralIlluminationOn', %noYes }, 0x20703 => { Name => 'ChromaticAberration', PrintConv => 'sprintf "%g", $val', PrintConvInv => '$val', }, '0x20703.0' => { Name => 'ChromaticAberrationOn', %noYes }, 0x20704 => { Name => 'ColorBlurOn', %noYes }, 0x20705 => { Name => 'DistortionCorrection', PrintConv => 'sprintf "%g", $val', PrintConvInv => '$val', }, '0x20705.0' => { Name => 'DistortionCorrectionOn', %noYes }, 0x20706 => 'DLOSetting', '0x20706.0' => { Name => 'DLOOn', %noYes }, 0x20707 => { Name => 'ChromaticAberrationRed', PrintConv => 'sprintf "%g", $val', PrintConvInv => '$val', }, 0x20708 => { Name => 'ChromaticAberrationBlue', PrintConv => 'sprintf "%g", $val', PrintConvInv => '$val', }, 0x20709 => { Name => 'DistortionEffect', PrintConv => { 0 => 'Shot Settings', 1 => 'Emphasize Linearity', 2 => 'Emphasize Distance', 3 => 'Emphasize Periphery', 4 => 'Emphasize Center', }, }, # 0x20800 - fmt=1: 0 # 0x20801 - fmt=1: 0 0x2070b => { Name => 'DiffractionCorrectionOn', %noYes }, 0x20900 => 'ColorHue', 0x20901 => 'SaturationAdj', 0x20910 => 'RedHSL', 0x20911 => 'OrangeHSL', 0x20912 => 'YellowHSL', 0x20913 => 'GreenHSL', 0x20914 => 'AquaHSL', 0x20915 => 'BlueHSL', 0x20916 => 'PurpleHSL', 0x20917 => 'MagentaHSL', 0x20a00 => { Name => 'GammaInfo', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::GammaInfo' }, }, # 0x20a01 - Auto picture style settings # 0x20a02 - Standard picture style settings # 0x20a03 - Portrait picture style settings # 0x20a04 - Landscape picture style settings # 0x20a05 - Neutral picture style settings # 0x20a06 - Faithful picture style settings # 0x20a07 - Monochrome picture style settings # 0x20a08 - (unknown picture style settings) # 0x20a09 - Custom picture style settings # 0x20a20 - Fine Detail picture style settings 0x30101 => { Name => 'CropAspectRatio', PrintConv => { 0 => 'Free', 1 => 'Custom', 2 => '1:1', 3 => '3:2', 4 => '2:3', 5 => '4:3', 6 => '3:4', 7 => '5:4', 8 => '4:5', 9 => '16:9', 10 => '9:16', }, }, 0x30102 => 'CropAspectRatioCustom', # 0x30103 - fmt=33: "0 0 8" 0xf0100 => { Name => 'CropInfo', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::CropInfo' }, }, 0xf0500 => { Name => 'CustomPictureStyleData', Binary => 1, }, 0xf0510 => { Name => 'StampInfo', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::StampInfo' }, }, 0xf0511 => { Name => 'DustInfo', SubDirectory => { TagTable => 'Image::ExifTool::CanonVRD::DustInfo' }, }, 0xf0512 => 'LensFocalLength', # 0xf0521 - DLO data # 0xf0520 - DLO data # 0xf0530 - created when dust delete data applied (4 bytes, all zero) # 0xf0600 - fmt=253 (2308 bytes, JPG images) # 0xf0601 - fmt=253 (2308 bytes, JPG images) # 0x1ff52c - values: 129,130,132 (related to custom picture style somehow) # to do: # - find 8-15mm CR2 sample and decode linear distortion effect fine-tune ); # Version 4 header information (ref PH) %Image::ExifTool::CanonVRD::DR4Header = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, WRITE_PROC => \&Image::ExifTool::WriteBinaryData, CHECK_PROC => \&Image::ExifTool::CheckBinaryData, WRITABLE => 1, FIRST_ENTRY => 0, FORMAT => 'int32u', GROUPS => { 2 => 'Image' }, # 0 - value: 'IIII' (presumably byte order) # 1 - value: 0x00040004 (currently use this for magic number) # 2 - value: 6 3 => { Name => 'DR4CameraModel', Writable => 'int32u', PrintHex => 1, SeparateTable => 'Canon CanonModelID', PrintConv => \%Image::ExifTool::Canon::canonModelID, }, # 4 - value: 3 # 5 - value: 4 # 6 - value: 5 # 7 - DR4 directory entry count ); # Version 4 RGB tone curve information (ref PH) %Image::ExifTool::CanonVRD::ToneCurve = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, WRITE_PROC => \&Image::ExifTool::WriteBinaryData, CHECK_PROC => \&Image::ExifTool::CheckBinaryData, WRITABLE => 1, FIRST_ENTRY => 0, FORMAT => 'int32u', GROUPS => { 2 => 'Image' }, 0x00 => { Name => 'ToneCurveColorSpace', PrintConv => { 0 => 'RGB', 1 => 'Luminance', }, }, 0x01 => { Name => 'ToneCurveShape', PrintConv => { 0 => 'Curve', 1 => 'Straight', }, }, 0x03 => { Name => 'ToneCurveInputRange', Format => 'int32u[2]', Notes => '255 max' }, 0x05 => { Name => 'ToneCurveOutputRange', Format => 'int32u[2]', Notes => '255 max' }, 0x07 => { Name => 'RGBCurvePoints', Format => 'int32u[21]', PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', }, 0x0a => 'ToneCurveX', 0x0b => 'ToneCurveY', 0x2d => { Name => 'RedCurvePoints', Format => 'int32u[21]', PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', }, 0x53 => { Name => 'GreenCurvePoints', Format => 'int32u[21]', PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', }, 0x79 => { Name => 'BlueCurvePoints', Format => 'int32u[21]', PrintConv => 'Image::ExifTool::CanonVRD::ToneCurvePrint($val)', PrintConvInv => 'Image::ExifTool::CanonVRD::ToneCurvePrintInv($val)', }, ); # Version 4 gamma curve information (ref PH) %Image::ExifTool::CanonVRD::GammaInfo = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, WRITE_PROC => \&Image::ExifTool::WriteBinaryData, CHECK_PROC => \&Image::ExifTool::CheckBinaryData, WRITABLE => 1, FIRST_ENTRY => 0, FORMAT => 'double', GROUPS => { 2 => 'Image' }, 0x02 => 'GammaContrast', 0x03 => 'GammaColorTone', 0x04 => 'GammaSaturation', 0x05 => 'GammaUnsharpMaskStrength', 0x06 => 'GammaUnsharpMaskFineness', 0x07 => 'GammaUnsharpMaskThreshold', 0x08 => 'GammaSharpnessStrength', 0x09 => 'GammaShadow', 0x0a => 'GammaHighlight', # 0x0b-0x10 are the same as first 6 doubles of tag DR4_0x20302 # 0x0b - value: 14 0x0c => { Name => 'GammaBlackPoint', ValueConv => q{ return 0 if $val <= 0; $val = log($val / 4.6875) / log(2) + 1; return abs($val) > 1e-10 ? $val : 0; }, ValueConvInv => '$val ? exp(($val - 1) * log(2)) * 4.6876 : 0', PrintConv => 'sprintf("%+.3f", $val)', PrintConvInv => '$val', }, 0x0d => { Name => 'GammaWhitePoint', ValueConv => q{ return $val if $val <= 0; $val = log($val / 4.6875) / log(2) - 11.77109325169954; return abs($val) > 1e-10 ? $val : 0; }, ValueConvInv => '$val ? exp(($val + 11.77109325169954) * log(2)) * 4.6875 : 0', PrintConv => 'sprintf("%+.3f", $val)', PrintConvInv => '$val', }, 0x0e => { Name => 'GammaMidPoint', ValueConv => q{ return $val if $val <= 0; $val = log($val / 4.6875) / log(2) - 8; return abs($val) > 1e-10 ? $val : 0; }, ValueConvInv => '$val ? exp(($val + 8) * log(2)) * 4.6876 : 0', PrintConv => 'sprintf("%+.3f", $val)', PrintConvInv => '$val', }, 0x0f => { Name => 'GammaCurveOutputRange', Format => 'double[2]', Notes => '16383 max' }, ); # Version 4 crop information (ref PH) %Image::ExifTool::CanonVRD::CropInfo = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, WRITE_PROC => \&Image::ExifTool::WriteBinaryData, CHECK_PROC => \&Image::ExifTool::CheckBinaryData, WRITABLE => 1, FIRST_ENTRY => 0, FORMAT => 'int32s', GROUPS => { 2 => 'Image' }, 0 => { Name => 'CropActive', %noYes }, 1 => 'CropRotatedOriginalWidth', 2 => 'CropRotatedOriginalHeight', 3 => 'CropX', 4 => 'CropY', 5 => 'CropWidth', 6 => 'CropHeight', 8 => { Name => 'CropRotation', Format => 'double', PrintConv => 'sprintf("%.7g",$val)', PrintConvInv => '$val', }, 0x0a => 'CropOriginalWidth', 0x0b => 'CropOriginalHeight', # 0x0c double - value: 100 ); # DR4 Stamp Tool tags (ref PH) %Image::ExifTool::CanonVRD::StampInfo = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, GROUPS => { 2 => 'Image' }, FORMAT => 'int32u', FIRST_ENTRY => 0, 0x02 => 'StampToolCount', ); # DR4 dust delete information (ref PH) %Image::ExifTool::CanonVRD::DustInfo = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, GROUPS => { 2 => 'Image' }, FORMAT => 'int32u', FIRST_ENTRY => 0, 0x02 => { Name => 'DustDeleteApplied', %noYes }, ); #------------------------------------------------------------------------------ # sort DR4 tag ID's for the documentation sub SortDR4($$) { my ($a, $b) = @_; my ($aHex, $aDec, $bHex, $bDec); ($aHex, $aDec) = ($1, $2) if $a =~ /^(0x[0-9a-f]+)?\.?(\d*?)$/; ($bHex, $bDec) = ($1, $2) if $b =~ /^(0x[0-9a-f]+)?\.?(\d*?)$/; if ($aHex) { return 1 unless defined $bDec; # $b is 'header'; return hex($aHex) <=> hex($bHex) || $aDec <=> $bDec if $bHex; return hex($aHex) <=> $bDec || 1; } elsif ($bHex) { return -1 unless defined $aDec; return $aDec <=> hex($bHex) || -1; } else { return 1 unless defined $bDec; return -1 unless defined $aDec; return $aDec <=> $bDec; } } #------------------------------------------------------------------------------ # Tone curve print conversion sub ToneCurvePrint($) { my $val = shift; my @vals = split ' ', $val; return $val unless @vals == 21; my $n = shift @vals; return $val unless $n >= 2 and $n <= 10; $val = ''; while ($n--) { $val and $val .= ' '; $val .= '(' . shift(@vals) . ',' . shift(@vals) . ')'; } return $val; } #------------------------------------------------------------------------------ # Inverse print conversion for tone curve sub ToneCurvePrintInv($) { my $val = shift; my @vals = ($val =~ /\((\d+),(\d+)\)/g); return undef unless @vals >= 4 and @vals <= 20 and not @vals & 0x01; unshift @vals, scalar(@vals) / 2; while (@vals < 21) { push @vals, 0 } return join(' ',@vals); } #------------------------------------------------------------------------------ # Read/Write VRD edit data # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref # Returns: Reading: 1 on success; Writing: modified edit data, or undef if nothing changed sub ProcessEditData($$$) { my ($et, $dirInfo, $tagTablePtr) = @_; $et or return 1; # allow dummy access my $dataPt = $$dirInfo{DataPt}; my $pos = $$dirInfo{DirStart}; my $dataPos = $$dirInfo{DataPos}; my $outfile = $$dirInfo{OutFile}; my $dirLen = $$dirInfo{DirLen}; my $verbose = $et->Options('Verbose'); my $out = $et->Options('TextOut'); my $oldChanged = $$et{CHANGED}; $et->VerboseDir('VRD Edit Data', 0, $dirLen) unless $outfile; if ($outfile) { # make a copy for editing in place my $buff = substr($$dataPt, $pos, $dirLen); $dataPt = $$dirInfo{DataPt} = \$buff; $dataPos += $pos; $pos = $$dirInfo{DirStart} = 0; } my $dirEnd = $pos + $dirLen; # loop through all records in the edit data my ($recNum, $recLen, $err); for ($recNum=0;; ++$recNum, $pos+=$recLen) { if ($pos + 4 > $dirEnd) { last if $pos == $dirEnd; # all done if we arrived at end $recLen = 0; # just reset record size (will exit loop on test below) } else { $recLen = Get32u($dataPt, $pos); # (DR4 has a null terminator) last if $recLen == 0 and $pos + 4 == $dirEnd; } $pos += 4; # move to start of record if ($pos + $recLen > $dirEnd) { $et->Warn('Possibly corrupt CanonVRD Edit record'); $err = 1; last; } my $saveRecLen = $recLen; if ($verbose > 1 and not $outfile) { printf $out "$$et{INDENT}CanonVRD Edit record ($recLen bytes at offset 0x%x)\n", $pos + $dataPos; $et->VerboseDump($dataPt, Len => $recLen, Start => $pos, Addr => $pos + $dataPos) if $recNum; } # our edit information is the 0th record, so don't process the others next if $recNum; # process VRD edit information my $subTablePtr = $tagTablePtr; my $index; my %subdirInfo = ( DataPt => $dataPt, DataPos => $dataPos, DirStart => $pos, DirLen => $recLen, OutFile => $outfile, ); my $subStart = 0; # loop through various sections of the VRD edit data for ($index=0; ; ++$index) { my $tagInfo = $$subTablePtr{$index} or last; my $subLen; my $maxLen = $recLen - $subStart; if ($$tagInfo{Size}) { $subLen = $$tagInfo{Size}; } elsif (defined $$tagInfo{Size}) { # get size from int32u at $subStart last unless $subStart + 4 <= $recLen; $subLen = Get32u($dataPt, $subStart + $pos); $subStart += 4; # skip the length word } else { $subLen = $maxLen; } $subLen > $maxLen and $subLen = $maxLen; if ($subLen) { my $subTable = GetTagTable($$tagInfo{SubDirectory}{TagTable}); my $subName = $$tagInfo{Name}; $subdirInfo{DirStart} = $subStart + $pos; $subdirInfo{DirLen} = $subLen; $subdirInfo{DirName} = $subName; if ($outfile) { # rewrite this section of the VRD edit information $verbose and print $out " Rewriting Canon $subName\n"; my $newVal = $et->WriteDirectory(\%subdirInfo, $subTable); if ($newVal) { my $sizeDiff = length($newVal) - $subLen; substr($$dataPt, $pos+$subStart, $subLen) = $newVal; if ($sizeDiff) { $subLen = length $newVal; $recLen += $sizeDiff; $dirEnd += $sizeDiff; $dirLen += $sizeDiff; } } } else { $et->VPrint(0, "$$et{INDENT}$subName (SubDirectory) -->\n"); $et->VerboseDump($dataPt, Start => $pos + $subStart, Addr => $dataPos + $pos + $subStart, Len => $subLen, ); # extract tags from this section of the VRD edit information $et->ProcessDirectory(\%subdirInfo, $subTable); } } # next section starts at the end of this one $subStart += $subLen; } if ($outfile and $saveRecLen ne $recLen) { # update record length if necessary Set32u($recLen, $dataPt, $pos - 4) } } if ($outfile) { return undef if $oldChanged == $$et{CHANGED}; return substr($$dataPt, $$dirInfo{DirStart}, $dirLen); } return $err ? 0 : 1; } #------------------------------------------------------------------------------ # Process VRD IHL data # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref # Returns: 1 on success sub ProcessIHL($$$) { my ($et, $dirInfo, $tagTablePtr) = @_; my $dataPt = $$dirInfo{DataPt}; my $dataPos = $$dirInfo{DataPos}; my $pos = $$dirInfo{DirStart}; my $dirLen = $$dirInfo{DirLen}; my $dirEnd = $pos + $dirLen; $et->VerboseDir('VRD IHL', 0, $dirLen); SetByteOrder('II'); # (make up your mind, Canon!) while ($pos + 48 <= $dirEnd) { my $hdr = substr($$dataPt, $pos, 48); unless ($hdr =~ /^IHL Created Optional Item Data\0\0/) { $et->Warn('Possibly corrupted VRD IHL data'); last; } my $tag = Get32u($dataPt, $pos + 36); my $size = Get32u($dataPt, $pos + 40); # size of data in IHL record my $next = Get32u($dataPt, $pos + 44); # size of complete IHL record if ($size > $next or $pos + 48 + $next > $dirEnd) { $et->Warn(sprintf('Bad size for VRD IHL tag 0x%.4x', $tag)); last; } $pos += 48; $et->HandleTag($tagTablePtr, $tag, substr($$dataPt, $pos, $size), DataPt => $dataPt, DataPos => $dataPos, Start => $pos, Size => $size ); $pos += $next; } return 1; } #------------------------------------------------------------------------------ # Process VRD IHL EXIF data # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref # Returns: 1 on success sub ProcessIHLExif($$$) { my ($et, $dirInfo, $tagTablePtr) = @_; $$et{DOC_NUM} = 1; # the IHL-edited maker notes may look messed up, but the offsets should be OK my $oldFix = $et->Options(FixBase => 0); my $rtnVal = $et->ProcessTIFF($dirInfo, $tagTablePtr); $et->Options(FixBase => $oldFix); delete $$et{DOC_NUM}; return $rtnVal; } #------------------------------------------------------------------------------ # Wrap DR4 data with the VRD header/footer and edit record # Inputs: 0) DR4 record # Returns: VRD[Edit[DR4]] data sub WrapDR4($) { my $val = shift; my $n = length $val; my $oldOrder = GetByteOrder(); SetByteOrder('MM'); $val = $blankHeader . "\xff\xff\0\xf7" . Set32u($n+8) . Set32u($n) . $val . "\0\0\0\0" . $blankFooter; # update the new VRD length in the header/footer Set32u($n + 16, \$val, 0x18); # (extra 16 bytes for the edit record wrapper) Set32u($n + 16, \$val, length($val) - 0x2c); SetByteOrder($oldOrder); return $val; } #------------------------------------------------------------------------------ # Read/Write DPP version 4 edit data or DR4 file # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref # Returns: # Reading from memory (not RAF and not IsWriting): 1 on success # Editing from memory (not RAF and IsWriting): modified edit data, or undef if nothing changed # Reading file (RAF and not OutFile): 1 if a valid DR4 file, 0 if not # Writing file (RAF and OutFile): 1 if valid DR4 file, 0 if not, -1 on write error # (serves me right for not having a consistent interface for the various modes of operation) sub ProcessDR4($$;$) { my ($et, $dirInfo, $tagTablePtr) = @_; $et or return 1; # allow dummy access my $dataPt = $$dirInfo{DataPt}; my $raf = $$dirInfo{RAF}; my $outfile = $$dirInfo{OutFile}; my $isWriting = $outfile || $$dirInfo{IsWriting}; my $dataPos = $$dirInfo{DataPos} || 0; my $verbose = $et->Options('Verbose'); my $unknown = $et->Options('Unknown'); my ($pos, $dirLen, $numEntries, $err, $newTags); # write CanonDR4 as a block if specified if ($isWriting) { my $nvHash; my $newVal = $et->GetNewValue('CanonDR4', \$nvHash); if ($newVal) { $et->VPrint(0, " Writing CanonDR4 as a block\n"); $$et{DidCanonVRD} = 1; # set flag so we don't add this twice ++$$et{CHANGED}; if ($outfile) { Write($$dirInfo{OutFile}, $newVal) or return -1; return 1; } else { return $newVal; } } elsif (not $dataPt and ($nvHash or $$et{DEL_GROUP}{CanonVRD})) { $et->Error("Can't delete all CanonDR4 information from a DR4 file"); return 1; } } if ($dataPt) { $pos = $$dirInfo{DirStart} || 0; $dirLen = $$dirInfo{DirLen} || length($$dataPt) - $pos; } else { # load DR4 file into memory my $buff; $raf->Read($buff, 8) == 8 and $buff eq "IIII\x04\0\x04\0" or return 0; $et->SetFileType(); $raf->Seek(0, 2) or return $err = 1; $dirLen = $raf->Tell(); $raf->Seek(0, 0) or return $err = 1; $raf->Read($buff, $dirLen) == $dirLen or $err = 1; $err and $et->Warn('Error reading DR4 file'), return 1; $tagTablePtr = GetTagTable('Image::ExifTool::CanonVRD::DR4'); $dataPt = \$buff; $pos = 0; } my $dirEnd = $pos + $dirLen; if (($$et{TAGS_FROM_FILE} and not $$et{EXCL_TAG_LOOKUP}{canondr4}) or $$et{REQ_TAG_LOOKUP}{canondr4}) { # extract CanonDR4 block if copying tags, or if requested $et->FoundTag('CanonDR4', substr($$dataPt, $pos, $dirLen)); } # version 4 header is 32 bytes (int32u[8]) if ($dirLen < 32) { $err = 1; } else { SetByteOrder(substr($$dataPt, $pos, 2)) or $err = 1; # process the DR4 header my %hdrInfo = ( DataPt => $dataPt, DirStart => $pos, DirLen => 32, DirName => 'DR4Header', ); my $hdrTable = GetTagTable('Image::ExifTool::CanonVRD::DR4Header'); if ($outfile) { my $hdr = $et->WriteDirectory(\%hdrInfo, $hdrTable); substr($$dataPt, $pos, 32) = $hdr if $hdr and length $hdr == 32; } else { $et->VerboseDir('DR4Header', undef, 32); $et->ProcessDirectory(\%hdrInfo, $hdrTable); } # number of entries in the DR4 directory $numEntries = Get32u($dataPt, $pos + 28); $err = 1 if $dirLen < 36 + 28 * $numEntries; } $err and $et->Warn('Invalid DR4 directory'), return $outfile ? undef : 0; if ($outfile) { $newTags = $et->GetNewTagInfoHash($tagTablePtr); } else { $et->VerboseDir('DR4', $numEntries, $dirLen); } my $index; for ($index=0; $index<$numEntries; ++$index) { my ($val, @flg, $i); my $entry = $pos + 36 + 28 * $index; last if $entry + 28 > $dirEnd; my $tag = Get32u($dataPt, $entry); my $fmt = Get32u($dataPt, $entry + 4); $flg[0] = Get32u($dataPt, $entry + 8); $flg[1] = Get32u($dataPt, $entry + 12); $flg[2] = Get32u($dataPt, $entry + 16); my $off = Get32u($dataPt, $entry + 20) + $pos; my $len = Get32u($dataPt, $entry + 24); next if $off + $len >= $dirEnd; my $format = $vrdFormat{$fmt}; if (not $format) { $val = unpack 'H*', substr($$dataPt, $off, $len); $format = 'undef'; } elsif ($format eq 'double' and $len eq 8) { # avoid teeny weeny values $val = ReadValue($dataPt, $off, $format, undef, $len); $val = 0 if abs($val) < 1e-100; } if ($outfile) { # write (binary data) subdirectory if it exists my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); if ($tagInfo and $$tagInfo{SubDirectory}) { my %subdirInfo = ( DataPt => $dataPt, DirStart => $off, DirLen => $len, DirName => $$tagInfo{Name}, ); my $subTablePtr = GetTagTable($$tagInfo{SubDirectory}{TagTable}); my $saveChanged = $$et{CHANGED}; my $dat = $et->WriteDirectory(\%subdirInfo, $subTablePtr); if (defined $dat and length($dat) == $len) { substr($$dataPt, $off, $len) = $dat; } else { $$et{CHANGED} = $saveChanged; # didn't change anything after all } } else { # loop through main tag and flags (don't yet consider flag 2) for ($i=-1; $i<2; ++$i) { $tagInfo = $$newTags{$i>=0 ? sprintf('0x%x.%d',$tag,$i) : $tag}; next unless $tagInfo; if ($i >= 0) { $off = $entry + 8 + 4 * $i; $format = 'int32u'; $len = 4; undef $val; } $val = ReadValue($dataPt, $off, $format, undef, $len) unless defined $val; my $nvHash; my $newVal = $et->GetNewValue($tagInfo, \$nvHash); if ($et->IsOverwriting($nvHash, $val) and defined $newVal) { my $count = int($len / Image::ExifTool::FormatSize($format)); my $rtnVal = WriteValue($newVal, $format, $count, $dataPt, $off); if (defined $rtnVal) { $et->VerboseValue("- CanonVRD:$$tagInfo{Name}", $val); $et->VerboseValue("+ CanonVRD:$$tagInfo{Name}", $newVal); ++$$et{CHANGED}; } } } } next; } $et->HandleTag($tagTablePtr, $tag, $val, DataPt => $dataPt, DataPos => $dataPos, Start => $off, Size => $len, Index => $index, Format => $format, # $flg[0] is on/off flag # $flg[1] "is default" flag? # $flg[2] changed to 0 when some unsharp mask settings were changed Extra => ", fmt=$fmt flags=" . join(',', @flg), ); foreach $i (0..2) { my $flagID = sprintf('0x%x.%d', $tag, $i); $et->HandleTag($tagTablePtr, $flagID, $flg[$i]) if $$tagTablePtr{$flagID}; } } return 1 unless $isWriting; return substr($$dataPt, $pos, $dirLen) unless $raf; return 1 if Write($outfile, substr($$dataPt, $pos, $dirLen)); return -1; } #------------------------------------------------------------------------------ # Read/write Canon VRD file # Inputs: 0) ExifTool object reference, 1) dirInfo reference # Returns: 1 if this was a Canon VRD file, 0 otherwise, -1 on write error sub ProcessVRD($$) { my ($et, $dirInfo) = @_; my $raf = $$dirInfo{RAF}; my $buff; my $num = $raf->Read($buff, 0x1c); # initialize write directories if necessary $et->InitWriteDirs(\%vrdMap, 'XMP') if $$dirInfo{OutFile}; if (not $num and $$dirInfo{OutFile}) { # create new VRD file from scratch my $newVal = $et->GetNewValue('CanonVRD'); if ($newVal) { $et->VPrint(0, " Writing CanonVRD as a block\n"); Write($$dirInfo{OutFile}, $newVal) or return -1; $$et{DidCanonVRD} = 1; ++$$et{CHANGED}; } else { # allow VRD to be created from individual tags if ($$et{ADD_DIRS}{CanonVRD}) { my $newVal = ''; if (ProcessCanonVRD($et, { OutFile => \$newVal }) > 0) { Write($$dirInfo{OutFile}, $newVal) or return -1; ++$$et{CHANGED}; return 1; } } $et->Error('No CanonVRD information to write'); } } else { $num == 0x1c or return 0; $buff =~ /^CANON OPTIONAL DATA\0/ or return 0; $et->SetFileType(); $$dirInfo{DirName} = 'CanonVRD'; # set directory name for verbose output my $result = ProcessCanonVRD($et, $dirInfo); return $result if $result < 0; $result or $et->Warn('Format error in VRD file'); } return 1; } #------------------------------------------------------------------------------ # Write VRD data record as a block # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref # Returns: VRD data block (may be empty if no VRD data) # Notes: Increments ExifTool CHANGED flag if changed sub WriteCanonVRD($$;$) { my ($et, $dirInfo, $tagTablePtr) = @_; $et or return 1; # allow dummy access my $nvHash = $et->GetNewValueHash($Image::ExifTool::Extra{CanonVRD}); my $val = $et->GetNewValue($nvHash); $val = '' unless defined $val; return undef unless $et->IsOverwriting($nvHash, $val); ++$$et{CHANGED}; return $val; } #------------------------------------------------------------------------------ # Read/write CanonVRD information (from VRD file or VRD trailer) # Inputs: 0) ExifTool object reference, 1) dirInfo reference # Returns: 1 on success, 0 not valid VRD, or -1 error writing # - updates DataPos to point to start of CanonVRD information # - updates DirLen to existing trailer length sub ProcessCanonVRD($$;$) { my ($et, $dirInfo, $tagTablePtr) = @_; my $raf = $$dirInfo{RAF}; my $offset = $$dirInfo{Offset} || 0; my $outfile = $$dirInfo{OutFile}; my $dataPt = $$dirInfo{DataPt}; my $verbose = $et->Options('Verbose'); my $out = $et->Options('TextOut'); my ($buff, $created, $err, $blockLen, $blockType, %didDir, $fromFile); # # The CanonVRD trailer has a 0x1c-byte header and a 0x40-byte footer, # each beginning with "CANON OPTIONAL DATA\0" and containing an int32u # giving the size of the contained data (at byte 0x18 and 0x14 respectively) # if ($raf) { $fromFile = 1; } else { unless ($dataPt) { return 1 unless $outfile; # create blank VRD data from scratch my $blank = $blankHeader . $blankFooter; $dataPt = \$blank; $verbose and print $out " Creating CanonVRD trailer\n"; $created = 1; } $raf = new File::RandomAccess($dataPt); } # read and validate the footer $raf->Seek(-0x40-$offset, 2) or return 0; $raf->Read($buff, 0x40) == 0x40 or return 0; $buff =~ /^CANON OPTIONAL DATA\0(.{4})/s or return 0; my $dirLen = unpack('N', $1) + 0x5c; # size including header+footer # read and validate the header unless ($dirLen < 0x80000000 and $raf->Seek(-$dirLen, 1) and $raf->Read($buff, 0x1c) == 0x1c and $buff =~ /^CANON OPTIONAL DATA\0/ and $raf->Seek(-0x1c, 1)) { $et->Warn('Bad CanonVRD trailer'); return 0; } # set variables returned in dirInfo hash $$dirInfo{DataPos} = $raf->Tell(); $$dirInfo{DirLen} = $dirLen; if ($outfile and ref $outfile eq 'SCALAR' and not length $$outfile) { # write directly to outfile to avoid duplicating data in memory $$outfile = $$dataPt unless $fromFile; # TRICKY! -- copy to outfile memory buffer and edit in place # (so we must disable all Write() calls for this case) $dataPt = $outfile; } if ($fromFile) { $dataPt = \$buff unless $dataPt; # read VRD data into memory if necessary unless ($raf->Read($$dataPt, $dirLen) == $dirLen) { $$dataPt = '' if $outfile and $outfile eq $dataPt; $et->Warn('Error reading CanonVRD data'); return 0; } } # exit quickly if writing and no CanonVRD tags are being edited if ($outfile and not exists $$et{EDIT_DIRS}{CanonVRD}) { print $out "$$et{INDENT} [nothing changed]\n" if $verbose; return 1 if $outfile eq $dataPt; return Write($outfile, $$dataPt) ? 1 : -1; } my $vrdType = 'VRD'; if ($outfile) { $verbose and not $created and print $out " Rewriting CanonVRD trailer\n"; # delete CanonVRD information if specified my $doDel = $$et{DEL_GROUP}{CanonVRD}; unless ($doDel) { $doDel = 1 if $$et{DEL_GROUP}{Trailer} and $$et{FILE_TYPE} ne 'VRD'; unless ($doDel) { # also delete if writing as a block (will get added back again later) if ($$et{NEW_VALUE}{$Image::ExifTool::Extra{CanonVRD}}) { # delete if this isn't version 4 $doDel = 1 unless $$dataPt =~ /^.{28}\xff\xff\0\xf7/s; } if ($$et{NEW_VALUE}{$Image::ExifTool::Extra{CanonDR4}} and not $doDel) { # delete if this is version 4 $doDel = 1 if $$dataPt =~ /^.{28}\xff\xff\0\xf7/s; } } } if ($doDel) { if ($$et{FILE_TYPE} eq 'VRD') { my $newVal = $et->GetNewValue('CanonVRD'); if ($newVal) { $verbose and print $out " Writing CanonVRD as a block\n"; if ($outfile eq $dataPt) { $$outfile = $newVal; } else { Write($outfile, $newVal) or return -1; } $$et{DidCanonVRD} = 1; ++$$et{CHANGED}; } else { $et->Error("Can't delete all CanonVRD information from a VRD file"); } } else { $verbose and print $out " Deleting CanonVRD trailer\n"; $$outfile = '' if $outfile eq $dataPt; ++$$et{CHANGED}; } return 1; } # write now and return if CanonVRD was set as a block my $val = $et->GetNewValue('CanonVRD'); unless ($val) { $val = $et->GetNewValue('CanonDR4'); $vrdType = 'DR4' if $val; } if ($val) { $verbose and print $out " Writing Canon$vrdType as a block\n"; # must wrap DR4 data with the VRD header/footer and edit record $val = WrapDR4($val) if $vrdType eq 'DR4'; if ($outfile eq $dataPt) { $$outfile = $val; } else { Write($outfile, $val) or return -1; } $$et{DidCanonVRD} = 1; ++$$et{CHANGED}; return 1; } } elsif ($verbose or $$et{HTML_DUMP}) { $et->DumpTrailer($dirInfo) if $$dirInfo{RAF}; } $tagTablePtr = GetTagTable('Image::ExifTool::CanonVRD::Main'); # validate VRD trailer and get position and length of edit record SetByteOrder('MM'); # VRD header/footer is big-endian my $pos = 0x1c; # start at end of header # loop through the VRD blocks for (;;) { my $end = $dirLen - 0x40; # end of last VRD block (and start of footer) if ($pos + 8 > $end) { last if $pos == $end; $blockLen = $end; # mark as invalid } else { $blockType = Get32u($dataPt, $pos); $blockLen = Get32u($dataPt, $pos + 4); } $vrdType = 'DR4' if $blockType eq 0xffff00f7; $pos += 8; # move to start of block if ($pos + $blockLen > $end) { $et->Warn('Possibly corrupt CanonVRD block'); last; } if ($verbose > 1 and not $outfile) { printf $out " CanonVRD block 0x%.8x ($blockLen bytes at offset 0x%x)\n", $blockType, $pos + $$dirInfo{DataPos}; $et->VerboseDump($dataPt, Len => $blockLen, Start => $pos, Addr => $pos + $$dirInfo{DataPos}); } my $tagInfo = $$tagTablePtr{$blockType}; unless ($tagInfo) { unless ($et->Options('Unknown')) { $pos += $blockLen; # step to next block next; } my $name = sprintf('CanonVRD_0x%.8x', $blockType); my $desc = $name; $desc =~ tr/_/ /; $tagInfo = { Name => $name, Description => $desc, Binary => 1, }; AddTagToTable($tagTablePtr, $blockType, $tagInfo); } if ($$tagInfo{SubDirectory}) { my $subTablePtr = GetTagTable($$tagInfo{SubDirectory}{TagTable}); my %subdirInfo = ( DataPt => $dataPt, DataLen => length $$dataPt, DataPos => $$dirInfo{DataPos}, DirStart => $pos, DirLen => $blockLen, DirName => $$tagInfo{Name}, Parent => 'CanonVRD', OutFile => $outfile, ); if ($outfile) { # set flag indicating we did this directory $didDir{$$tagInfo{Name}} = 1; my ($dat, $diff); if ($$et{NEW_VALUE}{$tagInfo}) { # write as a block $et->VPrint(0, "Writing $$tagInfo{Name} as a block\n"); $dat = $et->GetNewValue($tagInfo); $dat = '' unless defined $dat; ++$$et{CHANGED}; } else { $dat = $et->WriteDirectory(\%subdirInfo, $subTablePtr); } # update data with new directory if (defined $dat) { if (length $dat or $$et{FILE_TYPE} !~ /^(CRW|VRD)$/) { # replace with new block (updating the block length word) substr($$dataPt, $pos-4, $blockLen+4) = Set32u(length $dat) . $dat; } else { # remove block totally (CRW/VRD files only) substr($$dataPt, $pos-8, $blockLen+8) = ''; } # make necessary adjustments if block changes length if (($diff = length($$dataPt) - $dirLen) != 0) { $pos += $diff; $dirLen += $diff; # update the new VRD length in the header/footer Set32u($dirLen - 0x5c, $dataPt, 0x18); Set32u($dirLen - 0x5c, $dataPt, $dirLen - 0x2c); } } } else { # extract as a block if requested $et->ProcessDirectory(\%subdirInfo, $subTablePtr); } } else { $et->HandleTag($tagTablePtr, $blockType, substr($$dataPt, $pos, $blockLen)); } $pos += $blockLen; # step to next block } if ($outfile) { # create XMP block if necessary (CRW/VRD files only) if ($$et{ADD_DIRS}{CanonVRD} and not $didDir{XMP}) { my $subTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); my $dat = $et->WriteDirectory({ Parent => 'CanonVRD' }, $subTablePtr); if ($dat) { my $blockLen = length $dat; substr($$dataPt, -0x40, 0) = Set32u(0xffff00f6) . Set32u(length $dat) . $dat; $dirLen = length $$dataPt; # update the new VRD length in the header/footer Set32u($dirLen - 0x5c, $dataPt, 0x18); Set32u($dirLen - 0x5c, $dataPt, $dirLen - 0x2c); } } # write CanonVRD trailer unless it is empty if (length $$dataPt) { Write($outfile, $$dataPt) or $err = 1 unless $outfile eq $dataPt; } else { $verbose and print $out " Deleting CanonVRD trailer\n"; } } elsif ($vrdType eq 'VRD' and (($$et{TAGS_FROM_FILE} and not $$et{EXCL_TAG_LOOKUP}{canonvrd}) or $$et{REQ_TAG_LOOKUP}{canonvrd})) { # extract CanonVRD block if copying tags, or if requested (and not DR4 info) $et->FoundTag('CanonVRD', $buff); } undef $buff; return $err ? -1 : 1; } 1; # end __END__ =head1 NAME Image::ExifTool::CanonVRD - Read/write Canon VRD and DR4 information =head1 SYNOPSIS This module is used by Image::ExifTool =head1 DESCRIPTION This module contains definitions required by Image::ExifTool to read and write VRD and DR4 Recipe Data information as written by the Canon Digital Photo Professional software. This information is written to VRD and DR4 files, and as a trailer in JPEG, CRW, CR2 and TIFF images. =head1 AUTHOR Copyright 2003-2018, Phil Harvey (phil at owl.phy.queensu.ca) This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 ACKNOWLEDGEMENTS Thanks to Bogdan and Gert Kello for decoding some tags. =head1 SEE ALSO L, L =cut