exiftool_vendored 13.21.0 → 13.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e09f4452fe21aa8e743a6060c2eab2bd13d43b65eceb0a3665f5c85d58db0103
4
- data.tar.gz: 33e426b70e4a07102f6d60c61d1e162fbc5164198be900b9548f665e4a58f03a
3
+ metadata.gz: ad918781e3d8bd85a5482713e9f0007a5c6a49baf150bc89899989a38d2976b4
4
+ data.tar.gz: 6c73a902a729e31ca68eb9cfb10162cc9cb58e4f5530a8383c791bd00f358f93
5
5
  SHA512:
6
- metadata.gz: 5195564935d181ce7d5338fe53be6b88918ddcef2042d41045dc6dae27046aac9fe4317e0e3d333882315339bf11f8811d00084fa9274e3c57388cafe8c179d4
7
- data.tar.gz: a04e8fc3f16ba37322ba42c7e6ec9bd8b7662232975b0f7cf6ee9483cc78ce1e7b4e136df76f1e6b7f3682a3a17b2084a4ac1fc35e3d4bc38448129b0ac88a79
6
+ metadata.gz: 7140b651487bbf72d3977a7af1dff81162a4e11de366121a9fcfffd6ef767df7c91e5e84c4f184ee2f0691fd014f90bdf1ed24bb71262d9b2b10225e3ae59374
7
+ data.tar.gz: 607ce339d770ac81eb2bea3d1cf1a2029c3916bd717ebc300c20807ad7c443218c34ede0769f3329dea40fba4abdd1fedf6e7e18ae367c5fa6901a790f6048fd
data/bin/Changes CHANGED
@@ -7,10 +7,22 @@ RSS feed: https://exiftool.org/rss.xml
7
7
  Note: The most recent production release is Version 13.10. (Other versions are
8
8
  considered development releases, and are not uploaded to MetaCPAN.)
9
9
 
10
+ Mar. 1, 2025 - Version 13.22
11
+
12
+ - Improvements and additions to the new plot feature
13
+ - Decode a few more DJI timed metadata tags
14
+ - Extract GainMapImage from PNG images
15
+ - Reverted WebP FileType change of version 13.20
16
+ - Set $^W in the exiftool application to re-enable global warnings (they have
17
+ been disabled since version 12.92 due to the shebang change)
18
+ - Fixed possible incorrect IFD number in "No size tag" warning
19
+ - Fixed "File is empty" error when using -plot with -w to write the output
20
+ file into the same directory as the input files
21
+
10
22
  Feb. 20, 2025 - Version 13.21
11
23
 
12
24
  - Patched issue that could result in runtime warning for some video files
13
- - Fixed a bug that could cause a compiler error when using the -plot option
25
+ - Fixed a bug with the new -plot option that could generate a compiler error
14
26
 
15
27
  Feb. 20, 2025 - Version 13.20
16
28
 
data/bin/MANIFEST CHANGED
@@ -208,6 +208,7 @@ html/htmldump.html
208
208
  html/idiosyncracies.html
209
209
  html/index.html
210
210
  html/install.html
211
+ html/markers.svg
211
212
  html/metafiles.html
212
213
  html/mistakes.html
213
214
  html/overview.png
@@ -216,6 +217,7 @@ html/plot1.svg
216
217
  html/plot2.svg
217
218
  html/plot3.svg
218
219
  html/plot4.svg
220
+ html/plot5.svg
219
221
  html/standards.html
220
222
  html/struct.html
221
223
  html/style.css
data/bin/META.json CHANGED
@@ -50,5 +50,5 @@
50
50
  }
51
51
  },
52
52
  "release_status" : "stable",
53
- "version" : "13.21"
53
+ "version" : "13.22"
54
54
  }
data/bin/META.yml CHANGED
@@ -31,4 +31,4 @@ recommends:
31
31
  Time::HiRes: '0'
32
32
  requires:
33
33
  perl: '5.004'
34
- version: '13.21'
34
+ version: '13.22'
data/bin/README CHANGED
@@ -109,8 +109,8 @@ your home directory, then you would type the following commands in a
109
109
  terminal window to extract and run ExifTool:
110
110
 
111
111
  cd ~/Desktop
112
- gzip -dc Image-ExifTool-13.21.tar.gz | tar -xf -
113
- cd Image-ExifTool-13.21
112
+ gzip -dc Image-ExifTool-13.22.tar.gz | tar -xf -
113
+ cd Image-ExifTool-13.22
114
114
  ./exiftool t/images/ExifTool.jpg
115
115
 
116
116
  Note: These commands extract meta information from one of the test images.
data/bin/exiftool CHANGED
@@ -11,7 +11,9 @@ use strict;
11
11
  use warnings;
12
12
  require 5.004;
13
13
 
14
- my $version = '13.21';
14
+ my $version = '13.22';
15
+
16
+ $^W = 1; # enable global warnings
15
17
 
16
18
  # add our 'lib' directory to the include list BEFORE 'use Image::ExifTool'
17
19
  my $exePath;
@@ -1551,10 +1553,11 @@ if ($textOut2) {
1551
1553
  undef $textOut2;
1552
1554
  next;
1553
1555
  }
1556
+ # make sure we can write the output text file before processing all input files
1554
1557
  CreateDirectory($textOut2); # create directory if necessary
1555
1558
  if ($mt->Open(\*OUTFILE, $textOut2, '>')) {
1556
- $tmpText = $textOut2; # delete if command aborted
1557
- $textOut2 = \*OUTFILE;
1559
+ close(\*OUTFILE);
1560
+ unlink($textOut2); # (this was just a test)
1558
1561
  } else {
1559
1562
  Error("Error creating $textOut2\n");
1560
1563
  undef $textOut2;
@@ -1968,27 +1971,36 @@ if ($textOut) {
1968
1971
  } else {
1969
1972
  print $sectTrailer if $sectTrailer;
1970
1973
  print $fileTrailer if $fileTrailer and not $fileHeader;
1971
- # print CSV information if necessary
1972
- my $err;
1973
- PrintCSV($textOut2) if $csv and not $isWriting;
1974
- # print SVG plot
1975
- if ($plot) {
1976
- $plot->Draw($textOut2 || \*STDOUT);
1977
- if ($$plot{Error}) {
1978
- Error("Error: $$plot{Error}\n");
1974
+ # print CSV or SVG output file if necessary
1975
+ my ($fp, $err);
1976
+ if ($textOut2) {
1977
+ if ($mt->Open(\*OUTFILE, $textOut2, '>')) {
1978
+ $fp = \*OUTFILE;
1979
+ } else {
1980
+ Error("Error creating $textOut2\n");
1979
1981
  $err = 1;
1980
- } elsif ($$plot{Warn}) {
1981
- Warn("Warning: $$plot{Warn}\n");
1982
1982
  }
1983
1983
  }
1984
- if ($textOut2) {
1985
- close($textOut2) or $err = 1;
1984
+ unless ($err) {
1985
+ PrintCSV($fp) if $csv and not $isWriting;
1986
+ # print SVG plot
1987
+ if ($plot) {
1988
+ $plot->Draw($fp || \*STDOUT);
1989
+ if ($$plot{Error}) {
1990
+ Error("Error: $$plot{Error}\n");
1991
+ $err = 1;
1992
+ } elsif ($$plot{Warn}) {
1993
+ Warn("Warning: $$plot{Warn}\n");
1994
+ }
1995
+ }
1996
+ }
1997
+ if ($fp) {
1998
+ close($fp) or $err = 1;
1986
1999
  if ($err) {
1987
- $mt->Unlink($tmpText);
2000
+ $mt->Unlink($textOut2);
1988
2001
  } else {
1989
2002
  $created{$textOut2} = 1;
1990
2003
  }
1991
- undef $tmpText;
1992
2004
  }
1993
2005
  }
1994
2006
 
@@ -6041,7 +6053,7 @@ with this command:
6041
6053
 
6042
6054
  produces output like this:
6043
6055
 
6044
- -- Generated by ExifTool 13.21 --
6056
+ -- Generated by ExifTool 13.22 --
6045
6057
  File: a.jpg - 2003:10:31 15:44:19
6046
6058
  (f/5.6, 1/60s, ISO 100)
6047
6059
  File: b.jpg - 2006:05:23 11:57:38
@@ -18,7 +18,7 @@ use Image::ExifTool::XMP;
18
18
  use Image::ExifTool::GPS;
19
19
  use Image::ExifTool::Protobuf;
20
20
 
21
- $VERSION = '1.13';
21
+ $VERSION = '1.14';
22
22
 
23
23
  sub ProcessDJIInfo($$$);
24
24
  sub ProcessSettings($$$);
@@ -266,6 +266,9 @@ my %convFloat2 = (
266
266
  },
267
267
  'dvtm_ac203_3-2-6-1' => { Name => 'ColorTemperature', Format => 'unsigned' }, # (NC)
268
268
  # dvtm_ac203_3-2-9-1 - looks like Z accerometer measurement, but 2 and 3 don't look like other components
269
+ 'dvtm_ac203_3-2-10-2' => { Name => 'AccelerometerX', Format => 'float' } , # (NC) left/right
270
+ 'dvtm_ac203_3-2-10-3' => { Name => 'AccelerometerY', Format => 'float' } , # (NC) front/back
271
+ 'dvtm_ac203_3-2-10-4' => { Name => 'AccelerometerZ', Format => 'float' } , # (NC) up/down
269
272
  # dvtm_ac203_3-4-1-4 - model code?
270
273
  'dvtm_ac203_3-4-2-1' => {
271
274
  Name => 'GPSInfo',
@@ -301,6 +304,9 @@ my %convFloat2 = (
301
304
  PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
302
305
  },
303
306
  'dvtm_ac204_3-2-6-1' => { Name => 'ColorTemperature', Format => 'unsigned' }, # (NC)
307
+ 'dvtm_ac204_3-2-10-2' => { Name => 'AccelerometerX', Format => 'float' } , # (NC) left/right
308
+ 'dvtm_ac204_3-2-10-3' => { Name => 'AccelerometerY', Format => 'float' } , # (NC) front/back
309
+ 'dvtm_ac204_3-2-10-4' => { Name => 'AccelerometerZ', Format => 'float' } , # (NC) up/down
304
310
  # dvtm_ac204_3-4-1-4 - model code?
305
311
  'dvtm_ac204_3-4-2-1' => {
306
312
  Name => 'GPSInfo',
@@ -413,6 +419,11 @@ my %convFloat2 = (
413
419
  Format => 'rational',
414
420
  PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
415
421
  },
422
+ 'dvtm_pm320_3-2-4-1' => { # (NC)
423
+ Name => 'FNumber',
424
+ Format => 'rational',
425
+ PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
426
+ },
416
427
  'dvtm_pm320_3-2-6-1' => { Name => 'DigitalZoom', Format => 'float' },
417
428
  'dvtm_pm320_3-3-4-1' => {
418
429
  Name => 'GPSInfo',
@@ -437,7 +437,7 @@ sub Process_marl($$$)
437
437
  {
438
438
  my ($et, $dirInfo, $tagTablePtr) = @_;
439
439
  my $dataPt = $$dirInfo{DataPt};
440
- my $dataPos = $$dirInfo{DataPos} + $$dirInfo{Base};
440
+ my $dataPos = ($$dirInfo{DataPos} || 0) + ($$dirInfo{Base} || 0);
441
441
  my $dataLen = length $$dataPt;
442
442
  my $vals = $$et{GMVals}; # running values for each channel (0=TimeStamp)
443
443
  my $chan = $$et{GMChan}; # running channel number
@@ -202,7 +202,7 @@ sub ProcessJPEG_HDR($$$);
202
202
  Groups => { 0 => 'APP6', 1 => 'DJI' },
203
203
  Notes => 'DJI Thermal Analysis Tool record',
204
204
  ValueConv => 'substr($val,7)',
205
- # also seen Motorola APP6 "MMIMETA\0", with sub-types: AL3A,ALED,MMI0,MOTD,QC3A
205
+ # also seen Motorola APP6 "MMIMETA\0", with sub-types: AL3A,ALED,MMI0,MOTD,QC3A,LMB1
206
206
  }],
207
207
  APP7 => [{
208
208
  Name => 'Pentax',
@@ -4833,17 +4833,17 @@ my %base64coord = (
4833
4833
  0x2f => [
4834
4834
  {
4835
4835
  Name => 'FocusPositionHorizontal', # 209/231 focus point cameras
4836
- Condition => '$$self{Model} =~ /^NIKON (Z 30|Z 50|Z fc)\b/i and $$self{AFAreaXPosition} != 0', #models Z30, Z50, Zfc
4836
+ Condition => '$$self{Model} =~ /^NIKON (Z 30|Z 50|Z fc)\b/i and $$self{AFAreaXPosition}', #models Z30, Z50, Zfc
4837
4837
  ValueConv => 'int($$self{AFAreaXPosition} / 260 )', #divisor is an estimate (chosen to cause center point to report 'C')
4838
4838
  PrintConv => sub { my ($val) = @_; PrintAFPointsLeftRight($val, 19 ) },
4839
4839
  },{
4840
4840
  Name => 'FocusPositionHorizontal', #273/299 focus point cameras
4841
- Condition => '$$self{Model} =~ /^NIKON (Z 5|Z 6|Z 6_2|D780)\b/i and $$self{AFAreaXPosition} != 0', #models Z5, Z6, Z6ii, D780
4841
+ Condition => '$$self{Model} =~ /^NIKON (Z 5|Z 6|Z 6_2|D780)\b/i and $$self{AFAreaXPosition}', #models Z5, Z6, Z6ii, D780
4842
4842
  ValueConv => 'int($$self{AFAreaXPosition} / 260 )', #divisor is an estimate (chosen to cause center point to report 'C')
4843
4843
  PrintConv => sub { my ($val) = @_; PrintAFPointsLeftRight($val, 21 ) },
4844
4844
  },{
4845
4845
  Name => 'FocusPositionHorizontal', #405/493 focus point cameras
4846
- Condition => '$$self{Model} =~ /^NIKON (Z 7|Z 7_2)\b/i and $$self{AFAreaXPosition} != 0', #models Z7/Z7ii
4846
+ Condition => '$$self{Model} =~ /^NIKON (Z 7|Z 7_2)\b/i and $$self{AFAreaXPosition}', #models Z7/Z7ii
4847
4847
  ValueConv => 'int($$self{AFAreaXPosition} / 260 )', #divisor is the measured horizontal pixel separation between adjacent points
4848
4848
  PrintConv => sub { my ($val) = @_; PrintAFPointsLeftRight($val, 29 ) },
4849
4849
  },
@@ -4858,17 +4858,17 @@ my %base64coord = (
4858
4858
  0x31 => [
4859
4859
  {
4860
4860
  Name => 'FocusPositionVertical', # 209/233 focus point cameras
4861
- Condition => '$$self{Model} =~ /^NIKON (Z 30|Z 50|Z fc)\b/i and $$self{AFAreaYPosition} != 0', #models Z30, Z50, Zfc
4861
+ Condition => '$$self{Model} =~ /^NIKON (Z 30|Z 50|Z fc)\b/i and $$self{AFAreaYPosition}', #models Z30, Z50, Zfc
4862
4862
  ValueConv => 'int($$self{AFAreaYPosition} / 286 )', #divisor is an estimate (chosen to cause center point to report 'C')
4863
4863
  PrintConv => sub { my ($val) = @_; PrintAFPointsUpDown($val, 11 ) },
4864
4864
  },{
4865
4865
  Name => 'FocusPositionVertical', #273/299 focus point cameras
4866
- Condition => '$$self{Model} =~ /^NIKON (Z 5|Z 6|Z 6_2|D780)\b/i and $$self{AFAreaYPosition} != 0', #models Z5, Z6, Z6ii, D780
4866
+ Condition => '$$self{Model} =~ /^NIKON (Z 5|Z 6|Z 6_2|D780)\b/i and $$self{AFAreaYPosition}', #models Z5, Z6, Z6ii, D780
4867
4867
  ValueConv => 'int($$self{AFAreaYPosition} / 286 )', #divisor is an estimate (chosen to cause center point to report 'C')
4868
4868
  PrintConv => sub { my ($val) = @_; PrintAFPointsUpDown($val, 13 ) },
4869
4869
  },{
4870
4870
  Name => 'FocusPositionVertical', #405/493 focus point cameras
4871
- Condition => '$$self{Model} =~ /^NIKON (Z 7|Z 7_2)\b/i and $$self{AFAreaYPosition} != 0', #models Z7/Z7ii
4871
+ Condition => '$$self{Model} =~ /^NIKON (Z 7|Z 7_2)\b/i and $$self{AFAreaYPosition}', #models Z7/Z7ii
4872
4872
  ValueConv => 'int($$self{AFAreaYPosition} / 292 )', #divisor is the measured vertical pixel separation between adjacent points
4873
4873
  PrintConv => sub { my ($val) = @_; PrintAFPointsUpDown($val, 17 ) },
4874
4874
  },
@@ -36,7 +36,7 @@ use strict;
36
36
  use vars qw($VERSION $AUTOLOAD %stdCase);
37
37
  use Image::ExifTool qw(:DataAccess :Utils);
38
38
 
39
- $VERSION = '1.70';
39
+ $VERSION = '1.71';
40
40
 
41
41
  sub ProcessPNG_tEXt($$$);
42
42
  sub ProcessPNG_iTXt($$$);
@@ -371,6 +371,12 @@ my %noLeapFrog = ( SAVE => 1, SEEK => 1, IHDR => 1, JHDR => 1, IEND => 1, MEND =
371
371
  IgnoreProp => { meta => 1 }, # ignore 'meta' container
372
372
  },
373
373
  },
374
+ gdAT => {
375
+ Name => 'GainMapImage',
376
+ Groups => { 2 => 'Preview' },
377
+ Binary => 1,
378
+ },
379
+ # gmAP - https://github.com/w3c/png/issues/380 does't correspond to my only sample
374
380
  seAl => {
375
381
  Name => 'SEAL',
376
382
  SubDirectory => { TagTable => 'Image::ExifTool::XMP::SEAL' },
@@ -18,16 +18,17 @@ my %defaults = (
18
18
  size => [ 800, 600 ], # width,height of output image
19
19
  margin => [ 60, 15, 15, 30 ], # left,top,right,bottom margins around plot area
20
20
  legend => [ 0, 0 ], # top,right offset for legend
21
- txtPad => [ 10, 10 ], # padding between text and x,y scale
22
- lineSpacing => 20, # text line spacing
23
- # colors for plot lines
24
- cols => [ qw(red green blue black orange gray purple cyan brown pink
25
- goldenrod lightsalmon seagreen goldenrod cadetblue plum
26
- deepskyblue mediumpurple royalblue tomato) ],
27
- grid => 'darkgray', # grid color
28
- text => 'black', # text and plot frame color
21
+ txtpad => [ 10, 10 ], # padding between text and x,y scale
22
+ linespacing => 20, # text line spacing
23
+ # colours for plot lines
24
+ cols => [ qw(red green blue black orange gray fuchsia brown turquoise gold
25
+ lime violet maroon aqua navy pink olive indigo silver teal) ],
26
+ marks => [ qw(circle square triangle diamond star plus pentagon left down right) ],
27
+ stroke => 1, # stroke width and marker scaling
28
+ grid => 'darkgray', # grid colour
29
+ text => 'black', # text and plot frame colour
29
30
  type => 'line', # plot type, 'line' or 'scatter'
30
- style => 'line', # 'line', 'marker' or 'line+marker'
31
+ style => '', # 'line', 'marker' or 'line+marker'
31
32
  xlabel => '', # x axis label
32
33
  ylabel => '', # y axis label
33
34
  title => '', # plot title
@@ -35,24 +36,33 @@ my %defaults = (
35
36
  # xmin, xmax # x axis minimum,maximum
36
37
  # ymin, ymax # y axis minimum,maximum
37
38
  # split # split list of numbers into separate plot lines
38
- # bkg # background color
39
+ # bkg # background colour
40
+ # multi # flag to make one plot per dataset
41
+ #
42
+ # members containing capital letters are used internally
43
+ #
39
44
  Data => { }, # data arrays for each variable
40
45
  Name => [ ], # variable names
41
- XMax => 0, # number of points in plot so far
46
+ # XMin, XMax # min/max data index
47
+ # YMin, YMax # min/max data value
48
+ # SaveName, Save # saved variables between plots
42
49
  );
43
50
 
44
- my @markerName = qw(circle square triangle diamond triangle2 triangle3 triangle4);
45
- my @markerData = (
46
- '<circle cx="6" cy="6" r="4" stroke-width="1.5" stroke="context-stroke" fill="none" />',
47
- '<path stroke-width="1.5" stroke="context-stroke" fill="none" d="M2.5 2.5 l7 0 0 7 -7 0 z"/>',
48
- '<path stroke-width="1.5" stroke="context-stroke" fill="none" d="M6 1.2 l4 8 -8 0 z"/>',
49
- '<path stroke-width="1.5" stroke="context-stroke" fill="none" d="M6 1.5 l4.5 4.5 -4.5 4.5 -4.5 -4.5 z"/>',
50
- '<path stroke-width="1.5" stroke="context-stroke" fill="none" d="M1.2 6 l8 4 0 -8 z"/>',
51
- '<path stroke-width="1.5" stroke="context-stroke" fill="none" d="M6 10.8 l4 -8 -8 0 z"/>',
52
- '<path stroke-width="1.5" stroke="context-stroke" fill="none" d="M10.8 6 l-8 4 0 -8 z"/>',
51
+ my %markerData = (
52
+ circle => '<circle cx="4" cy="4" r="2.667"',
53
+ square => '<path d="M1.667 1.667 l4.667 0 0 4.667 -4.667 0 z"',
54
+ triangle => '<path d="M4 0.8 l2.667 5.333 -5.333 0 z"',
55
+ diamond => '<path d="M4 1 l3 3 -3 3 -3 -3 z"',
56
+ star => '<path d="M4 0.8 L5 2.625 7.043 3.011 5.617 4.525 5.881 6.589 4 5.7 2.119 6.589 2.383 4.525 0.957 3.011 3 2.625 z"',
57
+ plus => '<path d="M2.75 1 l2.5 0 0 1.75 1.75 0 0 2.5 -1.75 0 0 1.75 -2.5 0 0 -1.75 -1.75 0 0 -2.5 1.75 0 z"',
58
+ pentagon => '<path d="M4 1 L6.853 3.073 5.763 6.427 2.237 6.427 1.147 3.073 z"',
59
+ left => '<path d="M0.8 4 l5.333 2.667 0 -5.333 z"',
60
+ down => '<path d="M4 7.2 l2.667 -5.333 -5.333 0 z"',
61
+ right => '<path d="M7.2 4 l-5.333 2.667 0 -5.333 z"',
53
62
  );
54
- # optimal number grid lines in X and Y for a 800x600 plot and nominal character width
55
- my ($nx, $ny, $wch) = (15, 12, 8);
63
+
64
+ my @ng = (20, 15); # optimal number grid lines in X and Y for a 800x600 plot
65
+ my $wch = 8; # nominal width of a character (measured at 7.92)
56
66
 
57
67
  #------------------------------------------------------------------------------
58
68
  # Create new plot object
@@ -78,17 +88,40 @@ sub Settings($$)
78
88
  return unless $set;
79
89
  foreach (split /,\s*/, $set) {
80
90
  next unless /^([a-z].*?)(=(.*))?$/i;
81
- my ($name, $val) = ($1, $3);
91
+ my ($name, $val) = (lc $1, $3);
82
92
  if (ref $$self{$name} eq 'ARRAY') {
83
93
  next unless defined $val;
84
- $$self{lc $name} = [ split /[\s\/]+/, $val ]; # split on space or slash
94
+ my $isNum = $$self{$name}[0] =~ /^\d+$/;
95
+ # also allow numbers to also be separated by 'x'
96
+ my @vals = $isNum ? split(/\s*[x\s\/+]\s*/, $val) : split(/\s*[\s\/+]\s*/, $val);
97
+ my $i;
98
+ for ($i=0; @vals; ++$i) {
99
+ my $val = lc shift @vals;
100
+ next unless length $val;
101
+ if ($name eq 'marks') {
102
+ my @v = split /-/, $val;
103
+ if ($v[0]) {
104
+ if ($v[0] =~ /^n/) {
105
+ $v[0] = 'none';
106
+ } else {
107
+ ($v[0]) = grep /^$v[0]/, @{$defaults{marks}};
108
+ $v[0] or $$self{Warn} = 'Invalid marker name', next;
109
+ }
110
+ } else {
111
+ # cycle through default markers if none specified
112
+ $v[0] = $defaults{marks}[$i % @{$defaults{marks}}];
113
+ }
114
+ $val = join '-', @v;
115
+ }
116
+ $$self{$name}[$i] = $val;
117
+ }
85
118
  } else {
86
119
  $val = 1 unless defined $val; # default to 1 if no "="
87
120
  my %charName = ('&'=>'amp', '<'=>'lt', '>'=>'gt');
88
121
  # escape necessary XML characters, but allow numerical entities
89
122
  $val =~ s/([&><])/&$charName{$1};/sg and $val =~ s/&amp;(#(\d+|x[0-9a-fA-F]+);)/&$1/;
90
123
  undef $val unless length $val;
91
- $$self{lc $name} = $val;
124
+ $$self{$name} = $val;
92
125
  }
93
126
  }
94
127
  }
@@ -102,12 +135,13 @@ sub AddPoints($$$)
102
135
  my ($tag, $name, %num, $index, $mod, $val, @vals);
103
136
  my ($ee, $docNum, $data, $xmin, $xmax) = @$self{qw(EE DocNum Data XMin XMax)};
104
137
  $$self{type} or $$self{type} = 'line';
105
- my $scat = $$self{type} =~ /^s/i;
138
+ my $scat = $$self{type} =~ /^s/ ? 1 : 0;
106
139
  my $xname = $$self{Name}[0]; # (x-axis name if using scatter plot)
107
- my $maxLines = $$self{type} =~ /^h/i ? 1 : 20;
140
+ my $maxLines = ($$self{type} =~ /^h/ and not $$self{multi}) ? 1 : 20;
108
141
  for (;;) {
109
142
  if (@vals) {
110
143
  $val = shift @vals;
144
+ next unless $val =~ /^[+-]?(?=\.?\d)\d*\.?\d*(?:e[+-]?\d+)?$/;
111
145
  } else {
112
146
  $tag = shift @$tags or last;
113
147
  # ignore non-floating-point values
@@ -127,7 +161,7 @@ sub AddPoints($$$)
127
161
  }
128
162
  }
129
163
  }
130
- next unless defined $val and $val =~ /^[+-]?(?=\.?\d)\d*\.?\d*(?:e[+-]?\d+)?([ ,;\t\n\r]?|\z)/i;
164
+ next unless defined $val and $val =~ /^[+-]?(?=\.?\d)\d*\.?\d*(?:e[+-]?\d+)?([ ,;\t\n\r]+|$)/i;
131
165
  if ($1) {
132
166
  # split a string of numbers into separate plot points (eg. histogram tags)
133
167
  if ($$self{'split'}) {
@@ -145,10 +179,10 @@ sub AddPoints($$$)
145
179
  my $docNum = $docNum ? $$docNum{$tag} || 0 : 0;
146
180
  next if $docNum and not $ee;
147
181
  unless ($$data{$name}) {
148
- if (@{$$self{Name}} >= $maxLines) {
182
+ if (@{$$self{Name}} >= $maxLines + $scat) {
149
183
  unless ($$self{MaxTags}) {
150
- if ($$self{type} =~ /^h/i) {
151
- $$self{Warn} = 'A histogram can only plot one variable';
184
+ if ($$self{type} =~ /^h/ and not $$self{multi}) {
185
+ $$self{Warn} = 'Use the Multi setting to make a separate histogram for each dataset';
152
186
  } else {
153
187
  $$self{Warn} = 'Too many variables to plot all of them';
154
188
  }
@@ -158,10 +192,11 @@ sub AddPoints($$$)
158
192
  }
159
193
  push @{$$self{Name}}, $name;
160
194
  $xname or $xname = $name; # x-axis data for scatter plot
161
- unless (defined $$self{Min}) {
162
- $$self{Min} = $$self{Max} = $val unless $scat and $name eq $xname;
163
- $xmin = $xmax = $docNum unless defined $xmin;
195
+ unless ($scat and $name eq $xname) {
196
+ $$self{Max} = $val if not defined $$self{Max} or $val > $$self{Max};
197
+ $$self{Min} = $val if not defined $$self{Min} or $val < $$self{Min};
164
198
  }
199
+ $xmin = $xmax = $docNum unless defined $xmin;
165
200
  $num{$name} = $xmax;
166
201
  $$data{$name}[$xmax - $xmin] = $val if $xmax >= $xmin;
167
202
  next;
@@ -186,20 +221,40 @@ sub AddPoints($$$)
186
221
 
187
222
  #------------------------------------------------------------------------------
188
223
  # Calculate a nice round number for grid spacing
189
- # Inputs: 0) nominal spacing (must be positive)
224
+ # Inputs: 0) nominal spacing (must be positive), 1) flag to increment to next number
190
225
  # Returns: spacing rounded to an even number
191
- sub GetGridSpacing($)
226
+ sub GetGridSpacing($;$)
192
227
  {
193
- my $nom = shift;
194
- my $rounded;
228
+ my ($nom, $inc) = @_;
229
+ my ($rounded, $spc);
195
230
  my $div = sprintf('%.3e', $nom);
196
231
  my $num = substr($div, 0, 1);
197
232
  my $exp = $div =~ s/.*e// ? $div : 0;
198
- # look for nearest factor to 1, 1.5, 2 or 5 * 10^x
199
- ($num, $exp) = $num < 8 ? (5, $exp) : (1, $exp+1) if $num > 2;
233
+ if ($inc) {
234
+ # increment to next highest even number
235
+ $num = $num < 2 ? 2 : ($num < 5 ? 5 : (++$exp, 1));
236
+ } else {
237
+ # look for nearest factor to 1, 2 or 5 * 10^x
238
+ $num = $num < 8 ? 5 : (++$exp, 1) if $num > 2;
239
+ }
200
240
  return $exp >= 0 ? $num . ('0' x $exp) : '.' . ('0' x (-$exp - 1)) . $num;
201
241
  }
202
242
 
243
+ #------------------------------------------------------------------------------
244
+ # Get plot range
245
+ # Inputs: 0) minimum, 1) maximum
246
+ # Returns: difference
247
+ # Notes: Adjusts min/max if necessary to make difference positive
248
+ sub GetRange($$)
249
+ {
250
+ if ($_[0] >= $_[1]) {
251
+ $_[0] = ($_[0] + $_[1]) / 2;
252
+ $_[0] -= 0.5 if $_[0];
253
+ $_[1] = $_[0] + 1;
254
+ }
255
+ return $_[1] - $_[0];
256
+ }
257
+
203
258
  #------------------------------------------------------------------------------
204
259
  # Draw SVG plot
205
260
  # Inputs: 0) Plot ref, 1) Output file reference
@@ -208,29 +263,85 @@ sub Draw($$)
208
263
  my ($self, $fp) = @_;
209
264
  my ($min, $max, $xmin, $xmax, $name, $style) = @$self{qw(Min Max XMin XMax Name style)};
210
265
 
211
- if (not defined $min or not defined $xmin or not $style) {
266
+ if (not defined $min or not defined $xmin) {
212
267
  $$self{Error} = 'Nothing to plot';
213
268
  return;
214
269
  }
215
- my ($data, $title, $xlabel, $ylabel, $cols) = @$self{qw(Data title xlabel ylabel cols)};
216
- my ($i, $n, %col, %class, $dx, $dy, $dx2, $xAxis, $x, $y, $px, $py);
217
- my ($grid, $lastLen, $noLegend, $xname, $xdat, $xdiff, $diff);
218
- my $scat = $$self{type} =~ /^s/i ? 1 : 0;
219
- my $hist = $$self{type} =~ /^h/i ? [ ] : 0;
270
+ my ($i, $n, %col, %class, $dx, $dy, $dx2, $xAxis, $x, $y, $px, $py, @og);
271
+ my ($noLegend, $xname, $xdat, $xdiff, $diff, %markID, $plotNum);
272
+ my $scat = $$self{type} =~ /^s/ ? 1 : 0;
273
+ my $hist = $$self{type} =~ /^h/ ? [ ] : 0;
274
+ my $multi = int($$self{multi} || 0);
275
+ $multi = 0 unless $multi > 0;
276
+ $style or $style = $hist ? 'line+fill' : 'line';
277
+ unless ($style =~ /\b[mpl]/ or ($hist and $style =~ /\bf/)) {
278
+ $$self{Error} = 'Invalid plot Style setting';
279
+ return;
280
+ }
281
+ my $numPlots = $multi ? scalar(@$name) - $scat : 1;
282
+ my @size = @{$$self{size}};
283
+ my $sy = $size[1];
284
+ if ($multi) {
285
+ $sy *= int(($numPlots + $multi - 1) / $multi) / $multi;
286
+ $_ /= $multi foreach @size;
287
+ }
288
+ my $tmp = $$self{title} || "Plot by ExifTool $Image::ExifTool::VERSION";
289
+ print $fp qq{<?xml version="1.0" standalone="no"?>
290
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
291
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="$$self{size}[0]" height="$sy"
292
+ preserveAspectRatio="xMidYMid meet" viewBox="0 0 $$self{size}[0] $sy">
293
+ <title>$tmp</title>};
294
+ # loop through all plots
295
+ for ($plotNum=0; $plotNum<$numPlots; ++$plotNum) {
296
+ if ($numPlots > 1) {
297
+ print $fp "\n<g transform='translate(", ($plotNum % $multi) * $size[0],
298
+ ',', int($plotNum/$multi) * $size[1], ")'>";
299
+ if ($plotNum) {
300
+ @$self{qw(XMin XMax title xlabel ylabel)} = @{$$self{Save}};
301
+ } else {
302
+ $$self{Save} = [ @$self{qw(XMin XMax title xlabel ylabel)} ];
303
+ $$self{SaveName} = [ @{$$self{Name}} ];
304
+ }
305
+ $name = $$self{Name} = [ ];
306
+ push @{$$self{Name}}, $$self{SaveName}[0] if $scat;
307
+ push @{$$self{Name}}, $$self{SaveName}[$scat + $plotNum];
308
+ my $dat = $$self{Data}{$$self{Name}[$scat]};
309
+ undef $min; undef $max;
310
+ foreach (@$dat) {
311
+ defined or next;
312
+ defined $min or $min = $max = $_, next;
313
+ $min > $_ and $min = $_;
314
+ $max < $_ and $max = $_;
315
+ }
316
+ }
317
+ my ($data, $title, $xlabel, $ylabel, $cols, $marks, $tpad, $wid) =
318
+ @$self{qw(Data title xlabel ylabel cols marks txtpad stroke)};
220
319
  my @name = @$name;
221
320
  my @margin = ( @{$$self{margin}} );
222
321
 
223
322
  # set reasonable default titles and labels
224
323
  $xname = shift @name if $scat;
225
324
  $title = "$name[0] vs $xname" if $scat and defined $title and not $title and @name == 1;
226
- $xlabel = $$name[0] if $scat || $hist and defined $xlabel and not $xlabel;
227
- $ylabel = ($hist ? 'Count' : $name[0]) and $noLegend=1 if defined $ylabel and not $ylabel and @name == 1;
325
+ if ($scat || $hist and defined $xlabel and not $xlabel) {
326
+ $xlabel = $$name[0];
327
+ $noLegend = 1 if $hist;
328
+ }
329
+ if (defined $ylabel and not $ylabel and @name == 1) {
330
+ $ylabel = $hist ? 'Count' : $name[0];
331
+ $noLegend = 1 unless $hist;
332
+ }
228
333
 
229
334
  # make room for title/labels
230
- $margin[1] += $$self{lineSpacing} * 1.5 if $title;
231
- $margin[3] += $$self{lineSpacing} if $xlabel;
232
- $margin[0] += $$self{lineSpacing} if $ylabel;
335
+ $margin[1] += $$self{linespacing} * 1.5 if $title;
336
+ $margin[3] += $$self{linespacing} if $xlabel;
337
+ $margin[0] += $$self{linespacing} if $ylabel;
233
338
 
339
+ # calculate optimal number of X/Y grid lines
340
+ for ($i=0; $i<2; ++$i) {
341
+ $og[$i] = $ng[$i] * ($size[$i] - $margin[$i] - $margin[$i+2]) /
342
+ ($defaults{size}[$i] - $defaults{margin}[$i] - $defaults{margin}[$i+2]);
343
+ $og[$i] <= 0 and $$self{Error} = 'Invalid plot size', return;
344
+ }
234
345
  if ($scat) {
235
346
  $xdat = $$self{Data}{$xname};
236
347
  unless (defined $$self{xmin} and defined $$self{xmax}) {
@@ -241,7 +352,7 @@ sub Draw($$)
241
352
  $xmin = $_ if $xmin > $_;
242
353
  $xmax = $_ if $xmax < $_;
243
354
  }
244
- my $dnx2 = ($xmax - $xmin) / ($nx * 2);
355
+ my $dnx2 = ($xmax - $xmin) / ($og[0] * 2);
245
356
  # leave a bit of a left/right margin, but don't pass 0
246
357
  $xmin = ($xmin >= 0 and $xmin < $dnx2) ? 0 : $xmin - $dnx2;
247
358
  $xmax = ($xmax <= 0 and -$xmax < $dnx2) ? 0 : $xmax + $dnx2;
@@ -261,7 +372,7 @@ sub Draw($$)
261
372
  $max = $$self{xmax} if defined $$self{xmax};
262
373
  } else {
263
374
  # leave a bit of a margin above/below data when autoscaling but don't pass 0
264
- my $dny2 = ($max - $min) / ($ny * 2);
375
+ my $dny2 = ($max - $min) / ($og[1] * 2);
265
376
  $min = ($min >= 0 and $min < $dny2) ? 0 : $min - $dny2;
266
377
  $max = ($max <= 0 and -$max < $dny2) ? 0 : $max + $dny2;
267
378
  # adjust to user-defined range if specified
@@ -269,15 +380,15 @@ sub Draw($$)
269
380
  $max = $$self{ymax} if defined $$self{ymax};
270
381
  }
271
382
  # generate random colors if we need more
272
- while (@$cols < @$name) {#138
383
+ while (@$cols < @$name) {
273
384
  $$self{seeded} or srand(141), $$self{seeded} = 1;
274
385
  push @$cols, sprintf("#%.2x%.2x%.2x",int(rand(220)),int(rand(220)),int(rand(220)));
275
386
  }
276
- $diff = $max - $min || 1;
277
- $xdiff = $xmax - $xmin || 1;
387
+ $diff = GetRange($min, $max);
388
+ $xdiff = GetRange($xmin, $xmax);
278
389
 
279
390
  # determine y grid spacing (nice even numbers)
280
- $dy = GetGridSpacing($diff / ($hist ? $$self{nbins} : $ny));
391
+ $dy = GetGridSpacing($diff / ($hist ? $$self{nbins} : $og[1]));
281
392
  # expand plot min/max to the nearest even multiple of our grid spacing
282
393
  $min = ($min > 0 ? int($min/$dy) : int($min/$dy-0.9999)) * $dy;
283
394
  $max = ($max > 0 ? int($max/$dy+0.9999) : int($max/$dy)) * $dy;
@@ -300,125 +411,137 @@ sub Draw($$)
300
411
  } else {
301
412
  $max < $_ and $max = $_ foreach @$hist; # find max count
302
413
  }
303
- $diff = $max - $min || 1;
414
+ $diff = GetRange($min, $max);
304
415
  $dx = $dy;
305
- $dy = GetGridSpacing($diff / $ny);
416
+ $dy = GetGridSpacing($diff / $og[1]);
306
417
  $max = ($max > 0 ? int($max/$dy+0.9999) : int($max/$dy)) * $dy;
307
418
  $$data{$name[0]} = $hist;
308
419
  } else {
309
- $dx = GetGridSpacing($xdiff / $nx);
420
+ $dx = GetGridSpacing($xdiff / $og[0]);
310
421
  }
311
422
  if ($scat) {
312
423
  $xmin = ($xmin > 0 ? int($xmin/$dx) : int($xmin/$dx-0.9999)) * $dx;
313
424
  $xmax = ($xmax > 0 ? int($xmax/$dx+0.9999) : int($xmax/$dx)) * $dx;
314
425
  }
315
- $diff = $max - $min || 1;
316
- $xdiff = $xmax - $xmin || 1;
426
+ $diff = GetRange($min, $max);
427
+ $xdiff = GetRange($xmin, $xmax);
317
428
  # width/height of plot area
318
- my $width = $$self{size}[0] - $margin[0] - $margin[2];
319
- my $height = $$self{size}[1] - $margin[1] - $margin[3];
429
+ my $width = $size[0] - $margin[0] - $margin[2];
430
+ my $height = $size[1] - $margin[1] - $margin[3];
320
431
  my $yscl = $height / $diff;
321
432
  my $xscl = $width / $xdiff;
322
433
  my $px0 = $margin[0] - $xmin * $xscl;
323
434
  my $py0 = $margin[1] + $height + $min * $yscl;
324
- my $tmp = $title || "Plot by ExifTool $Image::ExifTool::VERSION";
325
- print $fp qq{<?xml version="1.0" standalone="no"?>
326
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
327
- <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="$$self{size}[0]" height="$$self{size}[1]"
328
- preserveAspectRatio="xMidYMid meet" viewBox="0 0 $$self{size}[0] $$self{size}[1]">
329
- <title>$tmp</title>};
330
- print $fp "<rect x='0' y='0' width='$$self{size}[0]' height='$$self{size}[1]' fill='$$self{bkg}'/>" if $$self{bkg};
435
+ my @clip = ($margin[0]-6*$wid, $margin[1]-6*$wid, $width+12*$wid, $height+12*$wid);
436
+ print $fp "\n<!-- Definitions -->\n<defs>\n<clipPath id='plot-area'>";
437
+ print $fp "<rect x='$clip[0]' y='$clip[1]' width='$clip[2]' height='$clip[3]'/></clipPath>";
438
+ if ($style =~ /\b[mp]/) { # 'm' for 'marker' or 'p' for 'point' (undocumented)
439
+ for ($i=0; $i<@name; ++$i) {
440
+ my @m = split /-/, ($$marks[$i] || $defaults{marks}[$i % @{$defaults{marks}}]);
441
+ my ($fill, $mark);
442
+ $fill = $m[2] || $$cols[$i] if $m[1] ? $m[1] =~ /^f/ : $style =~ /\bf/;
443
+ $mark = $markerData{$m[0]};
444
+ $mark or $markID{$mark} = '', next; # skip 'none' or unrecognized marker name
445
+ if ($fill and $fill ne 'none') {
446
+ my $op = $m[3] || ($$cols[$i] eq 'none' ? 50 : 20);
447
+ $mark .= qq( fill="$fill" style="fill-opacity: $op%");
448
+ } else {
449
+ $mark .= ' fill="none"';
450
+ }
451
+ $mark .= ' stroke="context-stroke"/>';
452
+ # don't re-define mark if it is the same as a previous one
453
+ $markID{$mark} and $markID{$i} = $markID{$mark}, next;
454
+ $markID{$mark} = $markID{$i} = "mark$i";
455
+ print $fp "\n<marker id='$markID{$i}' markerWidth='8' markerHeight='8' refX='4'",
456
+ " refY='4'>\n$mark\n</marker>";
457
+ }
458
+ print $fp "\n</defs>\n<style>";
459
+ for ($i=0; $i<@name; ++$i) {
460
+ next unless $markID{$i} eq "mark$i";
461
+ print $fp "\n path.mark$i { marker: url(#mark$i) }";
462
+ }
463
+ } else {
464
+ print $fp "\n</defs>\n<style>";
465
+ }
466
+ print $fp "\n text { fill: $$self{text} }\n</style>";
467
+ print $fp "\n<rect x='0' y='0' width='$size[0]' height='$size[1]' fill='$$self{bkg}'/>" if $$self{bkg};
331
468
  print $fp "\n<!-- X axis -->";
332
469
  print $fp "\n<g dominant-baseline='hanging' text-anchor='middle'>";
333
- $py = int(($margin[1] + $height + $$self{txtPad}[1]) * 10 + 0.5) / 10;
470
+ $py = int(($margin[1] + $height + $$tpad[1]) * 10 + 0.5) / 10;
334
471
  $px = int(($margin[0] + $width / 2) * 10 + 0.5) / 10;
335
472
  if ($title) {
336
473
  print $fp "\n<text x='${px}' y='14' font-size='150%'>$title</text>";
337
474
  }
338
475
  if ($xlabel) {
339
- $y = $py + $$self{lineSpacing};
476
+ $y = $py + $$self{linespacing};
340
477
  print $fp "\n<text x='${px}' y='${y}'>$xlabel</text>";
341
478
  }
342
479
  if ($ylabel) {
343
480
  $y = $margin[1] + $height / 2;
344
481
  print $fp "\n<text x='10' y='${y}' transform='rotate(-90,10,$y)'>$ylabel</text>";
345
482
  }
346
- # check to be sure the X labels will fit
347
- my $len = 0;
348
- for ($i=0, $x=$xmax; $i<3; ++$i, $x-=$dx) {
349
- $n = length sprintf('%g', $x);
350
- $len = $n if $len < $n;
483
+ # make sure the X labels will fit
484
+ my $spc = $dx;
485
+ for (;;) {
486
+ # find longest label at current spacing
487
+ my $len = 0;
488
+ my $x0 = int($xmax / $spc + 0.5) * $spc; # get value of last x label
489
+ for ($i=0, $x=$x0; $i<3; ++$i, $x-=$spc) {
490
+ $n = length sprintf('%g', $x);
491
+ $len = $n if $len < $n;
492
+ }
493
+ last if $spc >= ($len + 1) * $wch * $xdiff / $width;
494
+ # increase label spacing by one increment and try again
495
+ $spc = $dx2 = GetGridSpacing($spc, 1);
351
496
  }
352
- my $n = $wch * $len * $xdiff / $dx; # conservative length of all x-axis text
353
- $dx2 = GetGridSpacing($dx * $n * 1.5 / 500) if $n > 500; # use larger label spacing
354
- ($grid, $lastLen) = ('', 0);
497
+ my ($grid, $lastLen) = ('', 0);
355
498
  for ($x=int($xmin/$dx-1)*$dx; ; $x+=$dx) {
356
499
  $px = int(($margin[0] + ($x - $xmin) * $width / $xdiff) * 10 + 0.5) / 10;
357
500
  next if $px < $margin[0] - 0.5;
358
501
  last if $px > $margin[0] + $width + 0.5;
359
- if (not $dx2 or $x/$dx2 - int($x/$dx2) < 0.1) {
502
+ my $h = $height;
503
+ if (not $dx2 or abs($x/$dx2 - int($x/$dx2+($x>0 ? 0.5 : -0.5))) < 0.01) {
360
504
  printf $fp "\n<text x='${px}' y='${py}'>%g</text>", $x;
505
+ $h += $$tpad[1]/2;
361
506
  }
362
507
  length($grid) - $lastLen > 80 and $grid .= "\n", $lastLen = length($grid);
363
- $grid .= sprintf("M$px $margin[1] v$height ");
508
+ $grid .= sprintf("M$px $margin[1] v$h ");
364
509
  }
365
510
  print $fp "\n<path stroke='$$self{grid}' stroke-width='0.5' d='\n${grid}'/>";
366
511
  print $fp "\n</g>\n<!-- Y axis -->\n<g dominant-baseline='middle' text-anchor='end'>";
367
- $px = int(($margin[0] - $$self{txtPad}[0]) * 10 + 0.5) / 10;
512
+ $px = int(($margin[0] - $$tpad[0]) * 10 + 0.5) / 10;
368
513
  ($grid, $lastLen) = ('', 0);
514
+ my ($gx, $gw) = ($margin[0]-$$tpad[0]/2, $width + $$tpad[0]/2);
369
515
  for ($y=$min; ; $y+=$dy) {
370
516
  $py = int(($margin[1] + $height - ($y - $min) * $yscl) * 10 + 0.5) / 10;
371
517
  last if $py < $margin[1] - 0.5;
372
- $y = 0 if $y < $dy/2 and $y > -$dy/2; # (avoid round-off errors)
518
+ $y = 0 if $y < $dy/2 and $y > -$dy/2; # (avoid round-off errors)
373
519
  printf $fp "\n<text x='${px}' y='${py}'>%g</text>", $y;
374
- $y < $dy/2 and $y > -$dy/2 and $xAxis = 1, next; # draw x axis later
520
+ $y < $dy/2 and $y > -$dy/2 and $xAxis = 1; # redraw x axis later
375
521
  length($grid) - $lastLen > 80 and $grid .= "\n", $lastLen = length($grid);
376
- $grid .= "M$margin[0] $py h$width ";
522
+ $grid .= "M$gx $py h$gw ";
377
523
  }
378
524
  if ($xAxis and $min!=0) {
379
525
  $py = $margin[1] + $height + $min * $yscl;
380
- print $fp "\n<path stroke='$$self{text}' d='M$margin[0] $py h${width}'/>";
526
+ print $fp "\n<path stroke='$$self{text}' d='M$margin[0] $py h$width'/>";
381
527
  }
382
528
  print $fp "\n<path stroke='$$self{grid}' stroke-width='0.5' d='\n${grid}'/>";
383
- print $fp "\n</g>\n<!-- Plot box and legend-->\n<g dominant-baseline='middle' text-anchor='start'>";
529
+ print $fp "\n</g>\n<!-- Plot box and legend -->\n<g dominant-baseline='middle' text-anchor='start'>";
384
530
  print $fp "\n<path stroke='$$self{text}' fill='none' d='M$margin[0] $margin[1] l0 $height $width 0 0 -$height z'/>";
385
531
  for ($i=0; $i<@name and not $noLegend; ++$i) {
386
- next if $scat and not $i;
387
- $x = $margin[0] + $$self{legend}[0] + 550;
388
- $y = $margin[1] + $$self{legend}[1] + 15 + $$self{lineSpacing} * ($i - $scat + 0.5);
532
+ $x = $size[0] - $margin[2] - 175 + $$self{legend}[0];
533
+ $y = $margin[1] + $$self{legend}[1] + 15 + $$self{linespacing} * ($i + 0.5);
389
534
  my $col = $$cols[$i];
390
- my $mark = '';
391
- if ($style =~ /\b[mp]/i) { # 'm' for 'marker' or 'p' for 'point' (undocumented)
392
- my $id = $markerName[$i % @markerName];
393
- $mark = " marker-end='url(#$id)' fill='none'";
394
- }
395
- my $line = ($style =~ /\bl/i) ? ' l-20 0' : '';
396
- print $fp "\n<path$mark stroke-width='2' stroke='${col}' d='M$x $y m-7 -1${line}'/>";
535
+ my $mark = $markID{$i} ? " marker-end='url(#$markID{$i})' fill='none'" : '';
536
+ my $line = ($style =~ /\bl/) ? ' l-20 0' : sprintf(' m%.4g 0', -5 * $wid);
537
+ my $sw = ($style =~ /\bm/ ? 1.5 : 2) * $wid; # (wider for line-only style so colour is more visible)
538
+ print $fp "\n<path$mark stroke-width='${sw}' stroke='${col}' d='M$x $y m-7 -1${line}'/>";
397
539
  print $fp "\n<text x='${x}' y='${y}'>$name[$i]</text>";
398
540
  }
399
- my @clip = ($margin[0]-6, $margin[1]-6, $width+12, $height+12);
400
- print $fp "\n</g>\n<!-- Definitions -->\n<defs>";
401
- print $fp "\n<clipPath id='plot-area'><rect x='$clip[0]' y='$clip[1]' width='$clip[2]' height='$clip[3]' /></clipPath>";
402
- if ($style =~ /\b[mp]/i) {
403
- for ($i=0; $i<@markerName and $i<@name; ++$i) {
404
- print $fp "\n<marker id='@markerName[$i]' markerWidth='12' markerHeight='12' refX='6' refY='6' markerUnits='userSpaceOnUse'>";
405
- my $mark = $markerData[$i];
406
- $mark =~ s/"none"/"$$cols[$i]"/ if $style =~ /\bf/i;
407
- print $fp "\n$mark\n</marker>";
408
- }
409
- print $fp "\n</defs>\n<style>";
410
- for ($i=0; $i<@markerName and $i<@name; ++$i) {
411
- print $fp "\n path.$markerName[$i] { marker: url(#$markerName[$i]) }";
412
- }
413
- print $fp "\n text { fill: $$self{text}] }";
414
- print $fp "\n</style>";
415
- } else {
416
- print $fp "\n</defs><style>\n text { fill: $$self{text} }\n</style>";
417
- }
418
- print $fp "\n<g fill='none' clip-path='url(#plot-area)' stroke-linejoin='round' stroke-linecap='round' stroke-width='1.5'>";
541
+ # print the data
419
542
  foreach (0..$#name) {
420
543
  $col{$name[$_]} = $$cols[$_];
421
- $class{$name[$_]} = $style =~ /\b[mp]/i ? ' class="' . $markerName[$_ % @markerName] . '"' : '';
544
+ $class{$name[$_]} = $markID{$_} ? " class='$markID{$_}'" : '';
422
545
  }
423
546
  my ($i0, $i1, $xsclr);
424
547
  my $fill = '';
@@ -429,17 +552,25 @@ sub Draw($$)
429
552
  $xscl = $width / @$hist;
430
553
  $px0 = $margin[0];
431
554
  $xsclr = int($xscl * 100 + 0.5) / 100;
432
- $fill = qq( fill="$$cols[0]" style="fill-opacity: .20") if $$self{style} =~ /\bf/i;
555
+ if ($style =~ /\bf/) {
556
+ my @m = split /-/, $$marks[0];
557
+ my $op = $m[3] || ($style =~ /\bl/ ? 20 : 50);
558
+ $fill = " fill='$$cols[0]'";
559
+ $fill .= " style='fill-opacity: $op%'" if $$cols[0] ne 'none';
560
+ }
433
561
  } else {
434
562
  $i0 = int($xmin) - 1;
435
563
  $i0 = 0 if $i0 < 0;
436
564
  $i1 = int($xmax) + 1;
437
565
  }
566
+ print $fp "\n</g>\n<!-- Datasets -->\n<g fill='none' clip-path='url(#plot-area)'",
567
+ " stroke-linejoin='round' stroke-linecap='round' stroke-width='",1.5*$wid,"'>";
568
+ my $doLines = $style =~ /\bl/;
438
569
  foreach (@name) {
570
+ my $stroke = ($hist and not $doLines) ? 'none' : $col{$_};
439
571
  my $dat = $$data{$_};
440
- my $doLines = $style =~ /\bl/i;
441
572
  print $fp "\n<!-- $_ -->";
442
- print $fp "\n<path$class{$_}$fill stroke='$col{$_}' d='";
573
+ print $fp "\n<path$class{$_}$fill stroke='${stroke}' d='";
443
574
  print $fp 'M' if $doLines;
444
575
  my $m = $doLines ? '' : ' M';
445
576
  for ($i=$i0; $i<=$i1; ++$i) {
@@ -456,12 +587,15 @@ sub Draw($$)
456
587
  next;
457
588
  }
458
589
  }
459
- print $fp $m, ($i % 20 ? ' ' : "\n"), "$x $y";
590
+ print $fp $m, ($i % 10 ? ' ' : "\n"), "$x $y";
460
591
  }
461
592
  print $fp ' V', $margin[1]+$height, " H$margin[0] z" if $hist and $fill;
462
593
  print $fp "'/>";
463
594
  }
464
- print $fp "\n</g></svg>\n";
595
+ print $fp "\n</g>";
596
+ print $fp "\n</g>" if $numPlots > 1;
597
+ }
598
+ print $fp "</svg>\n" or $$self{Error} = 'Error writing output plot file';
465
599
  }
466
600
 
467
601
  1; # end
@@ -472,14 +606,92 @@ __END__
472
606
 
473
607
  Image::ExifTool::Plot - Plot tag values in SVG format
474
608
 
475
- =head1 SYNOPSIS
609
+ =head1 DESCRIPTION
476
610
 
477
- This module is used by Image::ExifTool
611
+ Output plots in SVG format based on ExifTool tag information.
478
612
 
479
- =head1 DESCRIPTION
613
+ =head1 METHODS
614
+
615
+ =head2 new
616
+
617
+ Create a new Plot object.
618
+
619
+ $plot = Image::ExifTool::Plot->new;
620
+
621
+ =head2 Settings
622
+
623
+ Change plot settings.
624
+
625
+ =over 4
626
+
627
+ =item Inputs:
628
+
629
+ 0) Plot object reference
630
+
631
+ 1) Comma-delimited string of options
632
+
633
+ =item Options:
634
+
635
+ "Type=Line" - plot type (Line, Scatter or Histogram)
636
+ "Style=Line" - data style (Line, Marker and/or Fill)
637
+ "NBins=20" - number of bins for histogram plot
638
+ "Size=800 600" - width,height of output image
639
+ "Margin=60 15 15 30" - left,top,right,bottom margins around plot area
640
+ "Legend=0 0" - x,y offset to shift plot legend
641
+ "TxtPad=10 10" - padding between text and x,y scale
642
+ "LineSpacing=20" - spacing between text lines
643
+ "Stroke=1" - plot stroke width and marker-size scaling factor
644
+ Title, XLabel, YLabel - plot title and x/y axis labels (no default)
645
+ XMin, XMax - x axis minimum/maximum (autoscaling if not set)
646
+ YMin, YMax - y axis minimum/maximum
647
+ Multi - flag to draw multiple plots, one for each dataset
648
+ Split - flag to split strings of numbers into lists
649
+ (> 1 to split into lists of N items)
650
+ "Grid=darkgray" - grid color
651
+ "Text=black" - color of text and plot border
652
+ "Bkg=" - background color (default is transparent)
653
+ "Cols=red green blue black orange gray fuchsia brown turquoise gold"
654
+ - colors for plot data
655
+ "Marks=circle square triangle diamond star plus pentagon left down right"
656
+ - marker-shape names for each dataset
657
+
658
+ =back
480
659
 
481
- This module contains definitions required by Image::ExifTool to plot tag
482
- values in SVG format.
660
+ =head2 AddPoints
661
+
662
+ Add points to be plotted.
663
+
664
+ =over 4
665
+
666
+ =item Inputs:
667
+
668
+ 0) Plot object reference
669
+
670
+ 1) Tag information hash reference from ExifTool
671
+
672
+ 2) List of tag keys to plot
673
+
674
+ =back
675
+
676
+ =head2 Draw
677
+
678
+ Draw the SVG plot to the specified output file.
679
+
680
+ =over 4
681
+
682
+ =item Inputs:
683
+
684
+ 0) Plot object reference
685
+
686
+ 1) Output file reference
687
+
688
+ =item Notes:
689
+
690
+ On return, the Plot Error and Warn members contain error or warning strings
691
+ if there were any problems. If an Error is set, then the output SVG is
692
+ invalid.
693
+
694
+ =back
483
695
 
484
696
  =head1 AUTHOR
485
697
 
@@ -488,5 +700,13 @@ Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
488
700
  This library is free software; you can redistribute it and/or modify it
489
701
  under the same terms as Perl itself.
490
702
 
703
+ =head1 SEE ALSO
704
+
705
+ =over 4
706
+
707
+ =item L<https://exiftool.org/plot.html>
708
+
709
+ =back
710
+
491
711
  =cut
492
712
 
@@ -30,7 +30,7 @@ use strict;
30
30
  use vars qw($VERSION $AUTOLOAD);
31
31
  use Image::ExifTool qw(:DataAccess :Utils);
32
32
 
33
- $VERSION = '1.71';
33
+ $VERSION = '1.70';
34
34
 
35
35
  sub ConvertTimecode($);
36
36
  sub ProcessSGLT($$$);
@@ -1319,9 +1319,9 @@ my %code2charset = (
1319
1319
  Name => 'ImageWidth',
1320
1320
  Format => 'int16u',
1321
1321
  Priority => 0,
1322
- # add " Lossless" to FileType since image has a VP8L (lossless) chunk
1322
+ # add " (lossless)" to FileType since image has a VP8L (lossless) chunk
1323
1323
  RawConv => q{
1324
- $self->OverrideFileType($$self{VALUE}{FileType} . ' Lossless', undef, 'webp');
1324
+ $self->OverrideFileType($$self{VALUE}{FileType} . ' (lossless)', undef, 'webp');
1325
1325
  return $val;
1326
1326
  },
1327
1327
  ValueConv => '($val & 0x3fff) + 1',
@@ -12,7 +12,7 @@ meta information extracted from or written to a file.
12
12
  =head1 TAG TABLES
13
13
 
14
14
  The tables listed below give the names of all tags recognized by ExifTool.
15
- They contain a total of 28526 tags, with 17595 unique tag names.
15
+ They contain a total of 28534 tags, with 17595 unique tag names.
16
16
 
17
17
  B<Tag ID>, B<Index#> or B<Sequence> is given in the first column of each
18
18
  table. A B<Tag ID> is the computer-readable equivalent of a tag name, and
@@ -11561,6 +11561,9 @@ for a given protocol should be considered to have the default value of 0.
11561
11561
  'dvtm_ac203_3-2-2-1' ISO no
11562
11562
  'dvtm_ac203_3-2-4-1' ShutterSpeed no
11563
11563
  'dvtm_ac203_3-2-6-1' ColorTemperature no
11564
+ 'dvtm_ac203_3-2-10-2' AccelerometerX no
11565
+ 'dvtm_ac203_3-2-10-3' AccelerometerY no
11566
+ 'dvtm_ac203_3-2-10-4' AccelerometerZ no
11564
11567
  'dvtm_ac203_3-4-2-1' GPSInfo DJI GPSInfo
11565
11568
  'dvtm_ac203_3-4-2-2' GPSAltitude no
11566
11569
  'dvtm_ac203_3-4-2-6-1' GPSDateTime no
@@ -11569,6 +11572,9 @@ for a given protocol should be considered to have the default value of 0.
11569
11572
  'dvtm_ac204_2-3' FrameInfo DJI FrameInfo
11570
11573
  'dvtm_ac204_3-2-4-1' ShutterSpeed no
11571
11574
  'dvtm_ac204_3-2-6-1' ColorTemperature no
11575
+ 'dvtm_ac204_3-2-10-2' AccelerometerX no
11576
+ 'dvtm_ac204_3-2-10-3' AccelerometerY no
11577
+ 'dvtm_ac204_3-2-10-4' AccelerometerZ no
11572
11578
  'dvtm_ac204_3-4-2-1' GPSInfo DJI GPSInfo
11573
11579
  'dvtm_ac204_3-4-2-2' GPSAltitude no
11574
11580
  'dvtm_ac204_3-4-2-6-1' GPSDateTime no
@@ -11577,6 +11583,7 @@ for a given protocol should be considered to have the default value of 0.
11577
11583
  'dvtm_pm320_2-2' FrameInfo DJI FrameInfo
11578
11584
  'dvtm_pm320_3-2-2-1' ISO no
11579
11585
  'dvtm_pm320_3-2-3-1' ShutterSpeed no
11586
+ 'dvtm_pm320_3-2-4-1' FNumber no
11580
11587
  'dvtm_pm320_3-2-6-1' DigitalZoom no
11581
11588
  'dvtm_pm320_3-3-3' DroneInfo DJI DroneInfo
11582
11589
  'dvtm_pm320_3-3-4-1' GPSInfo DJI GPSInfo
@@ -28670,6 +28677,7 @@ check if speed is more of a concern.
28670
28677
  'gIFg' GIFGraphicControlExtension no
28671
28678
  'gIFt' GIFPlainTextExtension no
28672
28679
  'gIFx' GIFApplicationExtension no
28680
+ 'gdAT' GainMapImage no
28673
28681
  'hIST' PaletteHistogram no
28674
28682
  'iCCP' ICC_Profile ICC_Profile
28675
28683
  'iCCP-name' ProfileName yes
@@ -12,7 +12,7 @@ use strict;
12
12
  use vars qw($VERSION);
13
13
  use Image::ExifTool qw(:DataAccess :Utils);
14
14
 
15
- $VERSION = '1.00';
15
+ $VERSION = '1.01';
16
16
 
17
17
  %Image::ExifTool::Trailer::Vivo = (
18
18
  GROUPS => { 0 => 'Trailer', 1 => 'Vivo', 2 => 'Image' },
@@ -271,7 +271,7 @@ sub ProcessGoogle($$)
271
271
  DataPos => $start + $pos,
272
272
  DirLen => $$len[$i],
273
273
  });
274
- $et->HandleTag($tagTable, $$tag[$i], \$buff, { DataPos => $start + $pos, DataPt => \$buff });
274
+ $et->HandleTag($tagTable, $$tag[$i], \$buff, DataPos => $start + $pos, DataPt => \$buff);
275
275
  # (haven't seen non-zero padding, but I assume this is how it works
276
276
  $pos += $$len[$i] + (($pad and $$pad[$i]) ? $$pad[$i] : 0);
277
277
  }
@@ -2269,6 +2269,11 @@ NoOverwrite: next if $isNew > 0;
2269
2269
  # build list of offsets to process
2270
2270
  my @offsetList;
2271
2271
  if ($ifd >= 0) {
2272
+ $dirName = $$dirInfo{DirName} || 'unknown';
2273
+ if ($ifd) {
2274
+ $dirName =~ s/\d+$//;
2275
+ $dirName .= $ifd;
2276
+ }
2272
2277
  my $offsetInfo = $offsetInfo[$ifd] or next;
2273
2278
  if ($$offsetInfo{0x111} and $$offsetInfo{0x144}) {
2274
2279
  # SubIFD may contain double-referenced data as both strips and tiles
@@ -29,7 +29,7 @@ use vars qw($VERSION $RELEASE @ISA @EXPORT_OK %EXPORT_TAGS $AUTOLOAD @fileTypes
29
29
  %jpegMarker %specialTags %fileTypeLookup $testLen $exeDir
30
30
  %static_vars $advFmtSelf);
31
31
 
32
- $VERSION = '13.21';
32
+ $VERSION = '13.22';
33
33
  $RELEASE = '';
34
34
  @ISA = qw(Exporter);
35
35
  %EXPORT_TAGS = (
@@ -1009,16 +1009,20 @@ values are:
1009
1009
  "Legend=0 0" - x,y offset to shift plot legend
1010
1010
  "TxtPad=10 10" - padding between text and x,y scale
1011
1011
  "LineSpacing=20" - spacing between text lines
1012
+ "Stroke=1" - plot stroke width and marker-size scaling
1012
1013
  Title, XLabel, YLabel - plot title and x/y axis labels (no default)
1013
1014
  XMin, XMax - x axis minimum/maximum (autoscaling if not set)
1014
1015
  YMin, YMax - y axis minimum/maximum
1016
+ Multi - flag to draw multiple plots, one for each dataset
1015
1017
  Split - flag to split strings of numbers into lists
1016
1018
  (> 1 to split into lists of N items)
1017
1019
  "Grid=darkgray" - grid color
1018
1020
  "Text=black" - color of text and plot border
1019
1021
  "Bkg=" - background color (default is transparent)
1020
- "Cols=red green blue black orange gray purple cyan brown pink"
1021
- - colors for plot data
1022
+ "Cols=red green blue black orange gray fuchsia brown turquoise gold"
1023
+ - colors for plot data
1024
+ "Marks=circle square triangle diamond star plus pentagon left down right"
1025
+ - marker-shape names for each dataset
1022
1026
 
1023
1027
  =item PrintConv
1024
1028
 
@@ -1,6 +1,6 @@
1
1
  Summary: perl module for image data extraction
2
2
  Name: perl-Image-ExifTool
3
- Version: 13.21
3
+ Version: 13.22
4
4
  Release: 1
5
5
  License: Artistic/GPL
6
6
  Group: Development/Libraries/Perl
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExiftoolVendored
4
- VERSION = Gem::Version.new('13.21.0')
4
+ VERSION = Gem::Version.new('13.22.0')
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exiftool_vendored
3
3
  version: !ruby/object:Gem::Version
4
- version: 13.21.0
4
+ version: 13.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-03-01 00:00:00.000000000 Z
12
+ date: 2025-03-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: exiftool