-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathreverse-stream.lisp
128 lines (100 loc) · 4.87 KB
/
reverse-stream.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
(defpackage :reverse-stream
(:use :common-lisp :trivial-gray-streams :lisp-binary/integer :lisp-binary-utils)
(:export :wrap-in-reverse-stream :with-wrapped-in-reverse-stream :reverse-stream)
(:documentation "A stream that reads from another stream and reverses the bit order
of each byte so that the low-order bit becomes the high-order bit and vice versa.
This functionality is called for by the TIFF file format, because \"It is easy and
inexpensive for writers to reverse bit order by using a 256-byte lookup table.\" It
is devillishly tricky to include this functionality directly in the DEFBINARY macro,
however, when the macro was written without gratuitous bit-reversal in mind.
The REVERSE-STREAM does not keep track of any file positioning information. That means
it can coexist with its client stream, and you can mix reads and/or writes between
the two.
REVERSE-STREAM is not limited to 8-bit bytes. It can handle any byte size that the
underlying Lisp implementation supports. On PC hardware, some Lisps can read byte
sizes that are multiples of 8 bits, such as (UNSIGNED-BYTE 24)."))
(in-package :reverse-stream)
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun compute-reversed-byte (n bits)
(let ((result 0))
(loop repeat bits
for bits-left downfrom bits
do (push-bits (pop-bits 1 bits-left n) (- bits bits-left) result))
result)))
(defun make-lookup-table (bits)
(make-array (list (expt 2 bits))
:element-type `(unsigned-byte ,bits)
:initial-contents
(loop for n from 0 below (expt 2 bits)
collect (compute-reversed-byte n bits))))
(defvar *8-bit-lookup-table* (make-lookup-table 8))
(defclass reverse-stream (fundamental-binary-stream)
((element-bits :type fixnum :initform 8 :initarg :element-bits)
(lookup-table :initform nil :initarg :lookup-table)
(real-stream :type stream :initarg :real-stream)))
(defgeneric wrap-in-reverse-stream (object)
(:documentation "Creates a REVERSE-STREAM that can read one bit at a time from the OBJECT. The REVERSE-STREAM
can be discarded if BYTE-ALIGNED-P returns T."))
(defmethod wrap-in-reverse-stream ((object stream))
(let ((element-type (stream-element-type object)))
(assert (and (listp element-type)
(= (length element-type) 2)
(eq (car element-type) 'unsigned-byte)))
(let ((bits (cadr element-type)))
(make-instance 'reverse-stream :real-stream object
:element-bits bits
:lookup-table (if (= bits 8)
*8-bit-lookup-table*
(make-lookup-table bits))))))
(defmacro with-wrapped-in-reverse-stream ((var non-bitstream &key close-when-done) &body body)
`(let ((,var (wrap-in-reverse-stream ,non-bitstream)))
(unwind-protect
(progn
,@body)
(finish-output ,var)
,@(if close-when-done
`((if ,close-when-done
(close ,var)))))))
(defmethod stream-finish-output ((stream reverse-stream))
(finish-output (slot-value stream 'real-stream)))
(defmethod stream-force-output ((stream reverse-stream))
(force-output (slot-value stream 'real-stream)))
(defmethod close ((stream reverse-stream) &key abort)
(apply #'close (list* (slot-value stream 'real-stream)
(if abort
(list :abort abort)))))
(defun reverse-byte (byte lookup-table)
(aref lookup-table byte))
(declaim (inline reverse-byte))
(defmethod stream-read-byte ((stream reverse-stream))
(reverse-byte
(read-byte (slot-value stream 'real-stream))
(slot-value stream 'lookup-table)))
(defmethod stream-write-byte ((stream reverse-stream) integer)
(write-byte (reverse-byte integer
(slot-value stream 'lookup-table))
stream))
(defun %stream-write-sequence (stream sequence start end)
(let ((reversed (mapseq (lambda (element)
(reverse-byte element (slot-value stream 'lookup-table)))
sequence)))
(write-sequence reversed stream :start start :end end)))
(defmethod stream-write-sequence ((stream reverse-stream) sequence start end &key &allow-other-keys)
(%stream-write-sequence stream sequence (or start 0) (or end (1- (length sequence)))))
(defun %stream-read-sequence (stream sequence start end)
(prog1 (read-sequence sequence (slot-value stream 'real-stream) :start start :end end)
(if (listp sequence)
(loop for hd on sequence
while hd
do (rplaca hd (reverse-byte (car hd)
(slot-value stream 'lookup-table))))
(loop for ix from 0 below (length sequence)
do (setf (aref sequence ix)
(reverse-byte (aref sequence ix)
(slot-value stream 'lookup-table)))))))
(defmethod stream-read-sequence ((stream reverse-stream) sequence start end &key &allow-other-keys)
(%stream-read-sequence stream sequence start end))
(defmethod stream-file-position ((stream reverse-stream))
(file-position (slot-value stream 'real-stream)))
(defmethod (setf stream-file-position) (position-spec (stream reverse-stream))
(file-position (slot-value stream 'real-stream) position-spec))