Warm tip: This article is reproduced from stackoverflow.com, please click
common-lisp sbcl

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

发布于 2020-04-19 09:46:21

I guess this is an easy question for someone with Common Lisp experience. Not so much for me, who just started out with LISP.

As you see in the next snippet below, I create a 800 by 600 array of type 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)))

And the function in my utilities package is supposed to write that in P5 PGM format do disk.

(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)

The equivalent (and working) C-function which does the same thing looks like this.

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;
}

Who can tell me how to fix my save-array-as-pgm function, preferably with writing the array in one go, instead of using a loop and (write-byte (aref buffer y x) stream)?

Before I decided to ask this question here, I googled a lot and only found references to some packages which do fancy binary stuff - but this is a simple case and I look for a simple solution.

Questioner
BitTickler
Viewed
45
Rainer Joswig 2020-02-05 17:23

Common Lisp supports displaced arrays:

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

A displaced array has no storage on its own, but uses the storage of another array. It can have different dimensions.

Now there is the question how efficiently the Lisp implementation can access the array through the displaced array.