如何在 Common Lisp 中将二维字节数组写入二进制文件?

How to write a 2D byte array to a binary file in Common Lisp?

我想这对于有 Common Lisp 经验的人来说是个简单的问题。对刚开始使用 LISP 的我来说不是那么多。

正如您在下面的下一个片段中看到的,我创建了一个 800 x 600 类型的 UNSIGNED BYTE 数组。

(defun test-binary-save ()
    (let*
        ((width 800)
         (height 600)
         (arr (make-array (list width height) 
                :element-type '(mod 256)
                :initial-element 0)))
        (utilities::save-array-as-pgm "test.pgm" arr)))

我的实用程序包中的函数应该以 P5 PGM 格式写入磁盘。

(defun save-array-as-pgm (filename buffer)
    "Writes a byte array as a PGM file (P5) to a file."
    (with-open-file 
        (stream filename 
            :element-type '(unsigned-byte 8)
            :direction :output 
            :if-does-not-exist :create
            :if-exists :supersede)
        (let* 
            ((dimensions (array-dimensions buffer))
             (width (first dimensions))
             (height (second dimensions))
             (header (format nil "P5~A~D ~D~A255~A" 
                #\newline
                width height #\newline
                #\newline)))

            (loop 
                :for char :across header 
                :do (write-byte (char-code char) stream))
            ;(write-sequence buffer stream) <<-- DOES NOT WORK - is not of type SEQUENCE
        ))
    filename)

做同样事情的等效(和工作)C 函数如下所示。

static
int
save_pgm
( const char* filename
, size_t width
, size_t height
, const uint8_t* pixels
)
{
    if(NULL == filename)
        return 0;
    if(NULL == pixels)
        return 0;

    FILE *out = fopen(filename, "wb");
    if(NULL != out)
    {
        fprintf(out, "P5\n%zu %zu\n255\n", width, height);
        size_t nbytes = width * height;
        fwrite(pixels,1,nbytes,out);
        fclose(out);
        return 1;
    }

    return 0;
}

谁能告诉我如何修复我的 save-array-as-pgm 函数,最好是一次写入数组,而不是使用循环和 (write-byte (aref buffer y x) stream)?

在我决定在这里问这个问题之前,我在谷歌上搜索了很多,只找到了对一些做花哨的二进制内容的包的引用——但这是一个简单的案例,我正在寻找一个简单的解决方案。

如果你想在 Common Lisp 中做真正的位推,使用一维数组。

看起来这毕竟不是那么困难...一旦我发现可以将 2D 数组 "cast" 转换为 1D 数组然后简单地使用 write-sequence.

为了找到解决方案,我必须检查 github 上的 sbcl 源代码以了解 make-array 的工作原理并找到 - sbcl - 特定函数 array-storage-vector

如我所料,多维数组使用一维后备数组进行数据存储。

函数 save-array-as-pgm 现在看起来像这样:

(defun save-array-as-pgm (filename buffer)
    "Writes a byte array as a PGM file (P5) to a file."
    (with-open-file 
        (stream filename 
            :element-type '(unsigned-byte 8)
            :direction :output 
            :if-does-not-exist :create
            :if-exists :supersede)
        (let* 
            ((dimensions (array-dimensions buffer))
             (width (first dimensions))
             (height (second dimensions))
             (header (format nil "P5~A~D ~D~A255~A" 
                #\newline
                width height #\newline
                #\newline)))

            (loop 
                :for char :across header 
                :do (write-byte (char-code char) stream))
            (write-sequence (sb-c::array-storage-vector buffer) stream)
        ))
    filename)

Common Lisp 支持 displaced 数组:

CL-USER 6 > (let ((array (make-array (list 3 4)
                                     :initial-element 1
                                     :element-type 'bit)))
              (make-array (reduce #'* (array-dimensions array))
                          :element-type 'bit
                          :displaced-to array))
#*111111111111

置换数组本身没有存储空间,而是使用另一个数组的存储空间。它可以有不同的维度。

现在的问题是 Lisp 实现如何有效地通过置换数组访问数组。