1#------------------------------------------------------------------------------
2# File:         PanasonicRaw.pm
3#
4# Description:  Read/write Panasonic/Leica RAW/RW2/RWL meta information
5#
6# Revisions:    2009/03/24 - P. Harvey Created
7#               2009/05/12 - PH Added RWL file type (same format as RW2)
8#
9# References:   1) https://exiftool.org/forum/index.php/topic,1542.0.html
10#               2) http://www.cybercom.net/~dcoffin/dcraw/
11#               3) http://syscall.eu/#pana
12#               4) Klaus Homeister private communication
13#              IB) Iliah Borg private communication (LibRaw)
14#              JD) Jens Duttke private communication (TZ3,FZ30,FZ50)
15#------------------------------------------------------------------------------
16
17package Image::ExifTool::PanasonicRaw;
18
19use strict;
20use vars qw($VERSION);
21use Image::ExifTool qw(:DataAccess :Utils);
22use Image::ExifTool::Exif;
23
24$VERSION = '1.25';
25
26sub ProcessJpgFromRaw($$$);
27sub WriteJpgFromRaw($$$);
28sub WriteDistortionInfo($$$);
29sub ProcessDistortionInfo($$$);
30
31my %jpgFromRawMap = (
32    IFD1         => 'IFD0',
33    EXIF         => 'IFD0', # to write EXIF as a block
34    ExifIFD      => 'IFD0',
35    GPS          => 'IFD0',
36    SubIFD       => 'IFD0',
37    GlobParamIFD => 'IFD0',
38    PrintIM      => 'IFD0',
39    InteropIFD   => 'ExifIFD',
40    MakerNotes   => 'ExifIFD',
41    IFD0         => 'APP1',
42    MakerNotes   => 'ExifIFD',
43    Comment      => 'COM',
44);
45
46my %wbTypeInfo = (
47    PrintConv => \%Image::ExifTool::Exif::lightSource,
48    SeparateTable => 'EXIF LightSource',
49);
50
51my %panasonicWhiteBalance = ( #forum9396
52    0 => 'Auto',
53    1 => 'Daylight',
54    2 => 'Cloudy',
55    3 => 'Tungsten',
56    4 => 'n/a',
57    5 => 'Flash',
58    6 => 'n/a',
59    7 => 'n/a',
60    8 => 'Custom#1',
61    9 => 'Custom#2',
62    10 => 'Custom#3',
63    11 => 'Custom#4',
64    12 => 'Shade',
65    13 => 'Kelvin',
66    16 => 'AWBc', # GH5 and G9 (Makernotes WB==19)
67);
68
69# Tags found in Panasonic RAW/RW2/RWL images (ref PH)
70%Image::ExifTool::PanasonicRaw::Main = (
71    GROUPS => { 0 => 'EXIF', 1 => 'IFD0', 2 => 'Image'},
72    WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
73    CHECK_PROC => \&Image::ExifTool::Exif::CheckExif,
74    WRITE_GROUP => 'IFD0',   # default write group
75    NOTES => 'These tags are found in IFD0 of Panasonic/Leica RAW, RW2 and RWL images.',
76    0x01 => {
77        Name => 'PanasonicRawVersion',
78        Writable => 'undef',
79    },
80    0x02 => 'SensorWidth', #1/PH
81    0x03 => 'SensorHeight', #1/PH
82    0x04 => 'SensorTopBorder', #JD
83    0x05 => 'SensorLeftBorder', #JD
84    0x06 => 'SensorBottomBorder', #PH
85    0x07 => 'SensorRightBorder', #PH
86    # observed values for unknown tags - PH
87    # 0x08: 1
88    # 0x09: 1,3,4
89    # 0x0a: 12
90    # (IB gave 0x08-0x0a as BlackLevel tags, but Klaus' decoding makes more sense)
91    0x08 => { Name => 'SamplesPerPixel', Writable => 'int16u', Protected => 1 }, #4
92    0x09 => { #4
93        Name => 'CFAPattern',
94        Writable => 'int16u',
95        Protected => 1,
96        PrintConv => {
97            0 => 'n/a',
98            1 => '[Red,Green][Green,Blue]', # (CM-1, FZ70)
99            2 => '[Green,Red][Blue,Green]', # (LX-7)
100            3 => '[Green,Blue][Red,Green]', # (ZS100, FZ2500, FZ1000, ...)
101            4 => '[Blue,Green][Green,Red]', # (LC-100, G-7, V-LUX1, ...)
102        },
103    },
104    0x0a => { Name => 'BitsPerSample', Writable => 'int16u', Protected => 1 }, #4
105    0x0b => { #4
106        Name => 'Compression',
107        Writable => 'int16u',
108        Protected => 1,
109        PrintConv => {
110            34316 => 'Panasonic RAW 1', # (most models - RAW/RW2/RWL)
111            34826 => 'Panasonic RAW 2', # (DIGILUX 2 - RAW)
112            34828 => 'Panasonic RAW 3', # (D-LUX2,D-LUX3,FZ30,LX1 - RAW)
113            34830 => 'Panasonic RAW 4', #IB (Leica DIGILUX 3, Panasonic DMC-L1)
114        },
115    },
116    # 0x0c: 2 (only Leica Digilux 2)
117    # 0x0d: 0,1
118    # 0x0e,0x0f,0x10: 4095
119    0x0e => { Name => 'LinearityLimitRed',   Writable => 'int16u' }, #IB
120    0x0f => { Name => 'LinearityLimitGreen', Writable => 'int16u' }, #IB
121    0x10 => { Name => 'LinearityLimitBlue',  Writable => 'int16u' }, #IB
122    0x11 => { #JD
123        Name => 'RedBalance',
124        Writable => 'int16u',
125        ValueConv => '$val / 256',
126        ValueConvInv => 'int($val * 256 + 0.5)',
127        Notes => 'found in Digilux 2 RAW images',
128    },
129    0x12 => { #JD
130        Name => 'BlueBalance',
131        Writable => 'int16u',
132        ValueConv => '$val / 256',
133        ValueConvInv => 'int($val * 256 + 0.5)',
134    },
135    0x13 => { #IB
136        Name => 'WBInfo',
137        SubDirectory => { TagTable => 'Image::ExifTool::PanasonicRaw::WBInfo' },
138    },
139    0x17 => { #1
140        Name => 'ISO',
141        Writable => 'int16u',
142    },
143    # 0x18,0x19,0x1a: 0
144    0x18 => { #IB
145        Name => 'HighISOMultiplierRed',
146        Writable => 'int16u',
147        ValueConv => '$val / 256',
148        ValueConvInv => 'int($val * 256 + 0.5)',
149    },
150    0x19 => { #IB
151        Name => 'HighISOMultiplierGreen',
152        Writable => 'int16u',
153        ValueConv => '$val / 256',
154        ValueConvInv => 'int($val * 256 + 0.5)',
155    },
156    0x1a => { #IB
157        Name => 'HighISOMultiplierBlue',
158        Writable => 'int16u',
159        ValueConv => '$val / 256',
160        ValueConvInv => 'int($val * 256 + 0.5)',
161    },
162    # 0x1b: [binary data] (something to do with the camera ISO cababilities: int16u count N,
163    #                      followed by table of  N entries: int16u ISO, int16u[3] RGB gains - ref IB)
164    0x1c => { Name => 'BlackLevelRed',   Writable => 'int16u' }, #IB
165    0x1d => { Name => 'BlackLevelGreen', Writable => 'int16u' }, #IB
166    0x1e => { Name => 'BlackLevelBlue',  Writable => 'int16u' }, #IB
167    0x24 => { #2
168        Name => 'WBRedLevel',
169        Writable => 'int16u',
170    },
171    0x25 => { #2
172        Name => 'WBGreenLevel',
173        Writable => 'int16u',
174    },
175    0x26 => { #2
176        Name => 'WBBlueLevel',
177        Writable => 'int16u',
178    },
179    0x27 => { #IB
180        Name => 'WBInfo2',
181        SubDirectory => { TagTable => 'Image::ExifTool::PanasonicRaw::WBInfo2' },
182    },
183    # 0x27,0x29,0x2a,0x2b,0x2c: [binary data]
184    0x2d => { #IB
185        Name => 'RawFormat',
186        Writable => 'int16u',
187        Protected => 1,
188        # 2 - RAW DMC-FZ8/FZ18
189        # 3 - RAW DMC-L10
190        # 4 - RW2 for most other models, including G9 in "pixel shift off" mode and YUNEEC CGO4
191        #     (must add 15 to black levels for RawFormat == 4)
192        # 5 - RW2 DC-GH5s; G9 in "pixel shift on" mode
193        # 6 - RW2 DC-S1, DC-S1r in "pixel shift off" mode
194        # 7 - RW2 DC-S1r (and probably DC-S1, have no raw samples) in "pixel shift on" mode
195        # not used - DMC-LX1/FZ30/FZ50/L1/LX1/LX2
196        # (modes 5 and 7 are lossless)
197    },
198    0x2e => { #JD
199        Name => 'JpgFromRaw', # (writable directory!)
200        Groups => { 2 => 'Preview' },
201        Writable => 'undef',
202        # protect this tag because it contains all the metadata
203        Flags => [ 'Binary', 'Protected', 'NestedHtmlDump', 'BlockExtract' ],
204        Notes => 'processed as an embedded document because it contains full EXIF',
205        WriteCheck => '$val eq "none" ? undef : $self->CheckImage(\$val)',
206        DataTag => 'JpgFromRaw',
207        RawConv => '$self->ValidateImage(\$val,$tag)',
208        SubDirectory => {
209            # extract information from embedded image since it is metadata-rich,
210            # unless HtmlDump option set (note that the offsets will be relative,
211            # not absolute like they should be in verbose mode)
212            TagTable => 'Image::ExifTool::JPEG::Main',
213            WriteProc => \&WriteJpgFromRaw,
214            ProcessProc => \&ProcessJpgFromRaw,
215        },
216    },
217    0x2f => { Name => 'CropTop',    Writable => 'int16u' },
218    0x30 => { Name => 'CropLeft',   Writable => 'int16u' },
219    0x31 => { Name => 'CropBottom', Writable => 'int16u' },
220    0x32 => { Name => 'CropRight',  Writable => 'int16u' },
221    0x10f => {
222        Name => 'Make',
223        Groups => { 2 => 'Camera' },
224        Writable => 'string',
225        DataMember => 'Make',
226        # save this value as an ExifTool member variable
227        RawConv => '$self->{Make} = $val',
228    },
229    0x110 => {
230        Name => 'Model',
231        Description => 'Camera Model Name',
232        Groups => { 2 => 'Camera' },
233        Writable => 'string',
234        DataMember => 'Model',
235        # save this value as an ExifTool member variable
236        RawConv => '$self->{Model} = $val',
237    },
238    0x111 => {
239        Name => 'StripOffsets',
240        # (this value is 0xffffffff for some models, and RawDataOffset must be used)
241        Flags => [ 'IsOffset', 'PanasonicHack' ],
242        OffsetPair => 0x117,  # point to associated byte counts
243        ValueConv => 'length($val) > 32 ? \$val : $val',
244    },
245    0x112 => {
246        Name => 'Orientation',
247        Writable => 'int16u',
248        PrintConv => \%Image::ExifTool::Exif::orientation,
249        Priority => 0,  # so IFD1 doesn't take precedence
250    },
251    0x116 => {
252        Name => 'RowsPerStrip',
253        Priority => 0,
254    },
255    0x117 => {
256        Name => 'StripByteCounts',
257        # (note that this value may represent something like uncompressed byte count
258        # for RAW/RW2/RWL images from some models, and is zero for some other models)
259        OffsetPair => 0x111,   # point to associated offset
260        ValueConv => 'length($val) > 32 ? \$val : $val',
261    },
262    0x118 => {
263        Name => 'RawDataOffset', #PH (RW2/RWL)
264        IsOffset => '$$et{TIFF_TYPE} =~ /^(RW2|RWL)$/', # (invalid in DNG-converted files)
265        PanasonicHack => 1,
266        OffsetPair => 0x117, # (use StripByteCounts as the offset pair)
267        NotRealPair => 1,    # (to avoid Validate warning)
268    },
269    0x119 => {
270        Name => 'DistortionInfo',
271        SubDirectory => { TagTable => 'Image::ExifTool::PanasonicRaw::DistortionInfo' },
272    },
273    # 0x11b - chromatic aberration correction (ref 3) (also see forum9366)
274    0x11c => { #forum9373
275        Name => 'Gamma',
276        Writable => 'int16u',
277        # unfortunately it seems that the scaling factor varies with model...
278        ValueConv => '$val / ($val >= 1024 ? 1024 : ($val >= 256 ? 256 : 100))',
279        ValueConvInv => 'int($val * 256 + 0.5)',
280    },
281    0x120 => {
282        Name => 'CameraIFD',
283        SubDirectory => {
284            TagTable => 'Image::ExifTool::PanasonicRaw::CameraIFD',
285            Base => '$start',
286            ProcessProc => \&Image::ExifTool::ProcessTIFF,
287        },
288    },
289    0x121 => { #forum9295
290        Name => 'Multishot',
291        Writable => 'int32u',
292        PrintConv => {
293            0 => 'Off',
294            65536 => 'Pixel Shift',
295        },
296    },
297    # 0x122 - int32u: RAWDataOffset for the GH5s/GX9, or pointer to end of raw data for G9 (forum9295)
298    0x2bc => { # PH Extension!!
299        Name => 'ApplicationNotes', # (writable directory!)
300        Writable => 'int8u',
301        Format => 'undef',
302        Flags => [ 'Binary', 'Protected' ],
303        SubDirectory => {
304            DirName => 'XMP',
305            TagTable => 'Image::ExifTool::XMP::Main',
306        },
307    },
308    0x001b => { #forum9250
309        Name => 'NoiseReductionParams',
310        Writable => 'undef',
311        Format => 'int16u',
312        Count => -1,
313        Flags => 'Protected',
314        Notes => q{
315            the camera's default noise reduction setup.  The first number is the number
316            of entries, then for each entry there are 4 numbers: an ISO speed, and
317            noise-reduction strengths the R, G and B channels
318        },
319    },
320    0x83bb => { # PH Extension!!
321        Name => 'IPTC-NAA', # (writable directory!)
322        Format => 'undef',      # convert binary values as undef
323        Writable => 'int32u',   # but write int32u format code in IFD
324        WriteGroup => 'IFD0',
325        Flags => [ 'Binary', 'Protected' ],
326        SubDirectory => {
327            DirName => 'IPTC',
328            TagTable => 'Image::ExifTool::IPTC::Main',
329        },
330    },
331    0x8769 => {
332        Name => 'ExifOffset',
333        Groups => { 1 => 'ExifIFD' },
334        Flags => 'SubIFD',
335        SubDirectory => {
336            TagTable => 'Image::ExifTool::Exif::Main',
337            DirName => 'ExifIFD',
338            Start => '$val',
339        },
340    },
341    0x8825 => {
342        Name => 'GPSInfo',
343        Groups => { 1 => 'GPS' },
344        Flags => 'SubIFD',
345        SubDirectory => {
346            DirName => 'GPS',
347            TagTable => 'Image::ExifTool::GPS::Main',
348            Start => '$val',
349        },
350    },
351    # 0xffff => 'DCSHueShiftValues', #exifprobe (NC)
352);
353
354# white balance information (ref IB)
355# (PanasonicRawVersion<200: Digilux 2)
356%Image::ExifTool::PanasonicRaw::WBInfo = (
357    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
358    WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
359    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
360    WRITABLE => 1,
361    FORMAT => 'int16u',
362    FIRST_ENTRY => 0,
363    0 => 'NumWBEntries',
364    1 => { Name => 'WBType1', %wbTypeInfo },
365    2 => { Name => 'WB_RBLevels1', Format => 'int16u[2]' },
366    4 => { Name => 'WBType2', %wbTypeInfo },
367    5 => { Name => 'WB_RBLevels2', Format => 'int16u[2]' },
368    7 => { Name => 'WBType3', %wbTypeInfo },
369    8 => { Name => 'WB_RBLevels3', Format => 'int16u[2]' },
370    10 => { Name => 'WBType4', %wbTypeInfo },
371    11 => { Name => 'WB_RBLevels4', Format => 'int16u[2]' },
372    13 => { Name => 'WBType5', %wbTypeInfo },
373    14 => { Name => 'WB_RBLevels5', Format => 'int16u[2]' },
374    16 => { Name => 'WBType6', %wbTypeInfo },
375    17 => { Name => 'WB_RBLevels6', Format => 'int16u[2]' },
376    19 => { Name => 'WBType7', %wbTypeInfo },
377    20 => { Name => 'WB_RBLevels7', Format => 'int16u[2]' },
378);
379
380# white balance information (ref IB)
381# (PanasonicRawVersion>=200: D-Lux2, D-Lux3, DMC-FZ18/FZ30/LX1/L10)
382%Image::ExifTool::PanasonicRaw::WBInfo2 = (
383    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
384    WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
385    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
386    WRITABLE => 1,
387    FORMAT => 'int16u',
388    FIRST_ENTRY => 0,
389    0 => 'NumWBEntries',
390    1 => { Name => 'WBType1', %wbTypeInfo },
391    2 => { Name => 'WB_RGBLevels1', Format => 'int16u[3]' },
392    5 => { Name => 'WBType2', %wbTypeInfo },
393    6 => { Name => 'WB_RGBLevels2', Format => 'int16u[3]' },
394    9 => { Name => 'WBType3', %wbTypeInfo },
395    10 => { Name => 'WB_RGBLevels3', Format => 'int16u[3]' },
396    13 => { Name => 'WBType4', %wbTypeInfo },
397    14 => { Name => 'WB_RGBLevels4', Format => 'int16u[3]' },
398    17 => { Name => 'WBType5', %wbTypeInfo },
399    18 => { Name => 'WB_RGBLevels5', Format => 'int16u[3]' },
400    21 => { Name => 'WBType6', %wbTypeInfo },
401    22 => { Name => 'WB_RGBLevels6', Format => 'int16u[3]' },
402    25 => { Name => 'WBType7', %wbTypeInfo },
403    26 => { Name => 'WB_RGBLevels7', Format => 'int16u[3]' },
404);
405
406# lens distortion information (ref 3)
407# (distortion correction equation: Ru = scale*(Rd + a*Rd^3 + b*Rd^5 + c*Rd^7), ref 3)
408%Image::ExifTool::PanasonicRaw::DistortionInfo = (
409    PROCESS_PROC => \&ProcessDistortionInfo,
410    WRITE_PROC => \&WriteDistortionInfo,
411    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
412    # (don't make this family 0 MakerNotes because we don't want it to be a deletable group)
413    GROUPS => { 0 => 'PanasonicRaw', 1 => 'PanasonicRaw', 2 => 'Image'},
414    WRITABLE => 1,
415    FORMAT => 'int16s',
416    FIRST_ENTRY => 0,
417    NOTES => 'Lens distortion correction information.',
418    # 0,1 - checksums
419    2 => {
420        Name => 'DistortionParam02',
421        ValueConv => '$val / 32768',
422        ValueConvInv => '$val * 32768',
423    },
424    # 3 - usually 0, but seen 0x026b when value 5 is non-zero
425    4 => {
426        Name => 'DistortionParam04',
427        ValueConv => '$val / 32768',
428        ValueConvInv => '$val * 32768',
429    },
430    5 => {
431        Name => 'DistortionScale',
432        ValueConv => '1 / (1 + $val/32768)',
433        ValueConvInv => '(1/$val - 1) * 32768',
434    },
435    # 6 - seen 0x0000-0x027f
436    7.1 => {
437        Name => 'DistortionCorrection',
438        Mask => 0x0f,
439        # (have seen the upper 4 bits set for GF5 and GX1, giving a value of -4095 - PH)
440        PrintConv => { 0 => 'Off', 1 => 'On' },
441    },
442    8 => {
443        Name => 'DistortionParam08',
444        ValueConv => '$val / 32768',
445        ValueConvInv => '$val * 32768',
446    },
447    9 => {
448        Name => 'DistortionParam09',
449        ValueConv => '$val / 32768',
450        ValueConvInv => '$val * 32768',
451    },
452    # 10 - seen 0xfc,0x0101,0x01f4,0x021d,0x0256
453    11 => {
454        Name => 'DistortionParam11',
455        ValueConv => '$val / 32768',
456        ValueConvInv => '$val * 32768',
457    },
458    12 => {
459        Name => 'DistortionN',
460        Unknown => 1,
461    },
462    # 13 - seen 0x0000,0x01f9-0x02b2
463    # 14,15 - checksums
464);
465
466# Panasonic RW2 camera IFD written by GH5 (ref PH)
467# (doesn't seem to be valid for the GF7 or GM5 -- encrypted?)
468%Image::ExifTool::PanasonicRaw::CameraIFD = (
469    GROUPS => { 0 => 'PanasonicRaw', 1 => 'CameraIFD', 2 => 'Camera'},
470    # (don't know what format codes 0x101 and 0x102 are for, so just
471    #  map them into 4 = int32u for now)
472    VARS => { MAP_FORMAT => { 0x101 => 4, 0x102 => 4 } },
473    0x1001 => { #forum9388
474        Name => 'MultishotOn',
475        Writable => 'int32u',
476        PrintConv => { 0 => 'No', 1 => 'Yes' },
477    },
478    0x1100 => { #forum9274
479        Name => 'FocusStepNear',
480        Writable => 'int16s',
481    },
482    0x1101 => { #forum9274 (was forum8484)
483        Name => 'FocusStepCount',
484        Writable => 'int16s',
485    },
486    0x1102 => { #forum9417
487        Name => 'FlashFired',
488        Writable => 'int32u',
489        PrintConv => { 0 => 'No', 1 => 'Yes' },
490    },
491    # 0x1104 - set when camera shoots on lowest possible Extended-ISO (forum9290)
492    0x1105 => { #forum9392
493        Name => 'ZoomPosition',
494        Notes => 'in the range 0-255 for most cameras',
495        Writable => 'int32u',
496    },
497    0x1200 => { #forum9278
498        Name => 'LensAttached',
499        Notes => 'many CameraIFD tags are invalid if there is no lens attached',
500        Writable => 'int32u',
501        PrintConv => { 0 => 'No', 1 => 'Yes' },
502    },
503    # Note: LensTypeMake and LensTypeModel are combined into a Composite LensType tag
504    # defined in Olympus.pm which has the same values as Olympus:LensType
505    0x1201 => { #IB
506        Name => 'LensTypeMake',
507        Condition => '$format eq "int16u"',
508        Writable => 'int16u',
509        # when format is int16u, these values have been observed:
510        #  0 - Olympus or unknown lens
511        #  2 - Leica or Lumix lens
512        # when format is int32u (S models), these values have been observed (ref IB):
513        #  256 - Leica lens
514        #  257 - Lumix lens
515        #  258 - ? (seen once)
516    },
517    0x1202 => { #IB
518        Name => 'LensTypeModel',
519        Condition => '$format eq "int16u"',
520        Writable => 'int16u',
521        RawConv => q{
522            return undef unless $val;
523            require Image::ExifTool::Olympus; # (to load Composite LensID)
524            return $val;
525        },
526        ValueConv => '$_=sprintf("%.4x",$val); s/(..)(..)/$2 $1/; $_',
527        ValueConvInv => '$val =~ s/(..) (..)/$2$1/; hex($val)',
528    },
529    0x1203 => { #4
530        Name => 'FocalLengthIn35mmFormat',
531        Writable => 'int16u',
532        PrintConv => '"$val mm"',
533        PrintConvInv => '$val=~s/\s*mm$//;$val',
534    },
535    # 0x1300 - incident light value? (ref forum11395)
536    0x1301 => { #forum11395
537        Name => 'ApertureValue',
538        Writable => 'int16s',
539        Priority => 0,
540        ValueConv => '2 ** ($val / 512)',
541        ValueConvInv => '$val>0 ? 512*log($val)/log(2) : 0',
542        PrintConv => 'sprintf("%.1f",$val)',
543        PrintConvInv => '$val',
544    },
545    0x1302 => { #forum11395
546        Name => 'ShutterSpeedValue',
547        Writable => 'int16s',
548        Priority => 0,
549        ValueConv => 'abs($val/256)<100 ? 2**(-$val/256) : 0',
550        ValueConvInv => '$val>0 ? -256*log($val)/log(2) : -25600',
551        PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
552        PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
553    },
554    0x1303 => { #forum11395
555        Name => 'SensitivityValue',
556        Writable => 'int16s',
557        ValueConv => '$val / 256',
558        ValueConvInv => 'int($val * 256)',
559    },
560    0x1305 => { #forum9384
561        Name => 'HighISOMode',
562        Writable => 'int16u',
563        RawConv => '$val || undef',
564        PrintConv => { 1 => 'On', 2 => 'Off' },
565    },
566    # 0x1306 EV for some models like the GX8 (forum11395)
567    # 0x140b - scaled overall black level? (ref forum9281)
568    # 0x1411 - scaled black level per channel difference (ref forum9281)
569    0x1412 => { #forum11397
570        Name => 'FacesDetected',
571        Writable => 'int8u',
572        PrintConv => { 0 => 'No', 1 => 'Yes' },
573    },
574    # 0x2000 - WB tungsten=3, daylight=4 (ref forum9467)
575    # 0x2009 - scaled black level per channel (ref forum9281)
576    # 0x3000-0x310b - red/blue balances * 1024 (ref forum9467)
577    #  0x3000 modifiedTungsten-Red (-2?)
578    #  0x3001 modifiedTungsten-Blue (-2?)
579    #  0x3002 modifiedDaylight-Red (-2?)
580    #  0x3003 modifiedDaylight-Blue (-2?)
581    #  0x3004 modifiedTungsten-Red (-1?)
582    #  0x3005 modifiedTungsten-Blue (-1?)
583    #  0x3006 modifiedDaylight-Red (-1?)
584    #  0x3007 modifiedDaylight-Blue (-1?)
585    #  0x3100 DefaultTungsten-Red
586    #  0x3101 DefaultTungsten-Blue
587    #  0x3102 DefaultDaylight-Red
588    #  0x3103 DefaultDaylight-Blue
589    #  0x3104 modifiedTungsten-Red (+1?)
590    #  0x3105 modifiedTungsten-Blue (+1?)
591    #  0x3106 modifiedDaylight-Red (+1?)
592    #  0x3107 modifiedDaylight-Blue (+1?)
593    #  0x3108 modifiedTungsten-Red (+2?)
594    #  0x3109 modifiedTungsten-Blue (+2?)
595    #  0x310a modifiedDaylight-Red (+2?)
596    #  0x310b modifiedDaylight-Blue (+2?)
597    0x3200 => { #forum9275
598        Name => 'WB_CFA0_LevelDaylight',
599        Writable => 'int16u',
600    },
601    0x3201 => { #forum9275
602        Name => 'WB_CFA1_LevelDaylight',
603        Writable => 'int16u',
604    },
605    0x3202 => { #forum9275
606        Name => 'WB_CFA2_LevelDaylight',
607        Writable => 'int16u',
608    },
609    0x3203 => { #forum9275
610        Name => 'WB_CFA3_LevelDaylight',
611        Writable => 'int16u',
612    },
613    # 0x3204-0x3207 - user multipliers * 1024 ? (ref forum9275)
614    # 0x320a - scaled maximum value of raw data (scaling = 4x) (ref forum9281)
615    # 0x3209 - gamma (x256) (ref forum9281)
616    0x3300 => { #forum9296/9396
617        Name => 'WhiteBalanceSet',
618        Writable => 'int8u',
619        PrintConv => \%panasonicWhiteBalance,
620        SeparateTable => 'WhiteBalance',
621    },
622    0x3420 => { #forum9276
623        Name => 'WB_RedLevelAuto',
624        Writable => 'int16u',
625    },
626    0x3421 => { #forum9276
627        Name => 'WB_BlueLevelAuto',
628        Writable => 'int16u',
629    },
630    0x3501 => { #4
631        Name => 'Orientation',
632        Writable => 'int8u',
633        PrintConv => \%Image::ExifTool::Exif::orientation,
634    },
635    # 0x3504 = Tag 0x1301+0x1302-0x1303 (Bv = Av+Tv-Sv) (forum11395)
636    # 0x3505 - same as 0x1300 (forum11395)
637    0x3600 => { #forum9396
638        Name => 'WhiteBalanceDetected',
639        Writable => 'int8u',
640        PrintConv => \%panasonicWhiteBalance,
641        SeparateTable => 'WhiteBalance',
642    },
643);
644
645# PanasonicRaw composite tags
646%Image::ExifTool::PanasonicRaw::Composite = (
647    ImageWidth => {
648        Require => {
649            0 => 'IFD0:SensorLeftBorder',
650            1 => 'IFD0:SensorRightBorder',
651        },
652        ValueConv => '$val[1] - $val[0]',
653    },
654    ImageHeight => {
655        Require => {
656            0 => 'IFD0:SensorTopBorder',
657            1 => 'IFD0:SensorBottomBorder',
658        },
659        ValueConv => '$val[1] - $val[0]',
660    },
661);
662
663# add our composite tags
664Image::ExifTool::AddCompositeTags('Image::ExifTool::PanasonicRaw');
665
666
667#------------------------------------------------------------------------------
668# checksum algorithm for lens distortion correction information (ref 3)
669# Inputs: 0) data ref, 1) start position, 2) number of bytes, 3) incement
670# Returns: checksum value
671sub Checksum($$$$)
672{
673    my ($dataPt, $start, $num, $inc) = @_;
674    my $csum = 0;
675    my $i;
676    for ($i=0; $i<$num; ++$i) {
677        $csum = (73 * $csum + Get8u($dataPt, $start + $i * $inc)) % 0xffef;
678    }
679    return $csum;
680}
681
682#------------------------------------------------------------------------------
683# Read lens distortion information
684# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
685# Returns: 1 on success
686sub ProcessDistortionInfo($$$)
687{
688    my ($et, $dirInfo, $tagTablePtr) = @_;
689    my $dataPt = $$dirInfo{DataPt};
690    my $start = $$dirInfo{DirStart} || 0;
691    my $size = $$dirInfo{DirLen} || (length($$dataPt) - $start);
692    if ($size == 32) {
693        # verify the checksums (ref 3)
694        my $csum1 = Checksum($dataPt, $start +  4, 12, 1);
695        my $csum2 = Checksum($dataPt, $start + 16, 12, 1);
696        my $csum3 = Checksum($dataPt, $start +  2, 14, 2);
697        my $csum4 = Checksum($dataPt, $start +  3, 14, 2);
698        my $res = $csum1 ^ Get16u($dataPt, $start + 2) ^
699                  $csum2 ^ Get16u($dataPt, $start + 28) ^
700                  $csum3 ^ Get16u($dataPt, $start + 0) ^
701                  $csum4 ^ Get16u($dataPt, $start + 30);
702        $et->Warn('Invalid DistortionInfo checksum',1) if $res;
703    } else {
704        $et->Warn('Invalid DistortionInfo',1);
705    }
706    return $et->ProcessBinaryData($dirInfo, $tagTablePtr);
707}
708
709#------------------------------------------------------------------------------
710# Write lens distortion information
711# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
712# Returns: updated distortion information or undef on error
713sub WriteDistortionInfo($$$)
714{
715    my ($et, $dirInfo, $tagTablePtr) = @_;
716    $et or return 1;  # (allow dummy access)
717    my $dat = $et->WriteBinaryData($dirInfo, $tagTablePtr);
718    if (defined $dat and length($dat) == 32) {
719        # fix checksums (ref 3)
720        Set16u(Checksum(\$dat,  4, 12, 1), \$dat,  2);
721        Set16u(Checksum(\$dat, 16, 12, 1), \$dat, 28);
722        Set16u(Checksum(\$dat,  2, 14, 2), \$dat,  0);
723        Set16u(Checksum(\$dat,  3, 14, 2), \$dat, 30);
724    } else {
725        $et->Warn('Error wriing DistortionInfo',1);
726    }
727    return $dat;
728}
729
730#------------------------------------------------------------------------------
731# Patch for writing non-standard Panasonic RAW/RW2/RWL raw data
732# Inputs: 0) offset info ref, 1) raf ref, 2) IFD number
733# Returns: error string, or undef on success
734# OffsetInfo is a hash by tag ID of lists with the following elements:
735#  0 - tag info ref
736#  1 - pointer to int32u offset in IFD or value data
737#  2 - value count
738#  3 - reference to list of original offset values
739#  4 - IFD format number
740sub PatchRawDataOffset($$$)
741{
742    my ($offsetInfo, $raf, $ifd) = @_;
743    my $stripOffsets = $$offsetInfo{0x111};
744    my $stripByteCounts = $$offsetInfo{0x117};
745    my $rawDataOffset = $$offsetInfo{0x118};
746    my $err;
747    $err = 1 unless $ifd == 0;
748    $err = 1 unless $stripOffsets and $stripByteCounts and $$stripOffsets[2] == 1;
749    if ($rawDataOffset) {
750        $err = 1 unless $$rawDataOffset[2] == 1;
751        $err = 1 unless $$stripOffsets[3][0] == 0xffffffff or $$stripByteCounts[3][0] == 0;
752    }
753    $err and return 'Unsupported Panasonic/Leica RAW variant';
754    if ($rawDataOffset) {
755        # update StripOffsets along with this tag if it contains a reasonable value
756        unless ($$stripOffsets[3][0] == 0xffffffff) {
757            # save pointer to StripOffsets value for updating later
758            push @$rawDataOffset, $$stripOffsets[1];
759        }
760        # handle via RawDataOffset instead of StripOffsets
761        $stripOffsets = $$offsetInfo{0x111} = $rawDataOffset;
762        delete $$offsetInfo{0x118};
763    }
764    # determine the length of the raw data
765    my $pos = $raf->Tell();
766    $raf->Seek(0, 2) or $err = 1; # seek to end of file
767    my $len = $raf->Tell() - $$stripOffsets[3][0];
768    $raf->Seek($pos, 0);
769    # quick check to be sure the raw data length isn't unreasonable
770    # (the 22-byte length is for '<Dummy raw image data>' in our tests)
771    $err = 1 if ($len < 1000 and $len != 22) or $len & 0x80000000;
772    $err and return 'Error reading Panasonic raw data';
773    # update StripByteCounts info with raw data length
774    # (note that the original value is maintained in the file)
775    $$stripByteCounts[3][0] = $len;
776
777    return undef;
778}
779
780#------------------------------------------------------------------------------
781# Write meta information to Panasonic JpgFromRaw in RAW/RW2/RWL image
782# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
783# Returns: updated image data, or undef if nothing changed
784sub WriteJpgFromRaw($$$)
785{
786    my ($et, $dirInfo, $tagTablePtr) = @_;
787    my $dataPt = $$dirInfo{DataPt};
788    my $byteOrder = GetByteOrder();
789    my $fileType = $$et{TIFF_TYPE};   # RAW, RW2 or RWL
790    my $dirStart = $$dirInfo{DirStart};
791    if ($dirStart) { # DirStart is non-zero in DNG-converted RW2/RWL
792        my $dirLen = $$dirInfo{DirLen} | length($$dataPt) - $dirStart;
793        my $buff = substr($$dataPt, $dirStart, $dirLen);
794        $dataPt = \$buff;
795    }
796    my $raf = new File::RandomAccess($dataPt);
797    my $outbuff;
798    my %dirInfo = (
799        RAF => $raf,
800        OutFile => \$outbuff,
801    );
802    $$et{BASE} = $$dirInfo{DataPos};
803    $$et{FILE_TYPE} = $$et{TIFF_TYPE} = 'JPEG';
804    # use a specialized map so we don't write XMP or IPTC (or other junk) into the JPEG
805    my $editDirs = $$et{EDIT_DIRS};
806    my $addDirs = $$et{ADD_DIRS};
807    $et->InitWriteDirs(\%jpgFromRawMap);
808    # don't add XMP segment (IPTC won't get added because it is in Photoshop record)
809    delete $$et{ADD_DIRS}{XMP};
810    my $result = $et->WriteJPEG(\%dirInfo);
811    # restore variables we changed
812    $$et{BASE} = 0;
813    $$et{FILE_TYPE} = 'TIFF';
814    $$et{TIFF_TYPE} = $fileType;
815    $$et{EDIT_DIRS} = $editDirs;
816    $$et{ADD_DIRS} = $addDirs;
817    SetByteOrder($byteOrder);
818    return $result > 0 ? $outbuff : $$dataPt;
819}
820
821#------------------------------------------------------------------------------
822# Extract meta information from an Panasonic JpgFromRaw
823# Inputs: 0) ExifTool object reference, 1) dirInfo reference
824# Returns: 1 on success, 0 if this wasn't a valid JpgFromRaw image
825sub ProcessJpgFromRaw($$$)
826{
827    my ($et, $dirInfo, $tagTablePtr) = @_;
828    my $dataPt = $$dirInfo{DataPt};
829    my $byteOrder = GetByteOrder();
830    my $fileType = $$et{TIFF_TYPE};   # RAW, RW2 or RWL
831    my $tagInfo = $$dirInfo{TagInfo};
832    my $verbose = $et->Options('Verbose');
833    my ($indent, $out);
834    $tagInfo or $et->Warn('No tag info for Panasonic JpgFromRaw'), return 0;
835    my $dirStart = $$dirInfo{DirStart};
836    if ($dirStart) { # DirStart is non-zero in DNG-converted RW2/RWL
837        my $dirLen = $$dirInfo{DirLen} | length($$dataPt) - $dirStart;
838        my $buff = substr($$dataPt, $dirStart, $dirLen);
839        $dataPt = \$buff;
840    }
841    $$et{BASE} = $$dirInfo{DataPos} + ($dirStart || 0);
842    $$et{FILE_TYPE} = $$et{TIFF_TYPE} = 'JPEG';
843    $$et{DOC_NUM} = 1;
844    # extract information from embedded JPEG
845    my %dirInfo = (
846        Parent => 'RAF',
847        RAF    => new File::RandomAccess($dataPt),
848    );
849    if ($verbose) {
850        my $indent = $$et{INDENT};
851        $$et{INDENT} = '  ';
852        $out = $et->Options('TextOut');
853        print $out '--- DOC1:JpgFromRaw ',('-'x56),"\n";
854    }
855    # fudge HtmlDump base offsets to show as a stand-alone JPEG
856    $$et{BASE_FUDGE} = $$et{BASE};
857    my $rtnVal = $et->ProcessJPEG(\%dirInfo);
858    $$et{BASE_FUDGE} = 0;
859    # restore necessary variables for continued RW2/RWL processing
860    $$et{BASE} = 0;
861    $$et{FILE_TYPE} = 'TIFF';
862    $$et{TIFF_TYPE} = $fileType;
863    delete $$et{DOC_NUM};
864    SetByteOrder($byteOrder);
865    if ($verbose) {
866        $$et{INDENT} = $indent;
867        print $out ('-'x76),"\n";
868    }
869    return $rtnVal;
870}
871
8721;  # end
873
874__END__
875
876=head1 NAME
877
878Image::ExifTool::PanasonicRaw - Read/write Panasonic/Leica RAW/RW2/RWL meta information
879
880=head1 SYNOPSIS
881
882This module is loaded automatically by Image::ExifTool when required.
883
884=head1 DESCRIPTION
885
886This module contains definitions required by Image::ExifTool to read and
887write meta information in Panasonic/Leica RAW, RW2 and RWL images.
888
889=head1 AUTHOR
890
891Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
892
893This library is free software; you can redistribute it and/or modify it
894under the same terms as Perl itself.
895
896=head1 REFERENCES
897
898=over 4
899
900=item L<http://www.cybercom.net/~dcoffin/dcraw/>
901
902=back
903
904=head1 SEE ALSO
905
906L<Image::ExifTool::TagNames/PanasonicRaw Tags>,
907L<Image::ExifTool(3pm)|Image::ExifTool>
908
909=cut
910