;;; R7RS-PNM --- Library for reading and writing PNM (Portable Any Map) files for R7RS ;;; Copyright © 2024 Masaya Tojo <masaya@tojo.tokyo> ;;; ;;; This file is part of R7RS-PNM. ;;; ;;; R7RS-PNM is free software: you can redistribute it and/or modify it ;;; under the terms of the GNU Lesser General Public License as published ;;; by the Free Software Foundation, either version 3 of the License, or ;;; (at your option) any later version. ;;; ;;; R7RS-PNM 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 Lesser General Public License for more details. ;;; ;;; You should have received a copy of the GNU Lesser General Public License ;;; along with R7RS-PNM. If not, see <https://www.gnu.org/licenses/>. (define-library (image-format pnm read) (export pnm-image-read pnm-parse-error?) (import (scheme base) (scheme case-lambda) (image-format pnm image) (image-format pnm pbm) (image-format pnm pgm) (image-format pnm ppm)) (begin (define pnm-image-read (case-lambda ((in) (pnm-image-read in #f)) ((in unsafe?) (define (read-u8*) (let ((u8 (read-u8 in))) (when (eof-object? u8) (unexpected-eof-error)) u8)) (define (read-magic-number*) (let ((magic-number (read-magic-number in))) (when (eof-object? magic-number) (unexpected-eof-error)) (unless magic-number (unexpected-magic-number-error)) magic-number)) (define (read-number*) (let ((n (read-number in))) (when (eof-object? n) (unexpected-eof-error)) (unless n (unexpected-character-error)) n)) (define (read-whitespaces*) (let ((result (read-whitespaces in))) (when (eof-object? result) (unexpected-eof-error)) (unless result (unexpected-character-error)))) (define (read-single-whitespace*) (unless (whitespace? (read-u8*)) (unexpected-character-error))) (let ((magic-number (read-magic-number*))) (read-whitespaces*) (let ((width (read-number in))) (read-whitespaces*) (let ((height (read-number in))) (case magic-number ((P4) (read-single-whitespace*) (%make-pbm-image width height (read-bytevector (* (ceiling (/ width 8)) height) in) unsafe?)) ((P1) (let ((byte-width (ceiling (/ width 8))) (pbm-image (make-pbm-image width height unsafe?))) (read-text-raster width height #f in (lambda (x y v) (pnm-image-set! pbm-image x y (= v 1))) unexpected-eof-error unexpected-character-error) pbm-image)) (else (read-whitespaces*) (let ((maxval (read-number in))) (case magic-number ((P6) (read-single-whitespace*) (%make-ppm-image width height maxval (if (< maxval 256) (read-bytevector (* 3 width height) in) (read-bytevector (* 6 width height) in)) unsafe?)) ((P3) (let ((ppm-image (make-ppm-image width height maxval unsafe?))) (read-text-raster width height #t in (pnm-image-pixel-setter ppm-image) unexpected-eof-error unexpected-character-error) ppm-image)) ((P5) (read-single-whitespace*) (%make-pgm-image width height maxval (if (< maxval 256) (read-bytevector (* width height) in) (read-bytevector (* 2 width height) in)) unsafe?)) ((P2) (let ((pgm-image (make-pgm-image width height maxval unsafe?))) (read-text-raster width height #f in (pnm-image-pixel-setter pgm-image) unexpected-eof-error unexpected-character-error) pgm-image)))))))))))) (define (whitespace? u8) (case (integer->char u8) ((#\newline #\return #\tab #\space #\x000B ;; Vertical Tab #\x000c ;; Form Feed ) #t) (else #f))) (define (comment? u8) (char=? #\# (integer->char u8))) (define (newline? u8) (case (integer->char u8) ((#\newline) #t) (else #f))) (define (read-comment in) (let ((u8 (peek-u8 in))) (cond ((eof-object? u8) (eof-object)) ((newline? u8) (read-u8 in) (skip-whitespace in)) (else (read-u8 in) (read-comment in))))) (define (skip-whitespace in) (let ((u8 (peek-u8 in))) (cond ((eof-object? u8) #t) ((whitespace? u8) (read-u8 in) (skip-whitespace in)) ((comment? u8) (read-u8 in) (read-comment in)) (else #t)))) (define (digit? u8) (and (<= 48 u8) (<= u8 57))) (define (u8->integer u8) (- u8 48)) (define (read-magic-number in) (let* ((P/u8 (char->integer #\P)) (u8-1 (read-u8 in)) (u8-2 (read-u8 in)) (u8-2/int (u8->integer u8-2))) (cond ((or (eof-object? u8-1) (eof-object? u8-2)) (eof-object)) ((and (= u8-1 P/u8) (= u8-2/int 6)) 'P6) ((and (= u8-1 P/u8) (= u8-2/int 5)) 'P5) ((and (= u8-1 P/u8) (= u8-2/int 4)) 'P4) ((and (= u8-1 P/u8) (= u8-2/int 3)) 'P3) ((and (= u8-1 P/u8) (= u8-2/int 2)) 'P2) ((and (= u8-1 P/u8) (= u8-2/int 1)) 'P1) (else #f)))) (define (read-whitespaces in) (let ((u8 (peek-u8 in))) (cond ((eof-object? u8) (eof-object)) ((whitespace? u8) (read-u8 in) (skip-whitespace in)) (else #f)))) (define (read-text-raster width height rgb? in proc fail-eof-object fail-unexpected-char) (call-with-current-continuation (lambda (return) (define (read-whitespaces*) (let ((result (read-whitespaces in))) (when (eof-object? result) (return (fail-eof-object))) (unless result (return (fail-unexpected-char))))) (define (read-number*) (let ((number (read-number in))) (when (eof-object? number) (return (fail-eof-object))) (unless number (return (fail-unexpected-char))) number)) (let ((size (* width height))) (do ((y 0 (+ y 1))) ((= y height)) (do ((x 0 (+ x 1))) ((= x width)) (if rgb? (let* ((r (begin (read-whitespaces*) (read-number*))) (g (begin (read-whitespaces*) (read-number*))) (b (begin (read-whitespaces*) (read-number*)))) (proc x y r g b)) (proc x y (begin (read-whitespaces*) (read-number*)))))))))) (define (read-number in) (call-with-current-continuation (lambda (return) (let ((u8 (peek-u8 in))) (when (eof-object? u8) (return (eof-object))) (when (not (digit? u8)) (return #f))) (let loop ((number 0)) (let ((u8 (peek-u8 in))) (cond ((eof-object? u8) (eof-object)) ((digit? u8) (read-u8 in) (loop (+ (* number 10) (u8->integer u8)))) (else number))))))) (define (unexpected-magic-number-error) (raise (pnm-parse-error "(image-format pnm read) pnm-image-read: Not supported magic number"))) (define (unexpected-eof-error) (raise (pnm-parse-error "(image-format pnm read) pnm-image-read: Unexpected end of file"))) (define (unexpected-character-error) (raise (pnm-parse-error "(image-format pnm read) pnm-image-read: Unexpected character")))) (cond-expand ((library (srfi 35)) (import (srfi 35)) (begin (define-condition-type &pnm-parse-error &error pnm-parse-error?) (define (pnm-parse-error msg) (raise (condition (&pnm-parse-error) (&message (message msg))))))) (r7rs (begin (define-record-type <pnm-parse-error> (make-pnm-parse-error message) pnm-parse-error? (message pnm-parse-error-message)) (define (pnm-parse-error msg) (raise (make-pnm-parse-error msg)))))))