Как записать двумерный байтовый массив в двоичный файл в Common Lisp? - PullRequest
3 голосов
/ 05 февраля 2020

Думаю, это простой вопрос для тех, кто имеет опыт работы с Common Lisp. Не так много для меня, который только начал с LISP.

Как вы видите в следующем фрагменте ниже, я создаю массив размером 800 на 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 функцию, желательно с записью массива в один go вместо использования al oop и (write-byte (aref buffer y x) stream)?

Прежде чем я решил задать этот вопрос здесь, я много гуглил и нашел ссылки только на некоторые пакеты, которые делают причудливые двоичные вещи - но это простой случай, и я ищу простое решение.

Ответы [ 3 ]

4 голосов
/ 05 февраля 2020

Common Lisp поддерживает смещенные массивы:

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 может получить доступ к массиву через смещенный массив.

0 голосов
/ 05 февраля 2020

Похоже, что это было не так уж и сложно ... как только я обнаружил, что можно "привести" 2D-массив к 1D-массиву, а затем просто использовать write-sequence.

. решение, которое я должен был проверить исходный код sbcl на github, чтобы получить gr asp о том, как make-array работает, и найти - sbcl - конкретную c функцию array-storage-vector.

, как я уже догадался , многомерные массивы используют 1d резервный массив для хранения данных.

Функция 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)

0 голосов
/ 05 февраля 2020

Если вы хотите, чтобы в Common Lisp выполнялось серьезное добавление битов, используйте одномерные массивы.

...