File Coverage

blib/lib/Math/DCT.pm
Criterion Covered Total %
statement 40 40 100.0
branch 22 22 100.0
condition 15 15 100.0
subroutine 7 7 100.0
pod 3 3 100.0
total 87 87 100.0


line stmt bran cond sub pod time code
1             package Math::DCT;
2              
3 4     4   863029 use 5.008;
  4         25  
4 4     4   18 use strict;
  4         6  
  4         63  
5 4     4   15 use warnings;
  4         5  
  4         220  
6              
7             =head1 NAME
8              
9             Math::DCT - 1D and NxN 2D Fast Discreet Cosine Transforms (DCT-II)
10              
11             =head1 SYNOPSIS
12              
13             use Math::DCT qw/dct dct1d dct2d/;
14              
15             my $dct1d = dct([[1,2,3,4]]);
16             $dct1d = dct1d([1,2,3,4]);
17              
18             my $dct2d = dct([[1,2],[3,4]]);
19             $dct2d = dct2d([1,2,3,4]);
20              
21             =head1 VERSION
22              
23             Version 0.03
24              
25             =cut
26              
27             our $VERSION = '0.03';
28              
29             require XSLoader;
30             XSLoader::load('Math::DCT', $VERSION);
31              
32 4     4   20 use Exporter qw(import);
  4         7  
  4         1872  
33              
34             our @EXPORT_OK = qw(
35             dct
36             dct1d
37             dct2d
38             );
39              
40             our %EXPORT_TAGS;
41             $EXPORT_TAGS{all} = [@EXPORT_OK];
42              
43             =head1 DESCRIPTION
44              
45             An unscaled DCT-II implementation for 1D and NxN 2D matrices implemented in XS.
46             For array sizes which are a power of 2, a fast algorithm (FCT) described by Lee is
47             used (with the addition of a coefficient table that makes it even faster than some
48             common implementations), plus an unscaled version of the Arai, Agui, Nakajima
49             algorithm is used for size 1x8, 8x8 matrices.
50              
51             The module was written for a perceptual hash project that needed 32x32 DCT-II,
52             and on a 2.5GHz 2015 Macbook Pro over 11500/s per thread are processed (C function).
53             The common 8x8 DCT-II uses a special path, (abut 250000 transforms per sec on the same CPU),
54             although for most image/video applications that require 8x8 DCT there are much faster
55             implementations (SIMD, approximations etc) that usually produce an already scaled result
56             for the specific application.
57              
58             None of the algorithms used on this module are approximate, the test suite verifies
59             against a naive DCT-II implementation with a tolerance of 1e-08.
60              
61             =head1 METHODS
62            
63             =head2 C
64              
65             my $dct = dct([[1,2],[3,4]]);
66              
67             Pass an array (ref) of either a single array, or N N-length arrays for 1D and 2D
68             DCT-II calculation respectivelly. The output will be an arrayref of array(s) with
69             the result of the transform.
70              
71             =cut
72              
73             sub dct {
74 19     19 1 12946 my $vector = shift;
75 19 100 100     211 die "Expect array of array(s)"
      100        
      100        
76             unless $vector && ref $vector eq 'ARRAY'
77             && $vector->[0] && ref $vector->[0] eq 'ARRAY';
78              
79 15         26 my $dim = scalar(@$vector);
80 15         23 my $sz = scalar(@{$vector->[0]});
  15         50  
81 15 100 100     71 die "Expect 1d or NxN 2d arrays" unless $dim == 1 || $dim == $sz;
82              
83 14         22 my $pack;
84 14         39 for (my $x = 0; $x < $dim; $x++) {
85 131         149 $pack .= pack "d$sz", @{$vector->[$x]}
  131         468  
86             }
87              
88 14 100       35 if ($dim > 1) {
89 7 100       671 $sz == 8
    100          
90             ? fct8_2d($pack)
91             : 0 == ($sz & ($sz - 1)) ? fast_dct_2d($pack, $sz) : dct_2d($pack, $sz);
92             } else {
93 7 100       63 $sz == 8
    100          
94             ? fct8_1d($pack)
95             : 0 == ($sz & ($sz - 1)) ? fast_dct_1d($pack, $sz) : dct_1d($pack, $sz);
96             }
97              
98 14         21 my $result;
99 14         35 foreach (0..$dim-1) {
100 131         621 $result->[$_] = [unpack "d".($sz), substr $pack, $_ * $sz*8, $sz*8];
101             }
102 14         59 return $result;
103             }
104              
105             =head2 C
106              
107             my $dct = dct1d([1,2,3]);
108              
109             Pass an array (ref) for a 1D DCT-II calculation. The output will be an arrayref
110             with the result of the transform.
111              
112             =cut
113              
114             sub dct1d {
115 7     7 1 788292 my $input = shift;
116 7         14 my $sz = scalar @$input;
117 7         53 my $pack = pack "d$sz", @$input;
118 7 100       101 $sz == 8
    100          
119             ? fct8_1d($pack)
120             : 0 == ($sz & ($sz - 1)) ? fast_dct_1d($pack, $sz) : dct_1d($pack, $sz);
121 7         43 my @result = unpack "d$sz", $pack;
122 7         30 return \@result;
123             }
124              
125             =head2 C
126              
127             my $dct = dct2d(
128             [1,2,3,4], # Arrayref containing your NxN matrix
129             2 # Optionally, the size N of your array (sqrt of its length)
130             );
131              
132             Pass an array (ref) for a 2D DCT-II calculation. The length of the array is expected
133             to be a square (as only NxN arrays are supported) - you can optionally pass N as
134             the second argument to avoid a C calculation.
135             The output will be an arrayref with the result of the transform.
136              
137             If your 2D data is available in a 1d array as is usual with most image manipulation
138             etc cases, this function will be faster than C, as the DCT calculation is
139             in any case done on a 1D array, hence you save the cost of conversion.
140              
141             =cut
142              
143             sub dct2d {
144 10     10 1 665012 my $input = shift;
145 10   100     127 my $sz = shift || sqrt(scalar @$input);
146 9         307 my $pack = pack "d".($sz*$sz), @$input;
147 9 100       701 $sz == 8
    100          
148             ? fct8_2d($pack)
149             : 0 == ($sz & ($sz - 1)) ? fast_dct_2d($pack, $sz) : dct_2d($pack, $sz);
150 9         291 my @result = unpack "d".($sz*$sz), $pack;
151 9         56 return \@result;
152             }
153              
154             =head1 USAGE NOTES
155              
156             The C functions are not exported, but theoretically you could use them directly
157             if you do your own C. The fast versions for power-of-2 size arrays
158             are C and C, while the generic versions are C
159             and C. The specialized size-8 versions are C and C.
160             First argument is a C (use C), second is the size N (except
161             for the fct8* functions which don't need a second argument).
162              
163             =head1 ACKNOWLEDGEMENTS
164              
165             Some code from Project Nayuki was adapted and improved upon.
166              
167             (L)
168              
169             =head1 AUTHOR
170              
171             Dimitrios Kechagias, C<< >>
172            
173             =head1 BUGS
174              
175             Please report any bugs or feature requests to C,
176             or through the web interface at L.
177             I will be notified, and then you'll automatically be notified of progress on your
178             bug as I make changes.
179              
180             =head1 GIT
181              
182             L
183            
184             =head1 COPYRIGHT & LICENSE
185              
186             Copyright (C) 2019, SpareRoom.com
187              
188             This program is free software; you can redistribute
189             it and/or modify it under the same terms as Perl itself.
190              
191             =cut
192              
193             1;