6 Commits 13536e665b ... 02a5346d40

Author SHA1 Message Date
  Andrew Whatson 02a5346d40 Add autotools build system 1 year ago
  Andrew Whatson 13536e665b Add autotools build system 1 year ago
  Andrew Whatson 4893b1260b Implement wand-error return type 1 year ago
  Andrew Whatson ecca95359a Add magick-convert-image & error handling 1 year ago
  Andrew Whatson 78aca33506 Export c-type routines & minor cleanup 1 year ago
  Andrew Whatson 8ca1f32a51 Add prototype image-magick FFI 1 year ago
8 changed files with 519 additions and 33 deletions
  1. 3 1
      .gitignore
  2. 3 22
      Makefile.am
  3. 22 0
      build-aux/guile-rules.am
  4. 7 1
      configure.ac
  5. 8 9
      guix.scm
  6. 23 0
      openai/config.scm.in
  7. 255 0
      openai/utils/foreign.scm
  8. 198 0
      openai/utils/magick.scm

+ 3 - 1
.gitignore

@@ -2,7 +2,8 @@
 /Makefile.in
 /aclocal.m4
 /autom4te.cache/
-/build-aux/
+/build-aux/install-sh
+/build-aux/missing
 /configure
 /configure~
 
@@ -10,6 +11,7 @@
 /Makefile
 /config.log
 /config.status
+/openai/config.scm
 /pre-inst-env
 
 # files from make

+ 3 - 22
Makefile.am

@@ -1,25 +1,4 @@
-GOBJECTS = $(SOURCES:%.scm=%.go)
-
-nobase_mod_DATA = $(SOURCES) $(NOCOMP_SOURCES)
-nobase_go_DATA = $(GOBJECTS)
-
-# Make sure source files are installed first, so that the mtime of
-# installed compiled files is greater than that of installed source
-# files.  See
-# <http://lists.gnu.org/archive/html/guile-devel/2010-07/msg00125.html>
-# for details.
-guile_install_go_files = install-nobase_goDATA
-$(guile_install_go_files): install-nobase_modDATA
-
-CLEANFILES = $(GOBJECTS)
-EXTRA_DIST = $(SOURCES) $(NOCOMP_SOURCES)
-GUILE_WARNINGS = -Wunbound-variable -Warity-mismatch -Wformat
-SUFFIXES = .scm .go
-.scm.go:
-	$(AM_V_GEN)$(top_builddir)/pre-inst-env $(GUILE_TOOLS) compile $(GUILE_WARNINGS) -o "$@" "$<"
-
-moddir=$(prefix)/share/guile/site/$(GUILE_EFFECTIVE_VERSION)
-godir=$(libdir)/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache
+include $(top_srcdir)/build-aux/guile-rules.am
 
 SOURCES = \
   openai.scm \
@@ -40,6 +19,8 @@ SOURCES = \
   openai/api/moderation.scm \
   openai/utils/colorized.scm \
   openai/utils/event-stream.scm \
+  openai/utils/foreign.scm \
   openai/utils/json.scm \
+  openai/utils/magick.scm \
   openai/utils/multipart.scm \
   openai/utils/uri.scm

+ 22 - 0
build-aux/guile-rules.am

@@ -0,0 +1,22 @@
+GOBJECTS = $(SOURCES:%.scm=%.go)
+
+nobase_mod_DATA = $(SOURCES) $(NOCOMP_SOURCES)
+nobase_go_DATA = $(GOBJECTS)
+
+# Make sure source files are installed first, so that the mtime of
+# installed compiled files is greater than that of installed source
+# files.  See
+# <http://lists.gnu.org/archive/html/guile-devel/2010-07/msg00125.html>
+# for details.
+guile_install_go_files = install-nobase_goDATA
+$(guile_install_go_files): install-nobase_modDATA
+
+CLEANFILES = $(GOBJECTS)
+EXTRA_DIST = $(SOURCES) $(NOCOMP_SOURCES)
+GUILE_WARNINGS = -Wunbound-variable -Warity-mismatch -Wformat
+SUFFIXES = .scm .go
+.scm.go:
+	$(AM_V_GEN)$(top_builddir)/pre-inst-env $(GUILE_TOOLS) compile $(GUILE_WARNINGS) -o "$@" "$<"
+
+moddir=$(prefix)/share/guile/site/$(GUILE_EFFECTIVE_VERSION)
+godir=$(libdir)/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache

+ 7 - 1
configure.ac

@@ -1,5 +1,5 @@
 AC_INIT(guile-openai, 0.1)
-AC_CONFIG_SRCDIR([openai.scm])
+AC_CONFIG_SRCDIR([openai])
 AC_CONFIG_AUX_DIR([build-aux])
 AM_INIT_AUTOMAKE([color-tests -Wall -Wno-portability foreign])
 AM_SILENT_RULES([yes])
@@ -7,12 +7,18 @@ AM_SILENT_RULES([yes])
 AC_PATH_PROG([GUILE], [guile])
 AC_CONFIG_FILES([Makefile])
 AC_CONFIG_FILES([pre-inst-env], [chmod +x pre-inst-env])
+AC_CONFIG_FILES([openai/config.scm])
 
 GUILE_PKG([3.0])
 GUILE_PROGS
+
 GUILE_MODULE_REQUIRED([ice-9 colorized])
 GUILE_MODULE_REQUIRED([gnutls])
 GUILE_MODULE_REQUIRED([json])
 GUILE_MODULE_REQUIRED([pict base64])
 
+PKG_CHECK_MODULES([MAGICKWAND], [MagickWand])
+PKG_CHECK_VAR([MAGICKWAND_LIBDIR], [MagickWand], [libdir])
+PKG_CHECK_VAR([MAGICKWAND_LIBNAME], [MagickWand], [libname])
+
 AC_OUTPUT

+ 8 - 9
guix.scm

@@ -26,6 +26,7 @@
              (gnu packages autotools)
              (gnu packages guile)
              (gnu packages guile-xyz)
+             (gnu packages imagemagick)
              (gnu packages pkg-config)
              (gnu packages tls)
              (ice-9 popen)
@@ -46,15 +47,13 @@
                        #:recursive? #t
                        #:select? (git-predicate %source-dir)))
    (build-system gnu-build-system)
-   (native-inputs
-     (list automake autoconf libtool pkg-config))
-   (inputs
-     (list guile-3.0-latest))
-   (propagated-inputs
-     (list guile-colorized
-           guile-gnutls
-           guile-json-4
-           guile-picture-language))
+   (arguments (list #:strip-binaries? #f))
+   (native-inputs (list automake autoconf pkg-config))
+   (inputs (list guile-3.0-latest imagemagick))
+   (propagated-inputs (list guile-colorized
+                            guile-gnutls
+                            guile-json-4
+                            guile-picture-language))
    (home-page "https://notabug.org/flatwhatson/guile-openai")
    (synopsis "Guile implementation of the OpenAI API")
    (description

+ 23 - 0
openai/config.scm.in

@@ -0,0 +1,23 @@
+;;; guile-openai --- An OpenAI API client for Guile
+;;; Copyright © 2023 Andrew Whatson <whatson@tailcall.au>
+;;;
+;;; This file is part of guile-openai.
+;;;
+;;; guile-openai is free software: you can redistribute it and/or modify
+;;; it under the terms of the GNU Affero General Public License as
+;;; published by the Free Software Foundation, either version 3 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; guile-openai is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;;; Affero General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU Affero General Public
+;;; License along with guile-openai.  If not, see
+;;; <https://www.gnu.org/licenses/>.
+
+(define-module (openai config)
+  #:export (%libmagickwand))
+
+(define %libmagickwand "@MAGICKWAND_LIBDIR@/lib@MAGICKWAND_LIBNAME@")

+ 255 - 0
openai/utils/foreign.scm

@@ -0,0 +1,255 @@
+;;; guile-openai --- An OpenAI API client for Guile
+;;; Copyright © 2023 Andrew Whatson <whatson@tailcall.au>
+;;;
+;;; This file is part of guile-openai.
+;;;
+;;; guile-openai is free software: you can redistribute it and/or modify
+;;; it under the terms of the GNU Affero General Public License as
+;;; published by the Free Software Foundation, either version 3 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; guile-openai is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;;; Affero General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU Affero General Public
+;;; License along with guile-openai.  If not, see
+;;; <https://www.gnu.org/licenses/>.
+
+(define-module (openai utils foreign)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 vlist)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-9)
+  #:use-module (srfi srfi-9 gnu)
+  #:use-module ((system foreign) #:prefix ffi:)
+  #:use-module ((system foreign) #:select (define-wrapped-pointer-type))
+  #:use-module (system foreign-library)
+  #:export (c-type?
+            c-type-name
+            c-type-size
+
+            int8 uint8 int16 uint16 int32 uint32 int64 uint64
+            float double complex-double complex-float
+            int unsigned-int long unsigned-long short unsigned-short
+            size_t ssize_t ptrdiff_t intptr_t uintptr_t
+            void pointer cstring bool
+
+            define-foreign-type
+            define-foreign-arg-type
+            define-foreign-return-type
+
+            define-foreign-enum-type
+            define-foreign-pointer-type
+
+            define-foreign-library
+            define-foreign-function
+            define-foreign-functions))
+
+;;; C type marshalling
+
+(define-record-type <c-type>
+  (%make-c-type name repr wrapper unwrapper)
+  c-type?
+  (name c-type-name)
+  (repr c-type-repr)
+  (wrapper c-type-wrapper)
+  (unwrapper c-type-unwrapper))
+
+(define* (print-c-type type #:optional port)
+  (format port "#<c-type ~a ~a>"
+          (c-type-name type)
+          (c-type-name (get-base-type (c-type-repr type)))))
+
+(define (c-type-size type)
+  (ffi:sizeof (c-type-repr type)))
+
+(set-record-type-printer! <c-type> print-c-type)
+
+(define-syntax-rule (define-foreign-type type-name base wrapper unwrapper)
+  (define type-name
+    (%make-c-type (symbol->string 'type-name)
+                  (c-type-repr base)
+                  wrapper unwrapper)))
+
+(define-syntax-rule (define-foreign-arg-type type-name base unwrapper)
+  (define-foreign-type type-name base #f unwrapper))
+
+(define-syntax-rule (define-foreign-return-type type-name base wrapper)
+  (define-foreign-type type-name base wrapper #f))
+
+;;; Base types
+
+(define %base-types vlist-null)
+
+(define (register-base-type! type)
+  (let ((repr (c-type-repr type)))
+    (unless (has-base-type? repr)
+      (set! %base-types (vhash-consv repr type %base-types)))))
+
+(define (has-base-type? repr)
+  (and (vhash-assv repr %base-types) #t))
+
+(define (get-base-type repr)
+  (match (vhash-assv repr %base-types)
+    ((_ . type) type)))
+
+(define-syntax-rule (define-base-type type-name repr)
+  (begin
+    (define type-name
+      (%make-c-type (symbol->string 'type-name) repr identity identity))
+    (register-base-type! type-name)))
+
+(define-base-type int8           ffi:int8)
+(define-base-type uint8          ffi:uint8)
+(define-base-type int16          ffi:int16)
+(define-base-type uint16         ffi:uint16)
+(define-base-type int32          ffi:int32)
+(define-base-type uint32         ffi:uint32)
+(define-base-type int64          ffi:int64)
+(define-base-type uint64         ffi:uint64)
+(define-base-type float          ffi:float)
+(define-base-type double         ffi:double)
+(define-base-type complex-double ffi:complex-double)
+(define-base-type complex-float  ffi:complex-float)
+(define-base-type int            ffi:int)
+(define-base-type unsigned-int   ffi:unsigned-int)
+(define-base-type long           ffi:long)
+(define-base-type unsigned-long  ffi:unsigned-long)
+(define-base-type short          ffi:short)
+(define-base-type unsigned-short ffi:unsigned-short)
+(define-base-type size_t         ffi:size_t)
+(define-base-type ssize_t        ffi:ssize_t)
+(define-base-type ptrdiff_t      ffi:ptrdiff_t)
+(define-base-type intptr_t       ffi:intptr_t)
+(define-base-type uintptr_t      ffi:uintptr_t)
+(define-base-type void           ffi:void)
+(define-base-type pointer        '*)
+
+;;; Common types
+
+(define-foreign-type cstring pointer
+  ffi:pointer->string
+  ffi:string->pointer)
+
+(define-foreign-type bool int
+  (lambda (int) (not (zero? int)))
+  (lambda (bool) (if bool 1 0)))
+
+;;; Enum types
+
+(define-syntax-rule (define-foreign-enum-type enum-name enum-base
+                      enumerator? enumerator-list
+                      int->enumerator enumerator->int
+                      (enumerator ...))
+  (begin
+    (define (enumerator? sym)
+      (and (enumerator->int sym) #t))
+    (define (enumerator-list)
+      (%dfe-enum-symbols (enumerator ...)))
+    (define enumerator->int
+      (let ((lookup (alist->vhash (map cons
+                                       (%dfe-enum-symbols (enumerator ...))
+                                       (%dfe-enum-values (enumerator ...)))
+                                  hashq)))
+        (lambda (sym)
+          (and=> (vhash-assq sym lookup) cdr))))
+    (define int->enumerator
+      (let ((lookup (alist->vhash (map cons
+                                       (%dfe-enum-values (enumerator ...))
+                                       (%dfe-enum-symbols (enumerator ...)))
+                                  hashv)))
+        (lambda (int)
+          (and=> (vhash-assv int lookup) cdr))))
+    (define-foreign-type enum-name enum-base
+      int->enumerator enumerator->int)))
+
+(define-syntax %dfe-enum-symbols
+  (syntax-rules (=>)
+    ((_ (args ...))
+     (%dfe-enum-symbols (args ...) ()))
+    ((_ (symbol => value args ...) (syms ...))
+     (%dfe-enum-symbols (args ...) (syms ... symbol)))
+    ((_ (symbol args ...) (syms ...))
+     (%dfe-enum-symbols (args ...) (syms ... symbol)))
+    ((_ () (syms ...))
+     '(syms ...))))
+
+(define-syntax %dfe-enum-values
+  (syntax-rules (=>)
+    ((_ (args ...))
+     (%dfe-enum-values (args ...) () -1))
+    ((_ (symbol => value args ...) (vals ...) previous)
+     (%dfe-enum-values (args ...) (vals ... value) value))
+    ((_ (symbol args ...) (vals ...) previous)
+     (%dfe-enum-values (args ...) (vals ... (1+ previous)) (1+ previous)))
+    ((_ () (vals ...) previous)
+     (list vals ...))))
+
+;;; Pointer types
+
+(define-syntax-rule (define-foreign-pointer-type pointer-name record-type
+                      record? pointer->record record->pointer)
+  (begin
+    (define-wrapped-pointer-type record-type
+      record? pointer->record record->pointer
+      (lambda (rec port)
+        (let ((address (ffi:pointer-address (record->pointer rec))))
+          (format port "#<~a 0x~x>" 'pointer-name address))))
+    (define-foreign-type pointer-name pointer
+      pointer->record record->pointer)))
+
+;;; Function wrappers
+
+(define-syntax-rule (define-foreign-library library path args ...)
+  (define library
+    (load-foreign-library path args ...)))
+
+(define-syntax-rule (define-foreign-function library
+                      (function-name signature ...))
+  (define function-name
+    (apply wrapped-foreign-library-function library
+           (symbol->string 'function-name)
+           (%dff-parse-signature (signature ...)))))
+
+(define-syntax %dff-parse-signature
+  (syntax-rules (->)
+    ((_ (-> return-type) arg-types ...)
+     (list #:return-type return-type
+           #:arg-types (list arg-types ...)))
+    ((_ (next rest ...) arg-types ...)
+     (%dff-parse-signature (rest ...) arg-types ... next))))
+
+(define-syntax-rule (define-foreign-functions library
+                      (function-name signature ...) ...)
+  (begin
+    (define-foreign-function library
+      (function-name signature ...))
+    ...))
+
+(define (procedure-takes-rest? proc)
+  (caddr (procedure-minimum-arity proc)))
+
+(define* (wrapped-foreign-library-function library function-name
+                                           #:key return-type arg-types)
+  (let* ((wrapper (c-type-wrapper return-type))
+         (wrap-result (if (procedure-takes-rest? wrapper)
+                          wrapper
+                          (lambda (result . args)
+                            (wrapper result))))
+         (unwrappers (map c-type-unwrapper arg-types))
+         (unwrap-args (lambda (args)
+                        (map (lambda (unwrap arg)
+                               (unwrap arg))
+                             unwrappers args)))
+         (foreign-function
+          (foreign-library-function library function-name
+                                    #:return-type (c-type-repr return-type)
+                                    #:arg-types (map c-type-repr arg-types))))
+    (lambda args
+      (let* ((raw-args   (unwrap-args args))
+             (raw-result (apply foreign-function raw-args))
+             (result     (apply wrap-result raw-result args)))
+        result))))

+ 198 - 0
openai/utils/magick.scm

@@ -0,0 +1,198 @@
+;;; guile-openai --- An OpenAI API client for Guile
+;;; Copyright © 2023 Andrew Whatson <whatson@tailcall.au>
+;;;
+;;; This file is part of guile-openai.
+;;;
+;;; guile-openai is free software: you can redistribute it and/or modify
+;;; it under the terms of the GNU Affero General Public License as
+;;; published by the Free Software Foundation, either version 3 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; guile-openai is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;;; Affero General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU Affero General Public
+;;; License along with guile-openai.  If not, see
+;;; <https://www.gnu.org/licenses/>.
+
+(define-module (openai utils magick)
+  #:use-module (openai config)
+  #:use-module (openai utils foreign)
+  #:use-module (ice-9 match)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-9)
+  #:use-module ((system foreign) #:prefix ffi:)
+  #:export (magick-alpha-channel-options
+            magick-convert-image))
+
+(define-foreign-library libMagickWand %libmagickwand)
+
+(define-foreign-enum-type alpha-channel-enum int
+  alpha-channel? alpha-channel-list
+  int->alpha-channel alpha-channel->int
+  (Undefined
+   Activate
+   Associate
+   Background
+   Copy
+   Deactivate
+   Discrete
+   Disassociate
+   Extract
+   Off
+   On
+   Opaque
+   Remove
+   Set
+   Shape
+   Transparent))
+
+(define-foreign-enum-type exception-type-enum int
+  exception-type? exception-type-list
+  int->exception-type exception-type->int
+  (UndefinedException
+   WarningException => 300
+   ResourceLimitWarning => 300
+   TypeWarning => 305
+   OptionWarning => 310
+   DelegateWarning => 315
+   MissingDelegateWarning => 320
+   CorruptImageWarning => 325
+   FileOpenWarning => 330
+   BlobWarning => 335
+   StreamWarning => 340
+   CacheWarning => 345
+   CoderWarning => 350
+   FilterWarning => 352
+   ModuleWarning => 355
+   DrawWarning => 360
+   ImageWarning => 365
+   WandWarning => 370
+   RandomWarning => 375
+   XServerWarning => 380
+   MonitorWarning => 385
+   RegistryWarning => 390
+   ConfigureWarning => 395
+   PolicyWarning => 399
+   ErrorException => 400
+   ResourceLimitError => 400
+   TypeError => 405
+   OptionError => 410
+   DelegateError => 415
+   MissingDelegateError => 420
+   CorruptImageError => 425
+   FileOpenError => 430
+   BlobError => 435
+   StreamError => 440
+   CacheError => 445
+   CoderError => 450
+   FilterError => 452
+   ModuleError => 455
+   DrawError => 460
+   ImageError => 465
+   WandError => 470
+   RandomError => 475
+   XServerError => 480
+   MonitorError => 485
+   RegistryError => 490
+   ConfigureError => 495
+   PolicyError => 499
+   FatalErrorException => 700
+   ResourceLimitFatalError => 700
+   TypeFatalError => 705
+   OptionFatalError => 710
+   DelegateFatalError => 715
+   MissingDelegateFatalError => 720
+   CorruptImageFatalError => 725
+   FileOpenFatalError => 730
+   BlobFatalError => 735
+   StreamFatalError => 740
+   CacheFatalError => 745
+   CoderFatalError => 750
+   FilterFatalError => 752
+   ModuleFatalError => 755
+   DrawFatalError => 760
+   ImageFatalError => 765
+   WandFatalError => 770
+   RandomFatalError => 775
+   XServerFatalError => 780
+   MonitorFatalError => 785
+   RegistryFatalError => 790
+   ConfigureFatalError => 795
+   PolicyFatalError => 799))
+
+(define-foreign-pointer-type wand-ptr <wand>
+  wand? pointer->wand wand->pointer)
+
+(define-foreign-return-type wand-error int
+  (lambda (result wand . _)
+    (when (zero? result)
+      (apply error (MagickGetException wand)))))
+
+(define-foreign-functions libMagickWand
+  (MagickWandGenesis -> void)
+  (MagickWandTerminus -> void)
+  (NewMagickWand -> wand-ptr)
+  (DestroyMagickWand wand-ptr -> wand-ptr)
+  (MagickGetException wand-ptr pointer -> cstring)
+  (MagickReadImage wand-ptr cstring -> wand-error)
+  (MagickReadImageBlob wand-ptr pointer size_t -> wand-error)
+  (MagickWriteImage wand-ptr cstring -> bool)
+  (MagickGetImageBlob wand-ptr pointer -> pointer)
+  (MagickGetImageFormat wand-ptr -> cstring)
+  (MagickSetImageFormat wand-ptr cstring -> wand-error)
+  (MagickGetImageAlphaChannel wand-ptr -> alpha-channel-enum)
+  (MagickSetImageAlphaChannel wand-ptr alpha-channel-enum -> wand-error))
+
+(set! MagickGetException
+      (let ((func MagickGetException))
+        (lambda (wand)
+          (let* ((type-ptr (ffi:make-c-struct (list ffi:int) (list 0)))
+                 (message (func wand type-ptr))
+                 (type-int (car (ffi:parse-c-struct type-ptr (list ffi:int))))
+                 (type (int->exception-type type-int)))
+            (list type message)))))
+
+(set! MagickReadImageBlob
+      (let ((func MagickReadImageBlob))
+        (lambda (wand bv)
+          (let ((ptr (ffi:bytevector->pointer bv))
+                (len (bytevector-length bv)))
+            (func wand ptr len)))))
+
+(set! MagickGetImageBlob
+      (let ((func MagickGetImageBlob))
+        (lambda (wand)
+          (let* ((len-ptr (ffi:make-c-struct (list ffi:size_t) (list 0)))
+                 (data-ptr (func wand len-ptr))
+                 (data-len (car (ffi:parse-c-struct len-ptr (list ffi:size_t)))))
+            (ffi:pointer->bytevector data-ptr data-len)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (call-with-magick-wand proc)
+  (let ((wand #f))
+    (dynamic-wind
+      (const #t)
+      (lambda ()
+        (MagickWandGenesis)
+        (set! wand (NewMagickWand))
+        (proc wand))
+      (lambda ()
+        (when wand
+          (DestroyMagickWand wand))))))
+
+(define (magick-alpha-channel-options)
+  (alpha-channel-list))
+
+(define* (magick-convert-image image-bytes #:key
+                               format
+                               alpha-channel)
+  (call-with-magick-wand
+   (lambda (wand)
+     (MagickReadImageBlob wand image-bytes)
+     (MagickSetImageFormat wand format)
+     (MagickSetImageAlphaChannel wand alpha-channel)
+     (MagickGetImageBlob wand))))