-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
wrapper.lisp
336 lines (290 loc) · 16.4 KB
/
wrapper.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
(in-package #:org.shirakumo.fraf.turbojpeg)
(define-condition jpeg-condition (condition)
((jpeg :initarg :jpeg :reader jpeg)
(message :initarg :message :reader message))
(:report (lambda (c s) (format s "~a failed to perform:~%~a"
(jpeg c) (message c)))))
(define-condition jpeg-error (jpeg-condition error) ())
(define-condition jpeg-warning (jpeg-condition warning) ())
(defun init ()
(unless (cffi:foreign-library-loaded-p 'turbo:libturbojpeg)
(cffi:load-foreign-library 'turbo:libturbojpeg)))
(defmethod free (ptr)
(check-type ptr cffi:foreign-pointer)
(turbo:free ptr))
(defun %write-file (jpeg buf size path)
(let ((file (cffi:foreign-funcall "fopen" :string (uiop:native-namestring path) :string "wb" :pointer)))
(unwind-protect
(progn
(when (cffi:null-pointer-p file)
(error 'jpeg-error :jpeg jpeg :message "Failed to open file to write to."))
(when (< (cffi:foreign-funcall "fwrite" :pointer buf :size size :size 1 :pointer file :int) 1)
(error 'jpeg-error :jpeg jpeg :message "Failed to write to file."))
path)
(cffi:foreign-funcall "fclose" :pointer file :int))))
(defclass jpeg ()
((handle :initarg :handle :initform NIL :accessor handle)))
(defmethod report-error ((jpeg jpeg))
(case (turbo:error-type (handle jpeg))
(:warning (warn 'jpeg-warning :jpeg jpeg :message (turbo:error-string (handle jpeg))))
(:fatal (error 'jpeg-error :jpeg jpeg :message (turbo:error-string (handle jpeg))))))
(defun test-error (jpeg result)
(if (< result 0)
(report-error jpeg)
result))
(defmacro check-error (form)
`(test-error ,(second form)
(,(first form) (handle ,(second form)) ,@(cddr form))))
(defmacro %set-boolean (name)
`(when ,(intern (format NIL "~a-~a" (string name) (string 'p)))
(check-error (turbo:set-parameter jpeg ,(intern (string name) "KEYWORD")
(if ,name 1 0)))))
(defmacro %set-property (name &optional enum)
`(when ,name
(check-error (turbo:set-parameter jpeg ,(intern (string name) "KEYWORD")
,(if enum
`(cffi:foreign-enum-value ',enum ,name)
name)))))
(defmethod shared-initialize :after ((jpeg jpeg) slots &key (stop-on-warning NIL stop-on-warning-p)
(bottom-up NIL bottom-up-p)
(progressive NIL progressive-p)
(arithmetic NIL arithmetic-p))
(init)
(unless (handle jpeg)
(let ((handle (make-handle jpeg)))
(if (cffi:null-pointer-p handle)
(error "Failed to allocate JPEG handle.")
(setf (handle jpeg) handle))))
(%set-boolean stop-on-warning)
(%set-boolean arithmetic)
(%set-boolean bottom-up)
(%set-boolean progressive))
(defmethod free ((jpeg jpeg))
(when (handle jpeg)
(turbo:destroy (handle jpeg))
(setf (handle jpeg) NIL)))
(defmacro define-property-wrapper (class name &optional (value 'value) &body transform)
(destructuring-bind (method &optional (property (intern (string method) "KEYWORD")))
(if (listp name) name (list name))
`(defmethod ,method ((,class ,class))
(let ((,value (turbo:get-parameter (handle ,class) ,property)))
,(or transform value)))))
(defclass compressor (jpeg)
())
(defmethod make-handle ((_ compressor))
(turbo:make-handle :compress))
(defmethod shared-initialize :after ((jpeg compressor) slots &key (no-realloc NIL no-realloc-p)
(fast-dct NIL fast-dct-p)
(optimize NIL optimize-p)
(lossless NIL lossless-p)
quality
subsampling
color-space
restart-blocks
restart-rows
x-density
y-density)
(%set-boolean no-realloc)
(%set-property quality)
(%set-property subsampling turbo:chrominance-sampling)
(%set-property color-space turbo:color-space)
(%set-boolean fast-dct)
(%set-boolean optimize)
(%set-boolean lossless)
(%set-property restart-blocks)
(%set-property restart-rows)
(%set-property x-density)
(%set-property y-density))
(defmethod save-image (dst buffer width height (jpeg compressor) &key (pixel-format :rgb)
pitch
(bit-depth 8)
size)
(unless pitch
(setf pitch (* (turbo:pixel-size pixel-format) (width jpeg))))
(cffi:with-foreign-objects ((dst-ptr :pointer)
(size-ptr :size))
(setf (cffi:mem-ref dst-ptr :pointer) (if dst dst (cffi:null-pointer)))
(setf (cffi:mem-ref size-ptr :size) (if dst size 0))
(let ((result (ecase bit-depth
(8 (turbo:compress (handle jpeg) buffer width pitch height pixel-format dst-ptr size-ptr))
(12 (turbo:compress/12 (handle jpeg) buffer width pitch height pixel-format dst-ptr size-ptr))
(16 (turbo:compress/16 (handle jpeg) buffer width pitch height pixel-format dst-ptr size-ptr)))))
(test-error jpeg result)
(values (cffi:mem-aref dst-ptr :pointer)
(cffi:mem-aref size-ptr :size)))))
(defmethod save-image ((destination vector) src width height (jpeg compressor) &rest args &key size &allow-other-keys)
(cffi:with-pointer-to-vector-data (ptr destination)
(check-error (turbo:set-parameter jpeg :no-realloc 1))
(apply #'save-image ptr src width height jpeg :size (or size (length destination)) args)))
(defmethod save-image ((destination pathname) src width height (jpeg compressor) &rest args &key &allow-other-keys)
(multiple-value-bind (buf size) (apply #'save-image NIL src width height jpeg args)
(unwind-protect (%write-file jpeg buf size destination)
(turbo:free buf))))
(defmethod save-image ((source string) src width height (jpeg compressor) &rest args &key &allow-other-keys)
(apply #'save-image (pathname source) src width height jpeg args))
(defmethod save-image (destination (source vector) width height (jpeg compressor) &rest args &key &allow-other-keys)
(cffi:with-pointer-to-vector-data (ptr source)
(apply #'save-image ptr source width height jpeg args)))
(defmethod save-image (destination src width height (jpeg (eql T)) &rest args &key &allow-other-keys)
(let ((jpeg (make-instance 'compressor)))
(unwind-protect (apply #'save-image destination src width height jpeg args)
(free jpeg))))
(defmacro with-compressor ((name &rest args) &body body)
`(let ((,name (make-instance 'compressor ,@args)))
(unwind-protect (let ((,name ,name)) ,@body)
(free ,name))))
(defclass decompressor (jpeg)
())
(defmethod make-handle ((_ decompressor))
(turbo:make-handle :decompress))
(defmethod shared-initialize :after ((jpeg decompressor) slots &key (fast-dct NIL fast-dct-p)
(fast-upsample NIL fast-upsample-p)
(optimize NIL optimize-p)
(lossless NIL lossless-p)
subsampling
color-space
scan-limit
x-density
y-density)
(%set-boolean fast-upsample)
(%set-property subsampling turbo:chrominance-sampling)
(%set-property color-space turbo:color-space)
(%set-boolean fast-dct)
(%set-boolean optimize)
(%set-boolean lossless)
(%set-property scan-limit)
(%set-property x-density)
(%set-property y-density))
(define-property-wrapper decompressor width)
(define-property-wrapper decompressor height)
(define-property-wrapper decompressor precision)
(defmethod load-image (ptr (jpeg decompressor) &key (pixel-format :rgb)
pitch
(bit-depth 8)
size
buffer)
(check-type ptr cffi:foreign-pointer)
(check-error (turbo:decompress-header jpeg ptr size))
(unless pitch
(setf pitch (* (turbo:pixel-size pixel-format) (width jpeg))))
(let ((buffer-size (* pitch (height jpeg))))
(labels ((load-image/ptr (buffer)
(let ((result (ecase bit-depth
(8 (turbo:decompress (handle jpeg) ptr size buffer pitch pixel-format))
(12 (turbo:decompress/12 (handle jpeg) ptr size buffer pitch pixel-format))
(16 (turbo:decompress/16 (handle jpeg) ptr size buffer pitch pixel-format)))))
(test-error jpeg result)
(values buffer
(width jpeg)
(height jpeg)
pixel-format
buffer-size)))
(load-image/vec (vec)
(cffi:with-pointer-to-vector-data (ptr vec)
(multiple-value-bind (ptr w h p s) (load-image/ptr ptr)
(declare (ignore ptr))
(values vec w h p s)))))
(etypecase buffer
(null
(load-image/ptr (turbo:alloc (ceiling (* bit-depth buffer-size) 8))))
(cffi:foreign-pointer
(load-image/ptr buffer))
(vector
(load-image/vec (if (<= buffer-size (length buffer))
buffer
(adjust-array buffer buffer-size))))
((eql :vector)
(load-image/vec (ecase bit-depth
(8 (make-array buffer-size :element-type '(unsigned-byte 8)))
(16 (make-array buffer-size :element-type '(unsigned-byte 16))))))))))
(defmethod load-image ((source pathname) (jpeg decompressor) &rest args &key &allow-other-keys)
(let (vec)
(with-open-file (stream source :element-type '(unsigned-byte 8))
(setf vec (make-array (file-length stream) :element-type '(unsigned-byte 8)))
(read-sequence vec stream))
(apply #'load-image vec jpeg args)))
(defmethod load-image ((source string) (jpeg decompressor) &rest args &key &allow-other-keys)
(apply #'load-image (pathname source) jpeg args))
(defmethod load-image ((source vector) (jpeg decompressor) &rest args &key size &allow-other-keys)
(cffi:with-pointer-to-vector-data (ptr source)
(apply #'load-image ptr jpeg :size (or size (length source)) args)))
(defmethod load-image (source (jpeg (eql T)) &rest args &key &allow-other-keys)
(let ((jpeg (make-instance 'decompressor)))
(unwind-protect (apply #'load-image source jpeg args)
(free jpeg))))
(defmacro with-decompressor ((name &rest args) &body body)
`(let ((,name (make-instance 'decompressor ,@args)))
(unwind-protect (let ((,name ,name)) ,@body)
(free ,name))))
(defclass transformer (jpeg)
())
(defmethod make-handle ((_ transformer))
(turbo:make-handle :transform))
(defmethod shared-initialize :after ((jpeg transformer) slots &key (no-realloc NIL no-realloc-p)
subsampling
scan-limit)
(%set-boolean no-realloc)
(%set-property subsampling turbo:chrominance-sampling)
(%set-property scan-limit))
(defmethod transform-image (source destination operation (jpeg transformer) &key source-size
destination-size
perfect
trim
crop
gray
progressive
copy-none
arithmetic
optimize)
(check-type source cffi:foreign-pointer)
(cffi:with-foreign-objects ((buf-ptr :pointer)
(size-ptr :size)
(transform '(:struct turbo:transform)))
(setf (cffi:mem-ref buf-ptr :pointer) (if destination destination (cffi:null-pointer)))
(setf (cffi:mem-ref size-ptr :size) (if destination destination-size 0))
(setf (turbo:transform-operation transform) operation)
(let ((options ()))
(when perfect (push :perfect options))
(when trim (push :trim options))
(when gray (push :gray options))
(when progressive (push :progressive options))
(when copy-none (push :copy-none options))
(when arithmetic (push :arithmetic options))
(when optimize (push :optimize options))
(when crop
(push :crop options)
(destructuring-bind (x y w h) crop
(setf (turbo:transform-region transform) (list :x x :y y :w w :h h))))
(setf (turbo:transform-options transform) options))
(check-error (turbo:transform jpeg source source-size 1 buf-ptr size-ptr transform))
(values (cffi:mem-ref buf-ptr :pointer)
(cffi:mem-ref size-ptr :size))))
(defmethod transform-image ((source vector) destination operation (jpeg transformer) &rest args &key source-size &allow-other-keys)
(cffi:with-pointer-to-vector-data (ptr source)
(apply #'transform-image ptr destination operation jpeg :source-size (or source-size (length source)) args)))
(defmethod transform-image (source (destination vector) operation (jpeg transformer) &rest args &key destination-size &allow-other-keys)
(cffi:with-pointer-to-vector-data (ptr source)
(check-error (turbo:set-parameter jpeg :no-realloc 1))
(apply #'transform-image source ptr operation jpeg :destination-size (or destination-size (length source)) args)))
(defmethod transform-image (source (destination pathname) operation (jpeg transformer) &rest args &key &allow-other-keys)
(multiple-value-bind (buf size) (apply #'transform-image source NIL operation jpeg args)
(unwind-protect (%write-file jpeg buf size destination)
(turbo:free buf))))
(defmethod transform-image (source (destination string) operation (jpeg decompressor) &rest args &key &allow-other-keys)
(apply #'transform-image source (pathname destination) operation jpeg args))
(defmethod transform-image ((source pathname) destination operation (jpeg transformer) &rest args &key &allow-other-keys)
(let (vec)
(with-open-file (stream source :element-type '(unsigned-byte 8))
(setf vec (make-array (file-length stream) :element-type '(unsigned-byte 8)))
(read-sequence vec stream))
(apply #'transform-image vec destination operation jpeg args)))
(defmethod transform-image ((source string) destination operation (jpeg decompressor) &rest args &key &allow-other-keys)
(apply #'transform-image (pathname source) destination operation jpeg args))
(defmethod transform-image (source destination operation (jpeg (eql T)) &rest args &key &allow-other-keys)
(let ((jpeg (make-instance 'transformer)))
(unwind-protect (apply #'transform-image source destination operation jpeg args)
(free jpeg))))
(defmacro with-transformer ((name &rest args) &body body)
`(let ((,name (make-instance 'transformer ,@args)))
(unwind-protect (let ((,name ,name)) ,@body)
(free ,name))))