exiftool_vendored 13.18.0 → 13.21.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 +4 -4
- data/bin/Changes +40 -3
- data/bin/MANIFEST +7 -0
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +2 -2
- data/bin/exiftool +138 -64
- data/bin/lib/Image/ExifTool/DJI.pm +179 -29
- data/bin/lib/Image/ExifTool/EXE.pm +2 -9
- data/bin/lib/Image/ExifTool/GoPro.pm +28 -0
- data/bin/lib/Image/ExifTool/ICO.pm +2 -2
- data/bin/lib/Image/ExifTool/JSON.pm +5 -1
- data/bin/lib/Image/ExifTool/Kodak.pm +3 -2
- data/bin/lib/Image/ExifTool/Nikon.pm +808 -524
- data/bin/lib/Image/ExifTool/Olympus.pm +2 -1
- data/bin/lib/Image/ExifTool/PDF.pm +10 -1
- data/bin/lib/Image/ExifTool/PLIST.pm +91 -28
- data/bin/lib/Image/ExifTool/Plot.pm +492 -0
- data/bin/lib/Image/ExifTool/Protobuf.pm +22 -11
- data/bin/lib/Image/ExifTool/QuickTime.pm +58 -41
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +75 -27
- data/bin/lib/Image/ExifTool/RIFF.pm +3 -3
- data/bin/lib/Image/ExifTool/Sony.pm +16 -10
- data/bin/lib/Image/ExifTool/TagLookup.pm +5002 -4964
- data/bin/lib/Image/ExifTool/TagNames.pod +259 -112
- data/bin/lib/Image/ExifTool/WritePDF.pl +1 -1
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +45 -2
- data/bin/lib/Image/ExifTool/Writer.pl +0 -1
- data/bin/lib/Image/ExifTool/XMP.pm +18 -4
- data/bin/lib/Image/ExifTool.pm +20 -6
- data/bin/lib/Image/ExifTool.pod +27 -0
- data/bin/perl-Image-ExifTool.spec +1 -1
- data/lib/exiftool_vendored/version.rb +1 -1
- metadata +3 -2
@@ -0,0 +1,492 @@
|
|
1
|
+
#------------------------------------------------------------------------------
|
2
|
+
# File: Plot.pm
|
3
|
+
#
|
4
|
+
# Description: Plot tag values in SVG format
|
5
|
+
#
|
6
|
+
# Revisions: 2025-02-14 - P. Harvey Created
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
|
9
|
+
package Image::ExifTool::Plot;
|
10
|
+
|
11
|
+
use strict;
|
12
|
+
use vars qw($VERSION);
|
13
|
+
|
14
|
+
$VERSION = '1.01';
|
15
|
+
|
16
|
+
# default plot settings (lower-case settings may be overridden by the user)
|
17
|
+
my %defaults = (
|
18
|
+
size => [ 800, 600 ], # width,height of output image
|
19
|
+
margin => [ 60, 15, 15, 30 ], # left,top,right,bottom margins around plot area
|
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
|
29
|
+
type => 'line', # plot type, 'line' or 'scatter'
|
30
|
+
style => 'line', # 'line', 'marker' or 'line+marker'
|
31
|
+
xlabel => '', # x axis label
|
32
|
+
ylabel => '', # y axis label
|
33
|
+
title => '', # plot title
|
34
|
+
nbins => 20, # number of histogram bins
|
35
|
+
# xmin, xmax # x axis minimum,maximum
|
36
|
+
# ymin, ymax # y axis minimum,maximum
|
37
|
+
# split # split list of numbers into separate plot lines
|
38
|
+
# bkg # background color
|
39
|
+
Data => { }, # data arrays for each variable
|
40
|
+
Name => [ ], # variable names
|
41
|
+
XMax => 0, # number of points in plot so far
|
42
|
+
);
|
43
|
+
|
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"/>',
|
53
|
+
);
|
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);
|
56
|
+
|
57
|
+
#------------------------------------------------------------------------------
|
58
|
+
# Create new plot object
|
59
|
+
sub new
|
60
|
+
{
|
61
|
+
my $that = shift;
|
62
|
+
my $class = ref($that) || $that || 'Image::ExifTool::Plot';
|
63
|
+
my $self = bless { }, $class;
|
64
|
+
foreach (keys %defaults) {
|
65
|
+
ref $defaults{$_} eq 'HASH' and $$self{$_} = { %{$defaults{$_}} }, next;
|
66
|
+
ref $defaults{$_} eq 'ARRAY' and $$self{$_} = [ @{$defaults{$_}} ], next;
|
67
|
+
$$self{$_} = $defaults{$_};
|
68
|
+
}
|
69
|
+
return $self;
|
70
|
+
}
|
71
|
+
|
72
|
+
#------------------------------------------------------------------------------
|
73
|
+
# Set plot settings
|
74
|
+
# Inputs: 0) Plot ref, 1) comma-separated options
|
75
|
+
sub Settings($$)
|
76
|
+
{
|
77
|
+
my ($self, $set) = @_;
|
78
|
+
return unless $set;
|
79
|
+
foreach (split /,\s*/, $set) {
|
80
|
+
next unless /^([a-z].*?)(=(.*))?$/i;
|
81
|
+
my ($name, $val) = ($1, $3);
|
82
|
+
if (ref $$self{$name} eq 'ARRAY') {
|
83
|
+
next unless defined $val;
|
84
|
+
$$self{lc $name} = [ split /[\s\/]+/, $val ]; # split on space or slash
|
85
|
+
} else {
|
86
|
+
$val = 1 unless defined $val; # default to 1 if no "="
|
87
|
+
my %charName = ('&'=>'amp', '<'=>'lt', '>'=>'gt');
|
88
|
+
# escape necessary XML characters, but allow numerical entities
|
89
|
+
$val =~ s/([&><])/&$charName{$1};/sg and $val =~ s/&(#(\d+|x[0-9a-fA-F]+);)/&$1/;
|
90
|
+
undef $val unless length $val;
|
91
|
+
$$self{lc $name} = $val;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
#------------------------------------------------------------------------------
|
97
|
+
# Add points to SVG plot
|
98
|
+
# Inputs: 0) Plot object ref, 1) tag value hash ref, 2) tag ID list ref
|
99
|
+
sub AddPoints($$$)
|
100
|
+
{
|
101
|
+
my ($self, $info, $tags) = @_;
|
102
|
+
my ($tag, $name, %num, $index, $mod, $val, @vals);
|
103
|
+
my ($ee, $docNum, $data, $xmin, $xmax) = @$self{qw(EE DocNum Data XMin XMax)};
|
104
|
+
$$self{type} or $$self{type} = 'line';
|
105
|
+
my $scat = $$self{type} =~ /^s/i;
|
106
|
+
my $xname = $$self{Name}[0]; # (x-axis name if using scatter plot)
|
107
|
+
my $maxLines = $$self{type} =~ /^h/i ? 1 : 20;
|
108
|
+
for (;;) {
|
109
|
+
if (@vals) {
|
110
|
+
$val = shift @vals;
|
111
|
+
} else {
|
112
|
+
$tag = shift @$tags or last;
|
113
|
+
# ignore non-floating-point values
|
114
|
+
$val = $$info{$tag};
|
115
|
+
($name) = $tag =~ /^(\S+)/g; # remove index number
|
116
|
+
if (ref $val) {
|
117
|
+
if (ref $val eq 'ARRAY') {
|
118
|
+
$index = defined $index ? $index + 1 : 0;
|
119
|
+
$val = $$val[$index];
|
120
|
+
defined $val or undef($index), undef($mod), next;
|
121
|
+
$name .= $mod ? '['.($index % $mod).']' : "[$index]";
|
122
|
+
unshift @$tags, $tag; # will continue with this tag later
|
123
|
+
} elsif (ref $val eq 'SCALAR') {
|
124
|
+
$val = $$val; # handle binary values
|
125
|
+
} else {
|
126
|
+
next;
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
next unless defined $val and $val =~ /^[+-]?(?=\.?\d)\d*\.?\d*(?:e[+-]?\d+)?([ ,;\t\n\r]?|\z)/i;
|
131
|
+
if ($1) {
|
132
|
+
# split a string of numbers into separate plot points (eg. histogram tags)
|
133
|
+
if ($$self{'split'}) {
|
134
|
+
# make an array
|
135
|
+
$$info{$tag} = [ split /[ ,;\t\n\r][\n\r]? */, $val ];
|
136
|
+
unshift @$tags, $tag;
|
137
|
+
# split into lists of 'split' elements if split > 1
|
138
|
+
$mod = $$self{'split'} if $$self{'split'} > 1;
|
139
|
+
next;
|
140
|
+
} else {
|
141
|
+
@vals = split /[ ,;\t\n\r][\n\r]? */, $val;
|
142
|
+
$val = shift @vals;
|
143
|
+
}
|
144
|
+
}
|
145
|
+
my $docNum = $docNum ? $$docNum{$tag} || 0 : 0;
|
146
|
+
next if $docNum and not $ee;
|
147
|
+
unless ($$data{$name}) {
|
148
|
+
if (@{$$self{Name}} >= $maxLines) {
|
149
|
+
unless ($$self{MaxTags}) {
|
150
|
+
if ($$self{type} =~ /^h/i) {
|
151
|
+
$$self{Warn} = 'A histogram can only plot one variable';
|
152
|
+
} else {
|
153
|
+
$$self{Warn} = 'Too many variables to plot all of them';
|
154
|
+
}
|
155
|
+
$$self{MaxTags} = 1;
|
156
|
+
}
|
157
|
+
next;
|
158
|
+
}
|
159
|
+
push @{$$self{Name}}, $name;
|
160
|
+
$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;
|
164
|
+
}
|
165
|
+
$num{$name} = $xmax;
|
166
|
+
$$data{$name}[$xmax - $xmin] = $val if $xmax >= $xmin;
|
167
|
+
next;
|
168
|
+
}
|
169
|
+
if ($docNum and $num{$name} < $docNum) {
|
170
|
+
$num{$name} = $docNum; # keep documents synchronized if some tags are missing
|
171
|
+
} else {
|
172
|
+
$num{$name} = $xmax unless defined $num{$name};
|
173
|
+
++$num{$name};
|
174
|
+
}
|
175
|
+
$$data{$name}[$num{$name} - $xmin] = $val if $num{$name} >= $xmin;
|
176
|
+
unless ($scat and $name eq $xname) {
|
177
|
+
$$self{Max} = $val if $val > $$self{Max};
|
178
|
+
$$self{Min} = $val if $val < $$self{Min};
|
179
|
+
}
|
180
|
+
}
|
181
|
+
# start next file at x value so far
|
182
|
+
$xmax < $num{$_} and $xmax = $num{$_} foreach keys %num;
|
183
|
+
$$self{XMin} = $xmin;
|
184
|
+
$$self{XMax} = $xmax;
|
185
|
+
}
|
186
|
+
|
187
|
+
#------------------------------------------------------------------------------
|
188
|
+
# Calculate a nice round number for grid spacing
|
189
|
+
# Inputs: 0) nominal spacing (must be positive)
|
190
|
+
# Returns: spacing rounded to an even number
|
191
|
+
sub GetGridSpacing($)
|
192
|
+
{
|
193
|
+
my $nom = shift;
|
194
|
+
my $rounded;
|
195
|
+
my $div = sprintf('%.3e', $nom);
|
196
|
+
my $num = substr($div, 0, 1);
|
197
|
+
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;
|
200
|
+
return $exp >= 0 ? $num . ('0' x $exp) : '.' . ('0' x (-$exp - 1)) . $num;
|
201
|
+
}
|
202
|
+
|
203
|
+
#------------------------------------------------------------------------------
|
204
|
+
# Draw SVG plot
|
205
|
+
# Inputs: 0) Plot ref, 1) Output file reference
|
206
|
+
sub Draw($$)
|
207
|
+
{
|
208
|
+
my ($self, $fp) = @_;
|
209
|
+
my ($min, $max, $xmin, $xmax, $name, $style) = @$self{qw(Min Max XMin XMax Name style)};
|
210
|
+
|
211
|
+
if (not defined $min or not defined $xmin or not $style) {
|
212
|
+
$$self{Error} = 'Nothing to plot';
|
213
|
+
return;
|
214
|
+
}
|
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;
|
220
|
+
my @name = @$name;
|
221
|
+
my @margin = ( @{$$self{margin}} );
|
222
|
+
|
223
|
+
# set reasonable default titles and labels
|
224
|
+
$xname = shift @name if $scat;
|
225
|
+
$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;
|
228
|
+
|
229
|
+
# 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;
|
233
|
+
|
234
|
+
if ($scat) {
|
235
|
+
$xdat = $$self{Data}{$xname};
|
236
|
+
unless (defined $$self{xmin} and defined $$self{xmax}) {
|
237
|
+
my $set;
|
238
|
+
foreach (@$xdat) {
|
239
|
+
next unless defined;
|
240
|
+
$set or $xmin = $xmax = $_, $set = 1, next;
|
241
|
+
$xmin = $_ if $xmin > $_;
|
242
|
+
$xmax = $_ if $xmax < $_;
|
243
|
+
}
|
244
|
+
my $dnx2 = ($xmax - $xmin) / ($nx * 2);
|
245
|
+
# leave a bit of a left/right margin, but don't pass 0
|
246
|
+
$xmin = ($xmin >= 0 and $xmin < $dnx2) ? 0 : $xmin - $dnx2;
|
247
|
+
$xmax = ($xmax <= 0 and -$xmax < $dnx2) ? 0 : $xmax + $dnx2;
|
248
|
+
}
|
249
|
+
$xmin = $$self{xmin} if defined $$self{xmin};
|
250
|
+
$xmax = $$self{xmax} if defined $$self{xmax};
|
251
|
+
} else {
|
252
|
+
# shift x range to correspond with index in data list
|
253
|
+
$xmax -= $xmin;
|
254
|
+
$xmin = 0;
|
255
|
+
}
|
256
|
+
if ($hist) {
|
257
|
+
$$self{nbins} > 0 or $$self{Error} = 'Invalid number of histogram bins', return;
|
258
|
+
$noLegend = 1;
|
259
|
+
# y axis becomes histogram x axis after binning
|
260
|
+
$min = $$self{xmin} if defined $$self{xmin};
|
261
|
+
$max = $$self{xmax} if defined $$self{xmax};
|
262
|
+
} else {
|
263
|
+
# leave a bit of a margin above/below data when autoscaling but don't pass 0
|
264
|
+
my $dny2 = ($max - $min) / ($ny * 2);
|
265
|
+
$min = ($min >= 0 and $min < $dny2) ? 0 : $min - $dny2;
|
266
|
+
$max = ($max <= 0 and -$max < $dny2) ? 0 : $max + $dny2;
|
267
|
+
# adjust to user-defined range if specified
|
268
|
+
$min = $$self{ymin} if defined $$self{ymin};
|
269
|
+
$max = $$self{ymax} if defined $$self{ymax};
|
270
|
+
}
|
271
|
+
# generate random colors if we need more
|
272
|
+
while (@$cols < @$name) {#138
|
273
|
+
$$self{seeded} or srand(141), $$self{seeded} = 1;
|
274
|
+
push @$cols, sprintf("#%.2x%.2x%.2x",int(rand(220)),int(rand(220)),int(rand(220)));
|
275
|
+
}
|
276
|
+
$diff = $max - $min || 1;
|
277
|
+
$xdiff = $xmax - $xmin || 1;
|
278
|
+
|
279
|
+
# determine y grid spacing (nice even numbers)
|
280
|
+
$dy = GetGridSpacing($diff / ($hist ? $$self{nbins} : $ny));
|
281
|
+
# expand plot min/max to the nearest even multiple of our grid spacing
|
282
|
+
$min = ($min > 0 ? int($min/$dy) : int($min/$dy-0.9999)) * $dy;
|
283
|
+
$max = ($max > 0 ? int($max/$dy+0.9999) : int($max/$dy)) * $dy;
|
284
|
+
|
285
|
+
# bin histogram
|
286
|
+
if ($hist) {
|
287
|
+
my $dat = $$data{$name[0]};
|
288
|
+
my $nmax = int(($max - $min) / $dy + 0.5);
|
289
|
+
@$hist = (0) x $nmax;
|
290
|
+
foreach (@$dat) {
|
291
|
+
next unless defined;
|
292
|
+
$n = ($_ - $min) / $dy;
|
293
|
+
next if $n < 0 or $n > $nmax + 0.00001;
|
294
|
+
$n = int($n);
|
295
|
+
++$$hist[$n < $nmax ? $n : $nmax - 1];
|
296
|
+
}
|
297
|
+
($xmin, $xmax, $min, $max) = ($min, $max, 0, 0);
|
298
|
+
if ($$self{ymax}) {
|
299
|
+
$max = $$self{ymax};
|
300
|
+
} else {
|
301
|
+
$max < $_ and $max = $_ foreach @$hist; # find max count
|
302
|
+
}
|
303
|
+
$diff = $max - $min || 1;
|
304
|
+
$dx = $dy;
|
305
|
+
$dy = GetGridSpacing($diff / $ny);
|
306
|
+
$max = ($max > 0 ? int($max/$dy+0.9999) : int($max/$dy)) * $dy;
|
307
|
+
$$data{$name[0]} = $hist;
|
308
|
+
} else {
|
309
|
+
$dx = GetGridSpacing($xdiff / $nx);
|
310
|
+
}
|
311
|
+
if ($scat) {
|
312
|
+
$xmin = ($xmin > 0 ? int($xmin/$dx) : int($xmin/$dx-0.9999)) * $dx;
|
313
|
+
$xmax = ($xmax > 0 ? int($xmax/$dx+0.9999) : int($xmax/$dx)) * $dx;
|
314
|
+
}
|
315
|
+
$diff = $max - $min || 1;
|
316
|
+
$xdiff = $xmax - $xmin || 1;
|
317
|
+
# 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];
|
320
|
+
my $yscl = $height / $diff;
|
321
|
+
my $xscl = $width / $xdiff;
|
322
|
+
my $px0 = $margin[0] - $xmin * $xscl;
|
323
|
+
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};
|
331
|
+
print $fp "\n<!-- X axis -->";
|
332
|
+
print $fp "\n<g dominant-baseline='hanging' text-anchor='middle'>";
|
333
|
+
$py = int(($margin[1] + $height + $$self{txtPad}[1]) * 10 + 0.5) / 10;
|
334
|
+
$px = int(($margin[0] + $width / 2) * 10 + 0.5) / 10;
|
335
|
+
if ($title) {
|
336
|
+
print $fp "\n<text x='${px}' y='14' font-size='150%'>$title</text>";
|
337
|
+
}
|
338
|
+
if ($xlabel) {
|
339
|
+
$y = $py + $$self{lineSpacing};
|
340
|
+
print $fp "\n<text x='${px}' y='${y}'>$xlabel</text>";
|
341
|
+
}
|
342
|
+
if ($ylabel) {
|
343
|
+
$y = $margin[1] + $height / 2;
|
344
|
+
print $fp "\n<text x='10' y='${y}' transform='rotate(-90,10,$y)'>$ylabel</text>";
|
345
|
+
}
|
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;
|
351
|
+
}
|
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);
|
355
|
+
for ($x=int($xmin/$dx-1)*$dx; ; $x+=$dx) {
|
356
|
+
$px = int(($margin[0] + ($x - $xmin) * $width / $xdiff) * 10 + 0.5) / 10;
|
357
|
+
next if $px < $margin[0] - 0.5;
|
358
|
+
last if $px > $margin[0] + $width + 0.5;
|
359
|
+
if (not $dx2 or $x/$dx2 - int($x/$dx2) < 0.1) {
|
360
|
+
printf $fp "\n<text x='${px}' y='${py}'>%g</text>", $x;
|
361
|
+
}
|
362
|
+
length($grid) - $lastLen > 80 and $grid .= "\n", $lastLen = length($grid);
|
363
|
+
$grid .= sprintf("M$px $margin[1] v$height ");
|
364
|
+
}
|
365
|
+
print $fp "\n<path stroke='$$self{grid}' stroke-width='0.5' d='\n${grid}'/>";
|
366
|
+
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;
|
368
|
+
($grid, $lastLen) = ('', 0);
|
369
|
+
for ($y=$min; ; $y+=$dy) {
|
370
|
+
$py = int(($margin[1] + $height - ($y - $min) * $yscl) * 10 + 0.5) / 10;
|
371
|
+
last if $py < $margin[1] - 0.5;
|
372
|
+
$y = 0 if $y < $dy/2 and $y > -$dy/2; # (avoid round-off errors)
|
373
|
+
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
|
375
|
+
length($grid) - $lastLen > 80 and $grid .= "\n", $lastLen = length($grid);
|
376
|
+
$grid .= "M$margin[0] $py h$width ";
|
377
|
+
}
|
378
|
+
if ($xAxis and $min!=0) {
|
379
|
+
$py = $margin[1] + $height + $min * $yscl;
|
380
|
+
print $fp "\n<path stroke='$$self{text}' d='M$margin[0] $py h${width}'/>";
|
381
|
+
}
|
382
|
+
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'>";
|
384
|
+
print $fp "\n<path stroke='$$self{text}' fill='none' d='M$margin[0] $margin[1] l0 $height $width 0 0 -$height z'/>";
|
385
|
+
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);
|
389
|
+
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}'/>";
|
397
|
+
print $fp "\n<text x='${x}' y='${y}'>$name[$i]</text>";
|
398
|
+
}
|
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'>";
|
419
|
+
foreach (0..$#name) {
|
420
|
+
$col{$name[$_]} = $$cols[$_];
|
421
|
+
$class{$name[$_]} = $style =~ /\b[mp]/i ? ' class="' . $markerName[$_ % @markerName] . '"' : '';
|
422
|
+
}
|
423
|
+
my ($i0, $i1, $xsclr);
|
424
|
+
my $fill = '';
|
425
|
+
if ($scat) {
|
426
|
+
($i0, $i1) = (0, $#$xdat);
|
427
|
+
} elsif ($hist) {
|
428
|
+
($i0, $i1) = (0, $#$hist);
|
429
|
+
$xscl = $width / @$hist;
|
430
|
+
$px0 = $margin[0];
|
431
|
+
$xsclr = int($xscl * 100 + 0.5) / 100;
|
432
|
+
$fill = qq( fill="$$cols[0]" style="fill-opacity: .20") if $$self{style} =~ /\bf/i;
|
433
|
+
} else {
|
434
|
+
$i0 = int($xmin) - 1;
|
435
|
+
$i0 = 0 if $i0 < 0;
|
436
|
+
$i1 = int($xmax) + 1;
|
437
|
+
}
|
438
|
+
foreach (@name) {
|
439
|
+
my $dat = $$data{$_};
|
440
|
+
my $doLines = $style =~ /\bl/i;
|
441
|
+
print $fp "\n<!-- $_ -->";
|
442
|
+
print $fp "\n<path$class{$_}$fill stroke='$col{$_}' d='";
|
443
|
+
print $fp 'M' if $doLines;
|
444
|
+
my $m = $doLines ? '' : ' M';
|
445
|
+
for ($i=$i0; $i<=$i1; ++$i) {
|
446
|
+
next unless defined $$dat[$i];
|
447
|
+
$y = int(($py0 - $$dat[$i] * $yscl) * 10 + 0.5) / 10;
|
448
|
+
if ($scat) {
|
449
|
+
next unless defined $$xdat[$i];
|
450
|
+
$x = int(($px0 + $$xdat[$i] * $xscl) * 10 + 0.5) / 10;
|
451
|
+
} else {
|
452
|
+
$x = int(($px0 + $i * $xscl) * 10 + 0.5) / 10;
|
453
|
+
if ($hist) {
|
454
|
+
print $fp $m, ($i % 5 ? ' ' : "\n"), "$x $y h$xsclr";
|
455
|
+
$m = ' L'; # (draw lines after the first point)
|
456
|
+
next;
|
457
|
+
}
|
458
|
+
}
|
459
|
+
print $fp $m, ($i % 20 ? ' ' : "\n"), "$x $y";
|
460
|
+
}
|
461
|
+
print $fp ' V', $margin[1]+$height, " H$margin[0] z" if $hist and $fill;
|
462
|
+
print $fp "'/>";
|
463
|
+
}
|
464
|
+
print $fp "\n</g></svg>\n";
|
465
|
+
}
|
466
|
+
|
467
|
+
1; # end
|
468
|
+
|
469
|
+
__END__
|
470
|
+
|
471
|
+
=head1 NAME
|
472
|
+
|
473
|
+
Image::ExifTool::Plot - Plot tag values in SVG format
|
474
|
+
|
475
|
+
=head1 SYNOPSIS
|
476
|
+
|
477
|
+
This module is used by Image::ExifTool
|
478
|
+
|
479
|
+
=head1 DESCRIPTION
|
480
|
+
|
481
|
+
This module contains definitions required by Image::ExifTool to plot tag
|
482
|
+
values in SVG format.
|
483
|
+
|
484
|
+
=head1 AUTHOR
|
485
|
+
|
486
|
+
Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
|
487
|
+
|
488
|
+
This library is free software; you can redistribute it and/or modify it
|
489
|
+
under the same terms as Perl itself.
|
490
|
+
|
491
|
+
=cut
|
492
|
+
|
@@ -18,7 +18,7 @@ use strict;
|
|
18
18
|
use vars qw($VERSION);
|
19
19
|
use Image::ExifTool qw(:DataAccess :Utils);
|
20
20
|
|
21
|
-
$VERSION = '1.
|
21
|
+
$VERSION = '1.03';
|
22
22
|
|
23
23
|
sub ProcessProtobuf($$$;$);
|
24
24
|
|
@@ -174,11 +174,11 @@ sub ProcessProtobuf($$$;$)
|
|
174
174
|
my $dir = { DataPt => \$buff, Pos => 0 };
|
175
175
|
my $num = VarInt($dir);
|
176
176
|
my $den = VarInt($dir);
|
177
|
-
$val = (defined $num and $den) ?
|
177
|
+
$val = (defined $num and $den) ? $num/$den : 'err';
|
178
178
|
} else {
|
179
179
|
$val = ReadValue(\$buff, 0, $$tagInfo{Format}, undef, length($buff));
|
180
180
|
}
|
181
|
-
} elsif ($type == 0) {
|
181
|
+
} elsif ($type == 0) { # varInt
|
182
182
|
$val = $buff;
|
183
183
|
my $hex = sprintf('%x', $val);
|
184
184
|
if (length($hex) == 16 and $hex =~ /^ffffffff/) {
|
@@ -188,9 +188,9 @@ sub ProcessProtobuf($$$;$)
|
|
188
188
|
my $signed = ($val & 1) ? -($val >> 1)-1 : ($val >> 1);
|
189
189
|
$val .= " (0x$hex, signed $signed)";
|
190
190
|
}
|
191
|
-
} elsif ($type == 1) {
|
191
|
+
} elsif ($type == 1) { # 64-bit number
|
192
192
|
$val = '0x' . unpack('H*', $buff) . ' (double ' . GetDouble(\$buff,0) . ')';
|
193
|
-
} elsif ($type == 2) {
|
193
|
+
} elsif ($type == 2) { # string, bytes or protobuf
|
194
194
|
if ($$tagInfo{SubDirectory}) {
|
195
195
|
# (fall through to process known SubDirectory)
|
196
196
|
} elsif ($$tagInfo{IsProtobuf}) {
|
@@ -203,14 +203,25 @@ sub ProcessProtobuf($$$;$)
|
|
203
203
|
ProcessProtobuf($et, \%subdir, $tagTbl, "$prefix$id-");
|
204
204
|
$$et{INDENT} = substr($$et{INDENT}, 0, -2);
|
205
205
|
next;
|
206
|
-
} elsif ($buff !~ /[^\x20-\x7e]/) {
|
207
|
-
$val = $buff; # assume this is an ASCII string
|
208
|
-
} elsif (length($buff) % 4) {
|
209
|
-
$val = '0x' . unpack('H*', $buff);
|
210
206
|
} else {
|
211
|
-
|
207
|
+
# check for rational value (2 varInt values)
|
208
|
+
my $rat;
|
209
|
+
my %dir = ( DataPt => \$buff, Pos => 0 );
|
210
|
+
my $num = VarInt(\%dir);
|
211
|
+
if (defined $num) {
|
212
|
+
my $denom = VarInt(\%dir);
|
213
|
+
$rat = " (rational $num/$denom)" if $denom and $dir{Pos} == length($buff);
|
214
|
+
}
|
215
|
+
if ($buff !~ /[^\x20-\x7e]/) {
|
216
|
+
$val = $buff; # assume this is an ASCII string
|
217
|
+
} elsif (length($buff) % 4) {
|
218
|
+
$val = '0x' . unpack('H*', $buff);
|
219
|
+
} else {
|
220
|
+
$val = '0x' . join(' ', unpack('(H8)*', $buff)); # (group in 4-byte blocks)
|
221
|
+
}
|
222
|
+
$val .= $rat if $rat;
|
212
223
|
}
|
213
|
-
} elsif ($type == 5) {
|
224
|
+
} elsif ($type == 5) { # 32-bit number
|
214
225
|
$val = '0x' . unpack('H*', $buff) . ' (int32u ' . Get32u(\$buff, 0);
|
215
226
|
$val .= ', int32s ' . Get32s(\$buff, 0) if ord(substr($buff,3,1)) & 0x80;
|
216
227
|
$val .= ', float ' . GetFloat(\$buff, 0) . ')';
|