line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# ====================================================================== |
2
|
|
|
|
|
|
|
# Project: Web Counter Parser |
3
|
|
|
|
|
|
|
# Project Leader: Peter Wise |
4
|
|
|
|
|
|
|
# Module component: Parse::WebCounter |
5
|
|
|
|
|
|
|
# ---------------------------------------------------------------------- |
6
|
|
|
|
|
|
|
# Module name: Parse::WebCounter |
7
|
|
|
|
|
|
|
# Module state: First Release |
8
|
|
|
|
|
|
|
# Module notes: Parses Image counters |
9
|
|
|
|
|
|
|
# |
10
|
|
|
|
|
|
|
# Module filename: Parse::WebCounter.pm |
11
|
|
|
|
|
|
|
# ---------------------------------------------------------------------- |
12
|
|
|
|
|
|
|
# Version Author Date Comment |
13
|
|
|
|
|
|
|
# ~~~~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
14
|
|
|
|
|
|
|
# 0.01 P.J.Wise 08/10/2004 Initial Version |
15
|
|
|
|
|
|
|
# 0.02 P.J.Wise 18/12/2006 First Release to CPAN |
16
|
|
|
|
|
|
|
# |
17
|
|
|
|
|
|
|
# ---------------------------------------------------------------------- |
18
|
|
|
|
|
|
|
# CVS: |
19
|
|
|
|
|
|
|
# ID: $Id: WebCounter.pm,v 1.13 2006/12/19 20:42:27 peter Exp $ |
20
|
|
|
|
|
|
|
# |
21
|
|
|
|
|
|
|
# ---------------------------------------------------------------------- |
22
|
|
|
|
|
|
|
# Notes: |
23
|
|
|
|
|
|
|
# ~~~~~~ |
24
|
|
|
|
|
|
|
# Module parses web counter images using GD and supplies the numeric |
25
|
|
|
|
|
|
|
# value represented by the image. Useful if you have a cron keeping |
26
|
|
|
|
|
|
|
# track of the number of hits you are getting per day and you don't |
27
|
|
|
|
|
|
|
# have real logs to go by. You will need copies of the images |
28
|
|
|
|
|
|
|
# representing the individual digits, or a strip of all of them for |
29
|
|
|
|
|
|
|
# it to compare to as the module is not very bright it does a simple |
30
|
|
|
|
|
|
|
# image comparison as apposed to any sophisticated image analysis |
31
|
|
|
|
|
|
|
# (This is not designed, nor intended to be a Captcha solver). |
32
|
|
|
|
|
|
|
# You will need to have GD compiled with support for the image format |
33
|
|
|
|
|
|
|
# that your counters are displayed in. (Usually gif) |
34
|
|
|
|
|
|
|
# ====================================================================== |
35
|
|
|
|
|
|
|
package Parse::WebCounter; |
36
|
|
|
|
|
|
|
|
37
|
3
|
|
|
3
|
|
135083
|
use 5.008; |
|
3
|
|
|
|
|
12
|
|
|
3
|
|
|
|
|
130
|
|
38
|
3
|
|
|
3
|
|
116
|
use strict; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
112
|
|
39
|
3
|
|
|
3
|
|
18
|
use warnings; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
98
|
|
40
|
|
|
|
|
|
|
|
41
|
3
|
|
|
3
|
|
6786
|
use GD; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
require Exporter; |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
our @ISA = qw(Exporter); |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# Items to export into callers namespace by default. Note: do not export |
48
|
|
|
|
|
|
|
# names by default without a very good reason. Use EXPORT_OK instead. |
49
|
|
|
|
|
|
|
# Do not simply export all your public functions/methods/constants. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# This allows declaration use Parse::WebCounter ':all'; |
52
|
|
|
|
|
|
|
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK |
53
|
|
|
|
|
|
|
# will save memory. |
54
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( 'all' => [ qw( readImage readDigit |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
) ] ); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
our @EXPORT = qw( |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
); |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
use vars qw($VERSION); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
$VERSION = '0.02'; |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
72
|
|
|
|
|
|
|
# Parse::WebCounter::new() |
73
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
74
|
|
|
|
|
|
|
# Purpose: |
75
|
|
|
|
|
|
|
# Constructor |
76
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
77
|
|
|
|
|
|
|
# Parameters: |
78
|
|
|
|
|
|
|
# takes a Hash or hashref of module parameters recognises the following |
79
|
|
|
|
|
|
|
# options |
80
|
|
|
|
|
|
|
# Name Default Notes |
81
|
|
|
|
|
|
|
# DIGITWIDTH 15 Width of individual digit |
82
|
|
|
|
|
|
|
# DIGITHEIGHT 20 Height of individual digit |
83
|
|
|
|
|
|
|
# STRIPORDER 1234567890 Order of digits in the image strip (if used) |
84
|
|
|
|
|
|
|
# MODE STRIP Use image strip or "DIGITS" |
85
|
|
|
|
|
|
|
# TYPE gif File type of images |
86
|
|
|
|
|
|
|
# PATTERN a Pattern dir |
87
|
|
|
|
|
|
|
# UNKOWNCHAR char Character to use if digit not matched |
88
|
|
|
|
|
|
|
# |
89
|
|
|
|
|
|
|
# (Image file loaded = PATTERN/STRIP.TYPE or PATTER/0.TYPE -> 9.TYPE) |
90
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------- |
91
|
|
|
|
|
|
|
# Returns: |
92
|
|
|
|
|
|
|
# ObjectRef Self |
93
|
|
|
|
|
|
|
# |
94
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
95
|
|
|
|
|
|
|
sub new{ |
96
|
|
|
|
|
|
|
my $proto = shift; |
97
|
|
|
|
|
|
|
my @args = @_; |
98
|
|
|
|
|
|
|
my $class = ref($proto) || $proto; |
99
|
|
|
|
|
|
|
my $self = {}; |
100
|
|
|
|
|
|
|
bless($self,$class); |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
#Defaults... |
103
|
|
|
|
|
|
|
$self->{DIGITWIDTH} = 15; |
104
|
|
|
|
|
|
|
$self->{DIGITHEIGHT}= 20; |
105
|
|
|
|
|
|
|
$self->{STRIPORDER} = "1234567890"; |
106
|
|
|
|
|
|
|
$self->{MODE} = "STRIP"; |
107
|
|
|
|
|
|
|
$self->{TYPE} = "gif"; |
108
|
|
|
|
|
|
|
$self->{PATTERN} = "a"; |
109
|
|
|
|
|
|
|
$self->{UNKNOWNCHAR}= "_"; |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
my $rprops; |
112
|
|
|
|
|
|
|
if (ref($args[0]) eq 'HASH'){ |
113
|
|
|
|
|
|
|
my $rtgash = %{$args[0]}; |
114
|
|
|
|
|
|
|
$rprops = $self->_cap_hash($args[0]); |
115
|
|
|
|
|
|
|
}else{ |
116
|
|
|
|
|
|
|
$rprops = $self->_cap_hash({ @args }); |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
foreach my $k (qw(DIGITWIDTH DIGITHEIGHT STRIPORDER TYPE PATTERN UNKNOWNCHAR)){ |
119
|
|
|
|
|
|
|
if (exists($rprops->{$k})){ |
120
|
|
|
|
|
|
|
$self->{$k} = $rprops->{$k}; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
#need to be special with MODE |
124
|
|
|
|
|
|
|
if (exists($rprops->{MODE})){ |
125
|
|
|
|
|
|
|
$rprops->{MODE} =~ tr/a-z/A-Z/; |
126
|
|
|
|
|
|
|
if ($rprops->{MODE} ne "STRIP" && $rprops->{MODE} ne "DIGITS"){ |
127
|
|
|
|
|
|
|
warn "Invalid mode " . $rprops->{MODE} . " using default\n"; |
128
|
|
|
|
|
|
|
}else{ |
129
|
|
|
|
|
|
|
$self->{MODE} = $rprops->{MODE}; |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
$self->_init(); |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
return $self; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
140
|
|
|
|
|
|
|
# Parse::WebCounter::_init() |
141
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
142
|
|
|
|
|
|
|
# Purpose: |
143
|
|
|
|
|
|
|
# Internal function to initialise the class data (loads image strip data) |
144
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
145
|
|
|
|
|
|
|
# Parameters: |
146
|
|
|
|
|
|
|
# None - pulls data from class object and $ENV |
147
|
|
|
|
|
|
|
# |
148
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
149
|
|
|
|
|
|
|
# Returns: |
150
|
|
|
|
|
|
|
# Nothing |
151
|
|
|
|
|
|
|
# |
152
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
153
|
|
|
|
|
|
|
sub _init{ |
154
|
|
|
|
|
|
|
my $self = shift; |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
if ($self->{MODE} eq "STRIP"){ |
157
|
|
|
|
|
|
|
$self->_loadStripImage(); |
158
|
|
|
|
|
|
|
}else{ |
159
|
|
|
|
|
|
|
$self->_loadDigitImages(); |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
166
|
|
|
|
|
|
|
# Parse::WebCounter::_cap_hash() |
167
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
168
|
|
|
|
|
|
|
# Purpose: |
169
|
|
|
|
|
|
|
# automagically convert the hash it is given into capitalised keys so users |
170
|
|
|
|
|
|
|
# of the module can pass any capitalisation they like as module options |
171
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
172
|
|
|
|
|
|
|
# Parameters: |
173
|
|
|
|
|
|
|
# HashRef |
174
|
|
|
|
|
|
|
# |
175
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
176
|
|
|
|
|
|
|
# Returns: |
177
|
|
|
|
|
|
|
# HashRef all the keys CAPITALISED |
178
|
|
|
|
|
|
|
# |
179
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
180
|
|
|
|
|
|
|
sub _cap_hash { |
181
|
|
|
|
|
|
|
my $self = shift; |
182
|
|
|
|
|
|
|
my $rhash = shift; |
183
|
|
|
|
|
|
|
my %hash = map { |
184
|
|
|
|
|
|
|
my $k = $_; |
185
|
|
|
|
|
|
|
my $v = $rhash->{$k}; |
186
|
|
|
|
|
|
|
$k =~ tr/a-z/A-Z/; |
187
|
|
|
|
|
|
|
$k => $v; |
188
|
|
|
|
|
|
|
} keys(%{$rhash}); |
189
|
|
|
|
|
|
|
return \%hash; |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
193
|
|
|
|
|
|
|
# Parse::WebCounter::_loadStripImage() |
194
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
195
|
|
|
|
|
|
|
# Purpose: |
196
|
|
|
|
|
|
|
# Loads the images required for matching from a single strip of digits in |
197
|
|
|
|
|
|
|
# one image and breaks it up into individual ones. |
198
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
199
|
|
|
|
|
|
|
# Parameters: None, but uses following class data |
200
|
|
|
|
|
|
|
# digitwidth int The width of the digits in the strip |
201
|
|
|
|
|
|
|
# digitheight int The height of the digits in the strip |
202
|
|
|
|
|
|
|
# strip order string the "order" of the digits ie "1234567890" |
203
|
|
|
|
|
|
|
# type string The "type" of image, essentially the extension |
204
|
|
|
|
|
|
|
# pattern string the pattern directory to load from relative to current |
205
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
206
|
|
|
|
|
|
|
# Returns: |
207
|
|
|
|
|
|
|
# Nothing, But stores the image data in the class. |
208
|
|
|
|
|
|
|
# |
209
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
210
|
|
|
|
|
|
|
sub _loadStripImage{ |
211
|
|
|
|
|
|
|
my $self = shift; |
212
|
|
|
|
|
|
|
my %reference_images; |
213
|
|
|
|
|
|
|
my $filename = $self->{PATTERN} . "/strip." . $self->{TYPE}; |
214
|
|
|
|
|
|
|
my $imagestrip = GD::Image->new($filename); |
215
|
|
|
|
|
|
|
my $left = 0; |
216
|
|
|
|
|
|
|
my @striporder = split('',$self->{STRIPORDER}); |
217
|
|
|
|
|
|
|
foreach my $number (@striporder){ |
218
|
|
|
|
|
|
|
my $digit = GD::Image->new($self->{DIGITWIDTH}, |
219
|
|
|
|
|
|
|
$self->{DIGITHEIGHT}); |
220
|
|
|
|
|
|
|
$digit->copy($imagestrip,0,0,$left,0,$self->{DIGITWIDTH} |
221
|
|
|
|
|
|
|
,$self->{DIGITHEIGHT}); |
222
|
|
|
|
|
|
|
$left += $self->{DIGITWIDTH}; |
223
|
|
|
|
|
|
|
$reference_images{$number} = $digit; |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
$self->{REFIMAGES} = \%reference_images; |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
229
|
|
|
|
|
|
|
# Parse::WebCounter::_loadDigitImages() |
230
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
231
|
|
|
|
|
|
|
# Purpose: |
232
|
|
|
|
|
|
|
# Loads the images required for matching from separate digit files |
233
|
|
|
|
|
|
|
# |
234
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
235
|
|
|
|
|
|
|
# Parameters: None, but uses following class data |
236
|
|
|
|
|
|
|
# digitwidth int The width of the digits in the strip |
237
|
|
|
|
|
|
|
# digitheight int The height of the digits in the strip |
238
|
|
|
|
|
|
|
# strip order string the "order" of the digits ie "1234567890" |
239
|
|
|
|
|
|
|
# type string The "type" of image, essentially the extension |
240
|
|
|
|
|
|
|
# pattern string the pattern directory to load from relative to current |
241
|
|
|
|
|
|
|
# |
242
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
243
|
|
|
|
|
|
|
# Returns: |
244
|
|
|
|
|
|
|
# Nothing, But stores the image data in the class. |
245
|
|
|
|
|
|
|
# |
246
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
247
|
|
|
|
|
|
|
sub _loadDigitImages{ |
248
|
|
|
|
|
|
|
my $self = shift; |
249
|
|
|
|
|
|
|
my %reference_images; |
250
|
|
|
|
|
|
|
my @striporder = split('',$self->{STRIPORDER}); |
251
|
|
|
|
|
|
|
foreach my $number (@striporder){ |
252
|
|
|
|
|
|
|
$reference_images{$number} = GD::Image->new( $self->{PATTERN} . "/" . $number . "." . $self->{TYPE}); |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
$self->{REFIMAGES} = \%reference_images; |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
260
|
|
|
|
|
|
|
# Parse::WebCounter::readImage(image[,xoffset[,yoffset]]) |
261
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
262
|
|
|
|
|
|
|
# Purpose: |
263
|
|
|
|
|
|
|
# Reads the given image to determine the value of all the digits within |
264
|
|
|
|
|
|
|
# |
265
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
266
|
|
|
|
|
|
|
# Parameters: |
267
|
|
|
|
|
|
|
# image gdimage The image object to evaluate |
268
|
|
|
|
|
|
|
# xoffset int Offset value to use (in case image has a border) |
269
|
|
|
|
|
|
|
# yoffset int Offset value |
270
|
|
|
|
|
|
|
# |
271
|
|
|
|
|
|
|
# Values from classdata used |
272
|
|
|
|
|
|
|
# digitwidth int The width of the digits in the strip |
273
|
|
|
|
|
|
|
# digitheight int The height of the digits in the strip |
274
|
|
|
|
|
|
|
# |
275
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
276
|
|
|
|
|
|
|
# Returns: |
277
|
|
|
|
|
|
|
# The parsed value of the image. |
278
|
|
|
|
|
|
|
# |
279
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
280
|
|
|
|
|
|
|
sub readImage{ |
281
|
|
|
|
|
|
|
my $self = shift; |
282
|
|
|
|
|
|
|
my $image = shift; |
283
|
|
|
|
|
|
|
my $xoffset = shift || 0; |
284
|
|
|
|
|
|
|
my $yoffset = shift || 0; |
285
|
|
|
|
|
|
|
my ($width,$height) = $image->getBounds(); |
286
|
|
|
|
|
|
|
my $return = ""; |
287
|
|
|
|
|
|
|
for (my $i = $xoffset; $i < $width ; $i += $self->{DIGITWIDTH}){ |
288
|
|
|
|
|
|
|
my $digit = GD::Image->new($self->{DIGITWIDTH}, |
289
|
|
|
|
|
|
|
$self->{DIGITHEIGHT}); |
290
|
|
|
|
|
|
|
$digit->copy($image,0,0,$i,$yoffset, |
291
|
|
|
|
|
|
|
$self->{DIGITWIDTH}, |
292
|
|
|
|
|
|
|
$self->{DIGITHEIGHT}); |
293
|
|
|
|
|
|
|
$return .= $self->readDigit($digit); |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
return $return; |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
301
|
|
|
|
|
|
|
# Parse::WebCounter::readDigit(image) |
302
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
303
|
|
|
|
|
|
|
# Purpose: |
304
|
|
|
|
|
|
|
# Reads the given image digit to determine the value |
305
|
|
|
|
|
|
|
# |
306
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
307
|
|
|
|
|
|
|
# Parameters: |
308
|
|
|
|
|
|
|
# image gdimage The image object to evaluate |
309
|
|
|
|
|
|
|
# |
310
|
|
|
|
|
|
|
# Values from classdata used |
311
|
|
|
|
|
|
|
# REFIMAGES hashref The stored reference images for comparison |
312
|
|
|
|
|
|
|
# UNKNOWNCHAR char The character to return for an unmatched digit ('_') |
313
|
|
|
|
|
|
|
# |
314
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
315
|
|
|
|
|
|
|
# Returns: |
316
|
|
|
|
|
|
|
# The parsed value of the digit, or the UNKNOWNCHAR if the digit could not |
317
|
|
|
|
|
|
|
# be matched. |
318
|
|
|
|
|
|
|
# |
319
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
320
|
|
|
|
|
|
|
sub readDigit{ |
321
|
|
|
|
|
|
|
my $self = shift; |
322
|
|
|
|
|
|
|
my $image = shift; |
323
|
|
|
|
|
|
|
foreach my $number (keys(%{$self->{REFIMAGES}})){ |
324
|
|
|
|
|
|
|
if ($image->compare($self->{REFIMAGES}->{$number}) == 0){ |
325
|
|
|
|
|
|
|
return $number; |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
return $self->{UNKNOWNCHAR}; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
1; |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
__END__ |