sunterlib/scsh/tiff/tiff.scm

775 lines
30 KiB
Scheme
Raw Normal View History

2003-11-09 12:04:04 -05:00
;****************************************************************************
;
; Tag Image File Format (TIFF)
;
;
; Tiff tag definitions were borrowed from:
; > Copyright (c) 1988, 1990 by Sam Leffler.
; > All rights reserved.
; >
; > This file is provided for unrestricted use provided that this
; > legend is included on all tape media and as a part of the
; > software program in whole or part. Users may copy, modify or
; > distribute this file at will.
; >
; > Based on Rev 5.0 from:
; > Developer's Desk Window Marketing Group
; > Aldus Corporation Microsoft Corporation
; > 411 First Ave. South 16011 NE 36th Way
; > Suite 200 Box 97017
; > Seattle, WA 98104 Redmond, WA 98073-9717
; > 206-622-5500 206-882-8080
;
; Now updated for TIFF 6
; http://www.wotsit.org/download.asp?f=tiff6
;
; We rely on an ENDIAN-PORT
; A port with the following operations
; endian-port-set-bigendian!:: PORT -> UNSPECIFIED
; endian-port-set-littlendian!:: PORT -> UNSPECIFIED
; endian-port-read-int1:: PORT -> UINTEGER (byte)
; endian-port-read-int2:: PORT -> UINTEGER
; endian-port-read-int4:: PORT -> UINTEGER
; endian-port-setpos PORT INTEGER -> UNSPECIFIED
;
; Also needed SRFIs: SRFI-4 (uniform vectors), SRFI-9 (records)
; Actually, we're using structures, which can be translated to SRFI-9
; records.
;
; Derived from Oleg Kiselyov's tiff.scm 2.0 2003/09/29 20:05:12
; Changes:
; make-define-env, define-macro --> explicit-renaming
;------------------------------------------------------------------------
; TIFF file header
; It is always written at the very beginning of the TIFF file
; unsigned short magic; // magic number (defines byte order)
; unsigned short version; // TIFF version number
; unsigned long diroffset; // byte offset to the first directory
(define TIFF:BIGENDIAN-magic #x4d4d) ; 'MM'
(define TIFF:LITTLEENDIAN-magic #x4949) ; 'II'
(define TIFF:VERSION 42)
; procedure: TIFF:read-header ENDIAN-PORT -> OFFSET
;
; Reads and checks the TIFF header, sets the reader to
; the appropriate little/big endian mode and returns the byte
; offset to the first TIFF directory
(define (TIFF:read-header eport)
(let ((magic-word (endian-port-read-int2 eport)))
(cond
((= magic-word TIFF:BIGENDIAN-magic)
(endian-port-set-bigendian! eport))
((= magic-word TIFF:LITTLEENDIAN-magic)
(endian-port-set-littlendian! eport))
(else (error "invalid magic word 0x" (number->string magic-word 16)
"of the TIFF file"))))
(let ((version (endian-port-read-int2 eport)))
(if (not (= version TIFF:VERSION))
(error "TIFF file version " version "differs from the standard "
TIFF:VERSION)))
(endian-port-read-int4 eport))
;------------------------------------------------------------------------
; TIFF tags: codes and values
;
; A tag dictionary, tagdict, structure helps translate between
; tag-symbols and their numerical values.
;
; procedure: tagdict-get-by-name TAGDICT TAG-NAME -> INT
; where TAG-NAME is a symbol.
; Translate a symbolic representation of a TIFF tag into a numeric
; representation.
; An error is raised if the lookup fails.
;
; procedure: tagdict-get-by-num TAGDICT INT -> TAG-NAME or #f
; Translate from a numeric tag value to a symbolic representation,
; if it exists. Return #f otherwise.
;
; procedure: tagdict-tagval-get-by-name TAGDICT TAG-NAME VAL-NAME -> INT
; where VAL-NAME is a symbol.
; Translate from the symbolic representation of a value associated
; with TAG-NAME in the TIFF directory, into the numeric representation.
; An error is raised if the lookup fails.
;
; procedure: tagdict-tagval-get-by-num TAGDICT TAG-NAME INT -> VAL-NAME or #f
; Translate from a numeric value associated with TAG-NAME in the TIFF
; directory to a symbolic representation, if it exists. Return #f
; otherwise.
;
; procedure: make-tagdict ((TAG-NAME INT (VAL-NAME . INT) ...) ...)
; Build a tag dictionary
;
; procedure: tagdict? TAGDICT -> BOOL
;
; procedure: tagdict-add-all DEST-DICT SRC-DICT -> DEST-DICT
; Join two dictionaries
;
; The variable tiff-standard-tagdict is initialized to the dictionary
; of standard TIFF tags.
; Usage scenario:
; (tagdict-get-by-name tiff-standard-tagdict 'TIFFTAG:IMAGEWIDTH) => 256
; (tagdict-get-by-num tiff-standard-tagdict 256) => 'TIFFTAG:IMAGEWIDTH
; (tagdict-tagval-get-by-name tiff-standard-tagdict
; 'TIFFTAG:COMPRESSION 'LZW) => 5
; (tagdict-tagval-get-by-num tiff-standard-tagdict
; 'TIFFTAG:COMPRESSION 5) => 'LZW
;
; (define extended-tagdict
; (tagdict-add-all tiff-standard-tagdict
; (make-tagdict
; '((WAupper_left_lat 33004)
; (WAhemisphere 33003 (North . 1) (South . 2))))))
(define-structure p-tiff-tag-dict table)
(define tagdict? p-tiff-tag-dict?)
(define (make-tagdict args)
(for-each ; error-check each dict association
(lambda (arg)
(or
(and
(pair? arg)
(list? arg)
(symbol? (car arg))
(integer? (cadr arg)))
(error "make-tagdict: bad association to add: " arg))
(for-each
(lambda (val-assoc)
(or
(and
(pair? val-assoc)
(symbol? (car val-assoc))
(integer? (cdr val-assoc)))
(error "make-tagdict: bad tag value association: " val-assoc)))
(cddr arg)))
args)
(make-p-tiff-tag-dict args))
; procedure: tagdict-add-all DEST-DICT SRC-DICT -> DEST-DICT
; Join two dictionaries
(define (tagdict-add-all dest-dict src-dict)
(assert (tagdict? dest-dict) (tagdict? src-dict))
(make-p-tiff-tag-dict
(append (p-tiff-tag-dict-table dest-dict)
(p-tiff-tag-dict-table src-dict))))
; procedure: tagdict-get-by-name TAGDICT TAG-NAME -> INT
; An error is raised if the lookup fails.
(define (tagdict-get-by-name dict tag-name)
(assert (tagdict? dict))
(cond
((assq tag-name (p-tiff-tag-dict-table dict))
=> cadr)
(else
(error "tagdict-get-by-name: can't translate: " tag-name))))
; procedure: tagdict-get-by-num TAGDICT INT -> TAG-NAME or #f
(define (tagdict-get-by-num dict tag-int)
(assert (tagdict? dict))
(any?
(lambda (table-row)
(and (= (cadr table-row) tag-int) (car table-row)))
(p-tiff-tag-dict-table dict)))
; procedure: tagdict-tagval-get-by-name TAGDICT TAG-NAME VAL-NAME -> INT
; An error is raised if the lookup fails.
(define (tagdict-tagval-get-by-name dict tag-name val-name)
(assert (tagdict? dict))
(cond
((assq tag-name (p-tiff-tag-dict-table dict))
=> (lambda (table-row)
(cond
((assq val-name (cddr table-row)) => cdr)
(else
(error "tagdict-tagval-get-by-name: can't translate "
tag-name val-name)))))
(else
(error "tagdict-tagval-get-by-name: unknown tag: " tag-name))))
; procedure: tagdict-tagval-get-by-num TAGDICT TAG-NAME INT -> VAL-NAME or #f
; Translate from a numeric value associated with TAG-NAME in the
; TIFF directory.
(define (tagdict-tagval-get-by-num dict tag-name val-int)
(assert (tagdict? dict))
(cond
((assq tag-name (p-tiff-tag-dict-table dict))
=> (lambda (table-row)
(any?
(lambda (assc)
(and (= val-int (cdr assc)) (car assc)))
(cddr table-row))))
(else
(error "tagdict-tagval-get-by-num: unknown tag: " tag-name))))
(define tiff-standard-tagdict
(make-tagdict
'(
(TIFFTAG:SUBFILETYPE 254 ; subfile data descriptor
(TIFFTAG:REDUCEDIMAGE . #x1) ; reduced resolution version
(TIFFTAG:PAGE . #x2) ; one page of many
(TIFFTAG:MASK . #x4))
(TIFFTAG:OSUBFILETYPE 255 ; +kind of data in subfile
(TIFFTAG:IMAGE . 1) ; full resolution image data
(TIFFTAG:REDUCEDIMAGE . 2) ; reduced size image data
(TIFFTAG:PAGE . 3)) ; one page of many
(TIFFTAG:IMAGEWIDTH 256) ; image width in pixels
(TIFFTAG:IMAGELENGTH 257) ; image height in pixels
(TIFFTAG:BITSPERSAMPLE 258) ; bits per channel (sample)
(TIFFTAG:COMPRESSION 259 ; data compression technique
(NONE . 1) ; dump mode
(CCITTRLE . 2) ; CCITT modified Huffman RLE
(CCITTFAX3 . 3) ; CCITT Group 3 fax encoding
(CCITTFAX4 . 4) ; CCITT Group 4 fax encoding
(LZW . 5) ; Lempel-Ziv & Welch
(NEXT . 32766) ; NeXT 2-bit RLE
(CCITTRLEW . 32771) ; #1 w/ word alignment
(PACKBITS . 32773) ; Macintosh RLE
(THUNDERSCAN . 32809) ; ThunderScan RLE
(PICIO . 32900) ; old Pixar picio RLE
(SGIRLE . 32901)) ; Silicon Graphics RLE
(TIFFTAG:PHOTOMETRIC 262 ; photometric interpretation
(MINISWHITE . 0) ; min value is white
(MINISBLACK . 1) ; min value is black
(RGB . 2) ; RGB color model
(PALETTE . 3) ; color map indexed
(MASK . 4) ; holdout mask
(DEPTH . 32768)) ; z-depth data
(TIFFTAG:THRESHOLDING 263 ; +thresholding used on data
(BILEVEL . 1) ; b&w art scan
(HALFTONE . 2) ; or dithered scan
(ERRORDIFFUSE . 3)) ; usually floyd-steinberg
(TIFFTAG:CELLWIDTH 264) ; +dithering matrix width
(TIFFTAG:CELLLENGTH 265) ; +dithering matrix height
(TIFFTAG:FILLORDER 266 ; +data order within a byte
(MSB2LSB . 1) ; most significant -> least
(LSB2MSB . 2)) ; least significant -> most
(TIFFTAG:DOCUMENTNAME 269) ; name of doc. image is from
(TIFFTAG:IMAGEDESCRIPTION 270) ; info about image
(TIFFTAG:MAKE 271) ; scanner manufacturer name
(TIFFTAG:MODEL 272) ; scanner model name/number
(TIFFTAG:STRIPOFFSETS 273) ; offsets to data strips
(TIFFTAG:ORIENTATION 274 ; +image orientation
(TOPLEFT . 1) ; row 0 top, col 0 lhs
(TOPRIGHT . 2) ; row 0 top, col 0 rhs
(BOTRIGHT . 3) ; row 0 bottom, col 0 rhs
(BOTLEFT . 4) ; row 0 bottom, col 0 lhs
(LEFTTOP . 5) ; row 0 lhs, col 0 top
(RIGHTTOP . 6) ; row 0 rhs, col 0 top
(RIGHTBOT . 7) ; row 0 rhs, col 0 bottom
(LEFTBOT . 8)) ; row 0 lhs, col 0 bottom
(TIFFTAG:SAMPLESPERPIXEL 277) ; samples per pixel
(TIFFTAG:ROWSPERSTRIP 278) ; rows per strip of data
(TIFFTAG:STRIPBYTECOUNTS 279) ; bytes counts for strips
(TIFFTAG:MINSAMPLEVALUE 280) ; +minimum sample value
(TIFFTAG:MAXSAMPLEVALUE 281) ; maximum sample value
(TIFFTAG:XRESOLUTION 282) ; pixels/resolution in x
(TIFFTAG:YRESOLUTION 283) ; pixels/resolution in y
(TIFFTAG:PLANARCONFIG 284 ; storage organization
(CONTIG . 1) ; single image plane
(SEPARATE . 2)) ; separate planes of data
(TIFFTAG:PAGENAME 285) ; page name image is from
(TIFFTAG:XPOSITION 286) ; x page offset of image lhs
(TIFFTAG:YPOSITION 287) ; y page offset of image lhs
(TIFFTAG:FREEOFFSETS 288) ; +byte offset to free block
(TIFFTAG:FREEBYTECOUNTS 289) ; +sizes of free blocks
(TIFFTAG:GRAYRESPONSEUNIT 290 ; gray scale curve accuracy
(S10 . 1) ; tenths of a unit
(S100 . 2) ; hundredths of a unit
(S1000 . 3) ; thousandths of a unit
(S10000 . 4) ; ten-thousandths of a unit
(S100000 . 5)) ; hundred-thousandths
(TIFFTAG:GRAYRESPONSECURVE 291) ; gray scale response curve
(TIFFTAG:GROUP3OPTIONS 292 ; 32 flag bits
(ENCODING2D . #x1) ; 2-dimensional coding
(UNCOMPRESSED . #x2) ; data not compressed
(FILLBITS . #x4)) ; fill to byte boundary
(TIFFTAG:GROUP4OPTIONS 293 ; 32 flag bits
(UNCOMPRESSED . #x2)) ; data not compressed
(TIFFTAG:RESOLUTIONUNIT 296 ; units of resolutions
(NONE . 1) ; no meaningful units
(INCH . 2) ; english
(CENTIMETER . 3)) ; metric
(TIFFTAG:PAGENUMBER 297) ; page numbers of multi-page
(TIFFTAG:COLORRESPONSEUNIT 300 ; color scale curve accuracy
(S10 . 1) ; tenths of a unit
(S100 . 2) ; hundredths of a unit
(S1000 . 3) ; thousandths of a unit
(S10000 . 4) ; ten-thousandths of a unit
(S100000 . 5)) ; hundred-thousandths
(TIFFTAG:COLORRESPONSECURVE 301); RGB response curve
(TIFFTAG:SOFTWARE 305) ; name & release
(TIFFTAG:DATETIME 306) ; creation date and time
(TIFFTAG:ARTIST 315) ; creator of image
(TIFFTAG:HOSTCOMPUTER 316) ; machine where created
(TIFFTAG:PREDICTOR 317) ; prediction scheme w/ LZW
(TIFFTAG:WHITEPOINT 318) ; image white point
(TIFFTAG:PRIMARYCHROMATICITIES 319) ; primary chromaticities
(TIFFTAG:COLORMAP 320) ; RGB map for pallette image
(TIFFTAG:BADFAXLINES 326) ; lines w/ wrong pixel count
(TIFFTAG:CLEANFAXDATA 327 ; regenerated line info
(CLEAN . 0) ; no errors detected
(REGENERATED . 1) ; receiver regenerated lines
(UNCLEAN . 2)) ; uncorrected errors exist
(TIFFTAG:CONSECUTIVEBADFAXLINES 328); max consecutive bad lines
(TIFFTAG:MATTEING 32995) ; alpha channel is present
)))
;------------------------------------------------------------------------
; TIFF directory entry
; a descriptor of a TIFF "item", which can be image data, document description,
; time stamp, etc, depending on the tag. Thus an entry has the following
; structure:
; unsigned short tag;
; unsigned short type; // data type: byte, short word, etc.
; unsigned long count; // number of items; length in spec
; unsigned long val_offset; // byte offset to field data
;
; The values associated with each entry are disjoint and may appear anywhere
; in the file (so long as they are placed on a word boundary).
;
; Note, If the value takes 4 bytes or less, then it is placed in the offset
; field to save space. If the value takes less than 4 bytes, it is
; *left*-justified in the offset field.
; Note, that it's always *left* justified (stored in the lower bytes)
; no matter what the byte order (big- or little- endian) is!
; Here's the precise quote from the TIFF 6.0 specification:
; "To save time and space the Value Offset contains the Value instead of
; pointing to the Value if and only if the Value fits into 4 bytes. If
; the Value is shorter than 4 bytes, it is left-justified within the
; 4-byte Value Offset, i.e., stored in the lower- numbered
; bytes. Whether the Value fits within 4 bytes is determined by the Type
; and Count of the field."
; Could be easily implemented as a syntax-rule
(define-syntax make-define-env
(lambda (form rename name=)
(let ((env-name (cadr form))
(associations (cddr form))
(%begin (rename 'begin))
(%define (rename 'define)))
`(,%begin
(,%define ,env-name ',associations)
,@(map
(lambda (assc) ; (name val . other fields)
`(,%define ,(car assc) ,(cadr assc)))
associations)))))
; values of the 'data type' field
(make-define-env
TIFF:types
(TIFF:type-byte 1 "byte") ; 8-bit unsigned integer
(TIFF:type-ascii 2 "ascii str") ; 8-bit bytes w/ last byte null
(TIFF:type-short 3 "short") ; 16-bit unsigned integer
(TIFF:type-long 4 "long") ; 32-bit unsigned integer
(TIFF:type-rational 5 "rational") ; 64-bit fractional (numer+denominator)
; The following was added in TIFF 6.0
(TIFF:type-sbyte 6 "signed byte") ; 8-bit signed (2s-complement) integer
(TIFF:type-undefined 7 "8-bit chunk") ; An 8-bit byte
(TIFF:type-sshort 8 "signed short"); 16-bit signed (2s-complement) integer
(TIFF:type-slong 9 "signed long") ; 32-bit signed (2s-complement) integer
(TIFF:type-srational 10 "signed rational") ; two SLONGs (num+denominator)
(TIFF:type-float 11 "IEEE 32-bit float") ; single precision (4-byte)
(TIFF:type-double 12 "IEEE 64-bit double") ; double precision (8-byte)
)
(define-structure tiff-dir-entry tag type count val-offset value)
; procedure: TIFF:read-dir-entry EPORT -> TIFF-DIR-ENTRY
;
; This procedure parses the current directory entry and
; returns a tiff-dir-entry structure. EPORT must point to the beginning
; of the entry in the TIFF directory. On exit, EPORT points to the
; next entry or the end of the directory.
; TIFF-DIR-ENTRY contains all the data of the entry, plus a promise
; of entry's value.
; The promise is forced only when the value is specifically requested
; by an object's user. That is, we won't rush to read and make the
; value (which may be pretty big: say the pixel matrix, etc).
;
; The promise closes over the EPORT!
;
; The value of an entry corresponds to its type: character, string,
; exact integer, floating-point number, or a uniform
; (u8, u16, or u32) vector. SRFI-4 is implied.
(define (TIFF:read-dir-entry eport)
(let*
((tag (endian-port-read-int2 eport))
(type (endian-port-read-int2 eport))
(count (endian-port-read-int4 eport))
; we read the val-offset later. We need to check the size and the type
; of the datum, because val-offset may contain the value itself,
; in its lower-numbered bytes, regardless of the big/little endian
; order!
)
; Some conversion procedures
(define (u32->float x) ; unsigned 32-bit int -> IEEE float
(error "u32->float is not yet implemented"))
(define (u32->s32 x) ; unsigned 32-bit int -> signed 32 bit
(if (>= x #x80000000)
(- x #x100000000)
x))
; (= (u32->s32 #x7fffffff) #x7fffffff)
; (= (u32->s32 #xffffffff) -1)
(define (u16->s16 x) ; unsigned 16-bit int -> signed 16 bit
(if (>= x #x8000)
(- x #x10000) x))
; (= (u16->s16 32767) 32767)
; (= (u16->s16 32768) -32768)
; (= (u16->s16 65535) -1)
(define (u8->s8 x) ; unsigned 8-bit int -> signed 8-bit
(if (>= x #x80)
(- x #x100) x))
; (= (u8->s8 127) 127)
; (= (u8->s8 128) -128)
; (= (u8->s8 255) -1)
(define (read-double val-offset)
(error "read-double: not yet implemented"))
; read an ascii string. Note, the last byte of
; an ascii string is always zero, which is
; included in 'count'
; but we don't need to read it
(define (read-string val-offset)
(assert (= TIFF:type-ascii type) (positive? count))
(let ((str (make-string (- count 1))))
(endian-port-setpos eport val-offset)
(do ((i 0 (+ 1 i))) ((>= i (- count 1)) str)
(string-set! str i
(ascii->char (endian-port-read-int1 eport))))))
; read an array of 'count' items
; return a *uniform* vector of read data:
; u8vector, u16vector or u32vector
; We roll-out the code for efficiency
(define (read-array val-offset)
(endian-port-setpos eport val-offset)
(cond
((or (= type TIFF:type-byte) (= type TIFF:type-undefined))
(let ((array (make-u8vector count)))
(do ((i 0 (+ 1 i))) ((>= i count) array)
(u8vector-set! array i (endian-port-read-int1 eport)))))
((= type TIFF:type-short)
(let ((array (make-u16vector count)))
(do ((i 0 (+ 1 i))) ((>= i count) array)
(u16vector-set! array i (endian-port-read-int2 eport)))))
((= type TIFF:type-long)
(let ((array (make-u32vector count)))
(do ((i 0 (+ 1 i))) ((>= i count) array)
(u32vector-set! array i (endian-port-read-int4 eport)))))
(else (error "don't know how to read an array "
"of type " type))))
; Now we need to figure out if val-offset contains the offset
; or the value (or a part of the value). If val-offset contains the
; value, we read it in val-offset and make the value a trivial promise
; (delay val-offset).
; If val-offset is an offset, then value is a non-trivial promise
; (which closes over EPORT).
(assert (positive? count))
(let*-values
(((val-offset value)
(cond
((> count 4) ; for sure, val-offset is an offset
(let ((offset (endian-port-read-int4 eport)))
(if (= type TIFF:type-ascii)
(values offset (delay (read-string offset)))
(values offset (delay (read-array offset))))))
((> count 1) ; the iffy case
(cond
((and (= count 2) (= type TIFF:type-short))
(let* ((v1 (endian-port-read-int2 eport))
(v2 (endian-port-read-int2 eport))
(v (u16vector v1 v2)))
(values v (delay v))))
((and (= count 2) (= type TIFF:type-ascii)) ; 1-char string
(let ((v
(string (ascii->char (endian-port-read-int1 eport)))))
(endian-port-read-int1 eport) ; don't read '\0'
(endian-port-read-int1 eport) ; skip two more zeros:
(endian-port-read-int1 eport) ; padding
(values v (delay v))))
((and (= count 2) (or (= type TIFF:type-byte)
(= type TIFF:type-undefined)))
(let* ((v1 (endian-port-read-int1 eport))
(v2 (endian-port-read-int1 eport))
(v (u8vector v1 v2)))
(endian-port-read-int1 eport) ; skip two more zeros:
(endian-port-read-int1 eport) ; padding
(values v (delay v))))
((and (= count 3) (= type TIFF:type-ascii)) ; 2-char string
(let* ((v1 (endian-port-read-int1 eport))
(v2 (endian-port-read-int1 eport))
(v (string (ascii->char v1) (ascii->char v2))))
(endian-port-read-int1 eport) ; skip two more zeros:
(endian-port-read-int1 eport) ; padding
(values v (delay v))))
((and (= count 3) (or (= type TIFF:type-byte)
(= type TIFF:type-undefined)))
(let* ((v1 (endian-port-read-int1 eport))
(v2 (endian-port-read-int1 eport))
(v3 (endian-port-read-int1 eport))
(v (u8vector v1 v2 v3)))
(endian-port-read-int1 eport) ; skip padding
(values v (delay v))))
((and (= count 4) (= type TIFF:type-ascii)) ; 3-char string
(let* ((v1 (endian-port-read-int1 eport))
(v2 (endian-port-read-int1 eport))
(v3 (endian-port-read-int1 eport))
(v (string (ascii->char v1) (ascii->char v2)
(ascii->char v3))))
(endian-port-read-int1 eport) ; skip padding
(values v (delay v))))
((and (= count 4) (or (= type TIFF:type-byte)
(= type TIFF:type-undefined)))
(let* ((v1 (endian-port-read-int1 eport))
(v2 (endian-port-read-int1 eport))
(v3 (endian-port-read-int1 eport))
(v4 (endian-port-read-int1 eport))
(v (u8vector v1 v2 v3 v4)))
(values v (delay v))))
(else
(let ((offset (endian-port-read-int4 eport)))
(if (= type TIFF:type-ascii)
(values offset (delay (read-string offset)))
(values offset (delay (read-array offset))))))))
; Now count is 1
((or (= type TIFF:type-byte) (= type TIFF:type-undefined)
(= type TIFF:type-sbyte))
(let ((v1 (endian-port-read-int1 eport)))
(endian-port-read-int1 eport) ; skip the padding
(endian-port-read-int1 eport)
(endian-port-read-int1 eport)
(values v1 (delay
(if (= type TIFF:type-sbyte) (u8->s8 v1) v1)))))
((= type TIFF:type-ascii) ; 0-byte string: count=1 for terminator
(endian-port-read-int1 eport) ; skip the padding
(endian-port-read-int1 eport)
(endian-port-read-int1 eport)
(endian-port-read-int1 eport)
(values "" (delay "")))
((or (= type TIFF:type-short) (= type TIFF:type-sshort))
(let ((v1 (endian-port-read-int2 eport)))
(endian-port-read-int1 eport) ; skip the padding
(endian-port-read-int1 eport)
(values v1 (delay
(if (= type TIFF:type-sshort) (u16->s16 v1) v1)))))
((= type TIFF:type-long)
(let ((v1 (endian-port-read-int4 eport)))
(values v1 (delay v1))))
((= type TIFF:type-slong)
(let ((v1 (endian-port-read-int4 eport)))
(values v1 (delay (u32->s32 v1)))))
((= type TIFF:type-float)
(let ((v1 (endian-port-read-int4 eport)))
(values v1 (delay (u32->float v1)))))
((= type TIFF:type-double)
(let ((offset (endian-port-read-int4 eport)))
(values offset (delay (read-double offset)))))
((or (= type TIFF:type-rational) (= type TIFF:type-srational))
(let ((offset (endian-port-read-int4 eport)))
(values offset
(delay
(let* ((_ (endian-port-setpos eport offset))
(v1 (endian-port-read-int4 eport))
(v2 (endian-port-read-int4 eport)))
(if (= type TIFF:type-srational)
(/ (u32->s32 v1) (u32->s32 v2))
(/ v1 v2)))))))
(else (delay (error "unknown data type: " type))))))
(make-tiff-dir-entry tag type count val-offset value)
)))
; procedure: print-tiff-dir-entry TIFF-DIR-ENTRY TAGDICT OPORT -> UNSPECIFIED
;
; Print the contents of TIFF-DIR-ENTRY onto the output port OPORT
; using TAGDICT to convert tag identifiers to symbolic names
(define (print-tiff-dir-entry tiff-dir-entry tagdict oport)
(define (dspl . args) (for-each (lambda (item) (display item oport)) args))
(let* ((tag-num (tiff-dir-entry-tag tiff-dir-entry))
(tag-symbol (tagdict-get-by-num tagdict tag-num)))
(dspl
(or tag-symbol
(string-append "private tag " (number->string tag-num))))
(dspl ", count " (tiff-dir-entry-count tiff-dir-entry)
", type ")
(let ((type-str
(any?
(lambda (elem)
(and (= (cadr elem) (tiff-dir-entry-type tiff-dir-entry))
(caddr elem)))
TIFF:types)))
(if type-str
(dspl type-str)
(dspl "unknown (" (tiff-dir-entry-type tiff-dir-entry) ")")))
(let ((val-offset (tiff-dir-entry-val-offset tiff-dir-entry)))
(dspl ", value-offset " val-offset)
(if (integer? val-offset)
(dspl " (0x" (number->string val-offset 16) ")")))
(dspl nl)))
;------------------------------------------------------------------------
; TIFF Image File Directory
; TIFF directory is a collection of TIFF directory entries. The entries
; are sorted in an ascending order by tag.
; Note, a TIFF file can contain more than one directory (chained together).
; We handle only the first one.
;
; We treat a TIFF image directory somewhat as an ordered, immutable,
; dictionary collection, see SRFI-44.
; http://srfi.schemers.org/srfi-44/srfi-44.html
(define-structure tiff-directory entries tagdict)
; ; procedure: collection-name collection => symbol ('%)
; (define (collection-name coll)
; (and (tiff-directory? coll) 'tiff-directory))
; ; collection? value => value
; (define collection? tiff-directory?)
; ; procedure: tiff-directory? value => bool
; ; implied by the define-structure
; ; *-size collection => integer
(define (tiff-directory-size coll)
(vector-length (tiff-directory-entries coll)))
; (define (mutable-collection? coll) #f)
; (define (dictionary? coll) #t)
(define (tiff-directory-empty? coll)
(zero? (vector-length (tiff-directory-entries coll))))
; tiff-directory-fold-left tiff-directory fold-function seed-value
; ... => seed-value ...
; The fold function receives a tiff-directory-entry as a value
(define (tiff-directory-fold-left coll fn . seeds)
(let ((entries (tiff-directory-entries coll)))
(let loop ((i 0) (seeds seeds))
(if (>= i (vector-length entries))
(apply values seeds)
(let*-values
(((proceed? . seeds) (apply fn (vector-ref entries i) seeds)))
(loop (if proceed? (+ 1 i) (vector-length entries))
seeds))))))
; procedure: collection-fold-keys-left collection fold-function
; seed-value ... => seed-value ...
; *-keys->list dictionary => list
; read-tiff-file EPORT [PRIVATE-TAGDICT] => TIFF-DIRECTORY
(define (read-tiff-file eport . tag-dict-opt)
(endian-port-setpos eport (TIFF:read-header eport))
(let ((entries (make-vector (endian-port-read-int2 eport)))
(tagdict (if (null? tag-dict-opt)
tiff-standard-tagdict
(tagdict-add-all tiff-standard-tagdict
(car tag-dict-opt)))))
(do ((i 0 (+ 1 i))) ((>= i (vector-length entries)))
(vector-set! entries i (TIFF:read-dir-entry eport)))
(if (not (zero? (endian-port-read-int4 eport)))
(cerr "The TIFF file contains several images, only the first one "
"will be considered" nl))
(make-tiff-directory entries tagdict)))
; print-tiff-directory TIFF-DIRECTORY OPORT -> UNSPECIFIED
(define (print-tiff-directory tiff-directory oport)
(let*
((entries (tiff-directory-entries tiff-directory))
(nentries (vector-length entries))
(tagdict (tiff-directory-tagdict tiff-directory)))
(for-each (lambda (item) (display item oport))
(list
"There are " nentries " entries in the TIFF directory" nl
"they are" nl))
(do ((i 0 (+ 1 i))) ((>= i nentries))
(print-tiff-dir-entry (vector-ref entries i) tagdict oport))))
; *-get dictionary key [absence-thunk] => value
; key can be either a symbol or an integer
; tiff-directory-get TIFF-DIRECTORY KEY [ABSENCE-THUNK] -> VALUE
; If the lookup fails, ABSENCE-THUNK, if given, is evaluated and its value
; is returned. If ABSENCE-THUNK is omitted, the return value on failure
; is #f.
(define (tiff-directory-get coll key . default-val)
(let*
((key
(cond
((symbol? key)
(tagdict-get-by-name (tiff-directory-tagdict coll) key))
((integer? key) key)
(else (error "tiff-directory-get: bad type of key: " key))))
(entry
; look up the entry in the directory of entries
; We could have used a binary search... On the other hand,
; the directory is usually not that big, so that binary
; search is kind of overkill
(any?
(lambda (curr-elem)
(and (= (tiff-dir-entry-tag curr-elem) key) curr-elem))
(tiff-directory-entries coll)))
)
(if entry
(force (tiff-dir-entry-value entry))
(and (not (null? default-val)) ((car default-val))))))
; tiff-directory-get-as-symbol TIFF-DIRECTORY KEY [ABSENCE-THUNK] -> VALUE
; KEY must be a symbol
; If it is possible, the VALUE is returned as a symbol, as translated
; by the tagdict.
(define (tiff-directory-get-as-symbol coll key . default-val)
(let ((val (tiff-directory-get coll key)))
(if val
(if (integer? val)
(let ((val-symbol
(tagdict-tagval-get-by-num
(tiff-directory-tagdict coll) key val)))
(or val-symbol val))
val) ; val is not an integer: don't translate
(and (not (null? default-val)) ((car default-val)))
)))