File Coverage

blib/lib/Image/ExifTool/PanasonicRaw.pm
Criterion Covered Total %
statement 112 130 86.1
branch 24 48 50.0
condition 12 34 35.2
subroutine 10 10 100.0
pod 0 6 0.0
total 158 228 69.3


line stmt bran cond sub pod time code
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              
17             package Image::ExifTool::PanasonicRaw;
18              
19 17     17   148 use strict;
  17         41  
  17         859  
20 17     17   102 use vars qw($VERSION);
  17         39  
  17         1044  
21 17     17   122 use Image::ExifTool qw(:DataAccess :Utils);
  17         68  
  17         5216  
22 17     17   132 use Image::ExifTool::Exif;
  17         41  
  17         56021  
23              
24             $VERSION = '1.29';
25              
26             sub ProcessJpgFromRaw($$$);
27             sub WriteJpgFromRaw($$$);
28             sub WriteDistortionInfo($$$);
29             sub ProcessDistortionInfo($$$);
30              
31             my %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              
46             my %wbTypeInfo = (
47             PrintConv => \%Image::ExifTool::Exif::lightSource,
48             SeparateTable => 'EXIF LightSource',
49             );
50              
51             my %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             0x37 => { Name => 'ISO', Writable => 'int32u' },
222             # 0x44 - may contain another pointer to the raw data starting at byte 2 in this data (DC-GH6)
223             0x10f => {
224             Name => 'Make',
225             Groups => { 2 => 'Camera' },
226             Writable => 'string',
227             DataMember => 'Make',
228             # save this value as an ExifTool member variable
229             RawConv => '$self->{Make} = $val',
230             },
231             0x110 => {
232             Name => 'Model',
233             Description => 'Camera Model Name',
234             Groups => { 2 => 'Camera' },
235             Writable => 'string',
236             DataMember => 'Model',
237             # save this value as an ExifTool member variable
238             RawConv => '$self->{Model} = $val',
239             },
240             0x111 => {
241             Name => 'StripOffsets',
242             # (this value is 0xffffffff for some models, and RawDataOffset must be used)
243             Flags => [ 'IsOffset', 'PanasonicHack' ],
244             OffsetPair => 0x117, # point to associated byte counts
245             ValueConv => 'length($val) > 32 ? \$val : $val',
246             },
247             0x112 => {
248             Name => 'Orientation',
249             Writable => 'int16u',
250             PrintConv => \%Image::ExifTool::Exif::orientation,
251             Priority => 0, # so IFD1 doesn't take precedence
252             },
253             0x116 => {
254             Name => 'RowsPerStrip',
255             Priority => 0,
256             },
257             0x117 => {
258             Name => 'StripByteCounts',
259             # (note that this value may represent something like uncompressed byte count
260             # for RAW/RW2/RWL images from some models, and is zero for some other models)
261             OffsetPair => 0x111, # point to associated offset
262             ValueConv => 'length($val) > 32 ? \$val : $val',
263             },
264             0x118 => {
265             Name => 'RawDataOffset', #PH (RW2/RWL)
266             IsOffset => '$$et{TIFF_TYPE} =~ /^(RW2|RWL)$/', # (invalid in DNG-converted files)
267             PanasonicHack => 1,
268             OffsetPair => 0x117, # (use StripByteCounts as the offset pair)
269             NotRealPair => 1, # (to avoid Validate warning)
270             IsImageData => 1,
271             },
272             0x119 => {
273             Name => 'DistortionInfo',
274             SubDirectory => { TagTable => 'Image::ExifTool::PanasonicRaw::DistortionInfo' },
275             },
276             # 0x11b - chromatic aberration correction (ref 3) (also see forum9366)
277             0x11c => { #forum9373
278             Name => 'Gamma',
279             Writable => 'int16u',
280             # unfortunately it seems that the scaling factor varies with model...
281             ValueConv => '$val / ($val >= 1024 ? 1024 : ($val >= 256 ? 256 : 100))',
282             ValueConvInv => 'int($val * 256 + 0.5)',
283             },
284             0x120 => {
285             Name => 'CameraIFD',
286             SubDirectory => {
287             TagTable => 'Image::ExifTool::PanasonicRaw::CameraIFD',
288             Base => '$start',
289             ProcessProc => \&Image::ExifTool::ProcessTIFF,
290             },
291             },
292             0x121 => { #forum9295
293             Name => 'Multishot',
294             Writable => 'int32u',
295             PrintConv => {
296             0 => 'Off',
297             65536 => 'Pixel Shift',
298             },
299             },
300             # 0x122 - int32u: RAWDataOffset for the GH5s/GX9, or pointer to end of raw data for G9 (forum9295)
301             0x127 => { #github193 (newer models)
302             Name => 'JpgFromRaw2',
303             Groups => { 2 => 'Preview' },
304             DataTag => 'JpgFromRaw2',
305             RawConv => '$self->ValidateImage(\$val,$tag)',
306             },
307             0x13b => {
308             Name => 'Artist',
309             Groups => { 2 => 'Author' },
310             Permanent => 1, # (so we don't add it if the model doesn't write it)
311             Writable => 'string',
312             WriteGroup => 'IFD0',
313             RawConv => '$val =~ s/\s+$//; $val', # trim trailing blanks
314             },
315             0x2bc => { # PH Extension!!
316             Name => 'ApplicationNotes', # (writable directory!)
317             Writable => 'int8u',
318             Format => 'undef',
319             Flags => [ 'Binary', 'Protected' ],
320             SubDirectory => {
321             DirName => 'XMP',
322             TagTable => 'Image::ExifTool::XMP::Main',
323             },
324             },
325             0x001b => { #forum9250
326             Name => 'NoiseReductionParams',
327             Writable => 'undef',
328             Format => 'int16u',
329             Count => -1,
330             Flags => 'Protected',
331             Notes => q{
332             the camera's default noise reduction setup. The first number is the number
333             of entries, then for each entry there are 4 numbers: an ISO speed, and
334             noise-reduction strengths the R, G and B channels
335             },
336             },
337             0x8298 => { #github193
338             Name => 'Copyright',
339             Groups => { 2 => 'Author' },
340             Permanent => 1, # (so we don't add it if the model doesn't write it)
341             Format => 'undef',
342             Writable => 'string',
343             WriteGroup => 'IFD0',
344             RawConv => $Image::ExifTool::Exif::Main{0x8298}{RawConv},
345             RawConvInv => $Image::ExifTool::Exif::Main{0x8298}{RawConvInv},
346             PrintConvInv => $Image::ExifTool::Exif::Main{0x8298}{PrintConvInv},
347             },
348             0x83bb => { # PH Extension!!
349             Name => 'IPTC-NAA', # (writable directory!)
350             Format => 'undef', # convert binary values as undef
351             Writable => 'int32u', # but write int32u format code in IFD
352             WriteGroup => 'IFD0',
353             Flags => [ 'Binary', 'Protected' ],
354             SubDirectory => {
355             DirName => 'IPTC',
356             TagTable => 'Image::ExifTool::IPTC::Main',
357             },
358             },
359             0x8769 => {
360             Name => 'ExifOffset',
361             Groups => { 1 => 'ExifIFD' },
362             Flags => 'SubIFD',
363             SubDirectory => {
364             TagTable => 'Image::ExifTool::Exif::Main',
365             DirName => 'ExifIFD',
366             Start => '$val',
367             },
368             },
369             0x8825 => {
370             Name => 'GPSInfo',
371             Groups => { 1 => 'GPS' },
372             Flags => 'SubIFD',
373             SubDirectory => {
374             DirName => 'GPS',
375             TagTable => 'Image::ExifTool::GPS::Main',
376             Start => '$val',
377             },
378             },
379             # 0xffff => 'DCSHueShiftValues', #exifprobe (NC)
380             );
381              
382             # white balance information (ref IB)
383             # (PanasonicRawVersion<200: Digilux 2)
384             %Image::ExifTool::PanasonicRaw::WBInfo = (
385             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
386             WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
387             CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
388             WRITABLE => 1,
389             FORMAT => 'int16u',
390             FIRST_ENTRY => 0,
391             0 => 'NumWBEntries',
392             1 => { Name => 'WBType1', %wbTypeInfo },
393             2 => { Name => 'WB_RBLevels1', Format => 'int16u[2]' },
394             4 => { Name => 'WBType2', %wbTypeInfo },
395             5 => { Name => 'WB_RBLevels2', Format => 'int16u[2]' },
396             7 => { Name => 'WBType3', %wbTypeInfo },
397             8 => { Name => 'WB_RBLevels3', Format => 'int16u[2]' },
398             10 => { Name => 'WBType4', %wbTypeInfo },
399             11 => { Name => 'WB_RBLevels4', Format => 'int16u[2]' },
400             13 => { Name => 'WBType5', %wbTypeInfo },
401             14 => { Name => 'WB_RBLevels5', Format => 'int16u[2]' },
402             16 => { Name => 'WBType6', %wbTypeInfo },
403             17 => { Name => 'WB_RBLevels6', Format => 'int16u[2]' },
404             19 => { Name => 'WBType7', %wbTypeInfo },
405             20 => { Name => 'WB_RBLevels7', Format => 'int16u[2]' },
406             );
407              
408             # white balance information (ref IB)
409             # (PanasonicRawVersion>=200: D-Lux2, D-Lux3, DMC-FZ18/FZ30/LX1/L10)
410             %Image::ExifTool::PanasonicRaw::WBInfo2 = (
411             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
412             WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
413             CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
414             WRITABLE => 1,
415             FORMAT => 'int16u',
416             FIRST_ENTRY => 0,
417             0 => 'NumWBEntries',
418             1 => { Name => 'WBType1', %wbTypeInfo },
419             2 => { Name => 'WB_RGBLevels1', Format => 'int16u[3]' },
420             5 => { Name => 'WBType2', %wbTypeInfo },
421             6 => { Name => 'WB_RGBLevels2', Format => 'int16u[3]' },
422             9 => { Name => 'WBType3', %wbTypeInfo },
423             10 => { Name => 'WB_RGBLevels3', Format => 'int16u[3]' },
424             13 => { Name => 'WBType4', %wbTypeInfo },
425             14 => { Name => 'WB_RGBLevels4', Format => 'int16u[3]' },
426             17 => { Name => 'WBType5', %wbTypeInfo },
427             18 => { Name => 'WB_RGBLevels5', Format => 'int16u[3]' },
428             21 => { Name => 'WBType6', %wbTypeInfo },
429             22 => { Name => 'WB_RGBLevels6', Format => 'int16u[3]' },
430             25 => { Name => 'WBType7', %wbTypeInfo },
431             26 => { Name => 'WB_RGBLevels7', Format => 'int16u[3]' },
432             );
433              
434             # lens distortion information (ref 3)
435             # (distortion correction equation: Ru = scale*(Rd + a*Rd^3 + b*Rd^5 + c*Rd^7), ref 3)
436             %Image::ExifTool::PanasonicRaw::DistortionInfo = (
437             PROCESS_PROC => \&ProcessDistortionInfo,
438             WRITE_PROC => \&WriteDistortionInfo,
439             CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
440             # (don't make this family 0 MakerNotes because we don't want it to be a deletable group)
441             GROUPS => { 0 => 'PanasonicRaw', 1 => 'PanasonicRaw', 2 => 'Image'},
442             WRITABLE => 1,
443             FORMAT => 'int16s',
444             FIRST_ENTRY => 0,
445             NOTES => 'Lens distortion correction information.',
446             # 0,1 - checksums
447             2 => {
448             Name => 'DistortionParam02',
449             ValueConv => '$val / 32768',
450             ValueConvInv => '$val * 32768',
451             },
452             # 3 - usually 0, but seen 0x026b when value 5 is non-zero
453             4 => {
454             Name => 'DistortionParam04',
455             ValueConv => '$val / 32768',
456             ValueConvInv => '$val * 32768',
457             },
458             5 => {
459             Name => 'DistortionScale',
460             ValueConv => '1 / (1 + $val/32768)',
461             ValueConvInv => '(1/$val - 1) * 32768',
462             },
463             # 6 - seen 0x0000-0x027f
464             7.1 => {
465             Name => 'DistortionCorrection',
466             Mask => 0x0f,
467             # (have seen the upper 4 bits set for GF5 and GX1, giving a value of -4095 - PH)
468             PrintConv => { 0 => 'Off', 1 => 'On' },
469             },
470             8 => {
471             Name => 'DistortionParam08',
472             ValueConv => '$val / 32768',
473             ValueConvInv => '$val * 32768',
474             },
475             9 => {
476             Name => 'DistortionParam09',
477             ValueConv => '$val / 32768',
478             ValueConvInv => '$val * 32768',
479             },
480             # 10 - seen 0xfc,0x0101,0x01f4,0x021d,0x0256
481             11 => {
482             Name => 'DistortionParam11',
483             ValueConv => '$val / 32768',
484             ValueConvInv => '$val * 32768',
485             },
486             12 => {
487             Name => 'DistortionN',
488             Unknown => 1,
489             },
490             # 13 - seen 0x0000,0x01f9-0x02b2
491             # 14,15 - checksums
492             );
493              
494             # Panasonic RW2 camera IFD written by GH5 (ref PH)
495             # (doesn't seem to be valid for the GF7 or GM5 -- encrypted?)
496             %Image::ExifTool::PanasonicRaw::CameraIFD = (
497             GROUPS => { 0 => 'PanasonicRaw', 1 => 'CameraIFD', 2 => 'Camera'},
498             # (don't know what format codes 0x101 and 0x102 are for, so just
499             # map them into 4 = int32u for now)
500             VARS => { MAP_FORMAT => { 0x101 => 4, 0x102 => 4 } },
501             0x1001 => { #forum9388
502             Name => 'MultishotOn',
503             Writable => 'int32u',
504             PrintConv => { 0 => 'No', 1 => 'Yes' },
505             },
506             0x1100 => { #forum9274
507             Name => 'FocusStepNear',
508             Writable => 'int16s',
509             },
510             0x1101 => { #forum9274 (was forum8484)
511             Name => 'FocusStepCount',
512             Writable => 'int16s',
513             },
514             0x1102 => { #forum9417
515             Name => 'FlashFired',
516             Writable => 'int32u',
517             PrintConv => { 0 => 'No', 1 => 'Yes' },
518             },
519             # 0x1104 - set when camera shoots on lowest possible Extended-ISO (forum9290)
520             0x1105 => { #forum9392
521             Name => 'ZoomPosition',
522             Notes => 'in the range 0-255 for most cameras',
523             Writable => 'int32u',
524             },
525             0x1200 => { #forum9278
526             Name => 'LensAttached',
527             Notes => 'many CameraIFD tags are invalid if there is no lens attached',
528             Writable => 'int32u',
529             PrintConv => { 0 => 'No', 1 => 'Yes' },
530             },
531             # Note: LensTypeMake and LensTypeModel are combined into a Composite LensType tag
532             # defined in Olympus.pm which has the same values as Olympus:LensType
533             0x1201 => { #IB
534             Name => 'LensTypeMake',
535             Condition => '$format eq "int16u"',
536             Writable => 'int16u',
537             # when format is int16u, these values have been observed:
538             # 0 - Olympus or unknown lens
539             # 2 - Leica or Lumix lens
540             # when format is int32u (S models), these values have been observed (ref IB):
541             # 256 - Leica lens
542             # 257 - Lumix lens
543             # 258 - ? (seen once)
544             },
545             0x1202 => { #IB
546             Name => 'LensTypeModel',
547             Condition => '$format eq "int16u"',
548             Writable => 'int16u',
549             RawConv => q{
550             return undef unless $val;
551             require Image::ExifTool::Olympus; # (to load Composite LensID)
552             return $val;
553             },
554             ValueConv => '$_=sprintf("%.4x",$val); s/(..)(..)/$2 $1/; $_',
555             ValueConvInv => '$val =~ s/(..) (..)/$2$1/; hex($val)',
556             },
557             0x1203 => { #4
558             Name => 'FocalLengthIn35mmFormat',
559             Writable => 'int16u',
560             PrintConv => '"$val mm"',
561             PrintConvInv => '$val=~s/\s*mm$//;$val',
562             },
563             # 0x1300 - incident light value? (ref forum11395)
564             0x1301 => { #forum11395
565             Name => 'ApertureValue',
566             Writable => 'int16s',
567             Priority => 0,
568             ValueConv => '2 ** ($val / 512)',
569             ValueConvInv => '$val>0 ? 512*log($val)/log(2) : 0',
570             PrintConv => 'sprintf("%.1f",$val)',
571             PrintConvInv => '$val',
572             },
573             0x1302 => { #forum11395
574             Name => 'ShutterSpeedValue',
575             Writable => 'int16s',
576             Priority => 0,
577             ValueConv => 'abs($val/256)<100 ? 2**(-$val/256) : 0',
578             ValueConvInv => '$val>0 ? -256*log($val)/log(2) : -25600',
579             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
580             PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
581             },
582             0x1303 => { #forum11395
583             Name => 'SensitivityValue',
584             Writable => 'int16s',
585             ValueConv => '$val / 256',
586             ValueConvInv => 'int($val * 256)',
587             },
588             0x1305 => { #forum9384
589             Name => 'HighISOMode',
590             Writable => 'int16u',
591             RawConv => '$val || undef',
592             PrintConv => { 1 => 'On', 2 => 'Off' },
593             },
594             # 0x1306 EV for some models like the GX8 (forum11395)
595             # 0x140b - scaled overall black level? (ref forum9281)
596             # 0x1411 - scaled black level per channel difference (ref forum9281)
597             0x1412 => { #forum11397
598             Name => 'FacesDetected',
599             Writable => 'int8u',
600             PrintConv => { 0 => 'No', 1 => 'Yes' },
601             },
602             # 0x2000 - WB tungsten=3, daylight=4 (ref forum9467)
603             # 0x2009 - scaled black level per channel (ref forum9281)
604             # 0x3000-0x310b - red/blue balances * 1024 (ref forum9467)
605             # 0x3000 modifiedTungsten-Red (-2?)
606             # 0x3001 modifiedTungsten-Blue (-2?)
607             # 0x3002 modifiedDaylight-Red (-2?)
608             # 0x3003 modifiedDaylight-Blue (-2?)
609             # 0x3004 modifiedTungsten-Red (-1?)
610             # 0x3005 modifiedTungsten-Blue (-1?)
611             # 0x3006 modifiedDaylight-Red (-1?)
612             # 0x3007 modifiedDaylight-Blue (-1?)
613             # 0x3100 DefaultTungsten-Red
614             # 0x3101 DefaultTungsten-Blue
615             # 0x3102 DefaultDaylight-Red
616             # 0x3103 DefaultDaylight-Blue
617             # 0x3104 modifiedTungsten-Red (+1?)
618             # 0x3105 modifiedTungsten-Blue (+1?)
619             # 0x3106 modifiedDaylight-Red (+1?)
620             # 0x3107 modifiedDaylight-Blue (+1?)
621             # 0x3108 modifiedTungsten-Red (+2?)
622             # 0x3109 modifiedTungsten-Blue (+2?)
623             # 0x310a modifiedDaylight-Red (+2?)
624             # 0x310b modifiedDaylight-Blue (+2?)
625             0x3200 => { #forum9275
626             Name => 'WB_CFA0_LevelDaylight',
627             Writable => 'int16u',
628             },
629             0x3201 => { #forum9275
630             Name => 'WB_CFA1_LevelDaylight',
631             Writable => 'int16u',
632             },
633             0x3202 => { #forum9275
634             Name => 'WB_CFA2_LevelDaylight',
635             Writable => 'int16u',
636             },
637             0x3203 => { #forum9275
638             Name => 'WB_CFA3_LevelDaylight',
639             Writable => 'int16u',
640             },
641             # 0x3204-0x3207 - user multipliers * 1024 ? (ref forum9275)
642             # 0x320a - scaled maximum value of raw data (scaling = 4x) (ref forum9281)
643             # 0x3209 - gamma (x256) (ref forum9281)
644             0x3300 => { #forum9296/9396
645             Name => 'WhiteBalanceSet',
646             Writable => 'int8u',
647             PrintConv => \%panasonicWhiteBalance,
648             SeparateTable => 'WhiteBalance',
649             },
650             0x3420 => { #forum9276
651             Name => 'WB_RedLevelAuto',
652             Writable => 'int16u',
653             },
654             0x3421 => { #forum9276
655             Name => 'WB_BlueLevelAuto',
656             Writable => 'int16u',
657             },
658             0x3501 => { #4
659             Name => 'Orientation',
660             Writable => 'int8u',
661             PrintConv => \%Image::ExifTool::Exif::orientation,
662             },
663             # 0x3504 = Tag 0x1301+0x1302-0x1303 (Bv = Av+Tv-Sv) (forum11395)
664             # 0x3505 - same as 0x1300 (forum11395)
665             0x3600 => { #forum9396
666             Name => 'WhiteBalanceDetected',
667             Writable => 'int8u',
668             PrintConv => \%panasonicWhiteBalance,
669             SeparateTable => 'WhiteBalance',
670             },
671             );
672              
673             # PanasonicRaw composite tags
674             %Image::ExifTool::PanasonicRaw::Composite = (
675             ImageWidth => {
676             Require => {
677             0 => 'IFD0:SensorLeftBorder',
678             1 => 'IFD0:SensorRightBorder',
679             },
680             ValueConv => '$val[1] - $val[0]',
681             },
682             ImageHeight => {
683             Require => {
684             0 => 'IFD0:SensorTopBorder',
685             1 => 'IFD0:SensorBottomBorder',
686             },
687             ValueConv => '$val[1] - $val[0]',
688             },
689             );
690              
691             # add our composite tags
692             Image::ExifTool::AddCompositeTags('Image::ExifTool::PanasonicRaw');
693              
694              
695             #------------------------------------------------------------------------------
696             # checksum algorithm for lens distortion correction information (ref 3)
697             # Inputs: 0) data ref, 1) start position, 2) number of bytes, 3) incement
698             # Returns: checksum value
699             sub Checksum($$$$)
700             {
701 12     12 0 33 my ($dataPt, $start, $num, $inc) = @_;
702 12         16 my $csum = 0;
703 12         23 my $i;
704 12         35 for ($i=0; $i<$num; ++$i) {
705 156         373 $csum = (73 * $csum + Get8u($dataPt, $start + $i * $inc)) % 0xffef;
706             }
707 12         44 return $csum;
708             }
709              
710             #------------------------------------------------------------------------------
711             # Read lens distortion information
712             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
713             # Returns: 1 on success
714             sub ProcessDistortionInfo($$$)
715             {
716 2     2 0 9 my ($et, $dirInfo, $tagTablePtr) = @_;
717 2         5 my $dataPt = $$dirInfo{DataPt};
718 2   50     14 my $start = $$dirInfo{DirStart} || 0;
719 2   33     9 my $size = $$dirInfo{DirLen} || (length($$dataPt) - $start);
720 2 50       7 if ($size == 32) {
721             # verify the checksums (ref 3)
722 2         12 my $csum1 = Checksum($dataPt, $start + 4, 12, 1);
723 2         10 my $csum2 = Checksum($dataPt, $start + 16, 12, 1);
724 2         9 my $csum3 = Checksum($dataPt, $start + 2, 14, 2);
725 2         8 my $csum4 = Checksum($dataPt, $start + 3, 14, 2);
726 2         10 my $res = $csum1 ^ Get16u($dataPt, $start + 2) ^
727             $csum2 ^ Get16u($dataPt, $start + 28) ^
728             $csum3 ^ Get16u($dataPt, $start + 0) ^
729             $csum4 ^ Get16u($dataPt, $start + 30);
730 2 50       11 $et->Warn('Invalid DistortionInfo checksum',1) if $res;
731             } else {
732 0         0 $et->Warn('Invalid DistortionInfo',1);
733             }
734 2         14 return $et->ProcessBinaryData($dirInfo, $tagTablePtr);
735             }
736              
737             #------------------------------------------------------------------------------
738             # Write lens distortion information
739             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
740             # Returns: updated distortion information or undef on error
741             sub WriteDistortionInfo($$$)
742             {
743 9     9 0 30 my ($et, $dirInfo, $tagTablePtr) = @_;
744 9 100       71 $et or return 1; # (allow dummy access)
745 1         9 my $dat = $et->WriteBinaryData($dirInfo, $tagTablePtr);
746 1 50 33     11 if (defined $dat and length($dat) == 32) {
747             # fix checksums (ref 3)
748 1         7 Set16u(Checksum(\$dat, 4, 12, 1), \$dat, 2);
749 1         4 Set16u(Checksum(\$dat, 16, 12, 1), \$dat, 28);
750 1         5 Set16u(Checksum(\$dat, 2, 14, 2), \$dat, 0);
751 1         4 Set16u(Checksum(\$dat, 3, 14, 2), \$dat, 30);
752             } else {
753 0         0 $et->Warn('Error wriing DistortionInfo',1);
754             }
755 1         7 return $dat;
756             }
757              
758             #------------------------------------------------------------------------------
759             # Patch for writing non-standard Panasonic RAW/RW2/RWL raw data
760             # Inputs: 0) offset info ref, 1) raf ref, 2) IFD number
761             # Returns: error string, or undef on success
762             # OffsetInfo is a hash by tag ID of lists with the following elements:
763             # 0 - tag info ref
764             # 1 - pointer to int32u offset in IFD or value data
765             # 2 - value count
766             # 3 - reference to list of original offset values
767             # 4 - IFD format number
768             # 5 - (pointer to StripOffsets value added by this PatchRawDataOffset routine)
769             # 6 - flag set if this is a fixed offset (Panasonic GH6 fixed-offset hack)
770             sub PatchRawDataOffset($$$)
771             {
772 1     1 0 4 my ($offsetInfo, $raf, $ifd) = @_;
773 1         4 my $stripOffsets = $$offsetInfo{0x111};
774 1         3 my $stripByteCounts = $$offsetInfo{0x117};
775 1         3 my $rawDataOffset = $$offsetInfo{0x118};
776 1         2 my $err;
777 1 50       5 $err = 1 unless $ifd == 0;
778 1 50 33     9 if ($stripOffsets or $stripByteCounts) {
779 1 50 33     12 $err = 1 unless $stripOffsets and $stripByteCounts and $$stripOffsets[2] == 1;
      33        
780             } else {
781             # the DC-GH6 and DC-GH5M2 write RawDataOffset with no Strip tags, so we need
782             # to create fake StripByteCounts information for copying the data
783 0 0       0 if ($$offsetInfo{0x118}) { # (just to be safe)
784 0         0 $stripByteCounts = $$offsetInfo{0x117} = [ $PanasonicRaw::Main{0x117}, 0, 1, [ 0 ], 4 ];
785             # set flag so the offset will be fixed (GH6 hack, see https://exiftool.org/forum/index.php?topic=13861.0)
786             # (of course, fixing up the offset is now unnecessary, but continue to do this even
787             # though the fixup adjustment will be 0 because this allows us to delete the following
788             # line to remove the fix-offset restriction if Panasonic ever sees the light, but note
789             # that in this case we should investigate the purpose of the seemily-duplicate raw
790             # data offset contained within PanasonicRaw_0x0044)
791 0         0 $$offsetInfo{0x118}[6] = 1;
792             }
793             }
794 1 50 33     7 if ($rawDataOffset and not $err) {
795 1 50       5 $err = 1 unless $$rawDataOffset[2] == 1;
796 1 50       5 if ($stripOffsets) {
797 1 50 33     7 $err = 1 unless $$stripOffsets[3][0] == 0xffffffff or $$stripByteCounts[3][0] == 0;
798             }
799             }
800 1 50       5 $err and return 'Unsupported Panasonic/Leica RAW variant';
801 1 50       4 if ($rawDataOffset) {
802             # update StripOffsets along with this tag if it contains a reasonable value
803 1 50 33     8 if ($stripOffsets and $$stripOffsets[3][0] != 0xffffffff) {
804             # save pointer to StripOffsets value for updating later
805 0         0 push @$rawDataOffset, $$stripOffsets[1];
806             }
807             # handle via RawDataOffset instead of StripOffsets
808 1         3 $stripOffsets = $$offsetInfo{0x111} = $rawDataOffset;
809 1         3 delete $$offsetInfo{0x118};
810             }
811             # determine the length of the raw data
812 1         7 my $pos = $raf->Tell();
813 1 50       5 $raf->Seek(0, 2) or $err = 1; # seek to end of file
814 1         5 my $len = $raf->Tell() - $$stripOffsets[3][0];
815 1         6 $raf->Seek($pos, 0);
816             # quick check to be sure the raw data length isn't unreasonable
817             # (the 22-byte length is for '' in our tests)
818 1 50 33     16 $err = 1 if ($len < 1000 and $len != 22) or $len & 0x80000000;
      33        
819 1 50       6 $err and return 'Error reading Panasonic raw data';
820             # update StripByteCounts info with raw data length
821             # (note that the original value is maintained in the file)
822 1         4 $$stripByteCounts[3][0] = $len;
823              
824 1         4 return undef;
825             }
826              
827             #------------------------------------------------------------------------------
828             # Write meta information to Panasonic JpgFromRaw in RAW/RW2/RWL image
829             # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
830             # Returns: updated image data, or undef if nothing changed
831             sub WriteJpgFromRaw($$$)
832             {
833 1     1 0 5 my ($et, $dirInfo, $tagTablePtr) = @_;
834 1         3 my $dataPt = $$dirInfo{DataPt};
835 1         4 my $byteOrder = GetByteOrder();
836 1         4 my $fileType = $$et{TIFF_TYPE}; # RAW, RW2 or RWL
837 1         3 my $dirStart = $$dirInfo{DirStart};
838 1 50       7 if ($dirStart) { # DirStart is non-zero in DNG-converted RW2/RWL
839 0         0 my $dirLen = $$dirInfo{DirLen} | length($$dataPt) - $dirStart;
840 0         0 my $buff = substr($$dataPt, $dirStart, $dirLen);
841 0         0 $dataPt = \$buff;
842             }
843 1         7 my $raf = File::RandomAccess->new($dataPt);
844 1         3 my $outbuff;
845 1         6 my %dirInfo = (
846             RAF => $raf,
847             OutFile => \$outbuff,
848             );
849 1         5 $$et{BASE} = $$dirInfo{DataPos};
850 1         6 $$et{FILE_TYPE} = $$et{TIFF_TYPE} = 'JPEG';
851             # use a specialized map so we don't write XMP or IPTC (or other junk) into the JPEG
852 1         3 my $editDirs = $$et{EDIT_DIRS};
853 1         3 my $addDirs = $$et{ADD_DIRS};
854 1         8 $et->InitWriteDirs(\%jpgFromRawMap);
855             # don't add XMP segment (IPTC won't get added because it is in Photoshop record)
856 1         3 delete $$et{ADD_DIRS}{XMP};
857 1         11 my $result = $et->WriteJPEG(\%dirInfo);
858             # restore variables we changed
859 1         6 $$et{BASE} = 0;
860 1         4 $$et{FILE_TYPE} = 'TIFF';
861 1         5 $$et{TIFF_TYPE} = $fileType;
862 1         6 $$et{EDIT_DIRS} = $editDirs;
863 1         6 $$et{ADD_DIRS} = $addDirs;
864 1         6 SetByteOrder($byteOrder);
865 1 50       16 return $result > 0 ? $outbuff : $$dataPt;
866             }
867              
868             #------------------------------------------------------------------------------
869             # Extract meta information from an Panasonic JpgFromRaw
870             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
871             # Returns: 1 on success, 0 if this wasn't a valid JpgFromRaw image
872             sub ProcessJpgFromRaw($$$)
873             {
874 2     2 0 9 my ($et, $dirInfo, $tagTablePtr) = @_;
875 2         8 my $dataPt = $$dirInfo{DataPt};
876 2         8 my $byteOrder = GetByteOrder();
877 2         7 my $fileType = $$et{TIFF_TYPE}; # RAW, RW2 or RWL
878 2         7 my $tagInfo = $$dirInfo{TagInfo};
879 2         11 my $verbose = $et->Options('Verbose');
880 2         7 my ($indent, $out);
881 2 50       9 $tagInfo or $et->Warn('No tag info for Panasonic JpgFromRaw'), return 0;
882 2         5 my $dirStart = $$dirInfo{DirStart};
883 2 50       10 if ($dirStart) { # DirStart is non-zero in DNG-converted RW2/RWL
884 0         0 my $dirLen = $$dirInfo{DirLen} | length($$dataPt) - $dirStart;
885 0         0 my $buff = substr($$dataPt, $dirStart, $dirLen);
886 0         0 $dataPt = \$buff;
887             }
888 2   50     17 $$et{BASE} = $$dirInfo{DataPos} + ($dirStart || 0);
889 2         9 $$et{FILE_TYPE} = $$et{TIFF_TYPE} = 'JPEG';
890 2         8 $$et{DOC_NUM} = 1;
891             # extract information from embedded JPEG
892 2         18 my %dirInfo = (
893             Parent => 'RAF',
894             RAF => File::RandomAccess->new($dataPt),
895             );
896 2 50       8 if ($verbose) {
897 0         0 my $indent = $$et{INDENT};
898 0         0 $$et{INDENT} = ' ';
899 0         0 $out = $et->Options('TextOut');
900 0         0 print $out '--- DOC1:JpgFromRaw ',('-'x56),"\n";
901             }
902             # fudge HtmlDump base offsets to show as a stand-alone JPEG
903 2         10 $$et{BASE_FUDGE} = $$et{BASE};
904 2         14 my $rtnVal = $et->ProcessJPEG(\%dirInfo);
905 2         8 $$et{BASE_FUDGE} = 0;
906             # restore necessary variables for continued RW2/RWL processing
907 2         7 $$et{BASE} = 0;
908 2         9 $$et{FILE_TYPE} = 'TIFF';
909 2         8 $$et{TIFF_TYPE} = $fileType;
910 2         9 delete $$et{DOC_NUM};
911 2         12 SetByteOrder($byteOrder);
912 2 50       11 if ($verbose) {
913 0         0 $$et{INDENT} = $indent;
914 0         0 print $out ('-'x76),"\n";
915             }
916 2         17 return $rtnVal;
917             }
918              
919             1; # end
920              
921             __END__