summaryrefslogtreecommitdiff
path: root/chickadee/image.scm
blob: 48dddac3e7fad94896d4c98fa40d3d13ef3fcae9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
;;; Chickadee Game Toolkit
;;; Copyright © 2023 David Thompson <dthompson2@worcester.edu>
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;;
;;;    http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

;;; Commentary:
;;
;; Data type for representing images on disk.
;;
;;; Code:

(define-module (chickadee image)
  #:use-module (chickadee graphics color)
  #:use-module (chickadee image jpeg)
  #:use-module (chickadee image png)
  #:use-module (chickadee pixbuf)
  #:use-module (chickadee utils)
  #:use-module (ice-9 exceptions)
  #:use-module (ice-9 match)
  #:use-module (rnrs bytevectors)
  #:use-module (srfi srfi-9)
  #:use-module (srfi srfi-9 gnu)
  #:export (make-image
            image?
            image-file-name
            temp-image-file-name
            read-image
            write-image))

(define &image-error
  (make-exception-type '&image-error &error '()))

(define make-image-error (record-constructor &image-error))

(define (image-error message irritants)
 (raise-exception
  (make-exception
   (make-image-error)
   (make-exception-with-message message)
   (make-exception-with-irritants irritants))))

;; An image is pixel data that is stored on disk in one of a variety
;; of formats.
(define-record-type <image>
  (%make-image file-name)
  image?
  (file-name image-file-name))

(define (make-image file-name)
  (%make-image (absolute-file-name file-name)))

(define (print-image image port)
  ;; Rendering in a format that Geiser understands so that images can
  ;; be viewed inline at the REPL.
  (format port "#<Image: ~a>" (image-file-name image)))

(set-record-type-printer! <image> print-image)

(define (image-format image)
  (match (file-extension (image-file-name image))
    ((or "jpg" "jpeg") 'jpeg)
    ("png" 'png)
    (_ 'unknown)))

(define* (read-image image)
  "Read data from IMAGE and return a pixbuf."
  (define-values (pixels width height)
    (case (image-format image)
      ((jpeg)
       (load-jpeg (image-file-name image)))
      ((png)
       (load-png (image-file-name image)))
      (else
       (image-error "unsupported image format: ~a" (image-format image)))))
  (bytevector->pixbuf pixels width height #:format 'rgba #:bit-depth 8))

;; Not guaranteed to be a file that doesn't exist, but good enough.
(define (temp-image-file-name format)
  (string-append (tmpdir) "/"
                 (symbol->string
                  (gensym "chickadee-"))
                 "."
                 (symbol->string format)))

(define* (write-image pixbuf
                      #:optional (file-name (temp-image-file-name 'png))
                      #:key (format 'png))
  "Write PIXBUF to FILE-NAME and return an image object.  FORMAT may be
either 'png' or 'jpeg'."
  (unless (and (eq? (pixbuf-format pixbuf) 'rgba)
               (= (pixbuf-bit-depth pixbuf) 8))
    (image-error "can only write RGBA pixbufs with 8 bit depth: ~a" pixbuf))
  (case format
    ((png)
     (save-png (pixbuf-pixels pixbuf)
               (pixbuf-width pixbuf)
               (pixbuf-height pixbuf)
               file-name))
    (else
     (image-error "unsupport image format: ~a" format)))
  (make-image file-name))